using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Windows; using PCL.Core.App; using PCL.Core.App.Localization; using PCL.Core.Minecraft; using PCL.Core.Minecraft.Launch.Utils; using PCL.Core.Utils; using PCL.Core.Utils.OS; using PCL.Core.Utils.Secret; using PCL.Network; using PCL.Core.IO.Net.Http; using PCL.Core.Minecraft.IdentityModel.Yggdrasil; using System.Globalization; namespace PCL; public static class ModLaunch { public const string mesaLoaderWindowsVersion = "26.0.4"; #region 预检测 private static void McLaunchPrecheck() { if (Config.Debug.AddRandomDelay) Thread.Sleep(RandomUtils.NextInt(100, 2000)); // 检查路径 if (ModInstanceList.McMcInstanceSelected.PathIndie.Contains("!") || ModInstanceList.McMcInstanceSelected.PathIndie.Contains(";")) throw new Exception(Lang.Text("Minecraft.Launch.Precheck.InvalidPathChars", ModInstanceList.McMcInstanceSelected.PathIndie)); if (ModInstanceList.McMcInstanceSelected.PathInstance.Contains("!") || ModInstanceList.McMcInstanceSelected.PathInstance.Contains(";")) throw new Exception(Lang.Text("Minecraft.Launch.Precheck.InvalidPathChars", ModInstanceList.McMcInstanceSelected.PathInstance)); if (ModBase.IsUtf8CodePage() && !States.Hint.NonAsciiGamePath && !ModInstanceList.McMcInstanceSelected.PathInstance.IsASCII()) { var userChoice = ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Precheck.NonAsciiPath.Message", ModInstanceList.McMcInstanceSelected.Name), Lang.Text("Minecraft.Launch.Precheck.NonAsciiPath.Title"), Lang.Text("Minecraft.Launch.Precheck.NonAsciiPath.Continue"), Lang.Text("Minecraft.Launch.Precheck.NonAsciiPath.Back"), Lang.Text("Common.Hint.DoNotShowAgain")); if (userChoice == 2) throw new Exception("$$"); if (userChoice == 3) States.Hint.NonAsciiGamePath = true; } // 检查实例 if (ModInstanceList.McMcInstanceSelected is null) throw new Exception(Lang.Text("Minecraft.Launch.Precheck.NoInstance")); ModInstanceList.McMcInstanceSelected.Load(); if (ModInstanceList.McMcInstanceSelected.state == McInstanceState.Error) throw new Exception(Lang.Text("Minecraft.Launch.Precheck.InstanceError", ModInstanceList.McMcInstanceSelected.Desc)); // 检查输入信息 var checkResult = ""; ModBase.RunInUiWait(() => checkResult = ModProfile.IsProfileValid()); if (ModProfile.selectedProfile is null) // 没选档案 { checkResult = Lang.Text("Minecraft.Launch.Precheck.NoProfile"); } else if (ModInstanceList.McMcInstanceSelected.Info.HasLabyMod || Config.InstanceAuth.LoginRequirementSolution[ModInstanceList.McMcInstanceSelected?.PathInstance] == 1) // 要求正版验证 { if (ModProfile.selectedProfile.Type != McLoginType.Ms) checkResult = Lang.Text("Minecraft.Launch.Precheck.RequireMicrosoft"); } else if (Config.InstanceAuth.LoginRequirementSolution[ModInstanceList.McMcInstanceSelected?.PathInstance] == 2) // 要求第三方验证 { if (ModProfile.selectedProfile.Type != McLoginType.Auth) checkResult = Lang.Text("Minecraft.Launch.Precheck.RequireThirdParty"); else if (ModProfile.selectedProfile.Server.BeforeLast("/authserver") != Config.InstanceAuth.AuthServerAddress[ModInstanceList.McMcInstanceSelected?.PathInstance]) checkResult = Lang.Text("Minecraft.Launch.Precheck.AuthServerMismatch"); } else if (Config.InstanceAuth.LoginRequirementSolution[ModInstanceList.McMcInstanceSelected?.PathInstance] == 3) // 要求正版验证或第三方验证 { if (ModProfile.selectedProfile.Type == McLoginType.Legacy) checkResult = Lang.Text("Minecraft.Launch.Precheck.RequireMicrosoftOrThirdParty"); else if (ModProfile.selectedProfile.Type == McLoginType.Auth && ModProfile.selectedProfile.Server.BeforeLast("/authserver") != Config.InstanceAuth.AuthServerAddress[ModInstanceList.McMcInstanceSelected?.PathInstance]) checkResult = Lang.Text("Minecraft.Launch.Precheck.AuthServerMismatch"); } if (!string.IsNullOrEmpty(checkResult)) throw new ArgumentException(checkResult); #if BETA if (currentLaunchOptions?.SaveBatch is null) // 保存脚本时不提示 { ModBase.RunInNewThread(() => { switch (States.System.LaunchCount) { case 10: case 20: case 40: case 60: case 80: case 100: case 120: case 150: case 200: case 250: case 300: case 350: case 400: case 500: case 600: case 700: case 800: case 900: case 1000: case 1200: case 1400: case 1600: case 1800: case 2000: if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Donate.Message", States.System.LaunchCount), Lang.Text("Minecraft.Launch.Donate.Title", States.System.LaunchCount), Lang.Text("Minecraft.Launch.Donate.Support"), Lang.Text("Minecraft.Launch.Donate.Decline")) == 1) { ModBase.OpenWebsite("https://afdian.com/a/LTCat"); } break; } }, "Donate"); } #endif #if DEBUG || DEBUGCI return; #endif // 正版购买提示 if (!ModProfile.profileList.Any(x => x.Type == McLoginType.Ms)) { if (Lang.IsFeaturesUnrestricted) { if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.PurchaseHint.Message"), Lang.Text("Minecraft.Launch.PurchaseHint.Title"), Lang.Text("Minecraft.Launch.PurchaseHint.Purchase"), Lang.Text("Minecraft.Launch.PurchaseHint.Later")) == 1) ModBase.OpenWebsite( "https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"); } else { switch (ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.AccountVerification.Message"), Lang.Text("Minecraft.Launch.AccountVerification.Title"), Lang.Text("Minecraft.Launch.AccountVerification.Purchase"), Lang.Text("Minecraft.Launch.AccountVerification.Demo"), Lang.Text("Minecraft.Launch.AccountVerification.Back"), button1Action: () => ModBase.OpenWebsite( "https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"))) { case 2: { HintService.Hint(Lang.Text("Minecraft.Launch.DemoMode"), HintType.Error); currentLaunchOptions.ExtraArgs.Add("--demo"); break; } case 3: { throw new Exception("$$"); } } } } } #endregion #region 开始 public static bool isLaunching; public static McLaunchOptions currentLaunchOptions; public class McLaunchOptions { /// /// 额外的启动参数。 /// public List ExtraArgs = new(); /// /// 强行指定启动的 MC 实例。 /// 默认值:Nothing。使用 McInstanceCurrent。 /// public McInstance instance = null; /// /// 是否为 “测试游戏” 按钮启动的游戏。 /// 如果是,则显示游戏实时日志。 /// public bool IsTest = false; /// /// 将启动脚本保存到该地址,然后取消启动。这同时会改变启动时的提示等。 /// 默认值:Nothing。不保存。 /// public string SaveBatch = null; /// /// 强制指定在启动后进入的服务器 IP。 /// 默认值:Nothing。使用实例设置的值。 /// public string ServerIp = null; /// /// 指定在启动之后进入的存档名称。 /// 默认值:Nothing。使用实例设置的值。 /// public string WorldName = null; } /// /// 尝试启动 Minecraft。必须在 UI 线程调用。 /// 返回是否实际开始了启动(如果没有,则一定弹出了错误提示)。 /// public static bool McLaunchStart(McLaunchOptions options = null) { isLaunching = true; currentLaunchOptions = options ?? new McLaunchOptions(); // 预检查 if (!ModBase.RunInUi()) throw new Exception("McLaunchStart 必须在 UI 线程调用!"); if (mcLaunchLoader.State == ModBase.LoadState.Loading) { HintService.Hint(Lang.Text("Minecraft.Launch.Error.AlreadyLaunching"), HintType.Error); isLaunching = false; return false; } // 强制切换需要启动的实例 if (currentLaunchOptions.instance is not null && ModInstanceList.McMcInstanceSelected != currentLaunchOptions.instance) { McLaunchLog("在启动前切换到实例 " + currentLaunchOptions.instance.Name); // 检查实例 currentLaunchOptions.instance.Load(); if (currentLaunchOptions.instance.state == McInstanceState.Error) { HintService.Hint(Lang.Text("Minecraft.Launch.Error.CannotLaunch", currentLaunchOptions.instance.Desc), HintType.Error); isLaunching = false; return false; } // 切换实例 ModInstanceList.McMcInstanceSelected = currentLaunchOptions.instance; States.Game.SelectedInstance = ModInstanceList.McMcInstanceSelected.Name; ModMain.frmLaunchLeft.RefreshButtonsUI(); ModMain.frmLaunchLeft.RefreshPage(false); } ModMain.frmMain.AprilGiveup(); // 禁止进入实例选择页面(否则就可以在启动中切换 McInstanceCurrent 了) ModMain.frmMain.pageStack = ModMain.frmMain.pageStack.Where(p => p.page != FormMain.PageType.InstanceSelect).ToList(); // 实际启动加载器 mcLaunchLoader.Start(options, true); return true; } /// /// 记录启动日志。 /// public static void McLaunchLog(string text) { text = McLogFilter.FilterUserName(McLogFilter.FilterAccessToken(text, '*'), '*'); ModBase.RunInUi(() => ModMain.frmLaunchRight.LabLog.Text += "\r\n" + "[" + TimeUtils.GetTimeNow() + "] " + text); ModBase.Log("[Launch] " + text); } // 启动状态切换 public static ModLoader.LoaderTask mcLaunchLoader = new("Loader Launch", McLaunchStart) { OnStateChanged = a => McLaunchState((dynamic)a) }; public static ModLoader.LoaderCombo mcLaunchLoaderReal; public static Process mcLaunchProcess; public static ModWatcher.Watcher mcLaunchWatcher; private static void McLaunchState(ModLoader.LoaderTask loader) { switch (mcLaunchLoader.State) { case ModBase.LoadState.Finished: case ModBase.LoadState.Failed: case ModBase.LoadState.Waiting: case ModBase.LoadState.Aborted: { ModMain.frmLaunchLeft.PageChangeToLogin(); break; } case ModBase.LoadState.Loading: { // 在预检测结束后再触发动画 ModMain.frmLaunchRight.LabLog.Text = ""; break; } } } /// /// 指定启动中断时的提示文本。若不为 Nothing 则会显示为绿色。 /// private static string abortHint; // 实际的启动方法 private static void McLaunchStart(ModLoader.LoaderTask loader) { // 开始动画 ModBase.RunInUiWait(ModMain.frmLaunchLeft.PageChangeToLaunching); // 预检测(预检测的错误将直接抛出) try { McLaunchPrecheck(); McLaunchLog("预检测已通过"); } catch (Exception ex) { if (!ex.Message.StartsWithF("$$")) HintService.Hint(Lang.Text("Minecraft.Launch.Precheck.Failed.WithDetail", ex.Message), HintType.Error); throw; } // 正式加载 try { // 构造主加载器 var loaders = new List { new ModLoader.LoaderTask(Lang.Text("Minecraft.Launch.Stage.GetJava"), McLaunchJava) { ProgressWeight = 4d, block = false }, mcLoginLoader, new ModLoader.LoaderCombo(Lang.Text("Minecraft.Launch.Stage.CompleteFiles"), ModDownload.DlClientFix(ModInstanceList.McMcInstanceSelected, false, ModDownload.AssetsIndexExistsBehaviour.DownloadInBackground)) { ProgressWeight = 15d, show = false }, new ModLoader.LoaderTask>(Lang.Text("Minecraft.Launch.Stage.GetArguments"), McLaunchArgumentMain) { ProgressWeight = 2d }, new ModLoader.LoaderTask, int>(Lang.Text("Minecraft.Launch.Stage.ExtractNatives"), McLaunchNatives) { ProgressWeight = 2d }, new ModLoader.LoaderTask(Lang.Text("Minecraft.Launch.Stage.PreLaunch"), _ => McLaunchPrerun()) { ProgressWeight = 1d }, new ModLoader.LoaderTask(Lang.Text("Minecraft.Launch.Stage.CustomCommand"), McLaunchCustom) { ProgressWeight = 1d }, new ModLoader.LoaderTask(Lang.Text("Minecraft.Launch.Stage.StartProcess"), McLaunchRun) { ProgressWeight = 2d }, new ModLoader.LoaderTask(Lang.Text("Minecraft.Launch.Stage.WaitWindow"), McLaunchWait) { ProgressWeight = 1d }, new ModLoader.LoaderTask(Lang.Text("Minecraft.Launch.Stage.End"), _ => McLaunchEnd()) { ProgressWeight = 1d } }; // .ProgressWeight = 15, .Block = False var launchLoader = new ModLoader.LoaderCombo(Lang.Text("Minecraft.Launch.Stage.Root"), loaders) { show = false }; if (mcLoginLoader.State == ModBase.LoadState.Finished) mcLoginLoader.State = ModBase.LoadState.Waiting; // 要求重启登录主加载器,它会自行决定是否启动副加载器 // 等待加载器执行并更新 UI mcLaunchLoaderReal = launchLoader; abortHint = null; launchLoader.Start(); // 任务栏进度条 ModLoader.LoaderTaskbarAdd(launchLoader); while (launchLoader.State == ModBase.LoadState.Loading) { ModMain.frmLaunchLeft.Dispatcher.Invoke(ModMain.frmLaunchLeft.LaunchingRefresh); Thread.Sleep(100); } ModMain.frmLaunchLeft.Dispatcher.Invoke(ModMain.frmLaunchLeft.LaunchingRefresh); // 成功与失败处理 switch (launchLoader.State) { case ModBase.LoadState.Finished: { HintService.Hint(Lang.Text("Minecraft.Launch.Success", ModInstanceList.McMcInstanceSelected.Name), HintType.Success); break; } case ModBase.LoadState.Aborted: { if (abortHint is null) HintService.Hint(currentLaunchOptions?.SaveBatch is null ? Lang.Text("Minecraft.Launch.Cancelled") : Lang.Text("Minecraft.Launch.ExportScript.Cancelled")); else HintService.Hint(abortHint, HintType.Success); break; } case ModBase.LoadState.Failed: { throw launchLoader.Error; } default: { throw new Exception(Lang.Text("Minecraft.Launch.Error.InvalidState", ModBase.GetStringFromEnum(launchLoader.State))); } } isLaunching = false; } catch (Exception ex) { var currentEx = ex; while (currentEx is not null) { if (currentEx.Message.StartsWithF("$")) { // 若有以 $ 开头的错误信息,则以此为准显示提示 // 若错误信息为 $$,则不提示 if (currentEx.Message != "$$") ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Error.SpecialMessage.WithDetail", currentEx.Message.TrimStart('$')), currentLaunchOptions?.SaveBatch is null ? Lang.Text("Launch.Error.Title") : Lang.Text("Launch.Error.ExportScriptTitle")); throw; } if (currentEx.InnerException is null) break; // 检查下一级错误 currentEx = currentEx.InnerException; } // 没有特殊处理过的错误信息 McLaunchLog("错误:" + ex); ModBase.Log( ex, currentLaunchOptions?.SaveBatch is null ? "Minecraft launch failed" : "Export script failed", ModBase.LogLevel.Msgbox, currentLaunchOptions?.SaveBatch is null ? Lang.Text("Launch.Error.Title") : Lang.Text("Launch.Error.ExportScriptTitle"), userSummary: currentLaunchOptions?.SaveBatch is null ? Lang.Text("Minecraft.Launch.Error.LaunchFailed") : Lang.Text("Minecraft.Launch.Error.ExportScriptFailed")); throw; } } #endregion #region 档案验证 #region 主模块 // 登录方式 public enum McLoginType { Legacy = 1, Auth = 2, Ms = 3 } // 各个登录方式的对应数据 public abstract class McLoginData { /// /// 登录方式。 /// public McLoginType LoginType; public override bool Equals(object obj) { return obj is not null && obj.GetHashCode() == GetHashCode(); } } #region 第三方验证类型 public class McLoginServer : McLoginData { /// /// 登录服务器基础地址。 /// public string BaseUrl; /// /// 登录方式的描述字符串,如 “正版”、“统一通行证”。 /// public string Description; /// /// 是否在本次登录中强制要求玩家重新选择角色,目前仅对 Authlib-Injector 生效。 /// public bool ForceReselectProfile = false; /// /// 是否已经存在该验证信息,用于判断是否为新增档案。 /// public bool IsExist = false; /// /// 登录密码。 /// public string Password; /// /// 登录用户名。 /// public string UserName; public McLoginServer(McLoginType type) { this.LoginType = type; } public override int GetHashCode() { return (int)Math.Round(ModBase.GetHash(UserName + Password + BaseUrl + (int)LoginType) % (decimal)int.MaxValue); } } #endregion #region 正版验证类型 public class McLoginMs : McLoginData { public string AccessToken = ""; /// /// 缓存的 OAuth RefreshToken。若没有则为空字符串。 /// public string OAuthRefreshToken = ""; public string ProfileJson = ""; public string UserName = ""; public string Uuid = ""; public McLoginMs() { LoginType = McLoginType.Ms; } public override int GetHashCode() { return (int)Math.Round(ModBase.GetHash(OAuthRefreshToken + AccessToken + Uuid + UserName + ProfileJson) % (decimal)int.MaxValue); } } #endregion #region 离线验证类型 public class McLoginLegacy : McLoginData { /// /// 若采用正版皮肤,则为该皮肤名。 /// public string SkinName; /// /// 皮肤种类。 /// public int SkinType; /// /// 登录用户名。 /// public string UserName; /// /// UUID。 /// public string Uuid; public McLoginLegacy() { LoginType = McLoginType.Legacy; } public override int GetHashCode() { return (int)Math.Round( ModBase.GetHash(UserName + SkinType + SkinName + (int)LoginType) % (decimal)int.MaxValue); } } #endregion // 登录返回结果 public struct McLoginResult { public string Name; public string Uuid; public string AccessToken; public string Type; public string ClientToken; /// /// 进行微软登录时返回的 profile 信息。 /// public string ProfileJson; } // 登录主模块加载器 public static ModLoader.LoaderTask mcLoginLoader = new(Lang.Text("Minecraft.Launch.Stage.Login"), McLoginStart, McLoginInput, ThreadPriority.BelowNormal) { reloadTimeout = 1, ProgressWeight = 15d, block = false }; public static McLoginData McLoginInput() { McLoginData loginData = null; try { loginData = ModProfile.GetLoginData(); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Launch.Login.Error.Input"), ModBase.LogLevel.Feedback, userSummary: Lang.Text("Minecraft.Launch.Login.Error.Input")); } return loginData; } private static void McLoginStart(ModLoader.LoaderTask data) { ModBase.Log("[Profile] 开始加载选定档案"); // 校验登录信息 var checkResult = ModProfile.IsProfileValid(); if (!string.IsNullOrEmpty(checkResult)) throw new ArgumentException(checkResult); // 获取对应加载器 ModLoader.LoaderBase loader = null; switch (data.input.LoginType) { case McLoginType.Ms: { loader = mcLoginMsLoader; break; } case McLoginType.Legacy: { loader = mcLoginLegacyLoader; break; } case McLoginType.Auth: { loader = mcLoginAuthLoader; break; } } // 尝试加载 loader.WaitForExit(data.input, mcLoginLoader, data.isForceRestarting); data.output = (McLoginResult)((dynamic)loader).output; ModBase.RunInUi(() => ModMain.frmLaunchLeft.RefreshPage(false)); // 刷新自动填充列表 ModBase.Log("[Profile] 选定档案加载完成"); } #endregion // 各个登录方式的主对象与输入构造 public static ModLoader.LoaderTask mcLoginMsLoader = new("Loader Login Ms", McLoginMsStart) { reloadTimeout = 1 }; public static ModLoader.LoaderTask mcLoginLegacyLoader = new("Loader Login Legacy", McLoginLegacyStart); public static ModLoader.LoaderTask mcLoginAuthLoader = new("Loader Login Auth", McLoginServerStart) { reloadTimeout = 1000 * 60 * 10 }; // 主加载函数,返回所有需要的登录信息 private static long mcLoginMsRefreshTime; // 上次刷新登录的时间 #region 正版验证 private static void McLoginMsStart(ModLoader.LoaderTask data) { var input = data.input; var logUsername = input.UserName; var isNewProfile = true; ModProfile.ProfileLog($"验证方式:正版({(string.IsNullOrEmpty(logUsername) ? "尚未登录" : logUsername)})"); data.Progress = 0.05d; // 已登录且不需要强制重启且登录未过期 if (!data.isForceRestarting && !string.IsNullOrEmpty(input.AccessToken) && mcLoginMsRefreshTime > 0L && TimeUtils.GetTimeTick() - mcLoginMsRefreshTime < 1000 * 60 * 10) { data.output = new McLoginResult { AccessToken = input.AccessToken, Name = input.UserName, Uuid = input.Uuid, Type = "Microsoft", ClientToken = input.Uuid, ProfileJson = input.ProfileJson }; mcLoginMsRefreshTime = TimeUtils.GetTimeTick(); ModProfile.ProfileLog("正版验证完成"); return; } data.Progress = 0.1d; // 尝试获取 OAuthToken var oauthTokens = GetOAuthTokens(data, input, out var skipAuth); if (skipAuth) { data.Progress = 0.99d; var profile = ModProfile.selectedProfile; data.output = new McLoginResult { AccessToken = profile.AccessToken, Name = profile.Username, Uuid = profile.Uuid, Type = "Microsoft" }; return; } var oauthAccessToken = oauthTokens[0]; var oauthRefreshToken = oauthTokens[1]; ThrowIfAborted(data); data.Progress = 0.25d; // Step 2: XBL Token var xblToken = MsLoginStep2(oauthAccessToken); if (string.IsNullOrEmpty(xblToken) || xblToken == "Ignore") goto SkipLogin; data.Progress = 0.4d; ThrowIfAborted(data); // Step 3: XSTS / Minecraft login var tokens = MsLoginStep3(xblToken); if (tokens.Length < 2 || tokens[1] == "Ignore") goto SkipLogin; data.Progress = 0.55d; ThrowIfAborted(data); // Step 4: Final access token var accessToken = MsLoginStep4(tokens); if (string.IsNullOrEmpty(accessToken) || accessToken == "Ignore") goto SkipLogin; data.Progress = 0.7d; ThrowIfAborted(data); // Step 5: Additional setup MsLoginStep5(accessToken); data.Progress = 0.85d; ThrowIfAborted(data); // Step 6: Profile info var result = MsLoginStep6(accessToken); if (result.Length < 3 || result[2] == "Ignore") goto SkipLogin; data.Progress = 0.98d; // 检查是否已有相同档案 foreach (var profile in ModProfile.profileList) if (profile.Type == McLoginType.Ms && string.Equals(profile.Username, result[1], StringComparison.Ordinal) && string.Equals(profile.Uuid, result[0], StringComparison.Ordinal)) { isNewProfile = false; if (ModProfile.isCreatingProfile) { var index = ModProfile.profileList.IndexOf(profile); ModProfile.profileList[index].Username = result[1]; ModProfile.profileList[index].AccessToken = accessToken; ModProfile.profileList[index].RefreshToken = oauthRefreshToken; HintService.Hint(Lang.Text("Minecraft.Launch.Login.Microsoft.ProfileAlreadyAdded")); goto SkipLogin; } } // 输出登录结果 if (isNewProfile) { var newProfile = new ModProfile.McProfile { Type = McLoginType.Ms, Uuid = result[0], Username = result[1], AccessToken = accessToken, RefreshToken = oauthRefreshToken, Expires = 1743779140286L, Desc = "", RawJson = result[2] }; ModProfile.profileList.Add(newProfile); ModProfile.selectedProfile = newProfile; ModProfile.isCreatingProfile = false; } else { var index = ModProfile.profileList.IndexOf(ModProfile.selectedProfile); ModProfile.profileList[index].Username = result[1]; ModProfile.profileList[index].AccessToken = accessToken; ModProfile.profileList[index].RefreshToken = oauthRefreshToken; } ModProfile.SaveProfile(); data.output = new McLoginResult { AccessToken = accessToken, Name = result[1], Uuid = result[0], Type = "Microsoft", ClientToken = result[0], ProfileJson = result[2] }; SkipLogin: mcLoginMsRefreshTime = TimeUtils.GetTimeTick(); ModProfile.ProfileLog("正版验证完成"); } /// /// 获取 OAuth Tokens,处理刷新和重新登录逻辑 /// private static string[] GetOAuthTokens(ModLoader.LoaderTask data, McLoginMs input, out bool skipAuth) { skipAuth = false; string[] tokens; while (true) { if (string.IsNullOrEmpty(input.OAuthRefreshToken)) { tokens = MsLoginStep1New(data); } else { tokens = MsLoginStep1Refresh(input.OAuthRefreshToken); if (tokens.Length > 0 && tokens[0] == "Relogin") { // 刷新令牌已失效,清除后回退到设备代码流重新登录,避免无限循环 input.OAuthRefreshToken = ""; continue; } } if (tokens.Length > 0 && tokens[0] == "Ignore") { skipAuth = true; return tokens; } return tokens; } } /// /// 检查是否被中断 /// private static void ThrowIfAborted(ModLoader.LoaderTask data) { if (data.IsAborted) throw new ThreadInterruptedException(); } /// /// 正版验证步骤 1:通过设备代码流获取账号信息 /// /// OAuth 验证完成的返回结果 private static string[] MsLoginStep1New(ModLoader.LoaderTask data) { // 参考:https://learn.microsoft.com/zh-cn/entra/identity-platform/v2-oauth2-device-code // 初始请求 Retry: ; McLaunchLog("开始正版验证 Step 1/6(原始登录)"); JsonObject prepareJson; var parameters = new Dictionary { { "client_id", Secrets.MSOAuthClientId }, { "tenant", "/consumers" }, { "scope", "XboxLive.signin offline_access" } }; using (var response = HttpRequest .CreatePost("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode") .WithFormContent(parameters) .SendAsync() .GetAwaiter() .GetResult()) { response.EnsureSuccessStatusCode(); prepareJson = (JsonObject)ModBase.GetJson(response.AsString()); } McLaunchLog("网页登录地址:" + prepareJson["verification_uri"]); // 弹窗 var converter = new ModMain.MyMsgBoxConverter { Content = prepareJson, ForceWait = true, Type = ModMain.MyMsgBoxType.Login }; ModMain.WaitingMyMsgBox.Add(converter); while (converter.Result is null) Thread.Sleep(100); if (converter.Result is ModBase.RestartException) { if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.PasswordRequired.Message", ModBase.vbLQ, ModBase.vbRQ), Lang.Text("Minecraft.Launch.Login.PasswordRequired.Title"), Lang.Text("Minecraft.Launch.Login.PasswordRequired.Relogin"), Lang.Text("Minecraft.Launch.Login.PasswordRequired.SetPassword"), Lang.Text("Common.Action.Cancel"), button2Action: () => ModBase.OpenWebsite("https://account.live.com/password/Change")) == 1) goto Retry; throw new Exception("$$"); } if (converter.Result is Exception) throw (Exception)converter.Result; return (string[])converter.Result; } /// /// 正版验证步骤 1,刷新登录:从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth accessToken, OAuth RefreshToken} /// /// /// private static string[] MsLoginStep1Refresh(string code) { McLaunchLog("开始正版验证 Step 1/6(刷新登录)"); if (string.IsNullOrEmpty(code)) throw new ArgumentException("传入的 Code 为空", nameof(code)); string result = null; try { var parameters = new Dictionary { { "client_id", Secrets.MSOAuthClientId }, { "refresh_token", code }, { "grant_type", "refresh_token" }, { "scope", "XboxLive.signin offline_access" } }; using (var response = HttpRequest .CreatePost("https://login.live.com/oauth20_token.srf") .WithFormContent(parameters) .SendAsync() .GetAwaiter() .GetResult()) { result = response.AsString(); if (!response.IsSuccess) throw new HttpRequestException( $"刷新登录请求失败,状态码 {(int)response.StatusCode}:{result}"); } } catch (ThreadInterruptedException ex) { ModBase.Log(ex, "加载线程已终止"); } catch (Exception ex) { if (ex.Message.ContainsF("invalid_grant", true) || ex.Message.ContainsF("must sign in again", true) || ex.Message.ContainsF("must first sign in", true) || ex.Message.ContainsF("password expired", true) || (ex.Message.Contains("refresh_token") && ex.Message.Contains("is not valid"))) // #269 return new[] { "Relogin", "" }; ModProfile.ProfileLog("正版验证 Step 1/6 获取 OAuth Token 失败:" + ex); var isIgnore = false; ModBase.RunInUiWait(() => { if (!isLaunching) return; if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Message"), Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Title"), Lang.Text("Minecraft.Launch.Login.Continue"), Lang.Text("Common.Action.Cancel")) == 1) isIgnore = true; }); if (isIgnore) return new[] { "Ignore", "" }; // 用户取消或登录线程已结束,静默中止启动,避免落入下方的 JSON 解析空引用 throw new Exception("$$"); } var resultJson = (JsonObject)ModBase.GetJson(result); var accessToken = resultJson["access_token"].ToString(); var refreshToken = resultJson["refresh_token"].ToString(); return new[] { accessToken, refreshToken }; } private class XBLTokenRequestData { public PropertiesData Properties { get; set; } public string RelyingParty { get; set; } public string TokenType { get; set; } public class PropertiesData { public string AuthMethod { get; set; } public string SiteName { get; set; } public string RpsTicket { get; set; } } } /// /// 正版验证步骤 2:从 OAuth accessToken 获取 XBLToken /// /// OAuth accessToken /// XBLToken private static string MsLoginStep2(string accessToken) { ModProfile.ProfileLog("开始正版验证 Step 2/6: 获取 XBLToken"); if (string.IsNullOrEmpty(accessToken)) throw new ArgumentException("传入的 AccessToken 为空", nameof(accessToken)); var requestData = new XBLTokenRequestData { Properties = new XBLTokenRequestData.PropertiesData { AuthMethod = "RPS", SiteName = "user.auth.xboxlive.com", RpsTicket = $"d={accessToken}" }, RelyingParty = "http://auth.xboxlive.com", TokenType = "JWT" }; string result = null; try { using (var response = HttpRequest .CreatePost("https://user.auth.xboxlive.com/user/authenticate") .WithJsonContent(requestData) .SendAsync() .GetAwaiter() .GetResult()) { response.EnsureSuccessStatusCode(); result = response.AsString(); } } catch (Exception ex) { ModProfile.ProfileLog("正版验证 Step 2/6 获取 XBLToken 失败:" + ex); var isIgnore = false; ModBase.RunInUiWait(() => { if (!isLaunching) return; if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Message"), Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Title"), Lang.Text("Minecraft.Launch.Login.Continue"), Lang.Text("Common.Action.Cancel")) == 1) isIgnore = true; }); if (isIgnore) return "Ignore"; } var resultJson = (JsonObject)ModBase.GetJson(result); var xBLToken = resultJson["Token"].ToString(); return xBLToken; } private class XSTSTokenRequestData { public PropertiesData Properties { get; set; } public string RelyingParty { get; set; } public string TokenType { get; set; } public class PropertiesData { public string SandboxId { get; set; } public List UserTokens { get; set; } } } /// /// 正版验证步骤 3:从 XBLToken 获取 {XSTSToken, UHS} /// /// 包含 XSTSToken 与 UHS 的字符串组 private static string[] MsLoginStep3(string xBLToken) { ModProfile.ProfileLog("开始正版验证 Step 3/6: 获取 XSTSToken"); if (string.IsNullOrEmpty(xBLToken)) throw new ArgumentException("XBLToken 为空,无法获取数据", nameof(xBLToken)); var requestData = new XSTSTokenRequestData { Properties = new XSTSTokenRequestData.PropertiesData { SandboxId = "RETAIL", UserTokens = new[] { xBLToken }.ToList() }, RelyingParty = "rp://api.minecraftservices.com/", TokenType = "JWT" }; string result; using (var response = HttpRequest .CreatePost("https://xsts.auth.xboxlive.com/xsts/authorize") .WithJsonContent(requestData) .SendAsync() .GetAwaiter() .GetResult()) { result = response.AsString(); if (!response.IsSuccess) { // 参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js if (result.Contains("2148916227")) { ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.Login.Microsoft.Banned"), Lang.Text("Minecraft.Launch.Login.Failed"), Lang.Text("Minecraft.Launch.Login.IKnow"), isWarn: true); throw new Exception("$$"); } if (result.Contains("2148916233")) { if (ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.Login.Microsoft.XboxNotRegistered"), Lang.Text("Minecraft.Launch.Login.Hint"), Lang.Text("Minecraft.Launch.Login.Register"), Lang.Text("Common.Action.Cancel")) == 1) ModBase.OpenWebsite("https://signup.live.com/signup"); throw new Exception("$$"); } if (result.Contains("2148916235")) { ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.Login.Microsoft.RegionBlocked"), Lang.Text("Minecraft.Launch.Login.Failed"), Lang.Text("Minecraft.Launch.Login.IKnow")); throw new Exception("$$"); } if (result.Contains("2148916238")) { if (ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.Login.Microsoft.Underage.Message"), Lang.Text("Minecraft.Launch.Login.Hint"), Lang.Text("Minecraft.Launch.Login.Microsoft.Underage.AgeOver13"), Lang.Text("Minecraft.Launch.Login.Microsoft.Underage.AgeUnder13"), Lang.Text("Common.Option.IDontKnow")) == 1) { ModBase.OpenWebsite("https://account.live.com/editprof.aspx"); ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.Microsoft.ChangeBirthDate.Message"), Lang.Text("Minecraft.Launch.Login.Hint")); } else { ModBase.OpenWebsite( "https://support.microsoft.com/zh-cn/account-billing/如何更改-microsoft-帐户上的出生日期-837badbc-999e-54d2-2617-d19206b9540a"); ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.Microsoft.ChangeBirthDate.SupportMessage"), Lang.Text("Minecraft.Launch.Login.Hint")); } throw new Exception("$$"); } ModProfile.ProfileLog("正版验证 Step 3/6 获取 XSTSToken 失败:" + response.StatusCode); var isIgnore = false; ModBase.RunInUiWait(() => { if (!isLaunching) return; if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Message"), Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Title"), Lang.Text("Minecraft.Launch.Login.Continue"), Lang.Text("Common.Action.Cancel")) == 1) isIgnore = true; }); if (isIgnore) { return new[] { ModProfile.selectedProfile.AccessToken, "Ignore" }; return default; } response.EnsureSuccessStatusCode(); } } var resultJson = (JsonObject)ModBase.GetJson(result); var xSTSToken = resultJson["Token"].ToString(); var uhs = resultJson["DisplayClaims"]["xui"][0]["uhs"].ToString(); return new[] { xSTSToken, uhs }; } /// /// 正版验证步骤 4:从 {XSTSToken, UHS} 获取 Minecraft accessToken /// /// 包含 XSTSToken 与 UHS 的字符串组 /// Minecraft accessToken private static string MsLoginStep4(string[] tokens) { ModProfile.ProfileLog("开始正版验证 Step 4/6: 获取 Minecraft AccessToken"); if (tokens.Length < 2 || string.IsNullOrEmpty(tokens.ElementAt(0)) || string.IsNullOrEmpty(tokens.ElementAt(1))) throw new ArgumentException("传入的 XSTSToken 或者 UHS 错误", nameof(tokens)); var requestData = new Dictionary { { "identityToken", $"XBL3.0 x={tokens[1]};{tokens[0]}" } }; string result; try { using (var response = HttpRequest .CreatePost("https://api.minecraftservices.com/authentication/login_with_xbox") .WithJsonContent(requestData) .SendAsync() .GetAwaiter() .GetResult()) { response.EnsureSuccessStatusCode(); result = response.AsString(); } } catch (HttpRequestException ex) { var message = ex.Message; if (ex.StatusCode.Equals(HttpStatusCode.TooManyRequests)) { ModBase.Log(ex, "正版验证 Step 4 汇报 429"); throw new Exception(Lang.Text("Minecraft.Launch.Login.Microsoft.TooManyRequests")); } if (ex.StatusCode is { } arg1 && arg1 == HttpStatusCode.Forbidden) { ModBase.Log(ex, "正版验证 Step 4 汇报 403"); throw new Exception(Lang.Text("Minecraft.Launch.Login.Microsoft.AbnormalIp")); } ModProfile.ProfileLog("正版验证 Step 4/6 获取 MC AccessToken 失败:" + ex); var isIgnore = false; ModBase.RunInUiWait(() => { if (!isLaunching) return; if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Message"), Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Title"), Lang.Text("Minecraft.Launch.Login.Continue"), Lang.Text("Common.Action.Cancel")) == 1) isIgnore = true; }); if (isIgnore) { return "Ignore"; return default; } throw; } var resultJson = (JsonObject)ModBase.GetJson(result); var accessToken = resultJson["access_token"].ToString(); if (string.IsNullOrWhiteSpace(accessToken)) throw new Exception("获取到的 Minecraft AccessToken 为空,登录流程异常!"); return accessToken; } /// /// 正版验证步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP /// /// Minecraft accessToken private static void MsLoginStep5(string accessToken) { ModProfile.ProfileLog("开始正版验证 Step 5/6: 验证账户是否持有 MC"); if (string.IsNullOrEmpty(accessToken)) throw new ArgumentException("传入的 AccessToken 为空", nameof(accessToken)); var result = ""; try { using (var response = HttpRequest .Create("https://api.minecraftservices.com/entitlements/mcstore") .WithBearerToken(accessToken) .SendAsync() .GetAwaiter() .GetResult()) { response.EnsureSuccessStatusCode(); result = response.AsString(); } var resultJson = (JsonObject)ModBase.GetJson(result); if (!(resultJson.ContainsKey("items") && resultJson["items"].AsArray().Any(x => x["name"]?.ToString() == "product_minecraft" || x["name"]?.ToString() == "game_minecraft"))) { switch (ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.Login.Microsoft.NotPurchased"), Lang.Text("Minecraft.Launch.Login.Failed"), Lang.Text("Minecraft.Launch.Login.Microsoft.PurchaseMinecraft"), Lang.Text("Common.Action.Cancel"))) { case 1: { ModBase.OpenWebsite( "https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"); break; } } throw new Exception("$$"); } } catch (Exception ex) { ModBase.Log(ex, "正版验证 Step 5 异常:" + result); throw; } } /// /// 正版验证步骤 6:从 Minecraft accessToken 获取 {UUID, UserName, ProfileJson} /// /// Minecraft accessToken /// 包含 UUID, UserName 和 ProfileJson 的字符串组 private static string[] MsLoginStep6(string accessToken) { ModProfile.ProfileLog("开始正版验证 Step 6/6: 获取玩家 ID 与 UUID 等相关信息"); if (string.IsNullOrEmpty(accessToken)) throw new ArgumentException("传入的 AccessToken 为空", nameof(accessToken)); string result; try { using (var response = HttpRequest .Create("https://api.minecraftservices.com/minecraft/profile") .WithBearerToken(accessToken) .SendAsync() .GetAwaiter() .GetResult()) { response.EnsureSuccessStatusCode(); result = response.AsString(); } } catch (HttpRequestException ex) { var message = ex.Message; if (ex.StatusCode.Equals(HttpStatusCode.TooManyRequests)) { ModBase.Log(ex, "正版验证 Step 6 汇报 429"); throw new Exception(Lang.Text("Minecraft.Launch.Login.Microsoft.TooManyRequests")); } if (ex.StatusCode is { } arg2 && arg2 == HttpStatusCode.NotFound) { ModBase.Log(ex, "正版验证 Step 6 汇报 404"); ModBase.RunInNewThread(() => { switch (ModMain.MyMsgBox(Lang.Text("Minecraft.Launch.Login.Microsoft.CreateProfile.Message"), Lang.Text("Minecraft.Launch.Login.Failed"), Lang.Text("Minecraft.Launch.Login.Microsoft.CreateProfile.Button"), Lang.Text("Common.Action.Cancel"))) { case 1: { ModBase.OpenWebsite("https://www.minecraft.net/zh-hans/msaprofile/mygames/editprofile"); break; } } }, "Login Failed: Create Profile"); throw new Exception("$$"); } ModProfile.ProfileLog("正版验证 Step 6/6 获取玩家档案信息失败:" + ex); var isIgnore = false; ModBase.RunInUiWait(() => { if (!isLaunching) return; if (ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Message"), Lang.Text("Minecraft.Launch.Login.RefreshAccountFailed.Title"), Lang.Text("Minecraft.Launch.Login.Continue"), Lang.Text("Common.Action.Cancel")) == 1) isIgnore = true; }); if (isIgnore) { return new[] { ModProfile.selectedProfile.Uuid, ModProfile.selectedProfile.Username, "Ignore" }; return default; } throw; } var resultJson = (JsonObject)ModBase.GetJson(result); var uuid = resultJson["id"].ToString(); var userName = resultJson["name"].ToString(); return new[] { uuid, userName, result }; } #endregion #region 第三方验证 private static void McLoginServerStart(ModLoader.LoaderTask data) { var input = data.input; var needRefresh = false; var wasRefreshed = false; ModProfile.ProfileLog("验证方式:" + input.Description); data.Progress = 0.05d; // 尝试验证登录(如果不需要重新选择档案且不是创建档案) if (!input.ForceReselectProfile && !ModProfile.isCreatingProfile) { try { ThrowIfAborted(data); McLoginRequestValidate(ref data); data.Progress = 0.95d; return; // 登录成功,直接返回 } catch (WebException ex) { _HandleHttpWebException(ex, "验证登录失败"); } catch (HttpResponseException ex){ ModProfile.ProfileLog($"验证登录失败: {ex}"); } catch (Exception ex) { _HandleException(ex, "验证登录失败", "Minecraft.Launch.Login.Auth.ValidationFailed.WithDetail"); } data.Progress = 0.25d; // 尝试刷新登录 try { ThrowIfAborted(data); McLoginRequestRefresh(ref data, needRefresh); data.Progress = needRefresh ? 0.85d : 0.45d; data.Progress = 0.95d; return; // 刷新成功,直接返回 } catch (Exception ex) { ModProfile.ProfileLog(Lang.Text("Minecraft.Launch.Login.Auth.RefreshFailed") + ": " + ex); ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.Auth.RefreshFailed.WithDetail", ex.ToString()), Lang.Text("Minecraft.Launch.Login.Auth.FailedTitle"), isWarn: true); if (wasRefreshed) throw new Exception(Lang.Text("Minecraft.Launch.Login.Auth.SecondRefreshFailed"), ex); } } // 尝试普通登录 try { ThrowIfAborted(data); needRefresh = McLoginRequestLogin(ref data); } catch (WebException ex) { _HandleLoginHttpException(ex); } catch (Exception ex) { _HandleException(ex, "第三方登录失败", "Minecraft.Launch.Login.Auth.LoginFailed.WithDetail"); } // 如果需要刷新,循环刷新一次 if (needRefresh) { ModProfile.ProfileLog("重新进行刷新登录"); wasRefreshed = true; data.Progress = 0.65d; try { ThrowIfAborted(data); McLoginRequestRefresh(ref data, needRefresh); data.Progress = 0.95d; return; } catch (Exception ex) { ModProfile.ProfileLog(Lang.Text("Minecraft.Launch.Login.Auth.RefreshFailed") + ": " + ex); ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Login.Auth.RefreshFailed.WithDetail", ex.ToString()), Lang.Text("Minecraft.Launch.Login.Auth.FailedTitle"), isWarn: true); throw new Exception(Lang.Text("Minecraft.Launch.Login.Auth.SecondRefreshFailed"), ex); } } // 最终完成 // 兜底校验:若走到这里仍未取得有效 AccessToken(例如回退登录的 HTTP 失败被 McLoginRequestLogin // 吞掉并返回 false),说明登录实际失败,必须中止,避免带着空凭据继续启动(见 #3307 review)。 if (string.IsNullOrEmpty(data.output.AccessToken)) throw new Exception(Lang.Text("Minecraft.Launch.Login.Failed")); data.Progress = 0.95d; } /// /// 检查任务是否被中断 /// private static void ThrowIfAborted(ModLoader.LoaderTask data) { if (data.IsAborted) throw new ThreadInterruptedException(); } /// /// 统一处理 HttpWebException /// private static void _HandleHttpWebException(WebException ex, string logPrefix) { var allMessage = ex.ToString(); ModProfile.ProfileLog(logPrefix + ":" + allMessage); if ((!allMessage.Contains("超时") && !allMessage.Contains("imeout")) || allMessage.Contains("403")) return; ModProfile.ProfileLog("已触发超时登录失败"); var message = Lang.Text("Minecraft.Launch.Login.Auth.Timeout.WithDetail", ex.ToString()); ModMain.MyMsgBox( message, Lang.Text("Minecraft.Launch.Login.Auth.FailedTitle"), isWarn: true); throw new Exception("$" + message); } /// /// 统一处理普通异常 /// private static void _HandleException( Exception ex, string logPrefix, string userMessageKey) { ModProfile.ProfileLog(logPrefix + ":" + ex); var message = Lang.Text(userMessageKey, ex.ToString()); ModMain.MyMsgBox( message, Lang.Text("Minecraft.Launch.Login.Auth.FailedTitle"), isWarn: true); throw new Exception("$" + message); } /// /// 处理普通登录 HttpWebException /// private static void _HandleLoginHttpException(WebException ex) { ModProfile.ProfileLog("验证失败:" + ex); var message = Lang.Text("Minecraft.Launch.Login.Auth.NetworkFailed.WithDetail", ex.ToString()); ModMain.MyMsgBox( message, Lang.Text("Minecraft.Launch.Login.Auth.FailedTitle"), isWarn: true); throw new Exception("$" + message); } // Server 登录:三种验证方式的请求 private static void McLoginRequestValidate(ref ModLoader.LoaderTask data) { ModProfile.ProfileLog("验证登录开始(Validate, Authlib"); // 提前缓存信息,否则如果在登录请求过程中退出登录,设置项目会被清空,导致输出存在空值 var accessToken = ""; var clientToken = ""; var uuid = ""; var name = ""; if (ModProfile.selectedProfile is not null) { accessToken = ModProfile.selectedProfile.AccessToken; clientToken = ModProfile.selectedProfile.ClientToken; uuid = ModProfile.selectedProfile.Uuid; name = ModProfile.selectedProfile.Username; } // 发送登录请求 var requestData = new JsonObject { ["accessToken"] = accessToken, ["clientToken"] = clientToken }; Requester.Fetch(data.input.BaseUrl + "/validate", new FetchParam { Method = "POST", Content = requestData.ToJsonString(), Headers = new Dictionary { { "Accept-Language", "zh-CN" } }, ContentType = "application/json" }); // 没有返回值的 // 将登录结果输出 data.output.AccessToken = accessToken; data.output.ClientToken = clientToken; data.output.Uuid = uuid; data.output.Name = name; data.output.Type = "Auth"; // 不更改缓存,直接结束 ModProfile.ProfileLog("验证登录成功(Validate, Authlib"); } private static void McLoginRequestRefresh(ref ModLoader.LoaderTask data, bool requestUser) { try { var refreshInfo = new JsonObject(); var selectProfile = new JsonObject { { "name", ModProfile.selectedProfile.Username }, { "id", ModProfile.selectedProfile.Uuid } }; refreshInfo.Add("selectedProfile", selectProfile); refreshInfo.Add("accessToken", ModProfile.selectedProfile.AccessToken); refreshInfo.Add("requestUser", true); ModProfile.ProfileLog("刷新登录开始(Refresh, Authlib"); var loginJson = (JsonObject)ModBase.GetJson(Requester.Fetch(data.input.BaseUrl + "/refresh", new FetchParam { Method = "POST", Content = refreshInfo.ToJsonString(), Headers = new Dictionary { { "Accept-Language", "zh-CN" } }, ContentType = "application/json", RequireContent = true } )); // 将登录结果输出 if (loginJson["selectedProfile"] is null) throw new Exception(Lang.Text("Minecraft.Launch.Login.Auth.InvalidProfile", ModProfile.selectedProfile.Username)); data.output.AccessToken = loginJson["accessToken"].ToString(); data.output.ClientToken = loginJson["clientToken"].ToString(); data.output.Uuid = loginJson["selectedProfile"]["id"].ToString(); data.output.Name = loginJson["selectedProfile"]["name"].ToString(); data.output.Type = "Auth"; // 保存缓存 var profileIndex = ModProfile.profileList.IndexOf(ModProfile.selectedProfile); ModProfile.profileList[profileIndex].Username = data.output.Name; ModProfile.profileList[profileIndex].AccessToken = data.output.AccessToken; ModProfile.profileList[profileIndex].ClientToken = data.output.ClientToken; ModProfile.profileList[profileIndex].Uuid = data.output.Uuid; ModProfile.profileList[profileIndex].Name = data.input.UserName; ModProfile.profileList[profileIndex].Password = data.input.Password; ModProfile.ProfileLog("刷新登录成功(Refresh, Authlib)"); } catch (HttpResponseException ex) { // 刷新失败必须向上抛出:否则 McLoginServerStart 会把本次登录判为“刷新成功”、带着空令牌继续 // 启动,并丧失“回退到普通登录”的自动恢复机会。保留服务端错误详情作为消息,并把原始 // HttpResponseException(含状态码/堆栈)作为 InnerException 以便诊断;同时显式 Dispose 及时 // 释放底层 Response,不依赖终结器兜底(其回收时机不确定,可能令底层资源驻留)。 var message = _TryGetLastError(ex, out var detail) ? detail : ex.Message; ex.Dispose(); throw new Exception(message, ex); } } private static bool McLoginRequestLogin(ref ModLoader.LoaderTask data) { try { var needRefresh = false; ModProfile.ProfileLog("登录开始(Login, Authlib)"); var requestData = new JsonObject { ["agent"] = new JsonObject { ["name"] = "Minecraft", ["version"] = 1 }, ["username"] = data.input.UserName, ["password"] = data.input.Password, ["requestUser"] = true }; var loginJson = (JsonObject)ModBase.GetJson(Requester.Fetch(data.input.BaseUrl + "/authenticate", new FetchParam { Method = "POST", Content = requestData.ToJsonString(), Headers = new Dictionary { { "Accept-Language", "zh-CN" } }, ContentType = "application/json", RequireContent = true })); // 检查登录结果 if (loginJson["availableProfiles"].AsArray().Count == 0) { if (data.input.ForceReselectProfile) HintService.Hint(Lang.Text("Minecraft.Launch.Login.Auth.NoProfileCannotSwitch"), HintType.Error); throw new Exception(Lang.Text("Minecraft.Launch.Login.Auth.NoProfile")); } if (data.input.ForceReselectProfile && loginJson["availableProfiles"].AsArray().Count == 1) HintService.Hint(Lang.Text("Minecraft.Launch.Login.Auth.OnlyOneProfile"), HintType.Error); string selectedName = null; string selectedId = null; if ((loginJson["selectedProfile"] is null || data.input.ForceReselectProfile) && loginJson["availableProfiles"].AsArray().Count > 1) { // 要求选择档案;优先从缓存读取 needRefresh = true; var cacheId = ModProfile.selectedProfile is not null ? ModProfile.selectedProfile.Uuid : ""; foreach (var profile in loginJson["availableProfiles"].AsArray()) if ((profile["id"].ToString() ?? "") == (cacheId ?? "")) { selectedName = profile["name"].ToString(); selectedId = profile["id"].ToString(); ModProfile.ProfileLog("根据缓存选择的角色:" + selectedName); } // 缓存无效,要求玩家选择 if (selectedName is null) { ModProfile.ProfileLog("要求玩家选择角色"); ModBase.RunInUiWait(() => { var selectionControl = new List(); var selectionJson = new List(); foreach (var profile in loginJson["availableProfiles"].AsArray()) { selectionControl.Add(new MyRadioBox { Text = profile["name"].ToString() }); selectionJson.Add(profile); } var selectedIndex = (int)ModMain.MyMsgBoxSelect(selectionControl, Lang.Text("Minecraft.Launch.Login.Auth.SelectProfile")); selectedName = selectionJson[selectedIndex]["name"].ToString(); selectedId = selectionJson[selectedIndex]["id"].ToString(); }); ModProfile.ProfileLog("玩家选择的角色:" + selectedName); } } else { selectedName = loginJson["selectedProfile"]["name"].ToString(); selectedId = loginJson["selectedProfile"]["id"].ToString(); } // 将登录结果输出 data.output.AccessToken = loginJson["accessToken"].ToString(); data.output.ClientToken = loginJson["clientToken"].ToString(); data.output.Name = selectedName; data.output.Uuid = selectedId; data.output.Type = "Auth"; // 获取服务器信息 var response = Requester.FetchString(data.input.BaseUrl.Replace("/authserver", "")); var serverName = ModBase.GetJson(response)["meta"]?["serverName"]?.ToString() ?? data.input.BaseUrl.Replace("/authserver", ""); // 保存缓存 if (data.input.IsExist) { var profileIndex = ModProfile.profileList.IndexOf(ModProfile.selectedProfile); ModProfile.profileList[profileIndex].Username = data.output.Name; ModProfile.profileList[profileIndex].Uuid = data.output.Uuid; ModProfile.profileList[profileIndex].ServerName = serverName; ModProfile.profileList[profileIndex].AccessToken = data.output.AccessToken; ModProfile.profileList[profileIndex].ClientToken = data.output.ClientToken; } else { var newProfile = new ModProfile.McProfile { Type = McLoginType.Auth, Uuid = data.output.Uuid, Username = data.output.Name, Server = data.input.BaseUrl, ServerName = serverName, Name = data.input.UserName, Password = data.input.Password, AccessToken = data.output.AccessToken, ClientToken = data.output.ClientToken, Expires = 1743779140286L, Desc = "" }; ModProfile.profileList.Add(newProfile); ModProfile.selectedProfile = newProfile; ModProfile.isCreatingProfile = false; } ModProfile.SaveProfile(); ModProfile.ProfileLog("登录成功(Login, Authlib)"); return needRefresh; } catch (HttpResponseException ex) { if (_TryGetLastError(ex, out var message)) ModMain.MyMsgBox(message, Lang.Text("Minecraft.Launch.Login.Failed")); ex.Dispose(); return false; } catch (Exception ex) { ModProfile.ProfileLog($"第三方验证失败: {ex}"); if (ex.Message.StartsWithF("$")) throw; throw new Exception(Lang.Text("Minecraft.Launch.Login.Auth.LoginFailed", ex.Message), ex); } } private static bool _TryGetLastError(HttpResponseException ex,[NotNullWhen(true)] out string? message) { message = null; try { using var responseStream = ex.Response?.Content.ReadAsStream(); if (responseStream is null) return false; var result = JsonSerializer.Deserialize(responseStream, JsonCompat.SerializerOptions); if (result?.ErrorMessage is null) return false; message = result.ErrorMessage; return true; } catch (Exception) { // Suppress Exception } return false; } #endregion #region 离线验证 private static void McLoginLegacyStart(ModLoader.LoaderTask data) { var input = data.input; ModProfile.ProfileLog($"验证方式:离线({input.UserName}, {input.Uuid})"); data.Progress = 0.1d; { ref var withBlock = ref data.output; withBlock.Name = input.UserName; withBlock.Uuid = ModProfile.selectedProfile.Uuid; withBlock.Type = "Legacy"; } // 将结果扩展到所有项目中 data.output.AccessToken = data.output.Uuid; data.output.ClientToken = data.output.Uuid; } #endregion #endregion #region Java 处理 public static JavaEntry mcLaunchJavaSelected; private static void McLaunchJava(ModLoader.LoaderTask task) { var minVer = new Version(0, 0, 0, 0); var maxVer = new Version(999, 999, 999, 999); // MC 大版本检测 if ((!ModInstanceList.McMcInstanceSelected.Info.Valid && ModInstanceList.McMcInstanceSelected.releaseTime >= new DateTime(2024, 4, 2)) || (ModInstanceList.McMcInstanceSelected.Info.Valid && ModInstanceList.McMcInstanceSelected.Info.vanilla >= new Version(20, 0, 5))) { // 1.20.5+ (24w14a+):至少 Java 21 if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] MC 1.20.5+ (24w14a+) 要求至少 Java 21"); minVer = new Version(21, 0, 0, 0); } else if ((!ModInstanceList.McMcInstanceSelected.Info.Valid && ModInstanceList.McMcInstanceSelected.releaseTime >= new DateTime(2021, 11, 16)) || (ModInstanceList.McMcInstanceSelected.Info.Valid && ModInstanceList.McMcInstanceSelected.Info.vanilla.Major >= 18)) { // 1.18 pre2+:至少 Java 17 if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] MC 1.18 pre2+ 要求至少 Java 17"); minVer = new Version(17, 0, 0, 0); } else if ((!ModInstanceList.McMcInstanceSelected.Info.Valid && ModInstanceList.McMcInstanceSelected.releaseTime >= new DateTime(2021, 5, 11)) || (ModInstanceList.McMcInstanceSelected.Info.Valid && ModInstanceList.McMcInstanceSelected.Info.vanilla.Major >= 17)) { // 1.17+ (21w19a+):至少 Java 16 if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] MC 1.17+ (21w19a+) 要求至少 Java 16"); minVer = new Version(16, 0, 0, 0); } else if (ModInstanceList.McMcInstanceSelected.releaseTime.Year >= 2017) // Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了 { // 1.12+:至少 Java 8 if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] MC 1.12+ 要求至少 Java 8"); minVer = new Version(1, 8, 0, 0); } else if (ModInstanceList.McMcInstanceSelected.releaseTime <= new DateTime(2013, 5, 1) && ModInstanceList.McMcInstanceSelected.releaseTime.Year >= 2001) // 避免某些版本写个 1960 年 { // 1.5.2-:最高 Java 8 if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] MC 1.5.2- 要求最高 Java 12"); maxVer = new Version(1, 8, 999, 999); } // 原版 26+:获取 Mojang 要求的 Java 版本 string recommendedComponent = null; var recommendedCode = ModInstanceList.McMcInstanceSelected.JsonObject?["javaVersion"]?["majorVersion"]?.ToObject() ?? ModInstanceList.McMcInstanceSelected.JsonVersion?["java_version"]?.ToObject() ?? 0; if (recommendedCode >= 22) { McLaunchLog("Mojang 要求至少使用 Java " + recommendedCode); minVer = new Version(recommendedCode, 0, 0, 0); recommendedComponent = ModInstanceList.McMcInstanceSelected.JsonObject?["javaVersion"]?["component"]?.ToString() ?? ModInstanceList.McMcInstanceSelected.JsonVersion?["java_component"]?.ToString(); if (string.IsNullOrEmpty(recommendedComponent)) recommendedComponent = null; } // OptiFine 检测 if (ModInstanceList.McMcInstanceSelected.Info.HasOptiFine && ModInstanceList.McMcInstanceSelected.Info.Valid) // 不管非标准版本 { if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major < 7) { // <1.7:至多 Java 8 maxVer = new Version(1, 8, 999, 999); } else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major >= 8 && ModInstanceList.McMcInstanceSelected.Info.vanilla.Major < 12) { // 1.8 - 1.11:必须恰好 Java 8 minVer = new Version(1, 8, 0, 0); maxVer = new Version(1, 8, 999, 999); } else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major == 12) { // 1.12:最高 Java 8 maxVer = new Version(1, 8, 999, 999); } } // Forge 检测 if (ModInstanceList.McMcInstanceSelected.Info.HasForge) { if (ModInstanceList.McMcInstanceSelected.Info.vanilla >= new Version(6, 0, 1) && ModInstanceList.McMcInstanceSelected.Info.vanilla <= new Version(7, 0, 2)) { // 1.6.1 - 1.7.2:必须 Java 7 minVer = new Version(1, 7, 0, 0) > minVer ? new Version(1, 7, 0, 0) : minVer; maxVer = new Version(1, 7, 999, 999) < maxVer ? new Version(1, 7, 999, 999) : maxVer; } else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major <= 12 || !ModInstanceList.McMcInstanceSelected.Info.Valid) // 非标准版本 { // <=1.12:Java 8 maxVer = new Version(1, 8, 999, 999); } else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major <= 14) { // 1.13 - 1.14:Java 8 - 10 minVer = new Version(1, 8, 0, 0) > minVer ? new Version(1, 8, 0, 0) : minVer; maxVer = new Version(1, 10, 999, 999) < maxVer ? new Version(1, 10, 999, 999) : maxVer; } else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major == 15) { // 1.15:Java 8 - 15 minVer = new Version(1, 8, 0, 0) > minVer ? new Version(1, 8, 0, 0) : minVer; maxVer = new Version(1, 15, 999, 999) < maxVer ? new Version(1, 15, 999, 999) : maxVer; } else if (McVersionComparer.CompareVersionGe(ModInstanceList.McMcInstanceSelected.Info.Forge, "34.0.0") && McVersionComparer.CompareVersionGe("36.2.25", ModInstanceList.McMcInstanceSelected.Info.Forge)) { // 1.16,Forge 34.X ~ 36.2.25:最高 Java 8u321 maxVer = new Version(1, 8, 0, 320) < maxVer ? new Version(1, 8, 0, 321) : maxVer; } else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major >= 18 && ModInstanceList.McMcInstanceSelected.Info.vanilla.Major < 19 && ModInstanceList.McMcInstanceSelected.Info.HasOptiFine) // #305 { // 1.18:若安装了 OptiFine,最高 Java 18 maxVer = new Version(1, 18, 999, 999) < maxVer ? new Version(1, 18, 999, 999) : maxVer; } } // Cleanroom 检测 if (ModInstanceList.McMcInstanceSelected.Info.HasCleanroom) { if (!Version.TryParse(ModInstanceList.McMcInstanceSelected.Info.Cleanroom.Split('-')[0], out Version cleanroomVersion)) throw new FormatException("无法解析 Cleanroom 版本号:" + ModInstanceList.McMcInstanceSelected.Info.Cleanroom); if (cleanroomVersion < new Version(0, 5, 0, 0)) { if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] Cleanroom 版本低于 0.5,要求至少 Java 21"); minVer = new Version(21, 0, 0, 0) > minVer ? new Version(21, 0, 0, 0) : minVer; } else { if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] Cleanroom 版本高于 0.5,要求至少 Java 25"); minVer = new Version(25, 0, 0, 0) > minVer ? new Version(25, 0, 0, 0) : minVer; } } // Fabric 检测 if (ModInstanceList.McMcInstanceSelected.Info.HasFabric && ModInstanceList.McMcInstanceSelected.Info.Valid) // 不管非标准版本 { if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major >= 15 && ModInstanceList.McMcInstanceSelected.Info.vanilla.Major <= 16) // 1.15 - 1.16:Java 8+ minVer = new Version(1, 8, 0, 0) > minVer ? new Version(1, 8, 0, 0) : minVer; else if (ModInstanceList.McMcInstanceSelected.Info.vanilla.Major >= 18) // 1.18+:Java 17+ minVer = new Version(1, 17, 0, 0) > minVer ? new Version(1, 17, 0, 0) : minVer; } // LiteLoader 检测 if (ModInstanceList.McMcInstanceSelected.Info.HasLiteLoader && ModInstanceList.McMcInstanceSelected.Info.Valid) { // 最高 Java 8 if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] LiteLoader 要求最高 Java 8"); maxVer = new Version(8, 999, 999, 999) < maxVer ? new Version(8, 999, 999, 999) : maxVer; } // LabyMod 检测 if (ModInstanceList.McMcInstanceSelected.Info.HasLabyMod) { if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] LabyMod 要求至少 Java 21"); minVer = new Version(21, 0, 0, 0) > minVer ? new Version(21, 0, 0, 0) : minVer; maxVer = new Version(999, 999, 999, 999); } // JSON 中要求的版本 if (ModInstanceList.McMcInstanceSelected.JsonObject["javaVersion"] is not null) { var majorVersion = ModBase.Val(ModInstanceList.McMcInstanceSelected.JsonObject["javaVersion"]["majorVersion"]); if (ModBase.modeDebug) ModBase.Log("[Launch] [Debug] JSON 中参数要求至少 Java " + majorVersion); if (majorVersion <= 8d) minVer = new Version(1, (int)Math.Round(majorVersion), 0, 0) > minVer ? new Version(1, (int)Math.Round(majorVersion), 0, 0) : minVer; else minVer = new Version((int)Math.Round(majorVersion), 0, 0, 0) > minVer ? new Version((int)Math.Round(majorVersion), 0, 0, 0) : minVer; if (maxVer < minVer) maxVer = new Version(999, 999, 999, 999); } lock (ModJava.javaLock) { // 选择 Java McLaunchLog("Java 版本需求:最低 " + minVer + ",最高 " + maxVer); mcLaunchJavaSelected = ModJava.JavaSelect("$$", minVer, maxVer, ModInstanceList.McMcInstanceSelected); if (task.IsAborted) return; if (mcLaunchJavaSelected is not null) { McLaunchLog("选择的 Java:" + mcLaunchJavaSelected); return; } // 无合适的 Java if (task.IsAborted) return; // 中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java McLaunchLog("无合适的 Java,需要确认是否自动下载"); string javaCode; if (minVer >= new Version(1, 9)) { javaCode = minVer.Major.ToString(); } else if (maxVer < new Version(1, 8)) { if (ModInstanceList.McMcInstanceSelected.Info.HasForge) ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Java.NeedLegacyJavaFixerOrJava7"), Lang.Text("Minecraft.Launch.Java.NotFound.Title")); else ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Java.NeedJava7"), Lang.Text("Minecraft.Launch.Java.NotFound.Title")); throw new Exception("$$"); } else if (minVer > new Version(1, 8, 0, 140) && maxVer < new Version(1, 8, 0, 321)) { ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Java.NeedJava8U141ToU320"), Lang.Text("Minecraft.Launch.Java.NotFound.Title")); throw new Exception("$$"); } else if (minVer > new Version(1, 8, 0, 140)) { ModMain.MyMsgBox( Lang.Text("Minecraft.Launch.Java.NeedJava8U141OrLater"), Lang.Text("Minecraft.Launch.Java.NotFound.Title")); throw new Exception("$$"); } else { javaCode = 8.ToString(); } if (!ModJava.JavaDownloadConfirm($"Java {javaCode}")) throw new Exception("$$"); // 开始自动下载 var javaLoader = ModJava.GetJavaDownloadLoader(); try { javaLoader.Start(recommendedComponent ?? javaCode, true); // 在 Java 22+ 时优先使用 Mojang 提供的 Component 字段 while (javaLoader.State == ModBase.LoadState.Loading && !task.IsAborted) { task.Progress = javaLoader.Progress; Thread.Sleep(10); } } finally { javaLoader.Abort(); // 确保取消时中止 Java 下载 } // 检查下载结果 mcLaunchJavaSelected = ModJava.JavaSelect("$$", minVer, maxVer, ModInstanceList.McMcInstanceSelected); if (task.IsAborted) return; if (mcLaunchJavaSelected is not null) { McLaunchLog("选择的 Java:" + mcLaunchJavaSelected); } else { HintService.Hint(Lang.Text("Minecraft.Launch.Error.NoJava"), HintType.Error); throw new Exception("$$"); } } } #endregion #region 启动参数 internal static void SecretLaunchJvmArgs(ref List dataList) { var dataJvmCustom = Config.Instance.JvmArgs[ModInstanceList.McMcInstanceSelected?.PathInstance]; dataList.Insert(0, string.IsNullOrEmpty(dataJvmCustom) ? Config.Launch.JvmArgs : dataJvmCustom); // 可变 JVM 参数 switch (Config.Launch.PreferredIpStack) { case JvmPreferredIpStack.PreferV4: { dataList.Add("-Djava.net.preferIPv4Stack=true"); dataList.Add("-Djava.net.preferIPv4Addresses=true"); break; } case JvmPreferredIpStack.PreferV6: { dataList.Add("-Djava.net.preferIPv6Stack=true"); dataList.Add("-Djava.net.preferIPv6Addresses=true"); break; } } double availableGb = KernelInterop.GetAvailablePhysicalMemoryBytes() / 1073741824.0; ModLaunch.McLaunchLog($"当前剩余内存:{availableGb.ToString("N1", CultureInfo.InvariantCulture)}G"); double totalRamMb = PageInstanceSetup.GetRam(ModInstanceList.McMcInstanceSelected) * 1024d; var maxHeapArg = Math.Floor(totalRamMb).ToString(CultureInfo.InvariantCulture); dataList.Add("-Xmn" + Math.Floor(totalRamMb * 0.15).ToString(CultureInfo.InvariantCulture) + "m"); dataList.Add("-Xmx" + maxHeapArg + "m"); // #3282: 固定堆大小时追加 -Xms 使其等于 -Xmx(复用同一数值以保持一致),隐式禁用内存归还降低延迟抖动、利于 ZGC。 // 若 dataList 中已存在 -Xms(例如用户自定义参数已设)则跳过,避免重复/冲突。 if (Config.Launch.LockMemory && !dataList.Any(d => d.Contains("-Xms", StringComparison.OrdinalIgnoreCase))) dataList.Add("-Xms" + maxHeapArg + "m"); if (!dataList.Any(d => d.Contains("-Dlog4j2.formatMsgNoLookups=true"))) dataList.Add("-Dlog4j2.formatMsgNoLookups=true"); } public class LaunchArgument { private readonly List _features = new(); public LaunchArgument(McInstance minecraft) { var curArgu = string.Empty; if (minecraft.IsOldJson) _features = minecraft.JsonObject["minecraftArguments"].ToString().Split(' ').ToList(); else foreach (var item in minecraft.JsonObject["arguments"]["game"].AsArray()) if (item.GetValueKind() == JsonValueKind.String) _features.Add(item.ToString()); else if (item.GetValueKind() == JsonValueKind.Object) { var valueNode = item["value"]; if (valueNode.GetValueKind() == JsonValueKind.Array) _features.AddRange(valueNode.AsArray().Select(x => x.ToString())); else if (valueNode.GetValueKind() == JsonValueKind.String) _features.Add(valueNode.ToString()); } } public object HasArguments(string key) { return _features.Contains(key); } } private static string mcLaunchArgument; /// /// 释放 Java Wrapper 并返回完整文件路径。 /// public static string ExtractJavaWrapper() { var wrapperPath = Path.Combine(ModBase.pathPure, "JavaWrapper.jar"); ModBase.Log("[Java] 选定的 Java Wrapper 路径:" + wrapperPath); lock (extractJavaWrapperLock) // 避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突 { try { WriteJavaWrapper(wrapperPath); } catch (Exception ex) { if (File.Exists(wrapperPath)) { // 因为未知原因 Java Wrapper 可能变为只读文件(#4243) ModBase.Log(ex, "Java Wrapper 文件释放失败,但文件已存在,将在删除后尝试重新生成", ModBase.LogLevel.Developer); try { File.Delete(wrapperPath); WriteJavaWrapper(wrapperPath); } catch (Exception ex2) { ModBase.Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", ModBase.LogLevel.Developer); wrapperPath = Path.Combine(ModBase.pathPure, "JavaWrapper2.jar"); try { WriteJavaWrapper(wrapperPath); } catch (Exception ex3) { throw new FileNotFoundException("释放 Java Wrapper 最终尝试失败", ex3); } } } else { throw new FileNotFoundException("释放 Java Wrapper 失败", ex); } } } return wrapperPath; } private static readonly object extractJavaWrapperLock = new(); private static void WriteJavaWrapper(string path) { ModBase.WriteFile(path, ModBase.GetResourceStream("Resources/java-wrapper.jar")); } /// /// 释放 linkd 并返回完整文件路径。 /// public static string ExtractLinkD() { var linkDPath = Path.Combine(ModBase.pathPure, "linkd.exe"); lock (extractLinkDLock) // 避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突 { try { WriteLinkD(linkDPath); } catch (Exception ex) { if (File.Exists(linkDPath)) { ModBase.Log(ex, "linkd 文件释放失败,但文件已存在,将在删除后尝试重新生成", ModBase.LogLevel.Developer); try { File.Delete(linkDPath); WriteLinkD(linkDPath); } catch (Exception ex2) { throw new FileNotFoundException("释放 linkd 失败", ex2); } } else { throw new FileNotFoundException("释放 linkd 失败", ex); } } } return linkDPath; } private static readonly object extractLinkDLock = new(); private static void WriteLinkD(string path) { ModBase.WriteFile(path, ModBase.GetResourceStream("Resources/linkd.exe")); } /// /// 判断是否使用 LegacyFix。 /// private static bool McLaunchNeedsLegacyFix(McInstance mc) { if (Config.Launch.DisableLF || Config.Instance.DisableLF[mc.PathInstance]) { ModBase.Log("[Launch] LegacyFix 已被禁用"); return false; } if (mc.releaseTime < new DateTime(2013, 6, 25) && mc.releaseTime.Year > 2000) { return true; } return false; } /// /// 获取实例所依赖的 LWJGL 版本 /// private static string McLaunchGetLwjglVersion(McInstance mc) { foreach (ModLibrary.McLibToken library in ModLibrary.McLibListGet(mc, false)) { if (string.IsNullOrWhiteSpace(library.OriginalName)) continue; string[] parts = library.OriginalName.Split(':'); if (parts.Length >= 3 && parts[0].Equals("org.lwjgl", StringComparison.OrdinalIgnoreCase) && parts[1].Equals("lwjgl", StringComparison.OrdinalIgnoreCase)) { return parts[2]; } } return null; } /// /// 判断是否启用了针对 Minecraft 26.1 的性能问题补丁 /// private static bool McLaunchUsesLwjglUnsafeAgent(McInstance mc) { if (McLaunchGetLwjglVersion(mc) == "3.4.1") { bool globalDisabled = Config.Launch.DisableLwjglUnsafeAgent; bool instanceDisabled = Config.Instance.DisableLwjglUnsafeAgent[mc.PathInstance]; return !globalDisabled && !instanceDisabled; } else { return false; } } // 主方法,合并 Jvm、Game、Replace 三部分的参数数据 private static void McLaunchArgumentMain(ModLoader.LoaderTask> loader) { McLaunchLog("开始获取 Minecraft 启动参数"); // 获取基准字符串与参数信息 string arguments; if (ModInstanceList.McMcInstanceSelected.JsonObject["arguments"] is not null && ModInstanceList.McMcInstanceSelected.JsonObject["arguments"]["jvm"] is not null) { McLaunchLog("获取新版 JVM 参数"); arguments = McLaunchArgumentsJvmNew(ModInstanceList.McMcInstanceSelected); McLaunchLog("新版 JVM 参数获取成功:"); McLaunchLog(arguments); } else { McLaunchLog("获取旧版 JVM 参数"); arguments = McLaunchArgumentsJvmOld(ModInstanceList.McMcInstanceSelected); McLaunchLog("旧版 JVM 参数获取成功:"); McLaunchLog(arguments); } if (!string.IsNullOrEmpty( (string)ModInstanceList.McMcInstanceSelected.JsonObject["minecraftArguments"])) // 有的实例 JSON 中是空字符串 { McLaunchLog("获取旧版 Game 参数"); arguments += " " + McLaunchArgumentsGameOld(ModInstanceList.McMcInstanceSelected); McLaunchLog("旧版 Game 参数获取成功"); } if (ModInstanceList.McMcInstanceSelected.JsonObject["arguments"] is not null && ModInstanceList.McMcInstanceSelected.JsonObject["arguments"]["game"] is not null) { McLaunchLog("获取新版 Game 参数"); arguments += " " + McLaunchArgumentsGameNew(ModInstanceList.McMcInstanceSelected); McLaunchLog("新版 Game 参数获取成功"); } // 编码参数(#4700、#5892、#5909) if (mcLaunchJavaSelected.Installation.MajorVersion > 8) { if (!arguments.Contains("-Dstdout.encoding=")) arguments = "-Dstdout.encoding=UTF-8 " + arguments; if (!arguments.Contains("-Dstderr.encoding=")) arguments = "-Dstderr.encoding=UTF-8 " + arguments; } if (mcLaunchJavaSelected.Installation.MajorVersion >= 18) if (!arguments.Contains("-Dfile.encoding=")) arguments = "-Dfile.encoding=COMPAT " + arguments; // MJSB arguments = arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=\"Windows 10\""); // 全屏 if (Config.Launch.GameWindowMode == 0) arguments += " --fullscreen"; // 由 Option 传入的额外参数 foreach (var arg in currentLaunchOptions.ExtraArgs) arguments += " " + arg.Trim(); // 自定义参数 var argumentGame = Config.Instance.GameArgs[ModInstanceList.McMcInstanceSelected?.PathInstance]; arguments = arguments + " " + (string.IsNullOrEmpty(argumentGame) ? Config.Launch.GameArgs : argumentGame); // 替换参数 var replaceArguments = McLaunchArgumentsReplace(ModInstanceList.McMcInstanceSelected, ref loader); if (string.IsNullOrWhiteSpace(replaceArguments["${version_type}"])) { // 若自定义信息为空,则去掉该部分 arguments = arguments.Replace(" --versionType ${version_type}", ""); replaceArguments["${version_type}"] = "\"\""; } var finalArguments = ""; foreach (var argumentRaw in arguments.Split(" ")) { var argument = argumentRaw; foreach (var entry in replaceArguments) argument = argument.Replace(entry.Key, entry.Value); if ((argument.Contains(" ") || argument.Contains(@":\")) && !argument.EndsWithF("\"")) argument = $"\"{argument}\""; finalArguments += argument + " "; } finalArguments = finalArguments.TrimEnd(); // 进存档 var worldName = currentLaunchOptions.WorldName; if (worldName is not null) finalArguments += $" --quickPlaySingleplayer \"{worldName}\""; // 进服 var server = string.IsNullOrEmpty(currentLaunchOptions.ServerIp) ? Config.Instance.ServerToEnter[ModInstanceList.McMcInstanceSelected?.PathInstance] : currentLaunchOptions.ServerIp; if (string.IsNullOrWhiteSpace(worldName) && !string.IsNullOrWhiteSpace(server)) { if (ModInstanceList.McMcInstanceSelected.releaseTime > new DateTime(2023, 4, 4)) { // QuickPlay finalArguments += $" --quickPlayMultiplayer \"{server}\""; } else { // 老版本 if (server.Contains(":")) // 包含端口号 finalArguments += " --server " + server.Split(":")[0] + " --port " + server.Split(":")[1]; else // 不包含端口号 finalArguments += " --server " + server + " --port 25565"; if (ModInstanceList.McMcInstanceSelected.Info.HasOptiFine) HintService.Hint(Lang.Text("Minecraft.Launch.Error.OptiFineAutoJoinWarning"), HintType.Error); } } // 输出 McLaunchLog("Minecraft 启动参数:"); McLaunchLog(finalArguments); mcLaunchArgument = finalArguments; } // Jvm 部分(第一段) private static string McLaunchArgumentsJvmOld(McInstance instance) { // 存储以空格为间隔的启动参数列表 var dataList = new List(); // 输出固定参数 dataList.Add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"); var argumentJvm = Config.Instance.JvmArgs[ModInstanceList.McMcInstanceSelected?.PathInstance]; if (string.IsNullOrEmpty(argumentJvm)) argumentJvm = Config.Launch.JvmArgs; if (!argumentJvm.Contains("-Dlog4j2.formatMsgNoLookups=true")) argumentJvm += " -Dlog4j2.formatMsgNoLookups=true"; argumentJvm = argumentJvm.Replace(" -XX:MaxDirectMemorySize=256M", ""); // #3511 的清理 dataList.Insert(0, argumentJvm); // 可变 JVM 参数 dataList.Add("-Xmn" + Math.Floor(PageInstanceSetup.GetRam(ModInstanceList.McMcInstanceSelected, !mcLaunchJavaSelected.Installation.Is64Bit) * 1024d * 0.15d) + "m"); var maxHeapArg = Math.Floor(PageInstanceSetup.GetRam(ModInstanceList.McMcInstanceSelected, !mcLaunchJavaSelected.Installation.Is64Bit) * 1024d); dataList.Add("-Xmx" + maxHeapArg + "m"); // #3282: 固定堆大小时追加 -Xms 使其等于 -Xmx(复用同一数值以保持一致),隐式禁用内存归还降低延迟抖动、利于 ZGC。 // 若 dataList 中已存在 -Xms(例如用户自定义参数已设)则跳过,避免重复/冲突。 if (Config.Launch.LockMemory && !dataList.Any(d => d.Contains("-Xms", StringComparison.OrdinalIgnoreCase))) dataList.Add("-Xms" + maxHeapArg + "m"); dataList.Add("\"-Djava.library.path=" + GetNativesFolder() + "\""); dataList.Add("-cp ${classpath}"); // 把支持库添加进启动参数表 // Authlib-Injector if (mcLoginLoader.output.Type == "Auth") { if (mcLaunchJavaSelected.Installation.MajorVersion >= 6) dataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT"); // 信任系统根证书(Meloong-Git/#5252) var server = mcLoginAuthLoader.input.BaseUrl.Replace("/authserver", ""); try { var response = Requester.FetchString(server); dataList.Insert(0, "-javaagent:\"" + Path.Combine(ModBase.pathPure, "authlib-injector.jar") + "\"=" + server + " -Dauthlibinjector.side=client" + " -Dauthlibinjector.yggdrasil.prefetched=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(response))); } catch (WebException ex) { throw new Exception( Lang.Text("Minecraft.Launch.Error.CannotConnectAuthServerWithDetail", server ?? null) + ex.InnerException, ex); } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Launch.Error.CannotConnectAuthServer", server ?? null), ex); } } if (Config.Instance.UseDebugLof4j2Config[instance.PathIndie]) { if (ModInstanceList.McMcInstanceSelected.releaseTime.Year >= 2017) dataList.Insert(0, "-Dlog4j.configurationFile=\"" + LaunchEnvUtils.ExtractDebugLog4j2Config() + "\""); else dataList.Insert(0, "-Dlog4j.configurationFile=\"" + LaunchEnvUtils.ExtractLegacyDebugLog4j2Config() + "\""); } // 渲染器 var renderer = 0; var instanceRenderer = Config.Instance.Renderer[ModInstanceList.McMcInstanceSelected?.PathInstance]; if (instanceRenderer != 0) renderer = instanceRenderer - 1; else renderer = Config.Launch.Renderer; var mesaLoaderWindowsTargetFile = Path.Combine(ModBase.pathPure, "mesa-loader-windows", mesaLoaderWindowsVersion, "Loader.jar"); if (renderer != 0) dataList.Insert(0, "-javaagent:\"" + mesaLoaderWindowsTargetFile + "\"=" + (renderer == 1 ? "llvmpipe" : renderer == 2 ? "d3d12" : "zink")); // 设置代理 if (Config.Instance.UseProxy[instance.PathIndie] && Config.Network.HttpProxy.Type.Equals(2) && !string.IsNullOrWhiteSpace(Config.Network.HttpProxy.CustomAddress)) try { var proxyAddress = new Uri(Config.Network.HttpProxy.CustomAddress); dataList.Add( $"-D{(proxyAddress.Scheme.StartsWithF("https:") ? "https" : "http")}.proxyHost={proxyAddress.AbsoluteUri}"); dataList.Add( $"-D{(proxyAddress.Scheme.StartsWithF("https:") ? "https" : "http")}.proxyPort={proxyAddress.Port}"); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Launch.Error.Proxy"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Minecraft.Launch.Error.Proxy")); } // 添加 LegacyFix 相关参数 if (McLaunchNeedsLegacyFix(instance)) { var legacyFixPath = Path.Combine(ModBase.pathPure, "legacyfix.jar"); dataList.Add("-javaagent:\"" + legacyFixPath + "\""); // Beta 1.6 以前版本需要添加的参数 if (instance.releaseTime < new DateTime(2011, 5, 25)) { dataList.Add("-Djava.util.Arrays.useLegacyMergeSort=true"); } } // 添加 Java Wrapper 作为主 Jar if (ModBase.IsUtf8CodePage() && !Config.Launch.DisableJlw && !Config.Instance.DisableJlw[ModInstanceList.McMcInstanceSelected?.PathInstance]) { if (mcLaunchJavaSelected.Installation.MajorVersion >= 9) dataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED"); dataList.Add("-Doolloo.jlw.tmpdir=\"" + ModBase.pathPure.TrimEnd('\\') + "\""); dataList.Add("-jar \"" + ExtractJavaWrapper() + "\""); } // 添加 MainClass if (instance.JsonObject["mainClass"] is null) throw new Exception(Lang.Text("Minecraft.Launch.Error.MissingMainClass")); dataList.Add((string)instance.JsonObject["mainClass"]); return dataList.Join(" "); } private static string McLaunchArgumentsJvmNew(McInstance instance) { var dataList = new List(); // 获取 Json 中的 DataList var currentInstance = instance; while (true) { if (currentInstance.JsonObject["arguments"] is not null && currentInstance.JsonObject["arguments"]["jvm"] is not null) foreach (var subJson in currentInstance.JsonObject["arguments"]["jvm"].AsArray()) if (subJson.GetValueKind() == JsonValueKind.String) { // 字符串类型 dataList.Add(subJson.ToString()); } // 非字符串类型 else if (ModLibrary.McJsonRuleCheck(subJson["rules"])) { // 满足准则 if (subJson["value"].GetValueKind() == JsonValueKind.String) dataList.Add(subJson["value"].ToString()); else foreach (var value in subJson["value"].AsArray()) dataList.Add(value.ToString()); } if (string.IsNullOrEmpty(currentInstance.InheritInstanceName)) break; currentInstance = new McInstance(currentInstance.InheritInstanceName); } // 内存、Log4j 防御参数等 SecretLaunchJvmArgs(ref dataList); // Authlib-Injector if (mcLoginLoader.output.Type == "Auth") { if (mcLaunchJavaSelected.Installation.MajorVersion >= 6) dataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT"); // 信任系统根证书(Meloong-Git/#5252) var server = mcLoginAuthLoader.input.BaseUrl.Replace("/authserver", ""); try { var response = ModNet.NetGetCodeByRequestRetry(server, Encoding.UTF8)?.ToString(); dataList.Insert(0, "-javaagent:\"" + Path.Combine(ModBase.pathPure, "authlib-injector.jar") + "\"=" + server + " -Dauthlibinjector.side=client" + " -Dauthlibinjector.yggdrasil.prefetched=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(response))); } catch (Exception ex) { throw new Exception(Lang.Text("Minecraft.Launch.Error.CannotConnectAuthServer", server ?? null), ex); } } // LWJGL Unsafe Agent if (McLaunchUsesLwjglUnsafeAgent(ModInstanceList.McMcInstanceSelected)) { ModBase.Log($"获取到的 LWJGL 版本:{McLaunchGetLwjglVersion(ModInstanceList.McMcInstanceSelected)}"); dataList.Insert(0, $"-javaagent:\"{ModBase.pathPure}lwjgl-unsafe-agent.jar\""); } if (Config.Instance.UseDebugLof4j2Config[instance.PathIndie]) { if (ModInstanceList.McMcInstanceSelected.releaseTime.Year >= 2017) dataList.Insert(0, "-Dlog4j.configurationFile=\"" + LaunchEnvUtils.ExtractDebugLog4j2Config() + "\""); else dataList.Insert(0, "-Dlog4j.configurationFile=\"" + LaunchEnvUtils.ExtractLegacyDebugLog4j2Config() + "\""); } // 渲染器 var renderer = 0; var instanceRenderer = Config.Instance.Renderer[ModInstanceList.McMcInstanceSelected?.PathInstance]; if (instanceRenderer != 0) renderer = instanceRenderer - 1; else renderer = Config.Launch.Renderer; var mesaLoaderWindowsTargetFile = Path.Combine(ModBase.pathPure, "mesa-loader-windows", mesaLoaderWindowsVersion, "Loader.jar"); if (renderer != 0) dataList.Insert(0, "-javaagent:\"" + mesaLoaderWindowsTargetFile + "\"=" + (renderer == 1 ? "llvmpipe" : renderer == 2 ? "d3d12" : "zink")); // 设置代理 if (Config.Instance.UseProxy[instance.PathIndie] && Config.Network.HttpProxy.Type.Equals(2) && !string.IsNullOrWhiteSpace(Config.Network.HttpProxy.CustomAddress)) try { var proxyAddress = new Uri(Config.Network.HttpProxy.CustomAddress); dataList.Add( $"-D{(proxyAddress.Scheme.StartsWithF("https:") ? "https" : "http")}.proxyHost={proxyAddress.AbsoluteUri}"); dataList.Add( $"-D{(proxyAddress.Scheme.StartsWithF("https:") ? "https" : "http")}.proxyPort={proxyAddress.Port}"); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Launch.Error.Proxy"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Minecraft.Launch.Error.Proxy")); } // 添加 Java Wrapper 作为主 Jar if (ModBase.IsUtf8CodePage() && !Config.Launch.DisableJlw && !Config.Instance.DisableJlw[ModInstanceList.McMcInstanceSelected?.PathInstance]) { if (mcLaunchJavaSelected.Installation.MajorVersion >= 9) dataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED"); dataList.Add("-Doolloo.jlw.tmpdir=\"" + ModBase.pathPure.TrimEnd('\\') + "\""); dataList.Add("-jar \"" + ExtractJavaWrapper() + "\""); } // 将 "-XXX" 与后面 "XXX" 合并到一起 // 如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重 var deDuplicateDataList = new List(); for (int i = 0, loopTo = dataList.Count - 1; i <= loopTo; i++) { var currentEntry = dataList[i]; if (dataList[i].StartsWithF("-")) while (i < dataList.Count - 1) { if (dataList[i + 1].StartsWithF("-")) break; i += 1; currentEntry += " " + dataList[i]; } deDuplicateDataList.Add(currentEntry.Trim().Replace("McEmu= ", "McEmu=")); } // #3511 的清理 deDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M"); // 去重 var result = deDuplicateDataList.Distinct().ToList().Join(" "); // 添加 MainClass if (instance.JsonObject["mainClass"] is null) throw new Exception(Lang.Text("Minecraft.Launch.Error.MissingMainClass")); result += " " + instance.JsonObject["mainClass"]; return result; } // Game 部分(第二段) private static string McLaunchArgumentsGameOld(McInstance version) { var dataList = new List(); // 本地化 Minecraft 启动信息 var basicString = version.JsonObject["minecraftArguments"].ToString(); if (!basicString.Contains("--height")) basicString += " --height ${resolution_height} --width ${resolution_width}"; dataList.Add(basicString); var result = dataList.Join(" "); // 特别改变 OptiFineTweaker if ((version.Info.HasForge || version.Info.HasLiteLoader) && version.Info.HasOptiFine) { // 把 OptiFineForgeTweaker 放在最后,不然会导致崩溃! if (result.Contains("--tweakClass optifine.OptiFineForgeTweaker")) { ModBase.Log("[Launch] 发现正确的 OptiFineForge TweakClass,目前参数:" + result); result = result.Replace(" --tweakClass optifine.OptiFineForgeTweaker", "") .Replace("--tweakClass optifine.OptiFineForgeTweaker ", "") + " --tweakClass optifine.OptiFineForgeTweaker"; } if (result.Contains("--tweakClass optifine.OptiFineTweaker")) { ModBase.Log("[Launch] 发现错误的 OptiFineForge TweakClass,目前参数:" + result); result = result.Replace(" --tweakClass optifine.OptiFineTweaker", "") .Replace("--tweakClass optifine.OptiFineTweaker ", "") + " --tweakClass optifine.OptiFineForgeTweaker"; try { ModBase.WriteFile(Path.Combine(version.PathInstance, version.Name + ".json"), ModBase.ReadFile(Path.Combine(version.PathInstance, version.Name + ".json")) .Replace("optifine.OptiFineTweaker", "optifine.OptiFineForgeTweaker")); } catch (Exception ex) { ModBase.Log(ex, "替换 OptiFineForge TweakClass 失败"); } } } return result; } private static string McLaunchArgumentsGameNew(McInstance instance) { string mcLaunchArgumentsGameNewRet = default; var dataList = new List(); // 获取 Json 中的 DataList var currentInstance = instance; while (true) { if (currentInstance.JsonObject["arguments"] is not null && currentInstance.JsonObject["arguments"]["game"] is not null) foreach (var subJson in currentInstance.JsonObject["arguments"]["game"].AsArray()) if (subJson.GetValueKind() == JsonValueKind.String) { // 字符串类型 dataList.Add(subJson.ToString()); } // 非字符串类型 else if (ModLibrary.McJsonRuleCheck(subJson["rules"])) { // 满足准则 if (subJson["value"].GetValueKind() == JsonValueKind.String) dataList.Add(subJson["value"].ToString()); else foreach (var value in subJson["value"].AsArray()) dataList.Add(value.ToString()); } if (string.IsNullOrEmpty(currentInstance.InheritInstanceName)) break; currentInstance = new McInstance(currentInstance.InheritInstanceName); } // 将 "-XXX" 与后面 "XXX" 合并到一起 // 如果不进行合并 Impact 会启动无效,它有两个 --tweakclass var deDuplicateDataList = new List(); for (int i = 0, loopTo = dataList.Count - 1; i <= loopTo; i++) { var currentEntry = dataList[i]; if (dataList[i].StartsWithF("-")) while (i < dataList.Count - 1) { if (dataList[i + 1].StartsWithF("-")) break; i += 1; currentEntry += " " + dataList[i]; } deDuplicateDataList.Add(currentEntry); } // 去重 mcLaunchArgumentsGameNewRet = deDuplicateDataList.Distinct().ToList().Join(" "); // 特别改变 OptiFineTweaker if ((instance.Info.HasForge || instance.Info.HasLiteLoader) && instance.Info.HasOptiFine) { // 把 OptiFineForgeTweaker 放在最后,不然会导致崩溃! if (mcLaunchArgumentsGameNewRet.Contains("--tweakClass optifine.OptiFineForgeTweaker")) { ModBase.Log("[Launch] 发现正确的 OptiFineForge TweakClass,目前参数:" + mcLaunchArgumentsGameNewRet); mcLaunchArgumentsGameNewRet = mcLaunchArgumentsGameNewRet.Replace(" --tweakClass optifine.OptiFineForgeTweaker", "") .Replace("--tweakClass optifine.OptiFineForgeTweaker ", "") + " --tweakClass optifine.OptiFineForgeTweaker"; } if (mcLaunchArgumentsGameNewRet.Contains("--tweakClass optifine.OptiFineTweaker")) { ModBase.Log("[Launch] 发现错误的 OptiFineForge TweakClass,目前参数:" + mcLaunchArgumentsGameNewRet); mcLaunchArgumentsGameNewRet = mcLaunchArgumentsGameNewRet.Replace(" --tweakClass optifine.OptiFineTweaker", "") .Replace("--tweakClass optifine.OptiFineTweaker ", "") + " --tweakClass optifine.OptiFineForgeTweaker"; try { ModBase.WriteFile(Path.Combine(instance.PathInstance, instance.Name + ".json"), ModBase.ReadFile(Path.Combine(instance.PathInstance, instance.Name + ".json")) .Replace("optifine.OptiFineTweaker", "optifine.OptiFineForgeTweaker")); } catch (Exception ex) { ModBase.Log(ex, "替换 OptiFineForge TweakClass 失败"); } } } return mcLaunchArgumentsGameNewRet; } // 替换 Arguments private static Dictionary McLaunchArgumentsReplace(McInstance instance, ref ModLoader.LoaderTask> loader) { var gameArguments = new Dictionary(); // 基础参数 gameArguments.Add("${classpath_separator}", ";"); gameArguments.Add("${natives_directory}", ModBase.ShortenPath(GetNativesFolder())); gameArguments.Add("${library_directory}", ModBase.ShortenPath(ModFolder.mcFolderSelected + "libraries")); gameArguments.Add("${libraries_directory}", ModBase.ShortenPath(ModFolder.mcFolderSelected + "libraries")); gameArguments.Add("${launcher_name}", "PCLCE"); gameArguments.Add("${launcher_version}", ModBase.versionCode.ToString()); gameArguments.Add("${version_name}", instance.Name); var argumentInfo = Config.Instance.TypeInfo[ModInstanceList.McMcInstanceSelected?.PathInstance]; gameArguments.Add("${version_type}", string.IsNullOrEmpty(argumentInfo) ? Config.Launch.TypeInfo : argumentInfo); gameArguments.Add("${game_directory}", ModBase.ShortenPath(ModInstanceList.McMcInstanceSelected.PathIndie[..^1])); gameArguments.Add("${assets_root}", ModBase.ShortenPath(ModFolder.mcFolderSelected + "assets")); gameArguments.Add("${user_properties}", "{}"); gameArguments.Add("${auth_player_name}", mcLoginLoader.output.Name); gameArguments.Add("${auth_uuid}", mcLoginLoader.output.Uuid); gameArguments.Add("${auth_access_token}", mcLoginLoader.output.AccessToken); gameArguments.Add("${access_token}", mcLoginLoader.output.AccessToken); gameArguments.Add("${auth_session}", mcLoginLoader.output.AccessToken); gameArguments.Add("${user_type}", "msa"); // #1221 // 窗口尺寸参数 Size gameSize; switch (Config.Launch.GameWindowMode) { case GameWindowSizeMode.Launcher: // 与启动器尺寸一致 { Size result; ModBase.RunInUiWait(() => result = new Size(ModBase.GetPixelSize(ModMain.frmMain.PanForm.ActualWidth), ModBase.GetPixelSize(ModMain.frmMain.PanForm.ActualHeight))); gameSize = result; gameSize.Height -= 29.5d * ModBase.dpi / 96d; // 标题栏高度 break; } case GameWindowSizeMode.Custom: // 自定义 { gameSize = new Size(Math.Max(100, (double)Config.Launch.GameWindowWidth), Math.Max(100, (double)Config.Launch.GameWindowHeight)); break; } default: { gameSize = new Size(854d, 480d); break; } } if (ModInstanceList.McMcInstanceSelected.Info.Drop <= 120 && mcLaunchJavaSelected.Installation.MajorVersion <= 8 && mcLaunchJavaSelected.Installation.Version.Revision >= 200 && mcLaunchJavaSelected.Installation.Version.Revision <= 321 && !ModInstanceList.McMcInstanceSelected.Info.HasOptiFine && !ModInstanceList.McMcInstanceSelected.Info.HasForge) { // 修复 #3463:1.12.2-,JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍 McLaunchLog($"已应用窗口大小过大修复({mcLaunchJavaSelected.Installation.Version.Revision})"); gameSize.Width /= ModBase.dpi / 96d; gameSize.Height /= ModBase.dpi / 96d; } gameArguments.Add("${resolution_width}", Math.Round(gameSize.Width).ToString(CultureInfo.InvariantCulture)); gameArguments.Add("${resolution_height}", Math.Round(gameSize.Height).ToString(CultureInfo.InvariantCulture)); // Assets 相关参数 gameArguments.Add("${game_assets}", ModBase.ShortenPath(ModFolder.mcFolderSelected + @"assets\virtual\legacy")); // 1.5.2 的 pre-1.6 资源索引应与 legacy 合并 gameArguments.Add("${assets_index_name}", ModAssets.McAssetsGetIndexName(instance)); // 支持库参数 var libList = ModLibrary.McLibListGet(instance, true); loader.output = libList; var cpStrings = new List(); string optiFineCp = null; // LegacyFix 释放 if (McLaunchNeedsLegacyFix(instance)) { var legacyFixPath = Path.Combine(ModBase.pathPure, "legacyfix.jar"); try { ModBase.WriteFile(legacyFixPath, ModBase.GetResourceStream("Resources/legacyfix.jar")); } catch (Exception ex) { ModBase.Log(ex, "LegacyFix 释放失败"); } } // LWJGL Unsafe Agent 释放 if (McLaunchUsesLwjglUnsafeAgent(instance)) { string agentPath = Path.Combine(ModBase.pathPure, "lwjgl-unsafe-agent.jar"); try { ModBase.WriteFile(agentPath, ModBase.GetResourceStream("Resources/lwjgl-unsafe-agent.jar")); cpStrings.Add(agentPath); } catch (Exception ex) { ModBase.Log(ex, "LWJGL Unsafe Agent 释放失败"); } } foreach (var library in libList) { if (library.IsNatives) continue; if (ModInstanceList.McMcInstanceSelected.Info.HasCleanroom && library.OriginalName is not null && (library.OriginalName.Contains("org.lwjgl.lwjgl:lwjgl:2.9.4") || library.OriginalName.Contains("net.java.dev.jna:platform:3.4.0") || library.OriginalName.Contains("com.ibm.icu:icu4j-core-mojang:51.2"))) continue; if (library.Name is not null && library.Name == "optifine:OptiFine") optiFineCp = library.LocalPath; else cpStrings.Add(library.LocalPath); } foreach (var library in Config.Instance.ClasspathHead[instance.PathInstance].Split(";")) // 自定义 Classpath 头部 { if (string.IsNullOrWhiteSpace(library)) continue; cpStrings.Insert(0, library); } if (optiFineCp is not null) cpStrings.Insert(cpStrings.Count - 2, optiFineCp); // OptiFine 的总是需要放到倒数第二位 gameArguments.Add("${classpath}", cpStrings.Select(c => ModBase.ShortenPath(c)).Join(";")); return gameArguments; } #endregion #region 解压 Natives private static void McLaunchNatives(ModLoader.LoaderTask, int> loader) { // 创建文件夹 var target = GetNativesFolder() + @"\"; Directory.CreateDirectory(target); // 解压文件 McLaunchLog("正在解压 Natives 文件"); var existFiles = new List(); foreach (var native in loader.input) { if (!native.IsNatives) continue; ZipArchive zip; try { zip = new ZipArchive(new FileStream(native.LocalPath, FileMode.Open)); } catch (InvalidDataException ex) { ModBase.Log(ex, "打开 Natives 文件失败(" + native.LocalPath + ")"); File.Delete(native.LocalPath); throw new Exception(Lang.Text("Minecraft.Launch.Error.NativesCorrupted", native.LocalPath)); } foreach (var entry in zip.Entries) { var fileName = entry.FullName; if (fileName.EndsWithF(".dll", true)) { // 实际解压文件的步骤 var filePath = target + fileName; existFiles.Add(filePath); var originalFile = new FileInfo(filePath); if (originalFile.Exists) { if (originalFile.Length == entry.Length) { if (ModBase.modeDebug) McLaunchLog("无需解压:" + filePath); continue; } // 删除原文件 try { File.Delete(filePath); } catch (UnauthorizedAccessException ex) { McLaunchLog("删除原 dll 访问被拒绝,这通常代表有一个 MC 正在运行,跳过解压:" + filePath); McLaunchLog("实际的错误信息:" + ex); break; } } // 解压新文件 ModBase.WriteFile(filePath, entry.Open()); McLaunchLog("已解压:" + filePath); } } if (zip is not null) zip.Dispose(); } // 删除多余文件 foreach (var fileName in Directory.GetFiles(target)) { if (existFiles.Contains(fileName)) continue; try { McLaunchLog("删除:" + fileName); File.Delete(fileName); } catch (UnauthorizedAccessException ex) { McLaunchLog("删除多余文件访问被拒绝,跳过删除步骤"); McLaunchLog("实际的错误信息:" + ex); return; } } } /// /// 获取 Natives 文件夹路径,不以 \ 结尾。 /// private static string GetNativesFolder() { var result = Path.Combine(ModInstanceList.McMcInstanceSelected.PathInstance, ModInstanceList.McMcInstanceSelected.Name + "-natives"); if (SystemInfo.IsGBKEncoding || result.IsASCII()) return result; result = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".minecraft", "bin", "natives"); if (result.IsASCII()) return result; return Path.Combine(SystemPaths.DriveLetter, "ProgramData", "PCL", "natives"); } #endregion #region 启动与前后处理 private static void McLaunchPrerun() { // 要求 Java 使用高性能显卡 var javaExePath = mcLaunchJavaSelected.Installation.JavawExePath ?? mcLaunchJavaSelected.Installation.JavaExePath; try { ModMain.SetGPUPreference(javaExePath, Config.Launch.SetGpuPreference); } catch (Exception ex) { if (ProcessInterop.IsAdmin() || !Config.Launch.SetGpuPreference) { ModBase.Log(ex, "直接调整显卡设置失败"); } else { ModBase.Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试"); try { if (ProcessInterop.StartAsAdmin($"--gpu \"{javaExePath}\"").ExitCode == (int)ModBase.ProcessReturnValues.TaskDone) McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功"); else throw new Exception("调整过程中出现异常"); } catch (Exception exx) { ModBase.Log( exx, Lang.Text("Minecraft.Launch.Error.GpuSet"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Minecraft.Launch.Error.GpuSet")); } } } // 更新 launcher_profiles.json do { try { // 确保可用 if (mcLoginLoader.output.Type != "Microsoft") break; ModFolder.McFolderLauncherProfilesJsonCreate(ModFolder.mcFolderSelected); // 构建需要替换的 Json 对象 var replaceJsonString = @" { ""authenticationDatabase"": { ""00000111112222233333444445555566"": { ""username"": """ + mcLoginLoader.output.Name.Replace("\"", "-") + @""", ""profiles"": { ""66666555554444433333222221111100"": { ""displayName"": """ + mcLoginLoader.output.Name + @""" } } } }, ""clientToken"": """ + mcLoginLoader.output.ClientToken + @""", ""selectedUser"": { ""account"": ""00000111112222233333444445555566"", ""profile"": ""66666555554444433333222221111100"" } }"; var replaceJson = (JsonObject)ModBase.GetJson(replaceJsonString); // 更新文件 var profiles = (JsonObject)ModBase.GetJson( ModBase.ReadFile(ModFolder.mcFolderSelected + "launcher_profiles.json")); profiles.Merge(replaceJson); ModBase.WriteFile(ModFolder.mcFolderSelected + "launcher_profiles.json", profiles.ToString(), encoding: Encoding.GetEncoding("GB18030")); McLaunchLog("已更新 launcher_profiles.json"); } catch (Exception ex) { ModBase.Log(ex, "更新 launcher_profiles.json 失败,将在删除文件后重试"); try { File.Delete(ModFolder.mcFolderSelected + "launcher_profiles.json"); ModFolder.McFolderLauncherProfilesJsonCreate(ModFolder.mcFolderSelected); // 构建需要替换的 Json 对象 var replaceJsonString = @" { ""authenticationDatabase"": { ""00000111112222233333444445555566"": { ""username"": """ + mcLoginLoader.output.Name.Replace("\"", "-") + @""", ""profiles"": { ""66666555554444433333222221111100"": { ""displayName"": """ + mcLoginLoader.output.Name + @""" } } } }, ""clientToken"": """ + mcLoginLoader.output.ClientToken + @""", ""selectedUser"": { ""account"": ""00000111112222233333444445555566"", ""profile"": ""66666555554444433333222221111100"" } }"; var replaceJson = (JsonObject)ModBase.GetJson(replaceJsonString); // 更新文件 var profiles = (JsonObject)ModBase.GetJson( ModBase.ReadFile(ModFolder.mcFolderSelected + "launcher_profiles.json")); profiles.Merge(replaceJson); ModBase.WriteFile(ModFolder.mcFolderSelected + "launcher_profiles.json", profiles.ToString(), encoding: Encoding.GetEncoding("GB18030")); McLaunchLog("已在删除后更新 launcher_profiles.json"); } catch (Exception exx) { ModBase.Log( exx, "更新 launcher_profiles.json 失败", ModBase.LogLevel.Feedback, userSummary: Lang.Text("Minecraft.Launch.Error.UpdateProfilesFailed")); } } } while (false); // 更新 options.txt var setupFileAddress = Path.Combine(ModInstanceList.McMcInstanceSelected.PathIndie, "options.txt"); // 辅助切换游戏语言 if (Config.Tool.AutoChangeLanguage) { if (!File.Exists(setupFileAddress)) { // Yosbr Mod 兼容(#2385):https://www.curseforge.com/minecraft/mc-mods/yosbr var yosbrFileAddress = Path.Combine(ModInstanceList.McMcInstanceSelected.PathIndie, "config", "yosbr", "options.txt"); if (File.Exists(yosbrFileAddress)) { McLaunchLog("将修改 Yosbr Mod 中的 options.txt"); setupFileAddress = yosbrFileAddress; ModBase.WriteIni(setupFileAddress, "lang", "none"); // 忽略默认语言 } } try { // 语言 // 1.0- :没有语言选项 // 1.1 ~ 5 :zh_CN 时正常,zh_cn 时崩溃(最后两位字母必须大写,否则将会 NPE 崩溃) // 1.6 ~ 10 :zh_CN 时正常,zh_cn 时自动切换为英文 // 1.11 ~ 12:zh_cn 时正常,zh_CN 时虽然显示了中文但语言设置会错误地显示选择英文 // 1.13+ :zh_cn 时正常,zh_CN 时自动切换为英文 var currentLang = ModBase.ReadIni(setupFileAddress, "lang", "none"); var isLanguageUnconfigured = string.Equals(currentLang, "none", StringComparison.OrdinalIgnoreCase); var hasExistingSaves = Directory.Exists(Path.Combine(ModInstanceList.McMcInstanceSelected.PathIndie, "saves")); var shouldUseDefault = isLanguageUnconfigured || !hasExistingSaves; var requiredLang = _ResolveMinecraftLanguage(currentLang, shouldUseDefault, ModInstanceList.McMcInstanceSelected.releaseTime); if (currentLang == requiredLang) { McLaunchLog($"需要的语言为 {requiredLang},当前语言为 {currentLang},无需修改"); } else { ModBase.WriteIni(setupFileAddress, "lang", "-"); // 触发缓存更改,避免删除后重新下载残留缓存 ModBase.WriteIni(setupFileAddress, "lang", requiredLang); McLaunchLog($"已将语言从 {currentLang} 修改为 {requiredLang}"); } // 如果是初次设置,一并按启动器语言需要修改 forceUnicodeFont,确保 CJK 字符正常显示 if ((isLanguageUnconfigured || !hasExistingSaves) && _ShouldEnableForceUnicodeFont()) { ModBase.WriteIni(setupFileAddress, "forceUnicodeFont", "true"); McLaunchLog("已开启 forceUnicodeFont,确保当前启动器语言字体正常显示"); } } catch (Exception ex) { ModBase.Log( ex, "更新 options.txt 失败", ModBase.LogLevel.Hint, userSummary: Lang.Text("Minecraft.Launch.Error.UpdateOptionsFailed")); } } // 窗口 switch (Config.Launch.GameWindowMode) { case GameWindowSizeMode.Fullscreen: // 全屏 { ModBase.WriteIni(setupFileAddress, "fullscreen", "true"); break; } case GameWindowSizeMode.Default: // 默认 // 其他 { break; } default: { ModBase.WriteIni(setupFileAddress, "fullscreen", "false"); break; } } } private static string _ResolveMinecraftLanguage(string? currentLanguage, bool shouldUseLauncherLanguage, DateTime? mcReleaseTime) { if (_IsMinecraftVersionUnder1Dot1(mcReleaseTime)) return "none"; var useLegacyRegionCase = _ShouldUseLegacyMinecraftLanguageCode(mcReleaseTime); var languageCode = shouldUseLauncherLanguage ? LocalizationService.CurrentLanguage.Code : currentLanguage; return _NormalizeMinecraftLanguageCode(languageCode, useLegacyRegionCase); } private static string _NormalizeMinecraftLanguageCode(string? languageCode, bool useLegacyRegionCase) { var normalizedCode = string.IsNullOrWhiteSpace(languageCode) ? "none" : languageCode.Replace('-', '_').Trim(); if (string.Equals(normalizedCode, "none", StringComparison.OrdinalIgnoreCase)) return "none"; var segments = normalizedCode.Split('_', 2, StringSplitOptions.RemoveEmptyEntries); if (segments.Length < 2) return normalizedCode.ToLowerInvariant(); var language = segments[0].ToLowerInvariant(); var region = useLegacyRegionCase ? segments[1].ToUpperInvariant() : segments[1].ToLowerInvariant(); return $"{language}_{region}"; } private static bool _IsMinecraftVersionUnder1Dot1(DateTime? releaseTime) { return releaseTime.HasValue && releaseTime.Value > new DateTime(2000, 1, 1) && releaseTime.Value <= new DateTime(2011, 11, 18); } private static bool _ShouldUseLegacyMinecraftLanguageCode(DateTime? releaseTime) { return releaseTime.HasValue && releaseTime.Value >= new DateTime(2012, 1, 12) && releaseTime.Value <= new DateTime(2016, 6, 8); } private static bool _ShouldEnableForceUnicodeFont() { return LocalizationService.CurrentLanguage.FontProfile is LocalizationFontProfile.SimplifiedChinese or LocalizationFontProfile.TraditionalChinese or LocalizationFontProfile.Japanese or LocalizationFontProfile.Korean; } private static void McLaunchCustom(ModLoader.LoaderTask loader) { // 获取自定义命令 var customCommandGlobal = Config.Launch.PreLaunchCommand; if (!string.IsNullOrEmpty(customCommandGlobal)) customCommandGlobal = ArgumentReplace(customCommandGlobal, true); var customCommandVersion = Config.Instance.PreLaunchCommand[ModInstanceList.McMcInstanceSelected?.PathInstance]; if (!string.IsNullOrEmpty(customCommandVersion)) customCommandVersion = ArgumentReplace(customCommandVersion, true); // 输出 bat try { var cmdString = $"{(mcLaunchJavaSelected.Installation.MajorVersion > 8 ? "chcp 65001>nul" + "\r\n" : "")}" + "@echo off" + "\r\n" + $"title 启动 - {ModInstanceList.McMcInstanceSelected.Name}" + "\r\n" + "echo 游戏正在启动,请稍候。" + "\r\n" + $"cd /D \"{ModBase.ShortenPath(ModInstanceList.McMcInstanceSelected.PathIndie)}\"" + "\r\n" + customCommandGlobal + "\r\n" + customCommandVersion + "\r\n" + $"\"{mcLaunchJavaSelected.Installation.JavaExePath}\" {mcLaunchArgument}" + "\r\n" + "echo 游戏已退出。" + "\r\n" + "pause"; ModBase.WriteFile(currentLaunchOptions.SaveBatch ?? ModBase.exePath + @"PCL\LatestLaunch.bat", McLogFilter.FilterAccessToken(cmdString, 'F'), encoding: mcLaunchJavaSelected.Installation.MajorVersion > 8 ? Encoding.UTF8 : Encoding.Default); if (currentLaunchOptions.SaveBatch is not null) { McLaunchLog("导出启动脚本完成,强制结束启动过程"); abortHint = Lang.Text("Minecraft.Launch.ExportScript.Success"); ModBase.OpenExplorer(currentLaunchOptions.SaveBatch); loader.parent.Abort(); return; // 导出脚本完成 } } catch (Exception ex) { ModBase.Log(ex, "输出启动脚本失败"); if (currentLaunchOptions.SaveBatch is not null) throw; // 直接触发启动失败 } // 执行自定义命令 if (!string.IsNullOrEmpty(customCommandGlobal)) { McLaunchLog("正在执行全局自定义命令:" + customCommandGlobal); var customProcess = new Process(); try { customProcess.StartInfo.FileName = "cmd.exe"; customProcess.StartInfo.Arguments = "/c \"" + customCommandGlobal + "\""; customProcess.StartInfo.WorkingDirectory = ModBase.ShortenPath(ModFolder.mcFolderSelected); customProcess.StartInfo.UseShellExecute = false; customProcess.StartInfo.CreateNoWindow = true; customProcess.Start(); if (Config.Launch.PreLaunchCommandWait) while (!customProcess.HasExited && !loader.IsAborted) Thread.Sleep(10); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Launch.Error.CustomCommand"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Minecraft.Launch.Error.CustomCommand")); } finally { if (!customProcess.HasExited && loader.IsAborted) { McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程"); // #1183 customProcess.Kill(); } } } if (!string.IsNullOrEmpty(customCommandVersion)) { McLaunchLog("正在执行实例自定义命令:" + customCommandVersion); var customProcess = new Process(); try { customProcess.StartInfo.FileName = "cmd.exe"; customProcess.StartInfo.Arguments = "/c \"" + customCommandVersion + "\""; customProcess.StartInfo.WorkingDirectory = ModBase.ShortenPath(ModFolder.mcFolderSelected); customProcess.StartInfo.UseShellExecute = false; customProcess.StartInfo.CreateNoWindow = true; customProcess.Start(); if (Config.Instance.PreLaunchCommandWait[ModInstanceList.McMcInstanceSelected?.PathInstance]) while (!customProcess.HasExited && !loader.IsAborted) Thread.Sleep(10); } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Launch.Error.CustomCommand"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Minecraft.Launch.Error.CustomCommand")); } finally { if (!customProcess.HasExited && loader.IsAborted) { McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程"); // #1183 customProcess.Kill(); } } } } private static void McLaunchRun(ModLoader.LoaderTask loader) { var noJavaw = Config.Launch.NoJavaw && mcLaunchJavaSelected.Installation.JavawExePath is not null; // 启动信息 var gameProcess = new Process(); var startInfo = new ProcessStartInfo(noJavaw ? mcLaunchJavaSelected.Installation.JavaExePath : mcLaunchJavaSelected.Installation.JavawExePath); // 设置环境变量 var paths = new List(startInfo.EnvironmentVariables["Path"].Split(";")); paths.Add(ModBase.ShortenPath(mcLaunchJavaSelected.Installation.JavaFolder)); startInfo.EnvironmentVariables["Path"] = paths.Distinct().ToList().Join(";"); startInfo.EnvironmentVariables["appdata"] = ModBase.ShortenPath(ModFolder.mcFolderSelected); // 设置其他参数 startInfo.WorkingDirectory = ModBase.ShortenPath(ModInstanceList.McMcInstanceSelected.PathIndie); startInfo.UseShellExecute = false; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.CreateNoWindow = noJavaw; startInfo.Arguments = mcLaunchArgument; gameProcess.StartInfo = startInfo; // 开始进程 gameProcess.Start(); McLaunchLog("已启动游戏进程:" + startInfo.FileName); if (loader.IsAborted) { McLaunchLog("由于取消启动,已强制结束游戏进程"); // #1631 gameProcess.Kill(); return; } loader.output = gameProcess; mcLaunchProcess = gameProcess; // 进程优先级处理 try { gameProcess.PriorityBoostEnabled = true; switch (Config.Launch.ProcessPriority) { case GameProcessPriority.RealTime: // 实时 { gameProcess.PriorityClass = ProcessPriorityClass.RealTime; break; } case GameProcessPriority.High: // 极高 { gameProcess.PriorityClass = ProcessPriorityClass.High; break; } case GameProcessPriority.AboveNormal: // 高 { gameProcess.PriorityClass = ProcessPriorityClass.AboveNormal; break; } case GameProcessPriority.BelowNormal: // 低 { gameProcess.PriorityClass = ProcessPriorityClass.BelowNormal; break; } } } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Minecraft.Launch.Error.PrioritySet"), ModBase.LogLevel.Feedback, userSummary: Lang.Text("Minecraft.Launch.Error.PrioritySet")); } } private static void McLaunchWait(ModLoader.LoaderTask loader) { // 输出信息 McLaunchLog(""); McLaunchLog("~ 基础参数 ~"); McLaunchLog("PCL 版本:" + ModBase.versionBaseName + " (" + ModBase.versionCode + ")"); McLaunchLog( $"游戏版本:{ModInstanceList.McMcInstanceSelected.Info.VanillaName}({ModInstanceList.McMcInstanceSelected.Info.vanilla},Drop {ModInstanceList.McMcInstanceSelected.Info.Drop}{(ModInstanceList.McMcInstanceSelected.Info.Reliable ? "" : ",无法完全确定")})"); McLaunchLog("资源版本:" + ModAssets.McAssetsGetIndexName(ModInstanceList.McMcInstanceSelected)); McLaunchLog("实例继承:" + (string.IsNullOrEmpty(ModInstanceList.McMcInstanceSelected.InheritInstanceName) ? "无" : ModInstanceList.McMcInstanceSelected.InheritInstanceName)); var launchRamGb = PageInstanceSetup.GetRam(ModInstanceList.McMcInstanceSelected, !mcLaunchJavaSelected.Installation.Is64Bit); McLaunchLog("分配的内存:" + launchRamGb.ToString("N1", CultureInfo.InvariantCulture) + " GiB(" + Math.Round(launchRamGb * 1024d).ToString("N0", CultureInfo.InvariantCulture) + " MiB)"); McLaunchLog("MC 文件夹:" + ModFolder.mcFolderSelected); McLaunchLog("实例文件夹:" + ModInstanceList.McMcInstanceSelected.PathInstance); McLaunchLog("版本隔离:" + ((ModInstanceList.McMcInstanceSelected.PathIndie ?? "") == (ModInstanceList.McMcInstanceSelected.PathInstance ?? ""))); McLaunchLog("HMCL 格式:" + ModInstanceList.McMcInstanceSelected.IsHmclFormatJson); McLaunchLog("Java 信息:" + mcLaunchJavaSelected.Installation); // McLaunchLog("环境变量:" & If(McLaunchJavaSelected IsNot Nothing, If(McLaunchJavaSelected.HasEnvironment, "已设置", "未设置"), "未设置")) McLaunchLog("Natives 文件夹:" + GetNativesFolder()); McLaunchLog(""); McLaunchLog("~ 档案参数 ~"); McLaunchLog("玩家用户名:" + mcLoginLoader.output.Name); McLaunchLog("AccessToken:" + mcLoginLoader.output.AccessToken); McLaunchLog("ClientToken:" + mcLoginLoader.output.ClientToken); McLaunchLog("UUID:" + mcLoginLoader.output.Uuid); McLaunchLog("验证方式:" + mcLoginLoader.output.Type); McLaunchLog(""); // 获取窗口标题 var windowTitle = Config.Instance.Title[ModInstanceList.McMcInstanceSelected?.PathInstance]; if (string.IsNullOrEmpty(windowTitle) && !Config.Instance.UseGlobalTitle[ModInstanceList.McMcInstanceSelected?.PathInstance]) windowTitle = Config.Launch.Title; windowTitle = ArgumentReplace(windowTitle, false); // JStack 路径 var jStackPath = Path.Combine(mcLaunchJavaSelected.Installation.JavaFolder, "jstack.exe"); // 初始化等待 var watcher = new ModWatcher.Watcher(loader, ModInstanceList.McMcInstanceSelected, windowTitle, File.Exists(jStackPath) ? jStackPath : "", currentLaunchOptions.IsTest); mcLaunchWatcher = watcher; // 显示实时日志 if (currentLaunchOptions.IsTest) { if (ModMain.frmLogLeft is null) ModBase.RunInUiWait(() => ModMain.frmLogLeft = new PageLogLeft()); if (ModMain.frmLogRight is null) ModBase.RunInUiWait(() => { ModAnimation.AniControlEnabled += 1; ModMain.frmLogRight = new PageLogRight(); ModAnimation.AniControlEnabled -= 1; }); ModMain.frmLogLeft.Add(watcher); McLaunchLog("已显示游戏实时日志"); } // 等待 while (watcher.State == ModWatcher.Watcher.MinecraftState.Loading) Thread.Sleep(100); if (watcher.State == ModWatcher.Watcher.MinecraftState.Crashed) throw new Exception("$$"); } private static void McLaunchEnd() { McLaunchLog("开始启动结束处理"); // 暂停或开始音乐播放 if (Config.Preference.Music.StopInGame) ModBase.RunInUi(() => { if (ModMusic.MusicPause()) ModBase.Log("[Music] 已根据设置,在启动后暂停音乐播放"); }); else if (Config.Preference.Music.StartInGame) ModBase.RunInUi(() => { if (ModMusic.MusicResume()) ModBase.Log("[Music] 已根据设置,在启动后开始音乐播放"); }); // 暂停视频背景播放 ModVideoBack.IsGaming = true; ModVideoBack.VideoPause(); // 启动器可见性 McLaunchLog( "启动器可见性:" + Config.Launch.LauncherVisibility); switch (Config.Launch.LauncherVisibility) { case LauncherVisibility.ExitImmediately: { // 直接关闭 McLaunchLog("已根据设置,在启动后关闭启动器"); ModBase.RunInUi(() => ModMain.frmMain.EndProgram(false)); break; } case LauncherVisibility.HideAndExit: case LauncherVisibility.HideAndReopen: { // 隐藏 McLaunchLog("已根据设置,在启动后隐藏启动器"); ModBase.RunInUi(() => ModMain.frmMain.Hidden = true); break; } case LauncherVisibility.MinimizeAndReopen: { // 最小化 McLaunchLog("已根据设置,在启动后最小化启动器"); ModBase.RunInUi(() => ModMain.frmMain.WindowState = WindowState.Minimized); break; } case LauncherVisibility.DoNothing: { break; } // 啥都不干 } // 启动计数 States.System.LaunchCount += 1; States.Instance.LaunchCount[ModInstanceList.McMcInstanceSelected.PathInstance] = States.Instance.LaunchCount[ModInstanceList.McMcInstanceSelected.PathInstance] + 1; } /// /// 对替换标记进行处理。会对替换内容使用 EscapeHandler 进行转义。 /// private static string ArgumentReplace(string text, bool replaceTime, Func escapeHandler = null) { // 预处理 if (text is null) return null; string replacer(string s) { if (s is null) return ""; if (escapeHandler is null) return s; if (s.Contains(@":\")) s = ModBase.ShortenPath(s); return escapeHandler(s); } ; // 基础 text = text.Replace("{pcl_version}", replacer(ModBase.versionBaseName)); text = text.Replace("{pcl_version_code}", replacer(ModBase.versionCode.ToString())); text = text.Replace("{pcl_version_branch}", replacer(ModBase.versionBranchName)); text = text.Replace("{identify}", replacer(Identify.LauncherId)); text = text.Replace("{path}", replacer(Basics.CurrentDirectory)); text = text.Replace("{path_with_name}", replacer(Basics.ExecutablePath)); text = text.Replace("{path_temp}", replacer(ModBase.pathTemp)); // 时间 if (replaceTime) // 在窗口标题中,时间会被后续动态替换,所以此时不应该替换 { text = text.Replace("{date}", replacer(Lang.Date(DateTime.Now, "d"))); text = text.Replace("{time}", replacer(Lang.Date(DateTime.Now, "T"))); } // Minecraft text = text.Replace("{java}", replacer(mcLaunchJavaSelected?.Installation.JavaFolder)); text = text.Replace("{minecraft}", replacer(ModFolder.mcFolderSelected)); if (ModInstanceList.McMcInstanceSelected?.IsLoaded == true) { text = text.Replace("{version_path}", replacer(ModInstanceList.McMcInstanceSelected.PathInstance)); text = text.Replace("{verpath}", replacer(ModInstanceList.McMcInstanceSelected.PathInstance)); text = text.Replace("{version_indie}", replacer(ModInstanceList.McMcInstanceSelected.PathIndie)); text = text.Replace("{verindie}", replacer(ModInstanceList.McMcInstanceSelected.PathIndie)); text = text.Replace("{name}", replacer(ModInstanceList.McMcInstanceSelected.Name)); if (new[] { "unknown", "old", "pending" }.Contains( ModInstanceList.McMcInstanceSelected.Info.VanillaName.ToLower())) text = text.Replace("{version}", replacer(ModInstanceList.McMcInstanceSelected.Name)); else text = text.Replace("{version}", replacer(ModInstanceList.McMcInstanceSelected.Info.VanillaName)); } else { text = text.Replace("{version_path}", replacer(null)); text = text.Replace("{verpath}", replacer(null)); text = text.Replace("{version_indie}", replacer(null)); text = text.Replace("{verindie}", replacer(null)); text = text.Replace("{name}", replacer(null)); text = text.Replace("{version}", replacer(null)); } // 登录信息 if (mcLoginLoader.State == ModBase.LoadState.Finished) { text = text.Replace("{user}", replacer(mcLoginLoader.output.Name)); text = text.Replace("{uuid}", replacer(mcLoginLoader.output.Uuid?.ToLower())); switch (mcLoginLoader.input.LoginType) { case McLoginType.Legacy: { text = text.Replace("{login}", replacer("离线")); break; } case McLoginType.Ms: { text = text.Replace("{login}", replacer("正版")); break; } case McLoginType.Auth: { text = text.Replace("{login}", replacer("Authlib-Injector")); break; } } } else { text = text.Replace("{user}", replacer(null)); text = text.Replace("{uuid}", replacer(null)); text = text.Replace("{login}", replacer(null)); } return text; } #endregion }