Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c4a4a81
Add instanceService, settings properties, and createInstance() to Lau…
Lhy723 May 27, 2026
cdb9a93
Add InstanceCreateSheet view for new instance dialog
Lhy723 May 27, 2026
9fa6e6d
Wire store through App → ContentView, update SettingsView to use stor…
Lhy723 May 27, 2026
2dbfb9c
feat: implement LogViewerSheet and wire open-log button
Lhy723 May 27, 2026
2619cec
Add instance deletion with store method, context menu, and test
Lhy723 May 27, 2026
2bd5d95
feat: concurrent downloads with cancel support
Lhy723 May 27, 2026
b4ca227
feat: monitor launch session and report process exit diagnostics
Lhy723 May 27, 2026
06fb198
Add Fabric loader installation support
Lhy723 May 27, 2026
1c5fc97
feat: add Modrinth search, project details, and version download inte…
Lhy723 May 27, 2026
a993ed1
feat: add Quilt and Forge loader support
Lhy723 May 27, 2026
1646a9e
Add Modrinth mod install flow with version picker sheet
Lhy723 May 27, 2026
243f005
feat: add Microsoft OAuth login with device code flow
Lhy723 May 27, 2026
ba7f8bd
feat: add instance rename/copy and local mod/resource pack management
Lhy723 May 27, 2026
c2b6d40
feat: add NeoForge support, download speed display, crash analysis, a…
Lhy723 May 27, 2026
4c1dbe9
feat: add real-time log viewer, CurseForge search, auto-update, and help
Lhy723 May 27, 2026
e81a04d
Add 5 PCL-parity features: shader packs, version filter, resolution, …
Lhy723 May 27, 2026
292b0d3
feat: add download pause/resume/cancel with streaming progress
Lhy723 May 27, 2026
6ccf266
feat: add auto-update download and install from GitHub releases
Lhy723 May 27, 2026
dcd0bd7
feat: add skin management, profile import/export, and custom background
Lhy723 May 27, 2026
1afdfd9
feat: add multiplayer server list management
Lhy723 May 27, 2026
dae974d
fix: sidebar buttons not responding — use List selection tags instead…
Lhy723 May 28, 2026
9cc1461
feat: auto-scan Java runtimes on app launch
Lhy723 May 28, 2026
fcc1b35
feat: major feature update — animations, Java install, launcher redes…
Lhy723 May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
push:
branches: [main, codex-mmcl-phase0]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select Xcode
run: |
xcode_path=$(ls -d /Applications/Xcode*.app | sort -V | tail -1)
sudo xcode-select -s "$xcode_path"

- name: Build
run: |
xcodebuild build \
-project MMCL.xcodeproj \
-scheme MMCL \
-destination 'platform=macOS' \
-quiet

- name: Test
run: |
xcodebuild test \
-project MMCL.xcodeproj \
-scheme MMCL \
-destination 'platform=macOS' \
-only-testing:MMCLTests \
-quiet

lint:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: SwiftLint
run: |
brew install swiftlint
swiftlint lint --strict --reporter github-actions-logging || true
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

on:
push:
tags:
- 'v*'

jobs:
build-and-release:
runs-on: macos-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select Xcode
run: |
xcode_path=$(ls -d /Applications/Xcode*.app | sort -V | tail -1)
sudo xcode-select -s "$xcode_path"

- name: Build Release
run: |
xcodebuild build \
-project MMCL.xcodeproj \
-scheme MMCL \
-destination 'platform=macOS' \
-configuration Release \
-quiet

- name: Create DMG
run: |
app_path=$(find DerivedData/Build/Products/Release -name "MMCL.app" -type d | head -1)
hdiutil create -volname "MMCL" \
-srcfolder "$app_path" \
-ov -format UDZO \
"MMCL-${GITHUB_REF_NAME}.dmg"

- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
MMCL-${{ github.ref_name }}.dmg
generate_release_notes: true
81 changes: 81 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Xcode
build/
DerivedData/
*.xcodeproj/xcuserdata/
*.xcodeproj/xcworkspace/xcuserdata/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
*.xccheckout
*.moved-aside
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

# Swift Package Manager
.build/
Packages/
Package.pins
Package.resolved

