CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
3782 lines
158 KiB
C#
3782 lines
158 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 额外的启动参数。
|
||
/// </summary>
|
||
public List<string> ExtraArgs = new();
|
||
|
||
/// <summary>
|
||
/// 强行指定启动的 MC 实例。
|
||
/// 默认值:Nothing。使用 McInstanceCurrent。
|
||
/// </summary>
|
||
public McInstance instance = null;
|
||
|
||
/// <summary>
|
||
/// 是否为 “测试游戏” 按钮启动的游戏。
|
||
/// 如果是,则显示游戏实时日志。
|
||
/// </summary>
|
||
public bool IsTest = false;
|
||
|
||
/// <summary>
|
||
/// 将启动脚本保存到该地址,然后取消启动。这同时会改变启动时的提示等。
|
||
/// 默认值:Nothing。不保存。
|
||
/// </summary>
|
||
public string SaveBatch = null;
|
||
|
||
/// <summary>
|
||
/// 强制指定在启动后进入的服务器 IP。
|
||
/// 默认值:Nothing。使用实例设置的值。
|
||
/// </summary>
|
||
public string ServerIp = null;
|
||
|
||
/// <summary>
|
||
/// 指定在启动之后进入的存档名称。
|
||
/// 默认值:Nothing。使用实例设置的值。
|
||
/// </summary>
|
||
public string WorldName = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试启动 Minecraft。必须在 UI 线程调用。
|
||
/// 返回是否实际开始了启动(如果没有,则一定弹出了错误提示)。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 记录启动日志。
|
||
/// </summary>
|
||
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<McLaunchOptions, object> mcLaunchLoader = new("Loader Launch", McLaunchStart)
|
||
{ OnStateChanged = a => McLaunchState((dynamic)a) };
|
||
|
||
public static ModLoader.LoaderCombo<object> mcLaunchLoaderReal;
|
||
public static Process mcLaunchProcess;
|
||
public static ModWatcher.Watcher mcLaunchWatcher;
|
||
|
||
private static void McLaunchState(ModLoader.LoaderTask<McLaunchOptions, object> 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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 指定启动中断时的提示文本。若不为 Nothing 则会显示为绿色。
|
||
/// </summary>
|
||
private static string abortHint;
|
||
|
||
// 实际的启动方法
|
||
private static void McLaunchStart(ModLoader.LoaderTask<McLaunchOptions, object> 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<ModLoader.LoaderBase>
|
||
{
|
||
new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Launch.Stage.GetJava"), McLaunchJava) { ProgressWeight = 4d, block = false },
|
||
mcLoginLoader,
|
||
new ModLoader.LoaderCombo<string>(Lang.Text("Minecraft.Launch.Stage.CompleteFiles"),
|
||
ModDownload.DlClientFix(ModInstanceList.McMcInstanceSelected, false,
|
||
ModDownload.AssetsIndexExistsBehaviour.DownloadInBackground))
|
||
{ ProgressWeight = 15d, show = false },
|
||
new ModLoader.LoaderTask<string, List<ModLibrary.McLibToken>>(Lang.Text("Minecraft.Launch.Stage.GetArguments"), McLaunchArgumentMain)
|
||
{ ProgressWeight = 2d },
|
||
new ModLoader.LoaderTask<List<ModLibrary.McLibToken>, int>(Lang.Text("Minecraft.Launch.Stage.ExtractNatives"), McLaunchNatives)
|
||
{ ProgressWeight = 2d },
|
||
new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Launch.Stage.PreLaunch"), _ => McLaunchPrerun()) { ProgressWeight = 1d },
|
||
new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Launch.Stage.CustomCommand"), McLaunchCustom) { ProgressWeight = 1d },
|
||
new ModLoader.LoaderTask<int, Process>(Lang.Text("Minecraft.Launch.Stage.StartProcess"), McLaunchRun) { ProgressWeight = 2d },
|
||
new ModLoader.LoaderTask<Process, int>(Lang.Text("Minecraft.Launch.Stage.WaitWindow"), McLaunchWait) { ProgressWeight = 1d },
|
||
new ModLoader.LoaderTask<int, int>(Lang.Text("Minecraft.Launch.Stage.End"), _ => McLaunchEnd()) { ProgressWeight = 1d }
|
||
}; // .ProgressWeight = 15, .Block = False
|
||
|
||
var launchLoader = new ModLoader.LoaderCombo<object>(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
|
||
{
|
||
/// <summary>
|
||
/// 登录方式。
|
||
/// </summary>
|
||
public McLoginType LoginType;
|
||
|
||
public override bool Equals(object obj)
|
||
{
|
||
return obj is not null && obj.GetHashCode() == GetHashCode();
|
||
}
|
||
}
|
||
|
||
#region 第三方验证类型
|
||
|
||
public class McLoginServer : McLoginData
|
||
{
|
||
/// <summary>
|
||
/// 登录服务器基础地址。
|
||
/// </summary>
|
||
public string BaseUrl;
|
||
|
||
/// <summary>
|
||
/// 登录方式的描述字符串,如 “正版”、“统一通行证”。
|
||
/// </summary>
|
||
public string Description;
|
||
|
||
/// <summary>
|
||
/// 是否在本次登录中强制要求玩家重新选择角色,目前仅对 Authlib-Injector 生效。
|
||
/// </summary>
|
||
public bool ForceReselectProfile = false;
|
||
|
||
/// <summary>
|
||
/// 是否已经存在该验证信息,用于判断是否为新增档案。
|
||
/// </summary>
|
||
public bool IsExist = false;
|
||
|
||
/// <summary>
|
||
/// 登录密码。
|
||
/// </summary>
|
||
public string Password;
|
||
|
||
/// <summary>
|
||
/// 登录用户名。
|
||
/// </summary>
|
||
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 = "";
|
||
|
||
/// <summary>
|
||
/// 缓存的 OAuth RefreshToken。若没有则为空字符串。
|
||
/// </summary>
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 若采用正版皮肤,则为该皮肤名。
|
||
/// </summary>
|
||
public string SkinName;
|
||
|
||
/// <summary>
|
||
/// 皮肤种类。
|
||
/// </summary>
|
||
public int SkinType;
|
||
|
||
/// <summary>
|
||
/// 登录用户名。
|
||
/// </summary>
|
||
public string UserName;
|
||
|
||
/// <summary>
|
||
/// UUID。
|
||
/// </summary>
|
||
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;
|
||
|
||
/// <summary>
|
||
/// 进行微软登录时返回的 profile 信息。
|
||
/// </summary>
|
||
public string ProfileJson;
|
||
}
|
||
|
||
// 登录主模块加载器
|
||
public static ModLoader.LoaderTask<McLoginData, McLoginResult> 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<McLoginData, McLoginResult> 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<McLoginMs, McLoginResult> mcLoginMsLoader =
|
||
new("Loader Login Ms", McLoginMsStart) { reloadTimeout = 1 };
|
||
|
||
public static ModLoader.LoaderTask<McLoginLegacy, McLoginResult> mcLoginLegacyLoader =
|
||
new("Loader Login Legacy", McLoginLegacyStart);
|
||
|
||
public static ModLoader.LoaderTask<McLoginServer, McLoginResult> mcLoginAuthLoader =
|
||
new("Loader Login Auth", McLoginServerStart) { reloadTimeout = 1000 * 60 * 10 };
|
||
|
||
// 主加载函数,返回所有需要的登录信息
|
||
private static long mcLoginMsRefreshTime; // 上次刷新登录的时间
|
||
|
||
#region 正版验证
|
||
|
||
private static void McLoginMsStart(ModLoader.LoaderTask<McLoginMs, McLoginResult> 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("正版验证完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 OAuth Tokens,处理刷新和重新登录逻辑
|
||
/// </summary>
|
||
private static string[] GetOAuthTokens(ModLoader.LoaderTask<McLoginMs, McLoginResult> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否被中断
|
||
/// </summary>
|
||
private static void ThrowIfAborted(ModLoader.LoaderTask<McLoginMs, McLoginResult> data)
|
||
{
|
||
if (data.IsAborted)
|
||
throw new ThreadInterruptedException();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 1:通过设备代码流获取账号信息
|
||
/// </summary>
|
||
/// <returns>OAuth 验证完成的返回结果</returns>
|
||
private static string[] MsLoginStep1New(ModLoader.LoaderTask<McLoginMs, McLoginResult> 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<string, string>
|
||
{
|
||
{ "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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 1,刷新登录:从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth accessToken, OAuth RefreshToken}
|
||
/// </summary>
|
||
/// <param name="code"></param>
|
||
/// <returns></returns>
|
||
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<string, string>
|
||
{
|
||
{ "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; }
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 2:从 OAuth accessToken 获取 XBLToken
|
||
/// </summary>
|
||
/// <param name="accessToken">OAuth accessToken</param>
|
||
/// <returns>XBLToken</returns>
|
||
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<string> UserTokens { get; set; }
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 3:从 XBLToken 获取 {XSTSToken, UHS}
|
||
/// </summary>
|
||
/// <returns>包含 XSTSToken 与 UHS 的字符串组</returns>
|
||
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 };
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 4:从 {XSTSToken, UHS} 获取 Minecraft accessToken
|
||
/// </summary>
|
||
/// <param name="tokens">包含 XSTSToken 与 UHS 的字符串组</param>
|
||
/// <returns>Minecraft accessToken</returns>
|
||
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<string, string> { { "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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP
|
||
/// </summary>
|
||
/// <param name="accessToken">Minecraft accessToken</param>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 正版验证步骤 6:从 Minecraft accessToken 获取 {UUID, UserName, ProfileJson}
|
||
/// </summary>
|
||
/// <param name="accessToken">Minecraft accessToken</param>
|
||
/// <returns>包含 UUID, UserName 和 ProfileJson 的字符串组</returns>
|
||
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<McLoginServer, McLoginResult> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查任务是否被中断
|
||
/// </summary>
|
||
private static void ThrowIfAborted(ModLoader.LoaderTask<McLoginServer, McLoginResult> data)
|
||
{
|
||
if (data.IsAborted)
|
||
throw new ThreadInterruptedException();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统一处理 HttpWebException
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统一处理普通异常
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理普通登录 HttpWebException
|
||
/// </summary>
|
||
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<McLoginServer, McLoginResult> 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<string, string> { { "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<McLoginServer, McLoginResult> 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<string, string> { { "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<McLoginServer, McLoginResult> 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<string, string> { { "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<IMyRadio>();
|
||
var selectionJson = new List<JsonNode>();
|
||
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<YggdrasilAuthenticateResult>(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<McLoginLegacy, McLoginResult> 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<int, int> 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<int>() ??
|
||
ModInstanceList.McMcInstanceSelected.JsonVersion?["java_version"]?.ToObject<int>() ?? 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<string> 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<string> _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;
|
||
|
||
/// <summary>
|
||
/// 释放 Java Wrapper 并返回完整文件路径。
|
||
/// </summary>
|
||
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"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放 linkd 并返回完整文件路径。
|
||
/// </summary>
|
||
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"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否使用 LegacyFix。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取实例所依赖的 LWJGL 版本
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否启用了针对 Minecraft 26.1 的性能问题补丁
|
||
/// </summary>
|
||
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<string, List<ModLibrary.McLibToken>> 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<string>();
|
||
|
||
// 输出固定参数
|
||
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<string>();
|
||
|
||
// 获取 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<string>();
|
||
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<string>();
|
||
|
||
// 本地化 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<string>();
|
||
|
||
// 获取 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<string>();
|
||
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<string, string> McLaunchArgumentsReplace(McInstance instance,
|
||
ref ModLoader.LoaderTask<string, List<ModLibrary.McLibToken>> loader)
|
||
{
|
||
var gameArguments = new Dictionary<string, string>();
|
||
|
||
// 基础参数
|
||
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>();
|
||
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<List<ModLibrary.McLibToken>, int> loader)
|
||
{
|
||
// 创建文件夹
|
||
var target = GetNativesFolder() + @"\";
|
||
Directory.CreateDirectory(target);
|
||
|
||
// 解压文件
|
||
McLaunchLog("正在解压 Natives 文件");
|
||
var existFiles = new List<string>();
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Natives 文件夹路径,不以 \ 结尾。
|
||
/// </summary>
|
||
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<int, int> 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<int, Process> 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<string>(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<Process, int> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对替换标记进行处理。会对替换内容使用 EscapeHandler 进行转义。
|
||
/// </summary>
|
||
private static string ArgumentReplace(string text, bool replaceTime, Func<string, string> 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
|
||
}
|