diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..a2b95422a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,46 @@ +# JavaAntLauncher — repository notes + +Android launcher for Minecraft: Java Edition. Fork of Zalith Launcher 2, which itself +descends from PojavLauncher. Repo root is the Gradle root; the Android app module is +`ZalithLauncher/`. + +## Build + +* `./gradlew :ZalithLauncher:assembleDebug` (needs a JDK and an Android SDK). +* A dev container may have neither installed — check before promising a compile or a run. +* `ZalithLauncher/gradle.properties` holds `oauth_client_id`; a git-ignored + `ZalithLauncher/.oauth_client_id.txt` overrides it locally. CI passes `OAUTH_CLIENT_ID`. +* Microsoft sign-in needs a real Azure app registration (public client flows enabled). + Without one, only offline/local accounts work. + +## Architecture worth knowing + +* Launch pipeline: `LaunchGameOperation.TryLaunch` (UI gate) → + `LaunchGameViewModel.start()` → `GameLaunchFlow.buildLaunchPhases()` (account check + + file verification) → `runGame()` → `GameLauncher.launch()`. +* `LaunchGameOperation` is a sealed interface in + `ui/screens/content/elements/LauncherElements.kt`. Adding a case means handling it in the + `when` in that same file — it is exhaustive, so the compiler enforces it. +* `Version` is `Parcelize`d and passed through Intents. `getVersionInfo()` returns + `VersionInfo?`; it is **null** whenever the version's `.json` is missing or fails to + parse, while the version can still exist on disk. `version.isValid()` is exactly + "versionInfo parsed successfully". +* `version.offlineAccountLogin` decides whether `GameLauncher` swaps in a LOCAL copy of the + account (`usingAccount`). Microsoft accounts with no entitlement are constructed as offline + identities (locally derived UUID, no official profile), so they *must* keep this flag or + the client fails session validation after launch. +* `OfflineYggdrasilServer` is only started for LOCAL accounts that have a local skin file; + it is what lets offline accounts resolve skins. + +## Conventions + +* UI strings live in `values/`, `values-zh-rCN/`, `values-zh-rTW/`. Add all three. +* Comments in this codebase are written in Chinese; match the surrounding file. +* `!!` on `getVersionInfo()` is a latent crash. Use `?.` and surface a launch error instead. + +## Gotchas + +* No JDK/Android SDK in the default dev container: verify Kotlin changes by reading, not by + compiling, unless you install a toolchain. +* `MCOptions` watches `options.txt` with a `FileObserver`; it stops watching around its own + atomic write to avoid re-entrant reloads. diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/handler/GameHandler.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/handler/GameHandler.kt index 5e82dbf74..c7da6c810 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/handler/GameHandler.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/handler/GameHandler.kt @@ -103,13 +103,16 @@ class GameHandler( // set("options.narrator", "0") // set("narrator", "0") - if (version.getVersionInfo()!!.minecraftVersion.isLowerVer("1.13")) { - //fix: 牢版本按键事件 - //shift + w -> 87 错误的触发了F11,切换全屏 - set("key_key.fullscreen", "0") - //输入字符@ -> 64 错误的触发了F6,触发“开始/停止直播” - set("key_key.streamStartStop", "0") - set("key_key.streamPauseUnpause", "0") + //版本信息缺失时无法判断版本号,跳过这些兼容性修正而不是中断启动 + version.getVersionInfo()?.minecraftVersion?.let { minecraftVersion -> + if (minecraftVersion.isLowerVer("1.13")) { + //fix: 牢版本按键事件 + //shift + w -> 87 错误的触发了F11,切换全屏 + set("key_key.fullscreen", "0") + //输入字符@ -> 64 错误的触发了F6,触发“开始/停止直播” + set("key_key.streamStartStop", "0") + set("key_key.streamPauseUnpause", "0") + } } set("overrideWidth", screenSize.width.toString()) @@ -126,7 +129,9 @@ class GameHandler( else -> set(graphicsOption, graphicsApi.option) } - loadLanguage(version.getVersionInfo()!!.minecraftVersion) + version.getVersionInfo()?.minecraftVersion?.let { minecraftVersion -> + loadLanguage(minecraftVersion) + } save() } diff --git a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/LauncherElements.kt b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/LauncherElements.kt index cc97b4a89..e2b038e2c 100644 --- a/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/LauncherElements.kt +++ b/ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/content/elements/LauncherElements.kt @@ -130,6 +130,8 @@ sealed interface LaunchGameOperation { data object NoVersion : LaunchGameOperation /** 版本名称非法时 */ data class InvalidVersionName(val th: InvalidFilenameException) : LaunchGameOperation + /** 选中的版本无法读取版本信息(缺少或损坏的 json),无法启动 */ + data class InvalidVersion(val version: Version) : LaunchGameOperation /** 没有可用账号 */ data object NoAccount : LaunchGameOperation @@ -228,6 +230,13 @@ fun LaunchGameOperation( launchGameViewModel.updateOperation(LaunchGameOperation.None) } } + is LaunchGameOperation.InvalidVersion -> { + LaunchedEffect(Unit) { + eventViewModel.sendToast(androidText(R.string.game_launch_invalid_version)) + toVersionManageScreen() + launchGameViewModel.updateOperation(LaunchGameOperation.None) + } + } is LaunchGameOperation.RendererNoStoragePermission -> { LaunchedEffect(Unit) { val renderer = operation.renderer @@ -303,7 +312,13 @@ fun LaunchGameOperation( Renderers.setCurrentRenderer(version.getRenderer()) val currentRenderer = Renderers.getCurrentRenderer() - val mcVer = version.getVersionInfo()!!.minecraftVersion + //缺少或损坏的版本 json 会导致版本信息为 null,后续所有取用都会失败 + //这里提前拦截,避免启动过程中直接崩溃 + val versionInfo = version.getVersionInfo() ?: run { + launchGameViewModel.updateOperation(LaunchGameOperation.InvalidVersion(version)) + return@LaunchedEffect + } + val mcVer = versionInfo.minecraftVersion // 设备完全支持 Vulkan 时跳过渲染器的版本支持检查 if (!version.hasVulkanBackend() || !ensureVulkanSupported(version)) { diff --git a/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml b/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml index 7064e18c8..d6c2fa89a 100644 --- a/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml +++ b/ZalithLauncher/src/main/res/values-zh-rCN/strings.xml @@ -401,6 +401,7 @@ 未选择游戏版本! 未登录游戏账号! + 该版本无法启动:版本文件缺失或已损坏。请重新安装该版本后再试。 未找到合适的运行环境,已设置为当前版本选择的运行环境。 输入法 强制关闭 diff --git a/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml b/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml index 5e4fa903c..d80e447f2 100644 --- a/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml +++ b/ZalithLauncher/src/main/res/values-zh-rTW/strings.xml @@ -358,6 +358,7 @@ 未選擇遊戲版本! 未登入遊戲帳號! + 該版本無法啟動:版本檔案缺失或已損毀。請重新安裝該版本後再試。 找不到合適的執行環境,已設定為目前版本選擇的執行環境。 輸入法 強制關閉 diff --git a/ZalithLauncher/src/main/res/values/strings.xml b/ZalithLauncher/src/main/res/values/strings.xml index 447331dae..75a1ad05e 100644 --- a/ZalithLauncher/src/main/res/values/strings.xml +++ b/ZalithLauncher/src/main/res/values/strings.xml @@ -401,6 +401,7 @@ No game version selected! No account logged in! + This version cannot be launched: its version file is missing or damaged. Reinstall the version and try again. No compatible runtime found. Using selected version\'s runtime. Input Method Force Close