# CocoaPods
Pods/

# Carthage
Carthage/Build/
Carthage/Checkouts/

# macOS
.DS_Store
.AppleDouble
.LSOverride
._*

# Thumbnails
Thumbs.db

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# Xcode Playground
timeline.xctimeline
playground.xcworkspace

# SwiftPM
.swiftpm/

# Codex
.codex/

# Claude
.claude/

# Superpowers (internal planning docs)
docs/superpowers/

# App-specific
*.mobileprovision
*.entitlements
*.cer
*.p12
*.pem
79 changes: 79 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
disabled_rules:
- trailing_whitespace
- todo
- force_try
- force_cast
- force_unwrap
- line_length
- file_length
- type_body_length
- function_body_length
- closure_body_length
- large_tuple
- nesting
- identifier_name
- private_outlet
- private_iboutlet
- no_fallthrough_only
- multiple_closures_with_trailing_closure
- implicitly_unwrapped_optional
- unavailable_function
- empty_enum_arguments
- empty_parameters
- trailing_semicolon
- vertical_whitespace
- opening_brace
- comma
- colon
- return_arrow_whitespace
- statement_position
- switch_case_alignment
- syntactic_sugar
- unused_closure_parameter
- unused_control_flow_label
- unused_enumerated
- unused_optional_binding
- unused_setter_value
- void_return
- weak_delegate
- no_fallthrough_only
- no_space_in_method_call
- no_trailing_spaces
- no_void_return

excluded:
- DerivedData
- build
- Pods
- Carthage
- .build
- MMCL/Persistence.swift

opt_in_rules:
- empty_count
- closure_spacing
- contains_over_first_not_nil
- discouraged_object_literal
- empty_collection_literal
- empty_string
- fatal_error_message
- first_where
- force_unwrapping
- implicitly_unwrapped_optional
- last_where
- legacy_multiple
- legacy_random
- legacy_constant
- legacy_constructor
- legacy_cggeometry_functions
- legacy_nsgeometry_functions
- modifier_order
- overridden_super_call
- private_action
- private_outlet
- prohibited_super_call
- redundant_nil_coalescing
- toggle_bool
- unneeded_parentheses_in_closure_argument
- unowned_variable_capture
- yoda_condition
86 changes: 86 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Test

```bash
# Build (debug)
xcodebuild build -project MMCL.xcodeproj -scheme MMCL -destination 'platform=macOS' -quiet

# Run tests
xcodebuild test -project MMCL.xcodeproj -scheme MMCL -destination 'platform=macOS' -quiet

# Run single test class
xcodebuild test -project MMCL.xcodeproj -scheme MMCL -destination 'platform=macOS' -only-testing:MMCLTests/LauncherStoreTests

# Build & run (uses script)
./script/build_and_run.sh
```

## Architecture

macOS Minecraft launcher. SwiftUI app with a **Models → Services → Store → Views** layered architecture. References PCL (Plain Craft Launcher) for interaction design and backend management patterns.

### Core layers

- **Models** (`MMCL/Models/LauncherModels.swift`): All data types — `LauncherInstance`, `VersionMetadata`, `DownloadJob`, `JavaRuntime`, `AssetIndex`, `LaunchSession`, `MinecraftAccount`, `FabricProfile`, `ModrinthVersion`, `ModInfo`, `ResourcePackInfo`, etc.
- **Services** (`MMCL/Services/LauncherServices.swift`): Protocol-based service layer.
- `InstanceServicing` — instance creation, slug generation, JSON persistence
- `VersionManifestServicing` — Mojang version manifest/metadata fetching
- `DownloadServicing` — download job creation, SHA-1 execution, native library unzipping
- `JavaRuntimeServicing` — `/usr/libexec/java_home -V` parsing, portable JDK install
- `LaunchServicing` — command line generation, preflight checks, game launch
- `DiagnosticServicing` — Java mismatch checks, crash log analysis
- `FabricServicing` — Fabric loader installation via meta.fabricmc.net
- `QuiltServicing` — Quilt loader installation via meta.quiltmc.org
- `ForgeServicing` — Forge loader installation via promotions_slim.json
- `NeoForgeServicing` — NeoForge loader installation via Maven
- `ModrinthServicing` — Modrinth search, project details, version download
- `CurseForgeServicing` — CurseForge mod search (requires API key)
- `AuthServicing` — Microsoft OAuth device code flow, XBL/XSTS/Minecraft token exchange
- **Store** (`MMCL/Stores/LauncherStore.swift`): `@MainActor` `ObservableObject` holding all app state. Orchestrates services, manages downloads, process monitoring, account management. All `@Published` modifications must happen on main actor.
- **Views** (`MMCL/Views/`): SwiftUI views. `NavigationSplitView` layout with sidebar + detail + sheets.
- `LauncherView` — instance picker, launch button, instance card with block icon
- `DownloadCenterView` — TabView with 7 tabs (新建实例, Mod, 整合包, 数据包, 资源包, 光影包, 下载进度)
- `DownloadVanillaView` — Minecraft version list, loader selection, create + download
- `DownloadResourceSearchView` — Modrinth/CurseForge search with stagger animation
- `DownloadProgressView` — concurrent download progress, pause/resume/cancel
- `InstanceSettingsView` — instance config (Java, memory, JVM args, management)
- `ModListView` — local mod management (enable/disable/delete)
- `ResourcePackListView` / `ShaderPackListView` — resource/shader pack management
- `ModrinthProjectDetailView` — Modrinth version picker and install
- `LogViewerSheet` — real-time game log viewer with auto-refresh
- `JDKInstallSheet` — portable JDK installation from Adoptium
- `SkinPickerView` — skin management
- `ServerListView` — multiplayer server management
- `WorkspaceViews` — DiagnosticsView, SettingsView (accounts, appearance, JVM presets, download source, about)
- `AnimationScale.swift` — `Animation.mmclSpring()` extension for consistent spring animations

### Key patterns

- `@MainActor` on `LauncherStore` prevents "Publishing changes from within view updates" warnings
- Services injected into `LauncherStore` via init (protocol types) for mock-based testing
- JSON uses `JSONEncoder.mmcl` / `JSONDecoder.mmcl` (ISO 8601, pretty printed)
- Instance files at `~/Library/Application Support/MMCL/Instances/{slug}/instance.json`
- Portable JDK installed to `~/Library/Application Support/MMCL/JDK/`
- Download sources: official, BMCLAPI, custom mirror
- Java recommendation: major version 8 (≤1.16), 17 (1.17–1.19), 21 (≥1.20)
- Apple Silicon auto-detection: ZGC + optimized JVM args for arm64
- Downloads execute concurrently via `TaskGroup` (max 4 parallel)
- Microsoft auth uses device code flow (browser-based OAuth)
- Mod management: toggle by renaming `.jar` ↔ `.jar.disabled`
- Instance status verified against actual files on disk at startup
- Block icons: Grass (release), CommandBlock (snapshot), CobbleStone (old), Anvil (Forge), Fabric, Egg (Quilt)
- Animations use `Animation.mmclSpring()` with configurable duration scale

## Testing

Tests use XCTest with protocol-based mocks (e.g., `MockDownloadService`, `MockVersionManifestService`, `MockInstanceService`). No external dependencies. Tests in `MMCLTests/`.

## Conventions

- UI text in Chinese; code identifiers and comments in English
- `Persistence.swift` is template CoreData — not used; app state lives in `LauncherStore`
- Project uses `PBXFileSystemSynchronizedRootGroup` — new `.swift` files auto-discovered by Xcode
- GitHub repo: `Lhy723/MMCL`
6 changes: 4 additions & 2 deletions MMCL.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,9 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FJ7T78T44N;
ENABLE_APP_SANDBOX = YES;
ENABLE_APP_SANDBOX = NO;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
Expand Down Expand Up @@ -430,8 +431,9 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FJ7T78T44N;
ENABLE_APP_SANDBOX = YES;
ENABLE_APP_SANDBOX = NO;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
Expand Down
Binary file added MMCL/Assets.xcassets/Anvil.imageset/Anvil.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions MMCL/Assets.xcassets/Anvil.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"images" : [
{ "idiom" : "universal", "filename" : "Anvil.png" }
],
"info" : { "version" : 1, "author" : "xcode" }
}
Loading
Loading