From 6b54d8d5232c562876e28e2f4029e259eb3ff059 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 10 Mar 2022 01:44:09 +0900 Subject: [PATCH 001/137] remove CmlLib.Core.Auth.Microsoft namespace --- .../Auth/Microsoft/AuthenticationResponse.cs | 26 ------- .../Core/Auth/Microsoft/XboxMinecraftLogin.cs | 72 ------------------- 2 files changed, 98 deletions(-) delete mode 100644 CmlLib/Core/Auth/Microsoft/AuthenticationResponse.cs delete mode 100644 CmlLib/Core/Auth/Microsoft/XboxMinecraftLogin.cs diff --git a/CmlLib/Core/Auth/Microsoft/AuthenticationResponse.cs b/CmlLib/Core/Auth/Microsoft/AuthenticationResponse.cs deleted file mode 100644 index 9861da3..0000000 --- a/CmlLib/Core/Auth/Microsoft/AuthenticationResponse.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace CmlLib.Core.Auth.Microsoft -{ - public class AuthenticationResponse - { - [JsonProperty("username")] - public string? Username { get; set; } - - [JsonProperty("roles")] - public string[]? Roles { get; set; } - - [JsonProperty("access_token")] - public string? AccessToken { get; set; } - - [JsonProperty("token_type")] - public string? TokenType { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("expires_on")] - public DateTime ExpiresOn { get; set; } - } -} diff --git a/CmlLib/Core/Auth/Microsoft/XboxMinecraftLogin.cs b/CmlLib/Core/Auth/Microsoft/XboxMinecraftLogin.cs deleted file mode 100644 index b06c287..0000000 --- a/CmlLib/Core/Auth/Microsoft/XboxMinecraftLogin.cs +++ /dev/null @@ -1,72 +0,0 @@ -using CmlLib.Core.Mojang; -using Newtonsoft.Json; -using System; -using System.IO; -using System.Net; - -namespace CmlLib.Core.Auth.Microsoft -{ - public class XboxMinecraftLogin - { - public static readonly string RelyingParty = "rp://api.minecraftservices.com/"; - - private void writeReq(WebRequest req, string data) - { - using var reqStream = req.GetRequestStream(); - using var sw = new StreamWriter(reqStream); - sw.Write(data); - } - - private string readRes(WebResponse res) - { - using var resStream = res.GetResponseStream(); - if (resStream == null) - return ""; - using var sr = new StreamReader(resStream); - return sr.ReadToEnd(); - } - - // login_with_xbox - public AuthenticationResponse LoginWithXbox(string uhs, string xstsToken) - { - var url = "https://api.minecraftservices.com/authentication/login_with_xbox"; - var req = WebRequest.CreateHttp(url); - req.ContentType = "application/json"; - req.Method = "POST"; - - var reqBody = $"{{\"identityToken\": \"XBL3.0 x={uhs};{xstsToken}\"}}"; - writeReq(req, reqBody); - - var res = req.GetResponse(); - var resBody = readRes(res); - - var obj = JsonConvert.DeserializeObject(resBody) - ?? new AuthenticationResponse(); - obj.ExpiresOn = DateTime.Now.AddSeconds(obj.ExpiresIn); - return obj; - } - - public MLoginResponse RequestSession(string accessToken) - { - try - { - if (!MojangAPI.CheckGameOwnership(accessToken)) - return new MLoginResponse(MLoginResult.NoProfile, null, null, null); - - var profile = MojangAPI.GetProfileUsingToken(accessToken); - var session = new MSession - { - Username = profile.Name, - AccessToken = accessToken, - UUID = profile.UUID - }; - - return new MLoginResponse(MLoginResult.Success, session, null, null); - } - catch (Exception ex) - { - return new MLoginResponse(MLoginResult.UnknownError, null, ex.ToString(), null); - } - } - } -} From ebfe4c9578b2a88eb0253cecc8e76c843fe66374 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 10 Mar 2022 03:08:00 +0900 Subject: [PATCH 002/137] migration: Newtonsoft.Json->System.Text.Json, WebClient->HttpClient --- CmlLib/CmlLib.csproj | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CmlLib/CmlLib.csproj b/CmlLib/CmlLib.csproj index da15e1f..49aa466 100644 --- a/CmlLib/CmlLib.csproj +++ b/CmlLib/CmlLib.csproj @@ -1,14 +1,14 @@  - net462;netstandard2.0 + netstandard2.0 8.0 enable 3.4.0 Minecraft Launcher Library for .NET Support all version, forge, optifine - Copyright (c) 2021 AlphaBs + Copyright (c) 2022 AlphaBs https://github.com/CmlLib/CmlLib.Core https://github.com/CmlLib/CmlLib.Core icon.png @@ -39,8 +39,9 @@ Support all version, forge, optifine all - + + From 85d01ccf9170706d055182f4ae22cf29004a19f9 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 10 Mar 2022 03:08:09 +0900 Subject: [PATCH 003/137] migration: CmlLib.Core.Auth --- CmlLib/Core/Auth/MLogin.cs | 295 ++++++++++++++--------------------- CmlLib/Core/Auth/MSession.cs | 12 +- CmlLib/Utils/HttpUtil.cs | 9 ++ CmlLib/Utils/JsonUtil.cs | 14 ++ 4 files changed, 146 insertions(+), 184 deletions(-) create mode 100644 CmlLib/Utils/HttpUtil.cs create mode 100644 CmlLib/Utils/JsonUtil.cs diff --git a/CmlLib/Core/Auth/MLogin.cs b/CmlLib/Core/Auth/MLogin.cs index 6c82d88..a1b75ca 100644 --- a/CmlLib/Core/Auth/MLogin.cs +++ b/CmlLib/Core/Auth/MLogin.cs @@ -1,9 +1,11 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using CmlLib.Utils; using System; using System.IO; using System.Net; +using System.Net.Http; using System.Text; +using System.Text.Json; +using System.Threading.Tasks; // use new library: // https://github.com/CmlLib/MojangAPI @@ -14,106 +16,116 @@ public enum MLoginResult { Success, BadRequest, WrongAccount, NeedLogin, Unknown public class MLogin { - public static readonly string DefaultLoginSessionFile = Path.Combine(MinecraftPath.GetOSDefaultPath(), "logintoken.json"); + public static readonly string DefaultLoginSessionFile + = Path.Combine(MinecraftPath.GetOSDefaultPath(), "logintoken.json"); - public MLogin() : this(DefaultLoginSessionFile) { } + public MLogin() : this(DefaultLoginSessionFile, HttpUtil.HttpClient) { } - public MLogin(string sessionCacheFilePath) + public MLogin(string sessionCacheFilePath, HttpClient client) { SessionCacheFilePath = sessionCacheFilePath; + this.httpClient = client; } + public readonly HttpClient httpClient; public string SessionCacheFilePath { get; private set; } public bool SaveSession { get; set; } = true; - private string CreateNewClientToken() + protected virtual string CreateNewClientToken() { return Guid.NewGuid().ToString().Replace("-", ""); } - private MSession createNewSession() + protected async virtual Task createNewSession() { var session = new MSession(); if (SaveSession) { session.ClientToken = CreateNewClientToken(); - writeSessionCache(session); + await writeSessionCache(session); } return session; } - private void writeSessionCache(MSession session) + private async Task writeSessionCache(MSession session) { if (!SaveSession) return; var directoryPath = Path.GetDirectoryName(SessionCacheFilePath); if (!string.IsNullOrEmpty(directoryPath)) Directory.CreateDirectory(directoryPath); - var json = JsonConvert.SerializeObject(session); - File.WriteAllText(SessionCacheFilePath, json, Encoding.UTF8); + var json = JsonSerializer.Serialize(session); + await IOUtil.WriteFileAsync(SessionCacheFilePath, json); } - public MSession ReadSessionCache() + public async Task ReadSessionCache() { if (File.Exists(SessionCacheFilePath)) { - var fileData = File.ReadAllText(SessionCacheFilePath, Encoding.UTF8); + var fileData = await IOUtil.ReadFileAsync(SessionCacheFilePath); try { - var session = JsonConvert.DeserializeObject(fileData, new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore - }) ?? new MSession(); + var session = JsonSerializer.Deserialize(fileData, JsonUtil.JsonOptions) + ?? new MSession(); if (SaveSession && string.IsNullOrEmpty(session.ClientToken)) session.ClientToken = CreateNewClientToken(); return session; } - catch (JsonReaderException) // invalid json + catch (JsonException) // invalid json { - return createNewSession(); + return await createNewSession(); } } else { - return createNewSession(); + return await createNewSession(); } } - private HttpWebResponse mojangRequest(string endpoint, string postdata) + protected async Task mojangRequest(string endpoint, object postdata) { - HttpWebRequest http = WebRequest.CreateHttp(MojangServer.Auth + endpoint); - http.ContentType = "application/json"; - http.Method = "POST"; - - using StreamWriter req = new StreamWriter(http.GetRequestStream()); - req.Write(postdata); - req.Flush(); - - HttpWebResponse res = http.GetResponseNoException(); + var json = JsonSerializer.Serialize(postdata, JsonUtil.JsonOptions); + var res = await httpClient.SendAsync(new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new Uri(MojangServer.Auth + endpoint), + Content = new StringContent(json, Encoding.UTF8, "application/json") + }); return res; } - private MLoginResponse parseSession(string json, string? clientToken) + protected async Task mojangRequestHandle(string endpoint, object postdata, string? clientToken=null) { - var job = JObject.Parse(json); //json parse + var res = await mojangRequest(endpoint, postdata); + + var str = await res.Content.ReadAsStringAsync(); + if (res.IsSuccessStatusCode) + return await parseSession(str, clientToken); + else + return errorHandle(str); + } - var profile = job["selectedProfile"]; - if (profile == null) + private async Task parseSession(string json, string? clientToken) + { + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; + + if (!root.TryGetProperty("selectedProfile", out var profile)) return new MLoginResponse(MLoginResult.NoProfile, null, null, json); else { var session = new MSession { - AccessToken = job["accessToken"]?.ToString(), - UUID = profile["id"]?.ToString(), - Username = profile["name"]?.ToString(), + AccessToken = root.GetProperty("accessToken").GetString(), + UUID = profile.GetProperty("id").GetString(), + Username = profile.GetProperty("name").GetString(), UserType = "Mojang", ClientToken = clientToken }; - writeSessionCache(session); + await writeSessionCache(session); return new MLoginResponse(MLoginResult.Success, session, null, null); } } @@ -122,10 +134,11 @@ private MLoginResponse errorHandle(string json) { try { - JObject job = JObject.Parse(json); + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; - string error = job["error"]?.ToString() ?? ""; // error type - string errorMessage = job["message"]?.ToString() ?? ""; // detail error message + string? error = root.GetProperty("error").GetString(); // error type + string? errorMessage = root.GetProperty("message").GetString(); // detail error message MLoginResult result; switch (error) @@ -146,73 +159,47 @@ private MLoginResponse errorHandle(string json) return new MLoginResponse(result, null, errorMessage, json); } - catch (Exception ex) + catch (JsonException ex) { return new MLoginResponse(MLoginResult.UnknownError, null, ex.ToString(), json); } } - public MLoginResponse Authenticate(string id, string pw) + public async Task Authenticate(string id, string pw) { - string? clientToken = ReadSessionCache().ClientToken; - return Authenticate(id, pw, clientToken); + var sessionCache = await ReadSessionCache(); + string? clientToken = sessionCache.ClientToken; + return await Authenticate(id, pw, clientToken); } - public MLoginResponse Authenticate(string id, string pw, string? clientToken) - { - JObject req = new JObject + public Task Authenticate(string id, string pw, string? clientToken) + => mojangRequestHandle("authenticate", new { - { "username", id }, - { "password", pw }, - { "clientToken", clientToken }, - { "agent", new JObject - { - { "name", "Minecraft" }, - { "version", 1 } - } + username = id, + password = pw, + clientToken = clientToken, + agent = new + { + name = "Minecraft", + version = 1 } - }; - - HttpWebResponse resHeader = mojangRequest("authenticate", req.ToString()); - - var stream = resHeader.GetResponseStream(); - if (stream == null) - return new MLoginResponse( - MLoginResult.UnknownError, - null, - "null response stream", - null); - - using StreamReader res = new StreamReader(stream); - string rawResponse = res.ReadToEnd(); - if (resHeader.StatusCode == HttpStatusCode.OK) // ResultCode == 200 - return parseSession(rawResponse, clientToken); - else // fail to login - return errorHandle(rawResponse); - } + }); - public MLoginResponse TryAutoLogin() + public async Task TryAutoLogin() { - MSession session = ReadSessionCache(); - return TryAutoLogin(session); + MSession session = await ReadSessionCache(); + return await TryAutoLogin(session); } - public MLoginResponse TryAutoLogin(MSession session) + public async Task TryAutoLogin(MSession session) { - try - { - MLoginResponse result = Validate(session); - if (result.Result != MLoginResult.Success) - result = Refresh(session); - return result; - } - catch (Exception ex) - { - return new MLoginResponse(MLoginResult.UnknownError, null, ex.ToString(), null); - } + MLoginResponse result = await Validate(session); + if (result.Result != MLoginResult.Success) + result = await Refresh(session); + return result; } - public MLoginResponse TryAutoLoginFromMojangLauncher() + public async Task TryAutoLoginFromMojangLauncher() { var mojangAccounts = MojangLauncher.MojangLauncherAccounts.FromDefaultPath(); var activeAccount = mojangAccounts?.GetActiveAccount(); @@ -220,10 +207,10 @@ public MLoginResponse TryAutoLoginFromMojangLauncher() if (activeAccount == null) return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); - return TryAutoLogin(activeAccount.ToSession()); + return await TryAutoLogin(activeAccount.ToSession()); } - public MLoginResponse TryAutoLoginFromMojangLauncher(string accountFilePath) + public async Task TryAutoLoginFromMojangLauncher(string accountFilePath) { var mojangAccounts = MojangLauncher.MojangLauncherAccounts.FromFile(accountFilePath); var activeAccount = mojangAccounts?.GetActiveAccount(); @@ -231,67 +218,39 @@ public MLoginResponse TryAutoLoginFromMojangLauncher(string accountFilePath) if (activeAccount == null) return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); - return TryAutoLogin(activeAccount.ToSession()); + return await TryAutoLogin(activeAccount.ToSession()); } - public MLoginResponse Refresh() + public async Task Refresh() { - MSession session = ReadSessionCache(); - return Refresh(session); + var session = await ReadSessionCache(); + return await Refresh(session); } - public MLoginResponse Refresh(MSession session) - { - JObject req = new JObject + public Task Refresh(MSession session) + => mojangRequestHandle("refresh", new + { + accessToken = session.AccessToken, + clientToken = session.ClientToken, + selectedProfile = new { - { "accessToken", session.AccessToken }, - { "clientToken", session.ClientToken }, - { "selectedProfile", new JObject - { - { "id", session.UUID }, - { "name", session.Username } - } - } - }; - - HttpWebResponse resHeader = mojangRequest("refresh", req.ToString()); - var stream = resHeader.GetResponseStream(); - if (stream == null) - return new MLoginResponse( - MLoginResult.UnknownError, - null, - "null response stream", - null); - - using StreamReader res = new StreamReader(stream); - string rawResponse = res.ReadToEnd(); - - if ((int)resHeader.StatusCode / 100 == 2) - return parseSession(rawResponse, session.ClientToken); - else - return errorHandle(rawResponse); - } + id = session.UUID, + name = session.Username + } + }); - public MLoginResponse Validate() + public async Task Validate() { - MSession session = ReadSessionCache(); - return Validate(session); + var session = await ReadSessionCache(); + return await Validate(session); } - public MLoginResponse Validate(MSession session) - { - JObject req = new JObject - { - { "accessToken", session.AccessToken }, - { "clientToken", session.ClientToken } - }; - - HttpWebResponse resHeader = mojangRequest("validate", req.ToString()); - if (resHeader.StatusCode == HttpStatusCode.NoContent) // StatusCode == 204 - return new MLoginResponse(MLoginResult.Success, session, null, null); - else - return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); - } + public Task Validate(MSession session) + => mojangRequestHandle("validate", new + { + accessToken = session.AccessToken, + clientToken = session.ClientToken + }); public void DeleteTokenFile() { @@ -299,52 +258,32 @@ public void DeleteTokenFile() File.Delete(SessionCacheFilePath); } - public bool Invalidate() + public async Task Invalidate() { - MSession session = ReadSessionCache(); - return Invalidate(session); + var session = await ReadSessionCache(); + return await Invalidate(session); } - public bool Invalidate(MSession session) + public async Task Invalidate(MSession session) { - JObject job = new JObject + var res = await mojangRequest("invalidate", new { - { "accessToken", session.AccessToken }, - { "clientToken", session.ClientToken } - }; + accessToken = session.AccessToken, + clientToken = session.ClientToken + }); - HttpWebResponse res = mojangRequest("invalidate", job.ToString()); - return res.StatusCode == HttpStatusCode.NoContent; // 204 + return res.IsSuccessStatusCode; } - public bool Signout(string id, string pw) + public async Task Signout(string id, string pw) { - JObject job = new JObject + var res = await mojangRequest("signout", new { - { "username", id }, - { "password", pw } - }; + username = id, + password = pw + }); - HttpWebResponse res = mojangRequest("signout", job.ToString()); return res.StatusCode == HttpStatusCode.NoContent; // 204 } } - - internal static class HttpWebResponseExt - { - public static HttpWebResponse GetResponseNoException(this HttpWebRequest req) - { - try - { - return (HttpWebResponse)req.GetResponse(); - } - catch (WebException we) - { - HttpWebResponse? resp = we.Response as HttpWebResponse; - if (resp == null) - throw; - return resp; - } - } - } } diff --git a/CmlLib/Core/Auth/MSession.cs b/CmlLib/Core/Auth/MSession.cs index 874bf69..3190e64 100644 --- a/CmlLib/Core/Auth/MSession.cs +++ b/CmlLib/Core/Auth/MSession.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CmlLib.Core.Auth { @@ -13,15 +13,15 @@ public MSession(string? username, string? accessToken, string? uuid) UUID = uuid; } - [JsonProperty("username")] + [JsonPropertyName("username")] public string? Username { get; set; } - [JsonProperty("session")] + [JsonPropertyName("session")] public string? AccessToken { get; set; } - [JsonProperty("uuid")] + [JsonPropertyName("uuid")] public string? UUID { get; set; } - [JsonProperty("clientToken")] + [JsonPropertyName("clientToken")] public string? ClientToken { get; set; } - + [JsonPropertyName("userType")] public string? UserType { get; set; } public bool CheckIsValid() diff --git a/CmlLib/Utils/HttpUtil.cs b/CmlLib/Utils/HttpUtil.cs new file mode 100644 index 0000000..d06d8a6 --- /dev/null +++ b/CmlLib/Utils/HttpUtil.cs @@ -0,0 +1,9 @@ +using System.Net.Http; + +namespace CmlLib.Utils +{ + public class HttpUtil + { + public static HttpClient HttpClient { get; } = new HttpClient(); + } +} diff --git a/CmlLib/Utils/JsonUtil.cs b/CmlLib/Utils/JsonUtil.cs new file mode 100644 index 0000000..2dbe5a4 --- /dev/null +++ b/CmlLib/Utils/JsonUtil.cs @@ -0,0 +1,14 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CmlLib.Utils +{ + public class JsonUtil + { + public static JsonSerializerOptions JsonOptions { get; } = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + } +} From c54382bf9100053c27ca00ea796fa1bb698363c3 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 15:38:52 +0900 Subject: [PATCH 004/137] remove CmlLib.Core.MojangLauncher --- CmlLib/Core/MojangLauncher/MojangAccount.cs | 66 ------------------ .../MojangLauncher/MojangLauncherAccounts.cs | 57 ---------------- .../MojangLauncher/MojangLauncherProfile.cs | 68 ------------------- 3 files changed, 191 deletions(-) delete mode 100644 CmlLib/Core/MojangLauncher/MojangAccount.cs delete mode 100644 CmlLib/Core/MojangLauncher/MojangLauncherAccounts.cs delete mode 100644 CmlLib/Core/MojangLauncher/MojangLauncherProfile.cs diff --git a/CmlLib/Core/MojangLauncher/MojangAccount.cs b/CmlLib/Core/MojangLauncher/MojangAccount.cs deleted file mode 100644 index b1f9903..0000000 --- a/CmlLib/Core/MojangLauncher/MojangAccount.cs +++ /dev/null @@ -1,66 +0,0 @@ -using CmlLib.Core.Auth; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; - -namespace CmlLib.Core.MojangLauncher -{ - public class MojangAccount - { - [JsonProperty("accessToken")] - public string? AccessToken { get; set; } - - [JsonProperty("accessTokenExpiresAt")] - public DateTime? AccessTokenExpiresAt { get; set; } - - [JsonProperty("avatar")] - public string? Avatar { get; set; } - - [JsonProperty("eligibleForMigration")] - public bool EligibleForMigration { get; set; } - - [JsonProperty("hasMultipleProfiles")] - public bool HasMultipleProfiles { get; set; } - - [JsonProperty("legacy")] - public bool Legacy { get; set; } - - [JsonProperty("localId")] - public string? LocalId { get; set; } - - [JsonProperty("minecraftProfile")] - public JObject? MinecraftProfile { get; set; } - - public string? MinecraftProfileId - => MinecraftProfile?["id"]?.ToString(); - - public string? MinecraftProfileName - => MinecraftProfile?["name"]?.ToString(); - - [JsonProperty("persistent")] - public bool Persistent { get; set; } - - [JsonProperty("remoteId")] - public string? RemoteId { get; set; } - - [JsonProperty("type")] - public string? Type { get; set; } - - [JsonProperty("userProperites")] - public List? UserProperites { get; set; } - - [JsonProperty("username")] - public string? Username { get; set; } - - public MSession ToSession() - { - return new MSession - { - Username = this.MinecraftProfileName, - UUID = this.MinecraftProfileId, - AccessToken = this.AccessToken - }; - } - } -} diff --git a/CmlLib/Core/MojangLauncher/MojangLauncherAccounts.cs b/CmlLib/Core/MojangLauncher/MojangLauncherAccounts.cs deleted file mode 100644 index ad097e2..0000000 --- a/CmlLib/Core/MojangLauncher/MojangLauncherAccounts.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using Newtonsoft.Json; - -namespace CmlLib.Core.MojangLauncher -{ - public class MojangLauncherAccounts - { - [JsonProperty("accounts")] - public Dictionary? Accounts { get; set; } - - [JsonProperty("ActiveAccountLocalId")] - public string? ActiveAccountLocalId { get; set; } - - [JsonProperty("mojangClientToken")] - public string? MojangClientToken { get; set; } - - public MojangAccount? GetActiveAccount() - { - if (string.IsNullOrEmpty(ActiveAccountLocalId)) - return null; - - MojangAccount? value = null; - var result = Accounts?.TryGetValue(ActiveAccountLocalId, out value); - if (result == null || result == false) - return null; - - return value; - } - - public void SaveTo(string path) - { - var json = JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore - }); - File.WriteAllText(path, json); - } - - public static MojangLauncherAccounts? FromDefaultPath() - { - var path = Path.Combine(MinecraftPath.GetOSDefaultPath(), "launcher_accounts.json"); - return FromFile(path); - } - - public static MojangLauncherAccounts? FromFile(string path) - { - var content = File.ReadAllText(path); - return FromJson(content); - } - - public static MojangLauncherAccounts? FromJson(string json) - { - return JsonConvert.DeserializeObject(json); - } - } -} diff --git a/CmlLib/Core/MojangLauncher/MojangLauncherProfile.cs b/CmlLib/Core/MojangLauncher/MojangLauncherProfile.cs deleted file mode 100644 index 021a188..0000000 --- a/CmlLib/Core/MojangLauncher/MojangLauncherProfile.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.IO; -using Newtonsoft.Json.Linq; -using CmlLib.Core.Auth; - -namespace CmlLib.Core.MojangLauncher -{ - public class MojangLauncherProfile - { - private MojangLauncherProfile() { } - - public static MojangLauncherProfile LoadFromDefaultPath() - { - return LoadFromFile(Path.Combine(MinecraftPath.GetOSDefaultPath(), "launcher_profiles.json")); - } - - public static MojangLauncherProfile LoadFromFile(string profilePath) - { - var json = File.ReadAllText(profilePath); - return Load(json); - } - - public static MojangLauncherProfile Load(string json) - { - var job = JObject.Parse(json); - - var profile = new MojangLauncherProfile(); - - profile.LauncherVersion = job["launcherVersion"]?["profilesFormat"]?.ToString(); - - var clientToken = job["clientToken"]?.ToString(); - profile.ClientToken = clientToken; - - var auths = job["authenticationDatabase"]; - var sessionList = new List(); - if (auths != null) - { - foreach (var item in auths) - { - var innerObj = item.Children().First(); - - var session = new MSession(); - session.AccessToken = innerObj["accessToken"]?.ToString(); - session.ClientToken = clientToken; - - var profiles = innerObj["profiles"] as JObject; - var firstProfileProperty = profiles?.Properties().First(); - - if (firstProfileProperty != null) - { - session.UUID = firstProfileProperty.Name; - session.Username = firstProfileProperty.Value["displayName"]?.ToString(); - } - - sessionList.Add(session); - } - } - - profile.Sessions = sessionList.ToArray(); - return profile; - } - - public string? LauncherVersion { get; private set; } - public string? ClientToken { get; private set; } - public MSession[]? Sessions { get; private set; } - } -} From e7f8f82534cd09f80fe815229b40f14268b48914 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 15:39:17 +0900 Subject: [PATCH 005/137] remove CmlLib.Core.Mojang --- CmlLib/Core/Mojang/MojangAPI.cs | 57 ---------------- CmlLib/Core/Mojang/MojangServerStatus.cs | 85 ------------------------ CmlLib/Core/Mojang/UserProfile.cs | 8 --- 3 files changed, 150 deletions(-) delete mode 100644 CmlLib/Core/Mojang/MojangAPI.cs delete mode 100644 CmlLib/Core/Mojang/MojangServerStatus.cs delete mode 100644 CmlLib/Core/Mojang/UserProfile.cs diff --git a/CmlLib/Core/Mojang/MojangAPI.cs b/CmlLib/Core/Mojang/MojangAPI.cs deleted file mode 100644 index 2e9fcfc..0000000 --- a/CmlLib/Core/Mojang/MojangAPI.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; -using System.IO; -using System.Net; - -namespace CmlLib.Core.Mojang -{ - // use MojangAPI library! - // https://github.com/CmlLib/MojangAPI - - [Obsolete("use MojangAPI library: https://github.com/CmlLib/MojangAPI")] - public static class MojangAPI - { - private static string readRes(WebResponse res) - { - using var resStream = res.GetResponseStream(); - if (resStream == null) - return ""; - using var sr = new StreamReader(resStream); - return sr.ReadToEnd(); - } - - // entitlements - public static bool CheckGameOwnership(string bearerToken) - { - var url = "https://api.minecraftservices.com/entitlements/mcstore"; - var req = WebRequest.CreateHttp(url); - req.Method = "GET"; - req.Headers["Authorization"] = "Bearer " + bearerToken; - - var res = req.GetResponse(); - var resBody = readRes(res); - - var job = JObject.Parse(resBody); - var itemsCount = (job["items"] as JArray)?.Count ?? 0; - return itemsCount != 0; - } - - public static UserProfile GetProfileUsingToken(string bearerToken) - { - var url = "https://api.minecraftservices.com/minecraft/profile"; - var req = WebRequest.CreateHttp(url); - req.Method = "GET"; - req.Headers["Authorization"] = "Bearer " + bearerToken; - - var res = req.GetResponse(); - var resBody = readRes(res); - var job = JObject.Parse(resBody); - - return new UserProfile - { - UUID = job["id"]?.ToString(), - Name = job["name"]?.ToString(), - }; - } - } -} diff --git a/CmlLib/Core/Mojang/MojangServerStatus.cs b/CmlLib/Core/Mojang/MojangServerStatus.cs deleted file mode 100644 index c1f2ad2..0000000 --- a/CmlLib/Core/Mojang/MojangServerStatus.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using System.Net; - -namespace CmlLib.Core.Mojang -{ - public class MojangServerStatus - { - public enum ServerStatusColor { Green, Yellow, Red, Unknown } - - public static MojangServerStatus GetStatus() - { - // request - string response; - using (var wc = new WebClient()) - { - response = wc.DownloadString("https://status.mojang.com/check"); - } - - // to dict - var jarr = JArray.Parse(response); - var dict = new Dictionary(); - foreach (var token in jarr) - { - var item = token as JObject; - var property = item?.First as JProperty; - if (property == null) - continue; - - ServerStatusColor color = toStatusColor(property.Value.ToString()); - dict.Add(property.Name, color); - } - - // to object - return new MojangServerStatus - { - Minecraft = getColorFromDict(dict, "minecraft.net"), - Session = getColorFromDict(dict, "session.minecraft.net"), - Account = getColorFromDict(dict, "account.mojang.com"), - Auth = getColorFromDict(dict, "auth.mojang.com"), - Skins = getColorFromDict(dict, "skins.minecraft.net"), - AuthServer = getColorFromDict(dict, "authserver.mojang.com"), - SessionServer = getColorFromDict(dict, "sessionserver.mojang.com"), - Api = getColorFromDict(dict, "api.mojang.com"), - Textures = getColorFromDict(dict, "textures.minecraft.net"), - Mojang = getColorFromDict(dict, "mojang.com"), - }; - } - - private static ServerStatusColor getColorFromDict(Dictionary dict, string key) - { - var result = dict.TryGetValue(key, out var color); - if (result) - return color; - else - return ServerStatusColor.Unknown; - } - - private static ServerStatusColor toStatusColor(string str) - { - switch (str) - { - case "green": - return ServerStatusColor.Green; - case "yellow": - return ServerStatusColor.Yellow; - case "red": - return ServerStatusColor.Red; - default: - return ServerStatusColor.Unknown; - } - } - - public ServerStatusColor Minecraft { get; private set; } - public ServerStatusColor Session { get; private set; } - public ServerStatusColor Account { get; private set; } - public ServerStatusColor Auth { get; private set; } - public ServerStatusColor Skins { get; private set; } - public ServerStatusColor AuthServer { get; private set; } - public ServerStatusColor SessionServer { get; private set; } - public ServerStatusColor Api { get; private set; } - public ServerStatusColor Textures { get; private set; } - public ServerStatusColor Mojang { get; private set; } - } -} diff --git a/CmlLib/Core/Mojang/UserProfile.cs b/CmlLib/Core/Mojang/UserProfile.cs deleted file mode 100644 index 4c396f7..0000000 --- a/CmlLib/Core/Mojang/UserProfile.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CmlLib.Core.Mojang -{ - public class UserProfile - { - public string? UUID { get; set; } - public string? Name { get; set; } - } -} \ No newline at end of file From d6ad3163fa92c451286021144541dfe55842d35a Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 15:41:03 +0900 Subject: [PATCH 006/137] add JsonUtil --- CmlLib/Utils/IOUtil.cs | 2 +- CmlLib/Utils/JsonUtil.cs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CmlLib/Utils/IOUtil.cs b/CmlLib/Utils/IOUtil.cs index ad27b40..6eb604c 100644 --- a/CmlLib/Utils/IOUtil.cs +++ b/CmlLib/Utils/IOUtil.cs @@ -111,7 +111,7 @@ public static bool CheckSHA1(string path, string? compareHash) return false; } } - + public static bool CheckFileValidation(string path, string? hash, bool checkHash) { if (!File.Exists(path)) diff --git a/CmlLib/Utils/JsonUtil.cs b/CmlLib/Utils/JsonUtil.cs index 2dbe5a4..83403df 100644 --- a/CmlLib/Utils/JsonUtil.cs +++ b/CmlLib/Utils/JsonUtil.cs @@ -3,12 +3,25 @@ namespace CmlLib.Utils { - public class JsonUtil + public static class JsonUtil { public static JsonSerializerOptions JsonOptions { get; } = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; + + public static JsonElement? SafeGetProperty(this JsonElement jsonElement, string propertyName) + { + if (jsonElement.TryGetProperty(propertyName, out var val)) + return val; + else + return null; + } + + public static string? GetPropertyValue(this JsonElement jsonElement, string propertyName) + { + return SafeGetProperty(jsonElement, propertyName)?.GetString(); + } } } From 0ed06a255610e0b366199aa49cef360eea0a1c04 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 15:42:34 +0900 Subject: [PATCH 007/137] MLogin: use System.Text.Json, System.Net.Http --- CmlLib/Core/Auth/MLogin.cs | 73 +++++++++++++++----------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/CmlLib/Core/Auth/MLogin.cs b/CmlLib/Core/Auth/MLogin.cs index a1b75ca..c2fa838 100644 --- a/CmlLib/Core/Auth/MLogin.cs +++ b/CmlLib/Core/Auth/MLogin.cs @@ -27,7 +27,7 @@ public MLogin(string sessionCacheFilePath, HttpClient client) this.httpClient = client; } - public readonly HttpClient httpClient; + private readonly HttpClient httpClient; public string SessionCacheFilePath { get; private set; } public bool SaveSession { get; set; } = true; @@ -36,7 +36,7 @@ protected virtual string CreateNewClientToken() return Guid.NewGuid().ToString().Replace("-", ""); } - protected async virtual Task createNewSession() + protected virtual async Task createNewSession() { var session = new MSession(); if (SaveSession) @@ -84,9 +84,9 @@ private async Task writeSessionCache(MSession session) } } - protected async Task mojangRequest(string endpoint, object postdata) + protected async Task mojangRequest(string endpoint, object postData) { - var json = JsonSerializer.Serialize(postdata, JsonUtil.JsonOptions); + var json = JsonSerializer.Serialize(postData, JsonUtil.JsonOptions); var res = await httpClient.SendAsync(new HttpRequestMessage { Method = HttpMethod.Post, @@ -118,9 +118,9 @@ private async Task parseSession(string json, string? clientToken { var session = new MSession { - AccessToken = root.GetProperty("accessToken").GetString(), - UUID = profile.GetProperty("id").GetString(), - Username = profile.GetProperty("name").GetString(), + AccessToken = root.GetPropertyValue("accessToken"), + UUID = profile.GetPropertyValue("id"), + Username = profile.GetPropertyValue("name"), UserType = "Mojang", ClientToken = clientToken }; @@ -137,8 +137,8 @@ private MLoginResponse errorHandle(string json) using var jsonDocument = JsonDocument.Parse(json); var root = jsonDocument.RootElement; - string? error = root.GetProperty("error").GetString(); // error type - string? errorMessage = root.GetProperty("message").GetString(); // detail error message + string? error = root.GetPropertyValue("error"); // error type + string? errorMessage = root.GetPropertyValue("message"); // detail error message MLoginResult result; switch (error) @@ -165,25 +165,25 @@ private MLoginResponse errorHandle(string json) } } - public async Task Authenticate(string id, string pw) + public async Task Authenticate(string username, string password) { var sessionCache = await ReadSessionCache(); string? clientToken = sessionCache.ClientToken; - return await Authenticate(id, pw, clientToken); + return await Authenticate(username, password, clientToken); } - public Task Authenticate(string id, string pw, string? clientToken) + public Task Authenticate(string username, string password, string? clientToken) => mojangRequestHandle("authenticate", new { - username = id, - password = pw, - clientToken = clientToken, + username, + password, + clientToken, agent = new { name = "Minecraft", version = 1 } - }); + }, clientToken); public async Task TryAutoLogin() { @@ -199,28 +199,6 @@ public async Task TryAutoLogin(MSession session) return result; } - public async Task TryAutoLoginFromMojangLauncher() - { - var mojangAccounts = MojangLauncher.MojangLauncherAccounts.FromDefaultPath(); - var activeAccount = mojangAccounts?.GetActiveAccount(); - - if (activeAccount == null) - return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); - - return await TryAutoLogin(activeAccount.ToSession()); - } - - public async Task TryAutoLoginFromMojangLauncher(string accountFilePath) - { - var mojangAccounts = MojangLauncher.MojangLauncherAccounts.FromFile(accountFilePath); - var activeAccount = mojangAccounts?.GetActiveAccount(); - - if (activeAccount == null) - return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); - - return await TryAutoLogin(activeAccount.ToSession()); - } - public async Task Refresh() { var session = await ReadSessionCache(); @@ -237,7 +215,7 @@ public Task Refresh(MSession session) id = session.UUID, name = session.Username } - }); + }, session.ClientToken); public async Task Validate() { @@ -245,13 +223,20 @@ public async Task Validate() return await Validate(session); } - public Task Validate(MSession session) - => mojangRequestHandle("validate", new + public async Task Validate(MSession session) + { + var res = await mojangRequest("validate", new { accessToken = session.AccessToken, clientToken = session.ClientToken }); + if (res.IsSuccessStatusCode) + return new MLoginResponse(MLoginResult.Success, session, null, null); + else + return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); + } + public void DeleteTokenFile() { if (File.Exists(SessionCacheFilePath)) @@ -275,12 +260,12 @@ public async Task Invalidate(MSession session) return res.IsSuccessStatusCode; } - public async Task Signout(string id, string pw) + public async Task Signout(string username, string password) { var res = await mojangRequest("signout", new { - username = id, - password = pw + username, + password }); return res.StatusCode == HttpStatusCode.NoContent; // 204 From 51b3524ec40d15811861e8bc243a20e5cc4ae348 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 15:57:55 +0900 Subject: [PATCH 008/137] create MFileMetadata to combine file informations --- CmlLib/Core/Files/MFileMetadata.cs | 32 ++++++++++++++++ CmlLib/Core/Files/MLogConfiguration.cs | 5 +-- CmlLib/Core/Version/MVersion.cs | 51 +++++++++++++------------- 3 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 CmlLib/Core/Files/MFileMetadata.cs diff --git a/CmlLib/Core/Files/MFileMetadata.cs b/CmlLib/Core/Files/MFileMetadata.cs new file mode 100644 index 0000000..d3a6089 --- /dev/null +++ b/CmlLib/Core/Files/MFileMetadata.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Files +{ + public class MFileMetadata + { + public MFileMetadata() + { + + } + + public MFileMetadata(string? id) + { + this.Id = id; + } + + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("sha1")] + public string? Sha1 { get; set; } + + [JsonPropertyName("size")] + public long Size { get; set; } + + [JsonPropertyName("url")] + public string? Url { get; set; } + } +} diff --git a/CmlLib/Core/Files/MLogConfiguration.cs b/CmlLib/Core/Files/MLogConfiguration.cs index dad216a..71ef097 100644 --- a/CmlLib/Core/Files/MLogConfiguration.cs +++ b/CmlLib/Core/Files/MLogConfiguration.cs @@ -2,11 +2,8 @@ { public class MLogConfiguration { + public MFileMetadata? LogFile { get; set; } public string? Argument { get; set; } - public string? Id { get; set; } - public string? Sha1 { get; set; } - public string? Size { get; set; } - public string? Url { get; set; } public string? Type { get; set; } } } diff --git a/CmlLib/Core/Version/MVersion.cs b/CmlLib/Core/Version/MVersion.cs index 3d78d54..154bfd4 100644 --- a/CmlLib/Core/Version/MVersion.cs +++ b/CmlLib/Core/Version/MVersion.cs @@ -1,5 +1,4 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.Files; +using CmlLib.Core.Files; using System.Linq; namespace CmlLib.Core.Version @@ -16,15 +15,12 @@ public MVersion(string id) public string Id { get; set; } - public string? AssetId { get; set; } - public string? AssetUrl { get; set; } - public string? AssetHash { get; set; } + public MFileMetadata? Assets { get; set; } public string? JavaVersion { get; set; } public string? JavaBinaryPath { get; set; } public string? Jar { get; set; } - public string? ClientDownloadUrl { get; set; } - public string? ClientHash { get; set; } + public MFileMetadata? Client { get; set; } public MLibrary[]? Libraries { get; set; } public string? MainClass { get; set; } public string? MinecraftArguments { get; set; } @@ -48,28 +44,38 @@ public void InheritFrom(MVersion parentVersion) // Overloads - if (nc(AssetId)) - AssetId = parentVersion.AssetId; + if (Assets == null) + Assets = parentVersion.Assets; + else + { + if (string.IsNullOrEmpty(Assets.Id)) + Assets.Id = parentVersion.Assets?.Id; - if (nc(AssetUrl)) - AssetUrl = parentVersion.AssetUrl; + if (string.IsNullOrEmpty(Assets.Url)) + Assets.Url = parentVersion.Assets?.Url; - if (nc(AssetHash)) - AssetHash = parentVersion.AssetHash; + if (string.IsNullOrEmpty(Assets.Sha1)) + Assets.Sha1 = parentVersion.Assets?.Sha1; + } - if (nc(ClientDownloadUrl)) - ClientDownloadUrl = parentVersion.ClientDownloadUrl; + if (Client == null) + Client = parentVersion.Client; + else + { + if (string.IsNullOrEmpty(Client.Url)) + Client.Url = parentVersion.Client?.Url; - if (nc(ClientHash)) - ClientHash = parentVersion.ClientHash; + if (string.IsNullOrEmpty(Client.Sha1)) + Client.Sha1 = parentVersion.Client?.Sha1; + } - if (nc(MainClass)) + if (string.IsNullOrEmpty(MainClass)) MainClass = parentVersion.MainClass; - if (nc(MinecraftArguments)) + if (string.IsNullOrEmpty(MinecraftArguments)) MinecraftArguments = parentVersion.MinecraftArguments; - if (nc(JavaVersion)) + if (string.IsNullOrEmpty(JavaVersion)) JavaVersion = parentVersion.JavaVersion; if (LoggingClient == null) @@ -106,11 +112,6 @@ public void InheritFrom(MVersion parentVersion) } } - private static bool nc(string? t) // check null string - { - return string.IsNullOrEmpty(t); - } - public override string ToString() { return this.Id; From 269de9e1d87c1baade2ece4d6580d4dbb3ce6a34 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 15:59:23 +0900 Subject: [PATCH 009/137] update MVersionParser, MLibraryParser to use System.Text.Json --- CmlLib/Core/Files/MLibraryParser.cs | 57 ++++++----- CmlLib/Core/Version/MVersionParser.cs | 133 +++++++++++++------------- 2 files changed, 101 insertions(+), 89 deletions(-) diff --git a/CmlLib/Core/Files/MLibraryParser.cs b/CmlLib/Core/Files/MLibraryParser.cs index 057e81c..b6450f1 100644 --- a/CmlLib/Core/Files/MLibraryParser.cs +++ b/CmlLib/Core/Files/MLibraryParser.cs @@ -1,6 +1,8 @@ -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using CmlLib.Utils; namespace CmlLib.Core.Files { @@ -8,49 +10,51 @@ public class MLibraryParser { public bool CheckOSRules { get; set; } = true; - public MLibrary[]? ParseJsonObject(JObject item) + public MLibrary[]? ParseJsonObject(JsonElement item) { try { - var list = new List(2); + var list = new List(); - var name = item["name"]?.ToString(); + var name = item.GetPropertyValue("name"); var isRequire = true; // check rules array - var rules = item["rules"]; - if (CheckOSRules && rules != null) - isRequire = MRule.CheckOSRequire((JArray)rules); + if (CheckOSRules && item.TryGetProperty("rules", out var rules)) + isRequire = MRule.CheckOSRequire(rules); // forge clientreq - var req = item["clientreq"]?.ToString(); + var req = item.GetPropertyValue("clientreq"); if (req != null && req.ToLower() != "true") isRequire = false; // support TLauncher - var artifact = item["artifact"] ?? item["downloads"]?["artifact"]; - var classifiers = item["classifies"] ?? item["downloads"]?["classifiers"]; - var natives = item["natives"]; + var artifact = item.SafeGetProperty("artifact") + ?? item.SafeGetProperty("downloads")?.SafeGetProperty("artifact"); + var classifiers = item.SafeGetProperty("classifies") + ?? item.SafeGetProperty("downloads")?.SafeGetProperty("classifiers"); // NATIVE library + var natives = item.SafeGetProperty("natives"); if (natives != null) { - var nativeId = natives[MRule.OSName]?.ToString().Replace("${arch}", MRule.Arch); + var nativeId = natives?.GetPropertyValue(MRule.OSName)? + .Replace("${arch}", MRule.Arch); if (classifiers != null && nativeId != null) { - JToken? lObj = classifiers[nativeId] ?? classifiers[MRule.OSName]; + var lObj = classifiers?.SafeGetProperty(nativeId) ?? classifiers?.SafeGetProperty(MRule.OSName); if (lObj != null) - list.Add(createMLibrary(name, nativeId, isRequire, (JObject)lObj)); + list.Add(createMLibrary(name, nativeId, isRequire, lObj)); } else - list.Add(createMLibrary(name, nativeId, isRequire, new JObject())); + list.Add(createMLibrary(name, nativeId, isRequire, null)); } // COMMON library if (artifact != null) { - MLibrary obj = createMLibrary(name, "", isRequire, (JObject)artifact); + var obj = createMLibrary(name, "", isRequire, artifact); list.Add(obj); } @@ -70,27 +74,30 @@ public class MLibraryParser } } - private MLibrary createMLibrary(string? name, string? nativeId, bool require, JObject job) + private MLibrary createMLibrary(string? name, string? nativeId, bool require, JsonElement? element) { - string? path = job["path"]?.ToString(); + string? path = element?.GetPropertyValue("path"); if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(name)) path = PackageName.Parse(name).GetPath(nativeId); - var hash = job["sha1"] ?? job["checksums"]?[0]; + var hash = element?.GetPropertyValue("sha1"); + if (string.IsNullOrEmpty(hash)) + { + var checksums = element?.SafeGetProperty("checksums")?.EnumerateArray(); + hash = checksums?.FirstOrDefault().GetString(); + } long size = 0; - string? sizeStr = job["size"]?.ToString(); - if (!string.IsNullOrEmpty(sizeStr)) - long.TryParse(sizeStr, out size); + element?.SafeGetProperty("size")?.TryGetInt64(out size); return new MLibrary { - Hash = hash?.ToString(), + Hash = hash, IsNative = !string.IsNullOrEmpty(nativeId), Name = name, Path = path, Size = size, - Url = job["url"]?.ToString(), + Url = element?.GetPropertyValue("url"), IsRequire = require }; } diff --git a/CmlLib/Core/Version/MVersionParser.cs b/CmlLib/Core/Version/MVersionParser.cs index 5ad8095..63ae963 100644 --- a/CmlLib/Core/Version/MVersionParser.cs +++ b/CmlLib/Core/Version/MVersionParser.cs @@ -1,8 +1,9 @@ using CmlLib.Core.Files; -using Newtonsoft.Json.Linq; +using CmlLib.Utils; using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; namespace CmlLib.Core.Version { @@ -13,51 +14,50 @@ public static MVersion ParseFromFile(string path) string json = File.ReadAllText(path); return ParseFromJson(json); } - + public static MVersion ParseFromJson(string json) + { + using var jsonDocument = JsonDocument.Parse(json); + return ParseFromJson(jsonDocument); + } + + public static MVersion ParseFromJson(JsonDocument document) { try { - var job = JObject.Parse(json); - + var root = document.RootElement; + // id - var id = job["id"]?.ToString(); - if (string.IsNullOrEmpty(id)) + if (!root.TryGetProperty("id", out var idElement)) throw new MVersionParseException("Empty version id"); + var id = idElement.GetString() + ?? throw new MVersionParseException("Empty version id"); + var version = new MVersion(id); // javaVersion - version.JavaVersion = job["javaVersion"]?["component"]?.ToString(); - + version.JavaVersion = root.SafeGetProperty("javaVersion")?.SafeGetProperty("component")?.GetString(); + // assets - var assetindex = job["assetIndex"]; - var assets = job["assets"]; - if (assetindex != null) - { - version.AssetId = assetindex["id"]?.ToString(); - version.AssetUrl = assetindex["url"]?.ToString(); - version.AssetHash = assetindex["sha1"]?.ToString(); - } - else if (assets != null) - version.AssetId = assets.ToString(); + if (root.TryGetProperty("assetIndex", out var assetIndex)) + version.Assets = assetIndex.Deserialize(JsonUtil.JsonOptions); + else if (root.TryGetProperty("assets", out var assets)) + version.Assets = new MFileMetadata(assets.GetString()); // client jar - var client = job["downloads"]?["client"]; - if (client != null) - { - version.ClientDownloadUrl = client["url"]?.ToString(); - version.ClientHash = client["sha1"]?.ToString(); - } + var client = root.SafeGetProperty("downloads")?.SafeGetProperty("client"); + if (client.HasValue) + version.Client = client.Value.Deserialize(JsonUtil.JsonOptions); // libraries - if (job["libraries"] is JArray libJArr) + if (root.TryGetProperty("libraries", out var libProp) && libProp.ValueKind == JsonValueKind.Array) { - var libList = new List(libJArr.Count); + var libList = new List(); var libParser = new MLibraryParser(); - foreach (var item in libJArr) + foreach (var item in libProp.EnumerateArray()) { - var libs = libParser.ParseJsonObject((JObject) item); + var libs = libParser.ParseJsonObject(item); if (libs != null) libList.AddRange(libs); } @@ -66,50 +66,44 @@ public static MVersion ParseFromJson(string json) } // mainClass - version.MainClass = job["mainClass"]?.ToString(); + version.MainClass = root.GetPropertyValue("mainClass"); // argument - version.MinecraftArguments = job["minecraftArguments"]?.ToString(); + version.MinecraftArguments = root.GetPropertyValue("minecraftArguments"); - var ag = job["arguments"]; - if (ag != null) + if (root.TryGetProperty("arguments", out var ag)) { - if (ag["game"] is JArray gameArg) + if (ag.TryGetProperty("game", out var gameArg) && gameArg.ValueKind == JsonValueKind.Array) version.GameArguments = argParse(gameArg); - if (ag["jvm"] is JArray jvmArg) + if (ag.TryGetProperty("jvm", out var jvmArg) && jvmArg.ValueKind == JsonValueKind.Array) version.JvmArguments = argParse(jvmArg); } // metadata - version.ReleaseTime = job["releaseTime"]?.ToString(); + version.ReleaseTime = root.GetPropertyValue("releaseTime");; - var type = job["type"]?.ToString(); + var type = root.GetPropertyValue("type"); version.TypeStr = type; version.Type = MVersionTypeConverter.FromString(type); // inherits - if (job["inheritsFrom"] != null) - { - version.IsInherited = true; - version.ParentVersionId = job["inheritsFrom"]?.ToString(); - } + version.ParentVersionId = root.GetPropertyValue("inheritsFrom"); + version.IsInherited = string.IsNullOrEmpty(version.ParentVersionId); - version.Jar = job["jar"]?.ToString(); + version.Jar = root.GetPropertyValue("jar"); if (string.IsNullOrEmpty(version.Jar)) version.Jar = version.Id; // logging - var loggingClient = job["logging"]?["client"]; + var loggingClient = root.SafeGetProperty("logging")?.SafeGetProperty("client"); if (loggingClient != null) { + var logFile = loggingClient.Value.SafeGetProperty("file")?.Deserialize(JsonUtil.JsonOptions); version.LoggingClient = new MLogConfiguration { - Id = loggingClient["file"]?["id"]?.ToString(), - Sha1 = loggingClient["file"]?["sha1"]?.ToString(), - Size = loggingClient["file"]?["size"]?.ToString(), - Url = loggingClient["file"]?["url"]?.ToString(), - Type = loggingClient["type"]?.ToString(), - Argument = loggingClient["argument"]?.ToString() + LogFile = logFile, + Type = loggingClient.Value.GetPropertyValue("type"), + Argument = loggingClient.Value.GetPropertyValue("argument") }; } @@ -126,37 +120,48 @@ public static MVersion ParseFromJson(string json) } // TODO: create argument object - private static string[] argParse(JArray arr) + private static string[] argParse(JsonElement arr) { - var strList = new List(arr.Count); + var strList = new List(); - foreach (var item in arr) + foreach (var item in arr.EnumerateArray()) { - if (item is JObject) + if (item.ValueKind == JsonValueKind.Object) { bool allow = true; - var rules = item["rules"] as JArray ?? item["compatibilityRules"] as JArray; - if (rules != null) - allow = MRule.CheckOSRequire(rules); + var rules = item.SafeGetProperty("rules"); + if (rules == null || rules.Value.ValueKind != JsonValueKind.Array) + rules = item.SafeGetProperty("compatibilityRules"); - var value = item["value"] ?? item["values"]; + if (rules != null) + allow = MRule.CheckOSRequire(rules.Value); - if (allow && value != null) + if (allow) { - if (value is JArray) + var value = item.SafeGetProperty("value") ?? item.SafeGetProperty("values"); + if (value != null) { - foreach (var str in value) + if (value.Value.ValueKind == JsonValueKind.Array) { - strList.Add(str.ToString()); + foreach (var strProp in value.Value.EnumerateArray()) + { + var str = strProp.GetString(); + if (!string.IsNullOrEmpty(str)) + strList.Add(str); + } } + else + strList.Add(value.ToString()); } - else - strList.Add(value.ToString()); } } else - strList.Add(item.ToString()); + { + var value = item.GetString(); + if (!string.IsNullOrEmpty(value)) + strList.Add(value); + } } return strList.ToArray(); From 1bbcf58130e06721da1aa2cd8d927289624b101f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:01:22 +0900 Subject: [PATCH 010/137] update MLaunch as MVersion class was changed --- CmlLib/Core/Launcher/MLaunch.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CmlLib/Core/Launcher/MLaunch.cs b/CmlLib/Core/Launcher/MLaunch.cs index 9ee0ab8..1f2cc41 100644 --- a/CmlLib/Core/Launcher/MLaunch.cs +++ b/CmlLib/Core/Launcher/MLaunch.cs @@ -96,12 +96,12 @@ public string[] CreateArg() { "version_name" , version.Id }, { "game_directory" , minecraftPath.BasePath }, { "assets_root" , minecraftPath.Assets }, - { "assets_index_name", version.AssetId ?? "legacy" }, + { "assets_index_name", version.Assets?.Id ?? "legacy" }, { "auth_uuid" , session.UUID }, { "auth_access_token", session.AccessToken }, { "user_properties" , "{}" }, { "user_type" , session.UserType ?? "Mojang" }, - { "game_assets" , minecraftPath.GetAssetLegacyPath(version.AssetId ?? "legacy") }, + { "game_assets" , minecraftPath.GetAssetLegacyPath(version.Assets?.Id ?? "legacy") }, { "auth_session" , session.AccessToken }, { "version_type" , useNotNull(launchOption.VersionType, version.TypeStr) } }; @@ -142,7 +142,7 @@ public string[] CreateArg() if (!string.IsNullOrEmpty(version.LoggingClient?.Argument)) args.Add(Mapper.Interpolation(version.LoggingClient?.Argument, new Dictionary() { - { "path", minecraftPath.GetLogConfigFilePath(version.LoggingClient?.Id ?? version.Id) } + { "path", minecraftPath.GetLogConfigFilePath(version.LoggingClient?.LogFile?.Id ?? version.Id) } }, true)); // main class From ddb95015e82016522661a2e42f5ab4431df90e55 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:03:06 +0900 Subject: [PATCH 011/137] MVersionMetadata: remove sync methods and use System.Text.Json --- .../VersionMetadata/LocalVersionMetadata.cs | 9 ---- .../Core/VersionMetadata/MVersionMetadata.cs | 23 +++++----- .../VersionMetadata/StringVersionMetadata.cs | 42 ++++--------------- .../VersionMetadata/WebVersionMetadata.cs | 21 +++------- 4 files changed, 24 insertions(+), 71 deletions(-) diff --git a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs b/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs index c7040e5..b86171d 100644 --- a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs @@ -12,15 +12,6 @@ public LocalVersionMetadata(string id) : base(id) IsLocalVersion = true; } - protected override string ReadMetadata() - { - if (string.IsNullOrEmpty(Path)) - throw new InvalidOperationException("Path property was null"); - - // FileNotFoundException will be thrown if Path does not exist. - return File.ReadAllText(Path); - } - protected override Task ReadMetadataAsync() { if (string.IsNullOrEmpty(Path)) diff --git a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs b/CmlLib/Core/VersionMetadata/MVersionMetadata.cs index 592c658..2454f97 100644 --- a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/MVersionMetadata.cs @@ -1,27 +1,30 @@ using System; +using System.Text.Json.Serialization; using System.Threading.Tasks; using CmlLib.Core.Version; -using Newtonsoft.Json; namespace CmlLib.Core.VersionMetadata { public abstract class MVersionMetadata { - protected MVersionMetadata(string id) + protected MVersionMetadata(string name) { - this.Name = id; + this.Name = name; } public bool IsLocalVersion { get; set; } - [JsonProperty("id")] public string Name { get; private set; } + [JsonPropertyName("id")] + public string Name { get; private set; } - [JsonProperty("type")] public string? Type { get; set; } + [JsonPropertyName("type")] + public string? Type { get; set; } public MVersionType MType { get; set; } - [JsonProperty("releaseTime")] public string? ReleaseTimeStr { get; set; } - + [JsonPropertyName("releaseTime")] + public string? ReleaseTimeStr { get; set; } + [JsonIgnore] public DateTime? ReleaseTime { get @@ -32,7 +35,8 @@ public DateTime? ReleaseTime } } - [JsonProperty("url")] public string? Path { get; set; } + [JsonPropertyName("url")] + public string? Path { get; set; } public override bool Equals(object? obj) { @@ -59,11 +63,8 @@ public override int GetHashCode() return Name.GetHashCode(); } - public abstract MVersion GetVersion(); - public abstract MVersion GetVersion(MinecraftPath savePath); public abstract Task GetVersionAsync(); public abstract Task GetVersionAsync(MinecraftPath savePath); - public abstract void Save(MinecraftPath path); public abstract Task SaveAsync(MinecraftPath path); } } diff --git a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs index 3a2c30a..7c0d97d 100644 --- a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs @@ -8,12 +8,11 @@ namespace CmlLib.Core.VersionMetadata { public abstract class StringVersionMetadata : MVersionMetadata { - protected StringVersionMetadata(string id) : base(id) + protected StringVersionMetadata(string name) : base(name) { } - protected abstract string ReadMetadata(); protected abstract Task ReadMetadataAsync(); private string? prepareSaveMetadata(MinecraftPath path) @@ -39,13 +38,6 @@ protected StringVersionMetadata(string id) : base(id) return metadataPath; } - - protected virtual void SaveMetadata(string metadata, MinecraftPath path) - { - var metadataPath = prepareSaveMetadata(path); - if (!string.IsNullOrEmpty(metadataPath)) - File.WriteAllText(metadataPath, metadata); - } protected virtual Task SaveMetadataAsync(string metadata, MinecraftPath path) { @@ -56,44 +48,24 @@ protected virtual Task SaveMetadataAsync(string metadata, MinecraftPath path) return Task.CompletedTask; } - // note: sync flag - // https://docs.microsoft.com/en-us/archive/msdn-magazine/2015/july/async-programming-brownfield-async-development#the-flag-argument-hack - private async Task getAsync(MinecraftPath? savePath, bool parse, bool sync) + private async Task getAsync(MinecraftPath? savePath, bool parse) { string metadataJson; - if (sync) - metadataJson = ReadMetadata(); - else - metadataJson = await ReadMetadataAsync().ConfigureAwait(false); + metadataJson = await ReadMetadataAsync().ConfigureAwait(false); if (savePath != null) - { - if (sync) - SaveMetadata(metadataJson, savePath); - else - await SaveMetadataAsync(metadataJson, savePath).ConfigureAwait(false); - } + await SaveMetadataAsync(metadataJson, savePath).ConfigureAwait(false); return parse ? MVersionParser.ParseFromJson(metadataJson) : null; } - - public override MVersion GetVersion() - => getAsync(null, true, true).GetAwaiter().GetResult()!; - - - public override MVersion GetVersion(MinecraftPath savePath) - => getAsync(savePath, true, true).GetAwaiter().GetResult()!; public override Task GetVersionAsync() - => getAsync(null, true, false)!; + => getAsync(null, true)!; public override Task GetVersionAsync(MinecraftPath savePath) - => getAsync(savePath, true, false)!; - - public override void Save(MinecraftPath path) - => getAsync(path, false, true).GetAwaiter().GetResult(); + => getAsync(savePath, true)!; public override Task SaveAsync(MinecraftPath path) - => getAsync(path, false, false); + => getAsync(path, false); } } \ No newline at end of file diff --git a/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs b/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs index 1d3fb81..2a1940d 100644 --- a/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs @@ -1,34 +1,23 @@ -using System; -using System.Net; +using CmlLib.Utils; +using System; using System.Threading.Tasks; namespace CmlLib.Core.VersionMetadata { public class WebVersionMetadata : StringVersionMetadata { - public WebVersionMetadata(string id) : base(id) + public WebVersionMetadata(string name) : base(name) { IsLocalVersion = false; } - protected override string ReadMetadata() - { - if (string.IsNullOrEmpty(Path)) - throw new InvalidOperationException("Path property was null"); - - // below code will throw ArgumentNullException when Path is null - using var wc = new WebClient(); - return wc.DownloadString(Path); - } - protected override async Task ReadMetadataAsync() { if (string.IsNullOrEmpty(Path)) throw new InvalidOperationException("Path property was null"); - + // below code will throw ArgumentNullException when Path is null - using var wc = new WebClient(); - return await wc.DownloadStringTaskAsync(Path) + return await HttpUtil.HttpClient.GetStringAsync(Path) .ConfigureAwait(false); } } From fa2f69fd8c6258c86be38ba32a07f9851ab88292 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:03:59 +0900 Subject: [PATCH 012/137] VersionLoader: remove sync methods and use System.Text.Json, System.Net.Http --- .../VersionLoader/DefaultVersionLoader.cs | 32 ++++++----------- CmlLib/Core/VersionLoader/IVersionLoader.cs | 1 - .../Core/VersionLoader/MojangVersionLoader.cs | 35 +++++++------------ 3 files changed, 23 insertions(+), 45 deletions(-) diff --git a/CmlLib/Core/VersionLoader/DefaultVersionLoader.cs b/CmlLib/Core/VersionLoader/DefaultVersionLoader.cs index 27d5b02..300bdd8 100644 --- a/CmlLib/Core/VersionLoader/DefaultVersionLoader.cs +++ b/CmlLib/Core/VersionLoader/DefaultVersionLoader.cs @@ -5,42 +5,30 @@ namespace CmlLib.Core.VersionLoader { public class DefaultVersionLoader : IVersionLoader { + private readonly MinecraftPath minecraftPath; + public DefaultVersionLoader(MinecraftPath path) { - MinecraftPath = path; + minecraftPath = path; } - protected MinecraftPath MinecraftPath; - - public MVersionCollection GetVersionMetadatas() + public async Task GetVersionMetadatasAsync() { - var localVersionLoader = new LocalVersionLoader(MinecraftPath); + var localVersionLoader = new LocalVersionLoader(minecraftPath); var mojangVersionLoader = new MojangVersionLoader(); - var mojangVersions = mojangVersionLoader.GetVersionMetadatas(); - var localVersions = localVersionLoader.GetVersionMetadatas(); + var mojangVersions = await mojangVersionLoader.GetVersionMetadatasAsync() + .ConfigureAwait(false); + var localVersions = await localVersionLoader.GetVersionMetadatasAsync() + .ConfigureAwait(false); //below code could break the order of version list //mojangVersions.Merge(localVersions); - + // normal order: local versions before mojang versions // local 1.16.~~ // local 1.15.~~ // mojang 1.14.~~ - - localVersions.Merge(mojangVersions); - return localVersions; - } - - public async Task GetVersionMetadatasAsync() - { - var localVersionLoader = new LocalVersionLoader(MinecraftPath); - var mojangVersionLoader = new MojangVersionLoader(); - - var mojangVersions = await mojangVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - var localVersions = await localVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); localVersions.Merge(mojangVersions); return localVersions; diff --git a/CmlLib/Core/VersionLoader/IVersionLoader.cs b/CmlLib/Core/VersionLoader/IVersionLoader.cs index 1ff4673..f66f217 100644 --- a/CmlLib/Core/VersionLoader/IVersionLoader.cs +++ b/CmlLib/Core/VersionLoader/IVersionLoader.cs @@ -6,6 +6,5 @@ namespace CmlLib.Core.VersionLoader public interface IVersionLoader { Task GetVersionMetadatasAsync(); - MVersionCollection GetVersionMetadatas(); } } diff --git a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs b/CmlLib/Core/VersionLoader/MojangVersionLoader.cs index 87f3ea4..9553e01 100644 --- a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs +++ b/CmlLib/Core/VersionLoader/MojangVersionLoader.cs @@ -1,25 +1,17 @@ using CmlLib.Core.Version; -using Newtonsoft.Json.Linq; using System.Collections.Generic; -using System.Net; using System.Threading.Tasks; using CmlLib.Core.VersionMetadata; +using CmlLib.Utils; +using System.Text.Json; namespace CmlLib.Core.VersionLoader { public class MojangVersionLoader : IVersionLoader { - public MVersionCollection GetVersionMetadatas() - { - using var wc = new WebClient(); - var res = wc.DownloadString(MojangServer.Version); - return parseList(res); - } - public async Task GetVersionMetadatasAsync() { - using var wc = new WebClient(); - var res = await wc.DownloadStringTaskAsync(MojangServer.Version); + var res = await HttpUtil.HttpClient.GetStringAsync(MojangServer.Version); return parseList(res); } @@ -31,26 +23,25 @@ private MVersionCollection parseList(string res) MVersionMetadata? latestRelease = null; MVersionMetadata? latestSnapshot = null; - - var jobj = JObject.Parse(res); - var jarr = jobj["versions"] as JArray; - var latest = jobj["latest"]; - if (latest != null) + using var jsonDocument = JsonDocument.Parse(res); + var root = jsonDocument.RootElement; + + if (root.TryGetProperty("latest", out var latest)) { - latestReleaseId = latest["release"]?.ToString(); - latestSnapshotId = latest["snapshot"]?.ToString(); + latestReleaseId = latest.GetPropertyValue("release"); + latestSnapshotId = latest.GetPropertyValue("snapshot"); } bool checkLatestRelease = !string.IsNullOrEmpty(latestReleaseId); bool checkLatestSnapshot = !string.IsNullOrEmpty(latestSnapshotId); - var arr = new List(jarr?.Count ?? 0); - if (jarr != null) + var arr = new List(); + if (root.TryGetProperty("versions", out var versions) && versions.ValueKind == JsonValueKind.Array) { - foreach (var t in jarr) + foreach (var t in versions.EnumerateArray()) { - var obj = t.ToObject(); + var obj = t.Deserialize(); if (obj == null) continue; From d8dea98606ecd3ae36778ac4500100bfefaa4725 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:04:39 +0900 Subject: [PATCH 013/137] MVersionCollection: remove sync method: GetVersion --- CmlLib/Core/Version/MVersionCollection.cs | 33 ----------------------- 1 file changed, 33 deletions(-) diff --git a/CmlLib/Core/Version/MVersionCollection.cs b/CmlLib/Core/Version/MVersionCollection.cs index 127fcdb..0237579 100644 --- a/CmlLib/Core/Version/MVersionCollection.cs +++ b/CmlLib/Core/Version/MVersionCollection.cs @@ -68,15 +68,6 @@ public MVersionMetadata[] ToArray(MVersionSortOption option) var sorter = new MVersionMetadataSorter(option); return sorter.Sort(this); } - - public virtual MVersion GetVersion(string name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - var versionMetadata = GetVersionMetadata(name); - return GetVersion(versionMetadata); - } public virtual Task GetVersionAsync(string name) { @@ -86,30 +77,6 @@ public virtual Task GetVersionAsync(string name) var versionMetadata = GetVersionMetadata(name); return GetVersionAsync(versionMetadata); } - - public virtual MVersion GetVersion(MVersionMetadata versionMetadata) - { - if (versionMetadata == null) - throw new ArgumentNullException(nameof(versionMetadata)); - - MVersion startVersion; - if (MinecraftPath == null) - startVersion = versionMetadata.GetVersion(); - else - startVersion = versionMetadata.GetVersion(MinecraftPath); - - if (startVersion.IsInherited && !string.IsNullOrEmpty(startVersion.ParentVersionId)) - { - if (startVersion.ParentVersionId == startVersion.Id) // prevent StackOverFlowException - throw new InvalidDataException( - "Invalid version json file : inheritFrom property is equal to id property."); - - var baseVersion = GetVersion(startVersion.ParentVersionId); - startVersion.InheritFrom(baseVersion); - } - - return startVersion; - } public virtual async Task GetVersionAsync(MVersionMetadata versionMetadata) { From 323cdd6bd052e7b9f5ec76b2d90f2c8185b2113d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:06:18 +0900 Subject: [PATCH 014/137] update MRule to use System.Text.Json --- CmlLib/Core/MRule.cs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/CmlLib/Core/MRule.cs b/CmlLib/Core/MRule.cs index e413186..bdf552e 100644 --- a/CmlLib/Core/MRule.cs +++ b/CmlLib/Core/MRule.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json.Linq; -using System; +using System; using System.Runtime.InteropServices; +using System.Text.Json; namespace CmlLib.Core { @@ -45,26 +45,27 @@ private static string getOSName() #endif } - public static bool CheckOSRequire(JArray arr) + public static bool CheckOSRequire(JsonElement arr) { - var require = true; + if (arr.ValueKind != JsonValueKind.Array) + throw new ArgumentException("input JsonElement was not array"); - foreach (var token in arr) + var require = true; + foreach (var token in arr.EnumerateArray()) { - var job = token as JObject; - if (job == null) + if (token.ValueKind != JsonValueKind.Object) continue; bool action = true; // true : "allow", false : "disallow" bool containCurrentOS = true; // if 'os' JArray contains current os name - foreach (var item in job) + foreach (var item in token.EnumerateObject()) { - if (item.Key == "action") - action = (item.Value?.ToString() == "allow"); - else if (item.Key == "os") - containCurrentOS = checkOSContains(item.Value as JObject); - else if (item.Key == "features") // etc + if (item.Name == "action") + action = (item.Value.GetString() == "allow"); + else if (item.Name == "os") + containCurrentOS = checkOSContains(item.Value); + else if (item.Name == "features") // etc return false; } @@ -79,14 +80,14 @@ public static bool CheckOSRequire(JArray arr) return require; } - private static bool checkOSContains(JObject? job) + private static bool checkOSContains(JsonElement? element) { - if (job == null) + if (element == null) return false; - foreach (var os in job) + foreach (var os in element.Value.EnumerateObject()) { - if (os.Key == "name" && os.Value?.ToString() == OSName) + if (os.Name == "name" && os.Value.GetString() == OSName) return true; } return false; From 9f93d5f76fb3f011eb86c6f02fabe5dc63d5ae38 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:07:14 +0900 Subject: [PATCH 015/137] update ClientChecker: MVersion class structure was changed --- CmlLib/Core/Files/ClientChecker.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CmlLib/Core/Files/ClientChecker.cs b/CmlLib/Core/Files/ClientChecker.cs index b7c2eb2..f305733 100644 --- a/CmlLib/Core/Files/ClientChecker.cs +++ b/CmlLib/Core/Files/ClientChecker.cs @@ -39,23 +39,24 @@ public sealed class ClientChecker : IFileChecker private DownloadFile? checkClientFile(MinecraftPath path, MVersion version) { - if (string.IsNullOrEmpty(version.ClientDownloadUrl) - || string.IsNullOrEmpty(version.Jar)) + var id = version.Jar; + var url = version.Client?.Url; + + if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(id)) return null; + + var clientPath = path.GetVersionJarPath(id); - string id = version.Jar; - string clientPath = path.GetVersionJarPath(id); - - if (!IOUtil.CheckFileValidation(clientPath, version.ClientHash, CheckHash)) + if (!IOUtil.CheckFileValidation(clientPath, version.Client?.Sha1, CheckHash)) { - return new DownloadFile(clientPath, version.ClientDownloadUrl) + return new DownloadFile(clientPath, url) { Type = MFile.Minecraft, Name = id }; } - else - return null; + + return null; } } } From 5f93204f336d8372abfe90c0a43c2c8a33e99e69 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:07:47 +0900 Subject: [PATCH 016/137] update LogChecker: MVersion class structure was changed --- CmlLib/Core/Files/LogChecker.cs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/CmlLib/Core/Files/LogChecker.cs b/CmlLib/Core/Files/LogChecker.cs index 39cccb1..7e186a9 100644 --- a/CmlLib/Core/Files/LogChecker.cs +++ b/CmlLib/Core/Files/LogChecker.cs @@ -30,7 +30,8 @@ public sealed class LogChecker : IFileChecker DownloadFile? result; - progress?.Report(new DownloadFileChangedEventArgs(MFile.Others, this, version.LoggingClient.Id, 1, 0)); + progress?.Report(new DownloadFileChangedEventArgs( + MFile.Others, this, version.LoggingClient?.LogFile?.Id, 1, 0)); if (async) { result = await Task.Run(() => internalCheckLogFile(path, version)) @@ -40,7 +41,8 @@ public sealed class LogChecker : IFileChecker { result = internalCheckLogFile(path, version); } - progress?.Report(new DownloadFileChangedEventArgs(MFile.Others, this, version.LoggingClient.Id, 1, 1)); + progress?.Report(new DownloadFileChangedEventArgs( + MFile.Others, this, version.LoggingClient?.LogFile?.Id, 1, 1)); if (result == null) return null; @@ -50,22 +52,26 @@ public sealed class LogChecker : IFileChecker private DownloadFile? internalCheckLogFile(MinecraftPath path, MVersion version) { - if (version.LoggingClient == null || string.IsNullOrEmpty(version.LoggingClient.Url)) + if (version.LoggingClient == null) return null; + + var url = version.LoggingClient?.LogFile?.Url; + if (string.IsNullOrEmpty(url)) + return null; + + var id = version.LoggingClient?.LogFile?.Id ?? version.Id; + var clientPath = path.GetLogConfigFilePath(id); - string id = version.LoggingClient.Id ?? version.Id; - string clientPath = path.GetLogConfigFilePath(id); - - if (!IOUtil.CheckFileValidation(clientPath, version.LoggingClient.Sha1, CheckHash)) + if (!IOUtil.CheckFileValidation(clientPath, version.LoggingClient?.LogFile?.Sha1, CheckHash)) { - return new DownloadFile(clientPath, version.LoggingClient.Url) + return new DownloadFile(clientPath, url) { Type = MFile.Others, - Name = version.LoggingClient.Id + Name = id }; } - else - return null; + + return null; } } } From fbf67e1cd277edd0871ad4ef5c8ce395bfea55a0 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:08:30 +0900 Subject: [PATCH 017/137] update AssetChecker: System.Text.Json, System.Net.Http --- CmlLib/Core/Files/AssetChecker.cs | 119 ++++++++++++++---------------- 1 file changed, 56 insertions(+), 63 deletions(-) diff --git a/CmlLib/Core/Files/AssetChecker.cs b/CmlLib/Core/Files/AssetChecker.cs index a0997b8..5023a2a 100644 --- a/CmlLib/Core/Files/AssetChecker.cs +++ b/CmlLib/Core/Files/AssetChecker.cs @@ -1,13 +1,13 @@ using CmlLib.Core.Downloader; using CmlLib.Core.Version; using CmlLib.Utils; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using System.Text.Json; using System.Threading.Tasks; namespace CmlLib.Core.Files @@ -43,110 +43,109 @@ public string AssetServer private DownloadFile[]? checkIndexAndAsset(MinecraftPath path, MVersion version, IProgress? progress) { - checkIndex(path, version); - return CheckAssetFiles(path, version, progress); + if (version.Assets == null) + return null; + checkIndex(path, version.Assets); + return CheckAssetFiles(path, version.Assets, progress); } - private void checkIndex(MinecraftPath path, MVersion version) + // Check index file validation and download + private void checkIndex(MinecraftPath path, MFileMetadata assets) { - if (string.IsNullOrEmpty(version.AssetId)) + if (string.IsNullOrEmpty(assets.Id) || string.IsNullOrEmpty(assets.Url)) return; - string index = path.GetIndexFilePath(version.AssetId); + var indexFilePath = path.GetIndexFilePath(assets.Id); - if (!string.IsNullOrEmpty(version.AssetUrl)) - if (!IOUtil.CheckFileValidation(index, version.AssetHash, CheckHash)) - { - var directoryName = Path.GetDirectoryName(index); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); + if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, CheckHash)) + return; + + var directoryName = Path.GetDirectoryName(indexFilePath); + if (!string.IsNullOrEmpty(directoryName)) + Directory.CreateDirectory(directoryName); - using (var wc = new WebClient()) - { - wc.DownloadFile(version.AssetUrl, index); - } - } + using var wc = new WebClient(); + wc.DownloadFile(assets.Url, indexFilePath); } + // Read index file and return it as object [MethodTimer.Time] - public JObject? ReadIndex(MinecraftPath path, MVersion version) + public JsonDocument? ReadIndex(MinecraftPath path, MFileMetadata assets) { - if (string.IsNullOrEmpty(version.AssetId)) + if (string.IsNullOrEmpty(assets.Id)) return null; - string indexPath = path.GetIndexFilePath(version.AssetId); - if (!File.Exists(indexPath)) return null; - - string json = File.ReadAllText(indexPath); - var index = JObject.Parse(json); // 100ms + var indexFilePath = path.GetIndexFilePath(assets.Id); + if (!File.Exists(indexFilePath)) + return null; - return index; + var indexFileContent = File.ReadAllText(indexFilePath); + var jsonDocument = JsonDocument.Parse(indexFileContent); + return jsonDocument; } [MethodTimer.Time] - public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MVersion version, + public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, IProgress? progress) { - JObject? index = ReadIndex(path, version); + var index = ReadIndex(path, assets); if (index == null) return null; + using (index) + { + var root = index.RootElement; - bool isVirtual = checkJsonTrue(index["virtual"]); // check virtual - bool mapResource = checkJsonTrue(index["map_to_resources"]); // check map_to_resources + var listProperty = root.SafeGetProperty("objects"); + if (!listProperty.HasValue) + return null; + var list = listProperty.Value; - var list = index["objects"] as JObject; - if (list == null) - return null; - - var downloadRequiredFiles = new List(list.Count); + var isVirtual = root.SafeGetProperty("virtual")?.GetBoolean() ?? false; + var mapResource = root.SafeGetProperty("map_to_resources")?.GetBoolean() ?? false; + + var downloadRequiredFiles = new List(); - int total = list.Count; - int progressed = 0; + //int total = list.Count; + int progressed = 0; - foreach (var item in list) - { - if (item.Value != null) + foreach (var prop in list.EnumerateObject()) { - var f = checkAssetFile(item.Key, item.Value, path, version, isVirtual, mapResource); + var f = checkAssetFile(prop.Name, prop.Value, path, assets, isVirtual, mapResource); if (f != null) downloadRequiredFiles.Add(f); - } - progressed++; + progressed++; - if (progressed % 50 == 0) // prevent ui freezing - progress?.Report( - new DownloadFileChangedEventArgs(MFile.Resource, this, "", total, progressed)); - } + if (progressed % 50 == 0) // prevent ui freezing + progress?.Report( + new DownloadFileChangedEventArgs(MFile.Resource, this, "", progressed, progressed)); + } - return downloadRequiredFiles.Distinct().ToArray(); // 10ms + return downloadRequiredFiles.Distinct().ToArray(); // 10ms + } } - private DownloadFile? checkAssetFile(string key, JToken job, MinecraftPath path, MVersion version, + private DownloadFile? checkAssetFile(string key, JsonElement element, MinecraftPath path, MFileMetadata assets, bool isVirtual, bool mapResource) { - if (string.IsNullOrEmpty(version.AssetId)) + if (string.IsNullOrEmpty(assets.Id)) return null; - // download hash resource - string? hash = job["hash"]?.ToString(); + var hash = element.GetPropertyValue("hash"); if (hash == null) return null; var hashName = hash.Substring(0, 2) + "/" + hash; - var hashPath = Path.Combine(path.GetAssetObjectPath(version.AssetId), hashName); + var hashPath = Path.Combine(path.GetAssetObjectPath(assets.Id), hashName); - long size = 0; - string? sizeStr = job["size"]?.ToString(); - if (!string.IsNullOrEmpty(sizeStr)) - long.TryParse(sizeStr, out size); + long size = element.SafeGetProperty("size")?.GetInt64() ?? 0; var afterDownload = new List>(1); if (isVirtual) { - var resPath = Path.Combine(path.GetAssetLegacyPath(version.AssetId), key); + var resPath = Path.Combine(path.GetAssetLegacyPath(assets.Id), key); afterDownload.Add(() => assetCopy(hashPath, resPath)); } @@ -199,11 +198,5 @@ private async Task assetCopy(string org, string des) Debug.WriteLine(ex); } } - - private bool checkJsonTrue(JToken? j) - { - string? str = j?.ToString().ToLowerInvariant(); - return str is "true"; - } } } From aaac5cee40591358af65a0ae5289afbaf85dd143 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:08:58 +0900 Subject: [PATCH 018/137] update JavaChecker: System.Text.Json, System.Net.Http --- CmlLib/Core/Files/JavaChecker.cs | 153 ++++++++++++++++--------------- 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/CmlLib/Core/Files/JavaChecker.cs b/CmlLib/Core/Files/JavaChecker.cs index e0a1f7c..40fa24c 100644 --- a/CmlLib/Core/Files/JavaChecker.cs +++ b/CmlLib/Core/Files/JavaChecker.cs @@ -2,24 +2,39 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; +using System.Text.Json; using System.Threading.Tasks; using CmlLib.Core.Downloader; using CmlLib.Core.Installer; using CmlLib.Core.Java; using CmlLib.Core.Version; using CmlLib.Utils; -using Newtonsoft.Json.Linq; namespace CmlLib.Core.Files { public class JavaChecker : IFileChecker { + class JavaCheckResult + { + public string? JavaBinaryPath { get; set; } + public DownloadFile[]? JavaFiles { get; set; } + } + public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; public bool CheckHash { get; set; } = true; public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, IProgress? downloadProgress) + { + return CheckFilesTaskAsync(path, version, downloadProgress) + .ConfigureAwait(false) + .GetAwaiter().GetResult(); + } + + public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, + IProgress? downloadProgress) { if (!string.IsNullOrEmpty(version.JavaBinaryPath) && File.Exists(version.JavaBinaryPath)) return null; @@ -28,57 +43,65 @@ public class JavaChecker : IFileChecker if (string.IsNullOrEmpty(javaVersion)) javaVersion = MinecraftJavaPathResolver.JreLegacyVersionName; - var files = CheckJava( - javaVersion, path, downloadProgress, out string binPath); + var result = await internalCheckJava(javaVersion, path, downloadProgress) + .ConfigureAwait(false); - version.JavaBinaryPath = binPath; - return files; + version.JavaBinaryPath = result.JavaBinaryPath; + return result.JavaFiles; } - public Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, + private async Task internalCheckJava(string javaVersion, MinecraftPath path, IProgress? downloadProgress) - { - return Task.Run(() => CheckFiles(path, version, downloadProgress)); - } - - public DownloadFile[] CheckJava(string javaVersion, MinecraftPath path, - IProgress? downloadProgress, out string binPath) { var javaPathResolver = new MinecraftJavaPathResolver(path); - binPath = javaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); + var binPath = javaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); + DownloadFile[]? downloadFiles; try { var osName = getJavaOSName(); // safe - var javaVersions = getJavaVersionsForOs(osName); // Net, JsonParse Exception + + var response = await HttpUtil.HttpClient.GetAsync(JavaManifestServer); + var str = await response.Content.ReadAsStringAsync(); + using var jsonDocument = JsonDocument.Parse(str); + + var root = jsonDocument.RootElement; + var javaVersions = root.SafeGetProperty(osName); + if (javaVersions != null) { - // Net, JsonParse - var javaManifest = getJavaVersionManifest(javaVersions, javaVersion); - + var javaManifest = await getJavaVersionManifest(javaVersions.Value, javaVersion); + if (javaManifest == null && javaVersion != MinecraftJavaPathResolver.JreLegacyVersionName) - javaManifest = getJavaVersionManifest(javaVersions, MinecraftJavaPathResolver.JreLegacyVersionName); + javaManifest = await getJavaVersionManifest(javaVersions.Value, MinecraftJavaPathResolver.JreLegacyVersionName); if (javaManifest == null) - return legacyJavaChecker(path, out binPath); + return await legacyJavaChecker(path); - var files = javaManifest["files"] as JObject; + using var manifestDocument = JsonDocument.Parse(javaManifest); + var files = manifestDocument.RootElement.SafeGetProperty("files"); if (files == null) - return legacyJavaChecker(path, out binPath); + return await legacyJavaChecker(path); - return toDownloadFiles(files, javaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); + downloadFiles = toDownloadFiles(files.Value, javaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); } else - return legacyJavaChecker(path, out binPath); + return await legacyJavaChecker(path); } catch (Exception e) { Debug.WriteLine(e); if (string.IsNullOrEmpty(binPath)) - return legacyJavaChecker(path, out binPath); + return await legacyJavaChecker(path); else - return new DownloadFile[] { }; + downloadFiles = new DownloadFile[] { }; } + + return new JavaCheckResult() + { + JavaFiles = downloadFiles, + JavaBinaryPath = binPath + }; } private string getJavaOSName() @@ -106,56 +129,38 @@ private string getJavaOSName() return osName; } - - private JObject? getJavaVersionsForOs(string osName) - { - string response; - - using (var wc = new WebClient()) - { - response = wc.DownloadString(JavaManifestServer); // ex - } - - var job = JObject.Parse(response); // ex - return job[osName] as JObject; - } - private JObject? getJavaVersionManifest(JObject job, string version) + private async Task getJavaVersionManifest(JsonElement job, string version) { - var versionArr = job[version] as JArray; - if (versionArr == null || versionArr.Count == 0) + var versionArr = job.SafeGetProperty(version)?.EnumerateArray(); + if (versionArr == null) return null; - - var manifestUrl = versionArr[0]["manifest"]?["url"]?.ToString(); + + var firstManifest = versionArr.Value.FirstOrDefault(); + var manifestUrl = firstManifest.SafeGetProperty("manifest")?.SafeGetProperty("url")?.GetString(); if (string.IsNullOrEmpty(manifestUrl)) return null; - string response; - using (var wc = new WebClient()) - { - response = wc.DownloadString(manifestUrl); // ex - } - - return JObject.Parse(response); // ex + return await HttpUtil.HttpClient.GetStringAsync(manifestUrl); } - private DownloadFile[] toDownloadFiles(JObject manifest, string path, + private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, IProgress? progress) { var progressed = 0; - var files = new List(manifest.Count); - foreach (var prop in manifest) + var files = new List(); + foreach (var prop in manifest.EnumerateObject()) { - var name = prop.Key; + var name = prop.Name; var value = prop.Value; - var type = value?["type"]?.ToString(); + var type = value.GetPropertyValue("type"); if (type == "file") { var filePath = Path.Combine(path, name); filePath = IOUtil.NormalizePath(filePath); - - bool.TryParse(value?["executable"]?.ToString(), out bool executable); + + var executable = value.SafeGetProperty("executable")?.GetBoolean() ?? false; var file = checkJavaFile(value, filePath); if (file != null) @@ -177,24 +182,23 @@ private DownloadFile[] toDownloadFiles(JObject manifest, string path, progressed++; progress?.Report(new DownloadFileChangedEventArgs( - MFile.Runtime, this, name, manifest.Count, progressed)); + MFile.Runtime, this, name, progressed, progressed)); } return files.ToArray(); } - private DownloadFile? checkJavaFile(JToken? value, string filePath) + private DownloadFile? checkJavaFile(JsonElement value, string filePath) { - var downloadObj = value?["downloads"]?["raw"]; + var downloadObj = value.SafeGetProperty("downloads")?.SafeGetProperty("raw"); if (downloadObj == null) return null; - - var url = downloadObj["url"]?.ToString(); + + var url = downloadObj.Value.GetPropertyValue("url"); if (string.IsNullOrEmpty(url)) return null; - - var hash = downloadObj["sha1"]?.ToString(); - var sizeStr = downloadObj["size"]?.ToString(); - long.TryParse(sizeStr, out long size); + + var hash = downloadObj.Value.GetPropertyValue("sha1"); + var size = downloadObj.Value.SafeGetProperty("size")?.GetInt64() ?? 0; if (IOUtil.CheckFileValidation(filePath, hash, CheckHash)) return null; @@ -205,20 +209,24 @@ private DownloadFile[] toDownloadFiles(JObject manifest, string path, }; } - private DownloadFile[] legacyJavaChecker(MinecraftPath path, out string binPath) + private async Task legacyJavaChecker(MinecraftPath path) { var javaPathResolver = new MinecraftJavaPathResolver(path); string legacyJavaPath = javaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersionName); MJava mJava = new MJava(legacyJavaPath); - binPath = mJava.GetBinaryPath(); + var result = new JavaCheckResult() + { + JavaBinaryPath = mJava.GetBinaryPath(), + JavaFiles = null + }; try { if (mJava.CheckJavaExistence()) - return new DownloadFile[] {}; + return result; - string javaUrl = mJava.GetJavaUrl(); + string javaUrl = await mJava.GetJavaUrlAsync(); string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); string zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); @@ -239,13 +247,14 @@ private DownloadFile[] legacyJavaChecker(MinecraftPath path, out string binPath) }) } }; + result.JavaFiles = new[] { file }; - return new[] {file}; + return result; } catch (Exception e) { Debug.WriteLine(e); - return new DownloadFile[] {}; + return result; } } From de54cebb22ae9ed6f6d649eb572e86004b838c0d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:09:21 +0900 Subject: [PATCH 019/137] remove MForge --- CmlLib/Core/Installer/MForge.cs | 415 -------------------------------- 1 file changed, 415 deletions(-) delete mode 100644 CmlLib/Core/Installer/MForge.cs diff --git a/CmlLib/Core/Installer/MForge.cs b/CmlLib/Core/Installer/MForge.cs deleted file mode 100644 index d5bb8b0..0000000 --- a/CmlLib/Core/Installer/MForge.cs +++ /dev/null @@ -1,415 +0,0 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.Files; -using CmlLib.Utils; -using ICSharpCode.SharpZipLib.Zip; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; - -namespace CmlLib.Core.Installer -{ - public class MForge - { - private const string MavenServer = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/"; - - public static string GetOldForgeName(string mcVersion, string forgeVersion) - { - return $"{mcVersion}-forge{mcVersion}-{forgeVersion}"; - } - - public static string GetForgeName(string mcVersion, string forgeVersion) - { - return $"{mcVersion}-forge-{forgeVersion}"; - } - - public MForge(MinecraftPath mc, string java) - { - this.minecraftPath = mc; - JavaPath = java; - downloader = new SequenceDownloader(); - } - - public string JavaPath { get; private set; } - private readonly MinecraftPath minecraftPath; - private readonly IDownloader downloader; - public event DownloadFileChangedHandler? FileChanged; - public event EventHandler? InstallerOutput; - - public string InstallForge(string mcVersion, string forgeVersion) - { - var minecraftJar = minecraftPath.GetVersionJarPath(mcVersion); - if (!File.Exists(minecraftJar)) - throw new IOException($"Install {mcVersion} first"); - - var installerPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - var installerStream = getInstallerStream(mcVersion, forgeVersion); // download installer - if (installerStream == null) - throw new InvalidOperationException("cannot open installer stream"); - var extractedFile = extractInstaller(installerStream, installerPath); // extract installer - - var profileObj = extractedFile.Item1; // version profile json - var installerObj = extractedFile.Item2; // installer info - - // copy forge libraries to minecraft - extractMaven(installerPath); // new installer - - var universalPath = installerObj["filePath"]?.ToString(); - if (string.IsNullOrEmpty(universalPath)) - throw new InvalidOperationException("filePath property in installer was null"); - - var destPath = installerObj["path"]?.ToString(); - if (string.IsNullOrEmpty(destPath)) - throw new InvalidOperationException("path property in installer was null"); - - extractUniversal(installerPath, universalPath, destPath); // old installer - - // download libraries and processors - checkLibraries(installerObj["libraries"] as JArray); - - // mapping client data - var installerData = installerObj["data"] as JObject; - var mapData = (installerData == null) - ? new Dictionary() - : mapping(installerData, "client", minecraftJar, installerPath); - - // process - process(installerObj["processors"] as JArray, mapData); - - // version name like 1.16.2-forge-33.0.20 - var versionName = installerObj["target"]?.ToString() - ?? installerObj["version"]?.ToString() - ?? GetForgeName(mcVersion, forgeVersion); - - var versionPath = minecraftPath.GetVersionJsonPath(versionName); - - // write version profile json - writeProfile(profileObj, versionPath); - - return versionName; - } - - private Stream? getInstallerStream(string mcVersion, string forgeVersion) - { - fireEvent(MFile.Library, "installer", 1, 0); - - var url = $"{MavenServer}{mcVersion}-{forgeVersion}/" + - $"forge-{mcVersion}-{forgeVersion}-installer.jar"; - - return WebRequest.Create(url).GetResponse().GetResponseStream(); - } - - private Tuple extractInstaller(Stream stream, string extractPath) - { - // extract installer - string? installProfile = null; - string? versionsJson = null; - - using (stream) - using (var s = new ZipInputStream(stream)) - { - ZipEntry e; - while ((e = s.GetNextEntry()) != null) - { - if (e.Name.Length <= 0) - continue; - - var realpath = Path.Combine(extractPath, e.Name); - - if (e.IsFile) - { - if (e.Name == "install_profile.json") - installProfile = readStreamString(s); - else if (e.Name == "version.json") - versionsJson = readStreamString(s); - else - { - var dirPath = Path.GetDirectoryName(realpath); - if (!string.IsNullOrEmpty(dirPath)) - Directory.CreateDirectory(dirPath); - - using var fs = File.OpenWrite(realpath); - s.CopyTo(fs); - } - } - } - } - - if (installProfile == null) - throw new InvalidOperationException("no install_profile.json in installer"); - if (versionsJson == null) - throw new InvalidOperationException("no version.json in installer"); - - JToken profileObj; - var installObj = JObject.Parse(installProfile); // installer info - var versionInfo = installObj["versionInfo"]; // version profile - - if (versionInfo == null) - profileObj = JObject.Parse(versionsJson); - else - { - installObj = installObj["install"] as JObject; - profileObj = versionInfo; - } - - if (installObj == null) - throw new InvalidOperationException("no 'install' object in install_profile.json"); - - return new Tuple(profileObj, installObj); - } - - private string readStreamString(Stream s) - { - var str = new StringBuilder(); - var buffer = new byte[1024]; - while (true) - { - int size = s.Read(buffer, 0, buffer.Length); - if (size == 0) - break; - - str.Append(Encoding.UTF8.GetString(buffer, 0, size)); - } - - return str.ToString(); - } - - // for new installer - private void extractMaven(string installerPath) - { - fireEvent(MFile.Library, "maven", 1, 0); - - // copy all libraries in maven (include universal) to minecraft - var org = Path.Combine(installerPath, "maven"); - if (Directory.Exists(org)) - IOUtil.CopyDirectory(org, minecraftPath.Library, true); - } - - // for old installer - private void extractUniversal(string installerPath, string universalPath, string destinyName) - { - fireEvent(MFile.Library, "universal", 1, 0); - - if (string.IsNullOrEmpty(universalPath) || string.IsNullOrEmpty(destinyName)) - return; - - // copy universal library to minecraft - var universal = Path.Combine(installerPath, universalPath); - var desPath = PackageName.Parse(destinyName).GetPath(); - var des = Path.Combine(minecraftPath.Library, desPath); - - if (File.Exists(universal)) - { - var dirPath = Path.GetDirectoryName(des); - if (!string.IsNullOrEmpty(dirPath)) - Directory.CreateDirectory(dirPath); - File.Copy(universal, des, true); - } - } - - // legacy - private void downloadUniversal(string mcVersion, string forgeVersion) - { - fireEvent(MFile.Library, "universal", 1, 0); - - var forgeName = $"forge-{mcVersion}-{forgeVersion}"; - var baseUrl = $"{MavenServer}{mcVersion}-{forgeVersion}"; - - var universalUrl = $"{baseUrl}/{forgeName}-universal.jar"; - var universalPath = Path.Combine( - minecraftPath.Library, - "net", - "minecraftforge", - "forge", - $"{mcVersion}-{forgeVersion}", - $"forge-{mcVersion}-{forgeVersion}.jar" - ); - - var dirPath = Path.GetDirectoryName(universalPath); - if (!string.IsNullOrEmpty(dirPath)) - Directory.CreateDirectory(dirPath); - - var dl = new WebDownload(); - dl.DownloadFile(universalUrl, universalPath); - } - - private Dictionary mapping(JObject data, string kind, - string minecraftJar, string installerPath) - { - // convert [path] to absolute path - - var dataMapping = new Dictionary(); - foreach (var item in data) - { - var key = item.Key; - var value = item.Value?[kind]?.ToString(); - - if (string.IsNullOrEmpty(value)) - continue; - - var fullPath = Mapper.ToFullPath(value, minecraftPath.Library); - if (fullPath == value) - { - value = value.Trim('/'); - dataMapping.Add(key, Path.Combine(installerPath, value)); - } - else - dataMapping.Add(key, fullPath); - } - - dataMapping.Add("SIDE", "CLIENT"); - dataMapping.Add("MINECRAFT_JAR", minecraftJar); - - return dataMapping; - } - - private void checkLibraries(JArray? jarr) - { - if (jarr == null || jarr.Count == 0) - return; - - var libs = new List(); - var parser = new MLibraryParser(); - foreach (var item in jarr) - { - var parsedLib = parser.ParseJsonObject((JObject)item); - if (parsedLib != null) - libs.AddRange(parsedLib); - } - - var fileProgress = new Progress( - e => FileChanged?.Invoke(e)); - - var libraryChecker = new LibraryChecker(); - var lostLibrary = libraryChecker.CheckFiles(minecraftPath, libs.ToArray(), fileProgress); - - if (lostLibrary != null) - downloader.DownloadFiles(lostLibrary, fileProgress, null); - } - - private void process(JArray? processors, Dictionary mapData) - { - if (processors == null || processors.Count == 0) - return; - - fireEvent(MFile.Library, "processors", processors.Count, 0); - - for (int i = 0; i < processors.Count; i++) - { - var item = processors[i]; - - var outputs = item["outputs"] as JObject; - if (outputs == null || !checkProcessorOutputs(outputs, mapData)) - startProcessor(item, mapData); - - fireEvent(MFile.Library, "processors", processors.Count, i + 1); - } - } - - private bool checkProcessorOutputs(JObject outputs, Dictionary mapData) - { - foreach (var item in outputs) - { - if (item.Value == null) - continue; - - var key = Mapper.Interpolation(item.Key, mapData, true); - var value = Mapper.Interpolation(item.Value.ToString(), mapData, true); - - if (!File.Exists(key) || !IOUtil.CheckSHA1(key, value)) - return false; - } - - return true; - } - - private void startProcessor(JToken processor, Dictionary mapData) - { - var name = processor["jar"]?.ToString(); - if (name == null) - return; - - // jar - var jar = PackageName.Parse(name); - var jarPath = Path.Combine(minecraftPath.Library, jar.GetPath()); - - var jarFile = new JarFile(jarPath); - var jarManifest = jarFile.GetManifest(); - - // mainclass - string? mainClass = null; - var hasMainclass = jarManifest?.TryGetValue("Main-Class", out mainClass) ?? false; - if (!hasMainclass || string.IsNullOrEmpty(mainClass)) - return; - - // classpath - var classpathObj = processor["classpath"]; - var classpath = new List(); - if (classpathObj != null) - { - foreach (var libName in classpathObj) - { - var libNameString = libName?.ToString(); - if (string.IsNullOrEmpty(libNameString)) - continue; - - var lib = Path.Combine(minecraftPath.Library, - PackageName.Parse(libNameString).GetPath()); - classpath.Add(lib); - } - } - classpath.Add(jarPath); - - // arg - var argsArr = processor["args"] as JArray; - string[]? args = null; - if (argsArr != null) - { - var arrStrs = argsArr.Select(x => x.ToString()).ToArray(); - args = Mapper.Map(arrStrs, mapData, minecraftPath.Library); - } - - startJava(classpath.ToArray(), mainClass, args); - } - - private void startJava(string[] classpath, string mainClass, string[]? args) - { - var arg = - $"-cp {IOUtil.CombinePath(classpath)} " + - $"{mainClass}"; - - if (args != null && args.Length > 0) - arg += " " + string.Join(" ", args); - - var process = new Process(); - process.StartInfo = new ProcessStartInfo() - { - FileName = JavaPath, - Arguments = arg, - }; - - var p = new ProcessUtil(process); - p.OutputReceived += (s, e) => InstallerOutput?.Invoke(this, e); - p.StartWithEvents(); - p.Process.WaitForExit(); - } - - private void writeProfile(JToken profileObj, string versionPath) - { - var dirPath = Path.GetDirectoryName(versionPath); - if (!string.IsNullOrEmpty(dirPath)) - Directory.CreateDirectory(dirPath); - File.WriteAllText(versionPath, profileObj.ToString()); - } - - private void fireEvent(MFile kind, string name, int total, int progressed) - { - FileChanged?.Invoke(new DownloadFileChangedEventArgs(kind, this, name, total, progressed)); - } - } -} From 5f64e3b0cfdda7678ce6b426144a752af21432e8 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:09:33 +0900 Subject: [PATCH 020/137] update Fabric installer --- .../Core/Installer/FabricMC/FabricLoader.cs | 12 ++-- .../Installer/FabricMC/FabricVersionLoader.cs | 64 ++++++------------- 2 files changed, 25 insertions(+), 51 deletions(-) diff --git a/CmlLib/Core/Installer/FabricMC/FabricLoader.cs b/CmlLib/Core/Installer/FabricMC/FabricLoader.cs index 0ad9466..d44ece8 100644 --- a/CmlLib/Core/Installer/FabricMC/FabricLoader.cs +++ b/CmlLib/Core/Installer/FabricMC/FabricLoader.cs @@ -1,18 +1,18 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CmlLib.Core.Installer.FabricMC { public class FabricLoader { - [JsonProperty("separator")] + [JsonPropertyName("separator")] public string? Separator { get; set; } - [JsonProperty("build")] + [JsonPropertyName("build")] public string? Build { get; set; } - [JsonProperty("maven")] + [JsonPropertyName("maven")] public string? Maven { get; set; } - [JsonProperty("version")] + [JsonPropertyName("version")] public string? Version { get; set; } - [JsonProperty("stable")] + [JsonPropertyName("stable")] public bool Stable { get; set; } } } diff --git a/CmlLib/Core/Installer/FabricMC/FabricVersionLoader.cs b/CmlLib/Core/Installer/FabricMC/FabricVersionLoader.cs index 70583f7..e06df24 100644 --- a/CmlLib/Core/Installer/FabricMC/FabricVersionLoader.cs +++ b/CmlLib/Core/Installer/FabricMC/FabricVersionLoader.cs @@ -1,10 +1,10 @@ using CmlLib.Core.Version; -using Newtonsoft.Json.Linq; using System.Collections.Generic; -using System.Net; +using System.Text.Json; using System.Threading.Tasks; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; +using CmlLib.Utils; namespace CmlLib.Core.Installer.FabricMC { @@ -20,36 +20,20 @@ protected string GetVersionName(string version, string loaderVersion) return $"fabric-loader-{loaderVersion}-{version}"; } - public MVersionCollection GetVersionMetadatas() - => internalGetVersionMetadatasAsync(sync: true).GetAwaiter().GetResult(); - - public Task GetVersionMetadatasAsync() - => internalGetVersionMetadatasAsync(sync: false); - - private async Task internalGetVersionMetadatasAsync(bool sync) + public async Task GetVersionMetadatasAsync() { if (string.IsNullOrEmpty(LoaderVersion)) { - FabricLoader[] loaders; - if (sync) - loaders = GetFabricLoaders(); - else - loaders = await GetFabricLoadersAsync().ConfigureAwait(false); + var loaders = await GetFabricLoadersAsync().ConfigureAwait(false); LoaderVersion = loaders[0].Version; if (loaders.Length == 0 || string.IsNullOrEmpty(LoaderVersion)) throw new KeyNotFoundException("can't find loaders"); } - string url = "https://meta.fabricmc.net/v2/versions/game/intermediary"; - string res; - using (var wc = new WebClient()) - { - if (sync) - res = wc.DownloadString(url); - else - res = await wc.DownloadStringTaskAsync(url).ConfigureAwait(false); - } + var url = "https://meta.fabricmc.net/v2/versions/game/intermediary"; + var res = await HttpUtil.HttpClient.GetStringAsync(url) + .ConfigureAwait(false); var versions = parseVersions(res, LoaderVersion!); return new MVersionCollection(versions.ToArray()); @@ -57,18 +41,19 @@ private async Task internalGetVersionMetadatasAsync(bool syn private List parseVersions(string res, string loader) { - var jarr = JArray.Parse(res); - var versionList = new List(jarr.Count); + using var jsonDocument = JsonDocument.Parse(res); + var root = jsonDocument.RootElement; + var versionList = new List(); - foreach (var item in jarr) + foreach (var item in root.EnumerateArray()) { - string? versionName = item["version"]?.ToString(); + var versionName = item.GetPropertyValue("version"); if (string.IsNullOrEmpty(versionName)) continue; - string jsonUrl = $"{ApiServer}/v2/versions/loader/{versionName}/{loader}/profile/json"; + var jsonUrl = $"{ApiServer}/v2/versions/loader/{versionName}/{loader}/profile/json"; - string id = GetVersionName(versionName, loader); + var id = GetVersionName(versionName, loader); var versionMetadata = new WebVersionMetadata(id) { MType = MVersionType.Custom, @@ -81,30 +66,19 @@ private List parseVersions(string res, string loader) return versionList; } - public FabricLoader[] GetFabricLoaders() - { - using var wc = new WebClient(); - var res = wc.DownloadString(LoaderUrl); - - return parseLoaders(res); - } - public async Task GetFabricLoadersAsync() { - using var wc = new WebClient(); - var res = await wc.DownloadStringTaskAsync(LoaderUrl) - .ConfigureAwait(false); - + var res = await HttpUtil.HttpClient.GetStringAsync(LoaderUrl); return parseLoaders(res); } private FabricLoader[] parseLoaders(string res) { - var jarr = JArray.Parse(res); - var loaderList = new List(jarr.Count); - foreach (var item in jarr) + using var jsonDocument = JsonDocument.Parse(res); + var loaderList = new List(); + foreach (var item in jsonDocument.RootElement.EnumerateArray()) { - var obj = item.ToObject(); + var obj = item.Deserialize(); if (obj != null) loaderList.Add(obj); } From 989d44445fdbd030b80e8a02c568722a7e2071e4 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:09:44 +0900 Subject: [PATCH 021/137] update LiteLodaer installer --- .../LiteLoader/LiteLoaderInstaller.cs | 13 +- .../LiteLoader/LiteLoaderVersionLoader.cs | 62 ++---- .../LiteLoader/LiteLoaderVersionMetadata.cs | 182 +++++++++--------- 3 files changed, 113 insertions(+), 144 deletions(-) diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs b/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs index e11b1fd..52bcb2c 100644 --- a/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs +++ b/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs @@ -37,7 +37,7 @@ public async Task GetAllLiteLoaderVersions() } // vanilla - public async Task Install(string liteLoaderVersion) + public async Task InstallAsync(string liteLoaderVersion) { var localVersionLoader = new LocalVersionLoader(minecraftPath); var localVersions = await localVersionLoader.GetVersionMetadatasAsync() @@ -57,15 +57,10 @@ public async Task Install(string liteLoaderVersion) if (vanillaVersion == null) throw new KeyNotFoundException(vanillaVersionName); - return liteLoader.Install(minecraftPath, vanillaVersion); - } - - public Task Install(string liteLoaderVersion, MVersionMetadata target) - { - return Install(liteLoaderVersion, target.GetVersion()); + return await liteLoader.InstallAsync(minecraftPath, vanillaVersion); } - public async Task Install(string liteLoaderVersion, MVersion target) + public async Task InstallAsync(string liteLoaderVersion, MVersion target) { if (liteLoaderVersions == null) await GetAllLiteLoaderVersions().ConfigureAwait(false); @@ -74,7 +69,7 @@ public async Task Install(string liteLoaderVersion, MVersion target) if (liteLoader == null) throw new KeyNotFoundException(liteLoaderVersion); - return liteLoader.Install(minecraftPath, target); + return await liteLoader.InstallAsync(minecraftPath, target); } } } \ No newline at end of file diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs b/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs index 26cda1d..df7e511 100644 --- a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs +++ b/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs @@ -1,77 +1,47 @@ using System.Collections.Generic; -using System.Net; +using System.Text.Json; using System.Threading.Tasks; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; -using Newtonsoft.Json.Linq; +using CmlLib.Utils; namespace CmlLib.Core.Installer.LiteLoader { public class LiteLoaderVersionLoader : IVersionLoader { - private const string LiteLoaderLibName = "com.mumfrey:liteloader"; - private const string ManifestServer = "http://dl.liteloader.com/versions/versions.json"; + public const string LiteLoaderLibName = "com.mumfrey:liteloader"; + public const string ManifestServer = "http://dl.liteloader.com/versions/versions.json"; public async Task GetVersionMetadatasAsync() { - using var wc = new WebClient(); - var res = await wc.DownloadStringTaskAsync(ManifestServer) + var res = await HttpUtil.HttpClient.GetStringAsync(ManifestServer) .ConfigureAwait(false); return parseVersions(res); } - public MVersionCollection GetVersionMetadatas() - { - using var wc = new WebClient(); - var res = wc.DownloadString(ManifestServer); - - return parseVersions(res); - } - private MVersionCollection parseVersions(string json) { - var job = JObject.Parse(json); - var versions = job["versions"] as JObject; + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; - List metadataList = new List(); - if (versions != null) + var metadataList = new List(); + if (root.TryGetProperty("versions", out var versions)) { - foreach (var item in versions) + foreach (var item in versions.EnumerateObject()) { - var vanillaVersion = item.Key; + var vanillaVersion = item.Name; var versionObj = item.Value; - var type = versionObj?["repo"]?["stream"]?.ToString(); + var libObj = versionObj.SafeGetProperty("artefacts") ?? versionObj.SafeGetProperty("snapshots"); + var latestLiteLoader = libObj?.SafeGetProperty(LiteLoaderLibName)?.SafeGetProperty("latest"); - var libObj = versionObj?["artefacts"] ?? versionObj?["snapshots"]; - var latestLLN = libObj?[LiteLoaderLibName]?["latest"]; - - if (latestLLN == null) + if (latestLiteLoader == null) continue; - var tweakClass = latestLLN["tweakClass"]?.ToString(); - var libraries = latestLLN["libraries"] as JArray; - var llVersion = latestLLN["version"]?.ToString(); - - if (libraries != null) - { - foreach (var lib in libraries) - { - // asm-all:5.2 is only available on LiteLoader server - var libName = lib["name"]?.ToString(); - if (libName == "org.ow2.asm:asm-all:5.2") - lib["url"] = "http://repo.liteloader.com/"; - } - } - - var llName = $"{LiteLoaderLibName}:{llVersion}"; - var versionName = $"LiteLoader{vanillaVersion}"; - - var metadata = new LiteLoaderVersionMetadata( - versionName, vanillaVersion, tweakClass, libraries, llName); - metadata.Type = type; + var metadata = new LiteLoaderVersionMetadata(vanillaVersion, latestLiteLoader.Value); + metadata.Type = versionObj.SafeGetProperty("repo")?.GetPropertyValue("stream"); metadataList.Add(metadata); } diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs index bb1d00d..5742506 100644 --- a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs @@ -1,80 +1,92 @@ -using System.IO; +using System.Collections.Generic; +using System.Data; +using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; -using Newtonsoft.Json.Linq; namespace CmlLib.Core.Installer.LiteLoader { public class LiteLoaderVersionMetadata : MVersionMetadata { - private const string LiteLoaderDl = "http://dl.liteloader.com/versions/"; + private const string LiteLoaderDl = "http://dl.liteloader.com/versions/"; // should be https + + public LiteLoaderVersionMetadata(string id, string vanillaVersionName) : base(id) + { + this.VanillaVersionName = vanillaVersionName; + } - public LiteLoaderVersionMetadata(string id, string vanillaVersion, string? tweakClass, JArray? libs, string? llnName) : base(id) + public LiteLoaderVersionMetadata(string vanillaVersion, JsonElement element) + : base($"LiteLoader-{vanillaVersion}") { IsLocalVersion = false; - this.VanillaVersionName = vanillaVersion; - this.tweakClass = tweakClass; - this.libraries = libs; - this.llnName = llnName; + this.element = element; } - - public string VanillaVersionName { get; set; } - private readonly string? tweakClass; - private readonly JArray? libraries; - private readonly string? llnName; - private JObject createVersion(string versionName, string baseVersionName, string? strArgs, string?[]? arrArgs) - { - // add libraries - var libs = JArray.FromObject(new[] - { - new - { - name = llnName, - url = LiteLoaderDl - } - }); + private readonly JsonElement? element; + public string VanillaVersionName { get; private set; } + private async Task writeVersion(Stream stream, + string versionName, string baseVersionName, string? strArgs, string?[]? arrArgs) + { + await using var writer = new Utf8JsonWriter(stream); + + var llVersion = element?.GetPropertyValue("version"); + var libraries = element?.SafeGetProperty("libraries"); + + writer.WriteStartObject(); + writer.WriteString("id", versionName); + writer.WriteString("type", "release"); + writer.WriteString("mainClass", "net.minecraft.launchwrapper.Launch"); + writer.WriteString("inheritsFrom", baseVersionName); + writer.WriteString("jar", baseVersionName); + + writer.WriteStartArray("libraries"); + writer.WriteStartObject(); + writer.WriteString("name", $"{LiteLoaderVersionLoader.LiteLoaderLibName}:{llVersion}"); + writer.WriteString("url", LiteLoaderDl); + writer.WriteEndObject(); + if (libraries != null) { - foreach (var item in libraries) + foreach (var lib in libraries.Value.EnumerateArray()) { - libs.Add(item); + // asm-all:5.2 is only available on LiteLoader server + var libName = lib.GetPropertyValue("name"); + var libUrl = lib.GetPropertyValue("url"); + if (libName == "org.ow2.asm:asm-all:5.2") + libUrl = "http://repo.liteloader.com/"; + + writer.WriteStartObject(); + writer.WriteString("name", libName); + writer.WriteString("url", libUrl); + writer.WriteEndObject(); } } - // create object - var obj = new - { - id = versionName, - type = "release", - libraries = libs, - mainClass = "net.minecraft.launchwrapper.Launch", - inheritsFrom = baseVersionName, - jar = baseVersionName - }; + writer.WriteEndArray(); - var job = JObject.FromObject(obj); - - // set arguments if (!string.IsNullOrEmpty(strArgs)) - job["minecraftArguments"] = strArgs; + writer.WriteString("minecraftArguments", strArgs); if (arrArgs != null) { - job["arguments"] = JObject.FromObject(new - { - game = arrArgs - }); + writer.WriteStartObject("arguments"); + writer.WriteStartArray(); + foreach (var item in arrArgs) + writer.WriteStringValue(item); + writer.WriteEndArray(); + writer.WriteEndObject(); } - return job; + writer.WriteEndObject(); } - - private string prepareWriteMetadata(MinecraftPath path, string name) + + private Stream createVersionWriteStream(MinecraftPath path, string name) { var metadataPath = path.GetVersionJsonPath(name); @@ -82,31 +94,22 @@ private string prepareWriteMetadata(MinecraftPath path, string name) if (!string.IsNullOrEmpty(directoryPath)) Directory.CreateDirectory(directoryPath); - return metadataPath; + return IOUtil.AsyncWriteStream(metadataPath, false); } - private void writeMetadata(string json, MinecraftPath path, string name) - { - var metadataPath = prepareWriteMetadata(path, name); - File.WriteAllText(metadataPath, json); - } - - private Task writeMetadataAsync(string json, MinecraftPath path, string name) - { - var metadataPath = prepareWriteMetadata(path, name); - return IOUtil.WriteFileAsync(metadataPath, json); - } - - public string Install(MinecraftPath path, MVersion baseVersion) + public async Task InstallAsync(MinecraftPath path, MVersion baseVersion) { var versionName = LiteLoaderInstaller.GetVersionName(VanillaVersionName, baseVersion.Id); + var tweakClass = element?.GetPropertyValue("tweakClass"); + + using var fs = createVersionWriteStream(path, versionName); if (!string.IsNullOrEmpty(baseVersion.MinecraftArguments)) { // com.mumfrey.liteloader.launch.LiteLoaderTweaker var newArguments = $"--tweakClass {tweakClass} {baseVersion.MinecraftArguments}"; - var json = createVersion(versionName, baseVersion.Id, newArguments, null).ToString(); - writeMetadata(json, path, versionName); + await writeVersion(fs, versionName, baseVersion.Id, newArguments, null) + .ConfigureAwait(false); } else if (baseVersion.GameArguments != null) { @@ -117,48 +120,49 @@ public string Install(MinecraftPath path, MVersion baseVersion) }; var newArguments = tweakArg.Concat(baseVersion.GameArguments).ToArray(); - var json = createVersion(versionName, baseVersion.Id, null, newArguments).ToString(); - writeMetadata(json, path, versionName); + await writeVersion(fs, versionName, baseVersion.Id, null, newArguments) + .ConfigureAwait(false); } return versionName; } - - public override MVersion GetVersion() - { - var json = createVersion(Name, VanillaVersionName, null, null).ToString(); - return MVersionParser.ParseFromJson(json); - } - public override MVersion GetVersion(MinecraftPath savePath) + private async Task writeVersionToMemory() { - var json = createVersion(Name, VanillaVersionName, null, null).ToString(); - writeMetadata(json, savePath, Name); - return MVersionParser.ParseFromJson(json); + var ms = new MemoryStream(); + await writeVersion(ms, Name, VanillaVersionName, null, null) + .ConfigureAwait(false); + return ms; } - - public override Task GetVersionAsync() + + public override async Task GetVersionAsync() { - return Task.FromResult(GetVersion()); + using var ms = await writeVersionToMemory() + .ConfigureAwait(false); + using var jsonDocument = await JsonDocument.ParseAsync(ms) + .ConfigureAwait(false); + return MVersionParser.ParseFromJson(jsonDocument); } public override async Task GetVersionAsync(MinecraftPath savePath) { - var json = createVersion(Name, VanillaVersionName, null, null).ToString(); - await writeMetadataAsync(json, savePath, Name).ConfigureAwait(false); - return MVersionParser.ParseFromJson(json); - } - - public override void Save(MinecraftPath path) - { - var json = createVersion(Name, VanillaVersionName, null, null).ToString(); - writeMetadata(json, path, Name); + using var ms = await writeVersionToMemory() + .ConfigureAwait(false); + using var fs = createVersionWriteStream(savePath, Name); + + using var jsonDocument = await JsonDocument.ParseAsync(ms); + var copyTask = ms.CopyToAsync(fs) + .ConfigureAwait(false); + var version = MVersionParser.ParseFromJson(jsonDocument); + await copyTask; + return version; } public override async Task SaveAsync(MinecraftPath path) { - var json = createVersion(Name, VanillaVersionName, null, null).ToString(); - await writeMetadataAsync(json, path, Name).ConfigureAwait(false); + using var fs = createVersionWriteStream(path, Name); + await writeVersion(fs, Name, VanillaVersionName, null, null) + .ConfigureAwait(false); } } } \ No newline at end of file From d93271f6b85983b17547c96f253089fb68a90c71 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:10:07 +0900 Subject: [PATCH 022/137] update MJava legacy java installer: use System.Text.Json, System.Net.Http --- CmlLib/Core/Installer/MJava.cs | 71 ++++++---------------------------- 1 file changed, 12 insertions(+), 59 deletions(-) diff --git a/CmlLib/Core/Installer/MJava.cs b/CmlLib/Core/Installer/MJava.cs index db9853a..3c80a73 100644 --- a/CmlLib/Core/Installer/MJava.cs +++ b/CmlLib/Core/Installer/MJava.cs @@ -1,11 +1,11 @@ using CmlLib.Utils; -using Newtonsoft.Json.Linq; using System; using System.ComponentModel; using System.IO; -using System.Net; using System.Threading.Tasks; using CmlLib.Core.Java; +using System.Text.Json; +using System.Net; namespace CmlLib.Core.Installer { @@ -37,30 +37,6 @@ public string GetBinaryPath() public bool CheckJavaExistence() => File.Exists(GetBinaryPath()); - public string CheckJava() - { - pProgressChanged = new Progress( - (e) => ProgressChanged?.Invoke(this, e)); - - string javaPath = GetBinaryPath(); - - if (!CheckJavaExistence()) - { - string javaUrl = GetJavaUrl(); - string lzmaPath = downloadJavaLzma(javaUrl); - - decompressJavaFile(lzmaPath); - - if (!File.Exists(javaPath)) - throw new WebException("failed to download"); - - if (MRule.OSName != MRule.Windows) - NativeMethods.Chmod(javaPath, NativeMethods.Chmod755); - } - - return javaPath; - } - public Task CheckJavaAsync() => CheckJavaAsync(null); @@ -96,51 +72,28 @@ public async Task CheckJavaAsync(IProgress? pr return javapath; } - public string GetJavaUrl() - { - using (var wc = new WebClient()) - { - string json = wc.DownloadString(MojangServer.LauncherMeta); - return parseLauncherMetadata(json); - } - } - public async Task GetJavaUrlAsync() { - using (var wc = new WebClient()) - { - string json = await wc.DownloadStringTaskAsync(MojangServer.LauncherMeta) - .ConfigureAwait(false); - return parseLauncherMetadata(json); - } + var json = await HttpUtil.HttpClient.GetStringAsync(MojangServer.LauncherMeta) + .ConfigureAwait(false); + return parseLauncherMetadata(json); } private string parseLauncherMetadata(string json) { - var javaUrl = JObject.Parse(json) - [MRule.OSName] - ?[MRule.Arch] - ?["jre"] - ?["url"] - ?.ToString(); + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; + + var javaUrl = root.SafeGetProperty(MRule.OSName)? + .SafeGetProperty(MRule.Arch)? + .SafeGetProperty("jre")? + .GetPropertyValue("url"); if (string.IsNullOrEmpty(javaUrl)) throw new PlatformNotSupportedException("Downloading JRE on current OS is not supported. Set JavaPath manually."); return javaUrl; } - private string downloadJavaLzma(string javaUrl) - { - Directory.CreateDirectory(RuntimeDirectory); - string lzmapath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - - var webdownloader = new WebDownload(); - webdownloader.DownloadProgressChangedEvent += Downloader_DownloadProgressChangedEvent; - webdownloader.DownloadFile(javaUrl, lzmapath); - - return lzmapath; - } - private async Task downloadJavaLzmaAsync(string javaUrl) { Directory.CreateDirectory(RuntimeDirectory); From e66785dc45596699fa0af2269ecbd546d58b8b8c Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:10:59 +0900 Subject: [PATCH 023/137] update CMLauncher: remove sync methods, remove forge install method --- CmlLib/Core/CMLauncher.cs | 75 --------------------------------------- 1 file changed, 75 deletions(-) diff --git a/CmlLib/Core/CMLauncher.cs b/CmlLib/Core/CMLauncher.cs index ce56a53..9a9efe1 100644 --- a/CmlLib/Core/CMLauncher.cs +++ b/CmlLib/Core/CMLauncher.cs @@ -51,12 +51,6 @@ public CMLauncher(MinecraftPath mc) public IJavaPathResolver JavaPathResolver { get; set; } - public MVersionCollection GetAllVersions() - { - Versions = VersionLoader.GetVersionMetadatas(); - return Versions; - } - public async Task GetAllVersionsAsync() { Versions = await VersionLoader.GetVersionMetadatasAsync() @@ -64,14 +58,6 @@ public async Task GetAllVersionsAsync() return Versions; } - public MVersion GetVersion(string versionName) - { - if (Versions == null) - GetAllVersions(); - - return Versions!.GetVersion(versionName); - } - public async Task GetVersionAsync(string versionName) { if (Versions == null) @@ -81,45 +67,6 @@ public async Task GetVersionAsync(string versionName) .ConfigureAwait(false); return version; } - - public string CheckForge(string mcversion, string forgeversion, string java) - { - if (Versions == null) - GetAllVersions(); - - var forgeNameOld = MForge.GetOldForgeName(mcversion, forgeversion); - var forgeName = MForge.GetForgeName(mcversion, forgeversion); - - var exist = false; - var name = ""; - foreach (var item in Versions!) - { - if (item.Name == forgeName) - { - exist = true; - name = forgeName; - break; - } - else if (item.Name == forgeNameOld) - { - exist = true; - name = forgeNameOld; - break; - } - } - - if (!exist) - { - var mforge = new MForge(MinecraftPath, java); - mforge.FileChanged += (e) => FileChanged?.Invoke(e); - mforge.InstallerOutput += (s, e) => LogOutput?.Invoke(this, e); - name = mforge.InstallForge(mcversion, forgeversion); - - GetAllVersions(); - } - - return name; - } public DownloadFile[] CheckLostGameFiles(MVersion version) { @@ -184,21 +131,6 @@ public async Task CheckAndDownloadAsync(MVersion version) } } - // not stable - public Process CreateProcess(string mcversion, string forgeversion, MLaunchOption option) - { - CheckAndDownload(GetVersion(mcversion)); - - var javaPath = option.JavaPath ?? GetDefaultJavaPath() - ?? throw new FileNotFoundException("Cannot find java path"); - var versionName = CheckForge(mcversion, forgeversion, javaPath); - - return CreateProcess(versionName, option); - } - - public Process CreateProcess(string versionName, MLaunchOption option, bool checkAndDownload=true) - => CreateProcess(GetVersion(versionName), option, checkAndDownload); - [MethodTimer.Time] public Process CreateProcess(MVersion version, MLaunchOption option, bool checkAndDownload=true) { @@ -242,13 +174,6 @@ public async Task CreateProcessAsync(MLaunchOption option) return await Task.Run(launch.GetProcess).ConfigureAwait(false); } - public Process Launch(string versionName, MLaunchOption option) - { - Process process = CreateProcess(versionName, option); - process.Start(); - return process; - } - public async Task LaunchAsync(string versionName, MLaunchOption option) { Process process = await CreateProcessAsync(versionName, option) From 4fab58c1b2c7052be674e4c26d5c496b1428962e Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:11:27 +0900 Subject: [PATCH 024/137] update Changelogs: use System.Text.Json, System.Net.Http --- CmlLib/Utils/Changelogs.cs | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/CmlLib/Utils/Changelogs.cs b/CmlLib/Utils/Changelogs.cs index 618d331..8685d28 100644 --- a/CmlLib/Utils/Changelogs.cs +++ b/CmlLib/Utils/Changelogs.cs @@ -1,15 +1,21 @@ using System.Collections.Generic; +using System.Linq; using System.Net; +using System.Runtime.CompilerServices; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; namespace CmlLib.Utils { public class Changelogs { - private static readonly Dictionary changelogUrls = new Dictionary + private static readonly string PatchNotesUrl = "https://launchercontent.mojang.com/javaPatchNotes.json"; + + // The urls below will only be retrieved if the version is not found in 'PatchNotesUrl' + // Versions that exist in 'PatchNotesUrl' do not need to be added the list below + private static readonly Dictionary AltUrls = new Dictionary { { "1.14.2", "https://feedback.minecraft.net/hc/en-us/articles/360028919851-Minecraft-Java-Edition-1-14-2" }, { "1.14.3", "https://feedback.minecraft.net/hc/en-us/articles/360030771451-Minecraft-Java-Edition-1-14-3" }, @@ -18,27 +24,22 @@ public class Changelogs public static async Task GetChangelogs() { - string response; - using (var wc = new WebClient()) - { - var url = "https://launchercontent.mojang.com/javaPatchNotes.json"; - var data = await wc.DownloadDataTaskAsync(url) - .ConfigureAwait(false); - response = Encoding.UTF8.GetString(data); - } - - var obj = JObject.Parse(response); + var response = await HttpUtil.HttpClient.GetStreamAsync(PatchNotesUrl) + .ConfigureAwait(false); + var jsonDocument = await JsonDocument.ParseAsync(response).ConfigureAwait(false); + var root = jsonDocument.RootElement; + var versionDict = new Dictionary(); - var array = obj["entries"] as JArray; + var array = root.SafeGetProperty("entries")?.EnumerateArray(); if (array != null) { foreach (var item in array) { - var version = item["version"]?.ToString(); + var version = item.GetPropertyValue("version"); if (string.IsNullOrEmpty(version)) continue; - - var body = item["body"]?.ToString(); + + var body = item.GetPropertyValue("body"); versionDict[version] = body; } } @@ -55,29 +56,28 @@ private Changelogs(Dictionary versions) public string[] GetAvailableVersions() { - var list = new List(); - list.AddRange(versions.Keys); - - foreach (var item in changelogUrls) - { - if (!versions.ContainsKey(item.Key)) - list.Add(item.Key); - } + var availableVersions = new HashSet(); + + foreach (var item in versions.Keys) + availableVersions.Add(item); + + foreach (var item in AltUrls) + availableVersions.Add(item.Key); - return list.ToArray(); + return availableVersions.ToArray(); } public async Task GetChangelogHtml(string version) { if (versions.TryGetValue(version, out string? body)) return body; - if (changelogUrls.TryGetValue(version, out string? url)) + if (AltUrls.TryGetValue(version, out string? url)) return await GetChangelogFromUrl(url).ConfigureAwait(false); return null; } - private static readonly Regex articleRegex = new Regex( + private static readonly Regex ArticleRegex = new Regex( "
(.*)<\\/article>", RegexOptions.Singleline); private async Task GetChangelogFromUrl(string url) @@ -89,7 +89,7 @@ private async Task GetChangelogFromUrl(string url) html = Encoding.UTF8.GetString(data); } - var regResult = articleRegex.Match(html); + var regResult = ArticleRegex.Match(html); if (!regResult.Success) return ""; From 89ce036cc0b2cc99c19f7b7af0f9a99cb917c0de Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:11:44 +0900 Subject: [PATCH 025/137] remove Newtonsoft.Json dependency --- CmlLib/CmlLib.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CmlLib/CmlLib.csproj b/CmlLib/CmlLib.csproj index 49aa466..49d5461 100644 --- a/CmlLib/CmlLib.csproj +++ b/CmlLib/CmlLib.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0;net6.0 8.0 enable 3.4.0 @@ -39,7 +39,6 @@ Support all version, forge, optifine all - From 3cdc37e0a1726333ef2272145181b15128cc43e7 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:12:03 +0900 Subject: [PATCH 026/137] update CmlLibCoreSample --- CmlLibCoreSample/InstallerTest.cs | 4 +-- CmlLibCoreSample/Program.cs | 48 +++++++------------------------ CmlLibCoreSample/Test.cs | 30 ++----------------- 3 files changed, 14 insertions(+), 68 deletions(-) diff --git a/CmlLibCoreSample/InstallerTest.cs b/CmlLibCoreSample/InstallerTest.cs index 53aa165..eb5b9d5 100644 --- a/CmlLibCoreSample/InstallerTest.cs +++ b/CmlLibCoreSample/InstallerTest.cs @@ -86,7 +86,7 @@ public async Task TestLiteLoader() // install LiteLoader var liteLoader = (LiteLoaderVersionMetadata)liteLoaderVersions.GetVersionMetadata(selectLiteLoaderVersion); - var startVersionName = liteLoader.Install(path, await versions.GetVersionAsync(selectGameVersion)); + var startVersionName = await liteLoader.InstallAsync(path, await versions.GetVersionAsync(selectGameVersion)); // update version list await launcher.GetAllVersionsAsync(); @@ -128,7 +128,7 @@ public async Task TestLiteLoaderVanilla() Console.WriteLine(vanillaVersionName); var vanillaVersion = await versions.GetVersionAsync(vanillaVersionName); - var liteLoaderVersionName = liteLoaderVersion.Install(path, vanillaVersion); + var liteLoaderVersionName = await liteLoaderVersion.InstallAsync(path, vanillaVersion); versions = await launcher.GetAllVersionsAsync(); // update version lists var process = await launcher.CreateProcessAsync(liteLoaderVersionName, new MLaunchOption()); diff --git a/CmlLibCoreSample/Program.cs b/CmlLibCoreSample/Program.cs index 1cfd9f4..b592c1d 100644 --- a/CmlLibCoreSample/Program.cs +++ b/CmlLibCoreSample/Program.cs @@ -9,7 +9,7 @@ namespace CmlLibCoreSample { class Program { - static void Main() + public static async Task Main() { Console.WriteLine(CmlLib._Test.tstr); var p = new Program(); @@ -19,18 +19,18 @@ static void Main() // There are two login methods, one is using mojang email and password, and the other is using only username // Choose one which you want. - session = p.PremiumLogin(); // Login by mojang email and password + session = await p.PremiumLogin(); // Login by mojang email and password //session = p.OfflineLogin(); // Login by username // log login session information Console.WriteLine("Success to login : {0} / {1} / {2}", session.Username, session.UUID, session.AccessToken); // Launch - p.Start(session); + await p.Start(session); //p.StartAsync(session).GetAwaiter().GetResult(); } - MSession PremiumLogin() + async Task PremiumLogin() { var login = new MLogin(); @@ -38,7 +38,7 @@ MSession PremiumLogin() // If the cached session is invalid, it refreshes the session automatically. // Refreshing the session doesn't always succeed, so you have to handle this. Console.WriteLine("Attempting to automatically log in."); - var response = login.TryAutoLogin(); + var response = await login.TryAutoLogin(); if (!response.IsSuccess) // if cached session is invalid and failed to refresh token { @@ -49,7 +49,7 @@ MSession PremiumLogin() Console.WriteLine("Input your Mojang password: "); var pw = Console.ReadLine(); - response = login.Authenticate(email, pw); + response = await login.Authenticate(email, pw); if (!response.IsSuccess) { @@ -70,7 +70,7 @@ MSession OfflineLogin() return MSession.GetOfflineSession("tester123"); } - void Start(MSession session) + async Task Start(MSession session) { // Initializing Launcher @@ -99,7 +99,7 @@ void Start(MSession session) Console.WriteLine($"Initialized in {launcher.MinecraftPath.BasePath}"); // Get all installed profiles and load all profiles from mojang server - var versions = launcher.GetAllVersions(); + var versions = await launcher.GetAllVersionsAsync(); foreach (var item in versions) // Display all profiles { @@ -131,7 +131,7 @@ void Start(MSession session) // var process = await launcher.CreateProcessAsync("fabric-loader-0.11.3-1.16.5") // fabric-loader Console.WriteLine("input version (example: 1.12.2) : "); - var process = launcher.CreateProcess(Console.ReadLine(), launchOption); + var process = await launcher.CreateProcessAsync(Console.ReadLine(), launchOption); //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); Console.WriteLine(process.StartInfo.FileName); @@ -141,7 +141,7 @@ void Start(MSession session) var processUtil = new CmlLib.Utils.ProcessUtil(process); processUtil.OutputReceived += (s, e) => Console.WriteLine(e); processUtil.StartWithEvents(); - process.WaitForExit(); + await process.WaitForExitAsync(); // or just start it without print logs // process.Start(); @@ -149,34 +149,6 @@ void Start(MSession session) Console.ReadLine(); } - async Task StartAsync(MSession session) // async version - { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path); - - System.Net.ServicePointManager.DefaultConnectionLimit = 256; - - var versions = await launcher.GetAllVersionsAsync(); - foreach (var item in versions) - { - Console.WriteLine(item.Type + " " + item.Name); - } - - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - Console.WriteLine("input version (example: 1.12.2) : "); - var versionName = Console.ReadLine(); - var process = await launcher.CreateProcessAsync(versionName, new MLaunchOption - { - Session = session, - MaximumRamMb = 1024 - }); - - Console.WriteLine(process.StartInfo.Arguments); - process.Start(); - } - #region QuickStart // this code is from README.md diff --git a/CmlLibCoreSample/Test.cs b/CmlLibCoreSample/Test.cs index 35cf4a4..7117ba9 100644 --- a/CmlLibCoreSample/Test.cs +++ b/CmlLibCoreSample/Test.cs @@ -53,7 +53,7 @@ async Task TestAll(MSession session) if (!item.IsLocalVersion) continue; - var process = launcher.CreateProcess(item.Name, launchOption); + var process = await launcher.CreateProcessAsync(item.Name, launchOption); //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); Console.WriteLine(process.StartInfo.Arguments); @@ -72,34 +72,8 @@ async Task TestAll(MSession session) } process.Kill(); - process.WaitForExit(); + await process.WaitForExitAsync(); } - - return; - } - - void TestStartSync(MSession session) - { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path); - - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - var versions = launcher.GetAllVersions(); - foreach (var item in versions) - { - Console.WriteLine(item.Name); - } - - var process = launcher.CreateProcess("1.5.2", new MLaunchOption - { - Session = session - }); - - var processUtil = new CmlLib.Utils.ProcessUtil(process); - processUtil.OutputReceived += (s, e) => Console.WriteLine(e); - processUtil.StartWithEvents(); } int endTop = -1; From 08c44d267ed1302af6f4638f3ae2c6ca3494ce30 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:12:18 +0900 Subject: [PATCH 027/137] update CmlLibWinFormSample --- .../CmlLibWinFormSample.csproj | 18 -- CmlLibWinFormSample/ForgeInstall.Designer.cs | 161 ------------------ CmlLibWinFormSample/ForgeInstall.cs | 86 ---------- CmlLibWinFormSample/ForgeInstall.resx | 123 ------------- CmlLibWinFormSample/LoginForm.Designer.cs | 22 +-- CmlLibWinFormSample/LoginForm.cs | 118 ++++--------- CmlLibWinFormSample/MainForm.Designer.cs | 52 ++---- CmlLibWinFormSample/MainForm.cs | 28 --- .../MojangServerForm.Designer.cs | 91 ---------- CmlLibWinFormSample/MojangServerForm.cs | 41 ----- CmlLibWinFormSample/MojangServerForm.resx | 120 ------------- 11 files changed, 52 insertions(+), 808 deletions(-) delete mode 100644 CmlLibWinFormSample/ForgeInstall.Designer.cs delete mode 100644 CmlLibWinFormSample/ForgeInstall.cs delete mode 100644 CmlLibWinFormSample/ForgeInstall.resx delete mode 100644 CmlLibWinFormSample/MojangServerForm.Designer.cs delete mode 100644 CmlLibWinFormSample/MojangServerForm.cs delete mode 100644 CmlLibWinFormSample/MojangServerForm.resx diff --git a/CmlLibWinFormSample/CmlLibWinFormSample.csproj b/CmlLibWinFormSample/CmlLibWinFormSample.csproj index 1b114ac..152299a 100644 --- a/CmlLibWinFormSample/CmlLibWinFormSample.csproj +++ b/CmlLibWinFormSample/CmlLibWinFormSample.csproj @@ -56,12 +56,6 @@ ChangeLog.cs - - Form - - - ForgeInstall.cs - Form @@ -98,12 +92,6 @@ MainForm.cs - - Form - - - MojangServerForm.cs - @@ -116,9 +104,6 @@ ChangeLog.cs - - ForgeInstall.cs - GameLog.cs @@ -128,9 +113,6 @@ JavaForm.cs - - MojangServerForm.cs - PathForm.cs diff --git a/CmlLibWinFormSample/ForgeInstall.Designer.cs b/CmlLibWinFormSample/ForgeInstall.Designer.cs deleted file mode 100644 index 012aefd..0000000 --- a/CmlLibWinFormSample/ForgeInstall.Designer.cs +++ /dev/null @@ -1,161 +0,0 @@ -namespace CmlLibWinFormSample -{ - partial class ForgeInstall - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.txtMC = new System.Windows.Forms.TextBox(); - this.txtForge = new System.Windows.Forms.TextBox(); - this.btnInstall = new System.Windows.Forms.Button(); - this.pbProgress = new System.Windows.Forms.ProgressBar(); - this.lbStatus = new System.Windows.Forms.Label(); - this.txtLog = new System.Windows.Forms.RichTextBox(); - this.label4 = new System.Windows.Forms.Label(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - this.SuspendLayout(); - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(19, 18); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(116, 12); - this.label1.TabIndex = 0; - this.label1.Text = "Minecraft Version : "; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(39, 45); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(96, 12); - this.label2.TabIndex = 1; - this.label2.Text = "Forge Version : "; - // - // txtMC - // - this.txtMC.Location = new System.Drawing.Point(141, 15); - this.txtMC.Name = "txtMC"; - this.txtMC.Size = new System.Drawing.Size(162, 21); - this.txtMC.TabIndex = 2; - // - // txtForge - // - this.txtForge.Location = new System.Drawing.Point(141, 42); - this.txtForge.Name = "txtForge"; - this.txtForge.Size = new System.Drawing.Size(162, 21); - this.txtForge.TabIndex = 3; - // - // btnInstall - // - this.btnInstall.Location = new System.Drawing.Point(309, 15); - this.btnInstall.Name = "btnInstall"; - this.btnInstall.Size = new System.Drawing.Size(75, 48); - this.btnInstall.TabIndex = 4; - this.btnInstall.Text = "Install"; - this.btnInstall.UseVisualStyleBackColor = true; - this.btnInstall.Click += new System.EventHandler(this.btnInstall_Click); - // - // pbProgress - // - this.pbProgress.Location = new System.Drawing.Point(21, 97); - this.pbProgress.Name = "pbProgress"; - this.pbProgress.Size = new System.Drawing.Size(363, 23); - this.pbProgress.TabIndex = 5; - // - // lbStatus - // - this.lbStatus.AutoSize = true; - this.lbStatus.Location = new System.Drawing.Point(19, 123); - this.lbStatus.Name = "lbStatus"; - this.lbStatus.Size = new System.Drawing.Size(38, 12); - this.lbStatus.TabIndex = 6; - this.lbStatus.Text = "label3"; - // - // txtLog - // - this.txtLog.Location = new System.Drawing.Point(21, 142); - this.txtLog.Name = "txtLog"; - this.txtLog.ReadOnly = true; - this.txtLog.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedVertical; - this.txtLog.Size = new System.Drawing.Size(363, 296); - this.txtLog.TabIndex = 7; - this.txtLog.Text = ""; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(139, 66); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(140, 24); - this.label4.TabIndex = 8; - this.label4.Text = "ex) 1.12.2 / 14.23.5.2768\r\n 1.16.2 / 33.0.5"; - // - // timer1 - // - this.timer1.Enabled = true; - this.timer1.Interval = 1000; - this.timer1.Tick += new System.EventHandler(this.timer1_Tick); - // - // ForgeInstall - // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(410, 450); - this.Controls.Add(this.label4); - this.Controls.Add(this.txtLog); - this.Controls.Add(this.lbStatus); - this.Controls.Add(this.pbProgress); - this.Controls.Add(this.btnInstall); - this.Controls.Add(this.txtForge); - this.Controls.Add(this.txtMC); - this.Controls.Add(this.label2); - this.Controls.Add(this.label1); - this.Name = "ForgeInstall"; - this.Text = "ForgeInstall"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.TextBox txtMC; - private System.Windows.Forms.TextBox txtForge; - private System.Windows.Forms.Button btnInstall; - private System.Windows.Forms.ProgressBar pbProgress; - private System.Windows.Forms.Label lbStatus; - private System.Windows.Forms.RichTextBox txtLog; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.Timer timer1; - } -} \ No newline at end of file diff --git a/CmlLibWinFormSample/ForgeInstall.cs b/CmlLibWinFormSample/ForgeInstall.cs deleted file mode 100644 index 6be8c15..0000000 --- a/CmlLibWinFormSample/ForgeInstall.cs +++ /dev/null @@ -1,86 +0,0 @@ -using CmlLib.Core; -using CmlLib.Core.Downloader; -using CmlLib.Core.Installer; -using System; -using System.Collections.Concurrent; -using System.Threading; -using System.Windows.Forms; - -namespace CmlLibWinFormSample -{ - public partial class ForgeInstall : Form - { - public ForgeInstall(MinecraftPath path, string java) - { - this.javapath = java; - this.Path = path; - logQueue = new ConcurrentQueue(); - InitializeComponent(); - } - - public string LastInstalledVersion { get; private set; } - MinecraftPath Path; - string javapath; - - ConcurrentQueue logQueue; - - private void btnInstall_Click(object sender, EventArgs e) - { - btnInstall.Enabled = false; - txtMC.Enabled = false; - txtForge.Enabled = false; - - new Thread(() => - { - try - { - var forge = new MForge(Path, javapath); - forge.FileChanged += Forge_FileChanged; - forge.InstallerOutput += Forge_InstallerOutput; - var versionName = forge.InstallForge(txtMC.Text, txtForge.Text); - LastInstalledVersion = versionName; - MessageBox.Show($"{versionName} was successfully installed"); - } - catch (Exception ex) - { - MessageBox.Show(ex.ToString()); - } - finally - { - Invoke(new Action(() => - { - btnInstall.Enabled = true; - txtMC.Enabled = true; - txtForge.Enabled = true; - })); - } - }).Start(); - } - - private void Forge_InstallerOutput(object sender, string e) - { - if (e != null) - logQueue.Enqueue(e); - } - - private void Forge_FileChanged(DownloadFileChangedEventArgs e) - { - Invoke(new Action(() => - { - lbStatus.Text = $"{e.FileKind} - {e.FileName}"; - pbProgress.Maximum = e.TotalFileCount; - pbProgress.Value = e.ProgressedFileCount; - })); - } - - private void timer1_Tick(object sender, EventArgs e) - { - string item; - while (logQueue.TryDequeue(out item)) - { - txtLog.AppendText(item); - txtLog.AppendText("\n"); - } - } - } -} diff --git a/CmlLibWinFormSample/ForgeInstall.resx b/CmlLibWinFormSample/ForgeInstall.resx deleted file mode 100644 index 1f666f2..0000000 --- a/CmlLibWinFormSample/ForgeInstall.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/CmlLibWinFormSample/LoginForm.Designer.cs b/CmlLibWinFormSample/LoginForm.Designer.cs index 84785db..3b9926a 100644 --- a/CmlLibWinFormSample/LoginForm.Designer.cs +++ b/CmlLibWinFormSample/LoginForm.Designer.cs @@ -38,7 +38,6 @@ private void InitializeComponent() this.btnDeleteToken = new System.Windows.Forms.Button(); this.label7 = new System.Windows.Forms.Label(); this.gMojangLogin = new System.Windows.Forms.GroupBox(); - this.btnAutoLoginMojangLauncher = new System.Windows.Forms.Button(); this.btnAutoLogin = new System.Windows.Forms.Button(); this.gOfflineLogin = new System.Windows.Forms.GroupBox(); this.btnOfflineLogin = new System.Windows.Forms.Button(); @@ -141,7 +140,6 @@ private void InitializeComponent() // // gMojangLogin // - this.gMojangLogin.Controls.Add(this.btnAutoLoginMojangLauncher); this.gMojangLogin.Controls.Add(this.btnAutoLogin); this.gMojangLogin.Controls.Add(this.btnLogin); this.gMojangLogin.Controls.Add(this.label7); @@ -161,20 +159,9 @@ private void InitializeComponent() this.gMojangLogin.TabStop = false; this.gMojangLogin.Text = "Mojang Login"; // - // btnAutoLoginMojangLauncher - // - this.btnAutoLoginMojangLauncher.Location = new System.Drawing.Point(104, 64); - this.btnAutoLoginMojangLauncher.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnAutoLoginMojangLauncher.Name = "btnAutoLoginMojangLauncher"; - this.btnAutoLoginMojangLauncher.Size = new System.Drawing.Size(322, 29); - this.btnAutoLoginMojangLauncher.TabIndex = 15; - this.btnAutoLoginMojangLauncher.Text = "TryAutoLogin (launcher_accounts.json)"; - this.btnAutoLoginMojangLauncher.UseVisualStyleBackColor = true; - this.btnAutoLoginMojangLauncher.Click += new System.EventHandler(this.btnAutoLoginMojangLauncher_Click); - // // btnAutoLogin // - this.btnAutoLogin.Location = new System.Drawing.Point(104, 27); + this.btnAutoLogin.Location = new System.Drawing.Point(107, 64); this.btnAutoLogin.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.btnAutoLogin.Name = "btnAutoLogin"; this.btnAutoLogin.Size = new System.Drawing.Size(322, 29); @@ -228,7 +215,7 @@ private void InitializeComponent() // label2 // this.label2.AutoSize = true; - this.label2.Font = new System.Drawing.Font("굴림", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte) (129))); + this.label2.Font = new System.Drawing.Font("굴림", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); this.label2.Location = new System.Drawing.Point(8, 15); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(532, 19); @@ -238,7 +225,7 @@ private void InitializeComponent() // label1 // this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte) (129))); + this.label1.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); this.label1.Location = new System.Drawing.Point(19, 42); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(317, 15); @@ -248,7 +235,7 @@ private void InitializeComponent() // label3 // this.label3.AutoSize = true; - this.label3.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte) (129))); + this.label3.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); this.label3.Location = new System.Drawing.Point(12, 298); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(280, 15); @@ -293,7 +280,6 @@ private void InitializeComponent() private System.Windows.Forms.Button btnOfflineLogin; private System.Windows.Forms.TextBox txtUsername; private System.Windows.Forms.Label label8; - private System.Windows.Forms.Button btnAutoLoginMojangLauncher; private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label3; diff --git a/CmlLibWinFormSample/LoginForm.cs b/CmlLibWinFormSample/LoginForm.cs index 611a8ce..d35065a 100644 --- a/CmlLibWinFormSample/LoginForm.cs +++ b/CmlLibWinFormSample/LoginForm.cs @@ -19,81 +19,39 @@ private void LoginForm_Load(object sender, EventArgs e) btnAutoLogin_Click(null, null); } - private void btnAutoLogin_Click(object sender, EventArgs e) + private async void btnAutoLogin_Click(object sender, EventArgs e) { gMojangLogin.Enabled = false; gOfflineLogin.Enabled = false; - var th = new Thread(() => - { - var result = login.TryAutoLogin(); + var result = await login.TryAutoLogin(); - if (result.Result != MLoginResult.Success) - { - MessageBox.Show($"Failed to AutoLogin : {result.Result}\n{result.ErrorMessage}"); - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; - })); - return; - } - - MessageBox.Show("Auto Login Success!"); + if (result.Result != MLoginResult.Success) + { + MessageBox.Show($"Failed to AutoLogin : {result.Result}\n{result.ErrorMessage}"); Invoke(new Action(() => { gMojangLogin.Enabled = true; gOfflineLogin.Enabled = true; - - btnAutoLogin.Enabled = false; - btnLogin.Enabled = false; - btnAutoLoginMojangLauncher.Enabled = false; - btnLogin.Text = "Auto Login\nSuccess"; - - UpdateSession(result.Session); })); - }); - th.Start(); - } - - private void btnAutoLoginMojangLauncher_Click(object sender, EventArgs e) - { - gMojangLogin.Enabled = false; - gOfflineLogin.Enabled = false; + return; + } - var th = new Thread(() => + MessageBox.Show("Auto Login Success!"); + Invoke(new Action(() => { - var result = login.TryAutoLoginFromMojangLauncher(); - - if (result.Result != MLoginResult.Success) - { - MessageBox.Show($"Failed to AutoLogin : {result.Result}\n{result.ErrorMessage}"); - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; - })); - return; - } - - MessageBox.Show("Auto Login Success!"); - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; + gMojangLogin.Enabled = true; + gOfflineLogin.Enabled = true; - btnAutoLogin.Enabled = false; - btnAutoLoginMojangLauncher.Enabled = false; - btnLogin.Enabled = false; - btnLogin.Text = "Auto Login\nSuccess"; + btnAutoLogin.Enabled = false; + btnLogin.Enabled = false; + btnLogin.Text = "Auto Login\nSuccess"; - UpdateSession(result.Session); - })); - }); - th.Start(); + UpdateSession(result.Session); + })); } - private void btnLogin_Click(object sender, EventArgs e) + private async void btnLogin_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(txtEmail.Text) || string.IsNullOrWhiteSpace(txtPassword.Text)) { @@ -104,33 +62,29 @@ private void btnLogin_Click(object sender, EventArgs e) gMojangLogin.Enabled = false; gOfflineLogin.Enabled = false; - var th = new Thread(new ThreadStart(delegate + var result = await login.Authenticate(txtEmail.Text, txtPassword.Text); + if (result.Result == MLoginResult.Success) { - var result = login.Authenticate(txtEmail.Text, txtPassword.Text); - if (result.Result == MLoginResult.Success) + MessageBox.Show("Login Success"); // Success Login + Invoke(new Action(() => { - MessageBox.Show("Login Success"); // Success Login - Invoke(new Action(() => - { - UpdateSession(result.Session); - })); - } - else + UpdateSession(result.Session); + })); + } + else + { + MessageBox.Show(result.Result.ToString() + "\n" + result.ErrorMessage); // Failed to login. Show error message + Invoke(new Action(() => { - MessageBox.Show(result.Result.ToString() + "\n" + result.ErrorMessage); // Failed to login. Show error message - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; - })); - } - })); - th.Start(); + gMojangLogin.Enabled = true; + gOfflineLogin.Enabled = true; + })); + } } - private void btnSignout_Click(object sender, EventArgs e) + private async void btnSignout_Click(object sender, EventArgs e) { - var result = login.Signout(txtEmail.Text, txtPassword.Text); + var result = await login.Signout(txtEmail.Text, txtPassword.Text); if (result) { MessageBox.Show("Success"); @@ -140,9 +94,9 @@ private void btnSignout_Click(object sender, EventArgs e) MessageBox.Show("Fail"); } - private void btnInvalidate_Click(object sender, EventArgs e) + private async void btnInvalidate_Click(object sender, EventArgs e) { - var result = login.Invalidate(); + var result = await login.Invalidate(); if (result) { MessageBox.Show("Success"); diff --git a/CmlLibWinFormSample/MainForm.Designer.cs b/CmlLibWinFormSample/MainForm.Designer.cs index 44a4c6c..eab12f8 100644 --- a/CmlLibWinFormSample/MainForm.Designer.cs +++ b/CmlLibWinFormSample/MainForm.Designer.cs @@ -80,13 +80,11 @@ private void InitializeComponent() this.cbSkipHashCheck = new System.Windows.Forms.CheckBox(); this.cbSkipAssetsDownload = new System.Windows.Forms.CheckBox(); this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.btnSortFilter = new System.Windows.Forms.Button(); this.btnRefreshVersion = new System.Windows.Forms.Button(); - this.btnForgeInstall = new System.Windows.Forms.Button(); this.btnSetLastVersion = new System.Windows.Forms.Button(); - this.btnMojangServer = new System.Windows.Forms.Button(); this.btnOptions = new System.Windows.Forms.Button(); this.lbLibraryVersion = new System.Windows.Forms.Label(); - this.btnSortFilter = new System.Windows.Forms.Button(); this.groupBox2.SuspendLayout(); this.groupBox1.SuspendLayout(); this.groupBox3.SuspendLayout(); @@ -613,7 +611,6 @@ private void InitializeComponent() // this.groupBox4.Controls.Add(this.btnSortFilter); this.groupBox4.Controls.Add(this.btnRefreshVersion); - this.groupBox4.Controls.Add(this.btnForgeInstall); this.groupBox4.Controls.Add(this.btnSetLastVersion); this.groupBox4.Controls.Add(this.cbVersion); this.groupBox4.Controls.Add(this.label1); @@ -627,6 +624,17 @@ private void InitializeComponent() this.groupBox4.TabStop = false; this.groupBox4.Text = "Launch"; // + // btnSortFilter + // + this.btnSortFilter.Location = new System.Drawing.Point(149, 65); + this.btnSortFilter.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.btnSortFilter.Name = "btnSortFilter"; + this.btnSortFilter.Size = new System.Drawing.Size(163, 29); + this.btnSortFilter.TabIndex = 5; + this.btnSortFilter.Text = "Sort option"; + this.btnSortFilter.UseVisualStyleBackColor = true; + this.btnSortFilter.Click += new System.EventHandler(this.btnSortFilter_Click); + // // btnRefreshVersion // this.btnRefreshVersion.Location = new System.Drawing.Point(323, 65); @@ -638,17 +646,6 @@ private void InitializeComponent() this.btnRefreshVersion.UseVisualStyleBackColor = true; this.btnRefreshVersion.Click += new System.EventHandler(this.btnRefreshVersion_Click); // - // btnForgeInstall - // - this.btnForgeInstall.Location = new System.Drawing.Point(32, 65); - this.btnForgeInstall.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnForgeInstall.Name = "btnForgeInstall"; - this.btnForgeInstall.Size = new System.Drawing.Size(111, 29); - this.btnForgeInstall.TabIndex = 3; - this.btnForgeInstall.Text = "Install Forge"; - this.btnForgeInstall.UseVisualStyleBackColor = true; - this.btnForgeInstall.Click += new System.EventHandler(this.btnForgeInstall_Click); - // // btnSetLastVersion // this.btnSetLastVersion.Location = new System.Drawing.Point(323, 31); @@ -660,17 +657,6 @@ private void InitializeComponent() this.btnSetLastVersion.UseVisualStyleBackColor = true; this.btnSetLastVersion.Click += new System.EventHandler(this.btnSetLastVersion_Click); // - // btnMojangServer - // - this.btnMojangServer.Location = new System.Drawing.Point(166, 558); - this.btnMojangServer.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnMojangServer.Name = "btnMojangServer"; - this.btnMojangServer.Size = new System.Drawing.Size(145, 29); - this.btnMojangServer.TabIndex = 29; - this.btnMojangServer.Text = "MojangServer"; - this.btnMojangServer.UseVisualStyleBackColor = true; - this.btnMojangServer.Click += new System.EventHandler(this.btnMojangServer_Click); - // // btnOptions // this.btnOptions.Location = new System.Drawing.Point(318, 559); @@ -690,17 +676,6 @@ private void InitializeComponent() this.lbLibraryVersion.TabIndex = 31; this.lbLibraryVersion.Text = "CmlLib.Core"; // - // btnSortFilter - // - this.btnSortFilter.Location = new System.Drawing.Point(149, 65); - this.btnSortFilter.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnSortFilter.Name = "btnSortFilter"; - this.btnSortFilter.Size = new System.Drawing.Size(163, 29); - this.btnSortFilter.TabIndex = 5; - this.btnSortFilter.Text = "Sort option"; - this.btnSortFilter.UseVisualStyleBackColor = true; - this.btnSortFilter.Click += new System.EventHandler(this.btnSortFilter_Click); - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); @@ -708,7 +683,6 @@ private void InitializeComponent() this.ClientSize = new System.Drawing.Size(918, 608); this.Controls.Add(this.lbLibraryVersion); this.Controls.Add(this.btnOptions); - this.Controls.Add(this.btnMojangServer); this.Controls.Add(this.groupBox4); this.Controls.Add(this.groupBox3); this.Controls.Add(this.btnChangelog); @@ -792,9 +766,7 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox cbSkipAssetsDownload; private System.Windows.Forms.GroupBox groupBox4; private System.Windows.Forms.Button btnSetLastVersion; - private System.Windows.Forms.Button btnMojangServer; private System.Windows.Forms.Button btnOptions; - private System.Windows.Forms.Button btnForgeInstall; private System.Windows.Forms.Button btnRefreshVersion; private System.Windows.Forms.CheckBox cbFullscreen; private System.Windows.Forms.CheckBox cbSkipHashCheck; diff --git a/CmlLibWinFormSample/MainForm.cs b/CmlLibWinFormSample/MainForm.cs index d733853..189537f 100644 --- a/CmlLibWinFormSample/MainForm.cs +++ b/CmlLibWinFormSample/MainForm.cs @@ -272,27 +272,6 @@ private void setUIEnabled(bool value) groupBox4.Enabled = value; } - // not stable - private void btnForgeInstall_Click(object sender, EventArgs e) - { - setUIEnabled(false); - new Thread(() => - { - var forgeJava = ""; - var java = new MJava(); - java.ProgressChanged += Launcher_ProgressChanged; - forgeJava = java.CheckJava(); - - Invoke(new Action(async () => - { - var forgeForm = new ForgeInstall(gamePath, forgeJava); - forgeForm.ShowDialog(); - setUIEnabled(true); - await refreshVersions(forgeForm.LastInstalledVersion); - })); - }).Start(); - } - private void StartProcess(Process process) { File.WriteAllText("launcher.txt", process.StartInfo.Arguments); @@ -318,13 +297,6 @@ private void btnChangelog_Click(object sender, EventArgs e) f.Show(); } - private void btnMojangServer_Click(object sender, EventArgs e) - { - // Mojang Server - var f = new MojangServerForm(); - f.Show(); - } - private void btnOptions_Click(object sender, EventArgs e) { // options.txt diff --git a/CmlLibWinFormSample/MojangServerForm.Designer.cs b/CmlLibWinFormSample/MojangServerForm.Designer.cs deleted file mode 100644 index 9ec8f8e..0000000 --- a/CmlLibWinFormSample/MojangServerForm.Designer.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace CmlLibWinFormSample -{ - partial class MojangServerForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.listView1 = new System.Windows.Forms.ListView(); - this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.btnClose = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // listView1 - // - this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeader1, - this.columnHeader2}); - this.listView1.HideSelection = false; - this.listView1.Location = new System.Drawing.Point(12, 12); - this.listView1.Name = "listView1"; - this.listView1.Size = new System.Drawing.Size(422, 174); - this.listView1.TabIndex = 0; - this.listView1.UseCompatibleStateImageBehavior = false; - this.listView1.View = System.Windows.Forms.View.Details; - // - // columnHeader1 - // - this.columnHeader1.Text = "Name"; - this.columnHeader1.Width = 345; - // - // columnHeader2 - // - this.columnHeader2.Text = "Value"; - this.columnHeader2.Width = 69; - // - // btnClose - // - this.btnClose.Location = new System.Drawing.Point(12, 192); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(422, 37); - this.btnClose.TabIndex = 1; - this.btnClose.Text = "Close"; - this.btnClose.UseVisualStyleBackColor = true; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // MojangServerForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(446, 241); - this.Controls.Add(this.btnClose); - this.Controls.Add(this.listView1); - this.Name = "MojangServerForm"; - this.Text = "MojangServerForm"; - this.Load += new System.EventHandler(this.MojangServerForm_Load); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.ListView listView1; - private System.Windows.Forms.ColumnHeader columnHeader1; - private System.Windows.Forms.ColumnHeader columnHeader2; - private System.Windows.Forms.Button btnClose; - } -} \ No newline at end of file diff --git a/CmlLibWinFormSample/MojangServerForm.cs b/CmlLibWinFormSample/MojangServerForm.cs deleted file mode 100644 index 19481fc..0000000 --- a/CmlLibWinFormSample/MojangServerForm.cs +++ /dev/null @@ -1,41 +0,0 @@ -using CmlLib.Core.Mojang; -using System; -using System.Threading; -using System.Windows.Forms; - -namespace CmlLibWinFormSample -{ - public partial class MojangServerForm : Form - { - public MojangServerForm() - { - InitializeComponent(); - } - - private void MojangServerForm_Load(object sender, EventArgs e) - { - new Thread(() => - { - var status = MojangServerStatus.GetStatus(); - var properties = status.GetType().GetProperties(); - - Invoke(new Action(() => - { - foreach (var item in properties) - { - listView1.Items.Add(new ListViewItem(new [] - { - item.Name, - item.GetValue(status).ToString() - })); - } - })); - }).Start(); - } - - private void btnClose_Click(object sender, EventArgs e) - { - this.Close(); - } - } -} diff --git a/CmlLibWinFormSample/MojangServerForm.resx b/CmlLibWinFormSample/MojangServerForm.resx deleted file mode 100644 index 1af7de1..0000000 --- a/CmlLibWinFormSample/MojangServerForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file From 8f544a155a7930f75fbe9a0f060be92939a3676f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 12 Apr 2022 16:19:43 +0900 Subject: [PATCH 028/137] move FileCheckers to CmlLib.Core.FileChecker namespace --- CmlLib/Core/CMLauncher.cs | 1 + CmlLib/Core/{Files => FileChecker}/AssetChecker.cs | 11 ++++++----- CmlLib/Core/{Files => FileChecker}/ClientChecker.cs | 5 +++-- .../{Files => FileChecker}/FileCheckerCollection.cs | 3 ++- CmlLib/Core/{Files => FileChecker}/IFileChecker.cs | 4 ++-- CmlLib/Core/{Files => FileChecker}/JavaChecker.cs | 3 +-- CmlLib/Core/{Files => FileChecker}/LibraryChecker.cs | 11 ++++++----- CmlLib/Core/{Files => FileChecker}/LogChecker.cs | 8 ++++---- CmlLib/Core/{Files => FileChecker}/ModChecker.cs | 11 ++++++----- 9 files changed, 31 insertions(+), 26 deletions(-) rename CmlLib/Core/{Files => FileChecker}/AssetChecker.cs (98%) rename CmlLib/Core/{Files => FileChecker}/ClientChecker.cs (97%) rename CmlLib/Core/{Files => FileChecker}/FileCheckerCollection.cs (98%) rename CmlLib/Core/{Files => FileChecker}/IFileChecker.cs (93%) rename CmlLib/Core/{Files => FileChecker}/JavaChecker.cs (99%) rename CmlLib/Core/{Files => FileChecker}/LibraryChecker.cs (97%) rename CmlLib/Core/{Files => FileChecker}/LogChecker.cs (96%) rename CmlLib/Core/{Files => FileChecker}/ModChecker.cs (95%) diff --git a/CmlLib/Core/CMLauncher.cs b/CmlLib/Core/CMLauncher.cs index 9a9efe1..7e0106b 100644 --- a/CmlLib/Core/CMLauncher.cs +++ b/CmlLib/Core/CMLauncher.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; +using CmlLib.Core.FileChecker; using CmlLib.Core.Java; namespace CmlLib.Core diff --git a/CmlLib/Core/Files/AssetChecker.cs b/CmlLib/Core/FileChecker/AssetChecker.cs similarity index 98% rename from CmlLib/Core/Files/AssetChecker.cs rename to CmlLib/Core/FileChecker/AssetChecker.cs index 5023a2a..46aeeef 100644 --- a/CmlLib/Core/Files/AssetChecker.cs +++ b/CmlLib/Core/FileChecker/AssetChecker.cs @@ -1,7 +1,4 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.Version; -using CmlLib.Utils; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -9,8 +6,12 @@ using System.Net; using System.Text.Json; using System.Threading.Tasks; +using CmlLib.Core.Downloader; +using CmlLib.Core.Files; +using CmlLib.Core.Version; +using CmlLib.Utils; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public sealed class AssetChecker : IFileChecker { diff --git a/CmlLib/Core/Files/ClientChecker.cs b/CmlLib/Core/FileChecker/ClientChecker.cs similarity index 97% rename from CmlLib/Core/Files/ClientChecker.cs rename to CmlLib/Core/FileChecker/ClientChecker.cs index f305733..bea278a 100644 --- a/CmlLib/Core/Files/ClientChecker.cs +++ b/CmlLib/Core/FileChecker/ClientChecker.cs @@ -1,10 +1,11 @@ using System; +using System.Threading.Tasks; using CmlLib.Core.Downloader; +using CmlLib.Core.Files; using CmlLib.Core.Version; using CmlLib.Utils; -using System.Threading.Tasks; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public sealed class ClientChecker : IFileChecker { diff --git a/CmlLib/Core/Files/FileCheckerCollection.cs b/CmlLib/Core/FileChecker/FileCheckerCollection.cs similarity index 98% rename from CmlLib/Core/Files/FileCheckerCollection.cs rename to CmlLib/Core/FileChecker/FileCheckerCollection.cs index eb7940d..95b94c5 100644 --- a/CmlLib/Core/Files/FileCheckerCollection.cs +++ b/CmlLib/Core/FileChecker/FileCheckerCollection.cs @@ -1,8 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using CmlLib.Core.Files; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public class FileCheckerCollection : IEnumerable { diff --git a/CmlLib/Core/Files/IFileChecker.cs b/CmlLib/Core/FileChecker/IFileChecker.cs similarity index 93% rename from CmlLib/Core/Files/IFileChecker.cs rename to CmlLib/Core/FileChecker/IFileChecker.cs index 5a620b8..3115e2f 100644 --- a/CmlLib/Core/Files/IFileChecker.cs +++ b/CmlLib/Core/FileChecker/IFileChecker.cs @@ -1,9 +1,9 @@ using System; +using System.Threading.Tasks; using CmlLib.Core.Downloader; using CmlLib.Core.Version; -using System.Threading.Tasks; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public interface IFileChecker { diff --git a/CmlLib/Core/Files/JavaChecker.cs b/CmlLib/Core/FileChecker/JavaChecker.cs similarity index 99% rename from CmlLib/Core/Files/JavaChecker.cs rename to CmlLib/Core/FileChecker/JavaChecker.cs index 40fa24c..bf0ef1d 100644 --- a/CmlLib/Core/Files/JavaChecker.cs +++ b/CmlLib/Core/FileChecker/JavaChecker.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; using System.Text.Json; using System.Threading.Tasks; using CmlLib.Core.Downloader; @@ -12,7 +11,7 @@ using CmlLib.Core.Version; using CmlLib.Utils; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public class JavaChecker : IFileChecker { diff --git a/CmlLib/Core/Files/LibraryChecker.cs b/CmlLib/Core/FileChecker/LibraryChecker.cs similarity index 97% rename from CmlLib/Core/Files/LibraryChecker.cs rename to CmlLib/Core/FileChecker/LibraryChecker.cs index 43f782c..383ae73 100644 --- a/CmlLib/Core/Files/LibraryChecker.cs +++ b/CmlLib/Core/FileChecker/LibraryChecker.cs @@ -1,13 +1,14 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.Version; -using CmlLib.Utils; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using CmlLib.Core.Downloader; +using CmlLib.Core.Files; +using CmlLib.Core.Version; +using CmlLib.Utils; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public sealed class LibraryChecker : IFileChecker { diff --git a/CmlLib/Core/Files/LogChecker.cs b/CmlLib/Core/FileChecker/LogChecker.cs similarity index 96% rename from CmlLib/Core/Files/LogChecker.cs rename to CmlLib/Core/FileChecker/LogChecker.cs index 7e186a9..98dd831 100644 --- a/CmlLib/Core/Files/LogChecker.cs +++ b/CmlLib/Core/FileChecker/LogChecker.cs @@ -1,10 +1,10 @@ -using CmlLib.Core.Downloader; +using System; +using System.Threading.Tasks; +using CmlLib.Core.Downloader; using CmlLib.Core.Version; using CmlLib.Utils; -using System; -using System.Threading.Tasks; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public sealed class LogChecker : IFileChecker { diff --git a/CmlLib/Core/Files/ModChecker.cs b/CmlLib/Core/FileChecker/ModChecker.cs similarity index 95% rename from CmlLib/Core/Files/ModChecker.cs rename to CmlLib/Core/FileChecker/ModChecker.cs index 8d4060d..091d7ab 100644 --- a/CmlLib/Core/Files/ModChecker.cs +++ b/CmlLib/Core/FileChecker/ModChecker.cs @@ -1,13 +1,14 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.Version; -using CmlLib.Utils; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using CmlLib.Core.Downloader; +using CmlLib.Core.Files; +using CmlLib.Core.Version; +using CmlLib.Utils; -namespace CmlLib.Core.Files +namespace CmlLib.Core.FileChecker { public class ModChecker : IFileChecker { From ce104b7f4a60baa439e487360ece497d33400869 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 26 Apr 2022 15:23:08 +0900 Subject: [PATCH 029/137] add test project --- CmlLib.Core.Test/IOUtilTest.cs | 53 +++++++++++++++++++++++++ CmlLib.Core.Test/MapperTest.cs | 28 ++++++++++++++ CmlLib.Core.Test/PackageNameTest.cs | 60 +++++++++++++++++++++++++++++ CmlLib.sln | 6 +++ CmlLib/CmlLib.csproj | 4 ++ 5 files changed, 151 insertions(+) create mode 100644 CmlLib.Core.Test/IOUtilTest.cs create mode 100644 CmlLib.Core.Test/MapperTest.cs create mode 100644 CmlLib.Core.Test/PackageNameTest.cs diff --git a/CmlLib.Core.Test/IOUtilTest.cs b/CmlLib.Core.Test/IOUtilTest.cs new file mode 100644 index 0000000..2543e7a --- /dev/null +++ b/CmlLib.Core.Test/IOUtilTest.cs @@ -0,0 +1,53 @@ +using CmlLib.Utils; +using NUnit.Framework; + +namespace CmlLib.Core.Test; + +public class IOUtilTest +{ + [Platform("Win")] + [TestCase(@"C:\\a/", @"C:\a")] + [TestCase(@"C:\\a\/b.txt", @"C:\a\b.txt")] + [TestCase(@"C:\/a\\b/\c.txt", @"C:\a\b\c.txt")] + [TestCase(@"/root/f1\.txt", @"C:\root\f1\.txt")] + public void TestNormalizePathWin(string path, string expected) + { + var normalizedPath = IOUtil.NormalizePath(path); + Assert.AreEqual(expected, normalizedPath); + } + + [Platform("Unix")] + [TestCase(@"/root/f1\.txt", @"/root/f1\.txt")] + [TestCase(@"/root/path1/", @"/root/path1")] + public void TestNormalizePathUnix(string path, string expected) + { + var normalizedPath = IOUtil.NormalizePath(path); + Assert.AreEqual(expected, normalizedPath); + } + + [Platform("Win")] + [Test] + public void TestCombinePathWin() + { + var paths = new[] + { + @"C:\test\lib1.zip", @"C:\test\lib space 2.zip" + }; + var exp = @"C:\test\lib1.zip;""C:\test\lib space 2.zip"""; + + Assert.AreEqual(exp, IOUtil.CombinePath(paths)); + } + + [Platform("Unix")] + [Test] + public void TestCombinePathUnix() + { + var paths = new[] + { + @"/root/f1.zip", @"/root/f2 space sss.txt" + }; + var exp = @"/root/f1.zip:""/root/f2 space sss.txt"""; + + Assert.AreEqual(exp, IOUtil.CombinePath(paths)); + } +} \ No newline at end of file diff --git a/CmlLib.Core.Test/MapperTest.cs b/CmlLib.Core.Test/MapperTest.cs new file mode 100644 index 0000000..ff9f6e4 --- /dev/null +++ b/CmlLib.Core.Test/MapperTest.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; + +namespace CmlLib.Core.Test; + +public class MapperTest +{ + [TestCase( + @"[de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip]", + @"C:\libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip")] + [TestCase( + @"[net.minecraft:client:1.16.2-20200812.004259:slim]", + @"C:\libraries\net\minecraft\client\1.16.2-20200812.004259\client-1.16.2-20200812.004259-slim.jar")] + public void TestFullPath(string input, string exp) + { + Assert.AreEqual(exp, Mapper.ToFullPath(input, @"C:\libraries\")); + } + + [TestCase("", "")] + [TestCase("key=value", "key=value")] + [TestCase("key=value 1", "key=\"value 1\"")] + [TestCase("key=\"value 2\"", "key=\"value 2\"")] + [TestCase("value 3", "\"value 3\"")] + [TestCase("\"value 4\"", "\"value 4\"")] + public void TestHandleEmptyArg(string input, string exp) + { + Assert.AreEqual(exp, Mapper.HandleEmptyArg(input)); + } +} \ No newline at end of file diff --git a/CmlLib.Core.Test/PackageNameTest.cs b/CmlLib.Core.Test/PackageNameTest.cs new file mode 100644 index 0000000..201a6a4 --- /dev/null +++ b/CmlLib.Core.Test/PackageNameTest.cs @@ -0,0 +1,60 @@ +using NUnit.Framework; + +namespace CmlLib.Core.Test; + +public class PackageNameTest +{ + [TestCase("a:b:c", "a", "b", "c")] + [TestCase("a.b:c.d:e.f", "a.b", "c.d", "e.f")] + public void TestParse(string input, string package, string name, string version) + { + var packageName = PackageName.Parse(input); + Assert.AreEqual(name, packageName.Name); + Assert.AreEqual(package, packageName.Package); + Assert.AreEqual(version, packageName.Version); + } + + [Platform("Win")] + [TestCase("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", + @"de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259-mappings.jar")] + [TestCase("net.java.dev.jna:platform:3.4.0", + @"net\java\dev\jna\platform\3.4.0\platform-3.4.0.jar")] + public void TestGetPathWin(string input, string exp) + { + var packageName = PackageName.Parse(input); + var result = packageName.GetPath(null, "jar"); + Assert.AreEqual(exp, result); + } + + [Platform("Unix")] + [TestCase("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", + @"de/oceanlabs/mcp/mcp_config/1.16.2-20200812.004259/mcp_config-1.16.2-20200812.004259.jar")] + [TestCase("net.java.dev.jna:platform:3.4.0", + @"net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar")] + public void TestGetPathUnix(string input, string exp) + { + var packageName = PackageName.Parse(input); + var result = packageName.GetPath(null, "jar"); + Assert.AreEqual(exp, result); + } + + [Platform("Win")] + [TestCase("com.mojang:text2speech:1.10.3", + @"com\mojang\text2speech\1.10.3\text2speech-1.10.3-natives-windows.jar")] + public void TestGetPathWithNativeWin(string input, string exp) + { + var packageName = PackageName.Parse(input); + var result = packageName.GetPath("natives-windows"); + Assert.AreEqual(exp, result); + } + + [Platform("Unix")] + [TestCase("ca.weblite:java-objc-bridge:1.0.0", + "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-natives-osx.jar")] + public void TestGetPathWithNativeUnix(string input, string exp) + { + var packageName = PackageName.Parse(input); + var result = packageName.GetPath("natives-osx"); + Assert.AreEqual(exp, result); + } +} \ No newline at end of file diff --git a/CmlLib.sln b/CmlLib.sln index 920102d..c4b91ba 100644 --- a/CmlLib.sln +++ b/CmlLib.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibWinFormSample", "CmlL EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLib", "CmlLib\CmlLib.csproj", "{AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Test", "CmlLib.Core.Test\CmlLib.Core.Test.csproj", "{39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Release|Any CPU.Build.0 = Release|Any CPU + {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CmlLib/CmlLib.csproj b/CmlLib/CmlLib.csproj index 49d5461..91a3152 100644 --- a/CmlLib/CmlLib.csproj +++ b/CmlLib/CmlLib.csproj @@ -42,6 +42,10 @@ Support all version, forge, optifine + + + <_Parameter1>CmlLib.Core.Test + From 6876a8e7679d5c86f8efc0a645acf9b7955bfce9 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 26 Apr 2022 15:24:07 +0900 Subject: [PATCH 030/137] remove unused methods, unnecessary comment --- CmlLib/Core/Mapper.cs | 6 ----- CmlLib/Utils/IOUtil.cs | 59 +----------------------------------------- 2 files changed, 1 insertion(+), 64 deletions(-) diff --git a/CmlLib/Core/Mapper.cs b/CmlLib/Core/Mapper.cs index cdfab60..19d35f0 100644 --- a/CmlLib/Core/Mapper.cs +++ b/CmlLib/Core/Mapper.cs @@ -76,12 +76,6 @@ public static string Interpolation(string str, Dictionary dicts public static string ToFullPath(string str, string prepath) { - // [de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip] - // \libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip - - // [net.minecraft:client:1.16.2-20200812.004259:slim] - // /libraries\net\minecraft\client\1.16.2-20200812.004259\client-1.16.2-20200812.004259-slim.jar - if (str.StartsWith("[") && str.EndsWith("]") && !string.IsNullOrEmpty(prepath)) { var innerStr = str.TrimStart('[').TrimEnd(']').Split('@'); diff --git a/CmlLib/Utils/IOUtil.cs b/CmlLib/Utils/IOUtil.cs index 6eb604c..c0a86c7 100644 --- a/CmlLib/Utils/IOUtil.cs +++ b/CmlLib/Utils/IOUtil.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -16,31 +17,6 @@ public static string NormalizePath(string path) .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) .TrimEnd(Path.DirectorySeparatorChar); } - - public static void DeleteDirectory(string targetDir) - { - try - { - string[] files = Directory.GetFiles(targetDir); - string[] dirs = Directory.GetDirectories(targetDir); - - foreach (string file in files) - { - File.Delete(file); - } - - foreach (string dir in dirs) - { - DeleteDirectory(dir); - } - - Directory.Delete(targetDir, true); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex); - } - } public static string CombinePath(string[] paths) { @@ -55,39 +31,6 @@ public static string CombinePath(string[] paths) })); } - public static void CopyDirectory(string org, string des, bool overwrite) - { - var dir = new DirectoryInfo(org); - if (!dir.Exists) - return; - - copyDirectoryFiles(org, des, "", overwrite); - } - - private static void copyDirectoryFiles(string org, string des, string path, bool overwrite) - { - var orgpath = Path.Combine(org, path); - var orgdir = new DirectoryInfo(orgpath); - - var despath = Path.Combine(des, path); - if (!Directory.Exists(despath)) - Directory.CreateDirectory(despath); - - foreach (var dir in orgdir.GetDirectories("*", SearchOption.TopDirectoryOnly)) - { - var innerpath = Path.Combine(path, dir.Name); - copyDirectoryFiles(org, des, innerpath, overwrite); - } - - foreach (var file in orgdir.GetFiles("*", SearchOption.TopDirectoryOnly)) - { - var innerpath = Path.Combine(path, file.Name); - var desfile = Path.Combine(des, innerpath); - - file.CopyTo(desfile, overwrite); - } - } - public static bool CheckSHA1(string path, string? compareHash) { if (string.IsNullOrEmpty(compareHash)) From 28f48407b3de284bbed9d6d9314982916631372c Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 26 Apr 2022 15:25:08 +0900 Subject: [PATCH 031/137] fix PackageName.GetDirectory method does not proper directory separator. '/' => Path.DirectorySeparatorChar --- CmlLib/Core/PackageName.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CmlLib/Core/PackageName.cs b/CmlLib/Core/PackageName.cs index 1a4c03d..0ec7490 100644 --- a/CmlLib/Core/PackageName.cs +++ b/CmlLib/Core/PackageName.cs @@ -62,7 +62,7 @@ public string GetPath(string? nativeId, string extension) public string GetDirectory() { - string dir = Package.Replace(".", "/"); + string dir = Package.Replace('.', Path.DirectorySeparatorChar); return Path.Combine(dir, Name, Version); } From 221dcc321049b6318b30615cd0365cee7b3982a8 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:08:42 +0900 Subject: [PATCH 032/137] add comments, remove unnecessary comment, rename some local variables --- .../DownloadFileChangedEventArgs.cs | 3 +-- CmlLib/Core/Files/MFileMetadata.cs | 1 + CmlLib/Core/Files/MLogConfiguration.cs | 1 + CmlLib/Core/Launcher/MLaunch.cs | 5 ++++ CmlLib/Core/Version/MVersion.cs | 3 --- CmlLib/Core/Version/MVersionCollection.cs | 23 +++++++++++-------- CmlLib/Core/Version/MVersionParser.cs | 1 + .../Core/VersionLoader/LocalVersionLoader.cs | 18 +++++++-------- 8 files changed, 32 insertions(+), 23 deletions(-) diff --git a/CmlLib/Core/Downloader/DownloadFileChangedEventArgs.cs b/CmlLib/Core/Downloader/DownloadFileChangedEventArgs.cs index 69afa3f..0ac4100 100644 --- a/CmlLib/Core/Downloader/DownloadFileChangedEventArgs.cs +++ b/CmlLib/Core/Downloader/DownloadFileChangedEventArgs.cs @@ -10,7 +10,6 @@ public class DownloadFileChangedEventArgs : EventArgs { public DownloadFileChangedEventArgs(MFile type, object source, string? filename, int total, int progressed) { - FileKind = type; FileType = type; FileName = filename; TotalFileCount = total; @@ -20,7 +19,7 @@ public DownloadFileChangedEventArgs(MFile type, object source, string? filename, // FileType and FileKind is exactly same. public MFile FileType { get; private set; } - public MFile FileKind { get; private set; } + public MFile FileKind => FileType; // backward compatible public string? FileName { get; private set; } public int TotalFileCount { get; private set; } public int ProgressedFileCount { get; private set; } diff --git a/CmlLib/Core/Files/MFileMetadata.cs b/CmlLib/Core/Files/MFileMetadata.cs index d3a6089..d1017ca 100644 --- a/CmlLib/Core/Files/MFileMetadata.cs +++ b/CmlLib/Core/Files/MFileMetadata.cs @@ -2,6 +2,7 @@ namespace CmlLib.Core.Files { + // Represent common file metadata. most files in version.json file follow this form public class MFileMetadata { public MFileMetadata() diff --git a/CmlLib/Core/Files/MLogConfiguration.cs b/CmlLib/Core/Files/MLogConfiguration.cs index 71ef097..fe51621 100644 --- a/CmlLib/Core/Files/MLogConfiguration.cs +++ b/CmlLib/Core/Files/MLogConfiguration.cs @@ -1,5 +1,6 @@ namespace CmlLib.Core.Files { + // Represent log configuration. "logging" property of .json file public class MLogConfiguration { public MFileMetadata? LogFile { get; set; } diff --git a/CmlLib/Core/Launcher/MLaunch.cs b/CmlLib/Core/Launcher/MLaunch.cs index 1f2cc41..c76ed58 100644 --- a/CmlLib/Core/Launcher/MLaunch.cs +++ b/CmlLib/Core/Launcher/MLaunch.cs @@ -34,6 +34,7 @@ public MLaunch(MLaunchOption option) private readonly MinecraftPath minecraftPath; private readonly MLaunchOption launchOption; + // make process that ready to launch game public Process GetProcess() { string arg = string.Join(" ", CreateArg()); @@ -46,10 +47,13 @@ public Process GetProcess() return mc; } + // make library files into jvm classpath string private string createClassPath(MVersion version) { + // if there is no libraries then launcher would need only one library file: .jar file itself var classpath = new List(version.Libraries?.Length ?? 1); + // libraries if (version.Libraries != null) { var libraries = version.Libraries @@ -58,6 +62,7 @@ private string createClassPath(MVersion version) classpath.AddRange(libraries); } + // .jar file if (!string.IsNullOrEmpty(version.Jar)) classpath.Add(minecraftPath.GetVersionJarPath(version.Jar)); diff --git a/CmlLib/Core/Version/MVersion.cs b/CmlLib/Core/Version/MVersion.cs index 154bfd4..db43c51 100644 --- a/CmlLib/Core/Version/MVersion.cs +++ b/CmlLib/Core/Version/MVersion.cs @@ -88,7 +88,6 @@ public void InheritFrom(MVersion parentVersion) { if (Libraries != null) Libraries = Libraries.Concat(parentVersion.Libraries).ToArray(); - //Libraries = parentVersion.Libraries.Concat(Libraries).ToArray(); else Libraries = parentVersion.Libraries; } @@ -96,7 +95,6 @@ public void InheritFrom(MVersion parentVersion) if (parentVersion.GameArguments != null) { if (GameArguments != null) - //GameArguments = GameArguments.Concat(parentVersion.GameArguments).ToArray(); GameArguments = parentVersion.GameArguments.Concat(GameArguments).ToArray(); else GameArguments = parentVersion.GameArguments; @@ -105,7 +103,6 @@ public void InheritFrom(MVersion parentVersion) if (parentVersion.JvmArguments != null) { if (JvmArguments != null) - //JvmArguments = JvmArguments.Concat(parentVersion.JvmArguments).ToArray(); JvmArguments = parentVersion.JvmArguments.Concat(JvmArguments).ToArray(); else JvmArguments = parentVersion.JvmArguments; diff --git a/CmlLib/Core/Version/MVersionCollection.cs b/CmlLib/Core/Version/MVersionCollection.cs index 0237579..949544b 100644 --- a/CmlLib/Core/Version/MVersionCollection.cs +++ b/CmlLib/Core/Version/MVersionCollection.cs @@ -8,31 +8,33 @@ namespace CmlLib.Core.Version { + // Collection for MVersionMetadata + // return MVersion object from MVersionMetadata public class MVersionCollection : IEnumerable { - public MVersionCollection(MVersionMetadata[] datas) - : this(datas, null, null, null) + public MVersionCollection(MVersionMetadata[] versions) + : this(versions, null, null, null) { } - public MVersionCollection(MVersionMetadata[] datas, MinecraftPath originalPath) - : this(datas, originalPath, null, null) + public MVersionCollection(MVersionMetadata[] versions, MinecraftPath originalPath) + : this(versions, originalPath, null, null) { } public MVersionCollection( - MVersionMetadata[] datas, + IEnumerable versions, MinecraftPath? originalPath, MVersionMetadata? latestRelease, MVersionMetadata? latestSnapshot) { - if (datas == null) - throw new ArgumentNullException(nameof(datas)); + if (versions == null) + throw new ArgumentNullException(nameof(versions)); Versions = new OrderedDictionary(); - foreach (var item in datas) + foreach (var item in versions) { Versions.Add(item.Name, item); } @@ -42,10 +44,12 @@ public MVersionCollection( LatestSnapshotVersion = latestSnapshot; } + // Use OrderedDictionary to keep version order + protected OrderedDictionary Versions; + public MVersionMetadata? LatestReleaseVersion { get; private set; } public MVersionMetadata? LatestSnapshotVersion { get; private set; } public MinecraftPath? MinecraftPath { get; private set; } - protected OrderedDictionary Versions; public MVersionMetadata this[int index] => (MVersionMetadata)Versions[index]!; @@ -78,6 +82,7 @@ public virtual Task GetVersionAsync(string name) return GetVersionAsync(versionMetadata); } + // get MVersion from MVersionMetadata public virtual async Task GetVersionAsync(MVersionMetadata versionMetadata) { if (versionMetadata == null) diff --git a/CmlLib/Core/Version/MVersionParser.cs b/CmlLib/Core/Version/MVersionParser.cs index 63ae963..b52a12f 100644 --- a/CmlLib/Core/Version/MVersionParser.cs +++ b/CmlLib/Core/Version/MVersionParser.cs @@ -21,6 +21,7 @@ public static MVersion ParseFromJson(string json) return ParseFromJson(jsonDocument); } + [MethodTimer.Time] public static MVersion ParseFromJson(JsonDocument document) { try diff --git a/CmlLib/Core/VersionLoader/LocalVersionLoader.cs b/CmlLib/Core/VersionLoader/LocalVersionLoader.cs index 0318b83..547ec8e 100644 --- a/CmlLib/Core/VersionLoader/LocalVersionLoader.cs +++ b/CmlLib/Core/VersionLoader/LocalVersionLoader.cs @@ -35,18 +35,18 @@ private List getFromLocal(MinecraftPath path) var dirs = versionDirectory.GetDirectories(); var arr = new List(dirs.Length); - for (int i = 0; i < dirs.Length; i++) + foreach (var dir in dirs) { - var dir = dirs[i]; var filepath = Path.Combine(dir.FullName, dir.Name + ".json"); - if (File.Exists(filepath)) + if (!File.Exists(filepath)) continue; + + var info = new LocalVersionMetadata(dir.Name) { - var info = new LocalVersionMetadata(dir.Name); - info.Path = filepath; - info.Type = "local"; - info.MType = MVersionType.Custom; - arr.Add(info); - } + Path = filepath, + Type = "local", + MType = MVersionType.Custom + }; + arr.Add(info); } return arr; From 374c8e3395e4ba4638a6a9856ffd5c493ae5097a Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:09:11 +0900 Subject: [PATCH 033/137] fix AssetChecker couldn't count total files --- CmlLib/Core/FileChecker/AssetChecker.cs | 68 ++++++++++--------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/CmlLib/Core/FileChecker/AssetChecker.cs b/CmlLib/Core/FileChecker/AssetChecker.cs index 46aeeef..c3538fd 100644 --- a/CmlLib/Core/FileChecker/AssetChecker.cs +++ b/CmlLib/Core/FileChecker/AssetChecker.cs @@ -69,62 +69,50 @@ private void checkIndex(MinecraftPath path, MFileMetadata assets) wc.DownloadFile(assets.Url, indexFilePath); } - // Read index file and return it as object [MethodTimer.Time] - public JsonDocument? ReadIndex(MinecraftPath path, MFileMetadata assets) + public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, + IProgress? progress) { if (string.IsNullOrEmpty(assets.Id)) return null; - + var indexFilePath = path.GetIndexFilePath(assets.Id); - if (!File.Exists(indexFilePath)) + if (!File.Exists(indexFilePath)) return null; var indexFileContent = File.ReadAllText(indexFilePath); - var jsonDocument = JsonDocument.Parse(indexFileContent); - return jsonDocument; - } + using var index = JsonDocument.Parse(indexFileContent); + var root = index.RootElement; - [MethodTimer.Time] - public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, - IProgress? progress) - { - var index = ReadIndex(path, assets); - if (index == null) + var listProperty = root.SafeGetProperty("objects"); + if (!listProperty.HasValue) return null; - using (index) - { - var root = index.RootElement; - - var listProperty = root.SafeGetProperty("objects"); - if (!listProperty.HasValue) - return null; - var list = listProperty.Value; + var list = listProperty.Value; - var isVirtual = root.SafeGetProperty("virtual")?.GetBoolean() ?? false; - var mapResource = root.SafeGetProperty("map_to_resources")?.GetBoolean() ?? false; + var isVirtual = root.SafeGetProperty("virtual")?.GetBoolean() ?? false; + var mapResource = root.SafeGetProperty("map_to_resources")?.GetBoolean() ?? false; - var downloadRequiredFiles = new List(); - - //int total = list.Count; - int progressed = 0; - - foreach (var prop in list.EnumerateObject()) - { - var f = checkAssetFile(prop.Name, prop.Value, path, assets, isVirtual, mapResource); + var objects = list.EnumerateObject(); + var totalObject = objects.Count(); + objects.Reset(); + var downloadRequiredFiles = new List(totalObject); + + int progressed = 0; + foreach (var prop in objects) + { + var f = checkAssetFile(prop.Name, prop.Value, path, assets, isVirtual, mapResource); - if (f != null) - downloadRequiredFiles.Add(f); + if (f != null) + downloadRequiredFiles.Add(f); - progressed++; - - if (progressed % 50 == 0) // prevent ui freezing - progress?.Report( - new DownloadFileChangedEventArgs(MFile.Resource, this, "", progressed, progressed)); - } + progressed++; - return downloadRequiredFiles.Distinct().ToArray(); // 10ms + if (progressed % 50 == 0) // prevent ui freezing + progress?.Report( + new DownloadFileChangedEventArgs(MFile.Resource, this, "", totalObject, progressed)); } + + return downloadRequiredFiles.Distinct().ToArray(); // 10ms } private DownloadFile? checkAssetFile(string key, JsonElement element, MinecraftPath path, MFileMetadata assets, From 5dc10c86a079ad4186340bba44c5829025f08362 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:09:43 +0900 Subject: [PATCH 034/137] divide MVersionSort file: MVersionSorter, MVersionSortOption --- CmlLib/Core/Version/MVersionSortOption.cs | 43 +++++++++++++++++++ .../{MVersionSort.cs => MVersionSorter.cs} | 38 +--------------- 2 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 CmlLib/Core/Version/MVersionSortOption.cs rename CmlLib/Core/Version/{MVersionSort.cs => MVersionSorter.cs} (83%) diff --git a/CmlLib/Core/Version/MVersionSortOption.cs b/CmlLib/Core/Version/MVersionSortOption.cs new file mode 100644 index 0000000..57d7c8f --- /dev/null +++ b/CmlLib/Core/Version/MVersionSortOption.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CmlLib.Core.Version +{ + public enum MVersionSortPropertyOption + { + Name, + Version, + ReleaseDate + } + + // Decide how to sort version if ReleaseDate is undefined + public enum MVersionNullReleaseDateSortOption + { + AsLatest, + AsOldest + } + + public class MVersionSortOption + { + public MVersionType[] TypeOrder { get; set; } = + { + MVersionType.Custom, + MVersionType.Release, + MVersionType.Snapshot, + MVersionType.OldBeta, + MVersionType.OldAlpha + }; + + public MVersionSortPropertyOption PropertyOrderBy { get; set; } + = MVersionSortPropertyOption.Version; + + public bool AscendingPropertyOrder { get; set; } = true; + + public MVersionNullReleaseDateSortOption NullReleaseDateSortOption { get; set; } = + MVersionNullReleaseDateSortOption.AsOldest; + + public bool CustomAsRelease { get; set; } = false; + public bool TypeClassification { get; set; } = true; + } +} diff --git a/CmlLib/Core/Version/MVersionSort.cs b/CmlLib/Core/Version/MVersionSorter.cs similarity index 83% rename from CmlLib/Core/Version/MVersionSort.cs rename to CmlLib/Core/Version/MVersionSorter.cs index cc013d2..e01afe3 100644 --- a/CmlLib/Core/Version/MVersionSort.cs +++ b/CmlLib/Core/Version/MVersionSorter.cs @@ -5,42 +5,8 @@ namespace CmlLib.Core.Version { - public enum MVersionSortPropertyOption - { - Name, - Version, - ReleaseDate - } - - public enum MVersionNullReleaseDateSortOption - { - AsLatest, - AsOldest - } - - public class MVersionSortOption - { - public MVersionType[] TypeOrder { get; set; } = - { - MVersionType.Custom, - MVersionType.Release, - MVersionType.Snapshot, - MVersionType.OldBeta, - MVersionType.OldAlpha - }; - - public MVersionSortPropertyOption PropertyOrderBy { get; set; } - = MVersionSortPropertyOption.Version; - - public bool AscendingPropertyOrder { get; set; } = true; - - public MVersionNullReleaseDateSortOption NullReleaseDateSortOption { get; set; } = - MVersionNullReleaseDateSortOption.AsOldest; - - public bool CustomAsRelease { get; set; } = false; - public bool TypeClassification { get; set; } = true; - } - + // Sort MVersionMetadata + // TODO: measure performance and optimizing public class MVersionMetadataSorter { public MVersionMetadataSorter(MVersionSortOption option) From 2e495eb3fbf308024d40ee1602f7ca1023e1c312 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:12:29 +0900 Subject: [PATCH 035/137] rename StringVersionMetadata.ReadMetadataAsync => ReadVesionMetadataAsync --- .../VersionMetadata/LocalVersionMetadata.cs | 5 ++- .../Core/VersionMetadata/MVersionMetadata.cs | 20 +++++++++ .../VersionMetadata/StringVersionMetadata.cs | 45 ++++++++++++------- .../VersionMetadata/WebVersionMetadata.cs | 5 ++- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs b/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs index b86171d..6b9550d 100644 --- a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs @@ -5,6 +5,9 @@ namespace CmlLib.Core.VersionMetadata { + /// + /// Represent metadata where the actual version data is in local file + /// public class LocalVersionMetadata : StringVersionMetadata { public LocalVersionMetadata(string id) : base(id) @@ -12,7 +15,7 @@ public LocalVersionMetadata(string id) : base(id) IsLocalVersion = true; } - protected override Task ReadMetadataAsync() + protected override Task ReadVersionDataAsync() { if (string.IsNullOrEmpty(Path)) throw new InvalidOperationException("Path property was null"); diff --git a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs b/CmlLib/Core/VersionMetadata/MVersionMetadata.cs index 2454f97..ed02ea9 100644 --- a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/MVersionMetadata.cs @@ -5,6 +5,10 @@ namespace CmlLib.Core.VersionMetadata { + /// + /// Represent version metadata + /// It does not contains actual version data, but contains some metadata and the way to get version data (MVersion) + /// public abstract class MVersionMetadata { protected MVersionMetadata(string name) @@ -63,8 +67,24 @@ public override int GetHashCode() return Name.GetHashCode(); } + /// + /// Get version data + /// + /// MVersion object containing actual version data public abstract Task GetVersionAsync(); + + /// + /// Get version data and save version data into file + /// + /// Game directory + /// MVersion object containing actual version data public abstract Task GetVersionAsync(MinecraftPath savePath); + + /// + /// Get version data and save version data into file. This method may not make MVersion object + /// + /// Game directory + /// public abstract Task SaveAsync(MinecraftPath path); } } diff --git a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs index 7c0d97d..d056a53 100644 --- a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs @@ -6,6 +6,9 @@ namespace CmlLib.Core.VersionMetadata { + /// + /// Represent JSON text based version metadata + /// public abstract class StringVersionMetadata : MVersionMetadata { protected StringVersionMetadata(string name) : base(name) @@ -13,16 +16,35 @@ protected StringVersionMetadata(string name) : base(name) } - protected abstract Task ReadMetadataAsync(); + /// + /// Get actual version data as string + /// + /// Version metadata + protected abstract Task ReadVersionDataAsync(); + + /// + /// Save version data into a file. It does not overwrite file + /// + /// The content of version metadata + /// Game directory + /// + protected virtual Task SaveVersionDataAsync(string metadata, MinecraftPath path) + { + var metadataPath = prepareSaveVersiondata(path); + if (!string.IsNullOrEmpty(metadataPath)) + return IOUtil.WriteFileAsync(metadataPath, metadata); + else + return Task.CompletedTask; + } - private string? prepareSaveMetadata(MinecraftPath path) + private string? prepareSaveVersiondata(MinecraftPath path) { if (string.IsNullOrEmpty(Name)) return null; var metadataPath = path.GetVersionJsonPath(Name); - // check if target path and current path are same + // check if target path and current path are same to prevent overwriting if (IsLocalVersion && !string.IsNullOrEmpty(Path)) { var result = string.Compare(IOUtil.NormalizePath(Path), metadataPath, @@ -31,30 +53,21 @@ protected StringVersionMetadata(string name) : base(name) if (result == 0) // same path return null; } - + var directoryPath = System.IO.Path.GetDirectoryName(metadataPath); if (!string.IsNullOrEmpty(directoryPath)) Directory.CreateDirectory(directoryPath); return metadataPath; } - - protected virtual Task SaveMetadataAsync(string metadata, MinecraftPath path) - { - var metadataPath = prepareSaveMetadata(path); - if (!string.IsNullOrEmpty(metadataPath)) - return IOUtil.WriteFileAsync(metadataPath, metadata); - else - return Task.CompletedTask; - } - + private async Task getAsync(MinecraftPath? savePath, bool parse) { string metadataJson; - metadataJson = await ReadMetadataAsync().ConfigureAwait(false); + metadataJson = await ReadVersionDataAsync().ConfigureAwait(false); if (savePath != null) - await SaveMetadataAsync(metadataJson, savePath).ConfigureAwait(false); + await SaveVersionDataAsync(metadataJson, savePath).ConfigureAwait(false); return parse ? MVersionParser.ParseFromJson(metadataJson) : null; } diff --git a/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs b/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs index 2a1940d..60081ea 100644 --- a/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs @@ -4,6 +4,9 @@ namespace CmlLib.Core.VersionMetadata { + /// + /// Represent metadata where the actual version data is on web + /// public class WebVersionMetadata : StringVersionMetadata { public WebVersionMetadata(string name) : base(name) @@ -11,7 +14,7 @@ public WebVersionMetadata(string name) : base(name) IsLocalVersion = false; } - protected override async Task ReadMetadataAsync() + protected override async Task ReadVersionDataAsync() { if (string.IsNullOrEmpty(Path)) throw new InvalidOperationException("Path property was null"); From e375eecf41e09c0194a9fafde7462ec812fab39d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:13:43 +0900 Subject: [PATCH 036/137] use IJavaPathResolver in JavaChecker instead of using its own resolver --- CmlLib/Core/CMLauncher.cs | 26 ++++++++------ CmlLib/Core/FileChecker/JavaChecker.cs | 50 +++++++++++++++++++------- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/CmlLib/Core/CMLauncher.cs b/CmlLib/Core/CMLauncher.cs index 7e0106b..981e364 100644 --- a/CmlLib/Core/CMLauncher.cs +++ b/CmlLib/Core/CMLauncher.cs @@ -1,16 +1,13 @@ using CmlLib.Core.Downloader; -using CmlLib.Core.Files; -using CmlLib.Core.Installer; +using CmlLib.Core.FileChecker; +using CmlLib.Core.Java; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.IO; using System.Threading.Tasks; -using CmlLib.Core.FileChecker; -using CmlLib.Core.Java; namespace CmlLib.Core { @@ -20,9 +17,9 @@ public CMLauncher(string path) : this(new MinecraftPath(path)) { } - public CMLauncher(MinecraftPath mc) + public CMLauncher(MinecraftPath path) { - this.MinecraftPath = mc; + this.MinecraftPath = path; GameFileCheckers = new FileCheckerCollection(); FileDownloader = new AsyncParallelDownloader(); @@ -33,7 +30,7 @@ public CMLauncher(MinecraftPath mc) pProgressChanged = new Progress( e => ProgressChanged?.Invoke(this, e)); - JavaPathResolver = new MinecraftJavaPathResolver(mc); + JavaPathResolver = new MinecraftJavaPathResolver(path); } public event DownloadFileChangedHandler? FileChanged; @@ -49,8 +46,17 @@ public CMLauncher(MinecraftPath mc) public FileCheckerCollection GameFileCheckers { get; private set; } public IDownloader? FileDownloader { get; set; } - - public IJavaPathResolver JavaPathResolver { get; set; } + + private IJavaPathResolver javaPathResolver; + public IJavaPathResolver JavaPathResolver { + get => javaPathResolver; + set + { + javaPathResolver = value; + if (GameFileCheckers.JavaFileChecker != null) + GameFileCheckers.JavaFileChecker.JavaPathResolver = value; + } + } public async Task GetAllVersionsAsync() { diff --git a/CmlLib/Core/FileChecker/JavaChecker.cs b/CmlLib/Core/FileChecker/JavaChecker.cs index bf0ef1d..d6c9084 100644 --- a/CmlLib/Core/FileChecker/JavaChecker.cs +++ b/CmlLib/Core/FileChecker/JavaChecker.cs @@ -20,10 +20,21 @@ class JavaCheckResult public string? JavaBinaryPath { get; set; } public DownloadFile[]? JavaFiles { get; set; } } - + + public IJavaPathResolver? JavaPathResolver { get; set; } public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; public bool CheckHash { get; set; } = true; + public JavaChecker() + { + + } + + public JavaChecker(IJavaPathResolver javaPathResolver) + { + JavaPathResolver = javaPathResolver; + } + public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, IProgress? downloadProgress) { @@ -52,21 +63,24 @@ class JavaCheckResult private async Task internalCheckJava(string javaVersion, MinecraftPath path, IProgress? downloadProgress) { - var javaPathResolver = new MinecraftJavaPathResolver(path); - var binPath = javaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); + if (JavaPathResolver == null) + throw new InvalidOperationException("JavaPathResolver was null"); + + var binPath = JavaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); DownloadFile[]? downloadFiles; try { var osName = getJavaOSName(); // safe - var response = await HttpUtil.HttpClient.GetAsync(JavaManifestServer); + var response = await HttpUtil.HttpClient.GetAsync(JavaManifestServer); // get all java versions var str = await response.Content.ReadAsStringAsync(); using var jsonDocument = JsonDocument.Parse(str); var root = jsonDocument.RootElement; - var javaVersions = root.SafeGetProperty(osName); + var javaVersions = root.SafeGetProperty(osName); // get os specific java version + // try three versions: latest, JreLegacyVersionName(jre-legacy), legacy java (MJava) if (javaVersions != null) { var javaManifest = await getJavaVersionManifest(javaVersions.Value, javaVersion); @@ -76,14 +90,15 @@ private async Task internalCheckJava(string javaVersion, Minecr if (javaManifest == null) return await legacyJavaChecker(path); + // get file objects using var manifestDocument = JsonDocument.Parse(javaManifest); var files = manifestDocument.RootElement.SafeGetProperty("files"); if (files == null) return await legacyJavaChecker(path); - downloadFiles = toDownloadFiles(files.Value, javaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); + downloadFiles = toDownloadFiles(files.Value, JavaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); } - else + else // no java version for osName return await legacyJavaChecker(path); } catch (Exception e) @@ -128,7 +143,8 @@ private string getJavaOSName() return osName; } - + + // get java files of the specific version private async Task getJavaVersionManifest(JsonElement job, string version) { var versionArr = job.SafeGetProperty(version)?.EnumerateArray(); @@ -143,12 +159,17 @@ private string getJavaOSName() return await HttpUtil.HttpClient.GetStringAsync(manifestUrl); } + // compare local files with `manifest` private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, IProgress? progress) { + var objects = manifest.EnumerateObject(); + var total = objects.Count(); + objects.Reset(); + var progressed = 0; - var files = new List(); - foreach (var prop in manifest.EnumerateObject()) + var files = new List(total); + foreach (var prop in objects) { var name = prop.Name; var value = prop.Value; @@ -181,7 +202,7 @@ private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, progressed++; progress?.Report(new DownloadFileChangedEventArgs( - MFile.Runtime, this, name, progressed, progressed)); + MFile.Runtime, this, name, total: total, progressed: progressed)); } return files.ToArray(); } @@ -208,10 +229,13 @@ private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, }; } + // legacy java checker that use MJava private async Task legacyJavaChecker(MinecraftPath path) { - var javaPathResolver = new MinecraftJavaPathResolver(path); - string legacyJavaPath = javaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersionName); + if (JavaPathResolver == null) + throw new InvalidOperationException("JavaPathResolver was null"); + + string legacyJavaPath = JavaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersionName); MJava mJava = new MJava(legacyJavaPath); var result = new JavaCheckResult() From 679c78921bb77a8700437201abee344fc9444afb Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:14:04 +0900 Subject: [PATCH 037/137] small changes --- CmlLib.Core.Test/CmlLib.Core.Test.csproj | 21 +++++++++++++++++++ .../Core/VersionLoader/MojangVersionLoader.cs | 2 +- CmlLibWinFormSample/MainForm.cs | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 CmlLib.Core.Test/CmlLib.Core.Test.csproj diff --git a/CmlLib.Core.Test/CmlLib.Core.Test.csproj b/CmlLib.Core.Test/CmlLib.Core.Test.csproj new file mode 100644 index 0000000..b1a5bf4 --- /dev/null +++ b/CmlLib.Core.Test/CmlLib.Core.Test.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + diff --git a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs b/CmlLib/Core/VersionLoader/MojangVersionLoader.cs index 9553e01..235cf72 100644 --- a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs +++ b/CmlLib/Core/VersionLoader/MojangVersionLoader.cs @@ -55,7 +55,7 @@ private MVersionCollection parseList(string res) } } - return new MVersionCollection(arr.ToArray(), null, latestRelease, latestSnapshot); + return new MVersionCollection(arr, null, latestRelease, latestSnapshot); } } } diff --git a/CmlLibWinFormSample/MainForm.cs b/CmlLibWinFormSample/MainForm.cs index 189537f..8fa0c4a 100644 --- a/CmlLibWinFormSample/MainForm.cs +++ b/CmlLibWinFormSample/MainForm.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Windows.Forms; using CmlLib.Core.Downloader; +using CmlLib.Core.FileChecker; using CmlLib.Core.Version; namespace CmlLibWinFormSample From c68765aba159d93c2b4d885b957ae5fc7fc08f39 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:41:46 +0900 Subject: [PATCH 038/137] add Xuid, ClientId, ArgumentDictionary --- CmlLib/Core/Auth/MSession.cs | 2 ++ CmlLib/Core/Launcher/MLaunch.cs | 26 ++++++++++++++++++-------- CmlLib/Core/Launcher/MLaunchOption.cs | 6 ++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CmlLib/Core/Auth/MSession.cs b/CmlLib/Core/Auth/MSession.cs index 3190e64..2b36886 100644 --- a/CmlLib/Core/Auth/MSession.cs +++ b/CmlLib/Core/Auth/MSession.cs @@ -23,6 +23,8 @@ public MSession(string? username, string? accessToken, string? uuid) public string? ClientToken { get; set; } [JsonPropertyName("userType")] public string? UserType { get; set; } + [JsonPropertyName("xuid")] + public string? Xuid { get; set; } public bool CheckIsValid() { diff --git a/CmlLib/Core/Launcher/MLaunch.cs b/CmlLib/Core/Launcher/MLaunch.cs index c76ed58..168d225 100644 --- a/CmlLib/Core/Launcher/MLaunch.cs +++ b/CmlLib/Core/Launcher/MLaunch.cs @@ -11,7 +11,7 @@ public class MLaunch { private const int DefaultServerPort = 25565; - public static readonly string SupportVersion = "1.17.1"; + public static readonly string SupportVersion = "1.18.2"; public readonly static string[] DefaultJavaParameter = { "-XX:+UnlockExperimentalVMOptions", @@ -90,13 +90,14 @@ public string[] CreateArg() var argDict = new Dictionary { - { "library_directory", minecraftPath.Library }, - { "natives_directory", nativePath }, - { "launcher_name", useNotNull(launchOption.GameLauncherName, "minecraft-launcher") }, - { "launcher_version", useNotNull(launchOption.GameLauncherVersion, "2") }, + { "library_directory" , minecraftPath.Library }, + { "natives_directory" , nativePath }, + { "launcher_name" , useNotNull(launchOption.GameLauncherName, "minecraft-launcher") }, + { "launcher_version" , useNotNull(launchOption.GameLauncherVersion, "2") }, { "classpath_separator", Path.PathSeparator.ToString() }, - { "classpath", classpath }, - + { "classpath" , classpath }, + + { "auth_xuid" , session.Xuid }, { "auth_player_name" , session.Username }, { "version_name" , version.Id }, { "game_directory" , minecraftPath.BasePath }, @@ -108,9 +109,18 @@ public string[] CreateArg() { "user_type" , session.UserType ?? "Mojang" }, { "game_assets" , minecraftPath.GetAssetLegacyPath(version.Assets?.Id ?? "legacy") }, { "auth_session" , session.AccessToken }, - { "version_type" , useNotNull(launchOption.VersionType, version.TypeStr) } + { "version_type" , useNotNull(launchOption.VersionType, version.TypeStr) }, + { "clientid" , "" } }; + if (launchOption.ArgumentDictionary != null) + { + foreach (var argument in launchOption.ArgumentDictionary) + { + argDict[argument.Key] = argument.Value; + } + } + // JVM argument // version-specific jvm arguments diff --git a/CmlLib/Core/Launcher/MLaunchOption.cs b/CmlLib/Core/Launcher/MLaunchOption.cs index eac3e85..4aad96d 100644 --- a/CmlLib/Core/Launcher/MLaunchOption.cs +++ b/CmlLib/Core/Launcher/MLaunchOption.cs @@ -1,6 +1,7 @@ using CmlLib.Core.Auth; using CmlLib.Core.Version; using System; +using System.Collections.Generic; namespace CmlLib.Core { @@ -30,6 +31,11 @@ public class MLaunchOption public string? GameLauncherName { get; set; } public string? GameLauncherVersion { get; set; } + public string? UserProperties { get; set; } + public string? ClientId { get; set; } + + public Dictionary? ArgumentDictionary { get; set; } + internal MinecraftPath GetMinecraftPath() => Path!; internal MVersion GetStartVersion() => StartVersion!; internal MSession GetSession() => Session!; From e47ab7be58fe9a1514914b41fa1002add3cb1265 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 27 Apr 2022 17:42:05 +0900 Subject: [PATCH 039/137] do not call CreateDirs on constructor of MinecraftPath --- CmlLib/Core/MinecraftPath.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/CmlLib/Core/MinecraftPath.cs b/CmlLib/Core/MinecraftPath.cs index bcbd012..86ed4e5 100644 --- a/CmlLib/Core/MinecraftPath.cs +++ b/CmlLib/Core/MinecraftPath.cs @@ -46,8 +46,6 @@ public MinecraftPath(string basePath, string basePathForAssets) Runtime = NormalizePath(BasePath + "/runtime"); Assets = NormalizePath(basePathForAssets + "/assets"); - - CreateDirs(); } public void CreateDirs() From e8027f99e92b94e286a58ef37edb27eaaf490a63 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Apr 2022 15:00:13 +0900 Subject: [PATCH 040/137] add CreateParentDirectory in IOUtil --- CmlLib/Core/Auth/MLogin.cs | 4 +--- CmlLib/Core/FileChecker/AssetChecker.cs | 9 ++------- .../VersionMetadata/StringVersionMetadata.cs | 5 +---- CmlLib/Utils/IOUtil.cs | 16 ++++++++++++++-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CmlLib/Core/Auth/MLogin.cs b/CmlLib/Core/Auth/MLogin.cs index c2fa838..f63fb52 100644 --- a/CmlLib/Core/Auth/MLogin.cs +++ b/CmlLib/Core/Auth/MLogin.cs @@ -50,9 +50,7 @@ protected virtual async Task createNewSession() private async Task writeSessionCache(MSession session) { if (!SaveSession) return; - var directoryPath = Path.GetDirectoryName(SessionCacheFilePath); - if (!string.IsNullOrEmpty(directoryPath)) - Directory.CreateDirectory(directoryPath); + IOUtil.CreateParentDirectory(SessionCacheFilePath); var json = JsonSerializer.Serialize(session); await IOUtil.WriteFileAsync(SessionCacheFilePath, json); diff --git a/CmlLib/Core/FileChecker/AssetChecker.cs b/CmlLib/Core/FileChecker/AssetChecker.cs index c3538fd..56e7739 100644 --- a/CmlLib/Core/FileChecker/AssetChecker.cs +++ b/CmlLib/Core/FileChecker/AssetChecker.cs @@ -61,9 +61,7 @@ private void checkIndex(MinecraftPath path, MFileMetadata assets) if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, CheckHash)) return; - var directoryName = Path.GetDirectoryName(indexFilePath); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); + IOUtil.CreateParentDirectory(indexFilePath); using var wc = new WebClient(); wc.DownloadFile(assets.Url, indexFilePath); @@ -175,10 +173,7 @@ private async Task assetCopy(string org, string des) if (!desFile.Exists || orgFile.Length != desFile.Length) { - var directoryName = Path.GetDirectoryName(des); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); - + IOUtil.CreateParentDirectory(des); await IOUtil.CopyFileAsync(org, des); } } diff --git a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs index d056a53..8739ae7 100644 --- a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs @@ -54,10 +54,7 @@ protected virtual Task SaveVersionDataAsync(string metadata, MinecraftPath path) return null; } - var directoryPath = System.IO.Path.GetDirectoryName(metadataPath); - if (!string.IsNullOrEmpty(directoryPath)) - Directory.CreateDirectory(directoryPath); - + IOUtil.CreateParentDirectory(metadataPath); return metadataPath; } diff --git a/CmlLib/Utils/IOUtil.cs b/CmlLib/Utils/IOUtil.cs index c0a86c7..a56e7be 100644 --- a/CmlLib/Utils/IOUtil.cs +++ b/CmlLib/Utils/IOUtil.cs @@ -3,14 +3,23 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace CmlLib.Utils { internal static class IOUtil { + // from dotnet core source code, default buffer size of file stream is 4096 private const int DefaultBufferSize = 4096; + public static void CreateParentDirectory(string filePath) + { + var dirPath = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(dirPath)) + Directory.CreateDirectory(dirPath); + } + public static string NormalizePath(string path) { return Path.GetFullPath(path) @@ -31,6 +40,7 @@ public static string CombinePath(string[] paths) })); } +#pragma warning disable SYSLIB0021 public static bool CheckSHA1(string path, string? compareHash) { if (string.IsNullOrEmpty(compareHash)) @@ -41,6 +51,7 @@ public static bool CheckSHA1(string path, string? compareHash) string fileHash; using (var file = File.OpenRead(path)) + using (var hasher = new System.Security.Cryptography.SHA1CryptoServiceProvider()) { var binaryHash = hasher.ComputeHash(file); @@ -54,7 +65,8 @@ public static bool CheckSHA1(string path, string? compareHash) return false; } } - +#pragma warning restore SYSLIB0021 + public static bool CheckFileValidation(string path, string? hash, bool checkHash) { if (!File.Exists(path)) @@ -84,7 +96,7 @@ public static bool CheckFileValidation(string path, string hash, long size) } #region Async File IO - + // from .NET Framework reference source code // If we use the path-taking constructors we will not have FileOptions.Asynchronous set and // we will have asynchronous file access faked by the thread pool. We want the real thing. From a99c836bab96ac6112d02a943fc05e2ab3aecfaa Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Apr 2022 15:00:41 +0900 Subject: [PATCH 041/137] Downloader: WebClient to HttpClient --- .../Downloader/AsyncParallelDownloader.cs | 32 +++++----- .../Downloader/DownloadFileByteProgress.cs | 16 +++++ .../Core/Downloader/DownloadFileProgress.cs | 20 ------ .../Downloader/HttpClientDownloadHelper.cs | 62 +++++++++++++++++++ CmlLib/Core/Downloader/SequenceDownloader.cs | 32 ++++++---- 5 files changed, 113 insertions(+), 49 deletions(-) create mode 100644 CmlLib/Core/Downloader/DownloadFileByteProgress.cs delete mode 100644 CmlLib/Core/Downloader/DownloadFileProgress.cs create mode 100644 CmlLib/Core/Downloader/HttpClientDownloadHelper.cs diff --git a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs b/CmlLib/Core/Downloader/AsyncParallelDownloader.cs index d7fecf0..1347c86 100644 --- a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs +++ b/CmlLib/Core/Downloader/AsyncParallelDownloader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,8 @@ namespace CmlLib.Core.Downloader { public class AsyncParallelDownloader : IDownloader { + private readonly HttpClientDownloadHelper downloader; + public int MaxThread { get; private set; } public bool IgnoreInvalidFiles { get; set; } = true; @@ -25,15 +28,18 @@ public class AsyncParallelDownloader : IDownloader private IProgress? pChangeProgress; private IProgress? pChangeFile; + private IProgress fileByteProgress; - public AsyncParallelDownloader() : this(10) + public AsyncParallelDownloader() : this(10, HttpUtil.HttpClient) { } - public AsyncParallelDownloader(int parallelism) + public AsyncParallelDownloader(int parallelism, HttpClient httpClient) { MaxThread = parallelism; + downloader = new HttpClientDownloadHelper(httpClient); + fileByteProgress = new Progress(byteProgressHandler); } public async Task DownloadFiles(DownloadFile[] files, @@ -71,7 +77,7 @@ public async Task DownloadFiles(DownloadFile[] files, } private async Task ForEachAsyncSemaphore(IEnumerable source, - int degreeOfParallelism, Func body) + int degreeOfParallelism, Func body) { List tasks = new List(); using SemaphoreSlim throttler = new SemaphoreSlim(degreeOfParallelism); @@ -113,10 +119,7 @@ private async Task doDownload(DownloadFile file, int retry) { try { - WebDownload downloader = new WebDownload(); - downloader.FileDownloadProgressChanged += Downloader_FileDownloadProgressChanged; - - await downloader.DownloadFileAsync(file).ConfigureAwait(false); + await downloader.DownloadFileAsync(file, fileByteProgress).ConfigureAwait(false); if (file.AfterDownload != null) { @@ -142,26 +145,23 @@ private async Task doDownload(DownloadFile file, int retry) } } - private void Downloader_FileDownloadProgressChanged(object? sender, DownloadFileProgress e) + private void byteProgressHandler(DownloadFileByteProgress progress) { lock (progressEventLock) { - if (e.File.Size <= 0) + if (progress.File.Size <= 0) { - totalBytes += e.TotalBytes; - e.File.Size = e.TotalBytes; + totalBytes += progress.TotalBytes; + progress.File.Size = progress.TotalBytes; } - receivedBytes += e.ProgressedBytes; + receivedBytes += progress.ProgressedBytes; if (receivedBytes > totalBytes) return; float percent = (float)receivedBytes / totalBytes * 100; - if (percent >= 0) - pChangeProgress?.Report(new FileProgressChangedEventArgs(totalBytes, receivedBytes, (int)percent)); - else - Debug.WriteLine($"total: {totalBytes} received: {receivedBytes} filename: {e.File.Name} filesize: {e.File.Size}"); + pChangeProgress?.Report(new FileProgressChangedEventArgs(totalBytes, receivedBytes, (int)percent)); } } } diff --git a/CmlLib/Core/Downloader/DownloadFileByteProgress.cs b/CmlLib/Core/Downloader/DownloadFileByteProgress.cs new file mode 100644 index 0000000..a162799 --- /dev/null +++ b/CmlLib/Core/Downloader/DownloadFileByteProgress.cs @@ -0,0 +1,16 @@ +namespace CmlLib.Core.Downloader +{ + public class DownloadFileByteProgress + { + public DownloadFileByteProgress(DownloadFile file, long total, long progressed) + { + this.File = file; + this.TotalBytes = total; + this.ProgressedBytes = progressed; + } + + public DownloadFile File { get; private set; } + public long TotalBytes { get; private set; } + public long ProgressedBytes { get; private set; } + } +} diff --git a/CmlLib/Core/Downloader/DownloadFileProgress.cs b/CmlLib/Core/Downloader/DownloadFileProgress.cs deleted file mode 100644 index abcd466..0000000 --- a/CmlLib/Core/Downloader/DownloadFileProgress.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace CmlLib.Core.Downloader -{ - public class DownloadFileProgress - { - public DownloadFileProgress(DownloadFile file, long total, long progressed, long received, int percent) - { - this.File = file; - this.TotalBytes = total; - this.ProgressedBytes = progressed; - this.ReceivedBytes = received; - this.ProgressPercentage = percent; - } - - public DownloadFile File { get; private set; } - public long TotalBytes { get; private set; } - public long ProgressedBytes { get; private set; } - public long ReceivedBytes { get; private set; } - public int ProgressPercentage { get; private set; } - } -} diff --git a/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs b/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs new file mode 100644 index 0000000..af60dfb --- /dev/null +++ b/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs @@ -0,0 +1,62 @@ +using CmlLib.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CmlLib.Core.Downloader +{ + internal class HttpClientDownloadHelper + { + public HttpClientDownloadHelper(HttpClient client) + { + this.httpClient = client; + } + + private readonly HttpClient httpClient; + + public int BufferSize { get; set; } = 81920; + + public Task DownloadFileAsync(DownloadFile file, + IProgress? progress = null, CancellationToken cancellationToken = default) + { + IOUtil.CreateParentDirectory(file.Path); + var destination = File.Create(file.Path); + return DownloadFileAsync(file, destination, progress, cancellationToken); + } + + public async Task DownloadFileAsync(DownloadFile file, Stream destination, + IProgress? progress = null, CancellationToken cancellationToken = default) + { + // Get the http headers first to examine the content length + using var response = await httpClient.GetAsync(file.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + var fileSize = response.Content.Headers.ContentLength ?? file.Size; + + using var download = await response.Content.ReadAsStreamAsync(); + + // Ignore progress reporting when no progress reporter was + // passed or when the content length is unknown + if (progress == null) + { + await download.CopyToAsync(destination); + return; + } + + var buffer = new byte[BufferSize]; + int bytesRead; + while ((bytesRead = await download.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + progress?.Report(new DownloadFileByteProgress( + file: file, + total: fileSize, + progressed: bytesRead)); + } + + progress?.Report(new DownloadFileByteProgress(file, fileSize, fileSize)); + } + } +} diff --git a/CmlLib/Core/Downloader/SequenceDownloader.cs b/CmlLib/Core/Downloader/SequenceDownloader.cs index 3068082..62e1062 100644 --- a/CmlLib/Core/Downloader/SequenceDownloader.cs +++ b/CmlLib/Core/Downloader/SequenceDownloader.cs @@ -2,15 +2,28 @@ using System; using System.ComponentModel; using System.IO; +using System.Net.Http; using System.Threading.Tasks; namespace CmlLib.Core.Downloader { public class SequenceDownloader : IDownloader { + private readonly HttpClientDownloadHelper downloader; + public bool IgnoreInvalidFiles { get; set; } = true; private IProgress? pChangeProgress; + public SequenceDownloader() : this(HttpUtil.HttpClient) + { + + } + + public SequenceDownloader(HttpClient client) + { + downloader = new HttpClientDownloadHelper(client); + } + public async Task DownloadFiles(DownloadFile[] files, IProgress? fileProgress, IProgress? downloadProgress) @@ -18,11 +31,13 @@ public async Task DownloadFiles(DownloadFile[] files, if (files.Length == 0) return; + var byteProgress = new Progress(progress => + { + var percent = (float)progress.ProgressedBytes / progress.TotalBytes * 100; + downloadProgress?.Report(new ProgressChangedEventArgs((int)percent, null)); + }); pChangeProgress = downloadProgress; - WebDownload downloader = new WebDownload(); - downloader.FileDownloadProgressChanged += Downloader_FileDownloadProgressChanged; - fileProgress?.Report( new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); @@ -32,11 +47,7 @@ public async Task DownloadFiles(DownloadFile[] files, try { - var directoryPath = Path.GetDirectoryName(file.Path); - if (!string.IsNullOrEmpty(directoryPath)) - Directory.CreateDirectory(directoryPath); - - await downloader.DownloadFileAsync(file).ConfigureAwait(false); + await downloader.DownloadFileAsync(file, byteProgress).ConfigureAwait(false); if (file.AfterDownload != null) { @@ -58,10 +69,5 @@ public async Task DownloadFiles(DownloadFile[] files, } } } - - private void Downloader_FileDownloadProgressChanged(object? sender, DownloadFileProgress e) - { - pChangeProgress?.Report(new ProgressChangedEventArgs(e.ProgressPercentage, null)); - } } } From 57cd51fb900a84bc2a967326329b4aae3f36b4f8 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Apr 2022 17:18:48 +0900 Subject: [PATCH 042/137] optimize HttpClientDownloadHelper --- .../Downloader/DownloadFileByteProgress.cs | 16 ------ .../Downloader/HttpClientDownloadHelper.cs | 51 +++++++++++++------ CmlLib/Core/Mapper.cs | 11 ++-- 3 files changed, 41 insertions(+), 37 deletions(-) delete mode 100644 CmlLib/Core/Downloader/DownloadFileByteProgress.cs diff --git a/CmlLib/Core/Downloader/DownloadFileByteProgress.cs b/CmlLib/Core/Downloader/DownloadFileByteProgress.cs deleted file mode 100644 index a162799..0000000 --- a/CmlLib/Core/Downloader/DownloadFileByteProgress.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CmlLib.Core.Downloader -{ - public class DownloadFileByteProgress - { - public DownloadFileByteProgress(DownloadFile file, long total, long progressed) - { - this.File = file; - this.TotalBytes = total; - this.ProgressedBytes = progressed; - } - - public DownloadFile File { get; private set; } - public long TotalBytes { get; private set; } - public long ProgressedBytes { get; private set; } - } -} diff --git a/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs b/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs index af60dfb..74f63c7 100644 --- a/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs +++ b/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs @@ -9,8 +9,20 @@ namespace CmlLib.Core.Downloader { + internal class DownloadFileByteProgress + { + public DownloadFile? File { get; set; } + public long TotalBytes { get; set; } + public long ProgressedBytes { get; set; } + } + + // To implement System.Net.WebClient.DownloadFileTaskAsync + // https://source.dot.net/#System.Net.WebClient/System/Net/WebClient.cs,c2224e40a6960929 internal class HttpClientDownloadHelper { + private const int DefaultCopyBufferLength = 8192; + private const int DefaultDownloadBufferLength = 65536; + public HttpClientDownloadHelper(HttpClient client) { this.httpClient = client; @@ -18,14 +30,12 @@ public HttpClientDownloadHelper(HttpClient client) private readonly HttpClient httpClient; - public int BufferSize { get; set; } = 81920; - - public Task DownloadFileAsync(DownloadFile file, + public async Task DownloadFileAsync(DownloadFile file, IProgress? progress = null, CancellationToken cancellationToken = default) { IOUtil.CreateParentDirectory(file.Path); - var destination = File.Create(file.Path); - return DownloadFileAsync(file, destination, progress, cancellationToken); + using var destination = IOUtil.AsyncWriteStream(file.Path, false); + await DownloadFileAsync(file, destination, progress, cancellationToken).ConfigureAwait(false); } public async Task DownloadFileAsync(DownloadFile file, Stream destination, @@ -33,7 +43,7 @@ public async Task DownloadFileAsync(DownloadFile file, Stream destination, { // Get the http headers first to examine the content length using var response = await httpClient.GetAsync(file.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - var fileSize = response.Content.Headers.ContentLength ?? file.Size; + var contentLength = response.Content.Headers.ContentLength ?? file.Size; using var download = await response.Content.ReadAsStreamAsync(); @@ -45,18 +55,27 @@ public async Task DownloadFileAsync(DownloadFile file, Stream destination, return; } - var buffer = new byte[BufferSize]; - int bytesRead; - while ((bytesRead = await download.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + byte[] copyBuffer = new byte[contentLength == -1 || contentLength > DefaultDownloadBufferLength ? DefaultDownloadBufferLength : contentLength]; + while (true) { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - progress?.Report(new DownloadFileByteProgress( - file: file, - total: fileSize, - progressed: bytesRead)); - } + if (cancellationToken.IsCancellationRequested) + return; + + int bytesRead = await download.ReadAsync(copyBuffer, 0, copyBuffer.Length).ConfigureAwait(false); + if (bytesRead == 0) + break; - progress?.Report(new DownloadFileByteProgress(file, fileSize, fileSize)); + //await writeStream.WriteAsync(new ReadOnlyMemory(copyBuffer, 0, bytesRead)).ConfigureAwait(false); + //await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(copyBuffer, 0, bytesRead).ConfigureAwait(false); + + progress?.Report(new DownloadFileByteProgress() + { + File = file, + TotalBytes = contentLength, + ProgressedBytes = bytesRead + }); + } } } } diff --git a/CmlLib/Core/Mapper.cs b/CmlLib/Core/Mapper.cs index 19d35f0..b2df369 100644 --- a/CmlLib/Core/Mapper.cs +++ b/CmlLib/Core/Mapper.cs @@ -31,7 +31,8 @@ public static string[] MapInterpolation(string[] arg, Dictionary dicts, bool handleEmpty) { - str = argBracket.Replace(str, (match => + str = argBracket.Replace(str, match => { if (match.Groups.Count < 2) return match.Value; @@ -66,7 +67,7 @@ public static string Interpolation(string str, Dictionary dicts } return match.Value; - })); + }); if (handleEmpty) return HandleEmptyArg(str); @@ -117,12 +118,12 @@ public static string HandleEmptyArg(string input) { var s = input.Split('='); - if (s[1].Contains(" ") && !checkEmptyHandled(s[1])) + if ((s[1].Contains(" ") && !checkEmptyHandled(s[1])) || string.IsNullOrWhiteSpace(s[1])) return s[0] + "=\"" + s[1] + "\""; else return input; } - else if (input.Contains(" ") && !checkEmptyHandled(input)) + else if ((input.Contains(" ") && !checkEmptyHandled(input)) || string.IsNullOrWhiteSpace(input)) return "\"" + input + "\""; else return input; From a6f475843851d5ffcf58c877126dbcad2b68a39b Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Apr 2022 18:44:23 +0900 Subject: [PATCH 043/137] obsolete WebDownload.cs --- CmlLib/Utils/WebDownload.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/CmlLib/Utils/WebDownload.cs b/CmlLib/Utils/WebDownload.cs index 7a33978..2965f0b 100644 --- a/CmlLib/Utils/WebDownload.cs +++ b/CmlLib/Utils/WebDownload.cs @@ -7,6 +7,7 @@ namespace CmlLib.Utils { + [Obsolete] internal class WebDownload { public static bool IgnoreProxy { get; set; } = true; @@ -15,12 +16,9 @@ internal class WebDownload private class TimeoutWebClient : WebClient { - protected override WebRequest? GetWebRequest(Uri uri) + protected override WebRequest GetWebRequest(Uri uri) { - WebRequest? w = base.GetWebRequest(uri); - if (w == null) - return null; - + WebRequest w = base.GetWebRequest(uri); w.Timeout = DefaultWebRequestTimeout; if (IgnoreProxy) @@ -36,7 +34,7 @@ private class TimeoutWebClient : WebClient private static readonly int DefaultBufferSize = 1024 * 64; // 64kb private readonly object locker = new object(); - internal event EventHandler? FileDownloadProgressChanged; + internal event EventHandler? FileDownloadProgressChanged; internal event ProgressChangedEventHandler? DownloadProgressChangedEvent; internal void DownloadFile(string url, string path) @@ -94,8 +92,17 @@ internal async Task DownloadFileAsync(DownloadFile file) lastBytes = e.BytesReceived; - var progress = new DownloadFileProgress( - file, e.TotalBytesToReceive, progressedBytes, e.BytesReceived, e.ProgressPercentage); + var progress = new DownloadFileByteProgress() + { + File = file, + TotalBytes = e.TotalBytesToReceive, + ProgressedBytes = progressedBytes + }; + //file: file, + //total: e.TotalBytesToReceive, + //progressed: progressedBytes); + //received: e.BytesReceived, + //percent: e.ProgressPercentage); FileDownloadProgressChanged?.Invoke(this, progress); } }; From 41c26ffceced039bb941855a86490f64e0005c5f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Apr 2022 18:47:47 +0900 Subject: [PATCH 044/137] clean code --- .../Downloader/AsyncParallelDownloader.cs | 2 +- CmlLib/Core/Downloader/DownloadFile.cs | 4 ++-- CmlLib/Core/Downloader/SequenceDownloader.cs | 3 --- CmlLib/Core/FileChecker/ClientChecker.cs | 1 - .../Core/FileChecker/FileCheckerCollection.cs | 1 - CmlLib/Core/Files/MLibraryParser.cs | 4 ++-- .../LiteLoader/LiteLoaderInstaller.cs | 1 - .../LiteLoader/LiteLoaderVersionMetadata.cs | 5 +---- CmlLib/Core/Version/MVersionCollection.cs | 6 ------ CmlLib/Core/Version/MVersionParser.cs | 13 +++++++++--- CmlLib/Core/Version/MVersionSortOption.cs | 6 +----- .../VersionMetadata/LocalVersionMetadata.cs | 1 - .../Core/VersionMetadata/MVersionMetadata.cs | 2 +- .../VersionMetadata/StringVersionMetadata.cs | 1 - CmlLib/Utils/IOUtil.cs | 20 ++++++++++--------- CmlLib/Utils/NativeMethods.cs | 1 + 16 files changed, 30 insertions(+), 41 deletions(-) diff --git a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs b/CmlLib/Core/Downloader/AsyncParallelDownloader.cs index 1347c86..02edcc9 100644 --- a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs +++ b/CmlLib/Core/Downloader/AsyncParallelDownloader.cs @@ -149,7 +149,7 @@ private void byteProgressHandler(DownloadFileByteProgress progress) { lock (progressEventLock) { - if (progress.File.Size <= 0) + if (progress.File != null && progress.File.Size <= 0) { totalBytes += progress.TotalBytes; progress.File.Size = progress.TotalBytes; diff --git a/CmlLib/Core/Downloader/DownloadFile.cs b/CmlLib/Core/Downloader/DownloadFile.cs index 51499fe..31911fe 100644 --- a/CmlLib/Core/Downloader/DownloadFile.cs +++ b/CmlLib/Core/Downloader/DownloadFile.cs @@ -13,8 +13,8 @@ public DownloadFile(string path, string url) public MFile Type { get; set; } public string? Name { get; set; } - public string Path { get; private set; } - public string Url { get; private set; } + public string Path { get; } + public string Url { get; } public long Size { get; set; } public Func[]? AfterDownload { get; set; } diff --git a/CmlLib/Core/Downloader/SequenceDownloader.cs b/CmlLib/Core/Downloader/SequenceDownloader.cs index 62e1062..82aa964 100644 --- a/CmlLib/Core/Downloader/SequenceDownloader.cs +++ b/CmlLib/Core/Downloader/SequenceDownloader.cs @@ -1,7 +1,6 @@ using CmlLib.Utils; using System; using System.ComponentModel; -using System.IO; using System.Net.Http; using System.Threading.Tasks; @@ -12,7 +11,6 @@ public class SequenceDownloader : IDownloader private readonly HttpClientDownloadHelper downloader; public bool IgnoreInvalidFiles { get; set; } = true; - private IProgress? pChangeProgress; public SequenceDownloader() : this(HttpUtil.HttpClient) { @@ -36,7 +34,6 @@ public async Task DownloadFiles(DownloadFile[] files, var percent = (float)progress.ProgressedBytes / progress.TotalBytes * 100; downloadProgress?.Report(new ProgressChangedEventArgs((int)percent, null)); }); - pChangeProgress = downloadProgress; fileProgress?.Report( new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); diff --git a/CmlLib/Core/FileChecker/ClientChecker.cs b/CmlLib/Core/FileChecker/ClientChecker.cs index bea278a..f344d48 100644 --- a/CmlLib/Core/FileChecker/ClientChecker.cs +++ b/CmlLib/Core/FileChecker/ClientChecker.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using CmlLib.Core.Downloader; -using CmlLib.Core.Files; using CmlLib.Core.Version; using CmlLib.Utils; diff --git a/CmlLib/Core/FileChecker/FileCheckerCollection.cs b/CmlLib/Core/FileChecker/FileCheckerCollection.cs index 95b94c5..25ed391 100644 --- a/CmlLib/Core/FileChecker/FileCheckerCollection.cs +++ b/CmlLib/Core/FileChecker/FileCheckerCollection.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using CmlLib.Core.Files; namespace CmlLib.Core.FileChecker { diff --git a/CmlLib/Core/Files/MLibraryParser.cs b/CmlLib/Core/Files/MLibraryParser.cs index b6450f1..340c31a 100644 --- a/CmlLib/Core/Files/MLibraryParser.cs +++ b/CmlLib/Core/Files/MLibraryParser.cs @@ -38,12 +38,12 @@ public class MLibraryParser var natives = item.SafeGetProperty("natives"); if (natives != null) { - var nativeId = natives?.GetPropertyValue(MRule.OSName)? + var nativeId = natives.Value.GetPropertyValue(MRule.OSName)? .Replace("${arch}", MRule.Arch); if (classifiers != null && nativeId != null) { - var lObj = classifiers?.SafeGetProperty(nativeId) ?? classifiers?.SafeGetProperty(MRule.OSName); + var lObj = classifiers.Value.SafeGetProperty(nativeId) ?? classifiers.Value.SafeGetProperty(MRule.OSName); if (lObj != null) list.Add(createMLibrary(name, nativeId, isRequire, lObj)); } diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs b/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs index 52bcb2c..fe64a83 100644 --- a/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs +++ b/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; namespace CmlLib.Core.Installer.LiteLoader { diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs index 5742506..057c131 100644 --- a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs @@ -1,10 +1,7 @@ -using System.Collections.Generic; -using System.Data; -using System.IO; +using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using CmlLib.Core.Files; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; diff --git a/CmlLib/Core/Version/MVersionCollection.cs b/CmlLib/Core/Version/MVersionCollection.cs index 949544b..e00c1ac 100644 --- a/CmlLib/Core/Version/MVersionCollection.cs +++ b/CmlLib/Core/Version/MVersionCollection.cs @@ -154,9 +154,6 @@ public IEnumerator GetEnumerator() { foreach (DictionaryEntry? item in Versions) { - if (!item.HasValue) - continue; - var entry = item.Value; var version = (MVersionMetadata)entry.Value!; @@ -168,9 +165,6 @@ IEnumerator IEnumerable.GetEnumerator() { foreach (DictionaryEntry? item in Versions) { - if (!item.HasValue) - continue; - yield return item.Value; } } diff --git a/CmlLib/Core/Version/MVersionParser.cs b/CmlLib/Core/Version/MVersionParser.cs index b52a12f..972c237 100644 --- a/CmlLib/Core/Version/MVersionParser.cs +++ b/CmlLib/Core/Version/MVersionParser.cs @@ -81,7 +81,7 @@ public static MVersion ParseFromJson(JsonDocument document) } // metadata - version.ReleaseTime = root.GetPropertyValue("releaseTime");; + version.ReleaseTime = root.GetPropertyValue("releaseTime"); var type = root.GetPropertyValue("type"); version.TypeStr = type; @@ -147,13 +147,20 @@ private static string[] argParse(JsonElement arr) { foreach (var strProp in value.Value.EnumerateArray()) { + if (strProp.ValueKind != JsonValueKind.String) + continue; + var str = strProp.GetString(); if (!string.IsNullOrEmpty(str)) strList.Add(str); } } - else - strList.Add(value.ToString()); + else if (value.Value.ValueKind == JsonValueKind.String) + { + var valueString = value.Value.GetString(); + if (!string.IsNullOrEmpty(valueString)) + strList.Add(valueString); + } } } } diff --git a/CmlLib/Core/Version/MVersionSortOption.cs b/CmlLib/Core/Version/MVersionSortOption.cs index 57d7c8f..b0d15cd 100644 --- a/CmlLib/Core/Version/MVersionSortOption.cs +++ b/CmlLib/Core/Version/MVersionSortOption.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace CmlLib.Core.Version +namespace CmlLib.Core.Version { public enum MVersionSortPropertyOption { diff --git a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs b/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs index 6b9550d..adfa9e4 100644 --- a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Threading.Tasks; using CmlLib.Utils; diff --git a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs b/CmlLib/Core/VersionMetadata/MVersionMetadata.cs index ed02ea9..d0503fd 100644 --- a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/MVersionMetadata.cs @@ -19,7 +19,7 @@ protected MVersionMetadata(string name) public bool IsLocalVersion { get; set; } [JsonPropertyName("id")] - public string Name { get; private set; } + public string Name { get; } [JsonPropertyName("type")] public string? Type { get; set; } diff --git a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs index 8739ae7..7989510 100644 --- a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs +++ b/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Threading.Tasks; using CmlLib.Core.Version; using CmlLib.Utils; diff --git a/CmlLib/Utils/IOUtil.cs b/CmlLib/Utils/IOUtil.cs index a56e7be..e1879c3 100644 --- a/CmlLib/Utils/IOUtil.cs +++ b/CmlLib/Utils/IOUtil.cs @@ -1,9 +1,7 @@ using System; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace CmlLib.Utils @@ -39,24 +37,27 @@ public static string CombinePath(string[] paths) return path; })); } - -#pragma warning disable SYSLIB0021 + public static bool CheckSHA1(string path, string? compareHash) { if (string.IsNullOrEmpty(compareHash)) return true; - + try { string fileHash; +#pragma warning disable CS0618 +#pragma warning disable SYSLIB0021 using (var file = File.OpenRead(path)) - using (var hasher = new System.Security.Cryptography.SHA1CryptoServiceProvider()) + { var binaryHash = hasher.ComputeHash(file); fileHash = BitConverter.ToString(binaryHash).Replace("-", "").ToLowerInvariant(); } +#pragma warning restore SYSLIB0021 +#pragma warning restore CS0618 return fileHash == compareHash; } @@ -65,7 +66,6 @@ public static bool CheckSHA1(string path, string? compareHash) return false; } } -#pragma warning restore SYSLIB0021 public static bool CheckFileValidation(string path, string? hash, bool checkHash) { @@ -100,6 +100,7 @@ public static bool CheckFileValidation(string path, string hash, long size) // from .NET Framework reference source code // If we use the path-taking constructors we will not have FileOptions.Asynchronous set and // we will have asynchronous file access faked by the thread pool. We want the real thing. + // https://source.dot.net/#System.Private.CoreLib/File.cs,c7e38336f651ba69 public static FileStream AsyncReadStream(string path) { FileStream stream = new FileStream( @@ -108,7 +109,8 @@ public static FileStream AsyncReadStream(string path) return stream; } - + + // https://source.dot.net/#System.Private.CoreLib/File.cs,b5563532b5be50f6 public static FileStream AsyncWriteStream(string path, bool append) { FileStream stream = new FileStream( @@ -117,7 +119,7 @@ public static FileStream AsyncWriteStream(string path, bool append) return stream; } - + public static StreamReader AsyncStreamReader(string path, Encoding encoding) { FileStream stream = AsyncReadStream(path); diff --git a/CmlLib/Utils/NativeMethods.cs b/CmlLib/Utils/NativeMethods.cs index 4b01489..8860284 100644 --- a/CmlLib/Utils/NativeMethods.cs +++ b/CmlLib/Utils/NativeMethods.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; +// ReSharper disable UnusedMember.Local // ReSharper disable InconsistentNaming namespace CmlLib.Utils From 52ff7a2f9e5201edaf9944e2073b73a0f5285270 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Apr 2022 01:12:52 +0900 Subject: [PATCH 045/137] use HttpClient in AssetChecker --- CmlLib/Core/FileChecker/AssetChecker.cs | 38 ++++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/CmlLib/Core/FileChecker/AssetChecker.cs b/CmlLib/Core/FileChecker/AssetChecker.cs index 56e7739..03c8777 100644 --- a/CmlLib/Core/FileChecker/AssetChecker.cs +++ b/CmlLib/Core/FileChecker/AssetChecker.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; +using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; using CmlLib.Core.Downloader; @@ -15,6 +15,18 @@ namespace CmlLib.Core.FileChecker { public sealed class AssetChecker : IFileChecker { + private readonly HttpClient httpClient; + + public AssetChecker() : this(HttpUtil.HttpClient) + { + + } + + public AssetChecker(HttpClient client) + { + this.httpClient = client; + } + private string assetServer = MojangServer.ResourceDownload; public string AssetServer { @@ -32,26 +44,24 @@ public string AssetServer public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, IProgress? progress) { - return checkIndexAndAsset(path, version, progress); - } - - public Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? progress) - { - return Task.Run(() => checkIndexAndAsset(path, version, progress)); + if (version.Assets == null) + return null; + checkIndex(path, version.Assets).GetAwaiter().GetResult(); + return CheckAssetFiles(path, version.Assets, progress); } - private DownloadFile[]? checkIndexAndAsset(MinecraftPath path, MVersion version, + public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, IProgress? progress) { if (version.Assets == null) return null; - checkIndex(path, version.Assets); - return CheckAssetFiles(path, version.Assets, progress); + + await checkIndex(path, version.Assets).ConfigureAwait(false); + return await Task.Run(() => CheckAssetFiles(path, version.Assets, progress)).ConfigureAwait(false); } // Check index file validation and download - private void checkIndex(MinecraftPath path, MFileMetadata assets) + private async Task checkIndex(MinecraftPath path, MFileMetadata assets) { if (string.IsNullOrEmpty(assets.Id) || string.IsNullOrEmpty(assets.Url)) return; @@ -63,8 +73,8 @@ private void checkIndex(MinecraftPath path, MFileMetadata assets) IOUtil.CreateParentDirectory(indexFilePath); - using var wc = new WebClient(); - wc.DownloadFile(assets.Url, indexFilePath); + var downloader = new HttpClientDownloadHelper(httpClient); + await downloader.DownloadFileAsync(new DownloadFile(indexFilePath, assets.Url)).ConfigureAwait(false); } [MethodTimer.Time] From 6527a52f7954b1abcfc5065a93929a596f54436a Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Apr 2022 01:13:07 +0900 Subject: [PATCH 046/137] use HttpClient in Changelogs --- CmlLib/Utils/Changelogs.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CmlLib/Utils/Changelogs.cs b/CmlLib/Utils/Changelogs.cs index 8685d28..a789aa4 100644 --- a/CmlLib/Utils/Changelogs.cs +++ b/CmlLib/Utils/Changelogs.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Runtime.CompilerServices; -using System.Text; +using System.Net.Http; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -17,14 +15,19 @@ public class Changelogs // Versions that exist in 'PatchNotesUrl' do not need to be added the list below private static readonly Dictionary AltUrls = new Dictionary { - { "1.14.2", "https://feedback.minecraft.net/hc/en-us/articles/360028919851-Minecraft-Java-Edition-1-14-2" }, + { "1.14.2", "https://feedback.minecraft.net/hc/en-us/articles/360028919851-Minecraft-Java-Edition-1-14-2" }, { "1.14.3", "https://feedback.minecraft.net/hc/en-us/articles/360030771451-Minecraft-Java-Edition-1-14-3" }, { "1.14.4", "https://feedback.minecraft.net/hc/en-us/articles/360030780172-Minecraft-Java-Edition-1-14-4" }, }; + + public static Task GetChangelogs() + { + return GetChangelogs(HttpUtil.HttpClient); + } - public static async Task GetChangelogs() + public static async Task GetChangelogs(HttpClient client) { - var response = await HttpUtil.HttpClient.GetStreamAsync(PatchNotesUrl) + var response = await client.GetStreamAsync(PatchNotesUrl) .ConfigureAwait(false); var jsonDocument = await JsonDocument.ParseAsync(response).ConfigureAwait(false); var root = jsonDocument.RootElement; @@ -44,14 +47,16 @@ public static async Task GetChangelogs() } } - return new Changelogs(versionDict); + return new Changelogs(versionDict, client); } - private Changelogs(Dictionary versions) + private Changelogs(Dictionary versions, HttpClient client) { + this.httpClient = client; this.versions = versions; } + private readonly HttpClient httpClient; private readonly Dictionary versions; public string[] GetAvailableVersions() @@ -82,12 +87,7 @@ public string[] GetAvailableVersions() private async Task GetChangelogFromUrl(string url) { - string html; - using (var wc = new WebClient()) - { - var data = await wc.DownloadDataTaskAsync(url).ConfigureAwait(false); - html = Encoding.UTF8.GetString(data); - } + var html = await httpClient.GetStringAsync(url).ConfigureAwait(false); var regResult = ArticleRegex.Match(html); if (!regResult.Success) From b6ce2aa99dc410cb9db7b48fe0c91fa1afdbdfbc Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Apr 2022 01:13:41 +0900 Subject: [PATCH 047/137] use HttpClient in MJava --- CmlLib/Core/Installer/MJava.cs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/CmlLib/Core/Installer/MJava.cs b/CmlLib/Core/Installer/MJava.cs index 3c80a73..8c8b655 100644 --- a/CmlLib/Core/Installer/MJava.cs +++ b/CmlLib/Core/Installer/MJava.cs @@ -6,6 +6,8 @@ using CmlLib.Core.Java; using System.Text.Json; using System.Net; +using System.Net.Http; +using CmlLib.Core.Downloader; namespace CmlLib.Core.Installer { @@ -19,16 +21,19 @@ public static readonly string DefaultRuntimeDirectory public event ProgressChangedEventHandler? ProgressChanged; public string RuntimeDirectory { get; private set; } + private readonly HttpClient httpClient; private IProgress? pProgressChanged; - public IJavaPathResolver JavaPathResolver { get; set; } - public MJava() : this(DefaultRuntimeDirectory) { } + public MJava() : this(HttpUtil.HttpClient) { } + public MJava(HttpClient client) : this(client, DefaultRuntimeDirectory) { } + public MJava(string runtimePath) : this(HttpUtil.HttpClient, runtimePath) { } - public MJava(string runtimePath) + public MJava(HttpClient client, string runtimePath) { RuntimeDirectory = runtimePath; JavaPathResolver = new MinecraftJavaPathResolver(runtimePath); + httpClient = client; } public string GetBinaryPath() @@ -97,16 +102,17 @@ private string parseLauncherMetadata(string json) private async Task downloadJavaLzmaAsync(string javaUrl) { Directory.CreateDirectory(RuntimeDirectory); - string lzmapath = Path.Combine(Path.GetTempPath(), "jre.lzma"); + string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - using (var wc = new WebClient()) + var downloader = new HttpClientDownloadHelper(httpClient); + var progress = new Progress(p => { - wc.DownloadProgressChanged += Downloader_DownloadProgressChangedEvent; - await wc.DownloadFileTaskAsync(javaUrl, lzmapath) - .ConfigureAwait(false); - } + var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; + pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); + }); + await downloader.DownloadFileAsync(new DownloadFile(lzmaPath, javaUrl), progress); - return lzmapath; + return lzmaPath; } private void decompressJavaFile(string lzmaPath) @@ -123,10 +129,5 @@ private void Z_ProgressEvent(object? sender, int e) { pProgressChanged?.Report(new ProgressChangedEventArgs(50 + e / 2, null)); } - - private void Downloader_DownloadProgressChangedEvent(object? sender, ProgressChangedEventArgs e) - { - pProgressChanged?.Report(new ProgressChangedEventArgs(e.ProgressPercentage / 2, null)); - } } -} +} \ No newline at end of file From f169b202b2e3e239889a1f2af9ee6f4a25ee853f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Apr 2022 01:14:01 +0900 Subject: [PATCH 048/137] refactoring --- CmlLib/Core/CMLauncher.cs | 6 +++--- CmlLib/Core/Downloader/AsyncParallelDownloader.cs | 2 +- CmlLib/Core/Downloader/HttpClientDownloadHelper.cs | 6 +++--- CmlLib/Core/FileChecker/JavaChecker.cs | 14 +++++++------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CmlLib/Core/CMLauncher.cs b/CmlLib/Core/CMLauncher.cs index 981e364..4813308 100644 --- a/CmlLib/Core/CMLauncher.cs +++ b/CmlLib/Core/CMLauncher.cs @@ -30,12 +30,12 @@ public CMLauncher(MinecraftPath path) pProgressChanged = new Progress( e => ProgressChanged?.Invoke(this, e)); - JavaPathResolver = new MinecraftJavaPathResolver(path); + // to prevent null-reference warning, set both field, property + JavaPathResolver = javaPathResolver = new MinecraftJavaPathResolver(path); } public event DownloadFileChangedHandler? FileChanged; public event ProgressChangedEventHandler? ProgressChanged; - public event EventHandler? LogOutput; private readonly IProgress pFileChanged; private readonly IProgress pProgressChanged; @@ -49,7 +49,7 @@ public CMLauncher(MinecraftPath path) private IJavaPathResolver javaPathResolver; public IJavaPathResolver JavaPathResolver { - get => javaPathResolver; + get => javaPathResolver; set { javaPathResolver = value; diff --git a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs b/CmlLib/Core/Downloader/AsyncParallelDownloader.cs index 02edcc9..cf389a2 100644 --- a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs +++ b/CmlLib/Core/Downloader/AsyncParallelDownloader.cs @@ -160,7 +160,7 @@ private void byteProgressHandler(DownloadFileByteProgress progress) if (receivedBytes > totalBytes) return; - float percent = (float)receivedBytes / totalBytes * 100; + double percent = (double)receivedBytes / totalBytes * 100; pChangeProgress?.Report(new FileProgressChangedEventArgs(totalBytes, receivedBytes, (int)percent)); } } diff --git a/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs b/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs index 74f63c7..59f20d5 100644 --- a/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs +++ b/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs @@ -1,9 +1,7 @@ using CmlLib.Utils; using System; -using System.Collections.Generic; using System.IO; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,7 +18,6 @@ internal class DownloadFileByteProgress // https://source.dot.net/#System.Net.WebClient/System/Net/WebClient.cs,c2224e40a6960929 internal class HttpClientDownloadHelper { - private const int DefaultCopyBufferLength = 8192; private const int DefaultDownloadBufferLength = 65536; public HttpClientDownloadHelper(HttpClient client) @@ -55,6 +52,9 @@ public async Task DownloadFileAsync(DownloadFile file, Stream destination, return; } + if (download.CanTimeout) + download.ReadTimeout = 10000; + byte[] copyBuffer = new byte[contentLength == -1 || contentLength > DefaultDownloadBufferLength ? DefaultDownloadBufferLength : contentLength]; while (true) { diff --git a/CmlLib/Core/FileChecker/JavaChecker.cs b/CmlLib/Core/FileChecker/JavaChecker.cs index d6c9084..ced3bf7 100644 --- a/CmlLib/Core/FileChecker/JavaChecker.cs +++ b/CmlLib/Core/FileChecker/JavaChecker.cs @@ -53,14 +53,14 @@ public JavaChecker(IJavaPathResolver javaPathResolver) if (string.IsNullOrEmpty(javaVersion)) javaVersion = MinecraftJavaPathResolver.JreLegacyVersionName; - var result = await internalCheckJava(javaVersion, path, downloadProgress) + var result = await internalCheckJava(javaVersion, downloadProgress) .ConfigureAwait(false); version.JavaBinaryPath = result.JavaBinaryPath; return result.JavaFiles; } - private async Task internalCheckJava(string javaVersion, MinecraftPath path, + private async Task internalCheckJava(string javaVersion, IProgress? downloadProgress) { if (JavaPathResolver == null) @@ -88,25 +88,25 @@ private async Task internalCheckJava(string javaVersion, Minecr if (javaManifest == null && javaVersion != MinecraftJavaPathResolver.JreLegacyVersionName) javaManifest = await getJavaVersionManifest(javaVersions.Value, MinecraftJavaPathResolver.JreLegacyVersionName); if (javaManifest == null) - return await legacyJavaChecker(path); + return await legacyJavaChecker(); // get file objects using var manifestDocument = JsonDocument.Parse(javaManifest); var files = manifestDocument.RootElement.SafeGetProperty("files"); if (files == null) - return await legacyJavaChecker(path); + return await legacyJavaChecker(); downloadFiles = toDownloadFiles(files.Value, JavaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); } else // no java version for osName - return await legacyJavaChecker(path); + return await legacyJavaChecker(); } catch (Exception e) { Debug.WriteLine(e); if (string.IsNullOrEmpty(binPath)) - return await legacyJavaChecker(path); + return await legacyJavaChecker(); else downloadFiles = new DownloadFile[] { }; } @@ -230,7 +230,7 @@ private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, } // legacy java checker that use MJava - private async Task legacyJavaChecker(MinecraftPath path) + private async Task legacyJavaChecker() { if (JavaPathResolver == null) throw new InvalidOperationException("JavaPathResolver was null"); From e97495c6d9b6a3531b30c95f29df259784e63e62 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Apr 2022 01:14:28 +0900 Subject: [PATCH 049/137] update CmlLibCoreSample --- CmlLibCoreSample/CmlLibCoreSample.csproj | 2 +- CmlLibCoreSample/InstallerTest.cs | 4 ---- CmlLibCoreSample/Program.cs | 23 ++++++++++------------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/CmlLibCoreSample/CmlLibCoreSample.csproj b/CmlLibCoreSample/CmlLibCoreSample.csproj index e77fc62..e6ec59a 100644 --- a/CmlLibCoreSample/CmlLibCoreSample.csproj +++ b/CmlLibCoreSample/CmlLibCoreSample.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6.0 diff --git a/CmlLibCoreSample/InstallerTest.cs b/CmlLibCoreSample/InstallerTest.cs index eb5b9d5..c9dae4e 100644 --- a/CmlLibCoreSample/InstallerTest.cs +++ b/CmlLibCoreSample/InstallerTest.cs @@ -1,12 +1,8 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using CmlLib.Core; using CmlLib.Core.Auth; using CmlLib.Core.Downloader; -using CmlLib.Core.Installer; using CmlLib.Core.Installer.FabricMC; using CmlLib.Core.Installer.LiteLoader; diff --git a/CmlLibCoreSample/Program.cs b/CmlLibCoreSample/Program.cs index b592c1d..2c61d50 100644 --- a/CmlLibCoreSample/Program.cs +++ b/CmlLibCoreSample/Program.cs @@ -3,7 +3,6 @@ using CmlLib.Core.Downloader; using System; using System.Threading.Tasks; -using CmlLib.Core.VersionLoader; namespace CmlLibCoreSample { @@ -19,8 +18,8 @@ public static async Task Main() // There are two login methods, one is using mojang email and password, and the other is using only username // Choose one which you want. - session = await p.PremiumLogin(); // Login by mojang email and password - //session = p.OfflineLogin(); // Login by username + //session = await p.PremiumLogin(); // Login by mojang email and password + session = p.OfflineLogin(); // Login by username // log login session information Console.WriteLine("Success to login : {0} / {1} / {2}", session.Username, session.UUID, session.AccessToken); @@ -131,7 +130,9 @@ async Task Start(MSession session) // var process = await launcher.CreateProcessAsync("fabric-loader-0.11.3-1.16.5") // fabric-loader Console.WriteLine("input version (example: 1.12.2) : "); - var process = await launcher.CreateProcessAsync(Console.ReadLine(), launchOption); + var versionName = Console.ReadLine(); + //var versionName = "1.18.2"; + var process = await launcher.CreateProcessAsync(versionName, launchOption); //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); Console.WriteLine(process.StartInfo.FileName); @@ -199,16 +200,13 @@ async Task QuickStart() #region Pretty event handler - int endTop = -1; - private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) { - Console.SetCursorPosition(0, endTop); - + var top = Console.CursorTop; + Console.SetCursorPosition(0, top); // e.ProgressPercentage: 0~100 - Console.Write("{0}% ", e.ProgressPercentage); - - Console.SetCursorPosition(0, endTop); + Console.Write($"{e.ProgressPercentage}% "); + Console.SetCursorPosition(0, top); } private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) @@ -217,8 +215,6 @@ private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - - endTop = Console.CursorTop; } #endregion @@ -236,3 +232,4 @@ private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) #endregion } } + From 3ee68836540b4c0cc6463b7d2372f739a8a81d0c Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 30 Jul 2023 08:39:31 +0000 Subject: [PATCH 050/137] update project --- CmlLib/CmlLib.csproj | 28 +++++++++---------- CmlLib/Core/CMLauncher.cs | 1 - CmlLib/Core/FileChecker/AssetChecker.cs | 1 - CmlLib/Core/FileChecker/LibraryChecker.cs | 1 - CmlLib/Core/Launcher/MLaunch.cs | 1 - CmlLib/Core/Launcher/MNative.cs | 1 - CmlLib/Core/Version/MVersionParser.cs | 1 - .../Core/VersionLoader/MojangVersionLoader.cs | 1 - CmlLib/FodyWeavers.xml | 1 - CmlLib/FodyWeavers.xsd | 1 - CmlLib/_Test.cs | 14 ---------- CmlLibCoreSample/Program.cs | 1 - 12 files changed, 13 insertions(+), 39 deletions(-) delete mode 100644 CmlLib/_Test.cs diff --git a/CmlLib/CmlLib.csproj b/CmlLib/CmlLib.csproj index 91a3152..c1f33ed 100644 --- a/CmlLib/CmlLib.csproj +++ b/CmlLib/CmlLib.csproj @@ -1,8 +1,8 @@  - netstandard2.0;net6.0 - 8.0 + netstandard2.0 + 10.0 enable 3.4.0 Minecraft Launcher Library for .NET @@ -19,14 +19,14 @@ Support all version, forge, optifine AlphaBs CmlLib.Core - - - - - - + + + + + + all @@ -35,17 +35,15 @@ Support all version, forge, optifine all runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - - - + + + + + <_Parameter1>CmlLib.Core.Test - diff --git a/CmlLib/Core/CMLauncher.cs b/CmlLib/Core/CMLauncher.cs index 4813308..1850c12 100644 --- a/CmlLib/Core/CMLauncher.cs +++ b/CmlLib/Core/CMLauncher.cs @@ -138,7 +138,6 @@ public async Task CheckAndDownloadAsync(MVersion version) } } - [MethodTimer.Time] public Process CreateProcess(MVersion version, MLaunchOption option, bool checkAndDownload=true) { option.StartVersion = version; diff --git a/CmlLib/Core/FileChecker/AssetChecker.cs b/CmlLib/Core/FileChecker/AssetChecker.cs index 03c8777..4a359eb 100644 --- a/CmlLib/Core/FileChecker/AssetChecker.cs +++ b/CmlLib/Core/FileChecker/AssetChecker.cs @@ -77,7 +77,6 @@ private async Task checkIndex(MinecraftPath path, MFileMetadata assets) await downloader.DownloadFileAsync(new DownloadFile(indexFilePath, assets.Url)).ConfigureAwait(false); } - [MethodTimer.Time] public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, IProgress? progress) { diff --git a/CmlLib/Core/FileChecker/LibraryChecker.cs b/CmlLib/Core/FileChecker/LibraryChecker.cs index 383ae73..aac2112 100644 --- a/CmlLib/Core/FileChecker/LibraryChecker.cs +++ b/CmlLib/Core/FileChecker/LibraryChecker.cs @@ -49,7 +49,6 @@ public string LibraryServer return checkLibraries(path, libs, progress); } - [MethodTimer.Time] private DownloadFile[]? checkLibraries(MinecraftPath path, MLibrary[]? libs, IProgress? progress) { diff --git a/CmlLib/Core/Launcher/MLaunch.cs b/CmlLib/Core/Launcher/MLaunch.cs index 168d225..b9676be 100644 --- a/CmlLib/Core/Launcher/MLaunch.cs +++ b/CmlLib/Core/Launcher/MLaunch.cs @@ -78,7 +78,6 @@ private string createNativePath(MVersion version) return nativePath; } - [MethodTimer.Time] public string[] CreateArg() { MVersion version = launchOption.GetStartVersion(); diff --git a/CmlLib/Core/Launcher/MNative.cs b/CmlLib/Core/Launcher/MNative.cs index 4a82804..afd377f 100644 --- a/CmlLib/Core/Launcher/MNative.cs +++ b/CmlLib/Core/Launcher/MNative.cs @@ -15,7 +15,6 @@ public MNative(MinecraftPath gamePath, MVersion version) private readonly MVersion version; private readonly MinecraftPath gamePath; - [MethodTimer.Time] public string ExtractNatives() { string path = gamePath.GetNativePath(version.Id); diff --git a/CmlLib/Core/Version/MVersionParser.cs b/CmlLib/Core/Version/MVersionParser.cs index 972c237..46f19a2 100644 --- a/CmlLib/Core/Version/MVersionParser.cs +++ b/CmlLib/Core/Version/MVersionParser.cs @@ -21,7 +21,6 @@ public static MVersion ParseFromJson(string json) return ParseFromJson(jsonDocument); } - [MethodTimer.Time] public static MVersion ParseFromJson(JsonDocument document) { try diff --git a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs b/CmlLib/Core/VersionLoader/MojangVersionLoader.cs index 235cf72..82bf61b 100644 --- a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs +++ b/CmlLib/Core/VersionLoader/MojangVersionLoader.cs @@ -15,7 +15,6 @@ public async Task GetVersionMetadatasAsync() return parseList(res); } - [MethodTimer.Time] private MVersionCollection parseList(string res) { string? latestReleaseId = null; diff --git a/CmlLib/FodyWeavers.xml b/CmlLib/FodyWeavers.xml index e4ed8c5..bd6def3 100644 --- a/CmlLib/FodyWeavers.xml +++ b/CmlLib/FodyWeavers.xml @@ -1,4 +1,3 @@  - \ No newline at end of file diff --git a/CmlLib/FodyWeavers.xsd b/CmlLib/FodyWeavers.xsd index a416c19..3f3946e 100644 --- a/CmlLib/FodyWeavers.xsd +++ b/CmlLib/FodyWeavers.xsd @@ -4,7 +4,6 @@ - diff --git a/CmlLib/_Test.cs b/CmlLib/_Test.cs deleted file mode 100644 index 22d7763..0000000 --- a/CmlLib/_Test.cs +++ /dev/null @@ -1,14 +0,0 @@ -#pragma warning disable -// ReSharper disable All - -using System; - -namespace CmlLib -{ - public class _Test - { - public static string tstr = "462,31"; - } -} - -#pragma warning restore \ No newline at end of file diff --git a/CmlLibCoreSample/Program.cs b/CmlLibCoreSample/Program.cs index 2c61d50..50af9b8 100644 --- a/CmlLibCoreSample/Program.cs +++ b/CmlLibCoreSample/Program.cs @@ -10,7 +10,6 @@ class Program { public static async Task Main() { - Console.WriteLine(CmlLib._Test.tstr); var p = new Program(); // Login From 991090f8e0d9a26e01cd14f67a901cb14604b55f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 30 Jul 2023 08:53:46 +0000 Subject: [PATCH 051/137] fix QuiltLoader --- CmlLib/CmlLib.csproj | 3 ++- CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs | 10 +++++----- .../Installer/QuiltMC/QuiltVersionLoader.cs | 20 ++++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CmlLib/CmlLib.csproj b/CmlLib/CmlLib.csproj index 9798fa9..50e67b9 100644 --- a/CmlLib/CmlLib.csproj +++ b/CmlLib/CmlLib.csproj @@ -3,8 +3,9 @@ netstandard2.0 10.0 + enable enable - 3.3.7 + 3.4.0 Minecraft Launcher Library for .NET Support all version, forge, optifine diff --git a/CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs b/CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs index 3739b35..2ee34f5 100644 --- a/CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs +++ b/CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CmlLib.Core.Installer.QuiltMC { public class QuiltLoader { - [JsonProperty("separator")] + [JsonPropertyName("separator")] public string? Separator { get; set; } - [JsonProperty("build")] + [JsonPropertyName("build")] public string? Build { get; set; } - [JsonProperty("maven")] + [JsonPropertyName("maven")] public string? Maven { get; set; } - [JsonProperty("version")] + [JsonPropertyName("version")] public string? Version { get; set; } } } diff --git a/CmlLib/Core/Installer/QuiltMC/QuiltVersionLoader.cs b/CmlLib/Core/Installer/QuiltMC/QuiltVersionLoader.cs index 7af0ed4..f627524 100644 --- a/CmlLib/Core/Installer/QuiltMC/QuiltVersionLoader.cs +++ b/CmlLib/Core/Installer/QuiltMC/QuiltVersionLoader.cs @@ -1,8 +1,6 @@ using CmlLib.Core.Version; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; using System.Net; -using System.Threading.Tasks; +using System.Text.Json; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; @@ -58,12 +56,15 @@ private async Task internalGetVersionMetadatasAsync(bool syn private List parseVersions(string res, string loader) { - var jarr = JArray.Parse(res); - var versionList = new List(jarr.Count); + using var json = JsonDocument.Parse(res); + var jarr = json.RootElement.EnumerateArray(); + var versionList = new List(); foreach (var item in jarr) { - string? versionName = item["version"]?.ToString(); + if (!item.TryGetProperty("version", out var versionProp)) + continue; + var versionName = versionProp.GetString(); if (string.IsNullOrEmpty(versionName)) continue; @@ -101,11 +102,12 @@ public async Task GetQuiltLoadersAsync() private QuiltLoader[] parseLoaders(string res) { - var jarr = JArray.Parse(res); - var loaderList = new List(jarr.Count); + using var json = JsonDocument.Parse(res); + var jarr = json.RootElement.EnumerateArray(); + var loaderList = new List(); foreach (var item in jarr) { - var obj = item.ToObject(); + var obj = item.Deserialize(); if (obj != null) loaderList.Add(obj); } From 39ebd98f6f892f7ad6b8b5ea32025c0cf339c87e Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 30 Jul 2023 09:03:55 +0000 Subject: [PATCH 052/137] migrate CmlLibWinFormSample to net6.0-windows --- CmlLibCoreSample/CmlLibCoreSample.csproj | 24 ++- CmlLibWinFormSample/App.config | 6 - .../CmlLibWinFormSample.csproj | 171 ++---------------- CmlLibWinFormSample/Program.cs | 1 - .../Properties/AssemblyInfo.cs | 36 ---- .../Properties/Resources.Designer.cs | 63 ------- CmlLibWinFormSample/Properties/Resources.resx | 117 ------------ .../Properties/Settings.Designer.cs | 26 --- .../Properties/Settings.settings | 7 - 9 files changed, 25 insertions(+), 426 deletions(-) delete mode 100644 CmlLibWinFormSample/App.config delete mode 100644 CmlLibWinFormSample/Properties/AssemblyInfo.cs delete mode 100644 CmlLibWinFormSample/Properties/Resources.Designer.cs delete mode 100644 CmlLibWinFormSample/Properties/Resources.resx delete mode 100644 CmlLibWinFormSample/Properties/Settings.Designer.cs delete mode 100644 CmlLibWinFormSample/Properties/Settings.settings diff --git a/CmlLibCoreSample/CmlLibCoreSample.csproj b/CmlLibCoreSample/CmlLibCoreSample.csproj index e6ec59a..cca24bb 100644 --- a/CmlLibCoreSample/CmlLibCoreSample.csproj +++ b/CmlLibCoreSample/CmlLibCoreSample.csproj @@ -1,15 +1,13 @@ - - - Exe - net6.0 - - - - - - - - - + + 10.0 + enable + enable + Exe + net6.0 + + + + + diff --git a/CmlLibWinFormSample/App.config b/CmlLibWinFormSample/App.config deleted file mode 100644 index ecdcf8a..0000000 --- a/CmlLibWinFormSample/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/CmlLibWinFormSample/CmlLibWinFormSample.csproj b/CmlLibWinFormSample/CmlLibWinFormSample.csproj index 152299a..b8ec0eb 100644 --- a/CmlLibWinFormSample/CmlLibWinFormSample.csproj +++ b/CmlLibWinFormSample/CmlLibWinFormSample.csproj @@ -1,158 +1,15 @@ - - - - - Debug - AnyCPU - {CC996F52-7DF9-4A67-B3BF-6D7A346AEC67} - WinExe - CmlLibWinFormSample - CmlLibWinFormSample - v4.7.2 - 512 - true - - PackageReference - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - Form - - - ChangeLog.cs - - - Form - - - GameLog.cs - - - Form - - - GameOptions.cs - - - Form - - - JavaForm.cs - - - Form - - - PathForm.cs - - - Form - - - LoginForm.cs - - - Form - - - MainForm.cs - - - - - - Form - - - VersionSortOptionForm.cs - - - ChangeLog.cs - - - GameLog.cs - - - GameOptions.cs - - - JavaForm.cs - - - PathForm.cs - - - LoginForm.cs - - - MainForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - VersionSortOptionForm.cs - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - {af1c09f9-c4bd-420a-85b9-9eb454f55fcd} - CmlLib - - - + + + 10.0 + enable + enable + WinExe + true + net6.0-windows + true + + + + + \ No newline at end of file diff --git a/CmlLibWinFormSample/Program.cs b/CmlLibWinFormSample/Program.cs index 53dde9c..9468bb2 100644 --- a/CmlLibWinFormSample/Program.cs +++ b/CmlLibWinFormSample/Program.cs @@ -11,7 +11,6 @@ static class Program [STAThread] static void Main() { - Console.WriteLine(CmlLib._Test.tstr); // for test Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new LoginForm()); diff --git a/CmlLibWinFormSample/Properties/AssemblyInfo.cs b/CmlLibWinFormSample/Properties/AssemblyInfo.cs deleted file mode 100644 index d5eb47f..0000000 --- a/CmlLibWinFormSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 -// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 -// 이러한 특성 값을 변경하세요. -[assembly: AssemblyTitle("CmlLibWinFormSample")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("CmlLibWinFormSample")] -[assembly: AssemblyCopyright("Copyright © 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 -// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 -// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. -[assembly: ComVisible(false)] - -// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. -[assembly: Guid("cc996f52-7df9-4a67-b3bf-6d7a346aec67")] - -// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. -// -// 주 버전 -// 부 버전 -// 빌드 번호 -// 수정 버전 -// -// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를 -// 기본값으로 할 수 있습니다. -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CmlLibWinFormSample/Properties/Resources.Designer.cs b/CmlLibWinFormSample/Properties/Resources.Designer.cs deleted file mode 100644 index 174a2c9..0000000 --- a/CmlLibWinFormSample/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 이 코드는 도구를 사용하여 생성되었습니다. -// 런타임 버전:4.0.30319.42000 -// -// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 -// 이러한 변경 내용이 손실됩니다. -// -//------------------------------------------------------------------------------ - -namespace CmlLibWinFormSample.Properties { - using System; - - - /// - /// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다. - /// - // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder - // 클래스에서 자동으로 생성되었습니다. - // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 ResGen을 - // 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CmlLibWinFormSample.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을 - /// 재정의합니다. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/CmlLibWinFormSample/Properties/Resources.resx b/CmlLibWinFormSample/Properties/Resources.resx deleted file mode 100644 index af7dbeb..0000000 --- a/CmlLibWinFormSample/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/CmlLibWinFormSample/Properties/Settings.Designer.cs b/CmlLibWinFormSample/Properties/Settings.Designer.cs deleted file mode 100644 index d5e5f42..0000000 --- a/CmlLibWinFormSample/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 이 코드는 도구를 사용하여 생성되었습니다. -// 런타임 버전:4.0.30319.42000 -// -// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 -// 이러한 변경 내용이 손실됩니다. -// -//------------------------------------------------------------------------------ - -namespace CmlLibWinFormSample.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/CmlLibWinFormSample/Properties/Settings.settings b/CmlLibWinFormSample/Properties/Settings.settings deleted file mode 100644 index 3964565..0000000 --- a/CmlLibWinFormSample/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - From ad47907e78f31b74f2bfa541492bba59b2c7e006 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 30 Jul 2023 09:10:02 +0000 Subject: [PATCH 053/137] new project structure --- CmlLib.Core.sln | 46 ++++++++ CmlLib.sln | 43 -------- CmlLib.sln.DotSettings | 3 - .../console}/CmlLibCoreSample.csproj | 0 .../console}/InstallerTest.cs | 0 .../console}/Program.cs | 0 .../console}/Test.cs | 0 .../winform}/ChangeLog.Designer.cs | 0 .../winform}/ChangeLog.cs | 0 .../winform}/ChangeLog.resx | 0 .../winform}/CmlLibWinFormSample.csproj | 0 .../winform}/GameLog.Designer.cs | 0 .../winform}/GameLog.cs | 0 .../winform}/GameLog.resx | 0 .../winform}/GameOptions.Designer.cs | 0 .../winform}/GameOptions.cs | 0 .../winform}/GameOptions.resx | 0 .../winform}/JavaForm.Designer.cs | 0 .../winform}/JavaForm.cs | 0 .../winform}/JavaForm.resx | 0 .../winform}/LoginForm.Designer.cs | 0 .../winform}/LoginForm.cs | 0 .../winform}/LoginForm.resx | 0 .../winform}/MainForm.Designer.cs | 0 .../winform}/MainForm.cs | 0 .../winform}/MainForm.resx | 0 .../winform}/PathForm.Designer.cs | 0 .../winform}/PathForm.cs | 0 .../winform}/PathForm.resx | 0 .../winform}/Program.cs | 0 .../winform}/Util.cs | 0 .../VersionSortOptionForm.Designer.cs | 0 .../winform}/VersionSortOptionForm.cs | 0 .../winform}/VersionSortOptionForm.resx | 0 release.sh | 6 +- {CmlLib => src}/CmlLib.csproj | 104 +++++++++--------- {CmlLib => src}/Core/Auth/MLogin.cs | 0 {CmlLib => src}/Core/Auth/MLoginResponse.cs | 0 {CmlLib => src}/Core/Auth/MSession.cs | 0 {CmlLib => src}/Core/CMLauncher.cs | 0 .../Downloader/AsyncParallelDownloader.cs | 0 .../Core/Downloader/DownloadFile.cs | 0 .../DownloadFileChangedEventArgs.cs | 0 .../FileProgressChangedEventArgs.cs | 0 .../Downloader/HttpClientDownloadHelper.cs | 0 .../Core/Downloader/IDownloader.cs | 0 .../Core/Downloader/MDownloadFileException.cs | 0 .../Core/Downloader/SequenceDownloader.cs | 0 .../Core/FileChecker/AssetChecker.cs | 0 .../Core/FileChecker/ClientChecker.cs | 0 .../Core/FileChecker/FileCheckerCollection.cs | 0 .../Core/FileChecker/IFileChecker.cs | 0 .../Core/FileChecker/JavaChecker.cs | 0 .../Core/FileChecker/LibraryChecker.cs | 0 .../Core/FileChecker/LogChecker.cs | 0 .../Core/FileChecker/ModChecker.cs | 0 {CmlLib => src}/Core/Files/MFileMetadata.cs | 0 {CmlLib => src}/Core/Files/MLibrary.cs | 0 {CmlLib => src}/Core/Files/MLibraryParser.cs | 0 .../Core/Files/MLogConfiguration.cs | 0 {CmlLib => src}/Core/Files/ModFile.cs | 0 {CmlLib => src}/Core/Files/ModFileFactory.cs | 0 .../Core/Installer/FabricMC/FabricLoader.cs | 0 .../Installer/FabricMC/FabricVersionLoader.cs | 0 .../LiteLoader/LiteLoaderInstaller.cs | 0 .../LiteLoader/LiteLoaderVersionLoader.cs | 0 .../LiteLoader/LiteLoaderVersionMetadata.cs | 0 {CmlLib => src}/Core/Installer/MJava.cs | 0 .../Core/Installer/QuiltMC/QuiltLoader.cs | 0 .../Installer/QuiltMC/QuiltVersionLoader.cs | 0 .../Core/Java/IJavaPathResolver.cs | 0 .../Core/Java/MinecraftJavaPathResolver.cs | 0 {CmlLib => src}/Core/Launcher/MLaunch.cs | 0 .../Core/Launcher/MLaunchOption.cs | 0 {CmlLib => src}/Core/Launcher/MNative.cs | 0 {CmlLib => src}/Core/MRule.cs | 0 {CmlLib => src}/Core/Mapper.cs | 0 {CmlLib => src}/Core/MinecraftPath.cs | 0 {CmlLib => src}/Core/MojangServer.cs | 0 {CmlLib => src}/Core/PackageName.cs | 0 {CmlLib => src}/Core/Version/MVersion.cs | 0 .../Core/Version/MVersionCollection.cs | 0 .../Core/Version/MVersionParseException.cs | 0 .../Core/Version/MVersionParser.cs | 0 .../Core/Version/MVersionSortOption.cs | 0 .../Core/Version/MVersionSorter.cs | 0 {CmlLib => src}/Core/Version/MVersionType.cs | 0 .../VersionLoader/DefaultVersionLoader.cs | 0 .../Core/VersionLoader/IVersionLoader.cs | 0 .../Core/VersionLoader/LocalVersionLoader.cs | 0 .../Core/VersionLoader/MojangVersionLoader.cs | 0 .../VersionMetadata/LocalVersionMetadata.cs | 0 .../Core/VersionMetadata/MVersionMetadata.cs | 0 .../VersionMetadata/StringVersionMetadata.cs | 0 .../VersionMetadata/WebVersionMetadata.cs | 0 {CmlLib => src}/FodyWeavers.xml | 0 {CmlLib => src}/FodyWeavers.xsd | 0 {CmlLib => src}/Utils/Changelogs.cs | 0 {CmlLib => src}/Utils/GameOptionsFile.cs | 0 {CmlLib => src}/Utils/HttpUtil.cs | 0 {CmlLib => src}/Utils/IOUtil.cs | 0 {CmlLib => src}/Utils/JarFile.cs | 0 {CmlLib => src}/Utils/JsonUtil.cs | 0 {CmlLib => src}/Utils/NativeMethods.cs | 0 {CmlLib => src}/Utils/ProcessUtil.cs | 0 {CmlLib => src}/Utils/SemiVersion.cs | 0 {CmlLib => src}/Utils/SevenZipWrapper.cs | 0 {CmlLib => src}/Utils/SharpZip.cs | 0 {CmlLib => src}/Utils/WebDownload.cs | 0 .../CmlLib.Core.Test.csproj | 0 {CmlLib.Core.Test => test}/IOUtilTest.cs | 0 {CmlLib.Core.Test => test}/MapperTest.cs | 0 {CmlLib.Core.Test => test}/PackageNameTest.cs | 0 113 files changed, 101 insertions(+), 101 deletions(-) create mode 100644 CmlLib.Core.sln delete mode 100644 CmlLib.sln delete mode 100644 CmlLib.sln.DotSettings rename {CmlLibCoreSample => examples/console}/CmlLibCoreSample.csproj (100%) rename {CmlLibCoreSample => examples/console}/InstallerTest.cs (100%) rename {CmlLibCoreSample => examples/console}/Program.cs (100%) rename {CmlLibCoreSample => examples/console}/Test.cs (100%) rename {CmlLibWinFormSample => examples/winform}/ChangeLog.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/ChangeLog.cs (100%) rename {CmlLibWinFormSample => examples/winform}/ChangeLog.resx (100%) rename {CmlLibWinFormSample => examples/winform}/CmlLibWinFormSample.csproj (100%) rename {CmlLibWinFormSample => examples/winform}/GameLog.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/GameLog.cs (100%) rename {CmlLibWinFormSample => examples/winform}/GameLog.resx (100%) rename {CmlLibWinFormSample => examples/winform}/GameOptions.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/GameOptions.cs (100%) rename {CmlLibWinFormSample => examples/winform}/GameOptions.resx (100%) rename {CmlLibWinFormSample => examples/winform}/JavaForm.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/JavaForm.cs (100%) rename {CmlLibWinFormSample => examples/winform}/JavaForm.resx (100%) rename {CmlLibWinFormSample => examples/winform}/LoginForm.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/LoginForm.cs (100%) rename {CmlLibWinFormSample => examples/winform}/LoginForm.resx (100%) rename {CmlLibWinFormSample => examples/winform}/MainForm.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/MainForm.cs (100%) rename {CmlLibWinFormSample => examples/winform}/MainForm.resx (100%) rename {CmlLibWinFormSample => examples/winform}/PathForm.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/PathForm.cs (100%) rename {CmlLibWinFormSample => examples/winform}/PathForm.resx (100%) rename {CmlLibWinFormSample => examples/winform}/Program.cs (100%) rename {CmlLibWinFormSample => examples/winform}/Util.cs (100%) rename {CmlLibWinFormSample => examples/winform}/VersionSortOptionForm.Designer.cs (100%) rename {CmlLibWinFormSample => examples/winform}/VersionSortOptionForm.cs (100%) rename {CmlLibWinFormSample => examples/winform}/VersionSortOptionForm.resx (100%) rename {CmlLib => src}/CmlLib.csproj (97%) rename {CmlLib => src}/Core/Auth/MLogin.cs (100%) rename {CmlLib => src}/Core/Auth/MLoginResponse.cs (100%) rename {CmlLib => src}/Core/Auth/MSession.cs (100%) rename {CmlLib => src}/Core/CMLauncher.cs (100%) rename {CmlLib => src}/Core/Downloader/AsyncParallelDownloader.cs (100%) rename {CmlLib => src}/Core/Downloader/DownloadFile.cs (100%) rename {CmlLib => src}/Core/Downloader/DownloadFileChangedEventArgs.cs (100%) rename {CmlLib => src}/Core/Downloader/FileProgressChangedEventArgs.cs (100%) rename {CmlLib => src}/Core/Downloader/HttpClientDownloadHelper.cs (100%) rename {CmlLib => src}/Core/Downloader/IDownloader.cs (100%) rename {CmlLib => src}/Core/Downloader/MDownloadFileException.cs (100%) rename {CmlLib => src}/Core/Downloader/SequenceDownloader.cs (100%) rename {CmlLib => src}/Core/FileChecker/AssetChecker.cs (100%) rename {CmlLib => src}/Core/FileChecker/ClientChecker.cs (100%) rename {CmlLib => src}/Core/FileChecker/FileCheckerCollection.cs (100%) rename {CmlLib => src}/Core/FileChecker/IFileChecker.cs (100%) rename {CmlLib => src}/Core/FileChecker/JavaChecker.cs (100%) rename {CmlLib => src}/Core/FileChecker/LibraryChecker.cs (100%) rename {CmlLib => src}/Core/FileChecker/LogChecker.cs (100%) rename {CmlLib => src}/Core/FileChecker/ModChecker.cs (100%) rename {CmlLib => src}/Core/Files/MFileMetadata.cs (100%) rename {CmlLib => src}/Core/Files/MLibrary.cs (100%) rename {CmlLib => src}/Core/Files/MLibraryParser.cs (100%) rename {CmlLib => src}/Core/Files/MLogConfiguration.cs (100%) rename {CmlLib => src}/Core/Files/ModFile.cs (100%) rename {CmlLib => src}/Core/Files/ModFileFactory.cs (100%) rename {CmlLib => src}/Core/Installer/FabricMC/FabricLoader.cs (100%) rename {CmlLib => src}/Core/Installer/FabricMC/FabricVersionLoader.cs (100%) rename {CmlLib => src}/Core/Installer/LiteLoader/LiteLoaderInstaller.cs (100%) rename {CmlLib => src}/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs (100%) rename {CmlLib => src}/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs (100%) rename {CmlLib => src}/Core/Installer/MJava.cs (100%) rename {CmlLib => src}/Core/Installer/QuiltMC/QuiltLoader.cs (100%) rename {CmlLib => src}/Core/Installer/QuiltMC/QuiltVersionLoader.cs (100%) rename {CmlLib => src}/Core/Java/IJavaPathResolver.cs (100%) rename {CmlLib => src}/Core/Java/MinecraftJavaPathResolver.cs (100%) rename {CmlLib => src}/Core/Launcher/MLaunch.cs (100%) rename {CmlLib => src}/Core/Launcher/MLaunchOption.cs (100%) rename {CmlLib => src}/Core/Launcher/MNative.cs (100%) rename {CmlLib => src}/Core/MRule.cs (100%) rename {CmlLib => src}/Core/Mapper.cs (100%) rename {CmlLib => src}/Core/MinecraftPath.cs (100%) rename {CmlLib => src}/Core/MojangServer.cs (100%) rename {CmlLib => src}/Core/PackageName.cs (100%) rename {CmlLib => src}/Core/Version/MVersion.cs (100%) rename {CmlLib => src}/Core/Version/MVersionCollection.cs (100%) rename {CmlLib => src}/Core/Version/MVersionParseException.cs (100%) rename {CmlLib => src}/Core/Version/MVersionParser.cs (100%) rename {CmlLib => src}/Core/Version/MVersionSortOption.cs (100%) rename {CmlLib => src}/Core/Version/MVersionSorter.cs (100%) rename {CmlLib => src}/Core/Version/MVersionType.cs (100%) rename {CmlLib => src}/Core/VersionLoader/DefaultVersionLoader.cs (100%) rename {CmlLib => src}/Core/VersionLoader/IVersionLoader.cs (100%) rename {CmlLib => src}/Core/VersionLoader/LocalVersionLoader.cs (100%) rename {CmlLib => src}/Core/VersionLoader/MojangVersionLoader.cs (100%) rename {CmlLib => src}/Core/VersionMetadata/LocalVersionMetadata.cs (100%) rename {CmlLib => src}/Core/VersionMetadata/MVersionMetadata.cs (100%) rename {CmlLib => src}/Core/VersionMetadata/StringVersionMetadata.cs (100%) rename {CmlLib => src}/Core/VersionMetadata/WebVersionMetadata.cs (100%) rename {CmlLib => src}/FodyWeavers.xml (100%) rename {CmlLib => src}/FodyWeavers.xsd (100%) rename {CmlLib => src}/Utils/Changelogs.cs (100%) rename {CmlLib => src}/Utils/GameOptionsFile.cs (100%) rename {CmlLib => src}/Utils/HttpUtil.cs (100%) rename {CmlLib => src}/Utils/IOUtil.cs (100%) rename {CmlLib => src}/Utils/JarFile.cs (100%) rename {CmlLib => src}/Utils/JsonUtil.cs (100%) rename {CmlLib => src}/Utils/NativeMethods.cs (100%) rename {CmlLib => src}/Utils/ProcessUtil.cs (100%) rename {CmlLib => src}/Utils/SemiVersion.cs (100%) rename {CmlLib => src}/Utils/SevenZipWrapper.cs (100%) rename {CmlLib => src}/Utils/SharpZip.cs (100%) rename {CmlLib => src}/Utils/WebDownload.cs (100%) rename {CmlLib.Core.Test => test}/CmlLib.Core.Test.csproj (100%) rename {CmlLib.Core.Test => test}/IOUtilTest.cs (100%) rename {CmlLib.Core.Test => test}/MapperTest.cs (100%) rename {CmlLib.Core.Test => test}/PackageNameTest.cs (100%) diff --git a/CmlLib.Core.sln b/CmlLib.Core.sln new file mode 100644 index 0000000..a3ce091 --- /dev/null +++ b/CmlLib.Core.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib", "src\CmlLib.csproj", "{DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Test", "test\CmlLib.Core.Test.csproj", "{86827E6C-1353-4DF1-968F-31801A5EB032}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibCoreSample", "examples\console\CmlLibCoreSample.csproj", "{C9F876F3-5579-4B3A-A808-17845BB9C744}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibWinFormSample", "examples\winform\CmlLibWinFormSample.csproj", "{362D93FE-B624-451E-AAD9-D66EFBE90EC3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Release|Any CPU.Build.0 = Release|Any CPU + {86827E6C-1353-4DF1-968F-31801A5EB032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86827E6C-1353-4DF1-968F-31801A5EB032}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86827E6C-1353-4DF1-968F-31801A5EB032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86827E6C-1353-4DF1-968F-31801A5EB032}.Release|Any CPU.Build.0 = Release|Any CPU + {C9F876F3-5579-4B3A-A808-17845BB9C744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9F876F3-5579-4B3A-A808-17845BB9C744}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9F876F3-5579-4B3A-A808-17845BB9C744}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9F876F3-5579-4B3A-A808-17845BB9C744}.Release|Any CPU.Build.0 = Release|Any CPU + {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C9F876F3-5579-4B3A-A808-17845BB9C744} = {6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE} + {362D93FE-B624-451E-AAD9-D66EFBE90EC3} = {6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE} + EndGlobalSection +EndGlobal diff --git a/CmlLib.sln b/CmlLib.sln deleted file mode 100644 index c4b91ba..0000000 --- a/CmlLib.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLibCoreSample", "CmlLibCoreSample\CmlLibCoreSample.csproj", "{F4BFC9AD-62F7-40D4-B857-3669BC30DC24}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibWinFormSample", "CmlLibWinFormSample\CmlLibWinFormSample.csproj", "{CC996F52-7DF9-4A67-B3BF-6D7A346AEC67}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLib", "CmlLib\CmlLib.csproj", "{AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Test", "CmlLib.Core.Test\CmlLib.Core.Test.csproj", "{39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F4BFC9AD-62F7-40D4-B857-3669BC30DC24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4BFC9AD-62F7-40D4-B857-3669BC30DC24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4BFC9AD-62F7-40D4-B857-3669BC30DC24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4BFC9AD-62F7-40D4-B857-3669BC30DC24}.Release|Any CPU.Build.0 = Release|Any CPU - {CC996F52-7DF9-4A67-B3BF-6D7A346AEC67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC996F52-7DF9-4A67-B3BF-6D7A346AEC67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC996F52-7DF9-4A67-B3BF-6D7A346AEC67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC996F52-7DF9-4A67-B3BF-6D7A346AEC67}.Release|Any CPU.Build.0 = Release|Any CPU - {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF1C09F9-C4BD-420A-85B9-9EB454F55FCD}.Release|Any CPU.Build.0 = Release|Any CPU - {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39BB9440-1CBC-42DD-BBCF-E7C0FDE5719A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9A943E77-7D4A-4BBC-9D1E-83185AA2E7B5} - EndGlobalSection -EndGlobal diff --git a/CmlLib.sln.DotSettings b/CmlLib.sln.DotSettings deleted file mode 100644 index c192fbf..0000000 --- a/CmlLib.sln.DotSettings +++ /dev/null @@ -1,3 +0,0 @@ - - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Public" Description="public method"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private method"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> \ No newline at end of file diff --git a/CmlLibCoreSample/CmlLibCoreSample.csproj b/examples/console/CmlLibCoreSample.csproj similarity index 100% rename from CmlLibCoreSample/CmlLibCoreSample.csproj rename to examples/console/CmlLibCoreSample.csproj diff --git a/CmlLibCoreSample/InstallerTest.cs b/examples/console/InstallerTest.cs similarity index 100% rename from CmlLibCoreSample/InstallerTest.cs rename to examples/console/InstallerTest.cs diff --git a/CmlLibCoreSample/Program.cs b/examples/console/Program.cs similarity index 100% rename from CmlLibCoreSample/Program.cs rename to examples/console/Program.cs diff --git a/CmlLibCoreSample/Test.cs b/examples/console/Test.cs similarity index 100% rename from CmlLibCoreSample/Test.cs rename to examples/console/Test.cs diff --git a/CmlLibWinFormSample/ChangeLog.Designer.cs b/examples/winform/ChangeLog.Designer.cs similarity index 100% rename from CmlLibWinFormSample/ChangeLog.Designer.cs rename to examples/winform/ChangeLog.Designer.cs diff --git a/CmlLibWinFormSample/ChangeLog.cs b/examples/winform/ChangeLog.cs similarity index 100% rename from CmlLibWinFormSample/ChangeLog.cs rename to examples/winform/ChangeLog.cs diff --git a/CmlLibWinFormSample/ChangeLog.resx b/examples/winform/ChangeLog.resx similarity index 100% rename from CmlLibWinFormSample/ChangeLog.resx rename to examples/winform/ChangeLog.resx diff --git a/CmlLibWinFormSample/CmlLibWinFormSample.csproj b/examples/winform/CmlLibWinFormSample.csproj similarity index 100% rename from CmlLibWinFormSample/CmlLibWinFormSample.csproj rename to examples/winform/CmlLibWinFormSample.csproj diff --git a/CmlLibWinFormSample/GameLog.Designer.cs b/examples/winform/GameLog.Designer.cs similarity index 100% rename from CmlLibWinFormSample/GameLog.Designer.cs rename to examples/winform/GameLog.Designer.cs diff --git a/CmlLibWinFormSample/GameLog.cs b/examples/winform/GameLog.cs similarity index 100% rename from CmlLibWinFormSample/GameLog.cs rename to examples/winform/GameLog.cs diff --git a/CmlLibWinFormSample/GameLog.resx b/examples/winform/GameLog.resx similarity index 100% rename from CmlLibWinFormSample/GameLog.resx rename to examples/winform/GameLog.resx diff --git a/CmlLibWinFormSample/GameOptions.Designer.cs b/examples/winform/GameOptions.Designer.cs similarity index 100% rename from CmlLibWinFormSample/GameOptions.Designer.cs rename to examples/winform/GameOptions.Designer.cs diff --git a/CmlLibWinFormSample/GameOptions.cs b/examples/winform/GameOptions.cs similarity index 100% rename from CmlLibWinFormSample/GameOptions.cs rename to examples/winform/GameOptions.cs diff --git a/CmlLibWinFormSample/GameOptions.resx b/examples/winform/GameOptions.resx similarity index 100% rename from CmlLibWinFormSample/GameOptions.resx rename to examples/winform/GameOptions.resx diff --git a/CmlLibWinFormSample/JavaForm.Designer.cs b/examples/winform/JavaForm.Designer.cs similarity index 100% rename from CmlLibWinFormSample/JavaForm.Designer.cs rename to examples/winform/JavaForm.Designer.cs diff --git a/CmlLibWinFormSample/JavaForm.cs b/examples/winform/JavaForm.cs similarity index 100% rename from CmlLibWinFormSample/JavaForm.cs rename to examples/winform/JavaForm.cs diff --git a/CmlLibWinFormSample/JavaForm.resx b/examples/winform/JavaForm.resx similarity index 100% rename from CmlLibWinFormSample/JavaForm.resx rename to examples/winform/JavaForm.resx diff --git a/CmlLibWinFormSample/LoginForm.Designer.cs b/examples/winform/LoginForm.Designer.cs similarity index 100% rename from CmlLibWinFormSample/LoginForm.Designer.cs rename to examples/winform/LoginForm.Designer.cs diff --git a/CmlLibWinFormSample/LoginForm.cs b/examples/winform/LoginForm.cs similarity index 100% rename from CmlLibWinFormSample/LoginForm.cs rename to examples/winform/LoginForm.cs diff --git a/CmlLibWinFormSample/LoginForm.resx b/examples/winform/LoginForm.resx similarity index 100% rename from CmlLibWinFormSample/LoginForm.resx rename to examples/winform/LoginForm.resx diff --git a/CmlLibWinFormSample/MainForm.Designer.cs b/examples/winform/MainForm.Designer.cs similarity index 100% rename from CmlLibWinFormSample/MainForm.Designer.cs rename to examples/winform/MainForm.Designer.cs diff --git a/CmlLibWinFormSample/MainForm.cs b/examples/winform/MainForm.cs similarity index 100% rename from CmlLibWinFormSample/MainForm.cs rename to examples/winform/MainForm.cs diff --git a/CmlLibWinFormSample/MainForm.resx b/examples/winform/MainForm.resx similarity index 100% rename from CmlLibWinFormSample/MainForm.resx rename to examples/winform/MainForm.resx diff --git a/CmlLibWinFormSample/PathForm.Designer.cs b/examples/winform/PathForm.Designer.cs similarity index 100% rename from CmlLibWinFormSample/PathForm.Designer.cs rename to examples/winform/PathForm.Designer.cs diff --git a/CmlLibWinFormSample/PathForm.cs b/examples/winform/PathForm.cs similarity index 100% rename from CmlLibWinFormSample/PathForm.cs rename to examples/winform/PathForm.cs diff --git a/CmlLibWinFormSample/PathForm.resx b/examples/winform/PathForm.resx similarity index 100% rename from CmlLibWinFormSample/PathForm.resx rename to examples/winform/PathForm.resx diff --git a/CmlLibWinFormSample/Program.cs b/examples/winform/Program.cs similarity index 100% rename from CmlLibWinFormSample/Program.cs rename to examples/winform/Program.cs diff --git a/CmlLibWinFormSample/Util.cs b/examples/winform/Util.cs similarity index 100% rename from CmlLibWinFormSample/Util.cs rename to examples/winform/Util.cs diff --git a/CmlLibWinFormSample/VersionSortOptionForm.Designer.cs b/examples/winform/VersionSortOptionForm.Designer.cs similarity index 100% rename from CmlLibWinFormSample/VersionSortOptionForm.Designer.cs rename to examples/winform/VersionSortOptionForm.Designer.cs diff --git a/CmlLibWinFormSample/VersionSortOptionForm.cs b/examples/winform/VersionSortOptionForm.cs similarity index 100% rename from CmlLibWinFormSample/VersionSortOptionForm.cs rename to examples/winform/VersionSortOptionForm.cs diff --git a/CmlLibWinFormSample/VersionSortOptionForm.resx b/examples/winform/VersionSortOptionForm.resx similarity index 100% rename from CmlLibWinFormSample/VersionSortOptionForm.resx rename to examples/winform/VersionSortOptionForm.resx diff --git a/release.sh b/release.sh index 4e4245c..0215fd8 100755 --- a/release.sh +++ b/release.sh @@ -12,9 +12,9 @@ fi baseDir=$(dirname "$0") -csprojCmlLib="${baseDir}/CmlLib/CmlLib.csproj" -csprojCmlLibCoreSample="${baseDir}/CmlLibCoreSample/CmlLibCoreSample.csproj" -csprojCmlLibWinFormSample="${baseDir}/CmlLibWinFormSample/CmlLibWinFormSample.csproj" +csprojCmlLib="${baseDir}/src/CmlLib.csproj" +csprojCmlLibCoreSample="${baseDir}/examples/console/CmlLibCoreSample.csproj" +csprojCmlLibWinFormSample="${baseDir}/examples/winform/CmlLibWinFormSample.csproj" [ ! -f $csprojCmlLib ] && { echo "Cannot find CmlLib.csproj file"; exit; } [ ! -f $csprojCmlLibCoreSample ] && { echo "Cannot find CmlLibCoreSample.csproj file"; exit; } diff --git a/CmlLib/CmlLib.csproj b/src/CmlLib.csproj similarity index 97% rename from CmlLib/CmlLib.csproj rename to src/CmlLib.csproj index 50e67b9..7e8ba2b 100644 --- a/CmlLib/CmlLib.csproj +++ b/src/CmlLib.csproj @@ -1,52 +1,52 @@ - - - - netstandard2.0 - 10.0 - enable - enable - 3.4.0 - Minecraft Launcher Library for .NET -Support all version, forge, optifine - - Copyright (c) 2023 AlphaBs - https://github.com/CmlLib/CmlLib.Core - https://github.com/CmlLib/CmlLib.Core - icon.png - git - true - Minecraft Launcher forge optifine mojang Crossplatform C# - MIT - AlphaBs - - CmlLib.Core - README.md - - - - - - - - - - - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - <_Parameter1>CmlLib.Core.Test - - - + + + + netstandard2.0 + 10.0 + enable + enable + 3.4.0 + Minecraft Launcher Library for .NET +Support all version, forge, optifine + + Copyright (c) 2023 AlphaBs + https://github.com/CmlLib/CmlLib.Core + https://github.com/CmlLib/CmlLib.Core + icon.png + git + true + Minecraft Launcher forge optifine mojang Crossplatform C# + MIT + AlphaBs + + CmlLib.Core + README.md + + + + + + + + + + + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + <_Parameter1>CmlLib.Core.Test + + + diff --git a/CmlLib/Core/Auth/MLogin.cs b/src/Core/Auth/MLogin.cs similarity index 100% rename from CmlLib/Core/Auth/MLogin.cs rename to src/Core/Auth/MLogin.cs diff --git a/CmlLib/Core/Auth/MLoginResponse.cs b/src/Core/Auth/MLoginResponse.cs similarity index 100% rename from CmlLib/Core/Auth/MLoginResponse.cs rename to src/Core/Auth/MLoginResponse.cs diff --git a/CmlLib/Core/Auth/MSession.cs b/src/Core/Auth/MSession.cs similarity index 100% rename from CmlLib/Core/Auth/MSession.cs rename to src/Core/Auth/MSession.cs diff --git a/CmlLib/Core/CMLauncher.cs b/src/Core/CMLauncher.cs similarity index 100% rename from CmlLib/Core/CMLauncher.cs rename to src/Core/CMLauncher.cs diff --git a/CmlLib/Core/Downloader/AsyncParallelDownloader.cs b/src/Core/Downloader/AsyncParallelDownloader.cs similarity index 100% rename from CmlLib/Core/Downloader/AsyncParallelDownloader.cs rename to src/Core/Downloader/AsyncParallelDownloader.cs diff --git a/CmlLib/Core/Downloader/DownloadFile.cs b/src/Core/Downloader/DownloadFile.cs similarity index 100% rename from CmlLib/Core/Downloader/DownloadFile.cs rename to src/Core/Downloader/DownloadFile.cs diff --git a/CmlLib/Core/Downloader/DownloadFileChangedEventArgs.cs b/src/Core/Downloader/DownloadFileChangedEventArgs.cs similarity index 100% rename from CmlLib/Core/Downloader/DownloadFileChangedEventArgs.cs rename to src/Core/Downloader/DownloadFileChangedEventArgs.cs diff --git a/CmlLib/Core/Downloader/FileProgressChangedEventArgs.cs b/src/Core/Downloader/FileProgressChangedEventArgs.cs similarity index 100% rename from CmlLib/Core/Downloader/FileProgressChangedEventArgs.cs rename to src/Core/Downloader/FileProgressChangedEventArgs.cs diff --git a/CmlLib/Core/Downloader/HttpClientDownloadHelper.cs b/src/Core/Downloader/HttpClientDownloadHelper.cs similarity index 100% rename from CmlLib/Core/Downloader/HttpClientDownloadHelper.cs rename to src/Core/Downloader/HttpClientDownloadHelper.cs diff --git a/CmlLib/Core/Downloader/IDownloader.cs b/src/Core/Downloader/IDownloader.cs similarity index 100% rename from CmlLib/Core/Downloader/IDownloader.cs rename to src/Core/Downloader/IDownloader.cs diff --git a/CmlLib/Core/Downloader/MDownloadFileException.cs b/src/Core/Downloader/MDownloadFileException.cs similarity index 100% rename from CmlLib/Core/Downloader/MDownloadFileException.cs rename to src/Core/Downloader/MDownloadFileException.cs diff --git a/CmlLib/Core/Downloader/SequenceDownloader.cs b/src/Core/Downloader/SequenceDownloader.cs similarity index 100% rename from CmlLib/Core/Downloader/SequenceDownloader.cs rename to src/Core/Downloader/SequenceDownloader.cs diff --git a/CmlLib/Core/FileChecker/AssetChecker.cs b/src/Core/FileChecker/AssetChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/AssetChecker.cs rename to src/Core/FileChecker/AssetChecker.cs diff --git a/CmlLib/Core/FileChecker/ClientChecker.cs b/src/Core/FileChecker/ClientChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/ClientChecker.cs rename to src/Core/FileChecker/ClientChecker.cs diff --git a/CmlLib/Core/FileChecker/FileCheckerCollection.cs b/src/Core/FileChecker/FileCheckerCollection.cs similarity index 100% rename from CmlLib/Core/FileChecker/FileCheckerCollection.cs rename to src/Core/FileChecker/FileCheckerCollection.cs diff --git a/CmlLib/Core/FileChecker/IFileChecker.cs b/src/Core/FileChecker/IFileChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/IFileChecker.cs rename to src/Core/FileChecker/IFileChecker.cs diff --git a/CmlLib/Core/FileChecker/JavaChecker.cs b/src/Core/FileChecker/JavaChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/JavaChecker.cs rename to src/Core/FileChecker/JavaChecker.cs diff --git a/CmlLib/Core/FileChecker/LibraryChecker.cs b/src/Core/FileChecker/LibraryChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/LibraryChecker.cs rename to src/Core/FileChecker/LibraryChecker.cs diff --git a/CmlLib/Core/FileChecker/LogChecker.cs b/src/Core/FileChecker/LogChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/LogChecker.cs rename to src/Core/FileChecker/LogChecker.cs diff --git a/CmlLib/Core/FileChecker/ModChecker.cs b/src/Core/FileChecker/ModChecker.cs similarity index 100% rename from CmlLib/Core/FileChecker/ModChecker.cs rename to src/Core/FileChecker/ModChecker.cs diff --git a/CmlLib/Core/Files/MFileMetadata.cs b/src/Core/Files/MFileMetadata.cs similarity index 100% rename from CmlLib/Core/Files/MFileMetadata.cs rename to src/Core/Files/MFileMetadata.cs diff --git a/CmlLib/Core/Files/MLibrary.cs b/src/Core/Files/MLibrary.cs similarity index 100% rename from CmlLib/Core/Files/MLibrary.cs rename to src/Core/Files/MLibrary.cs diff --git a/CmlLib/Core/Files/MLibraryParser.cs b/src/Core/Files/MLibraryParser.cs similarity index 100% rename from CmlLib/Core/Files/MLibraryParser.cs rename to src/Core/Files/MLibraryParser.cs diff --git a/CmlLib/Core/Files/MLogConfiguration.cs b/src/Core/Files/MLogConfiguration.cs similarity index 100% rename from CmlLib/Core/Files/MLogConfiguration.cs rename to src/Core/Files/MLogConfiguration.cs diff --git a/CmlLib/Core/Files/ModFile.cs b/src/Core/Files/ModFile.cs similarity index 100% rename from CmlLib/Core/Files/ModFile.cs rename to src/Core/Files/ModFile.cs diff --git a/CmlLib/Core/Files/ModFileFactory.cs b/src/Core/Files/ModFileFactory.cs similarity index 100% rename from CmlLib/Core/Files/ModFileFactory.cs rename to src/Core/Files/ModFileFactory.cs diff --git a/CmlLib/Core/Installer/FabricMC/FabricLoader.cs b/src/Core/Installer/FabricMC/FabricLoader.cs similarity index 100% rename from CmlLib/Core/Installer/FabricMC/FabricLoader.cs rename to src/Core/Installer/FabricMC/FabricLoader.cs diff --git a/CmlLib/Core/Installer/FabricMC/FabricVersionLoader.cs b/src/Core/Installer/FabricMC/FabricVersionLoader.cs similarity index 100% rename from CmlLib/Core/Installer/FabricMC/FabricVersionLoader.cs rename to src/Core/Installer/FabricMC/FabricVersionLoader.cs diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs b/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs similarity index 100% rename from CmlLib/Core/Installer/LiteLoader/LiteLoaderInstaller.cs rename to src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs b/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs similarity index 100% rename from CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs rename to src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs diff --git a/CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs similarity index 100% rename from CmlLib/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs rename to src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs diff --git a/CmlLib/Core/Installer/MJava.cs b/src/Core/Installer/MJava.cs similarity index 100% rename from CmlLib/Core/Installer/MJava.cs rename to src/Core/Installer/MJava.cs diff --git a/CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs b/src/Core/Installer/QuiltMC/QuiltLoader.cs similarity index 100% rename from CmlLib/Core/Installer/QuiltMC/QuiltLoader.cs rename to src/Core/Installer/QuiltMC/QuiltLoader.cs diff --git a/CmlLib/Core/Installer/QuiltMC/QuiltVersionLoader.cs b/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs similarity index 100% rename from CmlLib/Core/Installer/QuiltMC/QuiltVersionLoader.cs rename to src/Core/Installer/QuiltMC/QuiltVersionLoader.cs diff --git a/CmlLib/Core/Java/IJavaPathResolver.cs b/src/Core/Java/IJavaPathResolver.cs similarity index 100% rename from CmlLib/Core/Java/IJavaPathResolver.cs rename to src/Core/Java/IJavaPathResolver.cs diff --git a/CmlLib/Core/Java/MinecraftJavaPathResolver.cs b/src/Core/Java/MinecraftJavaPathResolver.cs similarity index 100% rename from CmlLib/Core/Java/MinecraftJavaPathResolver.cs rename to src/Core/Java/MinecraftJavaPathResolver.cs diff --git a/CmlLib/Core/Launcher/MLaunch.cs b/src/Core/Launcher/MLaunch.cs similarity index 100% rename from CmlLib/Core/Launcher/MLaunch.cs rename to src/Core/Launcher/MLaunch.cs diff --git a/CmlLib/Core/Launcher/MLaunchOption.cs b/src/Core/Launcher/MLaunchOption.cs similarity index 100% rename from CmlLib/Core/Launcher/MLaunchOption.cs rename to src/Core/Launcher/MLaunchOption.cs diff --git a/CmlLib/Core/Launcher/MNative.cs b/src/Core/Launcher/MNative.cs similarity index 100% rename from CmlLib/Core/Launcher/MNative.cs rename to src/Core/Launcher/MNative.cs diff --git a/CmlLib/Core/MRule.cs b/src/Core/MRule.cs similarity index 100% rename from CmlLib/Core/MRule.cs rename to src/Core/MRule.cs diff --git a/CmlLib/Core/Mapper.cs b/src/Core/Mapper.cs similarity index 100% rename from CmlLib/Core/Mapper.cs rename to src/Core/Mapper.cs diff --git a/CmlLib/Core/MinecraftPath.cs b/src/Core/MinecraftPath.cs similarity index 100% rename from CmlLib/Core/MinecraftPath.cs rename to src/Core/MinecraftPath.cs diff --git a/CmlLib/Core/MojangServer.cs b/src/Core/MojangServer.cs similarity index 100% rename from CmlLib/Core/MojangServer.cs rename to src/Core/MojangServer.cs diff --git a/CmlLib/Core/PackageName.cs b/src/Core/PackageName.cs similarity index 100% rename from CmlLib/Core/PackageName.cs rename to src/Core/PackageName.cs diff --git a/CmlLib/Core/Version/MVersion.cs b/src/Core/Version/MVersion.cs similarity index 100% rename from CmlLib/Core/Version/MVersion.cs rename to src/Core/Version/MVersion.cs diff --git a/CmlLib/Core/Version/MVersionCollection.cs b/src/Core/Version/MVersionCollection.cs similarity index 100% rename from CmlLib/Core/Version/MVersionCollection.cs rename to src/Core/Version/MVersionCollection.cs diff --git a/CmlLib/Core/Version/MVersionParseException.cs b/src/Core/Version/MVersionParseException.cs similarity index 100% rename from CmlLib/Core/Version/MVersionParseException.cs rename to src/Core/Version/MVersionParseException.cs diff --git a/CmlLib/Core/Version/MVersionParser.cs b/src/Core/Version/MVersionParser.cs similarity index 100% rename from CmlLib/Core/Version/MVersionParser.cs rename to src/Core/Version/MVersionParser.cs diff --git a/CmlLib/Core/Version/MVersionSortOption.cs b/src/Core/Version/MVersionSortOption.cs similarity index 100% rename from CmlLib/Core/Version/MVersionSortOption.cs rename to src/Core/Version/MVersionSortOption.cs diff --git a/CmlLib/Core/Version/MVersionSorter.cs b/src/Core/Version/MVersionSorter.cs similarity index 100% rename from CmlLib/Core/Version/MVersionSorter.cs rename to src/Core/Version/MVersionSorter.cs diff --git a/CmlLib/Core/Version/MVersionType.cs b/src/Core/Version/MVersionType.cs similarity index 100% rename from CmlLib/Core/Version/MVersionType.cs rename to src/Core/Version/MVersionType.cs diff --git a/CmlLib/Core/VersionLoader/DefaultVersionLoader.cs b/src/Core/VersionLoader/DefaultVersionLoader.cs similarity index 100% rename from CmlLib/Core/VersionLoader/DefaultVersionLoader.cs rename to src/Core/VersionLoader/DefaultVersionLoader.cs diff --git a/CmlLib/Core/VersionLoader/IVersionLoader.cs b/src/Core/VersionLoader/IVersionLoader.cs similarity index 100% rename from CmlLib/Core/VersionLoader/IVersionLoader.cs rename to src/Core/VersionLoader/IVersionLoader.cs diff --git a/CmlLib/Core/VersionLoader/LocalVersionLoader.cs b/src/Core/VersionLoader/LocalVersionLoader.cs similarity index 100% rename from CmlLib/Core/VersionLoader/LocalVersionLoader.cs rename to src/Core/VersionLoader/LocalVersionLoader.cs diff --git a/CmlLib/Core/VersionLoader/MojangVersionLoader.cs b/src/Core/VersionLoader/MojangVersionLoader.cs similarity index 100% rename from CmlLib/Core/VersionLoader/MojangVersionLoader.cs rename to src/Core/VersionLoader/MojangVersionLoader.cs diff --git a/CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs b/src/Core/VersionMetadata/LocalVersionMetadata.cs similarity index 100% rename from CmlLib/Core/VersionMetadata/LocalVersionMetadata.cs rename to src/Core/VersionMetadata/LocalVersionMetadata.cs diff --git a/CmlLib/Core/VersionMetadata/MVersionMetadata.cs b/src/Core/VersionMetadata/MVersionMetadata.cs similarity index 100% rename from CmlLib/Core/VersionMetadata/MVersionMetadata.cs rename to src/Core/VersionMetadata/MVersionMetadata.cs diff --git a/CmlLib/Core/VersionMetadata/StringVersionMetadata.cs b/src/Core/VersionMetadata/StringVersionMetadata.cs similarity index 100% rename from CmlLib/Core/VersionMetadata/StringVersionMetadata.cs rename to src/Core/VersionMetadata/StringVersionMetadata.cs diff --git a/CmlLib/Core/VersionMetadata/WebVersionMetadata.cs b/src/Core/VersionMetadata/WebVersionMetadata.cs similarity index 100% rename from CmlLib/Core/VersionMetadata/WebVersionMetadata.cs rename to src/Core/VersionMetadata/WebVersionMetadata.cs diff --git a/CmlLib/FodyWeavers.xml b/src/FodyWeavers.xml similarity index 100% rename from CmlLib/FodyWeavers.xml rename to src/FodyWeavers.xml diff --git a/CmlLib/FodyWeavers.xsd b/src/FodyWeavers.xsd similarity index 100% rename from CmlLib/FodyWeavers.xsd rename to src/FodyWeavers.xsd diff --git a/CmlLib/Utils/Changelogs.cs b/src/Utils/Changelogs.cs similarity index 100% rename from CmlLib/Utils/Changelogs.cs rename to src/Utils/Changelogs.cs diff --git a/CmlLib/Utils/GameOptionsFile.cs b/src/Utils/GameOptionsFile.cs similarity index 100% rename from CmlLib/Utils/GameOptionsFile.cs rename to src/Utils/GameOptionsFile.cs diff --git a/CmlLib/Utils/HttpUtil.cs b/src/Utils/HttpUtil.cs similarity index 100% rename from CmlLib/Utils/HttpUtil.cs rename to src/Utils/HttpUtil.cs diff --git a/CmlLib/Utils/IOUtil.cs b/src/Utils/IOUtil.cs similarity index 100% rename from CmlLib/Utils/IOUtil.cs rename to src/Utils/IOUtil.cs diff --git a/CmlLib/Utils/JarFile.cs b/src/Utils/JarFile.cs similarity index 100% rename from CmlLib/Utils/JarFile.cs rename to src/Utils/JarFile.cs diff --git a/CmlLib/Utils/JsonUtil.cs b/src/Utils/JsonUtil.cs similarity index 100% rename from CmlLib/Utils/JsonUtil.cs rename to src/Utils/JsonUtil.cs diff --git a/CmlLib/Utils/NativeMethods.cs b/src/Utils/NativeMethods.cs similarity index 100% rename from CmlLib/Utils/NativeMethods.cs rename to src/Utils/NativeMethods.cs diff --git a/CmlLib/Utils/ProcessUtil.cs b/src/Utils/ProcessUtil.cs similarity index 100% rename from CmlLib/Utils/ProcessUtil.cs rename to src/Utils/ProcessUtil.cs diff --git a/CmlLib/Utils/SemiVersion.cs b/src/Utils/SemiVersion.cs similarity index 100% rename from CmlLib/Utils/SemiVersion.cs rename to src/Utils/SemiVersion.cs diff --git a/CmlLib/Utils/SevenZipWrapper.cs b/src/Utils/SevenZipWrapper.cs similarity index 100% rename from CmlLib/Utils/SevenZipWrapper.cs rename to src/Utils/SevenZipWrapper.cs diff --git a/CmlLib/Utils/SharpZip.cs b/src/Utils/SharpZip.cs similarity index 100% rename from CmlLib/Utils/SharpZip.cs rename to src/Utils/SharpZip.cs diff --git a/CmlLib/Utils/WebDownload.cs b/src/Utils/WebDownload.cs similarity index 100% rename from CmlLib/Utils/WebDownload.cs rename to src/Utils/WebDownload.cs diff --git a/CmlLib.Core.Test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj similarity index 100% rename from CmlLib.Core.Test/CmlLib.Core.Test.csproj rename to test/CmlLib.Core.Test.csproj diff --git a/CmlLib.Core.Test/IOUtilTest.cs b/test/IOUtilTest.cs similarity index 100% rename from CmlLib.Core.Test/IOUtilTest.cs rename to test/IOUtilTest.cs diff --git a/CmlLib.Core.Test/MapperTest.cs b/test/MapperTest.cs similarity index 100% rename from CmlLib.Core.Test/MapperTest.cs rename to test/MapperTest.cs diff --git a/CmlLib.Core.Test/PackageNameTest.cs b/test/PackageNameTest.cs similarity index 100% rename from CmlLib.Core.Test/PackageNameTest.cs rename to test/PackageNameTest.cs From 029038158a1fb38ed774759e3db5e87e09e5805f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 30 Jul 2023 09:12:14 +0000 Subject: [PATCH 054/137] update /docs --- docs/README-2.0.2.md | 135 ------------------- docs/README-chs.md | 6 +- docs/README-kr.md | 139 -------------------- examples/console/CmlLibCoreSample.csproj | 2 +- examples/winform/CmlLibWinFormSample.csproj | 2 +- test/CmlLib.Core.Test.csproj | 2 +- 6 files changed, 4 insertions(+), 282 deletions(-) delete mode 100644 docs/README-2.0.2.md delete mode 100644 docs/README-kr.md diff --git a/docs/README-2.0.2.md b/docs/README-2.0.2.md deleted file mode 100644 index c12b312..0000000 --- a/docs/README-2.0.2.md +++ /dev/null @@ -1,135 +0,0 @@ -# CmlLib.Core - -## Minecraft Launcher Library - -[![CodeFactor](https://www.codefactor.io/repository/github/alphabs/cmllib.core/badge)](https://www.codefactor.io/repository/github/alphabs/cmllib.core) - -This library is minecraft launcher library for .NET Core and .NET Framework -Support all version, with Forge - -[한국어 README](https://github.com/AlphaBs/CmlLib.Core/blob/master/docs/README-kr.md) -한국어 문서는 업데이트가 느립니다. - -**Current document describe [2.0.2](https://github.com/AlphaBs/CmlLib.Core/tree/v2.0.2)** - -**Latest version : [3.0.0 README](https://github.com/AlphaBs/CmlLib.Core/blob/master/README.md)** - -## What is different between CmlLib.Core and [CmlLib](https://github.com/AlphaBs/MinecraftLauncherLibrary)? - -CmlLib.Core is developed using .NET Core and support crossplatform. but CmlLib doesn't support it and will be deprecated. - -## Contacts - -Email : ksi123456ab@naver.com -Discord : ksi123456ab#3719 - -## License - -MIT License -[LICENSE](https://github.com/AlphaBs/CmlLib.Core/blob/master/LICENSE) - -## Crossplatform - -.NET Core version support crossplatform. It is tested in Windows10, Ubuntu 18.04, macOS Catalina - -## Dependency - -Newtonsoft.Json 12.0.3 -SharpZipLib 1.2.0 -LZMA-SDK 19.0.0 - -## Functions - -- [x] Online / Offline Login -- [x] Download game files in mojang file server -- [x] Launch All Versions (tested up to 1.15.2) -- [x] Launch Forge, Optifine or custom versions -- [x] Download minecraft java runtime in mojang file server -- [x] Launch with options (direct server connecting, screen resolution) -- [x] Support cross-platform - -## How To Use - -If you want to learn more features of this library, go to [wiki](https://github.com/AlphaBs/CmlLib.Core/wiki) - -**[Sample Code](https://github.com/AlphaBs/CmlLib.Core/wiki/Sample-Code)** - -### **Install** - -Install Nuget Package 'CmlLib.Core' -or download dll files in [Releases](https://github.com/AlphaBs/CmlLib.Core/releases) and add reference to your project. - -write this on the top of your source code: - - using CmlLib; - using CmlLib.Core; - -### **Sample** - -**[Sample Code](https://github.com/AlphaBs/CmlLib.Core/wiki/Sample-Code)** - -Write this : -`using CmlLib.Core;` -`using CmlLib;` - -**Login** - - var login = new MLogin(); - var session = login.TryAutoLogin(); // 'session' is used in LaunchOption - - if (session.Result != MLoginResult.Success) // failed to auto login - { - var email = Console.ReadLine(); - var pw = Console.ReadLine(); - session = login.Authenticate(email, pw); - - if (session.Result != MLoginResult.Success) - throw new Exception(session.Result.ToString()); // failed to login - } - -**Offline Login** - - var session = MSession.GetOfflineSession("USERNAME"); // 'session' is used in LaunchOption - -**Launch** - - //var path = "your minecraft directory"; - var path = Minecraft.GetOSDefaultPath(); // get default minecraft path - - var game = new Minecraft(path); - - var launcher = new CmlLib.CMLauncher(game); - launcher.ProgressChanged += (s, e) => - { - Console.WriteLine("{0}%", e.ProgressPercentage); - }; - launcher.FileChanged += (e) => - { - Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - }; - - launcher.UpdateProfiles(); - foreach (var item in launcher.Profiles) - { - Console.WriteLine(item.Name); - } - - var launchOption = new MLaunchOption - { - MaximumRamMb = 1024, - Session = session, // Login Session. ex) Session = MSession.GetOfflineSession("hello") - - //LauncherName = "MyLauncher", - //ScreenWidth = 1600, - //ScreenHeigth = 900, - //ServerIp = "mc.hypixel.net" - }; - - // launch vanila - var process = launcher.CreateProcess("1.15.2", launchOption); - - process.Start(); - -### More Information - -Go to [wiki](https://github.com/AlphaBs/CmlLib.Core/wiki/MLaunchOption) diff --git a/docs/README-chs.md b/docs/README-chs.md index c47850a..b0f12e4 100644 --- a/docs/README-chs.md +++ b/docs/README-chs.md @@ -13,10 +13,6 @@ CmlLib.Core 是.NET上的一个Minecraft启动库\ 它支持所有版本,包括Forge。 -[한국어 README](https://github.com/AlphaBs/CmlLib.Core/blob/master/docs/README-kr.md) - -[简体中文 README](https://github.com/AlphaBs/CmlLib.Core/blob/master/docs/README-chs.md) - ## 特点 * 异步 API @@ -30,7 +26,7 @@ CmlLib.Core 是.NET上的一个Minecraft启动库\ * 自定义参数启动 (服务器直连, 屏幕分辨率) * 跨平台 (Windows, Linux, macOS) -[去Wiki查看所有特性](https://github.com/CmlLib/CmlLib.Core/wiki) +[去Wiki查看所有特性](https://alphabs.gitbook.io/cmllib/cmllib.core/cmllib) ## 安装 diff --git a/docs/README-kr.md b/docs/README-kr.md deleted file mode 100644 index 60e5dbd..0000000 --- a/docs/README-kr.md +++ /dev/null @@ -1,139 +0,0 @@ -# CmlLib.Core - -## Minecraft Launcher Library - - - -[![Nuget Badge](https://img.shields.io/nuget/v/CmlLib.Core)](https://www.nuget.org/packages/CmlLib.Core) -[![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/CmlLib/CmlLib.Core/blob/master/LICENSE) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/3f55a130ec3f4bccb55e7def97cfa2ce)](https://www.codacy.com/gh/CmlLib/CmlLib.Core/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=CmlLib/CmlLib.Core\&utm_campaign=Badge_Grade) - -[![Discord](https://img.shields.io/discord/795952027443527690?label=discord\&logo=discord\&style=for-the-badge)](https://discord.gg/cDW2pvwHSc) - -CmlLib.Core 는 마인크래프트 커스텀 런처 제작을 위한 C# 라이브러리입니다.\ -포지를 포함한 모든 버전을 실행 가능합니다.\ - -## [AD - 주문제작] -**커스텀 런처 주문제작을 받고 있습니다!** -라이브러리에는 없는 다양한 추가 기능들이 포함되어 있습니다. -ksi123456ab#3719 디스코드로 연락주세요. - -## 기능 - -* 비동기 APIs -* 모장 인증(로그인) -* 마이크로소프트 엑스박스 계정으로 로그인 -* 모장 파일 서버에서 게임 파일 다운로드 -* 모든 버전 실행(1.17.1 까지 테스트 완료) -* 모든 커스텀 버전(포지, 옵티파인, Fabric, 라이트로더 등등) 실행 가능 -* 자바 런타임 설치 -* Forge, LiteLoader, FabricMC 설치 -* 다양한 실행 옵션 (서버 바로 접속, 화면 크기 조정 등) -* 크로스플랫폼 (windows, ubuntu, macOS) - -[모든 기능 보기](https://github.com/CmlLib/CmlLib.Core/wiki) - -## 설치 - -[CmlLib.Core Nuget package](https://www.nuget.org/packages/CmlLib.Core) 를 설치하거나, - -[Releases](https://github.com/AlphaBs/CmlLib.Core/releases) 에서 dll 파일을 다운받고 프로젝트에 참조 추가하세요. - -소스코드 최상단에 아래 소스코드를 입력하세요: - -```csharp -using CmlLib.Core; -using CmlLib.Core.Auth; -``` - -## 문서 - -커스텀 런처를 위한 많은 기능들이 있습니다. 위키에서 모든 기능 목록을 확인하세요.\ -**공식 문서: [wiki](https://github.com/CmlLib/CmlLib.Core/wiki)** - -## QuickStart - -### 마이크로소프트 엑스박스 계정으로 로그인 - -[Wiki](https://github.com/CmlLib/CmlLib.Core/wiki/Microsoft-Xbox-Live-Login) - -### 모장 계정으로 로그인 - -[Login Process](https://github.com/AlphaBs/CmlLib.Core/wiki/Login-and-Sessions) - -```csharp -var login = new MLogin(); -var response = login.TryAutoLogin(); - -if (!response.IsSuccess) // 자동 로그인 실패 -{ - var email = Console.ReadLine(); - var pw = Console.ReadLine(); - response = login.Authenticate(email, pw); - - if (!response.IsSuccess) - throw new Exception(response.Result.ToString()); // 로그인 실패 -} - -// session 변수가 로그인 결과를 나타내는 변수입니다. 아래 실행 부분에 있는 MLaunchOption에 같이 넣어서 게임을 실행하면 됩니다. -var session = response.Session; -``` - -### Offline Login - -```csharp -// session 변수가 로그인 결과를 나타내는 변수입니다. 아래 실행 부분에 있는 MLaunchOption에 같이 넣어서 게임을 실행하면 됩니다. -var session = MSession.GetOfflineSession("USERNAME"); -``` - -### 실행 - -```csharp -// 빠른 다운로드를 위해 커넥션 제한을 늘립니다. -System.Net.ServicePointManager.DefaultConnectionLimit = 256; - -//var path = new MinecraftPath("게임 폴더 경로"); -var path = new MinecraftPath(); // 기본 게임 경로 사용 - -var launcher = new CMLauncher(path); - -// 콘솔에 실행 진행률 표시 -launcher.FileChanged += (e) => -{ - Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); -}; -launcher.ProgressChanged += (s, e) => -{ - Console.WriteLine("{0}%", e.ProgressPercentage); -}; - -var versions = await launcher.GetAllVersionsAsync(); -foreach (var item in versions) -{ - // 모든 버전 이름 표시 - // 여기서 출력되는 버전 이름을 CreateProcessAsync 메서드를 호출할 때 사용하면 됩니다. - Console.WriteLine(item.Name); -} - -var launchOption = new MLaunchOption -{ - MaximumRamMb = 1024, - Session = MSession.GetOfflineSession("hello"), // Login Session. ex) Session = MSession.GetOfflineSession("hello") - - //ScreenWidth = 1600, - //ScreenHeigth = 900, - //ServerIp = "mc.hypixel.net" -}; - -//var process = await launcher.CreateProcessAsync("실행할 버전 이름을 여기에 입력하세요", launchOption); -var process = await launcher.CreateProcessAsync("1.15.2", launchOption); // 바닐라 -// var process = await launcher.CreateProcessAsync("1.12.2-forge1.12.2-14.23.5.2838", launchOption); // 포지 -// var process = await launcher.CreateProcessAsync("1.12.2-LiteLoader1.12.2"); // 라이트로더 -// var process = await launcher.CreateProcessAsync("fabric-loader-0.11.3-1.16.5") // fabricMC - -process.Start(); -``` - -## 예제 코드 - -[Sample Code](https://github.com/AlphaBs/CmlLib.Core/wiki/Sample-Code) diff --git a/examples/console/CmlLibCoreSample.csproj b/examples/console/CmlLibCoreSample.csproj index cca24bb..bc42691 100644 --- a/examples/console/CmlLibCoreSample.csproj +++ b/examples/console/CmlLibCoreSample.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/winform/CmlLibWinFormSample.csproj b/examples/winform/CmlLibWinFormSample.csproj index b8ec0eb..b310c0e 100644 --- a/examples/winform/CmlLibWinFormSample.csproj +++ b/examples/winform/CmlLibWinFormSample.csproj @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj index b1a5bf4..afe3242 100644 --- a/test/CmlLib.Core.Test.csproj +++ b/test/CmlLib.Core.Test.csproj @@ -15,7 +15,7 @@ - + From e05a7ad01e12cd8f8024bcebbea2f92498bdd164 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Mon, 31 Jul 2023 12:08:38 +0000 Subject: [PATCH 055/137] update lots --- examples/console/InstallerTest.cs | 255 +++++----- examples/console/Program.cs | 358 ++++++-------- examples/console/Test.cs | 8 +- examples/winform/ChangeLog.cs | 4 +- examples/winform/LoginForm.cs | 91 +--- examples/winform/MainForm.cs | 39 +- examples/winform/VersionSortOptionForm.cs | 10 +- src/Core/Auth/MLogin.cs | 272 ---------- src/Core/Auth/MLoginResponse.cs | 20 - src/Core/Auth/MSession.cs | 75 ++- src/Core/CMLauncher.cs | 329 ++++++------- .../Downloader/AsyncParallelDownloader.cs | 236 +++++---- src/Core/Downloader/SequenceDownloader.cs | 88 ++-- src/Core/FileChecker/AssetChecker.cs | 292 ++++++----- src/Core/FileChecker/FileCheckerCollection.cs | 203 ++------ src/Core/FileChecker/JavaChecker.cs | 465 +++++++++--------- src/Core/Files/MLibraryParser.cs | 158 +++--- .../Installer/FabricMC/FabricVersionLoader.cs | 129 +++-- .../LiteLoader/LiteLoaderInstaller.cs | 112 ++--- .../LiteLoader/LiteLoaderVersionLoader.cs | 80 +-- .../LiteLoader/LiteLoaderVersionMetadata.cs | 235 ++++----- src/Core/Installer/MJava.cs | 196 ++++---- .../Installer/QuiltMC/QuiltVersionLoader.cs | 165 +++---- src/Core/Launcher/MLaunch.cs | 356 +++++++------- src/Core/Version/JsonVersionParser.cs | 180 +++++++ src/Core/Version/MVersion.cs | 200 ++++---- src/Core/Version/MVersionCollection.cs | 172 ------- src/Core/Version/MVersionParseException.cs | 19 +- src/Core/Version/MVersionParser.cs | 177 ------- src/Core/Version/MVersionSortOption.cs | 39 -- src/Core/Version/MVersionSorter.cs | 144 ------ src/Core/Version/MVersionType.cs | 79 --- .../VersionLoader/DefaultVersionLoader.cs | 37 -- src/Core/VersionLoader/IVersionLoader.cs | 5 +- src/Core/VersionLoader/LocalVersionLoader.cs | 72 ++- src/Core/VersionLoader/MojangVersionLoader.cs | 80 ++- .../VersionLoader/VersionLoaderCollection.cs | 31 ++ src/Core/VersionMetadata/Extensions.cs | 9 + src/Core/VersionMetadata/IVersionMetadata.cs | 18 + .../VersionMetadata/JsonVersionMetadata.cs | 85 ++++ .../JsonVersionMetadataModel.cs | 18 + .../VersionMetadata/LocalVersionMetadata.cs | 40 +- .../VersionMetadata/MVersionCollection.cs | 156 ++++++ src/Core/VersionMetadata/MVersionMetadata.cs | 90 ---- .../VersionMetadata/MVersionMetadataSorter.cs | 138 ++++++ src/Core/VersionMetadata/MVersionType.cs | 78 +++ .../VersionMetadata/MojangVersionMetadata.cs | 25 + .../VersionMetadata/StringVersionMetadata.cs | 80 --- src/Core/VersionMetadata/VersionSortOption.cs | 38 ++ .../VersionMetadata/WebVersionMetadata.cs | 27 - src/Utils/Changelogs.cs | 146 +++--- src/Utils/HttpUtil.cs | 9 - src/Utils/JsonUtil.cs | 35 +- src/Utils/WebDownload.cs | 143 ------ 54 files changed, 2830 insertions(+), 3716 deletions(-) delete mode 100644 src/Core/Auth/MLogin.cs delete mode 100644 src/Core/Auth/MLoginResponse.cs create mode 100644 src/Core/Version/JsonVersionParser.cs delete mode 100644 src/Core/Version/MVersionCollection.cs delete mode 100644 src/Core/Version/MVersionParser.cs delete mode 100644 src/Core/Version/MVersionSortOption.cs delete mode 100644 src/Core/Version/MVersionSorter.cs delete mode 100644 src/Core/Version/MVersionType.cs delete mode 100644 src/Core/VersionLoader/DefaultVersionLoader.cs create mode 100644 src/Core/VersionLoader/VersionLoaderCollection.cs create mode 100644 src/Core/VersionMetadata/Extensions.cs create mode 100644 src/Core/VersionMetadata/IVersionMetadata.cs create mode 100644 src/Core/VersionMetadata/JsonVersionMetadata.cs create mode 100644 src/Core/VersionMetadata/JsonVersionMetadataModel.cs create mode 100644 src/Core/VersionMetadata/MVersionCollection.cs delete mode 100644 src/Core/VersionMetadata/MVersionMetadata.cs create mode 100644 src/Core/VersionMetadata/MVersionMetadataSorter.cs create mode 100644 src/Core/VersionMetadata/MVersionType.cs create mode 100644 src/Core/VersionMetadata/MojangVersionMetadata.cs delete mode 100644 src/Core/VersionMetadata/StringVersionMetadata.cs create mode 100644 src/Core/VersionMetadata/VersionSortOption.cs delete mode 100644 src/Core/VersionMetadata/WebVersionMetadata.cs delete mode 100644 src/Utils/HttpUtil.cs delete mode 100644 src/Utils/WebDownload.cs diff --git a/examples/console/InstallerTest.cs b/examples/console/InstallerTest.cs index c9dae4e..fa28992 100644 --- a/examples/console/InstallerTest.cs +++ b/examples/console/InstallerTest.cs @@ -1,159 +1,156 @@ -using System; -using System.Threading.Tasks; -using CmlLib.Core; +using CmlLib.Core; using CmlLib.Core.Auth; using CmlLib.Core.Downloader; using CmlLib.Core.Installer.FabricMC; using CmlLib.Core.Installer.LiteLoader; -namespace CmlLibCoreSample +namespace CmlLibCoreSample; + +public class InstallerTest { - public class InstallerTest + // FabricLoader + public async Task TestFabric() { - // FabricLoader - public async Task TestFabric() + var path = new MinecraftPath(); + var launcher = new CMLauncher(path, Program.HttpClient); + launcher.FileChanged += Downloader_ChangeFile; + launcher.ProgressChanged += Downloader_ChangeProgress; + + // initialize fabric version loader + var fabricVersionLoader = new FabricVersionLoader(Program.HttpClient); + var fabricVersions = await fabricVersionLoader.GetVersionMetadatasAsync(); + + // print fabric versions + foreach (var v in fabricVersions) { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path); - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; + Console.WriteLine(v.Name); + } - // initialize fabric version loader - var fabricVersionLoader = new FabricVersionLoader(); - var fabricVersions = await fabricVersionLoader.GetVersionMetadatasAsync(); + Console.WriteLine("select version: "); + var fabricVersionName = Console.ReadLine(); - // print fabric versions - foreach (var v in fabricVersions) - { - Console.WriteLine(v.Name); - } + if (string.IsNullOrEmpty(fabricVersionName)) + return; - Console.WriteLine("select version: "); - var fabricVersionName = Console.ReadLine(); + // install + var fabric = fabricVersions.GetVersionMetadata(fabricVersionName); + await fabric.SaveVersionAsync(path); - if (string.IsNullOrEmpty(fabricVersionName)) - return; + // update version list + await launcher.GetAllVersionsAsync(); - // install - var fabric = fabricVersions.GetVersionMetadata(fabricVersionName); - await fabric.SaveAsync(path); + var process = await launcher.CreateProcessAsync(fabricVersionName, new MLaunchOption()); + process.Start(); + } + + // LiteLoader + public async Task TestLiteLoader() + { + var path = new MinecraftPath(); + var launcher = new CMLauncher(path, Program.HttpClient); + launcher.FileChanged += Downloader_ChangeFile; + launcher.ProgressChanged += Downloader_ChangeProgress; - // update version list - await launcher.GetAllVersionsAsync(); + // initialize LiteLoader installer + var liteLoaderVersionLoader = new LiteLoaderVersionLoader(Program.HttpClient); + var liteLoaderVersions = await liteLoaderVersionLoader.GetVersionMetadatasAsync(); - var process = await launcher.CreateProcessAsync(fabricVersionName, new MLaunchOption()); - process.Start(); - } - - // LiteLoader - public async Task TestLiteLoader() + // print all LiteLoader versions + foreach (var item in liteLoaderVersions) { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path); - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - // initialize LiteLoader installer - var liteLoaderVersionLoader = new LiteLoaderVersionLoader(); - var liteLoaderVersions = await liteLoaderVersionLoader.GetVersionMetadatasAsync(); - - // print all LiteLoader versions - foreach (var item in liteLoaderVersions) - { - Console.WriteLine(item); - } - - Console.WriteLine("Select LiteLoader version name (ex: LiteLoader1.12.2) : "); - var selectLiteLoaderVersion = Console.ReadLine(); - - // print all game versions - var versions = await launcher.GetAllVersionsAsync(); - foreach (var item in versions) - { - Console.WriteLine(item); - } - - Console.WriteLine("Select minecraft version where to install : "); - var selectGameVersion = Console.ReadLine(); - - if (string.IsNullOrEmpty(selectLiteLoaderVersion) || string.IsNullOrEmpty(selectGameVersion)) - return; - - // install LiteLoader - var liteLoader = - (LiteLoaderVersionMetadata)liteLoaderVersions.GetVersionMetadata(selectLiteLoaderVersion); - var startVersionName = await liteLoader.InstallAsync(path, await versions.GetVersionAsync(selectGameVersion)); - - // update version list - await launcher.GetAllVersionsAsync(); - - // start - var process = await launcher.CreateProcessAsync(startVersionName, new MLaunchOption - { - Session = MSession.GetOfflineSession("liteloadertester"), - MaximumRamMb = 2048 - }); - - process.Start(); + Console.WriteLine(item); } - - // only vanilla - public async Task TestLiteLoaderVanilla() + + Console.WriteLine("Select LiteLoader version name (ex: LiteLoader1.12.2) : "); + var selectLiteLoaderVersion = Console.ReadLine(); + + // print all game versions + var versions = await launcher.GetAllVersionsAsync(); + foreach (var item in versions) { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path); - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - var versions = await launcher.GetAllVersionsAsync(); - - // initialize LiteLoader installer - var liteLoaderVersionLoader = new LiteLoaderVersionLoader(); - var liteLoaderVersions = await liteLoaderVersionLoader.GetVersionMetadatasAsync(); - - // print all LiteLoader versions - foreach (var item in liteLoaderVersions) - { - var liteLoaderVersion = item as LiteLoaderVersionMetadata; - if (liteLoaderVersion == null) - continue; - - Console.WriteLine(item.Name); - - var vanillaVersionName = liteLoaderVersion.VanillaVersionName; - Console.WriteLine(vanillaVersionName); - var vanillaVersion = await versions.GetVersionAsync(vanillaVersionName); - - var liteLoaderVersionName = await liteLoaderVersion.InstallAsync(path, vanillaVersion); - versions = await launcher.GetAllVersionsAsync(); // update version lists - - var process = await launcher.CreateProcessAsync(liteLoaderVersionName, new MLaunchOption()); - process.Start(); - Console.ReadLine(); - } + Console.WriteLine(item); } - - int endTop = -1; - private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) + Console.WriteLine("Select minecraft version where to install : "); + var selectGameVersion = Console.ReadLine(); + + if (string.IsNullOrEmpty(selectLiteLoaderVersion) || string.IsNullOrEmpty(selectGameVersion)) + return; + + // install LiteLoader + var liteLoader = + (LiteLoaderVersionMetadata)liteLoaderVersions.GetVersionMetadata(selectLiteLoaderVersion); + var startVersionName = await liteLoader.InstallAsync(path, await versions.GetVersionAsync(selectGameVersion)); + + // update version list + await launcher.GetAllVersionsAsync(); + + // start + var process = await launcher.CreateProcessAsync(startVersionName, new MLaunchOption { - Console.SetCursorPosition(0, endTop); + Session = MSession.GetOfflineSession("liteloadertester"), + MaximumRamMb = 2048 + }); - // e.ProgressPercentage: 0~100 - Console.Write("{0}% ", e.ProgressPercentage); + process.Start(); + } + + // only vanilla + public async Task TestLiteLoaderVanilla() + { + var path = new MinecraftPath(); + var launcher = new CMLauncher(path, Program.HttpClient); + launcher.FileChanged += Downloader_ChangeFile; + launcher.ProgressChanged += Downloader_ChangeProgress; - Console.SetCursorPosition(0, endTop); - } + var versions = await launcher.GetAllVersionsAsync(); + + // initialize LiteLoader installer + var liteLoaderVersionLoader = new LiteLoaderVersionLoader(Program.HttpClient); + var liteLoaderVersions = await liteLoaderVersionLoader.GetVersionMetadatasAsync(); - private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) + // print all LiteLoader versions + foreach (var item in liteLoaderVersions) { - // More information about DownloadFileChangedEventArgs - // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs + var liteLoaderVersion = item as LiteLoaderVersionMetadata; + if (liteLoaderVersion == null) + continue; + + Console.WriteLine(item.Name); - Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, - e.TotalFileCount); + var vanillaVersionName = liteLoaderVersion.VanillaVersionName; + Console.WriteLine(vanillaVersionName); + var vanillaVersion = await versions.GetVersionAsync(vanillaVersionName); - endTop = Console.CursorTop; + var liteLoaderVersionName = await liteLoaderVersion.InstallAsync(path, vanillaVersion); + versions = await launcher.GetAllVersionsAsync(); // update version lists + + var process = await launcher.CreateProcessAsync(liteLoaderVersionName, new MLaunchOption()); + process.Start(); + Console.ReadLine(); } } + + int endTop = -1; + + private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) + { + Console.SetCursorPosition(0, endTop); + + // e.ProgressPercentage: 0~100 + Console.Write("{0}% ", e.ProgressPercentage); + + Console.SetCursorPosition(0, endTop); + } + + private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) + { + // More information about DownloadFileChangedEventArgs + // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs + + Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, + e.TotalFileCount); + + endTop = Console.CursorTop; + } } \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 50af9b8..bd1348c 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -1,234 +1,194 @@ using CmlLib.Core; using CmlLib.Core.Auth; using CmlLib.Core.Downloader; -using System; -using System.Threading.Tasks; -namespace CmlLibCoreSample +namespace CmlLibCoreSample; + +class Program { - class Program + public static readonly HttpClient HttpClient = new(); + + public static async Task Main() { - public static async Task Main() - { - var p = new Program(); + var p = new Program(); - // Login - MSession session; // login session + // Login + MSession session; + session = p.OfflineLogin(); // Login by username - // There are two login methods, one is using mojang email and password, and the other is using only username - // Choose one which you want. - //session = await p.PremiumLogin(); // Login by mojang email and password - session = p.OfflineLogin(); // Login by username + // log login session information + Console.WriteLine("Success to login : {0} / {1} / {2}", session.Username, session.UUID, session.AccessToken); - // log login session information - Console.WriteLine("Success to login : {0} / {1} / {2}", session.Username, session.UUID, session.AccessToken); + // Launch + await p.Start(session); + } - // Launch - await p.Start(session); - //p.StartAsync(session).GetAwaiter().GetResult(); - } + MSession OfflineLogin() + { + // Create fake session by username + return MSession.GetOfflineSession("tester123"); + } - async Task PremiumLogin() - { - var login = new MLogin(); - - // TryAutoLogin() reads the login cache file and check validation. - // If the cached session is invalid, it refreshes the session automatically. - // Refreshing the session doesn't always succeed, so you have to handle this. - Console.WriteLine("Attempting to automatically log in."); - var response = await login.TryAutoLogin(); - - if (!response.IsSuccess) // if cached session is invalid and failed to refresh token - { - Console.WriteLine("Auto login failed: {0}", response.Result.ToString()); - - Console.WriteLine("Input your Mojang email: "); - var email = Console.ReadLine(); - Console.WriteLine("Input your Mojang password: "); - var pw = Console.ReadLine(); - - response = await login.Authenticate(email, pw); - - if (!response.IsSuccess) - { - // session.Message contains a detailed error message. It can be null or an empty string. - Console.WriteLine("failed to login. {0} : {1}", response.Result.ToString(), response.ErrorMessage); - Console.ReadLine(); - Environment.Exit(0); - return null; - } - } - - return response.Session; - } + async Task Start(MSession session) + { + // Initializing Launcher + + // Set minecraft home directory + // MinecraftPath.GetOSDefaultPath() return default minecraft BasePath of current OS. + // https://github.com/AlphaBs/CmlLib.Core/blob/master/CmlLib/Core/MinecraftPath.cs - MSession OfflineLogin() + // You can set this path to what you want like this : + //var path = "./testdir"; + var path = MinecraftPath.GetOSDefaultPath(); + var game = new MinecraftPath(path); + + // Create CMLauncher instance + var launcher = new CMLauncher(game, HttpClient); + + // if you want to download with parallel downloader, add below code : + System.Net.ServicePointManager.DefaultConnectionLimit = 256; + + // for offline mode + //launcher.VersionLoader = new LocalVersionLoader(launcher.MinecraftPath); + //launcher.FileDownloader = null; + + launcher.ProgressChanged += Downloader_ChangeProgress; + launcher.FileChanged += Downloader_ChangeFile; + + Console.WriteLine($"Initialized in {launcher.MinecraftPath.BasePath}"); + + // Get all installed profiles and load all profiles from mojang server + var versions = await launcher.GetAllVersionsAsync(); + + foreach (var item in versions) // Display all profiles { - // Create fake session by username - return MSession.GetOfflineSession("tester123"); + // You can filter snapshots and old versions to add if statement : + // if (item.MType == MProfileType.Custom || item.MType == MProfileType.Release) + Console.WriteLine(item.Type + " " + item.Name); } - async Task Start(MSession session) + var launchOption = new MLaunchOption { - // Initializing Launcher - - // Set minecraft home directory - // MinecraftPath.GetOSDefaultPath() return default minecraft BasePath of current OS. - // https://github.com/AlphaBs/CmlLib.Core/blob/master/CmlLib/Core/MinecraftPath.cs - - // You can set this path to what you want like this : - //var path = "./testdir"; - var path = MinecraftPath.GetOSDefaultPath(); - var game = new MinecraftPath(path); - - // Create CMLauncher instance - var launcher = new CMLauncher(game); - - // if you want to download with parallel downloader, add below code : - System.Net.ServicePointManager.DefaultConnectionLimit = 256; - - // for offline mode - //launcher.VersionLoader = new LocalVersionLoader(launcher.MinecraftPath); - //launcher.FileDownloader = null; - - launcher.ProgressChanged += Downloader_ChangeProgress; - launcher.FileChanged += Downloader_ChangeFile; - - Console.WriteLine($"Initialized in {launcher.MinecraftPath.BasePath}"); - - // Get all installed profiles and load all profiles from mojang server - var versions = await launcher.GetAllVersionsAsync(); - - foreach (var item in versions) // Display all profiles - { - // You can filter snapshots and old versions to add if statement : - // if (item.MType == MProfileType.Custom || item.MType == MProfileType.Release) - Console.WriteLine(item.Type + " " + item.Name); - } - - var launchOption = new MLaunchOption - { - MaximumRamMb = 1024, - Session = session, - - //ScreenWidth = 1600, - //ScreenHeight = 900, - //ServerIp = "mc.hypixel.net", - //MinimumRamMb = 102, - //FullScreen = true, - - // More options: - // https://github.com/AlphaBs/CmlLib.Core/wiki/MLaunchOption - }; - - // download essential files (ex: vanilla libraries) and create game process. - - // var process = await launcher.CreateProcessAsync("1.15.2", launchOption); // vanilla - // var process = await launcher.CreateProcessAsync("1.12.2-forge1.12.2-14.23.5.2838", launchOption); // forge - // var process = await launcher.CreateProcessAsync("1.12.2-LiteLoader1.12.2"); // liteloader - // var process = await launcher.CreateProcessAsync("fabric-loader-0.11.3-1.16.5") // fabric-loader - - Console.WriteLine("input version (example: 1.12.2) : "); - var versionName = Console.ReadLine(); - //var versionName = "1.18.2"; - var process = await launcher.CreateProcessAsync(versionName, launchOption); - - //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); - Console.WriteLine(process.StartInfo.FileName); - Console.WriteLine(process.StartInfo.Arguments); - - // Below codes are print game logs in Console. - var processUtil = new CmlLib.Utils.ProcessUtil(process); - processUtil.OutputReceived += (s, e) => Console.WriteLine(e); - processUtil.StartWithEvents(); - await process.WaitForExitAsync(); - - // or just start it without print logs - // process.Start(); - - Console.ReadLine(); - } + MaximumRamMb = 1024, + Session = session, - #region QuickStart + //ScreenWidth = 1600, + //ScreenHeight = 900, + //ServerIp = "mc.hypixel.net", + //MinimumRamMb = 102, + //FullScreen = true, - // this code is from README.md + // More options: + // https://github.com/AlphaBs/CmlLib.Core/wiki/MLaunchOption + }; - async Task QuickStart() - { - //var path = new MinecraftPath("game_directory_path"); - var path = new MinecraftPath(); // use default directory - - var launcher = new CMLauncher(path); - launcher.FileChanged += (e) => - { - Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - }; - launcher.ProgressChanged += (s, e) => - { - Console.WriteLine("{0}%", e.ProgressPercentage); - }; - - var versions = await launcher.GetAllVersionsAsync(); - foreach (var item in versions) - { - Console.WriteLine(item.Name); - } - - var launchOption = new MLaunchOption - { - MaximumRamMb = 1024, - Session = MSession.GetOfflineSession("hello"), // Login Session. ex) Session = MSession.GetOfflineSession("hello") - - //ScreenWidth = 1600, - //ScreenHeigth = 900, - //ServerIp = "mc.hypixel.net" - }; - - // launch vanila - var process = await launcher.CreateProcessAsync("1.15.2", launchOption); - - process.Start(); - } + // download essential files (ex: vanilla libraries) and create game process. + + // var process = await launcher.CreateProcessAsync("1.15.2", launchOption); // vanilla + // var process = await launcher.CreateProcessAsync("1.12.2-forge1.12.2-14.23.5.2838", launchOption); // forge + // var process = await launcher.CreateProcessAsync("1.12.2-LiteLoader1.12.2"); // liteloader + // var process = await launcher.CreateProcessAsync("fabric-loader-0.11.3-1.16.5") // fabric-loader + + Console.WriteLine("input version (example: 1.12.2) : "); + var versionName = Console.ReadLine(); + //var versionName = "1.18.2"; + var process = await launcher.CreateProcessAsync(versionName, launchOption); - #endregion + //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); + Console.WriteLine(process.StartInfo.FileName); + Console.WriteLine(process.StartInfo.Arguments); - // Event Handling + // Below codes are print game logs in Console. + var processUtil = new CmlLib.Utils.ProcessUtil(process); + processUtil.OutputReceived += (s, e) => Console.WriteLine(e); + processUtil.StartWithEvents(); + await process.WaitForExitAsync(); - // The code below has some tricks to display logs prettier. - // You can also use a simpler event handler + // or just start it without print logs + // process.Start(); - #region Pretty event handler + Console.ReadLine(); + } + + #region QuickStart + + // this code is from README.md - private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) + async Task QuickStart() + { + //var path = new MinecraftPath("game_directory_path"); + var path = new MinecraftPath(); // use default directory + + var launcher = new CMLauncher(path, HttpClient); + launcher.FileChanged += (e) => + { + Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); + }; + launcher.ProgressChanged += (s, e) => { - var top = Console.CursorTop; - Console.SetCursorPosition(0, top); - // e.ProgressPercentage: 0~100 - Console.Write($"{e.ProgressPercentage}% "); - Console.SetCursorPosition(0, top); + Console.WriteLine("{0}%", e.ProgressPercentage); + }; + + var versions = await launcher.GetAllVersionsAsync(); + foreach (var item in versions) + { + Console.WriteLine(item.Name); } - private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) + var launchOption = new MLaunchOption { - // More information about DownloadFileChangedEventArgs - // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs + MaximumRamMb = 1024, + Session = MSession.GetOfflineSession("hello"), // Login Session. ex) Session = MSession.GetOfflineSession("hello") - Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - } + //ScreenWidth = 1600, + //ScreenHeigth = 900, + //ServerIp = "mc.hypixel.net" + }; + + // launch vanila + var process = await launcher.CreateProcessAsync("1.15.2", launchOption); + + process.Start(); + } + + #endregion + + // Event Handling - #endregion + // The code below has some tricks to display logs prettier. + // You can also use a simpler event handler - #region Simple event handler - //private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) - //{ - // Console.WriteLine("{0}%", e.ProgressPercentage); - //} + #region Pretty event handler - //private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) - //{ - // Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - //} - #endregion + private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) + { + var top = Console.CursorTop; + Console.SetCursorPosition(0, top); + // e.ProgressPercentage: 0~100 + Console.Write($"{e.ProgressPercentage}% "); + Console.SetCursorPosition(0, top); } + + private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) + { + // More information about DownloadFileChangedEventArgs + // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs + + Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); + } + + #endregion + + #region Simple event handler + //private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) + //{ + // Console.WriteLine("{0}%", e.ProgressPercentage); + //} + + //private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) + //{ + // Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); + //} + #endregion } diff --git a/examples/console/Test.cs b/examples/console/Test.cs index 7117ba9..6325501 100644 --- a/examples/console/Test.cs +++ b/examples/console/Test.cs @@ -4,6 +4,7 @@ using CmlLib.Core; using CmlLib.Core.Auth; using CmlLib.Core.Downloader; +using CmlLib.Core.VersionMetadata; namespace CmlLibCoreSample { @@ -29,10 +30,10 @@ async Task TestAll(MSession session) var path = MinecraftPath.GetOSDefaultPath(); var game = new MinecraftPath(path); - var launcher = new CMLauncher(game); + var launcher = new CMLauncher(game, Program.HttpClient); System.Net.ServicePointManager.DefaultConnectionLimit = 256; - launcher.FileDownloader = new AsyncParallelDownloader(); + launcher.FileDownloader = new AsyncParallelDownloader(Program.HttpClient); launcher.ProgressChanged += Downloader_ChangeProgress; launcher.FileChanged += Downloader_ChangeFile; @@ -49,8 +50,7 @@ async Task TestAll(MSession session) foreach (var item in versions) { Console.WriteLine(item.Type + " " + item.Name); - - if (!item.IsLocalVersion) + if (item is LocalVersionMetadata) continue; var process = await launcher.CreateProcessAsync(item.Name, launchOption); diff --git a/examples/winform/ChangeLog.cs b/examples/winform/ChangeLog.cs index 78b3096..9887f85 100644 --- a/examples/winform/ChangeLog.cs +++ b/examples/winform/ChangeLog.cs @@ -1,6 +1,4 @@ using CmlLib.Utils; -using System; -using System.Windows.Forms; namespace CmlLibWinFormSample { @@ -16,7 +14,7 @@ public ChangeLog() private async void ChangeLog_Load(object sender, EventArgs e) { btnLoad.Enabled = false; - changelogs = await Changelogs.GetChangelogs(); + changelogs = await Changelogs.GetChangelogs(new HttpClient()); listBox1.Items.AddRange(changelogs.GetAvailableVersions()); btnLoad.Enabled = true; } diff --git a/examples/winform/LoginForm.cs b/examples/winform/LoginForm.cs index d35065a..38766d3 100644 --- a/examples/winform/LoginForm.cs +++ b/examples/winform/LoginForm.cs @@ -1,7 +1,4 @@ using CmlLib.Core.Auth; -using System; -using System.Threading; -using System.Windows.Forms; namespace CmlLibWinFormSample { @@ -12,105 +9,33 @@ public LoginForm() InitializeComponent(); } - MLogin login = new MLogin(); - private void LoginForm_Load(object sender, EventArgs e) { - btnAutoLogin_Click(null, null); } - private async void btnAutoLogin_Click(object sender, EventArgs e) + private void btnAutoLogin_Click(object sender, EventArgs e) { - gMojangLogin.Enabled = false; - gOfflineLogin.Enabled = false; - - var result = await login.TryAutoLogin(); - - if (result.Result != MLoginResult.Success) - { - MessageBox.Show($"Failed to AutoLogin : {result.Result}\n{result.ErrorMessage}"); - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; - })); - return; - } - - MessageBox.Show("Auto Login Success!"); - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; - - btnAutoLogin.Enabled = false; - btnLogin.Enabled = false; - btnLogin.Text = "Auto Login\nSuccess"; - - UpdateSession(result.Session); - })); + } - private async void btnLogin_Click(object sender, EventArgs e) + private void btnLogin_Click(object sender, EventArgs e) { - if (string.IsNullOrWhiteSpace(txtEmail.Text) || string.IsNullOrWhiteSpace(txtPassword.Text)) - { - MessageBox.Show("Empty Textbox"); - return; - } - - gMojangLogin.Enabled = false; - gOfflineLogin.Enabled = false; - var result = await login.Authenticate(txtEmail.Text, txtPassword.Text); - if (result.Result == MLoginResult.Success) - { - MessageBox.Show("Login Success"); // Success Login - Invoke(new Action(() => - { - UpdateSession(result.Session); - })); - } - else - { - MessageBox.Show(result.Result.ToString() + "\n" + result.ErrorMessage); // Failed to login. Show error message - Invoke(new Action(() => - { - gMojangLogin.Enabled = true; - gOfflineLogin.Enabled = true; - })); - } } - private async void btnSignout_Click(object sender, EventArgs e) + private void btnSignout_Click(object sender, EventArgs e) { - var result = await login.Signout(txtEmail.Text, txtPassword.Text); - if (result) - { - MessageBox.Show("Success"); - gMojangLogin.Enabled = true; - } - else - MessageBox.Show("Fail"); + } - private async void btnInvalidate_Click(object sender, EventArgs e) + private void btnInvalidate_Click(object sender, EventArgs e) { - var result = await login.Invalidate(); - if (result) - { - MessageBox.Show("Success"); - gMojangLogin.Enabled = true; - } - else - MessageBox.Show("Fail"); + } private void btnDeleteToken_Click(object sender, EventArgs e) { - login.DeleteTokenFile(); - MessageBox.Show("Success"); - gMojangLogin.Enabled = true; + } private void btnOfflineLogin_Click(object sender, EventArgs e) diff --git a/examples/winform/MainForm.cs b/examples/winform/MainForm.cs index 8fa0c4a..4bc38dc 100644 --- a/examples/winform/MainForm.cs +++ b/examples/winform/MainForm.cs @@ -1,23 +1,18 @@ using CmlLib.Core; using CmlLib.Core.Auth; -using CmlLib.Core.Installer; -using CmlLib.Core.Files; -using System; using System.ComponentModel; using System.Diagnostics; -using System.IO; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; using CmlLib.Core.Downloader; using CmlLib.Core.FileChecker; -using CmlLib.Core.Version; +using CmlLib.Core.VersionMetadata; namespace CmlLibWinFormSample { public partial class MainForm : Form { + private readonly HttpClient _httpClient = new(); + public MainForm(MSession session) { this.session = session; @@ -44,7 +39,7 @@ private async Task initializeLauncher(MinecraftPath path) txtPath.Text = path.BasePath; this.gamePath = path; - launcher = new CMLauncher(path); + launcher = new CMLauncher(path, _httpClient); launcher.FileChanged += Launcher_FileChanged; launcher.ProgressChanged += Launcher_ProgressChanged; await refreshVersions(null); @@ -77,7 +72,7 @@ private async Task refreshVersions(string showVersion) private void btnSetLastVersion_Click(object sender, EventArgs e) { - cbVersion.Text = launcher.Versions?.LatestReleaseVersion?.Name; + cbVersion.Text = launcher.Versions?.LatestReleaseName; } private void btnSortFilter_Click(object sender, EventArgs e) @@ -146,23 +141,23 @@ private async void Btn_Launch_Click(object sender, EventArgs e) if (rbParallelDownload.Checked) { System.Net.ServicePointManager.DefaultConnectionLimit = 256; - launcher.FileDownloader = new AsyncParallelDownloader(); + launcher.FileDownloader = new AsyncParallelDownloader(_httpClient); } else - launcher.FileDownloader = new SequenceDownloader(); + launcher.FileDownloader = new SequenceDownloader(_httpClient); - if (cbSkipAssetsDownload.Checked) - launcher.GameFileCheckers.AssetFileChecker = null; - else if (launcher.GameFileCheckers.AssetFileChecker == null) - launcher.GameFileCheckers.AssetFileChecker = new AssetChecker(); + //if (cbSkipAssetsDownload.Checked) + // launcher.GameFileCheckers.AssetFileChecker = null; + //else if (launcher.GameFileCheckers.AssetFileChecker == null) + // launcher.GameFileCheckers.AssetFileChecker = new AssetChecker(); // check file hash or don't check - if (launcher.GameFileCheckers.AssetFileChecker != null) - launcher.GameFileCheckers.AssetFileChecker.CheckHash = !cbSkipHashCheck.Checked; - if (launcher.GameFileCheckers.ClientFileChecker != null) - launcher.GameFileCheckers.ClientFileChecker.CheckHash = !cbSkipHashCheck.Checked; - if (launcher.GameFileCheckers.LibraryFileChecker != null) - launcher.GameFileCheckers.LibraryFileChecker.CheckHash = !cbSkipHashCheck.Checked; + //if (launcher.GameFileCheckers.AssetFileChecker != null) + // launcher.GameFileCheckers.AssetFileChecker.CheckHash = !cbSkipHashCheck.Checked; + //if (launcher.GameFileCheckers.ClientFileChecker != null) + // launcher.GameFileCheckers.ClientFileChecker.CheckHash = !cbSkipHashCheck.Checked; + //if (launcher.GameFileCheckers.LibraryFileChecker != null) + // launcher.GameFileCheckers.LibraryFileChecker.CheckHash = !cbSkipHashCheck.Checked; var process = await launcher.CreateProcessAsync(cbVersion.Text, launchOption); // Create Arguments and Process diff --git a/examples/winform/VersionSortOptionForm.cs b/examples/winform/VersionSortOptionForm.cs index 5dc7d8c..b2acfc5 100644 --- a/examples/winform/VersionSortOptionForm.cs +++ b/examples/winform/VersionSortOptionForm.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Windows.Forms; -using CmlLib.Core; -using CmlLib.Core.Version; +using CmlLib.Core; +using CmlLib.Core.VersionMetadata; namespace CmlLibWinFormSample { @@ -99,8 +96,7 @@ private void btnPreview_Click(object sender, EventArgs e) var vers = versions.ToArray(option); foreach (var item in vers) { - var tag = item.IsLocalVersion ? "Local" : item.MType.ToString(); - listPreview.Items.Add($"{tag} {item.Name}"); + listPreview.Items.Add($"{item.Type} {item.Name}"); } } diff --git a/src/Core/Auth/MLogin.cs b/src/Core/Auth/MLogin.cs deleted file mode 100644 index f63fb52..0000000 --- a/src/Core/Auth/MLogin.cs +++ /dev/null @@ -1,272 +0,0 @@ -using CmlLib.Utils; -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -// use new library: -// https://github.com/CmlLib/MojangAPI - -namespace CmlLib.Core.Auth -{ - public enum MLoginResult { Success, BadRequest, WrongAccount, NeedLogin, UnknownError, NoProfile } - - public class MLogin - { - public static readonly string DefaultLoginSessionFile - = Path.Combine(MinecraftPath.GetOSDefaultPath(), "logintoken.json"); - - public MLogin() : this(DefaultLoginSessionFile, HttpUtil.HttpClient) { } - - public MLogin(string sessionCacheFilePath, HttpClient client) - { - SessionCacheFilePath = sessionCacheFilePath; - this.httpClient = client; - } - - private readonly HttpClient httpClient; - public string SessionCacheFilePath { get; private set; } - public bool SaveSession { get; set; } = true; - - protected virtual string CreateNewClientToken() - { - return Guid.NewGuid().ToString().Replace("-", ""); - } - - protected virtual async Task createNewSession() - { - var session = new MSession(); - if (SaveSession) - { - session.ClientToken = CreateNewClientToken(); - await writeSessionCache(session); - } - return session; - } - - private async Task writeSessionCache(MSession session) - { - if (!SaveSession) return; - IOUtil.CreateParentDirectory(SessionCacheFilePath); - - var json = JsonSerializer.Serialize(session); - await IOUtil.WriteFileAsync(SessionCacheFilePath, json); - } - - public async Task ReadSessionCache() - { - if (File.Exists(SessionCacheFilePath)) - { - var fileData = await IOUtil.ReadFileAsync(SessionCacheFilePath); - try - { - var session = JsonSerializer.Deserialize(fileData, JsonUtil.JsonOptions) - ?? new MSession(); - - if (SaveSession && string.IsNullOrEmpty(session.ClientToken)) - session.ClientToken = CreateNewClientToken(); - - return session; - } - catch (JsonException) // invalid json - { - return await createNewSession(); - } - } - else - { - return await createNewSession(); - } - } - - protected async Task mojangRequest(string endpoint, object postData) - { - var json = JsonSerializer.Serialize(postData, JsonUtil.JsonOptions); - var res = await httpClient.SendAsync(new HttpRequestMessage - { - Method = HttpMethod.Post, - RequestUri = new Uri(MojangServer.Auth + endpoint), - Content = new StringContent(json, Encoding.UTF8, "application/json") - }); - return res; - } - - protected async Task mojangRequestHandle(string endpoint, object postdata, string? clientToken=null) - { - var res = await mojangRequest(endpoint, postdata); - - var str = await res.Content.ReadAsStringAsync(); - if (res.IsSuccessStatusCode) - return await parseSession(str, clientToken); - else - return errorHandle(str); - } - - private async Task parseSession(string json, string? clientToken) - { - using var jsonDocument = JsonDocument.Parse(json); - var root = jsonDocument.RootElement; - - if (!root.TryGetProperty("selectedProfile", out var profile)) - return new MLoginResponse(MLoginResult.NoProfile, null, null, json); - else - { - var session = new MSession - { - AccessToken = root.GetPropertyValue("accessToken"), - UUID = profile.GetPropertyValue("id"), - Username = profile.GetPropertyValue("name"), - UserType = "Mojang", - ClientToken = clientToken - }; - - await writeSessionCache(session); - return new MLoginResponse(MLoginResult.Success, session, null, null); - } - } - - private MLoginResponse errorHandle(string json) - { - try - { - using var jsonDocument = JsonDocument.Parse(json); - var root = jsonDocument.RootElement; - - string? error = root.GetPropertyValue("error"); // error type - string? errorMessage = root.GetPropertyValue("message"); // detail error message - MLoginResult result; - - switch (error) - { - case "Method Not Allowed": - case "Not Found": - case "Unsupported Media Type": - result = MLoginResult.BadRequest; - break; - case "IllegalArgumentException": - case "ForbiddenOperationException": - result = MLoginResult.WrongAccount; - break; - default: - result = MLoginResult.UnknownError; - break; - } - - return new MLoginResponse(result, null, errorMessage, json); - } - catch (JsonException ex) - { - return new MLoginResponse(MLoginResult.UnknownError, null, ex.ToString(), json); - } - } - - public async Task Authenticate(string username, string password) - { - var sessionCache = await ReadSessionCache(); - string? clientToken = sessionCache.ClientToken; - return await Authenticate(username, password, clientToken); - } - - public Task Authenticate(string username, string password, string? clientToken) - => mojangRequestHandle("authenticate", new - { - username, - password, - clientToken, - agent = new - { - name = "Minecraft", - version = 1 - } - }, clientToken); - - public async Task TryAutoLogin() - { - MSession session = await ReadSessionCache(); - return await TryAutoLogin(session); - } - - public async Task TryAutoLogin(MSession session) - { - MLoginResponse result = await Validate(session); - if (result.Result != MLoginResult.Success) - result = await Refresh(session); - return result; - } - - public async Task Refresh() - { - var session = await ReadSessionCache(); - return await Refresh(session); - } - - public Task Refresh(MSession session) - => mojangRequestHandle("refresh", new - { - accessToken = session.AccessToken, - clientToken = session.ClientToken, - selectedProfile = new - { - id = session.UUID, - name = session.Username - } - }, session.ClientToken); - - public async Task Validate() - { - var session = await ReadSessionCache(); - return await Validate(session); - } - - public async Task Validate(MSession session) - { - var res = await mojangRequest("validate", new - { - accessToken = session.AccessToken, - clientToken = session.ClientToken - }); - - if (res.IsSuccessStatusCode) - return new MLoginResponse(MLoginResult.Success, session, null, null); - else - return new MLoginResponse(MLoginResult.NeedLogin, null, null, null); - } - - public void DeleteTokenFile() - { - if (File.Exists(SessionCacheFilePath)) - File.Delete(SessionCacheFilePath); - } - - public async Task Invalidate() - { - var session = await ReadSessionCache(); - return await Invalidate(session); - } - - public async Task Invalidate(MSession session) - { - var res = await mojangRequest("invalidate", new - { - accessToken = session.AccessToken, - clientToken = session.ClientToken - }); - - return res.IsSuccessStatusCode; - } - - public async Task Signout(string username, string password) - { - var res = await mojangRequest("signout", new - { - username, - password - }); - - return res.StatusCode == HttpStatusCode.NoContent; // 204 - } - } -} diff --git a/src/Core/Auth/MLoginResponse.cs b/src/Core/Auth/MLoginResponse.cs deleted file mode 100644 index 99faf6e..0000000 --- a/src/Core/Auth/MLoginResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace CmlLib.Core.Auth -{ - public class MLoginResponse - { - public MLoginResponse(MLoginResult result, MSession? session, string? errormsg, string? rawresponse) - { - Result = result; - Session = session; - ErrorMessage = errormsg; - RawResponse = rawresponse; - } - - public MLoginResult Result { get; private set; } - public MSession? Session { get; private set; } - public string? ErrorMessage { get; private set; } - public string? RawResponse { get; private set; } - - public bool IsSuccess => Result == MLoginResult.Success; - } -} diff --git a/src/Core/Auth/MSession.cs b/src/Core/Auth/MSession.cs index 2b36886..bdde90b 100644 --- a/src/Core/Auth/MSession.cs +++ b/src/Core/Auth/MSession.cs @@ -1,48 +1,47 @@ using System.Text.Json.Serialization; -namespace CmlLib.Core.Auth +namespace CmlLib.Core.Auth; + +public class MSession { - public class MSession - { - public MSession() { } + public MSession() { } - public MSession(string? username, string? accessToken, string? uuid) - { - Username = username; - AccessToken = accessToken; - UUID = uuid; - } + public MSession(string? username, string? accessToken, string? uuid) + { + Username = username; + AccessToken = accessToken; + UUID = uuid; + } - [JsonPropertyName("username")] - public string? Username { get; set; } - [JsonPropertyName("session")] - public string? AccessToken { get; set; } - [JsonPropertyName("uuid")] - public string? UUID { get; set; } - [JsonPropertyName("clientToken")] - public string? ClientToken { get; set; } - [JsonPropertyName("userType")] - public string? UserType { get; set; } - [JsonPropertyName("xuid")] - public string? Xuid { get; set; } + [JsonPropertyName("username")] + public string? Username { get; set; } + [JsonPropertyName("session")] + public string? AccessToken { get; set; } + [JsonPropertyName("uuid")] + public string? UUID { get; set; } + [JsonPropertyName("clientToken")] + public string? ClientToken { get; set; } + [JsonPropertyName("userType")] + public string? UserType { get; set; } + [JsonPropertyName("xuid")] + public string? Xuid { get; set; } - public bool CheckIsValid() - { - return !string.IsNullOrEmpty(Username) - && !string.IsNullOrEmpty(AccessToken) - && !string.IsNullOrEmpty(UUID); - } + public bool CheckIsValid() + { + return !string.IsNullOrEmpty(Username) + && !string.IsNullOrEmpty(AccessToken) + && !string.IsNullOrEmpty(UUID); + } - public static MSession GetOfflineSession(string username) + public static MSession GetOfflineSession(string username) + { + return new MSession { - return new MSession - { - Username = username, - AccessToken = "access_token", - UUID = "user_uuid", - UserType = "Mojang", - ClientToken = null - }; - } + Username = username, + AccessToken = "access_token", + UUID = "user_uuid", + UserType = "Mojang", + ClientToken = null + }; } } diff --git a/src/Core/CMLauncher.cs b/src/Core/CMLauncher.cs index 1850c12..9ec60a4 100644 --- a/src/Core/CMLauncher.cs +++ b/src/Core/CMLauncher.cs @@ -3,215 +3,210 @@ using CmlLib.Core.Java; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; -using System; -using System.Collections.Generic; +using CmlLib.Core.VersionMetadata; using System.ComponentModel; using System.Diagnostics; -using System.Threading.Tasks; -namespace CmlLib.Core +namespace CmlLib.Core; + +public class CMLauncher { - public class CMLauncher + private readonly HttpClient _httpClient; + + public CMLauncher(string path, HttpClient httpClient) : this(new MinecraftPath(path), httpClient) { - public CMLauncher(string path) : this(new MinecraftPath(path)) - { - } + } - public CMLauncher(MinecraftPath path) + public CMLauncher(MinecraftPath path, HttpClient httpClient) + { + _httpClient = httpClient; + + MinecraftPath = path; + GameFileCheckers = FileCheckerCollection.CreateDefault(_httpClient); + FileDownloader = new AsyncParallelDownloader(_httpClient); + VersionLoader = new VersionLoaderCollection { - this.MinecraftPath = path; + new LocalVersionLoader(MinecraftPath), + new MojangVersionLoader(_httpClient), + }; - GameFileCheckers = new FileCheckerCollection(); - FileDownloader = new AsyncParallelDownloader(); - VersionLoader = new DefaultVersionLoader(MinecraftPath); + pFileChanged = new Progress( + e => FileChanged?.Invoke(e)); + pProgressChanged = new Progress( + e => ProgressChanged?.Invoke(this, e)); - pFileChanged = new Progress( - e => FileChanged?.Invoke(e)); - pProgressChanged = new Progress( - e => ProgressChanged?.Invoke(this, e)); + // to prevent null-reference warning, set both field, property + JavaPathResolver = new MinecraftJavaPathResolver(path); + } - // to prevent null-reference warning, set both field, property - JavaPathResolver = javaPathResolver = new MinecraftJavaPathResolver(path); - } + public event DownloadFileChangedHandler? FileChanged; + public event ProgressChangedEventHandler? ProgressChanged; + + private readonly IProgress pFileChanged; + private readonly IProgress pProgressChanged; - public event DownloadFileChangedHandler? FileChanged; - public event ProgressChangedEventHandler? ProgressChanged; - - private readonly IProgress pFileChanged; - private readonly IProgress pProgressChanged; + public MinecraftPath MinecraftPath { get; private set; } + public MVersionCollection? Versions { get; private set; } + public IVersionLoader VersionLoader { get; set; } + + public FileCheckerCollection GameFileCheckers { get; private set; } + public IDownloader? FileDownloader { get; set; } - public MinecraftPath MinecraftPath { get; private set; } - public MVersionCollection? Versions { get; private set; } - public IVersionLoader VersionLoader { get; set; } - - public FileCheckerCollection GameFileCheckers { get; private set; } - public IDownloader? FileDownloader { get; set; } - - private IJavaPathResolver javaPathResolver; - public IJavaPathResolver JavaPathResolver { - get => javaPathResolver; - set - { - javaPathResolver = value; - if (GameFileCheckers.JavaFileChecker != null) - GameFileCheckers.JavaFileChecker.JavaPathResolver = value; - } - } + public IJavaPathResolver JavaPathResolver { get; set; } - public async Task GetAllVersionsAsync() - { - Versions = await VersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - return Versions; - } + public async Task GetAllVersionsAsync() + { + Versions = await VersionLoader.GetVersionMetadatasAsync() + .ConfigureAwait(false); + return Versions; + } - public async Task GetVersionAsync(string versionName) - { - if (Versions == null) - await GetAllVersionsAsync().ConfigureAwait(false); + public async Task GetVersionAsync(string versionName) + { + if (Versions == null) + await GetAllVersionsAsync().ConfigureAwait(false); - var version = await Versions!.GetVersionAsync(versionName) - .ConfigureAwait(false); - return version; - } + var version = await Versions!.GetVersionAsync(versionName) + .ConfigureAwait(false); + return version; + } - public DownloadFile[] CheckLostGameFiles(MVersion version) + public DownloadFile[] CheckLostGameFiles(MVersion version) + { + var lostFiles = new List(); + foreach (IFileChecker checker in this.GameFileCheckers) { - var lostFiles = new List(); - foreach (IFileChecker checker in this.GameFileCheckers) - { - DownloadFile[]? files = checker.CheckFiles(MinecraftPath, version, pFileChanged); - if (files != null) - lostFiles.AddRange(files); - } - - return lostFiles.ToArray(); + DownloadFile[]? files = checker.CheckFiles(MinecraftPath, version, pFileChanged); + if (files != null) + lostFiles.AddRange(files); } - public async Task CheckLostGameFilesTaskAsync(MVersion version) - { - var lostFiles = new List(); - foreach (IFileChecker checker in this.GameFileCheckers) - { - DownloadFile[]? files = await checker.CheckFilesTaskAsync(MinecraftPath, version, pFileChanged) - .ConfigureAwait(false); - if (files != null) - lostFiles.AddRange(files); - } - - return lostFiles.ToArray(); - } + return lostFiles.ToArray(); + } - public async Task DownloadGameFiles(DownloadFile[] files) + public async Task CheckLostGameFilesTaskAsync(MVersion version) + { + var lostFiles = new List(); + foreach (IFileChecker checker in this.GameFileCheckers) { - if (this.FileDownloader == null) - return; - - await FileDownloader.DownloadFiles(files, pFileChanged, pProgressChanged) + DownloadFile[]? files = await checker.CheckFilesTaskAsync(MinecraftPath, version, pFileChanged) .ConfigureAwait(false); + if (files != null) + lostFiles.AddRange(files); } - public void CheckAndDownload(MVersion version) - { - foreach (var checker in this.GameFileCheckers) - { - DownloadFile[]? files = checker.CheckFiles(MinecraftPath, version, pFileChanged); + return lostFiles.ToArray(); + } - if (files == null || files.Length == 0) - continue; + public async Task DownloadGameFiles(DownloadFile[] files) + { + if (this.FileDownloader == null) + return; - DownloadGameFiles(files).GetAwaiter().GetResult(); - } - } + await FileDownloader.DownloadFiles(files, pFileChanged, pProgressChanged) + .ConfigureAwait(false); + } - public async Task CheckAndDownloadAsync(MVersion version) + public void CheckAndDownload(MVersion version) + { + foreach (var checker in this.GameFileCheckers) { - foreach (var checker in this.GameFileCheckers) - { - DownloadFile[]? files = await checker.CheckFilesTaskAsync(MinecraftPath, version, pFileChanged) - .ConfigureAwait(false); + DownloadFile[]? files = checker.CheckFiles(MinecraftPath, version, pFileChanged); - if (files == null || files.Length == 0) - continue; + if (files == null || files.Length == 0) + continue; - await DownloadGameFiles(files).ConfigureAwait(false); - } + DownloadGameFiles(files).GetAwaiter().GetResult(); } + } - public Process CreateProcess(MVersion version, MLaunchOption option, bool checkAndDownload=true) + public async Task CheckAndDownloadAsync(MVersion version) + { + foreach (var checker in this.GameFileCheckers) { - option.StartVersion = version; - - if (checkAndDownload) - CheckAndDownload(option.StartVersion); - - return CreateProcess(option); - } + DownloadFile[]? files = await checker.CheckFilesTaskAsync(MinecraftPath, version, pFileChanged) + .ConfigureAwait(false); - public async Task CreateProcessAsync(string versionName, MLaunchOption option, - bool checkAndDownload=true) - { - var version = await GetVersionAsync(versionName).ConfigureAwait(false); - return await CreateProcessAsync(version, option, checkAndDownload).ConfigureAwait(false); - } + if (files == null || files.Length == 0) + continue; - public async Task CreateProcessAsync(MVersion version, MLaunchOption option, - bool checkAndDownload=true) - { - option.StartVersion = version; - - if (checkAndDownload) - await CheckAndDownloadAsync(option.StartVersion).ConfigureAwait(false); - - return await CreateProcessAsync(option).ConfigureAwait(false); + await DownloadGameFiles(files).ConfigureAwait(false); } + } + + public Process CreateProcess(MVersion version, MLaunchOption option, bool checkAndDownload=true) + { + option.StartVersion = version; - public Process CreateProcess(MLaunchOption option) - { - checkLaunchOption(option); - var launch = new MLaunch(option); - return launch.GetProcess(); - } + if (checkAndDownload) + CheckAndDownload(option.StartVersion); - public async Task CreateProcessAsync(MLaunchOption option) - { - checkLaunchOption(option); - var launch = new MLaunch(option); - return await Task.Run(launch.GetProcess).ConfigureAwait(false); - } + return CreateProcess(option); + } - public async Task LaunchAsync(string versionName, MLaunchOption option) - { - Process process = await CreateProcessAsync(versionName, option) - .ConfigureAwait(false); - process.Start(); - return process; - } + public async Task CreateProcessAsync(string versionName, MLaunchOption option, + bool checkAndDownload=true) + { + var version = await GetVersionAsync(versionName).ConfigureAwait(false); + return await CreateProcessAsync(version, option, checkAndDownload).ConfigureAwait(false); + } - private void checkLaunchOption(MLaunchOption option) - { - if (option.Path == null) - option.Path = MinecraftPath; - if (option.StartVersion != null) - { - if (!string.IsNullOrEmpty(option.JavaPath)) - option.StartVersion.JavaBinaryPath = option.JavaPath; - else if (!string.IsNullOrEmpty(option.JavaVersion)) - option.StartVersion.JavaVersion = option.JavaVersion; - else if (string.IsNullOrEmpty(option.StartVersion.JavaBinaryPath)) - option.StartVersion.JavaBinaryPath = - GetJavaPath(option.StartVersion) ?? GetDefaultJavaPath(); - } - } + public async Task CreateProcessAsync(MVersion version, MLaunchOption option, + bool checkAndDownload=true) + { + option.StartVersion = version; + + if (checkAndDownload) + await CheckAndDownloadAsync(option.StartVersion).ConfigureAwait(false); + + return await CreateProcessAsync(option).ConfigureAwait(false); + } + + public Process CreateProcess(MLaunchOption option) + { + checkLaunchOption(option); + var launch = new MLaunch(option); + return launch.GetProcess(); + } + + public async Task CreateProcessAsync(MLaunchOption option) + { + checkLaunchOption(option); + var launch = new MLaunch(option); + return await Task.Run(launch.GetProcess).ConfigureAwait(false); + } - public string? GetJavaPath(MVersion version) - { - if (string.IsNullOrEmpty(version.JavaVersion)) - return null; - - return JavaPathResolver.GetJavaBinaryPath(version.JavaVersion, MRule.OSName); + public async Task LaunchAsync(string versionName, MLaunchOption option) + { + Process process = await CreateProcessAsync(versionName, option) + .ConfigureAwait(false); + process.Start(); + return process; + } + + private void checkLaunchOption(MLaunchOption option) + { + if (option.Path == null) + option.Path = MinecraftPath; + if (option.StartVersion != null) + { + if (!string.IsNullOrEmpty(option.JavaPath)) + option.StartVersion.JavaBinaryPath = option.JavaPath; + else if (!string.IsNullOrEmpty(option.JavaVersion)) + option.StartVersion.JavaVersion = option.JavaVersion; + else if (string.IsNullOrEmpty(option.StartVersion.JavaBinaryPath)) + option.StartVersion.JavaBinaryPath = + GetJavaPath(option.StartVersion) ?? GetDefaultJavaPath(); } + } - public string? GetDefaultJavaPath() => JavaPathResolver.GetDefaultJavaBinaryPath(); + public string? GetJavaPath(MVersion version) + { + if (string.IsNullOrEmpty(version.JavaVersion)) + return null; + + return JavaPathResolver.GetJavaBinaryPath(version.JavaVersion, MRule.OSName); } + + public string? GetDefaultJavaPath() => JavaPathResolver.GetDefaultJavaBinaryPath(); } diff --git a/src/Core/Downloader/AsyncParallelDownloader.cs b/src/Core/Downloader/AsyncParallelDownloader.cs index cf389a2..fb610d1 100644 --- a/src/Core/Downloader/AsyncParallelDownloader.cs +++ b/src/Core/Downloader/AsyncParallelDownloader.cs @@ -1,168 +1,156 @@ -using CmlLib.Utils; -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -namespace CmlLib.Core.Downloader -{ - public class AsyncParallelDownloader : IDownloader - { - private readonly HttpClientDownloadHelper downloader; - - public int MaxThread { get; private set; } - public bool IgnoreInvalidFiles { get; set; } = true; - - private int totalFiles; - private int progressedFiles; +namespace CmlLib.Core.Downloader; - private long totalBytes; - private long receivedBytes; +public class AsyncParallelDownloader : IDownloader +{ + private readonly HttpClientDownloadHelper downloader; - private readonly object progressEventLock = new object(); + public int MaxThread { get; private set; } + public bool IgnoreInvalidFiles { get; set; } = true; - private bool isRunning; + private int totalFiles; + private int progressedFiles; - private IProgress? pChangeProgress; - private IProgress? pChangeFile; - private IProgress fileByteProgress; + private long totalBytes; + private long receivedBytes; - public AsyncParallelDownloader() : this(10, HttpUtil.HttpClient) - { + private readonly object progressEventLock = new object(); - } + private bool isRunning; - public AsyncParallelDownloader(int parallelism, HttpClient httpClient) - { - MaxThread = parallelism; - downloader = new HttpClientDownloadHelper(httpClient); - fileByteProgress = new Progress(byteProgressHandler); - } + private IProgress? pChangeProgress; + private IProgress? pChangeFile; + private IProgress fileByteProgress; - public async Task DownloadFiles(DownloadFile[] files, - IProgress? fileProgress, - IProgress? downloadProgress) - { - if (files.Length == 0) - return; - - if (isRunning) - throw new InvalidOperationException("already downloading"); + public AsyncParallelDownloader(HttpClient httpClient, int parallelism = 10) + { + MaxThread = parallelism; + downloader = new HttpClientDownloadHelper(httpClient); + fileByteProgress = new Progress(byteProgressHandler); + } - isRunning = true; + public async Task DownloadFiles(DownloadFile[] files, + IProgress? fileProgress, + IProgress? downloadProgress) + { + if (files.Length == 0) + return; + + if (isRunning) + throw new InvalidOperationException("already downloading"); - pChangeFile = fileProgress; - pChangeProgress = downloadProgress; - - totalFiles = files.Length; - progressedFiles = 0; + isRunning = true; - totalBytes = 0; - receivedBytes = 0; + pChangeFile = fileProgress; + pChangeProgress = downloadProgress; + + totalFiles = files.Length; + progressedFiles = 0; - foreach (DownloadFile item in files) - { - if (item.Size > 0) - totalBytes += item.Size; - } + totalBytes = 0; + receivedBytes = 0; - fileProgress?.Report( - new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); - await ForEachAsyncSemaphore(files, MaxThread, doDownload).ConfigureAwait(false); - - isRunning = false; + foreach (DownloadFile item in files) + { + if (item.Size > 0) + totalBytes += item.Size; } - private async Task ForEachAsyncSemaphore(IEnumerable source, - int degreeOfParallelism, Func body) + fileProgress?.Report( + new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); + await ForEachAsyncSemaphore(files, MaxThread, doDownload).ConfigureAwait(false); + + isRunning = false; + } + + private async Task ForEachAsyncSemaphore(IEnumerable source, + int degreeOfParallelism, Func body) + { + List tasks = new List(); + using SemaphoreSlim throttler = new SemaphoreSlim(degreeOfParallelism); + foreach (var element in source) { - List tasks = new List(); - using SemaphoreSlim throttler = new SemaphoreSlim(degreeOfParallelism); - foreach (var element in source) - { - await throttler.WaitAsync().ConfigureAwait(false); + await throttler.WaitAsync().ConfigureAwait(false); - async Task work(T item) + async Task work(T item) + { + try + { + await body(item).ConfigureAwait(false); + } + finally { - try - { - await body(item).ConfigureAwait(false); - } - finally - { - throttler.Release(); - } + throttler.Release(); } - - tasks.Add(work(element)); } - await Task.WhenAll(tasks).ConfigureAwait(false); + + tasks.Add(work(element)); } + await Task.WhenAll(tasks).ConfigureAwait(false); + } - private async Task doDownload(DownloadFile file) + private async Task doDownload(DownloadFile file) + { + try { - try - { - await doDownload(file, 3).ConfigureAwait(false); - } - catch (Exception ex) - { - if (!IgnoreInvalidFiles) - throw new MDownloadFileException("failed to download", ex, file); - } + await doDownload(file, 3).ConfigureAwait(false); } + catch (Exception ex) + { + if (!IgnoreInvalidFiles) + throw new MDownloadFileException("failed to download", ex, file); + } + } - private async Task doDownload(DownloadFile file, int retry) + private async Task doDownload(DownloadFile file, int retry) + { + try { - try - { - await downloader.DownloadFileAsync(file, fileByteProgress).ConfigureAwait(false); + await downloader.DownloadFileAsync(file, fileByteProgress).ConfigureAwait(false); - if (file.AfterDownload != null) + if (file.AfterDownload != null) + { + foreach (var item in file.AfterDownload) { - foreach (var item in file.AfterDownload) - { - await item.Invoke().ConfigureAwait(false); - } + await item.Invoke().ConfigureAwait(false); } - - Interlocked.Increment(ref progressedFiles); - pChangeFile?.Report( - new DownloadFileChangedEventArgs(file.Type, this, file.Name, totalFiles, progressedFiles)); } - catch (Exception ex) - { - if (retry <= 0) - return; - Debug.WriteLine(ex); - retry--; + Interlocked.Increment(ref progressedFiles); + pChangeFile?.Report( + new DownloadFileChangedEventArgs(file.Type, this, file.Name, totalFiles, progressedFiles)); + } + catch (Exception ex) + { + if (retry <= 0) + return; - await doDownload(file, retry).ConfigureAwait(false); - } + Debug.WriteLine(ex); + retry--; + + await doDownload(file, retry).ConfigureAwait(false); } + } - private void byteProgressHandler(DownloadFileByteProgress progress) + private void byteProgressHandler(DownloadFileByteProgress progress) + { + lock (progressEventLock) { - lock (progressEventLock) + if (progress.File != null && progress.File.Size <= 0) { - if (progress.File != null && progress.File.Size <= 0) - { - totalBytes += progress.TotalBytes; - progress.File.Size = progress.TotalBytes; - } + totalBytes += progress.TotalBytes; + progress.File.Size = progress.TotalBytes; + } - receivedBytes += progress.ProgressedBytes; + receivedBytes += progress.ProgressedBytes; - if (receivedBytes > totalBytes) - return; + if (receivedBytes > totalBytes) + return; - double percent = (double)receivedBytes / totalBytes * 100; - pChangeProgress?.Report(new FileProgressChangedEventArgs(totalBytes, receivedBytes, (int)percent)); - } + double percent = (double)receivedBytes / totalBytes * 100; + pChangeProgress?.Report(new FileProgressChangedEventArgs(totalBytes, receivedBytes, (int)percent)); } } } diff --git a/src/Core/Downloader/SequenceDownloader.cs b/src/Core/Downloader/SequenceDownloader.cs index 82aa964..e9b675f 100644 --- a/src/Core/Downloader/SequenceDownloader.cs +++ b/src/Core/Downloader/SequenceDownloader.cs @@ -1,69 +1,59 @@ -using CmlLib.Utils; -using System; -using System.ComponentModel; -using System.Net.Http; -using System.Threading.Tasks; +using System.ComponentModel; -namespace CmlLib.Core.Downloader +namespace CmlLib.Core.Downloader; + +public class SequenceDownloader : IDownloader { - public class SequenceDownloader : IDownloader - { - private readonly HttpClientDownloadHelper downloader; + private readonly HttpClientDownloadHelper downloader; - public bool IgnoreInvalidFiles { get; set; } = true; + public bool IgnoreInvalidFiles { get; set; } = true; - public SequenceDownloader() : this(HttpUtil.HttpClient) - { - - } + public SequenceDownloader(HttpClient client) + { + downloader = new HttpClientDownloadHelper(client); + } - public SequenceDownloader(HttpClient client) - { - downloader = new HttpClientDownloadHelper(client); - } + public async Task DownloadFiles(DownloadFile[] files, + IProgress? fileProgress, + IProgress? downloadProgress) + { + if (files.Length == 0) + return; - public async Task DownloadFiles(DownloadFile[] files, - IProgress? fileProgress, - IProgress? downloadProgress) + var byteProgress = new Progress(progress => { - if (files.Length == 0) - return; + var percent = (float)progress.ProgressedBytes / progress.TotalBytes * 100; + downloadProgress?.Report(new ProgressChangedEventArgs((int)percent, null)); + }); - var byteProgress = new Progress(progress => - { - var percent = (float)progress.ProgressedBytes / progress.TotalBytes * 100; - downloadProgress?.Report(new ProgressChangedEventArgs((int)percent, null)); - }); + fileProgress?.Report( + new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); - fileProgress?.Report( - new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); + for (int i = 0; i < files.Length; i++) + { + DownloadFile file = files[i]; - for (int i = 0; i < files.Length; i++) + try { - DownloadFile file = files[i]; + await downloader.DownloadFileAsync(file, byteProgress).ConfigureAwait(false); - try + if (file.AfterDownload != null) { - await downloader.DownloadFileAsync(file, byteProgress).ConfigureAwait(false); - - if (file.AfterDownload != null) + foreach (var item in file.AfterDownload) { - foreach (var item in file.AfterDownload) - { - await item().ConfigureAwait(false); - } + await item().ConfigureAwait(false); } - - fileProgress?.Report( - new DownloadFileChangedEventArgs(file.Type, this, file.Name, files.Length, i)); } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex.ToString()); + + fileProgress?.Report( + new DownloadFileChangedEventArgs(file.Type, this, file.Name, files.Length, i)); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.ToString()); - if (!IgnoreInvalidFiles) - throw new MDownloadFileException(ex.Message, ex, files[i]); - } + if (!IgnoreInvalidFiles) + throw new MDownloadFileException(ex.Message, ex, files[i]); } } } diff --git a/src/Core/FileChecker/AssetChecker.cs b/src/Core/FileChecker/AssetChecker.cs index 4a359eb..76b5543 100644 --- a/src/Core/FileChecker/AssetChecker.cs +++ b/src/Core/FileChecker/AssetChecker.cs @@ -1,195 +1,183 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; +using System.Diagnostics; using System.Text.Json; -using System.Threading.Tasks; using CmlLib.Core.Downloader; using CmlLib.Core.Files; using CmlLib.Core.Version; using CmlLib.Utils; -namespace CmlLib.Core.FileChecker +namespace CmlLib.Core.FileChecker; + +public sealed class AssetChecker : IFileChecker { - public sealed class AssetChecker : IFileChecker + private readonly HttpClient httpClient; + + public AssetChecker(HttpClient client) { - private readonly HttpClient httpClient; + this.httpClient = client; + } - public AssetChecker() : this(HttpUtil.HttpClient) + private string assetServer = MojangServer.ResourceDownload; + public string AssetServer + { + get => assetServer; + set { - + if (value.Last() == '/') + assetServer = value; + else + assetServer = value + "/"; } + } + public bool CheckHash { get; set; } = true; - public AssetChecker(HttpClient client) - { - this.httpClient = client; - } + public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, + IProgress? progress) + { + if (version.Assets == null) + return null; + checkIndex(path, version.Assets).GetAwaiter().GetResult(); + return CheckAssetFiles(path, version.Assets, progress); + } - private string assetServer = MojangServer.ResourceDownload; - public string AssetServer - { - get => assetServer; - set - { - if (value.Last() == '/') - assetServer = value; - else - assetServer = value + "/"; - } - } - public bool CheckHash { get; set; } = true; + public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, + IProgress? progress) + { + if (version.Assets == null) + return null; - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? progress) - { - if (version.Assets == null) - return null; - checkIndex(path, version.Assets).GetAwaiter().GetResult(); - return CheckAssetFiles(path, version.Assets, progress); - } + await checkIndex(path, version.Assets).ConfigureAwait(false); + return await Task.Run(() => CheckAssetFiles(path, version.Assets, progress)).ConfigureAwait(false); + } - public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? progress) - { - if (version.Assets == null) - return null; + // Check index file validation and download + private async Task checkIndex(MinecraftPath path, MFileMetadata assets) + { + if (string.IsNullOrEmpty(assets.Id) || string.IsNullOrEmpty(assets.Url)) + return; - await checkIndex(path, version.Assets).ConfigureAwait(false); - return await Task.Run(() => CheckAssetFiles(path, version.Assets, progress)).ConfigureAwait(false); - } + var indexFilePath = path.GetIndexFilePath(assets.Id); - // Check index file validation and download - private async Task checkIndex(MinecraftPath path, MFileMetadata assets) + if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, CheckHash)) + return; + + IOUtil.CreateParentDirectory(indexFilePath); + + var downloader = new HttpClientDownloadHelper(httpClient); + await downloader.DownloadFileAsync(new DownloadFile(indexFilePath, assets.Url)).ConfigureAwait(false); + } + + public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, + IProgress? progress) + { + if (string.IsNullOrEmpty(assets.Id)) + return null; + + var indexFilePath = path.GetIndexFilePath(assets.Id); + if (!File.Exists(indexFilePath)) + return null; + + var indexFileContent = File.ReadAllText(indexFilePath); + using var index = JsonDocument.Parse(indexFileContent); + var root = index.RootElement; + + var listProperty = root.GetPropertyOrNull("objects"); + if (!listProperty.HasValue) + return null; + var list = listProperty.Value; + + var isVirtual = root.GetPropertyOrNull("virtual")?.GetBoolean() ?? false; + var mapResource = root.GetPropertyOrNull("map_to_resources")?.GetBoolean() ?? false; + + var objects = list.EnumerateObject(); + var totalObject = objects.Count(); + objects.Reset(); + var downloadRequiredFiles = new List(totalObject); + + int progressed = 0; + foreach (var prop in objects) { - if (string.IsNullOrEmpty(assets.Id) || string.IsNullOrEmpty(assets.Url)) - return; + var f = checkAssetFile(prop.Name, prop.Value, path, assets, isVirtual, mapResource); - var indexFilePath = path.GetIndexFilePath(assets.Id); + if (f != null) + downloadRequiredFiles.Add(f); - if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, CheckHash)) - return; - - IOUtil.CreateParentDirectory(indexFilePath); + progressed++; - var downloader = new HttpClientDownloadHelper(httpClient); - await downloader.DownloadFileAsync(new DownloadFile(indexFilePath, assets.Url)).ConfigureAwait(false); + if (progressed % 50 == 0) // prevent ui freezing + progress?.Report( + new DownloadFileChangedEventArgs(MFile.Resource, this, "", totalObject, progressed)); } - public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, - IProgress? progress) - { - if (string.IsNullOrEmpty(assets.Id)) - return null; - - var indexFilePath = path.GetIndexFilePath(assets.Id); - if (!File.Exists(indexFilePath)) - return null; - - var indexFileContent = File.ReadAllText(indexFilePath); - using var index = JsonDocument.Parse(indexFileContent); - var root = index.RootElement; - - var listProperty = root.SafeGetProperty("objects"); - if (!listProperty.HasValue) - return null; - var list = listProperty.Value; - - var isVirtual = root.SafeGetProperty("virtual")?.GetBoolean() ?? false; - var mapResource = root.SafeGetProperty("map_to_resources")?.GetBoolean() ?? false; - - var objects = list.EnumerateObject(); - var totalObject = objects.Count(); - objects.Reset(); - var downloadRequiredFiles = new List(totalObject); - - int progressed = 0; - foreach (var prop in objects) - { - var f = checkAssetFile(prop.Name, prop.Value, path, assets, isVirtual, mapResource); + return downloadRequiredFiles.Distinct().ToArray(); // 10ms + } - if (f != null) - downloadRequiredFiles.Add(f); + private DownloadFile? checkAssetFile(string key, JsonElement element, MinecraftPath path, MFileMetadata assets, + bool isVirtual, bool mapResource) + { + if (string.IsNullOrEmpty(assets.Id)) + return null; + + var hash = element.GetPropertyValue("hash"); + if (hash == null) + return null; - progressed++; + var hashName = hash.Substring(0, 2) + "/" + hash; + var hashPath = Path.Combine(path.GetAssetObjectPath(assets.Id), hashName); - if (progressed % 50 == 0) // prevent ui freezing - progress?.Report( - new DownloadFileChangedEventArgs(MFile.Resource, this, "", totalObject, progressed)); - } + long size = element.GetPropertyOrNull("size")?.GetInt64() ?? 0; - return downloadRequiredFiles.Distinct().ToArray(); // 10ms - } + var afterDownload = new List>(1); - private DownloadFile? checkAssetFile(string key, JsonElement element, MinecraftPath path, MFileMetadata assets, - bool isVirtual, bool mapResource) + if (isVirtual) { - if (string.IsNullOrEmpty(assets.Id)) - return null; - - var hash = element.GetPropertyValue("hash"); - if (hash == null) - return null; - - var hashName = hash.Substring(0, 2) + "/" + hash; - var hashPath = Path.Combine(path.GetAssetObjectPath(assets.Id), hashName); - - long size = element.SafeGetProperty("size")?.GetInt64() ?? 0; - - var afterDownload = new List>(1); + var resPath = Path.Combine(path.GetAssetLegacyPath(assets.Id), key); + afterDownload.Add(() => assetCopy(hashPath, resPath)); + } - if (isVirtual) - { - var resPath = Path.Combine(path.GetAssetLegacyPath(assets.Id), key); - afterDownload.Add(() => assetCopy(hashPath, resPath)); - } + if (mapResource) + { + var desPath = Path.Combine(path.Resource, key); + afterDownload.Add(() => assetCopy(hashPath, desPath)); + } - if (mapResource) + if (!IOUtil.CheckFileValidation(hashPath, hash, CheckHash)) + { + string hashUrl = AssetServer + hashName; + return new DownloadFile(hashPath, hashUrl) { - var desPath = Path.Combine(path.Resource, key); - afterDownload.Add(() => assetCopy(hashPath, desPath)); - } - - if (!IOUtil.CheckFileValidation(hashPath, hash, CheckHash)) + Type = MFile.Resource, + Name = key, + Size = size, + AfterDownload = afterDownload.ToArray() + }; + } + else + { + foreach (var item in afterDownload) { - string hashUrl = AssetServer + hashName; - return new DownloadFile(hashPath, hashUrl) - { - Type = MFile.Resource, - Name = key, - Size = size, - AfterDownload = afterDownload.ToArray() - }; + item().GetAwaiter().GetResult(); } - else - { - foreach (var item in afterDownload) - { - item().GetAwaiter().GetResult(); - } - return null; - } + return null; } + } - private async Task assetCopy(string org, string des) + private async Task assetCopy(string org, string des) + { + try { - try - { - var orgFile = new FileInfo(org); - var desFile = new FileInfo(des); - - if (!desFile.Exists || orgFile.Length != desFile.Length) - { - IOUtil.CreateParentDirectory(des); - await IOUtil.CopyFileAsync(org, des); - } - } - catch (Exception ex) + var orgFile = new FileInfo(org); + var desFile = new FileInfo(des); + + if (!desFile.Exists || orgFile.Length != desFile.Length) { - Debug.WriteLine(ex); + IOUtil.CreateParentDirectory(des); + await IOUtil.CopyFileAsync(org, des); } } + catch (Exception ex) + { + Debug.WriteLine(ex); + } } } diff --git a/src/Core/FileChecker/FileCheckerCollection.cs b/src/Core/FileChecker/FileCheckerCollection.cs index 25ed391..ff69d1e 100644 --- a/src/Core/FileChecker/FileCheckerCollection.cs +++ b/src/Core/FileChecker/FileCheckerCollection.cs @@ -1,169 +1,74 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; -namespace CmlLib.Core.FileChecker +namespace CmlLib.Core.FileChecker; + +public class FileCheckerCollection : IEnumerable { - public class FileCheckerCollection : IEnumerable + public static FileCheckerCollection CreateDefault(HttpClient httpClient) { - public IFileChecker this[int index] => checkers[index]; - - private readonly List checkers; - - private AssetChecker? asset; - public AssetChecker? AssetFileChecker - { - get => asset; - set - { - if (asset != null) - checkers.Remove(asset); - - asset = value; - - if (asset != null) - checkers.Add(asset); - } - } - - private ClientChecker? client; - public ClientChecker? ClientFileChecker - { - get => client; - set - { - if (client != null) - checkers.Remove(client); - - client = value; - - if (client != null) - checkers.Add(client); - } - } - - private LibraryChecker? library; - public LibraryChecker? LibraryFileChecker - { - get => library; - set - { - if (library != null) - checkers.Remove(library); - - library = value; - - if (library != null) - checkers.Add(library); - } - } + var checkers = new FileCheckerCollection(); - private JavaChecker? java; + var library = new LibraryChecker(); + var asset = new AssetChecker(httpClient); + var client = new ClientChecker(); + var java = new JavaChecker(httpClient); + var log = new LogChecker(); - public JavaChecker? JavaFileChecker - { - get => java; - set - { - if (java != null) - checkers.Remove(java); - - java = value; - - if (java != null) - checkers.Add(java); - } - } - - private LogChecker? log; - public LogChecker? LogFileChecker - { - get => log; - set - { - if (log != null) - checkers.Remove(log); - - log = value; - - if (log != null) - checkers.Add(log); - } - } + checkers.Add(library); + checkers.Add(asset); + checkers.Add(client); + checkers.Add(log); + checkers.Add(java); - public FileCheckerCollection() - { - checkers = new List(4); + return checkers; + } - library = new LibraryChecker(); - asset = new AssetChecker(); - client = new ClientChecker(); - java = new JavaChecker(); - log = new LogChecker(); + public IFileChecker this[int index] => checkers[index]; - checkers.Add(library); - checkers.Add(asset); - checkers.Add(client); - checkers.Add(log); - checkers.Add(java); - } - - public void Add(IFileChecker item) - { - CheckArgument(item); - checkers.Add(item); - } + private readonly List checkers; - public void AddRange(IEnumerable items) - { - foreach (IFileChecker? item in items) - { - if (item != null) - Add(item); - } - } + public FileCheckerCollection() + { + checkers = new List(4); + } - public void Remove(IFileChecker item) - { - CheckArgument(item); - checkers.Remove(item); - } + public void Add(IFileChecker item) + { + checkers.Add(item); + } - public void RemoveAt(int index) + public void AddRange(IEnumerable items) + { + foreach (IFileChecker? item in items) { - IFileChecker item = checkers[index]; - Remove(item); + if (item != null) + Add(item); } + } - public void Insert(int index, IFileChecker item) - { - CheckArgument(item); - checkers.Insert(index, item); - } + public void Remove(IFileChecker item) + { + checkers.Remove(item); + } - private void CheckArgument(IFileChecker item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); + public void RemoveAt(int index) + { + IFileChecker item = checkers[index]; + Remove(item); + } - if (item is LibraryChecker) - throw new ArgumentException($"Set {nameof(LibraryFileChecker)} property."); - if (item is AssetChecker) - throw new ArgumentException($"Set {nameof(AssetFileChecker)} property."); - if (item is ClientChecker) - throw new ArgumentException($"Set {nameof(ClientFileChecker)} property."); - if (item is JavaChecker) - throw new ArgumentException($"Set {nameof(JavaFileChecker)} property."); - } + public void Insert(int index, IFileChecker item) + { + checkers.Insert(index, item); + } - public IEnumerator GetEnumerator() - { - return checkers.GetEnumerator(); - } + public IEnumerator GetEnumerator() + { + return checkers.GetEnumerator(); + } - IEnumerator IEnumerable.GetEnumerator() - { - return checkers.GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return checkers.GetEnumerator(); } } diff --git a/src/Core/FileChecker/JavaChecker.cs b/src/Core/FileChecker/JavaChecker.cs index ced3bf7..f7ae2dc 100644 --- a/src/Core/FileChecker/JavaChecker.cs +++ b/src/Core/FileChecker/JavaChecker.cs @@ -1,297 +1,294 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; +using System.Diagnostics; using System.Text.Json; -using System.Threading.Tasks; using CmlLib.Core.Downloader; using CmlLib.Core.Installer; using CmlLib.Core.Java; using CmlLib.Core.Version; using CmlLib.Utils; -namespace CmlLib.Core.FileChecker +namespace CmlLib.Core.FileChecker; + +public class JavaChecker : IFileChecker { - public class JavaChecker : IFileChecker + class JavaCheckResult { - class JavaCheckResult - { - public string? JavaBinaryPath { get; set; } - public DownloadFile[]? JavaFiles { get; set; } - } + public string? JavaBinaryPath { get; set; } + public DownloadFile[]? JavaFiles { get; set; } + } - public IJavaPathResolver? JavaPathResolver { get; set; } - public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; - public bool CheckHash { get; set; } = true; + private readonly HttpClient _httpClient; - public JavaChecker() - { + public IJavaPathResolver? JavaPathResolver { get; set; } + public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; + public bool CheckHash { get; set; } = true; - } + public JavaChecker(HttpClient httpClient) + { + _httpClient = httpClient; + } - public JavaChecker(IJavaPathResolver javaPathResolver) - { - JavaPathResolver = javaPathResolver; - } + public JavaChecker(HttpClient httpClient, IJavaPathResolver javaPathResolver) + { + _httpClient = httpClient; + JavaPathResolver = javaPathResolver; + } - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? downloadProgress) - { - return CheckFilesTaskAsync(path, version, downloadProgress) - .ConfigureAwait(false) - .GetAwaiter().GetResult(); - } + public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, + IProgress? downloadProgress) + { + return CheckFilesTaskAsync(path, version, downloadProgress) + .ConfigureAwait(false) + .GetAwaiter().GetResult(); + } - public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? downloadProgress) - { - if (!string.IsNullOrEmpty(version.JavaBinaryPath) && File.Exists(version.JavaBinaryPath)) - return null; - - var javaVersion = version.JavaVersion; - if (string.IsNullOrEmpty(javaVersion)) - javaVersion = MinecraftJavaPathResolver.JreLegacyVersionName; + public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, + IProgress? downloadProgress) + { + if (!string.IsNullOrEmpty(version.JavaBinaryPath) && File.Exists(version.JavaBinaryPath)) + return null; + + var javaVersion = version.JavaVersion; + if (string.IsNullOrEmpty(javaVersion)) + javaVersion = MinecraftJavaPathResolver.JreLegacyVersionName; - var result = await internalCheckJava(javaVersion, downloadProgress) - .ConfigureAwait(false); + var result = await internalCheckJava(javaVersion, downloadProgress) + .ConfigureAwait(false); - version.JavaBinaryPath = result.JavaBinaryPath; - return result.JavaFiles; - } + version.JavaBinaryPath = result.JavaBinaryPath; + return result.JavaFiles; + } - private async Task internalCheckJava(string javaVersion, - IProgress? downloadProgress) - { - if (JavaPathResolver == null) - throw new InvalidOperationException("JavaPathResolver was null"); + private async Task internalCheckJava(string javaVersion, + IProgress? downloadProgress) + { + if (JavaPathResolver == null) + throw new InvalidOperationException("JavaPathResolver was null"); - var binPath = JavaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); - DownloadFile[]? downloadFiles; + var binPath = JavaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); + DownloadFile[]? downloadFiles; - try + try + { + var osName = getJavaOSName(); // safe + + var response = await _httpClient.GetAsync(JavaManifestServer); // get all java versions + var str = await response.Content.ReadAsStringAsync(); + using var jsonDocument = JsonDocument.Parse(str); + + var root = jsonDocument.RootElement; + var javaVersions = root.GetPropertyOrNull(osName); // get os specific java version + + // try three versions: latest, JreLegacyVersionName(jre-legacy), legacy java (MJava) + if (javaVersions != null) { - var osName = getJavaOSName(); // safe - - var response = await HttpUtil.HttpClient.GetAsync(JavaManifestServer); // get all java versions - var str = await response.Content.ReadAsStringAsync(); - using var jsonDocument = JsonDocument.Parse(str); - - var root = jsonDocument.RootElement; - var javaVersions = root.SafeGetProperty(osName); // get os specific java version - - // try three versions: latest, JreLegacyVersionName(jre-legacy), legacy java (MJava) - if (javaVersions != null) - { - var javaManifest = await getJavaVersionManifest(javaVersions.Value, javaVersion); + var javaManifest = await getJavaVersionManifest(javaVersions.Value, javaVersion); - if (javaManifest == null && javaVersion != MinecraftJavaPathResolver.JreLegacyVersionName) - javaManifest = await getJavaVersionManifest(javaVersions.Value, MinecraftJavaPathResolver.JreLegacyVersionName); - if (javaManifest == null) - return await legacyJavaChecker(); - - // get file objects - using var manifestDocument = JsonDocument.Parse(javaManifest); - var files = manifestDocument.RootElement.SafeGetProperty("files"); - if (files == null) - return await legacyJavaChecker(); - - downloadFiles = toDownloadFiles(files.Value, JavaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); - } - else // no java version for osName + if (javaManifest == null && javaVersion != MinecraftJavaPathResolver.JreLegacyVersionName) + javaManifest = await getJavaVersionManifest(javaVersions.Value, MinecraftJavaPathResolver.JreLegacyVersionName); + if (javaManifest == null) return await legacyJavaChecker(); - } - catch (Exception e) - { - Debug.WriteLine(e); - if (string.IsNullOrEmpty(binPath)) + // get file objects + using var manifestDocument = JsonDocument.Parse(javaManifest); + var files = manifestDocument.RootElement.GetPropertyOrNull("files"); + if (files == null) return await legacyJavaChecker(); - else - downloadFiles = new DownloadFile[] { }; + + downloadFiles = toDownloadFiles(files.Value, JavaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); } + else // no java version for osName + return await legacyJavaChecker(); + } + catch (Exception e) + { + Debug.WriteLine(e); - return new JavaCheckResult() - { - JavaFiles = downloadFiles, - JavaBinaryPath = binPath - }; + if (string.IsNullOrEmpty(binPath)) + return await legacyJavaChecker(); + else + downloadFiles = new DownloadFile[] { }; } - private string getJavaOSName() + return new JavaCheckResult() { - string osName = ""; - - if (MRule.OSName == MRule.Windows) - { - if (MRule.Arch == "64") - osName = "windows-x64"; - else - osName = "windows-x86"; - } - else if (MRule.OSName == MRule.Linux) - { - if (MRule.Arch == "64") - osName = "linux"; - else - osName = "linux-i386"; - } - else if (MRule.OSName == MRule.OSX) - { - osName = "mac-os"; - } + JavaFiles = downloadFiles, + JavaBinaryPath = binPath + }; + } - return osName; - } + private string getJavaOSName() + { + string osName = ""; - // get java files of the specific version - private async Task getJavaVersionManifest(JsonElement job, string version) + if (MRule.OSName == MRule.Windows) + { + if (MRule.Arch == "64") + osName = "windows-x64"; + else + osName = "windows-x86"; + } + else if (MRule.OSName == MRule.Linux) + { + if (MRule.Arch == "64") + osName = "linux"; + else + osName = "linux-i386"; + } + else if (MRule.OSName == MRule.OSX) { - var versionArr = job.SafeGetProperty(version)?.EnumerateArray(); - if (versionArr == null) - return null; + osName = "mac-os"; + } - var firstManifest = versionArr.Value.FirstOrDefault(); - var manifestUrl = firstManifest.SafeGetProperty("manifest")?.SafeGetProperty("url")?.GetString(); - if (string.IsNullOrEmpty(manifestUrl)) - return null; + return osName; + } + + // get java files of the specific version + private async Task getJavaVersionManifest(JsonElement job, string version) + { + var versionArr = job.GetPropertyOrNull(version)?.EnumerateArray(); + if (versionArr == null) + return null; - return await HttpUtil.HttpClient.GetStringAsync(manifestUrl); - } + var firstManifest = versionArr.Value.FirstOrDefault(); + var manifestUrl = firstManifest.GetPropertyOrNull("manifest")?.GetPropertyOrNull("url")?.GetString(); + if (string.IsNullOrEmpty(manifestUrl)) + return null; + + return await _httpClient.GetStringAsync(manifestUrl); + } + + // compare local files with `manifest` + private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, + IProgress? progress) + { + var objects = manifest.EnumerateObject(); + var total = objects.Count(); + objects.Reset(); - // compare local files with `manifest` - private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, - IProgress? progress) + var progressed = 0; + var files = new List(total); + foreach (var prop in objects) { - var objects = manifest.EnumerateObject(); - var total = objects.Count(); - objects.Reset(); + var name = prop.Name; + var value = prop.Value; - var progressed = 0; - var files = new List(total); - foreach (var prop in objects) + var type = value.GetPropertyValue("type"); + if (type == "file") { - var name = prop.Name; - var value = prop.Value; - - var type = value.GetPropertyValue("type"); - if (type == "file") - { - var filePath = Path.Combine(path, name); - filePath = IOUtil.NormalizePath(filePath); + var filePath = Path.Combine(path, name); + filePath = IOUtil.NormalizePath(filePath); - var executable = value.SafeGetProperty("executable")?.GetBoolean() ?? false; - - var file = checkJavaFile(value, filePath); - if (file != null) - { - file.Name = name; - if (executable) - file.AfterDownload = new Func[] - { - () => Task.Run(() => tryChmod755(filePath)) - }; - files.Add(file); - } - } - else + var executable = value.GetPropertyOrNull("executable")?.GetBoolean() ?? false; + + var file = checkJavaFile(value, filePath); + if (file != null) { - if (type != "directory") - Debug.WriteLine(type); + file.Name = name; + if (executable) + file.AfterDownload = new Func[] + { + () => Task.Run(() => tryChmod755(filePath)) + }; + files.Add(file); } - - progressed++; - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Runtime, this, name, total: total, progressed: progressed)); } - return files.ToArray(); + else + { + if (type != "directory") + Debug.WriteLine(type); + } + + progressed++; + progress?.Report(new DownloadFileChangedEventArgs( + MFile.Runtime, this, name, total: total, progressed: progressed)); } + return files.ToArray(); + } - private DownloadFile? checkJavaFile(JsonElement value, string filePath) - { - var downloadObj = value.SafeGetProperty("downloads")?.SafeGetProperty("raw"); - if (downloadObj == null) - return null; + private DownloadFile? checkJavaFile(JsonElement value, string filePath) + { + var downloadObj = value.GetPropertyOrNull("downloads")?.GetPropertyOrNull("raw"); + if (downloadObj == null) + return null; - var url = downloadObj.Value.GetPropertyValue("url"); - if (string.IsNullOrEmpty(url)) - return null; + var url = downloadObj.Value.GetPropertyValue("url"); + if (string.IsNullOrEmpty(url)) + return null; - var hash = downloadObj.Value.GetPropertyValue("sha1"); - var size = downloadObj.Value.SafeGetProperty("size")?.GetInt64() ?? 0; + var hash = downloadObj.Value.GetPropertyValue("sha1"); + var size = downloadObj.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; - if (IOUtil.CheckFileValidation(filePath, hash, CheckHash)) - return null; - - return new DownloadFile(filePath, url) - { - Size = size - }; - } + if (IOUtil.CheckFileValidation(filePath, hash, CheckHash)) + return null; + + return new DownloadFile(filePath, url) + { + Size = size + }; + } - // legacy java checker that use MJava - private async Task legacyJavaChecker() + // legacy java checker that use MJava + private async Task legacyJavaChecker() + { + if (JavaPathResolver == null) + throw new InvalidOperationException("JavaPathResolver was null"); + + string legacyJavaPath = JavaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersionName); + + MJava mJava = new MJava(_httpClient, legacyJavaPath); + var result = new JavaCheckResult() { - if (JavaPathResolver == null) - throw new InvalidOperationException("JavaPathResolver was null"); + JavaBinaryPath = mJava.GetBinaryPath(), + JavaFiles = null + }; + + try + { + if (mJava.CheckJavaExistence()) + return result; - string legacyJavaPath = JavaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersionName); - - MJava mJava = new MJava(legacyJavaPath); - var result = new JavaCheckResult() + string javaUrl = await mJava.GetJavaUrlAsync(); + string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); + string zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); + + DownloadFile file = new DownloadFile(lzmaPath, javaUrl) { - JavaBinaryPath = mJava.GetBinaryPath(), - JavaFiles = null - }; - - try - { - if (mJava.CheckJavaExistence()) - return result; - - string javaUrl = await mJava.GetJavaUrlAsync(); - string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - string zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); - - DownloadFile file = new DownloadFile(lzmaPath, javaUrl) + Name = "jre.lzma", + Type = MFile.Runtime, + AfterDownload = new Func[] { - Name = "jre.lzma", - Type = MFile.Runtime, - AfterDownload = new Func[] + () => Task.Run(() => { - () => Task.Run(() => - { - SevenZipWrapper.DecompressFileLZMA(lzmaPath, zipPath); + SevenZipWrapper.DecompressFileLZMA(lzmaPath, zipPath); - var z = new SharpZip(zipPath); - z.Unzip(legacyJavaPath); + var z = new SharpZip(zipPath); + z.Unzip(legacyJavaPath); - tryChmod755(mJava.GetBinaryPath()); - }) - } - }; - result.JavaFiles = new[] { file }; + tryChmod755(mJava.GetBinaryPath()); + }) + } + }; + result.JavaFiles = new[] { file }; - return result; - } - catch (Exception e) - { - Debug.WriteLine(e); - return result; - } + return result; } + catch (Exception e) + { + Debug.WriteLine(e); + return result; + } + } - private void tryChmod755(string path) + private void tryChmod755(string path) + { + try { - try - { - if (MRule.OSName != MRule.Windows) - NativeMethods.Chmod(path, NativeMethods.Chmod755); - } - catch (Exception e) - { - Debug.WriteLine(e); - } + if (MRule.OSName != MRule.Windows) + NativeMethods.Chmod(path, NativeMethods.Chmod755); + } + catch (Exception e) + { + Debug.WriteLine(e); } } } \ No newline at end of file diff --git a/src/Core/Files/MLibraryParser.cs b/src/Core/Files/MLibraryParser.cs index 340c31a..e5b05be 100644 --- a/src/Core/Files/MLibraryParser.cs +++ b/src/Core/Files/MLibraryParser.cs @@ -1,105 +1,101 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; +using System.Text.Json; using CmlLib.Utils; -namespace CmlLib.Core.Files +namespace CmlLib.Core.Files; + +public class MLibraryParser { - public class MLibraryParser - { - public bool CheckOSRules { get; set; } = true; + public bool CheckOSRules { get; set; } = true; - public MLibrary[]? ParseJsonObject(JsonElement item) + public MLibrary[]? ParseJsonObject(JsonElement item) + { + try { - try - { - var list = new List(); + var list = new List(); - var name = item.GetPropertyValue("name"); - var isRequire = true; + var name = item.GetPropertyValue("name"); + var isRequire = true; - // check rules array - if (CheckOSRules && item.TryGetProperty("rules", out var rules)) - isRequire = MRule.CheckOSRequire(rules); + // check rules array + if (CheckOSRules && item.TryGetProperty("rules", out var rules)) + isRequire = MRule.CheckOSRequire(rules); - // forge clientreq - var req = item.GetPropertyValue("clientreq"); - if (req != null && req.ToLower() != "true") - isRequire = false; + // forge clientreq + var req = item.GetPropertyValue("clientreq"); + if (req != null && req.ToLower() != "true") + isRequire = false; - // support TLauncher - var artifact = item.SafeGetProperty("artifact") - ?? item.SafeGetProperty("downloads")?.SafeGetProperty("artifact"); - var classifiers = item.SafeGetProperty("classifies") - ?? item.SafeGetProperty("downloads")?.SafeGetProperty("classifiers"); - - // NATIVE library - var natives = item.SafeGetProperty("natives"); - if (natives != null) - { - var nativeId = natives.Value.GetPropertyValue(MRule.OSName)? - .Replace("${arch}", MRule.Arch); - - if (classifiers != null && nativeId != null) - { - var lObj = classifiers.Value.SafeGetProperty(nativeId) ?? classifiers.Value.SafeGetProperty(MRule.OSName); - if (lObj != null) - list.Add(createMLibrary(name, nativeId, isRequire, lObj)); - } - else - list.Add(createMLibrary(name, nativeId, isRequire, null)); - } + // support TLauncher + var artifact = item.GetPropertyOrNull("artifact") + ?? item.GetPropertyOrNull("downloads")?.GetPropertyOrNull("artifact"); + var classifiers = item.GetPropertyOrNull("classifies") + ?? item.GetPropertyOrNull("downloads")?.GetPropertyOrNull("classifiers"); - // COMMON library - if (artifact != null) - { - var obj = createMLibrary(name, "", isRequire, artifact); - list.Add(obj); - } + // NATIVE library + var natives = item.GetPropertyOrNull("natives"); + if (natives != null) + { + var nativeId = natives.Value.GetPropertyValue(MRule.OSName)? + .Replace("${arch}", MRule.Arch); - // library - if (artifact == null && natives == null) + if (classifiers != null && nativeId != null) { - MLibrary obj = createMLibrary(name, "", isRequire, item); - list.Add(obj); + var lObj = classifiers.Value.GetPropertyOrNull(nativeId) ?? classifiers.Value.GetPropertyOrNull(MRule.OSName); + if (lObj != null) + list.Add(createMLibrary(name, nativeId, isRequire, lObj)); } - - return list.ToArray(); + else + list.Add(createMLibrary(name, nativeId, isRequire, null)); } - catch (Exception ex) + + // COMMON library + if (artifact != null) { - System.Diagnostics.Debug.WriteLine(ex); - return null; + var obj = createMLibrary(name, "", isRequire, artifact); + list.Add(obj); } - } - private MLibrary createMLibrary(string? name, string? nativeId, bool require, JsonElement? element) - { - string? path = element?.GetPropertyValue("path"); - if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(name)) - path = PackageName.Parse(name).GetPath(nativeId); - - var hash = element?.GetPropertyValue("sha1"); - if (string.IsNullOrEmpty(hash)) + // library + if (artifact == null && natives == null) { - var checksums = element?.SafeGetProperty("checksums")?.EnumerateArray(); - hash = checksums?.FirstOrDefault().GetString(); + MLibrary obj = createMLibrary(name, "", isRequire, item); + list.Add(obj); } - long size = 0; - element?.SafeGetProperty("size")?.TryGetInt64(out size); + return list.ToArray(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex); + return null; + } + } - return new MLibrary - { - Hash = hash, - IsNative = !string.IsNullOrEmpty(nativeId), - Name = name, - Path = path, - Size = size, - Url = element?.GetPropertyValue("url"), - IsRequire = require - }; + private MLibrary createMLibrary(string? name, string? nativeId, bool require, JsonElement? element) + { + string? path = element?.GetPropertyValue("path"); + if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(name)) + path = PackageName.Parse(name).GetPath(nativeId); + + var hash = element?.GetPropertyValue("sha1"); + if (string.IsNullOrEmpty(hash)) + { + var checksums = element?.GetPropertyOrNull("checksums")?.EnumerateArray(); + hash = checksums?.FirstOrDefault().GetString(); } + + long size = 0; + element?.GetPropertyOrNull("size")?.TryGetInt64(out size); + + return new MLibrary + { + Hash = hash, + IsNative = !string.IsNullOrEmpty(nativeId), + Name = name, + Path = path, + Size = size, + Url = element?.GetPropertyValue("url"), + IsRequire = require + }; } } diff --git a/src/Core/Installer/FabricMC/FabricVersionLoader.cs b/src/Core/Installer/FabricMC/FabricVersionLoader.cs index 4cc1f16..f898768 100644 --- a/src/Core/Installer/FabricMC/FabricVersionLoader.cs +++ b/src/Core/Installer/FabricMC/FabricVersionLoader.cs @@ -1,90 +1,89 @@ using CmlLib.Core.Version; -using System.Collections.Generic; using System.Text.Json; -using System.Threading.Tasks; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; -namespace CmlLib.Core.Installer.FabricMC +namespace CmlLib.Core.Installer.FabricMC; + +public class FabricVersionLoader : IVersionLoader { - public class FabricVersionLoader : IVersionLoader + public static readonly string ApiServer = "https://meta.fabricmc.net"; + private static readonly string LoaderUrl = ApiServer + "/v2/versions/loader"; + + private readonly HttpClient _httpClient; + + public FabricVersionLoader(HttpClient httpClient) => + _httpClient = httpClient; + + public string? LoaderVersion { get; set; } + + protected string GetVersionName(string version, string loaderVersion) { - public static readonly string ApiServer = "https://meta.fabricmc.net"; - private static readonly string LoaderUrl = ApiServer + "/v2/versions/loader"; - - public string? LoaderVersion { get; set; } + return $"fabric-loader-{loaderVersion}-{version}"; + } - protected string GetVersionName(string version, string loaderVersion) + public async ValueTask GetVersionMetadatasAsync() + { + if (string.IsNullOrEmpty(LoaderVersion)) { - return $"fabric-loader-{loaderVersion}-{version}"; + var loaders = await GetFabricLoadersAsync().ConfigureAwait(false); + + if (loaders.Length == 0 || string.IsNullOrEmpty(loaders[0].Version)) + throw new KeyNotFoundException("can't find loaders"); + + LoaderVersion = loaders[0].Version; } - public async Task GetVersionMetadatasAsync() - { - if (string.IsNullOrEmpty(LoaderVersion)) - { - var loaders = await GetFabricLoadersAsync().ConfigureAwait(false); - - if (loaders.Length == 0 || string.IsNullOrEmpty(loaders[0].Version)) - throw new KeyNotFoundException("can't find loaders"); - - LoaderVersion = loaders[0].Version; - } + var url = "https://meta.fabricmc.net/v2/versions/game/intermediary"; + var res = await _httpClient.GetStringAsync(url) + .ConfigureAwait(false); - var url = "https://meta.fabricmc.net/v2/versions/game/intermediary"; - var res = await HttpUtil.HttpClient.GetStringAsync(url) - .ConfigureAwait(false); + var versions = parseVersions(res, LoaderVersion!); + return new MVersionCollection(versions, null, null); + } - var versions = parseVersions(res, LoaderVersion!); - return new MVersionCollection(versions.ToArray()); - } + private IEnumerable parseVersions(string res, string loader) + { + using var jsonDocument = JsonDocument.Parse(res); + var root = jsonDocument.RootElement; - private List parseVersions(string res, string loader) + foreach (var item in root.EnumerateArray()) { - using var jsonDocument = JsonDocument.Parse(res); - var root = jsonDocument.RootElement; - var versionList = new List(); - - foreach (var item in root.EnumerateArray()) - { - var versionName = item.GetPropertyValue("version"); - if (string.IsNullOrEmpty(versionName)) - continue; - - var jsonUrl = $"{ApiServer}/v2/versions/loader/{versionName}/{loader}/profile/json"; + var versionName = item.GetPropertyValue("version"); + if (string.IsNullOrEmpty(versionName)) + continue; - var id = GetVersionName(versionName, loader); - var versionMetadata = new WebVersionMetadata(id) - { - MType = MVersionType.Custom, - Path = jsonUrl, - Type = "fabric" - }; - versionList.Add(versionMetadata); - } + var jsonUrl = $"{ApiServer}/v2/versions/loader/{versionName}/{loader}/profile/json"; - return versionList; + var id = GetVersionName(versionName, loader); + var model = new JsonVersionMetadataModel + { + Name = id, + Url = jsonUrl, + Type = "fabric" + }; + yield return new MojangVersionMetadata(model, _httpClient); } + } - public async Task GetFabricLoadersAsync() - { - var res = await HttpUtil.HttpClient.GetStringAsync(LoaderUrl); - return parseLoaders(res); - } + public async Task GetFabricLoadersAsync() + { + var res = await _httpClient.GetStringAsync(LoaderUrl); + return parseLoaders(res); + } - private FabricLoader[] parseLoaders(string res) + private FabricLoader[] parseLoaders(string res) + { + using var jsonDocument = JsonDocument.Parse(res); + var loaderList = new List(); + foreach (var item in jsonDocument.RootElement.EnumerateArray()) { - using var jsonDocument = JsonDocument.Parse(res); - var loaderList = new List(); - foreach (var item in jsonDocument.RootElement.EnumerateArray()) - { - var obj = item.Deserialize(); - if (obj != null) - loaderList.Add(obj); - } - - return loaderList.ToArray(); + var obj = item.Deserialize(); + if (obj != null) + loaderList.Add(obj); } + + return loaderList.ToArray(); } } diff --git a/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs b/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs index fe64a83..6db66aa 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs +++ b/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs @@ -1,74 +1,74 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CmlLib.Core.Version; +using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; +using CmlLib.Core.VersionMetadata; -namespace CmlLib.Core.Installer.LiteLoader +namespace CmlLib.Core.Installer.LiteLoader; + +// 1.8.9 freezing +public class LiteLoaderInstaller { - // 1.8.9 freezing - public class LiteLoaderInstaller + private readonly HttpClient _httpClient; + + public LiteLoaderInstaller(MinecraftPath path, HttpClient httpClient) { - public LiteLoaderInstaller(MinecraftPath path) - { - this.minecraftPath = path; - } + _httpClient = httpClient; + this.minecraftPath = path; + } - private readonly MinecraftPath minecraftPath; - private MVersionCollection? liteLoaderVersions; + private readonly MinecraftPath minecraftPath; + private MVersionCollection? liteLoaderVersions; - public static string GetVersionName(string loaderVersion, string baseVersion) - { - loaderVersion = loaderVersion.Replace("LiteLoader", ""); - return $"{loaderVersion}-LiteLoader{baseVersion}"; - } + public static string GetVersionName(string loaderVersion, string baseVersion) + { + loaderVersion = loaderVersion.Replace("LiteLoader", ""); + return $"{loaderVersion}-LiteLoader{baseVersion}"; + } - public async Task GetAllLiteLoaderVersions() - { - var llVersionLoader = new LiteLoaderVersionLoader(); - liteLoaderVersions = await llVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); + public async Task GetAllLiteLoaderVersions() + { + var llVersionLoader = new LiteLoaderVersionLoader(_httpClient); + liteLoaderVersions = await llVersionLoader.GetVersionMetadatasAsync() + .ConfigureAwait(false); - return liteLoaderVersions - .Select(x => x as LiteLoaderVersionMetadata) - .Where(x => x != null) - .ToArray()!; - } + return liteLoaderVersions + .Select(x => x as LiteLoaderVersionMetadata) + .Where(x => x != null) + .ToArray()!; + } - // vanilla - public async Task InstallAsync(string liteLoaderVersion) - { - var localVersionLoader = new LocalVersionLoader(minecraftPath); - var localVersions = await localVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - - liteLoaderVersions = await localVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); + // vanilla + public async Task InstallAsync(string liteLoaderVersion) + { + var localVersionLoader = new LocalVersionLoader(minecraftPath); + var localVersions = await localVersionLoader.GetVersionMetadatasAsync() + .ConfigureAwait(false); + + liteLoaderVersions = await localVersionLoader.GetVersionMetadatasAsync() + .ConfigureAwait(false); - var liteLoader = liteLoaderVersions?.GetVersionMetadata(liteLoaderVersion) as LiteLoaderVersionMetadata; - if (liteLoader == null) - throw new KeyNotFoundException(liteLoaderVersion); + var liteLoader = liteLoaderVersions?.GetVersionMetadata(liteLoaderVersion) as LiteLoaderVersionMetadata; + if (liteLoader == null) + throw new KeyNotFoundException(liteLoaderVersion); - var vanillaVersionName = liteLoader.VanillaVersionName; - var vanillaVersion = await localVersions.GetVersionAsync(vanillaVersionName) - .ConfigureAwait(false); + var vanillaVersionName = liteLoader.VanillaVersionName; + var vanillaVersion = await localVersions.GetVersionAsync(vanillaVersionName) + .ConfigureAwait(false); - if (vanillaVersion == null) - throw new KeyNotFoundException(vanillaVersionName); + if (vanillaVersion == null) + throw new KeyNotFoundException(vanillaVersionName); - return await liteLoader.InstallAsync(minecraftPath, vanillaVersion); - } + return await liteLoader.InstallAsync(minecraftPath, vanillaVersion); + } - public async Task InstallAsync(string liteLoaderVersion, MVersion target) - { - if (liteLoaderVersions == null) - await GetAllLiteLoaderVersions().ConfigureAwait(false); + public async Task InstallAsync(string liteLoaderVersion, MVersion target) + { + if (liteLoaderVersions == null) + await GetAllLiteLoaderVersions().ConfigureAwait(false); - var liteLoader = liteLoaderVersions?.GetVersionMetadata(liteLoaderVersion) as LiteLoaderVersionMetadata; - if (liteLoader == null) - throw new KeyNotFoundException(liteLoaderVersion); + var liteLoader = liteLoaderVersions?.GetVersionMetadata(liteLoaderVersion) as LiteLoaderVersionMetadata; + if (liteLoader == null) + throw new KeyNotFoundException(liteLoaderVersion); - return await liteLoader.InstallAsync(minecraftPath, target); - } + return await liteLoader.InstallAsync(minecraftPath, target); } } \ No newline at end of file diff --git a/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs b/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs index df7e511..f2ad5a6 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs +++ b/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs @@ -1,53 +1,63 @@ -using System.Collections.Generic; -using System.Text.Json; -using System.Threading.Tasks; -using CmlLib.Core.Version; +using System.Text.Json; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; -namespace CmlLib.Core.Installer.LiteLoader +namespace CmlLib.Core.Installer.LiteLoader; + +public class LiteLoaderVersionLoader : IVersionLoader { - public class LiteLoaderVersionLoader : IVersionLoader + public const string LiteLoaderLibName = "com.mumfrey:liteloader"; + public const string ManifestServer = "http://dl.liteloader.com/versions/versions.json"; + + private readonly HttpClient _httpClient; + + public LiteLoaderVersionLoader(HttpClient httpClient) => + _httpClient = httpClient; + + public async ValueTask GetVersionMetadatasAsync() { - public const string LiteLoaderLibName = "com.mumfrey:liteloader"; - public const string ManifestServer = "http://dl.liteloader.com/versions/versions.json"; - - public async Task GetVersionMetadatasAsync() - { - var res = await HttpUtil.HttpClient.GetStringAsync(ManifestServer) - .ConfigureAwait(false); + var res = await _httpClient.GetStringAsync(ManifestServer) + .ConfigureAwait(false); - return parseVersions(res); - } + return parseVersions(res); + } - private MVersionCollection parseVersions(string json) + private MVersionCollection parseVersions(string json) + { + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; + + var metadataList = new List(); + if (root.TryGetProperty("versions", out var versions)) { - using var jsonDocument = JsonDocument.Parse(json); - var root = jsonDocument.RootElement; - - var metadataList = new List(); - if (root.TryGetProperty("versions", out var versions)) + foreach (var item in versions.EnumerateObject()) { - foreach (var item in versions.EnumerateObject()) - { - var vanillaVersion = item.Name; - var versionObj = item.Value; + var vanillaVersion = item.Name; + var versionObj = item.Value; - var libObj = versionObj.SafeGetProperty("artefacts") ?? versionObj.SafeGetProperty("snapshots"); - var latestLiteLoader = libObj?.SafeGetProperty(LiteLoaderLibName)?.SafeGetProperty("latest"); + var libObj = + versionObj.GetPropertyOrNull("artefacts") ?? + versionObj.GetPropertyOrNull("snapshots"); - if (latestLiteLoader == null) - continue; + var latestLiteLoader = libObj? + .GetPropertyOrNull(LiteLoaderLibName)? + .GetPropertyOrNull("latest"); - var metadata = new LiteLoaderVersionMetadata(vanillaVersion, latestLiteLoader.Value); - metadata.Type = versionObj.SafeGetProperty("repo")?.GetPropertyValue("stream"); + if (latestLiteLoader == null) + continue; - metadataList.Add(metadata); - } - } + var model = new JsonVersionMetadataModel + { + Name = $"LiteLoader-{vanillaVersion}", + Type = versionObj.GetPropertyOrNull("repo")?.GetPropertyValue("stream") + }; + var metadata = new LiteLoaderVersionMetadata(model, vanillaVersion, latestLiteLoader.Value); - return new MVersionCollection(metadataList.ToArray()); + metadataList.Add(metadata); + } } + + return new MVersionCollection(metadataList, null, null); } } \ No newline at end of file diff --git a/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs index 057c131..35738a5 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs @@ -1,165 +1,126 @@ -using System.IO; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; -namespace CmlLib.Core.Installer.LiteLoader +namespace CmlLib.Core.Installer.LiteLoader; + +public class LiteLoaderVersionMetadata : JsonVersionMetadata { - public class LiteLoaderVersionMetadata : MVersionMetadata - { - private const string LiteLoaderDl = "http://dl.liteloader.com/versions/"; // should be https + private const string LiteLoaderDl = "http://dl.liteloader.com/versions/"; // should be https - public LiteLoaderVersionMetadata(string id, string vanillaVersionName) : base(id) - { - this.VanillaVersionName = vanillaVersionName; - } - - public LiteLoaderVersionMetadata(string vanillaVersion, JsonElement element) - : base($"LiteLoader-{vanillaVersion}") - { - IsLocalVersion = false; - this.VanillaVersionName = vanillaVersion; - this.element = element; - } + public LiteLoaderVersionMetadata( + JsonVersionMetadataModel model, + string vanillaVersionName, + JsonElement element) : base(model) + { + IsSaved = false; + VanillaVersionName = vanillaVersionName; + _element = element; + } - private readonly JsonElement? element; - public string VanillaVersionName { get; private set; } + private readonly JsonElement? _element; + public string VanillaVersionName { get; private set; } - private async Task writeVersion(Stream stream, - string versionName, string baseVersionName, string? strArgs, string?[]? arrArgs) + private void writeVersion(Utf8JsonWriter writer, + string versionName, string baseVersionName, string? strArgs, string?[]? arrArgs) + { + var llVersion = _element?.GetPropertyValue("version"); + var libraries = _element?.GetPropertyOrNull("libraries"); + + writer.WriteStartObject(); + writer.WriteString("id", versionName); + writer.WriteString("type", "release"); + writer.WriteString("mainClass", "net.minecraft.launchwrapper.Launch"); + writer.WriteString("inheritsFrom", baseVersionName); + writer.WriteString("jar", baseVersionName); + + writer.WriteStartArray("libraries"); + writer.WriteStartObject(); + writer.WriteString("name", $"{LiteLoaderVersionLoader.LiteLoaderLibName}:{llVersion}"); + writer.WriteString("url", LiteLoaderDl); + writer.WriteEndObject(); + + if (libraries != null) { - await using var writer = new Utf8JsonWriter(stream); - - var llVersion = element?.GetPropertyValue("version"); - var libraries = element?.SafeGetProperty("libraries"); - - writer.WriteStartObject(); - writer.WriteString("id", versionName); - writer.WriteString("type", "release"); - writer.WriteString("mainClass", "net.minecraft.launchwrapper.Launch"); - writer.WriteString("inheritsFrom", baseVersionName); - writer.WriteString("jar", baseVersionName); - - writer.WriteStartArray("libraries"); - writer.WriteStartObject(); - writer.WriteString("name", $"{LiteLoaderVersionLoader.LiteLoaderLibName}:{llVersion}"); - writer.WriteString("url", LiteLoaderDl); - writer.WriteEndObject(); - - if (libraries != null) + foreach (var lib in libraries.Value.EnumerateArray()) { - foreach (var lib in libraries.Value.EnumerateArray()) - { - // asm-all:5.2 is only available on LiteLoader server - var libName = lib.GetPropertyValue("name"); - var libUrl = lib.GetPropertyValue("url"); - if (libName == "org.ow2.asm:asm-all:5.2") - libUrl = "http://repo.liteloader.com/"; - - writer.WriteStartObject(); - writer.WriteString("name", libName); - writer.WriteString("url", libUrl); - writer.WriteEndObject(); - } - } + // asm-all:5.2 is only available on LiteLoader server + var libName = lib.GetPropertyValue("name"); + var libUrl = lib.GetPropertyValue("url"); + if (libName == "org.ow2.asm:asm-all:5.2") + libUrl = "http://repo.liteloader.com/"; - writer.WriteEndArray(); - - if (!string.IsNullOrEmpty(strArgs)) - writer.WriteString("minecraftArguments", strArgs); - if (arrArgs != null) - { - writer.WriteStartObject("arguments"); - writer.WriteStartArray(); - foreach (var item in arrArgs) - writer.WriteStringValue(item); - writer.WriteEndArray(); + writer.WriteStartObject(); + writer.WriteString("name", libName); + writer.WriteString("url", libUrl); writer.WriteEndObject(); } - - writer.WriteEndObject(); } + + writer.WriteEndArray(); - private Stream createVersionWriteStream(MinecraftPath path, string name) + if (!string.IsNullOrEmpty(strArgs)) + writer.WriteString("minecraftArguments", strArgs); + if (arrArgs != null) { - var metadataPath = path.GetVersionJsonPath(name); - - var directoryPath = System.IO.Path.GetDirectoryName(metadataPath); - if (!string.IsNullOrEmpty(directoryPath)) - Directory.CreateDirectory(directoryPath); - - return IOUtil.AsyncWriteStream(metadataPath, false); + writer.WriteStartObject("arguments"); + writer.WriteStartArray(); + foreach (var item in arrArgs) + writer.WriteStringValue(item); + writer.WriteEndArray(); + writer.WriteEndObject(); } + + writer.WriteEndObject(); + } + + private Stream createVersionWriteStream(MinecraftPath path, string name) + { + var metadataPath = path.GetVersionJsonPath(name); + + var directoryPath = System.IO.Path.GetDirectoryName(metadataPath); + if (!string.IsNullOrEmpty(directoryPath)) + Directory.CreateDirectory(directoryPath); + return File.Create(metadataPath); + } - public async Task InstallAsync(MinecraftPath path, MVersion baseVersion) - { - var versionName = LiteLoaderInstaller.GetVersionName(VanillaVersionName, baseVersion.Id); - var tweakClass = element?.GetPropertyValue("tweakClass"); - - using var fs = createVersionWriteStream(path, versionName); - - if (!string.IsNullOrEmpty(baseVersion.MinecraftArguments)) - { - // com.mumfrey.liteloader.launch.LiteLoaderTweaker - var newArguments = $"--tweakClass {tweakClass} {baseVersion.MinecraftArguments}"; - await writeVersion(fs, versionName, baseVersion.Id, newArguments, null) - .ConfigureAwait(false); - } - else if (baseVersion.GameArguments != null) - { - var tweakArg = new [] - { - "--tweakClass", - tweakClass - }; - - var newArguments = tweakArg.Concat(baseVersion.GameArguments).ToArray(); - await writeVersion(fs, versionName, baseVersion.Id, null, newArguments) - .ConfigureAwait(false); - } - - return versionName; - } + public async Task InstallAsync(MinecraftPath path, MVersion baseVersion) + { + var versionName = LiteLoaderInstaller.GetVersionName(VanillaVersionName, baseVersion.Id); + var tweakClass = _element?.GetPropertyValue("tweakClass"); - private async Task writeVersionToMemory() - { - var ms = new MemoryStream(); - await writeVersion(ms, Name, VanillaVersionName, null, null) - .ConfigureAwait(false); - return ms; - } + using var fs = createVersionWriteStream(path, versionName); + using var writer = new Utf8JsonWriter(fs); - public override async Task GetVersionAsync() + if (!string.IsNullOrEmpty(baseVersion.MinecraftArguments)) { - using var ms = await writeVersionToMemory() - .ConfigureAwait(false); - using var jsonDocument = await JsonDocument.ParseAsync(ms) - .ConfigureAwait(false); - return MVersionParser.ParseFromJson(jsonDocument); + // com.mumfrey.liteloader.launch.LiteLoaderTweaker + var newArguments = $"--tweakClass {tweakClass} {baseVersion.MinecraftArguments}"; + writeVersion(writer, versionName, baseVersion.Id, newArguments, null); } - - public override async Task GetVersionAsync(MinecraftPath savePath) + else if (baseVersion.GameArguments != null) { - using var ms = await writeVersionToMemory() - .ConfigureAwait(false); - using var fs = createVersionWriteStream(savePath, Name); + var tweakArg = new [] + { + "--tweakClass", + tweakClass + }; - using var jsonDocument = await JsonDocument.ParseAsync(ms); - var copyTask = ms.CopyToAsync(fs) - .ConfigureAwait(false); - var version = MVersionParser.ParseFromJson(jsonDocument); - await copyTask; - return version; + var newArguments = tweakArg.Concat(baseVersion.GameArguments).ToArray(); + writeVersion(writer, versionName, baseVersion.Id, null, newArguments); } - public override async Task SaveAsync(MinecraftPath path) - { - using var fs = createVersionWriteStream(path, Name); - await writeVersion(fs, Name, VanillaVersionName, null, null) - .ConfigureAwait(false); - } + await fs.FlushAsync(); + await writer.FlushAsync(); + return versionName; + } + + protected override ValueTask GetVersionJsonString() + { + using var ms = new MemoryStream(); + using var writer = new Utf8JsonWriter(ms); + var json = System.Text.Encoding.UTF8.GetString(ms.ToArray()); + return new ValueTask(json); } } \ No newline at end of file diff --git a/src/Core/Installer/MJava.cs b/src/Core/Installer/MJava.cs index 8c8b655..c4b25e8 100644 --- a/src/Core/Installer/MJava.cs +++ b/src/Core/Installer/MJava.cs @@ -1,133 +1,127 @@ using CmlLib.Utils; -using System; using System.ComponentModel; -using System.IO; -using System.Threading.Tasks; using CmlLib.Core.Java; using System.Text.Json; using System.Net; -using System.Net.Http; using CmlLib.Core.Downloader; -namespace CmlLib.Core.Installer +namespace CmlLib.Core.Installer; + +// legacy java installer +// new java installer: CmlLib.Core.Files.JavaChecker +public class MJava { - // legacy java installer - // new java installer: CmlLib.Core.Files.JavaChecker - public class MJava - { - public static readonly string DefaultRuntimeDirectory - = Path.Combine(MinecraftPath.GetOSDefaultPath(), "runtime"); + public static readonly string DefaultRuntimeDirectory + = Path.Combine(MinecraftPath.GetOSDefaultPath(), "runtime"); - public event ProgressChangedEventHandler? ProgressChanged; - public string RuntimeDirectory { get; private set; } + public event ProgressChangedEventHandler? ProgressChanged; + public string RuntimeDirectory { get; private set; } - private readonly HttpClient httpClient; - private IProgress? pProgressChanged; - public IJavaPathResolver JavaPathResolver { get; set; } + private readonly HttpClient _httpClient; + private IProgress? pProgressChanged; + public IJavaPathResolver JavaPathResolver { get; set; } - public MJava() : this(HttpUtil.HttpClient) { } - public MJava(HttpClient client) : this(client, DefaultRuntimeDirectory) { } - public MJava(string runtimePath) : this(HttpUtil.HttpClient, runtimePath) { } + public MJava(HttpClient client) : this(client, DefaultRuntimeDirectory) { } - public MJava(HttpClient client, string runtimePath) - { - RuntimeDirectory = runtimePath; - JavaPathResolver = new MinecraftJavaPathResolver(runtimePath); - httpClient = client; - } + public MJava(HttpClient client, string runtimePath) + { + RuntimeDirectory = runtimePath; + JavaPathResolver = new MinecraftJavaPathResolver(runtimePath); + _httpClient = client; + } - public string GetBinaryPath() - => JavaPathResolver.GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersionName, MRule.OSName); + public string GetBinaryPath() + => JavaPathResolver.GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersionName, MRule.OSName); - public bool CheckJavaExistence() - => File.Exists(GetBinaryPath()); + public bool CheckJavaExistence() + => File.Exists(GetBinaryPath()); - public Task CheckJavaAsync() - => CheckJavaAsync(null); - - public async Task CheckJavaAsync(IProgress? progress) - { - string javapath = GetBinaryPath(); + public Task CheckJavaAsync() + => CheckJavaAsync(null); + + public async Task CheckJavaAsync(IProgress? progress) + { + string javapath = GetBinaryPath(); - if (!CheckJavaExistence()) + if (!CheckJavaExistence()) + { + if (progress == null) { - if (progress == null) - { - pProgressChanged = new Progress( - (e) => ProgressChanged?.Invoke(this, e)); - } - else - { - pProgressChanged = progress; - } - - string javaUrl = await GetJavaUrlAsync().ConfigureAwait(false); - string lzmaPath = await downloadJavaLzmaAsync(javaUrl).ConfigureAwait(false); - - Task decompressTask = Task.Run(() => decompressJavaFile(lzmaPath)); - await decompressTask.ConfigureAwait(false); - - if (!File.Exists(javapath)) - throw new WebException("failed to download"); - - if (MRule.OSName != MRule.Windows) - NativeMethods.Chmod(javapath, NativeMethods.Chmod755); + pProgressChanged = new Progress( + (e) => ProgressChanged?.Invoke(this, e)); } + else + { + pProgressChanged = progress; + } + + string javaUrl = await GetJavaUrlAsync().ConfigureAwait(false); + string lzmaPath = await downloadJavaLzmaAsync(javaUrl).ConfigureAwait(false); - return javapath; - } + Task decompressTask = Task.Run(() => decompressJavaFile(lzmaPath)); + await decompressTask.ConfigureAwait(false); - public async Task GetJavaUrlAsync() - { - var json = await HttpUtil.HttpClient.GetStringAsync(MojangServer.LauncherMeta) - .ConfigureAwait(false); - return parseLauncherMetadata(json); + if (!File.Exists(javapath)) + throw new WebException("failed to download"); + + if (MRule.OSName != MRule.Windows) + NativeMethods.Chmod(javapath, NativeMethods.Chmod755); } - private string parseLauncherMetadata(string json) - { - using var jsonDocument = JsonDocument.Parse(json); - var root = jsonDocument.RootElement; + return javapath; + } + + public async Task GetJavaUrlAsync() + { + var json = await _httpClient.GetStringAsync(MojangServer.LauncherMeta) + .ConfigureAwait(false); + return parseLauncherMetadata(json); + } - var javaUrl = root.SafeGetProperty(MRule.OSName)? - .SafeGetProperty(MRule.Arch)? - .SafeGetProperty("jre")? - .GetPropertyValue("url"); + private string parseLauncherMetadata(string json) + { + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; + + var javaUrl = root + .GetPropertyOrNull(MRule.OSName)? + .GetPropertyOrNull(MRule.Arch)? + .GetPropertyOrNull("jre")? + .GetPropertyValue("url"); + + if (string.IsNullOrEmpty(javaUrl)) + throw new PlatformNotSupportedException("Downloading JRE on current OS is not supported. Set JavaPath manually."); + return javaUrl; + } - if (string.IsNullOrEmpty(javaUrl)) - throw new PlatformNotSupportedException("Downloading JRE on current OS is not supported. Set JavaPath manually."); - return javaUrl; - } + private async Task downloadJavaLzmaAsync(string javaUrl) + { + Directory.CreateDirectory(RuntimeDirectory); + string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - private async Task downloadJavaLzmaAsync(string javaUrl) + var downloader = new HttpClientDownloadHelper(_httpClient); + var progress = new Progress(p => { - Directory.CreateDirectory(RuntimeDirectory); - string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - - var downloader = new HttpClientDownloadHelper(httpClient); - var progress = new Progress(p => - { - var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; - pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); - }); - await downloader.DownloadFileAsync(new DownloadFile(lzmaPath, javaUrl), progress); + var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; + pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); + }); + await downloader.DownloadFileAsync(new DownloadFile(lzmaPath, javaUrl), progress); - return lzmaPath; - } + return lzmaPath; + } - private void decompressJavaFile(string lzmaPath) - { - string zippath = Path.Combine(Path.GetTempPath(), "jre.zip"); - SevenZipWrapper.DecompressFileLZMA(lzmaPath, zippath); + private void decompressJavaFile(string lzmaPath) + { + string zippath = Path.Combine(Path.GetTempPath(), "jre.zip"); + SevenZipWrapper.DecompressFileLZMA(lzmaPath, zippath); - var z = new SharpZip(zippath); - z.ProgressEvent += Z_ProgressEvent; - z.Unzip(RuntimeDirectory); - } + var z = new SharpZip(zippath); + z.ProgressEvent += Z_ProgressEvent; + z.Unzip(RuntimeDirectory); + } - private void Z_ProgressEvent(object? sender, int e) - { - pProgressChanged?.Report(new ProgressChangedEventArgs(50 + e / 2, null)); - } + private void Z_ProgressEvent(object? sender, int e) + { + pProgressChanged?.Report(new ProgressChangedEventArgs(50 + e / 2, null)); } } \ No newline at end of file diff --git a/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs b/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs index f627524..ba8561a 100644 --- a/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs +++ b/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs @@ -1,118 +1,99 @@ -using CmlLib.Core.Version; -using System.Net; +using System.Net; using System.Text.Json; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; -namespace CmlLib.Core.Installer.QuiltMC +namespace CmlLib.Core.Installer.QuiltMC; + +public class QuiltVersionLoader : IVersionLoader { - public class QuiltVersionLoader : IVersionLoader - { - public static readonly string ApiServer = "https://meta.quiltmc.org"; - private static readonly string LoaderUrl = ApiServer + "/v3/versions/loader"; + public static readonly string ApiServer = "https://meta.quiltmc.org"; + private static readonly string LoaderUrl = ApiServer + "/v3/versions/loader"; - public string? LoaderVersion { get; set; } + private readonly HttpClient _httpClient; - protected string GetVersionName(string version, string loaderVersion) - { - return $"quilt-loader-{loaderVersion}-{version}"; - } + public QuiltVersionLoader(HttpClient httpClient) => _httpClient = httpClient; - public MVersionCollection GetVersionMetadatas() - => internalGetVersionMetadatasAsync(sync: true).GetAwaiter().GetResult(); + public string? LoaderVersion { get; set; } - public Task GetVersionMetadatasAsync() - => internalGetVersionMetadatasAsync(sync: false); + protected string GetVersionName(string version, string loaderVersion) + { + return $"quilt-loader-{loaderVersion}-{version}"; + } - private async Task internalGetVersionMetadatasAsync(bool sync) + public async ValueTask GetVersionMetadatasAsync() + { + if (string.IsNullOrEmpty(LoaderVersion)) { - if (string.IsNullOrEmpty(LoaderVersion)) - { - QuiltLoader[] loaders; - if (sync) - loaders = GetQuiltLoaders(); - else - loaders = await GetQuiltLoadersAsync().ConfigureAwait(false); - - if (loaders.Length == 0 || string.IsNullOrEmpty(loaders[0].Version)) - throw new KeyNotFoundException("can't find loaders"); - - LoaderVersion = loaders[0].Version; - } - - string url = "https://meta.quiltmc.org/v3/versions/game"; - string res; - using (var wc = new WebClient()) - { - if (sync) - res = wc.DownloadString(url); - else - res = await wc.DownloadStringTaskAsync(url).ConfigureAwait(false); - } - - var versions = parseVersions(res, LoaderVersion!); - return new MVersionCollection(versions.ToArray()); + var loaders = await GetQuiltLoadersAsync().ConfigureAwait(false); + + if (loaders.Length == 0 || string.IsNullOrEmpty(loaders[0].Version)) + throw new KeyNotFoundException("can't find loaders"); + + LoaderVersion = loaders[0].Version; } - private List parseVersions(string res, string loader) + var url = "https://meta.quiltmc.org/v3/versions/game"; + var res = await _httpClient.GetStringAsync(url); + + var versions = parseVersions(res, LoaderVersion!); + return new MVersionCollection(versions, null, null); + } + + private IEnumerable parseVersions(string res, string loader) + { + using var json = JsonDocument.Parse(res); + var jarr = json.RootElement.EnumerateArray(); + + foreach (var item in jarr) { - using var json = JsonDocument.Parse(res); - var jarr = json.RootElement.EnumerateArray(); - var versionList = new List(); + if (!item.TryGetProperty("version", out var versionProp)) + continue; + var versionName = versionProp.GetString(); + if (string.IsNullOrEmpty(versionName)) + continue; - foreach (var item in jarr) + string jsonUrl = $"{ApiServer}/v3/versions/loader/{versionName}/{loader}/profile/json"; + + string id = GetVersionName(versionName, loader); + var model = new JsonVersionMetadataModel { - if (!item.TryGetProperty("version", out var versionProp)) - continue; - var versionName = versionProp.GetString(); - if (string.IsNullOrEmpty(versionName)) - continue; - - string jsonUrl = $"{ApiServer}/v3/versions/loader/{versionName}/{loader}/profile/json"; - - string id = GetVersionName(versionName, loader); - var versionMetadata = new WebVersionMetadata(id) - { - MType = MVersionType.Custom, - Path = jsonUrl, - Type = "quilt" - }; - versionList.Add(versionMetadata); - } - - return versionList; + Type = "quilt", + Url = jsonUrl, + }; + yield return new MojangVersionMetadata(model, _httpClient); } + } - public QuiltLoader[] GetQuiltLoaders() - { - using var wc = new WebClient(); - var res = wc.DownloadString(LoaderUrl); + public QuiltLoader[] GetQuiltLoaders() + { + using var wc = new WebClient(); + var res = wc.DownloadString(LoaderUrl); - return parseLoaders(res); - } + return parseLoaders(res); + } - public async Task GetQuiltLoadersAsync() - { - using var wc = new WebClient(); - var res = await wc.DownloadStringTaskAsync(LoaderUrl) - .ConfigureAwait(false); + public async Task GetQuiltLoadersAsync() + { + using var wc = new WebClient(); + var res = await wc.DownloadStringTaskAsync(LoaderUrl) + .ConfigureAwait(false); - return parseLoaders(res); - } + return parseLoaders(res); + } - private QuiltLoader[] parseLoaders(string res) + private QuiltLoader[] parseLoaders(string res) + { + using var json = JsonDocument.Parse(res); + var jarr = json.RootElement.EnumerateArray(); + var loaderList = new List(); + foreach (var item in jarr) { - using var json = JsonDocument.Parse(res); - var jarr = json.RootElement.EnumerateArray(); - var loaderList = new List(); - foreach (var item in jarr) - { - var obj = item.Deserialize(); - if (obj != null) - loaderList.Add(obj); - } - - return loaderList.ToArray(); + var obj = item.Deserialize(); + if (obj != null) + loaderList.Add(obj); } + + return loaderList.ToArray(); } } diff --git a/src/Core/Launcher/MLaunch.cs b/src/Core/Launcher/MLaunch.cs index 3e0b563..7a7f8bc 100644 --- a/src/Core/Launcher/MLaunch.cs +++ b/src/Core/Launcher/MLaunch.cs @@ -1,215 +1,211 @@ using CmlLib.Utils; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using CmlLib.Core.Version; -namespace CmlLib.Core +namespace CmlLib.Core; + +public class MLaunch { - public class MLaunch - { - private const int DefaultServerPort = 25565; + private const int DefaultServerPort = 25565; - public static readonly string SupportVersion = "1.18.2"; - public readonly static string[] DefaultJavaParameter = - { - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=16M", - "-Dlog4j2.formatMsgNoLookups=true" - // "-Xss1M" - }; - - public MLaunch(MLaunchOption option) + public static readonly string SupportVersion = "1.18.2"; + public readonly static string[] DefaultJavaParameter = { - option.CheckValid(); - launchOption = option; - this.minecraftPath = option.GetMinecraftPath(); - } + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=16M", + "-Dlog4j2.formatMsgNoLookups=true" + // "-Xss1M" + }; + + public MLaunch(MLaunchOption option) + { + option.CheckValid(); + launchOption = option; + this.minecraftPath = option.GetMinecraftPath(); + } - private readonly MinecraftPath minecraftPath; - private readonly MLaunchOption launchOption; - - // make process that ready to launch game - public Process GetProcess() - { - string arg = string.Join(" ", CreateArg()); - Process mc = new Process(); - mc.StartInfo.FileName = - useNotNull(launchOption.GetStartVersion().JavaBinaryPath, launchOption.GetJavaPath()) ?? ""; - mc.StartInfo.Arguments = arg; - mc.StartInfo.WorkingDirectory = minecraftPath.BasePath; - - return mc; - } + private readonly MinecraftPath minecraftPath; + private readonly MLaunchOption launchOption; + + // make process that ready to launch game + public Process GetProcess() + { + string arg = string.Join(" ", CreateArg()); + Process mc = new Process(); + mc.StartInfo.FileName = + useNotNull(launchOption.GetStartVersion().JavaBinaryPath, launchOption.GetJavaPath()) ?? ""; + mc.StartInfo.Arguments = arg; + mc.StartInfo.WorkingDirectory = minecraftPath.BasePath; + + return mc; + } - // make library files into jvm classpath string - private string createClassPath(MVersion version) + // make library files into jvm classpath string + private string createClassPath(MVersion version) + { + // if there is no libraries then launcher would need only one library file: .jar file itself + var classpath = new List(version.Libraries?.Length ?? 1); + + // libraries + if (version.Libraries != null) { - // if there is no libraries then launcher would need only one library file: .jar file itself - var classpath = new List(version.Libraries?.Length ?? 1); + var libraries = version.Libraries + .Where(lib => lib.IsRequire && !lib.IsNative && !string.IsNullOrEmpty(lib.Path)) + .Select(lib => Path.GetFullPath(Path.Combine(minecraftPath.Library, lib.Path!))); + classpath.AddRange(libraries); + } - // libraries - if (version.Libraries != null) - { - var libraries = version.Libraries - .Where(lib => lib.IsRequire && !lib.IsNative && !string.IsNullOrEmpty(lib.Path)) - .Select(lib => Path.GetFullPath(Path.Combine(minecraftPath.Library, lib.Path!))); - classpath.AddRange(libraries); - } + // .jar file + if (!string.IsNullOrEmpty(version.Jar)) + classpath.Add(minecraftPath.GetVersionJarPath(version.Jar)); - // .jar file - if (!string.IsNullOrEmpty(version.Jar)) - classpath.Add(minecraftPath.GetVersionJarPath(version.Jar)); + var classpathStr = IOUtil.CombinePath(classpath.ToArray()); + return classpathStr; + } - var classpathStr = IOUtil.CombinePath(classpath.ToArray()); - return classpathStr; - } + private string createNativePath(MVersion version) + { + var native = new MNative(minecraftPath, version); + native.CleanNatives(); + var nativePath = native.ExtractNatives(); + return nativePath; + } - private string createNativePath(MVersion version) + public string[] CreateArg() + { + MVersion version = launchOption.GetStartVersion(); + var args = new List(); + + var classpath = createClassPath(version); + var nativePath = createNativePath(version); + var session = launchOption.GetSession(); + + var argDict = new Dictionary { - var native = new MNative(minecraftPath, version); - native.CleanNatives(); - var nativePath = native.ExtractNatives(); - return nativePath; - } - - public string[] CreateArg() + { "library_directory" , minecraftPath.Library }, + { "natives_directory" , nativePath }, + { "launcher_name" , useNotNull(launchOption.GameLauncherName, "minecraft-launcher") }, + { "launcher_version" , useNotNull(launchOption.GameLauncherVersion, "2") }, + { "classpath_separator", Path.PathSeparator.ToString() }, + { "classpath" , classpath }, + + { "auth_xuid" , session.Xuid }, + { "auth_player_name" , session.Username }, + { "version_name" , version.Id }, + { "game_directory" , minecraftPath.BasePath }, + { "assets_root" , minecraftPath.Assets }, + { "assets_index_name", version.Assets?.Id ?? "legacy" }, + { "auth_uuid" , session.UUID }, + { "auth_access_token", session.AccessToken }, + { "user_properties" , "{}" }, + { "auth_xuid" , session.Xuid ?? "xuid" }, + { "clientid" , launchOption.ClientId ?? "clientId" }, + { "user_type" , session.UserType ?? "Mojang" }, + { "game_assets" , minecraftPath.GetAssetLegacyPath(version.Assets?.Id ?? "legacy") }, + { "auth_session" , session.AccessToken }, + { "version_type" , useNotNull(launchOption.VersionType, version.Type) }, + }; + + if (launchOption.ArgumentDictionary != null) { - MVersion version = launchOption.GetStartVersion(); - var args = new List(); - - var classpath = createClassPath(version); - var nativePath = createNativePath(version); - var session = launchOption.GetSession(); - - var argDict = new Dictionary - { - { "library_directory" , minecraftPath.Library }, - { "natives_directory" , nativePath }, - { "launcher_name" , useNotNull(launchOption.GameLauncherName, "minecraft-launcher") }, - { "launcher_version" , useNotNull(launchOption.GameLauncherVersion, "2") }, - { "classpath_separator", Path.PathSeparator.ToString() }, - { "classpath" , classpath }, - - { "auth_xuid" , session.Xuid }, - { "auth_player_name" , session.Username }, - { "version_name" , version.Id }, - { "game_directory" , minecraftPath.BasePath }, - { "assets_root" , minecraftPath.Assets }, - { "assets_index_name", version.Assets?.Id ?? "legacy" }, - { "auth_uuid" , session.UUID }, - { "auth_access_token", session.AccessToken }, - { "user_properties" , "{}" }, - { "auth_xuid" , session.Xuid ?? "xuid" }, - { "clientid" , launchOption.ClientId ?? "clientId" }, - { "user_type" , session.UserType ?? "Mojang" }, - { "game_assets" , minecraftPath.GetAssetLegacyPath(version.Assets?.Id ?? "legacy") }, - { "auth_session" , session.AccessToken }, - { "version_type" , useNotNull(launchOption.VersionType, version.TypeStr) }, - }; - - if (launchOption.ArgumentDictionary != null) + foreach (var argument in launchOption.ArgumentDictionary) { - foreach (var argument in launchOption.ArgumentDictionary) - { - argDict[argument.Key] = argument.Value; - } + argDict[argument.Key] = argument.Value; } + } - // JVM argument - - // version-specific jvm arguments - if (version.JvmArguments != null) - args.AddRange(Mapper.MapInterpolation(version.JvmArguments, argDict)); + // JVM argument + + // version-specific jvm arguments + if (version.JvmArguments != null) + args.AddRange(Mapper.MapInterpolation(version.JvmArguments, argDict)); + + // default jvm arguments + if (launchOption.JVMArguments != null) + args.AddRange(launchOption.JVMArguments); + else + { + if (launchOption.MaximumRamMb > 0) + args.Add("-Xmx" + launchOption.MaximumRamMb + "m"); + + if (launchOption.MinimumRamMb > 0) + args.Add("-Xms" + launchOption.MinimumRamMb + "m"); - // default jvm arguments - if (launchOption.JVMArguments != null) - args.AddRange(launchOption.JVMArguments); - else - { - if (launchOption.MaximumRamMb > 0) - args.Add("-Xmx" + launchOption.MaximumRamMb + "m"); + args.AddRange(DefaultJavaParameter); + } - if (launchOption.MinimumRamMb > 0) - args.Add("-Xms" + launchOption.MinimumRamMb + "m"); - - args.AddRange(DefaultJavaParameter); - } + if (version.JvmArguments == null) + { + args.Add("-Djava.library.path=" + handleEmpty(nativePath)); + args.Add("-cp " + classpath); + } - if (version.JvmArguments == null) - { - args.Add("-Djava.library.path=" + handleEmpty(nativePath)); - args.Add("-cp " + classpath); - } + // for macOS + if (!string.IsNullOrEmpty(launchOption.DockName)) + args.Add("-Xdock:name=" + handleEmpty(launchOption.DockName)); + if (!string.IsNullOrEmpty(launchOption.DockIcon)) + args.Add("-Xdock:icon=" + handleEmpty(launchOption.DockIcon)); - // for macOS - if (!string.IsNullOrEmpty(launchOption.DockName)) - args.Add("-Xdock:name=" + handleEmpty(launchOption.DockName)); - if (!string.IsNullOrEmpty(launchOption.DockIcon)) - args.Add("-Xdock:icon=" + handleEmpty(launchOption.DockIcon)); - - // logging - var loggingArgument = version.LoggingClient?.Argument; - if (!string.IsNullOrEmpty(loggingArgument)) - args.Add(Mapper.Interpolation(loggingArgument, new Dictionary() - { - { "path", minecraftPath.GetLogConfigFilePath(version.LoggingClient?.LogFile?.Id ?? version.Id) } - }, true)); - - // main class - if (!string.IsNullOrEmpty(version.MainClass)) - args.Add(version.MainClass); - - // game arguments - if (version.GameArguments != null) - args.AddRange(Mapper.MapInterpolation(version.GameArguments, argDict)); - else if (!string.IsNullOrEmpty(version.MinecraftArguments)) - args.AddRange(Mapper.MapInterpolation(version.MinecraftArguments.Split(' '), argDict)); - - // options - if (!string.IsNullOrEmpty(launchOption.ServerIp)) + // logging + var loggingArgument = version.LoggingClient?.Argument; + if (!string.IsNullOrEmpty(loggingArgument)) + args.Add(Mapper.Interpolation(loggingArgument, new Dictionary() { - args.Add("--server " + handleEmpty(launchOption.ServerIp)); + { "path", minecraftPath.GetLogConfigFilePath(version.LoggingClient?.LogFile?.Id ?? version.Id) } + }, true)); - if (launchOption.ServerPort != DefaultServerPort) - args.Add("--port " + launchOption.ServerPort); - } + // main class + if (!string.IsNullOrEmpty(version.MainClass)) + args.Add(version.MainClass); - if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) - { - args.Add("--width " + launchOption.ScreenWidth); - args.Add("--height " + launchOption.ScreenHeight); - } + // game arguments + if (version.GameArguments != null) + args.AddRange(Mapper.MapInterpolation(version.GameArguments, argDict)); + else if (!string.IsNullOrEmpty(version.MinecraftArguments)) + args.AddRange(Mapper.MapInterpolation(version.MinecraftArguments.Split(' '), argDict)); - if (launchOption.FullScreen) - args.Add("--fullscreen"); + // options + if (!string.IsNullOrEmpty(launchOption.ServerIp)) + { + args.Add("--server " + handleEmpty(launchOption.ServerIp)); - return args.ToArray(); + if (launchOption.ServerPort != DefaultServerPort) + args.Add("--port " + launchOption.ServerPort); } - // if input1 is null, return input2 - private string? useNotNull(string? input1, string? input2) + if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) { - if (string.IsNullOrEmpty(input1)) - return input2; - else - return input1; + args.Add("--width " + launchOption.ScreenWidth); + args.Add("--height " + launchOption.ScreenHeight); } - private string? handleEmpty(string? input) - { - if (input == null) - return null; - - if (input.Contains(" ")) - return "\"" + input + "\""; - else - return input; - } + if (launchOption.FullScreen) + args.Add("--fullscreen"); + + return args.ToArray(); + } + + // if input1 is null, return input2 + private string? useNotNull(string? input1, string? input2) + { + if (string.IsNullOrEmpty(input1)) + return input2; + else + return input1; + } + + private string? handleEmpty(string? input) + { + if (input == null) + return null; + + if (input.Contains(" ")) + return "\"" + input + "\""; + else + return input; } } diff --git a/src/Core/Version/JsonVersionParser.cs b/src/Core/Version/JsonVersionParser.cs new file mode 100644 index 0000000..d621677 --- /dev/null +++ b/src/Core/Version/JsonVersionParser.cs @@ -0,0 +1,180 @@ +using CmlLib.Core.Files; +using CmlLib.Utils; +using System.Text.Json; + +namespace CmlLib.Core.Version; + +public class JsonVersionParser +{ + public MVersion ParseFromJsonString(string json) + { + using var document = JsonDocument.Parse(json); + return ParseFromJson(document.RootElement); + } + + public MVersion ParseFromJson(JsonElement element) + { + try + { + return parseInternal(element); + } + catch (MVersionParseException) + { + throw; + } + catch (Exception ex) + { + throw new MVersionParseException(ex); + } + } + + private MVersion parseInternal(JsonElement root) + { + // id + if (!root.TryGetProperty("id", out var idElement)) + throw new MVersionParseException("Empty version id"); + + var id = idElement.GetString() + ?? throw new MVersionParseException("Empty version id"); + + var version = new MVersion(id); + + // javaVersion + version.JavaVersion = root + .GetPropertyOrNull("javaVersion")? + .GetPropertyOrNull("component")? + .GetString(); + + // assets + if (root.TryGetProperty("assetIndex", out var assetIndex)) + version.Assets = assetIndex.Deserialize(JsonUtil.JsonOptions); + else if (root.TryGetProperty("assets", out var assets)) + version.Assets = new MFileMetadata(assets.GetString()); + + // client jar + var client = root + .GetPropertyOrNull("downloads")? + .GetPropertyOrNull("client"); + + if (client.HasValue) + version.Client = client.Value.Deserialize(JsonUtil.JsonOptions); + + // libraries + if (root.TryGetProperty("libraries", out var libProp) && libProp.ValueKind == JsonValueKind.Array) + { + var libList = new List(); + var libParser = new MLibraryParser(); + foreach (var item in libProp.EnumerateArray()) + { + var libs = libParser.ParseJsonObject(item); + if (libs != null) + libList.AddRange(libs); + } + + version.Libraries = libList.ToArray(); + } + + // mainClass + version.MainClass = root.GetPropertyValue("mainClass"); + + // argument + version.MinecraftArguments = root.GetPropertyValue("minecraftArguments"); + + if (root.TryGetProperty("arguments", out var ag)) + { + if (ag.TryGetProperty("game", out var gameArg) && gameArg.ValueKind == JsonValueKind.Array) + version.GameArguments = argParse(gameArg); + if (ag.TryGetProperty("jvm", out var jvmArg) && jvmArg.ValueKind == JsonValueKind.Array) + version.JvmArguments = argParse(jvmArg); + } + + // metadata + version.ReleaseTime = root.GetPropertyValue("releaseTime"); + + version.Type = root.GetPropertyValue("type"); + + // inherits + version.ParentVersionId = root.GetPropertyValue("inheritsFrom"); + version.IsInherited = string.IsNullOrEmpty(version.ParentVersionId); + + version.Jar = root.GetPropertyValue("jar"); + if (string.IsNullOrEmpty(version.Jar)) + version.Jar = version.Id; + + // logging + var loggingClient = root + .GetPropertyOrNull("logging")? + .GetPropertyOrNull("client"); + + if (loggingClient.HasValue) + { + var logFile = loggingClient.Value + .GetPropertyOrNull("file")? + .Deserialize(JsonUtil.JsonOptions); + + version.LoggingClient = new MLogConfiguration + { + LogFile = logFile, + Type = loggingClient.Value.GetPropertyValue("type"), + Argument = loggingClient.Value.GetPropertyValue("argument") + }; + } + + return version; + } + + // TODO: create argument object + private static string[] argParse(JsonElement arr) + { + var strList = new List(); + + foreach (var item in arr.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Object) + { + bool allow = true; + + var rules = item.GetPropertyOrNull("rules"); + if (rules == null || rules.Value.ValueKind != JsonValueKind.Array) + rules = item.GetPropertyOrNull("compatibilityRules"); + + if (rules != null) + allow = MRule.CheckOSRequire(rules.Value); + + if (allow) + { + var value = item.GetPropertyOrNull("value") ?? item.GetPropertyOrNull("values"); + if (value != null) + { + if (value.Value.ValueKind == JsonValueKind.Array) + { + foreach (var strProp in value.Value.EnumerateArray()) + { + if (strProp.ValueKind != JsonValueKind.String) + continue; + + var str = strProp.GetString(); + if (!string.IsNullOrEmpty(str)) + strList.Add(str); + } + } + else if (value.Value.ValueKind == JsonValueKind.String) + { + var valueString = value.Value.GetString(); + if (!string.IsNullOrEmpty(valueString)) + strList.Add(valueString); + } + } + } + } + else + { + var value = item.GetString(); + if (!string.IsNullOrEmpty(value)) + strList.Add(value); + } + } + + return strList.ToArray(); + } +} diff --git a/src/Core/Version/MVersion.cs b/src/Core/Version/MVersion.cs index db43c51..ba8b850 100644 --- a/src/Core/Version/MVersion.cs +++ b/src/Core/Version/MVersion.cs @@ -1,117 +1,117 @@ using CmlLib.Core.Files; -using System.Linq; +using CmlLib.Core.VersionMetadata; -namespace CmlLib.Core.Version +namespace CmlLib.Core.Version; + +public class MVersion { - public class MVersion + public MVersion(string id) + { + this.Id = id; + } + + public bool IsInherited { get; set; } + public string? ParentVersionId { get; set; } + + public string Id { get; set; } + + public MFileMetadata? Assets { get; set; } + + public string? JavaVersion { get; set; } + public string? JavaBinaryPath { get; set; } + public string? Jar { get; set; } + public MFileMetadata? Client { get; set; } + public MLibrary[]? Libraries { get; set; } + public string? MainClass { get; set; } + public string? MinecraftArguments { get; set; } + public string[]? GameArguments { get; set; } + public string[]? JvmArguments { get; set; } + public string? ReleaseTime { get; set; } + public string? Type { get; set; } + public MLogConfiguration? LoggingClient { get; set; } + + public MVersionType GetVersionType() => MVersionTypeConverter.FromString(Type); + + public void InheritFrom(MVersion parentVersion) { - public MVersion(string id) + /* + Overload : + AssetId, AssetUrl, AssetHash, ClientDownloadUrl, + ClientHash, MainClass, MinecraftArguments, JavaVersion + + Combine : + Libraries, GameArguments, JvmArguments + */ + + // Overloads + + if (Assets == null) + Assets = parentVersion.Assets; + else { - this.Id = id; + if (string.IsNullOrEmpty(Assets.Id)) + Assets.Id = parentVersion.Assets?.Id; + + if (string.IsNullOrEmpty(Assets.Url)) + Assets.Url = parentVersion.Assets?.Url; + + if (string.IsNullOrEmpty(Assets.Sha1)) + Assets.Sha1 = parentVersion.Assets?.Sha1; } - - public bool IsInherited { get; set; } - public string? ParentVersionId { get; set; } - - public string Id { get; set; } - - public MFileMetadata? Assets { get; set; } - - public string? JavaVersion { get; set; } - public string? JavaBinaryPath { get; set; } - public string? Jar { get; set; } - public MFileMetadata? Client { get; set; } - public MLibrary[]? Libraries { get; set; } - public string? MainClass { get; set; } - public string? MinecraftArguments { get; set; } - public string[]? GameArguments { get; set; } - public string[]? JvmArguments { get; set; } - public string? ReleaseTime { get; set; } - public MVersionType Type { get; set; } = MVersionType.Custom; - public string? TypeStr { get; set; } - public MLogConfiguration? LoggingClient { get; set; } - - public void InheritFrom(MVersion parentVersion) + + if (Client == null) + Client = parentVersion.Client; + else { - /* - Overload : - AssetId, AssetUrl, AssetHash, ClientDownloadUrl, - ClientHash, MainClass, MinecraftArguments, JavaVersion - - Combine : - Libraries, GameArguments, JvmArguments - */ - - // Overloads - - if (Assets == null) - Assets = parentVersion.Assets; - else - { - if (string.IsNullOrEmpty(Assets.Id)) - Assets.Id = parentVersion.Assets?.Id; + if (string.IsNullOrEmpty(Client.Url)) + Client.Url = parentVersion.Client?.Url; + + if (string.IsNullOrEmpty(Client.Sha1)) + Client.Sha1 = parentVersion.Client?.Sha1; + } + + if (string.IsNullOrEmpty(MainClass)) + MainClass = parentVersion.MainClass; + + if (string.IsNullOrEmpty(MinecraftArguments)) + MinecraftArguments = parentVersion.MinecraftArguments; - if (string.IsNullOrEmpty(Assets.Url)) - Assets.Url = parentVersion.Assets?.Url; + if (string.IsNullOrEmpty(JavaVersion)) + JavaVersion = parentVersion.JavaVersion; - if (string.IsNullOrEmpty(Assets.Sha1)) - Assets.Sha1 = parentVersion.Assets?.Sha1; - } + if (LoggingClient == null) + LoggingClient = parentVersion.LoggingClient; + //Jar = parentVersion.Jar; - if (Client == null) - Client = parentVersion.Client; + // Combine + + if (parentVersion.Libraries != null) + { + if (Libraries != null) + Libraries = Libraries.Concat(parentVersion.Libraries).ToArray(); + else + Libraries = parentVersion.Libraries; + } + + if (parentVersion.GameArguments != null) + { + if (GameArguments != null) + GameArguments = parentVersion.GameArguments.Concat(GameArguments).ToArray(); else - { - if (string.IsNullOrEmpty(Client.Url)) - Client.Url = parentVersion.Client?.Url; - - if (string.IsNullOrEmpty(Client.Sha1)) - Client.Sha1 = parentVersion.Client?.Sha1; - } - - if (string.IsNullOrEmpty(MainClass)) - MainClass = parentVersion.MainClass; - - if (string.IsNullOrEmpty(MinecraftArguments)) - MinecraftArguments = parentVersion.MinecraftArguments; - - if (string.IsNullOrEmpty(JavaVersion)) - JavaVersion = parentVersion.JavaVersion; - - if (LoggingClient == null) - LoggingClient = parentVersion.LoggingClient; - //Jar = parentVersion.Jar; - - // Combine - - if (parentVersion.Libraries != null) - { - if (Libraries != null) - Libraries = Libraries.Concat(parentVersion.Libraries).ToArray(); - else - Libraries = parentVersion.Libraries; - } - - if (parentVersion.GameArguments != null) - { - if (GameArguments != null) - GameArguments = parentVersion.GameArguments.Concat(GameArguments).ToArray(); - else - GameArguments = parentVersion.GameArguments; - } - - if (parentVersion.JvmArguments != null) - { - if (JvmArguments != null) - JvmArguments = parentVersion.JvmArguments.Concat(JvmArguments).ToArray(); - else - JvmArguments = parentVersion.JvmArguments; - } + GameArguments = parentVersion.GameArguments; } - public override string ToString() + if (parentVersion.JvmArguments != null) { - return this.Id; + if (JvmArguments != null) + JvmArguments = parentVersion.JvmArguments.Concat(JvmArguments).ToArray(); + else + JvmArguments = parentVersion.JvmArguments; } } + + public override string ToString() + { + return this.Id; + } } diff --git a/src/Core/Version/MVersionCollection.cs b/src/Core/Version/MVersionCollection.cs deleted file mode 100644 index e00c1ac..0000000 --- a/src/Core/Version/MVersionCollection.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Threading.Tasks; -using CmlLib.Core.VersionMetadata; - -namespace CmlLib.Core.Version -{ - // Collection for MVersionMetadata - // return MVersion object from MVersionMetadata - public class MVersionCollection : IEnumerable - { - public MVersionCollection(MVersionMetadata[] versions) - : this(versions, null, null, null) - { - - } - - public MVersionCollection(MVersionMetadata[] versions, MinecraftPath originalPath) - : this(versions, originalPath, null, null) - { - - } - - public MVersionCollection( - IEnumerable versions, - MinecraftPath? originalPath, - MVersionMetadata? latestRelease, - MVersionMetadata? latestSnapshot) - { - if (versions == null) - throw new ArgumentNullException(nameof(versions)); - - Versions = new OrderedDictionary(); - foreach (var item in versions) - { - Versions.Add(item.Name, item); - } - - MinecraftPath = originalPath; - LatestReleaseVersion = latestRelease; - LatestSnapshotVersion = latestSnapshot; - } - - // Use OrderedDictionary to keep version order - protected OrderedDictionary Versions; - - public MVersionMetadata? LatestReleaseVersion { get; private set; } - public MVersionMetadata? LatestSnapshotVersion { get; private set; } - public MinecraftPath? MinecraftPath { get; private set; } - - public MVersionMetadata this[int index] => (MVersionMetadata)Versions[index]!; - - public MVersionMetadata GetVersionMetadata(string name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - // Versions[name] will return null if index does not exists - // Casting from null to MVersionMetadata does not throw NullReferenceException - MVersionMetadata? versionMetadata = (MVersionMetadata?)Versions[name]; - if (versionMetadata == null) - throw new KeyNotFoundException("Cannot find " + name); - - return versionMetadata; - } - - public MVersionMetadata[] ToArray(MVersionSortOption option) - { - var sorter = new MVersionMetadataSorter(option); - return sorter.Sort(this); - } - - public virtual Task GetVersionAsync(string name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - var versionMetadata = GetVersionMetadata(name); - return GetVersionAsync(versionMetadata); - } - - // get MVersion from MVersionMetadata - public virtual async Task GetVersionAsync(MVersionMetadata versionMetadata) - { - if (versionMetadata == null) - throw new ArgumentNullException(nameof(versionMetadata)); - - MVersion startVersion; - if (MinecraftPath == null) - startVersion = await versionMetadata.GetVersionAsync() - .ConfigureAwait(false); - else - startVersion = await versionMetadata.GetVersionAsync(MinecraftPath) - .ConfigureAwait(false); - - if (startVersion.IsInherited && !string.IsNullOrEmpty(startVersion.ParentVersionId)) - { - if (startVersion.ParentVersionId == startVersion.Id) // prevent StackOverFlowException - throw new InvalidDataException( - "Invalid version json file : inheritFrom property is equal to id property."); - - var baseVersion = await GetVersionAsync(startVersion.ParentVersionId) - .ConfigureAwait(false); - startVersion.InheritFrom(baseVersion); - } - - return startVersion; - } - - public void AddVersion(MVersionMetadata version) - { - Versions[version.Name] = version; - } - - public bool Contains(string? versionName) - => !string.IsNullOrEmpty(versionName) && Versions.Contains(versionName); - - public virtual void Merge(MVersionCollection from) - { - foreach (var item in from) - { - var version = (MVersionMetadata?)Versions[item.Name]; - if (version == null) - { - Versions[item.Name] = item; - } - else - { - if (string.IsNullOrEmpty(version.Type)) - { - version.Type = item.Type; - version.MType = MVersionTypeConverter.FromString(item.Type); - } - - if (string.IsNullOrEmpty(version.ReleaseTimeStr)) - version.ReleaseTimeStr = item.ReleaseTimeStr; - } - } - - if (this.MinecraftPath == null && from.MinecraftPath != null) - this.MinecraftPath = from.MinecraftPath; - - if (this.LatestReleaseVersion == null && from.LatestReleaseVersion != null) - this.LatestReleaseVersion = from.LatestReleaseVersion; - - if (this.LatestSnapshotVersion == null && from.LatestSnapshotVersion != null) - this.LatestSnapshotVersion = from.LatestSnapshotVersion; - } - - public IEnumerator GetEnumerator() - { - foreach (DictionaryEntry? item in Versions) - { - var entry = item.Value; - - var version = (MVersionMetadata)entry.Value!; - yield return version; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - foreach (DictionaryEntry? item in Versions) - { - yield return item.Value; - } - } - } -} diff --git a/src/Core/Version/MVersionParseException.cs b/src/Core/Version/MVersionParseException.cs index 9fdcd51..25628b8 100644 --- a/src/Core/Version/MVersionParseException.cs +++ b/src/Core/Version/MVersionParseException.cs @@ -1,19 +1,16 @@ -using System; +namespace CmlLib.Core.Version; -namespace CmlLib.Core.Version +public class MVersionParseException : Exception { - public class MVersionParseException : Exception + public MVersionParseException(string message) : base(message) { - public MVersionParseException(string message) : base(message) - { - } - - public MVersionParseException(Exception inner) : base("Failed to parse version", inner) - { + } - } + public MVersionParseException(Exception inner) : base("Failed to parse version", inner) + { - public string? VersionName { get; internal set; } } + + public string? VersionName { get; internal set; } } diff --git a/src/Core/Version/MVersionParser.cs b/src/Core/Version/MVersionParser.cs deleted file mode 100644 index 46f19a2..0000000 --- a/src/Core/Version/MVersionParser.cs +++ /dev/null @@ -1,177 +0,0 @@ -using CmlLib.Core.Files; -using CmlLib.Utils; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; - -namespace CmlLib.Core.Version -{ - public static class MVersionParser - { - public static MVersion ParseFromFile(string path) - { - string json = File.ReadAllText(path); - return ParseFromJson(json); - } - - public static MVersion ParseFromJson(string json) - { - using var jsonDocument = JsonDocument.Parse(json); - return ParseFromJson(jsonDocument); - } - - public static MVersion ParseFromJson(JsonDocument document) - { - try - { - var root = document.RootElement; - - // id - if (!root.TryGetProperty("id", out var idElement)) - throw new MVersionParseException("Empty version id"); - - var id = idElement.GetString() - ?? throw new MVersionParseException("Empty version id"); - - var version = new MVersion(id); - - // javaVersion - version.JavaVersion = root.SafeGetProperty("javaVersion")?.SafeGetProperty("component")?.GetString(); - - // assets - if (root.TryGetProperty("assetIndex", out var assetIndex)) - version.Assets = assetIndex.Deserialize(JsonUtil.JsonOptions); - else if (root.TryGetProperty("assets", out var assets)) - version.Assets = new MFileMetadata(assets.GetString()); - - // client jar - var client = root.SafeGetProperty("downloads")?.SafeGetProperty("client"); - if (client.HasValue) - version.Client = client.Value.Deserialize(JsonUtil.JsonOptions); - - // libraries - if (root.TryGetProperty("libraries", out var libProp) && libProp.ValueKind == JsonValueKind.Array) - { - var libList = new List(); - var libParser = new MLibraryParser(); - foreach (var item in libProp.EnumerateArray()) - { - var libs = libParser.ParseJsonObject(item); - if (libs != null) - libList.AddRange(libs); - } - - version.Libraries = libList.ToArray(); - } - - // mainClass - version.MainClass = root.GetPropertyValue("mainClass"); - - // argument - version.MinecraftArguments = root.GetPropertyValue("minecraftArguments"); - - if (root.TryGetProperty("arguments", out var ag)) - { - if (ag.TryGetProperty("game", out var gameArg) && gameArg.ValueKind == JsonValueKind.Array) - version.GameArguments = argParse(gameArg); - if (ag.TryGetProperty("jvm", out var jvmArg) && jvmArg.ValueKind == JsonValueKind.Array) - version.JvmArguments = argParse(jvmArg); - } - - // metadata - version.ReleaseTime = root.GetPropertyValue("releaseTime"); - - var type = root.GetPropertyValue("type"); - version.TypeStr = type; - version.Type = MVersionTypeConverter.FromString(type); - - // inherits - version.ParentVersionId = root.GetPropertyValue("inheritsFrom"); - version.IsInherited = string.IsNullOrEmpty(version.ParentVersionId); - - version.Jar = root.GetPropertyValue("jar"); - if (string.IsNullOrEmpty(version.Jar)) - version.Jar = version.Id; - - // logging - var loggingClient = root.SafeGetProperty("logging")?.SafeGetProperty("client"); - if (loggingClient != null) - { - var logFile = loggingClient.Value.SafeGetProperty("file")?.Deserialize(JsonUtil.JsonOptions); - version.LoggingClient = new MLogConfiguration - { - LogFile = logFile, - Type = loggingClient.Value.GetPropertyValue("type"), - Argument = loggingClient.Value.GetPropertyValue("argument") - }; - } - - return version; - } - catch (MVersionParseException) - { - throw; - } - catch (Exception ex) - { - throw new MVersionParseException(ex); - } - } - - // TODO: create argument object - private static string[] argParse(JsonElement arr) - { - var strList = new List(); - - foreach (var item in arr.EnumerateArray()) - { - if (item.ValueKind == JsonValueKind.Object) - { - bool allow = true; - - var rules = item.SafeGetProperty("rules"); - if (rules == null || rules.Value.ValueKind != JsonValueKind.Array) - rules = item.SafeGetProperty("compatibilityRules"); - - if (rules != null) - allow = MRule.CheckOSRequire(rules.Value); - - if (allow) - { - var value = item.SafeGetProperty("value") ?? item.SafeGetProperty("values"); - if (value != null) - { - if (value.Value.ValueKind == JsonValueKind.Array) - { - foreach (var strProp in value.Value.EnumerateArray()) - { - if (strProp.ValueKind != JsonValueKind.String) - continue; - - var str = strProp.GetString(); - if (!string.IsNullOrEmpty(str)) - strList.Add(str); - } - } - else if (value.Value.ValueKind == JsonValueKind.String) - { - var valueString = value.Value.GetString(); - if (!string.IsNullOrEmpty(valueString)) - strList.Add(valueString); - } - } - } - } - else - { - var value = item.GetString(); - if (!string.IsNullOrEmpty(value)) - strList.Add(value); - } - } - - return strList.ToArray(); - } - } -} diff --git a/src/Core/Version/MVersionSortOption.cs b/src/Core/Version/MVersionSortOption.cs deleted file mode 100644 index b0d15cd..0000000 --- a/src/Core/Version/MVersionSortOption.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace CmlLib.Core.Version -{ - public enum MVersionSortPropertyOption - { - Name, - Version, - ReleaseDate - } - - // Decide how to sort version if ReleaseDate is undefined - public enum MVersionNullReleaseDateSortOption - { - AsLatest, - AsOldest - } - - public class MVersionSortOption - { - public MVersionType[] TypeOrder { get; set; } = - { - MVersionType.Custom, - MVersionType.Release, - MVersionType.Snapshot, - MVersionType.OldBeta, - MVersionType.OldAlpha - }; - - public MVersionSortPropertyOption PropertyOrderBy { get; set; } - = MVersionSortPropertyOption.Version; - - public bool AscendingPropertyOrder { get; set; } = true; - - public MVersionNullReleaseDateSortOption NullReleaseDateSortOption { get; set; } = - MVersionNullReleaseDateSortOption.AsOldest; - - public bool CustomAsRelease { get; set; } = false; - public bool TypeClassification { get; set; } = true; - } -} diff --git a/src/Core/Version/MVersionSorter.cs b/src/Core/Version/MVersionSorter.cs deleted file mode 100644 index c20d67e..0000000 --- a/src/Core/Version/MVersionSorter.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using CmlLib.Core.VersionMetadata; - -namespace CmlLib.Core.Version -{ - // Sort MVersionMetadata - // TODO: measure performance and optimizing - public class MVersionMetadataSorter - { - public MVersionMetadataSorter(MVersionSortOption option) - { - this.option = option; - - this.typePriority = new Dictionary(); - for (int i = 0; i < option.TypeOrder.Length; i++) - { - var type = option.TypeOrder[i]; - typePriority[type] = i; - } - - var propertyList = new List(); - propertyList.Add(option.PropertyOrderBy); - foreach (MVersionSortPropertyOption item in Enum.GetValues(typeof(MVersionSortPropertyOption))) - { - if (option.PropertyOrderBy != item) - propertyList.Add(item); - } - - propertyOptions = propertyList.ToArray(); - - switch (option.NullReleaseDateSortOption) - { - case MVersionNullReleaseDateSortOption.AsLatest: - defaultDateTime = DateTime.MaxValue; - break; - case MVersionNullReleaseDateSortOption.AsOldest: - defaultDateTime = DateTime.MinValue; - break; - } - } - - private readonly MVersionSortPropertyOption[] propertyOptions; - private readonly DateTime defaultDateTime; - private readonly Dictionary typePriority; - private readonly MVersionSortOption option; - - public MVersionMetadata[] Sort(IEnumerable org) - { - var filtered = org.Where(x => getTypePriority(x.MType) >= 0) - .ToArray(); - Array.Sort(filtered, compare); - return filtered; - } - - private int getTypePriority(MVersionType type) - { - if (option.CustomAsRelease && type == MVersionType.Custom) - type = MVersionType.Release; - - if (typePriority.TryGetValue(type, out int p)) - return p; - - return -1; - } - - private int compareType(MVersionMetadata v1, MVersionMetadata v2) - { - var v1TypePrior = getTypePriority(v1.MType); - var v2TypePrior = getTypePriority(v2.MType); - return v1TypePrior - v2TypePrior; - } - - private int compareName(MVersionMetadata v1, MVersionMetadata v2) - { - var result = string.CompareOrdinal(v1.Name, v2.Name); - if (!option.AscendingPropertyOrder) - result *= -1; - return result; - } - - private int compareVersion(MVersionMetadata v1, MVersionMetadata v2) - { - bool v1r = System.Version.TryParse(v1.Name, out System.Version? v1v); - bool v2r = System.Version.TryParse(v2.Name, out System.Version? v2v); - - if (!v1r && !v2r) - return 0; - if (!v1r || v1v == null) // v1 > v2 - return 1; - if (!v2r || v2v == null) // v1 < v2 - return -1; - - var result = v1v.CompareTo(v2v); - if (!option.AscendingPropertyOrder) - result *= -1; - return result; - } - - private int compareReleaseDate(MVersionMetadata v1, MVersionMetadata v2) - { - var v1DateTime = v1.ReleaseTime ?? defaultDateTime; - var v2DateTime = v2.ReleaseTime ?? defaultDateTime; - var result = DateTime.Compare(v1DateTime, v2DateTime); - if (!option.AscendingPropertyOrder) - result *= -1; - return result; - } - - private int compareProperty(MVersionMetadata v1, MVersionMetadata v2, - MVersionSortPropertyOption propertyOption) - { - switch (propertyOption) - { - case MVersionSortPropertyOption.Name: - return compareName(v1, v2); - case MVersionSortPropertyOption.ReleaseDate: - return compareReleaseDate(v1, v2); - case MVersionSortPropertyOption.Version: - return compareVersion(v1, v2); - } - - return 0; - } - - private int compare(MVersionMetadata v1, MVersionMetadata v2) - { - var typeCompareResult = compareType(v1, v2); - - if (option.TypeClassification && typeCompareResult != 0) - return typeCompareResult; - - foreach (var propOption in propertyOptions) - { - int result = compareProperty(v1, v2, propOption); - if (result != 0) - return result; - } - - return typeCompareResult; - } - } -} \ No newline at end of file diff --git a/src/Core/Version/MVersionType.cs b/src/Core/Version/MVersionType.cs deleted file mode 100644 index b1bc70d..0000000 --- a/src/Core/Version/MVersionType.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace CmlLib.Core.Version -{ - public static class MVersionTypeConverter - { - public static MVersionType FromString(string? str) - { - MVersionType e; - - switch (str) - { - case "release": - e = MVersionType.Release; - break; - case "snapshot": - e = MVersionType.Snapshot; - break; - case "old_alpha": - e = MVersionType.OldAlpha; - break; - case "old_beta": - e = MVersionType.OldBeta; - break; - default: - e = MVersionType.Custom; - break; - } - - return e; - } - - public static string ToString(MVersionType type) - { - string c; - - switch (type) - { - case MVersionType.OldAlpha: - c = "old_alpha"; - break; - case MVersionType.OldBeta: - c = "old_beta"; - break; - case MVersionType.Snapshot: - c = "snapshot"; - break; - case MVersionType.Release: - c = "release"; - break; - default: - c = "unknown"; - break; - } - - return c; - } - - public static bool CheckOld(string vn) - { - return CheckOld(FromString(vn)); - } - - public static bool CheckOld(MVersionType t) - { - if (t == MVersionType.OldAlpha || t == MVersionType.OldBeta) - return true; - else - return false; - } - } - - public enum MVersionType - { - OldAlpha, - OldBeta, - Snapshot, - Release, - Custom - } -} diff --git a/src/Core/VersionLoader/DefaultVersionLoader.cs b/src/Core/VersionLoader/DefaultVersionLoader.cs deleted file mode 100644 index 300bdd8..0000000 --- a/src/Core/VersionLoader/DefaultVersionLoader.cs +++ /dev/null @@ -1,37 +0,0 @@ -using CmlLib.Core.Version; -using System.Threading.Tasks; - -namespace CmlLib.Core.VersionLoader -{ - public class DefaultVersionLoader : IVersionLoader - { - private readonly MinecraftPath minecraftPath; - - public DefaultVersionLoader(MinecraftPath path) - { - minecraftPath = path; - } - - public async Task GetVersionMetadatasAsync() - { - var localVersionLoader = new LocalVersionLoader(minecraftPath); - var mojangVersionLoader = new MojangVersionLoader(); - - var mojangVersions = await mojangVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - var localVersions = await localVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - - //below code could break the order of version list - //mojangVersions.Merge(localVersions); - - // normal order: local versions before mojang versions - // local 1.16.~~ - // local 1.15.~~ - // mojang 1.14.~~ - - localVersions.Merge(mojangVersions); - return localVersions; - } - } -} diff --git a/src/Core/VersionLoader/IVersionLoader.cs b/src/Core/VersionLoader/IVersionLoader.cs index f66f217..adfc131 100644 --- a/src/Core/VersionLoader/IVersionLoader.cs +++ b/src/Core/VersionLoader/IVersionLoader.cs @@ -1,10 +1,9 @@ -using CmlLib.Core.Version; -using System.Threading.Tasks; +using CmlLib.Core.VersionMetadata; namespace CmlLib.Core.VersionLoader { public interface IVersionLoader { - Task GetVersionMetadatasAsync(); + ValueTask GetVersionMetadatasAsync(); } } diff --git a/src/Core/VersionLoader/LocalVersionLoader.cs b/src/Core/VersionLoader/LocalVersionLoader.cs index 547ec8e..642aaa6 100644 --- a/src/Core/VersionLoader/LocalVersionLoader.cs +++ b/src/Core/VersionLoader/LocalVersionLoader.cs @@ -1,55 +1,41 @@ -using CmlLib.Core.Version; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using CmlLib.Core.VersionMetadata; +using CmlLib.Core.VersionMetadata; -namespace CmlLib.Core.VersionLoader +namespace CmlLib.Core.VersionLoader; + +public class LocalVersionLoader : IVersionLoader { - public class LocalVersionLoader : IVersionLoader + public LocalVersionLoader(MinecraftPath path) { - public LocalVersionLoader(MinecraftPath path) - { - minecraftPath = path; - } - - private readonly MinecraftPath minecraftPath; + minecraftPath = path; + } - public MVersionCollection GetVersionMetadatas() - { - var list = getFromLocal(minecraftPath).ToArray(); - return new MVersionCollection(list, minecraftPath); - } + private readonly MinecraftPath minecraftPath; - public Task GetVersionMetadatasAsync() - { - return Task.FromResult(GetVersionMetadatas()); - } + public ValueTask GetVersionMetadatasAsync() + { + var versions = getFromLocal(minecraftPath); + var collection = new MVersionCollection(versions, null, null); + return new ValueTask(collection); + } - private List getFromLocal(MinecraftPath path) + private IEnumerable getFromLocal(MinecraftPath path) + { + var versionDirectory = new DirectoryInfo(path.Versions); + if (!versionDirectory.Exists) + yield break; + + var dirs = versionDirectory.GetDirectories(); + foreach (var dir in dirs) { - var versionDirectory = new DirectoryInfo(path.Versions); - if (!versionDirectory.Exists) - return new List(); - - var dirs = versionDirectory.GetDirectories(); - var arr = new List(dirs.Length); + var filepath = Path.Combine(dir.FullName, dir.Name + ".json"); + if (!File.Exists(filepath)) continue; - foreach (var dir in dirs) + var model = new JsonVersionMetadataModel { - var filepath = Path.Combine(dir.FullName, dir.Name + ".json"); - if (!File.Exists(filepath)) continue; - - var info = new LocalVersionMetadata(dir.Name) - { - Path = filepath, - Type = "local", - MType = MVersionType.Custom - }; - arr.Add(info); - } - - return arr; + Name = dir.Name, + Type = "local", + }; + yield return new LocalVersionMetadata(model, filepath); } } } diff --git a/src/Core/VersionLoader/MojangVersionLoader.cs b/src/Core/VersionLoader/MojangVersionLoader.cs index 82bf61b..5345393 100644 --- a/src/Core/VersionLoader/MojangVersionLoader.cs +++ b/src/Core/VersionLoader/MojangVersionLoader.cs @@ -1,60 +1,46 @@ -using CmlLib.Core.Version; -using System.Collections.Generic; -using System.Threading.Tasks; -using CmlLib.Core.VersionMetadata; +using CmlLib.Core.VersionMetadata; using CmlLib.Utils; using System.Text.Json; -namespace CmlLib.Core.VersionLoader -{ - public class MojangVersionLoader : IVersionLoader - { - public async Task GetVersionMetadatasAsync() - { - var res = await HttpUtil.HttpClient.GetStringAsync(MojangServer.Version); - return parseList(res); - } +namespace CmlLib.Core.VersionLoader; - private MVersionCollection parseList(string res) - { - string? latestReleaseId = null; - string? latestSnapshotId = null; +public class MojangVersionLoader : IVersionLoader +{ + private readonly HttpClient _httpClient; + private readonly string _endpoint; - MVersionMetadata? latestRelease = null; - MVersionMetadata? latestSnapshot = null; + public MojangVersionLoader(HttpClient httpClient) => + (_httpClient, _endpoint) = (httpClient, MojangServer.Version); - using var jsonDocument = JsonDocument.Parse(res); - var root = jsonDocument.RootElement; + public MojangVersionLoader(HttpClient httpClient, string endpoint) => + (_httpClient, _endpoint) = (httpClient, endpoint); - if (root.TryGetProperty("latest", out var latest)) + public async ValueTask GetVersionMetadatasAsync() + { + var res = await _httpClient.GetStreamAsync(_endpoint) + .ConfigureAwait(false); + + using var jsonDocument = await JsonDocument.ParseAsync(res); + var root = jsonDocument.RootElement; + + var latestReleaseId = root.GetPropertyOrNull("latest")?.GetPropertyValue("release"); + var latestSnapshotId = root.GetPropertyOrNull("latest")?.GetPropertyValue("snapshot"); + + var metadatas = new List(); + if (root.TryGetProperty("versions", out var versions) && + versions.ValueKind == JsonValueKind.Array) + { + foreach (var t in versions.EnumerateArray()) { - latestReleaseId = latest.GetPropertyValue("release"); - latestSnapshotId = latest.GetPropertyValue("snapshot"); - } - - bool checkLatestRelease = !string.IsNullOrEmpty(latestReleaseId); - bool checkLatestSnapshot = !string.IsNullOrEmpty(latestSnapshotId); + var metadataModel = t.Deserialize(); + if (metadataModel == null) + continue; - var arr = new List(); - if (root.TryGetProperty("versions", out var versions) && versions.ValueKind == JsonValueKind.Array) - { - foreach (var t in versions.EnumerateArray()) - { - var obj = t.Deserialize(); - if (obj == null) - continue; - - obj.MType = MVersionTypeConverter.FromString(obj.Type); - arr.Add(obj); - - if (checkLatestRelease && obj.Name == latestReleaseId) - latestRelease = obj; - if (checkLatestSnapshot && obj.Name == latestSnapshotId) - latestSnapshot = obj; - } + var metadata = new MojangVersionMetadata(metadataModel, _httpClient); + metadatas.Add(metadata); } - - return new MVersionCollection(arr, null, latestRelease, latestSnapshot); } + + return new MVersionCollection(metadatas, latestReleaseId, latestSnapshotId); } } diff --git a/src/Core/VersionLoader/VersionLoaderCollection.cs b/src/Core/VersionLoader/VersionLoaderCollection.cs new file mode 100644 index 0000000..627f554 --- /dev/null +++ b/src/Core/VersionLoader/VersionLoaderCollection.cs @@ -0,0 +1,31 @@ +using System.Collections; +using CmlLib.Core.VersionMetadata; + +namespace CmlLib.Core.VersionLoader; + +public class VersionLoaderCollection : IVersionLoader, ICollection +{ + private readonly List _collection = new(); + + public async ValueTask GetVersionMetadatasAsync() + { + var versions = new MVersionCollection(); + foreach (var loader in _collection) + { + var newVersions = await loader.GetVersionMetadatasAsync(); + versions.Merge(newVersions); + } + return versions; + } + + // ICollection implementation + public int Count => _collection.Count(); + public bool IsReadOnly => throw new NotImplementedException(); + public void Add(IVersionLoader item) => _collection.Add(item); + public void Clear() => _collection.Clear(); + public bool Contains(IVersionLoader item) => _collection.Contains(item); + public void CopyTo(IVersionLoader[] array, int arrayIndex) => _collection.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _collection.GetEnumerator(); + public bool Remove(IVersionLoader item) => _collection.Remove(item); + IEnumerator IEnumerable.GetEnumerator() => _collection.GetEnumerator(); +} diff --git a/src/Core/VersionMetadata/Extensions.cs b/src/Core/VersionMetadata/Extensions.cs new file mode 100644 index 0000000..b26cd1a --- /dev/null +++ b/src/Core/VersionMetadata/Extensions.cs @@ -0,0 +1,9 @@ +namespace CmlLib.Core.VersionMetadata; + +public static class Extensions +{ + public static MVersionType GetVersionType(this IVersionMetadata version) + { + return MVersionTypeConverter.FromString(version.Type); + } +} \ No newline at end of file diff --git a/src/Core/VersionMetadata/IVersionMetadata.cs b/src/Core/VersionMetadata/IVersionMetadata.cs new file mode 100644 index 0000000..b2adad3 --- /dev/null +++ b/src/Core/VersionMetadata/IVersionMetadata.cs @@ -0,0 +1,18 @@ +using CmlLib.Core.Version; + +namespace CmlLib.Core.VersionMetadata; + +/// +/// Represent version metadata +/// It does not contains actual version data, but contains some metadata and the way to get version data (MVersion) +/// +public interface IVersionMetadata +{ + string Name { get; } + string? Type { get; } + DateTime? ReleaseTime { get; } + + Task GetVersionAsync(); + Task GetAndSaveVersionAsync(MinecraftPath minecraftPath); + Task SaveVersionAsync(MinecraftPath minecraftPath); +} diff --git a/src/Core/VersionMetadata/JsonVersionMetadata.cs b/src/Core/VersionMetadata/JsonVersionMetadata.cs new file mode 100644 index 0000000..dc0fb06 --- /dev/null +++ b/src/Core/VersionMetadata/JsonVersionMetadata.cs @@ -0,0 +1,85 @@ +using CmlLib.Core.Version; +using CmlLib.Utils; + +namespace CmlLib.Core.VersionMetadata; + +/// +/// Represent JSON text based version metadata +/// +public abstract class JsonVersionMetadata : IVersionMetadata +{ + public JsonVersionMetadata(JsonVersionMetadataModel model) + { + if (string.IsNullOrEmpty(model.Name)) + throw new ArgumentNullException(nameof(model.Name)); + Name = model.Name; + Type = model.Type; + ReleaseTime = model.ReleaseTime; + } + + public bool IsSaved { get; set; } + public string Name { get; } + public string? Type { get; } + public DateTime? ReleaseTime { get; } + + + public override bool Equals(object? obj) + { + if (obj == null) + return false; + + var info = obj as IVersionMetadata; + + if (info?.Name != null) // obj is MVersionMetadata + return info.Name.Equals(Name); + if (obj is string) + return Name.Equals(obj.ToString()); + + return false; + } + + public override string ToString() + { + return Type + " " + Name; + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + /// + /// Get actual version data as string + /// + /// Version metadata + protected abstract ValueTask GetVersionJsonString(); + + public async Task GetVersionAsync() + { + var metadataJson = await GetVersionJsonString().ConfigureAwait(false); + return new JsonVersionParser().ParseFromJsonString(metadataJson); + } + + public async Task GetAndSaveVersionAsync(MinecraftPath path) + { + var metadataJson = await GetVersionJsonString().ConfigureAwait(false); + var version = new JsonVersionParser().ParseFromJsonString(metadataJson); + await saveMetdataJson(path, metadataJson); + return version; + } + + public async Task SaveVersionAsync(MinecraftPath path) + { + var metadataJson = await GetVersionJsonString().ConfigureAwait(false); + await saveMetdataJson(path, metadataJson); + } + + private async Task saveMetdataJson(MinecraftPath path, string json) + { + if (IsSaved) + return; + var metadataPath = path.GetVersionJsonPath(Name); + IOUtil.CreateParentDirectory(metadataPath); + await IOUtil.WriteFileAsync(metadataPath, json).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Core/VersionMetadata/JsonVersionMetadataModel.cs b/src/Core/VersionMetadata/JsonVersionMetadataModel.cs new file mode 100644 index 0000000..a8ad437 --- /dev/null +++ b/src/Core/VersionMetadata/JsonVersionMetadataModel.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.VersionMetadata; + +public class JsonVersionMetadataModel +{ + [JsonPropertyName("id")] + public string? Name { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } + + [JsonPropertyName("releaseTime")] + public DateTime? ReleaseTime { get; set; } + + [JsonPropertyName("url")] + public string? Url { get; set; } +} \ No newline at end of file diff --git a/src/Core/VersionMetadata/LocalVersionMetadata.cs b/src/Core/VersionMetadata/LocalVersionMetadata.cs index adfa9e4..87cac44 100644 --- a/src/Core/VersionMetadata/LocalVersionMetadata.cs +++ b/src/Core/VersionMetadata/LocalVersionMetadata.cs @@ -1,26 +1,26 @@ -using System; -using System.Threading.Tasks; -using CmlLib.Utils; +using CmlLib.Utils; -namespace CmlLib.Core.VersionMetadata +namespace CmlLib.Core.VersionMetadata; + +/// +/// Represent metadata where the actual version data is in local file +/// +public class LocalVersionMetadata : JsonVersionMetadata { - /// - /// Represent metadata where the actual version data is in local file - /// - public class LocalVersionMetadata : StringVersionMetadata + public string Path { get; } + + public LocalVersionMetadata(JsonVersionMetadataModel model, string path) : base(model) { - public LocalVersionMetadata(string id) : base(id) - { - IsLocalVersion = true; - } + IsSaved = true; + Path = path; + } - protected override Task ReadVersionDataAsync() - { - if (string.IsNullOrEmpty(Path)) - throw new InvalidOperationException("Path property was null"); - - // FileNotFoundException will be thrown if Path does not exist. - return IOUtil.ReadFileAsync(Path); - } + protected override async ValueTask GetVersionJsonString() + { + if (string.IsNullOrEmpty(Path)) + throw new InvalidOperationException("Path property was null"); + + // FileNotFoundException will be thrown if Path does not exist. + return await IOUtil.ReadFileAsync(Path); } } \ No newline at end of file diff --git a/src/Core/VersionMetadata/MVersionCollection.cs b/src/Core/VersionMetadata/MVersionCollection.cs new file mode 100644 index 0000000..c0dff2d --- /dev/null +++ b/src/Core/VersionMetadata/MVersionCollection.cs @@ -0,0 +1,156 @@ +using System.Collections; +using System.Collections.Specialized; +using CmlLib.Core.Version; + +namespace CmlLib.Core.VersionMetadata; + +// Collection for IVersionMetadata +// return MVersion object from IVersionMetadata +public class MVersionCollection : IEnumerable +{ + public MVersionCollection() + : this(Enumerable.Empty(), null, null) + { + + } + + public MVersionCollection( + IEnumerable versions, + string? latestRelease, + string? latestSnapshot) + { + if (versions == null) + throw new ArgumentNullException(nameof(versions)); + + Versions = new OrderedDictionary(); + foreach (var item in versions) + { + Versions.Add(item.Name, item); + } + + LatestReleaseName = latestRelease; + LatestSnapshotName = latestSnapshot; + } + + // Use OrderedDictionary to keep version order + protected OrderedDictionary Versions; + + public string? LatestReleaseName { get; private set; } + public string? LatestSnapshotName { get; private set; } + + public IVersionMetadata this[int index] => (IVersionMetadata)Versions[index]!; + + public IVersionMetadata GetVersionMetadata(string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + var versionMetadata = Versions[name] as IVersionMetadata; + if (versionMetadata == null) + throw new KeyNotFoundException("Cannot find " + name); + + return versionMetadata; + } + + public IVersionMetadata[] ToArray(MVersionSortOption option) + { + var sorter = new MVersionMetadataSorter(option); + return sorter.Sort(this); + } + + public Task GetVersionAsync(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); + + var versionMetadata = GetVersionMetadata(name); + return GetVersionAsync(versionMetadata); + } + + public Task GetAndSaveVersionAsync(string name, MinecraftPath path) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); + + var versionMetadata = GetVersionMetadata(name); + return GetAndSaveVersionAsync(versionMetadata, path); + } + + public Task GetVersionAsync(IVersionMetadata versionMetadata) => + getVersionInternal(versionMetadata, null); + + public Task GetAndSaveVersionAsync(IVersionMetadata versionMetadata, MinecraftPath path) => + getVersionInternal(versionMetadata, path); + + private async Task getVersionInternal(IVersionMetadata versionMetadata, MinecraftPath? path) + { + if (versionMetadata == null) + throw new ArgumentNullException(nameof(versionMetadata)); + + MVersion version; + if (path == null) + version = await versionMetadata.GetVersionAsync(); + else + version = await versionMetadata.GetAndSaveVersionAsync(path); + + await inheritIfRequired(version, path); + return version; + } + + private async Task inheritIfRequired(MVersion version, MinecraftPath? path) + { + if (version.IsInherited && !string.IsNullOrEmpty(version.ParentVersionId)) + { + if (version.ParentVersionId == version.Id) // prevent StackOverFlowException + throw new InvalidDataException( + "Invalid version json file : inheritFrom property is equal to id property."); + + var baseVersionMetadata = GetVersionMetadata(version.ParentVersionId); + var baseVersion = await getVersionInternal(baseVersionMetadata, path); + version.InheritFrom(baseVersion); + } + } + + public void AddVersion(IVersionMetadata version) + { + Versions[version.Name] = version; + } + + public bool Contains(string? versionName) + => !string.IsNullOrEmpty(versionName) && Versions.Contains(versionName); + + public void Merge(MVersionCollection from) + { + foreach (var item in from) + { + if (!Contains(item.Name)) + { + Versions[item.Name] = item; + } + } + + if (string.IsNullOrEmpty(this.LatestReleaseName)) + this.LatestReleaseName = from.LatestReleaseName; + if (string.IsNullOrEmpty(this.LatestSnapshotName)) + this.LatestSnapshotName = from.LatestSnapshotName; + } + + public IEnumerator GetEnumerator() + { + foreach (DictionaryEntry? item in Versions) + { + var entry = item.Value; + + var version = (IVersionMetadata)entry.Value!; + yield return version; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (DictionaryEntry? item in Versions) + { + yield return item.Value; + } + } +} diff --git a/src/Core/VersionMetadata/MVersionMetadata.cs b/src/Core/VersionMetadata/MVersionMetadata.cs deleted file mode 100644 index d0503fd..0000000 --- a/src/Core/VersionMetadata/MVersionMetadata.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.VersionMetadata -{ - /// - /// Represent version metadata - /// It does not contains actual version data, but contains some metadata and the way to get version data (MVersion) - /// - public abstract class MVersionMetadata - { - protected MVersionMetadata(string name) - { - this.Name = name; - } - - public bool IsLocalVersion { get; set; } - - [JsonPropertyName("id")] - public string Name { get; } - - [JsonPropertyName("type")] - public string? Type { get; set; } - - public MVersionType MType { get; set; } - - [JsonPropertyName("releaseTime")] - public string? ReleaseTimeStr { get; set; } - [JsonIgnore] - public DateTime? ReleaseTime - { - get - { - if (DateTime.TryParse(this.ReleaseTimeStr, out DateTime dt)) - return dt; - return null; - } - } - - [JsonPropertyName("url")] - public string? Path { get; set; } - - public override bool Equals(object? obj) - { - if (obj == null) - return false; - - var info = obj as MVersionMetadata; - - if (info?.Name != null) // obj is MVersionMetadata - return info.Name.Equals(Name); - if (obj is string) - return Name.Equals(obj.ToString()); - - return false; - } - - public override string ToString() - { - return Type + " " + Name; - } - - public override int GetHashCode() - { - return Name.GetHashCode(); - } - - /// - /// Get version data - /// - /// MVersion object containing actual version data - public abstract Task GetVersionAsync(); - - /// - /// Get version data and save version data into file - /// - /// Game directory - /// MVersion object containing actual version data - public abstract Task GetVersionAsync(MinecraftPath savePath); - - /// - /// Get version data and save version data into file. This method may not make MVersion object - /// - /// Game directory - /// - public abstract Task SaveAsync(MinecraftPath path); - } -} diff --git a/src/Core/VersionMetadata/MVersionMetadataSorter.cs b/src/Core/VersionMetadata/MVersionMetadataSorter.cs new file mode 100644 index 0000000..e60416c --- /dev/null +++ b/src/Core/VersionMetadata/MVersionMetadataSorter.cs @@ -0,0 +1,138 @@ +namespace CmlLib.Core.VersionMetadata; + +// Sort IVersionMetadata +// TODO: measure performance and optimizing +public class MVersionMetadataSorter +{ + public MVersionMetadataSorter(MVersionSortOption option) + { + this.option = option; + + this.typePriority = new Dictionary(); + for (int i = 0; i < option.TypeOrder.Length; i++) + { + var type = option.TypeOrder[i]; + typePriority[type] = i; + } + + var propertyList = new List(); + propertyList.Add(option.PropertyOrderBy); + foreach (MVersionSortPropertyOption item in Enum.GetValues(typeof(MVersionSortPropertyOption))) + { + if (option.PropertyOrderBy != item) + propertyList.Add(item); + } + + propertyOptions = propertyList.ToArray(); + + switch (option.NullReleaseDateSortOption) + { + case MVersionNullReleaseDateSortOption.AsLatest: + defaultDateTime = DateTime.MaxValue; + break; + case MVersionNullReleaseDateSortOption.AsOldest: + defaultDateTime = DateTime.MinValue; + break; + } + } + + private readonly MVersionSortPropertyOption[] propertyOptions; + private readonly DateTime defaultDateTime; + private readonly Dictionary typePriority; + private readonly MVersionSortOption option; + + public IVersionMetadata[] Sort(IEnumerable org) + { + var filtered = org.Where(x => getTypePriority(x.GetVersionType()) >= 0) + .ToArray(); + Array.Sort(filtered, compare); + return filtered; + } + + private int getTypePriority(MVersionType type) + { + if (option.CustomAsRelease && type == MVersionType.Custom) + type = MVersionType.Release; + + if (typePriority.TryGetValue(type, out int p)) + return p; + + return -1; + } + + private int compareType(IVersionMetadata v1, IVersionMetadata v2) + { + var v1TypePrior = getTypePriority(v1.GetVersionType()); + var v2TypePrior = getTypePriority(v2.GetVersionType()); + return v1TypePrior - v2TypePrior; + } + + private int compareName(IVersionMetadata v1, IVersionMetadata v2) + { + var result = string.CompareOrdinal(v1.Name, v2.Name); + if (!option.AscendingPropertyOrder) + result *= -1; + return result; + } + + private int compareVersion(IVersionMetadata v1, IVersionMetadata v2) + { + bool v1r = System.Version.TryParse(v1.Name, out System.Version? v1v); + bool v2r = System.Version.TryParse(v2.Name, out System.Version? v2v); + + if (!v1r && !v2r) + return 0; + if (!v1r || v1v == null) // v1 > v2 + return 1; + if (!v2r || v2v == null) // v1 < v2 + return -1; + + var result = v1v.CompareTo(v2v); + if (!option.AscendingPropertyOrder) + result *= -1; + return result; + } + + private int compareReleaseDate(IVersionMetadata v1, IVersionMetadata v2) + { + var v1DateTime = v1.ReleaseTime ?? defaultDateTime; + var v2DateTime = v2.ReleaseTime ?? defaultDateTime; + var result = DateTime.Compare(v1DateTime, v2DateTime); + if (!option.AscendingPropertyOrder) + result *= -1; + return result; + } + + private int compareProperty(IVersionMetadata v1, IVersionMetadata v2, + MVersionSortPropertyOption propertyOption) + { + switch (propertyOption) + { + case MVersionSortPropertyOption.Name: + return compareName(v1, v2); + case MVersionSortPropertyOption.ReleaseDate: + return compareReleaseDate(v1, v2); + case MVersionSortPropertyOption.Version: + return compareVersion(v1, v2); + } + + return 0; + } + + private int compare(IVersionMetadata v1, IVersionMetadata v2) + { + var typeCompareResult = compareType(v1, v2); + + if (option.TypeClassification && typeCompareResult != 0) + return typeCompareResult; + + foreach (var propOption in propertyOptions) + { + int result = compareProperty(v1, v2, propOption); + if (result != 0) + return result; + } + + return typeCompareResult; + } +} \ No newline at end of file diff --git a/src/Core/VersionMetadata/MVersionType.cs b/src/Core/VersionMetadata/MVersionType.cs new file mode 100644 index 0000000..bbbd09a --- /dev/null +++ b/src/Core/VersionMetadata/MVersionType.cs @@ -0,0 +1,78 @@ +namespace CmlLib.Core.VersionMetadata; + +public enum MVersionType +{ + OldAlpha, + OldBeta, + Snapshot, + Release, + Custom +} + +public static class MVersionTypeConverter +{ + public static MVersionType FromString(string? str) + { + MVersionType e; + + switch (str) + { + case "release": + e = MVersionType.Release; + break; + case "snapshot": + e = MVersionType.Snapshot; + break; + case "old_alpha": + e = MVersionType.OldAlpha; + break; + case "old_beta": + e = MVersionType.OldBeta; + break; + default: + e = MVersionType.Custom; + break; + } + + return e; + } + + public static string ToString(MVersionType type) + { + string c; + + switch (type) + { + case MVersionType.OldAlpha: + c = "old_alpha"; + break; + case MVersionType.OldBeta: + c = "old_beta"; + break; + case MVersionType.Snapshot: + c = "snapshot"; + break; + case MVersionType.Release: + c = "release"; + break; + default: + c = "unknown"; + break; + } + + return c; + } + + public static bool CheckOld(string vn) + { + return CheckOld(FromString(vn)); + } + + public static bool CheckOld(MVersionType t) + { + if (t == MVersionType.OldAlpha || t == MVersionType.OldBeta) + return true; + else + return false; + } +} \ No newline at end of file diff --git a/src/Core/VersionMetadata/MojangVersionMetadata.cs b/src/Core/VersionMetadata/MojangVersionMetadata.cs new file mode 100644 index 0000000..93d869d --- /dev/null +++ b/src/Core/VersionMetadata/MojangVersionMetadata.cs @@ -0,0 +1,25 @@ +namespace CmlLib.Core.VersionMetadata; + +public class MojangVersionMetadata : JsonVersionMetadata +{ + private HttpClient _httpClient; + + public string Url { get; } + + public MojangVersionMetadata(JsonVersionMetadataModel model, HttpClient httpClient) : base(model) + { + _httpClient = httpClient; + IsSaved = false; + + if (string.IsNullOrEmpty(model.Url)) + throw new ArgumentNullException(nameof(model.Url)); + + Url = model.Url; + } + + protected override async ValueTask GetVersionJsonString() + { + return await _httpClient.GetStringAsync(Url) + .ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Core/VersionMetadata/StringVersionMetadata.cs b/src/Core/VersionMetadata/StringVersionMetadata.cs deleted file mode 100644 index 7989510..0000000 --- a/src/Core/VersionMetadata/StringVersionMetadata.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Threading.Tasks; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.VersionMetadata -{ - /// - /// Represent JSON text based version metadata - /// - public abstract class StringVersionMetadata : MVersionMetadata - { - protected StringVersionMetadata(string name) : base(name) - { - - } - - /// - /// Get actual version data as string - /// - /// Version metadata - protected abstract Task ReadVersionDataAsync(); - - /// - /// Save version data into a file. It does not overwrite file - /// - /// The content of version metadata - /// Game directory - /// - protected virtual Task SaveVersionDataAsync(string metadata, MinecraftPath path) - { - var metadataPath = prepareSaveVersiondata(path); - if (!string.IsNullOrEmpty(metadataPath)) - return IOUtil.WriteFileAsync(metadataPath, metadata); - else - return Task.CompletedTask; - } - - private string? prepareSaveVersiondata(MinecraftPath path) - { - if (string.IsNullOrEmpty(Name)) - return null; - - var metadataPath = path.GetVersionJsonPath(Name); - - // check if target path and current path are same to prevent overwriting - if (IsLocalVersion && !string.IsNullOrEmpty(Path)) - { - var result = string.Compare(IOUtil.NormalizePath(Path), metadataPath, - StringComparison.InvariantCultureIgnoreCase); - - if (result == 0) // same path - return null; - } - - IOUtil.CreateParentDirectory(metadataPath); - return metadataPath; - } - - private async Task getAsync(MinecraftPath? savePath, bool parse) - { - string metadataJson; - metadataJson = await ReadVersionDataAsync().ConfigureAwait(false); - - if (savePath != null) - await SaveVersionDataAsync(metadataJson, savePath).ConfigureAwait(false); - - return parse ? MVersionParser.ParseFromJson(metadataJson) : null; - } - - public override Task GetVersionAsync() - => getAsync(null, true)!; - - public override Task GetVersionAsync(MinecraftPath savePath) - => getAsync(savePath, true)!; - - public override Task SaveAsync(MinecraftPath path) - => getAsync(path, false); - } -} \ No newline at end of file diff --git a/src/Core/VersionMetadata/VersionSortOption.cs b/src/Core/VersionMetadata/VersionSortOption.cs new file mode 100644 index 0000000..a5f7fd0 --- /dev/null +++ b/src/Core/VersionMetadata/VersionSortOption.cs @@ -0,0 +1,38 @@ +namespace CmlLib.Core.VersionMetadata; + +public enum MVersionSortPropertyOption +{ + Name, + Version, + ReleaseDate +} + +// Decide how to sort version if ReleaseDate is undefined +public enum MVersionNullReleaseDateSortOption +{ + AsLatest, + AsOldest +} + +public class MVersionSortOption +{ + public MVersionType[] TypeOrder { get; set; } = + { + MVersionType.Custom, + MVersionType.Release, + MVersionType.Snapshot, + MVersionType.OldBeta, + MVersionType.OldAlpha + }; + + public MVersionSortPropertyOption PropertyOrderBy { get; set; } + = MVersionSortPropertyOption.Version; + + public bool AscendingPropertyOrder { get; set; } = true; + + public MVersionNullReleaseDateSortOption NullReleaseDateSortOption { get; set; } = + MVersionNullReleaseDateSortOption.AsOldest; + + public bool CustomAsRelease { get; set; } = false; + public bool TypeClassification { get; set; } = true; +} diff --git a/src/Core/VersionMetadata/WebVersionMetadata.cs b/src/Core/VersionMetadata/WebVersionMetadata.cs deleted file mode 100644 index 60081ea..0000000 --- a/src/Core/VersionMetadata/WebVersionMetadata.cs +++ /dev/null @@ -1,27 +0,0 @@ -using CmlLib.Utils; -using System; -using System.Threading.Tasks; - -namespace CmlLib.Core.VersionMetadata -{ - /// - /// Represent metadata where the actual version data is on web - /// - public class WebVersionMetadata : StringVersionMetadata - { - public WebVersionMetadata(string name) : base(name) - { - IsLocalVersion = false; - } - - protected override async Task ReadVersionDataAsync() - { - if (string.IsNullOrEmpty(Path)) - throw new InvalidOperationException("Path property was null"); - - // below code will throw ArgumentNullException when Path is null - return await HttpUtil.HttpClient.GetStringAsync(Path) - .ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/Utils/Changelogs.cs b/src/Utils/Changelogs.cs index a789aa4..cccf5fb 100644 --- a/src/Utils/Changelogs.cs +++ b/src/Utils/Changelogs.cs @@ -1,99 +1,89 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text.Json; +using System.Text.Json; using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace CmlLib.Utils +namespace CmlLib.Utils; + +public class Changelogs { - public class Changelogs + private static readonly string PatchNotesUrl = "https://launchercontent.mojang.com/javaPatchNotes.json"; + + // The urls below will only be retrieved if the version is not found in 'PatchNotesUrl' + // Versions that exist in 'PatchNotesUrl' do not need to be added the list below + private static readonly Dictionary AltUrls = new Dictionary { - private static readonly string PatchNotesUrl = "https://launchercontent.mojang.com/javaPatchNotes.json"; - - // The urls below will only be retrieved if the version is not found in 'PatchNotesUrl' - // Versions that exist in 'PatchNotesUrl' do not need to be added the list below - private static readonly Dictionary AltUrls = new Dictionary - { - { "1.14.2", "https://feedback.minecraft.net/hc/en-us/articles/360028919851-Minecraft-Java-Edition-1-14-2" }, - { "1.14.3", "https://feedback.minecraft.net/hc/en-us/articles/360030771451-Minecraft-Java-Edition-1-14-3" }, - { "1.14.4", "https://feedback.minecraft.net/hc/en-us/articles/360030780172-Minecraft-Java-Edition-1-14-4" }, - }; - - public static Task GetChangelogs() - { - return GetChangelogs(HttpUtil.HttpClient); - } + { "1.14.2", "https://feedback.minecraft.net/hc/en-us/articles/360028919851-Minecraft-Java-Edition-1-14-2" }, + { "1.14.3", "https://feedback.minecraft.net/hc/en-us/articles/360030771451-Minecraft-Java-Edition-1-14-3" }, + { "1.14.4", "https://feedback.minecraft.net/hc/en-us/articles/360030780172-Minecraft-Java-Edition-1-14-4" }, + }; + + public static async Task GetChangelogs(HttpClient client) + { + var response = await client.GetStreamAsync(PatchNotesUrl) + .ConfigureAwait(false); + var jsonDocument = await JsonDocument.ParseAsync(response).ConfigureAwait(false); + var root = jsonDocument.RootElement; - public static async Task GetChangelogs(HttpClient client) + var versionDict = new Dictionary(); + var array = root.GetPropertyOrNull("entries")?.EnumerateArray(); + if (array != null) { - var response = await client.GetStreamAsync(PatchNotesUrl) - .ConfigureAwait(false); - var jsonDocument = await JsonDocument.ParseAsync(response).ConfigureAwait(false); - var root = jsonDocument.RootElement; - - var versionDict = new Dictionary(); - var array = root.SafeGetProperty("entries")?.EnumerateArray(); - if (array != null) + foreach (var item in array) { - foreach (var item in array) - { - var version = item.GetPropertyValue("version"); - if (string.IsNullOrEmpty(version)) - continue; + var version = item.GetPropertyValue("version"); + if (string.IsNullOrEmpty(version)) + continue; - var body = item.GetPropertyValue("body"); - versionDict[version] = body; - } + var body = item.GetPropertyValue("body"); + versionDict[version] = body; } - - return new Changelogs(versionDict, client); - } - - private Changelogs(Dictionary versions, HttpClient client) - { - this.httpClient = client; - this.versions = versions; } - private readonly HttpClient httpClient; - private readonly Dictionary versions; + return new Changelogs(versionDict, client); + } + + private Changelogs(Dictionary versions, HttpClient client) + { + this.httpClient = client; + this.versions = versions; + } + + private readonly HttpClient httpClient; + private readonly Dictionary versions; - public string[] GetAvailableVersions() - { - var availableVersions = new HashSet(); - - foreach (var item in versions.Keys) - availableVersions.Add(item); - - foreach (var item in AltUrls) - availableVersions.Add(item.Key); + public string[] GetAvailableVersions() + { + var availableVersions = new HashSet(); + + foreach (var item in versions.Keys) + availableVersions.Add(item); + + foreach (var item in AltUrls) + availableVersions.Add(item.Key); - return availableVersions.ToArray(); - } + return availableVersions.ToArray(); + } - public async Task GetChangelogHtml(string version) - { - if (versions.TryGetValue(version, out string? body)) - return body; - if (AltUrls.TryGetValue(version, out string? url)) - return await GetChangelogFromUrl(url).ConfigureAwait(false); + public async Task GetChangelogHtml(string version) + { + if (versions.TryGetValue(version, out string? body)) + return body; + if (AltUrls.TryGetValue(version, out string? url)) + return await GetChangelogFromUrl(url).ConfigureAwait(false); - return null; - } + return null; + } - private static readonly Regex ArticleRegex = new Regex( - "
(.*)<\\/article>", RegexOptions.Singleline); + private static readonly Regex ArticleRegex = new Regex( + "
(.*)<\\/article>", RegexOptions.Singleline); - private async Task GetChangelogFromUrl(string url) - { - var html = await httpClient.GetStringAsync(url).ConfigureAwait(false); + private async Task GetChangelogFromUrl(string url) + { + var html = await httpClient.GetStringAsync(url).ConfigureAwait(false); - var regResult = ArticleRegex.Match(html); - if (!regResult.Success) - return ""; + var regResult = ArticleRegex.Match(html); + if (!regResult.Success) + return ""; - return regResult.Value; - } + return regResult.Value; } } diff --git a/src/Utils/HttpUtil.cs b/src/Utils/HttpUtil.cs deleted file mode 100644 index d06d8a6..0000000 --- a/src/Utils/HttpUtil.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Net.Http; - -namespace CmlLib.Utils -{ - public class HttpUtil - { - public static HttpClient HttpClient { get; } = new HttpClient(); - } -} diff --git a/src/Utils/JsonUtil.cs b/src/Utils/JsonUtil.cs index 83403df..a284e09 100644 --- a/src/Utils/JsonUtil.cs +++ b/src/Utils/JsonUtil.cs @@ -1,27 +1,26 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace CmlLib.Utils +namespace CmlLib.Utils; + +public static class JsonUtil { - public static class JsonUtil + public static JsonSerializerOptions JsonOptions { get; } = new JsonSerializerOptions { - public static JsonSerializerOptions JsonOptions { get; } = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; - public static JsonElement? SafeGetProperty(this JsonElement jsonElement, string propertyName) - { - if (jsonElement.TryGetProperty(propertyName, out var val)) - return val; - else - return null; - } + public static JsonElement? GetPropertyOrNull(this JsonElement jsonElement, string propertyName) + { + if (jsonElement.TryGetProperty(propertyName, out var val)) + return val; + else + return null; + } - public static string? GetPropertyValue(this JsonElement jsonElement, string propertyName) - { - return SafeGetProperty(jsonElement, propertyName)?.GetString(); - } + public static string? GetPropertyValue(this JsonElement jsonElement, string propertyName) + { + return GetPropertyOrNull(jsonElement, propertyName)?.GetString(); } } diff --git a/src/Utils/WebDownload.cs b/src/Utils/WebDownload.cs deleted file mode 100644 index 2965f0b..0000000 --- a/src/Utils/WebDownload.cs +++ /dev/null @@ -1,143 +0,0 @@ -using CmlLib.Core.Downloader; -using System; -using System.ComponentModel; -using System.IO; -using System.Net; -using System.Threading.Tasks; - -namespace CmlLib.Utils -{ - [Obsolete] - internal class WebDownload - { - public static bool IgnoreProxy { get; set; } = true; - - public static int DefaultWebRequestTimeout { get; set; } = 20 * 1000; - - private class TimeoutWebClient : WebClient - { - protected override WebRequest GetWebRequest(Uri uri) - { - WebRequest w = base.GetWebRequest(uri); - w.Timeout = DefaultWebRequestTimeout; - - if (IgnoreProxy) - { - w.Proxy = null; - this.Proxy = null; - } - - return w; - } - } - - private static readonly int DefaultBufferSize = 1024 * 64; // 64kb - private readonly object locker = new object(); - - internal event EventHandler? FileDownloadProgressChanged; - internal event ProgressChangedEventHandler? DownloadProgressChangedEvent; - - internal void DownloadFile(string url, string path) - { - var req = WebRequest.CreateHttp(url); // Request - var response = req.GetResponse(); - var filesize = long.Parse(response.Headers.Get("Content-Length") ?? "0"); // Get File Length - - var webStream = response.GetResponseStream(); // Get NetworkStream - if (webStream == null) - throw new NullReferenceException(nameof(webStream)); - - var fileStream = File.Open(path, FileMode.Create); // Open FileStream - - var bufferSize = DefaultBufferSize; // Make buffer - var buffer = new byte[bufferSize]; - int length; - - var fireEvent = filesize > DefaultBufferSize; - var processedBytes = 0; - - while ((length = webStream.Read(buffer, 0, bufferSize)) > 0) // read to end and write file - { - fileStream.Write(buffer, 0, length); - - // raise event - if (fireEvent) - { - processedBytes += length; - progressChanged(processedBytes, filesize); - } - } - - webStream.Dispose(); // Close streams - fileStream.Dispose(); - } - - internal async Task DownloadFileAsync(DownloadFile file) - { - string? directoryName = Path.GetDirectoryName(file.Path); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); - - using (var wc = new TimeoutWebClient()) - { - long lastBytes = 0; - - wc.DownloadProgressChanged += (s, e) => - { - lock (locker) - { - var progressedBytes = e.BytesReceived - lastBytes; - if (progressedBytes < 0) - return; - - lastBytes = e.BytesReceived; - - var progress = new DownloadFileByteProgress() - { - File = file, - TotalBytes = e.TotalBytesToReceive, - ProgressedBytes = progressedBytes - }; - //file: file, - //total: e.TotalBytesToReceive, - //progressed: progressedBytes); - //received: e.BytesReceived, - //percent: e.ProgressPercentage); - FileDownloadProgressChanged?.Invoke(this, progress); - } - }; - await wc.DownloadFileTaskAsync(file.Url, file.Path) - .ConfigureAwait(false); - } - } - - internal void DownloadFileLimit(string url, string path) - { - string? directoryName = Path.GetDirectoryName(path); - if (!string.IsNullOrEmpty(directoryName)) - Directory.CreateDirectory(directoryName); - - var req = WebRequest.CreateHttp(url); - req.Method = "GET"; - req.Timeout = 5000; - req.ReadWriteTimeout = 5000; - req.ContinueTimeout = 5000; - var res = req.GetResponse(); - - using var httpStream = res.GetResponseStream(); - if (httpStream == null) - return; - - using var fs = File.OpenWrite(path); - httpStream.CopyTo(fs); - } - - private void progressChanged(long value, long max) - { - var percentage = (float)value / max * 100; - - var e = new ProgressChangedEventArgs((int)percentage, null); - DownloadProgressChangedEvent?.Invoke(this, e); - } - } -} From 9c2c1e4981b1020e3d15c77a5175290e3a6cba22 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 3 Aug 2023 12:29:53 +0000 Subject: [PATCH 056/137] update IVersion --- src/Core/CMLauncher.cs | 3 +- src/Core/FileChecker/AssetChecker.cs | 4 +- src/Core/FileChecker/IFileChecker.cs | 8 +- src/Core/Files/AssetMetadata.cs | 9 + src/Core/Files/MFileMetadata.cs | 44 ++-- src/Core/Files/MLibrary.cs | 77 ++++++- src/Core/Files/MLibraryParser.cs | 101 --------- src/Core/Files/MLogConfiguration.cs | 10 - src/Core/Files/MLogFileMetadata.cs | 15 ++ src/Core/Java/MinecraftJavaVersion.cs | 12 ++ src/Core/Launcher/MArgument.cs | 19 ++ src/Core/Launcher/MLaunch.cs | 170 +++++++++------- src/Core/Launcher/MLaunchOption.cs | 93 ++++----- src/Core/Launcher/MNative.cs | 96 ++++----- src/Core/MRule.cs | 47 ----- src/Core/Mapper.cs | 5 +- src/Core/Rules/IRulesEvaluator.cs | 6 + src/Core/Rules/LauncherRule.cs | 62 ++++++ src/Core/Rules/RulesEvaluator.cs | 29 +++ src/Core/Rules/RulesEvaluatorContext.cs | 12 ++ src/Core/Version/Extensions.cs | 41 ++++ src/Core/Version/IVersion.cs | 25 +++ src/Core/Version/JsonArgumentParser.cs | 74 +++++++ src/Core/Version/JsonLibraryParser.cs | 53 +++++ src/Core/Version/JsonRulesParser.cs | 38 ++++ src/Core/Version/JsonVersion.cs | 192 ++++++++++++++++++ src/Core/Version/JsonVersionParser.cs | 165 +-------------- src/Core/Version/JsonVersionParserOptions.cs | 13 ++ src/Core/Version/MVersion.cs | 117 ----------- src/Core/Version/VersionJsonModel.cs | 47 +++++ src/Core/VersionLoader/IVersionLoader.cs | 2 +- src/Core/VersionLoader/LocalVersionLoader.cs | 6 +- src/Core/VersionLoader/MojangVersionLoader.cs | 4 +- .../VersionLoader/VersionLoaderCollection.cs | 4 +- src/Core/VersionMetadata/IVersionMetadata.cs | 4 +- .../VersionMetadata/JsonVersionMetadata.cs | 8 +- .../VersionMetadata/MVersionCollection.cs | 32 +-- .../MinecraftArgumentBuilder.cs | 38 ++++ src/ProcessBuilder/ProcessArgumentBuilder.cs | 37 ++++ src/ProcessBuilder/ProcessUtil.cs | 43 ++++ src/Utils/IOUtil.cs | 2 +- src/Utils/ProcessUtil.cs | 46 ----- 42 files changed, 1090 insertions(+), 723 deletions(-) create mode 100644 src/Core/Files/AssetMetadata.cs delete mode 100644 src/Core/Files/MLibraryParser.cs delete mode 100644 src/Core/Files/MLogConfiguration.cs create mode 100644 src/Core/Files/MLogFileMetadata.cs create mode 100644 src/Core/Java/MinecraftJavaVersion.cs create mode 100644 src/Core/Launcher/MArgument.cs create mode 100644 src/Core/Rules/IRulesEvaluator.cs create mode 100644 src/Core/Rules/LauncherRule.cs create mode 100644 src/Core/Rules/RulesEvaluator.cs create mode 100644 src/Core/Rules/RulesEvaluatorContext.cs create mode 100644 src/Core/Version/Extensions.cs create mode 100644 src/Core/Version/IVersion.cs create mode 100644 src/Core/Version/JsonArgumentParser.cs create mode 100644 src/Core/Version/JsonLibraryParser.cs create mode 100644 src/Core/Version/JsonRulesParser.cs create mode 100644 src/Core/Version/JsonVersion.cs create mode 100644 src/Core/Version/JsonVersionParserOptions.cs delete mode 100644 src/Core/Version/MVersion.cs create mode 100644 src/Core/Version/VersionJsonModel.cs create mode 100644 src/ProcessBuilder/MinecraftArgumentBuilder.cs create mode 100644 src/ProcessBuilder/ProcessArgumentBuilder.cs create mode 100644 src/ProcessBuilder/ProcessUtil.cs delete mode 100644 src/Utils/ProcessUtil.cs diff --git a/src/Core/CMLauncher.cs b/src/Core/CMLauncher.cs index 9ec60a4..bf7a070 100644 --- a/src/Core/CMLauncher.cs +++ b/src/Core/CMLauncher.cs @@ -35,7 +35,6 @@ public CMLauncher(MinecraftPath path, HttpClient httpClient) pProgressChanged = new Progress( e => ProgressChanged?.Invoke(this, e)); - // to prevent null-reference warning, set both field, property JavaPathResolver = new MinecraftJavaPathResolver(path); } @@ -66,7 +65,7 @@ public async Task GetVersionAsync(string versionName) if (Versions == null) await GetAllVersionsAsync().ConfigureAwait(false); - var version = await Versions!.GetVersionAsync(versionName) + var version = await Versions!.GetAndSaveVersionAsync(versionName, MinecraftPath) .ConfigureAwait(false); return version; } diff --git a/src/Core/FileChecker/AssetChecker.cs b/src/Core/FileChecker/AssetChecker.cs index 76b5543..16bfe7b 100644 --- a/src/Core/FileChecker/AssetChecker.cs +++ b/src/Core/FileChecker/AssetChecker.cs @@ -30,7 +30,7 @@ public string AssetServer } public bool CheckHash { get; set; } = true; - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, + public DownloadFile[]? CheckFiles(MinecraftPath path, IVersion version, IProgress? progress) { if (version.Assets == null) @@ -39,7 +39,7 @@ public string AssetServer return CheckAssetFiles(path, version.Assets, progress); } - public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, + public async Task CheckFilesTaskAsync(MinecraftPath path, IVersion version, IProgress? progress) { if (version.Assets == null) diff --git a/src/Core/FileChecker/IFileChecker.cs b/src/Core/FileChecker/IFileChecker.cs index 3115e2f..cd80241 100644 --- a/src/Core/FileChecker/IFileChecker.cs +++ b/src/Core/FileChecker/IFileChecker.cs @@ -1,15 +1,13 @@ -using System; -using System.Threading.Tasks; -using CmlLib.Core.Downloader; +using CmlLib.Core.Downloader; using CmlLib.Core.Version; namespace CmlLib.Core.FileChecker { public interface IFileChecker { - DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, + DownloadFile[]? CheckFiles(MinecraftPath path, IVersion version, IProgress? downloadProgress); - Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, + Task CheckFilesTaskAsync(MinecraftPath path, IVersion version, IProgress? downloadProgress); } } diff --git a/src/Core/Files/AssetMetadata.cs b/src/Core/Files/AssetMetadata.cs new file mode 100644 index 0000000..14c8b18 --- /dev/null +++ b/src/Core/Files/AssetMetadata.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Files; + +public class AssetMetadata : MFileMetadata +{ + [JsonPropertyName("totalSize")] + public long TotalSize { get; set; } +} \ No newline at end of file diff --git a/src/Core/Files/MFileMetadata.cs b/src/Core/Files/MFileMetadata.cs index d1017ca..8cd66ba 100644 --- a/src/Core/Files/MFileMetadata.cs +++ b/src/Core/Files/MFileMetadata.cs @@ -1,33 +1,27 @@ using System.Text.Json.Serialization; -namespace CmlLib.Core.Files -{ - // Represent common file metadata. most files in version.json file follow this form - public class MFileMetadata - { - public MFileMetadata() - { +namespace CmlLib.Core.Files; - } +public class MFileMetadata +{ + [JsonPropertyName("id")] + public string? Id { get; set; } - public MFileMetadata(string? id) - { - this.Id = id; - } + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("path")] + public string? Path { get; set; } - [JsonPropertyName("id")] - public string? Id { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("sha1")] - public string? Sha1 { get; set; } + [JsonPropertyName("sha1")] + public string? Sha1 { get; set; } - [JsonPropertyName("size")] - public long Size { get; set; } + [JsonPropertyName("checksums")] + public string[]? Checksums { get; set; } - [JsonPropertyName("url")] - public string? Url { get; set; } - } + [JsonPropertyName("size")] + public long Size { get; set; } + + [JsonPropertyName("url")] + public string? Url { get; set; } } diff --git a/src/Core/Files/MLibrary.cs b/src/Core/Files/MLibrary.cs index f768237..e42b7b2 100644 --- a/src/Core/Files/MLibrary.cs +++ b/src/Core/Files/MLibrary.cs @@ -1,13 +1,70 @@ -namespace CmlLib.Core.Files +using CmlLib.Core.Files; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Version; + +public record MLibrary { - public class MLibrary - { - public string? Name { get; set; } - public string? Path { get; set; } - public string? Url { get; set; } - public string? Hash { get; set; } - public long Size { get; set; } - public bool IsRequire { get; set; } - public bool IsNative { get; set; } + public MLibrary(string name) => + Name = name; + + public MFileMetadata? Artifact { get; set; } + public Dictionary? Classifiers { get; set; } + public Dictionary? Natives { get; set; } + public LauncherRule[]? Rules { get; set; } + public string Name { get; set; } + public bool IsServerRequired { get; set; } = true; + public bool IsClientRequired { get; set; } = true; + + public bool CheckIsRequired(string side) + { + if (side == JsonVersionParserOptions.ClientSide) + return IsClientRequired; + else if (side == JsonVersionParserOptions.ServerSide) + return IsServerRequired; + else + return true; + } + + public string? GetClassifierId(LauncherOSRule os) + { + if (string.IsNullOrEmpty(os.Name) || string.IsNullOrEmpty(os.Arch)) + throw new ArgumentException(); + + var classifierId = Natives?[os.Name]?.Replace("${arch}", os.Arch); + return classifierId; + } + + public MFileMetadata? GetNativeLibrary(LauncherOSRule os) + { + var classifierId = GetClassifierId(os); + if (string.IsNullOrEmpty(classifierId) || Classifiers == null) + return null; + + if (Classifiers.TryGetValue(classifierId, out var classifier)) + return classifier; + else + return null; + } + + public string GetLibraryPath() + { + var path = Artifact?.Path; + if (!string.IsNullOrEmpty(path)) + return path; + + return PackageName.Parse(Name).GetPath(null); + } + + public string? GetNativeLibraryPath(LauncherOSRule os) + { + var classifier = GetNativeLibrary(os); + if (!string.IsNullOrEmpty(classifier?.Path)) + return classifier.Path; + + var classifierId = GetClassifierId(os); + if (string.IsNullOrEmpty(classifierId)) + return null; + return PackageName.Parse(Name).GetPath(classifierId); } } diff --git a/src/Core/Files/MLibraryParser.cs b/src/Core/Files/MLibraryParser.cs deleted file mode 100644 index e5b05be..0000000 --- a/src/Core/Files/MLibraryParser.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Text.Json; -using CmlLib.Utils; - -namespace CmlLib.Core.Files; - -public class MLibraryParser -{ - public bool CheckOSRules { get; set; } = true; - - public MLibrary[]? ParseJsonObject(JsonElement item) - { - try - { - var list = new List(); - - var name = item.GetPropertyValue("name"); - var isRequire = true; - - // check rules array - if (CheckOSRules && item.TryGetProperty("rules", out var rules)) - isRequire = MRule.CheckOSRequire(rules); - - // forge clientreq - var req = item.GetPropertyValue("clientreq"); - if (req != null && req.ToLower() != "true") - isRequire = false; - - // support TLauncher - var artifact = item.GetPropertyOrNull("artifact") - ?? item.GetPropertyOrNull("downloads")?.GetPropertyOrNull("artifact"); - var classifiers = item.GetPropertyOrNull("classifies") - ?? item.GetPropertyOrNull("downloads")?.GetPropertyOrNull("classifiers"); - - // NATIVE library - var natives = item.GetPropertyOrNull("natives"); - if (natives != null) - { - var nativeId = natives.Value.GetPropertyValue(MRule.OSName)? - .Replace("${arch}", MRule.Arch); - - if (classifiers != null && nativeId != null) - { - var lObj = classifiers.Value.GetPropertyOrNull(nativeId) ?? classifiers.Value.GetPropertyOrNull(MRule.OSName); - if (lObj != null) - list.Add(createMLibrary(name, nativeId, isRequire, lObj)); - } - else - list.Add(createMLibrary(name, nativeId, isRequire, null)); - } - - // COMMON library - if (artifact != null) - { - var obj = createMLibrary(name, "", isRequire, artifact); - list.Add(obj); - } - - // library - if (artifact == null && natives == null) - { - MLibrary obj = createMLibrary(name, "", isRequire, item); - list.Add(obj); - } - - return list.ToArray(); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex); - return null; - } - } - - private MLibrary createMLibrary(string? name, string? nativeId, bool require, JsonElement? element) - { - string? path = element?.GetPropertyValue("path"); - if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(name)) - path = PackageName.Parse(name).GetPath(nativeId); - - var hash = element?.GetPropertyValue("sha1"); - if (string.IsNullOrEmpty(hash)) - { - var checksums = element?.GetPropertyOrNull("checksums")?.EnumerateArray(); - hash = checksums?.FirstOrDefault().GetString(); - } - - long size = 0; - element?.GetPropertyOrNull("size")?.TryGetInt64(out size); - - return new MLibrary - { - Hash = hash, - IsNative = !string.IsNullOrEmpty(nativeId), - Name = name, - Path = path, - Size = size, - Url = element?.GetPropertyValue("url"), - IsRequire = require - }; - } -} diff --git a/src/Core/Files/MLogConfiguration.cs b/src/Core/Files/MLogConfiguration.cs deleted file mode 100644 index fe51621..0000000 --- a/src/Core/Files/MLogConfiguration.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CmlLib.Core.Files -{ - // Represent log configuration. "logging" property of .json file - public class MLogConfiguration - { - public MFileMetadata? LogFile { get; set; } - public string? Argument { get; set; } - public string? Type { get; set; } - } -} diff --git a/src/Core/Files/MLogFileMetadata.cs b/src/Core/Files/MLogFileMetadata.cs new file mode 100644 index 0000000..c98e162 --- /dev/null +++ b/src/Core/Files/MLogFileMetadata.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Files; + +public class MLogFileMetadata : MFileMetadata +{ + [JsonPropertyName("file")] + public MFileMetadata? LogFile { get; set; } + + [JsonPropertyName("argument")] + public string? Argument { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } +} diff --git a/src/Core/Java/MinecraftJavaVersion.cs b/src/Core/Java/MinecraftJavaVersion.cs new file mode 100644 index 0000000..e159ff3 --- /dev/null +++ b/src/Core/Java/MinecraftJavaVersion.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Java; + +public class MinecraftJavaVersion +{ + [JsonPropertyName("component")] + public string? Component { get; set; } + + [JsonPropertyName("majorVersion")] + public string? MajorVersion { get; set; } +} \ No newline at end of file diff --git a/src/Core/Launcher/MArgument.cs b/src/Core/Launcher/MArgument.cs new file mode 100644 index 0000000..25810b9 --- /dev/null +++ b/src/Core/Launcher/MArgument.cs @@ -0,0 +1,19 @@ +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Launcher; + +public class MArgument +{ + public MArgument() + { + + } + + public MArgument(string arg) + { + Values = new string[] { arg }; + } + + public string[]? Values { get; set; } + public LauncherRule[]? Rules { get; set; } +} \ No newline at end of file diff --git a/src/Core/Launcher/MLaunch.cs b/src/Core/Launcher/MLaunch.cs index 7a7f8bc..972d36e 100644 --- a/src/Core/Launcher/MLaunch.cs +++ b/src/Core/Launcher/MLaunch.cs @@ -1,6 +1,9 @@ -using CmlLib.Utils; -using System.Diagnostics; +using CmlLib.Core.Launcher; +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Rules; using CmlLib.Core.Version; +using CmlLib.Utils; +using System.Diagnostics; namespace CmlLib.Core; @@ -8,7 +11,7 @@ public class MLaunch { private const int DefaultServerPort = 25565; - public static readonly string SupportVersion = "1.18.2"; + public static readonly string SupportVersion = "1.20.1"; public readonly static string[] DefaultJavaParameter = { "-XX:+UnlockExperimentalVMOptions", @@ -25,16 +28,20 @@ public MLaunch(MLaunchOption option) { option.CheckValid(); launchOption = option; + version = option.GetStartVersion(); this.minecraftPath = option.GetMinecraftPath(); } + private readonly IVersion version; + private readonly IRulesEvaluator rulesEvaluator; + private readonly MinecraftArgumentBuilder builder; private readonly MinecraftPath minecraftPath; private readonly MLaunchOption launchOption; // make process that ready to launch game - public Process GetProcess() + public Process CreateProcess() { - string arg = string.Join(" ", CreateArg()); + string arg = string.Join(" ", BuildArguments()); Process mc = new Process(); mc.StartInfo.FileName = useNotNull(launchOption.GetStartVersion().JavaBinaryPath, launchOption.GetJavaPath()) ?? ""; @@ -44,45 +51,26 @@ public Process GetProcess() return mc; } - // make library files into jvm classpath string - private string createClassPath(MVersion version) + public IEnumerable BuildArguments() { - // if there is no libraries then launcher would need only one library file: .jar file itself - var classpath = new List(version.Libraries?.Length ?? 1); - - // libraries - if (version.Libraries != null) - { - var libraries = version.Libraries - .Where(lib => lib.IsRequire && !lib.IsNative && !string.IsNullOrEmpty(lib.Path)) - .Select(lib => Path.GetFullPath(Path.Combine(minecraftPath.Library, lib.Path!))); - classpath.AddRange(libraries); - } - - // .jar file - if (!string.IsNullOrEmpty(version.Jar)) - classpath.Add(minecraftPath.GetVersionJarPath(version.Jar)); - - var classpathStr = IOUtil.CombinePath(classpath.ToArray()); - return classpathStr; - } + var argDict = buildArgumentDictionary(); - private string createNativePath(MVersion version) - { - var native = new MNative(minecraftPath, version); - native.CleanNatives(); - var nativePath = native.ExtractNatives(); - return nativePath; + var jvmArgs = buildJvmArguments(argDict); + foreach (var item in jvmArgs) + yield return item; + + var gameArgs = buildGameArguments(argDict); + foreach (var item in gameArgs) + yield return item; } - public string[] CreateArg() - { - MVersion version = launchOption.GetStartVersion(); - var args = new List(); - - var classpath = createClassPath(version); - var nativePath = createNativePath(version); + private Dictionary buildArgumentDictionary() + { + var classpaths = getClasspaths(); + var classpath = IOUtil.CombinePath(classpaths); + var nativePath = createNativePath(); var session = launchOption.GetSession(); + var assetId = version.GetInheritedProperty(version => version.AssetIndex?.Id) ?? "legacy"; var argDict = new Dictionary { @@ -98,14 +86,14 @@ public string[] CreateArg() { "version_name" , version.Id }, { "game_directory" , minecraftPath.BasePath }, { "assets_root" , minecraftPath.Assets }, - { "assets_index_name", version.Assets?.Id ?? "legacy" }, + { "assets_index_name", assetId }, { "auth_uuid" , session.UUID }, { "auth_access_token", session.AccessToken }, { "user_properties" , "{}" }, { "auth_xuid" , session.Xuid ?? "xuid" }, { "clientid" , launchOption.ClientId ?? "clientId" }, { "user_type" , session.UserType ?? "Mojang" }, - { "game_assets" , minecraftPath.GetAssetLegacyPath(version.Assets?.Id ?? "legacy") }, + { "game_assets" , minecraftPath.GetAssetLegacyPath(assetId) }, { "auth_session" , session.AccessToken }, { "version_type" , useNotNull(launchOption.VersionType, version.Type) }, }; @@ -118,75 +106,113 @@ public string[] CreateArg() } } - // JVM argument - + return argDict; + } + + private IEnumerable buildJvmArguments(Dictionary argDict) + { // version-specific jvm arguments - if (version.JvmArguments != null) - args.AddRange(Mapper.MapInterpolation(version.JvmArguments, argDict)); + var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); + foreach (var item in builder.Build(jvmArgs, argDict)) + yield return item; // default jvm arguments - if (launchOption.JVMArguments != null) - args.AddRange(launchOption.JVMArguments); + if (launchOption.JVMArguments != null && launchOption.JVMArguments.Count() != 0) + foreach (var item in launchOption.JVMArguments) + yield return item; else { if (launchOption.MaximumRamMb > 0) - args.Add("-Xmx" + launchOption.MaximumRamMb + "m"); + yield return ("-Xmx" + launchOption.MaximumRamMb + "m"); if (launchOption.MinimumRamMb > 0) - args.Add("-Xms" + launchOption.MinimumRamMb + "m"); + yield return ("-Xms" + launchOption.MinimumRamMb + "m"); - args.AddRange(DefaultJavaParameter); + foreach (var item in DefaultJavaParameter) + yield return item; } - if (version.JvmArguments == null) + if (jvmArgs.Count() == 0) { - args.Add("-Djava.library.path=" + handleEmpty(nativePath)); - args.Add("-cp " + classpath); + yield return ("-Djava.library.path=" + handleEmpty(argDict["natives_directory"])); + yield return ("-cp " + argDict["classpath"]); } // for macOS if (!string.IsNullOrEmpty(launchOption.DockName)) - args.Add("-Xdock:name=" + handleEmpty(launchOption.DockName)); + yield return ("-Xdock:name=" + handleEmpty(launchOption.DockName)); if (!string.IsNullOrEmpty(launchOption.DockIcon)) - args.Add("-Xdock:icon=" + handleEmpty(launchOption.DockIcon)); + yield return ("-Xdock:icon=" + handleEmpty(launchOption.DockIcon)); // logging - var loggingArgument = version.LoggingClient?.Argument; - if (!string.IsNullOrEmpty(loggingArgument)) - args.Add(Mapper.Interpolation(loggingArgument, new Dictionary() + var logging = version.GetInheritedProperty(v => v.Logging); + if (!string.IsNullOrEmpty(logging?.Argument)) + { + var mappedArgs = builder.Build(new MArgument(logging.Argument), new Dictionary() { - { "path", minecraftPath.GetLogConfigFilePath(version.LoggingClient?.LogFile?.Id ?? version.Id) } - }, true)); + { "path", minecraftPath.GetLogConfigFilePath(logging.LogFile?.Id ?? version.Id) } + }); + foreach (var item in mappedArgs) + yield return item; + } // main class - if (!string.IsNullOrEmpty(version.MainClass)) - args.Add(version.MainClass); + var mainClass = version.GetInheritedProperty(v => v.MainClass); + if (!string.IsNullOrEmpty(mainClass)) + yield return (mainClass); + } + private IEnumerable buildGameArguments(Dictionary argDict) + { // game arguments - if (version.GameArguments != null) - args.AddRange(Mapper.MapInterpolation(version.GameArguments, argDict)); - else if (!string.IsNullOrEmpty(version.MinecraftArguments)) - args.AddRange(Mapper.MapInterpolation(version.MinecraftArguments.Split(' '), argDict)); + var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); + foreach (var item in builder.Build(gameArgs, argDict)) + yield return item; // options if (!string.IsNullOrEmpty(launchOption.ServerIp)) { - args.Add("--server " + handleEmpty(launchOption.ServerIp)); + yield return ("--server " + handleEmpty(launchOption.ServerIp)); if (launchOption.ServerPort != DefaultServerPort) - args.Add("--port " + launchOption.ServerPort); + yield return ("--port " + launchOption.ServerPort); } if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) { - args.Add("--width " + launchOption.ScreenWidth); - args.Add("--height " + launchOption.ScreenHeight); + yield return ("--width " + launchOption.ScreenWidth); + yield return ("--height " + launchOption.ScreenHeight); } if (launchOption.FullScreen) - args.Add("--fullscreen"); + yield return ("--fullscreen"); + } - return args.ToArray(); + // make library files into jvm classpath string + private IEnumerable getClasspaths() + { + // libraries + var libPaths = version + .ConcatInheritedCollection(v => v.Libraries) + .Where(lib => lib.CheckIsRequired("SIDE")) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules)) + .Where(lib => lib.Artifact != null) + .Select(lib => lib.GetLibraryPath()); + + foreach (var item in libPaths) + yield return item; + + // .jar file + if (!string.IsNullOrEmpty(version.Jar)) // !!!!!!!!!!!!!!!!!!!!!!!!! + yield return (minecraftPath.GetVersionJarPath(version.Jar)); + } + + private string createNativePath() + { + var native = new MNative(minecraftPath, version); + native.CleanNatives(); + var nativePath = native.ExtractNatives(); + return nativePath; } // if input1 is null, return input2 diff --git a/src/Core/Launcher/MLaunchOption.cs b/src/Core/Launcher/MLaunchOption.cs index 4182af4..4f7d1fd 100644 --- a/src/Core/Launcher/MLaunchOption.cs +++ b/src/Core/Launcher/MLaunchOption.cs @@ -1,70 +1,67 @@ using CmlLib.Core.Auth; using CmlLib.Core.Version; -using System; -using System.Collections.Generic; -namespace CmlLib.Core +namespace CmlLib.Core; + +public class MLaunchOption { - public class MLaunchOption - { - public MinecraftPath? Path { get; set; } - public MVersion? StartVersion { get; set; } - public MSession? Session { get; set; } + public MinecraftPath? Path { get; set; } + public IVersion? StartVersion { get; set; } + public MSession? Session { get; set; } - public string? JavaVersion { get; set; } - public string? JavaPath { get; set; } - public int MaximumRamMb { get; set; } = 1024; - public int MinimumRamMb { get; set; } - public string[]? JVMArguments { get; set; } + public string? JavaVersion { get; set; } + public string? JavaPath { get; set; } + public int MaximumRamMb { get; set; } = 1024; + public int MinimumRamMb { get; set; } + public string[]? JVMArguments { get; set; } - public string? DockName { get; set; } - public string? DockIcon { get; set; } + public string? DockName { get; set; } + public string? DockIcon { get; set; } - public string? ServerIp { get; set; } - public int ServerPort { get; set; } = 25565; + public string? ServerIp { get; set; } + public int ServerPort { get; set; } = 25565; - public int ScreenWidth { get; set; } - public int ScreenHeight { get; set; } - public bool FullScreen { get; set; } + public int ScreenWidth { get; set; } + public int ScreenHeight { get; set; } + public bool FullScreen { get; set; } - public string? ClientId { get; set; } - public string? VersionType { get; set; } - public string? GameLauncherName { get; set; } - public string? GameLauncherVersion { get; set; } + public string? ClientId { get; set; } + public string? VersionType { get; set; } + public string? GameLauncherName { get; set; } + public string? GameLauncherVersion { get; set; } - public string? UserProperties { get; set; } + public string? UserProperties { get; set; } - public Dictionary? ArgumentDictionary { get; set; } + public Dictionary? ArgumentDictionary { get; set; } - internal MinecraftPath GetMinecraftPath() => Path!; - internal MVersion GetStartVersion() => StartVersion!; - internal MSession GetSession() => Session!; - internal string GetJavaPath() => JavaPath!; + internal MinecraftPath GetMinecraftPath() => Path!; + internal IVersion GetStartVersion() => StartVersion!; + internal MSession GetSession() => Session!; + internal string GetJavaPath() => JavaPath!; - internal void CheckValid() - { - string? exMsg = null; // error message + internal void CheckValid() + { + string? exMsg = null; // error message - if (Path == null) - exMsg = nameof(Path) + " is null"; + if (Path == null) + exMsg = nameof(Path) + " is null"; - if (StartVersion == null) - exMsg = "StartVersion is null"; + if (StartVersion == null) + exMsg = "StartVersion is null"; - if (Session == null) - Session = MSession.GetOfflineSession("tester123"); + if (Session == null) + Session = MSession.GetOfflineSession("tester123"); - if (!Session.CheckIsValid()) - exMsg = "Invalid Session"; + if (!Session.CheckIsValid()) + exMsg = "Invalid Session"; - if (ServerPort < 0 || ServerPort > 65535) - exMsg = "Invalid ServerPort"; + if (ServerPort < 0 || ServerPort > 65535) + exMsg = "Invalid ServerPort"; - if (ScreenWidth < 0 || ScreenHeight < 0) - exMsg = "Screen Size must be greater than or equal to zero."; + if (ScreenWidth < 0 || ScreenHeight < 0) + exMsg = "Screen Size must be greater than or equal to zero."; - if (exMsg != null) // if launch option is invalid, throw exception - throw new ArgumentException(exMsg); - } + if (exMsg != null) // if launch option is invalid, throw exception + throw new ArgumentException(exMsg); } } diff --git a/src/Core/Launcher/MNative.cs b/src/Core/Launcher/MNative.cs index afd377f..2841aee 100644 --- a/src/Core/Launcher/MNative.cs +++ b/src/Core/Launcher/MNative.cs @@ -1,65 +1,67 @@ -using CmlLib.Core.Version; +using CmlLib.Core.Rules; +using CmlLib.Core.Version; using CmlLib.Utils; -using System.IO; -namespace CmlLib.Core +namespace CmlLib.Core; + +public class MNative { - public class MNative + public MNative(MinecraftPath gamePath, IVersion version) { - public MNative(MinecraftPath gamePath, MVersion version) - { - this.version = version; - this.gamePath = gamePath; - } + this.version = version; + this.gamePath = gamePath; + } + + private readonly IVersion version; + private readonly MinecraftPath gamePath; + private readonly IRulesEvaluator rulesEvaluator; - private readonly MVersion version; - private readonly MinecraftPath gamePath; + public string ExtractNatives() + { + var extractPath = gamePath.GetNativePath(version.Id); + Directory.CreateDirectory(extractPath); - public string ExtractNatives() + var nativeLibraries = getNativeLibraryPaths(); + foreach (var libPath in nativeLibraries) { - string path = gamePath.GetNativePath(version.Id); - Directory.CreateDirectory(path); + if (File.Exists(libPath)) + new SharpZip(libPath).Unzip(extractPath); + } - if (version.Libraries == null) return path; - - foreach (var item in version.Libraries) - { - // do not ignore exception - if (item.IsRequire && item.IsNative && !string.IsNullOrEmpty(item.Path)) - { - string zPath = Path.Combine(gamePath.Library, item.Path); - if (File.Exists(zPath)) - { - var z = new SharpZip(zPath); - z.Unzip(path); - } - } - } + return extractPath; + } - return path; - } + private IEnumerable getNativeLibraryPaths() + { + return version + .ConcatInheritedCollection(v => v.Libraries) + .Where(lib => lib.CheckIsRequired("SIDE")) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules)) + .Select(lib => lib.GetNativeLibraryPath(os)) + .Where(libPath => !string.IsNullOrEmpty(libPath)) + .Select(libPath => Path.Combine(gamePath.Library, libPath)); + } - public void CleanNatives() - { + public void CleanNatives() + { - try - { - string path = gamePath.GetNativePath(version.Id); - DirectoryInfo di = new DirectoryInfo(path); + try + { + string path = gamePath.GetNativePath(version.Id); + DirectoryInfo di = new DirectoryInfo(path); - if (!di.Exists) - return; + if (!di.Exists) + return; - foreach (var item in di.GetFiles()) - { - item.Delete(); - } - } - catch + foreach (var item in di.GetFiles()) { - // ignore exception - // will be overwriten to new file + item.Delete(); } } + catch + { + // ignore exception + // will be overwriten to new file + } } } diff --git a/src/Core/MRule.cs b/src/Core/MRule.cs index bdf552e..bbb7190 100644 --- a/src/Core/MRule.cs +++ b/src/Core/MRule.cs @@ -45,52 +45,5 @@ private static string getOSName() #endif } - public static bool CheckOSRequire(JsonElement arr) - { - if (arr.ValueKind != JsonValueKind.Array) - throw new ArgumentException("input JsonElement was not array"); - - var require = true; - foreach (var token in arr.EnumerateArray()) - { - if (token.ValueKind != JsonValueKind.Object) - continue; - - bool action = true; // true : "allow", false : "disallow" - bool containCurrentOS = true; // if 'os' JArray contains current os name - - foreach (var item in token.EnumerateObject()) - { - if (item.Name == "action") - action = (item.Value.GetString() == "allow"); - else if (item.Name == "os") - containCurrentOS = checkOSContains(item.Value); - else if (item.Name == "features") // etc - return false; - } - - if (!action && containCurrentOS) - require = false; - else if (action && containCurrentOS) - require = true; - else if (action && !containCurrentOS) - require = false; - } - - return require; - } - - private static bool checkOSContains(JsonElement? element) - { - if (element == null) - return false; - - foreach (var os in element.Value.EnumerateObject()) - { - if (os.Name == "name" && os.Value.GetString() == OSName) - return true; - } - return false; - } } } diff --git a/src/Core/Mapper.cs b/src/Core/Mapper.cs index b2df369..1a55301 100644 --- a/src/Core/Mapper.cs +++ b/src/Core/Mapper.cs @@ -60,10 +60,7 @@ public static string Interpolation(string str, Dictionary dicts var key = match.Groups[1].Value; if (dicts.TryGetValue(key, out string? value)) { - if (value == null) - value = ""; - - return value; + return value ?? ""; } return match.Value; diff --git a/src/Core/Rules/IRulesEvaluator.cs b/src/Core/Rules/IRulesEvaluator.cs new file mode 100644 index 0000000..6c72f98 --- /dev/null +++ b/src/Core/Rules/IRulesEvaluator.cs @@ -0,0 +1,6 @@ +namespace CmlLib.Core.Rules; + +public interface IRulesEvaluator +{ + bool Match(IEnumerable rules); +} \ No newline at end of file diff --git a/src/Core/Rules/LauncherRule.cs b/src/Core/Rules/LauncherRule.cs new file mode 100644 index 0000000..2be865f --- /dev/null +++ b/src/Core/Rules/LauncherRule.cs @@ -0,0 +1,62 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Rules; + +public class LauncherRule +{ + [JsonPropertyName("action")] + public string? Action { get; set; } + + [JsonPropertyName("features")] + public Dictionary? Features { get; set; } + + [JsonPropertyName("os")] + public LauncherOSRule? OS { get; set; } + + public bool MatchFeatures(Dictionary toMatch) + { + if (Features == null) + return true; + + foreach (var kv in Features) + { + var exists = toMatch.ContainsKey(kv.Key); + if (kv.Value) // feature: true + { + if (!exists) + return false; + if (!toMatch[kv.Key]) + return false; + } + else // feature: false + { + if (exists && toMatch[kv.Key]) + return false; + } + } + + return true; + } +} + +public record LauncherOSRule +{ + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("arch")] + public string? Arch { get; set; } + + public bool Match(LauncherOSRule toMatch) + { + return isPropMatch(Name, toMatch.Name) && + isPropMatch(Arch, toMatch.Arch); + } + + private bool isPropMatch(string? rule, string? toMatch) + { + if (string.IsNullOrEmpty(rule)) + return true; + return rule == toMatch; + } +} \ No newline at end of file diff --git a/src/Core/Rules/RulesEvaluator.cs b/src/Core/Rules/RulesEvaluator.cs new file mode 100644 index 0000000..c3b0613 --- /dev/null +++ b/src/Core/Rules/RulesEvaluator.cs @@ -0,0 +1,29 @@ +namespace CmlLib.Core.Rules; + +public class RulesEvaluator : IRulesEvaluator +{ + private readonly RulesEvaluatorContext _context; + + public RulesEvaluator() + { + } + + public RulesEvaluator(RulesEvaluatorContext context) => + _context = context; + + public bool Match(IEnumerable rules) + { + return rules.Any(rule => match(rule)); + } + + private bool match(LauncherRule rule) + { + var isAllow = rule.Action == "allow"; + var isOSMatched = rule.OS != null && rule.OS.Match(_context.OS); + + if (isAllow) + return isOSMatched; + else + return !isOSMatched; + } +} \ No newline at end of file diff --git a/src/Core/Rules/RulesEvaluatorContext.cs b/src/Core/Rules/RulesEvaluatorContext.cs new file mode 100644 index 0000000..0f06cf5 --- /dev/null +++ b/src/Core/Rules/RulesEvaluatorContext.cs @@ -0,0 +1,12 @@ +namespace CmlLib.Core.Rules; + +public class RulesEvaluatorContext +{ + public RulesEvaluatorContext(LauncherOSRule os) + { + OS = os; + } + + public LauncherOSRule OS { get; set; } + public string[]? Features { get; set; } +} \ No newline at end of file diff --git a/src/Core/Version/Extensions.cs b/src/Core/Version/Extensions.cs new file mode 100644 index 0000000..00cadde --- /dev/null +++ b/src/Core/Version/Extensions.cs @@ -0,0 +1,41 @@ +using CmlLib.Core.VersionMetadata; + +namespace CmlLib.Core.Version; + +public static class Extensions +{ + public static MVersionType GetVersionType(this IVersion version) + { + return MVersionTypeConverter.FromString(version.Type); + } + + public static T? GetInheritedProperty(this IVersion self, Func prop) + { + IVersion? version = self; + while (version != null) + { + var value = prop.Invoke(version); + if (value is string valueStr && !string.IsNullOrEmpty(valueStr)) + return value; + else if (value != null) + return value; + version = version.ParentVersion; + } + return default; + } + + public static IEnumerable ConcatInheritedCollection(this IVersion self, Func> prop) + { + IVersion? version = self; + while (version != null) + { + var value = prop.Invoke(version); + if (value != null) + { + foreach (var item in value) + yield return item; + } + version = version.ParentVersion; + } + } +} \ No newline at end of file diff --git a/src/Core/Version/IVersion.cs b/src/Core/Version/IVersion.cs new file mode 100644 index 0000000..3db57a5 --- /dev/null +++ b/src/Core/Version/IVersion.cs @@ -0,0 +1,25 @@ +using CmlLib.Core.Files; +using CmlLib.Core.Java; +using CmlLib.Core.Launcher; + +namespace CmlLib.Core.Version; + +public interface IVersion +{ + string Id { get; } + string? InheritsFrom { get; } + IVersion? ParentVersion { get; set; } + AssetMetadata? AssetIndex { get; } + MFileMetadata? Client { get; } + MinecraftJavaVersion? JavaVersion { get; } + MLibrary[] Libraries { get; } + string? Jar { get; } + MLogFileMetadata? Logging { get; } + string? MainClass { get; } + MArgument[] GameArguments { get; } + MArgument[] JvmArguments { get; } + DateTime ReleaseTime { get; } + string? Type { get; } + + string? GetProperty(string key); +} \ No newline at end of file diff --git a/src/Core/Version/JsonArgumentParser.cs b/src/Core/Version/JsonArgumentParser.cs new file mode 100644 index 0000000..dde4a11 --- /dev/null +++ b/src/Core/Version/JsonArgumentParser.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using CmlLib.Core.Launcher; +using CmlLib.Utils; + +namespace CmlLib.Core.Version; + +public static class JsonArgumentParser +{ + public static MArgument[] Parse(JsonElement element) + { + var list = new List(); + foreach (var item in element.EnumerateArray()) + { + var arg = ParseArgumentElement(item); + if (arg != null) + list.Add(arg); + } + return list.ToArray(); + } + + public static MArgument? ParseArgumentElement(JsonElement item) + { + if (item.ValueKind == JsonValueKind.Object) + { + return parseArgumentObject(item); + } + else + { + var value = item.GetString(); + if (string.IsNullOrEmpty(value)) + return null; + + return new MArgument + { + Values = new string[] { value } + }; + } + } + + private static MArgument? parseArgumentObject(JsonElement item) + { + var arg = new MArgument(); + + var rules = item.GetPropertyOrNull("rules"); + if (rules == null || rules.Value.ValueKind != JsonValueKind.Array) + rules = item.GetPropertyOrNull("compatibilityRules"); + if (rules.HasValue) + arg.Rules = JsonRulesParser.Parse(rules.Value); + + var value = item.GetPropertyOrNull("value") ?? + item.GetPropertyOrNull("values"); + if (value != null) + { + if (value.Value.ValueKind == JsonValueKind.Array) + { + arg.Values = value.Value.EnumerateArray() + .Where(item => item.ValueKind == JsonValueKind.String) + .Select(item => item.GetString()) + .Where(item => !string.IsNullOrEmpty(item)) + .ToArray()!; + } + else if (value.Value.ValueKind == JsonValueKind.String) + { + var valueString = value.Value.GetString(); + if (!string.IsNullOrEmpty(valueString)) + arg.Values = new string[] { valueString }; + } + } + + if (arg.Values == null || arg.Values.Length == 0) + return null; + return arg; + } +} \ No newline at end of file diff --git a/src/Core/Version/JsonLibraryParser.cs b/src/Core/Version/JsonLibraryParser.cs new file mode 100644 index 0000000..011eeaa --- /dev/null +++ b/src/Core/Version/JsonLibraryParser.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using CmlLib.Core.Files; +using CmlLib.Utils; + +namespace CmlLib.Core.Version; + +public static class JsonLibraryParser +{ + public static MLibrary? Parse(JsonElement element) + { + var name = element.GetPropertyValue("name"); + if (string.IsNullOrEmpty(name)) + return null; + + var library = new MLibrary(name); + + // rules + if (element.TryGetProperty("rules", out var rulesProp)) + library.Rules = JsonRulesParser.Parse(rulesProp); + + // forge serverreq, clientreq + library.IsServerRequired = element + .GetPropertyOrNull("serverreq")? + .GetBoolean() ?? + true; // default value is true + + library.IsClientRequired = element + .GetPropertyOrNull("clientreq")? + .GetBoolean() ?? + true; // default value is true + + // artifact + var artifact = element.GetPropertyOrNull("artifact") ?? + element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("artifact") ?? + element; + library.Artifact = artifact.Deserialize(); + + // classifiers + var classifiers = element.GetPropertyOrNull("classifies") ?? + element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("classifiers"); + if (classifiers.HasValue) + library.Classifiers = classifiers.Value.Deserialize>(); + + // natives + var natives = element.GetPropertyOrNull("natives"); + if (natives.HasValue) + { + library.Natives = natives.Value.Deserialize>(); + } + + return library; + } +} diff --git a/src/Core/Version/JsonRulesParser.cs b/src/Core/Version/JsonRulesParser.cs new file mode 100644 index 0000000..59d9d47 --- /dev/null +++ b/src/Core/Version/JsonRulesParser.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Version; + +public static class JsonRulesParser +{ + public static LauncherRule[] Parse(JsonElement element) + { + var list = new List(); + + if (element.ValueKind == JsonValueKind.Array) + { + foreach (var item in element.EnumerateArray()) + { + var rule = ParseRuleElement(item); + if (rule != null) + list.Add(rule); + } + } + else if (element.ValueKind == JsonValueKind.Object) + { + var rule = ParseRuleElement(element); + if (rule != null) + list.Add(rule); + } + + return list.ToArray(); + } + + public static LauncherRule? ParseRuleElement(JsonElement element) + { + if (element.ValueKind != JsonValueKind.Object) + return null; + + return element.Deserialize(); + } +} \ No newline at end of file diff --git a/src/Core/Version/JsonVersion.cs b/src/Core/Version/JsonVersion.cs new file mode 100644 index 0000000..3816e3a --- /dev/null +++ b/src/Core/Version/JsonVersion.cs @@ -0,0 +1,192 @@ +using System.Text.Json; +using CmlLib.Core.Files; +using CmlLib.Core.Java; +using CmlLib.Core.Launcher; + +namespace CmlLib.Core.Version; + +// JsonElement 의존성 빼고 parse 과정을 전부 JsonVersionParser 으로 이동 + +public class JsonVersion : IVersion +{ + private readonly JsonVersionParserOptions _options; + private readonly JsonElement _json; + private readonly VersionJsonModel _model; + + public JsonVersion(JsonElement json, JsonVersionParserOptions options) + { + _options = options; + _json = json; + _model = json.Deserialize() ?? throw new ArgumentNullException(); + Id = _model.Id ?? throw new ArgumentException("Null Id"); + } + + public string Id { get; } + + public string? InheritsFrom => _model.InheritsFrom; + + public IVersion? ParentVersion { get; set; } + + private AssetMetadata? _assetIndex; + public AssetMetadata? AssetIndex => _assetIndex ??= getAssetIndex(); + + private MFileMetadata? _client = null; + public MFileMetadata? Client => _client ??= getClient(); + + public MinecraftJavaVersion? JavaVersion => _model.JavaVersion; + + private MLibrary[]? _libs = null; + public MLibrary[] Libraries => _libs ??= getLibraries(); + + public string? Jar => _model.Jar; + + private MLogFileMetadata? _logging; + public MLogFileMetadata? Logging => _logging ??= getLogging(); + + public string? MainClass => _model.MainClass; + + public string? MinecraftArguments => _model.MinecraftArguments; + + public DateTime ReleaseTime => _model.ReleaseTime; + + public string? Type => _model.Type; + + private MArgument[]? _gameArgs = null; + public MArgument[] GameArguments => _gameArgs ??= getGameArguments(); + + private MArgument[]? _jvmArgs = null; + public MArgument[] JvmArguments => _jvmArgs ??= getJvmArguments(); + + private AssetMetadata? getAssetIndex() + { + if (string.IsNullOrEmpty(_model.AssetIndex?.Id)) + { + if (string.IsNullOrEmpty(_model.Assets)) + return null; + else + return new AssetMetadata() { Id = _model.Assets }; + } + else + return _model.AssetIndex; + } + + private MFileMetadata? getClient() + { + try + { + return _json + .GetProperty("downloads") + .GetProperty(_options.Side) + .Deserialize(); + } + catch (Exception) + { + if (!_options.SkipError) + throw; + return null; + } + } + + private MLibrary[] getLibraries() + { + try + { + var libProp = _json.GetProperty("libraries"); + var libList = new List(); + foreach (var libJson in libProp.EnumerateArray()) + { + var lib = JsonLibraryParser.Parse(libJson); + if (lib != null) + libList.Add(lib); + } + return libList.ToArray(); + } + catch (Exception) + { + if (!_options.SkipError) + throw; + return Array.Empty(); + } + } + + private MLogFileMetadata? getLogging() + { + try + { + return _json + .GetProperty("logging") + .GetProperty(_options.Side) + .Deserialize(); + } + catch (Exception) + { + if (!_options.SkipError) + throw; + return null; + } + } + + private MArgument[] getGameArguments() + { + try + { + var prop = _json + .GetProperty("arguments") + .GetProperty("game"); + return JsonArgumentParser.Parse(prop); + } + catch (KeyNotFoundException) + { + var args = GetProperty("minecraftArguments"); + if (string.IsNullOrEmpty(args)) + { + return Array.Empty(); + } + else + { + return new MArgument[] + { + new MArgument() + { + Values = new string[] { args } + } + }; + } + } + catch (Exception) + { + if (!_options.SkipError) + throw; + return Array.Empty(); + } + } + + private MArgument[] getJvmArguments() + { + try + { + var prop = _json + .GetProperty("arguments") + .GetProperty("jvm"); + return JsonArgumentParser.Parse(prop); + } + catch (KeyNotFoundException) + { + return Array.Empty(); + } + catch (Exception) + { + if (!_options.SkipError) + throw; + return Array.Empty(); + } + } + + public string? GetProperty(string key) + { + if (_json.TryGetProperty(key, out var prop)) + return prop.GetString(); + else + return null; + } +} \ No newline at end of file diff --git a/src/Core/Version/JsonVersionParser.cs b/src/Core/Version/JsonVersionParser.cs index d621677..7f0daa3 100644 --- a/src/Core/Version/JsonVersionParser.cs +++ b/src/Core/Version/JsonVersionParser.cs @@ -1,22 +1,20 @@ -using CmlLib.Core.Files; -using CmlLib.Utils; -using System.Text.Json; +using System.Text.Json; namespace CmlLib.Core.Version; -public class JsonVersionParser +public static class JsonVersionParser { - public MVersion ParseFromJsonString(string json) + public static IVersion ParseFromJsonString(string json, JsonVersionParserOptions options) { - using var document = JsonDocument.Parse(json); - return ParseFromJson(document.RootElement); + var document = JsonDocument.Parse(json); + return ParseFromJson(document.RootElement, options); } - public MVersion ParseFromJson(JsonElement element) + public static IVersion ParseFromJson(JsonElement element, JsonVersionParserOptions options) { try { - return parseInternal(element); + return parseInternal(element, options); } catch (MVersionParseException) { @@ -28,153 +26,8 @@ public MVersion ParseFromJson(JsonElement element) } } - private MVersion parseInternal(JsonElement root) + private static IVersion parseInternal(JsonElement root, JsonVersionParserOptions options) { - // id - if (!root.TryGetProperty("id", out var idElement)) - throw new MVersionParseException("Empty version id"); - - var id = idElement.GetString() - ?? throw new MVersionParseException("Empty version id"); - - var version = new MVersion(id); - - // javaVersion - version.JavaVersion = root - .GetPropertyOrNull("javaVersion")? - .GetPropertyOrNull("component")? - .GetString(); - - // assets - if (root.TryGetProperty("assetIndex", out var assetIndex)) - version.Assets = assetIndex.Deserialize(JsonUtil.JsonOptions); - else if (root.TryGetProperty("assets", out var assets)) - version.Assets = new MFileMetadata(assets.GetString()); - - // client jar - var client = root - .GetPropertyOrNull("downloads")? - .GetPropertyOrNull("client"); - - if (client.HasValue) - version.Client = client.Value.Deserialize(JsonUtil.JsonOptions); - - // libraries - if (root.TryGetProperty("libraries", out var libProp) && libProp.ValueKind == JsonValueKind.Array) - { - var libList = new List(); - var libParser = new MLibraryParser(); - foreach (var item in libProp.EnumerateArray()) - { - var libs = libParser.ParseJsonObject(item); - if (libs != null) - libList.AddRange(libs); - } - - version.Libraries = libList.ToArray(); - } - - // mainClass - version.MainClass = root.GetPropertyValue("mainClass"); - - // argument - version.MinecraftArguments = root.GetPropertyValue("minecraftArguments"); - - if (root.TryGetProperty("arguments", out var ag)) - { - if (ag.TryGetProperty("game", out var gameArg) && gameArg.ValueKind == JsonValueKind.Array) - version.GameArguments = argParse(gameArg); - if (ag.TryGetProperty("jvm", out var jvmArg) && jvmArg.ValueKind == JsonValueKind.Array) - version.JvmArguments = argParse(jvmArg); - } - - // metadata - version.ReleaseTime = root.GetPropertyValue("releaseTime"); - - version.Type = root.GetPropertyValue("type"); - - // inherits - version.ParentVersionId = root.GetPropertyValue("inheritsFrom"); - version.IsInherited = string.IsNullOrEmpty(version.ParentVersionId); - - version.Jar = root.GetPropertyValue("jar"); - if (string.IsNullOrEmpty(version.Jar)) - version.Jar = version.Id; - - // logging - var loggingClient = root - .GetPropertyOrNull("logging")? - .GetPropertyOrNull("client"); - - if (loggingClient.HasValue) - { - var logFile = loggingClient.Value - .GetPropertyOrNull("file")? - .Deserialize(JsonUtil.JsonOptions); - - version.LoggingClient = new MLogConfiguration - { - LogFile = logFile, - Type = loggingClient.Value.GetPropertyValue("type"), - Argument = loggingClient.Value.GetPropertyValue("argument") - }; - } - - return version; - } - - // TODO: create argument object - private static string[] argParse(JsonElement arr) - { - var strList = new List(); - - foreach (var item in arr.EnumerateArray()) - { - if (item.ValueKind == JsonValueKind.Object) - { - bool allow = true; - - var rules = item.GetPropertyOrNull("rules"); - if (rules == null || rules.Value.ValueKind != JsonValueKind.Array) - rules = item.GetPropertyOrNull("compatibilityRules"); - - if (rules != null) - allow = MRule.CheckOSRequire(rules.Value); - - if (allow) - { - var value = item.GetPropertyOrNull("value") ?? item.GetPropertyOrNull("values"); - if (value != null) - { - if (value.Value.ValueKind == JsonValueKind.Array) - { - foreach (var strProp in value.Value.EnumerateArray()) - { - if (strProp.ValueKind != JsonValueKind.String) - continue; - - var str = strProp.GetString(); - if (!string.IsNullOrEmpty(str)) - strList.Add(str); - } - } - else if (value.Value.ValueKind == JsonValueKind.String) - { - var valueString = value.Value.GetString(); - if (!string.IsNullOrEmpty(valueString)) - strList.Add(valueString); - } - } - } - } - else - { - var value = item.GetString(); - if (!string.IsNullOrEmpty(value)) - strList.Add(value); - } - } - - return strList.ToArray(); + return new JsonVersion(root, options); } } diff --git a/src/Core/Version/JsonVersionParserOptions.cs b/src/Core/Version/JsonVersionParserOptions.cs new file mode 100644 index 0000000..1ecc9ba --- /dev/null +++ b/src/Core/Version/JsonVersionParserOptions.cs @@ -0,0 +1,13 @@ +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Version; + +public class JsonVersionParserOptions +{ + public const string ClientSide = "client"; + public const string ServerSide = "server"; + + public bool SkipError { get; set; } = true; + public string Side { get; set; } = "client"; + public IRulesEvaluator RulesEvaluator { get; set; } = new RulesEvaluator(); +} \ No newline at end of file diff --git a/src/Core/Version/MVersion.cs b/src/Core/Version/MVersion.cs deleted file mode 100644 index ba8b850..0000000 --- a/src/Core/Version/MVersion.cs +++ /dev/null @@ -1,117 +0,0 @@ -using CmlLib.Core.Files; -using CmlLib.Core.VersionMetadata; - -namespace CmlLib.Core.Version; - -public class MVersion -{ - public MVersion(string id) - { - this.Id = id; - } - - public bool IsInherited { get; set; } - public string? ParentVersionId { get; set; } - - public string Id { get; set; } - - public MFileMetadata? Assets { get; set; } - - public string? JavaVersion { get; set; } - public string? JavaBinaryPath { get; set; } - public string? Jar { get; set; } - public MFileMetadata? Client { get; set; } - public MLibrary[]? Libraries { get; set; } - public string? MainClass { get; set; } - public string? MinecraftArguments { get; set; } - public string[]? GameArguments { get; set; } - public string[]? JvmArguments { get; set; } - public string? ReleaseTime { get; set; } - public string? Type { get; set; } - public MLogConfiguration? LoggingClient { get; set; } - - public MVersionType GetVersionType() => MVersionTypeConverter.FromString(Type); - - public void InheritFrom(MVersion parentVersion) - { - /* - Overload : - AssetId, AssetUrl, AssetHash, ClientDownloadUrl, - ClientHash, MainClass, MinecraftArguments, JavaVersion - - Combine : - Libraries, GameArguments, JvmArguments - */ - - // Overloads - - if (Assets == null) - Assets = parentVersion.Assets; - else - { - if (string.IsNullOrEmpty(Assets.Id)) - Assets.Id = parentVersion.Assets?.Id; - - if (string.IsNullOrEmpty(Assets.Url)) - Assets.Url = parentVersion.Assets?.Url; - - if (string.IsNullOrEmpty(Assets.Sha1)) - Assets.Sha1 = parentVersion.Assets?.Sha1; - } - - if (Client == null) - Client = parentVersion.Client; - else - { - if (string.IsNullOrEmpty(Client.Url)) - Client.Url = parentVersion.Client?.Url; - - if (string.IsNullOrEmpty(Client.Sha1)) - Client.Sha1 = parentVersion.Client?.Sha1; - } - - if (string.IsNullOrEmpty(MainClass)) - MainClass = parentVersion.MainClass; - - if (string.IsNullOrEmpty(MinecraftArguments)) - MinecraftArguments = parentVersion.MinecraftArguments; - - if (string.IsNullOrEmpty(JavaVersion)) - JavaVersion = parentVersion.JavaVersion; - - if (LoggingClient == null) - LoggingClient = parentVersion.LoggingClient; - //Jar = parentVersion.Jar; - - // Combine - - if (parentVersion.Libraries != null) - { - if (Libraries != null) - Libraries = Libraries.Concat(parentVersion.Libraries).ToArray(); - else - Libraries = parentVersion.Libraries; - } - - if (parentVersion.GameArguments != null) - { - if (GameArguments != null) - GameArguments = parentVersion.GameArguments.Concat(GameArguments).ToArray(); - else - GameArguments = parentVersion.GameArguments; - } - - if (parentVersion.JvmArguments != null) - { - if (JvmArguments != null) - JvmArguments = parentVersion.JvmArguments.Concat(JvmArguments).ToArray(); - else - JvmArguments = parentVersion.JvmArguments; - } - } - - public override string ToString() - { - return this.Id; - } -} diff --git a/src/Core/Version/VersionJsonModel.cs b/src/Core/Version/VersionJsonModel.cs new file mode 100644 index 0000000..4a87056 --- /dev/null +++ b/src/Core/Version/VersionJsonModel.cs @@ -0,0 +1,47 @@ +using System.Text.Json.Serialization; +using CmlLib.Core.Files; +using CmlLib.Core.Java; + +namespace CmlLib.Core.Version; + +public class VersionJsonModel +{ + [JsonPropertyName("inheritsFrom")] + public string? InheritsFrom { get; set; } + + [JsonPropertyName("assetIndex")] + public AssetMetadata? AssetIndex { get; set; } + + [JsonPropertyName("assets")] + public string? Assets { get; set; } + + [JsonPropertyName("complianceLevel")] + public string? ComplianceLevel { get; set;} + + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("javaVersion")] + public MinecraftJavaVersion? JavaVersion { get; set; } + + [JsonPropertyName("jar")] + public string? Jar { get; set; } + + [JsonPropertyName("mainClass")] + public string? MainClass { get; set; } + + [JsonPropertyName("minecraftArguments")] + public string? MinecraftArguments { get; set; } + + [JsonPropertyName("minimumLauncherVersion")] + public string? MinimumLauncherVersion { get; set; } + + [JsonPropertyName("releaseTime")] + public DateTime ReleaseTime { get; set; } + + [JsonPropertyName("time")] + public DateTime Time { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } +} \ No newline at end of file diff --git a/src/Core/VersionLoader/IVersionLoader.cs b/src/Core/VersionLoader/IVersionLoader.cs index adfc131..52d7184 100644 --- a/src/Core/VersionLoader/IVersionLoader.cs +++ b/src/Core/VersionLoader/IVersionLoader.cs @@ -4,6 +4,6 @@ namespace CmlLib.Core.VersionLoader { public interface IVersionLoader { - ValueTask GetVersionMetadatasAsync(); + ValueTask GetVersionMetadatasAsync(); } } diff --git a/src/Core/VersionLoader/LocalVersionLoader.cs b/src/Core/VersionLoader/LocalVersionLoader.cs index 642aaa6..890b24b 100644 --- a/src/Core/VersionLoader/LocalVersionLoader.cs +++ b/src/Core/VersionLoader/LocalVersionLoader.cs @@ -11,11 +11,11 @@ public LocalVersionLoader(MinecraftPath path) private readonly MinecraftPath minecraftPath; - public ValueTask GetVersionMetadatasAsync() + public ValueTask GetVersionMetadatasAsync() { var versions = getFromLocal(minecraftPath); - var collection = new MVersionCollection(versions, null, null); - return new ValueTask(collection); + var collection = new VersionCollection(versions, null, null); + return new ValueTask(collection); } private IEnumerable getFromLocal(MinecraftPath path) diff --git a/src/Core/VersionLoader/MojangVersionLoader.cs b/src/Core/VersionLoader/MojangVersionLoader.cs index 5345393..9d0661d 100644 --- a/src/Core/VersionLoader/MojangVersionLoader.cs +++ b/src/Core/VersionLoader/MojangVersionLoader.cs @@ -15,7 +15,7 @@ public MojangVersionLoader(HttpClient httpClient) => public MojangVersionLoader(HttpClient httpClient, string endpoint) => (_httpClient, _endpoint) = (httpClient, endpoint); - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { var res = await _httpClient.GetStreamAsync(_endpoint) .ConfigureAwait(false); @@ -41,6 +41,6 @@ public async ValueTask GetVersionMetadatasAsync() } } - return new MVersionCollection(metadatas, latestReleaseId, latestSnapshotId); + return new VersionCollection(metadatas, latestReleaseId, latestSnapshotId); } } diff --git a/src/Core/VersionLoader/VersionLoaderCollection.cs b/src/Core/VersionLoader/VersionLoaderCollection.cs index 627f554..d28da32 100644 --- a/src/Core/VersionLoader/VersionLoaderCollection.cs +++ b/src/Core/VersionLoader/VersionLoaderCollection.cs @@ -7,9 +7,9 @@ public class VersionLoaderCollection : IVersionLoader, ICollection _collection = new(); - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { - var versions = new MVersionCollection(); + var versions = new VersionCollection(); foreach (var loader in _collection) { var newVersions = await loader.GetVersionMetadatasAsync(); diff --git a/src/Core/VersionMetadata/IVersionMetadata.cs b/src/Core/VersionMetadata/IVersionMetadata.cs index b2adad3..cb945d5 100644 --- a/src/Core/VersionMetadata/IVersionMetadata.cs +++ b/src/Core/VersionMetadata/IVersionMetadata.cs @@ -12,7 +12,7 @@ public interface IVersionMetadata string? Type { get; } DateTime? ReleaseTime { get; } - Task GetVersionAsync(); - Task GetAndSaveVersionAsync(MinecraftPath minecraftPath); + Task GetVersionAsync(); + Task GetAndSaveVersionAsync(MinecraftPath minecraftPath); Task SaveVersionAsync(MinecraftPath minecraftPath); } diff --git a/src/Core/VersionMetadata/JsonVersionMetadata.cs b/src/Core/VersionMetadata/JsonVersionMetadata.cs index dc0fb06..fb2835c 100644 --- a/src/Core/VersionMetadata/JsonVersionMetadata.cs +++ b/src/Core/VersionMetadata/JsonVersionMetadata.cs @@ -54,16 +54,16 @@ public override int GetHashCode() /// Version metadata protected abstract ValueTask GetVersionJsonString(); - public async Task GetVersionAsync() + public async Task GetVersionAsync() { var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - return new JsonVersionParser().ParseFromJsonString(metadataJson); + return JsonVersionParser.ParseFromJsonString(metadataJson); } - public async Task GetAndSaveVersionAsync(MinecraftPath path) + public async Task GetAndSaveVersionAsync(MinecraftPath path) { var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - var version = new JsonVersionParser().ParseFromJsonString(metadataJson); + var version = JsonVersionParser.ParseFromJsonString(metadataJson); await saveMetdataJson(path, metadataJson); return version; } diff --git a/src/Core/VersionMetadata/MVersionCollection.cs b/src/Core/VersionMetadata/MVersionCollection.cs index c0dff2d..fad7b9d 100644 --- a/src/Core/VersionMetadata/MVersionCollection.cs +++ b/src/Core/VersionMetadata/MVersionCollection.cs @@ -5,16 +5,16 @@ namespace CmlLib.Core.VersionMetadata; // Collection for IVersionMetadata -// return MVersion object from IVersionMetadata -public class MVersionCollection : IEnumerable +// return IVersion object from IVersionMetadata +public class VersionCollection : IEnumerable { - public MVersionCollection() + public VersionCollection() : this(Enumerable.Empty(), null, null) { } - public MVersionCollection( + public VersionCollection( IEnumerable versions, string? latestRelease, string? latestSnapshot) @@ -58,7 +58,7 @@ public IVersionMetadata[] ToArray(MVersionSortOption option) return sorter.Sort(this); } - public Task GetVersionAsync(string name) + public Task GetVersionAsync(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); @@ -67,7 +67,7 @@ public Task GetVersionAsync(string name) return GetVersionAsync(versionMetadata); } - public Task GetAndSaveVersionAsync(string name, MinecraftPath path) + public Task GetAndSaveVersionAsync(string name, MinecraftPath path) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); @@ -76,18 +76,18 @@ public Task GetAndSaveVersionAsync(string name, MinecraftPath path) return GetAndSaveVersionAsync(versionMetadata, path); } - public Task GetVersionAsync(IVersionMetadata versionMetadata) => + public Task GetVersionAsync(IVersionMetadata versionMetadata) => getVersionInternal(versionMetadata, null); - public Task GetAndSaveVersionAsync(IVersionMetadata versionMetadata, MinecraftPath path) => + public Task GetAndSaveVersionAsync(IVersionMetadata versionMetadata, MinecraftPath path) => getVersionInternal(versionMetadata, path); - private async Task getVersionInternal(IVersionMetadata versionMetadata, MinecraftPath? path) + private async Task getVersionInternal(IVersionMetadata versionMetadata, MinecraftPath? path) { if (versionMetadata == null) throw new ArgumentNullException(nameof(versionMetadata)); - MVersion version; + IVersion version; if (path == null) version = await versionMetadata.GetVersionAsync(); else @@ -97,17 +97,17 @@ private async Task getVersionInternal(IVersionMetadata versionMetadata return version; } - private async Task inheritIfRequired(MVersion version, MinecraftPath? path) + private async Task inheritIfRequired(IVersion version, MinecraftPath? path) { - if (version.IsInherited && !string.IsNullOrEmpty(version.ParentVersionId)) + if (!string.IsNullOrEmpty(version.InheritsFrom)) { - if (version.ParentVersionId == version.Id) // prevent StackOverFlowException + if (version.InheritsFrom == version.Id) // prevent StackOverFlowException throw new InvalidDataException( "Invalid version json file : inheritFrom property is equal to id property."); - var baseVersionMetadata = GetVersionMetadata(version.ParentVersionId); + var baseVersionMetadata = GetVersionMetadata(version.InheritsFrom); var baseVersion = await getVersionInternal(baseVersionMetadata, path); - version.InheritFrom(baseVersion); + version.ParentVersion = baseVersion; } } @@ -119,7 +119,7 @@ public void AddVersion(IVersionMetadata version) public bool Contains(string? versionName) => !string.IsNullOrEmpty(versionName) && Versions.Contains(versionName); - public void Merge(MVersionCollection from) + public void Merge(VersionCollection from) { foreach (var item in from) { diff --git a/src/ProcessBuilder/MinecraftArgumentBuilder.cs b/src/ProcessBuilder/MinecraftArgumentBuilder.cs new file mode 100644 index 0000000..1fd14de --- /dev/null +++ b/src/ProcessBuilder/MinecraftArgumentBuilder.cs @@ -0,0 +1,38 @@ +using CmlLib.Core.Launcher; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.ProcessBuilder; + +public class MinecraftArgumentBuilder +{ + private readonly IRulesEvaluator _evaluator; + + public IEnumerable Build(IEnumerable arguments, Dictionary mapper) + { + foreach (var arg in arguments) + { + foreach (var value in Build(arg, mapper)) + yield return value; + } + } + + public IEnumerable Build(MArgument arg, Dictionary mapper) + { + if (arg.Values == null) + yield break; + + if (arg.Rules != null) + { + var isMatch = _evaluator.Match(arg.Rules); + if (!isMatch) + yield break; + } + + foreach (var value in arg.Values) + { + var mappedValue = Mapper.Interpolation(value, mapper, true); + if (!string.IsNullOrEmpty(mappedValue)) + yield return mappedValue; + } + } +} \ No newline at end of file diff --git a/src/ProcessBuilder/ProcessArgumentBuilder.cs b/src/ProcessBuilder/ProcessArgumentBuilder.cs new file mode 100644 index 0000000..a73fa32 --- /dev/null +++ b/src/ProcessBuilder/ProcessArgumentBuilder.cs @@ -0,0 +1,37 @@ +namespace CmlLib.Core.ProcessBuilder; + +public class ProcessArgumentBuilder +{ + private List _args = new(); + + public void AddArgument(string arg) + { + AddRawArgument(escapeEmpty(arg)); + } + + public void AddKeyValue(string key, string? value) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentNullException(key); + + var escapedValue = escapeEmpty(value); + if (string.IsNullOrEmpty(escapedValue)) + escapedValue = "\"\""; + + AddRawArgument($"{key}={escapedValue}"); + } + + public void AddRawArgument(string arg) + { + _args.Add(arg); + } + + private string escapeEmpty(string? input) + { + if (string.IsNullOrEmpty(input)) + return ""; + if (input.Contains(" ")) + return "\"" + input + "\""; + return input; + } +} \ No newline at end of file diff --git a/src/ProcessBuilder/ProcessUtil.cs b/src/ProcessBuilder/ProcessUtil.cs new file mode 100644 index 0000000..a669ffb --- /dev/null +++ b/src/ProcessBuilder/ProcessUtil.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; + +namespace CmlLib.Core.ProcessBuilder; + +public class ProcessWrapper +{ + public event EventHandler? OutputReceived; + public event EventHandler? Exited; + + public Process Process { get; private set; } + + public ProcessWrapper(Process process) + { + this.Process = process; + } + + public void StartWithEvents() + { + Process.StartInfo.CreateNoWindow = true; + Process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + Process.StartInfo.UseShellExecute = false; + Process.StartInfo.RedirectStandardError = true; + Process.StartInfo.RedirectStandardOutput = true; + Process.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8; + Process.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8; + Process.EnableRaisingEvents = true; + Process.ErrorDataReceived += (s, e) => OutputReceived?.Invoke(this, e.Data ?? ""); + Process.OutputDataReceived += (s, e) => OutputReceived?.Invoke(this, e.Data ?? ""); + Process.Exited += (s, e) => Exited?.Invoke(this, new EventArgs()); + + Process.Start(); + Process.BeginErrorReadLine(); + Process.BeginOutputReadLine(); + } + + public Task WaitForExitTaskAsync() + { + return Task.Run(() => + { + Process.WaitForExit(); + }); + } +} diff --git a/src/Utils/IOUtil.cs b/src/Utils/IOUtil.cs index e1879c3..9b15cdf 100644 --- a/src/Utils/IOUtil.cs +++ b/src/Utils/IOUtil.cs @@ -25,7 +25,7 @@ public static string NormalizePath(string path) .TrimEnd(Path.DirectorySeparatorChar); } - public static string CombinePath(string[] paths) + public static string CombinePath(IEnumerable paths) { return string.Join(Path.PathSeparator.ToString(), paths.Select(x => diff --git a/src/Utils/ProcessUtil.cs b/src/Utils/ProcessUtil.cs deleted file mode 100644 index 1ae65fd..0000000 --- a/src/Utils/ProcessUtil.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; - -namespace CmlLib.Utils -{ - public class ProcessUtil - { - public event EventHandler? OutputReceived; - public event EventHandler? Exited; - - public Process Process { get; private set; } - - public ProcessUtil(Process process) - { - this.Process = process; - } - - public void StartWithEvents() - { - Process.StartInfo.CreateNoWindow = true; - Process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - Process.StartInfo.UseShellExecute = false; - Process.StartInfo.RedirectStandardError = true; - Process.StartInfo.RedirectStandardOutput = true; - Process.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8; - Process.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8; - Process.EnableRaisingEvents = true; - Process.ErrorDataReceived += (s, e) => OutputReceived?.Invoke(this, e.Data ?? ""); - Process.OutputDataReceived += (s, e) => OutputReceived?.Invoke(this, e.Data ?? ""); - Process.Exited += (s, e) => Exited?.Invoke(this, new EventArgs()); - - Process.Start(); - Process.BeginErrorReadLine(); - Process.BeginOutputReadLine(); - } - - public Task WaitForExitTaskAsync() - { - return Task.Run(() => - { - Process.WaitForExit(); - }); - } - } -} From 3148e9ed92d2e357f9c7082f680588172744df28 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 5 Aug 2023 14:40:07 +0000 Subject: [PATCH 057/137] add TPL --- .vscode/launch.json | 7 +- .vscode/tasks.json | 6 +- examples/console/InstallerTest.cs | 156 ---------- examples/console/Program.cs | 159 ++++------ examples/console/Test.cs | 102 ------ src/CmlLib.csproj | 1 + src/Core/CMLauncher.cs | 151 +-------- src/Core/Executors/TPLTaskExecutor.cs | 74 +++++ src/Core/FileChecker/AssetChecker.cs | 183 ----------- src/Core/FileChecker/ClientChecker.cs | 62 ---- src/Core/FileChecker/FileCheckerCollection.cs | 74 ----- src/Core/FileChecker/IFileChecker.cs | 13 - src/Core/FileChecker/JavaChecker.cs | 294 ------------------ src/Core/FileChecker/LibraryChecker.cs | 111 ------- src/Core/FileChecker/LogChecker.cs | 77 ----- src/Core/FileChecker/ModChecker.cs | 70 ----- src/Core/FileExtractors/AssetFileExtractor.cs | 147 +++++++++ .../FileExtractors/ClientFileExtractor.cs | 36 +++ .../FileExtractors/FileCheckerCollection.cs | 80 +++++ src/Core/FileExtractors/IFileExtractor.cs | 9 + src/Core/FileExtractors/JavaFileExtractor.cs | 214 +++++++++++++ .../FileExtractors/LibraryFileExtractor.cs | 99 ++++++ src/Core/FileExtractors/LogFileExtractor.cs | 36 +++ src/Core/Files/MFileMetadata.cs | 8 + .../Installer/FabricMC/FabricVersionLoader.cs | 7 +- .../LiteLoader/LiteLoaderInstaller.cs | 4 +- .../LiteLoader/LiteLoaderVersionLoader.cs | 6 +- .../LiteLoader/LiteLoaderVersionMetadata.cs | 22 +- src/Core/Installer/MJava.cs | 21 +- .../Installer/QuiltMC/QuiltVersionLoader.cs | 4 +- src/Core/Java/IJavaPathResolver.cs | 18 +- src/Core/Java/MinecraftJavaPathResolver.cs | 125 ++++---- src/Core/Java/MinecraftJavaVersion.cs | 17 +- src/Core/Launcher/MLaunch.cs | 10 +- src/Core/Launcher/MLaunchOption.cs | 5 + src/Core/Launcher/MNative.cs | 15 +- .../MinecraftArgumentBuilder.cs | 9 +- .../ProcessBuilder/ProcessArgumentBuilder.cs | 0 src/{ => Core}/ProcessBuilder/ProcessUtil.cs | 0 src/Core/Rules/IRulesEvaluator.cs | 2 +- src/Core/Rules/LauncherRule.cs | 28 +- src/Core/Rules/RulesEvaluator.cs | 17 +- src/Core/Rules/RulesEvaluatorContext.cs | 2 + src/Core/Tasks/ActionTask.cs | 14 + src/Core/Tasks/ChmodTask.cs | 27 ++ src/Core/Tasks/DownloadTask.cs | 29 ++ src/Core/Tasks/FileCheckTask.cs | 32 ++ src/Core/Tasks/FileCopyTask.cs | 23 ++ src/Core/Tasks/LZMADecompressTask.cs | 14 + src/Core/Tasks/LinkedTask.cs | 44 +++ src/Core/Tasks/ResultTask.cs | 20 ++ src/Core/Tasks/TaskFile.cs | 10 + src/Core/Tasks/UnzipTask.cs | 14 + src/Core/Version/IVersion.cs | 2 +- src/Core/Version/JsonVersion.cs | 2 +- src/Core/Version/VersionJsonModel.cs | 10 +- .../VersionMetadata/JsonVersionMetadata.cs | 4 +- 57 files changed, 1194 insertions(+), 1532 deletions(-) delete mode 100644 examples/console/InstallerTest.cs delete mode 100644 examples/console/Test.cs create mode 100644 src/Core/Executors/TPLTaskExecutor.cs delete mode 100644 src/Core/FileChecker/AssetChecker.cs delete mode 100644 src/Core/FileChecker/ClientChecker.cs delete mode 100644 src/Core/FileChecker/FileCheckerCollection.cs delete mode 100644 src/Core/FileChecker/IFileChecker.cs delete mode 100644 src/Core/FileChecker/JavaChecker.cs delete mode 100644 src/Core/FileChecker/LibraryChecker.cs delete mode 100644 src/Core/FileChecker/LogChecker.cs delete mode 100644 src/Core/FileChecker/ModChecker.cs create mode 100644 src/Core/FileExtractors/AssetFileExtractor.cs create mode 100644 src/Core/FileExtractors/ClientFileExtractor.cs create mode 100644 src/Core/FileExtractors/FileCheckerCollection.cs create mode 100644 src/Core/FileExtractors/IFileExtractor.cs create mode 100644 src/Core/FileExtractors/JavaFileExtractor.cs create mode 100644 src/Core/FileExtractors/LibraryFileExtractor.cs create mode 100644 src/Core/FileExtractors/LogFileExtractor.cs rename src/{ => Core}/ProcessBuilder/MinecraftArgumentBuilder.cs (76%) rename src/{ => Core}/ProcessBuilder/ProcessArgumentBuilder.cs (100%) rename src/{ => Core}/ProcessBuilder/ProcessUtil.cs (100%) create mode 100644 src/Core/Tasks/ActionTask.cs create mode 100644 src/Core/Tasks/ChmodTask.cs create mode 100644 src/Core/Tasks/DownloadTask.cs create mode 100644 src/Core/Tasks/FileCheckTask.cs create mode 100644 src/Core/Tasks/FileCopyTask.cs create mode 100644 src/Core/Tasks/LZMADecompressTask.cs create mode 100644 src/Core/Tasks/LinkedTask.cs create mode 100644 src/Core/Tasks/ResultTask.cs create mode 100644 src/Core/Tasks/TaskFile.cs create mode 100644 src/Core/Tasks/UnzipTask.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index aa74641..a5f9cb4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,12 +10,11 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/CmlLibCoreSample/bin/Debug/net6.0/CmlLibCoreSample.dll", + "program": "${workspaceFolder}/examples/console/bin/Debug/net6.0/CmlLibCoreSample.dll", "args": [], - "cwd": "${workspaceFolder}/CmlLibCoreSample", + "cwd": "${workspaceFolder}/examples/console", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", + "console": "internalConsole", "stopAtEntry": false }, { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 294068d..302348e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/CmlLibCoreSample/CmlLibCoreSample.csproj", + "${workspaceFolder}/examples/console/CmlLibCoreSample.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -19,7 +19,7 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/CmlLibCoreSample/CmlLibCoreSample.csproj", + "${workspaceFolder}/examples/console/CmlLibCoreSample.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -33,7 +33,7 @@ "watch", "run", "--project", - "${workspaceFolder}/CmlLibCoreSample/CmlLibCoreSample.csproj" + "${workspaceFolder}/examples/console/CmlLibCoreSample.csproj" ], "problemMatcher": "$msCompile" } diff --git a/examples/console/InstallerTest.cs b/examples/console/InstallerTest.cs deleted file mode 100644 index fa28992..0000000 --- a/examples/console/InstallerTest.cs +++ /dev/null @@ -1,156 +0,0 @@ -using CmlLib.Core; -using CmlLib.Core.Auth; -using CmlLib.Core.Downloader; -using CmlLib.Core.Installer.FabricMC; -using CmlLib.Core.Installer.LiteLoader; - -namespace CmlLibCoreSample; - -public class InstallerTest -{ - // FabricLoader - public async Task TestFabric() - { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path, Program.HttpClient); - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - // initialize fabric version loader - var fabricVersionLoader = new FabricVersionLoader(Program.HttpClient); - var fabricVersions = await fabricVersionLoader.GetVersionMetadatasAsync(); - - // print fabric versions - foreach (var v in fabricVersions) - { - Console.WriteLine(v.Name); - } - - Console.WriteLine("select version: "); - var fabricVersionName = Console.ReadLine(); - - if (string.IsNullOrEmpty(fabricVersionName)) - return; - - // install - var fabric = fabricVersions.GetVersionMetadata(fabricVersionName); - await fabric.SaveVersionAsync(path); - - // update version list - await launcher.GetAllVersionsAsync(); - - var process = await launcher.CreateProcessAsync(fabricVersionName, new MLaunchOption()); - process.Start(); - } - - // LiteLoader - public async Task TestLiteLoader() - { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path, Program.HttpClient); - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - // initialize LiteLoader installer - var liteLoaderVersionLoader = new LiteLoaderVersionLoader(Program.HttpClient); - var liteLoaderVersions = await liteLoaderVersionLoader.GetVersionMetadatasAsync(); - - // print all LiteLoader versions - foreach (var item in liteLoaderVersions) - { - Console.WriteLine(item); - } - - Console.WriteLine("Select LiteLoader version name (ex: LiteLoader1.12.2) : "); - var selectLiteLoaderVersion = Console.ReadLine(); - - // print all game versions - var versions = await launcher.GetAllVersionsAsync(); - foreach (var item in versions) - { - Console.WriteLine(item); - } - - Console.WriteLine("Select minecraft version where to install : "); - var selectGameVersion = Console.ReadLine(); - - if (string.IsNullOrEmpty(selectLiteLoaderVersion) || string.IsNullOrEmpty(selectGameVersion)) - return; - - // install LiteLoader - var liteLoader = - (LiteLoaderVersionMetadata)liteLoaderVersions.GetVersionMetadata(selectLiteLoaderVersion); - var startVersionName = await liteLoader.InstallAsync(path, await versions.GetVersionAsync(selectGameVersion)); - - // update version list - await launcher.GetAllVersionsAsync(); - - // start - var process = await launcher.CreateProcessAsync(startVersionName, new MLaunchOption - { - Session = MSession.GetOfflineSession("liteloadertester"), - MaximumRamMb = 2048 - }); - - process.Start(); - } - - // only vanilla - public async Task TestLiteLoaderVanilla() - { - var path = new MinecraftPath(); - var launcher = new CMLauncher(path, Program.HttpClient); - launcher.FileChanged += Downloader_ChangeFile; - launcher.ProgressChanged += Downloader_ChangeProgress; - - var versions = await launcher.GetAllVersionsAsync(); - - // initialize LiteLoader installer - var liteLoaderVersionLoader = new LiteLoaderVersionLoader(Program.HttpClient); - var liteLoaderVersions = await liteLoaderVersionLoader.GetVersionMetadatasAsync(); - - // print all LiteLoader versions - foreach (var item in liteLoaderVersions) - { - var liteLoaderVersion = item as LiteLoaderVersionMetadata; - if (liteLoaderVersion == null) - continue; - - Console.WriteLine(item.Name); - - var vanillaVersionName = liteLoaderVersion.VanillaVersionName; - Console.WriteLine(vanillaVersionName); - var vanillaVersion = await versions.GetVersionAsync(vanillaVersionName); - - var liteLoaderVersionName = await liteLoaderVersion.InstallAsync(path, vanillaVersion); - versions = await launcher.GetAllVersionsAsync(); // update version lists - - var process = await launcher.CreateProcessAsync(liteLoaderVersionName, new MLaunchOption()); - process.Start(); - Console.ReadLine(); - } - } - - int endTop = -1; - - private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) - { - Console.SetCursorPosition(0, endTop); - - // e.ProgressPercentage: 0~100 - Console.Write("{0}% ", e.ProgressPercentage); - - Console.SetCursorPosition(0, endTop); - } - - private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) - { - // More information about DownloadFileChangedEventArgs - // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs - - Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, - e.TotalFileCount); - - endTop = Console.CursorTop; - } -} \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index bd1348c..f556fa9 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -1,6 +1,12 @@ using CmlLib.Core; using CmlLib.Core.Auth; using CmlLib.Core.Downloader; +using CmlLib.Core.Executors; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Java; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.VersionLoader; namespace CmlLibCoreSample; @@ -13,7 +19,7 @@ public static async Task Main() var p = new Program(); // Login - MSession session; + MSession session; session = p.OfflineLogin(); // Login by username // log login session information @@ -31,128 +37,67 @@ MSession OfflineLogin() async Task Start(MSession session) { - // Initializing Launcher + var minecraftPath = new MinecraftPath(); + var httpClient = new HttpClient(); + var rulesEvaluator = new RulesEvaluator(); + var javaPathResolver = new MinecraftJavaPathResolver(minecraftPath); + var rulesContext = new RulesEvaluatorContext(LauncherOSRule.CreateCurrent()); - // Set minecraft home directory - // MinecraftPath.GetOSDefaultPath() return default minecraft BasePath of current OS. - // https://github.com/AlphaBs/CmlLib.Core/blob/master/CmlLib/Core/MinecraftPath.cs - - // You can set this path to what you want like this : - //var path = "./testdir"; - var path = MinecraftPath.GetOSDefaultPath(); - var game = new MinecraftPath(path); - - // Create CMLauncher instance - var launcher = new CMLauncher(game, HttpClient); - - // if you want to download with parallel downloader, add below code : - System.Net.ServicePointManager.DefaultConnectionLimit = 256; - - // for offline mode - //launcher.VersionLoader = new LocalVersionLoader(launcher.MinecraftPath); - //launcher.FileDownloader = null; - - launcher.ProgressChanged += Downloader_ChangeProgress; - launcher.FileChanged += Downloader_ChangeFile; - - Console.WriteLine($"Initialized in {launcher.MinecraftPath.BasePath}"); - - // Get all installed profiles and load all profiles from mojang server - var versions = await launcher.GetAllVersionsAsync(); - - foreach (var item in versions) // Display all profiles - { - // You can filter snapshots and old versions to add if statement : - // if (item.MType == MProfileType.Custom || item.MType == MProfileType.Release) - Console.WriteLine(item.Type + " " + item.Name); - } - - var launchOption = new MLaunchOption + var versionLoader = new VersionLoaderCollection() { - MaximumRamMb = 1024, - Session = session, - - //ScreenWidth = 1600, - //ScreenHeight = 900, - //ServerIp = "mc.hypixel.net", - //MinimumRamMb = 102, - //FullScreen = true, - - // More options: - // https://github.com/AlphaBs/CmlLib.Core/wiki/MLaunchOption + new LocalVersionLoader(minecraftPath), + //new MojangVersionLoader(httpClient), }; - // download essential files (ex: vanilla libraries) and create game process. - - // var process = await launcher.CreateProcessAsync("1.15.2", launchOption); // vanilla - // var process = await launcher.CreateProcessAsync("1.12.2-forge1.12.2-14.23.5.2838", launchOption); // forge - // var process = await launcher.CreateProcessAsync("1.12.2-LiteLoader1.12.2"); // liteloader - // var process = await launcher.CreateProcessAsync("fabric-loader-0.11.3-1.16.5") // fabric-loader - - Console.WriteLine("input version (example: 1.12.2) : "); - var versionName = Console.ReadLine(); - //var versionName = "1.18.2"; - var process = await launcher.CreateProcessAsync(versionName, launchOption); + var versions = await versionLoader.GetVersionMetadatasAsync(); + foreach (var v in versions) + { + Console.WriteLine($"{v.Name}, {v.Type}, {v.ReleaseTime}"); + } - //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); - Console.WriteLine(process.StartInfo.FileName); - Console.WriteLine(process.StartInfo.Arguments); + var version = await versions.GetAndSaveVersionAsync( + "1.20.1", minecraftPath); - // Below codes are print game logs in Console. - var processUtil = new CmlLib.Utils.ProcessUtil(process); - processUtil.OutputReceived += (s, e) => Console.WriteLine(e); - processUtil.StartWithEvents(); - await process.WaitForExitAsync(); + var extractors = FileExtractorCollection.CreateDefault( + httpClient, javaPathResolver, rulesEvaluator, rulesContext); - // or just start it without print logs - // process.Start(); + //foreach (var ex in extractors) + //{ + // var tasks = await ex.Extract(minecraftPath, version); + // foreach (var task in tasks) + // { + // printTask(task); + // } + //} + var installer = new TPLTaskExecutor(); + await installer.Install(extractors, minecraftPath, version); + Console.WriteLine("DONE"); Console.ReadLine(); } - #region QuickStart - - // this code is from README.md - - async Task QuickStart() + private void printTask(LinkedTask? task) { - //var path = new MinecraftPath("game_directory_path"); - var path = new MinecraftPath(); // use default directory - - var launcher = new CMLauncher(path, HttpClient); - launcher.FileChanged += (e) => - { - Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - }; - launcher.ProgressChanged += (s, e) => - { - Console.WriteLine("{0}%", e.ProgressPercentage); - }; - - var versions = await launcher.GetAllVersionsAsync(); - foreach (var item in versions) + while (task != null) { - Console.WriteLine(item.Name); + Console.WriteLine(task.GetType().Name); + if (task is FileCheckTask fct) + { + Console.WriteLine(fct.Path); + Console.WriteLine(fct.Hash); + printTask(fct.OnTrue); + printTask(fct.OnFalse); + } + else if (task is DownloadTask dt) + { + Console.WriteLine(dt.Path); + Console.WriteLine(dt.Url); + } + Console.WriteLine(); + task = task.NextTask; } - - var launchOption = new MLaunchOption - { - MaximumRamMb = 1024, - Session = MSession.GetOfflineSession("hello"), // Login Session. ex) Session = MSession.GetOfflineSession("hello") - - //ScreenWidth = 1600, - //ScreenHeigth = 900, - //ServerIp = "mc.hypixel.net" - }; - - // launch vanila - var process = await launcher.CreateProcessAsync("1.15.2", launchOption); - - process.Start(); } - #endregion - // Event Handling // The code below has some tricks to display logs prettier. diff --git a/examples/console/Test.cs b/examples/console/Test.cs deleted file mode 100644 index 6325501..0000000 --- a/examples/console/Test.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using CmlLib.Core; -using CmlLib.Core.Auth; -using CmlLib.Core.Downloader; -using CmlLib.Core.VersionMetadata; - -namespace CmlLibCoreSample -{ - public class Test - { - public async Task TestDownloadFile() - { - // Console.WriteLine("TestDownloadFile"); - // var wd = new CmlLib.Utils.WebDownload(); - // wd.FileDownloadProgressChanged += (sender, args) => Console.WriteLine(args.ProgressPercentage); - // await wd.DownloadFileAsync(new DownloadFile() - // { - // Name = "test.bin", - // Size = 0, - // Url = "https://github.com/AlphaBs/YoutubeMusicPlayer/releases/download/v2.0-b2/YMP_b2.zip", - // Path = "test.bin" - // }); - // Console.WriteLine("done"); - } - - async Task TestAll(MSession session) - { - var path = MinecraftPath.GetOSDefaultPath(); - var game = new MinecraftPath(path); - - var launcher = new CMLauncher(game, Program.HttpClient); - - System.Net.ServicePointManager.DefaultConnectionLimit = 256; - launcher.FileDownloader = new AsyncParallelDownloader(Program.HttpClient); - - launcher.ProgressChanged += Downloader_ChangeProgress; - launcher.FileChanged += Downloader_ChangeFile; - - Console.WriteLine($"Initialized in {launcher.MinecraftPath.BasePath}"); - - var launchOption = new MLaunchOption - { - MaximumRamMb = 1024, - Session = session, - }; - - var versions = await launcher.GetAllVersionsAsync(); - foreach (var item in versions) - { - Console.WriteLine(item.Type + " " + item.Name); - if (item is LocalVersionMetadata) - continue; - - var process = await launcher.CreateProcessAsync(item.Name, launchOption); - - //var process = launcher.CreateProcess("1.16.2", "33.0.5", launchOption); - Console.WriteLine(process.StartInfo.Arguments); - - // Below codes are print game logs in Console. - var processUtil = new CmlLib.Utils.ProcessUtil(process); - processUtil.OutputReceived += (s, e) => Console.WriteLine(e); - processUtil.StartWithEvents(); - - Thread.Sleep(1000 * 15); - - if (process.HasExited) - { - Console.WriteLine("FAILED!!!!!!!!!"); - Console.ReadLine(); - } - - process.Kill(); - await process.WaitForExitAsync(); - } - } - - int endTop = -1; - - private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) - { - Console.SetCursorPosition(0, endTop); - - // e.ProgressPercentage: 0~100 - Console.Write("{0}% ", e.ProgressPercentage); - - Console.SetCursorPosition(0, endTop); - } - - private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) - { - // More information about DownloadFileChangedEventArgs - // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs - - Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, - e.TotalFileCount); - - endTop = Console.CursorTop; - } - } -} \ No newline at end of file diff --git a/src/CmlLib.csproj b/src/CmlLib.csproj index 7e8ba2b..446b54b 100644 --- a/src/CmlLib.csproj +++ b/src/CmlLib.csproj @@ -27,6 +27,7 @@ Support all version, forge, optifine + diff --git a/src/Core/CMLauncher.cs b/src/Core/CMLauncher.cs index bf7a070..15dae37 100644 --- a/src/Core/CMLauncher.cs +++ b/src/Core/CMLauncher.cs @@ -1,6 +1,7 @@ using CmlLib.Core.Downloader; -using CmlLib.Core.FileChecker; +using CmlLib.Core.FileExtractors; using CmlLib.Core.Java; +using CmlLib.Core.Rules; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; @@ -13,16 +14,16 @@ public class CMLauncher { private readonly HttpClient _httpClient; - public CMLauncher(string path, HttpClient httpClient) : this(new MinecraftPath(path), httpClient) + public CMLauncher(string path, HttpClient httpClient) : this(new MinecraftPath(path), httpClient, new LauncherOSRule()) { } - public CMLauncher(MinecraftPath path, HttpClient httpClient) + public CMLauncher(MinecraftPath path, HttpClient httpClient, LauncherOSRule os) { _httpClient = httpClient; + this.os = os; MinecraftPath = path; - GameFileCheckers = FileCheckerCollection.CreateDefault(_httpClient); FileDownloader = new AsyncParallelDownloader(_httpClient); VersionLoader = new VersionLoaderCollection { @@ -36,6 +37,7 @@ public CMLauncher(MinecraftPath path, HttpClient httpClient) e => ProgressChanged?.Invoke(this, e)); JavaPathResolver = new MinecraftJavaPathResolver(path); + GameFileCheckers = FileExtractorCollection.CreateDefault(_httpClient, JavaPathResolver, new RulesEvaluator(), new RulesEvaluatorContext(os)); } public event DownloadFileChangedHandler? FileChanged; @@ -44,23 +46,25 @@ public CMLauncher(MinecraftPath path, HttpClient httpClient) private readonly IProgress pFileChanged; private readonly IProgress pProgressChanged; + public readonly LauncherOSRule os; + public MinecraftPath MinecraftPath { get; private set; } - public MVersionCollection? Versions { get; private set; } + public VersionCollection? Versions { get; private set; } public IVersionLoader VersionLoader { get; set; } - public FileCheckerCollection GameFileCheckers { get; private set; } + public FileExtractorCollection GameFileCheckers { get; private set; } public IDownloader? FileDownloader { get; set; } public IJavaPathResolver JavaPathResolver { get; set; } - public async Task GetAllVersionsAsync() + public async Task GetAllVersionsAsync() { Versions = await VersionLoader.GetVersionMetadatasAsync() .ConfigureAwait(false); return Versions; } - public async Task GetVersionAsync(string versionName) + public async Task GetVersionAsync(string versionName) { if (Versions == null) await GetAllVersionsAsync().ConfigureAwait(false); @@ -70,142 +74,19 @@ public async Task GetVersionAsync(string versionName) return version; } - public DownloadFile[] CheckLostGameFiles(MVersion version) - { - var lostFiles = new List(); - foreach (IFileChecker checker in this.GameFileCheckers) - { - DownloadFile[]? files = checker.CheckFiles(MinecraftPath, version, pFileChanged); - if (files != null) - lostFiles.AddRange(files); - } - - return lostFiles.ToArray(); - } - - public async Task CheckLostGameFilesTaskAsync(MVersion version) - { - var lostFiles = new List(); - foreach (IFileChecker checker in this.GameFileCheckers) - { - DownloadFile[]? files = await checker.CheckFilesTaskAsync(MinecraftPath, version, pFileChanged) - .ConfigureAwait(false); - if (files != null) - lostFiles.AddRange(files); - } - - return lostFiles.ToArray(); - } - - public async Task DownloadGameFiles(DownloadFile[] files) - { - if (this.FileDownloader == null) - return; - - await FileDownloader.DownloadFiles(files, pFileChanged, pProgressChanged) - .ConfigureAwait(false); - } - - public void CheckAndDownload(MVersion version) - { - foreach (var checker in this.GameFileCheckers) - { - DownloadFile[]? files = checker.CheckFiles(MinecraftPath, version, pFileChanged); - - if (files == null || files.Length == 0) - continue; - - DownloadGameFiles(files).GetAwaiter().GetResult(); - } - } - - public async Task CheckAndDownloadAsync(MVersion version) - { - foreach (var checker in this.GameFileCheckers) - { - DownloadFile[]? files = await checker.CheckFilesTaskAsync(MinecraftPath, version, pFileChanged) - .ConfigureAwait(false); - - if (files == null || files.Length == 0) - continue; - - await DownloadGameFiles(files).ConfigureAwait(false); - } - } - - public Process CreateProcess(MVersion version, MLaunchOption option, bool checkAndDownload=true) - { - option.StartVersion = version; - - if (checkAndDownload) - CheckAndDownload(option.StartVersion); - - return CreateProcess(option); - } - - public async Task CreateProcessAsync(string versionName, MLaunchOption option, - bool checkAndDownload=true) - { - var version = await GetVersionAsync(versionName).ConfigureAwait(false); - return await CreateProcessAsync(version, option, checkAndDownload).ConfigureAwait(false); - } - - public async Task CreateProcessAsync(MVersion version, MLaunchOption option, - bool checkAndDownload=true) - { - option.StartVersion = version; - - if (checkAndDownload) - await CheckAndDownloadAsync(option.StartVersion).ConfigureAwait(false); - - return await CreateProcessAsync(option).ConfigureAwait(false); - } - - public Process CreateProcess(MLaunchOption option) - { - checkLaunchOption(option); - var launch = new MLaunch(option); - return launch.GetProcess(); - } - - public async Task CreateProcessAsync(MLaunchOption option) - { - checkLaunchOption(option); - var launch = new MLaunch(option); - return await Task.Run(launch.GetProcess).ConfigureAwait(false); - } - - public async Task LaunchAsync(string versionName, MLaunchOption option) - { - Process process = await CreateProcessAsync(versionName, option) - .ConfigureAwait(false); - process.Start(); - return process; - } - private void checkLaunchOption(MLaunchOption option) { if (option.Path == null) option.Path = MinecraftPath; - if (option.StartVersion != null) - { - if (!string.IsNullOrEmpty(option.JavaPath)) - option.StartVersion.JavaBinaryPath = option.JavaPath; - else if (!string.IsNullOrEmpty(option.JavaVersion)) - option.StartVersion.JavaVersion = option.JavaVersion; - else if (string.IsNullOrEmpty(option.StartVersion.JavaBinaryPath)) - option.StartVersion.JavaBinaryPath = - GetJavaPath(option.StartVersion) ?? GetDefaultJavaPath(); - } } - public string? GetJavaPath(MVersion version) + public string? GetJavaPath(IVersion version) { - if (string.IsNullOrEmpty(version.JavaVersion)) + if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion?.Component)) return null; - return JavaPathResolver.GetJavaBinaryPath(version.JavaVersion, MRule.OSName); + return JavaPathResolver.GetJavaBinaryPath(version.JavaVersion.Value, os); } - public string? GetDefaultJavaPath() => JavaPathResolver.GetDefaultJavaBinaryPath(); + public string? GetDefaultJavaPath() => JavaPathResolver.GetDefaultJavaBinaryPath(os); } diff --git a/src/Core/Executors/TPLTaskExecutor.cs b/src/Core/Executors/TPLTaskExecutor.cs new file mode 100644 index 0000000..fc53508 --- /dev/null +++ b/src/Core/Executors/TPLTaskExecutor.cs @@ -0,0 +1,74 @@ +using System.Threading.Tasks.Dataflow; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Executors; + +public class TPLTaskExecutor +{ + private readonly DataflowLinkOptions _linkOptions = new () + { + PropagateCompletion = true + }; + + public async ValueTask Install( + IEnumerable extractors, + MinecraftPath path, + IVersion version) + { + var executor = createTaskExecutorBlock(); + var installer = completeBlock(executor, path, extractors); + + await installer.SendAsync(version); + installer.Complete(); + await installer.Completion; + } + + private BufferBlock createTaskExecutorBlock() + { + var buffer = new BufferBlock(); + var executor = new TransformBlock(async task => + { + Console.WriteLine(task.GetType().Name); + if (task is FileCheckTask fct) + { + Console.WriteLine(fct.Path); + Console.WriteLine(fct.Hash); + } + else if (task is DownloadTask dt) + { + Console.WriteLine(dt.Path); + Console.WriteLine(dt.Url); + } + return await task.Execute(); + }, new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = 8 + }); + + buffer.LinkTo(executor, _linkOptions); + executor.LinkTo(buffer!, _linkOptions, next => next != null); + + return buffer; + } + + private ITargetBlock completeBlock( + ITargetBlock executor, + MinecraftPath path, + IEnumerable extractors) + { + var broadcaster = new BroadcastBlock(null); + foreach (var extractor in extractors) + { + var block = new TransformManyBlock(async v => + { + return await extractor.Extract(path, v); + }); + broadcaster.LinkTo(block, _linkOptions); + block.LinkTo(executor, _linkOptions); + } + + return broadcaster; + } +} \ No newline at end of file diff --git a/src/Core/FileChecker/AssetChecker.cs b/src/Core/FileChecker/AssetChecker.cs deleted file mode 100644 index 16bfe7b..0000000 --- a/src/Core/FileChecker/AssetChecker.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using CmlLib.Core.Downloader; -using CmlLib.Core.Files; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.FileChecker; - -public sealed class AssetChecker : IFileChecker -{ - private readonly HttpClient httpClient; - - public AssetChecker(HttpClient client) - { - this.httpClient = client; - } - - private string assetServer = MojangServer.ResourceDownload; - public string AssetServer - { - get => assetServer; - set - { - if (value.Last() == '/') - assetServer = value; - else - assetServer = value + "/"; - } - } - public bool CheckHash { get; set; } = true; - - public DownloadFile[]? CheckFiles(MinecraftPath path, IVersion version, - IProgress? progress) - { - if (version.Assets == null) - return null; - checkIndex(path, version.Assets).GetAwaiter().GetResult(); - return CheckAssetFiles(path, version.Assets, progress); - } - - public async Task CheckFilesTaskAsync(MinecraftPath path, IVersion version, - IProgress? progress) - { - if (version.Assets == null) - return null; - - await checkIndex(path, version.Assets).ConfigureAwait(false); - return await Task.Run(() => CheckAssetFiles(path, version.Assets, progress)).ConfigureAwait(false); - } - - // Check index file validation and download - private async Task checkIndex(MinecraftPath path, MFileMetadata assets) - { - if (string.IsNullOrEmpty(assets.Id) || string.IsNullOrEmpty(assets.Url)) - return; - - var indexFilePath = path.GetIndexFilePath(assets.Id); - - if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, CheckHash)) - return; - - IOUtil.CreateParentDirectory(indexFilePath); - - var downloader = new HttpClientDownloadHelper(httpClient); - await downloader.DownloadFileAsync(new DownloadFile(indexFilePath, assets.Url)).ConfigureAwait(false); - } - - public DownloadFile[]? CheckAssetFiles(MinecraftPath path, MFileMetadata assets, - IProgress? progress) - { - if (string.IsNullOrEmpty(assets.Id)) - return null; - - var indexFilePath = path.GetIndexFilePath(assets.Id); - if (!File.Exists(indexFilePath)) - return null; - - var indexFileContent = File.ReadAllText(indexFilePath); - using var index = JsonDocument.Parse(indexFileContent); - var root = index.RootElement; - - var listProperty = root.GetPropertyOrNull("objects"); - if (!listProperty.HasValue) - return null; - var list = listProperty.Value; - - var isVirtual = root.GetPropertyOrNull("virtual")?.GetBoolean() ?? false; - var mapResource = root.GetPropertyOrNull("map_to_resources")?.GetBoolean() ?? false; - - var objects = list.EnumerateObject(); - var totalObject = objects.Count(); - objects.Reset(); - var downloadRequiredFiles = new List(totalObject); - - int progressed = 0; - foreach (var prop in objects) - { - var f = checkAssetFile(prop.Name, prop.Value, path, assets, isVirtual, mapResource); - - if (f != null) - downloadRequiredFiles.Add(f); - - progressed++; - - if (progressed % 50 == 0) // prevent ui freezing - progress?.Report( - new DownloadFileChangedEventArgs(MFile.Resource, this, "", totalObject, progressed)); - } - - return downloadRequiredFiles.Distinct().ToArray(); // 10ms - } - - private DownloadFile? checkAssetFile(string key, JsonElement element, MinecraftPath path, MFileMetadata assets, - bool isVirtual, bool mapResource) - { - if (string.IsNullOrEmpty(assets.Id)) - return null; - - var hash = element.GetPropertyValue("hash"); - if (hash == null) - return null; - - var hashName = hash.Substring(0, 2) + "/" + hash; - var hashPath = Path.Combine(path.GetAssetObjectPath(assets.Id), hashName); - - long size = element.GetPropertyOrNull("size")?.GetInt64() ?? 0; - - var afterDownload = new List>(1); - - if (isVirtual) - { - var resPath = Path.Combine(path.GetAssetLegacyPath(assets.Id), key); - afterDownload.Add(() => assetCopy(hashPath, resPath)); - } - - if (mapResource) - { - var desPath = Path.Combine(path.Resource, key); - afterDownload.Add(() => assetCopy(hashPath, desPath)); - } - - if (!IOUtil.CheckFileValidation(hashPath, hash, CheckHash)) - { - string hashUrl = AssetServer + hashName; - return new DownloadFile(hashPath, hashUrl) - { - Type = MFile.Resource, - Name = key, - Size = size, - AfterDownload = afterDownload.ToArray() - }; - } - else - { - foreach (var item in afterDownload) - { - item().GetAwaiter().GetResult(); - } - - return null; - } - } - - private async Task assetCopy(string org, string des) - { - try - { - var orgFile = new FileInfo(org); - var desFile = new FileInfo(des); - - if (!desFile.Exists || orgFile.Length != desFile.Length) - { - IOUtil.CreateParentDirectory(des); - await IOUtil.CopyFileAsync(org, des); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - } -} diff --git a/src/Core/FileChecker/ClientChecker.cs b/src/Core/FileChecker/ClientChecker.cs deleted file mode 100644 index f344d48..0000000 --- a/src/Core/FileChecker/ClientChecker.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Threading.Tasks; -using CmlLib.Core.Downloader; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.FileChecker -{ - public sealed class ClientChecker : IFileChecker - { - public bool CheckHash { get; set; } = true; - - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? progress) - { - progress?.Report(new DownloadFileChangedEventArgs(MFile.Minecraft, this, version.Jar, 1, 0)); - DownloadFile? result = checkClientFile(path, version); - progress?.Report(new DownloadFileChangedEventArgs(MFile.Minecraft, this, version.Jar, 1, 1)); - - if (result == null) - return null; - else - return new [] { result }; - } - - public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? progress) - { - progress?.Report(new DownloadFileChangedEventArgs(MFile.Minecraft, this, version.Jar, 1, 0)); - DownloadFile? result = await Task.Run(() => checkClientFile(path, version)) - .ConfigureAwait(false); - progress?.Report(new DownloadFileChangedEventArgs(MFile.Minecraft, this, version.Jar, 1, 1)); - - if (result == null) - return null; - else - return new [] { result }; - } - - private DownloadFile? checkClientFile(MinecraftPath path, MVersion version) - { - var id = version.Jar; - var url = version.Client?.Url; - - if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(id)) - return null; - - var clientPath = path.GetVersionJarPath(id); - - if (!IOUtil.CheckFileValidation(clientPath, version.Client?.Sha1, CheckHash)) - { - return new DownloadFile(clientPath, url) - { - Type = MFile.Minecraft, - Name = id - }; - } - - return null; - } - } -} diff --git a/src/Core/FileChecker/FileCheckerCollection.cs b/src/Core/FileChecker/FileCheckerCollection.cs deleted file mode 100644 index ff69d1e..0000000 --- a/src/Core/FileChecker/FileCheckerCollection.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections; - -namespace CmlLib.Core.FileChecker; - -public class FileCheckerCollection : IEnumerable -{ - public static FileCheckerCollection CreateDefault(HttpClient httpClient) - { - var checkers = new FileCheckerCollection(); - - var library = new LibraryChecker(); - var asset = new AssetChecker(httpClient); - var client = new ClientChecker(); - var java = new JavaChecker(httpClient); - var log = new LogChecker(); - - checkers.Add(library); - checkers.Add(asset); - checkers.Add(client); - checkers.Add(log); - checkers.Add(java); - - return checkers; - } - - public IFileChecker this[int index] => checkers[index]; - - private readonly List checkers; - - public FileCheckerCollection() - { - checkers = new List(4); - } - - public void Add(IFileChecker item) - { - checkers.Add(item); - } - - public void AddRange(IEnumerable items) - { - foreach (IFileChecker? item in items) - { - if (item != null) - Add(item); - } - } - - public void Remove(IFileChecker item) - { - checkers.Remove(item); - } - - public void RemoveAt(int index) - { - IFileChecker item = checkers[index]; - Remove(item); - } - - public void Insert(int index, IFileChecker item) - { - checkers.Insert(index, item); - } - - public IEnumerator GetEnumerator() - { - return checkers.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return checkers.GetEnumerator(); - } -} diff --git a/src/Core/FileChecker/IFileChecker.cs b/src/Core/FileChecker/IFileChecker.cs deleted file mode 100644 index cd80241..0000000 --- a/src/Core/FileChecker/IFileChecker.cs +++ /dev/null @@ -1,13 +0,0 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.Version; - -namespace CmlLib.Core.FileChecker -{ - public interface IFileChecker - { - DownloadFile[]? CheckFiles(MinecraftPath path, IVersion version, - IProgress? downloadProgress); - Task CheckFilesTaskAsync(MinecraftPath path, IVersion version, - IProgress? downloadProgress); - } -} diff --git a/src/Core/FileChecker/JavaChecker.cs b/src/Core/FileChecker/JavaChecker.cs deleted file mode 100644 index f7ae2dc..0000000 --- a/src/Core/FileChecker/JavaChecker.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using CmlLib.Core.Downloader; -using CmlLib.Core.Installer; -using CmlLib.Core.Java; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.FileChecker; - -public class JavaChecker : IFileChecker -{ - class JavaCheckResult - { - public string? JavaBinaryPath { get; set; } - public DownloadFile[]? JavaFiles { get; set; } - } - - private readonly HttpClient _httpClient; - - public IJavaPathResolver? JavaPathResolver { get; set; } - public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; - public bool CheckHash { get; set; } = true; - - public JavaChecker(HttpClient httpClient) - { - _httpClient = httpClient; - } - - public JavaChecker(HttpClient httpClient, IJavaPathResolver javaPathResolver) - { - _httpClient = httpClient; - JavaPathResolver = javaPathResolver; - } - - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? downloadProgress) - { - return CheckFilesTaskAsync(path, version, downloadProgress) - .ConfigureAwait(false) - .GetAwaiter().GetResult(); - } - - public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? downloadProgress) - { - if (!string.IsNullOrEmpty(version.JavaBinaryPath) && File.Exists(version.JavaBinaryPath)) - return null; - - var javaVersion = version.JavaVersion; - if (string.IsNullOrEmpty(javaVersion)) - javaVersion = MinecraftJavaPathResolver.JreLegacyVersionName; - - var result = await internalCheckJava(javaVersion, downloadProgress) - .ConfigureAwait(false); - - version.JavaBinaryPath = result.JavaBinaryPath; - return result.JavaFiles; - } - - private async Task internalCheckJava(string javaVersion, - IProgress? downloadProgress) - { - if (JavaPathResolver == null) - throw new InvalidOperationException("JavaPathResolver was null"); - - var binPath = JavaPathResolver.GetJavaBinaryPath(javaVersion, MRule.OSName); - DownloadFile[]? downloadFiles; - - try - { - var osName = getJavaOSName(); // safe - - var response = await _httpClient.GetAsync(JavaManifestServer); // get all java versions - var str = await response.Content.ReadAsStringAsync(); - using var jsonDocument = JsonDocument.Parse(str); - - var root = jsonDocument.RootElement; - var javaVersions = root.GetPropertyOrNull(osName); // get os specific java version - - // try three versions: latest, JreLegacyVersionName(jre-legacy), legacy java (MJava) - if (javaVersions != null) - { - var javaManifest = await getJavaVersionManifest(javaVersions.Value, javaVersion); - - if (javaManifest == null && javaVersion != MinecraftJavaPathResolver.JreLegacyVersionName) - javaManifest = await getJavaVersionManifest(javaVersions.Value, MinecraftJavaPathResolver.JreLegacyVersionName); - if (javaManifest == null) - return await legacyJavaChecker(); - - // get file objects - using var manifestDocument = JsonDocument.Parse(javaManifest); - var files = manifestDocument.RootElement.GetPropertyOrNull("files"); - if (files == null) - return await legacyJavaChecker(); - - downloadFiles = toDownloadFiles(files.Value, JavaPathResolver.GetJavaDirPath(javaVersion), downloadProgress); - } - else // no java version for osName - return await legacyJavaChecker(); - } - catch (Exception e) - { - Debug.WriteLine(e); - - if (string.IsNullOrEmpty(binPath)) - return await legacyJavaChecker(); - else - downloadFiles = new DownloadFile[] { }; - } - - return new JavaCheckResult() - { - JavaFiles = downloadFiles, - JavaBinaryPath = binPath - }; - } - - private string getJavaOSName() - { - string osName = ""; - - if (MRule.OSName == MRule.Windows) - { - if (MRule.Arch == "64") - osName = "windows-x64"; - else - osName = "windows-x86"; - } - else if (MRule.OSName == MRule.Linux) - { - if (MRule.Arch == "64") - osName = "linux"; - else - osName = "linux-i386"; - } - else if (MRule.OSName == MRule.OSX) - { - osName = "mac-os"; - } - - return osName; - } - - // get java files of the specific version - private async Task getJavaVersionManifest(JsonElement job, string version) - { - var versionArr = job.GetPropertyOrNull(version)?.EnumerateArray(); - if (versionArr == null) - return null; - - var firstManifest = versionArr.Value.FirstOrDefault(); - var manifestUrl = firstManifest.GetPropertyOrNull("manifest")?.GetPropertyOrNull("url")?.GetString(); - if (string.IsNullOrEmpty(manifestUrl)) - return null; - - return await _httpClient.GetStringAsync(manifestUrl); - } - - // compare local files with `manifest` - private DownloadFile[] toDownloadFiles(JsonElement manifest, string path, - IProgress? progress) - { - var objects = manifest.EnumerateObject(); - var total = objects.Count(); - objects.Reset(); - - var progressed = 0; - var files = new List(total); - foreach (var prop in objects) - { - var name = prop.Name; - var value = prop.Value; - - var type = value.GetPropertyValue("type"); - if (type == "file") - { - var filePath = Path.Combine(path, name); - filePath = IOUtil.NormalizePath(filePath); - - var executable = value.GetPropertyOrNull("executable")?.GetBoolean() ?? false; - - var file = checkJavaFile(value, filePath); - if (file != null) - { - file.Name = name; - if (executable) - file.AfterDownload = new Func[] - { - () => Task.Run(() => tryChmod755(filePath)) - }; - files.Add(file); - } - } - else - { - if (type != "directory") - Debug.WriteLine(type); - } - - progressed++; - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Runtime, this, name, total: total, progressed: progressed)); - } - return files.ToArray(); - } - - private DownloadFile? checkJavaFile(JsonElement value, string filePath) - { - var downloadObj = value.GetPropertyOrNull("downloads")?.GetPropertyOrNull("raw"); - if (downloadObj == null) - return null; - - var url = downloadObj.Value.GetPropertyValue("url"); - if (string.IsNullOrEmpty(url)) - return null; - - var hash = downloadObj.Value.GetPropertyValue("sha1"); - var size = downloadObj.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; - - if (IOUtil.CheckFileValidation(filePath, hash, CheckHash)) - return null; - - return new DownloadFile(filePath, url) - { - Size = size - }; - } - - // legacy java checker that use MJava - private async Task legacyJavaChecker() - { - if (JavaPathResolver == null) - throw new InvalidOperationException("JavaPathResolver was null"); - - string legacyJavaPath = JavaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersionName); - - MJava mJava = new MJava(_httpClient, legacyJavaPath); - var result = new JavaCheckResult() - { - JavaBinaryPath = mJava.GetBinaryPath(), - JavaFiles = null - }; - - try - { - if (mJava.CheckJavaExistence()) - return result; - - string javaUrl = await mJava.GetJavaUrlAsync(); - string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - string zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); - - DownloadFile file = new DownloadFile(lzmaPath, javaUrl) - { - Name = "jre.lzma", - Type = MFile.Runtime, - AfterDownload = new Func[] - { - () => Task.Run(() => - { - SevenZipWrapper.DecompressFileLZMA(lzmaPath, zipPath); - - var z = new SharpZip(zipPath); - z.Unzip(legacyJavaPath); - - tryChmod755(mJava.GetBinaryPath()); - }) - } - }; - result.JavaFiles = new[] { file }; - - return result; - } - catch (Exception e) - { - Debug.WriteLine(e); - return result; - } - } - - private void tryChmod755(string path) - { - try - { - if (MRule.OSName != MRule.Windows) - NativeMethods.Chmod(path, NativeMethods.Chmod755); - } - catch (Exception e) - { - Debug.WriteLine(e); - } - } -} \ No newline at end of file diff --git a/src/Core/FileChecker/LibraryChecker.cs b/src/Core/FileChecker/LibraryChecker.cs deleted file mode 100644 index aac2112..0000000 --- a/src/Core/FileChecker/LibraryChecker.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using CmlLib.Core.Downloader; -using CmlLib.Core.Files; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.FileChecker -{ - public sealed class LibraryChecker : IFileChecker - { - private string libServer = MojangServer.Library; - public string LibraryServer - { - get => libServer; - set - { - if (value.Last() == '/') - libServer = value; - else - libServer = value + "/"; - } - } - public bool CheckHash { get; set; } = true; - - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? progress) - { - if (version == null) - throw new ArgumentNullException(nameof(version)); - return CheckFiles(path, version.Libraries, progress); - } - - public Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? progress) - { - if (version == null) - throw new ArgumentNullException(nameof(version)); - - return Task.Run(() => checkLibraries(path, version.Libraries, progress)); - } - - public DownloadFile[]? CheckFiles(MinecraftPath path, MLibrary[]? libs, - IProgress? progress) - { - return checkLibraries(path, libs, progress); - } - - private DownloadFile[]? checkLibraries(MinecraftPath path, MLibrary[]? libs, - IProgress? progress) - { - if (libs == null) - throw new ArgumentNullException(nameof(libs)); - - if (libs.Length == 0) - return null; - - int progressed = 0; - var files = new List(libs.Length); - foreach (MLibrary library in libs) - { - bool downloadRequire = checkDownloadRequire(path, library); - - if (downloadRequire) - { - string libPath = Path.Combine(path.Library, library.Path!); // cannot be null - string? libUrl = createDownloadUrl(library); - - if (!string.IsNullOrEmpty(libUrl)) - { - files.Add(new DownloadFile(libPath, libUrl) - { - Type = MFile.Library, - Name = library.Name, - Size = library.Size - }); - } - } - - progressed++; - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Library, this, library.Name, libs.Length, progressed)); - } - return files.Distinct().ToArray(); - } - - private string? createDownloadUrl(MLibrary lib) - { - string? url = lib.Url; - - if (url == null) - url = LibraryServer + lib.Path; - else if (url == "") - url = null; - else if (url.Split('/').Last() == "") - url += lib.Path?.Replace("\\", "/"); - - return url; - } - - private bool checkDownloadRequire(MinecraftPath path, MLibrary lib) - { - return lib.IsRequire - && !string.IsNullOrEmpty(lib.Path) - && !IOUtil.CheckFileValidation(Path.Combine(path.Library, lib.Path), lib.Hash, CheckHash); - } - } -} diff --git a/src/Core/FileChecker/LogChecker.cs b/src/Core/FileChecker/LogChecker.cs deleted file mode 100644 index 98dd831..0000000 --- a/src/Core/FileChecker/LogChecker.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Threading.Tasks; -using CmlLib.Core.Downloader; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.FileChecker -{ - public sealed class LogChecker : IFileChecker - { - public bool CheckHash { get; set; } = true; - - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? progress) - { - return internalCheckLogFile(path, version, progress, async: false).GetAwaiter().GetResult(); - } - - public Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? progress) - { - return internalCheckLogFile(path, version, progress, async: true); - } - - private async Task internalCheckLogFile(MinecraftPath path, MVersion version, - IProgress? progress, bool async) - { - if (version.LoggingClient == null) - return null; - - DownloadFile? result; - - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Others, this, version.LoggingClient?.LogFile?.Id, 1, 0)); - if (async) - { - result = await Task.Run(() => internalCheckLogFile(path, version)) - .ConfigureAwait(false); - } - else - { - result = internalCheckLogFile(path, version); - } - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Others, this, version.LoggingClient?.LogFile?.Id, 1, 1)); - - if (result == null) - return null; - else - return new[] { result }; - } - - private DownloadFile? internalCheckLogFile(MinecraftPath path, MVersion version) - { - if (version.LoggingClient == null) - return null; - - var url = version.LoggingClient?.LogFile?.Url; - if (string.IsNullOrEmpty(url)) - return null; - - var id = version.LoggingClient?.LogFile?.Id ?? version.Id; - var clientPath = path.GetLogConfigFilePath(id); - - if (!IOUtil.CheckFileValidation(clientPath, version.LoggingClient?.LogFile?.Sha1, CheckHash)) - { - return new DownloadFile(clientPath, url) - { - Type = MFile.Others, - Name = id - }; - } - - return null; - } - } -} diff --git a/src/Core/FileChecker/ModChecker.cs b/src/Core/FileChecker/ModChecker.cs deleted file mode 100644 index 091d7ab..0000000 --- a/src/Core/FileChecker/ModChecker.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using CmlLib.Core.Downloader; -using CmlLib.Core.Files; -using CmlLib.Core.Version; -using CmlLib.Utils; - -namespace CmlLib.Core.FileChecker -{ - public class ModChecker : IFileChecker - { - public bool CheckHash { get; set; } = true; - public ModFile[]? Mods { get; set; } - - public DownloadFile[]? CheckFiles(MinecraftPath path, MVersion version, - IProgress? progress) - { - if (version == null) - throw new ArgumentNullException(nameof(version)); - - return CheckFilesTaskAsync(path, version, progress) - .GetAwaiter().GetResult(); - } - - public async Task CheckFilesTaskAsync(MinecraftPath path, MVersion version, - IProgress? progress) - { - if (version == null) - throw new ArgumentNullException(nameof(version)); - if (Mods == null) - throw new NullReferenceException(nameof(Mods)); - - string? lastModName = ""; - int progressed = 0; - var files = new List(Mods.Length); - foreach (ModFile mod in Mods) - { - if (await checkDownloadRequireAsync(path, mod).ConfigureAwait(false)) - { - string modPath = Path.Combine(path.BasePath, mod.Path); - files.Add(new DownloadFile(modPath, mod.Url) - { - Type = MFile.Others, - Name = mod.Name - }); - lastModName = mod.Name; - } - - progressed++; - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Others, this, mod.Name, Mods.Length, progressed)); - } - - progress?.Report(new DownloadFileChangedEventArgs( - MFile.Others, this, lastModName, Mods.Length, Mods.Length)); - - return files.Distinct().ToArray(); - } - - private async Task checkDownloadRequireAsync(MinecraftPath path, ModFile mod) - { - return !string.IsNullOrEmpty(mod.Url) - && !string.IsNullOrEmpty(mod.Path) - && !await IOUtil.CheckFileValidationAsync(Path.Combine(path.BasePath, mod.Path), mod.Hash, CheckHash).ConfigureAwait(false); - } - } -} diff --git a/src/Core/FileExtractors/AssetFileExtractor.cs b/src/Core/FileExtractors/AssetFileExtractor.cs new file mode 100644 index 0000000..6d6f8aa --- /dev/null +++ b/src/Core/FileExtractors/AssetFileExtractor.cs @@ -0,0 +1,147 @@ +using System.Diagnostics; +using System.Text.Json; +using CmlLib.Core.Files; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; +using CmlLib.Utils; + +namespace CmlLib.Core.FileExtractors; + +public class AssetFileExtractor : IFileExtractor +{ + private readonly HttpClient httpClient; + + public AssetFileExtractor(HttpClient client) + { + this.httpClient = client; + } + + private string assetServer = MojangServer.ResourceDownload; + public string AssetServer + { + get => assetServer; + set + { + if (value.Last() == '/') + assetServer = value; + else + assetServer = value + "/"; + } + } + + public async ValueTask> Extract(MinecraftPath path, IVersion version) + { + if (version.AssetIndex == null || string.IsNullOrEmpty(version.AssetIndex.Id)) + return Enumerable.Empty(); + + var assetIndex = await getAssetIndex(path, version.AssetIndex); + if (assetIndex == null) + return Enumerable.Empty(); + + return extractFromIndex(path, version, assetIndex); + } + + private IEnumerable extractFromIndex( + MinecraftPath path, IVersion version, Stream stream) + { + using var s = stream; + using var doc = JsonDocument.Parse(s); + var root = doc.RootElement; + + var assetId = version.AssetIndex?.Id ?? "legacy"; + var isVirtual = root.GetPropertyOrNull("virtual")?.GetBoolean() ?? false; + var mapResource = root.GetPropertyOrNull("map_to_resources")?.GetBoolean() ?? false; + + var objectsProp = root.GetPropertyOrNull("objects"); + if (!objectsProp.HasValue) + yield break; + + var objects = objectsProp.Value.EnumerateObject(); + foreach (var prop in objects) + { + var hash = prop.Value.GetPropertyValue("hash"); + if (hash == null) + continue; + + var hashName = hash.Substring(0, 2) + "/" + hash; + var hashPath = Path.Combine(path.GetAssetObjectPath(assetId), hashName); + long size = prop.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; + + var copyPath = new List(2); + if (isVirtual) + copyPath.Add(Path.Combine(path.GetAssetLegacyPath(assetId), prop.Name)); + if (mapResource) + copyPath.Add(Path.Combine(path.Resource, prop.Name)); + + var file = new TaskFile + { + Name = prop.Name, + Path = hashPath, + Hash = hash, + Size = size, + Url = AssetServer + hashName + }; + + var checkTask = new FileCheckTask(file); + checkTask.OnFalse = new DownloadTask(file); + + if (copyPath.Count > 0) + checkTask.InsertNextTask(new FileCopyTask(hashPath, copyPath.ToArray())); + + yield return checkTask; + } + } + + // Check index file validation and download + private async ValueTask getAssetIndex(MinecraftPath path, MFileMetadata? assets) + { + if (assets == null || + string.IsNullOrEmpty(assets.Id) || + string.IsNullOrEmpty(assets.Url)) + return null; + + var indexFilePath = path.GetIndexFilePath(assets.Id); + + if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, true)) + { + return File.Open(indexFilePath, FileMode.Open); + } + else + { + IOUtil.CreateParentDirectory(indexFilePath); + + var ms = new MemoryStream(); + + using (var res = await httpClient.GetStreamAsync(assets.Url)) + { + await res.CopyToAsync(ms); + } // dispose immediately + + using (var fs = File.Create(indexFilePath)) + { + await ms.CopyToAsync(fs); + } // dispose immediately + + return ms; + } + } + + private async Task assetCopy(string org, string des) + { + try + { + var orgFile = new FileInfo(org); + var desFile = new FileInfo(des); + + if (!desFile.Exists || orgFile.Length != desFile.Length) + { + IOUtil.CreateParentDirectory(des); + await IOUtil.CopyFileAsync(org, des); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } +} diff --git a/src/Core/FileExtractors/ClientFileExtractor.cs b/src/Core/FileExtractors/ClientFileExtractor.cs new file mode 100644 index 0000000..efa8bd1 --- /dev/null +++ b/src/Core/FileExtractors/ClientFileExtractor.cs @@ -0,0 +1,36 @@ +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.FileExtractors; + +public class ClientFileExtractor : IFileExtractor +{ + public ValueTask> Extract(MinecraftPath path, IVersion version) + { + var result = extract(path, version); + return new ValueTask>(result); + } + + private IEnumerable extract(MinecraftPath path, IVersion version) + { + var id = version.Jar; + var url = version.Client?.Url; + + if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(id)) + yield break; + + var clientPath = path.GetVersionJarPath(id); + var file = new TaskFile + { + Name = id, + Path = clientPath, + Url = url, + Hash = version.Client?.GetSha1(), + Size = version.Client?.Size ?? 0 + }; + + var checkTask = new FileCheckTask(file); + checkTask.OnFalse = new DownloadTask(file); + yield return checkTask; + } +} diff --git a/src/Core/FileExtractors/FileCheckerCollection.cs b/src/Core/FileExtractors/FileCheckerCollection.cs new file mode 100644 index 0000000..d17100e --- /dev/null +++ b/src/Core/FileExtractors/FileCheckerCollection.cs @@ -0,0 +1,80 @@ +using System.Collections; +using CmlLib.Core.Java; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.FileExtractors; + +public class FileExtractorCollection : IEnumerable +{ + public static FileExtractorCollection CreateDefault( + HttpClient httpClient, + IJavaPathResolver javaPathResolver, + IRulesEvaluator rulesEvaluator, + RulesEvaluatorContext context) + { + var extractors = new FileExtractorCollection(); + + var library = new LibraryFileExtractor(rulesEvaluator, context); + var asset = new AssetFileExtractor(httpClient); + var client = new ClientFileExtractor(); + var java = new JavaFileExtractor(httpClient, javaPathResolver, context.OS); + var log = new LogFileExtractor(); + + extractors.Add(library); + extractors.Add(asset); + extractors.Add(client); + extractors.Add(log); + extractors.Add(java); + + return extractors; + } + + public IFileExtractor this[int index] => checkers[index]; + + private readonly List checkers; + + public FileExtractorCollection() + { + checkers = new List(4); + } + + public void Add(IFileExtractor item) + { + checkers.Add(item); + } + + public void AddRange(IEnumerable items) + { + foreach (IFileExtractor? item in items) + { + if (item != null) + Add(item); + } + } + + public void Remove(IFileExtractor item) + { + checkers.Remove(item); + } + + public void RemoveAt(int index) + { + IFileExtractor item = checkers[index]; + Remove(item); + } + + public void Insert(int index, IFileExtractor item) + { + checkers.Insert(index, item); + } + + public IEnumerator GetEnumerator() + { + return checkers.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return checkers.GetEnumerator(); + } +} diff --git a/src/Core/FileExtractors/IFileExtractor.cs b/src/Core/FileExtractors/IFileExtractor.cs new file mode 100644 index 0000000..a9cccac --- /dev/null +++ b/src/Core/FileExtractors/IFileExtractor.cs @@ -0,0 +1,9 @@ +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.FileExtractors; + +public interface IFileExtractor +{ + ValueTask> Extract(MinecraftPath path, IVersion version); +} \ No newline at end of file diff --git a/src/Core/FileExtractors/JavaFileExtractor.cs b/src/Core/FileExtractors/JavaFileExtractor.cs new file mode 100644 index 0000000..d3f2230 --- /dev/null +++ b/src/Core/FileExtractors/JavaFileExtractor.cs @@ -0,0 +1,214 @@ +using System.Diagnostics; +using System.Text.Json; +using CmlLib.Core.Installer; +using CmlLib.Core.Java; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; +using CmlLib.Utils; + +namespace CmlLib.Core.FileExtractors; + +public class JavaFileExtractor : IFileExtractor +{ + private readonly LauncherOSRule _os; + private readonly HttpClient _httpClient; + public readonly IJavaPathResolver _javaPathResolver; + public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; + + public JavaFileExtractor( + HttpClient httpClient, + IJavaPathResolver javaPathResolver, + LauncherOSRule rule) + { + _httpClient = httpClient; + _javaPathResolver = javaPathResolver; + _os = rule; + } + + public async ValueTask> Extract(MinecraftPath path, IVersion version) + { + //if (!string.IsNullOrEmpty(version.JavaBinaryPath) && File.Exists(version.JavaBinaryPath)) + // return null + JavaVersion javaVersion; + if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion.Value.Component)) + javaVersion = MinecraftJavaPathResolver.JreLegacyVersion; + else + javaVersion = version.JavaVersion.Value; + + // try three versions + // - latest + // - JreLegacyVersionName(jre-legacy) + // - legacy java (MJava) + try + { + // get all java version + using var response = await _httpClient.GetStreamAsync(JavaManifestServer); + using var jsonDocument = await JsonDocument.ParseAsync(response); + + var root = jsonDocument.RootElement; + var javaVersions = root.GetProperty(getJavaOSName()); // get os specific java version + + var latestVersionUrl = getJavaUrl(javaVersions, javaVersion); + var legacyVersionUrl = getJavaUrl(javaVersions, MinecraftJavaPathResolver.JreLegacyVersion); + + if (!string.IsNullOrEmpty(latestVersionUrl)) + { + var res = await _httpClient.GetStreamAsync(latestVersionUrl); + return extractTasks(res, javaVersion); + } + else if (!string.IsNullOrEmpty(legacyVersionUrl) && + javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) + { + var res = await _httpClient.GetStreamAsync(legacyVersionUrl); + return extractTasks(res, MinecraftJavaPathResolver.JreLegacyVersion); + } + else + { + throw new Exception(); + } + } + catch + { + return await legacyJavaChecker(); + } + } + + private string getJavaOSName() + { + string osName = ""; + + if (_os.Name == MRule.Windows) + { + if (_os.Arch == "64") + osName = "windows-x64"; + else + osName = "windows-x86"; + } + else if (_os.Name == MRule.Linux) + { + if (_os.Arch == "64") + osName = "linux"; + else + osName = "linux-i386"; + } + else if (_os.Name == MRule.OSX) + { + osName = "mac-os"; + } + + return osName; + } + + private string? getJavaUrl(JsonElement element, JavaVersion javaVersion) + { + return element + .GetPropertyOrNull(javaVersion.Component)? + .EnumerateArray() + .FirstOrDefault() + .GetPropertyOrNull("manifest")? + .GetPropertyOrNull("url")? + .GetString(); + } + + // compare local files with `manifest` + private IEnumerable extractTasks(Stream stream, JavaVersion version) + { + var path = _javaPathResolver.GetJavaDirPath(version); + using var s = stream; + using var manifestDocument = JsonDocument.Parse(stream); + var manifest = manifestDocument.RootElement; + + var files = manifestDocument.RootElement.GetPropertyOrNull("files"); + if (files == null) + yield break; + + var objects = files.Value.EnumerateObject(); + foreach (var prop in objects) + { + var name = prop.Name; + var value = prop.Value; + + var type = value.GetPropertyValue("type"); + if (type == "file") + { + var filePath = Path.Combine(path, name); + filePath = IOUtil.NormalizePath(filePath); + + var file = createTask(value, name, filePath); + if (file != null) + yield return file; // TODO: tryChmod + } + else + { + if (type != "directory") + Debug.WriteLine(type); + } + + } + } + + private LinkedTask? createTask(JsonElement value, string name, string filePath) + { + var downloadObj = value.GetPropertyOrNull("downloads")?.GetPropertyOrNull("raw"); + if (downloadObj == null) + return null; + + var url = downloadObj.Value.GetPropertyValue("url"); + if (string.IsNullOrEmpty(url)) + return null; + + var hash = downloadObj.Value.GetPropertyValue("sha1"); + var size = downloadObj.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; + + var executable = value.GetPropertyOrNull("executable")?.GetBoolean() ?? false; + + var file = new TaskFile + { + Name = name, + Hash = hash, + Path = filePath, + Url = url, + Size = size + }; + + var checkTask = new FileCheckTask(file); + checkTask.OnFalse = new DownloadTask(file); + + if (executable) + checkTask.InsertNextTask(new ChmodTask(file.Path)); + + return checkTask; + } + + // legacy java checker that use MJava + private async ValueTask> legacyJavaChecker() + { + var legacyJavaPath = _javaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersion); + + var mJava = new MJava(_httpClient, legacyJavaPath); + if (mJava.CheckJavaExistence(_os)) + return Enumerable.Empty(); + + var javaUrl = await mJava.GetJavaUrlAsync(); + var lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); + var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); + + var file = new TaskFile + { + Name = "jre.lzma", + Url = javaUrl, + Path = lzmaPath + }; + + var download = new DownloadTask(file); + var decompressLZMA = new LZMADecompressTask(lzmaPath, zipPath); + var unzip = new UnzipTask(zipPath, legacyJavaPath); + var chmod = new ChmodTask(mJava.GetBinaryPath(_os)); + + return new LinkedTask[] + { + LinkedTask.LinkTasks(download, decompressLZMA, unzip, chmod)! + }; + } +} \ No newline at end of file diff --git a/src/Core/FileExtractors/LibraryFileExtractor.cs b/src/Core/FileExtractors/LibraryFileExtractor.cs new file mode 100644 index 0000000..31b857a --- /dev/null +++ b/src/Core/FileExtractors/LibraryFileExtractor.cs @@ -0,0 +1,99 @@ +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.FileExtractors; + +public class LibraryFileExtractor : IFileExtractor +{ + private readonly RulesEvaluatorContext _rulesContext; + private readonly IRulesEvaluator _rulesEvaluator; + + public LibraryFileExtractor(IRulesEvaluator rulesEvaluator, RulesEvaluatorContext context) + { + this._rulesEvaluator = rulesEvaluator; + this._rulesContext = context; + } + + private string libServer = MojangServer.Library; + public string LibraryServer + { + get => libServer; + set + { + if (value.Last() == '/') + libServer = value; + else + libServer = value + "/"; + } + } + + public ValueTask> Extract(MinecraftPath path, IVersion version) + { + var result = extract(path, version); + return new ValueTask>(result); + } + + private IEnumerable extract(MinecraftPath path, IVersion version) + { + return version.Libraries + .Where(lib => lib.CheckIsRequired("SIDE")) + .Where(lib => lib.Rules == null || _rulesEvaluator.Match(lib.Rules, _rulesContext)) + .SelectMany(lib => createLibraryTasks(path, lib)); + } + + private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary library) + { + var artifact = library.Artifact; + if (artifact != null) + { + var libPath = library.GetLibraryPath(); + var file = new TaskFile + { + Name = library.Name, + Path = Path.Combine(path.Library, libPath), + Url = createDownloadUrl(artifact.Url, libPath), + Hash = artifact.GetSha1() + }; + + var task = new FileCheckTask(file); + task.OnFalse = new DownloadTask(file); + yield return task; + } + + var native = library.GetNativeLibrary(_rulesContext.OS); + if (native != null) + { + var libPath = library.GetNativeLibraryPath(_rulesContext.OS); + if (!string.IsNullOrEmpty(libPath)) + { + var file = new TaskFile + { + Name = library.Name, + Path = Path.Combine(path.Library, libPath), + Url = createDownloadUrl(native.Url, libPath), + Hash = native.GetSha1() + }; + + var task = new FileCheckTask(file); + task.OnFalse = new DownloadTask(file); + yield return task; + } + } + } + + private string? createDownloadUrl(string? url, string path) + { + if (string.IsNullOrEmpty(url) && string.IsNullOrEmpty(path)) + return null; + + if (url == null) + url = LibraryServer + path; + else if (url == "") + url = null; + else if (url.Split('/').Last() == "") + url += path.Replace("\\", "/"); + + return url; + } +} diff --git a/src/Core/FileExtractors/LogFileExtractor.cs b/src/Core/FileExtractors/LogFileExtractor.cs new file mode 100644 index 0000000..e56ce2d --- /dev/null +++ b/src/Core/FileExtractors/LogFileExtractor.cs @@ -0,0 +1,36 @@ +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.FileExtractors; + +public class LogFileExtractor : IFileExtractor +{ + public ValueTask> Extract(MinecraftPath path, IVersion version) + { + var result = extract(path, version); + return new ValueTask>(result); + } + + private IEnumerable extract(MinecraftPath path, IVersion version) + { + if (version.Logging == null) + yield break; + + var url = version.Logging?.LogFile?.Url; + if (string.IsNullOrEmpty(url)) + yield break; + + var id = version.Logging?.LogFile?.Id ?? version.Id; + + var file = new TaskFile + { + Name = id, + Path = path.GetLogConfigFilePath(id), + Url = url, + Hash = version.Logging?.LogFile?.GetSha1() + }; + var task = new FileCheckTask(file); + task.OnFalse = new DownloadTask(file); + yield return task; + } +} diff --git a/src/Core/Files/MFileMetadata.cs b/src/Core/Files/MFileMetadata.cs index 8cd66ba..31ebfcd 100644 --- a/src/Core/Files/MFileMetadata.cs +++ b/src/Core/Files/MFileMetadata.cs @@ -24,4 +24,12 @@ public class MFileMetadata [JsonPropertyName("url")] public string? Url { get; set; } + + public string? GetSha1() + { + var result = Sha1; + if (string.IsNullOrEmpty(result)) + result = Checksums?.FirstOrDefault(); + return result; + } } diff --git a/src/Core/Installer/FabricMC/FabricVersionLoader.cs b/src/Core/Installer/FabricMC/FabricVersionLoader.cs index f898768..1eae7a8 100644 --- a/src/Core/Installer/FabricMC/FabricVersionLoader.cs +++ b/src/Core/Installer/FabricMC/FabricVersionLoader.cs @@ -1,5 +1,4 @@ -using CmlLib.Core.Version; -using System.Text.Json; +using System.Text.Json; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; @@ -23,7 +22,7 @@ protected string GetVersionName(string version, string loaderVersion) return $"fabric-loader-{loaderVersion}-{version}"; } - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { if (string.IsNullOrEmpty(LoaderVersion)) { @@ -40,7 +39,7 @@ public async ValueTask GetVersionMetadatasAsync() .ConfigureAwait(false); var versions = parseVersions(res, LoaderVersion!); - return new MVersionCollection(versions, null, null); + return new VersionCollection(versions, null, null); } private IEnumerable parseVersions(string res, string loader) diff --git a/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs b/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs index 6db66aa..e9fe038 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs +++ b/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs @@ -16,7 +16,7 @@ public LiteLoaderInstaller(MinecraftPath path, HttpClient httpClient) } private readonly MinecraftPath minecraftPath; - private MVersionCollection? liteLoaderVersions; + private VersionCollection? liteLoaderVersions; public static string GetVersionName(string loaderVersion, string baseVersion) { @@ -60,7 +60,7 @@ public async Task InstallAsync(string liteLoaderVersion) return await liteLoader.InstallAsync(minecraftPath, vanillaVersion); } - public async Task InstallAsync(string liteLoaderVersion, MVersion target) + public async Task InstallAsync(string liteLoaderVersion, IVersion target) { if (liteLoaderVersions == null) await GetAllLiteLoaderVersions().ConfigureAwait(false); diff --git a/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs b/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs index f2ad5a6..f2f485c 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs +++ b/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs @@ -15,7 +15,7 @@ public class LiteLoaderVersionLoader : IVersionLoader public LiteLoaderVersionLoader(HttpClient httpClient) => _httpClient = httpClient; - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { var res = await _httpClient.GetStringAsync(ManifestServer) .ConfigureAwait(false); @@ -23,7 +23,7 @@ public async ValueTask GetVersionMetadatasAsync() return parseVersions(res); } - private MVersionCollection parseVersions(string json) + private VersionCollection parseVersions(string json) { using var jsonDocument = JsonDocument.Parse(json); var root = jsonDocument.RootElement; @@ -58,6 +58,6 @@ private MVersionCollection parseVersions(string json) } } - return new MVersionCollection(metadataList, null, null); + return new VersionCollection(metadataList, null, null); } } \ No newline at end of file diff --git a/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs index 35738a5..3952431 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using CmlLib.Core.Launcher; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using CmlLib.Utils; @@ -23,7 +24,7 @@ public LiteLoaderVersionMetadata( public string VanillaVersionName { get; private set; } private void writeVersion(Utf8JsonWriter writer, - string versionName, string baseVersionName, string? strArgs, string?[]? arrArgs) + string versionName, string baseVersionName, string? strArgs, IEnumerable? arrArgs) { var llVersion = _element?.GetPropertyValue("version"); var libraries = _element?.GetPropertyOrNull("libraries"); @@ -67,7 +68,7 @@ private void writeVersion(Utf8JsonWriter writer, writer.WriteStartObject("arguments"); writer.WriteStartArray(); foreach (var item in arrArgs) - writer.WriteStringValue(item); + JsonSerializer.Serialize(writer, item); writer.WriteEndArray(); writer.WriteEndObject(); } @@ -85,7 +86,7 @@ private Stream createVersionWriteStream(MinecraftPath path, string name) return File.Create(metadataPath); } - public async Task InstallAsync(MinecraftPath path, MVersion baseVersion) + public async Task InstallAsync(MinecraftPath path, IVersion baseVersion) { var versionName = LiteLoaderInstaller.GetVersionName(VanillaVersionName, baseVersion.Id); var tweakClass = _element?.GetPropertyValue("tweakClass"); @@ -93,21 +94,22 @@ public async Task InstallAsync(MinecraftPath path, MVersion baseVersion) using var fs = createVersionWriteStream(path, versionName); using var writer = new Utf8JsonWriter(fs); - if (!string.IsNullOrEmpty(baseVersion.MinecraftArguments)) + var minecraftArguments = baseVersion.GetProperty("minecraftArguments"); + if (!string.IsNullOrEmpty(minecraftArguments)) { // com.mumfrey.liteloader.launch.LiteLoaderTweaker - var newArguments = $"--tweakClass {tweakClass} {baseVersion.MinecraftArguments}"; + var newArguments = $"--tweakClass {tweakClass} {minecraftArguments}"; writeVersion(writer, versionName, baseVersion.Id, newArguments, null); } - else if (baseVersion.GameArguments != null) + else if (baseVersion.GameArguments.Any()) { - var tweakArg = new [] + var tweakArg = new MArgument[] { - "--tweakClass", - tweakClass + new MArgument("--tweakClass"), + new MArgument(tweakClass!) }; - var newArguments = tweakArg.Concat(baseVersion.GameArguments).ToArray(); + var newArguments = tweakArg.Concat(baseVersion.GameArguments); writeVersion(writer, versionName, baseVersion.Id, null, newArguments); } diff --git a/src/Core/Installer/MJava.cs b/src/Core/Installer/MJava.cs index c4b25e8..d6332b9 100644 --- a/src/Core/Installer/MJava.cs +++ b/src/Core/Installer/MJava.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Net; using CmlLib.Core.Downloader; +using CmlLib.Core.Rules; namespace CmlLib.Core.Installer; @@ -30,20 +31,22 @@ public MJava(HttpClient client, string runtimePath) _httpClient = client; } - public string GetBinaryPath() - => JavaPathResolver.GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersionName, MRule.OSName); + public string GetBinaryPath(LauncherOSRule os) + => JavaPathResolver.GetJavaBinaryPath( + MinecraftJavaPathResolver.CmlLegacyVersion, + os); - public bool CheckJavaExistence() - => File.Exists(GetBinaryPath()); + public bool CheckJavaExistence(LauncherOSRule os) + => File.Exists(GetBinaryPath(os)); - public Task CheckJavaAsync() - => CheckJavaAsync(null); + public Task CheckJavaAsync(LauncherOSRule os) + => CheckJavaAsync(os, null); - public async Task CheckJavaAsync(IProgress? progress) + public async Task CheckJavaAsync(LauncherOSRule os, IProgress? progress) { - string javapath = GetBinaryPath(); + string javapath = GetBinaryPath(os); - if (!CheckJavaExistence()) + if (!CheckJavaExistence(os)) { if (progress == null) { diff --git a/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs b/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs index ba8561a..0c9f913 100644 --- a/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs +++ b/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs @@ -21,7 +21,7 @@ protected string GetVersionName(string version, string loaderVersion) return $"quilt-loader-{loaderVersion}-{version}"; } - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { if (string.IsNullOrEmpty(LoaderVersion)) { @@ -37,7 +37,7 @@ public async ValueTask GetVersionMetadatasAsync() var res = await _httpClient.GetStringAsync(url); var versions = parseVersions(res, LoaderVersion!); - return new MVersionCollection(versions, null, null); + return new VersionCollection(versions, null, null); } private IEnumerable parseVersions(string res, string loader) diff --git a/src/Core/Java/IJavaPathResolver.cs b/src/Core/Java/IJavaPathResolver.cs index 0d87c81..a32214d 100644 --- a/src/Core/Java/IJavaPathResolver.cs +++ b/src/Core/Java/IJavaPathResolver.cs @@ -1,11 +1,11 @@ -namespace CmlLib.Core.Java +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Java; + +public interface IJavaPathResolver { - public interface IJavaPathResolver - { - string[] GetInstalledJavaVersions(); - string? GetDefaultJavaBinaryPath(); - string GetJavaBinaryPath(string javaVersionName); - string GetJavaBinaryPath(string javaVersionName, string osName); - string GetJavaDirPath(string javaVersionName); - } + string[] GetInstalledJavaVersions(); + string? GetDefaultJavaBinaryPath(LauncherOSRule os); + string GetJavaBinaryPath(JavaVersion javaVersionName, LauncherOSRule os); + string GetJavaDirPath(JavaVersion javaVersionName); } \ No newline at end of file diff --git a/src/Core/Java/MinecraftJavaPathResolver.cs b/src/Core/Java/MinecraftJavaPathResolver.cs index 9ab21df..02dfd16 100644 --- a/src/Core/Java/MinecraftJavaPathResolver.cs +++ b/src/Core/Java/MinecraftJavaPathResolver.cs @@ -1,79 +1,74 @@ -using System.IO; -using System.Linq; +using CmlLib.Core.Rules; -namespace CmlLib.Core.Java +namespace CmlLib.Core.Java; + +public class MinecraftJavaPathResolver : IJavaPathResolver { - public class MinecraftJavaPathResolver : IJavaPathResolver + public static readonly JavaVersion JreLegacyVersion = new JavaVersion("jre-legacy"); + public static readonly JavaVersion CmlLegacyVersion = new JavaVersion("m-legacy"); + + public MinecraftJavaPathResolver(MinecraftPath path) { - public static readonly string JreLegacyVersionName = "jre-legacy"; - public static readonly string CmlLegacyVersionName = "m-legacy"; - - public MinecraftJavaPathResolver(MinecraftPath path) - { - runtimeDirectory = path.Runtime; - } - - public MinecraftJavaPathResolver(string path) - { - runtimeDirectory = path; - } - - private readonly string runtimeDirectory; + runtimeDirectory = path.Runtime; + } - public string[] GetInstalledJavaVersions() - { - var dir = new DirectoryInfo(runtimeDirectory); - if (!dir.Exists) - return new string[] { }; + public MinecraftJavaPathResolver(string path) + { + runtimeDirectory = path; + } - return dir.GetDirectories() - .Select(x => x.Name) - .ToArray(); - } + private readonly string runtimeDirectory; - public string? GetDefaultJavaBinaryPath() - { - var javaVersions = GetInstalledJavaVersions(); - string? javaPath = null; - - if (string.IsNullOrEmpty(javaPath) && - javaVersions.Contains(MinecraftJavaPathResolver.JreLegacyVersionName)) - javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.JreLegacyVersionName, MRule.OSName); - - if (string.IsNullOrEmpty(javaPath) && - javaVersions.Contains(MinecraftJavaPathResolver.CmlLegacyVersionName)) - javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersionName, MRule.OSName); + public string[] GetInstalledJavaVersions() + { + var dir = new DirectoryInfo(runtimeDirectory); + if (!dir.Exists) + return new string[] { }; - if (string.IsNullOrEmpty(javaPath) && - javaVersions.Length > 0) - javaPath = GetJavaBinaryPath(javaVersions[0], MRule.OSName); + return dir.GetDirectories() + .Select(x => x.Name) + .ToArray(); + } - return javaPath; - } - - public string GetJavaBinaryPath(string javaVersionName) - => GetJavaBinaryPath(javaVersionName, MRule.OSName); + public string? GetDefaultJavaBinaryPath(LauncherOSRule os) + { + var javaVersions = GetInstalledJavaVersions(); + string? javaPath = null; - public string GetJavaBinaryPath(string javaVersionName, string osName) - { - return Path.Combine( - GetJavaDirPath(javaVersionName), - "bin", - GetJavaBinaryName(osName)); - } + if (string.IsNullOrEmpty(javaPath) && + javaVersions.Contains(MinecraftJavaPathResolver.JreLegacyVersion.Component)) + javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.JreLegacyVersion, os); - public string GetJavaDirPath() - => GetJavaDirPath(JreLegacyVersionName); + if (string.IsNullOrEmpty(javaPath) && + javaVersions.Contains(MinecraftJavaPathResolver.CmlLegacyVersion.Component)) + javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersion, os); + + if (string.IsNullOrEmpty(javaPath) && + javaVersions.Length > 0) + javaPath = GetJavaBinaryPath(new JavaVersion(javaVersions[0]), os); + + return javaPath; + } + + public string GetJavaBinaryPath(JavaVersion javaVersionName, LauncherOSRule os) + { + return Path.Combine( + GetJavaDirPath(javaVersionName), + "bin", + GetJavaBinaryName(os)); + } + + public string GetJavaDirPath() + => GetJavaDirPath(JreLegacyVersion); - public string GetJavaDirPath(string javaVersionName) - => Path.Combine(runtimeDirectory, javaVersionName); + public string GetJavaDirPath(JavaVersion javaVersionName) + => Path.Combine(runtimeDirectory, javaVersionName.Component); - public string GetJavaBinaryName(string osName) - { - string binaryName = "java"; - if (osName == MRule.Windows) - binaryName = "javaw.exe"; - return binaryName; - } + public string GetJavaBinaryName(LauncherOSRule os) + { + string binaryName = "java"; + if (os.Name == MRule.Windows) + binaryName = "javaw.exe"; + return binaryName; } } \ No newline at end of file diff --git a/src/Core/Java/MinecraftJavaVersion.cs b/src/Core/Java/MinecraftJavaVersion.cs index e159ff3..bb66405 100644 --- a/src/Core/Java/MinecraftJavaVersion.cs +++ b/src/Core/Java/MinecraftJavaVersion.cs @@ -2,11 +2,22 @@ namespace CmlLib.Core.Java; -public class MinecraftJavaVersion +public record struct JavaVersion { + public JavaVersion(string component) : this(component, 0) + { + + } + + public JavaVersion(string component, int majorVersion) + { + this.Component = component; + this.MajorVersion = majorVersion; + } + [JsonPropertyName("component")] - public string? Component { get; set; } + public string Component { get; set; } [JsonPropertyName("majorVersion")] - public string? MajorVersion { get; set; } + public int MajorVersion { get; set; } } \ No newline at end of file diff --git a/src/Core/Launcher/MLaunch.cs b/src/Core/Launcher/MLaunch.cs index 972d36e..00a94da 100644 --- a/src/Core/Launcher/MLaunch.cs +++ b/src/Core/Launcher/MLaunch.cs @@ -30,10 +30,11 @@ public MLaunch(MLaunchOption option) launchOption = option; version = option.GetStartVersion(); this.minecraftPath = option.GetMinecraftPath(); + builder = new MinecraftArgumentBuilder(rulesEvaluator, launchOption.RulesContext!); } private readonly IVersion version; - private readonly IRulesEvaluator rulesEvaluator; + private readonly IRulesEvaluator rulesEvaluator = new RulesEvaluator(); private readonly MinecraftArgumentBuilder builder; private readonly MinecraftPath minecraftPath; private readonly MLaunchOption launchOption; @@ -43,8 +44,7 @@ public Process CreateProcess() { string arg = string.Join(" ", BuildArguments()); Process mc = new Process(); - mc.StartInfo.FileName = - useNotNull(launchOption.GetStartVersion().JavaBinaryPath, launchOption.GetJavaPath()) ?? ""; + mc.StartInfo.FileName = launchOption.JavaPath!; mc.StartInfo.Arguments = arg; mc.StartInfo.WorkingDirectory = minecraftPath.BasePath; @@ -195,7 +195,7 @@ private IEnumerable getClasspaths() var libPaths = version .ConcatInheritedCollection(v => v.Libraries) .Where(lib => lib.CheckIsRequired("SIDE")) - .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules)) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, launchOption.RulesContext!)) .Where(lib => lib.Artifact != null) .Select(lib => lib.GetLibraryPath()); @@ -209,7 +209,7 @@ private IEnumerable getClasspaths() private string createNativePath() { - var native = new MNative(minecraftPath, version); + var native = new MNative(minecraftPath, version, rulesEvaluator, launchOption.RulesContext!); native.CleanNatives(); var nativePath = native.ExtractNatives(); return nativePath; diff --git a/src/Core/Launcher/MLaunchOption.cs b/src/Core/Launcher/MLaunchOption.cs index 4f7d1fd..1c1a863 100644 --- a/src/Core/Launcher/MLaunchOption.cs +++ b/src/Core/Launcher/MLaunchOption.cs @@ -1,4 +1,5 @@ using CmlLib.Core.Auth; +using CmlLib.Core.Rules; using CmlLib.Core.Version; namespace CmlLib.Core; @@ -8,6 +9,7 @@ public class MLaunchOption public MinecraftPath? Path { get; set; } public IVersion? StartVersion { get; set; } public MSession? Session { get; set; } + public RulesEvaluatorContext? RulesContext { get; set; } public string? JavaVersion { get; set; } public string? JavaPath { get; set; } @@ -43,6 +45,9 @@ internal void CheckValid() { string? exMsg = null; // error message + if (RulesContext == null) + exMsg = "RulesContext is null"; + if (Path == null) exMsg = nameof(Path) + " is null"; diff --git a/src/Core/Launcher/MNative.cs b/src/Core/Launcher/MNative.cs index 2841aee..af6f474 100644 --- a/src/Core/Launcher/MNative.cs +++ b/src/Core/Launcher/MNative.cs @@ -6,15 +6,22 @@ namespace CmlLib.Core; public class MNative { - public MNative(MinecraftPath gamePath, IVersion version) + public MNative( + MinecraftPath gamePath, + IVersion version, + IRulesEvaluator rulesEvaluator, + RulesEvaluatorContext context) { this.version = version; this.gamePath = gamePath; + this.rulesEvaluator = rulesEvaluator; + this.context = context; } private readonly IVersion version; private readonly MinecraftPath gamePath; private readonly IRulesEvaluator rulesEvaluator; + private readonly RulesEvaluatorContext context; public string ExtractNatives() { @@ -36,10 +43,10 @@ private IEnumerable getNativeLibraryPaths() return version .ConcatInheritedCollection(v => v.Libraries) .Where(lib => lib.CheckIsRequired("SIDE")) - .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules)) - .Select(lib => lib.GetNativeLibraryPath(os)) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, context)) + .Select(lib => lib.GetNativeLibraryPath(context.OS)) .Where(libPath => !string.IsNullOrEmpty(libPath)) - .Select(libPath => Path.Combine(gamePath.Library, libPath)); + .Select(libPath => Path.Combine(gamePath.Library, libPath!)); } public void CleanNatives() diff --git a/src/ProcessBuilder/MinecraftArgumentBuilder.cs b/src/Core/ProcessBuilder/MinecraftArgumentBuilder.cs similarity index 76% rename from src/ProcessBuilder/MinecraftArgumentBuilder.cs rename to src/Core/ProcessBuilder/MinecraftArgumentBuilder.cs index 1fd14de..c85ce57 100644 --- a/src/ProcessBuilder/MinecraftArgumentBuilder.cs +++ b/src/Core/ProcessBuilder/MinecraftArgumentBuilder.cs @@ -6,6 +6,13 @@ namespace CmlLib.Core.ProcessBuilder; public class MinecraftArgumentBuilder { private readonly IRulesEvaluator _evaluator; + private readonly RulesEvaluatorContext _context; + + public MinecraftArgumentBuilder(IRulesEvaluator evaluator, RulesEvaluatorContext context) + { + _evaluator = evaluator; + _context = context; + } public IEnumerable Build(IEnumerable arguments, Dictionary mapper) { @@ -23,7 +30,7 @@ public IEnumerable Build(MArgument arg, Dictionary mapp if (arg.Rules != null) { - var isMatch = _evaluator.Match(arg.Rules); + var isMatch = _evaluator.Match(arg.Rules, _context); if (!isMatch) yield break; } diff --git a/src/ProcessBuilder/ProcessArgumentBuilder.cs b/src/Core/ProcessBuilder/ProcessArgumentBuilder.cs similarity index 100% rename from src/ProcessBuilder/ProcessArgumentBuilder.cs rename to src/Core/ProcessBuilder/ProcessArgumentBuilder.cs diff --git a/src/ProcessBuilder/ProcessUtil.cs b/src/Core/ProcessBuilder/ProcessUtil.cs similarity index 100% rename from src/ProcessBuilder/ProcessUtil.cs rename to src/Core/ProcessBuilder/ProcessUtil.cs diff --git a/src/Core/Rules/IRulesEvaluator.cs b/src/Core/Rules/IRulesEvaluator.cs index 6c72f98..dbebf21 100644 --- a/src/Core/Rules/IRulesEvaluator.cs +++ b/src/Core/Rules/IRulesEvaluator.cs @@ -2,5 +2,5 @@ namespace CmlLib.Core.Rules; public interface IRulesEvaluator { - bool Match(IEnumerable rules); + bool Match(IEnumerable rules, RulesEvaluatorContext context); } \ No newline at end of file diff --git a/src/Core/Rules/LauncherRule.cs b/src/Core/Rules/LauncherRule.cs index 2be865f..277a478 100644 --- a/src/Core/Rules/LauncherRule.cs +++ b/src/Core/Rules/LauncherRule.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using System.Text.Json.Serialization; namespace CmlLib.Core.Rules; @@ -41,6 +42,31 @@ public bool MatchFeatures(Dictionary toMatch) public record LauncherOSRule { + public static readonly string Windows = "windows"; + public static readonly string OSX = "osx"; + public static readonly string Linux = "linux"; + + public static LauncherOSRule CreateCurrent() + { + var os = new LauncherOSRule(); + if (Environment.Is64BitOperatingSystem) + os.Arch = "64"; + else + os.Arch = "32"; + os.Name = getOSName(); + return os; + } + + private static string getOSName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return OSX; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return Windows; + else + return Linux; + } + [JsonPropertyName("name")] public string? Name { get; set; } @@ -49,7 +75,7 @@ public record LauncherOSRule public bool Match(LauncherOSRule toMatch) { - return isPropMatch(Name, toMatch.Name) && + return isPropMatch(Name, toMatch.Name) && isPropMatch(Arch, toMatch.Arch); } diff --git a/src/Core/Rules/RulesEvaluator.cs b/src/Core/Rules/RulesEvaluator.cs index c3b0613..f42e081 100644 --- a/src/Core/Rules/RulesEvaluator.cs +++ b/src/Core/Rules/RulesEvaluator.cs @@ -2,24 +2,15 @@ namespace CmlLib.Core.Rules; public class RulesEvaluator : IRulesEvaluator { - private readonly RulesEvaluatorContext _context; - - public RulesEvaluator() - { - } - - public RulesEvaluator(RulesEvaluatorContext context) => - _context = context; - - public bool Match(IEnumerable rules) + public bool Match(IEnumerable rules, RulesEvaluatorContext context) { - return rules.Any(rule => match(rule)); + return rules.Any(rule => match(rule, context)); } - private bool match(LauncherRule rule) + private bool match(LauncherRule rule, RulesEvaluatorContext context) { var isAllow = rule.Action == "allow"; - var isOSMatched = rule.OS != null && rule.OS.Match(_context.OS); + var isOSMatched = rule.OS != null && rule.OS.Match(context.OS); if (isAllow) return isOSMatched; diff --git a/src/Core/Rules/RulesEvaluatorContext.cs b/src/Core/Rules/RulesEvaluatorContext.cs index 0f06cf5..c77ffa0 100644 --- a/src/Core/Rules/RulesEvaluatorContext.cs +++ b/src/Core/Rules/RulesEvaluatorContext.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + namespace CmlLib.Core.Rules; public class RulesEvaluatorContext diff --git a/src/Core/Tasks/ActionTask.cs b/src/Core/Tasks/ActionTask.cs new file mode 100644 index 0000000..9a89866 --- /dev/null +++ b/src/Core/Tasks/ActionTask.cs @@ -0,0 +1,14 @@ +namespace CmlLib.Core.Tasks; + +public class ActionTask : LinkedTask +{ + private readonly Func> _action; + + public ActionTask(Func> action) => + _action = action; + + public override async ValueTask Execute() + { + return await _action.Invoke(); + } +} \ No newline at end of file diff --git a/src/Core/Tasks/ChmodTask.cs b/src/Core/Tasks/ChmodTask.cs new file mode 100644 index 0000000..fcec278 --- /dev/null +++ b/src/Core/Tasks/ChmodTask.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using CmlLib.Utils; + +namespace CmlLib.Core.Tasks; + +public class ChmodTask : LinkedTask +{ + public string Path { get; private set; } + + public ChmodTask(string path) => + Path = path; + + public override ValueTask Execute() + { + try + { + if (MRule.OSName != MRule.Windows) + NativeMethods.Chmod(Path, NativeMethods.Chmod755); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + + return new ValueTask(NextTask); + } +} \ No newline at end of file diff --git a/src/Core/Tasks/DownloadTask.cs b/src/Core/Tasks/DownloadTask.cs new file mode 100644 index 0000000..382542f --- /dev/null +++ b/src/Core/Tasks/DownloadTask.cs @@ -0,0 +1,29 @@ +namespace CmlLib.Core.Tasks; + +public class DownloadTask : LinkedTask +{ + public DownloadTask(TaskFile file) + { + if (string.IsNullOrEmpty(file.Path)) + throw new ArgumentException("file.Path was empty"); + if (string.IsNullOrEmpty(file.Url)) + throw new ArgumentException("file.Url was empty"); + + this.Path = file.Path; + this.Url = file.Url; + } + + public DownloadTask(string path, string url) + { + this.Path = path; + this.Url = url; + } + + public string Path { get; } + public string Url { get; } + + public override ValueTask Execute() + { + return new ValueTask(NextTask); + } +} \ No newline at end of file diff --git a/src/Core/Tasks/FileCheckTask.cs b/src/Core/Tasks/FileCheckTask.cs new file mode 100644 index 0000000..dab7d91 --- /dev/null +++ b/src/Core/Tasks/FileCheckTask.cs @@ -0,0 +1,32 @@ +using CmlLib.Utils; + +namespace CmlLib.Core.Tasks; + +public class FileCheckTask : ResultTask +{ + public FileCheckTask(TaskFile file) + { + if (string.IsNullOrEmpty(file.Path)) + throw new ArgumentException("file.Path was empty"); + this.Path = file.Path; + + if (string.IsNullOrEmpty(file.Hash)) + throw new ArgumentException("file.Hash return empty"); + this.Hash = file.Hash; + } + + public FileCheckTask(string path, string hash) + { + this.Path = path; + this.Hash = hash; + } + + public string Path { get; } + public string Hash { get; } + + protected override ValueTask ExecuteWithResult() + { + var result = IOUtil.CheckFileValidation(Path, Hash, true); + return new ValueTask(result); + } +} \ No newline at end of file diff --git a/src/Core/Tasks/FileCopyTask.cs b/src/Core/Tasks/FileCopyTask.cs new file mode 100644 index 0000000..76796d3 --- /dev/null +++ b/src/Core/Tasks/FileCopyTask.cs @@ -0,0 +1,23 @@ +namespace CmlLib.Core.Tasks; + +public class FileCopyTask : LinkedTask +{ + public FileCopyTask(string sourcePath, IEnumerable destPaths) => + (SourcePath, DestinationPaths) = (sourcePath, destPaths.ToArray()); + + public string SourcePath { get; } + public string[] DestinationPaths { get; } + + public override ValueTask Execute() + { + if (!File.Exists(SourcePath)) + throw new InvalidOperationException("The source file does not exists"); + + foreach (var destination in DestinationPaths) + { + File.Copy(SourcePath, destination); + } + + return new ValueTask(NextTask); + } +} \ No newline at end of file diff --git a/src/Core/Tasks/LZMADecompressTask.cs b/src/Core/Tasks/LZMADecompressTask.cs new file mode 100644 index 0000000..247e589 --- /dev/null +++ b/src/Core/Tasks/LZMADecompressTask.cs @@ -0,0 +1,14 @@ +namespace CmlLib.Core.Tasks; + +public class LZMADecompressTask : LinkedTask +{ + public LZMADecompressTask(string lzmaPath, string decompressTo) + { + + } + + public override ValueTask Execute() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/Tasks/LinkedTask.cs b/src/Core/Tasks/LinkedTask.cs new file mode 100644 index 0000000..0caed95 --- /dev/null +++ b/src/Core/Tasks/LinkedTask.cs @@ -0,0 +1,44 @@ +namespace CmlLib.Core.Tasks; + +public abstract class LinkedTask +{ + public string? Name { get; set; } + public LinkedTask? NextTask { get; private set; } + + public abstract ValueTask Execute(); + + public LinkedTask InsertNextTask(LinkedTask task) + { + if (NextTask == null) + { + NextTask = task; + } + else + { + task.NextTask = this.NextTask; + this.NextTask = task; + } + return NextTask; + } + + public static LinkedTask? LinkTasks(params LinkedTask[] tasks) + { + return LinkTasks(tasks); + } + + public static LinkedTask? LinkTasks(IEnumerable tasks) + { + var firstTask = tasks.FirstOrDefault(); + if (firstTask == null) + return null; + + var nextTask = firstTask; + foreach (var task in tasks.Skip(1)) + { + nextTask.InsertNextTask(task); + nextTask = task; + } + + return firstTask; + } +} \ No newline at end of file diff --git a/src/Core/Tasks/ResultTask.cs b/src/Core/Tasks/ResultTask.cs new file mode 100644 index 0000000..883b441 --- /dev/null +++ b/src/Core/Tasks/ResultTask.cs @@ -0,0 +1,20 @@ +namespace CmlLib.Core.Tasks; + +public abstract class ResultTask : LinkedTask +{ + public LinkedTask? OnTrue { get; set; } + public LinkedTask? OnFalse { get; set; } + + public override async ValueTask Execute() + { + var result = await ExecuteWithResult(); + var nextTask = result ? OnTrue : OnFalse; + + if (nextTask != null) + return InsertNextTask(nextTask); + else + return NextTask; + } + + protected abstract ValueTask ExecuteWithResult(); +} \ No newline at end of file diff --git a/src/Core/Tasks/TaskFile.cs b/src/Core/Tasks/TaskFile.cs new file mode 100644 index 0000000..c34b531 --- /dev/null +++ b/src/Core/Tasks/TaskFile.cs @@ -0,0 +1,10 @@ +namespace CmlLib.Core.Tasks; + +public struct TaskFile +{ + public string? Name { get; set; } + public string? Path { get; set; } + public string? Hash { get; set; } + public string? Url { get; set; } + public long Size { get; set; } +} \ No newline at end of file diff --git a/src/Core/Tasks/UnzipTask.cs b/src/Core/Tasks/UnzipTask.cs new file mode 100644 index 0000000..fed7f79 --- /dev/null +++ b/src/Core/Tasks/UnzipTask.cs @@ -0,0 +1,14 @@ +namespace CmlLib.Core.Tasks; + +public class UnzipTask : LinkedTask +{ + public UnzipTask(string zipPath, string unzipTo) + { + + } + + public override ValueTask Execute() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Core/Version/IVersion.cs b/src/Core/Version/IVersion.cs index 3db57a5..432f232 100644 --- a/src/Core/Version/IVersion.cs +++ b/src/Core/Version/IVersion.cs @@ -11,7 +11,7 @@ public interface IVersion IVersion? ParentVersion { get; set; } AssetMetadata? AssetIndex { get; } MFileMetadata? Client { get; } - MinecraftJavaVersion? JavaVersion { get; } + JavaVersion? JavaVersion { get; } MLibrary[] Libraries { get; } string? Jar { get; } MLogFileMetadata? Logging { get; } diff --git a/src/Core/Version/JsonVersion.cs b/src/Core/Version/JsonVersion.cs index 3816e3a..16165c8 100644 --- a/src/Core/Version/JsonVersion.cs +++ b/src/Core/Version/JsonVersion.cs @@ -33,7 +33,7 @@ public JsonVersion(JsonElement json, JsonVersionParserOptions options) private MFileMetadata? _client = null; public MFileMetadata? Client => _client ??= getClient(); - public MinecraftJavaVersion? JavaVersion => _model.JavaVersion; + public JavaVersion? JavaVersion => _model.JavaVersion; private MLibrary[]? _libs = null; public MLibrary[] Libraries => _libs ??= getLibraries(); diff --git a/src/Core/Version/VersionJsonModel.cs b/src/Core/Version/VersionJsonModel.cs index 4a87056..a556d53 100644 --- a/src/Core/Version/VersionJsonModel.cs +++ b/src/Core/Version/VersionJsonModel.cs @@ -15,14 +15,14 @@ public class VersionJsonModel [JsonPropertyName("assets")] public string? Assets { get; set; } - [JsonPropertyName("complianceLevel")] - public string? ComplianceLevel { get; set;} - [JsonPropertyName("id")] public string? Id { get; set; } + [JsonPropertyName("complianceLevel")] + public int ComplianceLevel { get; set; } + [JsonPropertyName("javaVersion")] - public MinecraftJavaVersion? JavaVersion { get; set; } + public JavaVersion? JavaVersion { get; set; } [JsonPropertyName("jar")] public string? Jar { get; set; } @@ -34,7 +34,7 @@ public class VersionJsonModel public string? MinecraftArguments { get; set; } [JsonPropertyName("minimumLauncherVersion")] - public string? MinimumLauncherVersion { get; set; } + public int MinimumLauncherVersion { get; set; } [JsonPropertyName("releaseTime")] public DateTime ReleaseTime { get; set; } diff --git a/src/Core/VersionMetadata/JsonVersionMetadata.cs b/src/Core/VersionMetadata/JsonVersionMetadata.cs index fb2835c..27f7a82 100644 --- a/src/Core/VersionMetadata/JsonVersionMetadata.cs +++ b/src/Core/VersionMetadata/JsonVersionMetadata.cs @@ -57,13 +57,13 @@ public override int GetHashCode() public async Task GetVersionAsync() { var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - return JsonVersionParser.ParseFromJsonString(metadataJson); + return JsonVersionParser.ParseFromJsonString(metadataJson, new JsonVersionParserOptions()); } public async Task GetAndSaveVersionAsync(MinecraftPath path) { var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - var version = JsonVersionParser.ParseFromJsonString(metadataJson); + var version = JsonVersionParser.ParseFromJsonString(metadataJson, new JsonVersionParserOptions()); await saveMetdataJson(path, metadataJson); return version; } From 188ca92c2fd1a9b913425a0eb304cd94d70231c0 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 6 Aug 2023 09:49:06 +0000 Subject: [PATCH 058/137] update TPL --- examples/console/Program.cs | 4 + examples/console/TPL.cs | 74 +++++++++++++++++++ src/Core/Executors/TPLTaskExecutor.cs | 54 +++++++++----- src/Core/FileExtractors/AssetFileExtractor.cs | 7 +- .../FileExtractors/ClientFileExtractor.cs | 3 +- src/Core/FileExtractors/JavaFileExtractor.cs | 14 ++-- .../FileExtractors/LibraryFileExtractor.cs | 6 +- src/Core/FileExtractors/LogFileExtractor.cs | 3 +- src/Core/Tasks/ActionTask.cs | 4 +- src/Core/Tasks/ChmodTask.cs | 4 +- src/Core/Tasks/DownloadTask.cs | 6 +- src/Core/Tasks/FileCheckTask.cs | 6 +- src/Core/Tasks/FileCopyTask.cs | 4 +- src/Core/Tasks/LZMADecompressTask.cs | 4 +- src/Core/Tasks/LinkedTask.cs | 24 +++++- src/Core/Tasks/ResultTask.cs | 16 +++- src/Core/Tasks/TaskFile.cs | 13 ++-- src/Core/Tasks/UnzipTask.cs | 4 +- 18 files changed, 187 insertions(+), 63 deletions(-) create mode 100644 examples/console/TPL.cs diff --git a/examples/console/Program.cs b/examples/console/Program.cs index f556fa9..74006d1 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -16,6 +16,10 @@ class Program public static async Task Main() { + //var t = new TPL(); + //await t.Test(); + //return; + var p = new Program(); // Login diff --git a/examples/console/TPL.cs b/examples/console/TPL.cs new file mode 100644 index 0000000..4050341 --- /dev/null +++ b/examples/console/TPL.cs @@ -0,0 +1,74 @@ +using System.Threading.Tasks.Dataflow; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLibCoreSample; + +public class TPL +{ + public async Task Test() + { + var linkOptions = new DataflowLinkOptions + { + PropagateCompletion = true + }; + + var buffer = new BufferBlock(); + var executor = new TransformManyBlock( + f => + { + if (f.Size < 0) + Console.WriteLine($"@@@ ERROR {f.Name}, {f.Size}"); + else + Console.WriteLine($"{f.Name}, {f.Size}"); + Thread.Sleep(100); + + var list = new List(); + for (int i = 0; i < f.Size; i++) + { + list.Add(new TaskFile(f.Name) + { + Size = f.Size - 1 + }); + } + return list; + }, + new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = 8 + }); + var done = new ActionBlock(f => Console.WriteLine($"DONE {f.Name} {f.Size}")); + + buffer.LinkTo(executor, linkOptions); + executor.LinkTo(done, linkOptions, f => f.Size == 0); + executor.LinkTo(buffer, linkOptions); + + var versionBroadcaster = new BroadcastBlock(null); + for (var i = 0; i < 4; i++) + { + int copy = i; + var extractor = new TransformManyBlock( + v => extract(v, copy)); + versionBroadcaster.LinkTo(extractor, linkOptions); + extractor.LinkTo(buffer, linkOptions); + } + + await versionBroadcaster.SendAsync("1.20.1"); + Console.ReadLine(); + Console.WriteLine("DONE"); + } + + private async IAsyncEnumerable extract(string version, int i) + { + Console.WriteLine($"extractor {i}"); + for (int j = 0; j < 4; j++) + { + await Task.Delay(100); + Console.WriteLine("extracted " + (i*10+j)); + yield return new TaskFile((i*10 + j).ToString()) + { + Size = j + }; + } + } +} \ No newline at end of file diff --git a/src/Core/Executors/TPLTaskExecutor.cs b/src/Core/Executors/TPLTaskExecutor.cs index fc53508..80a41a7 100644 --- a/src/Core/Executors/TPLTaskExecutor.cs +++ b/src/Core/Executors/TPLTaskExecutor.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; using CmlLib.Core.Tasks; @@ -7,13 +8,22 @@ namespace CmlLib.Core.Executors; public class TPLTaskExecutor { - private readonly DataflowLinkOptions _linkOptions = new () + private enum TaskStatus + { + Processing, + Done + } + + private readonly ConcurrentDictionary _runningTasks = new(); + private int proceed = 0; + + private readonly DataflowLinkOptions _linkOptions = new() { PropagateCompletion = true }; public async ValueTask Install( - IEnumerable extractors, + IEnumerable extractors, MinecraftPath path, IVersion version) { @@ -21,8 +31,6 @@ public async ValueTask Install( var installer = completeBlock(executor, path, extractors); await installer.SendAsync(version); - installer.Complete(); - await installer.Completion; } private BufferBlock createTaskExecutorBlock() @@ -30,25 +38,30 @@ private BufferBlock createTaskExecutorBlock() var buffer = new BufferBlock(); var executor = new TransformBlock(async task => { - Console.WriteLine(task.GetType().Name); - if (task is FileCheckTask fct) - { - Console.WriteLine(fct.Path); - Console.WriteLine(fct.Hash); - } - else if (task is DownloadTask dt) + if (_runningTasks.TryAdd(task.Name, TaskStatus.Processing)) + fireEvent(task.Name, TaskStatus.Processing); + var nextTask = await task.Execute(); + + if (nextTask == null || nextTask.Name != task.Name) { - Console.WriteLine(dt.Path); - Console.WriteLine(dt.Url); + Interlocked.Increment(ref proceed); + _runningTasks.TryUpdate(task.Name, TaskStatus.Done, TaskStatus.Processing); + fireEvent(task.Name, TaskStatus.Done); + + if (proceed == _runningTasks.Count) // TODO: check also extractor is all done + { + Console.WriteLine("ALL DONE"); + } } - return await task.Execute(); + + return nextTask; }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 8 }); buffer.LinkTo(executor, _linkOptions); - executor.LinkTo(buffer!, _linkOptions, next => next != null); + executor.LinkTo(buffer!, _linkOptions, t => t != null); return buffer; } @@ -61,14 +74,21 @@ private ITargetBlock completeBlock( var broadcaster = new BroadcastBlock(null); foreach (var extractor in extractors) { - var block = new TransformManyBlock(async v => + var block = new TransformManyBlock(async v => { return await extractor.Extract(path, v); }); broadcaster.LinkTo(block, _linkOptions); block.LinkTo(executor, _linkOptions); } - + return broadcaster; } + + private void fireEvent(string name, TaskStatus status) + { + var totalTasks = _runningTasks.Count; + var statusMsg = status == TaskStatus.Processing ? "START" : "END"; + Console.WriteLine($"[{proceed}/{totalTasks}][{statusMsg}] {name}"); + } } \ No newline at end of file diff --git a/src/Core/FileExtractors/AssetFileExtractor.cs b/src/Core/FileExtractors/AssetFileExtractor.cs index 6d6f8aa..bef0b20 100644 --- a/src/Core/FileExtractors/AssetFileExtractor.cs +++ b/src/Core/FileExtractors/AssetFileExtractor.cs @@ -73,9 +73,8 @@ private IEnumerable extractFromIndex( if (mapResource) copyPath.Add(Path.Combine(path.Resource, prop.Name)); - var file = new TaskFile + var file = new TaskFile(prop.Name) { - Name = prop.Name, Path = hashPath, Hash = hash, Size = size, @@ -86,8 +85,8 @@ private IEnumerable extractFromIndex( checkTask.OnFalse = new DownloadTask(file); if (copyPath.Count > 0) - checkTask.InsertNextTask(new FileCopyTask(hashPath, copyPath.ToArray())); - + checkTask.InsertNextTask(new FileCopyTask(prop.Name, hashPath, copyPath.ToArray())); + yield return checkTask; } } diff --git a/src/Core/FileExtractors/ClientFileExtractor.cs b/src/Core/FileExtractors/ClientFileExtractor.cs index efa8bd1..9456f2a 100644 --- a/src/Core/FileExtractors/ClientFileExtractor.cs +++ b/src/Core/FileExtractors/ClientFileExtractor.cs @@ -20,9 +20,8 @@ private IEnumerable extract(MinecraftPath path, IVersion version) yield break; var clientPath = path.GetVersionJarPath(id); - var file = new TaskFile + var file = new TaskFile(id) { - Name = id, Path = clientPath, Url = url, Hash = version.Client?.GetSha1(), diff --git a/src/Core/FileExtractors/JavaFileExtractor.cs b/src/Core/FileExtractors/JavaFileExtractor.cs index d3f2230..8dec9b4 100644 --- a/src/Core/FileExtractors/JavaFileExtractor.cs +++ b/src/Core/FileExtractors/JavaFileExtractor.cs @@ -163,9 +163,8 @@ private IEnumerable extractTasks(Stream stream, JavaVersion version) var executable = value.GetPropertyOrNull("executable")?.GetBoolean() ?? false; - var file = new TaskFile + var file = new TaskFile(name) { - Name = name, Hash = hash, Path = filePath, Url = url, @@ -176,7 +175,7 @@ private IEnumerable extractTasks(Stream stream, JavaVersion version) checkTask.OnFalse = new DownloadTask(file); if (executable) - checkTask.InsertNextTask(new ChmodTask(file.Path)); + checkTask.InsertNextTask(new ChmodTask(file.Name, file.Path)); return checkTask; } @@ -194,17 +193,16 @@ private async ValueTask> legacyJavaChecker() var lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); - var file = new TaskFile + var file = new TaskFile("jre.lzma") { - Name = "jre.lzma", Url = javaUrl, Path = lzmaPath }; var download = new DownloadTask(file); - var decompressLZMA = new LZMADecompressTask(lzmaPath, zipPath); - var unzip = new UnzipTask(zipPath, legacyJavaPath); - var chmod = new ChmodTask(mJava.GetBinaryPath(_os)); + var decompressLZMA = new LZMADecompressTask(file.Name, lzmaPath, zipPath); + var unzip = new UnzipTask(file.Name, zipPath, legacyJavaPath); + var chmod = new ChmodTask(file.Name, mJava.GetBinaryPath(_os)); return new LinkedTask[] { diff --git a/src/Core/FileExtractors/LibraryFileExtractor.cs b/src/Core/FileExtractors/LibraryFileExtractor.cs index 31b857a..119f9b4 100644 --- a/src/Core/FileExtractors/LibraryFileExtractor.cs +++ b/src/Core/FileExtractors/LibraryFileExtractor.cs @@ -48,9 +48,8 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary if (artifact != null) { var libPath = library.GetLibraryPath(); - var file = new TaskFile + var file = new TaskFile(library.Name) { - Name = library.Name, Path = Path.Combine(path.Library, libPath), Url = createDownloadUrl(artifact.Url, libPath), Hash = artifact.GetSha1() @@ -67,9 +66,8 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary var libPath = library.GetNativeLibraryPath(_rulesContext.OS); if (!string.IsNullOrEmpty(libPath)) { - var file = new TaskFile + var file = new TaskFile(library.Name) { - Name = library.Name, Path = Path.Combine(path.Library, libPath), Url = createDownloadUrl(native.Url, libPath), Hash = native.GetSha1() diff --git a/src/Core/FileExtractors/LogFileExtractor.cs b/src/Core/FileExtractors/LogFileExtractor.cs index e56ce2d..0ca8159 100644 --- a/src/Core/FileExtractors/LogFileExtractor.cs +++ b/src/Core/FileExtractors/LogFileExtractor.cs @@ -22,9 +22,8 @@ private IEnumerable extract(MinecraftPath path, IVersion version) var id = version.Logging?.LogFile?.Id ?? version.Id; - var file = new TaskFile + var file = new TaskFile(id) { - Name = id, Path = path.GetLogConfigFilePath(id), Url = url, Hash = version.Logging?.LogFile?.GetSha1() diff --git a/src/Core/Tasks/ActionTask.cs b/src/Core/Tasks/ActionTask.cs index 9a89866..74b0851 100644 --- a/src/Core/Tasks/ActionTask.cs +++ b/src/Core/Tasks/ActionTask.cs @@ -4,10 +4,10 @@ public class ActionTask : LinkedTask { private readonly Func> _action; - public ActionTask(Func> action) => + public ActionTask(string name, Func> action) : base(name) => _action = action; - public override async ValueTask Execute() + protected override async ValueTask OnExecuted() { return await _action.Invoke(); } diff --git a/src/Core/Tasks/ChmodTask.cs b/src/Core/Tasks/ChmodTask.cs index fcec278..ee818a4 100644 --- a/src/Core/Tasks/ChmodTask.cs +++ b/src/Core/Tasks/ChmodTask.cs @@ -7,10 +7,10 @@ public class ChmodTask : LinkedTask { public string Path { get; private set; } - public ChmodTask(string path) => + public ChmodTask(string name, string path) : base(name) => Path = path; - public override ValueTask Execute() + protected override ValueTask OnExecuted() { try { diff --git a/src/Core/Tasks/DownloadTask.cs b/src/Core/Tasks/DownloadTask.cs index 382542f..9062e9d 100644 --- a/src/Core/Tasks/DownloadTask.cs +++ b/src/Core/Tasks/DownloadTask.cs @@ -2,7 +2,7 @@ namespace CmlLib.Core.Tasks; public class DownloadTask : LinkedTask { - public DownloadTask(TaskFile file) + public DownloadTask(TaskFile file) : base(file) { if (string.IsNullOrEmpty(file.Path)) throw new ArgumentException("file.Path was empty"); @@ -13,7 +13,7 @@ public DownloadTask(TaskFile file) this.Url = file.Url; } - public DownloadTask(string path, string url) + public DownloadTask(string name, string path, string url) : base(name) { this.Path = path; this.Url = url; @@ -22,7 +22,7 @@ public DownloadTask(string path, string url) public string Path { get; } public string Url { get; } - public override ValueTask Execute() + protected override ValueTask OnExecuted() { return new ValueTask(NextTask); } diff --git a/src/Core/Tasks/FileCheckTask.cs b/src/Core/Tasks/FileCheckTask.cs index dab7d91..5759688 100644 --- a/src/Core/Tasks/FileCheckTask.cs +++ b/src/Core/Tasks/FileCheckTask.cs @@ -4,7 +4,7 @@ namespace CmlLib.Core.Tasks; public class FileCheckTask : ResultTask { - public FileCheckTask(TaskFile file) + public FileCheckTask(TaskFile file) : base(file) { if (string.IsNullOrEmpty(file.Path)) throw new ArgumentException("file.Path was empty"); @@ -15,7 +15,7 @@ public FileCheckTask(TaskFile file) this.Hash = file.Hash; } - public FileCheckTask(string path, string hash) + public FileCheckTask(string name, string path, string hash) : base(name) { this.Path = path; this.Hash = hash; @@ -24,7 +24,7 @@ public FileCheckTask(string path, string hash) public string Path { get; } public string Hash { get; } - protected override ValueTask ExecuteWithResult() + protected override ValueTask OnExecutedWithResult() { var result = IOUtil.CheckFileValidation(Path, Hash, true); return new ValueTask(result); diff --git a/src/Core/Tasks/FileCopyTask.cs b/src/Core/Tasks/FileCopyTask.cs index 76796d3..4c103fc 100644 --- a/src/Core/Tasks/FileCopyTask.cs +++ b/src/Core/Tasks/FileCopyTask.cs @@ -2,13 +2,13 @@ namespace CmlLib.Core.Tasks; public class FileCopyTask : LinkedTask { - public FileCopyTask(string sourcePath, IEnumerable destPaths) => + public FileCopyTask(string name, string sourcePath, IEnumerable destPaths) : base(name) => (SourcePath, DestinationPaths) = (sourcePath, destPaths.ToArray()); public string SourcePath { get; } public string[] DestinationPaths { get; } - public override ValueTask Execute() + protected override ValueTask OnExecuted() { if (!File.Exists(SourcePath)) throw new InvalidOperationException("The source file does not exists"); diff --git a/src/Core/Tasks/LZMADecompressTask.cs b/src/Core/Tasks/LZMADecompressTask.cs index 247e589..61423c2 100644 --- a/src/Core/Tasks/LZMADecompressTask.cs +++ b/src/Core/Tasks/LZMADecompressTask.cs @@ -2,12 +2,12 @@ namespace CmlLib.Core.Tasks; public class LZMADecompressTask : LinkedTask { - public LZMADecompressTask(string lzmaPath, string decompressTo) + public LZMADecompressTask(string name, string lzmaPath, string decompressTo) : base(name) { } - public override ValueTask Execute() + protected override ValueTask OnExecuted() { throw new NotImplementedException(); } diff --git a/src/Core/Tasks/LinkedTask.cs b/src/Core/Tasks/LinkedTask.cs index 0caed95..fafd339 100644 --- a/src/Core/Tasks/LinkedTask.cs +++ b/src/Core/Tasks/LinkedTask.cs @@ -2,10 +2,30 @@ namespace CmlLib.Core.Tasks; public abstract class LinkedTask { - public string? Name { get; set; } + public LinkedTask(TaskFile file) + { + if (string.IsNullOrEmpty(file.Name)) + throw new ArgumentException("file.Name was empty"); + this.Name = file.Name; + } + + public LinkedTask(string name) + { + this.Name = name; + } + + public string Name { get; set; } public LinkedTask? NextTask { get; private set; } - public abstract ValueTask Execute(); + public async ValueTask Execute() + { + var nextTask = await OnExecuted(); + if (nextTask != null && nextTask.Name != this.Name) + throw new InvalidOperationException("Name should be same"); + return nextTask; + } + + protected abstract ValueTask OnExecuted(); public LinkedTask InsertNextTask(LinkedTask task) { diff --git a/src/Core/Tasks/ResultTask.cs b/src/Core/Tasks/ResultTask.cs index 883b441..42bafe9 100644 --- a/src/Core/Tasks/ResultTask.cs +++ b/src/Core/Tasks/ResultTask.cs @@ -2,12 +2,22 @@ namespace CmlLib.Core.Tasks; public abstract class ResultTask : LinkedTask { + public ResultTask(string name) : base(name) + { + + } + + public ResultTask(TaskFile file) : base(file) + { + + } + public LinkedTask? OnTrue { get; set; } public LinkedTask? OnFalse { get; set; } - public override async ValueTask Execute() + protected override async ValueTask OnExecuted() { - var result = await ExecuteWithResult(); + var result = await OnExecutedWithResult(); var nextTask = result ? OnTrue : OnFalse; if (nextTask != null) @@ -16,5 +26,5 @@ public abstract class ResultTask : LinkedTask return NextTask; } - protected abstract ValueTask ExecuteWithResult(); + protected abstract ValueTask OnExecutedWithResult(); } \ No newline at end of file diff --git a/src/Core/Tasks/TaskFile.cs b/src/Core/Tasks/TaskFile.cs index c34b531..d330e55 100644 --- a/src/Core/Tasks/TaskFile.cs +++ b/src/Core/Tasks/TaskFile.cs @@ -2,9 +2,12 @@ namespace CmlLib.Core.Tasks; public struct TaskFile { - public string? Name { get; set; } - public string? Path { get; set; } - public string? Hash { get; set; } - public string? Url { get; set; } - public long Size { get; set; } + public TaskFile(string name) => + Name = name; + + public string Name { get; } + public string? Path { get; set; } = null; + public string? Hash { get; set; } = null; + public string? Url { get; set; } = null; + public long Size { get; set; } = 0; } \ No newline at end of file diff --git a/src/Core/Tasks/UnzipTask.cs b/src/Core/Tasks/UnzipTask.cs index fed7f79..8ac3b5b 100644 --- a/src/Core/Tasks/UnzipTask.cs +++ b/src/Core/Tasks/UnzipTask.cs @@ -2,12 +2,12 @@ namespace CmlLib.Core.Tasks; public class UnzipTask : LinkedTask { - public UnzipTask(string zipPath, string unzipTo) + public UnzipTask(string name, string zipPath, string unzipTo) : base(name) { } - public override ValueTask Execute() + protected override ValueTask OnExecuted() { throw new NotImplementedException(); } From 80f2fa9517d99c52d9605f5e14e6b9732e059d94 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 6 Aug 2023 20:47:05 +0900 Subject: [PATCH 059/137] update TPL --- examples/console/Program.cs | 18 +++- examples/console/TPL.cs | 60 +++++++----- src/Core/Executors/TPLTaskExecutor.cs | 97 +++++++++++++------ src/Core/FileExtractors/AssetFileExtractor.cs | 1 + src/Core/FileExtractors/JavaFileExtractor.cs | 2 +- 5 files changed, 118 insertions(+), 60 deletions(-) diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 74006d1..f646d0c 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -7,6 +7,7 @@ using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.VersionLoader; +using System.Diagnostics; namespace CmlLibCoreSample; @@ -50,7 +51,7 @@ async Task Start(MSession session) var versionLoader = new VersionLoaderCollection() { new LocalVersionLoader(minecraftPath), - //new MojangVersionLoader(httpClient), + new MojangVersionLoader(httpClient), }; var versions = await versionLoader.GetVersionMetadatasAsync(); @@ -60,7 +61,7 @@ async Task Start(MSession session) } var version = await versions.GetAndSaveVersionAsync( - "1.20.1", minecraftPath); + "1.16.5", minecraftPath); var extractors = FileExtractorCollection.CreateDefault( httpClient, javaPathResolver, rulesEvaluator, rulesContext); @@ -75,9 +76,18 @@ async Task Start(MSession session) //} var installer = new TPLTaskExecutor(); + + var sw = new Stopwatch(); + sw.Start(); await installer.Install(extractors, minecraftPath, version); - Console.WriteLine("DONE"); - Console.ReadLine(); + sw.Stop(); + Console.WriteLine(sw.ElapsedMilliseconds); + + while (true) + { + Console.ReadLine(); + installer.PrintStatus(); + } } private void printTask(LinkedTask? task) diff --git a/examples/console/TPL.cs b/examples/console/TPL.cs index 4050341..60c7091 100644 --- a/examples/console/TPL.cs +++ b/examples/console/TPL.cs @@ -4,6 +4,28 @@ namespace CmlLibCoreSample; +public class MockTask : LinkedTask +{ + public int Count { get; set; } + + public MockTask(string name) : base(name) + { + } + + protected override ValueTask OnExecuted() + { + Console.WriteLine($"{Name}: {Count}"); + if (Count == 0) + return new ValueTask(); + else + { + var nextTask = new MockTask(Name); + nextTask.Count = Count - 1; + return new ValueTask(nextTask); + } + } +} + public class TPL { public async Task Test() @@ -13,41 +35,27 @@ public async Task Test() PropagateCompletion = true }; - var buffer = new BufferBlock(); - var executor = new TransformManyBlock( - f => - { - if (f.Size < 0) - Console.WriteLine($"@@@ ERROR {f.Name}, {f.Size}"); - else - Console.WriteLine($"{f.Name}, {f.Size}"); - Thread.Sleep(100); - - var list = new List(); - for (int i = 0; i < f.Size; i++) - { - list.Add(new TaskFile(f.Name) - { - Size = f.Size - 1 - }); - } - return list; + var buffer = new BufferBlock(); + var executor = new TransformBlock( + async f => + { + var next = await f.Execute(); + return next as MockTask; }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 8 }); - var done = new ActionBlock(f => Console.WriteLine($"DONE {f.Name} {f.Size}")); buffer.LinkTo(executor, linkOptions); - executor.LinkTo(done, linkOptions, f => f.Size == 0); - executor.LinkTo(buffer, linkOptions); + executor.LinkTo(buffer!, linkOptions, f => f != null); + executor.LinkTo(DataflowBlock.NullTarget(), linkOptions); var versionBroadcaster = new BroadcastBlock(null); for (var i = 0; i < 4; i++) { int copy = i; - var extractor = new TransformManyBlock( + var extractor = new TransformManyBlock( v => extract(v, copy)); versionBroadcaster.LinkTo(extractor, linkOptions); extractor.LinkTo(buffer, linkOptions); @@ -58,16 +66,16 @@ public async Task Test() Console.WriteLine("DONE"); } - private async IAsyncEnumerable extract(string version, int i) + private async IAsyncEnumerable extract(string version, int i) { Console.WriteLine($"extractor {i}"); for (int j = 0; j < 4; j++) { await Task.Delay(100); Console.WriteLine("extracted " + (i*10+j)); - yield return new TaskFile((i*10 + j).ToString()) + yield return new MockTask((i*10 + j).ToString()) { - Size = j + Count = j }; } } diff --git a/src/Core/Executors/TPLTaskExecutor.cs b/src/Core/Executors/TPLTaskExecutor.cs index 80a41a7..8398f98 100644 --- a/src/Core/Executors/TPLTaskExecutor.cs +++ b/src/Core/Executors/TPLTaskExecutor.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; using CmlLib.Core.Tasks; @@ -10,85 +13,121 @@ public class TPLTaskExecutor { private enum TaskStatus { - Processing, + Queued, Done } private readonly ConcurrentDictionary _runningTasks = new(); private int proceed = 0; - private readonly DataflowLinkOptions _linkOptions = new() + public void PrintStatus() { - PropagateCompletion = true - }; + var runningTasks = _runningTasks + .Where(kv => kv.Value == TaskStatus.Queued) + .Select(kv => kv.Key); + + foreach (var task in runningTasks) + { + Console.WriteLine(task); + } + } + + private readonly DataflowLinkOptions _linkOptions = new(); public async ValueTask Install( IEnumerable extractors, MinecraftPath path, IVersion version) { - var executor = createTaskExecutorBlock(); - var installer = completeBlock(executor, path, extractors); + var extractBlock = createExtractBlock(version, path); + var executeBlock = createExecuteBlock(extractBlock.Completion); + extractBlock.LinkTo(executeBlock, _linkOptions); - await installer.SendAsync(version); + await Task.WhenAll(extractors.Select(extractor => extractBlock.SendAsync(extractor))); + extractBlock.Complete(); + await extractBlock.Completion; + + if (proceed == _runningTasks.Count) + return; + else + await executeBlock.Completion; } - private BufferBlock createTaskExecutorBlock() + private BufferBlock createExecuteBlock(Task extractTask) { var buffer = new BufferBlock(); var executor = new TransformBlock(async task => { - if (_runningTasks.TryAdd(task.Name, TaskStatus.Processing)) - fireEvent(task.Name, TaskStatus.Processing); - var nextTask = await task.Execute(); + LinkedTask? nextTask = null; + Exception? exception = null; + try + { + nextTask = await task.Execute(); + } + catch (Exception ex) + { + exception = ex; + Debug.WriteLine(ex.ToString()); + } - if (nextTask == null || nextTask.Name != task.Name) + if (nextTask == null) { Interlocked.Increment(ref proceed); - _runningTasks.TryUpdate(task.Name, TaskStatus.Done, TaskStatus.Processing); + _runningTasks.TryUpdate(task.Name, TaskStatus.Done, TaskStatus.Queued); fireEvent(task.Name, TaskStatus.Done); - if (proceed == _runningTasks.Count) // TODO: check also extractor is all done + if (proceed == _runningTasks.Count && extractTask.IsCompleted) { - Console.WriteLine("ALL DONE"); + buffer.Complete(); } } return nextTask; }, new ExecutionDataflowBlockOptions { - MaxDegreeOfParallelism = 8 + MaxDegreeOfParallelism = 6 }); buffer.LinkTo(executor, _linkOptions); executor.LinkTo(buffer!, _linkOptions, t => t != null); + executor.LinkTo(DataflowBlock.NullTarget()); return buffer; } - private ITargetBlock completeBlock( - ITargetBlock executor, - MinecraftPath path, - IEnumerable extractors) + private IPropagatorBlock createExtractBlock( + IVersion version, + MinecraftPath path) { - var broadcaster = new BroadcastBlock(null); - foreach (var extractor in extractors) + IEnumerable fireTasksEvent(IEnumerable tasks) { - var block = new TransformManyBlock(async v => + foreach (var task in tasks) { - return await extractor.Extract(path, v); - }); - broadcaster.LinkTo(block, _linkOptions); - block.LinkTo(executor, _linkOptions); + if (_runningTasks.TryAdd(task.Name, TaskStatus.Queued)) + fireEvent(task.Name, TaskStatus.Queued); + yield return task; + } } - return broadcaster; + var block = new TransformManyBlock(async extractor => + { + var tasks = await extractor.Extract(path, version); + var iterated = fireTasksEvent(tasks); + return iterated; + }, new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = 2 + }); + + return block; } private void fireEvent(string name, TaskStatus status) { + //if (status != TaskStatus.Done) return; + //if (proceed % 100 != 0) return; var totalTasks = _runningTasks.Count; - var statusMsg = status == TaskStatus.Processing ? "START" : "END"; + var statusMsg = status == TaskStatus.Queued ? "START" : "END"; Console.WriteLine($"[{proceed}/{totalTasks}][{statusMsg}] {name}"); } } \ No newline at end of file diff --git a/src/Core/FileExtractors/AssetFileExtractor.cs b/src/Core/FileExtractors/AssetFileExtractor.cs index bef0b20..bc42efe 100644 --- a/src/Core/FileExtractors/AssetFileExtractor.cs +++ b/src/Core/FileExtractors/AssetFileExtractor.cs @@ -121,6 +121,7 @@ private IEnumerable extractFromIndex( await ms.CopyToAsync(fs); } // dispose immediately + ms.Position = 0; return ms; } } diff --git a/src/Core/FileExtractors/JavaFileExtractor.cs b/src/Core/FileExtractors/JavaFileExtractor.cs index 8dec9b4..104b064 100644 --- a/src/Core/FileExtractors/JavaFileExtractor.cs +++ b/src/Core/FileExtractors/JavaFileExtractor.cs @@ -137,7 +137,7 @@ private IEnumerable extractTasks(Stream stream, JavaVersion version) var file = createTask(value, name, filePath); if (file != null) - yield return file; // TODO: tryChmod + yield return file; } else { From 9ee9689c13594f6909e73bac09572f5d076fdb14 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Mon, 7 Aug 2023 10:42:16 +0000 Subject: [PATCH 060/137] fix TPLTaskExecutor completion task --- src/Core/Executors/TPLTaskExecutor.cs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Core/Executors/TPLTaskExecutor.cs b/src/Core/Executors/TPLTaskExecutor.cs index 8398f98..8415953 100644 --- a/src/Core/Executors/TPLTaskExecutor.cs +++ b/src/Core/Executors/TPLTaskExecutor.cs @@ -14,16 +14,21 @@ public class TPLTaskExecutor private enum TaskStatus { Queued, + Processing, Done } + private readonly DataflowLinkOptions _completeLinkOptions = new() + { + PropagateCompletion = true + }; private readonly ConcurrentDictionary _runningTasks = new(); private int proceed = 0; public void PrintStatus() { var runningTasks = _runningTasks - .Where(kv => kv.Value == TaskStatus.Queued) + .Where(kv => kv.Value != TaskStatus.Done) .Select(kv => kv.Key); foreach (var task in runningTasks) @@ -32,8 +37,6 @@ public void PrintStatus() } } - private readonly DataflowLinkOptions _linkOptions = new(); - public async ValueTask Install( IEnumerable extractors, MinecraftPath path, @@ -41,7 +44,7 @@ public async ValueTask Install( { var extractBlock = createExtractBlock(version, path); var executeBlock = createExecuteBlock(extractBlock.Completion); - extractBlock.LinkTo(executeBlock, _linkOptions); + extractBlock.LinkTo(executeBlock); await Task.WhenAll(extractors.Select(extractor => extractBlock.SendAsync(extractor))); extractBlock.Complete(); @@ -62,6 +65,7 @@ private BufferBlock createExecuteBlock(Task extractTask) Exception? exception = null; try { + fireEvent(task.Name, TaskStatus.Processing); nextTask = await task.Execute(); } catch (Exception ex) @@ -88,8 +92,8 @@ private BufferBlock createExecuteBlock(Task extractTask) MaxDegreeOfParallelism = 6 }); - buffer.LinkTo(executor, _linkOptions); - executor.LinkTo(buffer!, _linkOptions, t => t != null); + buffer.LinkTo(executor); + executor.LinkTo(buffer!, t => t != null); executor.LinkTo(DataflowBlock.NullTarget()); return buffer; @@ -104,8 +108,10 @@ IEnumerable fireTasksEvent(IEnumerable tasks) foreach (var task in tasks) { if (_runningTasks.TryAdd(task.Name, TaskStatus.Queued)) + { fireEvent(task.Name, TaskStatus.Queued); - yield return task; + yield return task; + } } } @@ -116,7 +122,7 @@ IEnumerable fireTasksEvent(IEnumerable tasks) return iterated; }, new ExecutionDataflowBlockOptions { - MaxDegreeOfParallelism = 2 + MaxDegreeOfParallelism = 6 }); return block; @@ -127,7 +133,7 @@ private void fireEvent(string name, TaskStatus status) //if (status != TaskStatus.Done) return; //if (proceed % 100 != 0) return; var totalTasks = _runningTasks.Count; - var statusMsg = status == TaskStatus.Queued ? "START" : "END"; - Console.WriteLine($"[{proceed}/{totalTasks}][{statusMsg}] {name}"); + var now = DateTime.Now.ToString("hh:mm:ss.fff"); + Console.WriteLine($"[{now}][{proceed}/{totalTasks}][{status}] {name}"); } } \ No newline at end of file From 2e18f515495dcbb7560b08614bfc9aaed08362f6 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 8 Aug 2023 11:15:20 +0000 Subject: [PATCH 061/137] add benchmark --- .gitignore | 1 + CmlLib.Core.sln | 6 ++ benchmark/CmlLib.Core.Benchmarks.csproj | 15 +++++ benchmark/DummyVersion.cs | 42 ++++++++++++++ benchmark/Program.cs | 14 +++++ benchmark/TPLTaskExecutorBenchmark.cs | 57 +++++++++++++++++++ benchmark/TestFileExtractor.cs | 74 +++++++++++++++++++++++++ src/Core/Executors/TPLTaskExecutor.cs | 63 ++++++++++++++------- 8 files changed, 253 insertions(+), 19 deletions(-) create mode 100644 benchmark/CmlLib.Core.Benchmarks.csproj create mode 100644 benchmark/DummyVersion.cs create mode 100644 benchmark/Program.cs create mode 100644 benchmark/TPLTaskExecutorBenchmark.cs create mode 100644 benchmark/TestFileExtractor.cs diff --git a/.gitignore b/.gitignore index 729528b..c2671c8 100644 --- a/.gitignore +++ b/.gitignore @@ -261,3 +261,4 @@ __pycache__/ *.pyc ./release +benchmark/BenchmarkDotNet.Artifacts/ diff --git a/CmlLib.Core.sln b/CmlLib.Core.sln index a3ce091..d79358d 100644 --- a/CmlLib.Core.sln +++ b/CmlLib.Core.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibCoreSample", "example EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibWinFormSample", "examples\winform\CmlLibWinFormSample.csproj", "{362D93FE-B624-451E-AAD9-D66EFBE90EC3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Benchmarks", "benchmark\CmlLib.Core.Benchmarks.csproj", "{99852557-DBA7-4E13-A883-4B535F0D33FB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +40,10 @@ Global {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {362D93FE-B624-451E-AAD9-D66EFBE90EC3}.Release|Any CPU.Build.0 = Release|Any CPU + {99852557-DBA7-4E13-A883-4B535F0D33FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99852557-DBA7-4E13-A883-4B535F0D33FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99852557-DBA7-4E13-A883-4B535F0D33FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99852557-DBA7-4E13-A883-4B535F0D33FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {C9F876F3-5579-4B3A-A808-17845BB9C744} = {6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE} diff --git a/benchmark/CmlLib.Core.Benchmarks.csproj b/benchmark/CmlLib.Core.Benchmarks.csproj new file mode 100644 index 0000000..d166cb8 --- /dev/null +++ b/benchmark/CmlLib.Core.Benchmarks.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + diff --git a/benchmark/DummyVersion.cs b/benchmark/DummyVersion.cs new file mode 100644 index 0000000..96e9f66 --- /dev/null +++ b/benchmark/DummyVersion.cs @@ -0,0 +1,42 @@ +using CmlLib.Core.Files; +using CmlLib.Core.Java; +using CmlLib.Core.Launcher; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class DummyVersion : IVersion +{ + public string Id => throw new NotImplementedException(); + + public string? InheritsFrom => throw new NotImplementedException(); + + public IVersion? ParentVersion { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public AssetMetadata? AssetIndex => throw new NotImplementedException(); + + public MFileMetadata? Client => throw new NotImplementedException(); + + public JavaVersion? JavaVersion => throw new NotImplementedException(); + + public MLibrary[] Libraries => throw new NotImplementedException(); + + public string? Jar => throw new NotImplementedException(); + + public MLogFileMetadata? Logging => throw new NotImplementedException(); + + public string? MainClass => throw new NotImplementedException(); + + public MArgument[] GameArguments => throw new NotImplementedException(); + + public MArgument[] JvmArguments => throw new NotImplementedException(); + + public DateTime ReleaseTime => throw new NotImplementedException(); + + public string? Type => throw new NotImplementedException(); + + public string? GetProperty(string key) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/benchmark/Program.cs b/benchmark/Program.cs new file mode 100644 index 0000000..e196b28 --- /dev/null +++ b/benchmark/Program.cs @@ -0,0 +1,14 @@ +using BenchmarkDotNet.Running; +using CmlLib.Core.Benchmarks; + +var summary = BenchmarkRunner.Run(); + +async Task once() +{ + var benchmark = new TPLTaskExecutorBenchmark(); + benchmark.GlobalSetup(); + benchmark.IterationSetup(); + await benchmark.Benchmark(); + benchmark.IterationCleanup(); + Console.WriteLine("Done"); +} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorBenchmark.cs b/benchmark/TPLTaskExecutorBenchmark.cs new file mode 100644 index 0000000..d1bf6a4 --- /dev/null +++ b/benchmark/TPLTaskExecutorBenchmark.cs @@ -0,0 +1,57 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using CmlLib.Core.Executors; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +[SimpleJob(RunStrategy.Monitoring, iterationCount: 10)] +public class TPLTaskExecutorBenchmark +{ + public static bool Verbose { get; set; } = false; + + private int parallelism = 6; + private int extractorCount = 4; + + private MinecraftPath MinecraftPath = new MinecraftPath(); + private IVersion DummyVersion = new DummyVersion(); + private TestFileExtractor[] Extractors; + private TPLTaskExecutor Executor; + + [GlobalSetup] + public void GlobalSetup() + { + } + + [IterationSetup] + public void IterationSetup() + { + Extractors = new TestFileExtractor[extractorCount]; + for (int i = 0; i < extractorCount; i++) + { + var path = Path.GetFullPath("./benchmark" + i); + Extractors[i] = new TestFileExtractor(path, 1024, 1024*1024/2); + Extractors[i].Setup(); + } + Executor = new TPLTaskExecutor(parallelism); + + if (Verbose) + Executor.Progress += (s, e) => e.Print(); + } + + [IterationCleanup] + public void IterationCleanup() + { + foreach (var item in Extractors) + { + item.Cleanup(); + } + } + + [Benchmark] + public async Task Benchmark() + { + await Executor.Install(Extractors, MinecraftPath, DummyVersion); + } +} \ No newline at end of file diff --git a/benchmark/TestFileExtractor.cs b/benchmark/TestFileExtractor.cs new file mode 100644 index 0000000..1ba0bd7 --- /dev/null +++ b/benchmark/TestFileExtractor.cs @@ -0,0 +1,74 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class TestFileExtractor : IFileExtractor +{ + private readonly string _path; + private readonly int _fileCount; + private readonly int _fileSize; + + public TestFileExtractor(string path, int fileCount, int fileSize) + { + _path = path; + _fileCount = fileCount; + _fileSize = fileSize; + } + + public void Setup() + { + Directory.CreateDirectory(_path); + + for (int i = 0; i < _fileCount; i++) + { + var dummyFilePath = Path.Combine(_path, i + ".dat"); + if (TPLTaskExecutorBenchmark.Verbose) + Console.WriteLine(dummyFilePath); + using var fs = File.Create(dummyFilePath); + + var bufferSize = 1024 * 8; + var buffer = new byte[bufferSize]; + var size = 0; + while (size < _fileSize) + { + Random.Shared.NextBytes(buffer); + fs.Write(buffer, 0, bufferSize); + size += bufferSize; + } + + } + } + + public void Cleanup() + { + foreach (var file in Directory.GetFiles(_path)) + { + File.Delete(file); + } + Directory.Delete(_path); + } + + public ValueTask> Extract(MinecraftPath path, IVersion version) + { + var result = extract(); + return new ValueTask>(result); + } + + private IEnumerable extract() + { + foreach (var filePath in Directory.GetFiles(_path)) + { + var file = new TaskFile(filePath) + { + Path = filePath, + Hash = "-" + }; + + + var task = new FileCheckTask(file); + yield return task; + } + } +} \ No newline at end of file diff --git a/src/Core/Executors/TPLTaskExecutor.cs b/src/Core/Executors/TPLTaskExecutor.cs index 8415953..29e32c5 100644 --- a/src/Core/Executors/TPLTaskExecutor.cs +++ b/src/Core/Executors/TPLTaskExecutor.cs @@ -1,7 +1,5 @@ -using System; using System.Collections.Concurrent; using System.Diagnostics; -using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; using CmlLib.Core.Tasks; @@ -9,20 +7,45 @@ namespace CmlLib.Core.Executors; -public class TPLTaskExecutor +public enum TaskStatus +{ + Queued, + Processing, + Done +} + +public class TaskExecutorEventArgs { - private enum TaskStatus + public TaskExecutorEventArgs(string name) => + Name = name; + + public int TotalTasks { get; set; } + public int ProceedTasks { get; set; } + public TaskStatus EventType { get; set; } + public string Name { get; set; } + + public void Print() { - Queued, - Processing, - Done + //if (status != TaskStatus.Done) return; + //if (proceed % 100 != 0) return; + var now = DateTime.Now.ToString("hh:mm:ss.fff"); + Console.WriteLine($"[{now}][{ProceedTasks}/{TotalTasks}][{EventType}] {Name}"); } +} + +public class TPLTaskExecutor +{ + private readonly int _maxParallelism; + private readonly ConcurrentDictionary _runningTasks; - private readonly DataflowLinkOptions _completeLinkOptions = new() + public TPLTaskExecutor(int parallelism) { - PropagateCompletion = true - }; - private readonly ConcurrentDictionary _runningTasks = new(); + _maxParallelism = parallelism; + _runningTasks = new ConcurrentDictionary(_maxParallelism, 2047); + } + + public event EventHandler? Progress; + private int totalTasks = 0; private int proceed = 0; public void PrintStatus() @@ -89,7 +112,7 @@ private BufferBlock createExecuteBlock(Task extractTask) return nextTask; }, new ExecutionDataflowBlockOptions { - MaxDegreeOfParallelism = 6 + MaxDegreeOfParallelism = _maxParallelism }); buffer.LinkTo(executor); @@ -109,8 +132,9 @@ IEnumerable fireTasksEvent(IEnumerable tasks) { if (_runningTasks.TryAdd(task.Name, TaskStatus.Queued)) { - fireEvent(task.Name, TaskStatus.Queued); yield return task; + Interlocked.Increment(ref totalTasks); + fireEvent(task.Name, TaskStatus.Queued); } } } @@ -122,7 +146,7 @@ IEnumerable fireTasksEvent(IEnumerable tasks) return iterated; }, new ExecutionDataflowBlockOptions { - MaxDegreeOfParallelism = 6 + MaxDegreeOfParallelism = _maxParallelism }); return block; @@ -130,10 +154,11 @@ IEnumerable fireTasksEvent(IEnumerable tasks) private void fireEvent(string name, TaskStatus status) { - //if (status != TaskStatus.Done) return; - //if (proceed % 100 != 0) return; - var totalTasks = _runningTasks.Count; - var now = DateTime.Now.ToString("hh:mm:ss.fff"); - Console.WriteLine($"[{now}][{proceed}/{totalTasks}][{status}] {name}"); + Progress?.Invoke(this, new TaskExecutorEventArgs(name) + { + EventType = status, + TotalTasks = totalTasks, + ProceedTasks = proceed + }); } } \ No newline at end of file From 90e44d529a3971ea2aaf572d133c86278206253a Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 10 Aug 2023 09:36:56 +0000 Subject: [PATCH 062/137] update Tasks --- benchmark/CmlLib.Core.Benchmarks.csproj | 3 +- benchmark/HashBenchmark.cs | 36 +++ benchmark/Program.cs | 2 +- examples/console/Program.cs | 2 +- src/Core/CMLauncher.cs | 1 - src/Core/FileExtractors/AssetFileExtractor.cs | 115 ++++---- src/Core/FileExtractors/JavaFileExtractor.cs | 102 +++---- .../FileExtractors/LibraryFileExtractor.cs | 2 + src/Core/Installer/MJava.cs | 10 +- src/Core/Launcher/MNative.cs | 2 +- src/Core/Tasks/ChmodTask.cs | 12 +- src/Core/Tasks/FileCheckTask.cs | 11 +- src/Core/Tasks/FileCopyTask.cs | 10 +- src/Core/Tasks/LZMADecompressTask.cs | 13 +- src/Core/Tasks/UnzipTask.cs | 11 +- src/Utils/IOUtil.cs | 265 +++++++++--------- src/Utils/SevenZipWrapper.cs | 50 ++-- src/Utils/SharpZip.cs | 67 ++--- 18 files changed, 347 insertions(+), 367 deletions(-) create mode 100644 benchmark/HashBenchmark.cs diff --git a/benchmark/CmlLib.Core.Benchmarks.csproj b/benchmark/CmlLib.Core.Benchmarks.csproj index d166cb8..f379087 100644 --- a/benchmark/CmlLib.Core.Benchmarks.csproj +++ b/benchmark/CmlLib.Core.Benchmarks.csproj @@ -9,7 +9,8 @@ - + + diff --git a/benchmark/HashBenchmark.cs b/benchmark/HashBenchmark.cs new file mode 100644 index 0000000..c71b5e6 --- /dev/null +++ b/benchmark/HashBenchmark.cs @@ -0,0 +1,36 @@ +using BenchmarkDotNet.Attributes; +using Standart.Hash.xxHash; + +namespace CmlLib.Core.Benchmarks; + +public class HashBenchmark +{ + private byte[] Data; + + [GlobalSetup] + public void GlobalSetup() + { + Data = new byte[1024*1024]; + Random.Shared.NextBytes(Data); + } + + [Benchmark] + public ulong TestXX() + { + return xxHash64.ComputeHash(Data, Data.Length); + } + + [Benchmark] + public byte[] TestMD5() + { + using var md5 = System.Security.Cryptography.MD5.Create(); + return md5.ComputeHash(Data); + } + + [Benchmark] + public byte[] TestSHA1() + { + using var sha1 = System.Security.Cryptography.SHA1.Create(); + return sha1.ComputeHash(Data); + } +} \ No newline at end of file diff --git a/benchmark/Program.cs b/benchmark/Program.cs index e196b28..995f27d 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,7 +1,7 @@ using BenchmarkDotNet.Running; using CmlLib.Core.Benchmarks; -var summary = BenchmarkRunner.Run(); +var summary = BenchmarkRunner.Run(); async Task once() { diff --git a/examples/console/Program.cs b/examples/console/Program.cs index f646d0c..6074096 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -75,7 +75,7 @@ async Task Start(MSession session) // } //} - var installer = new TPLTaskExecutor(); + var installer = new TPLTaskExecutor(6); var sw = new Stopwatch(); sw.Start(); diff --git a/src/Core/CMLauncher.cs b/src/Core/CMLauncher.cs index 15dae37..fe2e129 100644 --- a/src/Core/CMLauncher.cs +++ b/src/Core/CMLauncher.cs @@ -6,7 +6,6 @@ using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; using System.ComponentModel; -using System.Diagnostics; namespace CmlLib.Core; diff --git a/src/Core/FileExtractors/AssetFileExtractor.cs b/src/Core/FileExtractors/AssetFileExtractor.cs index bc42efe..3b49364 100644 --- a/src/Core/FileExtractors/AssetFileExtractor.cs +++ b/src/Core/FileExtractors/AssetFileExtractor.cs @@ -31,32 +31,65 @@ public string AssetServer public async ValueTask> Extract(MinecraftPath path, IVersion version) { - if (version.AssetIndex == null || string.IsNullOrEmpty(version.AssetIndex.Id)) + var assets = version.AssetIndex; + if (assets == null || + string.IsNullOrEmpty(assets.Id) || + string.IsNullOrEmpty(assets.Url)) return Enumerable.Empty(); - var assetIndex = await getAssetIndex(path, version.AssetIndex); - if (assetIndex == null) - return Enumerable.Empty(); + using var assetIndexStream = await createAssetIndexStream(path, assets); + var assetIndexJson = JsonDocument.Parse(assetIndexStream); + return extractFromAssetIndexJson(assetIndexJson, path, version); + } + + // Check index file validation and download + private async ValueTask createAssetIndexStream(MinecraftPath path, MFileMetadata assets) + { + Debug.Assert(!string.IsNullOrEmpty(assets?.Id)); + Debug.Assert(!string.IsNullOrEmpty(assets?.Url)); + + var indexFilePath = path.GetIndexFilePath(assets.Id); + + if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, true)) + { + return File.Open(indexFilePath, FileMode.Open); + } + else + { + IOUtil.CreateParentDirectory(indexFilePath); + + var ms = new MemoryStream(); + using (var res = await httpClient.GetStreamAsync(assets.Url)) + { + await res.CopyToAsync(ms); + } // dispose immediately + + using (var fs = File.Create(indexFilePath)) + { + await ms.CopyToAsync(fs); + } // dispose immediately - return extractFromIndex(path, version, assetIndex); + ms.Position = 0; + return ms; + } } - private IEnumerable extractFromIndex( - MinecraftPath path, IVersion version, Stream stream) + private IEnumerable extractFromAssetIndexJson( + JsonDocument _json, MinecraftPath path, IVersion version) { - using var s = stream; - using var doc = JsonDocument.Parse(s); - var root = doc.RootElement; + Debug.Assert(!string.IsNullOrEmpty(version.AssetIndex?.Id)); - var assetId = version.AssetIndex?.Id ?? "legacy"; + using var json = _json; + var root = json.RootElement; + + var assetId = version.AssetIndex.Id; var isVirtual = root.GetPropertyOrNull("virtual")?.GetBoolean() ?? false; var mapResource = root.GetPropertyOrNull("map_to_resources")?.GetBoolean() ?? false; - var objectsProp = root.GetPropertyOrNull("objects"); - if (!objectsProp.HasValue) + if (!root.TryGetProperty("objects", out var objectsProp)) yield break; - var objects = objectsProp.Value.EnumerateObject(); + var objects = objectsProp.EnumerateObject(); foreach (var prop in objects) { var hash = prop.Value.GetPropertyValue("hash"); @@ -90,58 +123,4 @@ private IEnumerable extractFromIndex( yield return checkTask; } } - - // Check index file validation and download - private async ValueTask getAssetIndex(MinecraftPath path, MFileMetadata? assets) - { - if (assets == null || - string.IsNullOrEmpty(assets.Id) || - string.IsNullOrEmpty(assets.Url)) - return null; - - var indexFilePath = path.GetIndexFilePath(assets.Id); - - if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, true)) - { - return File.Open(indexFilePath, FileMode.Open); - } - else - { - IOUtil.CreateParentDirectory(indexFilePath); - - var ms = new MemoryStream(); - - using (var res = await httpClient.GetStreamAsync(assets.Url)) - { - await res.CopyToAsync(ms); - } // dispose immediately - - using (var fs = File.Create(indexFilePath)) - { - await ms.CopyToAsync(fs); - } // dispose immediately - - ms.Position = 0; - return ms; - } - } - - private async Task assetCopy(string org, string des) - { - try - { - var orgFile = new FileInfo(org); - var desFile = new FileInfo(des); - - if (!desFile.Exists || orgFile.Length != desFile.Length) - { - IOUtil.CreateParentDirectory(des); - await IOUtil.CopyFileAsync(org, des); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - } } diff --git a/src/Core/FileExtractors/JavaFileExtractor.cs b/src/Core/FileExtractors/JavaFileExtractor.cs index 104b064..1de59e8 100644 --- a/src/Core/FileExtractors/JavaFileExtractor.cs +++ b/src/Core/FileExtractors/JavaFileExtractor.cs @@ -17,7 +17,7 @@ public class JavaFileExtractor : IFileExtractor public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; public JavaFileExtractor( - HttpClient httpClient, + HttpClient httpClient, IJavaPathResolver javaPathResolver, LauncherOSRule rule) { @@ -28,53 +28,36 @@ public JavaFileExtractor( public async ValueTask> Extract(MinecraftPath path, IVersion version) { - //if (!string.IsNullOrEmpty(version.JavaBinaryPath) && File.Exists(version.JavaBinaryPath)) - // return null - JavaVersion javaVersion; if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion.Value.Component)) - javaVersion = MinecraftJavaPathResolver.JreLegacyVersion; + return await extractFromJavaVersion(MinecraftJavaPathResolver.JreLegacyVersion); else - javaVersion = version.JavaVersion.Value; + return await extractFromJavaVersion(version.JavaVersion.Value); + } - // try three versions - // - latest - // - JreLegacyVersionName(jre-legacy) - // - legacy java (MJava) - try - { - // get all java version - using var response = await _httpClient.GetStreamAsync(JavaManifestServer); - using var jsonDocument = await JsonDocument.ParseAsync(response); + private async ValueTask> extractFromJavaVersion(JavaVersion javaVersion) + { + using var response = await _httpClient.GetStreamAsync(JavaManifestServer); + using var jsonDocument = JsonDocument.Parse(response); + var root = jsonDocument.RootElement; - var root = jsonDocument.RootElement; - var javaVersions = root.GetProperty(getJavaOSName()); // get os specific java version + if (!root.TryGetProperty(getJavaOSName(), out var javaVersionsForOS)) + return await legacyJavaChecker(); - var latestVersionUrl = getJavaUrl(javaVersions, javaVersion); - var legacyVersionUrl = getJavaUrl(javaVersions, MinecraftJavaPathResolver.JreLegacyVersion); + var currentVersionManifestUrl = getManifestUrl(javaVersionsForOS, javaVersion); - if (!string.IsNullOrEmpty(latestVersionUrl)) - { - var res = await _httpClient.GetStreamAsync(latestVersionUrl); - return extractTasks(res, javaVersion); - } - else if (!string.IsNullOrEmpty(legacyVersionUrl) && - javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) - { - var res = await _httpClient.GetStreamAsync(legacyVersionUrl); - return extractTasks(res, MinecraftJavaPathResolver.JreLegacyVersion); - } - else - { - throw new Exception(); - } - } - catch + if (!string.IsNullOrEmpty(currentVersionManifestUrl)) + return await extractFromManifestUrl(currentVersionManifestUrl, javaVersion); + else if (javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) { - return await legacyJavaChecker(); + var legacyVersionManifestUrl = getManifestUrl(javaVersionsForOS, MinecraftJavaPathResolver.JreLegacyVersion); + if (!string.IsNullOrEmpty(legacyVersionManifestUrl)) + return await extractFromManifestUrl(legacyVersionManifestUrl, javaVersion); } + + return await legacyJavaChecker(); } - private string getJavaOSName() + private string getJavaOSName() // TODO: mac arm { string osName = ""; @@ -100,10 +83,10 @@ private string getJavaOSName() return osName; } - private string? getJavaUrl(JsonElement element, JavaVersion javaVersion) + private string? getManifestUrl(JsonElement element, JavaVersion version) { return element - .GetPropertyOrNull(javaVersion.Component)? + .GetPropertyOrNull(version.Component)? .EnumerateArray() .FirstOrDefault() .GetPropertyOrNull("manifest")? @@ -111,19 +94,25 @@ private string getJavaOSName() .GetString(); } - // compare local files with `manifest` - private IEnumerable extractTasks(Stream stream, JavaVersion version) + private async ValueTask> extractFromManifestUrl(string manifestUrl, JavaVersion version) + { + using var res = await _httpClient.GetStreamAsync(manifestUrl); + var json = JsonDocument.Parse(res); // should be disposed after extraction + return extractFromManifestJson(json, version); + } + + private IEnumerable extractFromManifestJson( + JsonDocument _json, JavaVersion version) { + using var json = _json; + var manifest = json.RootElement; + var path = _javaPathResolver.GetJavaDirPath(version); - using var s = stream; - using var manifestDocument = JsonDocument.Parse(stream); - var manifest = manifestDocument.RootElement; - var files = manifestDocument.RootElement.GetPropertyOrNull("files"); - if (files == null) + if (!manifest.TryGetProperty("files", out var files)) yield break; - var objects = files.Value.EnumerateObject(); + var objects = files.EnumerateObject(); foreach (var prop in objects) { var name = prop.Name; @@ -180,7 +169,7 @@ private IEnumerable extractTasks(Stream stream, JavaVersion version) return checkTask; } - // legacy java checker that use MJava + // legacy java checker using MJava private async ValueTask> legacyJavaChecker() { var legacyJavaPath = _javaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersion); @@ -199,14 +188,13 @@ private async ValueTask> legacyJavaChecker() Path = lzmaPath }; - var download = new DownloadTask(file); - var decompressLZMA = new LZMADecompressTask(file.Name, lzmaPath, zipPath); - var unzip = new UnzipTask(file.Name, zipPath, legacyJavaPath); - var chmod = new ChmodTask(file.Name, mJava.GetBinaryPath(_os)); - - return new LinkedTask[] + var task = LinkedTask.LinkTasks(new LinkedTask[] { - LinkedTask.LinkTasks(download, decompressLZMA, unzip, chmod)! - }; + new DownloadTask(file), + new LZMADecompressTask(file.Name, lzmaPath, zipPath), + new UnzipTask(file.Name, zipPath, legacyJavaPath), + new ChmodTask(file.Name, mJava.GetBinaryPath(_os)) + }); + return new LinkedTask[] { task! }; } } \ No newline at end of file diff --git a/src/Core/FileExtractors/LibraryFileExtractor.cs b/src/Core/FileExtractors/LibraryFileExtractor.cs index 119f9b4..304b15d 100644 --- a/src/Core/FileExtractors/LibraryFileExtractor.cs +++ b/src/Core/FileExtractors/LibraryFileExtractor.cs @@ -44,6 +44,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version) private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary library) { + // java library (*.jar) var artifact = library.Artifact; if (artifact != null) { @@ -60,6 +61,7 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary yield return task; } + // native library (*.dll, *.so) var native = library.GetNativeLibrary(_rulesContext.OS); if (native != null) { diff --git a/src/Core/Installer/MJava.cs b/src/Core/Installer/MJava.cs index d6332b9..6262a6c 100644 --- a/src/Core/Installer/MJava.cs +++ b/src/Core/Installer/MJava.cs @@ -118,13 +118,7 @@ private void decompressJavaFile(string lzmaPath) string zippath = Path.Combine(Path.GetTempPath(), "jre.zip"); SevenZipWrapper.DecompressFileLZMA(lzmaPath, zippath); - var z = new SharpZip(zippath); - z.ProgressEvent += Z_ProgressEvent; - z.Unzip(RuntimeDirectory); - } - - private void Z_ProgressEvent(object? sender, int e) - { - pProgressChanged?.Report(new ProgressChangedEventArgs(50 + e / 2, null)); + SharpZipWrapper.Unzip(zippath, RuntimeDirectory, new Progress(p => + pProgressChanged?.Report(new ProgressChangedEventArgs(50 + p / 2, null)))); } } \ No newline at end of file diff --git a/src/Core/Launcher/MNative.cs b/src/Core/Launcher/MNative.cs index af6f474..28fb637 100644 --- a/src/Core/Launcher/MNative.cs +++ b/src/Core/Launcher/MNative.cs @@ -32,7 +32,7 @@ public string ExtractNatives() foreach (var libPath in nativeLibraries) { if (File.Exists(libPath)) - new SharpZip(libPath).Unzip(extractPath); + SharpZipWrapper.Unzip(libPath, extractPath, null); } return extractPath; diff --git a/src/Core/Tasks/ChmodTask.cs b/src/Core/Tasks/ChmodTask.cs index ee818a4..86cfc58 100644 --- a/src/Core/Tasks/ChmodTask.cs +++ b/src/Core/Tasks/ChmodTask.cs @@ -12,16 +12,8 @@ public ChmodTask(string name, string path) : base(name) => protected override ValueTask OnExecuted() { - try - { - if (MRule.OSName != MRule.Windows) - NativeMethods.Chmod(Path, NativeMethods.Chmod755); - } - catch (Exception e) - { - Debug.WriteLine(e); - } - + if (MRule.OSName != MRule.Windows) + NativeMethods.Chmod(Path, NativeMethods.Chmod755); return new ValueTask(NextTask); } } \ No newline at end of file diff --git a/src/Core/Tasks/FileCheckTask.cs b/src/Core/Tasks/FileCheckTask.cs index 5759688..700c996 100644 --- a/src/Core/Tasks/FileCheckTask.cs +++ b/src/Core/Tasks/FileCheckTask.cs @@ -26,7 +26,16 @@ public FileCheckTask(string name, string path, string hash) : base(name) protected override ValueTask OnExecutedWithResult() { - var result = IOUtil.CheckFileValidation(Path, Hash, true); + var result = checkFile(); return new ValueTask(result); } + + private bool checkFile() + { + if (!File.Exists(Path)) + return false; + + var fileHash = IOUtil.ComputeFileSHA1(Path); + return fileHash == Hash; + } } \ No newline at end of file diff --git a/src/Core/Tasks/FileCopyTask.cs b/src/Core/Tasks/FileCopyTask.cs index 4c103fc..cd2ef55 100644 --- a/src/Core/Tasks/FileCopyTask.cs +++ b/src/Core/Tasks/FileCopyTask.cs @@ -1,3 +1,5 @@ +using CmlLib.Utils; + namespace CmlLib.Core.Tasks; public class FileCopyTask : LinkedTask @@ -13,9 +15,15 @@ public FileCopyTask(string name, string sourcePath, IEnumerable destPath if (!File.Exists(SourcePath)) throw new InvalidOperationException("The source file does not exists"); + var orgFile = new FileInfo(SourcePath); foreach (var destination in DestinationPaths) { - File.Copy(SourcePath, destination); + var desFile = new FileInfo(destination); + if (!desFile.Exists || orgFile.Length != desFile.Length) + { + IOUtil.CreateParentDirectory(destination); + orgFile.CopyTo(desFile.FullName, true); + } } return new ValueTask(NextTask); diff --git a/src/Core/Tasks/LZMADecompressTask.cs b/src/Core/Tasks/LZMADecompressTask.cs index 61423c2..953c7ef 100644 --- a/src/Core/Tasks/LZMADecompressTask.cs +++ b/src/Core/Tasks/LZMADecompressTask.cs @@ -1,14 +1,21 @@ +using CmlLib.Utils; + namespace CmlLib.Core.Tasks; public class LZMADecompressTask : LinkedTask { - public LZMADecompressTask(string name, string lzmaPath, string decompressTo) : base(name) - { + public string LZMAPath { get; set; } + public string ExtractPath { get; set; } + public LZMADecompressTask(string name, string lzmaPath, string extractTo) : base(name) + { + LZMAPath = lzmaPath; + ExtractPath = extractTo; } protected override ValueTask OnExecuted() { - throw new NotImplementedException(); + SevenZipWrapper.DecompressFileLZMA(LZMAPath, ExtractPath); + return new ValueTask(); } } \ No newline at end of file diff --git a/src/Core/Tasks/UnzipTask.cs b/src/Core/Tasks/UnzipTask.cs index 8ac3b5b..7e8f0aa 100644 --- a/src/Core/Tasks/UnzipTask.cs +++ b/src/Core/Tasks/UnzipTask.cs @@ -1,14 +1,21 @@ +using CmlLib.Utils; + namespace CmlLib.Core.Tasks; public class UnzipTask : LinkedTask { + public string ZipPath { get; set; } + public string ExtractTo { get; set; } + public UnzipTask(string name, string zipPath, string unzipTo) : base(name) { - + ZipPath = zipPath; + ExtractTo = unzipTo; } protected override ValueTask OnExecuted() { - throw new NotImplementedException(); + SharpZipWrapper.Unzip(ZipPath, ExtractTo, null); + return new ValueTask(NextTask); } } \ No newline at end of file diff --git a/src/Utils/IOUtil.cs b/src/Utils/IOUtil.cs index 9b15cdf..a149462 100644 --- a/src/Utils/IOUtil.cs +++ b/src/Utils/IOUtil.cs @@ -1,167 +1,164 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text; -namespace CmlLib.Utils +namespace CmlLib.Utils; + +internal static class IOUtil { - internal static class IOUtil + // from dotnet core source code, default buffer size of file stream is 4096 + private const int DefaultBufferSize = 4096; + + public static void CreateParentDirectory(string filePath) { - // from dotnet core source code, default buffer size of file stream is 4096 - private const int DefaultBufferSize = 4096; + var dirPath = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(dirPath)) + Directory.CreateDirectory(dirPath); + } - public static void CreateParentDirectory(string filePath) - { - var dirPath = Path.GetDirectoryName(filePath); - if (!string.IsNullOrEmpty(dirPath)) - Directory.CreateDirectory(dirPath); - } + public static string NormalizePath(string path) + { + return Path.GetFullPath(path) + .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) + .TrimEnd(Path.DirectorySeparatorChar); + } - public static string NormalizePath(string path) - { - return Path.GetFullPath(path) - .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) - .TrimEnd(Path.DirectorySeparatorChar); - } + public static string CombinePath(IEnumerable paths) + { + return string.Join(Path.PathSeparator.ToString(), + paths.Select(x => + { + string path = Path.GetFullPath(x); + if (path.Contains(' ')) + return "\"" + path + "\""; + else + return path; + })); + } + + public static bool CheckSHA1(string path, string? compareHash) + { + if (string.IsNullOrEmpty(compareHash)) + return true; - public static string CombinePath(IEnumerable paths) + try { - return string.Join(Path.PathSeparator.ToString(), - paths.Select(x => - { - string path = Path.GetFullPath(x); - if (path.Contains(' ')) - return "\"" + path + "\""; - else - return path; - })); + var fileHash = ComputeFileSHA1(path); + return fileHash == compareHash; } - - public static bool CheckSHA1(string path, string? compareHash) + catch { - if (string.IsNullOrEmpty(compareHash)) - return true; - - try - { - string fileHash; + return false; + } + } + public static string ComputeFileSHA1(string path) + { #pragma warning disable CS0618 #pragma warning disable SYSLIB0021 - using (var file = File.OpenRead(path)) - using (var hasher = new System.Security.Cryptography.SHA1CryptoServiceProvider()) - { - var binaryHash = hasher.ComputeHash(file); - fileHash = BitConverter.ToString(binaryHash).Replace("-", "").ToLowerInvariant(); - } + using var file = File.OpenRead(path); + using var hasher = new System.Security.Cryptography.SHA1CryptoServiceProvider(); + + var binaryHash = hasher.ComputeHash(file); + return BitConverter.ToString(binaryHash).Replace("-", "").ToLowerInvariant(); + #pragma warning restore SYSLIB0021 #pragma warning restore CS0618 + } - return fileHash == compareHash; - } - catch - { - return false; - } - } + public static bool CheckFileValidation(string path, string? hash, bool checkHash) + { + if (!File.Exists(path)) + return false; - public static bool CheckFileValidation(string path, string? hash, bool checkHash) - { - if (!File.Exists(path)) - return false; + if (!checkHash) + return true; + else + return CheckSHA1(path, hash); + } - if (!checkHash) - return true; - else - return CheckSHA1(path, hash); - } + public static async Task CheckFileValidationAsync(string path, string? hash, bool checkHash) + { + if (!File.Exists(path)) + return false; - public static async Task CheckFileValidationAsync(string path, string? hash, bool checkHash) - { - if (!File.Exists(path)) - return false; + if (!checkHash) + return true; + else + return await Task.Run(() => CheckSHA1(path, hash)).ConfigureAwait(false); + } - if (!checkHash) - return true; - else - return await Task.Run(() => CheckSHA1(path, hash)).ConfigureAwait(false); - } + public static bool CheckFileValidation(string path, string hash, long size) + { + var file = new FileInfo(path); + return file.Exists && file.Length == size && CheckSHA1(path, hash); + } - public static bool CheckFileValidation(string path, string hash, long size) - { - var file = new FileInfo(path); - return file.Exists && file.Length == size && CheckSHA1(path, hash); - } + #region Async File IO - #region Async File IO + // from .NET Framework reference source code + // If we use the path-taking constructors we will not have FileOptions.Asynchronous set and + // we will have asynchronous file access faked by the thread pool. We want the real thing. + // https://source.dot.net/#System.Private.CoreLib/File.cs,c7e38336f651ba69 + public static FileStream AsyncReadStream(string path) + { + FileStream stream = new FileStream( + path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); - // from .NET Framework reference source code - // If we use the path-taking constructors we will not have FileOptions.Asynchronous set and - // we will have asynchronous file access faked by the thread pool. We want the real thing. - // https://source.dot.net/#System.Private.CoreLib/File.cs,c7e38336f651ba69 - public static FileStream AsyncReadStream(string path) - { - FileStream stream = new FileStream( - path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, - FileOptions.Asynchronous | FileOptions.SequentialScan); + return stream; + } - return stream; - } + // https://source.dot.net/#System.Private.CoreLib/File.cs,b5563532b5be50f6 + public static FileStream AsyncWriteStream(string path, bool append) + { + FileStream stream = new FileStream( + path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); - // https://source.dot.net/#System.Private.CoreLib/File.cs,b5563532b5be50f6 - public static FileStream AsyncWriteStream(string path, bool append) - { - FileStream stream = new FileStream( - path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultBufferSize, - FileOptions.Asynchronous | FileOptions.SequentialScan); + return stream; + } - return stream; - } + public static StreamReader AsyncStreamReader(string path, Encoding encoding) + { + FileStream stream = AsyncReadStream(path); + return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false); + } - public static StreamReader AsyncStreamReader(string path, Encoding encoding) - { - FileStream stream = AsyncReadStream(path); - return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false); - } + public static StreamWriter AsyncStreamWriter(string path, Encoding encoding, bool append) + { + FileStream stream = AsyncWriteStream(path, append); + return new StreamWriter(stream, encoding); + } - public static StreamWriter AsyncStreamWriter(string path, Encoding encoding, bool append) - { - FileStream stream = AsyncWriteStream(path, append); - return new StreamWriter(stream, encoding); - } + // In .NET Standard 2.0, There is no File.ReadFileTextAsync. so I copied it from .NET Core source code + public static async Task ReadFileAsync(string path) + { + using var reader = AsyncStreamReader(path, Encoding.UTF8); + var content = await reader.ReadToEndAsync() + .ConfigureAwait(false); // **MUST be awaited in this scope** + await reader.BaseStream.FlushAsync().ConfigureAwait(false); + return content; + } - // In .NET Standard 2.0, There is no File.ReadFileTextAsync. so I copied it from .NET Core source code - public static async Task ReadFileAsync(string path) - { - using var reader = AsyncStreamReader(path, Encoding.UTF8); - var content = await reader.ReadToEndAsync() - .ConfigureAwait(false); // **MUST be awaited in this scope** - await reader.BaseStream.FlushAsync().ConfigureAwait(false); - return content; - } + // In .NET Standard 2.0, There is no File.WriteFileTextAsync. so I copied it from .NET Core source code + public static async Task WriteFileAsync(string path, string content) + { + // UTF8 with BOM might not be recognized by minecraft. not tested + var encoder = new UTF8Encoding(false); + using var writer = AsyncStreamWriter(path, encoder, false); + await writer.WriteAsync(content).ConfigureAwait(false); // **MUST be awaited in this scope** + await writer.FlushAsync().ConfigureAwait(false); + } - // In .NET Standard 2.0, There is no File.WriteFileTextAsync. so I copied it from .NET Core source code - public static async Task WriteFileAsync(string path, string content) - { - // UTF8 with BOM might not be recognized by minecraft. not tested - var encoder = new UTF8Encoding(false); - using var writer = AsyncStreamWriter(path, encoder, false); - await writer.WriteAsync(content).ConfigureAwait(false); // **MUST be awaited in this scope** - await writer.FlushAsync().ConfigureAwait(false); - } - - public static async Task CopyFileAsync(string sourceFile, string destinationFile) - { - using var sourceStream = AsyncReadStream(sourceFile); - using var destinationStream = AsyncWriteStream(destinationFile, false); - - await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); - - await destinationStream.FlushAsync().ConfigureAwait(false); - } + public static async Task CopyFileAsync(string sourceFile, string destinationFile) + { + using var sourceStream = AsyncReadStream(sourceFile); + using var destinationStream = AsyncWriteStream(destinationFile, false); - #endregion + await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); + + await destinationStream.FlushAsync().ConfigureAwait(false); } + + #endregion } diff --git a/src/Utils/SevenZipWrapper.cs b/src/Utils/SevenZipWrapper.cs index e50da8b..0a43f79 100644 --- a/src/Utils/SevenZipWrapper.cs +++ b/src/Utils/SevenZipWrapper.cs @@ -1,36 +1,32 @@ -using System; -using System.IO; +namespace CmlLib.Utils; -namespace CmlLib.Utils +internal static class SevenZipWrapper { - internal static class SevenZipWrapper + public static void DecompressFileLZMA(string inFile, string outFile) { - public static void DecompressFileLZMA(string inFile, string outFile) - { - SevenZip.Compression.LZMA.Decoder coder = new SevenZip.Compression.LZMA.Decoder(); - FileStream input = new FileStream(inFile, FileMode.Open); - FileStream output = new FileStream(outFile, FileMode.Create); + SevenZip.Compression.LZMA.Decoder coder = new SevenZip.Compression.LZMA.Decoder(); + FileStream input = new FileStream(inFile, FileMode.Open); + FileStream output = new FileStream(outFile, FileMode.Create); - // Read the decoder properties - byte[] properties = new byte[5]; - var readCount = input.Read(properties, 0, 5); + // Read the decoder properties + byte[] properties = new byte[5]; + var readCount = input.Read(properties, 0, 5); - if (readCount < 5) - return; - - // Read in the decompress file size. - byte[] fileLengthBytes = new byte[8]; - readCount = input.Read(fileLengthBytes, 0, 8); + if (readCount < 5) + return; + + // Read in the decompress file size. + byte[] fileLengthBytes = new byte[8]; + readCount = input.Read(fileLengthBytes, 0, 8); - if (readCount < 8) - return; - - long fileLength = BitConverter.ToInt64(fileLengthBytes, 0); + if (readCount < 8) + return; + + long fileLength = BitConverter.ToInt64(fileLengthBytes, 0); - coder.SetDecoderProperties(properties); - coder.Code(input, output, input.Length, fileLength, null); - output.Flush(); - output.Close(); - } + coder.SetDecoderProperties(properties); + coder.Code(input, output, input.Length, fileLength, null); + output.Flush(); + output.Close(); } } diff --git a/src/Utils/SharpZip.cs b/src/Utils/SharpZip.cs index 42c89bc..6184f73 100644 --- a/src/Utils/SharpZip.cs +++ b/src/Utils/SharpZip.cs @@ -1,62 +1,27 @@ using ICSharpCode.SharpZipLib.Zip; -using System; -using System.IO; -namespace CmlLib.Utils +namespace CmlLib.Utils; + +internal static class SharpZipWrapper { - internal class SharpZip + public static void Unzip(string zipPath, string extractTo, IProgress? progress) { - public SharpZip(string path) - { - this.ZipPath = path; - } + using var fs = File.OpenRead(zipPath); + using var s = new ZipInputStream(fs); - public event EventHandler? ProgressEvent; - public string ZipPath { get; private set; } - - public void Unzip(string path) + long length = fs.Length; + ZipEntry e; + while ((e = s.GetNextEntry()) != null) { - using (var fs = File.OpenRead(ZipPath)) - using (var s = new ZipInputStream(fs)) - { - long length = fs.Length; - ZipEntry e; - while ((e = s.GetNextEntry()) != null) - { - var zfile = Path.Combine(path, e.Name); - - var dirName = Path.GetDirectoryName(zfile); - var fileName = Path.GetFileName(zfile); - - if (!string.IsNullOrWhiteSpace(dirName)) - Directory.CreateDirectory(dirName); + var fullPath = Path.Combine(extractTo, e.Name); + IOUtil.CreateParentDirectory(fullPath); + var fileName = Path.GetFileName(fullPath); - if (!string.IsNullOrWhiteSpace(fileName)) - { - using (var zFileStream = File.OpenWrite(zfile)) - { - s.CopyTo(zFileStream); - } - } - - ev(s.Position, length); - } - } - } - - private int previousPerc; - - private void ev(long curr, long total) - { - if (ProgressEvent == null) - return; + using var output = File.Create(fullPath); + s.CopyTo(output); - int progress = (int)(curr / (double)total * 100); - if (previousPerc != progress) - { - previousPerc = progress; - ProgressEvent.Invoke(this, progress); - } + int percent = (int)(s.Position / (double)length * 100); + progress?.Report(percent); } } } From 5e04ebe00237f93e8c3b87b53689ca74e16ae943 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 11 Aug 2023 11:34:34 +0000 Subject: [PATCH 063/137] update --- src/Core/CMLauncher.cs | 2 +- .../Downloader/AsyncParallelDownloader.cs | 13 +- .../Downloader/HttpClientDownloadHelper.cs | 126 ++++----- src/Core/Downloader/SequenceDownloader.cs | 8 +- src/Core/FileExtractors/AssetFileExtractor.cs | 2 +- src/Core/FileExtractors/JavaFileExtractor.cs | 42 ++- src/Core/Installer/MJava.cs | 10 +- src/Core/Java/MinecraftJavaPathResolver.cs | 2 +- src/Core/MRule.cs | 49 ---- src/Core/MinecraftPath.cs | 158 ++++++------ src/Core/Rules/LauncherOSRule.cs | 60 +++++ src/Core/Rules/LauncherRule.cs | 48 ---- src/Core/Rules/RulesEvaluatorContext.cs | 2 - src/Core/Tasks/ChmodTask.cs | 3 +- src/Core/Tasks/FileCheckTask.cs | 11 +- src/{ => Core}/Utils/Changelogs.cs | 3 +- src/Core/Utils/GameOptionsFile.cs | 238 +++++++++++++++++ .../VersionMetadata/JsonVersionMetadata.cs | 2 +- .../VersionMetadata/LocalVersionMetadata.cs | 2 +- src/Utils/AsyncIO.cs | 74 ++++++ src/Utils/GameOptionsFile.cs | 243 ------------------ src/Utils/IOUtil.cs | 113 +------- src/Utils/JarFile.cs | 78 ------ src/Utils/JsonUtil.cs | 2 +- src/Utils/NativeMethods.cs | 55 ++-- src/Utils/SemiVersion.cs | 177 +++++++------ src/Utils/{SharpZip.cs => SharpZipWrapper.cs} | 0 27 files changed, 687 insertions(+), 836 deletions(-) delete mode 100644 src/Core/MRule.cs create mode 100644 src/Core/Rules/LauncherOSRule.cs rename src/{ => Core}/Utils/Changelogs.cs (98%) create mode 100644 src/Core/Utils/GameOptionsFile.cs create mode 100644 src/Utils/AsyncIO.cs delete mode 100644 src/Utils/GameOptionsFile.cs delete mode 100644 src/Utils/JarFile.cs rename src/Utils/{SharpZip.cs => SharpZipWrapper.cs} (100%) diff --git a/src/Core/CMLauncher.cs b/src/Core/CMLauncher.cs index fe2e129..78d6f21 100644 --- a/src/Core/CMLauncher.cs +++ b/src/Core/CMLauncher.cs @@ -13,7 +13,7 @@ public class CMLauncher { private readonly HttpClient _httpClient; - public CMLauncher(string path, HttpClient httpClient) : this(new MinecraftPath(path), httpClient, new LauncherOSRule()) + public CMLauncher(string path, HttpClient httpClient) : this(new MinecraftPath(path), httpClient, LauncherOSRule.Current) { } diff --git a/src/Core/Downloader/AsyncParallelDownloader.cs b/src/Core/Downloader/AsyncParallelDownloader.cs index fb610d1..4abda1a 100644 --- a/src/Core/Downloader/AsyncParallelDownloader.cs +++ b/src/Core/Downloader/AsyncParallelDownloader.cs @@ -5,9 +5,9 @@ namespace CmlLib.Core.Downloader; public class AsyncParallelDownloader : IDownloader { - private readonly HttpClientDownloadHelper downloader; + private readonly HttpClient _httpClient; - public int MaxThread { get; private set; } + public int MaxParallelism { get; private set; } public bool IgnoreInvalidFiles { get; set; } = true; private int totalFiles; @@ -26,8 +26,8 @@ public class AsyncParallelDownloader : IDownloader public AsyncParallelDownloader(HttpClient httpClient, int parallelism = 10) { - MaxThread = parallelism; - downloader = new HttpClientDownloadHelper(httpClient); + _httpClient = httpClient; + MaxParallelism = parallelism; fileByteProgress = new Progress(byteProgressHandler); } @@ -60,7 +60,7 @@ public async Task DownloadFiles(DownloadFile[] files, fileProgress?.Report( new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); - await ForEachAsyncSemaphore(files, MaxThread, doDownload).ConfigureAwait(false); + await ForEachAsyncSemaphore(files, MaxParallelism, doDownload).ConfigureAwait(false); isRunning = false; } @@ -108,7 +108,8 @@ private async Task doDownload(DownloadFile file, int retry) { try { - await downloader.DownloadFileAsync(file, fileByteProgress).ConfigureAwait(false); + await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, file, fileByteProgress) + .ConfigureAwait(false); if (file.AfterDownload != null) { diff --git a/src/Core/Downloader/HttpClientDownloadHelper.cs b/src/Core/Downloader/HttpClientDownloadHelper.cs index 59f20d5..df9c0b3 100644 --- a/src/Core/Downloader/HttpClientDownloadHelper.cs +++ b/src/Core/Downloader/HttpClientDownloadHelper.cs @@ -1,81 +1,89 @@ using CmlLib.Utils; -using System; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -namespace CmlLib.Core.Downloader +namespace CmlLib.Core.Downloader; + +internal struct DownloadFileByteProgress +{ + public DownloadFile? File { get; set; } + public long TotalBytes { get; set; } + public long ProgressedBytes { get; set; } +} + +// To implement System.Net.WebClient.DownloadFileTaskAsync +// https://source.dot.net/#System.Net.WebClient/System/Net/WebClient.cs,c2224e40a6960929 +internal static class HttpClientDownloadHelper { - internal class DownloadFileByteProgress + private const int DefaultDownloadBufferLength = 65536; + + public static async Task DownloadFileAsync( + this HttpClient httpClient, + DownloadFile file, + IProgress? progress = null, + CancellationToken cancellationToken = default) { - public DownloadFile? File { get; set; } - public long TotalBytes { get; set; } - public long ProgressedBytes { get; set; } + IOUtil.CreateParentDirectory(file.Path); + using var destination = File.Create(file.Path); + await DownloadFileAsync( + httpClient, + file, + destination, + progress, + cancellationToken) + .ConfigureAwait(false); } - // To implement System.Net.WebClient.DownloadFileTaskAsync - // https://source.dot.net/#System.Net.WebClient/System/Net/WebClient.cs,c2224e40a6960929 - internal class HttpClientDownloadHelper + public static async Task DownloadFileAsync( + this HttpClient httpClient, + DownloadFile file, + Stream destination, + IProgress? progress = null, + CancellationToken cancellationToken = default) { - private const int DefaultDownloadBufferLength = 65536; + // Get the http headers first to examine the content length + using var response = await httpClient.GetAsync( + file.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - public HttpClientDownloadHelper(HttpClient client) - { - this.httpClient = client; - } + var contentLength = response.Content.Headers.ContentLength ?? file.Size; + using var download = await response.Content.ReadAsStreamAsync(); - private readonly HttpClient httpClient; + if (download.CanTimeout) + download.ReadTimeout = 10000; - public async Task DownloadFileAsync(DownloadFile file, - IProgress? progress = null, CancellationToken cancellationToken = default) + // Ignore progress reporting when no progress reporter was + // passed or when the content length is unknown + if (progress == null) { - IOUtil.CreateParentDirectory(file.Path); - using var destination = IOUtil.AsyncWriteStream(file.Path, false); - await DownloadFileAsync(file, destination, progress, cancellationToken).ConfigureAwait(false); + await download.CopyToAsync(destination); + return; } + + var bufferSize = (contentLength == -1 || contentLength > DefaultDownloadBufferLength) + ? DefaultDownloadBufferLength + : contentLength; + var copyBuffer = new byte[bufferSize]; - public async Task DownloadFileAsync(DownloadFile file, Stream destination, - IProgress? progress = null, CancellationToken cancellationToken = default) + while (true) { - // Get the http headers first to examine the content length - using var response = await httpClient.GetAsync(file.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - var contentLength = response.Content.Headers.ContentLength ?? file.Size; - - using var download = await response.Content.ReadAsStreamAsync(); - - // Ignore progress reporting when no progress reporter was - // passed or when the content length is unknown - if (progress == null) - { - await download.CopyToAsync(destination); + if (cancellationToken.IsCancellationRequested) return; - } - if (download.CanTimeout) - download.ReadTimeout = 10000; - - byte[] copyBuffer = new byte[contentLength == -1 || contentLength > DefaultDownloadBufferLength ? DefaultDownloadBufferLength : contentLength]; - while (true) - { - if (cancellationToken.IsCancellationRequested) - return; + int bytesRead = await download.ReadAsync( + copyBuffer, + 0, + copyBuffer.Length) + .ConfigureAwait(false); - int bytesRead = await download.ReadAsync(copyBuffer, 0, copyBuffer.Length).ConfigureAwait(false); - if (bytesRead == 0) - break; + if (bytesRead == 0) + break; - //await writeStream.WriteAsync(new ReadOnlyMemory(copyBuffer, 0, bytesRead)).ConfigureAwait(false); - //await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - await destination.WriteAsync(copyBuffer, 0, bytesRead).ConfigureAwait(false); + await destination.WriteAsync(copyBuffer, 0, bytesRead).ConfigureAwait(false); - progress?.Report(new DownloadFileByteProgress() - { - File = file, - TotalBytes = contentLength, - ProgressedBytes = bytesRead - }); - } + progress?.Report(new DownloadFileByteProgress() + { + File = file, + TotalBytes = contentLength, + ProgressedBytes = bytesRead + }); } } } diff --git a/src/Core/Downloader/SequenceDownloader.cs b/src/Core/Downloader/SequenceDownloader.cs index e9b675f..05b3e8c 100644 --- a/src/Core/Downloader/SequenceDownloader.cs +++ b/src/Core/Downloader/SequenceDownloader.cs @@ -4,13 +4,12 @@ namespace CmlLib.Core.Downloader; public class SequenceDownloader : IDownloader { - private readonly HttpClientDownloadHelper downloader; - + private readonly HttpClient _httpClient; public bool IgnoreInvalidFiles { get; set; } = true; public SequenceDownloader(HttpClient client) { - downloader = new HttpClientDownloadHelper(client); + _httpClient = client; } public async Task DownloadFiles(DownloadFile[] files, @@ -35,7 +34,8 @@ public async Task DownloadFiles(DownloadFile[] files, try { - await downloader.DownloadFileAsync(file, byteProgress).ConfigureAwait(false); + await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, file, byteProgress) + .ConfigureAwait(false); if (file.AfterDownload != null) { diff --git a/src/Core/FileExtractors/AssetFileExtractor.cs b/src/Core/FileExtractors/AssetFileExtractor.cs index 3b49364..1913785 100644 --- a/src/Core/FileExtractors/AssetFileExtractor.cs +++ b/src/Core/FileExtractors/AssetFileExtractor.cs @@ -50,7 +50,7 @@ private async ValueTask createAssetIndexStream(MinecraftPath path, MFile var indexFilePath = path.GetIndexFilePath(assets.Id); - if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1, true)) + if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1)) { return File.Open(indexFilePath, FileMode.Open); } diff --git a/src/Core/FileExtractors/JavaFileExtractor.cs b/src/Core/FileExtractors/JavaFileExtractor.cs index 1de59e8..cfc6746 100644 --- a/src/Core/FileExtractors/JavaFileExtractor.cs +++ b/src/Core/FileExtractors/JavaFileExtractor.cs @@ -40,7 +40,10 @@ private async ValueTask> extractFromJavaVersion(JavaVers using var jsonDocument = JsonDocument.Parse(response); var root = jsonDocument.RootElement; - if (!root.TryGetProperty(getJavaOSName(), out var javaVersionsForOS)) + var osName = getJavaOSName(); + if (string.IsNullOrEmpty(osName)) + return await legacyJavaChecker(); + if (!root.TryGetProperty(osName, out var javaVersionsForOS)) return await legacyJavaChecker(); var currentVersionManifestUrl = getManifestUrl(javaVersionsForOS, javaVersion); @@ -57,30 +60,23 @@ private async ValueTask> extractFromJavaVersion(JavaVers return await legacyJavaChecker(); } - private string getJavaOSName() // TODO: mac arm + private string? getJavaOSName() // TODO: find exact version name { - string osName = ""; - - if (_os.Name == MRule.Windows) - { - if (_os.Arch == "64") - osName = "windows-x64"; - else - osName = "windows-x86"; - } - else if (_os.Name == MRule.Linux) + return (LauncherOSRule.Current.Name, LauncherOSRule.Current.Arch) switch { - if (_os.Arch == "64") - osName = "linux"; - else - osName = "linux-i386"; - } - else if (_os.Name == MRule.OSX) - { - osName = "mac-os"; - } - - return osName; + (LauncherOSRule.Windows, "64") => "windows-x64", + (LauncherOSRule.Windows, "32") => "windows-x86", + (LauncherOSRule.Windows, _) => null, + (LauncherOSRule.Linux, "64") => "linux", + (LauncherOSRule.Linux, "32") => "linux-i386", + (LauncherOSRule.Linux, _) => null, + (LauncherOSRule.OSX, "64") => "mac-os", + (LauncherOSRule.OSX, "32") => "mac-os", + (LauncherOSRule.OSX, "arm") => "mac-os", // TODO + (LauncherOSRule.OSX, "arm64") => "mac-os", + (LauncherOSRule.OSX, _) => null, + (_, _) => null + }; } private string? getManifestUrl(JsonElement element, JavaVersion version) diff --git a/src/Core/Installer/MJava.cs b/src/Core/Installer/MJava.cs index 6262a6c..dc0c652 100644 --- a/src/Core/Installer/MJava.cs +++ b/src/Core/Installer/MJava.cs @@ -67,7 +67,7 @@ public async Task CheckJavaAsync(LauncherOSRule os, IProgress downloadJavaLzmaAsync(string javaUrl) Directory.CreateDirectory(RuntimeDirectory); string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - var downloader = new HttpClientDownloadHelper(_httpClient); var progress = new Progress(p => { var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); }); - await downloader.DownloadFileAsync(new DownloadFile(lzmaPath, javaUrl), progress); - + await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, new DownloadFile(lzmaPath, javaUrl), progress); return lzmaPath; } diff --git a/src/Core/Java/MinecraftJavaPathResolver.cs b/src/Core/Java/MinecraftJavaPathResolver.cs index 02dfd16..048a2ae 100644 --- a/src/Core/Java/MinecraftJavaPathResolver.cs +++ b/src/Core/Java/MinecraftJavaPathResolver.cs @@ -67,7 +67,7 @@ public string GetJavaDirPath(JavaVersion javaVersionName) public string GetJavaBinaryName(LauncherOSRule os) { string binaryName = "java"; - if (os.Name == MRule.Windows) + if (os.Name == LauncherOSRule.Windows) binaryName = "javaw.exe"; return binaryName; } diff --git a/src/Core/MRule.cs b/src/Core/MRule.cs deleted file mode 100644 index bbb7190..0000000 --- a/src/Core/MRule.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Text.Json; - -namespace CmlLib.Core -{ - public static class MRule - { - static MRule() - { - OSName = getOSName(); - - if (Environment.Is64BitOperatingSystem) - Arch = "64"; - else - Arch = "32"; - } - - public static readonly string Windows = "windows"; - public static readonly string OSX = "osx"; - public static readonly string Linux = "linux"; - - public static string OSName { get; private set; } - public static string Arch { get; private set; } - - private static string getOSName() - { - // RuntimeInformation does not work in .NET Framework -#if NETFRAMEWORK - var osType = Environment.OSVersion.Platform; - - if (osType == PlatformID.MacOSX) - return OSX; - else if (osType == PlatformID.Unix) - return Linux; - else - return Windows; -#else - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return OSX; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return Windows; - else - return Linux; -#endif - } - - } -} diff --git a/src/Core/MinecraftPath.cs b/src/Core/MinecraftPath.cs index 86ed4e5..ceb7a29 100644 --- a/src/Core/MinecraftPath.cs +++ b/src/Core/MinecraftPath.cs @@ -1,108 +1,106 @@ -using System; -using System.IO; +using CmlLib.Core.Rules; using CmlLib.Utils; -namespace CmlLib.Core +namespace CmlLib.Core; + +public class MinecraftPath { - public class MinecraftPath - { - public static readonly string - MacDefaultPath = Environment.GetEnvironmentVariable("HOME") + "/Library/Application Support/minecraft", - LinuxDefaultPath = Environment.GetEnvironmentVariable("HOME") + "/.minecraft", - WindowsDefaultPath = Environment.GetEnvironmentVariable("appdata") + "\\.minecraft"; + public static readonly string + MacDefaultPath = Environment.GetEnvironmentVariable("HOME") + "/Library/Application Support/minecraft", + LinuxDefaultPath = Environment.GetEnvironmentVariable("HOME") + "/.minecraft", + WindowsDefaultPath = Environment.GetEnvironmentVariable("appdata") + "\\.minecraft"; - public static string GetOSDefaultPath() + public static string GetOSDefaultPath() + { + switch (LauncherOSRule.Current.Name) { - switch (MRule.OSName) - { - case "osx": - return MacDefaultPath; - case "linux": - return LinuxDefaultPath; - case "windows": - return WindowsDefaultPath; - default: - return Environment.CurrentDirectory; - } + case "osx": + return MacDefaultPath; + case "linux": + return LinuxDefaultPath; + case "windows": + return WindowsDefaultPath; + default: + return Environment.CurrentDirectory; } + } - public MinecraftPath() : this(GetOSDefaultPath()) - { + public MinecraftPath() : this(GetOSDefaultPath()) + { - } + } - public MinecraftPath(string basePath) : this(basePath, basePath) - { + public MinecraftPath(string basePath) : this(basePath, basePath) + { - } + } - public MinecraftPath(string basePath, string basePathForAssets) - { - BasePath = NormalizePath(basePath); + public MinecraftPath(string basePath, string basePathForAssets) + { + BasePath = NormalizePath(basePath); - Library = NormalizePath(BasePath + "/libraries"); - Versions = NormalizePath(BasePath + "/versions"); - Resource = NormalizePath(BasePath + "/resources"); + Library = NormalizePath(BasePath + "/libraries"); + Versions = NormalizePath(BasePath + "/versions"); + Resource = NormalizePath(BasePath + "/resources"); - Runtime = NormalizePath(BasePath + "/runtime"); - Assets = NormalizePath(basePathForAssets + "/assets"); - } + Runtime = NormalizePath(BasePath + "/runtime"); + Assets = NormalizePath(basePathForAssets + "/assets"); + } - public void CreateDirs() - { - Dir(BasePath); - Dir(Library); - Dir(Versions); - Dir(Resource); - Dir(Runtime); - Dir(Assets); - } + public void CreateDirs() + { + Dir(BasePath); + Dir(Library); + Dir(Versions); + Dir(Resource); + Dir(Runtime); + Dir(Assets); + } - public string BasePath { get; set; } - public string Library { get; set; } - public string Versions { get; set; } - public string Resource { get; set; } - public string Assets { get; set; } - public string Runtime { get; set; } + public string BasePath { get; set; } + public string Library { get; set; } + public string Versions { get; set; } + public string Resource { get; set; } + public string Assets { get; set; } + public string Runtime { get; set; } - public virtual string GetIndexFilePath(string assetId) - => NormalizePath($"{Assets}/indexes/{assetId}.json"); + public virtual string GetIndexFilePath(string assetId) + => NormalizePath($"{Assets}/indexes/{assetId}.json"); - public virtual string GetAssetObjectPath(string assetId) - => NormalizePath($"{Assets}/objects"); + public virtual string GetAssetObjectPath(string assetId) + => NormalizePath($"{Assets}/objects"); - public virtual string GetAssetLegacyPath(string assetId) - => NormalizePath($"{Assets}/virtual/legacy"); + public virtual string GetAssetLegacyPath(string assetId) + => NormalizePath($"{Assets}/virtual/legacy"); - public virtual string GetLogConfigFilePath(string configId) - => NormalizePath($"{Assets}/log_configs/{configId}" + (!configId.EndsWith(".xml") ? ".xml" : "")); + public virtual string GetLogConfigFilePath(string configId) + => NormalizePath($"{Assets}/log_configs/{configId}" + (!configId.EndsWith(".xml") ? ".xml" : "")); - public virtual string GetVersionJarPath(string id) - => NormalizePath($"{Versions}/{id}/{id}.jar"); + public virtual string GetVersionJarPath(string id) + => NormalizePath($"{Versions}/{id}/{id}.jar"); - public virtual string GetVersionJsonPath(string id) - => NormalizePath($"{Versions}/{id}/{id}.json"); + public virtual string GetVersionJsonPath(string id) + => NormalizePath($"{Versions}/{id}/{id}.json"); - public virtual string GetNativePath(string id) - => NormalizePath($"{Versions}/{id}/natives"); + public virtual string GetNativePath(string id) + => NormalizePath($"{Versions}/{id}/natives"); - public override string ToString() - { - return BasePath; - } + public override string ToString() + { + return BasePath; + } - protected static string Dir(string path) - { - string p = NormalizePath(path); - if (!Directory.Exists(p)) - Directory.CreateDirectory(p); + protected static string Dir(string path) + { + string p = NormalizePath(path); + if (!Directory.Exists(p)) + Directory.CreateDirectory(p); - return p; - } + return p; + } - protected static string NormalizePath(string path) - { - return IOUtil.NormalizePath(path); - } + protected static string NormalizePath(string path) + { + return IOUtil.NormalizePath(path); } } diff --git a/src/Core/Rules/LauncherOSRule.cs b/src/Core/Rules/LauncherOSRule.cs new file mode 100644 index 0000000..da2e28b --- /dev/null +++ b/src/Core/Rules/LauncherOSRule.cs @@ -0,0 +1,60 @@ +using System.Runtime.InteropServices; +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Rules; + +public record LauncherOSRule +{ + public const string Windows = "windows"; + public const string OSX = "osx"; + public const string Linux = "linux"; + + private static LauncherOSRule? _current; + public static LauncherOSRule Current => _current ??= createCurrent(); + + private static LauncherOSRule createCurrent() + { + string name, arch; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + name = OSX; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + name = Windows; + else + name = Linux; + + arch = RuntimeInformation.OSArchitecture switch // TODO: find exact value + { + Architecture.X86 => "32", + Architecture.X64 => "64", + Architecture.Arm => "arm", + Architecture.Arm64 => "arm", + _ => "" + }; + + return new LauncherOSRule(name, arch); + } + + [JsonConstructor] + public LauncherOSRule(string name, string arch) => + (Name, Arch) = (name, arch); + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("arch")] + public string Arch { get; set; } + + public bool Match(LauncherOSRule toMatch) + { + return isPropMatch(Name, toMatch.Name) && + isPropMatch(Arch, toMatch.Arch); + } + + private bool isPropMatch(string? rule, string? toMatch) + { + if (string.IsNullOrEmpty(rule)) + return true; + return rule == toMatch; + } +} \ No newline at end of file diff --git a/src/Core/Rules/LauncherRule.cs b/src/Core/Rules/LauncherRule.cs index 277a478..c527da8 100644 --- a/src/Core/Rules/LauncherRule.cs +++ b/src/Core/Rules/LauncherRule.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using System.Text.Json.Serialization; namespace CmlLib.Core.Rules; @@ -38,51 +37,4 @@ public bool MatchFeatures(Dictionary toMatch) return true; } -} - -public record LauncherOSRule -{ - public static readonly string Windows = "windows"; - public static readonly string OSX = "osx"; - public static readonly string Linux = "linux"; - - public static LauncherOSRule CreateCurrent() - { - var os = new LauncherOSRule(); - if (Environment.Is64BitOperatingSystem) - os.Arch = "64"; - else - os.Arch = "32"; - os.Name = getOSName(); - return os; - } - - private static string getOSName() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return OSX; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return Windows; - else - return Linux; - } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("arch")] - public string? Arch { get; set; } - - public bool Match(LauncherOSRule toMatch) - { - return isPropMatch(Name, toMatch.Name) && - isPropMatch(Arch, toMatch.Arch); - } - - private bool isPropMatch(string? rule, string? toMatch) - { - if (string.IsNullOrEmpty(rule)) - return true; - return rule == toMatch; - } } \ No newline at end of file diff --git a/src/Core/Rules/RulesEvaluatorContext.cs b/src/Core/Rules/RulesEvaluatorContext.cs index c77ffa0..0f06cf5 100644 --- a/src/Core/Rules/RulesEvaluatorContext.cs +++ b/src/Core/Rules/RulesEvaluatorContext.cs @@ -1,5 +1,3 @@ -using System.Runtime.InteropServices; - namespace CmlLib.Core.Rules; public class RulesEvaluatorContext diff --git a/src/Core/Tasks/ChmodTask.cs b/src/Core/Tasks/ChmodTask.cs index 86cfc58..c704629 100644 --- a/src/Core/Tasks/ChmodTask.cs +++ b/src/Core/Tasks/ChmodTask.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using CmlLib.Core.Rules; using CmlLib.Utils; namespace CmlLib.Core.Tasks; @@ -12,7 +13,7 @@ public ChmodTask(string name, string path) : base(name) => protected override ValueTask OnExecuted() { - if (MRule.OSName != MRule.Windows) + if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) NativeMethods.Chmod(Path, NativeMethods.Chmod755); return new ValueTask(NextTask); } diff --git a/src/Core/Tasks/FileCheckTask.cs b/src/Core/Tasks/FileCheckTask.cs index 700c996..2621723 100644 --- a/src/Core/Tasks/FileCheckTask.cs +++ b/src/Core/Tasks/FileCheckTask.cs @@ -26,16 +26,7 @@ public FileCheckTask(string name, string path, string hash) : base(name) protected override ValueTask OnExecutedWithResult() { - var result = checkFile(); + var result = IOUtil.CheckFileValidation(Path, Hash); return new ValueTask(result); } - - private bool checkFile() - { - if (!File.Exists(Path)) - return false; - - var fileHash = IOUtil.ComputeFileSHA1(Path); - return fileHash == Hash; - } } \ No newline at end of file diff --git a/src/Utils/Changelogs.cs b/src/Core/Utils/Changelogs.cs similarity index 98% rename from src/Utils/Changelogs.cs rename to src/Core/Utils/Changelogs.cs index cccf5fb..af674ea 100644 --- a/src/Utils/Changelogs.cs +++ b/src/Core/Utils/Changelogs.cs @@ -1,7 +1,8 @@ using System.Text.Json; using System.Text.RegularExpressions; +using CmlLib.Utils; -namespace CmlLib.Utils; +namespace CmlLib.Core.Utils; public class Changelogs { diff --git a/src/Core/Utils/GameOptionsFile.cs b/src/Core/Utils/GameOptionsFile.cs new file mode 100644 index 0000000..5531d98 --- /dev/null +++ b/src/Core/Utils/GameOptionsFile.cs @@ -0,0 +1,238 @@ +using System.Collections; +using System.Globalization; +using System.Text; + +namespace CmlLib.Core.Utils; + +public class GameOptionsFile : IEnumerable> +{ + public static readonly int MaxOptionFileLength = 1024 * 1024; // 1MB + + public static GameOptionsFile ReadFile(string filepath) + { + // Default Encoding : OS + return ReadFile(filepath, Encoding.Default); + } + + public static GameOptionsFile ReadFile(string filepath, Encoding encoding) + { + var fileinfo = new FileInfo(filepath); + if (fileinfo.Length > MaxOptionFileLength) + throw new IOException("File is too big"); + + var optionDict = new Dictionary(); + + using (var fs = fileinfo.OpenRead()) + using (var reader = new StreamReader(fs, encoding)) + { + string? line; + while ((line = reader.ReadLine()) != null) + { + if (!line.Contains(":")) + optionDict[line] = null; + else + { + var keyvalue = FromKeyValueString(line); + optionDict[keyvalue.Key] = keyvalue.Value; + } + } + } + + return new GameOptionsFile(optionDict, filepath); + } + + public static KeyValuePair FromKeyValueString(string keyvalue) + { + var spliter = ':'; + var spliterIndex = keyvalue.IndexOf(spliter); + + var key = keyvalue.Substring(0, spliterIndex); + var value = keyvalue.Substring(spliterIndex + 1); + + return new KeyValuePair(key, value); + } + + public string? this[string key] => GetRawValue(key); + public string? FilePath { get; private set; } + private readonly Dictionary options; + + public GameOptionsFile() + { + this.options = new Dictionary(); + } + + public GameOptionsFile(Dictionary options) + { + this.options = options; + } + + public GameOptionsFile(Dictionary options, string path) + { + this.options = options; + this.FilePath = path; + } + + public bool ContainsKey(string key) + { + return options.ContainsKey(key); + } + + public string? GetRawValue(string key) + { + return options[key]; + } + + public string? GetValueAsString(string key) + { + string? value = GetRawValue(key); + if (value == null) + return null; + return value.Trim().Trim('\"'); + } + + public string[]? GetValueAsArray(string key) + { + string? value = GetRawValue(key); + if (value == null) + return null; + + return value + .Trim() + .TrimStart('[') + .TrimEnd(']') + .Split(',') + .Select(x => x.Trim().Trim('\"')) + .ToArray(); + } + + public int GetValueAsInt(string key) + { + string? value = GetRawValue(key); + if (value == null) + return 0; + + return int.Parse(value); + } + + public double GetValueAsDouble(string key) + { + string? value = GetRawValue(key); + if (value == null) + return 0; + + return double.Parse(value); + } + + public bool GetValueAsBool(string key) + { + string? value = GetRawValue(key); + if (value == null) + return false; + + return bool.Parse(value); + } + + public void SetRawValue(string key, string value) + { + options[key] = value; + } + + public void SetValue(string key, string value) + { + SetRawValue(key, handleEmpty(value)); + } + + public void SetValue(string key, int value) + { + SetRawValue(key, value.ToString()); + } + + public void SetValue(string key, double value) + { + SetRawValue(key, value.ToString(CultureInfo.InvariantCulture)); + } + + public void SetValue(string key, bool value) + { + SetRawValue(key, value.ToString().ToLowerInvariant()); + } + + public void SetValue(string key, string[] array) + { + var arrayNotation = string.Join(",", array.Select(x => $"\"{x}\"")); + SetRawValue(key, $"[{arrayNotation}]"); + } + + public void SetValue(string key, object obj) + { + var str = obj.ToString(); + if (str == null) + return; + SetValue(key, str); + } + + public void Save() + { + if (string.IsNullOrEmpty(FilePath)) + throw new InvalidOperationException("FilePath was null"); + + Save(FilePath); + } + + public void Save(string path) + { + // IMPORTANT: UTF8 with BOM could not be recognized by minecraft + Save(path, new UTF8Encoding(false)); + } + + public void Save(Encoding encoding) + { + if (string.IsNullOrEmpty(FilePath)) + throw new InvalidOperationException("FilePath was null"); + + Save(FilePath, encoding); + } + + public void Save(string path, Encoding encoding) + { + using (var fs = File.OpenWrite(path)) + using (var writer = new StreamWriter(fs, encoding)) + { + foreach (KeyValuePair keyvalue in options) + { + if (keyvalue.Value == null) + writer.WriteLine(keyvalue.Key); + else + { + var line = ToKeyValueString(keyvalue.Key, keyvalue.Value); + writer.WriteLine(line); + } + } + } + } + + private string ToKeyValueString(string key, string value, bool useHandleEmpty = true) + { + if (useHandleEmpty) + value = handleEmpty(value); + + return key + ":" + value; + } + + private static string handleEmpty(string value) + { + if (value.Contains(" ")) + value = "\"" + value + "\""; + return value; + } + + public IEnumerator> GetEnumerator() + { + return options.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/src/Core/VersionMetadata/JsonVersionMetadata.cs b/src/Core/VersionMetadata/JsonVersionMetadata.cs index 27f7a82..df4c2a4 100644 --- a/src/Core/VersionMetadata/JsonVersionMetadata.cs +++ b/src/Core/VersionMetadata/JsonVersionMetadata.cs @@ -80,6 +80,6 @@ private async Task saveMetdataJson(MinecraftPath path, string json) return; var metadataPath = path.GetVersionJsonPath(Name); IOUtil.CreateParentDirectory(metadataPath); - await IOUtil.WriteFileAsync(metadataPath, json).ConfigureAwait(false); + await AsyncIO.WriteFileAsync(metadataPath, json).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/Core/VersionMetadata/LocalVersionMetadata.cs b/src/Core/VersionMetadata/LocalVersionMetadata.cs index 87cac44..a5bbd2d 100644 --- a/src/Core/VersionMetadata/LocalVersionMetadata.cs +++ b/src/Core/VersionMetadata/LocalVersionMetadata.cs @@ -21,6 +21,6 @@ protected override async ValueTask GetVersionJsonString() throw new InvalidOperationException("Path property was null"); // FileNotFoundException will be thrown if Path does not exist. - return await IOUtil.ReadFileAsync(Path); + return await AsyncIO.ReadFileAsync(Path); } } \ No newline at end of file diff --git a/src/Utils/AsyncIO.cs b/src/Utils/AsyncIO.cs new file mode 100644 index 0000000..1e6d690 --- /dev/null +++ b/src/Utils/AsyncIO.cs @@ -0,0 +1,74 @@ +using System.Text; + +namespace CmlLib.Utils; + +internal static class AsyncIO +{ + // from dotnet core source code, default buffer size of file stream is 4096 + private const int DefaultBufferSize = 4096; + + // from .NET Framework reference source code + // If we use the path-taking constructors we will not have FileOptions.Asynchronous set and + // we will have asynchronous file access faked by the thread pool. We want the real thing. + // https://source.dot.net/#System.Private.CoreLib/File.cs,c7e38336f651ba69 + public static FileStream AsyncReadStream(string path) + { + FileStream stream = new FileStream( + path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); + + return stream; + } + + // https://source.dot.net/#System.Private.CoreLib/File.cs,b5563532b5be50f6 + public static FileStream AsyncWriteStream(string path, bool append) + { + FileStream stream = new FileStream( + path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); + + return stream; + } + + public static StreamReader AsyncStreamReader(string path, Encoding encoding) + { + FileStream stream = AsyncReadStream(path); + return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false); + } + + public static StreamWriter AsyncStreamWriter(string path, Encoding encoding, bool append) + { + FileStream stream = AsyncWriteStream(path, append); + return new StreamWriter(stream, encoding); + } + + // In .NET Standard 2.0, There is no File.ReadFileTextAsync. so I copied it from .NET Core source code + public static async Task ReadFileAsync(string path) + { + using var reader = AsyncStreamReader(path, Encoding.UTF8); + var content = await reader.ReadToEndAsync() + .ConfigureAwait(false); // **MUST be awaited in this scope** + await reader.BaseStream.FlushAsync().ConfigureAwait(false); + return content; + } + + // In .NET Standard 2.0, There is no File.WriteFileTextAsync. so I copied it from .NET Core source code + public static async Task WriteFileAsync(string path, string content) + { + // UTF8 with BOM might not be recognized by minecraft. not tested + var encoder = new UTF8Encoding(false); + using var writer = AsyncStreamWriter(path, encoder, false); + await writer.WriteAsync(content).ConfigureAwait(false); // **MUST be awaited in this scope** + await writer.FlushAsync().ConfigureAwait(false); + } + + public static async Task CopyFileAsync(string sourceFile, string destinationFile) + { + using var sourceStream = AsyncReadStream(sourceFile); + using var destinationStream = AsyncWriteStream(destinationFile, false); + + await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); + + await destinationStream.FlushAsync().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Utils/GameOptionsFile.cs b/src/Utils/GameOptionsFile.cs deleted file mode 100644 index 0bebee2..0000000 --- a/src/Utils/GameOptionsFile.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; - -namespace CmlLib.Utils -{ - public class GameOptionsFile : IEnumerable> - { - public static readonly int MaxOptionFileLength = 1024 * 1024; // 1MB - - public static GameOptionsFile ReadFile(string filepath) - { - // Default Encoding : OS - return ReadFile(filepath, Encoding.Default); - } - - public static GameOptionsFile ReadFile(string filepath, Encoding encoding) - { - var fileinfo = new FileInfo(filepath); - if (fileinfo.Length > MaxOptionFileLength) - throw new IOException("File is too big"); - - var optionDict = new Dictionary(); - - using (var fs = fileinfo.OpenRead()) - using (var reader = new StreamReader(fs, encoding)) - { - string? line; - while ((line = reader.ReadLine()) != null) - { - if (!line.Contains(":")) - optionDict[line] = null; - else - { - var keyvalue = FromKeyValueString(line); - optionDict[keyvalue.Key] = keyvalue.Value; - } - } - } - - return new GameOptionsFile(optionDict, filepath); - } - - public static KeyValuePair FromKeyValueString(string keyvalue) - { - var spliter = ':'; - var spliterIndex = keyvalue.IndexOf(spliter); - - var key = keyvalue.Substring(0, spliterIndex); - var value = keyvalue.Substring(spliterIndex + 1); - - return new KeyValuePair(key, value); - } - - public string? this[string key] => GetRawValue(key); - public string? FilePath { get; private set; } - private readonly Dictionary options; - - public GameOptionsFile() - { - this.options = new Dictionary(); - } - - public GameOptionsFile(Dictionary options) - { - this.options = options; - } - - public GameOptionsFile(Dictionary options, string path) - { - this.options = options; - this.FilePath = path; - } - - public bool ContainsKey(string key) - { - return options.ContainsKey(key); - } - - public string? GetRawValue(string key) - { - return options[key]; - } - - public string? GetValueAsString(string key) - { - string? value = GetRawValue(key); - if (value == null) - return null; - return value.Trim().Trim('\"'); - } - - public string[]? GetValueAsArray(string key) - { - string? value = GetRawValue(key); - if (value == null) - return null; - - return value - .Trim() - .TrimStart('[') - .TrimEnd(']') - .Split(',') - .Select(x => x.Trim().Trim('\"')) - .ToArray(); - } - - public int GetValueAsInt(string key) - { - string? value = GetRawValue(key); - if (value == null) - return 0; - - return int.Parse(value); - } - - public double GetValueAsDouble(string key) - { - string? value = GetRawValue(key); - if (value == null) - return 0; - - return double.Parse(value); - } - - public bool GetValueAsBool(string key) - { - string? value = GetRawValue(key); - if (value == null) - return false; - - return bool.Parse(value); - } - - public void SetRawValue(string key, string value) - { - options[key] = value; - } - - public void SetValue(string key, string value) - { - SetRawValue(key, handleEmpty(value)); - } - - public void SetValue(string key, int value) - { - SetRawValue(key, value.ToString()); - } - - public void SetValue(string key, double value) - { - SetRawValue(key, value.ToString(CultureInfo.InvariantCulture)); - } - - public void SetValue(string key, bool value) - { - SetRawValue(key, value.ToString().ToLowerInvariant()); - } - - public void SetValue(string key, string[] array) - { - var arrayNotation = string.Join(",", array.Select(x => $"\"{x}\"")); - SetRawValue(key, $"[{arrayNotation}]"); - } - - public void SetValue(string key, object obj) - { - var str = obj.ToString(); - if (str == null) - return; - SetValue(key, str); - } - - public void Save() - { - if (string.IsNullOrEmpty(FilePath)) - throw new InvalidOperationException("FilePath was null"); - - Save(FilePath); - } - - public void Save(string path) - { - // IMPORTANT: UTF8 with BOM could not be recognized by minecraft - Save(path, new UTF8Encoding(false)); - } - - public void Save(Encoding encoding) - { - if (string.IsNullOrEmpty(FilePath)) - throw new InvalidOperationException("FilePath was null"); - - Save(FilePath, encoding); - } - - public void Save(string path, Encoding encoding) - { - using (var fs = File.OpenWrite(path)) - using (var writer = new StreamWriter(fs, encoding)) - { - foreach (KeyValuePair keyvalue in options) - { - if (keyvalue.Value == null) - writer.WriteLine(keyvalue.Key); - else - { - var line = ToKeyValueString(keyvalue.Key, keyvalue.Value); - writer.WriteLine(line); - } - } - } - } - - private string ToKeyValueString(string key, string value, bool useHandleEmpty = true) - { - if (useHandleEmpty) - value = handleEmpty(value); - - return key + ":" + value; - } - - private static string handleEmpty(string value) - { - if (value.Contains(" ")) - value = "\"" + value + "\""; - return value; - } - - public IEnumerator> GetEnumerator() - { - return options.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/src/Utils/IOUtil.cs b/src/Utils/IOUtil.cs index a149462..ab93282 100644 --- a/src/Utils/IOUtil.cs +++ b/src/Utils/IOUtil.cs @@ -1,12 +1,7 @@ -using System.Text; - -namespace CmlLib.Utils; +namespace CmlLib.Utils; internal static class IOUtil { - // from dotnet core source code, default buffer size of file stream is 4096 - private const int DefaultBufferSize = 4096; - public static void CreateParentDirectory(string filePath) { var dirPath = Path.GetDirectoryName(filePath); @@ -34,6 +29,15 @@ public static string CombinePath(IEnumerable paths) })); } + public static bool CheckFileValidation(string path, string? compareHash) + { + if (!File.Exists(path)) + return false; + + var fileHash = IOUtil.ComputeFileSHA1(path); + return fileHash == compareHash; + } + public static bool CheckSHA1(string path, string? compareHash) { if (string.IsNullOrEmpty(compareHash)) @@ -64,101 +68,4 @@ public static string ComputeFileSHA1(string path) #pragma warning restore SYSLIB0021 #pragma warning restore CS0618 } - - public static bool CheckFileValidation(string path, string? hash, bool checkHash) - { - if (!File.Exists(path)) - return false; - - if (!checkHash) - return true; - else - return CheckSHA1(path, hash); - } - - public static async Task CheckFileValidationAsync(string path, string? hash, bool checkHash) - { - if (!File.Exists(path)) - return false; - - if (!checkHash) - return true; - else - return await Task.Run(() => CheckSHA1(path, hash)).ConfigureAwait(false); - } - - public static bool CheckFileValidation(string path, string hash, long size) - { - var file = new FileInfo(path); - return file.Exists && file.Length == size && CheckSHA1(path, hash); - } - - #region Async File IO - - // from .NET Framework reference source code - // If we use the path-taking constructors we will not have FileOptions.Asynchronous set and - // we will have asynchronous file access faked by the thread pool. We want the real thing. - // https://source.dot.net/#System.Private.CoreLib/File.cs,c7e38336f651ba69 - public static FileStream AsyncReadStream(string path) - { - FileStream stream = new FileStream( - path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, - FileOptions.Asynchronous | FileOptions.SequentialScan); - - return stream; - } - - // https://source.dot.net/#System.Private.CoreLib/File.cs,b5563532b5be50f6 - public static FileStream AsyncWriteStream(string path, bool append) - { - FileStream stream = new FileStream( - path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultBufferSize, - FileOptions.Asynchronous | FileOptions.SequentialScan); - - return stream; - } - - public static StreamReader AsyncStreamReader(string path, Encoding encoding) - { - FileStream stream = AsyncReadStream(path); - return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false); - } - - public static StreamWriter AsyncStreamWriter(string path, Encoding encoding, bool append) - { - FileStream stream = AsyncWriteStream(path, append); - return new StreamWriter(stream, encoding); - } - - // In .NET Standard 2.0, There is no File.ReadFileTextAsync. so I copied it from .NET Core source code - public static async Task ReadFileAsync(string path) - { - using var reader = AsyncStreamReader(path, Encoding.UTF8); - var content = await reader.ReadToEndAsync() - .ConfigureAwait(false); // **MUST be awaited in this scope** - await reader.BaseStream.FlushAsync().ConfigureAwait(false); - return content; - } - - // In .NET Standard 2.0, There is no File.WriteFileTextAsync. so I copied it from .NET Core source code - public static async Task WriteFileAsync(string path, string content) - { - // UTF8 with BOM might not be recognized by minecraft. not tested - var encoder = new UTF8Encoding(false); - using var writer = AsyncStreamWriter(path, encoder, false); - await writer.WriteAsync(content).ConfigureAwait(false); // **MUST be awaited in this scope** - await writer.FlushAsync().ConfigureAwait(false); - } - - public static async Task CopyFileAsync(string sourceFile, string destinationFile) - { - using var sourceStream = AsyncReadStream(sourceFile); - using var destinationStream = AsyncWriteStream(destinationFile, false); - - await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); - - await destinationStream.FlushAsync().ConfigureAwait(false); - } - - #endregion } diff --git a/src/Utils/JarFile.cs b/src/Utils/JarFile.cs deleted file mode 100644 index 6b4e611..0000000 --- a/src/Utils/JarFile.cs +++ /dev/null @@ -1,78 +0,0 @@ -using ICSharpCode.SharpZipLib.Zip; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace CmlLib.Utils -{ - public class JarFile - { - public JarFile(string path) - { - this.Path = path; - } - - public string Path { get; private set; } - - public Dictionary? GetManifest() - { - string? manifest = null; - - using (var fs = File.OpenRead(Path)) - using (var s = new ZipInputStream(fs)) - { - ZipEntry e; - while ((e = s.GetNextEntry()) != null) - { - if (e.Name == "META-INF/MANIFEST.MF") - { - manifest = readStreamString(s); - break; - } - } - } - - if (string.IsNullOrEmpty(manifest)) - return null; - - var dict = new Dictionary(); - foreach (var item in manifest.Split('\n')) - { - if (string.IsNullOrWhiteSpace(item)) - continue; - - var split = item.Split(':'); - - var key = split[0].Trim(); - - if (split.Length == 1) - dict.Add(key, null); - else if (split.Length == 2) - dict.Add(key, split[1].Trim()); - else - { - var value = string.Join(":", split, 1, split.Length - 1).Trim(); - dict.Add(key, value); - } - } - - return dict; - } - - private static string readStreamString(Stream s) - { - var str = new StringBuilder(); - var buffer = new byte[1024]; - while (true) - { - int size = s.Read(buffer, 0, buffer.Length); - if (size == 0) - break; - - str.Append(Encoding.UTF8.GetString(buffer, 0, size)); - } - - return str.ToString(); - } - } -} diff --git a/src/Utils/JsonUtil.cs b/src/Utils/JsonUtil.cs index a284e09..193c2f4 100644 --- a/src/Utils/JsonUtil.cs +++ b/src/Utils/JsonUtil.cs @@ -3,7 +3,7 @@ namespace CmlLib.Utils; -public static class JsonUtil +internal static class JsonUtil { public static JsonSerializerOptions JsonOptions { get; } = new JsonSerializerOptions { diff --git a/src/Utils/NativeMethods.cs b/src/Utils/NativeMethods.cs index 8860284..6617e03 100644 --- a/src/Utils/NativeMethods.cs +++ b/src/Utils/NativeMethods.cs @@ -3,35 +3,34 @@ // ReSharper disable UnusedMember.Local // ReSharper disable InconsistentNaming -namespace CmlLib.Utils +namespace CmlLib.Utils; + +internal static class NativeMethods { - internal static class NativeMethods + [DllImport("libc", SetLastError = true)] + private static extern int chmod(string pathname, int mode); + + // user permissions + private const int S_IRUSR = 0x100; + private const int S_IWUSR = 0x80; + private const int S_IXUSR = 0x40; + + // group permission + private const int S_IRGRP = 0x20; + private const int S_IWGRP = 0x10; + private const int S_IXGRP = 0x8; + + // other permissions + private const int S_IROTH = 0x4; + private const int S_IWOTH = 0x2; + private const int S_IXOTH = 0x1; + + public static readonly int Chmod755 = S_IRUSR | S_IXUSR | S_IWUSR + | S_IRGRP | S_IXGRP + | S_IROTH | S_IXOTH; + + public static void Chmod(string path, int mode) { - [DllImport("libc", SetLastError = true)] - private static extern int chmod(string pathname, int mode); - - // user permissions - private const int S_IRUSR = 0x100; - private const int S_IWUSR = 0x80; - private const int S_IXUSR = 0x40; - - // group permission - private const int S_IRGRP = 0x20; - private const int S_IWGRP = 0x10; - private const int S_IXGRP = 0x8; - - // other permissions - private const int S_IROTH = 0x4; - private const int S_IWOTH = 0x2; - private const int S_IXOTH = 0x1; - - public static readonly int Chmod755 = S_IRUSR | S_IXUSR | S_IWUSR - | S_IRGRP | S_IXGRP - | S_IROTH | S_IXOTH; - - public static void Chmod(string path, int mode) - { - chmod(path, mode); - } + chmod(path, mode); } } \ No newline at end of file diff --git a/src/Utils/SemiVersion.cs b/src/Utils/SemiVersion.cs index 9a681f6..d9ec479 100644 --- a/src/Utils/SemiVersion.cs +++ b/src/Utils/SemiVersion.cs @@ -1,113 +1,112 @@ -namespace CmlLib.Utils +namespace CmlLib.Utils; + +internal class SemiVersion { - public class SemiVersion + public bool IsPreRelease { get; private set; } + public int Major { get; private set; } + public int Minor { get; private set; } + public int Patch { get; private set; } + public string Tag { get; private set; } = ""; + + public int ToInt() { - public bool IsPreRelease { get; private set; } - public int Major { get; private set; } - public int Minor { get; private set; } - public int Patch { get; private set; } - public string Tag { get; private set; } = ""; + return Major * 100 + Minor * 10 + Patch; + } - public int ToInt() - { - return Major * 100 + Minor * 10 + Patch; - } + public override int GetHashCode() + { + return ToInt(); + } - public override int GetHashCode() - { - return ToInt(); - } + public override bool Equals(object? obj) + { + if (obj is SemiVersion ver) + return ToInt() == ver.ToInt(); + if (obj is int num) + return ToInt() == num; + if (obj is string verStr) + return ToInt() == Parse(verStr)?.ToInt(); + + return false; + } - public override bool Equals(object? obj) - { - if (obj is SemiVersion ver) - return ToInt() == ver.ToInt(); - if (obj is int num) - return ToInt() == num; - if (obj is string verStr) - return ToInt() == Parse(verStr)?.ToInt(); - - return false; - } + public override string ToString() + { + var ver = $"{Major}.{Minor}.{Patch}"; + if (IsPreRelease) + ver += "-pre"; + return ver; + } + + public static SemiVersion? Parse(string version) + { + var semiVer = new SemiVersion(); - public override string ToString() + version = version.Trim(); + if (char.IsLetter(version[0])) { - var ver = $"{Major}.{Minor}.{Patch}"; - if (IsPreRelease) - ver += "-pre"; - return ver; + semiVer.IsPreRelease = true; + version = version.Substring(1); } - public static SemiVersion? Parse(string version) - { - var semiVer = new SemiVersion(); + var verSplit = version.Split('.'); + if (verSplit.Length <= 1) + return null; - version = version.Trim(); - if (char.IsLetter(version[0])) + var last = verSplit[verSplit.Length - 1]; + var lastCharArr = last.ToCharArray(); + int versionEndPosition = 0; + for (; versionEndPosition < last.Length; versionEndPosition++) + { + if (!char.IsDigit(lastCharArr[versionEndPosition])) { - semiVer.IsPreRelease = true; - version = version.Substring(1); + if (versionEndPosition == 0) + return null; // invalid version + break; } + } - var verSplit = version.Split('.'); - if (verSplit.Length <= 1) - return null; - - var last = verSplit[verSplit.Length - 1]; - var lastCharArr = last.ToCharArray(); - int versionEndPosition = 0; - for (; versionEndPosition < last.Length; versionEndPosition++) - { - if (!char.IsDigit(lastCharArr[versionEndPosition])) - { - if (versionEndPosition == 0) - return null; // invalid version - break; - } - } + verSplit[verSplit.Length - 1] = last.Substring(0, versionEndPosition + 1); + + if (versionEndPosition > last.Length - 1) + semiVer.Tag = last.Substring(versionEndPosition + 1); - verSplit[verSplit.Length - 1] = last.Substring(0, versionEndPosition + 1); - - if (versionEndPosition > last.Length - 1) - semiVer.Tag = last.Substring(versionEndPosition + 1); + if (!int.TryParse(verSplit[0], out int major)) + return null; - if (!int.TryParse(verSplit[0], out int major)) - return null; + if (!int.TryParse(verSplit[1], out int minor)) + return null; - if (!int.TryParse(verSplit[1], out int minor)) - return null; + int patch = 0; + if (verSplit.Length > 2 && !int.TryParse(verSplit[2], out patch)) + return null; - int patch = 0; - if (verSplit.Length > 2 && !int.TryParse(verSplit[2], out patch)) - return null; + semiVer.Major = major; + semiVer.Minor = minor; + semiVer.Patch = patch; + return semiVer; + } - semiVer.Major = major; - semiVer.Minor = minor; - semiVer.Patch = patch; - return semiVer; - } + public static int operator <(SemiVersion v1, SemiVersion v2) + { + var v1Ver = v1.ToInt(); + var v2Ver = v2.ToInt(); - public static int operator <(SemiVersion v1, SemiVersion v2) + if (v1Ver == v2Ver) { - var v1Ver = v1.ToInt(); - var v2Ver = v2.ToInt(); - - if (v1Ver == v2Ver) - { - if (v1.IsPreRelease && v2.IsPreRelease) - return 0; - if (v1.IsPreRelease) - return -1; - if (v2.IsPreRelease) - return 1; - } - - return v1Ver - v2Ver; + if (v1.IsPreRelease && v2.IsPreRelease) + return 0; + if (v1.IsPreRelease) + return -1; + if (v2.IsPreRelease) + return 1; } - public static int operator >(SemiVersion v1, SemiVersion v2) - { - return (v1 < v2) * -1; - } + return v1Ver - v2Ver; + } + + public static int operator >(SemiVersion v1, SemiVersion v2) + { + return (v1 < v2) * -1; } } \ No newline at end of file diff --git a/src/Utils/SharpZip.cs b/src/Utils/SharpZipWrapper.cs similarity index 100% rename from src/Utils/SharpZip.cs rename to src/Utils/SharpZipWrapper.cs From bdef7e7c22000ba64dcd5291d64b37345c70beec Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 11 Aug 2023 11:40:03 +0000 Subject: [PATCH 064/137] update project structure --- src/{Core => }/Auth/MSession.cs | 0 src/{Core => }/CMLauncher.cs | 0 src/{CmlLib.csproj => CmlLib.Core.csproj} | 0 src/{Core => }/Downloader/AsyncParallelDownloader.cs | 0 src/{Core => }/Downloader/DownloadFile.cs | 0 src/{Core => }/Downloader/DownloadFileChangedEventArgs.cs | 0 src/{Core => }/Downloader/FileProgressChangedEventArgs.cs | 0 src/{Core => }/Downloader/HttpClientDownloadHelper.cs | 2 +- src/{Core => }/Downloader/IDownloader.cs | 0 src/{Core => }/Downloader/MDownloadFileException.cs | 0 src/{Core => }/Downloader/SequenceDownloader.cs | 0 src/{Core => }/Executors/TPLTaskExecutor.cs | 0 src/{Core => }/FileExtractors/AssetFileExtractor.cs | 2 +- src/{Core => }/FileExtractors/ClientFileExtractor.cs | 0 src/{Core => }/FileExtractors/FileCheckerCollection.cs | 0 src/{Core => }/FileExtractors/IFileExtractor.cs | 0 src/{Core => }/FileExtractors/JavaFileExtractor.cs | 2 +- src/{Core => }/FileExtractors/LibraryFileExtractor.cs | 0 src/{Core => }/FileExtractors/LogFileExtractor.cs | 0 src/{Core => }/Files/AssetMetadata.cs | 0 src/{Core => }/Files/MFileMetadata.cs | 0 src/{Core => }/Files/MLibrary.cs | 0 src/{Core => }/Files/MLogFileMetadata.cs | 0 src/{Core => }/Files/ModFile.cs | 0 src/{Core => }/Files/ModFileFactory.cs | 0 src/{Core => }/Installer/FabricMC/FabricLoader.cs | 0 src/{Core => }/Installer/FabricMC/FabricVersionLoader.cs | 2 +- src/{Core => }/Installer/LiteLoader/LiteLoaderInstaller.cs | 0 .../Installer/LiteLoader/LiteLoaderVersionLoader.cs | 2 +- .../Installer/LiteLoader/LiteLoaderVersionMetadata.cs | 2 +- src/{Core => }/Installer/MJava.cs | 4 ++-- src/{Core => }/Installer/QuiltMC/QuiltLoader.cs | 0 src/{Core => }/Installer/QuiltMC/QuiltVersionLoader.cs | 0 src/{Utils => Internals}/AsyncIO.cs | 2 +- src/{Utils => Internals}/IOUtil.cs | 2 +- src/{Utils => Internals}/JsonUtil.cs | 2 +- src/{Utils => Internals}/NativeMethods.cs | 2 +- src/{Utils => Internals}/SemiVersion.cs | 2 +- src/{Utils => Internals}/SevenZipWrapper.cs | 2 +- src/{Utils => Internals}/SharpZipWrapper.cs | 2 +- src/{Core => }/Java/IJavaPathResolver.cs | 0 src/{Core => }/Java/MinecraftJavaPathResolver.cs | 0 src/{Core => }/Java/MinecraftJavaVersion.cs | 0 src/{Core => }/Launcher/MArgument.cs | 0 src/{Core => }/Launcher/MLaunch.cs | 2 +- src/{Core => }/Launcher/MLaunchOption.cs | 0 src/{Core => }/Launcher/MNative.cs | 2 +- src/{Core => }/Mapper.cs | 0 src/{Core => }/MinecraftPath.cs | 2 +- src/{Core => }/MojangServer.cs | 0 src/{Core => }/PackageName.cs | 0 src/{Core => }/ProcessBuilder/MinecraftArgumentBuilder.cs | 0 src/{Core => }/ProcessBuilder/ProcessArgumentBuilder.cs | 0 src/{Core => }/ProcessBuilder/ProcessUtil.cs | 0 src/{Core => }/Rules/IRulesEvaluator.cs | 0 src/{Core => }/Rules/LauncherOSRule.cs | 0 src/{Core => }/Rules/LauncherRule.cs | 0 src/{Core => }/Rules/RulesEvaluator.cs | 0 src/{Core => }/Rules/RulesEvaluatorContext.cs | 0 src/{Core => }/Tasks/ActionTask.cs | 0 src/{Core => }/Tasks/ChmodTask.cs | 3 +-- src/{Core => }/Tasks/DownloadTask.cs | 0 src/{Core => }/Tasks/FileCheckTask.cs | 2 +- src/{Core => }/Tasks/FileCopyTask.cs | 2 +- src/{Core => }/Tasks/LZMADecompressTask.cs | 2 +- src/{Core => }/Tasks/LinkedTask.cs | 0 src/{Core => }/Tasks/ResultTask.cs | 0 src/{Core => }/Tasks/TaskFile.cs | 0 src/{Core => }/Tasks/UnzipTask.cs | 2 +- src/{Core => }/Utils/Changelogs.cs | 2 +- src/{Core => }/Utils/GameOptionsFile.cs | 0 src/{Core => }/Version/Extensions.cs | 0 src/{Core => }/Version/IVersion.cs | 0 src/{Core => }/Version/JsonArgumentParser.cs | 2 +- src/{Core => }/Version/JsonLibraryParser.cs | 2 +- src/{Core => }/Version/JsonRulesParser.cs | 0 src/{Core => }/Version/JsonVersion.cs | 0 src/{Core => }/Version/JsonVersionParser.cs | 0 src/{Core => }/Version/JsonVersionParserOptions.cs | 0 src/{Core => }/Version/MVersionParseException.cs | 0 src/{Core => }/Version/VersionJsonModel.cs | 0 src/{Core => }/VersionLoader/IVersionLoader.cs | 0 src/{Core => }/VersionLoader/LocalVersionLoader.cs | 0 src/{Core => }/VersionLoader/MojangVersionLoader.cs | 2 +- src/{Core => }/VersionLoader/VersionLoaderCollection.cs | 0 src/{Core => }/VersionMetadata/Extensions.cs | 0 src/{Core => }/VersionMetadata/IVersionMetadata.cs | 0 src/{Core => }/VersionMetadata/JsonVersionMetadata.cs | 2 +- src/{Core => }/VersionMetadata/JsonVersionMetadataModel.cs | 0 src/{Core => }/VersionMetadata/LocalVersionMetadata.cs | 2 +- src/{Core => }/VersionMetadata/MVersionCollection.cs | 0 src/{Core => }/VersionMetadata/MVersionMetadataSorter.cs | 0 src/{Core => }/VersionMetadata/MVersionType.cs | 0 src/{Core => }/VersionMetadata/MojangVersionMetadata.cs | 0 src/{Core => }/VersionMetadata/VersionSortOption.cs | 0 95 files changed, 29 insertions(+), 30 deletions(-) rename src/{Core => }/Auth/MSession.cs (100%) rename src/{Core => }/CMLauncher.cs (100%) rename src/{CmlLib.csproj => CmlLib.Core.csproj} (100%) rename src/{Core => }/Downloader/AsyncParallelDownloader.cs (100%) rename src/{Core => }/Downloader/DownloadFile.cs (100%) rename src/{Core => }/Downloader/DownloadFileChangedEventArgs.cs (100%) rename src/{Core => }/Downloader/FileProgressChangedEventArgs.cs (100%) rename src/{Core => }/Downloader/HttpClientDownloadHelper.cs (98%) rename src/{Core => }/Downloader/IDownloader.cs (100%) rename src/{Core => }/Downloader/MDownloadFileException.cs (100%) rename src/{Core => }/Downloader/SequenceDownloader.cs (100%) rename src/{Core => }/Executors/TPLTaskExecutor.cs (100%) rename src/{Core => }/FileExtractors/AssetFileExtractor.cs (99%) rename src/{Core => }/FileExtractors/ClientFileExtractor.cs (100%) rename src/{Core => }/FileExtractors/FileCheckerCollection.cs (100%) rename src/{Core => }/FileExtractors/IFileExtractor.cs (100%) rename src/{Core => }/FileExtractors/JavaFileExtractor.cs (99%) rename src/{Core => }/FileExtractors/LibraryFileExtractor.cs (100%) rename src/{Core => }/FileExtractors/LogFileExtractor.cs (100%) rename src/{Core => }/Files/AssetMetadata.cs (100%) rename src/{Core => }/Files/MFileMetadata.cs (100%) rename src/{Core => }/Files/MLibrary.cs (100%) rename src/{Core => }/Files/MLogFileMetadata.cs (100%) rename src/{Core => }/Files/ModFile.cs (100%) rename src/{Core => }/Files/ModFileFactory.cs (100%) rename src/{Core => }/Installer/FabricMC/FabricLoader.cs (100%) rename src/{Core => }/Installer/FabricMC/FabricVersionLoader.cs (98%) rename src/{Core => }/Installer/LiteLoader/LiteLoaderInstaller.cs (100%) rename src/{Core => }/Installer/LiteLoader/LiteLoaderVersionLoader.cs (98%) rename src/{Core => }/Installer/LiteLoader/LiteLoaderVersionMetadata.cs (99%) rename src/{Core => }/Installer/MJava.cs (98%) rename src/{Core => }/Installer/QuiltMC/QuiltLoader.cs (100%) rename src/{Core => }/Installer/QuiltMC/QuiltVersionLoader.cs (100%) rename src/{Utils => Internals}/AsyncIO.cs (98%) rename src/{Utils => Internals}/IOUtil.cs (98%) rename src/{Utils => Internals}/JsonUtil.cs (95%) rename src/{Utils => Internals}/NativeMethods.cs (96%) rename src/{Utils => Internals}/SemiVersion.cs (98%) rename src/{Utils => Internals}/SevenZipWrapper.cs (96%) rename src/{Utils => Internals}/SharpZipWrapper.cs (95%) rename src/{Core => }/Java/IJavaPathResolver.cs (100%) rename src/{Core => }/Java/MinecraftJavaPathResolver.cs (100%) rename src/{Core => }/Java/MinecraftJavaVersion.cs (100%) rename src/{Core => }/Launcher/MArgument.cs (100%) rename src/{Core => }/Launcher/MLaunch.cs (99%) rename src/{Core => }/Launcher/MLaunchOption.cs (100%) rename src/{Core => }/Launcher/MNative.cs (98%) rename src/{Core => }/Mapper.cs (100%) rename src/{Core => }/MinecraftPath.cs (99%) rename src/{Core => }/MojangServer.cs (100%) rename src/{Core => }/PackageName.cs (100%) rename src/{Core => }/ProcessBuilder/MinecraftArgumentBuilder.cs (100%) rename src/{Core => }/ProcessBuilder/ProcessArgumentBuilder.cs (100%) rename src/{Core => }/ProcessBuilder/ProcessUtil.cs (100%) rename src/{Core => }/Rules/IRulesEvaluator.cs (100%) rename src/{Core => }/Rules/LauncherOSRule.cs (100%) rename src/{Core => }/Rules/LauncherRule.cs (100%) rename src/{Core => }/Rules/RulesEvaluator.cs (100%) rename src/{Core => }/Rules/RulesEvaluatorContext.cs (100%) rename src/{Core => }/Tasks/ActionTask.cs (100%) rename src/{Core => }/Tasks/ChmodTask.cs (91%) rename src/{Core => }/Tasks/DownloadTask.cs (100%) rename src/{Core => }/Tasks/FileCheckTask.cs (96%) rename src/{Core => }/Tasks/FileCopyTask.cs (97%) rename src/{Core => }/Tasks/LZMADecompressTask.cs (94%) rename src/{Core => }/Tasks/LinkedTask.cs (100%) rename src/{Core => }/Tasks/ResultTask.cs (100%) rename src/{Core => }/Tasks/TaskFile.cs (100%) rename src/{Core => }/Tasks/UnzipTask.cs (94%) rename src/{Core => }/Utils/Changelogs.cs (99%) rename src/{Core => }/Utils/GameOptionsFile.cs (100%) rename src/{Core => }/Version/Extensions.cs (100%) rename src/{Core => }/Version/IVersion.cs (100%) rename src/{Core => }/Version/JsonArgumentParser.cs (98%) rename src/{Core => }/Version/JsonLibraryParser.cs (98%) rename src/{Core => }/Version/JsonRulesParser.cs (100%) rename src/{Core => }/Version/JsonVersion.cs (100%) rename src/{Core => }/Version/JsonVersionParser.cs (100%) rename src/{Core => }/Version/JsonVersionParserOptions.cs (100%) rename src/{Core => }/Version/MVersionParseException.cs (100%) rename src/{Core => }/Version/VersionJsonModel.cs (100%) rename src/{Core => }/VersionLoader/IVersionLoader.cs (100%) rename src/{Core => }/VersionLoader/LocalVersionLoader.cs (100%) rename src/{Core => }/VersionLoader/MojangVersionLoader.cs (98%) rename src/{Core => }/VersionLoader/VersionLoaderCollection.cs (100%) rename src/{Core => }/VersionMetadata/Extensions.cs (100%) rename src/{Core => }/VersionMetadata/IVersionMetadata.cs (100%) rename src/{Core => }/VersionMetadata/JsonVersionMetadata.cs (98%) rename src/{Core => }/VersionMetadata/JsonVersionMetadataModel.cs (100%) rename src/{Core => }/VersionMetadata/LocalVersionMetadata.cs (95%) rename src/{Core => }/VersionMetadata/MVersionCollection.cs (100%) rename src/{Core => }/VersionMetadata/MVersionMetadataSorter.cs (100%) rename src/{Core => }/VersionMetadata/MVersionType.cs (100%) rename src/{Core => }/VersionMetadata/MojangVersionMetadata.cs (100%) rename src/{Core => }/VersionMetadata/VersionSortOption.cs (100%) diff --git a/src/Core/Auth/MSession.cs b/src/Auth/MSession.cs similarity index 100% rename from src/Core/Auth/MSession.cs rename to src/Auth/MSession.cs diff --git a/src/Core/CMLauncher.cs b/src/CMLauncher.cs similarity index 100% rename from src/Core/CMLauncher.cs rename to src/CMLauncher.cs diff --git a/src/CmlLib.csproj b/src/CmlLib.Core.csproj similarity index 100% rename from src/CmlLib.csproj rename to src/CmlLib.Core.csproj diff --git a/src/Core/Downloader/AsyncParallelDownloader.cs b/src/Downloader/AsyncParallelDownloader.cs similarity index 100% rename from src/Core/Downloader/AsyncParallelDownloader.cs rename to src/Downloader/AsyncParallelDownloader.cs diff --git a/src/Core/Downloader/DownloadFile.cs b/src/Downloader/DownloadFile.cs similarity index 100% rename from src/Core/Downloader/DownloadFile.cs rename to src/Downloader/DownloadFile.cs diff --git a/src/Core/Downloader/DownloadFileChangedEventArgs.cs b/src/Downloader/DownloadFileChangedEventArgs.cs similarity index 100% rename from src/Core/Downloader/DownloadFileChangedEventArgs.cs rename to src/Downloader/DownloadFileChangedEventArgs.cs diff --git a/src/Core/Downloader/FileProgressChangedEventArgs.cs b/src/Downloader/FileProgressChangedEventArgs.cs similarity index 100% rename from src/Core/Downloader/FileProgressChangedEventArgs.cs rename to src/Downloader/FileProgressChangedEventArgs.cs diff --git a/src/Core/Downloader/HttpClientDownloadHelper.cs b/src/Downloader/HttpClientDownloadHelper.cs similarity index 98% rename from src/Core/Downloader/HttpClientDownloadHelper.cs rename to src/Downloader/HttpClientDownloadHelper.cs index df9c0b3..a07d5e3 100644 --- a/src/Core/Downloader/HttpClientDownloadHelper.cs +++ b/src/Downloader/HttpClientDownloadHelper.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Downloader; diff --git a/src/Core/Downloader/IDownloader.cs b/src/Downloader/IDownloader.cs similarity index 100% rename from src/Core/Downloader/IDownloader.cs rename to src/Downloader/IDownloader.cs diff --git a/src/Core/Downloader/MDownloadFileException.cs b/src/Downloader/MDownloadFileException.cs similarity index 100% rename from src/Core/Downloader/MDownloadFileException.cs rename to src/Downloader/MDownloadFileException.cs diff --git a/src/Core/Downloader/SequenceDownloader.cs b/src/Downloader/SequenceDownloader.cs similarity index 100% rename from src/Core/Downloader/SequenceDownloader.cs rename to src/Downloader/SequenceDownloader.cs diff --git a/src/Core/Executors/TPLTaskExecutor.cs b/src/Executors/TPLTaskExecutor.cs similarity index 100% rename from src/Core/Executors/TPLTaskExecutor.cs rename to src/Executors/TPLTaskExecutor.cs diff --git a/src/Core/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs similarity index 99% rename from src/Core/FileExtractors/AssetFileExtractor.cs rename to src/FileExtractors/AssetFileExtractor.cs index 1913785..67f2a79 100644 --- a/src/Core/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -3,7 +3,7 @@ using CmlLib.Core.Files; using CmlLib.Core.Tasks; using CmlLib.Core.Version; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.FileExtractors; diff --git a/src/Core/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs similarity index 100% rename from src/Core/FileExtractors/ClientFileExtractor.cs rename to src/FileExtractors/ClientFileExtractor.cs diff --git a/src/Core/FileExtractors/FileCheckerCollection.cs b/src/FileExtractors/FileCheckerCollection.cs similarity index 100% rename from src/Core/FileExtractors/FileCheckerCollection.cs rename to src/FileExtractors/FileCheckerCollection.cs diff --git a/src/Core/FileExtractors/IFileExtractor.cs b/src/FileExtractors/IFileExtractor.cs similarity index 100% rename from src/Core/FileExtractors/IFileExtractor.cs rename to src/FileExtractors/IFileExtractor.cs diff --git a/src/Core/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs similarity index 99% rename from src/Core/FileExtractors/JavaFileExtractor.cs rename to src/FileExtractors/JavaFileExtractor.cs index cfc6746..38dba26 100644 --- a/src/Core/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -5,7 +5,7 @@ using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.FileExtractors; diff --git a/src/Core/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs similarity index 100% rename from src/Core/FileExtractors/LibraryFileExtractor.cs rename to src/FileExtractors/LibraryFileExtractor.cs diff --git a/src/Core/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs similarity index 100% rename from src/Core/FileExtractors/LogFileExtractor.cs rename to src/FileExtractors/LogFileExtractor.cs diff --git a/src/Core/Files/AssetMetadata.cs b/src/Files/AssetMetadata.cs similarity index 100% rename from src/Core/Files/AssetMetadata.cs rename to src/Files/AssetMetadata.cs diff --git a/src/Core/Files/MFileMetadata.cs b/src/Files/MFileMetadata.cs similarity index 100% rename from src/Core/Files/MFileMetadata.cs rename to src/Files/MFileMetadata.cs diff --git a/src/Core/Files/MLibrary.cs b/src/Files/MLibrary.cs similarity index 100% rename from src/Core/Files/MLibrary.cs rename to src/Files/MLibrary.cs diff --git a/src/Core/Files/MLogFileMetadata.cs b/src/Files/MLogFileMetadata.cs similarity index 100% rename from src/Core/Files/MLogFileMetadata.cs rename to src/Files/MLogFileMetadata.cs diff --git a/src/Core/Files/ModFile.cs b/src/Files/ModFile.cs similarity index 100% rename from src/Core/Files/ModFile.cs rename to src/Files/ModFile.cs diff --git a/src/Core/Files/ModFileFactory.cs b/src/Files/ModFileFactory.cs similarity index 100% rename from src/Core/Files/ModFileFactory.cs rename to src/Files/ModFileFactory.cs diff --git a/src/Core/Installer/FabricMC/FabricLoader.cs b/src/Installer/FabricMC/FabricLoader.cs similarity index 100% rename from src/Core/Installer/FabricMC/FabricLoader.cs rename to src/Installer/FabricMC/FabricLoader.cs diff --git a/src/Core/Installer/FabricMC/FabricVersionLoader.cs b/src/Installer/FabricMC/FabricVersionLoader.cs similarity index 98% rename from src/Core/Installer/FabricMC/FabricVersionLoader.cs rename to src/Installer/FabricMC/FabricVersionLoader.cs index 1eae7a8..c8a8daf 100644 --- a/src/Core/Installer/FabricMC/FabricVersionLoader.cs +++ b/src/Installer/FabricMC/FabricVersionLoader.cs @@ -1,7 +1,7 @@ using System.Text.Json; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Installer.FabricMC; diff --git a/src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs b/src/Installer/LiteLoader/LiteLoaderInstaller.cs similarity index 100% rename from src/Core/Installer/LiteLoader/LiteLoaderInstaller.cs rename to src/Installer/LiteLoader/LiteLoaderInstaller.cs diff --git a/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs b/src/Installer/LiteLoader/LiteLoaderVersionLoader.cs similarity index 98% rename from src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs rename to src/Installer/LiteLoader/LiteLoaderVersionLoader.cs index f2f485c..6fbf188 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderVersionLoader.cs +++ b/src/Installer/LiteLoader/LiteLoaderVersionLoader.cs @@ -1,7 +1,7 @@ using System.Text.Json; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Installer.LiteLoader; diff --git a/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/src/Installer/LiteLoader/LiteLoaderVersionMetadata.cs similarity index 99% rename from src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs rename to src/Installer/LiteLoader/LiteLoaderVersionMetadata.cs index 3952431..f538e73 100644 --- a/src/Core/Installer/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/src/Installer/LiteLoader/LiteLoaderVersionMetadata.cs @@ -2,7 +2,7 @@ using CmlLib.Core.Launcher; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Installer.LiteLoader; diff --git a/src/Core/Installer/MJava.cs b/src/Installer/MJava.cs similarity index 98% rename from src/Core/Installer/MJava.cs rename to src/Installer/MJava.cs index dc0c652..416e5ba 100644 --- a/src/Core/Installer/MJava.cs +++ b/src/Installer/MJava.cs @@ -1,10 +1,10 @@ -using CmlLib.Utils; -using System.ComponentModel; +using System.ComponentModel; using CmlLib.Core.Java; using System.Text.Json; using System.Net; using CmlLib.Core.Downloader; using CmlLib.Core.Rules; +using CmlLib.Core.Internals; namespace CmlLib.Core.Installer; diff --git a/src/Core/Installer/QuiltMC/QuiltLoader.cs b/src/Installer/QuiltMC/QuiltLoader.cs similarity index 100% rename from src/Core/Installer/QuiltMC/QuiltLoader.cs rename to src/Installer/QuiltMC/QuiltLoader.cs diff --git a/src/Core/Installer/QuiltMC/QuiltVersionLoader.cs b/src/Installer/QuiltMC/QuiltVersionLoader.cs similarity index 100% rename from src/Core/Installer/QuiltMC/QuiltVersionLoader.cs rename to src/Installer/QuiltMC/QuiltVersionLoader.cs diff --git a/src/Utils/AsyncIO.cs b/src/Internals/AsyncIO.cs similarity index 98% rename from src/Utils/AsyncIO.cs rename to src/Internals/AsyncIO.cs index 1e6d690..58edb78 100644 --- a/src/Utils/AsyncIO.cs +++ b/src/Internals/AsyncIO.cs @@ -1,6 +1,6 @@ using System.Text; -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal static class AsyncIO { diff --git a/src/Utils/IOUtil.cs b/src/Internals/IOUtil.cs similarity index 98% rename from src/Utils/IOUtil.cs rename to src/Internals/IOUtil.cs index ab93282..bd086e5 100644 --- a/src/Utils/IOUtil.cs +++ b/src/Internals/IOUtil.cs @@ -1,4 +1,4 @@ -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal static class IOUtil { diff --git a/src/Utils/JsonUtil.cs b/src/Internals/JsonUtil.cs similarity index 95% rename from src/Utils/JsonUtil.cs rename to src/Internals/JsonUtil.cs index 193c2f4..2ef19d7 100644 --- a/src/Utils/JsonUtil.cs +++ b/src/Internals/JsonUtil.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal static class JsonUtil { diff --git a/src/Utils/NativeMethods.cs b/src/Internals/NativeMethods.cs similarity index 96% rename from src/Utils/NativeMethods.cs rename to src/Internals/NativeMethods.cs index 6617e03..27fbf8a 100644 --- a/src/Utils/NativeMethods.cs +++ b/src/Internals/NativeMethods.cs @@ -3,7 +3,7 @@ // ReSharper disable UnusedMember.Local // ReSharper disable InconsistentNaming -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal static class NativeMethods { diff --git a/src/Utils/SemiVersion.cs b/src/Internals/SemiVersion.cs similarity index 98% rename from src/Utils/SemiVersion.cs rename to src/Internals/SemiVersion.cs index d9ec479..6c64675 100644 --- a/src/Utils/SemiVersion.cs +++ b/src/Internals/SemiVersion.cs @@ -1,4 +1,4 @@ -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal class SemiVersion { diff --git a/src/Utils/SevenZipWrapper.cs b/src/Internals/SevenZipWrapper.cs similarity index 96% rename from src/Utils/SevenZipWrapper.cs rename to src/Internals/SevenZipWrapper.cs index 0a43f79..450717a 100644 --- a/src/Utils/SevenZipWrapper.cs +++ b/src/Internals/SevenZipWrapper.cs @@ -1,4 +1,4 @@ -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal static class SevenZipWrapper { diff --git a/src/Utils/SharpZipWrapper.cs b/src/Internals/SharpZipWrapper.cs similarity index 95% rename from src/Utils/SharpZipWrapper.cs rename to src/Internals/SharpZipWrapper.cs index 6184f73..5c61572 100644 --- a/src/Utils/SharpZipWrapper.cs +++ b/src/Internals/SharpZipWrapper.cs @@ -1,6 +1,6 @@ using ICSharpCode.SharpZipLib.Zip; -namespace CmlLib.Utils; +namespace CmlLib.Core.Internals; internal static class SharpZipWrapper { diff --git a/src/Core/Java/IJavaPathResolver.cs b/src/Java/IJavaPathResolver.cs similarity index 100% rename from src/Core/Java/IJavaPathResolver.cs rename to src/Java/IJavaPathResolver.cs diff --git a/src/Core/Java/MinecraftJavaPathResolver.cs b/src/Java/MinecraftJavaPathResolver.cs similarity index 100% rename from src/Core/Java/MinecraftJavaPathResolver.cs rename to src/Java/MinecraftJavaPathResolver.cs diff --git a/src/Core/Java/MinecraftJavaVersion.cs b/src/Java/MinecraftJavaVersion.cs similarity index 100% rename from src/Core/Java/MinecraftJavaVersion.cs rename to src/Java/MinecraftJavaVersion.cs diff --git a/src/Core/Launcher/MArgument.cs b/src/Launcher/MArgument.cs similarity index 100% rename from src/Core/Launcher/MArgument.cs rename to src/Launcher/MArgument.cs diff --git a/src/Core/Launcher/MLaunch.cs b/src/Launcher/MLaunch.cs similarity index 99% rename from src/Core/Launcher/MLaunch.cs rename to src/Launcher/MLaunch.cs index 00a94da..2ee18b7 100644 --- a/src/Core/Launcher/MLaunch.cs +++ b/src/Launcher/MLaunch.cs @@ -2,7 +2,7 @@ using CmlLib.Core.ProcessBuilder; using CmlLib.Core.Rules; using CmlLib.Core.Version; -using CmlLib.Utils; +using CmlLib.Core.Internals; using System.Diagnostics; namespace CmlLib.Core; diff --git a/src/Core/Launcher/MLaunchOption.cs b/src/Launcher/MLaunchOption.cs similarity index 100% rename from src/Core/Launcher/MLaunchOption.cs rename to src/Launcher/MLaunchOption.cs diff --git a/src/Core/Launcher/MNative.cs b/src/Launcher/MNative.cs similarity index 98% rename from src/Core/Launcher/MNative.cs rename to src/Launcher/MNative.cs index 28fb637..360653e 100644 --- a/src/Core/Launcher/MNative.cs +++ b/src/Launcher/MNative.cs @@ -1,6 +1,6 @@ using CmlLib.Core.Rules; using CmlLib.Core.Version; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core; diff --git a/src/Core/Mapper.cs b/src/Mapper.cs similarity index 100% rename from src/Core/Mapper.cs rename to src/Mapper.cs diff --git a/src/Core/MinecraftPath.cs b/src/MinecraftPath.cs similarity index 99% rename from src/Core/MinecraftPath.cs rename to src/MinecraftPath.cs index ceb7a29..088e7ce 100644 --- a/src/Core/MinecraftPath.cs +++ b/src/MinecraftPath.cs @@ -1,5 +1,5 @@ using CmlLib.Core.Rules; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core; diff --git a/src/Core/MojangServer.cs b/src/MojangServer.cs similarity index 100% rename from src/Core/MojangServer.cs rename to src/MojangServer.cs diff --git a/src/Core/PackageName.cs b/src/PackageName.cs similarity index 100% rename from src/Core/PackageName.cs rename to src/PackageName.cs diff --git a/src/Core/ProcessBuilder/MinecraftArgumentBuilder.cs b/src/ProcessBuilder/MinecraftArgumentBuilder.cs similarity index 100% rename from src/Core/ProcessBuilder/MinecraftArgumentBuilder.cs rename to src/ProcessBuilder/MinecraftArgumentBuilder.cs diff --git a/src/Core/ProcessBuilder/ProcessArgumentBuilder.cs b/src/ProcessBuilder/ProcessArgumentBuilder.cs similarity index 100% rename from src/Core/ProcessBuilder/ProcessArgumentBuilder.cs rename to src/ProcessBuilder/ProcessArgumentBuilder.cs diff --git a/src/Core/ProcessBuilder/ProcessUtil.cs b/src/ProcessBuilder/ProcessUtil.cs similarity index 100% rename from src/Core/ProcessBuilder/ProcessUtil.cs rename to src/ProcessBuilder/ProcessUtil.cs diff --git a/src/Core/Rules/IRulesEvaluator.cs b/src/Rules/IRulesEvaluator.cs similarity index 100% rename from src/Core/Rules/IRulesEvaluator.cs rename to src/Rules/IRulesEvaluator.cs diff --git a/src/Core/Rules/LauncherOSRule.cs b/src/Rules/LauncherOSRule.cs similarity index 100% rename from src/Core/Rules/LauncherOSRule.cs rename to src/Rules/LauncherOSRule.cs diff --git a/src/Core/Rules/LauncherRule.cs b/src/Rules/LauncherRule.cs similarity index 100% rename from src/Core/Rules/LauncherRule.cs rename to src/Rules/LauncherRule.cs diff --git a/src/Core/Rules/RulesEvaluator.cs b/src/Rules/RulesEvaluator.cs similarity index 100% rename from src/Core/Rules/RulesEvaluator.cs rename to src/Rules/RulesEvaluator.cs diff --git a/src/Core/Rules/RulesEvaluatorContext.cs b/src/Rules/RulesEvaluatorContext.cs similarity index 100% rename from src/Core/Rules/RulesEvaluatorContext.cs rename to src/Rules/RulesEvaluatorContext.cs diff --git a/src/Core/Tasks/ActionTask.cs b/src/Tasks/ActionTask.cs similarity index 100% rename from src/Core/Tasks/ActionTask.cs rename to src/Tasks/ActionTask.cs diff --git a/src/Core/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs similarity index 91% rename from src/Core/Tasks/ChmodTask.cs rename to src/Tasks/ChmodTask.cs index c704629..e797dc6 100644 --- a/src/Core/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -1,6 +1,5 @@ -using System.Diagnostics; using CmlLib.Core.Rules; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Core/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs similarity index 100% rename from src/Core/Tasks/DownloadTask.cs rename to src/Tasks/DownloadTask.cs diff --git a/src/Core/Tasks/FileCheckTask.cs b/src/Tasks/FileCheckTask.cs similarity index 96% rename from src/Core/Tasks/FileCheckTask.cs rename to src/Tasks/FileCheckTask.cs index 2621723..7bae6c8 100644 --- a/src/Core/Tasks/FileCheckTask.cs +++ b/src/Tasks/FileCheckTask.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Core/Tasks/FileCopyTask.cs b/src/Tasks/FileCopyTask.cs similarity index 97% rename from src/Core/Tasks/FileCopyTask.cs rename to src/Tasks/FileCopyTask.cs index cd2ef55..e56c5a9 100644 --- a/src/Core/Tasks/FileCopyTask.cs +++ b/src/Tasks/FileCopyTask.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Core/Tasks/LZMADecompressTask.cs b/src/Tasks/LZMADecompressTask.cs similarity index 94% rename from src/Core/Tasks/LZMADecompressTask.cs rename to src/Tasks/LZMADecompressTask.cs index 953c7ef..b0ef31e 100644 --- a/src/Core/Tasks/LZMADecompressTask.cs +++ b/src/Tasks/LZMADecompressTask.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Core/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs similarity index 100% rename from src/Core/Tasks/LinkedTask.cs rename to src/Tasks/LinkedTask.cs diff --git a/src/Core/Tasks/ResultTask.cs b/src/Tasks/ResultTask.cs similarity index 100% rename from src/Core/Tasks/ResultTask.cs rename to src/Tasks/ResultTask.cs diff --git a/src/Core/Tasks/TaskFile.cs b/src/Tasks/TaskFile.cs similarity index 100% rename from src/Core/Tasks/TaskFile.cs rename to src/Tasks/TaskFile.cs diff --git a/src/Core/Tasks/UnzipTask.cs b/src/Tasks/UnzipTask.cs similarity index 94% rename from src/Core/Tasks/UnzipTask.cs rename to src/Tasks/UnzipTask.cs index 7e8f0aa..07989e8 100644 --- a/src/Core/Tasks/UnzipTask.cs +++ b/src/Tasks/UnzipTask.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Core/Utils/Changelogs.cs b/src/Utils/Changelogs.cs similarity index 99% rename from src/Core/Utils/Changelogs.cs rename to src/Utils/Changelogs.cs index af674ea..1452489 100644 --- a/src/Core/Utils/Changelogs.cs +++ b/src/Utils/Changelogs.cs @@ -1,6 +1,6 @@ using System.Text.Json; using System.Text.RegularExpressions; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Utils; diff --git a/src/Core/Utils/GameOptionsFile.cs b/src/Utils/GameOptionsFile.cs similarity index 100% rename from src/Core/Utils/GameOptionsFile.cs rename to src/Utils/GameOptionsFile.cs diff --git a/src/Core/Version/Extensions.cs b/src/Version/Extensions.cs similarity index 100% rename from src/Core/Version/Extensions.cs rename to src/Version/Extensions.cs diff --git a/src/Core/Version/IVersion.cs b/src/Version/IVersion.cs similarity index 100% rename from src/Core/Version/IVersion.cs rename to src/Version/IVersion.cs diff --git a/src/Core/Version/JsonArgumentParser.cs b/src/Version/JsonArgumentParser.cs similarity index 98% rename from src/Core/Version/JsonArgumentParser.cs rename to src/Version/JsonArgumentParser.cs index dde4a11..1e1431e 100644 --- a/src/Core/Version/JsonArgumentParser.cs +++ b/src/Version/JsonArgumentParser.cs @@ -1,6 +1,6 @@ using System.Text.Json; using CmlLib.Core.Launcher; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Version; diff --git a/src/Core/Version/JsonLibraryParser.cs b/src/Version/JsonLibraryParser.cs similarity index 98% rename from src/Core/Version/JsonLibraryParser.cs rename to src/Version/JsonLibraryParser.cs index 011eeaa..dbac825 100644 --- a/src/Core/Version/JsonLibraryParser.cs +++ b/src/Version/JsonLibraryParser.cs @@ -1,6 +1,6 @@ using System.Text.Json; using CmlLib.Core.Files; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.Version; diff --git a/src/Core/Version/JsonRulesParser.cs b/src/Version/JsonRulesParser.cs similarity index 100% rename from src/Core/Version/JsonRulesParser.cs rename to src/Version/JsonRulesParser.cs diff --git a/src/Core/Version/JsonVersion.cs b/src/Version/JsonVersion.cs similarity index 100% rename from src/Core/Version/JsonVersion.cs rename to src/Version/JsonVersion.cs diff --git a/src/Core/Version/JsonVersionParser.cs b/src/Version/JsonVersionParser.cs similarity index 100% rename from src/Core/Version/JsonVersionParser.cs rename to src/Version/JsonVersionParser.cs diff --git a/src/Core/Version/JsonVersionParserOptions.cs b/src/Version/JsonVersionParserOptions.cs similarity index 100% rename from src/Core/Version/JsonVersionParserOptions.cs rename to src/Version/JsonVersionParserOptions.cs diff --git a/src/Core/Version/MVersionParseException.cs b/src/Version/MVersionParseException.cs similarity index 100% rename from src/Core/Version/MVersionParseException.cs rename to src/Version/MVersionParseException.cs diff --git a/src/Core/Version/VersionJsonModel.cs b/src/Version/VersionJsonModel.cs similarity index 100% rename from src/Core/Version/VersionJsonModel.cs rename to src/Version/VersionJsonModel.cs diff --git a/src/Core/VersionLoader/IVersionLoader.cs b/src/VersionLoader/IVersionLoader.cs similarity index 100% rename from src/Core/VersionLoader/IVersionLoader.cs rename to src/VersionLoader/IVersionLoader.cs diff --git a/src/Core/VersionLoader/LocalVersionLoader.cs b/src/VersionLoader/LocalVersionLoader.cs similarity index 100% rename from src/Core/VersionLoader/LocalVersionLoader.cs rename to src/VersionLoader/LocalVersionLoader.cs diff --git a/src/Core/VersionLoader/MojangVersionLoader.cs b/src/VersionLoader/MojangVersionLoader.cs similarity index 98% rename from src/Core/VersionLoader/MojangVersionLoader.cs rename to src/VersionLoader/MojangVersionLoader.cs index 9d0661d..579340d 100644 --- a/src/Core/VersionLoader/MojangVersionLoader.cs +++ b/src/VersionLoader/MojangVersionLoader.cs @@ -1,5 +1,5 @@ using CmlLib.Core.VersionMetadata; -using CmlLib.Utils; +using CmlLib.Core.Internals; using System.Text.Json; namespace CmlLib.Core.VersionLoader; diff --git a/src/Core/VersionLoader/VersionLoaderCollection.cs b/src/VersionLoader/VersionLoaderCollection.cs similarity index 100% rename from src/Core/VersionLoader/VersionLoaderCollection.cs rename to src/VersionLoader/VersionLoaderCollection.cs diff --git a/src/Core/VersionMetadata/Extensions.cs b/src/VersionMetadata/Extensions.cs similarity index 100% rename from src/Core/VersionMetadata/Extensions.cs rename to src/VersionMetadata/Extensions.cs diff --git a/src/Core/VersionMetadata/IVersionMetadata.cs b/src/VersionMetadata/IVersionMetadata.cs similarity index 100% rename from src/Core/VersionMetadata/IVersionMetadata.cs rename to src/VersionMetadata/IVersionMetadata.cs diff --git a/src/Core/VersionMetadata/JsonVersionMetadata.cs b/src/VersionMetadata/JsonVersionMetadata.cs similarity index 98% rename from src/Core/VersionMetadata/JsonVersionMetadata.cs rename to src/VersionMetadata/JsonVersionMetadata.cs index df4c2a4..4079f39 100644 --- a/src/Core/VersionMetadata/JsonVersionMetadata.cs +++ b/src/VersionMetadata/JsonVersionMetadata.cs @@ -1,5 +1,5 @@ using CmlLib.Core.Version; -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.VersionMetadata; diff --git a/src/Core/VersionMetadata/JsonVersionMetadataModel.cs b/src/VersionMetadata/JsonVersionMetadataModel.cs similarity index 100% rename from src/Core/VersionMetadata/JsonVersionMetadataModel.cs rename to src/VersionMetadata/JsonVersionMetadataModel.cs diff --git a/src/Core/VersionMetadata/LocalVersionMetadata.cs b/src/VersionMetadata/LocalVersionMetadata.cs similarity index 95% rename from src/Core/VersionMetadata/LocalVersionMetadata.cs rename to src/VersionMetadata/LocalVersionMetadata.cs index a5bbd2d..4ad1a3e 100644 --- a/src/Core/VersionMetadata/LocalVersionMetadata.cs +++ b/src/VersionMetadata/LocalVersionMetadata.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; namespace CmlLib.Core.VersionMetadata; diff --git a/src/Core/VersionMetadata/MVersionCollection.cs b/src/VersionMetadata/MVersionCollection.cs similarity index 100% rename from src/Core/VersionMetadata/MVersionCollection.cs rename to src/VersionMetadata/MVersionCollection.cs diff --git a/src/Core/VersionMetadata/MVersionMetadataSorter.cs b/src/VersionMetadata/MVersionMetadataSorter.cs similarity index 100% rename from src/Core/VersionMetadata/MVersionMetadataSorter.cs rename to src/VersionMetadata/MVersionMetadataSorter.cs diff --git a/src/Core/VersionMetadata/MVersionType.cs b/src/VersionMetadata/MVersionType.cs similarity index 100% rename from src/Core/VersionMetadata/MVersionType.cs rename to src/VersionMetadata/MVersionType.cs diff --git a/src/Core/VersionMetadata/MojangVersionMetadata.cs b/src/VersionMetadata/MojangVersionMetadata.cs similarity index 100% rename from src/Core/VersionMetadata/MojangVersionMetadata.cs rename to src/VersionMetadata/MojangVersionMetadata.cs diff --git a/src/Core/VersionMetadata/VersionSortOption.cs b/src/VersionMetadata/VersionSortOption.cs similarity index 100% rename from src/Core/VersionMetadata/VersionSortOption.cs rename to src/VersionMetadata/VersionSortOption.cs From 71b5a0610b5771a8b40effae1e9d9ef29d7880c3 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 11 Aug 2023 14:41:56 +0000 Subject: [PATCH 065/137] update --- CmlLib.Core.sln | 12 ++-- benchmark/CmlLib.Core.Benchmarks.csproj | 2 +- benchmark/DummyTaskExtractor.cs | 44 +++++++++++++++ benchmark/Program.cs | 8 +-- ...ileExtractor.cs => RandomFileExtractor.cs} | 14 ++--- .../TPLTaskExecutorWithDummyFileBenchmark.cs | 33 +++++++++++ ...TPLTaskExecutorWithRandomFileBenchmark.cs} | 10 ++-- examples/console/CmlLibCoreSample.csproj | 2 +- examples/console/Program.cs | 14 +---- examples/console/TPL.cs | 6 +- src/Executors/TPLTaskExecutor.cs | 56 +++++++++++++++---- src/FileExtractors/AssetFileExtractor.cs | 9 +-- src/FileExtractors/ClientFileExtractor.cs | 9 +-- src/FileExtractors/IFileExtractor.cs | 2 +- src/FileExtractors/JavaFileExtractor.cs | 30 +++++----- src/FileExtractors/LibraryFileExtractor.cs | 12 ++-- src/FileExtractors/LogFileExtractor.cs | 8 +-- src/Tasks/DownloadTask.cs | 2 + src/Tasks/LinkedTaskHead.cs | 11 ++++ 19 files changed, 203 insertions(+), 81 deletions(-) create mode 100644 benchmark/DummyTaskExtractor.cs rename benchmark/{TestFileExtractor.cs => RandomFileExtractor.cs} (76%) create mode 100644 benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs rename benchmark/{TPLTaskExecutorBenchmark.cs => TPLTaskExecutorWithRandomFileBenchmark.cs} (80%) create mode 100644 src/Tasks/LinkedTaskHead.cs diff --git a/CmlLib.Core.sln b/CmlLib.Core.sln index d79358d..4ad0fba 100644 --- a/CmlLib.Core.sln +++ b/CmlLib.Core.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib", "src\CmlLib.csproj", "{DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Test", "test\CmlLib.Core.Test.csproj", "{86827E6C-1353-4DF1-968F-31801A5EB032}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE}" @@ -15,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibWinFormSample", "exam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Benchmarks", "benchmark\CmlLib.Core.Benchmarks.csproj", "{99852557-DBA7-4E13-A883-4B535F0D33FB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core", "src\CmlLib.Core.csproj", "{F9718097-45C5-40CD-80B1-42408BEA8935}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,10 +24,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA4BA9FE-B2A2-4C05-B107-4D8BB620CB8A}.Release|Any CPU.Build.0 = Release|Any CPU {86827E6C-1353-4DF1-968F-31801A5EB032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86827E6C-1353-4DF1-968F-31801A5EB032}.Debug|Any CPU.Build.0 = Debug|Any CPU {86827E6C-1353-4DF1-968F-31801A5EB032}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -44,6 +40,10 @@ Global {99852557-DBA7-4E13-A883-4B535F0D33FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {99852557-DBA7-4E13-A883-4B535F0D33FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {99852557-DBA7-4E13-A883-4B535F0D33FB}.Release|Any CPU.Build.0 = Release|Any CPU + {F9718097-45C5-40CD-80B1-42408BEA8935}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F9718097-45C5-40CD-80B1-42408BEA8935}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F9718097-45C5-40CD-80B1-42408BEA8935}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F9718097-45C5-40CD-80B1-42408BEA8935}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {C9F876F3-5579-4B3A-A808-17845BB9C744} = {6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE} diff --git a/benchmark/CmlLib.Core.Benchmarks.csproj b/benchmark/CmlLib.Core.Benchmarks.csproj index f379087..0dadd20 100644 --- a/benchmark/CmlLib.Core.Benchmarks.csproj +++ b/benchmark/CmlLib.Core.Benchmarks.csproj @@ -10,7 +10,7 @@ - + diff --git a/benchmark/DummyTaskExtractor.cs b/benchmark/DummyTaskExtractor.cs new file mode 100644 index 0000000..a3e1265 --- /dev/null +++ b/benchmark/DummyTaskExtractor.cs @@ -0,0 +1,44 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class DummyTask : LinkedTask +{ + public static int Seed { get; set; } + + public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; + + protected override ValueTask OnExecuted() + { + for (int j = 0; j < 1024*512; j++) + Seed += j; + return new ValueTask(NextTask); + } +} + +public class DummyTaskExtractor : IFileExtractor +{ + private readonly int _count; + + public DummyTaskExtractor(int count) => _count = count; + + public ValueTask> Extract(MinecraftPath path, IVersion version) + { + var r = extract(); + return new ValueTask>(r); + } + + private IEnumerable extract() + { + for (int i = 0; i < _count; i++) + { + var file = new TaskFile(i.ToString()); + var task = new DummyTask(file, i); + task.InsertNextTask(new DummyTask(file, i)); + task.InsertNextTask(new DummyTask(file, i)); + yield return new LinkedTaskHead(task, file); + } + } +} \ No newline at end of file diff --git a/benchmark/Program.cs b/benchmark/Program.cs index 995f27d..52ba02c 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,14 +1,14 @@ using BenchmarkDotNet.Running; using CmlLib.Core.Benchmarks; -var summary = BenchmarkRunner.Run(); +var summary = BenchmarkRunner.Run(); +return; +await once(); async Task once() { - var benchmark = new TPLTaskExecutorBenchmark(); - benchmark.GlobalSetup(); + var benchmark = new TPLTaskExecutorWithDummyTaskBenchmark(); benchmark.IterationSetup(); await benchmark.Benchmark(); - benchmark.IterationCleanup(); Console.WriteLine("Done"); } \ No newline at end of file diff --git a/benchmark/TestFileExtractor.cs b/benchmark/RandomFileExtractor.cs similarity index 76% rename from benchmark/TestFileExtractor.cs rename to benchmark/RandomFileExtractor.cs index 1ba0bd7..914bc02 100644 --- a/benchmark/TestFileExtractor.cs +++ b/benchmark/RandomFileExtractor.cs @@ -4,13 +4,13 @@ namespace CmlLib.Core.Benchmarks; -public class TestFileExtractor : IFileExtractor +public class RandomFileExtractor : IFileExtractor { private readonly string _path; private readonly int _fileCount; private readonly int _fileSize; - public TestFileExtractor(string path, int fileCount, int fileSize) + public RandomFileExtractor(string path, int fileCount, int fileSize) { _path = path; _fileCount = fileCount; @@ -24,7 +24,7 @@ public void Setup() for (int i = 0; i < _fileCount; i++) { var dummyFilePath = Path.Combine(_path, i + ".dat"); - if (TPLTaskExecutorBenchmark.Verbose) + if (TPLTaskExecutorWithRandomFileBenchmark.Verbose) Console.WriteLine(dummyFilePath); using var fs = File.Create(dummyFilePath); @@ -50,13 +50,13 @@ public void Cleanup() Directory.Delete(_path); } - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version) { var result = extract(); - return new ValueTask>(result); + return new ValueTask>(result); } - private IEnumerable extract() + private IEnumerable extract() { foreach (var filePath in Directory.GetFiles(_path)) { @@ -68,7 +68,7 @@ private IEnumerable extract() var task = new FileCheckTask(file); - yield return task; + yield return new LinkedTaskHead(task, file); } } } \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs new file mode 100644 index 0000000..9c5faa8 --- /dev/null +++ b/benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs @@ -0,0 +1,33 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using CmlLib.Core.Executors; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class TPLTaskExecutorWithDummyTaskBenchmark +{ + private int parallelism = 6; + private int extractorCount = 8; + + private MinecraftPath MinecraftPath = new MinecraftPath(); + private IVersion DummyVersion = new DummyVersion(); + private DummyTaskExtractor[] Extractors; + private TPLTaskExecutor Executor; + + [IterationSetup] + public void IterationSetup() + { + Extractors = new DummyTaskExtractor[extractorCount]; + for (int i = 0; i < extractorCount; i++) + Extractors[i] = new DummyTaskExtractor(1024); + Executor = new TPLTaskExecutor(parallelism); + } + + [Benchmark] + public async Task Benchmark() + { + await Executor.Install(Extractors, MinecraftPath, DummyVersion); + } +} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs similarity index 80% rename from benchmark/TPLTaskExecutorBenchmark.cs rename to benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs index d1bf6a4..19437ff 100644 --- a/benchmark/TPLTaskExecutorBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs @@ -7,7 +7,7 @@ namespace CmlLib.Core.Benchmarks; [SimpleJob(RunStrategy.Monitoring, iterationCount: 10)] -public class TPLTaskExecutorBenchmark +public class TPLTaskExecutorWithRandomFileBenchmark { public static bool Verbose { get; set; } = false; @@ -16,7 +16,7 @@ public class TPLTaskExecutorBenchmark private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); - private TestFileExtractor[] Extractors; + private RandomFileExtractor[] Extractors; private TPLTaskExecutor Executor; [GlobalSetup] @@ -27,17 +27,17 @@ public void GlobalSetup() [IterationSetup] public void IterationSetup() { - Extractors = new TestFileExtractor[extractorCount]; + Extractors = new RandomFileExtractor[extractorCount]; for (int i = 0; i < extractorCount; i++) { var path = Path.GetFullPath("./benchmark" + i); - Extractors[i] = new TestFileExtractor(path, 1024, 1024*1024/2); + Extractors[i] = new RandomFileExtractor(path, 1024, 1024*1024/2); Extractors[i].Setup(); } Executor = new TPLTaskExecutor(parallelism); if (Verbose) - Executor.Progress += (s, e) => e.Print(); + Executor.FileProgress += (s, e) => e.Print(); } [IterationCleanup] diff --git a/examples/console/CmlLibCoreSample.csproj b/examples/console/CmlLibCoreSample.csproj index bc42691..a71949b 100644 --- a/examples/console/CmlLibCoreSample.csproj +++ b/examples/console/CmlLibCoreSample.csproj @@ -8,6 +8,6 @@ - + diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 6074096..0e75a85 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -46,12 +46,12 @@ async Task Start(MSession session) var httpClient = new HttpClient(); var rulesEvaluator = new RulesEvaluator(); var javaPathResolver = new MinecraftJavaPathResolver(minecraftPath); - var rulesContext = new RulesEvaluatorContext(LauncherOSRule.CreateCurrent()); + var rulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); var versionLoader = new VersionLoaderCollection() { new LocalVersionLoader(minecraftPath), - new MojangVersionLoader(httpClient), + //new MojangVersionLoader(httpClient), }; var versions = await versionLoader.GetVersionMetadatasAsync(); @@ -66,16 +66,8 @@ async Task Start(MSession session) var extractors = FileExtractorCollection.CreateDefault( httpClient, javaPathResolver, rulesEvaluator, rulesContext); - //foreach (var ex in extractors) - //{ - // var tasks = await ex.Extract(minecraftPath, version); - // foreach (var task in tasks) - // { - // printTask(task); - // } - //} - var installer = new TPLTaskExecutor(6); + installer.Progress += (s, e) => e.Print(); var sw = new Stopwatch(); sw.Start(); diff --git a/examples/console/TPL.cs b/examples/console/TPL.cs index 60c7091..346728a 100644 --- a/examples/console/TPL.cs +++ b/examples/console/TPL.cs @@ -56,7 +56,7 @@ public async Task Test() { int copy = i; var extractor = new TransformManyBlock( - v => extract(v, copy)); + v => extract(v, i)); versionBroadcaster.LinkTo(extractor, linkOptions); extractor.LinkTo(buffer, linkOptions); } @@ -66,12 +66,12 @@ public async Task Test() Console.WriteLine("DONE"); } - private async IAsyncEnumerable extract(string version, int i) + private IEnumerable extract(string version, int i) { Console.WriteLine($"extractor {i}"); for (int j = 0; j < 4; j++) { - await Task.Delay(100); + Thread.Sleep(100); Console.WriteLine("extracted " + (i*10+j)); yield return new MockTask((i*10 + j).ToString()) { diff --git a/src/Executors/TPLTaskExecutor.cs b/src/Executors/TPLTaskExecutor.cs index 29e32c5..bae30e6 100644 --- a/src/Executors/TPLTaskExecutor.cs +++ b/src/Executors/TPLTaskExecutor.cs @@ -35,23 +35,33 @@ public void Print() public class TPLTaskExecutor { + private struct TaskState + { + public TaskStatus Status { get; set; } + public long TotalBytes { get; set; } + public long ProgressedBytes { get; set; } + } + private readonly int _maxParallelism; - private readonly ConcurrentDictionary _runningTasks; + private readonly ConcurrentDictionary _runningTasks; public TPLTaskExecutor(int parallelism) { _maxParallelism = parallelism; - _runningTasks = new ConcurrentDictionary(_maxParallelism, 2047); + _runningTasks = new ConcurrentDictionary(_maxParallelism, 2047); } - public event EventHandler? Progress; + public event EventHandler? FileProgress; + public event EventHandler? BytesProgress; + + private long totalBytes = 0; private int totalTasks = 0; private int proceed = 0; public void PrintStatus() { var runningTasks = _runningTasks - .Where(kv => kv.Value != TaskStatus.Done) + .Where(kv => kv.Value.Status != TaskStatus.Done) .Select(kv => kv.Key); foreach (var task in runningTasks) @@ -75,8 +85,18 @@ public async ValueTask Install( if (proceed == _runningTasks.Count) return; - else - await executeBlock.Completion; + + var executeTask = executeBlock.Completion; + while (!executeTask.IsCompleted) + { + long progressedBytes = _runningTasks + .Select(kv => kv.Value.ProgressedBytes) + .Sum(); + + var percent = (int)(progressedBytes / (double)totalBytes * 100); + BytesProgress?.Invoke(this, percent); + await Task.Delay(100); + } } private BufferBlock createExecuteBlock(Task extractTask) @@ -100,7 +120,11 @@ private BufferBlock createExecuteBlock(Task extractTask) if (nextTask == null) { Interlocked.Increment(ref proceed); - _runningTasks.TryUpdate(task.Name, TaskStatus.Done, TaskStatus.Queued); + _runningTasks.AddOrUpdate( + task.Name, + new TaskState { Status = TaskStatus.Done }, + (key, oldValue) => oldValue with { Status = TaskStatus.Done }); + fireEvent(task.Name, TaskStatus.Done); if (proceed == _runningTasks.Count && extractTask.IsCompleted) @@ -126,14 +150,24 @@ private IPropagatorBlock createExtractBlock( IVersion version, MinecraftPath path) { - IEnumerable fireTasksEvent(IEnumerable tasks) + IEnumerable fireTasksEvent(IEnumerable tasks) { foreach (var task in tasks) { - if (_runningTasks.TryAdd(task.Name, TaskStatus.Queued)) + if (task.First == null) + continue; + + var state = new TaskState + { + Status = TaskStatus.Queued, + TotalBytes = task.File.Size + }; + + if (_runningTasks.TryAdd(task.Name, state)) { - yield return task; + yield return task.First; Interlocked.Increment(ref totalTasks); + Interlocked.Add(ref totalBytes, task.File.Size); fireEvent(task.Name, TaskStatus.Queued); } } @@ -154,7 +188,7 @@ IEnumerable fireTasksEvent(IEnumerable tasks) private void fireEvent(string name, TaskStatus status) { - Progress?.Invoke(this, new TaskExecutorEventArgs(name) + FileProgress?.Invoke(this, new TaskExecutorEventArgs(name) { EventType = status, TotalTasks = totalTasks, diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index 67f2a79..c61f2fc 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -29,13 +29,13 @@ public string AssetServer } } - public async ValueTask> Extract(MinecraftPath path, IVersion version) + public async ValueTask> Extract(MinecraftPath path, IVersion version) { var assets = version.AssetIndex; if (assets == null || string.IsNullOrEmpty(assets.Id) || string.IsNullOrEmpty(assets.Url)) - return Enumerable.Empty(); + return Enumerable.Empty(); using var assetIndexStream = await createAssetIndexStream(path, assets); var assetIndexJson = JsonDocument.Parse(assetIndexStream); @@ -74,7 +74,7 @@ private async ValueTask createAssetIndexStream(MinecraftPath path, MFile } } - private IEnumerable extractFromAssetIndexJson( + private IEnumerable extractFromAssetIndexJson( JsonDocument _json, MinecraftPath path, IVersion version) { Debug.Assert(!string.IsNullOrEmpty(version.AssetIndex?.Id)); @@ -120,7 +120,8 @@ private IEnumerable extractFromAssetIndexJson( if (copyPath.Count > 0) checkTask.InsertNextTask(new FileCopyTask(prop.Name, hashPath, copyPath.ToArray())); - yield return checkTask; + var taskHead = new LinkedTaskHead(checkTask, file); + yield return taskHead; } } } diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 9456f2a..29f5136 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -5,13 +5,13 @@ namespace CmlLib.Core.FileExtractors; public class ClientFileExtractor : IFileExtractor { - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version) { var result = extract(path, version); - return new ValueTask>(result); + return new ValueTask>(result); } - private IEnumerable extract(MinecraftPath path, IVersion version) + private IEnumerable extract(MinecraftPath path, IVersion version) { var id = version.Jar; var url = version.Client?.Url; @@ -30,6 +30,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version) var checkTask = new FileCheckTask(file); checkTask.OnFalse = new DownloadTask(file); - yield return checkTask; + + yield return new LinkedTaskHead(checkTask, file); } } diff --git a/src/FileExtractors/IFileExtractor.cs b/src/FileExtractors/IFileExtractor.cs index a9cccac..16d27f7 100644 --- a/src/FileExtractors/IFileExtractor.cs +++ b/src/FileExtractors/IFileExtractor.cs @@ -5,5 +5,5 @@ namespace CmlLib.Core.FileExtractors; public interface IFileExtractor { - ValueTask> Extract(MinecraftPath path, IVersion version); + ValueTask> Extract(MinecraftPath path, IVersion version); } \ No newline at end of file diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 38dba26..412ce9d 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -26,7 +26,7 @@ public JavaFileExtractor( _os = rule; } - public async ValueTask> Extract(MinecraftPath path, IVersion version) + public async ValueTask> Extract(MinecraftPath path, IVersion version) { if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion.Value.Component)) return await extractFromJavaVersion(MinecraftJavaPathResolver.JreLegacyVersion); @@ -34,7 +34,7 @@ public async ValueTask> Extract(MinecraftPath path, IVer return await extractFromJavaVersion(version.JavaVersion.Value); } - private async ValueTask> extractFromJavaVersion(JavaVersion javaVersion) + private async ValueTask> extractFromJavaVersion(JavaVersion javaVersion) { using var response = await _httpClient.GetStreamAsync(JavaManifestServer); using var jsonDocument = JsonDocument.Parse(response); @@ -69,7 +69,7 @@ private async ValueTask> extractFromJavaVersion(JavaVers (LauncherOSRule.Windows, _) => null, (LauncherOSRule.Linux, "64") => "linux", (LauncherOSRule.Linux, "32") => "linux-i386", - (LauncherOSRule.Linux, _) => null, + (LauncherOSRule.Linux, _) => "linux", (LauncherOSRule.OSX, "64") => "mac-os", (LauncherOSRule.OSX, "32") => "mac-os", (LauncherOSRule.OSX, "arm") => "mac-os", // TODO @@ -90,14 +90,14 @@ private async ValueTask> extractFromJavaVersion(JavaVers .GetString(); } - private async ValueTask> extractFromManifestUrl(string manifestUrl, JavaVersion version) + private async ValueTask> extractFromManifestUrl(string manifestUrl, JavaVersion version) { using var res = await _httpClient.GetStreamAsync(manifestUrl); var json = JsonDocument.Parse(res); // should be disposed after extraction return extractFromManifestJson(json, version); } - private IEnumerable extractFromManifestJson( + private IEnumerable extractFromManifestJson( JsonDocument _json, JavaVersion version) { using var json = _json; @@ -120,9 +120,9 @@ private IEnumerable extractFromManifestJson( var filePath = Path.Combine(path, name); filePath = IOUtil.NormalizePath(filePath); - var file = createTask(value, name, filePath); - if (file != null) - yield return file; + var task = createTask(value, name, filePath); + if (task.HasValue) + yield return task.Value; } else { @@ -133,7 +133,7 @@ private IEnumerable extractFromManifestJson( } } - private LinkedTask? createTask(JsonElement value, string name, string filePath) + private LinkedTaskHead? createTask(JsonElement value, string name, string filePath) { var downloadObj = value.GetPropertyOrNull("downloads")?.GetPropertyOrNull("raw"); if (downloadObj == null) @@ -162,17 +162,17 @@ private IEnumerable extractFromManifestJson( if (executable) checkTask.InsertNextTask(new ChmodTask(file.Name, file.Path)); - return checkTask; + return new LinkedTaskHead(checkTask, file); } // legacy java checker using MJava - private async ValueTask> legacyJavaChecker() + private async ValueTask> legacyJavaChecker() { var legacyJavaPath = _javaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersion); var mJava = new MJava(_httpClient, legacyJavaPath); if (mJava.CheckJavaExistence(_os)) - return Enumerable.Empty(); + return Enumerable.Empty(); var javaUrl = await mJava.GetJavaUrlAsync(); var lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); @@ -191,6 +191,10 @@ private async ValueTask> legacyJavaChecker() new UnzipTask(file.Name, zipPath, legacyJavaPath), new ChmodTask(file.Name, mJava.GetBinaryPath(_os)) }); - return new LinkedTask[] { task! }; + + return new LinkedTaskHead[] + { + new LinkedTaskHead(task!, file) + }; } } \ No newline at end of file diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index 304b15d..1a5b62f 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -28,13 +28,13 @@ public string LibraryServer } } - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version) { var result = extract(path, version); - return new ValueTask>(result); + return new ValueTask>(result); } - private IEnumerable extract(MinecraftPath path, IVersion version) + private IEnumerable extract(MinecraftPath path, IVersion version) { return version.Libraries .Where(lib => lib.CheckIsRequired("SIDE")) @@ -42,7 +42,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version) .SelectMany(lib => createLibraryTasks(path, lib)); } - private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary library) + private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary library) { // java library (*.jar) var artifact = library.Artifact; @@ -58,7 +58,7 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary var task = new FileCheckTask(file); task.OnFalse = new DownloadTask(file); - yield return task; + yield return new LinkedTaskHead(task, file); } // native library (*.dll, *.so) @@ -77,7 +77,7 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary var task = new FileCheckTask(file); task.OnFalse = new DownloadTask(file); - yield return task; + yield return new LinkedTaskHead(task, file); } } } diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index 0ca8159..0869063 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -5,13 +5,13 @@ namespace CmlLib.Core.FileExtractors; public class LogFileExtractor : IFileExtractor { - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version) { var result = extract(path, version); - return new ValueTask>(result); + return new ValueTask>(result); } - private IEnumerable extract(MinecraftPath path, IVersion version) + private IEnumerable extract(MinecraftPath path, IVersion version) { if (version.Logging == null) yield break; @@ -30,6 +30,6 @@ private IEnumerable extract(MinecraftPath path, IVersion version) }; var task = new FileCheckTask(file); task.OnFalse = new DownloadTask(file); - yield return task; + yield return new LinkedTaskHead(task, file); } } diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs index 9062e9d..553e734 100644 --- a/src/Tasks/DownloadTask.cs +++ b/src/Tasks/DownloadTask.cs @@ -1,3 +1,5 @@ +using CmlLib.Core.Downloader; + namespace CmlLib.Core.Tasks; public class DownloadTask : LinkedTask diff --git a/src/Tasks/LinkedTaskHead.cs b/src/Tasks/LinkedTaskHead.cs new file mode 100644 index 0000000..8c39576 --- /dev/null +++ b/src/Tasks/LinkedTaskHead.cs @@ -0,0 +1,11 @@ +namespace CmlLib.Core.Tasks; + +public struct LinkedTaskHead +{ + public LinkedTaskHead(LinkedTask? first, TaskFile file) => + (First, File) = (first, file); + + public string Name => File.Name; + public LinkedTask? First { get; } + public TaskFile File { get; } +} \ No newline at end of file From a5b6cd35aac9c925212d6f7b9b511d5444ba2fb4 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 13 Aug 2023 08:48:28 +0000 Subject: [PATCH 066/137] r1 --- benchmark/DummyDownloadTask.cs | 31 +++ benchmark/DummyDownloaderExtractor.cs | 37 +++ benchmark/DummyTask.cs | 19 ++ benchmark/DummyTaskExtractor.cs | 35 ++- benchmark/Program.cs | 14 +- ...askExecutorWithDummyDownloaderBenchmark.cs | 71 ++++++ .../TPLTaskExecutorWithDummyFileBenchmark.cs | 33 --- .../TPLTaskExecutorWithDummyTaskBenchmark.cs | 71 ++++++ .../TPLTaskExecutorWithRandomFileBenchmark.cs | 11 +- global.json | 6 + src/ByteProgressEventArgs.cs | 7 + src/CMLauncher.cs | 2 - src/Downloader/AsyncParallelDownloader.cs | 157 ------------ src/Downloader/DownloadFile.cs | 39 --- src/Downloader/IDownloader.cs | 13 - src/Downloader/MDownloadFileException.cs | 18 -- src/Downloader/SequenceDownloader.cs | 60 ----- src/Executors/AsyncTaskExecutor.cs | 47 ++++ src/Executors/SyncProgress.cs | 14 ++ src/Executors/TPLTaskExecutor.cs | 234 +++++++++++------- .../TaskExecutorProgressChangedEventArgs.cs | 20 ++ src/Executors/TaskState.cs | 8 + src/FileExtractors/AssetFileExtractor.cs | 2 +- src/FileExtractors/ClientFileExtractor.cs | 9 +- src/FileExtractors/FileCheckerCollection.cs | 6 +- src/FileExtractors/JavaFileExtractor.cs | 4 +- src/FileExtractors/LibraryFileExtractor.cs | 15 +- src/FileExtractors/LogFileExtractor.cs | 9 +- src/Installer/MJava.cs | 12 +- src/Internals/SharpZipWrapper.cs | 15 +- src/Tasks/ActionTask.cs | 7 +- src/Tasks/ChmodTask.cs | 4 +- src/Tasks/DownloadTask.cs | 34 ++- src/Tasks/FileCheckTask.cs | 4 +- src/Tasks/FileCopyTask.cs | 6 +- .../HttpClientDownloadHelper.cs | 45 ++-- src/Tasks/LZMADecompressTask.cs | 4 +- src/Tasks/LinkedTask.cs | 17 +- src/Tasks/LinkedTaskHead.cs | 11 + src/Tasks/ResultTask.cs | 10 +- src/Tasks/TaskExecutionContext.cs | 14 ++ src/Tasks/UnzipTask.cs | 6 +- 42 files changed, 681 insertions(+), 500 deletions(-) create mode 100644 benchmark/DummyDownloadTask.cs create mode 100644 benchmark/DummyDownloaderExtractor.cs create mode 100644 benchmark/DummyTask.cs create mode 100644 benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs delete mode 100644 benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs create mode 100644 benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs create mode 100644 global.json create mode 100644 src/ByteProgressEventArgs.cs delete mode 100644 src/Downloader/AsyncParallelDownloader.cs delete mode 100644 src/Downloader/DownloadFile.cs delete mode 100644 src/Downloader/IDownloader.cs delete mode 100644 src/Downloader/MDownloadFileException.cs delete mode 100644 src/Downloader/SequenceDownloader.cs create mode 100644 src/Executors/AsyncTaskExecutor.cs create mode 100644 src/Executors/SyncProgress.cs create mode 100644 src/Executors/TaskExecutorProgressChangedEventArgs.cs create mode 100644 src/Executors/TaskState.cs rename src/{Downloader => Tasks}/HttpClientDownloadHelper.cs (70%) create mode 100644 src/Tasks/TaskExecutionContext.cs diff --git a/benchmark/DummyDownloadTask.cs b/benchmark/DummyDownloadTask.cs new file mode 100644 index 0000000..5c5a95d --- /dev/null +++ b/benchmark/DummyDownloadTask.cs @@ -0,0 +1,31 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public class DummyDownloadTask : DownloadTask +{ + public static int Seed = 0; + + public DummyDownloadTask(TaskFile file, HttpClient httpClient) : base(file, httpClient) + { + } + + protected override async ValueTask DownloadFile( + IProgress? progress, + CancellationToken cancellationToken) + { + for (int i = 0; i < Size; i += 1) + { + if (Size % 512 == 0) + { + progress?.Report(new ByteProgressEventArgs + { + TotalBytes = Size, + ProgressedBytes = i + }); + } + Seed += i; + //await Task.Delay(1); + } + } +} \ No newline at end of file diff --git a/benchmark/DummyDownloaderExtractor.cs b/benchmark/DummyDownloaderExtractor.cs new file mode 100644 index 0000000..078b246 --- /dev/null +++ b/benchmark/DummyDownloaderExtractor.cs @@ -0,0 +1,37 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class DummyDownloaderExtractor : IFileExtractor +{ + private readonly int _count; + private readonly string _prefix; + public DummyDownloaderExtractor(string prefix, int count) => + (_prefix, _count) = (prefix, count); + + public ValueTask> Extract(MinecraftPath path, IVersion version) + { + var r = extract(); + return new ValueTask>(r); + } + + private IEnumerable extract() + { + for (int i = 0; i < _count; i++) + { + var file = new TaskFile(_prefix + "-" + i.ToString()) + { + Size = 1024 * 256, + Path = "a.dat", + Url = "a.dat" + }; + var task = LinkedTask.LinkTasks( + new DummyTask(file, i), + new DummyDownloadTask(file, null!) + ); + yield return new LinkedTaskHead(task, file); + } + } +} \ No newline at end of file diff --git a/benchmark/DummyTask.cs b/benchmark/DummyTask.cs new file mode 100644 index 0000000..6704239 --- /dev/null +++ b/benchmark/DummyTask.cs @@ -0,0 +1,19 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public class DummyTask : LinkedTask +{ + public static int Seed { get; set; } + + public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; + + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) + { + for (int j = 0; j < 1024*256; j++) + Seed += j; + return new ValueTask(NextTask); + } +} \ No newline at end of file diff --git a/benchmark/DummyTaskExtractor.cs b/benchmark/DummyTaskExtractor.cs index a3e1265..a59166c 100644 --- a/benchmark/DummyTaskExtractor.cs +++ b/benchmark/DummyTaskExtractor.cs @@ -4,25 +4,12 @@ namespace CmlLib.Core.Benchmarks; -public class DummyTask : LinkedTask -{ - public static int Seed { get; set; } - - public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; - - protected override ValueTask OnExecuted() - { - for (int j = 0; j < 1024*512; j++) - Seed += j; - return new ValueTask(NextTask); - } -} - public class DummyTaskExtractor : IFileExtractor { private readonly int _count; - - public DummyTaskExtractor(int count) => _count = count; + private readonly string _prefix; + public DummyTaskExtractor(string prefix, int count) => + (_prefix, _count) = (prefix, count); public ValueTask> Extract(MinecraftPath path, IVersion version) { @@ -34,10 +21,18 @@ private IEnumerable extract() { for (int i = 0; i < _count; i++) { - var file = new TaskFile(i.ToString()); - var task = new DummyTask(file, i); - task.InsertNextTask(new DummyTask(file, i)); - task.InsertNextTask(new DummyTask(file, i)); + var file = new TaskFile(_prefix + "-" + i.ToString()) + { + Size = 1024 * 2, + Path = "a.dat", + Url = "a.dat" + }; + var task = LinkedTask.LinkTasks( + new DummyTask(file, i), + new DummyTask(file, i), + new DummyTask(file, i) + //new DummyDownloadTask(file, null!) + ); yield return new LinkedTaskHead(task, file); } } diff --git a/benchmark/Program.cs b/benchmark/Program.cs index 52ba02c..1b39db8 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,14 +1,20 @@ -using BenchmarkDotNet.Running; +using System.Diagnostics; +using BenchmarkDotNet.Running; using CmlLib.Core.Benchmarks; -var summary = BenchmarkRunner.Run(); -return; +//var summary = BenchmarkRunner.Run(); +//return; await once(); async Task once() { - var benchmark = new TPLTaskExecutorWithDummyTaskBenchmark(); + var benchmark = new TPLTaskExecutorWithDummyDownloaderBenchmark(); + TPLTaskExecutorWithDummyDownloaderBenchmark.Verbose = true; benchmark.IterationSetup(); + var sw = new Stopwatch(); + sw.Start(); await benchmark.Benchmark(); + sw.Stop(); Console.WriteLine("Done"); + Console.WriteLine(sw.Elapsed.ToString()); } \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs new file mode 100644 index 0000000..b1f993c --- /dev/null +++ b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs @@ -0,0 +1,71 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using CmlLib.Core.Executors; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class TPLTaskExecutorWithDummyDownloaderBenchmark +{ + private int parallelism = 6; + private int extractorCount = 8; + + public static bool Verbose = false; + public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; + public static ByteProgressEventArgs BytesProgressArgs; + + private MinecraftPath MinecraftPath = new MinecraftPath(); + private IVersion DummyVersion = new DummyVersion(); + private DummyDownloaderExtractor[] Extractors; + private TPLTaskExecutor Executor; + + private ByteProgressEventArgs previousEvent; + private object consoleLock = new object(); + private string? bottomMsg; + + [IterationSetup] + public void IterationSetup() + { + Extractors = new DummyDownloaderExtractor[extractorCount]; + for (int i = 0; i < extractorCount; i++) + Extractors[i] = new DummyDownloaderExtractor(i.ToString(), 1024); + Executor = new TPLTaskExecutor(parallelism); + Executor.FileProgress += (s, e) => FileProgressArgs = e; + Executor.ByteProgress += (s, e) => BytesProgressArgs = e; + if (Verbose) + { + Executor.FileProgress += (s, e) => + { + lock (consoleLock) + { + Console.SetCursorPosition(0, Console.CursorTop - 1); + e.Print(); + Console.WriteLine(bottomMsg); + } + }; + Executor.ByteProgress += (s, e) => + { + lock (consoleLock) + { + if (previousEvent.ProgressedBytes >= e.ProgressedBytes) + return; + previousEvent = e; + bottomMsg = $"{e.ProgressedBytes} / {e.TotalBytes} "; + Console.SetCursorPosition(0, Console.CursorTop - 1); + Console.WriteLine(bottomMsg); + } + }; + } + } + + [Benchmark] + public async Task Benchmark() + { + await Executor.Install( + Extractors, + MinecraftPath, + DummyVersion, + default); + } +} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs deleted file mode 100644 index 9c5faa8..0000000 --- a/benchmark/TPLTaskExecutorWithDummyFileBenchmark.cs +++ /dev/null @@ -1,33 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using CmlLib.Core.Executors; -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -public class TPLTaskExecutorWithDummyTaskBenchmark -{ - private int parallelism = 6; - private int extractorCount = 8; - - private MinecraftPath MinecraftPath = new MinecraftPath(); - private IVersion DummyVersion = new DummyVersion(); - private DummyTaskExtractor[] Extractors; - private TPLTaskExecutor Executor; - - [IterationSetup] - public void IterationSetup() - { - Extractors = new DummyTaskExtractor[extractorCount]; - for (int i = 0; i < extractorCount; i++) - Extractors[i] = new DummyTaskExtractor(1024); - Executor = new TPLTaskExecutor(parallelism); - } - - [Benchmark] - public async Task Benchmark() - { - await Executor.Install(Extractors, MinecraftPath, DummyVersion); - } -} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs new file mode 100644 index 0000000..cdad95c --- /dev/null +++ b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs @@ -0,0 +1,71 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using CmlLib.Core.Executors; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Benchmarks; + +public class TPLTaskExecutorWithDummyTaskBenchmark +{ + private int parallelism = 6; + private int extractorCount = 8; + + public static bool Verbose = false; + public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; + public static ByteProgressEventArgs BytesProgressArgs; + + private MinecraftPath MinecraftPath = new MinecraftPath(); + private IVersion DummyVersion = new DummyVersion(); + private DummyTaskExtractor[] Extractors; + private TPLTaskExecutor Executor; + + private ByteProgressEventArgs previousEvent; + private object consoleLock = new object(); + private string? bottomMsg; + + [IterationSetup] + public void IterationSetup() + { + Extractors = new DummyTaskExtractor[extractorCount]; + for (int i = 0; i < extractorCount; i++) + Extractors[i] = new DummyTaskExtractor(i.ToString(), 1024); + Executor = new TPLTaskExecutor(parallelism); + Executor.FileProgress += (s, e) => FileProgressArgs = e; + Executor.ByteProgress += (s, e) => BytesProgressArgs = e; + if (Verbose) + { + Executor.FileProgress += (s, e) => + { + lock (consoleLock) + { + Console.SetCursorPosition(0, Console.CursorTop - 1); + e.Print(); + Console.WriteLine(bottomMsg); + } + }; + Executor.ByteProgress += (s, e) => + { + lock (consoleLock) + { + if (previousEvent.ProgressedBytes >= e.ProgressedBytes) + return; + previousEvent = e; + bottomMsg = $"{e.ProgressedBytes} / {e.TotalBytes} "; + Console.SetCursorPosition(0, Console.CursorTop - 1); + Console.WriteLine(bottomMsg); + } + }; + } + } + + [Benchmark] + public async Task Benchmark() + { + await Executor.Install( + Extractors, + MinecraftPath, + DummyVersion, + default); + } +} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs index 19437ff..87a26c0 100644 --- a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs @@ -14,6 +14,9 @@ public class TPLTaskExecutorWithRandomFileBenchmark private int parallelism = 6; private int extractorCount = 4; + public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; + public static ByteProgressEventArgs BytesProgressArgs; + private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private RandomFileExtractor[] Extractors; @@ -35,6 +38,8 @@ public void IterationSetup() Extractors[i].Setup(); } Executor = new TPLTaskExecutor(parallelism); + Executor.FileProgress += (s, e) => FileProgressArgs = e; + Executor.ByteProgress += (s, e) => BytesProgressArgs = e; if (Verbose) Executor.FileProgress += (s, e) => e.Print(); @@ -52,6 +57,10 @@ public void IterationCleanup() [Benchmark] public async Task Benchmark() { - await Executor.Install(Extractors, MinecraftPath, DummyVersion); + await Executor.Install( + Extractors, + MinecraftPath, + DummyVersion, + default); } } \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..5932159 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "6.0.400", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/src/ByteProgressEventArgs.cs b/src/ByteProgressEventArgs.cs new file mode 100644 index 0000000..4edc084 --- /dev/null +++ b/src/ByteProgressEventArgs.cs @@ -0,0 +1,7 @@ +namespace CmlLib.Core; + +public struct ByteProgressEventArgs +{ + public long TotalBytes; + public long ProgressedBytes; +} \ No newline at end of file diff --git a/src/CMLauncher.cs b/src/CMLauncher.cs index 78d6f21..9e4c7a8 100644 --- a/src/CMLauncher.cs +++ b/src/CMLauncher.cs @@ -23,7 +23,6 @@ public CMLauncher(MinecraftPath path, HttpClient httpClient, LauncherOSRule os) this.os = os; MinecraftPath = path; - FileDownloader = new AsyncParallelDownloader(_httpClient); VersionLoader = new VersionLoaderCollection { new LocalVersionLoader(MinecraftPath), @@ -52,7 +51,6 @@ public CMLauncher(MinecraftPath path, HttpClient httpClient, LauncherOSRule os) public IVersionLoader VersionLoader { get; set; } public FileExtractorCollection GameFileCheckers { get; private set; } - public IDownloader? FileDownloader { get; set; } public IJavaPathResolver JavaPathResolver { get; set; } diff --git a/src/Downloader/AsyncParallelDownloader.cs b/src/Downloader/AsyncParallelDownloader.cs deleted file mode 100644 index 4abda1a..0000000 --- a/src/Downloader/AsyncParallelDownloader.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.ComponentModel; -using System.Diagnostics; - -namespace CmlLib.Core.Downloader; - -public class AsyncParallelDownloader : IDownloader -{ - private readonly HttpClient _httpClient; - - public int MaxParallelism { get; private set; } - public bool IgnoreInvalidFiles { get; set; } = true; - - private int totalFiles; - private int progressedFiles; - - private long totalBytes; - private long receivedBytes; - - private readonly object progressEventLock = new object(); - - private bool isRunning; - - private IProgress? pChangeProgress; - private IProgress? pChangeFile; - private IProgress fileByteProgress; - - public AsyncParallelDownloader(HttpClient httpClient, int parallelism = 10) - { - _httpClient = httpClient; - MaxParallelism = parallelism; - fileByteProgress = new Progress(byteProgressHandler); - } - - public async Task DownloadFiles(DownloadFile[] files, - IProgress? fileProgress, - IProgress? downloadProgress) - { - if (files.Length == 0) - return; - - if (isRunning) - throw new InvalidOperationException("already downloading"); - - isRunning = true; - - pChangeFile = fileProgress; - pChangeProgress = downloadProgress; - - totalFiles = files.Length; - progressedFiles = 0; - - totalBytes = 0; - receivedBytes = 0; - - foreach (DownloadFile item in files) - { - if (item.Size > 0) - totalBytes += item.Size; - } - - fileProgress?.Report( - new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); - await ForEachAsyncSemaphore(files, MaxParallelism, doDownload).ConfigureAwait(false); - - isRunning = false; - } - - private async Task ForEachAsyncSemaphore(IEnumerable source, - int degreeOfParallelism, Func body) - { - List tasks = new List(); - using SemaphoreSlim throttler = new SemaphoreSlim(degreeOfParallelism); - foreach (var element in source) - { - await throttler.WaitAsync().ConfigureAwait(false); - - async Task work(T item) - { - try - { - await body(item).ConfigureAwait(false); - } - finally - { - throttler.Release(); - } - } - - tasks.Add(work(element)); - } - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - private async Task doDownload(DownloadFile file) - { - try - { - await doDownload(file, 3).ConfigureAwait(false); - } - catch (Exception ex) - { - if (!IgnoreInvalidFiles) - throw new MDownloadFileException("failed to download", ex, file); - } - } - - private async Task doDownload(DownloadFile file, int retry) - { - try - { - await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, file, fileByteProgress) - .ConfigureAwait(false); - - if (file.AfterDownload != null) - { - foreach (var item in file.AfterDownload) - { - await item.Invoke().ConfigureAwait(false); - } - } - - Interlocked.Increment(ref progressedFiles); - pChangeFile?.Report( - new DownloadFileChangedEventArgs(file.Type, this, file.Name, totalFiles, progressedFiles)); - } - catch (Exception ex) - { - if (retry <= 0) - return; - - Debug.WriteLine(ex); - retry--; - - await doDownload(file, retry).ConfigureAwait(false); - } - } - - private void byteProgressHandler(DownloadFileByteProgress progress) - { - lock (progressEventLock) - { - if (progress.File != null && progress.File.Size <= 0) - { - totalBytes += progress.TotalBytes; - progress.File.Size = progress.TotalBytes; - } - - receivedBytes += progress.ProgressedBytes; - - if (receivedBytes > totalBytes) - return; - - double percent = (double)receivedBytes / totalBytes * 100; - pChangeProgress?.Report(new FileProgressChangedEventArgs(totalBytes, receivedBytes, (int)percent)); - } - } -} diff --git a/src/Downloader/DownloadFile.cs b/src/Downloader/DownloadFile.cs deleted file mode 100644 index 31911fe..0000000 --- a/src/Downloader/DownloadFile.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace CmlLib.Core.Downloader -{ - public class DownloadFile : IEquatable - { - public DownloadFile(string path, string url) - { - this.Path = path; - this.Url = url; - } - - public MFile Type { get; set; } - public string? Name { get; set; } - public string Path { get; } - public string Url { get; } - public long Size { get; set; } - - public Func[]? AfterDownload { get; set; } - - public override int GetHashCode() - { - return Path.GetHashCode(); - } - - public bool Equals(DownloadFile? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Path == other.Path; - } - - public override bool Equals(object? obj) - { - return ReferenceEquals(this, obj) || obj is DownloadFile other && Equals(other); - } - } -} diff --git a/src/Downloader/IDownloader.cs b/src/Downloader/IDownloader.cs deleted file mode 100644 index 64275d7..0000000 --- a/src/Downloader/IDownloader.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.ComponentModel; -using System.Threading.Tasks; - -namespace CmlLib.Core.Downloader -{ - public interface IDownloader - { - Task DownloadFiles(DownloadFile[] files, - IProgress? fileProgress, - IProgress? downloadProgress); - } -} diff --git a/src/Downloader/MDownloadFileException.cs b/src/Downloader/MDownloadFileException.cs deleted file mode 100644 index 5d98639..0000000 --- a/src/Downloader/MDownloadFileException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace CmlLib.Core.Downloader -{ - public class MDownloadFileException : Exception - { - public MDownloadFileException(DownloadFile exFile) - : this(null, null, exFile) { } - - public MDownloadFileException(string? message, Exception? innerException, DownloadFile? exFile) - : base(message, innerException) - { - ExceptionFile = exFile; - } - - public DownloadFile? ExceptionFile { get; private set; } - } -} diff --git a/src/Downloader/SequenceDownloader.cs b/src/Downloader/SequenceDownloader.cs deleted file mode 100644 index 05b3e8c..0000000 --- a/src/Downloader/SequenceDownloader.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.ComponentModel; - -namespace CmlLib.Core.Downloader; - -public class SequenceDownloader : IDownloader -{ - private readonly HttpClient _httpClient; - public bool IgnoreInvalidFiles { get; set; } = true; - - public SequenceDownloader(HttpClient client) - { - _httpClient = client; - } - - public async Task DownloadFiles(DownloadFile[] files, - IProgress? fileProgress, - IProgress? downloadProgress) - { - if (files.Length == 0) - return; - - var byteProgress = new Progress(progress => - { - var percent = (float)progress.ProgressedBytes / progress.TotalBytes * 100; - downloadProgress?.Report(new ProgressChangedEventArgs((int)percent, null)); - }); - - fileProgress?.Report( - new DownloadFileChangedEventArgs(files[0].Type, this, null, files.Length, 0)); - - for (int i = 0; i < files.Length; i++) - { - DownloadFile file = files[i]; - - try - { - await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, file, byteProgress) - .ConfigureAwait(false); - - if (file.AfterDownload != null) - { - foreach (var item in file.AfterDownload) - { - await item().ConfigureAwait(false); - } - } - - fileProgress?.Report( - new DownloadFileChangedEventArgs(file.Type, this, file.Name, files.Length, i)); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex.ToString()); - - if (!IgnoreInvalidFiles) - throw new MDownloadFileException(ex.Message, ex, files[i]); - } - } - } -} diff --git a/src/Executors/AsyncTaskExecutor.cs b/src/Executors/AsyncTaskExecutor.cs new file mode 100644 index 0000000..a764429 --- /dev/null +++ b/src/Executors/AsyncTaskExecutor.cs @@ -0,0 +1,47 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Executors; + +public class AsyncTaskExecutor : IDisposable +{ + private readonly SemaphoreSlim _semaphore; + + public AsyncTaskExecutor(int maxParallelism) + { + _semaphore = new SemaphoreSlim(maxParallelism); + } + + public ValueTask QueueTask( + LinkedTask task, + IProgress progress, + CancellationToken cancellationToken) + { + var context = new TaskExecutionContext(progress, cancellationToken); + return createQueuedTask(task, progress, cancellationToken); + } + + private async ValueTask createQueuedTask( + LinkedTask task, + IProgress? progress, + CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(); + + LinkedTask? nextTask = null; + try + { + nextTask = await task.Execute(progress, cancellationToken); + } + finally + { + _semaphore.Release(); + } + return nextTask; + } + + + public void Dispose() + { + ((IDisposable)_semaphore).Dispose(); + } +} \ No newline at end of file diff --git a/src/Executors/SyncProgress.cs b/src/Executors/SyncProgress.cs new file mode 100644 index 0000000..e38f862 --- /dev/null +++ b/src/Executors/SyncProgress.cs @@ -0,0 +1,14 @@ +namespace CmlLib.Core.Executors; + +public class SyncProgress : IProgress +{ + private readonly Action _report; + + public SyncProgress(Action report) => + _report = report; + + public void Report(T value) + { + _report.Invoke(value); + } +} \ No newline at end of file diff --git a/src/Executors/TPLTaskExecutor.cs b/src/Executors/TPLTaskExecutor.cs index bae30e6..8392cf1 100644 --- a/src/Executors/TPLTaskExecutor.cs +++ b/src/Executors/TPLTaskExecutor.cs @@ -7,74 +7,42 @@ namespace CmlLib.Core.Executors; -public enum TaskStatus -{ - Queued, - Processing, - Done -} - -public class TaskExecutorEventArgs -{ - public TaskExecutorEventArgs(string name) => - Name = name; - - public int TotalTasks { get; set; } - public int ProceedTasks { get; set; } - public TaskStatus EventType { get; set; } - public string Name { get; set; } - - public void Print() - { - //if (status != TaskStatus.Done) return; - //if (proceed % 100 != 0) return; - var now = DateTime.Now.ToString("hh:mm:ss.fff"); - Console.WriteLine($"[{now}][{ProceedTasks}/{TotalTasks}][{EventType}] {Name}"); - } -} - public class TPLTaskExecutor { - private struct TaskState + private struct TaskProgress { - public TaskStatus Status { get; set; } - public long TotalBytes { get; set; } - public long ProgressedBytes { get; set; } + public long TotalBytes; + public long ProgressedBytes; } + //private readonly ConcurrentDictionary _tasks; + private readonly ConcurrentDictionary _totalProgressStorage; + private readonly ThreadLocal> _progressPerThread; private readonly int _maxParallelism; - private readonly ConcurrentDictionary _runningTasks; public TPLTaskExecutor(int parallelism) { _maxParallelism = parallelism; - _runningTasks = new ConcurrentDictionary(_maxParallelism, 2047); + _totalProgressStorage = new ConcurrentDictionary(); + _progressPerThread = new ThreadLocal>( + () => new Dictionary(), true); } - public event EventHandler? FileProgress; - public event EventHandler? BytesProgress; + public event EventHandler? FileProgress; + public event EventHandler? ByteProgress; - private long totalBytes = 0; + private CancellationToken CancellationToken; private int totalTasks = 0; private int proceed = 0; - public void PrintStatus() - { - var runningTasks = _runningTasks - .Where(kv => kv.Value.Status != TaskStatus.Done) - .Select(kv => kv.Key); - - foreach (var task in runningTasks) - { - Console.WriteLine(task); - } - } - public async ValueTask Install( IEnumerable extractors, MinecraftPath path, - IVersion version) + IVersion version, + CancellationToken cancellationToken) { + CancellationToken = cancellationToken; + var extractBlock = createExtractBlock(version, path); var executeBlock = createExecuteBlock(extractBlock.Completion); extractBlock.LinkTo(executeBlock); @@ -83,20 +51,56 @@ public async ValueTask Install( extractBlock.Complete(); await extractBlock.Completion; - if (proceed == _runningTasks.Count) + if (proceed == totalTasks) return; - + var executeTask = executeBlock.Completion; while (!executeTask.IsCompleted) { - long progressedBytes = _runningTasks - .Select(kv => kv.Value.ProgressedBytes) - .Sum(); - - var percent = (int)(progressedBytes / (double)totalBytes * 100); - BytesProgress?.Invoke(this, percent); + reportByteProgress(); await Task.Delay(100); } + reportByteProgress(); + } + + private void reportByteProgress() + { + long totalBytes = 0; + long progressedBytes = 0; + + foreach (var dict in _progressPerThread.Values) + { + lock (dict) + { + foreach (var kv in dict) + { + _totalProgressStorage.AddOrUpdate( + kv.Key, + new TaskProgress + { + TotalBytes = kv.Value.TotalBytes, + ProgressedBytes = kv.Value.ProgressedBytes + }, + (_, old) => new TaskProgress + { + TotalBytes = kv.Value.TotalBytes, + ProgressedBytes = kv.Value.ProgressedBytes + }); + } + } + } + + foreach (var kv in _totalProgressStorage) + { + totalBytes += kv.Value.TotalBytes; + progressedBytes += kv.Value.ProgressedBytes; + } + + ByteProgress?.Invoke(this, new ByteProgressEventArgs + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); } private BufferBlock createExecuteBlock(Task extractTask) @@ -108,8 +112,7 @@ private BufferBlock createExecuteBlock(Task extractTask) Exception? exception = null; try { - fireEvent(task.Name, TaskStatus.Processing); - nextTask = await task.Execute(); + nextTask = await task.Execute(null, CancellationToken); } catch (Exception ex) { @@ -117,31 +120,100 @@ private BufferBlock createExecuteBlock(Task extractTask) Debug.WriteLine(ex.ToString()); } + return finalizeTask(task, nextTask); + + }, new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = _maxParallelism + }); + + var downloader = new TransformBlock(async task => + { + var downloadTask = task as DownloadTask; + if (downloadTask == null) + return task.NextTask; + + var progress = new SyncProgress(e => + { + var dict = _progressPerThread.Value!; + lock (dict) + { + dict[task.Name] = new TaskProgress + { + TotalBytes = e.TotalBytes, + ProgressedBytes = e.ProgressedBytes + }; + } + //_progressPerThread.Value!.AddOrUpdate( + // task.Name, + // new TaskProgress + // { + // TotalBytes = e.TotalBytes, + // ProgressedBytes = e.ProgressedBytes + // }, + // (_, old) => new TaskProgress + // { + // TotalBytes = e.TotalBytes, + // ProgressedBytes = e.ProgressedBytes + // }); + }); + + var nextTask = await downloadTask.Execute(progress, CancellationToken); + return finalizeTask(task, nextTask); + }, new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = _maxParallelism + }); + + + LinkedTask? finalizeTask(LinkedTask task, LinkedTask? nextTask) + { if (nextTask == null) { Interlocked.Increment(ref proceed); - _runningTasks.AddOrUpdate( - task.Name, - new TaskState { Status = TaskStatus.Done }, - (key, oldValue) => oldValue with { Status = TaskStatus.Done }); - + TaskProgress progress; + var dict = _progressPerThread.Value!; + lock (dict) + { + if (!dict.TryGetValue(task.Name, out progress) && + !_totalProgressStorage.TryGetValue(task.Name, out progress)) + progress = new TaskProgress(); + dict[task.Name] = progress; + } + //_progressPerThread.Value!.AddOrUpdate( + // task.Name, + // new TaskProgress + // { + // TotalBytes = 0, + // ProgressedBytes = 0 + // }, + // (_, old) => old with + // { + // TotalBytes = old.TotalBytes, + // ProgressedBytes = old.TotalBytes + // }); fireEvent(task.Name, TaskStatus.Done); - if (proceed == _runningTasks.Count && extractTask.IsCompleted) + if (proceed == totalTasks && extractTask.IsCompleted) { buffer.Complete(); } } return nextTask; - }, new ExecutionDataflowBlockOptions - { - MaxDegreeOfParallelism = _maxParallelism - }); + } + buffer.LinkTo(downloader!, t => t is DownloadTask); buffer.LinkTo(executor); - executor.LinkTo(buffer!, t => t != null); - executor.LinkTo(DataflowBlock.NullTarget()); + + void linkToBuffer(IPropagatorBlock block, BufferBlock buffer) + { + block.LinkTo(buffer!, t => t != null); + block.LinkTo(DataflowBlock.NullTarget()); + } + + linkToBuffer(executor, buffer); + linkToBuffer(downloader, buffer); return buffer; } @@ -157,19 +229,16 @@ IEnumerable fireTasksEvent(IEnumerable tasks) if (task.First == null) continue; - var state = new TaskState - { - Status = TaskStatus.Queued, - TotalBytes = task.File.Size + Interlocked.Increment(ref totalTasks); + var state = new TaskProgress + { + TotalBytes = task.File.Size, + ProgressedBytes = 0 }; + _totalProgressStorage.TryAdd(task.Name, state); - if (_runningTasks.TryAdd(task.Name, state)) - { - yield return task.First; - Interlocked.Increment(ref totalTasks); - Interlocked.Add(ref totalBytes, task.File.Size); - fireEvent(task.Name, TaskStatus.Queued); - } + yield return task.First; + fireEvent(task.Name, TaskStatus.Queued); } } @@ -188,9 +257,8 @@ IEnumerable fireTasksEvent(IEnumerable tasks) private void fireEvent(string name, TaskStatus status) { - FileProgress?.Invoke(this, new TaskExecutorEventArgs(name) + FileProgress?.Invoke(this, new TaskExecutorProgressChangedEventArgs(name, status) { - EventType = status, TotalTasks = totalTasks, ProceedTasks = proceed }); diff --git a/src/Executors/TaskExecutorProgressChangedEventArgs.cs b/src/Executors/TaskExecutorProgressChangedEventArgs.cs new file mode 100644 index 0000000..040227a --- /dev/null +++ b/src/Executors/TaskExecutorProgressChangedEventArgs.cs @@ -0,0 +1,20 @@ +namespace CmlLib.Core.Executors; + +public struct TaskExecutorProgressChangedEventArgs +{ + public TaskExecutorProgressChangedEventArgs(string name, TaskStatus status) => + (Name, EventType) = (name, status); + + public int TotalTasks { get; set; } = 0; + public int ProceedTasks { get; set; } = 0; + public TaskStatus EventType { get; } + public string Name { get; } + + public void Print() + { + //if (status != TaskStatus.Done) return; + //if (proceed % 100 != 0) return; + var now = DateTime.Now.ToString("hh:mm:ss.fff"); + Console.WriteLine($"[{now}][{ProceedTasks}/{TotalTasks}][{EventType}] {Name}"); + } +} \ No newline at end of file diff --git a/src/Executors/TaskState.cs b/src/Executors/TaskState.cs new file mode 100644 index 0000000..b7a07a9 --- /dev/null +++ b/src/Executors/TaskState.cs @@ -0,0 +1,8 @@ +namespace CmlLib.Core.Executors; + +public enum TaskStatus +{ + Queued, + Processing, + Done +} \ No newline at end of file diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index c61f2fc..e838740 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -115,7 +115,7 @@ private IEnumerable extractFromAssetIndexJson( }; var checkTask = new FileCheckTask(file); - checkTask.OnFalse = new DownloadTask(file); + checkTask.OnFalse = new DownloadTask(file, httpClient); if (copyPath.Count > 0) checkTask.InsertNextTask(new FileCopyTask(prop.Name, hashPath, copyPath.ToArray())); diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 29f5136..603d6cf 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -5,6 +5,13 @@ namespace CmlLib.Core.FileExtractors; public class ClientFileExtractor : IFileExtractor { + private readonly HttpClient _httpClient; + + public ClientFileExtractor(HttpClient httpClient) + { + _httpClient = httpClient; + } + public ValueTask> Extract(MinecraftPath path, IVersion version) { var result = extract(path, version); @@ -29,7 +36,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version }; var checkTask = new FileCheckTask(file); - checkTask.OnFalse = new DownloadTask(file); + checkTask.OnFalse = new DownloadTask(file, _httpClient); yield return new LinkedTaskHead(checkTask, file); } diff --git a/src/FileExtractors/FileCheckerCollection.cs b/src/FileExtractors/FileCheckerCollection.cs index d17100e..4857832 100644 --- a/src/FileExtractors/FileCheckerCollection.cs +++ b/src/FileExtractors/FileCheckerCollection.cs @@ -14,11 +14,11 @@ public static FileExtractorCollection CreateDefault( { var extractors = new FileExtractorCollection(); - var library = new LibraryFileExtractor(rulesEvaluator, context); + var library = new LibraryFileExtractor(rulesEvaluator, context, httpClient); var asset = new AssetFileExtractor(httpClient); - var client = new ClientFileExtractor(); + var client = new ClientFileExtractor(httpClient); var java = new JavaFileExtractor(httpClient, javaPathResolver, context.OS); - var log = new LogFileExtractor(); + var log = new LogFileExtractor(httpClient); extractors.Add(library); extractors.Add(asset); diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 412ce9d..0584ac2 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -157,7 +157,7 @@ private IEnumerable extractFromManifestJson( }; var checkTask = new FileCheckTask(file); - checkTask.OnFalse = new DownloadTask(file); + checkTask.OnFalse = new DownloadTask(file, _httpClient); if (executable) checkTask.InsertNextTask(new ChmodTask(file.Name, file.Path)); @@ -186,7 +186,7 @@ private async ValueTask> legacyJavaChecker() var task = LinkedTask.LinkTasks(new LinkedTask[] { - new DownloadTask(file), + new DownloadTask(file, _httpClient), new LZMADecompressTask(file.Name, lzmaPath, zipPath), new UnzipTask(file.Name, zipPath, legacyJavaPath), new ChmodTask(file.Name, mJava.GetBinaryPath(_os)) diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index 1a5b62f..b365097 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -8,11 +8,16 @@ public class LibraryFileExtractor : IFileExtractor { private readonly RulesEvaluatorContext _rulesContext; private readonly IRulesEvaluator _rulesEvaluator; + private readonly HttpClient _httpClient; - public LibraryFileExtractor(IRulesEvaluator rulesEvaluator, RulesEvaluatorContext context) + public LibraryFileExtractor( + IRulesEvaluator rulesEvaluator, + RulesEvaluatorContext context, + HttpClient httpClient) { - this._rulesEvaluator = rulesEvaluator; - this._rulesContext = context; + _rulesEvaluator = rulesEvaluator; + _rulesContext = context; + _httpClient = httpClient; } private string libServer = MojangServer.Library; @@ -57,7 +62,7 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibr }; var task = new FileCheckTask(file); - task.OnFalse = new DownloadTask(file); + task.OnFalse = new DownloadTask(file, _httpClient); yield return new LinkedTaskHead(task, file); } @@ -76,7 +81,7 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibr }; var task = new FileCheckTask(file); - task.OnFalse = new DownloadTask(file); + task.OnFalse = new DownloadTask(file, _httpClient); yield return new LinkedTaskHead(task, file); } } diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index 0869063..84f7b47 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -5,6 +5,13 @@ namespace CmlLib.Core.FileExtractors; public class LogFileExtractor : IFileExtractor { + private readonly HttpClient _httpClient; + + public LogFileExtractor(HttpClient httpClient) + { + _httpClient = httpClient; + } + public ValueTask> Extract(MinecraftPath path, IVersion version) { var result = extract(path, version); @@ -29,7 +36,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version Hash = version.Logging?.LogFile?.GetSha1() }; var task = new FileCheckTask(file); - task.OnFalse = new DownloadTask(file); + task.OnFalse = new DownloadTask(file, _httpClient); yield return new LinkedTaskHead(task, file); } } diff --git a/src/Installer/MJava.cs b/src/Installer/MJava.cs index 416e5ba..5f42e9d 100644 --- a/src/Installer/MJava.cs +++ b/src/Installer/MJava.cs @@ -5,6 +5,7 @@ using CmlLib.Core.Downloader; using CmlLib.Core.Rules; using CmlLib.Core.Internals; +using CmlLib.Core.Tasks; namespace CmlLib.Core.Installer; @@ -102,12 +103,12 @@ private async Task downloadJavaLzmaAsync(string javaUrl) Directory.CreateDirectory(RuntimeDirectory); string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - var progress = new Progress(p => + var progress = new Progress(p => { var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); }); - await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, new DownloadFile(lzmaPath, javaUrl), progress); + await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, javaUrl, 0, lzmaPath, progress); return lzmaPath; } @@ -116,7 +117,10 @@ private void decompressJavaFile(string lzmaPath) string zippath = Path.Combine(Path.GetTempPath(), "jre.zip"); SevenZipWrapper.DecompressFileLZMA(lzmaPath, zippath); - SharpZipWrapper.Unzip(zippath, RuntimeDirectory, new Progress(p => - pProgressChanged?.Report(new ProgressChangedEventArgs(50 + p / 2, null)))); + SharpZipWrapper.Unzip(zippath, RuntimeDirectory, new Progress(p => + { + var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; + pProgressChanged?.Report(new ProgressChangedEventArgs(50 + (int)percent / 2, null)); + })); } } \ No newline at end of file diff --git a/src/Internals/SharpZipWrapper.cs b/src/Internals/SharpZipWrapper.cs index 5c61572..62e1a38 100644 --- a/src/Internals/SharpZipWrapper.cs +++ b/src/Internals/SharpZipWrapper.cs @@ -4,7 +4,11 @@ namespace CmlLib.Core.Internals; internal static class SharpZipWrapper { - public static void Unzip(string zipPath, string extractTo, IProgress? progress) + public static void Unzip( + string zipPath, + string extractTo, + IProgress? progress, + CancellationToken cancellationToken = default) { using var fs = File.OpenRead(zipPath); using var s = new ZipInputStream(fs); @@ -13,6 +17,8 @@ public static void Unzip(string zipPath, string extractTo, IProgress? progr ZipEntry e; while ((e = s.GetNextEntry()) != null) { + cancellationToken.ThrowIfCancellationRequested(); + var fullPath = Path.Combine(extractTo, e.Name); IOUtil.CreateParentDirectory(fullPath); var fileName = Path.GetFileName(fullPath); @@ -20,8 +26,11 @@ public static void Unzip(string zipPath, string extractTo, IProgress? progr using var output = File.Create(fullPath); s.CopyTo(output); - int percent = (int)(s.Position / (double)length * 100); - progress?.Report(percent); + progress?.Report(new ByteProgressEventArgs + { + TotalBytes = length, + ProgressedBytes = s.Position + }); } } } diff --git a/src/Tasks/ActionTask.cs b/src/Tasks/ActionTask.cs index 74b0851..9b430ca 100644 --- a/src/Tasks/ActionTask.cs +++ b/src/Tasks/ActionTask.cs @@ -4,10 +4,13 @@ public class ActionTask : LinkedTask { private readonly Func> _action; - public ActionTask(string name, Func> action) : base(name) => + public ActionTask(string name, Func> action) + : base(name) => _action = action; - protected override async ValueTask OnExecuted() + protected override async ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) { return await _action.Invoke(); } diff --git a/src/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs index e797dc6..95992bb 100644 --- a/src/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -10,7 +10,9 @@ public class ChmodTask : LinkedTask public ChmodTask(string name, string path) : base(name) => Path = path; - protected override ValueTask OnExecuted() + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) { if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) NativeMethods.Chmod(Path, NativeMethods.Chmod755); diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs index 553e734..a1a9ed3 100644 --- a/src/Tasks/DownloadTask.cs +++ b/src/Tasks/DownloadTask.cs @@ -1,31 +1,43 @@ -using CmlLib.Core.Downloader; - namespace CmlLib.Core.Tasks; public class DownloadTask : LinkedTask { - public DownloadTask(TaskFile file) : base(file) + public DownloadTask(TaskFile file, HttpClient httpClient) : base(file) { if (string.IsNullOrEmpty(file.Path)) throw new ArgumentException("file.Path was empty"); if (string.IsNullOrEmpty(file.Url)) throw new ArgumentException("file.Url was empty"); + HttpClient = httpClient; this.Path = file.Path; this.Url = file.Url; + this.Size = file.Size; } - public DownloadTask(string name, string path, string url) : base(name) - { - this.Path = path; - this.Url = url; - } - + protected HttpClient HttpClient; public string Path { get; } public string Url { get; } + public long Size { get; } + + protected async override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) + { + await DownloadFile(progress, cancellationToken); + return NextTask; + } - protected override ValueTask OnExecuted() + protected async virtual ValueTask DownloadFile( + IProgress? progress, + CancellationToken cancellationToken) { - return new ValueTask(NextTask); + await HttpClientDownloadHelper.DownloadFileAsync( + HttpClient, + Url, + Size, + Path, + progress, + cancellationToken); } } \ No newline at end of file diff --git a/src/Tasks/FileCheckTask.cs b/src/Tasks/FileCheckTask.cs index 7bae6c8..7219a81 100644 --- a/src/Tasks/FileCheckTask.cs +++ b/src/Tasks/FileCheckTask.cs @@ -24,7 +24,9 @@ public FileCheckTask(string name, string path, string hash) : base(name) public string Path { get; } public string Hash { get; } - protected override ValueTask OnExecutedWithResult() + protected override ValueTask OnExecutedWithResult( + IProgress? progress, + CancellationToken cancellationToken) { var result = IOUtil.CheckFileValidation(Path, Hash); return new ValueTask(result); diff --git a/src/Tasks/FileCopyTask.cs b/src/Tasks/FileCopyTask.cs index e56c5a9..6d7d373 100644 --- a/src/Tasks/FileCopyTask.cs +++ b/src/Tasks/FileCopyTask.cs @@ -10,7 +10,9 @@ public FileCopyTask(string name, string sourcePath, IEnumerable destPath public string SourcePath { get; } public string[] DestinationPaths { get; } - protected override ValueTask OnExecuted() + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) { if (!File.Exists(SourcePath)) throw new InvalidOperationException("The source file does not exists"); @@ -18,6 +20,8 @@ public FileCopyTask(string name, string sourcePath, IEnumerable destPath var orgFile = new FileInfo(SourcePath); foreach (var destination in DestinationPaths) { + cancellationToken.ThrowIfCancellationRequested(); + var desFile = new FileInfo(destination); if (!desFile.Exists || orgFile.Length != desFile.Length) { diff --git a/src/Downloader/HttpClientDownloadHelper.cs b/src/Tasks/HttpClientDownloadHelper.cs similarity index 70% rename from src/Downloader/HttpClientDownloadHelper.cs rename to src/Tasks/HttpClientDownloadHelper.cs index a07d5e3..b84c446 100644 --- a/src/Downloader/HttpClientDownloadHelper.cs +++ b/src/Tasks/HttpClientDownloadHelper.cs @@ -1,13 +1,7 @@ -using CmlLib.Core.Internals; +using CmlLib.Core.Executors; +using CmlLib.Core.Internals; -namespace CmlLib.Core.Downloader; - -internal struct DownloadFileByteProgress -{ - public DownloadFile? File { get; set; } - public long TotalBytes { get; set; } - public long ProgressedBytes { get; set; } -} +namespace CmlLib.Core.Tasks; // To implement System.Net.WebClient.DownloadFileTaskAsync // https://source.dot.net/#System.Net.WebClient/System/Net/WebClient.cs,c2224e40a6960929 @@ -16,16 +10,19 @@ internal static class HttpClientDownloadHelper private const int DefaultDownloadBufferLength = 65536; public static async Task DownloadFileAsync( - this HttpClient httpClient, - DownloadFile file, - IProgress? progress = null, + HttpClient httpClient, + string url, + long size, + string path, + IProgress? progress = null, CancellationToken cancellationToken = default) { - IOUtil.CreateParentDirectory(file.Path); - using var destination = File.Create(file.Path); + IOUtil.CreateParentDirectory(path); + using var destination = File.Create(path); await DownloadFileAsync( httpClient, - file, + url, + size, destination, progress, cancellationToken) @@ -33,17 +30,18 @@ await DownloadFileAsync( } public static async Task DownloadFileAsync( - this HttpClient httpClient, - DownloadFile file, + HttpClient httpClient, + string url, + long size, Stream destination, - IProgress? progress = null, + IProgress? progress = null, CancellationToken cancellationToken = default) { // Get the http headers first to examine the content length using var response = await httpClient.GetAsync( - file.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - var contentLength = response.Content.Headers.ContentLength ?? file.Size; + var contentLength = response.Content.Headers.ContentLength ?? size; using var download = await response.Content.ReadAsStreamAsync(); if (download.CanTimeout) @@ -62,6 +60,7 @@ public static async Task DownloadFileAsync( : contentLength; var copyBuffer = new byte[bufferSize]; + long totalRead = 0; while (true) { if (cancellationToken.IsCancellationRequested) @@ -78,11 +77,11 @@ public static async Task DownloadFileAsync( await destination.WriteAsync(copyBuffer, 0, bytesRead).ConfigureAwait(false); - progress?.Report(new DownloadFileByteProgress() + totalRead += bytesRead; + progress?.Report(new ByteProgressEventArgs { - File = file, TotalBytes = contentLength, - ProgressedBytes = bytesRead + ProgressedBytes = totalRead }); } } diff --git a/src/Tasks/LZMADecompressTask.cs b/src/Tasks/LZMADecompressTask.cs index b0ef31e..525f59d 100644 --- a/src/Tasks/LZMADecompressTask.cs +++ b/src/Tasks/LZMADecompressTask.cs @@ -13,7 +13,9 @@ public LZMADecompressTask(string name, string lzmaPath, string extractTo) : base ExtractPath = extractTo; } - protected override ValueTask OnExecuted() + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) { SevenZipWrapper.DecompressFileLZMA(LZMAPath, ExtractPath); return new ValueTask(); diff --git a/src/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs index fafd339..6bd96f6 100644 --- a/src/Tasks/LinkedTask.cs +++ b/src/Tasks/LinkedTask.cs @@ -17,15 +17,19 @@ public LinkedTask(string name) public string Name { get; set; } public LinkedTask? NextTask { get; private set; } - public async ValueTask Execute() + public async ValueTask Execute( + IProgress? progress, + CancellationToken cancellationToken) { - var nextTask = await OnExecuted(); + var nextTask = await OnExecuted(progress, cancellationToken); if (nextTask != null && nextTask.Name != this.Name) throw new InvalidOperationException("Name should be same"); return nextTask; } - protected abstract ValueTask OnExecuted(); + protected abstract ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken); public LinkedTask InsertNextTask(LinkedTask task) { @@ -43,10 +47,13 @@ public LinkedTask InsertNextTask(LinkedTask task) public static LinkedTask? LinkTasks(params LinkedTask[] tasks) { - return LinkTasks(tasks); + return internalLinkTasks(tasks); } - public static LinkedTask? LinkTasks(IEnumerable tasks) + public static LinkedTask? LinkTasks(IEnumerable tasks) => + internalLinkTasks(tasks); + + private static LinkedTask? internalLinkTasks(IEnumerable tasks) { var firstTask = tasks.FirstOrDefault(); if (firstTask == null) diff --git a/src/Tasks/LinkedTaskHead.cs b/src/Tasks/LinkedTaskHead.cs index 8c39576..a05e5fa 100644 --- a/src/Tasks/LinkedTaskHead.cs +++ b/src/Tasks/LinkedTaskHead.cs @@ -8,4 +8,15 @@ public LinkedTaskHead(LinkedTask? first, TaskFile file) => public string Name => File.Name; public LinkedTask? First { get; } public TaskFile File { get; } + + public ValueTask Execute( + IProgress? progress = null, + CancellationToken cancellationToken = default) + { + if (First == null) + return new ValueTask(); + + var context = new TaskExecutionContext(progress, cancellationToken); + return First.Execute(progress, cancellationToken); + } } \ No newline at end of file diff --git a/src/Tasks/ResultTask.cs b/src/Tasks/ResultTask.cs index 42bafe9..58ffe62 100644 --- a/src/Tasks/ResultTask.cs +++ b/src/Tasks/ResultTask.cs @@ -15,9 +15,11 @@ public ResultTask(TaskFile file) : base(file) public LinkedTask? OnTrue { get; set; } public LinkedTask? OnFalse { get; set; } - protected override async ValueTask OnExecuted() + protected override async ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) { - var result = await OnExecutedWithResult(); + var result = await OnExecutedWithResult(progress, cancellationToken); var nextTask = result ? OnTrue : OnFalse; if (nextTask != null) @@ -26,5 +28,7 @@ public ResultTask(TaskFile file) : base(file) return NextTask; } - protected abstract ValueTask OnExecutedWithResult(); + protected abstract ValueTask OnExecutedWithResult( + IProgress? progress, + CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Tasks/TaskExecutionContext.cs b/src/Tasks/TaskExecutionContext.cs new file mode 100644 index 0000000..6ff5781 --- /dev/null +++ b/src/Tasks/TaskExecutionContext.cs @@ -0,0 +1,14 @@ +namespace CmlLib.Core.Tasks; + +public class TaskExecutionContext +{ + public TaskExecutionContext( + IProgress? progress, + CancellationToken cancellationToken) + { + (ProgressChanged, CancellationToken) = (progress, cancellationToken); + } + + public CancellationToken CancellationToken { get; } + public IProgress? ProgressChanged { get; } +} \ No newline at end of file diff --git a/src/Tasks/UnzipTask.cs b/src/Tasks/UnzipTask.cs index 07989e8..9b5e1a9 100644 --- a/src/Tasks/UnzipTask.cs +++ b/src/Tasks/UnzipTask.cs @@ -13,9 +13,11 @@ public UnzipTask(string name, string zipPath, string unzipTo) : base(name) ExtractTo = unzipTo; } - protected override ValueTask OnExecuted() + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) { - SharpZipWrapper.Unzip(ZipPath, ExtractTo, null); + SharpZipWrapper.Unzip(ZipPath, ExtractTo, null, cancellationToken); return new ValueTask(NextTask); } } \ No newline at end of file From 24f3cb26ec6049de4643c135e2edafb4df22349d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 13 Aug 2023 13:48:05 +0000 Subject: [PATCH 067/137] r2 --- benchmark/DummyDownloadTask.cs | 4 +- benchmark/DummyTask.cs | 2 +- ...askExecutorWithDummyDownloaderBenchmark.cs | 4 +- .../TPLTaskExecutorWithDummyTaskBenchmark.cs | 4 +- .../TPLTaskExecutorWithRandomFileBenchmark.cs | 2 +- src/ByteProgressEventArgs.cs | 2 +- src/Executors/AsyncTaskExecutor.cs | 4 +- src/Executors/TPLTaskExecutor.cs | 122 ++++++------------ src/Installer/MJava.cs | 4 +- src/Internals/SharpZipWrapper.cs | 4 +- src/Tasks/ActionTask.cs | 2 +- src/Tasks/ChmodTask.cs | 2 +- src/Tasks/DownloadTask.cs | 4 +- src/Tasks/FileCheckTask.cs | 2 +- src/Tasks/FileCopyTask.cs | 2 +- src/Tasks/HttpClientDownloadHelper.cs | 6 +- src/Tasks/LZMADecompressTask.cs | 2 +- src/Tasks/LinkedTask.cs | 4 +- src/Tasks/LinkedTaskHead.cs | 2 +- src/Tasks/ResultTask.cs | 4 +- src/Tasks/TaskExecutionContext.cs | 4 +- src/Tasks/UnzipTask.cs | 2 +- 22 files changed, 76 insertions(+), 112 deletions(-) diff --git a/benchmark/DummyDownloadTask.cs b/benchmark/DummyDownloadTask.cs index 5c5a95d..787bf71 100644 --- a/benchmark/DummyDownloadTask.cs +++ b/benchmark/DummyDownloadTask.cs @@ -11,14 +11,14 @@ public DummyDownloadTask(TaskFile file, HttpClient httpClient) : base(file, http } protected override async ValueTask DownloadFile( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { for (int i = 0; i < Size; i += 1) { if (Size % 512 == 0) { - progress?.Report(new ByteProgressEventArgs + progress?.Report(new ByteProgress { TotalBytes = Size, ProgressedBytes = i diff --git a/benchmark/DummyTask.cs b/benchmark/DummyTask.cs index 6704239..f541c8a 100644 --- a/benchmark/DummyTask.cs +++ b/benchmark/DummyTask.cs @@ -9,7 +9,7 @@ public class DummyTask : LinkedTask public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; protected override ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { for (int j = 0; j < 1024*256; j++) diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs index b1f993c..e32c4f0 100644 --- a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs @@ -13,14 +13,14 @@ public class TPLTaskExecutorWithDummyDownloaderBenchmark public static bool Verbose = false; public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; - public static ByteProgressEventArgs BytesProgressArgs; + public static ByteProgress BytesProgressArgs; private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private DummyDownloaderExtractor[] Extractors; private TPLTaskExecutor Executor; - private ByteProgressEventArgs previousEvent; + private ByteProgress previousEvent; private object consoleLock = new object(); private string? bottomMsg; diff --git a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs index cdad95c..6ca18ff 100644 --- a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs @@ -13,14 +13,14 @@ public class TPLTaskExecutorWithDummyTaskBenchmark public static bool Verbose = false; public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; - public static ByteProgressEventArgs BytesProgressArgs; + public static ByteProgress BytesProgressArgs; private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private DummyTaskExtractor[] Extractors; private TPLTaskExecutor Executor; - private ByteProgressEventArgs previousEvent; + private ByteProgress previousEvent; private object consoleLock = new object(); private string? bottomMsg; diff --git a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs index 87a26c0..fac29a5 100644 --- a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs @@ -15,7 +15,7 @@ public class TPLTaskExecutorWithRandomFileBenchmark private int extractorCount = 4; public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; - public static ByteProgressEventArgs BytesProgressArgs; + public static ByteProgress BytesProgressArgs; private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); diff --git a/src/ByteProgressEventArgs.cs b/src/ByteProgressEventArgs.cs index 4edc084..28e27ba 100644 --- a/src/ByteProgressEventArgs.cs +++ b/src/ByteProgressEventArgs.cs @@ -1,6 +1,6 @@ namespace CmlLib.Core; -public struct ByteProgressEventArgs +public struct ByteProgress { public long TotalBytes; public long ProgressedBytes; diff --git a/src/Executors/AsyncTaskExecutor.cs b/src/Executors/AsyncTaskExecutor.cs index a764429..784f6ce 100644 --- a/src/Executors/AsyncTaskExecutor.cs +++ b/src/Executors/AsyncTaskExecutor.cs @@ -13,7 +13,7 @@ public AsyncTaskExecutor(int maxParallelism) public ValueTask QueueTask( LinkedTask task, - IProgress progress, + IProgress progress, CancellationToken cancellationToken) { var context = new TaskExecutionContext(progress, cancellationToken); @@ -22,7 +22,7 @@ public AsyncTaskExecutor(int maxParallelism) private async ValueTask createQueuedTask( LinkedTask task, - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { await _semaphore.WaitAsync(); diff --git a/src/Executors/TPLTaskExecutor.cs b/src/Executors/TPLTaskExecutor.cs index 8392cf1..d72b1b3 100644 --- a/src/Executors/TPLTaskExecutor.cs +++ b/src/Executors/TPLTaskExecutor.cs @@ -9,27 +9,32 @@ namespace CmlLib.Core.Executors; public class TPLTaskExecutor { - private struct TaskProgress + private struct WorkerState { - public long TotalBytes; - public long ProgressedBytes; + public DateTime LastUpdate; + public Dictionary ProgressStorage; } - //private readonly ConcurrentDictionary _tasks; - private readonly ConcurrentDictionary _totalProgressStorage; - private readonly ThreadLocal> _progressPerThread; + private readonly ConcurrentDictionary _progressStorage; + private readonly ThreadLocal _progressPerThread; private readonly int _maxParallelism; public TPLTaskExecutor(int parallelism) { _maxParallelism = parallelism; - _totalProgressStorage = new ConcurrentDictionary(); - _progressPerThread = new ThreadLocal>( - () => new Dictionary(), true); + _progressStorage = new ConcurrentDictionary(); + _progressPerThread = new ThreadLocal( + () => + new WorkerState + { + LastUpdate = DateTime.MinValue, + ProgressStorage = new Dictionary() + }, + true); } public event EventHandler? FileProgress; - public event EventHandler? ByteProgress; + public event EventHandler? ByteProgress; private CancellationToken CancellationToken; private int totalTasks = 0; @@ -68,35 +73,13 @@ private void reportByteProgress() long totalBytes = 0; long progressedBytes = 0; - foreach (var dict in _progressPerThread.Values) - { - lock (dict) - { - foreach (var kv in dict) - { - _totalProgressStorage.AddOrUpdate( - kv.Key, - new TaskProgress - { - TotalBytes = kv.Value.TotalBytes, - ProgressedBytes = kv.Value.ProgressedBytes - }, - (_, old) => new TaskProgress - { - TotalBytes = kv.Value.TotalBytes, - ProgressedBytes = kv.Value.ProgressedBytes - }); - } - } - } - - foreach (var kv in _totalProgressStorage) + foreach (var kv in _progressStorage) { totalBytes += kv.Value.TotalBytes; progressedBytes += kv.Value.ProgressedBytes; } - ByteProgress?.Invoke(this, new ByteProgressEventArgs + ByteProgress?.Invoke(this, new ByteProgress { TotalBytes = totalBytes, ProgressedBytes = progressedBytes @@ -133,29 +116,10 @@ private BufferBlock createExecuteBlock(Task extractTask) if (downloadTask == null) return task.NextTask; - var progress = new SyncProgress(e => + var progress = new SyncProgress(e => { - var dict = _progressPerThread.Value!; - lock (dict) - { - dict[task.Name] = new TaskProgress - { - TotalBytes = e.TotalBytes, - ProgressedBytes = e.ProgressedBytes - }; - } - //_progressPerThread.Value!.AddOrUpdate( - // task.Name, - // new TaskProgress - // { - // TotalBytes = e.TotalBytes, - // ProgressedBytes = e.ProgressedBytes - // }, - // (_, old) => new TaskProgress - // { - // TotalBytes = e.TotalBytes, - // ProgressedBytes = e.ProgressedBytes - // }); + _progressPerThread.Value.ProgressStorage[task.Name] = e; + updateLocalProgress(); }); var nextTask = await downloadTask.Execute(progress, CancellationToken); @@ -165,34 +129,34 @@ private BufferBlock createExecuteBlock(Task extractTask) MaxDegreeOfParallelism = _maxParallelism }); + void updateLocalProgress() + { + if (DateTime.Now > _progressPerThread.Value.LastUpdate.AddSeconds(1)) + { + var storage = _progressPerThread.Value!.ProgressStorage; + foreach (var kv in storage) + { + _progressStorage.AddOrUpdate( + kv.Key, + kv.Value, + (_, _) => kv.Value); + } + storage.Clear(); + _progressPerThread.Value = new WorkerState + { + LastUpdate = DateTime.Now, + ProgressStorage = storage + }; + } + } LinkedTask? finalizeTask(LinkedTask task, LinkedTask? nextTask) { if (nextTask == null) { Interlocked.Increment(ref proceed); - TaskProgress progress; - var dict = _progressPerThread.Value!; - lock (dict) - { - if (!dict.TryGetValue(task.Name, out progress) && - !_totalProgressStorage.TryGetValue(task.Name, out progress)) - progress = new TaskProgress(); - dict[task.Name] = progress; - } - //_progressPerThread.Value!.AddOrUpdate( - // task.Name, - // new TaskProgress - // { - // TotalBytes = 0, - // ProgressedBytes = 0 - // }, - // (_, old) => old with - // { - // TotalBytes = old.TotalBytes, - // ProgressedBytes = old.TotalBytes - // }); fireEvent(task.Name, TaskStatus.Done); + updateLocalProgress(); if (proceed == totalTasks && extractTask.IsCompleted) { @@ -230,12 +194,12 @@ IEnumerable fireTasksEvent(IEnumerable tasks) continue; Interlocked.Increment(ref totalTasks); - var state = new TaskProgress + var progress = new ByteProgress { TotalBytes = task.File.Size, ProgressedBytes = 0 }; - _totalProgressStorage.TryAdd(task.Name, state); + _progressStorage.TryAdd(task.Name, progress); yield return task.First; fireEvent(task.Name, TaskStatus.Queued); diff --git a/src/Installer/MJava.cs b/src/Installer/MJava.cs index 5f42e9d..6dbef2b 100644 --- a/src/Installer/MJava.cs +++ b/src/Installer/MJava.cs @@ -103,7 +103,7 @@ private async Task downloadJavaLzmaAsync(string javaUrl) Directory.CreateDirectory(RuntimeDirectory); string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - var progress = new Progress(p => + var progress = new Progress(p => { var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); @@ -117,7 +117,7 @@ private void decompressJavaFile(string lzmaPath) string zippath = Path.Combine(Path.GetTempPath(), "jre.zip"); SevenZipWrapper.DecompressFileLZMA(lzmaPath, zippath); - SharpZipWrapper.Unzip(zippath, RuntimeDirectory, new Progress(p => + SharpZipWrapper.Unzip(zippath, RuntimeDirectory, new Progress(p => { var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; pProgressChanged?.Report(new ProgressChangedEventArgs(50 + (int)percent / 2, null)); diff --git a/src/Internals/SharpZipWrapper.cs b/src/Internals/SharpZipWrapper.cs index 62e1a38..005781c 100644 --- a/src/Internals/SharpZipWrapper.cs +++ b/src/Internals/SharpZipWrapper.cs @@ -7,7 +7,7 @@ internal static class SharpZipWrapper public static void Unzip( string zipPath, string extractTo, - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken = default) { using var fs = File.OpenRead(zipPath); @@ -26,7 +26,7 @@ public static void Unzip( using var output = File.Create(fullPath); s.CopyTo(output); - progress?.Report(new ByteProgressEventArgs + progress?.Report(new ByteProgress { TotalBytes = length, ProgressedBytes = s.Position diff --git a/src/Tasks/ActionTask.cs b/src/Tasks/ActionTask.cs index 9b430ca..a3ac226 100644 --- a/src/Tasks/ActionTask.cs +++ b/src/Tasks/ActionTask.cs @@ -9,7 +9,7 @@ public ActionTask(string name, Func> action) _action = action; protected override async ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { return await _action.Invoke(); diff --git a/src/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs index 95992bb..f3badde 100644 --- a/src/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -11,7 +11,7 @@ public ChmodTask(string name, string path) : base(name) => Path = path; protected override ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs index a1a9ed3..13f46fe 100644 --- a/src/Tasks/DownloadTask.cs +++ b/src/Tasks/DownloadTask.cs @@ -21,7 +21,7 @@ public DownloadTask(TaskFile file, HttpClient httpClient) : base(file) public long Size { get; } protected async override ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { await DownloadFile(progress, cancellationToken); @@ -29,7 +29,7 @@ public DownloadTask(TaskFile file, HttpClient httpClient) : base(file) } protected async virtual ValueTask DownloadFile( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { await HttpClientDownloadHelper.DownloadFileAsync( diff --git a/src/Tasks/FileCheckTask.cs b/src/Tasks/FileCheckTask.cs index 7219a81..b07a2e8 100644 --- a/src/Tasks/FileCheckTask.cs +++ b/src/Tasks/FileCheckTask.cs @@ -25,7 +25,7 @@ public FileCheckTask(string name, string path, string hash) : base(name) public string Hash { get; } protected override ValueTask OnExecutedWithResult( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { var result = IOUtil.CheckFileValidation(Path, Hash); diff --git a/src/Tasks/FileCopyTask.cs b/src/Tasks/FileCopyTask.cs index 6d7d373..a5bcb12 100644 --- a/src/Tasks/FileCopyTask.cs +++ b/src/Tasks/FileCopyTask.cs @@ -11,7 +11,7 @@ public FileCopyTask(string name, string sourcePath, IEnumerable destPath public string[] DestinationPaths { get; } protected override ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { if (!File.Exists(SourcePath)) diff --git a/src/Tasks/HttpClientDownloadHelper.cs b/src/Tasks/HttpClientDownloadHelper.cs index b84c446..0304c0b 100644 --- a/src/Tasks/HttpClientDownloadHelper.cs +++ b/src/Tasks/HttpClientDownloadHelper.cs @@ -14,7 +14,7 @@ public static async Task DownloadFileAsync( string url, long size, string path, - IProgress? progress = null, + IProgress? progress = null, CancellationToken cancellationToken = default) { IOUtil.CreateParentDirectory(path); @@ -34,7 +34,7 @@ public static async Task DownloadFileAsync( string url, long size, Stream destination, - IProgress? progress = null, + IProgress? progress = null, CancellationToken cancellationToken = default) { // Get the http headers first to examine the content length @@ -78,7 +78,7 @@ public static async Task DownloadFileAsync( await destination.WriteAsync(copyBuffer, 0, bytesRead).ConfigureAwait(false); totalRead += bytesRead; - progress?.Report(new ByteProgressEventArgs + progress?.Report(new ByteProgress { TotalBytes = contentLength, ProgressedBytes = totalRead diff --git a/src/Tasks/LZMADecompressTask.cs b/src/Tasks/LZMADecompressTask.cs index 525f59d..c511e2a 100644 --- a/src/Tasks/LZMADecompressTask.cs +++ b/src/Tasks/LZMADecompressTask.cs @@ -14,7 +14,7 @@ public LZMADecompressTask(string name, string lzmaPath, string extractTo) : base } protected override ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { SevenZipWrapper.DecompressFileLZMA(LZMAPath, ExtractPath); diff --git a/src/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs index 6bd96f6..a369121 100644 --- a/src/Tasks/LinkedTask.cs +++ b/src/Tasks/LinkedTask.cs @@ -18,7 +18,7 @@ public LinkedTask(string name) public LinkedTask? NextTask { get; private set; } public async ValueTask Execute( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { var nextTask = await OnExecuted(progress, cancellationToken); @@ -28,7 +28,7 @@ public LinkedTask(string name) } protected abstract ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken); public LinkedTask InsertNextTask(LinkedTask task) diff --git a/src/Tasks/LinkedTaskHead.cs b/src/Tasks/LinkedTaskHead.cs index a05e5fa..cd8d5b0 100644 --- a/src/Tasks/LinkedTaskHead.cs +++ b/src/Tasks/LinkedTaskHead.cs @@ -10,7 +10,7 @@ public LinkedTaskHead(LinkedTask? first, TaskFile file) => public TaskFile File { get; } public ValueTask Execute( - IProgress? progress = null, + IProgress? progress = null, CancellationToken cancellationToken = default) { if (First == null) diff --git a/src/Tasks/ResultTask.cs b/src/Tasks/ResultTask.cs index 58ffe62..6f98c02 100644 --- a/src/Tasks/ResultTask.cs +++ b/src/Tasks/ResultTask.cs @@ -16,7 +16,7 @@ public ResultTask(TaskFile file) : base(file) public LinkedTask? OnFalse { get; set; } protected override async ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { var result = await OnExecutedWithResult(progress, cancellationToken); @@ -29,6 +29,6 @@ public ResultTask(TaskFile file) : base(file) } protected abstract ValueTask OnExecutedWithResult( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Tasks/TaskExecutionContext.cs b/src/Tasks/TaskExecutionContext.cs index 6ff5781..756968f 100644 --- a/src/Tasks/TaskExecutionContext.cs +++ b/src/Tasks/TaskExecutionContext.cs @@ -3,12 +3,12 @@ namespace CmlLib.Core.Tasks; public class TaskExecutionContext { public TaskExecutionContext( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { (ProgressChanged, CancellationToken) = (progress, cancellationToken); } public CancellationToken CancellationToken { get; } - public IProgress? ProgressChanged { get; } + public IProgress? ProgressChanged { get; } } \ No newline at end of file diff --git a/src/Tasks/UnzipTask.cs b/src/Tasks/UnzipTask.cs index 9b5e1a9..c4d3a47 100644 --- a/src/Tasks/UnzipTask.cs +++ b/src/Tasks/UnzipTask.cs @@ -14,7 +14,7 @@ public UnzipTask(string name, string zipPath, string unzipTo) : base(name) } protected override ValueTask OnExecuted( - IProgress? progress, + IProgress? progress, CancellationToken cancellationToken) { SharpZipWrapper.Unzip(ZipPath, ExtractTo, null, cancellationToken); From 612f20c07adaa8cd121d1215eacf4b3b896157c5 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 19 Aug 2023 07:23:53 +0000 Subject: [PATCH 068/137] add new benchmarks --- benchmark/DummyDownloadTask.cs | 5 +- benchmark/DummyDownloaderExtractor.cs | 8 +- benchmark/DummyTask.cs | 2 +- .../ConcurrentDictionaryBenchmark.cs | 68 ++++++ .../Executors/ConcurrentQueueBenchmark.cs | 69 ++++++ benchmark/Executors/ExecutorBenchmarkBase.cs | 114 +++++++++ benchmark/Executors/LockBenchmark.cs | 78 +++++++ benchmark/Executors/SemaphoreSlimBenchmark.cs | 111 +++++++++ benchmark/Executors/ThreadLocalBenchmark.cs | 74 ++++++ benchmark/ExecutorsBenchmark.cs | 63 +++++ benchmark/Program.cs | 21 +- ...askExecutorWithDummyDownloaderBenchmark.cs | 2 +- src/Executors/TPLTaskExecutor.cs | 217 +++++++----------- 13 files changed, 689 insertions(+), 143 deletions(-) create mode 100644 benchmark/Executors/ConcurrentDictionaryBenchmark.cs create mode 100644 benchmark/Executors/ConcurrentQueueBenchmark.cs create mode 100644 benchmark/Executors/ExecutorBenchmarkBase.cs create mode 100644 benchmark/Executors/LockBenchmark.cs create mode 100644 benchmark/Executors/SemaphoreSlimBenchmark.cs create mode 100644 benchmark/Executors/ThreadLocalBenchmark.cs create mode 100644 benchmark/ExecutorsBenchmark.cs diff --git a/benchmark/DummyDownloadTask.cs b/benchmark/DummyDownloadTask.cs index 787bf71..6cfd65a 100644 --- a/benchmark/DummyDownloadTask.cs +++ b/benchmark/DummyDownloadTask.cs @@ -10,13 +10,13 @@ public DummyDownloadTask(TaskFile file, HttpClient httpClient) : base(file, http { } - protected override async ValueTask DownloadFile( + protected override ValueTask DownloadFile( IProgress? progress, CancellationToken cancellationToken) { for (int i = 0; i < Size; i += 1) { - if (Size % 512 == 0) + if (Size % 128 == 0) { progress?.Report(new ByteProgress { @@ -27,5 +27,6 @@ protected override async ValueTask DownloadFile( Seed += i; //await Task.Delay(1); } + return new ValueTask(); } } \ No newline at end of file diff --git a/benchmark/DummyDownloaderExtractor.cs b/benchmark/DummyDownloaderExtractor.cs index 078b246..d664617 100644 --- a/benchmark/DummyDownloaderExtractor.cs +++ b/benchmark/DummyDownloaderExtractor.cs @@ -8,8 +8,10 @@ public class DummyDownloaderExtractor : IFileExtractor { private readonly int _count; private readonly string _prefix; - public DummyDownloaderExtractor(string prefix, int count) => - (_prefix, _count) = (prefix, count); + private readonly long _size; + + public DummyDownloaderExtractor(string prefix, int count, long size) => + (_prefix, _count, _size) = (prefix, count, size); public ValueTask> Extract(MinecraftPath path, IVersion version) { @@ -23,7 +25,7 @@ private IEnumerable extract() { var file = new TaskFile(_prefix + "-" + i.ToString()) { - Size = 1024 * 256, + Size = _size, Path = "a.dat", Url = "a.dat" }; diff --git a/benchmark/DummyTask.cs b/benchmark/DummyTask.cs index f541c8a..620582b 100644 --- a/benchmark/DummyTask.cs +++ b/benchmark/DummyTask.cs @@ -12,7 +12,7 @@ public class DummyTask : LinkedTask IProgress? progress, CancellationToken cancellationToken) { - for (int j = 0; j < 1024*256; j++) + for (int j = 0; j < 1024; j++) Seed += j; return new ValueTask(NextTask); } diff --git a/benchmark/Executors/ConcurrentDictionaryBenchmark.cs b/benchmark/Executors/ConcurrentDictionaryBenchmark.cs new file mode 100644 index 0000000..89205e7 --- /dev/null +++ b/benchmark/Executors/ConcurrentDictionaryBenchmark.cs @@ -0,0 +1,68 @@ +using System.Collections.Concurrent; +using CmlLib.Core.Benchmarks; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Executors; + +public class ConcurrentDictionaryBenchmark : ExecutorBenchmarkBase +{ + private ConcurrentDictionary progressStorage = null!; + + protected override void Setup() + { + progressStorage = new ConcurrentDictionary(); + } + + protected override void OnTaskAdded(LinkedTaskHead head) + { + progressStorage[head.Name] = new ByteProgress + { + TotalBytes = head.File.Size, + ProgressedBytes = 0 + }; + } + + protected override void OnReportEvent() + { + long totalBytes = 0; + long progressedBytes = 0; + + foreach (var kv in progressStorage) + { + totalBytes += kv.Value.TotalBytes; + progressedBytes += kv.Value.ProgressedBytes; + } + + FireProgress(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } + + protected async override ValueTask Transform(LinkedTask t) + { + var task = t as DownloadTask; + if (task == null) + return t.NextTask; + + var progress = new SyncProgress(e => + { + progressStorage.AddOrUpdate(task.Name, e, (_, _) => e); + }); + + var nextTask = await task.Execute(progress, default); + + if (nextTask == null) + { + var p = new ByteProgress + { + TotalBytes = task.Size, + ProgressedBytes = task.Size + }; + progress.Report(p); + } + return nextTask; + } +} + diff --git a/benchmark/Executors/ConcurrentQueueBenchmark.cs b/benchmark/Executors/ConcurrentQueueBenchmark.cs new file mode 100644 index 0000000..34fb5fb --- /dev/null +++ b/benchmark/Executors/ConcurrentQueueBenchmark.cs @@ -0,0 +1,69 @@ +using System.Collections.Concurrent; +using CmlLib.Core.Tasks; +using CmlLib.Core.Executors; + +namespace CmlLib.Core.Benchmarks; + +public class ConcurrentQueueBenchmark : ExecutorBenchmarkBase +{ + private struct TaskSizeChangedEventArgs + { + public string Name; + public long TaskSize; + } + + private Dictionary taskSizeStorage = null!; + private ConcurrentQueue messageQueue = null!; + private long progressedBytes; + + protected override void Setup() + { + taskSizeStorage = new Dictionary(); + messageQueue = new ConcurrentQueue(); + } + + protected override void OnReportEvent() + { + while (messageQueue.TryDequeue(out var message)) + { + taskSizeStorage[message.Name] = message.TaskSize; + } + + long totalSize = 0; + foreach (var kv in taskSizeStorage) + { + totalSize += kv.Value; + } + + FireProgress(new ByteProgress + { + TotalBytes = totalSize, + ProgressedBytes = Interlocked.Read(ref progressedBytes) + }); + } + + protected override void OnTaskAdded(LinkedTaskHead head) + { + messageQueue.Enqueue(new TaskSizeChangedEventArgs + { + Name = head.Name, + TaskSize = head.File.Size + }); + } + + protected override async ValueTask Transform(LinkedTask task) + { + long prevBytes = 0; + var progress = new SyncProgress(e => + { + var d = e.ProgressedBytes - prevBytes; + //if (d < 1024 * 8) // 8kb + // return; + prevBytes = e.ProgressedBytes; + Interlocked.Add(ref progressedBytes, d); + }); + + var nextTask = await task.Execute(progress, default); + return nextTask; + } +} \ No newline at end of file diff --git a/benchmark/Executors/ExecutorBenchmarkBase.cs b/benchmark/Executors/ExecutorBenchmarkBase.cs new file mode 100644 index 0000000..a2950bf --- /dev/null +++ b/benchmark/Executors/ExecutorBenchmarkBase.cs @@ -0,0 +1,114 @@ +using System.Threading.Tasks.Dataflow; +using BenchmarkDotNet.Attributes; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public abstract class ExecutorBenchmarkBase +{ + public static ByteProgress LastEvent; + + public int MaxParallelism { get; set; } = 6; + public string Name { get; set; } = "D"; + public int Count { get; set; } = 1024*2; // 256 + public int Size { get; set; } = 1024*16; // 1024*128 + public bool Verbose { get; set; } = false; + + protected int TotalTasks = 0; + protected int ProceedTasks = 0; + protected LinkedTask[] Tasks { get; private set; } = null!; + public event EventHandler? ByteProgress; + + public async Task IterationSetup() + { + Setup(); + + var minecraftPath = new MinecraftPath(); + var version = new DummyVersion(); + var taskExtractor = new DummyDownloaderExtractor(Name, Count, Size); + var result = await taskExtractor.Extract(minecraftPath, version); + + var list = new List(); + foreach (var head in result) + { + if (head.First == null) + continue; + + OnTaskAdded(head); + Interlocked.Increment(ref TotalTasks); + list.Add(head.First); + } + Tasks = list.ToArray(); + + ByteProgress += (s, e) => LastEvent = e; + if (Verbose) + ByteProgress += (s, e) => Console.WriteLine($"{e.ProgressedBytes} / {e.TotalBytes}"); + } + + protected abstract void Setup(); + + protected virtual void OnTaskAdded(LinkedTaskHead head) + { + + } + + + public async Task Benchmark() + { + var block = CreateExecutorBlock(); + Task? lastSendTask = null; + foreach (var item in Tasks) + { + lastSendTask = block.SendAsync(item); + } + if (lastSendTask == null) + return; + await lastSendTask; + + var executeTask = block.Completion; + while (!executeTask.IsCompleted) + { + OnReportEvent(); + await Task.Delay(100); + } + OnReportEvent(); + await executeTask; + } + + protected virtual BufferBlock CreateExecutorBlock() + { + + var buffer = new BufferBlock(); + var executor = new TransformBlock( + async t => + { + var result = await Transform(t); + if (result == null) + { + Interlocked.Increment(ref ProceedTasks); + if (TotalTasks == ProceedTasks) + buffer.Complete(); + } + return result; + }, + new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = MaxParallelism + }); + buffer.LinkTo(executor); + executor.LinkTo(buffer!, t => t != null); + executor.LinkTo(DataflowBlock.NullTarget()); + return buffer; + } + + protected virtual ValueTask Transform(LinkedTask task) + { + return new ValueTask(task.NextTask); + } + protected abstract void OnReportEvent(); + + protected void FireProgress(ByteProgress progress) + { + ByteProgress?.Invoke(this, progress); + } +} \ No newline at end of file diff --git a/benchmark/Executors/LockBenchmark.cs b/benchmark/Executors/LockBenchmark.cs new file mode 100644 index 0000000..fbe8bb8 --- /dev/null +++ b/benchmark/Executors/LockBenchmark.cs @@ -0,0 +1,78 @@ +using CmlLib.Core.Executors; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public class LockBenchmark : ExecutorBenchmarkBase +{ + private Dictionary sizeStorage = null!; + private ThreadLocal> progressStorage = null!; + + protected override void Setup() + { + progressStorage = new ThreadLocal>( + () => new Dictionary(), + true); + sizeStorage = new Dictionary(); + } + + protected override void OnTaskAdded(LinkedTaskHead head) + { + sizeStorage[head.Name] = head.File.Size; + } + + protected override void OnReportEvent() + { + long totalBytes = 0; + long progressedBytes = 0; + + foreach (var dict in progressStorage.Values) + { + lock (dict) + { + foreach (var kv in dict) + { + totalBytes += kv.Value.TotalBytes; + progressedBytes += kv.Value.ProgressedBytes; + sizeStorage.Remove(kv.Key); + } + } + } + + foreach (var kv in sizeStorage) + totalBytes += kv.Value; + + FireProgress(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } + + protected override async ValueTask Transform(LinkedTask t) + { + var task = t as DownloadTask; + if (task == null) + return t.NextTask; + + var progress = new SyncProgress(e => + { + lock (progressStorage.Value!) + { + progressStorage.Value![task.Name] = e; + } + }); + + var nextTask = await task.Execute(progress, default); + + if (nextTask == null) + { + progress.Report(new ByteProgress + { + TotalBytes = task.Size, + ProgressedBytes = task.Size + }); + } + return nextTask; + } +} \ No newline at end of file diff --git a/benchmark/Executors/SemaphoreSlimBenchmark.cs b/benchmark/Executors/SemaphoreSlimBenchmark.cs new file mode 100644 index 0000000..b82cf6b --- /dev/null +++ b/benchmark/Executors/SemaphoreSlimBenchmark.cs @@ -0,0 +1,111 @@ +using System.Diagnostics; +using CmlLib.Core.Executors; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public class SemaphoreSlimBenchmark : ExecutorBenchmarkBase +{ + private Dictionary sizeStorage = null!; + private ThreadLocal> progressStorage = null!; + private SemaphoreSlim semaphore = null!; + + protected override void Setup() + { + sizeStorage = new Dictionary(); + progressStorage = new ThreadLocal>( + () => new Dictionary(), + true); + semaphore = new SemaphoreSlim(1); + } + + protected override void OnTaskAdded(LinkedTaskHead head) + { + sizeStorage[head.Name] = head.File.Size; + } + + protected override void OnReportEvent() + { + try + { + semaphore.Wait(100); + + long totalBytes = 0; + long progressedBytes = 0; + + foreach (var dict in progressStorage.Values) + { + foreach (var kv in dict) + { + totalBytes += kv.Value.TotalBytes; + progressedBytes += kv.Value.ProgressedBytes; + sizeStorage.Remove(kv.Key); + } + } + + foreach (var kv in sizeStorage) + totalBytes += kv.Value; + + FireProgress(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } + finally + { + semaphore.Release(); + } + } + + protected override async ValueTask Transform(LinkedTask task) + { + var progress = new SyncProgress(e => + { + var isLocked = false; + try + { + if (semaphore.Wait(0)) + { + isLocked = true; + progressStorage.Value![task.Name] = e; + //Console.WriteLine("progress"); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.ToString()); + } + finally + { + if (isLocked) + semaphore.Release(); + } + }); + + var nextTask = await task.Execute(progress, default); + if (nextTask == null) + { + try + { + semaphore.Wait(1000); + var previousProgress = progressStorage.Value![task.Name]; + progressStorage.Value![task.Name] = new ByteProgress + { + TotalBytes = previousProgress.TotalBytes, + ProgressedBytes = previousProgress.TotalBytes + }; + //Console.WriteLine("completed"); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + finally + { + semaphore.Release(); + } + } + return nextTask; + } +} \ No newline at end of file diff --git a/benchmark/Executors/ThreadLocalBenchmark.cs b/benchmark/Executors/ThreadLocalBenchmark.cs new file mode 100644 index 0000000..2c69798 --- /dev/null +++ b/benchmark/Executors/ThreadLocalBenchmark.cs @@ -0,0 +1,74 @@ +using System.Collections.Concurrent; +using CmlLib.Core.Executors; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public class ThreadLocalBenchmark : ExecutorBenchmarkBase +{ + private ConcurrentDictionary totalProgress = null!; + private ThreadLocal lastUpdate = null!; + + protected override void Setup() + { + totalProgress = new ConcurrentDictionary(); + lastUpdate = new ThreadLocal(() => 0); + } + + protected override void OnTaskAdded(LinkedTaskHead head) + { + var p = new ByteProgress + { + TotalBytes = head.File.Size, + ProgressedBytes = 0 + }; + totalProgress.AddOrUpdate(head.Name, p, (_, _) => p); + } + + protected override void OnReportEvent() + { + long totalBytes = 0; + long progressedBytes = 0; + + foreach (var kv in totalProgress) + { + totalBytes += kv.Value.TotalBytes; + progressedBytes += kv.Value.ProgressedBytes; + } + + FireProgress(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } + + protected async override ValueTask Transform(LinkedTask t) + { + var task = t as DownloadTask; + if (task == null) + return t.NextTask; + + var progress = new SyncProgress(e => + { + if (Math.Abs(lastUpdate.Value - Environment.TickCount) > 256) + { + totalProgress.AddOrUpdate(task.Name, e, (_, _) => e); + lastUpdate.Value = Environment.TickCount; + } + }); + + var nextTask = await task.Execute(progress, default); + + if (nextTask == null) + { + var p = new ByteProgress + { + TotalBytes = task.Size, + ProgressedBytes = task.Size + }; + totalProgress.AddOrUpdate(task.Name, p, (_, _) => p); + } + return nextTask; + } +} \ No newline at end of file diff --git a/benchmark/ExecutorsBenchmark.cs b/benchmark/ExecutorsBenchmark.cs new file mode 100644 index 0000000..45ba102 --- /dev/null +++ b/benchmark/ExecutorsBenchmark.cs @@ -0,0 +1,63 @@ +using BenchmarkDotNet.Attributes; +using CmlLib.Core.Executors; + +namespace CmlLib.Core.Benchmarks; + +[MemoryDiagnoser(false)] +public class ExecutorsBenchmark +{ + ConcurrentDictionaryBenchmark _concurrent; + ConcurrentQueueBenchmark _queue; + LockBenchmark _lock; + SemaphoreSlimBenchmark _semaphore; + ThreadLocalBenchmark _thread; + + [IterationSetup] + public void IterationSetup() + { + _concurrent = new ConcurrentDictionaryBenchmark(); + _concurrent.IterationSetup().Wait(); + + _queue = new ConcurrentQueueBenchmark(); + _queue.IterationSetup().Wait(); + + _lock = new LockBenchmark(); + _lock.IterationSetup().Wait(); + + _semaphore = new SemaphoreSlimBenchmark(); + _semaphore.IterationSetup().Wait(); + + _thread = new ThreadLocalBenchmark(); + _thread.IterationSetup().Wait(); + } + + //[Benchmark(Baseline = true)] + //public async Task StartConcurrent() + //{ + // await _concurrent.Benchmark(); + //} + + [Benchmark] + public async Task StartLock() + { + await _lock.Benchmark(); + } + + [Benchmark] + public async Task StartSemaphore() + { + await _semaphore.Benchmark(); + } + + [Benchmark(Baseline = true)] + public async Task StartQueue() + { + await _queue.Benchmark(); + } + + //[Benchmark] + //public async Task StartThread() + //{ + // await _thread.Benchmark(); + //} +} \ No newline at end of file diff --git a/benchmark/Program.cs b/benchmark/Program.cs index 1b39db8..90a6d8f 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,16 +1,23 @@ using System.Diagnostics; using BenchmarkDotNet.Running; using CmlLib.Core.Benchmarks; +using CmlLib.Core.Executors; -//var summary = BenchmarkRunner.Run(); -//return; +var summary = BenchmarkRunner.Run(); +return; -await once(); -async Task once() +//await once(); +//async Task once() { - var benchmark = new TPLTaskExecutorWithDummyDownloaderBenchmark(); - TPLTaskExecutorWithDummyDownloaderBenchmark.Verbose = true; - benchmark.IterationSetup(); + var benchmark = new SemaphoreSlimBenchmark(); + benchmark.Verbose = true; + benchmark.Size = 1024 * 1; + benchmark.Count = 1024 * 256; + //benchmark.MaxParallelism = 1; + //benchmark.MaxParallelism = 12; + //benchmark.Count = 1024 * 16; + + await benchmark.IterationSetup(); var sw = new Stopwatch(); sw.Start(); await benchmark.Benchmark(); diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs index e32c4f0..95ca225 100644 --- a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs @@ -29,7 +29,7 @@ public void IterationSetup() { Extractors = new DummyDownloaderExtractor[extractorCount]; for (int i = 0; i < extractorCount; i++) - Extractors[i] = new DummyDownloaderExtractor(i.ToString(), 1024); + Extractors[i] = new DummyDownloaderExtractor(i.ToString(), 1024, 1024*256); Executor = new TPLTaskExecutor(parallelism); Executor.FileProgress += (s, e) => FileProgressArgs = e; Executor.ByteProgress += (s, e) => BytesProgressArgs = e; diff --git a/src/Executors/TPLTaskExecutor.cs b/src/Executors/TPLTaskExecutor.cs index d72b1b3..f5dd852 100644 --- a/src/Executors/TPLTaskExecutor.cs +++ b/src/Executors/TPLTaskExecutor.cs @@ -1,5 +1,3 @@ -using System.Collections.Concurrent; -using System.Diagnostics; using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; using CmlLib.Core.Tasks; @@ -9,33 +7,26 @@ namespace CmlLib.Core.Executors; public class TPLTaskExecutor { - private struct WorkerState - { - public DateTime LastUpdate; - public Dictionary ProgressStorage; - } - - private readonly ConcurrentDictionary _progressStorage; - private readonly ThreadLocal _progressPerThread; + private readonly Dictionary _sizeStorage; + private readonly ThreadLocal> _progressStorage; + private readonly SemaphoreSlim _semaphore; private readonly int _maxParallelism; public TPLTaskExecutor(int parallelism) { - _maxParallelism = parallelism; - _progressStorage = new ConcurrentDictionary(); - _progressPerThread = new ThreadLocal( - () => - new WorkerState - { - LastUpdate = DateTime.MinValue, - ProgressStorage = new Dictionary() - }, + _sizeStorage = new Dictionary(); + _progressStorage = new ThreadLocal>( + () => new Dictionary(), true); + + _semaphore = new SemaphoreSlim(1); + _maxParallelism = parallelism; } public event EventHandler? FileProgress; public event EventHandler? ByteProgress; + private bool isStarted = false; private CancellationToken CancellationToken; private int totalTasks = 0; private int proceed = 0; @@ -46,6 +37,10 @@ public async ValueTask Install( IVersion version, CancellationToken cancellationToken) { + if (isStarted) + throw new InvalidOperationException("Already started"); + isStarted = true; + CancellationToken = cancellationToken; var extractBlock = createExtractBlock(version, path); @@ -62,122 +57,89 @@ public async ValueTask Install( var executeTask = executeBlock.Completion; while (!executeTask.IsCompleted) { - reportByteProgress(); + await reportByteProgress(); await Task.Delay(100); } - reportByteProgress(); + await reportByteProgress(); } - private void reportByteProgress() + private async Task reportByteProgress() { - long totalBytes = 0; - long progressedBytes = 0; - - foreach (var kv in _progressStorage) + try { - totalBytes += kv.Value.TotalBytes; - progressedBytes += kv.Value.ProgressedBytes; - } + await _semaphore.WaitAsync(); - ByteProgress?.Invoke(this, new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); - } + long totalBytes = 0; + long progressedBytes = 0; - private BufferBlock createExecuteBlock(Task extractTask) - { - var buffer = new BufferBlock(); - var executor = new TransformBlock(async task => - { - LinkedTask? nextTask = null; - Exception? exception = null; - try - { - nextTask = await task.Execute(null, CancellationToken); - } - catch (Exception ex) + foreach (var dict in _progressStorage.Values) { - exception = ex; - Debug.WriteLine(ex.ToString()); + foreach (var kv in dict) + { + totalBytes += kv.Value.TotalBytes; + progressedBytes += kv.Value.ProgressedBytes; + _sizeStorage.Remove(kv.Key); + } } - return finalizeTask(task, nextTask); + foreach (var kv in _sizeStorage) + totalBytes += kv.Value; - }, new ExecutionDataflowBlockOptions + fireByteProgress(totalBytes, progressedBytes); + } + finally { - MaxDegreeOfParallelism = _maxParallelism - }); + _semaphore.Release(); + } + } - var downloader = new TransformBlock(async task => + private BufferBlock createExecuteBlock(Task extractTask) + { + var buffer = new BufferBlock(); + var executor = new TransformBlock(async task => { - var downloadTask = task as DownloadTask; - if (downloadTask == null) - return task.NextTask; - var progress = new SyncProgress(e => { - _progressPerThread.Value.ProgressStorage[task.Name] = e; - updateLocalProgress(); - }); - - var nextTask = await downloadTask.Execute(progress, CancellationToken); - return finalizeTask(task, nextTask); - }, new ExecutionDataflowBlockOptions - { - MaxDegreeOfParallelism = _maxParallelism - }); - - void updateLocalProgress() - { - if (DateTime.Now > _progressPerThread.Value.LastUpdate.AddSeconds(1)) - { - var storage = _progressPerThread.Value!.ProgressStorage; - foreach (var kv in storage) + bool isLocked = false; + try { - _progressStorage.AddOrUpdate( - kv.Key, - kv.Value, - (_, _) => kv.Value); + if (_semaphore.Wait(0)) + { + isLocked = true; + _progressStorage.Value![task.Name] = e; + } } - storage.Clear(); - _progressPerThread.Value = new WorkerState + finally { - LastUpdate = DateTime.Now, - ProgressStorage = storage - }; - } - } + if (isLocked) + _semaphore.Release(); + } + }); - LinkedTask? finalizeTask(LinkedTask task, LinkedTask? nextTask) - { + var nextTask = await task.Execute(progress, CancellationToken); if (nextTask == null) { - Interlocked.Increment(ref proceed); - fireEvent(task.Name, TaskStatus.Done); - updateLocalProgress(); - - if (proceed == totalTasks && extractTask.IsCompleted) + try + { + _semaphore.Wait(); + var previousProgress = _progressStorage.Value![task.Name]; + _progressStorage.Value![task.Name] = new ByteProgress + { + TotalBytes = previousProgress.TotalBytes, + ProgressedBytes = previousProgress.TotalBytes + }; + } + finally { - buffer.Complete(); + _semaphore.Release(); } } return nextTask; - } - - buffer.LinkTo(downloader!, t => t is DownloadTask); - buffer.LinkTo(executor); - - void linkToBuffer(IPropagatorBlock block, BufferBlock buffer) + }, new ExecutionDataflowBlockOptions { - block.LinkTo(buffer!, t => t != null); - block.LinkTo(DataflowBlock.NullTarget()); - } - - linkToBuffer(executor, buffer); - linkToBuffer(downloader, buffer); + MaxDegreeOfParallelism = _maxParallelism + }); return buffer; } @@ -186,31 +148,19 @@ private IPropagatorBlock createExtractBlock( IVersion version, MinecraftPath path) { - IEnumerable fireTasksEvent(IEnumerable tasks) - { - foreach (var task in tasks) - { - if (task.First == null) - continue; - - Interlocked.Increment(ref totalTasks); - var progress = new ByteProgress - { - TotalBytes = task.File.Size, - ProgressedBytes = 0 - }; - _progressStorage.TryAdd(task.Name, progress); - - yield return task.First; - fireEvent(task.Name, TaskStatus.Queued); - } - } - var block = new TransformManyBlock(async extractor => { var tasks = await extractor.Extract(path, version); - var iterated = fireTasksEvent(tasks); - return iterated; + return tasks + .Where(task => task.First != null) + .Select(task => + { + Interlocked.Increment(ref totalTasks); + fireFileProgress(task.Name, TaskStatus.Queued); + _sizeStorage[task.Name] = task.File.Size; + + return task.First; + })!; }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = _maxParallelism @@ -219,7 +169,7 @@ IEnumerable fireTasksEvent(IEnumerable tasks) return block; } - private void fireEvent(string name, TaskStatus status) + private void fireFileProgress(string name, TaskStatus status) { FileProgress?.Invoke(this, new TaskExecutorProgressChangedEventArgs(name, status) { @@ -227,4 +177,13 @@ private void fireEvent(string name, TaskStatus status) ProceedTasks = proceed }); } + + private void fireByteProgress(long totalBytes, long progressedBytes) + { + ByteProgress?.Invoke(this, new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } } \ No newline at end of file From 108e307f0c0f8657db10385683b602642547bdbc Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 24 Aug 2023 13:58:09 +0000 Subject: [PATCH 069/137] update --- ...teProgressEventArgs.cs => ByteProgress.cs} | 0 src/CMLauncher.cs | 89 -------- .../DownloadFileChangedEventArgs.cs | 28 --- .../FileProgressChangedEventArgs.cs | 16 -- src/Executors/AsyncTaskExecutor.cs | 47 ---- .../TaskExecutorProgressChangedEventArgs.cs | 20 -- src/FileExtractors/AssetFileExtractor.cs | 7 +- src/FileExtractors/ClientFileExtractor.cs | 9 +- src/FileExtractors/DefaultFileExtractors.cs | 46 ++++ ...llection.cs => FileExtractorCollection.cs} | 27 +-- src/FileExtractors/IFileExtractor.cs | 9 +- src/FileExtractors/JavaFileExtractor.cs | 100 +++++---- src/FileExtractors/LegacyJavaFileExtractor.cs | 96 +++++++++ src/FileExtractors/LibraryFileExtractor.cs | 31 +-- src/FileExtractors/LogFileExtractor.cs | 9 +- src/Files/ModFile.cs | 22 -- src/Files/ModFileFactory.cs | 25 --- src/Installer/MJava.cs | 126 ----------- src/Installers/IGameInstaller.cs | 17 ++ .../InstallerProgressChangedEventArgs.cs | 12 ++ src/{Executors => Installers}/SyncProgress.cs | 2 +- .../TPLGameInstaller.cs} | 149 +++++++------ src/{Executors => Installers}/TaskState.cs | 2 +- src/Internals/HttpUtil.cs | 6 + src/Internals/IOUtil.cs | 16 -- src/Java/IJavaPathResolver.cs | 8 +- src/Java/MinecraftJavaPathResolver.cs | 41 ++-- src/LauncherBuilder.cs | 123 +++++++++++ src/Mapper.cs | 200 +++++++++--------- src/MinecraftLauncher.cs | 135 ++++++++++++ .../FabricMC/FabricLoader.cs | 2 +- .../FabricMC/FabricVersionLoader.cs | 2 +- .../LiteLoader/LiteLoaderInstaller.cs | 2 +- .../LiteLoader/LiteLoaderVersionLoader.cs | 2 +- .../LiteLoader/LiteLoaderVersionMetadata.cs | 4 +- .../QuiltMC/QuiltLoader.cs | 2 +- .../QuiltMC/QuiltVersionLoader.cs | 2 +- src/MojangServer.cs | 21 +- src/Natives/INativeLibraryExtractor.cs | 10 + .../NativeLibraryExtractor.cs} | 45 ++-- src/PackageName.cs | 102 +++++---- src/{Launcher => ProcessBuilder}/MArgument.cs | 2 +- .../MLaunchOption.cs | 4 +- .../MinecraftArgumentBuilder.cs | 45 ---- .../MinecraftProcessBuilder.cs} | 117 +++++----- src/ProcessBuilder/ProcessArgumentBuilder.cs | 184 ++++++++++++++-- src/Rules/IRulesEvaluator.cs | 1 + src/Tasks/ActionTask.cs | 6 +- src/Tasks/FileCheckTask.cs | 25 ++- src/Tasks/HttpClientDownloadHelper.cs | 3 +- src/Tasks/TaskExecutionContext.cs | 4 +- src/Version/IVersion.cs | 2 +- src/Version/JsonArgumentParser.cs | 2 +- src/Version/JsonVersion.cs | 2 +- src/VersionMetadata/MVersionCollection.cs | 16 +- 55 files changed, 1091 insertions(+), 934 deletions(-) rename src/{ByteProgressEventArgs.cs => ByteProgress.cs} (100%) delete mode 100644 src/CMLauncher.cs delete mode 100644 src/Downloader/DownloadFileChangedEventArgs.cs delete mode 100644 src/Downloader/FileProgressChangedEventArgs.cs delete mode 100644 src/Executors/AsyncTaskExecutor.cs delete mode 100644 src/Executors/TaskExecutorProgressChangedEventArgs.cs create mode 100644 src/FileExtractors/DefaultFileExtractors.cs rename src/FileExtractors/{FileCheckerCollection.cs => FileExtractorCollection.cs} (55%) create mode 100644 src/FileExtractors/LegacyJavaFileExtractor.cs delete mode 100644 src/Files/ModFile.cs delete mode 100644 src/Files/ModFileFactory.cs delete mode 100644 src/Installer/MJava.cs create mode 100644 src/Installers/IGameInstaller.cs create mode 100644 src/Installers/InstallerProgressChangedEventArgs.cs rename src/{Executors => Installers}/SyncProgress.cs (87%) rename src/{Executors/TPLTaskExecutor.cs => Installers/TPLGameInstaller.cs} (51%) rename src/{Executors => Installers}/TaskState.cs (65%) create mode 100644 src/Internals/HttpUtil.cs create mode 100644 src/LauncherBuilder.cs create mode 100644 src/MinecraftLauncher.cs rename src/{Installer => ModLoaders}/FabricMC/FabricLoader.cs (92%) rename src/{Installer => ModLoaders}/FabricMC/FabricVersionLoader.cs (98%) rename src/{Installer => ModLoaders}/LiteLoader/LiteLoaderInstaller.cs (98%) rename src/{Installer => ModLoaders}/LiteLoader/LiteLoaderVersionLoader.cs (97%) rename src/{Installer => ModLoaders}/LiteLoader/LiteLoaderVersionMetadata.cs (98%) rename src/{Installer => ModLoaders}/QuiltMC/QuiltLoader.cs (91%) rename src/{Installer => ModLoaders}/QuiltMC/QuiltVersionLoader.cs (98%) create mode 100644 src/Natives/INativeLibraryExtractor.cs rename src/{Launcher/MNative.cs => Natives/NativeLibraryExtractor.cs} (53%) rename src/{Launcher => ProcessBuilder}/MArgument.cs (87%) rename src/{Launcher => ProcessBuilder}/MLaunchOption.cs (93%) delete mode 100644 src/ProcessBuilder/MinecraftArgumentBuilder.cs rename src/{Launcher/MLaunch.cs => ProcessBuilder/MinecraftProcessBuilder.cs} (66%) diff --git a/src/ByteProgressEventArgs.cs b/src/ByteProgress.cs similarity index 100% rename from src/ByteProgressEventArgs.cs rename to src/ByteProgress.cs diff --git a/src/CMLauncher.cs b/src/CMLauncher.cs deleted file mode 100644 index 9e4c7a8..0000000 --- a/src/CMLauncher.cs +++ /dev/null @@ -1,89 +0,0 @@ -using CmlLib.Core.Downloader; -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Java; -using CmlLib.Core.Rules; -using CmlLib.Core.Version; -using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; -using System.ComponentModel; - -namespace CmlLib.Core; - -public class CMLauncher -{ - private readonly HttpClient _httpClient; - - public CMLauncher(string path, HttpClient httpClient) : this(new MinecraftPath(path), httpClient, LauncherOSRule.Current) - { - } - - public CMLauncher(MinecraftPath path, HttpClient httpClient, LauncherOSRule os) - { - _httpClient = httpClient; - - this.os = os; - MinecraftPath = path; - VersionLoader = new VersionLoaderCollection - { - new LocalVersionLoader(MinecraftPath), - new MojangVersionLoader(_httpClient), - }; - - pFileChanged = new Progress( - e => FileChanged?.Invoke(e)); - pProgressChanged = new Progress( - e => ProgressChanged?.Invoke(this, e)); - - JavaPathResolver = new MinecraftJavaPathResolver(path); - GameFileCheckers = FileExtractorCollection.CreateDefault(_httpClient, JavaPathResolver, new RulesEvaluator(), new RulesEvaluatorContext(os)); - } - - public event DownloadFileChangedHandler? FileChanged; - public event ProgressChangedEventHandler? ProgressChanged; - - private readonly IProgress pFileChanged; - private readonly IProgress pProgressChanged; - - public readonly LauncherOSRule os; - - public MinecraftPath MinecraftPath { get; private set; } - public VersionCollection? Versions { get; private set; } - public IVersionLoader VersionLoader { get; set; } - - public FileExtractorCollection GameFileCheckers { get; private set; } - - public IJavaPathResolver JavaPathResolver { get; set; } - - public async Task GetAllVersionsAsync() - { - Versions = await VersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - return Versions; - } - - public async Task GetVersionAsync(string versionName) - { - if (Versions == null) - await GetAllVersionsAsync().ConfigureAwait(false); - - var version = await Versions!.GetAndSaveVersionAsync(versionName, MinecraftPath) - .ConfigureAwait(false); - return version; - } - - private void checkLaunchOption(MLaunchOption option) - { - if (option.Path == null) - option.Path = MinecraftPath; - } - - public string? GetJavaPath(IVersion version) - { - if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion?.Component)) - return null; - - return JavaPathResolver.GetJavaBinaryPath(version.JavaVersion.Value, os); - } - - public string? GetDefaultJavaPath() => JavaPathResolver.GetDefaultJavaBinaryPath(os); -} diff --git a/src/Downloader/DownloadFileChangedEventArgs.cs b/src/Downloader/DownloadFileChangedEventArgs.cs deleted file mode 100644 index 0ac4100..0000000 --- a/src/Downloader/DownloadFileChangedEventArgs.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace CmlLib.Core.Downloader -{ - public delegate void DownloadFileChangedHandler(DownloadFileChangedEventArgs e); - - public enum MFile { Runtime, Library, Resource, Minecraft, Others } - - public class DownloadFileChangedEventArgs : EventArgs - { - public DownloadFileChangedEventArgs(MFile type, object source, string? filename, int total, int progressed) - { - FileType = type; - FileName = filename; - TotalFileCount = total; - ProgressedFileCount = progressed; - Source = source; - } - - // FileType and FileKind is exactly same. - public MFile FileType { get; private set; } - public MFile FileKind => FileType; // backward compatible - public string? FileName { get; private set; } - public int TotalFileCount { get; private set; } - public int ProgressedFileCount { get; private set; } - public object Source { get; private set; } - } -} diff --git a/src/Downloader/FileProgressChangedEventArgs.cs b/src/Downloader/FileProgressChangedEventArgs.cs deleted file mode 100644 index 08aa6bc..0000000 --- a/src/Downloader/FileProgressChangedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.ComponentModel; - -namespace CmlLib.Core.Downloader -{ - public class FileProgressChangedEventArgs : ProgressChangedEventArgs - { - public FileProgressChangedEventArgs(long total, long received, int percent) : base(percent, null) - { - this.TotalBytes = total; - this.ReceivedBytes = received; - } - - public long TotalBytes { get; private set; } - public long ReceivedBytes { get; private set; } - } -} diff --git a/src/Executors/AsyncTaskExecutor.cs b/src/Executors/AsyncTaskExecutor.cs deleted file mode 100644 index 784f6ce..0000000 --- a/src/Executors/AsyncTaskExecutor.cs +++ /dev/null @@ -1,47 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Executors; - -public class AsyncTaskExecutor : IDisposable -{ - private readonly SemaphoreSlim _semaphore; - - public AsyncTaskExecutor(int maxParallelism) - { - _semaphore = new SemaphoreSlim(maxParallelism); - } - - public ValueTask QueueTask( - LinkedTask task, - IProgress progress, - CancellationToken cancellationToken) - { - var context = new TaskExecutionContext(progress, cancellationToken); - return createQueuedTask(task, progress, cancellationToken); - } - - private async ValueTask createQueuedTask( - LinkedTask task, - IProgress? progress, - CancellationToken cancellationToken) - { - await _semaphore.WaitAsync(); - - LinkedTask? nextTask = null; - try - { - nextTask = await task.Execute(progress, cancellationToken); - } - finally - { - _semaphore.Release(); - } - return nextTask; - } - - - public void Dispose() - { - ((IDisposable)_semaphore).Dispose(); - } -} \ No newline at end of file diff --git a/src/Executors/TaskExecutorProgressChangedEventArgs.cs b/src/Executors/TaskExecutorProgressChangedEventArgs.cs deleted file mode 100644 index 040227a..0000000 --- a/src/Executors/TaskExecutorProgressChangedEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace CmlLib.Core.Executors; - -public struct TaskExecutorProgressChangedEventArgs -{ - public TaskExecutorProgressChangedEventArgs(string name, TaskStatus status) => - (Name, EventType) = (name, status); - - public int TotalTasks { get; set; } = 0; - public int ProceedTasks { get; set; } = 0; - public TaskStatus EventType { get; } - public string Name { get; } - - public void Print() - { - //if (status != TaskStatus.Done) return; - //if (proceed % 100 != 0) return; - var now = DateTime.Now.ToString("hh:mm:ss.fff"); - Console.WriteLine($"[{now}][{ProceedTasks}/{TotalTasks}][{EventType}] {Name}"); - } -} \ No newline at end of file diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index e838740..6c6b42d 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -4,6 +4,7 @@ using CmlLib.Core.Tasks; using CmlLib.Core.Version; using CmlLib.Core.Internals; +using CmlLib.Core.Rules; namespace CmlLib.Core.FileExtractors; @@ -29,7 +30,11 @@ public string AssetServer } } - public async ValueTask> Extract(MinecraftPath path, IVersion version) + public async ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { var assets = version.AssetIndex; if (assets == null || diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 603d6cf..3e40ea4 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -1,4 +1,5 @@ -using CmlLib.Core.Tasks; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; @@ -12,7 +13,11 @@ public ClientFileExtractor(HttpClient httpClient) _httpClient = httpClient; } - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { var result = extract(path, version); return new ValueTask>(result); diff --git a/src/FileExtractors/DefaultFileExtractors.cs b/src/FileExtractors/DefaultFileExtractors.cs new file mode 100644 index 0000000..395299e --- /dev/null +++ b/src/FileExtractors/DefaultFileExtractors.cs @@ -0,0 +1,46 @@ +using CmlLib.Core.Java; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.FileExtractors; + +public class DefaultFileExtractors +{ + public static DefaultFileExtractors CreateDefault( + HttpClient httpClient, + IRulesEvaluator rulesEvaluator, + IJavaPathResolver javaPathResolver) + { + var extractors = new DefaultFileExtractors(); + extractors.Library = new LibraryFileExtractor(rulesEvaluator, httpClient); + extractors.Asset = new AssetFileExtractor(httpClient); + extractors.Client = new ClientFileExtractor(httpClient); + extractors.Java = new JavaFileExtractor(httpClient, javaPathResolver); + extractors.Log = new LogFileExtractor(httpClient); + return extractors; + } + + public LibraryFileExtractor? Library { get; set; } + public AssetFileExtractor? Asset { get; set; } + public ClientFileExtractor? Client { get; set; } + public JavaFileExtractor? Java { get; set; } + public LogFileExtractor? Log { get; set; } + public IEnumerable ExtraExtractors { get; set; } = + Enumerable.Empty(); + + public FileExtractorCollection ToExtractorCollection() + { + var extractors = new FileExtractorCollection(); + if (Library != null) + extractors.Add(Library); + if (Asset != null) + extractors.Add(Asset); + if (Client != null) + extractors.Add(Client); + if (Log != null) + extractors.Add(Log); + if (Java != null) + extractors.Add(Java); + extractors.AddRange(ExtraExtractors); + return extractors; + } +} \ No newline at end of file diff --git a/src/FileExtractors/FileCheckerCollection.cs b/src/FileExtractors/FileExtractorCollection.cs similarity index 55% rename from src/FileExtractors/FileCheckerCollection.cs rename to src/FileExtractors/FileExtractorCollection.cs index 4857832..5c672c5 100644 --- a/src/FileExtractors/FileCheckerCollection.cs +++ b/src/FileExtractors/FileExtractorCollection.cs @@ -1,41 +1,16 @@ using System.Collections; -using CmlLib.Core.Java; -using CmlLib.Core.Rules; namespace CmlLib.Core.FileExtractors; public class FileExtractorCollection : IEnumerable { - public static FileExtractorCollection CreateDefault( - HttpClient httpClient, - IJavaPathResolver javaPathResolver, - IRulesEvaluator rulesEvaluator, - RulesEvaluatorContext context) - { - var extractors = new FileExtractorCollection(); - - var library = new LibraryFileExtractor(rulesEvaluator, context, httpClient); - var asset = new AssetFileExtractor(httpClient); - var client = new ClientFileExtractor(httpClient); - var java = new JavaFileExtractor(httpClient, javaPathResolver, context.OS); - var log = new LogFileExtractor(httpClient); - - extractors.Add(library); - extractors.Add(asset); - extractors.Add(client); - extractors.Add(log); - extractors.Add(java); - - return extractors; - } - public IFileExtractor this[int index] => checkers[index]; private readonly List checkers; public FileExtractorCollection() { - checkers = new List(4); + checkers = new List(5); } public void Add(IFileExtractor item) diff --git a/src/FileExtractors/IFileExtractor.cs b/src/FileExtractors/IFileExtractor.cs index 16d27f7..ad1d304 100644 --- a/src/FileExtractors/IFileExtractor.cs +++ b/src/FileExtractors/IFileExtractor.cs @@ -1,9 +1,14 @@ -using CmlLib.Core.Tasks; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; public interface IFileExtractor { - ValueTask> Extract(MinecraftPath path, IVersion version); + ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 0584ac2..7d97c46 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.Text.Json; -using CmlLib.Core.Installer; using CmlLib.Core.Java; using CmlLib.Core.Rules; using CmlLib.Core.Tasks; @@ -11,30 +10,43 @@ namespace CmlLib.Core.FileExtractors; public class JavaFileExtractor : IFileExtractor { - private readonly LauncherOSRule _os; private readonly HttpClient _httpClient; - public readonly IJavaPathResolver _javaPathResolver; + private readonly IJavaPathResolver _javaPathResolver; public string JavaManifestServer { get; set; } = MojangServer.JavaManifest; public JavaFileExtractor( HttpClient httpClient, - IJavaPathResolver javaPathResolver, - LauncherOSRule rule) + IJavaPathResolver javaPathResolver) { _httpClient = httpClient; _javaPathResolver = javaPathResolver; - _os = rule; } - public async ValueTask> Extract(MinecraftPath path, IVersion version) + public async ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { + JavaVersion javaVersion; if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion.Value.Component)) - return await extractFromJavaVersion(MinecraftJavaPathResolver.JreLegacyVersion); + javaVersion = MinecraftJavaPathResolver.JreLegacyVersion; else - return await extractFromJavaVersion(version.JavaVersion.Value); + javaVersion = version.JavaVersion.Value; + return await extractFromJavaVersion( + path, + javaVersion, + version, + rulesContext, + cancellationToken); } - private async ValueTask> extractFromJavaVersion(JavaVersion javaVersion) + private async ValueTask> extractFromJavaVersion( + MinecraftPath minecraftPath, + JavaVersion javaVersion, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { using var response = await _httpClient.GetStreamAsync(JavaManifestServer); using var jsonDocument = JsonDocument.Parse(response); @@ -42,22 +54,22 @@ private async ValueTask> extractFromJavaVersion(Java var osName = getJavaOSName(); if (string.IsNullOrEmpty(osName)) - return await legacyJavaChecker(); + return await createLegacyJavaTask(minecraftPath, version, rulesContext, cancellationToken); if (!root.TryGetProperty(osName, out var javaVersionsForOS)) - return await legacyJavaChecker(); + return await createLegacyJavaTask(minecraftPath, version, rulesContext, cancellationToken); var currentVersionManifestUrl = getManifestUrl(javaVersionsForOS, javaVersion); if (!string.IsNullOrEmpty(currentVersionManifestUrl)) - return await extractFromManifestUrl(currentVersionManifestUrl, javaVersion); + return await extractFromManifestUrl(minecraftPath, currentVersionManifestUrl, javaVersion, rulesContext, cancellationToken); else if (javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) { var legacyVersionManifestUrl = getManifestUrl(javaVersionsForOS, MinecraftJavaPathResolver.JreLegacyVersion); if (!string.IsNullOrEmpty(legacyVersionManifestUrl)) - return await extractFromManifestUrl(legacyVersionManifestUrl, javaVersion); + return await extractFromManifestUrl(minecraftPath, legacyVersionManifestUrl, javaVersion, rulesContext, cancellationToken); } - return await legacyJavaChecker(); + return await createLegacyJavaTask(minecraftPath, version, rulesContext, cancellationToken); } private string? getJavaOSName() // TODO: find exact version name @@ -90,20 +102,29 @@ private async ValueTask> extractFromJavaVersion(Java .GetString(); } - private async ValueTask> extractFromManifestUrl(string manifestUrl, JavaVersion version) + private async ValueTask> extractFromManifestUrl( + MinecraftPath minecraftPath, + string manifestUrl, + JavaVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { - using var res = await _httpClient.GetStreamAsync(manifestUrl); - var json = JsonDocument.Parse(res); // should be disposed after extraction - return extractFromManifestJson(json, version); + using var res = await _httpClient.GetAsync(manifestUrl, cancellationToken); + using var stream = await res.Content.ReadAsStreamAsync(); + var json = JsonDocument.Parse(stream); // should be disposed after extraction + return extractFromManifestJson(minecraftPath, json, version, rulesContext); } private IEnumerable extractFromManifestJson( - JsonDocument _json, JavaVersion version) + MinecraftPath minecraftPath, + JsonDocument _json, + JavaVersion version, + RulesEvaluatorContext rulesContext) { using var json = _json; var manifest = json.RootElement; - var path = _javaPathResolver.GetJavaDirPath(version); + var path = _javaPathResolver.GetJavaDirPath(minecraftPath, version, rulesContext); if (!manifest.TryGetProperty("files", out var files)) yield break; @@ -165,36 +186,13 @@ private IEnumerable extractFromManifestJson( return new LinkedTaskHead(checkTask, file); } - // legacy java checker using MJava - private async ValueTask> legacyJavaChecker() + private async ValueTask> createLegacyJavaTask( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { - var legacyJavaPath = _javaPathResolver.GetJavaDirPath(MinecraftJavaPathResolver.CmlLegacyVersion); - - var mJava = new MJava(_httpClient, legacyJavaPath); - if (mJava.CheckJavaExistence(_os)) - return Enumerable.Empty(); - - var javaUrl = await mJava.GetJavaUrlAsync(); - var lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); - - var file = new TaskFile("jre.lzma") - { - Url = javaUrl, - Path = lzmaPath - }; - - var task = LinkedTask.LinkTasks(new LinkedTask[] - { - new DownloadTask(file, _httpClient), - new LZMADecompressTask(file.Name, lzmaPath, zipPath), - new UnzipTask(file.Name, zipPath, legacyJavaPath), - new ChmodTask(file.Name, mJava.GetBinaryPath(_os)) - }); - - return new LinkedTaskHead[] - { - new LinkedTaskHead(task!, file) - }; + var legacyJava = new LegacyJavaFileExtractor(_httpClient, _javaPathResolver); + return await legacyJava.Extract(path, version, rulesContext, cancellationToken); } } \ No newline at end of file diff --git a/src/FileExtractors/LegacyJavaFileExtractor.cs b/src/FileExtractors/LegacyJavaFileExtractor.cs new file mode 100644 index 0000000..0ffb51b --- /dev/null +++ b/src/FileExtractors/LegacyJavaFileExtractor.cs @@ -0,0 +1,96 @@ +using System.ComponentModel; +using CmlLib.Core.Java; +using System.Text.Json; +using System.Net; +using CmlLib.Core.Rules; +using CmlLib.Core.Internals; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.FileExtractors; + +public class LegacyJavaFileExtractor : IFileExtractor +{ + private readonly HttpClient _httpClient; + private readonly IJavaPathResolver _javaPathResolver; + + public LegacyJavaFileExtractor( + HttpClient httpClient, + IJavaPathResolver resolver) => + (_httpClient, _javaPathResolver) = (httpClient, resolver); + + public ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) + { + var task = createTask(path, rulesContext, cancellationToken); + return new ValueTask>(new LinkedTaskHead[] { task }); + } + + private LinkedTaskHead createTask( + MinecraftPath path, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) + { + var javaVersion = new JavaVersion + { + Component = "m-legacy", + MajorVersion = 17 + }; + + var javaBinaryPath = _javaPathResolver.GetJavaBinaryPath(path, javaVersion, rulesContext); + var javaBinaryDir = _javaPathResolver.GetJavaDirPath(path, javaVersion, rulesContext); + var file = new TaskFile(javaVersion.Component) + { + Path = javaBinaryPath + }; + + var checkTask = new FileCheckTask(file); + var javaUrlTask = new ActionTask(file.Name, async task => + { + var javaUrl = await GetJavaUrlAsync(); + var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); + var lzmaFile = new TaskFile("jre.lzma") + { + Path = Path.Combine(Path.GetTempPath(), "jre.lzma"), + Url = javaUrl + }; + + var linkedTask = LinkedTask.LinkTasks( + new DownloadTask(lzmaFile, _httpClient), + new LZMADecompressTask("jre.lzma", lzmaFile.Path, zipPath), + new UnzipTask("jre.zip", zipPath, javaBinaryDir), + new ChmodTask(javaVersion.Component, javaBinaryPath) + ); + return task.InsertNextTask(linkedTask!); + }); + + checkTask.OnFalse = javaUrlTask; + return new LinkedTaskHead(checkTask, file); + } + + public async Task GetJavaUrlAsync() + { + var json = await _httpClient.GetStringAsync(MojangServer.LauncherMeta) + .ConfigureAwait(false); + return parseLauncherMetadata(json); + } + + private string parseLauncherMetadata(string json) + { + using var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; + + var javaUrl = root + .GetPropertyOrNull(LauncherOSRule.Current.Name)? + .GetPropertyOrNull(LauncherOSRule.Current.Arch)? + .GetPropertyOrNull("jre")? + .GetPropertyValue("url"); + + if (string.IsNullOrEmpty(javaUrl)) + throw new PlatformNotSupportedException("Downloading JRE on current OS is not supported. Set JavaPath manually."); + return javaUrl; + } +} \ No newline at end of file diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index b365097..dbc58b3 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -6,17 +6,14 @@ namespace CmlLib.Core.FileExtractors; public class LibraryFileExtractor : IFileExtractor { - private readonly RulesEvaluatorContext _rulesContext; private readonly IRulesEvaluator _rulesEvaluator; private readonly HttpClient _httpClient; public LibraryFileExtractor( - IRulesEvaluator rulesEvaluator, - RulesEvaluatorContext context, + IRulesEvaluator rulesEvaluator, HttpClient httpClient) { _rulesEvaluator = rulesEvaluator; - _rulesContext = context; _httpClient = httpClient; } @@ -33,21 +30,31 @@ public string LibraryServer } } - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { - var result = extract(path, version); + var result = extract(path, version, rulesContext); return new ValueTask>(result); } - private IEnumerable extract(MinecraftPath path, IVersion version) + private IEnumerable extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext) { return version.Libraries .Where(lib => lib.CheckIsRequired("SIDE")) - .Where(lib => lib.Rules == null || _rulesEvaluator.Match(lib.Rules, _rulesContext)) - .SelectMany(lib => createLibraryTasks(path, lib)); + .Where(lib => lib.Rules == null || _rulesEvaluator.Match(lib.Rules, rulesContext)) + .SelectMany(lib => createLibraryTasks(path, lib, rulesContext)); } - private IEnumerable createLibraryTasks(MinecraftPath path, MLibrary library) + private IEnumerable createLibraryTasks( + MinecraftPath path, + MLibrary library, + RulesEvaluatorContext rulesContext) { // java library (*.jar) var artifact = library.Artifact; @@ -67,10 +74,10 @@ private IEnumerable createLibraryTasks(MinecraftPath path, MLibr } // native library (*.dll, *.so) - var native = library.GetNativeLibrary(_rulesContext.OS); + var native = library.GetNativeLibrary(rulesContext.OS); if (native != null) { - var libPath = library.GetNativeLibraryPath(_rulesContext.OS); + var libPath = library.GetNativeLibraryPath(rulesContext.OS); if (!string.IsNullOrEmpty(libPath)) { var file = new TaskFile(library.Name) diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index 84f7b47..b3a6753 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -1,4 +1,5 @@ -using CmlLib.Core.Tasks; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; @@ -12,7 +13,11 @@ public LogFileExtractor(HttpClient httpClient) _httpClient = httpClient; } - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + CancellationToken cancellationToken) { var result = extract(path, version); return new ValueTask>(result); diff --git a/src/Files/ModFile.cs b/src/Files/ModFile.cs deleted file mode 100644 index ab5e586..0000000 --- a/src/Files/ModFile.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace CmlLib.Core.Files -{ - public class ModFile - { - public ModFile(string filename, string url) - { - this.Name = filename; - this.Path = System.IO.Path.Combine("mods", filename); - this.Url = url; - } - - public ModFile(string filename, string url, string hash) : this(filename, url) - { - this.Hash = hash; - } - - public string? Name { get; set; } - public string? Hash { get; set; } - public string Path { get; set; } - public string Url { get; set; } - } -} diff --git a/src/Files/ModFileFactory.cs b/src/Files/ModFileFactory.cs deleted file mode 100644 index 0e0a9eb..0000000 --- a/src/Files/ModFileFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.IO; - -namespace CmlLib.Core.Files -{ - public class ModFileFactory - { - public ModFile GetCurseForgeModFile(string modName, string fileId) - { - string path = Path.Combine("mods", modName + ".jar"); - string url = $"https://www.curseforge.com/minecraft/mc-mods/{modName}/download/{fileId}/file"; - - return new ModFile(path, url) - { - Name = modName - }; - } - - public ModFile GetCurseForgeModFile(string modName, string fileId, string fileHash) - { - ModFile mod = GetCurseForgeModFile(modName, fileId); - mod.Hash = fileHash; - return mod; - } - } -} diff --git a/src/Installer/MJava.cs b/src/Installer/MJava.cs deleted file mode 100644 index 6dbef2b..0000000 --- a/src/Installer/MJava.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.ComponentModel; -using CmlLib.Core.Java; -using System.Text.Json; -using System.Net; -using CmlLib.Core.Downloader; -using CmlLib.Core.Rules; -using CmlLib.Core.Internals; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Installer; - -// legacy java installer -// new java installer: CmlLib.Core.Files.JavaChecker -public class MJava -{ - public static readonly string DefaultRuntimeDirectory - = Path.Combine(MinecraftPath.GetOSDefaultPath(), "runtime"); - - public event ProgressChangedEventHandler? ProgressChanged; - public string RuntimeDirectory { get; private set; } - - private readonly HttpClient _httpClient; - private IProgress? pProgressChanged; - public IJavaPathResolver JavaPathResolver { get; set; } - - public MJava(HttpClient client) : this(client, DefaultRuntimeDirectory) { } - - public MJava(HttpClient client, string runtimePath) - { - RuntimeDirectory = runtimePath; - JavaPathResolver = new MinecraftJavaPathResolver(runtimePath); - _httpClient = client; - } - - public string GetBinaryPath(LauncherOSRule os) - => JavaPathResolver.GetJavaBinaryPath( - MinecraftJavaPathResolver.CmlLegacyVersion, - os); - - public bool CheckJavaExistence(LauncherOSRule os) - => File.Exists(GetBinaryPath(os)); - - public Task CheckJavaAsync(LauncherOSRule os) - => CheckJavaAsync(os, null); - - public async Task CheckJavaAsync(LauncherOSRule os, IProgress? progress) - { - string javapath = GetBinaryPath(os); - - if (!CheckJavaExistence(os)) - { - if (progress == null) - { - pProgressChanged = new Progress( - (e) => ProgressChanged?.Invoke(this, e)); - } - else - { - pProgressChanged = progress; - } - - string javaUrl = await GetJavaUrlAsync().ConfigureAwait(false); - string lzmaPath = await downloadJavaLzmaAsync(javaUrl).ConfigureAwait(false); - - Task decompressTask = Task.Run(() => decompressJavaFile(lzmaPath)); - await decompressTask.ConfigureAwait(false); - - if (!File.Exists(javapath)) - throw new WebException("failed to download"); - - if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) - NativeMethods.Chmod(javapath, NativeMethods.Chmod755); - } - - return javapath; - } - - public async Task GetJavaUrlAsync() - { - var json = await _httpClient.GetStringAsync(MojangServer.LauncherMeta) - .ConfigureAwait(false); - return parseLauncherMetadata(json); - } - - private string parseLauncherMetadata(string json) - { - using var jsonDocument = JsonDocument.Parse(json); - var root = jsonDocument.RootElement; - - var javaUrl = root - .GetPropertyOrNull(LauncherOSRule.Current.Name)? - .GetPropertyOrNull(LauncherOSRule.Current.Arch)? - .GetPropertyOrNull("jre")? - .GetPropertyValue("url"); - - if (string.IsNullOrEmpty(javaUrl)) - throw new PlatformNotSupportedException("Downloading JRE on current OS is not supported. Set JavaPath manually."); - return javaUrl; - } - - private async Task downloadJavaLzmaAsync(string javaUrl) - { - Directory.CreateDirectory(RuntimeDirectory); - string lzmaPath = Path.Combine(Path.GetTempPath(), "jre.lzma"); - - var progress = new Progress(p => - { - var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; - pProgressChanged?.Report(new ProgressChangedEventArgs((int)percent / 2, null)); - }); - await HttpClientDownloadHelper.DownloadFileAsync(_httpClient, javaUrl, 0, lzmaPath, progress); - return lzmaPath; - } - - private void decompressJavaFile(string lzmaPath) - { - string zippath = Path.Combine(Path.GetTempPath(), "jre.zip"); - SevenZipWrapper.DecompressFileLZMA(lzmaPath, zippath); - - SharpZipWrapper.Unzip(zippath, RuntimeDirectory, new Progress(p => - { - var percent = (float)p.ProgressedBytes / p.TotalBytes * 100; - pProgressChanged?.Report(new ProgressChangedEventArgs(50 + (int)percent / 2, null)); - })); - } -} \ No newline at end of file diff --git a/src/Installers/IGameInstaller.cs b/src/Installers/IGameInstaller.cs new file mode 100644 index 0000000..6701bfb --- /dev/null +++ b/src/Installers/IGameInstaller.cs @@ -0,0 +1,17 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Installers; + +public interface IGameInstaller +{ + ValueTask Install( + IEnumerable extractors, + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + IProgress? fileProgress, + IProgress? byteProgress, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Installers/InstallerProgressChangedEventArgs.cs b/src/Installers/InstallerProgressChangedEventArgs.cs new file mode 100644 index 0000000..db7c409 --- /dev/null +++ b/src/Installers/InstallerProgressChangedEventArgs.cs @@ -0,0 +1,12 @@ +namespace CmlLib.Core.Installers; + +public struct InstallerProgressChangedEventArgs +{ + public InstallerProgressChangedEventArgs(string name, TaskStatus status) => + (Name, EventType) = (name, status); + + public int TotalTasks { get; set; } = 0; + public int ProceedTasks { get; set; } = 0; + public TaskStatus EventType { get; } + public string Name { get; } +} \ No newline at end of file diff --git a/src/Executors/SyncProgress.cs b/src/Installers/SyncProgress.cs similarity index 87% rename from src/Executors/SyncProgress.cs rename to src/Installers/SyncProgress.cs index e38f862..35d0815 100644 --- a/src/Executors/SyncProgress.cs +++ b/src/Installers/SyncProgress.cs @@ -1,4 +1,4 @@ -namespace CmlLib.Core.Executors; +namespace CmlLib.Core.Installers; public class SyncProgress : IProgress { diff --git a/src/Executors/TPLTaskExecutor.cs b/src/Installers/TPLGameInstaller.cs similarity index 51% rename from src/Executors/TPLTaskExecutor.cs rename to src/Installers/TPLGameInstaller.cs index f5dd852..ba24db0 100644 --- a/src/Executors/TPLTaskExecutor.cs +++ b/src/Installers/TPLGameInstaller.cs @@ -1,49 +1,71 @@ using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; -namespace CmlLib.Core.Executors; +namespace CmlLib.Core.Installers; -public class TPLTaskExecutor +public class TPLGameInstaller : IGameInstaller { - private readonly Dictionary _sizeStorage; - private readonly ThreadLocal> _progressStorage; - private readonly SemaphoreSlim _semaphore; private readonly int _maxParallelism; - public TPLTaskExecutor(int parallelism) - { - _sizeStorage = new Dictionary(); - _progressStorage = new ThreadLocal>( - () => new Dictionary(), - true); - - _semaphore = new SemaphoreSlim(1); + public TPLGameInstaller(int parallelism) => _maxParallelism = parallelism; + + public async ValueTask Install( + IEnumerable extractors, + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + IProgress? fileProgress, + IProgress? byteProgress, + CancellationToken cancellationToken) + { + var executor = new TPLGameInstallerExecutor(_maxParallelism, path, version, rulesContext); + executor.FileProgress += (s, e) => fileProgress?.Report(e); + executor.ByteProgress += (s, e) => byteProgress?.Report(e); + await executor.Install(extractors, cancellationToken); } +} - public event EventHandler? FileProgress; +class TPLGameInstallerExecutor +{ + private readonly int _maxParallelism; + private readonly MinecraftPath _path; + private readonly IVersion _version; + private readonly RulesEvaluatorContext _rulesContext; + + public TPLGameInstallerExecutor( + int parallelism, + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext) => + (_maxParallelism, _path, _version, _rulesContext) = + (parallelism, path, version, rulesContext); + + public event EventHandler? FileProgress; public event EventHandler? ByteProgress; private bool isStarted = false; + private Dictionary sizeStorage = null!; // we don't use null-safety check since the performance is most important part here + private ThreadLocal> progressStorage = null!; private CancellationToken CancellationToken; private int totalTasks = 0; private int proceed = 0; public async ValueTask Install( IEnumerable extractors, - MinecraftPath path, - IVersion version, CancellationToken cancellationToken) { if (isStarted) throw new InvalidOperationException("Already started"); - isStarted = true; + isStarted = true; + initializeResources(); CancellationToken = cancellationToken; - var extractBlock = createExtractBlock(version, path); + var extractBlock = createExtractBlock(cancellationToken); var executeBlock = createExecuteBlock(extractBlock.Completion); extractBlock.LinkTo(executeBlock); @@ -57,40 +79,51 @@ public async ValueTask Install( var executeTask = executeBlock.Completion; while (!executeTask.IsCompleted) { - await reportByteProgress(); + reportByteProgress(); await Task.Delay(100); } - await reportByteProgress(); + reportByteProgress(); + disposeResources(); } - private async Task reportByteProgress() + private void initializeResources() { - try - { - await _semaphore.WaitAsync(); + sizeStorage = new Dictionary(); + progressStorage = new ThreadLocal>( + () => new Dictionary(), + true); + totalTasks = 0; + proceed = 0; + } - long totalBytes = 0; - long progressedBytes = 0; + private void disposeResources() + { + sizeStorage.Clear(); + progressStorage.Dispose(); + } - foreach (var dict in _progressStorage.Values) + private void reportByteProgress() + { + long totalBytes = 0; + long progressedBytes = 0; + + foreach (var dict in progressStorage.Values) + { + lock (dict) { foreach (var kv in dict) { totalBytes += kv.Value.TotalBytes; progressedBytes += kv.Value.ProgressedBytes; - _sizeStorage.Remove(kv.Key); + sizeStorage.Remove(kv.Key); } } + } - foreach (var kv in _sizeStorage) - totalBytes += kv.Value; + foreach (var kv in sizeStorage) + totalBytes += kv.Value; - fireByteProgress(totalBytes, progressedBytes); - } - finally - { - _semaphore.Release(); - } + fireByteProgress(totalBytes, progressedBytes); } private BufferBlock createExecuteBlock(Task extractTask) @@ -98,41 +131,25 @@ private BufferBlock createExecuteBlock(Task extractTask) var buffer = new BufferBlock(); var executor = new TransformBlock(async task => { + long taskSize = 0; var progress = new SyncProgress(e => { - bool isLocked = false; - try - { - if (_semaphore.Wait(0)) - { - isLocked = true; - _progressStorage.Value![task.Name] = e; - } - } - finally + lock (progressStorage.Value!) { - if (isLocked) - _semaphore.Release(); + progressStorage.Value![task.Name] = e; } + taskSize = e.TotalBytes; }); var nextTask = await task.Execute(progress, CancellationToken); if (nextTask == null) { - try + progress.Report(new ByteProgress { - _semaphore.Wait(); - var previousProgress = _progressStorage.Value![task.Name]; - _progressStorage.Value![task.Name] = new ByteProgress - { - TotalBytes = previousProgress.TotalBytes, - ProgressedBytes = previousProgress.TotalBytes - }; - } - finally - { - _semaphore.Release(); - } + TotalBytes = taskSize, + ProgressedBytes = taskSize + }); + fireFileProgress(task.Name, TaskStatus.Done); } return nextTask; @@ -144,20 +161,18 @@ private BufferBlock createExecuteBlock(Task extractTask) return buffer; } - private IPropagatorBlock createExtractBlock( - IVersion version, - MinecraftPath path) + private IPropagatorBlock createExtractBlock(CancellationToken cancellationToken) { var block = new TransformManyBlock(async extractor => { - var tasks = await extractor.Extract(path, version); + var tasks = await extractor.Extract(_path, _version, _rulesContext, cancellationToken); return tasks .Where(task => task.First != null) .Select(task => { Interlocked.Increment(ref totalTasks); fireFileProgress(task.Name, TaskStatus.Queued); - _sizeStorage[task.Name] = task.File.Size; + sizeStorage[task.Name] = task.File.Size; return task.First; })!; @@ -171,7 +186,7 @@ private IPropagatorBlock createExtractBlock( private void fireFileProgress(string name, TaskStatus status) { - FileProgress?.Invoke(this, new TaskExecutorProgressChangedEventArgs(name, status) + FileProgress?.Invoke(this, new InstallerProgressChangedEventArgs(name, status) { TotalTasks = totalTasks, ProceedTasks = proceed diff --git a/src/Executors/TaskState.cs b/src/Installers/TaskState.cs similarity index 65% rename from src/Executors/TaskState.cs rename to src/Installers/TaskState.cs index b7a07a9..9663323 100644 --- a/src/Executors/TaskState.cs +++ b/src/Installers/TaskState.cs @@ -1,4 +1,4 @@ -namespace CmlLib.Core.Executors; +namespace CmlLib.Core.Installers; public enum TaskStatus { diff --git a/src/Internals/HttpUtil.cs b/src/Internals/HttpUtil.cs new file mode 100644 index 0000000..4b2b15d --- /dev/null +++ b/src/Internals/HttpUtil.cs @@ -0,0 +1,6 @@ +namespace CmlLib.Core.Internals; + +internal static class HttpUtil +{ + public readonly static Lazy SharedHttpClient = new Lazy(() => new HttpClient()); +} \ No newline at end of file diff --git a/src/Internals/IOUtil.cs b/src/Internals/IOUtil.cs index bd086e5..520fbf2 100644 --- a/src/Internals/IOUtil.cs +++ b/src/Internals/IOUtil.cs @@ -38,22 +38,6 @@ public static bool CheckFileValidation(string path, string? compareHash) return fileHash == compareHash; } - public static bool CheckSHA1(string path, string? compareHash) - { - if (string.IsNullOrEmpty(compareHash)) - return true; - - try - { - var fileHash = ComputeFileSHA1(path); - return fileHash == compareHash; - } - catch - { - return false; - } - } - public static string ComputeFileSHA1(string path) { #pragma warning disable CS0618 diff --git a/src/Java/IJavaPathResolver.cs b/src/Java/IJavaPathResolver.cs index a32214d..f90a36c 100644 --- a/src/Java/IJavaPathResolver.cs +++ b/src/Java/IJavaPathResolver.cs @@ -4,8 +4,8 @@ namespace CmlLib.Core.Java; public interface IJavaPathResolver { - string[] GetInstalledJavaVersions(); - string? GetDefaultJavaBinaryPath(LauncherOSRule os); - string GetJavaBinaryPath(JavaVersion javaVersionName, LauncherOSRule os); - string GetJavaDirPath(JavaVersion javaVersionName); + string[] GetInstalledJavaVersions(MinecraftPath path); + string? GetDefaultJavaBinaryPath(MinecraftPath path, RulesEvaluatorContext rules); + string GetJavaBinaryPath(MinecraftPath path, JavaVersion javaVersion, RulesEvaluatorContext rules); + string GetJavaDirPath(MinecraftPath path, JavaVersion javaVersion, RulesEvaluatorContext rules); } \ No newline at end of file diff --git a/src/Java/MinecraftJavaPathResolver.cs b/src/Java/MinecraftJavaPathResolver.cs index 048a2ae..75b3541 100644 --- a/src/Java/MinecraftJavaPathResolver.cs +++ b/src/Java/MinecraftJavaPathResolver.cs @@ -6,63 +6,48 @@ public class MinecraftJavaPathResolver : IJavaPathResolver { public static readonly JavaVersion JreLegacyVersion = new JavaVersion("jre-legacy"); public static readonly JavaVersion CmlLegacyVersion = new JavaVersion("m-legacy"); - - public MinecraftJavaPathResolver(MinecraftPath path) - { - runtimeDirectory = path.Runtime; - } - - public MinecraftJavaPathResolver(string path) - { - runtimeDirectory = path; - } - private readonly string runtimeDirectory; - - public string[] GetInstalledJavaVersions() + public string[] GetInstalledJavaVersions(MinecraftPath path) { - var dir = new DirectoryInfo(runtimeDirectory); + var dir = new DirectoryInfo(path.Runtime); if (!dir.Exists) - return new string[] { }; + return Array.Empty(); return dir.GetDirectories() .Select(x => x.Name) .ToArray(); } - public string? GetDefaultJavaBinaryPath(LauncherOSRule os) + public string? GetDefaultJavaBinaryPath(MinecraftPath path, RulesEvaluatorContext rules) { - var javaVersions = GetInstalledJavaVersions(); + var javaVersions = GetInstalledJavaVersions(path); string? javaPath = null; if (string.IsNullOrEmpty(javaPath) && javaVersions.Contains(MinecraftJavaPathResolver.JreLegacyVersion.Component)) - javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.JreLegacyVersion, os); + javaPath = GetJavaBinaryPath(path, MinecraftJavaPathResolver.JreLegacyVersion, rules); if (string.IsNullOrEmpty(javaPath) && javaVersions.Contains(MinecraftJavaPathResolver.CmlLegacyVersion.Component)) - javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersion, os); + javaPath = GetJavaBinaryPath(path, MinecraftJavaPathResolver.CmlLegacyVersion, rules); if (string.IsNullOrEmpty(javaPath) && javaVersions.Length > 0) - javaPath = GetJavaBinaryPath(new JavaVersion(javaVersions[0]), os); + javaPath = GetJavaBinaryPath(path, new JavaVersion(javaVersions[0]), rules); return javaPath; } - public string GetJavaBinaryPath(JavaVersion javaVersionName, LauncherOSRule os) + public string GetJavaBinaryPath(MinecraftPath path, JavaVersion javaVersionName, RulesEvaluatorContext rules) { return Path.Combine( - GetJavaDirPath(javaVersionName), + GetJavaDirPath(path, javaVersionName, rules), "bin", - GetJavaBinaryName(os)); + GetJavaBinaryName(rules.OS)); } - - public string GetJavaDirPath() - => GetJavaDirPath(JreLegacyVersion); - public string GetJavaDirPath(JavaVersion javaVersionName) - => Path.Combine(runtimeDirectory, javaVersionName.Component); + public string GetJavaDirPath(MinecraftPath path, JavaVersion javaVersionName, RulesEvaluatorContext rules) + => Path.Combine(path.Runtime, javaVersionName.Component); public string GetJavaBinaryName(LauncherOSRule os) { diff --git a/src/LauncherBuilder.cs b/src/LauncherBuilder.cs new file mode 100644 index 0000000..fd014ea --- /dev/null +++ b/src/LauncherBuilder.cs @@ -0,0 +1,123 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; +using CmlLib.Core.Internals; +using CmlLib.Core.Java; +using CmlLib.Core.Natives; +using CmlLib.Core.Rules; +using CmlLib.Core.VersionLoader; + +namespace CmlLib.Core; + +public class LauncherBuilder +{ + public static LauncherBuilder Create() => new LauncherBuilder(); + private LauncherBuilder() {} + + private HttpClient? _httpClient = null; + public HttpClient HttpClient + { + get => _httpClient ??= HttpUtil.SharedHttpClient.Value; + set => _httpClient = value; + } + + private MinecraftPath? _minecraftPath = null; + public MinecraftPath MinecraftPath + { + get => _minecraftPath ??= new MinecraftPath(); + set => _minecraftPath = value; + } + + private IVersionLoader? _versionLoader; + private IJavaPathResolver? _javaPathResolver; + private FileExtractorCollection? _extractors; + private IGameInstaller? _gameInstaller; + private INativeLibraryExtractor? _nativeExtractor; + private IRulesEvaluator? _rulesEvaluator; + + public LauncherBuilder WithHttpClient(HttpClient httpClient) + { + HttpClient = httpClient; + return this; + } + + public LauncherBuilder WithMinecraftPath(MinecraftPath path) + { + MinecraftPath = path; + return this; + } + + public LauncherBuilder WithMinecraftPath(string path) + { + MinecraftPath = new MinecraftPath(path); + return this; + } + + public LauncherBuilder WithVersionLoader(IVersionLoader versionLoader) + { + _versionLoader = versionLoader; + return this; + } + + public LauncherBuilder WithJavaPathResolver(IJavaPathResolver javaPathResolver) + { + _javaPathResolver = javaPathResolver; + return this; + } + + public LauncherBuilder WithFileExtractors(FileExtractorCollection extractors) + { + _extractors = extractors; + return this; + } + + public LauncherBuilder WithGameInstaller(IGameInstaller installer) + { + _gameInstaller = installer; + return this; + } + + public LauncherBuilder WithNativeLibraryExtractor(INativeLibraryExtractor nativeLibraryExtractor) + { + _nativeExtractor = nativeLibraryExtractor; + return this; + } + + public LauncherBuilder WithRulesEvaluator(IRulesEvaluator rulesEvaluator) + { + _rulesEvaluator = rulesEvaluator; + return this; + } + + public MinecraftLauncher Build() + { + _versionLoader ??= new VersionLoaderCollection + { + new LocalVersionLoader(MinecraftPath), + new MojangVersionLoader(HttpClient) + }; + _rulesEvaluator ??= new RulesEvaluator(); + _javaPathResolver ??= new MinecraftJavaPathResolver(); + _extractors ??= new FileExtractorCollection(); + _gameInstaller ??= new TPLGameInstaller(getBestParallism()); + _nativeExtractor ??= new NativeLibraryExtractor(_rulesEvaluator); + + return new MinecraftLauncher( + minecraftPath: MinecraftPath, + versionLoader: _versionLoader, + javaPathResolver: _javaPathResolver, + fileExtractors: _extractors, + gameInstaller: _gameInstaller, + nativeLibraryExtractor: _nativeExtractor, + rulesEvaluator: _rulesEvaluator + ); + } + + private int getBestParallism() + { + // 2 <= p <= 8 + var p = Environment.ProcessorCount; + p = Math.Max(p, 2); + p = Math.Min(p, 8); + return p; + } +} \ No newline at end of file diff --git a/src/Mapper.cs b/src/Mapper.cs index 1a55301..8783c1c 100644 --- a/src/Mapper.cs +++ b/src/Mapper.cs @@ -1,134 +1,134 @@ -using System.Collections.Generic; -using System.IO; -using System.Text; +using System.Text; using System.Text.RegularExpressions; -namespace CmlLib.Core +namespace CmlLib.Core; + +public static class Mapper { - public static class Mapper + private static readonly Regex argBracket = new Regex(@"\$?\{(.*?)}"); + + public static string[] Map(string[] arg, Dictionary dicts, string prepath) { - private static readonly Regex argBracket = new Regex(@"\$?\{(.*?)}"); + var checkPath = !string.IsNullOrEmpty(prepath); - public static string[] Map(string[] arg, Dictionary dicts, string prepath) + var args = new List(arg.Length); + foreach (string item in arg) { - var checkPath = !string.IsNullOrEmpty(prepath); - - var args = new List(arg.Length); - foreach (string item in arg) - { - var a = Interpolation(item, dicts, false); - if (checkPath) - a = ToFullPath(a, prepath); - args.Add(HandleEmptyArg(a)); - } - - return args.ToArray(); + var a = Interpolation(item, dicts); + if (checkPath) + a = ToFullPath(a, prepath); + args.Add(HandleEmptyArg(a, out _)); } - public static string[] MapInterpolation(string[] arg, Dictionary dicts) - { - var args = new List(arg.Length); - foreach (string item in arg) - { - var a = Interpolation(item, dicts, true); - if (!string.IsNullOrEmpty(a)) - args.Add(a); - } - - return args.ToArray(); - } + return args.ToArray(); + } - public static string[] MapPathString(string[] arg, string prepath) + public static string[] MapInterpolation(string[] arg, Dictionary dicts) + { + var args = new List(arg.Length); + foreach (string item in arg) { - var args = new List(arg.Length); - foreach (string item in arg) - { - var a = ToFullPath(item, prepath); - args.Add(HandleEmptyArg(a)); - } - - return args.ToArray(); + var a = Interpolation(item, dicts); + if (!string.IsNullOrEmpty(a)) + args.Add(HandleEmptyArg(a, out _)); } - public static string Interpolation(string str, Dictionary dicts, bool handleEmpty) + return args.ToArray(); + } + + public static string[] MapPathString(string[] arg, string prepath) + { + var args = new List(arg.Length); + foreach (string item in arg) { - str = argBracket.Replace(str, match => - { - if (match.Groups.Count < 2) - return match.Value; + var a = ToFullPath(item, prepath); + args.Add(HandleEmptyArg(a, out _)); + } - var key = match.Groups[1].Value; - if (dicts.TryGetValue(key, out string? value)) - { - return value ?? ""; - } + return args.ToArray(); + } + public static string Interpolation(string str, Dictionary dicts) + { + return argBracket.Replace(str, match => + { + if (match.Groups.Count < 2) return match.Value; - }); - if (handleEmpty) - return HandleEmptyArg(str); - else - return str; - } + var key = match.Groups[1].Value; + if (dicts.TryGetValue(key, out string? value)) + { + return value ?? ""; + } + + return match.Value; + }); + } - public static string ToFullPath(string str, string prepath) + public static string ToFullPath(string str, string prepath) + { + if (str.StartsWith("[") && str.EndsWith("]") && !string.IsNullOrEmpty(prepath)) { - if (str.StartsWith("[") && str.EndsWith("]") && !string.IsNullOrEmpty(prepath)) - { - var innerStr = str.TrimStart('[').TrimEnd(']').Split('@'); - var pathName = innerStr[0]; - var extension = "jar"; + var innerStr = str.TrimStart('[').TrimEnd(']').Split('@'); + var pathName = innerStr[0]; + var extension = "jar"; - if (innerStr.Length > 1) - extension = innerStr[1]; + if (innerStr.Length > 1) + extension = innerStr[1]; - return Path.Combine(prepath, - PackageName.Parse(pathName).GetPath(null, extension)); - } - else if (str.StartsWith("\'") && str.EndsWith("\'")) - return str.Trim('\''); - else - return str; + return Path.Combine(prepath, + PackageName.Parse(pathName).GetPath(null, extension)); } + else if (str.StartsWith("\'") && str.EndsWith("\'")) + return str.Trim('\''); + else + return str; + } - static string replaceByPos(string input, string replace, int startIndex, int length) - { - var sb = new StringBuilder(input); - return replaceByPos(sb, replace, startIndex, length); - } + static string replaceByPos(string input, string replace, int startIndex, int length) + { + var sb = new StringBuilder(input); + return replaceByPos(sb, replace, startIndex, length); + } - static string replaceByPos(StringBuilder sb, string replace, int startIndex, int length) - { - sb.Remove(startIndex, length); - sb.Insert(startIndex, replace); - return sb.ToString(); - } + static string replaceByPos(StringBuilder sb, string replace, int startIndex, int length) + { + sb.Remove(startIndex, length); + sb.Insert(startIndex, replace); + return sb.ToString(); + } - // key=value 1 => key="value 1" - // key="va l" => key="va l" - // va lue => "va lue" - // "va lue" => "va lue" - public static string HandleEmptyArg(string input) + // key=value 1 => key="value 1" + // key="va l" => key="va l" + // va lue => "va lue" + // "va lue" => "va lue" + public static string HandleEmptyArg(string input, out string key) + { + var seperatorIndex = input.IndexOf('='); + if (seperatorIndex != -1) { - if (input.Contains("=")) - { - var s = input.Split('='); + key = input.Substring(0, seperatorIndex); + var value = input.Substring(seperatorIndex + 1); - if ((s[1].Contains(" ") && !checkEmptyHandled(s[1])) || string.IsNullOrWhiteSpace(s[1])) - return s[0] + "=\"" + s[1] + "\""; - else - return input; - } - else if ((input.Contains(" ") && !checkEmptyHandled(input)) || string.IsNullOrWhiteSpace(input)) - return "\"" + input + "\""; + if ((key.Contains(" ") && !checkEmptyHandled(key)) || string.IsNullOrWhiteSpace(key)) + return key + "=\"" + value + "\""; else return input; } - - static bool checkEmptyHandled(string str) + else if ((input.Contains(" ") && !checkEmptyHandled(input)) || string.IsNullOrWhiteSpace(input)) + { + key = ""; + return "\"" + input + "\""; + } + else { - return str.StartsWith("\"") || str.EndsWith("\""); + key = ""; + return input; } } + + static bool checkEmptyHandled(string str) + { + return str.StartsWith("\"") || str.EndsWith("\""); + } } diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs new file mode 100644 index 0000000..7fda095 --- /dev/null +++ b/src/MinecraftLauncher.cs @@ -0,0 +1,135 @@ +using System.Diagnostics; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; +using CmlLib.Core.Java; +using CmlLib.Core.Natives; +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Rules; +using CmlLib.Core.Version; +using CmlLib.Core.VersionLoader; +using CmlLib.Core.VersionMetadata; + +namespace CmlLib.Core; + +public class MinecraftLauncher +{ + public MinecraftPath MinecraftPath { get; } + public IVersionLoader VersionLoader { get; } + public IJavaPathResolver JavaPathResolver { get; } + public FileExtractorCollection FileExtractors { get; } + public IGameInstaller GameInstaller { get; } + public INativeLibraryExtractor NativeLibraryExtractor { get; } + public IRulesEvaluator RulesEvaluator { get; } + + private readonly Progress _fileProgress; + private readonly Progress _byteProgress; + public event EventHandler? FileProgressEvent; + public event EventHandler? ByteProgressEvent; + + public MinecraftLauncher( + MinecraftPath minecraftPath, + IVersionLoader versionLoader, + IJavaPathResolver javaPathResolver, + FileExtractorCollection fileExtractors, + IGameInstaller gameInstaller, + INativeLibraryExtractor nativeLibraryExtractor, + IRulesEvaluator rulesEvaluator) + { + MinecraftPath = minecraftPath; + VersionLoader = versionLoader; + JavaPathResolver = javaPathResolver; + FileExtractors = fileExtractors; + GameInstaller = gameInstaller; + NativeLibraryExtractor = nativeLibraryExtractor; + RulesEvaluator = rulesEvaluator; + RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); + + _fileProgress = new Progress(e => FileProgressEvent?.Invoke(this, e)); + _byteProgress = new Progress(e => ByteProgressEvent?.Invoke(this, e)); + } + + public RulesEvaluatorContext RulesContext { get; set; } + public VersionCollection? Versions { get; private set; } + + public async ValueTask GetAllVersionsAsync() + { + Versions = await VersionLoader.GetVersionMetadatasAsync(); + return Versions; + } + + public async ValueTask GetVersionAsync(string versionName) + { + if (Versions == null) + Versions = await GetAllVersionsAsync(); + + if (Versions.TryGetVersionMetadata(versionName, out var version)) + { + return await Versions.GetAndSaveVersionAsync(version, MinecraftPath); + } + else + { + Versions = await GetAllVersionsAsync(); + return await Versions.GetAndSaveVersionAsync(versionName, MinecraftPath); + } + } + + public async ValueTask InstallAsync( + string versionName, + CancellationToken cancellationToken = default) + { + var version = await GetVersionAsync(versionName); + await InstallAsync(version); + } + + public async ValueTask InstallAsync( + IVersion version, + CancellationToken cancellationToken = default) + { + await GameInstaller.Install( + FileExtractors, + MinecraftPath, + version, + RulesContext, + _fileProgress, + _byteProgress, + cancellationToken); + } + + public Process BuildProcess( + IVersion version, + MLaunchOption launchOption) + { + launchOption.NativesDirectory = createNativePath(version); + setLaunchOption(version, launchOption); + var processBuilder = new MinecraftProcessBuilder(RulesEvaluator, launchOption); + var process = processBuilder.CreateProcess(); + return process; + } + + private void setLaunchOption(IVersion version, MLaunchOption option) + { + option.Path ??= MinecraftPath; + option.StartVersion ??= version; + option.JavaPath = GetJavaPath(version); + } + + public string? GetJavaPath(IVersion version) + { + if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion?.Component)) + return null; + + return JavaPathResolver.GetJavaBinaryPath( + MinecraftPath, + version.JavaVersion.Value, + RulesContext); + } + + public string? GetDefaultJavaPath() => JavaPathResolver + .GetDefaultJavaBinaryPath(MinecraftPath, RulesContext); + + private string createNativePath(IVersion version) + { + NativeLibraryExtractor.Clean(MinecraftPath, version); + return NativeLibraryExtractor.Extract(MinecraftPath, version, RulesContext); + } +} diff --git a/src/Installer/FabricMC/FabricLoader.cs b/src/ModLoaders/FabricMC/FabricLoader.cs similarity index 92% rename from src/Installer/FabricMC/FabricLoader.cs rename to src/ModLoaders/FabricMC/FabricLoader.cs index d44ece8..dd5c788 100644 --- a/src/Installer/FabricMC/FabricLoader.cs +++ b/src/ModLoaders/FabricMC/FabricLoader.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace CmlLib.Core.Installer.FabricMC +namespace CmlLib.Core.ModLoaders.FabricMC { public class FabricLoader { diff --git a/src/Installer/FabricMC/FabricVersionLoader.cs b/src/ModLoaders/FabricMC/FabricVersionLoader.cs similarity index 98% rename from src/Installer/FabricMC/FabricVersionLoader.cs rename to src/ModLoaders/FabricMC/FabricVersionLoader.cs index c8a8daf..a677f2c 100644 --- a/src/Installer/FabricMC/FabricVersionLoader.cs +++ b/src/ModLoaders/FabricMC/FabricVersionLoader.cs @@ -3,7 +3,7 @@ using CmlLib.Core.VersionMetadata; using CmlLib.Core.Internals; -namespace CmlLib.Core.Installer.FabricMC; +namespace CmlLib.Core.ModLoaders.FabricMC; public class FabricVersionLoader : IVersionLoader { diff --git a/src/Installer/LiteLoader/LiteLoaderInstaller.cs b/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs similarity index 98% rename from src/Installer/LiteLoader/LiteLoaderInstaller.cs rename to src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs index e9fe038..a7740fb 100644 --- a/src/Installer/LiteLoader/LiteLoaderInstaller.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs @@ -2,7 +2,7 @@ using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; -namespace CmlLib.Core.Installer.LiteLoader; +namespace CmlLib.Core.ModLoaders.LiteLoader; // 1.8.9 freezing public class LiteLoaderInstaller diff --git a/src/Installer/LiteLoader/LiteLoaderVersionLoader.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs similarity index 97% rename from src/Installer/LiteLoader/LiteLoaderVersionLoader.cs rename to src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs index 6fbf188..71e124d 100644 --- a/src/Installer/LiteLoader/LiteLoaderVersionLoader.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs @@ -3,7 +3,7 @@ using CmlLib.Core.VersionMetadata; using CmlLib.Core.Internals; -namespace CmlLib.Core.Installer.LiteLoader; +namespace CmlLib.Core.ModLoaders.LiteLoader; public class LiteLoaderVersionLoader : IVersionLoader { diff --git a/src/Installer/LiteLoader/LiteLoaderVersionMetadata.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs similarity index 98% rename from src/Installer/LiteLoader/LiteLoaderVersionMetadata.cs rename to src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs index f538e73..5905011 100644 --- a/src/Installer/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs @@ -1,10 +1,10 @@ using System.Text.Json; -using CmlLib.Core.Launcher; +using CmlLib.Core.ProcessBuilder; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using CmlLib.Core.Internals; -namespace CmlLib.Core.Installer.LiteLoader; +namespace CmlLib.Core.ModLoaders.LiteLoader; public class LiteLoaderVersionMetadata : JsonVersionMetadata { diff --git a/src/Installer/QuiltMC/QuiltLoader.cs b/src/ModLoaders/QuiltMC/QuiltLoader.cs similarity index 91% rename from src/Installer/QuiltMC/QuiltLoader.cs rename to src/ModLoaders/QuiltMC/QuiltLoader.cs index 2ee34f5..b93aacc 100644 --- a/src/Installer/QuiltMC/QuiltLoader.cs +++ b/src/ModLoaders/QuiltMC/QuiltLoader.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace CmlLib.Core.Installer.QuiltMC +namespace CmlLib.Core.ModLoaders.QuiltMC { public class QuiltLoader { diff --git a/src/Installer/QuiltMC/QuiltVersionLoader.cs b/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs similarity index 98% rename from src/Installer/QuiltMC/QuiltVersionLoader.cs rename to src/ModLoaders/QuiltMC/QuiltVersionLoader.cs index 0c9f913..6dc9ad5 100644 --- a/src/Installer/QuiltMC/QuiltVersionLoader.cs +++ b/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs @@ -3,7 +3,7 @@ using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; -namespace CmlLib.Core.Installer.QuiltMC; +namespace CmlLib.Core.ModLoaders.QuiltMC; public class QuiltVersionLoader : IVersionLoader { diff --git a/src/MojangServer.cs b/src/MojangServer.cs index e432990..0d3edcb 100644 --- a/src/MojangServer.cs +++ b/src/MojangServer.cs @@ -1,13 +1,12 @@ -namespace CmlLib.Core +namespace CmlLib.Core; + +public static class MojangServer { - public static class MojangServer - { - public static readonly string - Auth = "https://authserver.mojang.com/", - Version = "https://launchermeta.mojang.com/mc/game/version_manifest.json", - Library = "https://libraries.minecraft.net/", - ResourceDownload = "https://resources.download.minecraft.net/", - LauncherMeta = "https://launchermeta.mojang.com/mc/launcher.json", - JavaManifest = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; - } + public static readonly string + Auth = "https://authserver.mojang.com/", + Version = "https://launchermeta.mojang.com/mc/game/version_manifest.json", + Library = "https://libraries.minecraft.net/", + ResourceDownload = "https://resources.download.minecraft.net/", + LauncherMeta = "https://launchermeta.mojang.com/mc/launcher.json", + JavaManifest = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; } diff --git a/src/Natives/INativeLibraryExtractor.cs b/src/Natives/INativeLibraryExtractor.cs new file mode 100644 index 0000000..74e045a --- /dev/null +++ b/src/Natives/INativeLibraryExtractor.cs @@ -0,0 +1,10 @@ +using CmlLib.Core.Rules; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Natives; + +public interface INativeLibraryExtractor +{ + string Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext); + void Clean(MinecraftPath path, IVersion version); +} \ No newline at end of file diff --git a/src/Launcher/MNative.cs b/src/Natives/NativeLibraryExtractor.cs similarity index 53% rename from src/Launcher/MNative.cs rename to src/Natives/NativeLibraryExtractor.cs index 360653e..e7546ac 100644 --- a/src/Launcher/MNative.cs +++ b/src/Natives/NativeLibraryExtractor.cs @@ -2,33 +2,27 @@ using CmlLib.Core.Version; using CmlLib.Core.Internals; -namespace CmlLib.Core; +namespace CmlLib.Core.Natives; -public class MNative +public class NativeLibraryExtractor : INativeLibraryExtractor { - public MNative( - MinecraftPath gamePath, - IVersion version, - IRulesEvaluator rulesEvaluator, - RulesEvaluatorContext context) + public NativeLibraryExtractor( + IRulesEvaluator rulesEvaluator) { - this.version = version; - this.gamePath = gamePath; this.rulesEvaluator = rulesEvaluator; - this.context = context; } - private readonly IVersion version; - private readonly MinecraftPath gamePath; private readonly IRulesEvaluator rulesEvaluator; - private readonly RulesEvaluatorContext context; - public string ExtractNatives() + public string Extract( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext) { - var extractPath = gamePath.GetNativePath(version.Id); + var extractPath = path.GetNativePath(version.Id); Directory.CreateDirectory(extractPath); - var nativeLibraries = getNativeLibraryPaths(); + var nativeLibraries = getNativeLibraryPaths(path, version, rulesContext); foreach (var libPath in nativeLibraries) { if (File.Exists(libPath)) @@ -38,24 +32,27 @@ public string ExtractNatives() return extractPath; } - private IEnumerable getNativeLibraryPaths() + private IEnumerable getNativeLibraryPaths( + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext) { return version .ConcatInheritedCollection(v => v.Libraries) .Where(lib => lib.CheckIsRequired("SIDE")) - .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, context)) - .Select(lib => lib.GetNativeLibraryPath(context.OS)) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) + .Select(lib => lib.GetNativeLibraryPath(rulesContext.OS)) .Where(libPath => !string.IsNullOrEmpty(libPath)) - .Select(libPath => Path.Combine(gamePath.Library, libPath!)); + .Select(libPath => Path.Combine(path.Library, libPath!)); } - public void CleanNatives() + public void Clean(MinecraftPath path, IVersion version) { try { - string path = gamePath.GetNativePath(version.Id); - DirectoryInfo di = new DirectoryInfo(path); + var nativePath = path.GetNativePath(version.Id); + DirectoryInfo di = new DirectoryInfo(nativePath); if (!di.Exists) return; @@ -71,4 +68,4 @@ public void CleanNatives() // will be overwriten to new file } } -} +} \ No newline at end of file diff --git a/src/PackageName.cs b/src/PackageName.cs index 0ec7490..8982f26 100644 --- a/src/PackageName.cs +++ b/src/PackageName.cs @@ -1,74 +1,70 @@ -using System; -using System.IO; +namespace CmlLib.Core; -namespace CmlLib.Core +public class PackageName { - public class PackageName + public static PackageName Parse(string name) { - public static PackageName Parse(string name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); + if (name == null) + throw new ArgumentNullException(nameof(name)); - var spliter = name.Split(':'); - if (spliter.Length < 3) - throw new ArgumentException("invalid name"); + var spliter = name.Split(':'); + if (spliter.Length < 3) + throw new ArgumentException("invalid name"); - return new PackageName(spliter); - } + return new PackageName(spliter); + } - private PackageName(string[] names) - { - this.names = names; - } + private PackageName(string[] names) + { + this.names = names; + } - private readonly string[] names; + private readonly string[] names; - public string this[int index] => names[index]; + public string this[int index] => names[index]; - public string Package => names[0]; - public string Name => names[1]; - public string Version => names[2]; + public string Package => names[0]; + public string Name => names[1]; + public string Version => names[2]; - public string GetPath() - { - return GetPath(""); - } + public string GetPath() + { + return GetPath(""); + } - public string GetPath(string? nativeId) - { - return GetPath(nativeId, "jar"); - } + public string GetPath(string? nativeId) + { + return GetPath(nativeId, "jar"); + } - public string GetPath(string? nativeId, string extension) - { - // de.oceanlabs.mcp : mcp_config : 1.16.2-20200812.004259 : mappings - // de\oceanlabs\mcp \ mcp_config \ 1.16.2-20200812.004259 \ mcp_config-1.16.2-20200812.004259.zip + public string GetPath(string? nativeId, string extension) + { + // de.oceanlabs.mcp : mcp_config : 1.16.2-20200812.004259 : mappings + // de\oceanlabs\mcp \ mcp_config \ 1.16.2-20200812.004259 \ mcp_config-1.16.2-20200812.004259.zip - // [de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip] - // \libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip + // [de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip] + // \libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip - // [net.minecraft:client:1.16.2-20200812.004259:slim] - // /libraries\net\minecraft\client\1.16.2-20200812.004259\client-1.16.2-20200812.004259-slim.jar + // [net.minecraft:client:1.16.2-20200812.004259:slim] + // /libraries\net\minecraft\client\1.16.2-20200812.004259\client-1.16.2-20200812.004259-slim.jar - string filename = string.Join("-", names, 1, names.Length - 1); + string filename = string.Join("-", names, 1, names.Length - 1); - if (!string.IsNullOrEmpty(nativeId)) - filename += "-" + nativeId; - filename += "." + extension; + if (!string.IsNullOrEmpty(nativeId)) + filename += "-" + nativeId; + filename += "." + extension; - return Path.Combine(GetDirectory(), filename); - } + return Path.Combine(GetDirectory(), filename); + } - public string GetDirectory() - { - string dir = Package.Replace('.', Path.DirectorySeparatorChar); - return Path.Combine(dir, Name, Version); - } + public string GetDirectory() + { + string dir = Package.Replace('.', Path.DirectorySeparatorChar); + return Path.Combine(dir, Name, Version); + } - public string GetClassPath() - { - return Package + "." + Name; - } + public string GetClassPath() + { + return Package + "." + Name; } } diff --git a/src/Launcher/MArgument.cs b/src/ProcessBuilder/MArgument.cs similarity index 87% rename from src/Launcher/MArgument.cs rename to src/ProcessBuilder/MArgument.cs index 25810b9..5f3cfd2 100644 --- a/src/Launcher/MArgument.cs +++ b/src/ProcessBuilder/MArgument.cs @@ -1,6 +1,6 @@ using CmlLib.Core.Rules; -namespace CmlLib.Core.Launcher; +namespace CmlLib.Core.ProcessBuilder; public class MArgument { diff --git a/src/Launcher/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs similarity index 93% rename from src/Launcher/MLaunchOption.cs rename to src/ProcessBuilder/MLaunchOption.cs index 1c1a863..1224dda 100644 --- a/src/Launcher/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -2,13 +2,14 @@ using CmlLib.Core.Rules; using CmlLib.Core.Version; -namespace CmlLib.Core; +namespace CmlLib.Core.ProcessBuilder; public class MLaunchOption { public MinecraftPath? Path { get; set; } public IVersion? StartVersion { get; set; } public MSession? Session { get; set; } + public string? NativesDirectory { get; set; } public RulesEvaluatorContext? RulesContext { get; set; } public string? JavaVersion { get; set; } @@ -40,6 +41,7 @@ public class MLaunchOption internal IVersion GetStartVersion() => StartVersion!; internal MSession GetSession() => Session!; internal string GetJavaPath() => JavaPath!; + internal RulesEvaluatorContext GetRulesContext() => RulesContext!; internal void CheckValid() { diff --git a/src/ProcessBuilder/MinecraftArgumentBuilder.cs b/src/ProcessBuilder/MinecraftArgumentBuilder.cs deleted file mode 100644 index c85ce57..0000000 --- a/src/ProcessBuilder/MinecraftArgumentBuilder.cs +++ /dev/null @@ -1,45 +0,0 @@ -using CmlLib.Core.Launcher; -using CmlLib.Core.Rules; - -namespace CmlLib.Core.ProcessBuilder; - -public class MinecraftArgumentBuilder -{ - private readonly IRulesEvaluator _evaluator; - private readonly RulesEvaluatorContext _context; - - public MinecraftArgumentBuilder(IRulesEvaluator evaluator, RulesEvaluatorContext context) - { - _evaluator = evaluator; - _context = context; - } - - public IEnumerable Build(IEnumerable arguments, Dictionary mapper) - { - foreach (var arg in arguments) - { - foreach (var value in Build(arg, mapper)) - yield return value; - } - } - - public IEnumerable Build(MArgument arg, Dictionary mapper) - { - if (arg.Values == null) - yield break; - - if (arg.Rules != null) - { - var isMatch = _evaluator.Match(arg.Rules, _context); - if (!isMatch) - yield break; - } - - foreach (var value in arg.Values) - { - var mappedValue = Mapper.Interpolation(value, mapper, true); - if (!string.IsNullOrEmpty(mappedValue)) - yield return mappedValue; - } - } -} \ No newline at end of file diff --git a/src/Launcher/MLaunch.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs similarity index 66% rename from src/Launcher/MLaunch.cs rename to src/ProcessBuilder/MinecraftProcessBuilder.cs index 2ee18b7..6412352 100644 --- a/src/Launcher/MLaunch.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -1,13 +1,11 @@ -using CmlLib.Core.Launcher; -using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.Rules; +using CmlLib.Core.Rules; using CmlLib.Core.Version; using CmlLib.Core.Internals; using System.Diagnostics; -namespace CmlLib.Core; +namespace CmlLib.Core.ProcessBuilder; -public class MLaunch +public class MinecraftProcessBuilder { private const int DefaultServerPort = 25565; @@ -24,26 +22,29 @@ public class MLaunch // "-Xss1M" }; - public MLaunch(MLaunchOption option) + public MinecraftProcessBuilder( + IRulesEvaluator evaluator, + MLaunchOption option) { option.CheckValid(); + launchOption = option; version = option.GetStartVersion(); - this.minecraftPath = option.GetMinecraftPath(); - builder = new MinecraftArgumentBuilder(rulesEvaluator, launchOption.RulesContext!); + minecraftPath = option.GetMinecraftPath(); + rulesContext = option.GetRulesContext(); + rulesEvaluator = evaluator; } private readonly IVersion version; - private readonly IRulesEvaluator rulesEvaluator = new RulesEvaluator(); - private readonly MinecraftArgumentBuilder builder; + private readonly RulesEvaluatorContext rulesContext; + private readonly IRulesEvaluator rulesEvaluator; private readonly MinecraftPath minecraftPath; private readonly MLaunchOption launchOption; - // make process that ready to launch game public Process CreateProcess() { - string arg = string.Join(" ", BuildArguments()); - Process mc = new Process(); + var arg = string.Join(" ", BuildArguments()); + var mc = new Process(); mc.StartInfo.FileName = launchOption.JavaPath!; mc.StartInfo.Arguments = arg; mc.StartInfo.WorkingDirectory = minecraftPath.BasePath; @@ -68,14 +69,13 @@ public IEnumerable BuildArguments() { var classpaths = getClasspaths(); var classpath = IOUtil.CombinePath(classpaths); - var nativePath = createNativePath(); var session = launchOption.GetSession(); var assetId = version.GetInheritedProperty(version => version.AssetIndex?.Id) ?? "legacy"; var argDict = new Dictionary { { "library_directory" , minecraftPath.Library }, - { "natives_directory" , nativePath }, + { "natives_directory" , launchOption.NativesDirectory }, { "launcher_name" , useNotNull(launchOption.GameLauncherName, "minecraft-launcher") }, { "launcher_version" , useNotNull(launchOption.GameLauncherVersion, "2") }, { "classpath_separator", Path.PathSeparator.ToString() }, @@ -111,81 +111,85 @@ public IEnumerable BuildArguments() private IEnumerable buildJvmArguments(Dictionary argDict) { + var builder = new ProcessArgumentBuilder(rulesEvaluator, rulesContext); + // version-specific jvm arguments var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); - foreach (var item in builder.Build(jvmArgs, argDict)) - yield return item; + builder.AddArguments(jvmArgs, argDict); // default jvm arguments - if (launchOption.JVMArguments != null && launchOption.JVMArguments.Count() != 0) + if (launchOption.JVMArguments != null) foreach (var item in launchOption.JVMArguments) - yield return item; + builder.Add(item); else - { - if (launchOption.MaximumRamMb > 0) - yield return ("-Xmx" + launchOption.MaximumRamMb + "m"); - - if (launchOption.MinimumRamMb > 0) - yield return ("-Xms" + launchOption.MinimumRamMb + "m"); - foreach (var item in DefaultJavaParameter) - yield return item; - } + builder.Add(item); - if (jvmArgs.Count() == 0) + // libraries + builder.TryAddKeyValue("-Djava.library.path", argDict["natives_directory"]); + if (!builder.CheckKeyAdded("-cp")) { - yield return ("-Djava.library.path=" + handleEmpty(argDict["natives_directory"])); - yield return ("-cp " + argDict["classpath"]); + builder.Add("-cp"); + builder.AddRaw(argDict["classpath"]); } + // -Xmx, -Xms + if (!builder.CheckKeyAdded("-Xmx") && launchOption.MaximumRamMb > 0) + builder.Add("-Xmx" + launchOption.MaximumRamMb + "m"); + if (!builder.CheckKeyAdded("-Xms") && launchOption.MinimumRamMb > 0) + builder.Add("-Xms" + launchOption.MinimumRamMb + "m"); + // for macOS if (!string.IsNullOrEmpty(launchOption.DockName)) - yield return ("-Xdock:name=" + handleEmpty(launchOption.DockName)); + builder.TryAddKeyValue("-Xdock:name", launchOption.DockName); if (!string.IsNullOrEmpty(launchOption.DockIcon)) - yield return ("-Xdock:icon=" + handleEmpty(launchOption.DockIcon)); + builder.TryAddKeyValue("-Xdock:icon", launchOption.DockIcon); // logging var logging = version.GetInheritedProperty(v => v.Logging); if (!string.IsNullOrEmpty(logging?.Argument)) { - var mappedArgs = builder.Build(new MArgument(logging.Argument), new Dictionary() + builder.AddArgument(new MArgument(logging.Argument), new Dictionary() { { "path", minecraftPath.GetLogConfigFilePath(logging.LogFile?.Id ?? version.Id) } }); - foreach (var item in mappedArgs) - yield return item; } // main class var mainClass = version.GetInheritedProperty(v => v.MainClass); if (!string.IsNullOrEmpty(mainClass)) - yield return (mainClass); + builder.Add(mainClass); + + return builder.Build(); } private IEnumerable buildGameArguments(Dictionary argDict) { + var builder = new ProcessArgumentBuilder(rulesEvaluator, rulesContext); + // game arguments var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); - foreach (var item in builder.Build(gameArgs, argDict)) - yield return item; + builder.AddArguments(gameArgs, argDict); // options if (!string.IsNullOrEmpty(launchOption.ServerIp)) { - yield return ("--server " + handleEmpty(launchOption.ServerIp)); + builder.AddRange("--server", launchOption.ServerIp); if (launchOption.ServerPort != DefaultServerPort) - yield return ("--port " + launchOption.ServerPort); + builder.AddRange("--port", launchOption.ServerPort.ToString()); } if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) { - yield return ("--width " + launchOption.ScreenWidth); - yield return ("--height " + launchOption.ScreenHeight); + builder.AddRange("--width", launchOption.ScreenWidth.ToString()); + builder.AddRange("--height", launchOption.ScreenHeight.ToString()); } - if (launchOption.FullScreen) - yield return ("--fullscreen"); + if (!builder.CheckKeyAdded("--fullscreen") && launchOption.FullScreen) + builder.Add("--fullscreen"); + + return builder.Build(); } // make library files into jvm classpath string @@ -195,7 +199,7 @@ private IEnumerable getClasspaths() var libPaths = version .ConcatInheritedCollection(v => v.Libraries) .Where(lib => lib.CheckIsRequired("SIDE")) - .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, launchOption.RulesContext!)) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) .Where(lib => lib.Artifact != null) .Select(lib => lib.GetLibraryPath()); @@ -203,18 +207,10 @@ private IEnumerable getClasspaths() yield return item; // .jar file - if (!string.IsNullOrEmpty(version.Jar)) // !!!!!!!!!!!!!!!!!!!!!!!!! + if (!string.IsNullOrEmpty(version.Jar)) // TODO: decide what Jar file should be used. current jar or parent jar yield return (minecraftPath.GetVersionJarPath(version.Jar)); } - private string createNativePath() - { - var native = new MNative(minecraftPath, version, rulesEvaluator, launchOption.RulesContext!); - native.CleanNatives(); - var nativePath = native.ExtractNatives(); - return nativePath; - } - // if input1 is null, return input2 private string? useNotNull(string? input1, string? input2) { @@ -223,15 +219,4 @@ private string createNativePath() else return input1; } - - private string? handleEmpty(string? input) - { - if (input == null) - return null; - - if (input.Contains(" ")) - return "\"" + input + "\""; - else - return input; - } } diff --git a/src/ProcessBuilder/ProcessArgumentBuilder.cs b/src/ProcessBuilder/ProcessArgumentBuilder.cs index a73fa32..8411484 100644 --- a/src/ProcessBuilder/ProcessArgumentBuilder.cs +++ b/src/ProcessBuilder/ProcessArgumentBuilder.cs @@ -1,37 +1,187 @@ +using CmlLib.Core.Rules; + namespace CmlLib.Core.ProcessBuilder; public class ProcessArgumentBuilder { - private List _args = new(); + private readonly IRulesEvaluator _evaluator; + private readonly RulesEvaluatorContext _context; + private readonly HashSet _keys = new(); + private readonly List _args = new(); + + public ProcessArgumentBuilder(IRulesEvaluator evaluator, RulesEvaluatorContext context) + { + _evaluator = evaluator; + _context = context; + } + + public bool CheckKeyAdded(string key) => _keys.Contains(key); - public void AddArgument(string arg) + public void AddRaw(string? value) { - AddRawArgument(escapeEmpty(arg)); + if (string.IsNullOrEmpty(value)) + return; + _args.Add(value); + } + + public void AddRange(IEnumerable values) + { + foreach (var item in values) + Add(item); + } + + public void AddRange(params string?[] values) + { + foreach (var item in values) + Add(item); + } + + public void Add(string? value) + { + if (string.IsNullOrEmpty(value)) + return; + + string result; + var trimmed = value.Trim(); + if (trimmed.StartsWith("-")) + { + if (trimmed == "-") + { + result = "-"; + } + else + { + var seperator = trimmed.IndexOf('='); + if (seperator == -1) // does not contain '=' + { + if (trimmed.Contains(" ")) + throw new FormatException(); + result = trimmed; + } + else + { + var k = trimmed.Substring(0, seperator); + if (seperator == trimmed.Length - 1) // last character was '=' + { + if (trimmed.Contains(" ")) + throw new FormatException(); + result = trimmed; + addKey(k); + } + else // contains '=' + { + var v = trimmed.Substring(seperator + 1); + if (!isEscaped(v) && v.Contains(" ")) + throw new FormatException(); + AddKeyValue(k, v); + return; + } + } + } + } + else if (isEscaped(value)) + { + result = value; + } + else + { + result = escapeValue(value); + } + + AddRaw(result); + } + + public bool TryAddKeyValue(string key, string? value) + { + if (CheckKeyAdded(key)) + return false; + else + { + AddKeyValue(key, value); + return true; + } } public void AddKeyValue(string key, string? value) { - if (string.IsNullOrEmpty(key)) - throw new ArgumentNullException(key); + if (string.IsNullOrEmpty(key) || + isEscaped(key) || + key.Contains(" ")) + throw new FormatException(); + + string result; + if (value == null) + result = key; + else if (value == "") + result = $"{key}=\"\""; + else if (value == "\"\"") + result = $"{key}=\"\""; + else if (isEscaped(value)) + result = $"{key}={value}"; + else + result = $"{key}={escapeValue(value)}"; + + addKey(key); + AddRaw(result); + } + + private void addKey(string key) + { + if (key.StartsWith("-Xmx")) + _keys.Add("-Xmx"); + else if (key.StartsWith("-Xms")) + _keys.Add("-Xms"); + else + _keys.Add(key); + } + + public void AddArguments(IEnumerable arguments, Dictionary mapper) + { + foreach (var arg in arguments) + { + AddArgument(arg, mapper); + } + } + + public void AddArgument(MArgument arg, Dictionary mapper) + { + if (arg.Values == null) + return; + + if (arg.Rules != null) + { + var isMatch = _evaluator.Match(arg.Rules, _context); + if (!isMatch) + return; + } - var escapedValue = escapeEmpty(value); - if (string.IsNullOrEmpty(escapedValue)) - escapedValue = "\"\""; + foreach (var value in arg.Values) + { + var mappedValue = Mapper.Interpolation(value, mapper); + if (!string.IsNullOrEmpty(mappedValue)) + Add(mappedValue); + } + } - AddRawArgument($"{key}={escapedValue}"); + public string[] Build() + { + return _args.ToArray(); } - public void AddRawArgument(string arg) + private string escapeValue(string value) { - _args.Add(arg); + if (value.Contains(' ') && !isEscaped(value)) + { + return $"\"{value}\""; + } + else + { + return value; + } } - private string escapeEmpty(string? input) + private bool isEscaped(string value) { - if (string.IsNullOrEmpty(input)) - return ""; - if (input.Contains(" ")) - return "\"" + input + "\""; - return input; + return value.StartsWith("\"") && value.EndsWith("\""); } } \ No newline at end of file diff --git a/src/Rules/IRulesEvaluator.cs b/src/Rules/IRulesEvaluator.cs index dbebf21..9b4030c 100644 --- a/src/Rules/IRulesEvaluator.cs +++ b/src/Rules/IRulesEvaluator.cs @@ -1,3 +1,4 @@ + namespace CmlLib.Core.Rules; public interface IRulesEvaluator diff --git a/src/Tasks/ActionTask.cs b/src/Tasks/ActionTask.cs index a3ac226..fcdbe27 100644 --- a/src/Tasks/ActionTask.cs +++ b/src/Tasks/ActionTask.cs @@ -2,9 +2,9 @@ namespace CmlLib.Core.Tasks; public class ActionTask : LinkedTask { - private readonly Func> _action; + private readonly Func> _action; - public ActionTask(string name, Func> action) + public ActionTask(string name, Func> action) : base(name) => _action = action; @@ -12,6 +12,6 @@ public ActionTask(string name, Func> action) IProgress? progress, CancellationToken cancellationToken) { - return await _action.Invoke(); + return await _action.Invoke(this); } } \ No newline at end of file diff --git a/src/Tasks/FileCheckTask.cs b/src/Tasks/FileCheckTask.cs index b07a2e8..cf3d3ce 100644 --- a/src/Tasks/FileCheckTask.cs +++ b/src/Tasks/FileCheckTask.cs @@ -9,26 +9,39 @@ public FileCheckTask(TaskFile file) : base(file) if (string.IsNullOrEmpty(file.Path)) throw new ArgumentException("file.Path was empty"); this.Path = file.Path; - - if (string.IsNullOrEmpty(file.Hash)) - throw new ArgumentException("file.Hash return empty"); this.Hash = file.Hash; } - public FileCheckTask(string name, string path, string hash) : base(name) + public FileCheckTask(string name, string path, string? hash) : base(name) { this.Path = path; this.Hash = hash; } public string Path { get; } - public string Hash { get; } + public string? Hash { get; } protected override ValueTask OnExecutedWithResult( IProgress? progress, CancellationToken cancellationToken) { - var result = IOUtil.CheckFileValidation(Path, Hash); + var result = checkFile(); return new ValueTask(result); } + + private bool checkFile() + { + if (File.Exists(Path)) + { + if (string.IsNullOrEmpty(Hash)) + return true; + else + { + var realHash = IOUtil.ComputeFileSHA1(Path); + return realHash == Hash; + } + } + else + return false; + } } \ No newline at end of file diff --git a/src/Tasks/HttpClientDownloadHelper.cs b/src/Tasks/HttpClientDownloadHelper.cs index 0304c0b..fcba45c 100644 --- a/src/Tasks/HttpClientDownloadHelper.cs +++ b/src/Tasks/HttpClientDownloadHelper.cs @@ -1,5 +1,4 @@ -using CmlLib.Core.Executors; -using CmlLib.Core.Internals; +using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Tasks/TaskExecutionContext.cs b/src/Tasks/TaskExecutionContext.cs index 756968f..ffdd7bb 100644 --- a/src/Tasks/TaskExecutionContext.cs +++ b/src/Tasks/TaskExecutionContext.cs @@ -4,10 +4,8 @@ public class TaskExecutionContext { public TaskExecutionContext( IProgress? progress, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) => (ProgressChanged, CancellationToken) = (progress, cancellationToken); - } public CancellationToken CancellationToken { get; } public IProgress? ProgressChanged { get; } diff --git a/src/Version/IVersion.cs b/src/Version/IVersion.cs index 432f232..4982e71 100644 --- a/src/Version/IVersion.cs +++ b/src/Version/IVersion.cs @@ -1,6 +1,6 @@ using CmlLib.Core.Files; using CmlLib.Core.Java; -using CmlLib.Core.Launcher; +using CmlLib.Core.ProcessBuilder; namespace CmlLib.Core.Version; diff --git a/src/Version/JsonArgumentParser.cs b/src/Version/JsonArgumentParser.cs index 1e1431e..d42752f 100644 --- a/src/Version/JsonArgumentParser.cs +++ b/src/Version/JsonArgumentParser.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using CmlLib.Core.Launcher; +using CmlLib.Core.ProcessBuilder; using CmlLib.Core.Internals; namespace CmlLib.Core.Version; diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index 16165c8..c349820 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -1,7 +1,7 @@ using System.Text.Json; using CmlLib.Core.Files; using CmlLib.Core.Java; -using CmlLib.Core.Launcher; +using CmlLib.Core.ProcessBuilder; namespace CmlLib.Core.Version; diff --git a/src/VersionMetadata/MVersionCollection.cs b/src/VersionMetadata/MVersionCollection.cs index fad7b9d..e613859 100644 --- a/src/VersionMetadata/MVersionCollection.cs +++ b/src/VersionMetadata/MVersionCollection.cs @@ -41,15 +41,21 @@ public VersionCollection( public IVersionMetadata this[int index] => (IVersionMetadata)Versions[index]!; public IVersionMetadata GetVersionMetadata(string name) + { + if (TryGetVersionMetadata(name, out var version)) + return version; + else + throw new KeyNotFoundException("Cannot find " + name); + } + + public bool TryGetVersionMetadata(string name, out IVersionMetadata version) { if (name == null) throw new ArgumentNullException(nameof(name)); - var versionMetadata = Versions[name] as IVersionMetadata; - if (versionMetadata == null) - throw new KeyNotFoundException("Cannot find " + name); - - return versionMetadata; + var metadata = Versions[name] as IVersionMetadata; + version = metadata!; + return metadata != null; } public IVersionMetadata[] ToArray(MVersionSortOption option) From e6102b4e79a8bc1a92d5bba6eace072ad2efb1fe Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 26 Aug 2023 05:07:41 +0000 Subject: [PATCH 070/137] update --- benchmark/ExecutorsBenchmark.cs | 8 + ...askExecutorWithDummyDownloaderBenchmark.cs | 2 +- .../TPLTaskExecutorWithDummyTaskBenchmark.cs | 2 +- .../TPLTaskExecutorWithRandomFileBenchmark.cs | 2 +- examples/console/Program.cs | 179 ++++++++---------- examples/console/TPL.cs | 82 -------- examples/winform/CmlLibWinFormSample.csproj | 2 +- release.sh | 4 +- src/FileExtractors/JavaFileExtractor.cs | 1 - src/Installers/TPLGameInstaller.cs | 40 ++-- src/LauncherBuilder.cs | 123 ------------ src/LauncherParameters.cs | 62 ++++++ src/MinecraftLauncher.cs | 112 +++++++---- src/ProcessBuilder/MinecraftProcessBuilder.cs | 10 +- .../{ProcessUtil.cs => ProcessWrapper.cs} | 5 +- src/Tasks/DownloadTask.cs | 21 +- src/Tasks/LinkedTask.cs | 11 +- test/CmlLib.Core.Test.csproj | 4 +- test/IOUtilTest.cs | 2 +- test/MapperTest.cs | 3 +- test/PackageNameTest.cs | 2 +- test/ProcessArgumentBuilderTest.cs | 87 +++++++++ 22 files changed, 382 insertions(+), 382 deletions(-) delete mode 100644 examples/console/TPL.cs delete mode 100644 src/LauncherBuilder.cs create mode 100644 src/LauncherParameters.cs rename src/ProcessBuilder/{ProcessUtil.cs => ProcessWrapper.cs} (92%) create mode 100644 test/ProcessArgumentBuilderTest.cs diff --git a/benchmark/ExecutorsBenchmark.cs b/benchmark/ExecutorsBenchmark.cs index 45ba102..9f846a7 100644 --- a/benchmark/ExecutorsBenchmark.cs +++ b/benchmark/ExecutorsBenchmark.cs @@ -60,4 +60,12 @@ public async Task StartQueue() //{ // await _thread.Benchmark(); //} + + public static void PrintProgress(TaskExecutorProgressChangedEventArgs e) + { + //if (status != TaskStatus.Done) return; + //if (proceed % 100 != 0) return; + var now = DateTime.Now.ToString("hh:mm:ss.fff"); + Console.WriteLine($"[{now}][{e.ProceedTasks}/{e.TotalTasks}][{e.EventType}] {e.Name}"); + } } \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs index 95ca225..343b4c7 100644 --- a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs @@ -40,7 +40,7 @@ public void IterationSetup() lock (consoleLock) { Console.SetCursorPosition(0, Console.CursorTop - 1); - e.Print(); + ExecutorsBenchmark.PrintProgress(e); Console.WriteLine(bottomMsg); } }; diff --git a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs index 6ca18ff..10dd41c 100644 --- a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs @@ -40,7 +40,7 @@ public void IterationSetup() lock (consoleLock) { Console.SetCursorPosition(0, Console.CursorTop - 1); - e.Print(); + ExecutorsBenchmark.PrintProgress(e); Console.WriteLine(bottomMsg); } }; diff --git a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs index fac29a5..ac2f5fa 100644 --- a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs @@ -42,7 +42,7 @@ public void IterationSetup() Executor.ByteProgress += (s, e) => BytesProgressArgs = e; if (Verbose) - Executor.FileProgress += (s, e) => e.Print(); + Executor.FileProgress += (s, e) => ExecutorsBenchmark.PrintProgress(e); } [IterationCleanup] diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 0e75a85..eb33369 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -1,145 +1,116 @@ -using CmlLib.Core; +using System.Diagnostics; +using CmlLib.Core; using CmlLib.Core.Auth; -using CmlLib.Core.Downloader; -using CmlLib.Core.Executors; using CmlLib.Core.FileExtractors; -using CmlLib.Core.Java; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Installers; +using CmlLib.Core.ProcessBuilder; using CmlLib.Core.VersionLoader; -using System.Diagnostics; namespace CmlLibCoreSample; class Program { - public static readonly HttpClient HttpClient = new(); - public static async Task Main() { - //var t = new TPL(); - //await t.Test(); - //return; - var p = new Program(); - - // Login - MSession session; - session = p.OfflineLogin(); // Login by username - - // log login session information - Console.WriteLine("Success to login : {0} / {1} / {2}", session.Username, session.UUID, session.AccessToken); - - // Launch - await p.Start(session); - } - - MSession OfflineLogin() - { - // Create fake session by username - return MSession.GetOfflineSession("tester123"); + await p.Start(); } - async Task Start(MSession session) + private async Task Start() { - var minecraftPath = new MinecraftPath(); - var httpClient = new HttpClient(); - var rulesEvaluator = new RulesEvaluator(); - var javaPathResolver = new MinecraftJavaPathResolver(minecraftPath); - var rulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); + var sw = new Stopwatch(); - var versionLoader = new VersionLoaderCollection() + // initialize launcher + var parameters = LauncherParameters.CreateDefault(); + parameters.VersionLoader = new VersionLoaderCollection { - new LocalVersionLoader(minecraftPath), - //new MojangVersionLoader(httpClient), + new LocalVersionLoader(parameters.MinecraftPath!) }; + var launcher = new MinecraftLauncher(parameters); + + // add event handler + launcher.FileProgressChanged += Launcher_FileProgressChanged; + launcher.ByteProgressChanged += Launcher_ByteProgressChanged; - var versions = await versionLoader.GetVersionMetadatasAsync(); + // list versions + var versions = await launcher.GetAllVersionsAsync(); foreach (var v in versions) { Console.WriteLine($"{v.Name}, {v.Type}, {v.ReleaseTime}"); } - var version = await versions.GetAndSaveVersionAsync( - "1.16.5", minecraftPath); - - var extractors = FileExtractorCollection.CreateDefault( - httpClient, javaPathResolver, rulesEvaluator, rulesContext); + // select version + Console.WriteLine("Select the version to launch: "); + Console.Write("> "); + //var startVersion = Console.ReadLine(); + var startVersion = "1.20.1"; + if (string.IsNullOrEmpty(startVersion)) + return; - var installer = new TPLTaskExecutor(6); - installer.Progress += (s, e) => e.Print(); - - var sw = new Stopwatch(); + // install sw.Start(); - await installer.Install(extractors, minecraftPath, version); + await launcher.InstallAsync(startVersion); sw.Stop(); - Console.WriteLine(sw.ElapsedMilliseconds); - - while (true) + + // build process + var process = await launcher.BuildProcessAsync(startVersion, new MLaunchOption { - Console.ReadLine(); - installer.PrintStatus(); - } + Session = MSession.GetOfflineSession("username"), + JavaPath = "java" + }); + + // print debug information and start process + Console.WriteLine(); + Console.WriteLine("Elapsed time to install: " + sw.Elapsed); + Console.WriteLine("Java:"); + Console.WriteLine(process.StartInfo.FileName); + Console.WriteLine("Arguments:"); + Console.WriteLine(process.StartInfo.Arguments); + + var processWrapper = new ProcessWrapper(process); + processWrapper.OutputReceived += (s, e) => Console.WriteLine(e); + processWrapper.StartWithEvents(); + var exitCode = await processWrapper.WaitForExitTaskAsync(); + Console.WriteLine($"Exited with code {exitCode}"); + Console.ReadLine(); } - private void printTask(LinkedTask? task) + private readonly object consoleLock = new object(); + private string bottomText = "..."; + private int previousProceed; + private int lastCursorLeft = 0; + + // print installation progress to console + private void Launcher_FileProgressChanged(object? sender, InstallerProgressChangedEventArgs e) { - while (task != null) + lock (consoleLock) { - Console.WriteLine(task.GetType().Name); - if (task is FileCheckTask fct) - { - Console.WriteLine(fct.Path); - Console.WriteLine(fct.Hash); - printTask(fct.OnTrue); - printTask(fct.OnFalse); - } - else if (task is DownloadTask dt) - { - Console.WriteLine(dt.Path); - Console.WriteLine(dt.Url); - } - Console.WriteLine(); - task = task.NextTask; - } - } - - // Event Handling + if (previousProceed > e.ProceedTasks) + return; - // The code below has some tricks to display logs prettier. - // You can also use a simpler event handler + Console.WriteLine($"[{e.ProceedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"); + printBottomProgress(); - #region Pretty event handler + previousProceed = e.ProceedTasks; + } + } - private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) + private void Launcher_ByteProgressChanged(object? sender, ByteProgress e) { - var top = Console.CursorTop; - Console.SetCursorPosition(0, top); - // e.ProgressPercentage: 0~100 - Console.Write($"{e.ProgressPercentage}% "); - Console.SetCursorPosition(0, top); + lock (consoleLock) + { + var percent = (e.ProgressedBytes / (double)e.TotalBytes) * 100; + bottomText = $"{percent}% ({e.ProgressedBytes} bytes / {e.TotalBytes} bytes)"; + printBottomProgress(); + } } - private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) + private void printBottomProgress() { - // More information about DownloadFileChangedEventArgs - // https://github.com/AlphaBs/CmlLib.Core/wiki/Handling-Events#downloadfilechangedeventargs - - Console.WriteLine("[{0}] ({2}/{3}) {1} ", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); + bottomText.PadRight(lastCursorLeft); + Console.Write(bottomText); + lastCursorLeft = Console.CursorLeft; + Console.CursorLeft = 0; } - - #endregion - - #region Simple event handler - //private void Downloader_ChangeProgress(object sender, System.ComponentModel.ProgressChangedEventArgs e) - //{ - // Console.WriteLine("{0}%", e.ProgressPercentage); - //} - - //private void Downloader_ChangeFile(DownloadFileChangedEventArgs e) - //{ - // Console.WriteLine("[{0}] {1} - {2}/{3}", e.FileKind.ToString(), e.FileName, e.ProgressedFileCount, e.TotalFileCount); - //} - #endregion } diff --git a/examples/console/TPL.cs b/examples/console/TPL.cs deleted file mode 100644 index 346728a..0000000 --- a/examples/console/TPL.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Threading.Tasks.Dataflow; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLibCoreSample; - -public class MockTask : LinkedTask -{ - public int Count { get; set; } - - public MockTask(string name) : base(name) - { - } - - protected override ValueTask OnExecuted() - { - Console.WriteLine($"{Name}: {Count}"); - if (Count == 0) - return new ValueTask(); - else - { - var nextTask = new MockTask(Name); - nextTask.Count = Count - 1; - return new ValueTask(nextTask); - } - } -} - -public class TPL -{ - public async Task Test() - { - var linkOptions = new DataflowLinkOptions - { - PropagateCompletion = true - }; - - var buffer = new BufferBlock(); - var executor = new TransformBlock( - async f => - { - var next = await f.Execute(); - return next as MockTask; - }, - new ExecutionDataflowBlockOptions - { - MaxDegreeOfParallelism = 8 - }); - - buffer.LinkTo(executor, linkOptions); - executor.LinkTo(buffer!, linkOptions, f => f != null); - executor.LinkTo(DataflowBlock.NullTarget(), linkOptions); - - var versionBroadcaster = new BroadcastBlock(null); - for (var i = 0; i < 4; i++) - { - int copy = i; - var extractor = new TransformManyBlock( - v => extract(v, i)); - versionBroadcaster.LinkTo(extractor, linkOptions); - extractor.LinkTo(buffer, linkOptions); - } - - await versionBroadcaster.SendAsync("1.20.1"); - Console.ReadLine(); - Console.WriteLine("DONE"); - } - - private IEnumerable extract(string version, int i) - { - Console.WriteLine($"extractor {i}"); - for (int j = 0; j < 4; j++) - { - Thread.Sleep(100); - Console.WriteLine("extracted " + (i*10+j)); - yield return new MockTask((i*10 + j).ToString()) - { - Count = j - }; - } - } -} \ No newline at end of file diff --git a/examples/winform/CmlLibWinFormSample.csproj b/examples/winform/CmlLibWinFormSample.csproj index b310c0e..ee72a61 100644 --- a/examples/winform/CmlLibWinFormSample.csproj +++ b/examples/winform/CmlLibWinFormSample.csproj @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/release.sh b/release.sh index 0215fd8..16af35f 100755 --- a/release.sh +++ b/release.sh @@ -12,11 +12,11 @@ fi baseDir=$(dirname "$0") -csprojCmlLib="${baseDir}/src/CmlLib.csproj" +csprojCmlLib="${baseDir}/src/CmlLib.Core.csproj" csprojCmlLibCoreSample="${baseDir}/examples/console/CmlLibCoreSample.csproj" csprojCmlLibWinFormSample="${baseDir}/examples/winform/CmlLibWinFormSample.csproj" -[ ! -f $csprojCmlLib ] && { echo "Cannot find CmlLib.csproj file"; exit; } +[ ! -f $csprojCmlLib ] && { echo "Cannot find CmlLib.Core.csproj file"; exit; } [ ! -f $csprojCmlLibCoreSample ] && { echo "Cannot find CmlLibCoreSample.csproj file"; exit; } [ ! -f $csprojCmlLibWinFormSample ] && { echo "Cannot find CmlLibWinFormSample.csproj file"; exit; } diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 7d97c46..706a668 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -179,7 +179,6 @@ private IEnumerable extractFromManifestJson( var checkTask = new FileCheckTask(file); checkTask.OnFalse = new DownloadTask(file, _httpClient); - if (executable) checkTask.InsertNextTask(new ChmodTask(file.Name, file.Path)); diff --git a/src/Installers/TPLGameInstaller.cs b/src/Installers/TPLGameInstaller.cs index ba24db0..572c256 100644 --- a/src/Installers/TPLGameInstaller.cs +++ b/src/Installers/TPLGameInstaller.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; using CmlLib.Core.Rules; @@ -8,6 +9,17 @@ namespace CmlLib.Core.Installers; public class TPLGameInstaller : IGameInstaller { + private static int? _bestMaxParallelism; + public static int BestMaxParallelism => _bestMaxParallelism ??= getBestMaxParallelism(); + public static int getBestMaxParallelism() + { + // 2 <= p <= 8 + var p = Environment.ProcessorCount; + p = Math.Max(p, 2); + p = Math.Min(p, 8); + return p; + } + private readonly int _maxParallelism; public TPLGameInstaller(int parallelism) => @@ -48,7 +60,8 @@ public TPLGameInstallerExecutor( public event EventHandler? ByteProgress; private bool isStarted = false; - private Dictionary sizeStorage = null!; // we don't use null-safety check since the performance is most important part here + private IDataflowBlock? extractionBlock; + private ConcurrentDictionary sizeStorage = null!; // we don't use null-safety check since the performance is most important part here private ThreadLocal> progressStorage = null!; private CancellationToken CancellationToken; private int totalTasks = 0; @@ -67,6 +80,7 @@ public async ValueTask Install( var extractBlock = createExtractBlock(cancellationToken); var executeBlock = createExecuteBlock(extractBlock.Completion); + extractionBlock = extractBlock; extractBlock.LinkTo(executeBlock); await Task.WhenAll(extractors.Select(extractor => extractBlock.SendAsync(extractor))); @@ -80,7 +94,7 @@ public async ValueTask Install( while (!executeTask.IsCompleted) { reportByteProgress(); - await Task.Delay(100); + await Task.Delay(200); } reportByteProgress(); disposeResources(); @@ -88,7 +102,7 @@ public async ValueTask Install( private void initializeResources() { - sizeStorage = new Dictionary(); + sizeStorage = new ConcurrentDictionary(); progressStorage = new ThreadLocal>( () => new Dictionary(), true); @@ -115,13 +129,15 @@ private void reportByteProgress() { totalBytes += kv.Value.TotalBytes; progressedBytes += kv.Value.ProgressedBytes; - sizeStorage.Remove(kv.Key); + sizeStorage.TryRemove(kv.Key, out _); } } } foreach (var kv in sizeStorage) + { totalBytes += kv.Value; + } fireByteProgress(totalBytes, progressedBytes); } @@ -131,25 +147,23 @@ private BufferBlock createExecuteBlock(Task extractTask) var buffer = new BufferBlock(); var executor = new TransformBlock(async task => { - long taskSize = 0; var progress = new SyncProgress(e => { lock (progressStorage.Value!) { progressStorage.Value![task.Name] = e; } - taskSize = e.TotalBytes; }); var nextTask = await task.Execute(progress, CancellationToken); if (nextTask == null) { - progress.Report(new ByteProgress - { - TotalBytes = taskSize, - ProgressedBytes = taskSize - }); fireFileProgress(task.Name, TaskStatus.Done); + var totalSize = task.LinkedSize + task.Size; + fireByteProgress(totalSize, totalSize); + Interlocked.Increment(ref proceed); + if (totalTasks == proceed && (extractionBlock?.Completion.IsCompleted ?? false)) + buffer.Complete(); } return nextTask; @@ -158,6 +172,10 @@ private BufferBlock createExecuteBlock(Task extractTask) MaxDegreeOfParallelism = _maxParallelism }); + buffer.LinkTo(executor); + executor.LinkTo(buffer!, t => t != null); + executor.LinkTo(DataflowBlock.NullTarget()); + return buffer; } diff --git a/src/LauncherBuilder.cs b/src/LauncherBuilder.cs deleted file mode 100644 index fd014ea..0000000 --- a/src/LauncherBuilder.cs +++ /dev/null @@ -1,123 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Installers; -using CmlLib.Core.Internals; -using CmlLib.Core.Java; -using CmlLib.Core.Natives; -using CmlLib.Core.Rules; -using CmlLib.Core.VersionLoader; - -namespace CmlLib.Core; - -public class LauncherBuilder -{ - public static LauncherBuilder Create() => new LauncherBuilder(); - private LauncherBuilder() {} - - private HttpClient? _httpClient = null; - public HttpClient HttpClient - { - get => _httpClient ??= HttpUtil.SharedHttpClient.Value; - set => _httpClient = value; - } - - private MinecraftPath? _minecraftPath = null; - public MinecraftPath MinecraftPath - { - get => _minecraftPath ??= new MinecraftPath(); - set => _minecraftPath = value; - } - - private IVersionLoader? _versionLoader; - private IJavaPathResolver? _javaPathResolver; - private FileExtractorCollection? _extractors; - private IGameInstaller? _gameInstaller; - private INativeLibraryExtractor? _nativeExtractor; - private IRulesEvaluator? _rulesEvaluator; - - public LauncherBuilder WithHttpClient(HttpClient httpClient) - { - HttpClient = httpClient; - return this; - } - - public LauncherBuilder WithMinecraftPath(MinecraftPath path) - { - MinecraftPath = path; - return this; - } - - public LauncherBuilder WithMinecraftPath(string path) - { - MinecraftPath = new MinecraftPath(path); - return this; - } - - public LauncherBuilder WithVersionLoader(IVersionLoader versionLoader) - { - _versionLoader = versionLoader; - return this; - } - - public LauncherBuilder WithJavaPathResolver(IJavaPathResolver javaPathResolver) - { - _javaPathResolver = javaPathResolver; - return this; - } - - public LauncherBuilder WithFileExtractors(FileExtractorCollection extractors) - { - _extractors = extractors; - return this; - } - - public LauncherBuilder WithGameInstaller(IGameInstaller installer) - { - _gameInstaller = installer; - return this; - } - - public LauncherBuilder WithNativeLibraryExtractor(INativeLibraryExtractor nativeLibraryExtractor) - { - _nativeExtractor = nativeLibraryExtractor; - return this; - } - - public LauncherBuilder WithRulesEvaluator(IRulesEvaluator rulesEvaluator) - { - _rulesEvaluator = rulesEvaluator; - return this; - } - - public MinecraftLauncher Build() - { - _versionLoader ??= new VersionLoaderCollection - { - new LocalVersionLoader(MinecraftPath), - new MojangVersionLoader(HttpClient) - }; - _rulesEvaluator ??= new RulesEvaluator(); - _javaPathResolver ??= new MinecraftJavaPathResolver(); - _extractors ??= new FileExtractorCollection(); - _gameInstaller ??= new TPLGameInstaller(getBestParallism()); - _nativeExtractor ??= new NativeLibraryExtractor(_rulesEvaluator); - - return new MinecraftLauncher( - minecraftPath: MinecraftPath, - versionLoader: _versionLoader, - javaPathResolver: _javaPathResolver, - fileExtractors: _extractors, - gameInstaller: _gameInstaller, - nativeLibraryExtractor: _nativeExtractor, - rulesEvaluator: _rulesEvaluator - ); - } - - private int getBestParallism() - { - // 2 <= p <= 8 - var p = Environment.ProcessorCount; - p = Math.Max(p, 2); - p = Math.Min(p, 8); - return p; - } -} \ No newline at end of file diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs new file mode 100644 index 0000000..0bcbd32 --- /dev/null +++ b/src/LauncherParameters.cs @@ -0,0 +1,62 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; +using CmlLib.Core.Internals; +using CmlLib.Core.Java; +using CmlLib.Core.Natives; +using CmlLib.Core.Rules; +using CmlLib.Core.VersionLoader; + +namespace CmlLib.Core; + +public class LauncherParameters +{ + public static LauncherParameters CreateDefault() => + CreateDefault(HttpUtil.SharedHttpClient.Value); + + public static LauncherParameters CreateDefault(HttpClient httpClient) + { + var parameters = new LauncherParameters(); + parameters.HttpClient = httpClient; + parameters.MinecraftPath = new MinecraftPath(); + parameters.RulesEvaluator = new RulesEvaluator(); + parameters.VersionLoader = new VersionLoaderCollection + { + new LocalVersionLoader(parameters.MinecraftPath), + new MojangVersionLoader(httpClient) + }; + parameters.JavaPathResolver = new MinecraftJavaPathResolver(); + parameters.GameInstaller = new TPLGameInstaller(TPLGameInstaller.BestMaxParallelism); + parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); + var extractors = DefaultFileExtractors.CreateDefault( + parameters.HttpClient, + parameters.RulesEvaluator, + parameters.JavaPathResolver); + parameters.FileExtractors = extractors.ToExtractorCollection(); + return parameters; + } + + public static LauncherParameters CreateEmpty() + { + return new LauncherParameters(); + } + + private LauncherParameters() + { + + } + + private HttpClient? _httpClient; + public HttpClient HttpClient + { + get => _httpClient ??= HttpUtil.SharedHttpClient.Value; + set => _httpClient = value; + } + + public MinecraftPath? MinecraftPath { get; set; } + public IVersionLoader? VersionLoader { get; set; } + public IJavaPathResolver? JavaPathResolver { get; set; } + public FileExtractorCollection? FileExtractors { get; set; } + public IGameInstaller? GameInstaller { get; set; } + public INativeLibraryExtractor? NativeLibraryExtractor { get; set; } + public IRulesEvaluator? RulesEvaluator { get; set; } +} \ No newline at end of file diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 7fda095..f04d9ac 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -13,6 +13,11 @@ namespace CmlLib.Core; public class MinecraftLauncher { + private readonly Progress _fileProgress; + private readonly Progress _byteProgress; + public event EventHandler? FileProgressChanged; + public event EventHandler? ByteProgressChanged; + public MinecraftPath MinecraftPath { get; } public IVersionLoader VersionLoader { get; } public IJavaPathResolver JavaPathResolver { get; } @@ -21,35 +26,47 @@ public class MinecraftLauncher public INativeLibraryExtractor NativeLibraryExtractor { get; } public IRulesEvaluator RulesEvaluator { get; } - private readonly Progress _fileProgress; - private readonly Progress _byteProgress; - public event EventHandler? FileProgressEvent; - public event EventHandler? ByteProgressEvent; - - public MinecraftLauncher( - MinecraftPath minecraftPath, - IVersionLoader versionLoader, - IJavaPathResolver javaPathResolver, - FileExtractorCollection fileExtractors, - IGameInstaller gameInstaller, - INativeLibraryExtractor nativeLibraryExtractor, - IRulesEvaluator rulesEvaluator) + public RulesEvaluatorContext RulesContext { get; set; } + public VersionCollection? Versions { get; private set; } + + public MinecraftLauncher(string path) : this(WithMinecraftPath(new MinecraftPath(path))) { - MinecraftPath = minecraftPath; - VersionLoader = versionLoader; - JavaPathResolver = javaPathResolver; - FileExtractors = fileExtractors; - GameInstaller = gameInstaller; - NativeLibraryExtractor = nativeLibraryExtractor; - RulesEvaluator = rulesEvaluator; - RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - _fileProgress = new Progress(e => FileProgressEvent?.Invoke(this, e)); - _byteProgress = new Progress(e => ByteProgressEvent?.Invoke(this, e)); } - public RulesEvaluatorContext RulesContext { get; set; } - public VersionCollection? Versions { get; private set; } + public MinecraftLauncher(MinecraftPath path) : this(WithMinecraftPath(path)) + { + + } + + private static LauncherParameters WithMinecraftPath(MinecraftPath path) + { + var parameters = LauncherParameters.CreateDefault(); + parameters.MinecraftPath = path; + return parameters; + } + + public MinecraftLauncher(LauncherParameters parameters) + { + MinecraftPath = parameters.MinecraftPath + ?? throw new ArgumentException(nameof(parameters.MinecraftPath) + " was null"); + VersionLoader = parameters.VersionLoader + ?? throw new ArgumentException(nameof(parameters.VersionLoader) + " was null"); + JavaPathResolver = parameters.JavaPathResolver + ?? throw new ArgumentException(nameof(parameters.JavaPathResolver) + " was null"); + FileExtractors = parameters.FileExtractors + ?? throw new ArgumentException(nameof(parameters.FileExtractors) + " was null"); + GameInstaller = parameters.GameInstaller + ?? throw new ArgumentException(nameof(parameters.GameInstaller) + " was null"); + NativeLibraryExtractor = parameters.NativeLibraryExtractor + ?? throw new ArgumentException(nameof(parameters.NativeLibraryExtractor) + " was null"); + RulesEvaluator = parameters.RulesEvaluator + ?? throw new ArgumentException(nameof(parameters.RulesEvaluator) + " was null"); + RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); + + _fileProgress = new Progress(e => FileProgressChanged?.Invoke(this, e)); + _byteProgress = new Progress(e => ByteProgressChanged?.Invoke(this, e)); + } public async ValueTask GetAllVersionsAsync() { @@ -95,24 +112,43 @@ await GameInstaller.Install( cancellationToken); } + // Install and build game process + public async ValueTask CreateProcessAsync( + string versionName, + MLaunchOption launchOption, + bool checkAndDownload = true) + { + if (checkAndDownload) + { + await InstallAsync(versionName, default); + } + + return await BuildProcessAsync(versionName, launchOption); + } + + public async ValueTask BuildProcessAsync( + string versionName, + MLaunchOption launchOption) + { + var version = await GetVersionAsync(versionName); + return BuildProcess(version, launchOption); + } + public Process BuildProcess( IVersion version, MLaunchOption launchOption) { - launchOption.NativesDirectory = createNativePath(version); - setLaunchOption(version, launchOption); + launchOption.NativesDirectory ??= createNativePath(version); + launchOption.Path ??= MinecraftPath; + launchOption.StartVersion ??= version; + launchOption.JavaPath ??= GetJavaPath(version); + launchOption.RulesContext ??= RulesContext; + var processBuilder = new MinecraftProcessBuilder(RulesEvaluator, launchOption); var process = processBuilder.CreateProcess(); return process; } - private void setLaunchOption(IVersion version, MLaunchOption option) - { - option.Path ??= MinecraftPath; - option.StartVersion ??= version; - option.JavaPath = GetJavaPath(version); - } - public string? GetJavaPath(IVersion version) { if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion?.Component)) @@ -124,9 +160,11 @@ private void setLaunchOption(IVersion version, MLaunchOption option) RulesContext); } - public string? GetDefaultJavaPath() => JavaPathResolver - .GetDefaultJavaBinaryPath(MinecraftPath, RulesContext); - + public string? GetDefaultJavaPath() + { + return JavaPathResolver.GetDefaultJavaBinaryPath(MinecraftPath, RulesContext); + } + private string createNativePath(IVersion version) { NativeLibraryExtractor.Clean(MinecraftPath, version); diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index 6412352..87ad131 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -81,7 +81,6 @@ public IEnumerable BuildArguments() { "classpath_separator", Path.PathSeparator.ToString() }, { "classpath" , classpath }, - { "auth_xuid" , session.Xuid }, { "auth_player_name" , session.Username }, { "version_name" , version.Id }, { "game_directory" , minecraftPath.BasePath }, @@ -201,14 +200,17 @@ private IEnumerable getClasspaths() .Where(lib => lib.CheckIsRequired("SIDE")) .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) .Where(lib => lib.Artifact != null) - .Select(lib => lib.GetLibraryPath()); + .Select(lib => Path.Combine(minecraftPath.Library, lib.GetLibraryPath())); foreach (var item in libPaths) yield return item; // .jar file - if (!string.IsNullOrEmpty(version.Jar)) // TODO: decide what Jar file should be used. current jar or parent jar - yield return (minecraftPath.GetVersionJarPath(version.Jar)); + // TODO: decide what Jar file should be used. current jar or parent jar + var jar = version.GetInheritedProperty(v => v.Jar); + if (string.IsNullOrEmpty(jar)) + jar = version.Id; + yield return (minecraftPath.GetVersionJarPath(jar)); } // if input1 is null, return input2 diff --git a/src/ProcessBuilder/ProcessUtil.cs b/src/ProcessBuilder/ProcessWrapper.cs similarity index 92% rename from src/ProcessBuilder/ProcessUtil.cs rename to src/ProcessBuilder/ProcessWrapper.cs index a669ffb..8b532bd 100644 --- a/src/ProcessBuilder/ProcessUtil.cs +++ b/src/ProcessBuilder/ProcessWrapper.cs @@ -33,11 +33,12 @@ public void StartWithEvents() Process.BeginOutputReadLine(); } - public Task WaitForExitTaskAsync() + public async Task WaitForExitTaskAsync() { - return Task.Run(() => + await Task.Run(() => { Process.WaitForExit(); }); + return Process.ExitCode; } } diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs index 13f46fe..a22cd15 100644 --- a/src/Tasks/DownloadTask.cs +++ b/src/Tasks/DownloadTask.cs @@ -1,3 +1,5 @@ +using CmlLib.Core.Installers; + namespace CmlLib.Core.Tasks; public class DownloadTask : LinkedTask @@ -18,7 +20,6 @@ public DownloadTask(TaskFile file, HttpClient httpClient) : base(file) protected HttpClient HttpClient; public string Path { get; } public string Url { get; } - public long Size { get; } protected async override ValueTask OnExecuted( IProgress? progress, @@ -32,12 +33,22 @@ protected async virtual ValueTask DownloadFile( IProgress? progress, CancellationToken cancellationToken) { + IProgress? interceptedProgress = progress; + if (this.Size <= 0) + { + interceptedProgress = new SyncProgress(e => + { + this.Size = e.TotalBytes; + progress?.Report(e); + }); + } + await HttpClientDownloadHelper.DownloadFileAsync( - HttpClient, - Url, - Size, + HttpClient, + Url, + Size, Path, - progress, + interceptedProgress, cancellationToken); } } \ No newline at end of file diff --git a/src/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs index a369121..aae128b 100644 --- a/src/Tasks/LinkedTask.cs +++ b/src/Tasks/LinkedTask.cs @@ -15,6 +15,8 @@ public LinkedTask(string name) } public string Name { get; set; } + public long LinkedSize { get; protected set; } + public long Size { get; protected set; } public LinkedTask? NextTask { get; private set; } public async ValueTask Execute( @@ -22,8 +24,13 @@ public LinkedTask(string name) CancellationToken cancellationToken) { var nextTask = await OnExecuted(progress, cancellationToken); - if (nextTask != null && nextTask.Name != this.Name) - throw new InvalidOperationException("Name should be same"); + if (nextTask != null) + { + if (nextTask.Name != this.Name) + throw new InvalidOperationException("Name should be same"); + nextTask.LinkedSize = LinkedSize + Size; + } + return nextTask; } diff --git a/test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj index afe3242..acfe900 100644 --- a/test/CmlLib.Core.Test.csproj +++ b/test/CmlLib.Core.Test.csproj @@ -3,7 +3,7 @@ net6.0 enable - + enable false @@ -15,7 +15,7 @@ - + diff --git a/test/IOUtilTest.cs b/test/IOUtilTest.cs index 2543e7a..12eb0e0 100644 --- a/test/IOUtilTest.cs +++ b/test/IOUtilTest.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Internals; using NUnit.Framework; namespace CmlLib.Core.Test; diff --git a/test/MapperTest.cs b/test/MapperTest.cs index ff9f6e4..462565d 100644 --- a/test/MapperTest.cs +++ b/test/MapperTest.cs @@ -4,6 +4,7 @@ namespace CmlLib.Core.Test; public class MapperTest { + [Platform("win")] [TestCase( @"[de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip]", @"C:\libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip")] @@ -23,6 +24,6 @@ public void TestFullPath(string input, string exp) [TestCase("\"value 4\"", "\"value 4\"")] public void TestHandleEmptyArg(string input, string exp) { - Assert.AreEqual(exp, Mapper.HandleEmptyArg(input)); + //Assert.AreEqual(exp, Mapper.HandleEmptyArg(input)); } } \ No newline at end of file diff --git a/test/PackageNameTest.cs b/test/PackageNameTest.cs index 201a6a4..f04603b 100644 --- a/test/PackageNameTest.cs +++ b/test/PackageNameTest.cs @@ -28,7 +28,7 @@ public void TestGetPathWin(string input, string exp) [Platform("Unix")] [TestCase("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", - @"de/oceanlabs/mcp/mcp_config/1.16.2-20200812.004259/mcp_config-1.16.2-20200812.004259.jar")] + @"de/oceanlabs/mcp/mcp_config/1.16.2-20200812.004259/mcp_config-1.16.2-20200812.004259-mappings.jar")] [TestCase("net.java.dev.jna:platform:3.4.0", @"net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar")] public void TestGetPathUnix(string input, string exp) diff --git a/test/ProcessArgumentBuilderTest.cs b/test/ProcessArgumentBuilderTest.cs new file mode 100644 index 0000000..5ff6cce --- /dev/null +++ b/test/ProcessArgumentBuilderTest.cs @@ -0,0 +1,87 @@ +using NUnit.Framework; +using CmlLib.Core.Rules; +using CmlLib.Core.ProcessBuilder; + +namespace CmlLib.Core.Test; + +[TestFixture] +public class ProcessArgumentBuilderTest +{ + [TestCase(null)] + [TestCase("")] + public void TestAddEmptyValue(string test) + { + var builder = createBuilder(); + builder.Add(test); + var actual = builder.Build(); + Assert.That(actual.Length, Is.Zero); + } + + [TestCase("\"\"", "\"\"")] + [TestCase("word", "word")] + [TestCase("\"word\"", "\"word\"")] + [TestCase("double word", "\"double word\"")] + [TestCase("\"double word\"", "\"double word\"")] + [TestCase("\"-k=v\"", "\"-k=v\"")] + [TestCase("\"-k=double word\"", "\"-k=double word\"")] + [TestCase(" - ", "-")] + [TestCase("-k", "-k")] + [TestCase("-k=", "-k=")] + [TestCase("-k=\"\"", "-k=\"\"")] + [TestCase("-k=v", "-k=v")] + [TestCase("-k=\"v\"", "-k=\"v\"")] + [TestCase("-k=\"double word\"", "-k=\"double word\"")] + public void TestAddValue(string test, string expected) + { + var builder = createBuilder(); + builder.Add(test); + var actual = builder.Build(); + Assert.That(actual, Is.EqualTo(new string[] { expected })); + } + + [TestCase("-k", null, "-k")] + [TestCase("-k", "", "-k=\"\"")] + [TestCase("-k", "value", "-k=value")] + [TestCase("-k", "\"value\"", "-k=\"value\"")] + [TestCase("-k", "double word", "-k=\"double word\"")] + [TestCase("-k", "\"double word\"", "-k=\"double word\"")] + [TestCase("a", "b", "a=b")] // key not starts with '-' + public void TestAddKeyValue(string key, string value, string expected) + { + var builder = createBuilder(); + builder.AddKeyValue(key, value); + var actual = builder.Build(); + Assert.That(actual, Is.EqualTo(new string[] { expected })); + } + + [TestCase("-key value")] + [TestCase("-key=value and")] + public void TestAddValueException(string test) + { + var builder = createBuilder(); + Assert.Throws(() => + { + builder.Add(test); + }); + } + + [Test, Combinatorial] + public void TestAddKeyValueException( + [Values(null, "", "-k -v", "-k and", "\"key\"")] string key, + [Values(null, "", "word", "double word")] string value) + { + var builder = createBuilder(); + Assert.Throws(() => + { + builder.AddKeyValue(key, value); + }); + } + + private ProcessArgumentBuilder createBuilder() + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(LauncherOSRule.Current); + var builder = new ProcessArgumentBuilder(evaluator, context); + return builder; + } +} \ No newline at end of file From fb671c525f2a9d245ba4d859580efe2c5b315e49 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 27 Aug 2023 21:02:06 +0900 Subject: [PATCH 071/137] update --- CmlLib.Core.sln | 19 ++- examples/console/Program.cs | 25 ++- src/FileExtractors/AssetFileExtractor.cs | 14 +- src/FileExtractors/ClientFileExtractor.cs | 3 +- src/FileExtractors/JavaFileExtractor.cs | 10 +- src/FileExtractors/LibraryFileExtractor.cs | 23 ++- src/FileExtractors/LogFileExtractor.cs | 4 +- src/Installers/TPLGameInstaller.cs | 189 +++++++++++++-------- src/Tasks/DownloadTask.cs | 38 +++-- src/Tasks/LinkedTask.cs | 2 - src/Tasks/LinkedTaskHead.cs | 1 - src/Tasks/ProgressTask.cs | 31 ++++ src/Version/IVersion.cs | 1 + src/Version/JsonVersion.cs | 9 + test/CmlLib.Core.Test.csproj | 13 +- test/DummyDownloadTask.cs | 37 ++++ test/DummyDownloaderExtractor.cs | 40 +++++ test/DummyTask.cs | 19 +++ test/DummyTaskExtractor.cs | 40 +++++ test/DummyVersion.cs | 42 +++++ test/Program.cs | 14 ++ test/TPLGameInstallerTest.cs | 29 ++++ 22 files changed, 481 insertions(+), 122 deletions(-) create mode 100644 src/Tasks/ProgressTask.cs create mode 100644 test/DummyDownloadTask.cs create mode 100644 test/DummyDownloaderExtractor.cs create mode 100644 test/DummyTask.cs create mode 100644 test/DummyTaskExtractor.cs create mode 100644 test/DummyVersion.cs create mode 100644 test/Program.cs create mode 100644 test/TPLGameInstallerTest.cs diff --git a/CmlLib.Core.sln b/CmlLib.Core.sln index 4ad0fba..42cffa5 100644 --- a/CmlLib.Core.sln +++ b/CmlLib.Core.sln @@ -3,26 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Test", "test\CmlLib.Core.Test.csproj", "{86827E6C-1353-4DF1-968F-31801A5EB032}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLib.Core.Test", "test\CmlLib.Core.Test.csproj", "{86827E6C-1353-4DF1-968F-31801A5EB032}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibCoreSample", "examples\console\CmlLibCoreSample.csproj", "{C9F876F3-5579-4B3A-A808-17845BB9C744}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLibCoreSample", "examples\console\CmlLibCoreSample.csproj", "{C9F876F3-5579-4B3A-A808-17845BB9C744}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLibWinFormSample", "examples\winform\CmlLibWinFormSample.csproj", "{362D93FE-B624-451E-AAD9-D66EFBE90EC3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLibWinFormSample", "examples\winform\CmlLibWinFormSample.csproj", "{362D93FE-B624-451E-AAD9-D66EFBE90EC3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core.Benchmarks", "benchmark\CmlLib.Core.Benchmarks.csproj", "{99852557-DBA7-4E13-A883-4B535F0D33FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLib.Core.Benchmarks", "benchmark\CmlLib.Core.Benchmarks.csproj", "{99852557-DBA7-4E13-A883-4B535F0D33FB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CmlLib.Core", "src\CmlLib.Core.csproj", "{F9718097-45C5-40CD-80B1-42408BEA8935}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CmlLib.Core", "src\CmlLib.Core.csproj", "{F9718097-45C5-40CD-80B1-42408BEA8935}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {86827E6C-1353-4DF1-968F-31801A5EB032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86827E6C-1353-4DF1-968F-31801A5EB032}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -45,8 +42,14 @@ Global {F9718097-45C5-40CD-80B1-42408BEA8935}.Release|Any CPU.ActiveCfg = Release|Any CPU {F9718097-45C5-40CD-80B1-42408BEA8935}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection GlobalSection(NestedProjects) = preSolution {C9F876F3-5579-4B3A-A808-17845BB9C744} = {6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE} {362D93FE-B624-451E-AAD9-D66EFBE90EC3} = {6C52D5EC-0391-43EB-B34A-E5AA1E5BBBFE} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B19F9927-91FB-4CA6-96D9-6C375C06B3C8} + EndGlobalSection EndGlobal diff --git a/examples/console/Program.cs b/examples/console/Program.cs index eb33369..ea7262c 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -22,10 +22,11 @@ private async Task Start() // initialize launcher var parameters = LauncherParameters.CreateDefault(); - parameters.VersionLoader = new VersionLoaderCollection - { - new LocalVersionLoader(parameters.MinecraftPath!) - }; + //parameters.GameInstaller = new TPLGameInstaller(1); + //parameters.VersionLoader = new VersionLoaderCollection + //{ + // new LocalVersionLoader(parameters.MinecraftPath!) + //}; var launcher = new MinecraftLauncher(parameters); // add event handler @@ -66,7 +67,7 @@ private async Task Start() Console.WriteLine(process.StartInfo.FileName); Console.WriteLine("Arguments:"); Console.WriteLine(process.StartInfo.Arguments); - + return; var processWrapper = new ProcessWrapper(process); processWrapper.OutputReceived += (s, e) => Console.WriteLine(e); processWrapper.StartWithEvents(); @@ -79,6 +80,8 @@ private async Task Start() private string bottomText = "..."; private int previousProceed; private int lastCursorLeft = 0; + private int lastUpdate = 0; + private long lastProgressed = 0; // print installation progress to console private void Launcher_FileProgressChanged(object? sender, InstallerProgressChangedEventArgs e) @@ -88,7 +91,8 @@ private void Launcher_FileProgressChanged(object? sender, InstallerProgressChang if (previousProceed > e.ProceedTasks) return; - Console.WriteLine($"[{e.ProceedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"); + var msg = $"[{e.ProceedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"; + Console.WriteLine(msg.PadRight(lastCursorLeft)); printBottomProgress(); previousProceed = e.ProceedTasks; @@ -100,7 +104,14 @@ private void Launcher_ByteProgressChanged(object? sender, ByteProgress e) lock (consoleLock) { var percent = (e.ProgressedBytes / (double)e.TotalBytes) * 100; - bottomText = $"{percent}% ({e.ProgressedBytes} bytes / {e.TotalBytes} bytes)"; + var total = e.TotalBytes.ToString().PadRight(12); + var progressed = e.ProgressedBytes.ToString().PadLeft(12); + + var now = Environment.TickCount; + var speed = (e.ProgressedBytes - lastProgressed) / (double)(now - lastUpdate); + bottomText = $"==> {percent:F2}%, ({progressed} / {total}) bytes, {speed:F2} KB/s"; + lastProgressed = e.ProgressedBytes; + lastUpdate = now; printBottomProgress(); } } diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index 6c6b42d..1fe5d4c 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -69,6 +69,7 @@ private async ValueTask createAssetIndexStream(MinecraftPath path, MFile await res.CopyToAsync(ms); } // dispose immediately + ms.Position = 0; using (var fs = File.Create(indexFilePath)) { await ms.CopyToAsync(fs); @@ -120,11 +121,16 @@ private IEnumerable extractFromAssetIndexJson( }; var checkTask = new FileCheckTask(file); - checkTask.OnFalse = new DownloadTask(file, httpClient); + var downloadTask = new DownloadTask(file, httpClient); + var progressTask = ProgressTask.CreateDoneTask(file); + var copyTask = new FileCopyTask(prop.Name, hashPath, copyPath.ToArray()); + + checkTask.OnTrue = progressTask; + checkTask.OnFalse = downloadTask; + + if (copyPath.Any()) + downloadTask.InsertNextTask(copyTask); - if (copyPath.Count > 0) - checkTask.InsertNextTask(new FileCopyTask(prop.Name, hashPath, copyPath.ToArray())); - var taskHead = new LinkedTaskHead(checkTask, file); yield return taskHead; } diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 3e40ea4..74906a2 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -25,7 +25,7 @@ public ValueTask> Extract( private IEnumerable extract(MinecraftPath path, IVersion version) { - var id = version.Jar; + var id = version.JarId; var url = version.Client?.Url; if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(id)) @@ -41,6 +41,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version }; var checkTask = new FileCheckTask(file); + checkTask.OnTrue = ProgressTask.CreateDoneTask(file); checkTask.OnFalse = new DownloadTask(file, _httpClient); yield return new LinkedTaskHead(checkTask, file); diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 706a668..40cf9b2 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -178,9 +178,15 @@ private IEnumerable extractFromManifestJson( }; var checkTask = new FileCheckTask(file); - checkTask.OnFalse = new DownloadTask(file, _httpClient); + var downloadTask = new DownloadTask(file, _httpClient); + var chmodTask = new ChmodTask(file.Name, file.Path); + var progressTask = ProgressTask.CreateDoneTask(file); + + checkTask.OnTrue = progressTask; + checkTask.OnFalse = downloadTask; + if (executable) - checkTask.InsertNextTask(new ChmodTask(file.Name, file.Path)); + downloadTask.InsertNextTask(chmodTask); return new LinkedTaskHead(checkTask, file); } diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index dbc58b3..a49779a 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -65,12 +65,11 @@ private IEnumerable createLibraryTasks( { Path = Path.Combine(path.Library, libPath), Url = createDownloadUrl(artifact.Url, libPath), - Hash = artifact.GetSha1() + Hash = artifact.GetSha1(), + Size = artifact.Size }; - var task = new FileCheckTask(file); - task.OnFalse = new DownloadTask(file, _httpClient); - yield return new LinkedTaskHead(task, file); + yield return createTaskFromFile(file); } // native library (*.dll, *.so) @@ -84,16 +83,22 @@ private IEnumerable createLibraryTasks( { Path = Path.Combine(path.Library, libPath), Url = createDownloadUrl(native.Url, libPath), - Hash = native.GetSha1() + Hash = native.GetSha1(), + Size = native.Size }; - - var task = new FileCheckTask(file); - task.OnFalse = new DownloadTask(file, _httpClient); - yield return new LinkedTaskHead(task, file); + yield return createTaskFromFile(file); } } } + private LinkedTaskHead createTaskFromFile(TaskFile file) + { + var task = new FileCheckTask(file); + task.OnTrue = ProgressTask.CreateDoneTask(file); + task.OnFalse = new DownloadTask(file, _httpClient); + return new LinkedTaskHead(task, file); + } + private string? createDownloadUrl(string? url, string path) { if (string.IsNullOrEmpty(url) && string.IsNullOrEmpty(path)) diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index b3a6753..8abf87b 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -38,9 +38,11 @@ private IEnumerable extract(MinecraftPath path, IVersion version { Path = path.GetLogConfigFilePath(id), Url = url, - Hash = version.Logging?.LogFile?.GetSha1() + Hash = version.Logging?.LogFile?.GetSha1(), + Size = version.Logging?.LogFile?.Size ?? 0 }; var task = new FileCheckTask(file); + task.OnTrue = ProgressTask.CreateDoneTask(file); task.OnFalse = new DownloadTask(file, _httpClient); yield return new LinkedTaskHead(task, file); } diff --git a/src/Installers/TPLGameInstaller.cs b/src/Installers/TPLGameInstaller.cs index 572c256..e8d6102 100644 --- a/src/Installers/TPLGameInstaller.cs +++ b/src/Installers/TPLGameInstaller.cs @@ -1,9 +1,9 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks.Dataflow; using CmlLib.Core.FileExtractors; using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; +using System.Collections.Concurrent; +using System.Threading.Tasks.Dataflow; namespace CmlLib.Core.Installers; @@ -13,25 +13,25 @@ public class TPLGameInstaller : IGameInstaller public static int BestMaxParallelism => _bestMaxParallelism ??= getBestMaxParallelism(); public static int getBestMaxParallelism() { - // 2 <= p <= 8 + // 2 <= p <= 6 var p = Environment.ProcessorCount; p = Math.Max(p, 2); - p = Math.Min(p, 8); + p = Math.Min(p, 6); return p; } private readonly int _maxParallelism; - public TPLGameInstaller(int parallelism) => + public TPLGameInstaller(int parallelism) => _maxParallelism = parallelism; public async ValueTask Install( - IEnumerable extractors, - MinecraftPath path, - IVersion version, - RulesEvaluatorContext rulesContext, - IProgress? fileProgress, - IProgress? byteProgress, + IEnumerable extractors, + MinecraftPath path, + IVersion version, + RulesEvaluatorContext rulesContext, + IProgress? fileProgress, + IProgress? byteProgress, CancellationToken cancellationToken) { var executor = new TPLGameInstallerExecutor(_maxParallelism, path, version, rulesContext); @@ -41,6 +41,13 @@ public async ValueTask Install( } } +public enum GameInstallerExceptionMode +{ + Ignore, + ThrowException, + ThrowAggreateException +} + class TPLGameInstallerExecutor { private readonly int _maxParallelism; @@ -53,19 +60,22 @@ public TPLGameInstallerExecutor( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext) => - (_maxParallelism, _path, _version, _rulesContext) = + (_maxParallelism, _path, _version, _rulesContext) = (parallelism, path, version, rulesContext); public event EventHandler? FileProgress; public event EventHandler? ByteProgress; + public GameInstallerExceptionMode ExceptionMode { get; set; } + + // 庰 ȭ + private ThreadLocal progressStorage = null!; private bool isStarted = false; - private IDataflowBlock? extractionBlock; - private ConcurrentDictionary sizeStorage = null!; // we don't use null-safety check since the performance is most important part here - private ThreadLocal> progressStorage = null!; private CancellationToken CancellationToken; private int totalTasks = 0; private int proceed = 0; + private ConcurrentBag exceptions = null!; + private HashSet distinctStorage = null!; public async ValueTask Install( IEnumerable extractors, @@ -78,14 +88,14 @@ public async ValueTask Install( initializeResources(); CancellationToken = cancellationToken; - var extractBlock = createExtractBlock(cancellationToken); - var executeBlock = createExecuteBlock(extractBlock.Completion); - extractionBlock = extractBlock; - extractBlock.LinkTo(executeBlock); + var extractorBlock = createExtractBlock(cancellationToken); + var executeBlock = createExecuteBlock(extractorBlock.Completion); + extractorBlock.LinkTo(executeBlock!, t => t != null); + extractorBlock.LinkTo(DataflowBlock.NullTarget()); - await Task.WhenAll(extractors.Select(extractor => extractBlock.SendAsync(extractor))); - extractBlock.Complete(); - await extractBlock.Completion; + await Task.WhenAll(extractors.Select(extractor => extractorBlock.SendAsync(extractor))); + extractorBlock.Complete(); + await extractorBlock.Completion; if (proceed == totalTasks) return; @@ -98,45 +108,40 @@ public async ValueTask Install( } reportByteProgress(); disposeResources(); + + if (!exceptions.IsEmpty) + { + throw new AggregateException(exceptions); + } } private void initializeResources() { - sizeStorage = new ConcurrentDictionary(); - progressStorage = new ThreadLocal>( - () => new Dictionary(), - true); + progressStorage = new ThreadLocal(() => new ByteProgress(), true); + exceptions = new ConcurrentBag(); + distinctStorage = new HashSet(); totalTasks = 0; proceed = 0; } private void disposeResources() { - sizeStorage.Clear(); progressStorage.Dispose(); + progressStorage = null!; + distinctStorage.Clear(); + distinctStorage = null!; } + // 庰 ȭ Ͽ private void reportByteProgress() { long totalBytes = 0; long progressedBytes = 0; - foreach (var dict in progressStorage.Values) - { - lock (dict) - { - foreach (var kv in dict) - { - totalBytes += kv.Value.TotalBytes; - progressedBytes += kv.Value.ProgressedBytes; - sizeStorage.TryRemove(kv.Key, out _); - } - } - } - - foreach (var kv in sizeStorage) + foreach (var v in progressStorage.Values) { - totalBytes += kv.Value; + totalBytes += v.TotalBytes; + progressedBytes += v.ProgressedBytes; } fireByteProgress(totalBytes, progressedBytes); @@ -145,61 +150,103 @@ private void reportByteProgress() private BufferBlock createExecuteBlock(Task extractTask) { var buffer = new BufferBlock(); - var executor = new TransformBlock(async task => - { - var progress = new SyncProgress(e => + var executor = createExecuteTransformBlock(extractTask, buffer, _maxParallelism); + buffer.LinkTo(executor); + executor.LinkTo(buffer!, t => t != null); + executor.LinkTo(DataflowBlock.NullTarget()); + + return buffer; + } + + private TransformBlock createExecuteTransformBlock(Task extractTask, IDataflowBlock buffer, int parallelism) + { + return new TransformBlock( + t => execute(t, extractTask, buffer), + new ExecutionDataflowBlockOptions { - lock (progressStorage.Value!) - { - progressStorage.Value![task.Name] = e; - } + MaxDegreeOfParallelism = parallelism }); + } + + private async Task execute(LinkedTask task, Task extractTask, IDataflowBlock buffer) + { + // task + var lastProgress = new ByteProgress + { + TotalBytes = task.Size, + ProgressedBytes = 0 + }; + var progress = new SyncProgress(e => + { + // ȭ + progressStorage.Value = new ByteProgress + { + TotalBytes = progressStorage.Value.TotalBytes + e.TotalBytes - lastProgress.TotalBytes, + ProgressedBytes = progressStorage.Value.ProgressedBytes + e.ProgressedBytes - lastProgress.ProgressedBytes + }; + lastProgress = e; + }); - var nextTask = await task.Execute(progress, CancellationToken); - if (nextTask == null) + LinkedTask? nextTask = null; + try + { + nextTask = await task.Execute(progress, CancellationToken); + } + catch (Exception ex) + { + exceptions.Add(ex); + if (ExceptionMode == GameInstallerExceptionMode.ThrowException) { - fireFileProgress(task.Name, TaskStatus.Done); - var totalSize = task.LinkedSize + task.Size; - fireByteProgress(totalSize, totalSize); - Interlocked.Increment(ref proceed); - if (totalTasks == proceed && (extractionBlock?.Completion.IsCompleted ?? false)) - buffer.Complete(); + buffer.Complete(); + throw; } + } - return nextTask; - }, new ExecutionDataflowBlockOptions + progress.Report(new ByteProgress // byte ä { - MaxDegreeOfParallelism = _maxParallelism + TotalBytes = lastProgress.TotalBytes, + ProgressedBytes = lastProgress.TotalBytes, }); - buffer.LinkTo(executor); - executor.LinkTo(buffer!, t => t != null); - executor.LinkTo(DataflowBlock.NullTarget()); + if (nextTask == null) + { + fireFileProgress(task.Name, TaskStatus.Done); + Interlocked.Increment(ref proceed); + if (totalTasks == proceed && extractTask.IsCompleted) + buffer.Complete(); + } - return buffer; + return nextTask; } private IPropagatorBlock createExtractBlock(CancellationToken cancellationToken) { - var block = new TransformManyBlock(async extractor => + var extractorBlock = new TransformManyBlock(async extractor => { var tasks = await extractor.Extract(_path, _version, _rulesContext, cancellationToken); return tasks .Where(task => task.First != null) + .Where(task => string.IsNullOrEmpty(task.File.Path) || distinctStorage.Add(task.File.Path)) .Select(task => { - Interlocked.Increment(ref totalTasks); + totalTasks++; fireFileProgress(task.Name, TaskStatus.Queued); - sizeStorage[task.Name] = task.File.Size; - return task.First; - })!; + var storedProgress = progressStorage.Value; + progressStorage.Value = new ByteProgress + { + TotalBytes = storedProgress.TotalBytes + task.File.Size, + ProgressedBytes = 0 + }; + + return task.First!; + }); }, new ExecutionDataflowBlockOptions { - MaxDegreeOfParallelism = _maxParallelism + MaxDegreeOfParallelism = 1 }); - return block; + return extractorBlock; } private void fireFileProgress(string name, TaskStatus status) diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs index a22cd15..cf7d04e 100644 --- a/src/Tasks/DownloadTask.cs +++ b/src/Tasks/DownloadTask.cs @@ -33,22 +33,32 @@ protected async virtual ValueTask DownloadFile( IProgress? progress, CancellationToken cancellationToken) { - IProgress? interceptedProgress = progress; - if (this.Size <= 0) + var interceptedProgress = new SyncProgress(e => { - interceptedProgress = new SyncProgress(e => + this.Size = e.TotalBytes; + progress?.Report(e); + }); + + for (int i = 3; i > 0; i--) + { + try { - this.Size = e.TotalBytes; - progress?.Report(e); - }); + // TODO: handle duplicated file, example: e9833a1512b57bcf88ac4fdcc8df4e5a7e9d701d + await HttpClientDownloadHelper.DownloadFileAsync( + HttpClient, + Url, + Size, + Path, + interceptedProgress, + cancellationToken); + break; + } + catch (Exception) + { + if (i == 1) + throw; + await Task.Delay(3000); + } } - - await HttpClientDownloadHelper.DownloadFileAsync( - HttpClient, - Url, - Size, - Path, - interceptedProgress, - cancellationToken); } } \ No newline at end of file diff --git a/src/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs index aae128b..de0b906 100644 --- a/src/Tasks/LinkedTask.cs +++ b/src/Tasks/LinkedTask.cs @@ -15,7 +15,6 @@ public LinkedTask(string name) } public string Name { get; set; } - public long LinkedSize { get; protected set; } public long Size { get; protected set; } public LinkedTask? NextTask { get; private set; } @@ -28,7 +27,6 @@ public LinkedTask(string name) { if (nextTask.Name != this.Name) throw new InvalidOperationException("Name should be same"); - nextTask.LinkedSize = LinkedSize + Size; } return nextTask; diff --git a/src/Tasks/LinkedTaskHead.cs b/src/Tasks/LinkedTaskHead.cs index cd8d5b0..765c3ab 100644 --- a/src/Tasks/LinkedTaskHead.cs +++ b/src/Tasks/LinkedTaskHead.cs @@ -16,7 +16,6 @@ public LinkedTaskHead(LinkedTask? first, TaskFile file) => if (First == null) return new ValueTask(); - var context = new TaskExecutionContext(progress, cancellationToken); return First.Execute(progress, cancellationToken); } } \ No newline at end of file diff --git a/src/Tasks/ProgressTask.cs b/src/Tasks/ProgressTask.cs new file mode 100644 index 0000000..a8a32c2 --- /dev/null +++ b/src/Tasks/ProgressTask.cs @@ -0,0 +1,31 @@ +namespace CmlLib.Core.Tasks; + +public class ProgressTask : LinkedTask +{ + public static ProgressTask CreateDoneTask(TaskFile file) + { + var task = new ProgressTask(file, new ByteProgress + { + TotalBytes = file.Size, + ProgressedBytes = file.Size + }); + task.Size = file.Size; + return task; + } + + public ProgressTask(TaskFile file, ByteProgress progress) : base(file) => + Progress = progress; + + public ProgressTask(string name, ByteProgress progress) : base(name) => + Progress = progress; + + public ByteProgress Progress { get; } + + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) + { + progress?.Report(Progress); + return new ValueTask(NextTask); + } +} diff --git a/src/Version/IVersion.cs b/src/Version/IVersion.cs index 4982e71..940dc80 100644 --- a/src/Version/IVersion.cs +++ b/src/Version/IVersion.cs @@ -7,6 +7,7 @@ namespace CmlLib.Core.Version; public interface IVersion { string Id { get; } + string JarId { get; } string? InheritsFrom { get; } IVersion? ParentVersion { get; set; } AssetMetadata? AssetIndex { get; } diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index c349820..c092509 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -22,6 +22,7 @@ public JsonVersion(JsonElement json, JsonVersionParserOptions options) } public string Id { get; } + public string? JarId => getJarId(); public string? InheritsFrom => _model.InheritsFrom; @@ -57,6 +58,14 @@ public JsonVersion(JsonElement json, JsonVersionParserOptions options) private MArgument[]? _jvmArgs = null; public MArgument[] JvmArguments => _jvmArgs ??= getJvmArguments(); + private string? getJarId() + { + var jar = this.GetInheritedProperty(v => v.Jar); + if (string.IsNullOrEmpty(jar)) + return Id; + return jar; + } + private AssetMetadata? getAssetIndex() { if (string.IsNullOrEmpty(_model.AssetIndex?.Id)) diff --git a/test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj index acfe900..d732829 100644 --- a/test/CmlLib.Core.Test.csproj +++ b/test/CmlLib.Core.Test.csproj @@ -5,15 +5,24 @@ enable enable false + false - + + + + + Exe + $(DefineConstants);TestSdk + + + - + diff --git a/test/DummyDownloadTask.cs b/test/DummyDownloadTask.cs new file mode 100644 index 0000000..ed773cd --- /dev/null +++ b/test/DummyDownloadTask.cs @@ -0,0 +1,37 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Test; + +public class DummyDownloadTask : DownloadTask +{ + public static int Seed = 0; + + public DummyDownloadTask(TaskFile file, HttpClient httpClient) : base(file, httpClient) + { + } + + protected override ValueTask DownloadFile( + IProgress? progress, + CancellationToken cancellationToken) + { + for (int i = 0; i < Size; i += 1) + { + if (Size % 128 == 0) + { + progress?.Report(new ByteProgress + { + TotalBytes = Size, + ProgressedBytes = i + }); + } + Seed += i; + //await Task.Delay(1); + } + progress?.Report(new ByteProgress + { + TotalBytes = Size, + ProgressedBytes = Size + }); + return new ValueTask(); + } +} \ No newline at end of file diff --git a/test/DummyDownloaderExtractor.cs b/test/DummyDownloaderExtractor.cs new file mode 100644 index 0000000..0d97416 --- /dev/null +++ b/test/DummyDownloaderExtractor.cs @@ -0,0 +1,40 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test; + +public class DummyDownloaderExtractor : IFileExtractor +{ + private readonly int _count; + private readonly string _prefix; + private readonly long _size; + + public DummyDownloaderExtractor(string prefix, int count, long size) => + (_prefix, _count, _size) = (prefix, count, size); + + public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) + { + var result = extract(); + return new ValueTask>(result); + } + + private IEnumerable extract() + { + for (int i = 0; i < _count; i++) + { + var file = new TaskFile(_prefix + "-" + i.ToString()) + { + Size = _size, + Path = "a.dat", + Url = "a.dat" + }; + var task = LinkedTask.LinkTasks( + new DummyTask(file, i), + new DummyDownloadTask(file, null!) + ); + yield return new LinkedTaskHead(task, file); + } + } +} \ No newline at end of file diff --git a/test/DummyTask.cs b/test/DummyTask.cs new file mode 100644 index 0000000..05b627d --- /dev/null +++ b/test/DummyTask.cs @@ -0,0 +1,19 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Test; + +public class DummyTask : LinkedTask +{ + public static int Seed { get; set; } + + public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; + + protected override ValueTask OnExecuted( + IProgress? progress, + CancellationToken cancellationToken) + { + for (int j = 0; j < 1024; j++) + Seed += j; + return new ValueTask(NextTask); + } +} \ No newline at end of file diff --git a/test/DummyTaskExtractor.cs b/test/DummyTaskExtractor.cs new file mode 100644 index 0000000..3a0bf52 --- /dev/null +++ b/test/DummyTaskExtractor.cs @@ -0,0 +1,40 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test; + +public class DummyTaskExtractor : IFileExtractor +{ + private readonly int _count; + private readonly string _prefix; + public DummyTaskExtractor(string prefix, int count) => + (_prefix, _count) = (prefix, count); + + public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) + { + var r = extract(); + return new ValueTask>(r); + } + + private IEnumerable extract() + { + for (int i = 0; i < _count; i++) + { + var file = new TaskFile(_prefix + "-" + i.ToString()) + { + Size = 1024 * 2, + Path = "a.dat", + Url = "a.dat" + }; + var task = LinkedTask.LinkTasks( + new DummyTask(file, i), + new DummyTask(file, i), + new DummyTask(file, i) + //new DummyDownloadTask(file, null!) + ); + yield return new LinkedTaskHead(task, file); + } + } +} \ No newline at end of file diff --git a/test/DummyVersion.cs b/test/DummyVersion.cs new file mode 100644 index 0000000..aa6db61 --- /dev/null +++ b/test/DummyVersion.cs @@ -0,0 +1,42 @@ +using CmlLib.Core.Files; +using CmlLib.Core.Java; +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test; + +public class DummyVersion : IVersion +{ + public string Id => throw new NotImplementedException(); + + public string? InheritsFrom => throw new NotImplementedException(); + + public IVersion? ParentVersion { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public AssetMetadata? AssetIndex => throw new NotImplementedException(); + + public MFileMetadata? Client => throw new NotImplementedException(); + + public JavaVersion? JavaVersion => throw new NotImplementedException(); + + public MLibrary[] Libraries => throw new NotImplementedException(); + + public string? Jar => throw new NotImplementedException(); + + public MLogFileMetadata? Logging => throw new NotImplementedException(); + + public string? MainClass => throw new NotImplementedException(); + + public MArgument[] GameArguments => throw new NotImplementedException(); + + public MArgument[] JvmArguments => throw new NotImplementedException(); + + public DateTime ReleaseTime => throw new NotImplementedException(); + + public string? Type => throw new NotImplementedException(); + + public string? GetProperty(string key) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/test/Program.cs b/test/Program.cs new file mode 100644 index 0000000..50c9fe0 --- /dev/null +++ b/test/Program.cs @@ -0,0 +1,14 @@ +#if TestSdk + +namespace CmlLib.Core.Test; + +internal class Program +{ + public static async Task Main() + { + var tester = new TPLGameInstallerTest(); + await tester.test(); + } +} + +#endif \ No newline at end of file diff --git a/test/TPLGameInstallerTest.cs b/test/TPLGameInstallerTest.cs new file mode 100644 index 0000000..3b7135c --- /dev/null +++ b/test/TPLGameInstallerTest.cs @@ -0,0 +1,29 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Test; + +public class TPLGameInstallerTest +{ + public async Task test() + { + var path = new MinecraftPath(); + var version = new DummyVersion(); + var rulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); + var fileProgress = new Progress(e => + { + //Console.WriteLine($"[{e.ProceedTasks}/{e.TotalTasks}][{e.EventType}] {e.Name}"); + }); + var byteProgress = new Progress(e => + { + Console.WriteLine($"{e.ProgressedBytes} / {e.TotalBytes}"); + }); + var installer = new TPLGameInstaller(1); + var extractors = new IFileExtractor[] + { + new DummyDownloaderExtractor("a", 1024, 1024 * 512) + }; + await installer.Install(extractors, path, version, rulesContext, fileProgress, byteProgress, default); + } +} \ No newline at end of file From c6791f2d2528f14ba9fbb8e911879dffd411deab Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 29 Aug 2023 10:37:30 +0000 Subject: [PATCH 072/137] update benchmark --- benchmark/DummyDownloaderExtractor.cs | 3 +- benchmark/DummyTaskExtractor.cs | 3 +- benchmark/DummyVersion.cs | 2 +- .../ConcurrentDictionaryBenchmark.cs | 1 + .../Executors/ConcurrentQueueBenchmark.cs | 1 + benchmark/Executors/ExecutorBenchmarkBase.cs | 4 +- benchmark/Executors/LockBenchmark.cs | 1 + benchmark/Executors/NoLockBenchmark.cs | 72 +++++++++++++++ benchmark/Executors/SemaphoreSlimBenchmark.cs | 1 + benchmark/Executors/ThreadLocalBenchmark.cs | 1 + benchmark/ExecutorsBenchmark.cs | 19 +++- benchmark/Program.cs | 10 +-- benchmark/RandomFileExtractor.cs | 3 +- ...askExecutorWithDummyDownloaderBenchmark.cs | 41 ++------- .../TPLTaskExecutorWithDummyTaskBenchmark.cs | 87 ++++++++++--------- .../TPLTaskExecutorWithRandomFileBenchmark.cs | 15 ++-- 16 files changed, 169 insertions(+), 95 deletions(-) create mode 100644 benchmark/Executors/NoLockBenchmark.cs diff --git a/benchmark/DummyDownloaderExtractor.cs b/benchmark/DummyDownloaderExtractor.cs index d664617..6b5b932 100644 --- a/benchmark/DummyDownloaderExtractor.cs +++ b/benchmark/DummyDownloaderExtractor.cs @@ -1,4 +1,5 @@ using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; @@ -13,7 +14,7 @@ public class DummyDownloaderExtractor : IFileExtractor public DummyDownloaderExtractor(string prefix, int count, long size) => (_prefix, _count, _size) = (prefix, count, size); - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var r = extract(); return new ValueTask>(r); diff --git a/benchmark/DummyTaskExtractor.cs b/benchmark/DummyTaskExtractor.cs index a59166c..d337e84 100644 --- a/benchmark/DummyTaskExtractor.cs +++ b/benchmark/DummyTaskExtractor.cs @@ -1,4 +1,5 @@ using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; @@ -11,7 +12,7 @@ public class DummyTaskExtractor : IFileExtractor public DummyTaskExtractor(string prefix, int count) => (_prefix, _count) = (prefix, count); - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var r = extract(); return new ValueTask>(r); diff --git a/benchmark/DummyVersion.cs b/benchmark/DummyVersion.cs index 96e9f66..9598931 100644 --- a/benchmark/DummyVersion.cs +++ b/benchmark/DummyVersion.cs @@ -1,6 +1,6 @@ using CmlLib.Core.Files; using CmlLib.Core.Java; -using CmlLib.Core.Launcher; +using CmlLib.Core.ProcessBuilder; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; diff --git a/benchmark/Executors/ConcurrentDictionaryBenchmark.cs b/benchmark/Executors/ConcurrentDictionaryBenchmark.cs index 89205e7..2bd3c1f 100644 --- a/benchmark/Executors/ConcurrentDictionaryBenchmark.cs +++ b/benchmark/Executors/ConcurrentDictionaryBenchmark.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using CmlLib.Core.Benchmarks; +using CmlLib.Core.Installers; using CmlLib.Core.Tasks; namespace CmlLib.Core.Executors; diff --git a/benchmark/Executors/ConcurrentQueueBenchmark.cs b/benchmark/Executors/ConcurrentQueueBenchmark.cs index 34fb5fb..33f5ad1 100644 --- a/benchmark/Executors/ConcurrentQueueBenchmark.cs +++ b/benchmark/Executors/ConcurrentQueueBenchmark.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using CmlLib.Core.Tasks; using CmlLib.Core.Executors; +using CmlLib.Core.Installers; namespace CmlLib.Core.Benchmarks; diff --git a/benchmark/Executors/ExecutorBenchmarkBase.cs b/benchmark/Executors/ExecutorBenchmarkBase.cs index a2950bf..c6c77ac 100644 --- a/benchmark/Executors/ExecutorBenchmarkBase.cs +++ b/benchmark/Executors/ExecutorBenchmarkBase.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks.Dataflow; using BenchmarkDotNet.Attributes; +using CmlLib.Core.Rules; using CmlLib.Core.Tasks; namespace CmlLib.Core.Benchmarks; @@ -8,6 +9,7 @@ public abstract class ExecutorBenchmarkBase { public static ByteProgress LastEvent; + public RulesEvaluatorContext RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); public int MaxParallelism { get; set; } = 6; public string Name { get; set; } = "D"; public int Count { get; set; } = 1024*2; // 256 @@ -26,7 +28,7 @@ public async Task IterationSetup() var minecraftPath = new MinecraftPath(); var version = new DummyVersion(); var taskExtractor = new DummyDownloaderExtractor(Name, Count, Size); - var result = await taskExtractor.Extract(minecraftPath, version); + var result = await taskExtractor.Extract(minecraftPath, version, RulesContext, default); var list = new List(); foreach (var head in result) diff --git a/benchmark/Executors/LockBenchmark.cs b/benchmark/Executors/LockBenchmark.cs index fbe8bb8..4cf9f33 100644 --- a/benchmark/Executors/LockBenchmark.cs +++ b/benchmark/Executors/LockBenchmark.cs @@ -1,4 +1,5 @@ using CmlLib.Core.Executors; +using CmlLib.Core.Installers; using CmlLib.Core.Tasks; namespace CmlLib.Core.Benchmarks; diff --git a/benchmark/Executors/NoLockBenchmark.cs b/benchmark/Executors/NoLockBenchmark.cs new file mode 100644 index 0000000..3390f60 --- /dev/null +++ b/benchmark/Executors/NoLockBenchmark.cs @@ -0,0 +1,72 @@ +using CmlLib.Core.Installers; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Benchmarks; + +public class NoLockBenchmark : ExecutorBenchmarkBase +{ + private ThreadLocal progressStorage = null!; + + protected override void Setup() + { + progressStorage = new ThreadLocal( + () => new ByteProgress(), + true); + } + + protected override void OnTaskAdded(LinkedTaskHead head) + { + progressStorage.Value = new ByteProgress + { + TotalBytes = progressStorage.Value.TotalBytes + head.File.Size, + ProgressedBytes = 0 + }; + } + + protected override void OnReportEvent() + { + long totalBytes = 0; + long progressedBytes = 0; + foreach (var p in progressStorage.Values) + { + totalBytes += p.TotalBytes; + progressedBytes += p.ProgressedBytes; + } + + FireProgress(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } + + protected override async ValueTask Transform(LinkedTask task) + { + var lastProgress = new ByteProgress + { + TotalBytes = task.Size, + ProgressedBytes = 0 + }; + var progress = new SyncProgress(e => + { + progressStorage.Value = new ByteProgress + { + TotalBytes = progressStorage.Value.TotalBytes + e.TotalBytes - lastProgress.TotalBytes, + ProgressedBytes = progressStorage.Value.ProgressedBytes + e.ProgressedBytes - lastProgress.ProgressedBytes + }; + lastProgress = e; + }); + + var nextTask = await task.Execute(progress, default); + + if (nextTask == null) + { + progress.Report(new ByteProgress + { + TotalBytes = lastProgress.TotalBytes, + ProgressedBytes = lastProgress.TotalBytes + }); + } + return nextTask; + } +} \ No newline at end of file diff --git a/benchmark/Executors/SemaphoreSlimBenchmark.cs b/benchmark/Executors/SemaphoreSlimBenchmark.cs index b82cf6b..5d86b10 100644 --- a/benchmark/Executors/SemaphoreSlimBenchmark.cs +++ b/benchmark/Executors/SemaphoreSlimBenchmark.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using CmlLib.Core.Executors; +using CmlLib.Core.Installers; using CmlLib.Core.Tasks; namespace CmlLib.Core.Benchmarks; diff --git a/benchmark/Executors/ThreadLocalBenchmark.cs b/benchmark/Executors/ThreadLocalBenchmark.cs index 2c69798..2c55c2e 100644 --- a/benchmark/Executors/ThreadLocalBenchmark.cs +++ b/benchmark/Executors/ThreadLocalBenchmark.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using CmlLib.Core.Executors; +using CmlLib.Core.Installers; using CmlLib.Core.Tasks; namespace CmlLib.Core.Benchmarks; diff --git a/benchmark/ExecutorsBenchmark.cs b/benchmark/ExecutorsBenchmark.cs index 9f846a7..3677e2e 100644 --- a/benchmark/ExecutorsBenchmark.cs +++ b/benchmark/ExecutorsBenchmark.cs @@ -1,5 +1,6 @@ using BenchmarkDotNet.Attributes; using CmlLib.Core.Executors; +using CmlLib.Core.Installers; namespace CmlLib.Core.Benchmarks; @@ -11,6 +12,7 @@ public class ExecutorsBenchmark LockBenchmark _lock; SemaphoreSlimBenchmark _semaphore; ThreadLocalBenchmark _thread; + NoLockBenchmark _nolock; [IterationSetup] public void IterationSetup() @@ -29,6 +31,9 @@ public void IterationSetup() _thread = new ThreadLocalBenchmark(); _thread.IterationSetup().Wait(); + + _nolock = new NoLockBenchmark(); + _nolock.IterationSetup().Wait(); } //[Benchmark(Baseline = true)] @@ -49,19 +54,25 @@ public async Task StartSemaphore() await _semaphore.Benchmark(); } - [Benchmark(Baseline = true)] - public async Task StartQueue() + [Benchmark] + public async Task StartNoLock() { - await _queue.Benchmark(); + await _nolock.Benchmark(); } + //[Benchmark(Baseline = true)] + //public async Task StartQueue() + //{ + // await _queue.Benchmark(); + //} + //[Benchmark] //public async Task StartThread() //{ // await _thread.Benchmark(); //} - public static void PrintProgress(TaskExecutorProgressChangedEventArgs e) + public static void PrintProgress(InstallerProgressChangedEventArgs e) { //if (status != TaskStatus.Done) return; //if (proceed % 100 != 0) return; diff --git a/benchmark/Program.cs b/benchmark/Program.cs index 90a6d8f..cd36fd5 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -6,13 +6,13 @@ var summary = BenchmarkRunner.Run(); return; -//await once(); -//async Task once() +await once(); +async Task once() { - var benchmark = new SemaphoreSlimBenchmark(); + var benchmark = new NoLockBenchmark(); benchmark.Verbose = true; - benchmark.Size = 1024 * 1; - benchmark.Count = 1024 * 256; + //benchmark.Size = 1024 * 1; + //benchmark.Count = 1024 * 256; //benchmark.MaxParallelism = 1; //benchmark.MaxParallelism = 12; //benchmark.Count = 1024 * 16; diff --git a/benchmark/RandomFileExtractor.cs b/benchmark/RandomFileExtractor.cs index 914bc02..2b33688 100644 --- a/benchmark/RandomFileExtractor.cs +++ b/benchmark/RandomFileExtractor.cs @@ -1,4 +1,5 @@ using CmlLib.Core.FileExtractors; +using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; @@ -50,7 +51,7 @@ public void Cleanup() Directory.Delete(_path); } - public ValueTask> Extract(MinecraftPath path, IVersion version) + public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var result = extract(); return new ValueTask>(result); diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs index 343b4c7..6e4f856 100644 --- a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Engines; using CmlLib.Core.Executors; using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; @@ -11,18 +12,13 @@ public class TPLTaskExecutorWithDummyDownloaderBenchmark private int parallelism = 6; private int extractorCount = 8; - public static bool Verbose = false; - public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; + public static InstallerProgressChangedEventArgs? FileProgressArgs; public static ByteProgress BytesProgressArgs; private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private DummyDownloaderExtractor[] Extractors; - private TPLTaskExecutor Executor; - - private ByteProgress previousEvent; - private object consoleLock = new object(); - private string? bottomMsg; + private TPLGameInstaller Executor; [IterationSetup] public void IterationSetup() @@ -30,33 +26,7 @@ public void IterationSetup() Extractors = new DummyDownloaderExtractor[extractorCount]; for (int i = 0; i < extractorCount; i++) Extractors[i] = new DummyDownloaderExtractor(i.ToString(), 1024, 1024*256); - Executor = new TPLTaskExecutor(parallelism); - Executor.FileProgress += (s, e) => FileProgressArgs = e; - Executor.ByteProgress += (s, e) => BytesProgressArgs = e; - if (Verbose) - { - Executor.FileProgress += (s, e) => - { - lock (consoleLock) - { - Console.SetCursorPosition(0, Console.CursorTop - 1); - ExecutorsBenchmark.PrintProgress(e); - Console.WriteLine(bottomMsg); - } - }; - Executor.ByteProgress += (s, e) => - { - lock (consoleLock) - { - if (previousEvent.ProgressedBytes >= e.ProgressedBytes) - return; - previousEvent = e; - bottomMsg = $"{e.ProgressedBytes} / {e.TotalBytes} "; - Console.SetCursorPosition(0, Console.CursorTop - 1); - Console.WriteLine(bottomMsg); - } - }; - } + Executor = new TPLGameInstaller(parallelism); } [Benchmark] @@ -66,6 +36,9 @@ await Executor.Install( Extractors, MinecraftPath, DummyVersion, + TPLTaskExecutorWithDummyTaskBenchmark.RulesContext, + TPLTaskExecutorWithDummyTaskBenchmark.FileProgress, + TPLTaskExecutorWithDummyTaskBenchmark.ByteProgress, default); } } \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs index 10dd41c..5105c28 100644 --- a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs @@ -2,27 +2,59 @@ using BenchmarkDotNet.Engines; using CmlLib.Core.Executors; using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; +using CmlLib.Core.Rules; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; public class TPLTaskExecutorWithDummyTaskBenchmark { - private int parallelism = 6; - private int extractorCount = 8; - public static bool Verbose = false; - public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; + public static InstallerProgressChangedEventArgs? FileProgressArgs; public static ByteProgress BytesProgressArgs; + private static object consoleLock = new object(); + private static string? bottomMsg; + private static ByteProgress previousEvent; + public static RulesEvaluatorContext RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); + public static IProgress FileProgress = new SyncProgress(e => + { + FileProgressArgs = e; + if (Verbose) + { + lock (consoleLock) + { + Console.SetCursorPosition(0, Console.CursorTop - 1); + ExecutorsBenchmark.PrintProgress(e); + Console.WriteLine(bottomMsg); + } + } + }); + public static IProgress ByteProgress = new SyncProgress(e => + { + BytesProgressArgs = e; + if (Verbose) + { + lock (consoleLock) + { + if (previousEvent.ProgressedBytes >= e.ProgressedBytes) + return; + previousEvent = e; + bottomMsg = $"{e.ProgressedBytes} / {e.TotalBytes} "; + Console.SetCursorPosition(0, Console.CursorTop - 1); + Console.WriteLine(bottomMsg); + } + } + }); + + private int parallelism = 6; + private int extractorCount = 8; + private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private DummyTaskExtractor[] Extractors; - private TPLTaskExecutor Executor; - - private ByteProgress previousEvent; - private object consoleLock = new object(); - private string? bottomMsg; + private TPLGameInstaller Executor; [IterationSetup] public void IterationSetup() @@ -30,42 +62,19 @@ public void IterationSetup() Extractors = new DummyTaskExtractor[extractorCount]; for (int i = 0; i < extractorCount; i++) Extractors[i] = new DummyTaskExtractor(i.ToString(), 1024); - Executor = new TPLTaskExecutor(parallelism); - Executor.FileProgress += (s, e) => FileProgressArgs = e; - Executor.ByteProgress += (s, e) => BytesProgressArgs = e; - if (Verbose) - { - Executor.FileProgress += (s, e) => - { - lock (consoleLock) - { - Console.SetCursorPosition(0, Console.CursorTop - 1); - ExecutorsBenchmark.PrintProgress(e); - Console.WriteLine(bottomMsg); - } - }; - Executor.ByteProgress += (s, e) => - { - lock (consoleLock) - { - if (previousEvent.ProgressedBytes >= e.ProgressedBytes) - return; - previousEvent = e; - bottomMsg = $"{e.ProgressedBytes} / {e.TotalBytes} "; - Console.SetCursorPosition(0, Console.CursorTop - 1); - Console.WriteLine(bottomMsg); - } - }; - } + Executor = new TPLGameInstaller(parallelism); } [Benchmark] public async Task Benchmark() { await Executor.Install( - Extractors, - MinecraftPath, - DummyVersion, + Extractors, + MinecraftPath, + DummyVersion, + RulesContext, + FileProgress, + ByteProgress, default); } } \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs index ac2f5fa..9968cea 100644 --- a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Engines; using CmlLib.Core.Executors; using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; @@ -14,13 +15,13 @@ public class TPLTaskExecutorWithRandomFileBenchmark private int parallelism = 6; private int extractorCount = 4; - public static TaskExecutorProgressChangedEventArgs? FileProgressArgs; + public static InstallerProgressChangedEventArgs? FileProgressArgs; public static ByteProgress BytesProgressArgs; private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private RandomFileExtractor[] Extractors; - private TPLTaskExecutor Executor; + private TPLGameInstaller Executor; [GlobalSetup] public void GlobalSetup() @@ -37,12 +38,7 @@ public void IterationSetup() Extractors[i] = new RandomFileExtractor(path, 1024, 1024*1024/2); Extractors[i].Setup(); } - Executor = new TPLTaskExecutor(parallelism); - Executor.FileProgress += (s, e) => FileProgressArgs = e; - Executor.ByteProgress += (s, e) => BytesProgressArgs = e; - - if (Verbose) - Executor.FileProgress += (s, e) => ExecutorsBenchmark.PrintProgress(e); + Executor = new TPLGameInstaller(parallelism); } [IterationCleanup] @@ -61,6 +57,9 @@ await Executor.Install( Extractors, MinecraftPath, DummyVersion, + TPLTaskExecutorWithDummyTaskBenchmark.RulesContext, + TPLTaskExecutorWithDummyTaskBenchmark.FileProgress, + TPLTaskExecutorWithDummyTaskBenchmark.ByteProgress, default); } } \ No newline at end of file From b87313a922a378d5bdb9370ec5f60dd2bb1e2e96 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Mon, 4 Sep 2023 12:00:14 +0000 Subject: [PATCH 073/137] fix bugs --- .vscode/launch.json | 4 +- .vscode/tasks.json | 6 +- src/FileExtractors/LegacyJavaFileExtractor.cs | 4 +- .../InstallerProgressChangedEventArgs.cs | 2 +- src/Installers/TPLGameInstaller.cs | 64 +++-- src/LauncherParameters.cs | 4 +- src/MinecraftLauncher.cs | 4 +- .../FabricMC/FabricVersionLoader.cs | 4 +- .../LiteLoader/LiteLoaderInstaller.cs | 4 +- .../LiteLoader/LiteLoaderVersionLoader.cs | 6 +- src/ModLoaders/QuiltMC/QuiltVersionLoader.cs | 4 +- src/Rules/LauncherOSRule.cs | 24 +- src/Rules/RulesEvaluator.cs | 55 +++- src/Tasks/DownloadTask.cs | 1 - src/Tasks/LinkedTask.cs | 1 + src/Version/Extensions.cs | 35 ++- src/Version/IVersion.cs | 2 +- src/Version/JsonVersion.cs | 33 +-- ...{VersionJsonModel.cs => JsonVersionDTO.cs} | 2 +- src/Version/JsonVersionParser.cs | 11 +- src/VersionLoader/IVersionLoader.cs | 2 +- ...ionLoader.cs => LocalJsonVersionLoader.cs} | 10 +- ...onLoader.cs => MojangJsonVersionLoader.cs} | 10 +- src/VersionLoader/VersionLoaderCollection.cs | 4 +- ...ection.cs => VersionMetadataCollection.cs} | 53 ++-- ...dataSorter.cs => VersionMetadataSorter.cs} | 5 +- test/CmlLib.Core.Test.csproj | 3 +- test/DummyDownloadTask.cs | 37 --- test/DummyDownloaderExtractor.cs | 40 --- test/DummyTask.cs | 19 -- test/DummyTaskExtractor.cs | 40 --- test/DummyVersion.cs | 42 ---- test/Installers/TPLGameInstallerTest.cs | 191 ++++++++++++++ .../ProcessArgumentBuilderTest.cs | 2 +- test/Program.cs | 14 +- test/Rules/RulesEvaluatorFeatureTest.cs | 119 +++++++++ test/Rules/RulesEvaluatorOSTest.cs | 237 ++++++++++++++++++ test/TPLGameInstallerTest.cs | 29 --- test/Tasks/MockDownloadTask.cs | 26 ++ test/Tasks/MockResultTask.cs | 17 ++ test/Tasks/MockTask.cs | 20 ++ test/Tasks/ResultTaskTest.cs | 82 ++++++ test/Version/MockVersion.cs | 46 ++++ .../VersionMetadataCollectionTest.cs | 200 +++++++++++++++ 44 files changed, 1153 insertions(+), 365 deletions(-) rename src/Version/{VersionJsonModel.cs => JsonVersionDTO.cs} (97%) rename src/VersionLoader/{LocalVersionLoader.cs => LocalJsonVersionLoader.cs} (73%) rename src/VersionLoader/{MojangVersionLoader.cs => MojangJsonVersionLoader.cs} (78%) rename src/VersionMetadata/{MVersionCollection.cs => VersionMetadataCollection.cs} (76%) rename src/VersionMetadata/{MVersionMetadataSorter.cs => VersionMetadataSorter.cs} (97%) delete mode 100644 test/DummyDownloadTask.cs delete mode 100644 test/DummyDownloaderExtractor.cs delete mode 100644 test/DummyTask.cs delete mode 100644 test/DummyTaskExtractor.cs delete mode 100644 test/DummyVersion.cs create mode 100644 test/Installers/TPLGameInstallerTest.cs rename test/{ => ProcessBuilder}/ProcessArgumentBuilderTest.cs (98%) create mode 100644 test/Rules/RulesEvaluatorFeatureTest.cs create mode 100644 test/Rules/RulesEvaluatorOSTest.cs delete mode 100644 test/TPLGameInstallerTest.cs create mode 100644 test/Tasks/MockDownloadTask.cs create mode 100644 test/Tasks/MockResultTask.cs create mode 100644 test/Tasks/MockTask.cs create mode 100644 test/Tasks/ResultTaskTest.cs create mode 100644 test/Version/MockVersion.cs create mode 100644 test/VersionMetadata/VersionMetadataCollectionTest.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index a5f9cb4..db9ed1a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,9 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/examples/console/bin/Debug/net6.0/CmlLibCoreSample.dll", + "program": "${workspaceFolder}/test/bin/Debug/net6.0/CmlLib.Core.Test.dll", "args": [], - "cwd": "${workspaceFolder}/examples/console", + "cwd": "${workspaceFolder}/test", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", "stopAtEntry": false diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 302348e..68e1b7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,7 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/examples/console/CmlLibCoreSample.csproj", + "${workspaceFolder}/test/CmlLib.Core.Test.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -19,7 +19,7 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/examples/console/CmlLibCoreSample.csproj", + "${workspaceFolder}/test/CmlLib.Core.Test.csproj", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -33,7 +33,7 @@ "watch", "run", "--project", - "${workspaceFolder}/examples/console/CmlLibCoreSample.csproj" + "${workspaceFolder}/test/CmlLib.Core.Test.csproj" ], "problemMatcher": "$msCompile" } diff --git a/src/FileExtractors/LegacyJavaFileExtractor.cs b/src/FileExtractors/LegacyJavaFileExtractor.cs index 0ffb51b..4131103 100644 --- a/src/FileExtractors/LegacyJavaFileExtractor.cs +++ b/src/FileExtractors/LegacyJavaFileExtractor.cs @@ -84,8 +84,8 @@ private string parseLauncherMetadata(string json) var root = jsonDocument.RootElement; var javaUrl = root - .GetPropertyOrNull(LauncherOSRule.Current.Name)? - .GetPropertyOrNull(LauncherOSRule.Current.Arch)? + .GetPropertyOrNull(LauncherOSRule.Current.Name ?? string.Empty)? + .GetPropertyOrNull(LauncherOSRule.Current.Arch ?? string.Empty)? .GetPropertyOrNull("jre")? .GetPropertyValue("url"); diff --git a/src/Installers/InstallerProgressChangedEventArgs.cs b/src/Installers/InstallerProgressChangedEventArgs.cs index db7c409..2f6081b 100644 --- a/src/Installers/InstallerProgressChangedEventArgs.cs +++ b/src/Installers/InstallerProgressChangedEventArgs.cs @@ -6,7 +6,7 @@ public InstallerProgressChangedEventArgs(string name, TaskStatus status) => (Name, EventType) = (name, status); public int TotalTasks { get; set; } = 0; - public int ProceedTasks { get; set; } = 0; + public int ProgressedTasks { get; set; } = 0; public TaskStatus EventType { get; } public string Name { get; } } \ No newline at end of file diff --git a/src/Installers/TPLGameInstaller.cs b/src/Installers/TPLGameInstaller.cs index e8d6102..6cfef25 100644 --- a/src/Installers/TPLGameInstaller.cs +++ b/src/Installers/TPLGameInstaller.cs @@ -66,16 +66,14 @@ public TPLGameInstallerExecutor( public event EventHandler? FileProgress; public event EventHandler? ByteProgress; - public GameInstallerExceptionMode ExceptionMode { get; set; } + public GameInstallerExceptionMode ExceptionMode { get; set; } = GameInstallerExceptionMode.ThrowAggreateException; - // 庰 ȭ private ThreadLocal progressStorage = null!; private bool isStarted = false; private CancellationToken CancellationToken; private int totalTasks = 0; private int proceed = 0; private ConcurrentBag exceptions = null!; - private HashSet distinctStorage = null!; public async ValueTask Install( IEnumerable extractors, @@ -88,7 +86,8 @@ public async ValueTask Install( initializeResources(); CancellationToken = cancellationToken; - var extractorBlock = createExtractBlock(cancellationToken); + var distinctStorage = new HashSet(); + var extractorBlock = createExtractBlock(distinctStorage, cancellationToken); var executeBlock = createExecuteBlock(extractorBlock.Completion); extractorBlock.LinkTo(executeBlock!, t => t != null); extractorBlock.LinkTo(DataflowBlock.NullTarget()); @@ -97,19 +96,23 @@ public async ValueTask Install( extractorBlock.Complete(); await extractorBlock.Completion; - if (proceed == totalTasks) - return; + distinctStorage.Clear(); - var executeTask = executeBlock.Completion; - while (!executeTask.IsCompleted) + if (proceed != totalTasks) { - reportByteProgress(); - await Task.Delay(200); + var executeTask = executeBlock.Completion; + while (!executeTask.IsCompleted) + { + reportByteProgress(); + await Task.WhenAny(Task.Delay(200), executeTask); + } } + reportByteProgress(); disposeResources(); - if (!exceptions.IsEmpty) + if (!exceptions.IsEmpty && + ExceptionMode == GameInstallerExceptionMode.ThrowAggreateException) { throw new AggregateException(exceptions); } @@ -119,7 +122,6 @@ private void initializeResources() { progressStorage = new ThreadLocal(() => new ByteProgress(), true); exceptions = new ConcurrentBag(); - distinctStorage = new HashSet(); totalTasks = 0; proceed = 0; } @@ -128,11 +130,8 @@ private void disposeResources() { progressStorage.Dispose(); progressStorage = null!; - distinctStorage.Clear(); - distinctStorage = null!; } - // 庰 ȭ Ͽ private void reportByteProgress() { long totalBytes = 0; @@ -143,7 +142,6 @@ private void reportByteProgress() totalBytes += v.TotalBytes; progressedBytes += v.ProgressedBytes; } - fireByteProgress(totalBytes, progressedBytes); } @@ -170,19 +168,18 @@ private BufferBlock createExecuteBlock(Task extractTask) private async Task execute(LinkedTask task, Task extractTask, IDataflowBlock buffer) { - // task - var lastProgress = new ByteProgress + ByteProgress lastProgress = new ByteProgress { TotalBytes = task.Size, ProgressedBytes = 0 }; + var progress = new SyncProgress(e => { - // ȭ progressStorage.Value = new ByteProgress { - TotalBytes = progressStorage.Value.TotalBytes + e.TotalBytes - lastProgress.TotalBytes, - ProgressedBytes = progressStorage.Value.ProgressedBytes + e.ProgressedBytes - lastProgress.ProgressedBytes + TotalBytes = progressStorage.Value.TotalBytes + (e.TotalBytes - lastProgress.TotalBytes), + ProgressedBytes = progressStorage.Value.ProgressedBytes + (e.ProgressedBytes - lastProgress.ProgressedBytes) }; lastProgress = e; }); @@ -190,7 +187,7 @@ private BufferBlock createExecuteBlock(Task extractTask) LinkedTask? nextTask = null; try { - nextTask = await task.Execute(progress, CancellationToken); + await task.Execute(progress, CancellationToken); } catch (Exception ex) { @@ -202,16 +199,16 @@ private BufferBlock createExecuteBlock(Task extractTask) } } - progress.Report(new ByteProgress // byte ä + progress.Report(new ByteProgress { TotalBytes = lastProgress.TotalBytes, - ProgressedBytes = lastProgress.TotalBytes, + ProgressedBytes = lastProgress.TotalBytes }); if (nextTask == null) { - fireFileProgress(task.Name, TaskStatus.Done); Interlocked.Increment(ref proceed); + fireFileProgress(task.Name, TaskStatus.Done); if (totalTasks == proceed && extractTask.IsCompleted) buffer.Complete(); } @@ -219,7 +216,9 @@ private BufferBlock createExecuteBlock(Task extractTask) return nextTask; } - private IPropagatorBlock createExtractBlock(CancellationToken cancellationToken) + private IPropagatorBlock createExtractBlock( + HashSet distinctStorage, + CancellationToken cancellationToken) { var extractorBlock = new TransformManyBlock(async extractor => { @@ -231,12 +230,10 @@ private IPropagatorBlock createExtractBlock(Cancella { totalTasks++; fireFileProgress(task.Name, TaskStatus.Queued); - - var storedProgress = progressStorage.Value; progressStorage.Value = new ByteProgress { - TotalBytes = storedProgress.TotalBytes + task.File.Size, - ProgressedBytes = 0 + TotalBytes = progressStorage.Value.TotalBytes + task.File.Size, + ProgressedBytes = progressStorage.Value.ProgressedBytes }; return task.First!; @@ -254,16 +251,17 @@ private void fireFileProgress(string name, TaskStatus status) FileProgress?.Invoke(this, new InstallerProgressChangedEventArgs(name, status) { TotalTasks = totalTasks, - ProceedTasks = proceed + ProgressedTasks = proceed }); } private void fireByteProgress(long totalBytes, long progressedBytes) { - ByteProgress?.Invoke(this, new ByteProgress + var progress = new ByteProgress { TotalBytes = totalBytes, ProgressedBytes = progressedBytes - }); + }; + ByteProgress?.Invoke(this, progress); } } \ No newline at end of file diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 0bcbd32..7296ab1 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -21,8 +21,8 @@ public static LauncherParameters CreateDefault(HttpClient httpClient) parameters.RulesEvaluator = new RulesEvaluator(); parameters.VersionLoader = new VersionLoaderCollection { - new LocalVersionLoader(parameters.MinecraftPath), - new MojangVersionLoader(httpClient) + new LocalJsonVersionLoader(parameters.MinecraftPath), + new MojangJsonVersionLoader(httpClient) }; parameters.JavaPathResolver = new MinecraftJavaPathResolver(); parameters.GameInstaller = new TPLGameInstaller(TPLGameInstaller.BestMaxParallelism); diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index f04d9ac..2675bbf 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -27,7 +27,7 @@ public class MinecraftLauncher public IRulesEvaluator RulesEvaluator { get; } public RulesEvaluatorContext RulesContext { get; set; } - public VersionCollection? Versions { get; private set; } + public VersionMetadataCollection? Versions { get; private set; } public MinecraftLauncher(string path) : this(WithMinecraftPath(new MinecraftPath(path))) { @@ -68,7 +68,7 @@ public MinecraftLauncher(LauncherParameters parameters) _byteProgress = new Progress(e => ByteProgressChanged?.Invoke(this, e)); } - public async ValueTask GetAllVersionsAsync() + public async ValueTask GetAllVersionsAsync() { Versions = await VersionLoader.GetVersionMetadatasAsync(); return Versions; diff --git a/src/ModLoaders/FabricMC/FabricVersionLoader.cs b/src/ModLoaders/FabricMC/FabricVersionLoader.cs index a677f2c..af5c0f2 100644 --- a/src/ModLoaders/FabricMC/FabricVersionLoader.cs +++ b/src/ModLoaders/FabricMC/FabricVersionLoader.cs @@ -22,7 +22,7 @@ protected string GetVersionName(string version, string loaderVersion) return $"fabric-loader-{loaderVersion}-{version}"; } - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { if (string.IsNullOrEmpty(LoaderVersion)) { @@ -39,7 +39,7 @@ public async ValueTask GetVersionMetadatasAsync() .ConfigureAwait(false); var versions = parseVersions(res, LoaderVersion!); - return new VersionCollection(versions, null, null); + return new VersionMetadataCollection(versions, null, null); } private IEnumerable parseVersions(string res, string loader) diff --git a/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs b/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs index a7740fb..c2e25d8 100644 --- a/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs @@ -16,7 +16,7 @@ public LiteLoaderInstaller(MinecraftPath path, HttpClient httpClient) } private readonly MinecraftPath minecraftPath; - private VersionCollection? liteLoaderVersions; + private VersionMetadataCollection? liteLoaderVersions; public static string GetVersionName(string loaderVersion, string baseVersion) { @@ -39,7 +39,7 @@ public async Task GetAllLiteLoaderVersions() // vanilla public async Task InstallAsync(string liteLoaderVersion) { - var localVersionLoader = new LocalVersionLoader(minecraftPath); + var localVersionLoader = new LocalJsonVersionLoader(minecraftPath); var localVersions = await localVersionLoader.GetVersionMetadatasAsync() .ConfigureAwait(false); diff --git a/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs index 71e124d..01b0b40 100644 --- a/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs @@ -15,7 +15,7 @@ public class LiteLoaderVersionLoader : IVersionLoader public LiteLoaderVersionLoader(HttpClient httpClient) => _httpClient = httpClient; - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { var res = await _httpClient.GetStringAsync(ManifestServer) .ConfigureAwait(false); @@ -23,7 +23,7 @@ public async ValueTask GetVersionMetadatasAsync() return parseVersions(res); } - private VersionCollection parseVersions(string json) + private VersionMetadataCollection parseVersions(string json) { using var jsonDocument = JsonDocument.Parse(json); var root = jsonDocument.RootElement; @@ -58,6 +58,6 @@ private VersionCollection parseVersions(string json) } } - return new VersionCollection(metadataList, null, null); + return new VersionMetadataCollection(metadataList, null, null); } } \ No newline at end of file diff --git a/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs b/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs index 6dc9ad5..921a66b 100644 --- a/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs +++ b/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs @@ -21,7 +21,7 @@ protected string GetVersionName(string version, string loaderVersion) return $"quilt-loader-{loaderVersion}-{version}"; } - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { if (string.IsNullOrEmpty(LoaderVersion)) { @@ -37,7 +37,7 @@ public async ValueTask GetVersionMetadatasAsync() var res = await _httpClient.GetStringAsync(url); var versions = parseVersions(res, LoaderVersion!); - return new VersionCollection(versions, null, null); + return new VersionMetadataCollection(versions, null, null); } private IEnumerable parseVersions(string res, string loader) diff --git a/src/Rules/LauncherOSRule.cs b/src/Rules/LauncherOSRule.cs index da2e28b..e18e729 100644 --- a/src/Rules/LauncherOSRule.cs +++ b/src/Rules/LauncherOSRule.cs @@ -35,26 +35,20 @@ private static LauncherOSRule createCurrent() return new LauncherOSRule(name, arch); } - [JsonConstructor] + public LauncherOSRule() + { + + } + public LauncherOSRule(string name, string arch) => (Name, Arch) = (name, arch); [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("arch")] - public string Arch { get; set; } + public string? Arch { get; set; } - public bool Match(LauncherOSRule toMatch) - { - return isPropMatch(Name, toMatch.Name) && - isPropMatch(Arch, toMatch.Arch); - } - - private bool isPropMatch(string? rule, string? toMatch) - { - if (string.IsNullOrEmpty(rule)) - return true; - return rule == toMatch; - } + [JsonPropertyName("version")] + public string? Version { get; set; } } \ No newline at end of file diff --git a/src/Rules/RulesEvaluator.cs b/src/Rules/RulesEvaluator.cs index f42e081..9ccfc07 100644 --- a/src/Rules/RulesEvaluator.cs +++ b/src/Rules/RulesEvaluator.cs @@ -1,20 +1,59 @@ +using System.Text.RegularExpressions; + namespace CmlLib.Core.Rules; public class RulesEvaluator : IRulesEvaluator { public bool Match(IEnumerable rules, RulesEvaluatorContext context) { - return rules.Any(rule => match(rule, context)); + var finalResult = false; + foreach (var rule in rules) + { + var isAllow = rule.Action == "allow"; + var isOSMatch = matchOS(rule, context); + var isFeatureMatch = matchFeature(rule, context); + + if (isOSMatch && isFeatureMatch) + finalResult = isAllow; + } + return finalResult; + } + + private bool matchOS(LauncherRule rule, RulesEvaluatorContext context) + { + bool isNameMatched = true; + if (!string.IsNullOrEmpty(rule.OS?.Name)) + isNameMatched = rule.OS?.Name == context.OS?.Name; + + bool isArchMatched = true; + if (!string.IsNullOrEmpty(rule.OS?.Arch)) + isArchMatched = rule.OS?.Arch == context.OS?.Arch; + + bool isVersionMatched = true; + if (string.IsNullOrEmpty(context.OS?.Version)) + isVersionMatched = true; + else if (!string.IsNullOrEmpty(rule.OS?.Version)) + isVersionMatched = Regex.IsMatch( + context.OS.Version, + rule.OS.Version, + RegexOptions.None, + TimeSpan.FromMilliseconds(100)); + + return isNameMatched && isArchMatched && isVersionMatched; } - private bool match(LauncherRule rule, RulesEvaluatorContext context) + private bool matchFeature(LauncherRule rule, RulesEvaluatorContext context) { - var isAllow = rule.Action == "allow"; - var isOSMatched = rule.OS != null && rule.OS.Match(context.OS); + if (rule.Features == null) + return true; + + foreach (var kv in rule.Features) + { + var isFeatured = context.Features?.Contains(kv.Key) ?? false; + if (isFeatured != kv.Value) + return false; + } - if (isAllow) - return isOSMatched; - else - return !isOSMatched; + return true; } } \ No newline at end of file diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs index cf7d04e..1341ed2 100644 --- a/src/Tasks/DownloadTask.cs +++ b/src/Tasks/DownloadTask.cs @@ -43,7 +43,6 @@ protected async virtual ValueTask DownloadFile( { try { - // TODO: handle duplicated file, example: e9833a1512b57bcf88ac4fdcc8df4e5a7e9d701d await HttpClientDownloadHelper.DownloadFileAsync( HttpClient, Url, diff --git a/src/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs index de0b906..c089927 100644 --- a/src/Tasks/LinkedTask.cs +++ b/src/Tasks/LinkedTask.cs @@ -7,6 +7,7 @@ public LinkedTask(TaskFile file) if (string.IsNullOrEmpty(file.Name)) throw new ArgumentException("file.Name was empty"); this.Name = file.Name; + this.Size = file.Size; } public LinkedTask(string name) diff --git a/src/Version/Extensions.cs b/src/Version/Extensions.cs index 00cadde..a939434 100644 --- a/src/Version/Extensions.cs +++ b/src/Version/Extensions.cs @@ -24,18 +24,37 @@ public static MVersionType GetVersionType(this IVersion version) return default; } - public static IEnumerable ConcatInheritedCollection(this IVersion self, Func> prop) + public static IEnumerable ConcatInheritedCollection( + this IVersion self, + Func> prop, + int maxDepth = 10) { - IVersion? version = self; - while (version != null) + foreach (var version in enumerateFromParent(self, maxDepth)) { - var value = prop.Invoke(version); - if (value != null) + foreach (var item in prop(version)) { - foreach (var item in value) - yield return item; + yield return item; } - version = version.ParentVersion; + } + } + + private static IEnumerable enumerateFromParent(IVersion version, int maxDepth) + { + var stack = new Stack(); + + IVersion? v = version; + while (v != null) + { + if (stack.Count >= maxDepth) + throw new Exception(); + + stack.Push(v); + v = v.ParentVersion; + } + + while (stack.Any()) + { + yield return stack.Pop(); } } } \ No newline at end of file diff --git a/src/Version/IVersion.cs b/src/Version/IVersion.cs index 940dc80..add5fe1 100644 --- a/src/Version/IVersion.cs +++ b/src/Version/IVersion.cs @@ -7,7 +7,7 @@ namespace CmlLib.Core.Version; public interface IVersion { string Id { get; } - string JarId { get; } + string? JarId { get; } string? InheritsFrom { get; } IVersion? ParentVersion { get; set; } AssetMetadata? AssetIndex { get; } diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index c092509..02f8887 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -5,19 +5,18 @@ namespace CmlLib.Core.Version; -// JsonElement 의존성 빼고 parse 과정을 전부 JsonVersionParser 으로 이동 - -public class JsonVersion : IVersion +public class JsonVersion : IVersion, IDisposable { private readonly JsonVersionParserOptions _options; - private readonly JsonElement _json; - private readonly VersionJsonModel _model; + private readonly JsonDocument _json; + private readonly JsonVersionDTO _model; - public JsonVersion(JsonElement json, JsonVersionParserOptions options) + public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) { _options = options; - _json = json; - _model = json.Deserialize() ?? throw new ArgumentNullException(); + _json = jsonDocument; + _model = jsonDocument.RootElement.Deserialize() ?? + throw new ArgumentNullException(); Id = _model.Id ?? throw new ArgumentException("Null Id"); } @@ -56,6 +55,7 @@ public JsonVersion(JsonElement json, JsonVersionParserOptions options) public MArgument[] GameArguments => _gameArgs ??= getGameArguments(); private MArgument[]? _jvmArgs = null; + public MArgument[] JvmArguments => _jvmArgs ??= getJvmArguments(); private string? getJarId() @@ -83,7 +83,7 @@ public JsonVersion(JsonElement json, JsonVersionParserOptions options) { try { - return _json + return _json.RootElement .GetProperty("downloads") .GetProperty(_options.Side) .Deserialize(); @@ -100,7 +100,7 @@ private MLibrary[] getLibraries() { try { - var libProp = _json.GetProperty("libraries"); + var libProp = _json.RootElement.GetProperty("libraries"); var libList = new List(); foreach (var libJson in libProp.EnumerateArray()) { @@ -122,7 +122,7 @@ private MLibrary[] getLibraries() { try { - return _json + return _json.RootElement .GetProperty("logging") .GetProperty(_options.Side) .Deserialize(); @@ -139,7 +139,7 @@ private MArgument[] getGameArguments() { try { - var prop = _json + var prop = _json.RootElement .GetProperty("arguments") .GetProperty("game"); return JsonArgumentParser.Parse(prop); @@ -174,7 +174,7 @@ private MArgument[] getJvmArguments() { try { - var prop = _json + var prop = _json.RootElement .GetProperty("arguments") .GetProperty("jvm"); return JsonArgumentParser.Parse(prop); @@ -193,9 +193,14 @@ private MArgument[] getJvmArguments() public string? GetProperty(string key) { - if (_json.TryGetProperty(key, out var prop)) + if (_json.RootElement.TryGetProperty(key, out var prop)) return prop.GetString(); else return null; } + + public void Dispose() + { + _json.Dispose(); + } } \ No newline at end of file diff --git a/src/Version/VersionJsonModel.cs b/src/Version/JsonVersionDTO.cs similarity index 97% rename from src/Version/VersionJsonModel.cs rename to src/Version/JsonVersionDTO.cs index a556d53..c47f6b6 100644 --- a/src/Version/VersionJsonModel.cs +++ b/src/Version/JsonVersionDTO.cs @@ -4,7 +4,7 @@ namespace CmlLib.Core.Version; -public class VersionJsonModel +public class JsonVersionDTO { [JsonPropertyName("inheritsFrom")] public string? InheritsFrom { get; set; } diff --git a/src/Version/JsonVersionParser.cs b/src/Version/JsonVersionParser.cs index 7f0daa3..ae12824 100644 --- a/src/Version/JsonVersionParser.cs +++ b/src/Version/JsonVersionParser.cs @@ -7,14 +7,14 @@ public static class JsonVersionParser public static IVersion ParseFromJsonString(string json, JsonVersionParserOptions options) { var document = JsonDocument.Parse(json); - return ParseFromJson(document.RootElement, options); + return ParseFromJson(document, options); } - public static IVersion ParseFromJson(JsonElement element, JsonVersionParserOptions options) + public static IVersion ParseFromJson(JsonDocument json, JsonVersionParserOptions options) { try { - return parseInternal(element, options); + return new JsonVersion(json, options); } catch (MVersionParseException) { @@ -25,9 +25,4 @@ public static IVersion ParseFromJson(JsonElement element, JsonVersionParserOptio throw new MVersionParseException(ex); } } - - private static IVersion parseInternal(JsonElement root, JsonVersionParserOptions options) - { - return new JsonVersion(root, options); - } } diff --git a/src/VersionLoader/IVersionLoader.cs b/src/VersionLoader/IVersionLoader.cs index 52d7184..430f01e 100644 --- a/src/VersionLoader/IVersionLoader.cs +++ b/src/VersionLoader/IVersionLoader.cs @@ -4,6 +4,6 @@ namespace CmlLib.Core.VersionLoader { public interface IVersionLoader { - ValueTask GetVersionMetadatasAsync(); + ValueTask GetVersionMetadatasAsync(); } } diff --git a/src/VersionLoader/LocalVersionLoader.cs b/src/VersionLoader/LocalJsonVersionLoader.cs similarity index 73% rename from src/VersionLoader/LocalVersionLoader.cs rename to src/VersionLoader/LocalJsonVersionLoader.cs index 890b24b..6743763 100644 --- a/src/VersionLoader/LocalVersionLoader.cs +++ b/src/VersionLoader/LocalJsonVersionLoader.cs @@ -2,20 +2,20 @@ namespace CmlLib.Core.VersionLoader; -public class LocalVersionLoader : IVersionLoader +public class LocalJsonVersionLoader : IVersionLoader { - public LocalVersionLoader(MinecraftPath path) + public LocalJsonVersionLoader(MinecraftPath path) { minecraftPath = path; } private readonly MinecraftPath minecraftPath; - public ValueTask GetVersionMetadatasAsync() + public ValueTask GetVersionMetadatasAsync() { var versions = getFromLocal(minecraftPath); - var collection = new VersionCollection(versions, null, null); - return new ValueTask(collection); + var collection = new VersionMetadataCollection(versions, null, null); + return new ValueTask(collection); } private IEnumerable getFromLocal(MinecraftPath path) diff --git a/src/VersionLoader/MojangVersionLoader.cs b/src/VersionLoader/MojangJsonVersionLoader.cs similarity index 78% rename from src/VersionLoader/MojangVersionLoader.cs rename to src/VersionLoader/MojangJsonVersionLoader.cs index 579340d..926a1ad 100644 --- a/src/VersionLoader/MojangVersionLoader.cs +++ b/src/VersionLoader/MojangJsonVersionLoader.cs @@ -4,18 +4,18 @@ namespace CmlLib.Core.VersionLoader; -public class MojangVersionLoader : IVersionLoader +public class MojangJsonVersionLoader : IVersionLoader { private readonly HttpClient _httpClient; private readonly string _endpoint; - public MojangVersionLoader(HttpClient httpClient) => + public MojangJsonVersionLoader(HttpClient httpClient) => (_httpClient, _endpoint) = (httpClient, MojangServer.Version); - public MojangVersionLoader(HttpClient httpClient, string endpoint) => + public MojangJsonVersionLoader(HttpClient httpClient, string endpoint) => (_httpClient, _endpoint) = (httpClient, endpoint); - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { var res = await _httpClient.GetStreamAsync(_endpoint) .ConfigureAwait(false); @@ -41,6 +41,6 @@ public async ValueTask GetVersionMetadatasAsync() } } - return new VersionCollection(metadatas, latestReleaseId, latestSnapshotId); + return new VersionMetadataCollection(metadatas, latestReleaseId, latestSnapshotId); } } diff --git a/src/VersionLoader/VersionLoaderCollection.cs b/src/VersionLoader/VersionLoaderCollection.cs index d28da32..cc695ed 100644 --- a/src/VersionLoader/VersionLoaderCollection.cs +++ b/src/VersionLoader/VersionLoaderCollection.cs @@ -7,9 +7,9 @@ public class VersionLoaderCollection : IVersionLoader, ICollection _collection = new(); - public async ValueTask GetVersionMetadatasAsync() + public async ValueTask GetVersionMetadatasAsync() { - var versions = new VersionCollection(); + var versions = new VersionMetadataCollection(); foreach (var loader in _collection) { var newVersions = await loader.GetVersionMetadatasAsync(); diff --git a/src/VersionMetadata/MVersionCollection.cs b/src/VersionMetadata/VersionMetadataCollection.cs similarity index 76% rename from src/VersionMetadata/MVersionCollection.cs rename to src/VersionMetadata/VersionMetadataCollection.cs index e613859..a8aea82 100644 --- a/src/VersionMetadata/MVersionCollection.cs +++ b/src/VersionMetadata/VersionMetadataCollection.cs @@ -6,15 +6,15 @@ namespace CmlLib.Core.VersionMetadata; // Collection for IVersionMetadata // return IVersion object from IVersionMetadata -public class VersionCollection : IEnumerable +public class VersionMetadataCollection : IEnumerable { - public VersionCollection() + public VersionMetadataCollection() : this(Enumerable.Empty(), null, null) { } - public VersionCollection( + public VersionMetadataCollection( IEnumerable versions, string? latestRelease, string? latestSnapshot) @@ -35,13 +35,14 @@ public VersionCollection( // Use OrderedDictionary to keep version order protected OrderedDictionary Versions; + public int MaxDepth { get; set; } = 10; public string? LatestReleaseName { get; private set; } public string? LatestSnapshotName { get; private set; } - + public IVersionMetadata this[int index] => (IVersionMetadata)Versions[index]!; public IVersionMetadata GetVersionMetadata(string name) - { + { if (TryGetVersionMetadata(name, out var version)) return version; else @@ -52,7 +53,7 @@ public bool TryGetVersionMetadata(string name, out IVersionMetadata version) { if (name == null) throw new ArgumentNullException(nameof(name)); - + var metadata = Versions[name] as IVersionMetadata; version = metadata!; return metadata != null; @@ -60,7 +61,7 @@ public bool TryGetVersionMetadata(string name, out IVersionMetadata version) public IVersionMetadata[] ToArray(MVersionSortOption option) { - var sorter = new MVersionMetadataSorter(option); + var sorter = new VersionMetadataSorter(option); return sorter.Sort(this); } @@ -83,12 +84,12 @@ public Task GetAndSaveVersionAsync(string name, MinecraftPath path) } public Task GetVersionAsync(IVersionMetadata versionMetadata) => - getVersionInternal(versionMetadata, null); + getVersionInternal(versionMetadata, null, new List()); public Task GetAndSaveVersionAsync(IVersionMetadata versionMetadata, MinecraftPath path) => - getVersionInternal(versionMetadata, path); + getVersionInternal(versionMetadata, path, new List()); - private async Task getVersionInternal(IVersionMetadata versionMetadata, MinecraftPath? path) + private async Task getVersionInternal(IVersionMetadata versionMetadata, MinecraftPath? path, List visited) { if (versionMetadata == null) throw new ArgumentNullException(nameof(versionMetadata)); @@ -99,22 +100,26 @@ private async Task getVersionInternal(IVersionMetadata versionMetadata else version = await versionMetadata.GetAndSaveVersionAsync(path); - await inheritIfRequired(version, path); + await inheritIfRequired(version, path, visited); return version; } - private async Task inheritIfRequired(IVersion version, MinecraftPath? path) + private async Task inheritIfRequired(IVersion version, MinecraftPath? path, List visited) { - if (!string.IsNullOrEmpty(version.InheritsFrom)) - { - if (version.InheritsFrom == version.Id) // prevent StackOverFlowException - throw new InvalidDataException( - "Invalid version json file : inheritFrom property is equal to id property."); - - var baseVersionMetadata = GetVersionMetadata(version.InheritsFrom); - var baseVersion = await getVersionInternal(baseVersionMetadata, path); - version.ParentVersion = baseVersion; - } + if (string.IsNullOrEmpty(version.InheritsFrom)) + return; + if (version.InheritsFrom == version.Id) + return; + + if (visited.Contains(version.InheritsFrom)) + throw new InvalidDataException(); // declare new exception + visited.Add(version.Id); + if (visited.Count >= MaxDepth) + throw new InvalidDataException(); // declare new exception + + var baseVersionMetadata = GetVersionMetadata(version.InheritsFrom); + var baseVersion = await getVersionInternal(baseVersionMetadata, path, visited); + version.ParentVersion = baseVersion; } public void AddVersion(IVersionMetadata version) @@ -125,7 +130,7 @@ public void AddVersion(IVersionMetadata version) public bool Contains(string? versionName) => !string.IsNullOrEmpty(versionName) && Versions.Contains(versionName); - public void Merge(VersionCollection from) + public void Merge(VersionMetadataCollection from) { foreach (var item in from) { @@ -146,7 +151,7 @@ public IEnumerator GetEnumerator() foreach (DictionaryEntry? item in Versions) { var entry = item.Value; - + var version = (IVersionMetadata)entry.Value!; yield return version; } diff --git a/src/VersionMetadata/MVersionMetadataSorter.cs b/src/VersionMetadata/VersionMetadataSorter.cs similarity index 97% rename from src/VersionMetadata/MVersionMetadataSorter.cs rename to src/VersionMetadata/VersionMetadataSorter.cs index e60416c..699038e 100644 --- a/src/VersionMetadata/MVersionMetadataSorter.cs +++ b/src/VersionMetadata/VersionMetadataSorter.cs @@ -2,9 +2,10 @@ // Sort IVersionMetadata // TODO: measure performance and optimizing -public class MVersionMetadataSorter +// TODO: refactoring as IComparer +public class VersionMetadataSorter { - public MVersionMetadataSorter(MVersionSortOption option) + public VersionMetadataSorter(MVersionSortOption option) { this.option = option; diff --git a/test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj index d732829..4560b2e 100644 --- a/test/CmlLib.Core.Test.csproj +++ b/test/CmlLib.Core.Test.csproj @@ -5,7 +5,7 @@ enable enable false - false + true @@ -21,6 +21,7 @@ + diff --git a/test/DummyDownloadTask.cs b/test/DummyDownloadTask.cs deleted file mode 100644 index ed773cd..0000000 --- a/test/DummyDownloadTask.cs +++ /dev/null @@ -1,37 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Test; - -public class DummyDownloadTask : DownloadTask -{ - public static int Seed = 0; - - public DummyDownloadTask(TaskFile file, HttpClient httpClient) : base(file, httpClient) - { - } - - protected override ValueTask DownloadFile( - IProgress? progress, - CancellationToken cancellationToken) - { - for (int i = 0; i < Size; i += 1) - { - if (Size % 128 == 0) - { - progress?.Report(new ByteProgress - { - TotalBytes = Size, - ProgressedBytes = i - }); - } - Seed += i; - //await Task.Delay(1); - } - progress?.Report(new ByteProgress - { - TotalBytes = Size, - ProgressedBytes = Size - }); - return new ValueTask(); - } -} \ No newline at end of file diff --git a/test/DummyDownloaderExtractor.cs b/test/DummyDownloaderExtractor.cs deleted file mode 100644 index 0d97416..0000000 --- a/test/DummyDownloaderExtractor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Test; - -public class DummyDownloaderExtractor : IFileExtractor -{ - private readonly int _count; - private readonly string _prefix; - private readonly long _size; - - public DummyDownloaderExtractor(string prefix, int count, long size) => - (_prefix, _count, _size) = (prefix, count, size); - - public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) - { - var result = extract(); - return new ValueTask>(result); - } - - private IEnumerable extract() - { - for (int i = 0; i < _count; i++) - { - var file = new TaskFile(_prefix + "-" + i.ToString()) - { - Size = _size, - Path = "a.dat", - Url = "a.dat" - }; - var task = LinkedTask.LinkTasks( - new DummyTask(file, i), - new DummyDownloadTask(file, null!) - ); - yield return new LinkedTaskHead(task, file); - } - } -} \ No newline at end of file diff --git a/test/DummyTask.cs b/test/DummyTask.cs deleted file mode 100644 index 05b627d..0000000 --- a/test/DummyTask.cs +++ /dev/null @@ -1,19 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Test; - -public class DummyTask : LinkedTask -{ - public static int Seed { get; set; } - - public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; - - protected override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - for (int j = 0; j < 1024; j++) - Seed += j; - return new ValueTask(NextTask); - } -} \ No newline at end of file diff --git a/test/DummyTaskExtractor.cs b/test/DummyTaskExtractor.cs deleted file mode 100644 index 3a0bf52..0000000 --- a/test/DummyTaskExtractor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Test; - -public class DummyTaskExtractor : IFileExtractor -{ - private readonly int _count; - private readonly string _prefix; - public DummyTaskExtractor(string prefix, int count) => - (_prefix, _count) = (prefix, count); - - public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) - { - var r = extract(); - return new ValueTask>(r); - } - - private IEnumerable extract() - { - for (int i = 0; i < _count; i++) - { - var file = new TaskFile(_prefix + "-" + i.ToString()) - { - Size = 1024 * 2, - Path = "a.dat", - Url = "a.dat" - }; - var task = LinkedTask.LinkTasks( - new DummyTask(file, i), - new DummyTask(file, i), - new DummyTask(file, i) - //new DummyDownloadTask(file, null!) - ); - yield return new LinkedTaskHead(task, file); - } - } -} \ No newline at end of file diff --git a/test/DummyVersion.cs b/test/DummyVersion.cs deleted file mode 100644 index aa6db61..0000000 --- a/test/DummyVersion.cs +++ /dev/null @@ -1,42 +0,0 @@ -using CmlLib.Core.Files; -using CmlLib.Core.Java; -using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Test; - -public class DummyVersion : IVersion -{ - public string Id => throw new NotImplementedException(); - - public string? InheritsFrom => throw new NotImplementedException(); - - public IVersion? ParentVersion { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public AssetMetadata? AssetIndex => throw new NotImplementedException(); - - public MFileMetadata? Client => throw new NotImplementedException(); - - public JavaVersion? JavaVersion => throw new NotImplementedException(); - - public MLibrary[] Libraries => throw new NotImplementedException(); - - public string? Jar => throw new NotImplementedException(); - - public MLogFileMetadata? Logging => throw new NotImplementedException(); - - public string? MainClass => throw new NotImplementedException(); - - public MArgument[] GameArguments => throw new NotImplementedException(); - - public MArgument[] JvmArguments => throw new NotImplementedException(); - - public DateTime ReleaseTime => throw new NotImplementedException(); - - public string? Type => throw new NotImplementedException(); - - public string? GetProperty(string key) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/test/Installers/TPLGameInstallerTest.cs b/test/Installers/TPLGameInstallerTest.cs new file mode 100644 index 0000000..bbdae19 --- /dev/null +++ b/test/Installers/TPLGameInstallerTest.cs @@ -0,0 +1,191 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Installers; +using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.Test.Tasks; +using CmlLib.Core.Version; +using Moq; +using NUnit.Framework; + +namespace CmlLib.Core.Test.Installers; + +public class TPLGameInstallerTest +{ + private const int TaskCount = 128; + private const int ExtractorCount = 4; + private const long TaskSize = 128 * 32; + private readonly RulesEvaluatorContext TestRulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); + private readonly MinecraftPath TestMinecraftPath = new Mock().Object; + private readonly IVersion TestVersion = new Mock().Object; + + [Test] + public async Task TestAllTaskExecuted() + { + var mocks = createMockTasks(); + var extractors = createMockExtractors(mocks); + + var installer = createInstaller(); + await installer.Install( + extractors, + TestMinecraftPath, + TestVersion, + TestRulesContext, + null, + null, + default); + + assertAllMockTasks(mocks); + } + + [Test] + public async Task TestFileProgressReachesTo100() + { + var mocks = createMockTasks(); + var extractors = createMockExtractors(mocks); + + int totalTasks = 0; + int progressedTasks = 0; + var fileProgress = new SyncProgress(e => + { + totalTasks = Math.Max(totalTasks, e.TotalTasks); + progressedTasks = Math.Max(progressedTasks, e.ProgressedTasks); + }); + + var installer = createInstaller(); + await installer.Install( + extractors, + TestMinecraftPath, + TestVersion, + TestRulesContext, + fileProgress, + null, + default + ); + + assertAllMockTasks(mocks); + Assert.That(totalTasks, Is.EqualTo(totalTasks), "TotalTasks"); + Assert.That(progressedTasks, Is.EqualTo(totalTasks), "ProgressedTasks"); + } + + [Test] + public async Task TestByteProgressReachesTo100() + { + var mocks = createMockDownloadTasks(); + var extractors = createMockExtractors(mocks); + + long totalBytes = 0; + long progressedBytes = 0; + var byteProgress = new SyncProgress(e => + { + totalBytes = Math.Max(totalBytes, e.TotalBytes); + progressedBytes = Math.Max(progressedBytes, e.ProgressedBytes); + }); + + var installer = createInstaller(); + await installer.Install( + extractors, + TestMinecraftPath, + TestVersion, + TestRulesContext, + null, + byteProgress, + default + ); + + assertAllMockTasks(mocks); + Assert.That(totalBytes, Is.EqualTo(TaskCount * TaskSize), "TotalBytes"); + Assert.That(progressedBytes, Is.EqualTo(totalBytes), + "ProgressedBytes, d: " + (totalBytes - progressedBytes) / (double)TaskSize); + } + + [Test] + public async Task TestFilterDuplicatedFile() + { + var divide = 2; + var mockTasks = Enumerable.Range(0, TaskCount) + .Select(i => + { + var setId = i / (TaskCount / divide); + return new MockTask(new TaskFile($"{i} {setId}") + { + Path = setId.ToString() + }); + }) + .ToArray(); + + var extractors = createMockExtractors(mockTasks); + + var installer = createInstaller(); + await installer.Install( + extractors, + TestMinecraftPath, + TestVersion, + TestRulesContext, + null, + null, + default + ); + + var set = new HashSet(); + foreach (var task in mockTasks) + { + var setId = task.Name.Split()[1]; + if (task.IsExecuted && !set.Add(setId)) + Assert.Fail("duplicated tasks are executed: " + setId); + } + + Assert.That(set.Count, Is.EqualTo(divide), "not all tasks are executed"); + } + + [Test] + public void TestExceptionMode() + { + + } + + private MockTask[] createMockTasks() => + Enumerable.Range(0, TaskCount) + .Select(i => new MockTask(new TaskFile(i.ToString()))) + .ToArray(); + + private MockDownloadTask[] createMockDownloadTasks() => + Enumerable.Range(0, TaskCount) + .Select(i => new MockDownloadTask(new TaskFile(i.ToString()) + { + Size = TaskSize, + })) + .ToArray(); + + private IEnumerable createMockExtractors(IEnumerable tasks) => tasks + .Select(task => new LinkedTaskHead(task, task.File)) + .Chunk(TaskCount / ExtractorCount) + .Select(chunkedTasks => + { + var mockExtractor = new Mock(); + mockExtractor.Setup(e => e.Extract( + It.IsAny(), + It.IsAny(), + It.IsAny(), + default).Result) + .Returns(chunkedTasks); + return mockExtractor.Object; + }); + + private void assertAllMockTasks(IEnumerable mocks) + { + var list = new List(); + foreach (var mock in mocks) + { + if (!mock.IsExecuted) + list.Add(mock.Name); + } + + if (list.Any()) + Assert.Fail("Find tasks not executed: " + string.Join(',', list)); + } + + public IGameInstaller createInstaller() + { + return new TPLGameInstaller(1); + } +} \ No newline at end of file diff --git a/test/ProcessArgumentBuilderTest.cs b/test/ProcessBuilder/ProcessArgumentBuilderTest.cs similarity index 98% rename from test/ProcessArgumentBuilderTest.cs rename to test/ProcessBuilder/ProcessArgumentBuilderTest.cs index 5ff6cce..bd0ea0e 100644 --- a/test/ProcessArgumentBuilderTest.cs +++ b/test/ProcessBuilder/ProcessArgumentBuilderTest.cs @@ -2,7 +2,7 @@ using CmlLib.Core.Rules; using CmlLib.Core.ProcessBuilder; -namespace CmlLib.Core.Test; +namespace CmlLib.Core.Test.ProcessBuilder; [TestFixture] public class ProcessArgumentBuilderTest diff --git a/test/Program.cs b/test/Program.cs index 50c9fe0..7f72323 100644 --- a/test/Program.cs +++ b/test/Program.cs @@ -1,14 +1,14 @@ #if TestSdk -namespace CmlLib.Core.Test; +using CmlLib.Core.Test.Installers; -internal class Program +var tester = new TPLGameInstallerTest(); +int count = 0; +while (true) { - public static async Task Main() - { - var tester = new TPLGameInstallerTest(); - await tester.test(); - } + Console.WriteLine("try " + count); + await tester.TestByteProgressReachesTo100(); + count++; } #endif \ No newline at end of file diff --git a/test/Rules/RulesEvaluatorFeatureTest.cs b/test/Rules/RulesEvaluatorFeatureTest.cs new file mode 100644 index 0000000..2c4605a --- /dev/null +++ b/test/Rules/RulesEvaluatorFeatureTest.cs @@ -0,0 +1,119 @@ +using CmlLib.Core.Rules; +using NUnit.Framework; + +namespace CmlLib.Core.Test.Rules; + +public class RulesEvaluatorFeatureTest +{ + private LauncherOSRule TestOS = new LauncherOSRule + { + Name = "linux", + Arch = "x86" + }; + + [Test] + public void TestAllowEmptyFeature() + { + testFeatures(true, new string[] {}, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary() + } + }); + } + + [Test] + [TestCase(true, "feature1", "feature1", true)] + [TestCase(false, "feature1", "a", true)] + [TestCase(false, "a", "feature1", true)] + [TestCase(false, "feature1", "feature1", false)] + [TestCase(true, "feature1", "a", false)] + [TestCase(true, "a", "feature1", false)] + public void TestOneFeature( + bool expected, + string inputFeature, + string ruleFeature, + bool ruleValue) + { + testFeatures(expected, new string[] { inputFeature }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + [ruleFeature] = ruleValue + } + } + }); + } + + [Test] + [TestCase(true, "a", "b", "a", "b", true, true)] + [TestCase(false, "a", "b", "a", "x", true, true)] + [TestCase(false, "a", "x", "a", "b", true, true)] + [TestCase(false, "a", "b", "a", "b", true, false)] + [TestCase(true, "x", "b", "a", "b", false, true)] + [TestCase(true, "a", "b", "x", "y", false, false)] + public void TestTwoMatchFeature( + bool expected, + string inputFeature1, string inputFeature2, + string ruleFeature1, string ruleFeature2, + bool ruleValue1, bool ruleValue2) + { + testFeatures(expected, new string[] { inputFeature1, inputFeature2 }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + [ruleFeature1] = ruleValue1, + [ruleFeature2] = ruleValue2 + } + } + }); + } + + [Test] + public void TestDisallowMismatchOS() + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(new LauncherOSRule + { + Name = "windows", Arch = "x86" + }); + context.Features = new string[] { "feature1" }; + var result = evaluator.Match(new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux", Arch = "x86" + }, + Features = new Dictionary + { + ["feature1"] = true, + ["feature2"] = false + } + } + }, context); + Assert.False(result); + } + + private void testFeatures(bool expected, string[] features, IEnumerable rules) + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(TestOS); + context.Features = features; + var result = evaluator.Match(rules, context); + Assert.That(result, Is.EqualTo(expected)); + } +} \ No newline at end of file diff --git a/test/Rules/RulesEvaluatorOSTest.cs b/test/Rules/RulesEvaluatorOSTest.cs new file mode 100644 index 0000000..093bada --- /dev/null +++ b/test/Rules/RulesEvaluatorOSTest.cs @@ -0,0 +1,237 @@ +using CmlLib.Core.Rules; +using NUnit.Framework; + +namespace CmlLib.Core.Test.Rules; + +public class RulesEvaluatorOSTest +{ + [Test] + [TestCase("windows", "x86", true)] + [TestCase("windows", "x64", true)] + [TestCase("linux", "x86", true)] + [TestCase("linux", "x64", true)] + [TestCase("osx", "x86", true)] + [TestCase("osx", "x64", true)] + public void TestAllow(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "allow" + } + }); + } + + [Test] + [TestCase("windows", "x86", false)] + [TestCase("windows", "x64", false)] + [TestCase("linux", "x86", false)] + [TestCase("linux", "x64", false)] + [TestCase("osx", "x86", false)] + [TestCase("osx", "x64", false)] + public void TestDisallow(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "disallow" + } + }); + } + + [Test] + [TestCase("windows", "x86", false)] + [TestCase("windows", "x64", false)] + [TestCase("linux", "x86", true)] + [TestCase("linux", "x64", true)] + [TestCase("osx", "x86", false)] + [TestCase("osx", "x64", false)] + public void TestAllowOSName(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux" + } + } + }); + } + + [Test] + [TestCase("windows", "x86", true)] + [TestCase("windows", "x64", false)] + [TestCase("linux", "x86", true)] + [TestCase("linux", "x64", false)] + [TestCase("osx", "x86", true)] + [TestCase("osx", "x64", false)] + public void TestAllowOSArch(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Arch = "x86" + } + } + }); + } + + + [Test] + [TestCase("windows", "10.0", true)] + [TestCase("windows", "7", false)] + [TestCase("linux", "10.0", false)] + public void TestAllowOSVersion(string osname, string version, bool expected) + { + var os = new LauncherOSRule + { + Name = osname, + Arch = "x86", + Version = version + }; + testOSRule(os, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Version = "^10\\." + } + } + }); + } + + [Test] + [TestCase("windows", "x86", true)] + [TestCase("windows", "x64", true)] + [TestCase("linux", "x86", true)] + [TestCase("linux", "x64", true)] + [TestCase("osx", "x86", false)] + [TestCase("osx", "x64", false)] + public void TestAllowCompositedOSName(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "allow" + }, + new LauncherRule + { + Action = "disallow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + }); + } + + [Test] + [TestCase("windows", "x86", false)] + [TestCase("windows", "x64", false)] + [TestCase("linux", "x86", false)] + [TestCase("linux", "x64", false)] + [TestCase("osx", "x86", true)] + [TestCase("osx", "x64", true)] + public void TestDisallowCompositedOSName(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "disallow" + }, + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + }); + } + + [Test] + [TestCase("windows", "x86", false)] + [TestCase("windows", "x64", true)] + [TestCase("linux", "x86", true)] + [TestCase("linux", "x64", false)] + [TestCase("osx", "x86", false)] + [TestCase("osx", "x64", false)] + public void TestCompositedAllows(string osname, string arch, bool expected) + { + testOSRule(osname, arch, expected, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Arch = "x64" + } + }, + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux", + Arch = "x86" + } + } + }); + } + + [Test] + public void TestDisallowMismatchFeatures() + { + var os = new LauncherOSRule + { + Name = "linux", Arch = "x86" + }; + var context = new RulesEvaluatorContext(os); + context.Features = new string[] { "feature1" }; + var evaluator = new RulesEvaluator(); + var result = evaluator.Match(new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = os, + Features = new Dictionary + { + ["feature1"] = false + } + } + }, context); + Assert.False(result); + } + + private void testOSRule(string osname, string arch, bool expected, IEnumerable rules) + { + var os = new LauncherOSRule(osname, arch); + testOSRule(os, expected, rules); + } + + private void testOSRule(LauncherOSRule os, bool expected, IEnumerable rules) + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(os); + var result = evaluator.Match(rules, context); + Assert.That(result, Is.EqualTo(expected)); + } +} \ No newline at end of file diff --git a/test/TPLGameInstallerTest.cs b/test/TPLGameInstallerTest.cs deleted file mode 100644 index 3b7135c..0000000 --- a/test/TPLGameInstallerTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Installers; -using CmlLib.Core.Rules; - -namespace CmlLib.Core.Test; - -public class TPLGameInstallerTest -{ - public async Task test() - { - var path = new MinecraftPath(); - var version = new DummyVersion(); - var rulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - var fileProgress = new Progress(e => - { - //Console.WriteLine($"[{e.ProceedTasks}/{e.TotalTasks}][{e.EventType}] {e.Name}"); - }); - var byteProgress = new Progress(e => - { - Console.WriteLine($"{e.ProgressedBytes} / {e.TotalBytes}"); - }); - var installer = new TPLGameInstaller(1); - var extractors = new IFileExtractor[] - { - new DummyDownloaderExtractor("a", 1024, 1024 * 512) - }; - await installer.Install(extractors, path, version, rulesContext, fileProgress, byteProgress, default); - } -} \ No newline at end of file diff --git a/test/Tasks/MockDownloadTask.cs b/test/Tasks/MockDownloadTask.cs new file mode 100644 index 0000000..f046159 --- /dev/null +++ b/test/Tasks/MockDownloadTask.cs @@ -0,0 +1,26 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Test.Tasks; + +public class MockDownloadTask : MockTask +{ + public MockDownloadTask(TaskFile file) : base(file) + { + TotalEventSize = file.Size; + } + + public long TotalEventSize { get; set; } + + protected override ValueTask OnExecuted(IProgress? progress, CancellationToken cancellationToken) + { + for (int i = 0; i <= TotalEventSize; i += 128) + { + progress?.Report(new ByteProgress + { + TotalBytes = Size, + ProgressedBytes = i + }); + } + return base.OnExecuted(progress, cancellationToken); + } +} \ No newline at end of file diff --git a/test/Tasks/MockResultTask.cs b/test/Tasks/MockResultTask.cs new file mode 100644 index 0000000..a12fb0d --- /dev/null +++ b/test/Tasks/MockResultTask.cs @@ -0,0 +1,17 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Test.Tasks; + +public class MockResultTask : ResultTask +{ + public bool Result { get; set; } + + public MockResultTask(string name) : base(name) + { + } + + protected override ValueTask OnExecutedWithResult(IProgress? progress, CancellationToken cancellationToken) + { + return new ValueTask(Result); + } +} \ No newline at end of file diff --git a/test/Tasks/MockTask.cs b/test/Tasks/MockTask.cs new file mode 100644 index 0000000..7b190f8 --- /dev/null +++ b/test/Tasks/MockTask.cs @@ -0,0 +1,20 @@ +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Test.Tasks; + +public class MockTask : LinkedTask +{ + public bool IsExecuted { get; private set; } + public TaskFile File { get; } + + public MockTask(TaskFile file) : base(file) + { + File = file; + } + + protected override ValueTask OnExecuted(IProgress? progress, CancellationToken cancellationToken) + { + IsExecuted = true; + return new ValueTask(NextTask); + } +} \ No newline at end of file diff --git a/test/Tasks/ResultTaskTest.cs b/test/Tasks/ResultTaskTest.cs new file mode 100644 index 0000000..ee0d24c --- /dev/null +++ b/test/Tasks/ResultTaskTest.cs @@ -0,0 +1,82 @@ +using CmlLib.Core.Tasks; +using NUnit.Framework; + +namespace CmlLib.Core.Test.Tasks; + +public class ResultTaskTest +{ + public void TestTrue() + { + var (task, onTrue, onFalse, next) = createTasks(); + + task.Result = true; + task.OnTrue = onTrue; + task.OnFalse = onFalse; + task.InsertNextTask(next); + + Assert.True(onTrue.IsExecuted); + Assert.False(onFalse.IsExecuted); + Assert.True(next.IsExecuted); + } + + public void TestFalse() + { + var (task, onTrue, onFalse, next) = createTasks(); + + task.Result = false; + task.OnTrue = onTrue; + task.OnFalse = onFalse; + task.InsertNextTask(next); + + Assert.False(onTrue.IsExecuted); + Assert.True(onFalse.IsExecuted); + Assert.True(next.IsExecuted); + } + + public void TestNullTrue() + { + var (task, onTrue, onFalse, next) = createTasks(); + + task.Result = true; + task.OnTrue = null; + task.OnFalse = onFalse; + task.InsertNextTask(next); + + Assert.False(onFalse.IsExecuted); + Assert.True(next.IsExecuted); + } + + public void TestNullFalse() + { + var (task, onTrue, onFalse, next) = createTasks(); + + task.Result = false; + task.OnTrue = onTrue; + task.OnFalse = null; + task.InsertNextTask(next); + + Assert.False(onTrue.IsExecuted); + Assert.True(next.IsExecuted); + } + + public void TestNull() + { + var (task, onTrue, onFalse, next) = createTasks(); + task.Result = true; + task.OnTrue = null; + task.OnFalse = null; + task.InsertNextTask(next); + + Assert.True(next.IsExecuted); + } + + private (MockResultTask, MockTask, MockTask, MockTask) createTasks() + { + var task = new MockResultTask("task"); + var onTrue = new MockTask(new TaskFile("OnTrue")); + var onFalse = new MockTask(new TaskFile("OnFalse")); + var next = new MockTask(new TaskFile("Next")); + + return (task, onTrue, onFalse, next); + } +} \ No newline at end of file diff --git a/test/Version/MockVersion.cs b/test/Version/MockVersion.cs new file mode 100644 index 0000000..c0bcbe3 --- /dev/null +++ b/test/Version/MockVersion.cs @@ -0,0 +1,46 @@ +using CmlLib.Core.Files; +using CmlLib.Core.Java; +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test.Version; + +public class MockVersion : IVersion +{ + public MockVersion(string id) => Id = id; + + public string Id { get; set; } + + public string? InheritsFrom { get; set; } + + public IVersion? ParentVersion { get; set; } + + public AssetMetadata? AssetIndex { get; set; } + + public MFileMetadata? Client { get; set; } + + public JavaVersion? JavaVersion { get; set; } + + public MLibrary[] Libraries { get; set; } = new MLibrary[0]; + + public string? Jar { get; set; } + + public MLogFileMetadata? Logging { get; set; } + + public string? MainClass { get; set; } + + public MArgument[] GameArguments { get; set; } = new MArgument[0]; + + public MArgument[] JvmArguments { get; set; } = new MArgument[0]; + + public DateTime ReleaseTime { get; set; } + + public string? Type { get; set; } + + public string? JarId { get; set; } + + public string? GetProperty(string key) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/test/VersionMetadata/VersionMetadataCollectionTest.cs b/test/VersionMetadata/VersionMetadataCollectionTest.cs new file mode 100644 index 0000000..f761db6 --- /dev/null +++ b/test/VersionMetadata/VersionMetadataCollectionTest.cs @@ -0,0 +1,200 @@ +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Test.Version; +using CmlLib.Core.Version; +using CmlLib.Core.VersionMetadata; +using Moq; +using NUnit.Framework; + +namespace CmlLib.Core.Test.VersionMetadata; + +public class VersionMetadataCollectionTest +{ + [Test] + public void TestGetVersionMetadata() + { + var (collection, parent, child) = createMocks(); + + var result = collection.GetVersionMetadata(parent.Id); + Assert.That(result.Name, Is.EqualTo("parent")); + } + + [Test] + public void TestGetVersionMetadataException() + { + var (collection, parent, child) = createMocks(); + + Assert.Throws(() => + { + collection.GetVersionMetadata("1234"); + }); + } + + [Test] + public void TestEnumerationOrder() + { + var (collection, parent, child) = createMocks(); + + var names = collection.Select(v => v.Name); + Assert.That(names, Is.EqualTo(new string[] { "parent", "child" })); + } + + [Test] + public async Task TestInheritance() + { + var (collection, parent, child) = createMocks(); + + var result = await collection.GetVersionAsync("child"); + Assert.That(result, Is.EqualTo(child)); + Assert.That(result.ParentVersion, Is.EqualTo(parent)); + Assert.That(result.GetInheritedProperty(v => v.AssetIndex)?.Id, Is.EqualTo("child_assetindex")); + Assert.That(result.GetInheritedProperty(v => v.MainClass), Is.EqualTo("parent_mainclass")); + + var concatArguments = result.ConcatInheritedCollection(v => v.GameArguments) + .SelectMany(args => args.Values ?? Enumerable.Empty()) + .ToArray(); + Assert.That(concatArguments, Is.EqualTo(new string[] { "parent_arg", "child_arg" })); + } + + [Test] + public void TestCircularInheritanceException() + { + var (collection, parent, child) = createMocks(); + parent.InheritsFrom = child.Id; + + Assert.ThrowsAsync(async () => + { + await collection.GetVersionAsync("parent"); + }); + } + + [Test] + public async Task TestSelfInheritance() + { + var (collection, parent, child) = createMocks(); + child.InheritsFrom = child.Id; + + var actual = await collection.GetVersionAsync("child"); + Assert.That(actual.InheritsFrom, Is.EqualTo(child.Id)); + Assert.Null(actual.ParentVersion); + } + + [Test] + public void TestNonExistInheritanceException() + { + var (collection, parent, child) = createMocks(); + child.InheritsFrom = "NON_EXISTENCE_VERSION_ID"; + + Assert.ThrowsAsync(async () => + { + await collection.GetVersionAsync("child"); + }); + } + + [Test] + public void TestInheritanceOverflow() + { + var v1 = new MockVersion("v1"); + var v2 = new MockVersion("v2"); + var v3 = new MockVersion("v3"); + var v4 = new MockVersion("v4"); + + v4.InheritsFrom = v3.Id; + v3.InheritsFrom = v2.Id; + v2.InheritsFrom = v1.Id; + + var collection = new VersionMetadataCollection(new IVersionMetadata[] + { + createMockVersionMetadata(v1), + createMockVersionMetadata(v2), + createMockVersionMetadata(v3), + createMockVersionMetadata(v4) + }, null, null); + + collection.MaxDepth = 3; + + Assert.ThrowsAsync(async () => + { + await collection.GetVersionAsync("v4"); + }); + } + + [Test] + public void TestMerge() + { + var v1 = createMockVersionMetadata(new MockVersion("v1")); + var v2 = createMockVersionMetadata(new MockVersion("v2")); + var v3 = createMockVersionMetadata(new MockVersion("v3")); + var v4 = createMockVersionMetadata(new MockVersion("v4")); + + var collection1 = new VersionMetadataCollection(new IVersionMetadata[] + { + v1, v2, v3 + }, "v1", null); + + var collection2 = new VersionMetadataCollection(new IVersionMetadata[] + { + v2, v3, v4 + }, "v2", "v4"); + + collection1.Merge(collection2); + + Assert.That( + collection1.Select(v => v.Name), Is.EqualTo( + new string[] { "v1", "v2", "v3", "v4" }), + "Wrong order"); + + Assert.That(collection1.LatestReleaseName, Is.EqualTo("v1"), "Wrong LatestReleaseName"); + Assert.That(collection1.LatestSnapshotName, Is.EqualTo("v4"), "Wrong LatestSnapshotName"); + } + + private (VersionMetadataCollection, MockVersion, MockVersion) createMocks() + { + var parent = new MockVersion("parent"); + parent.MainClass = "parent_mainclass"; + parent.AssetIndex = new Files.AssetMetadata + { + Id = "parent_assetindex" + }; + parent.GameArguments = new MArgument[] + { + new MArgument("parent_arg") + }; + + var child = new MockVersion("child"); + child.AssetIndex = new Files.AssetMetadata + { + Id = "child_assetindex" + }; + child.GameArguments = new MArgument[] + { + new MArgument("child_arg") + }; + + child.InheritsFrom = parent.Id; + + var collection = new VersionMetadataCollection( + new IVersionMetadata[] + { + createMockVersionMetadata(parent), + createMockVersionMetadata(child) + }, + null, + null + ); + + return (collection, parent, child); + } + + private IVersionMetadata createMockVersionMetadata(IVersion returnValue) + { + var mock = new Mock(); + + mock.Setup(v => v.Name) + .Returns(returnValue.Id); + + mock.Setup(v => v.GetVersionAsync().Result) + .Returns(returnValue); + + return mock.Object; + } +} \ No newline at end of file From 41b20fba993ada1be62ef97ac179fa6cabd5e02d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 14 Oct 2023 14:34:16 +0900 Subject: [PATCH 074/137] fix compiler errors of sub projects --- benchmark/DummyVersion.cs | 2 + benchmark/ExecutorsBenchmark.cs | 3 +- examples/console/Program.cs | 8 +- examples/winform/ChangeLog.cs | 10 ++- examples/winform/GameLog.cs | 2 +- examples/winform/GameOptions.cs | 14 ++-- examples/winform/JavaForm.cs | 9 +-- examples/winform/MainForm.cs | 99 ++++++++++++----------- examples/winform/PathForm.cs | 6 +- examples/winform/VersionSortOptionForm.cs | 16 ++-- src/LauncherParameters.cs | 14 ++-- src/MinecraftLauncher.cs | 6 +- 12 files changed, 102 insertions(+), 87 deletions(-) diff --git a/benchmark/DummyVersion.cs b/benchmark/DummyVersion.cs index 9598931..bed30bd 100644 --- a/benchmark/DummyVersion.cs +++ b/benchmark/DummyVersion.cs @@ -35,6 +35,8 @@ public class DummyVersion : IVersion public string? Type => throw new NotImplementedException(); + public string? JarId => throw new NotImplementedException(); + public string? GetProperty(string key) { throw new NotImplementedException(); diff --git a/benchmark/ExecutorsBenchmark.cs b/benchmark/ExecutorsBenchmark.cs index 3677e2e..7304be7 100644 --- a/benchmark/ExecutorsBenchmark.cs +++ b/benchmark/ExecutorsBenchmark.cs @@ -7,6 +7,7 @@ namespace CmlLib.Core.Benchmarks; [MemoryDiagnoser(false)] public class ExecutorsBenchmark { + #nullable disable ConcurrentDictionaryBenchmark _concurrent; ConcurrentQueueBenchmark _queue; LockBenchmark _lock; @@ -77,6 +78,6 @@ public static void PrintProgress(InstallerProgressChangedEventArgs e) //if (status != TaskStatus.Done) return; //if (proceed % 100 != 0) return; var now = DateTime.Now.ToString("hh:mm:ss.fff"); - Console.WriteLine($"[{now}][{e.ProceedTasks}/{e.TotalTasks}][{e.EventType}] {e.Name}"); + Console.WriteLine($"[{now}][{e.ProgressedTasks}/{e.TotalTasks}][{e.EventType}] {e.Name}"); } } \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index ea7262c..df454eb 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -21,7 +21,7 @@ private async Task Start() var sw = new Stopwatch(); // initialize launcher - var parameters = LauncherParameters.CreateDefault(); + var parameters = MinecraftLauncherParameters.CreateDefault(); //parameters.GameInstaller = new TPLGameInstaller(1); //parameters.VersionLoader = new VersionLoaderCollection //{ @@ -88,14 +88,14 @@ private void Launcher_FileProgressChanged(object? sender, InstallerProgressChang { lock (consoleLock) { - if (previousProceed > e.ProceedTasks) + if (previousProceed > e.ProgressedTasks) return; - var msg = $"[{e.ProceedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"; + var msg = $"[{e.ProgressedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"; Console.WriteLine(msg.PadRight(lastCursorLeft)); printBottomProgress(); - previousProceed = e.ProceedTasks; + previousProceed = e.ProgressedTasks; } } diff --git a/examples/winform/ChangeLog.cs b/examples/winform/ChangeLog.cs index 9887f85..56b25f2 100644 --- a/examples/winform/ChangeLog.cs +++ b/examples/winform/ChangeLog.cs @@ -1,4 +1,4 @@ -using CmlLib.Utils; +using CmlLib.Core.Utils; namespace CmlLibWinFormSample { @@ -9,7 +9,7 @@ public ChangeLog() InitializeComponent(); } - private Changelogs changelogs; + private Changelogs? changelogs; private async void ChangeLog_Load(object sender, EventArgs e) { @@ -21,6 +21,12 @@ private async void ChangeLog_Load(object sender, EventArgs e) private async void btnLoad_Click(object sender, EventArgs e) { + if (changelogs == null) + { + MessageBox.Show("Changelogs was not loaded yet"); + return; + } + var version = listBox1.SelectedItem?.ToString(); if (string.IsNullOrEmpty(version)) return; diff --git a/examples/winform/GameLog.cs b/examples/winform/GameLog.cs index 6602c6b..8f75691 100644 --- a/examples/winform/GameLog.cs +++ b/examples/winform/GameLog.cs @@ -33,7 +33,7 @@ private void timer1_Tick(object sender, EventArgs e) return; var sb = new StringBuilder(); - while (logQueue.TryDequeue(out string msg)) + while (logQueue.TryDequeue(out var msg)) { sb.AppendLine(msg); } diff --git a/examples/winform/GameOptions.cs b/examples/winform/GameOptions.cs index 214bfff..f6a5e7c 100644 --- a/examples/winform/GameOptions.cs +++ b/examples/winform/GameOptions.cs @@ -1,6 +1,4 @@ -using CmlLib.Utils; -using System; -using System.Windows.Forms; +using CmlLib.Core.Utils; namespace CmlLibWinFormSample { @@ -14,7 +12,7 @@ public GameOptions(string path) InitializeComponent(); } - GameOptionsFile optionFile; + GameOptionsFile? optionFile; private void GameOptions_Load(object sender, EventArgs e) { @@ -22,7 +20,7 @@ private void GameOptions_Load(object sender, EventArgs e) btnLoad_Click(null, null); } - private void btnLoad_Click(object sender, EventArgs e) + private void btnLoad_Click(object? sender, EventArgs? e) { this.Path = txtPath.Text; @@ -62,6 +60,12 @@ private void btnEdit_Click(object sender, EventArgs e) private void btnSave_Click(object sender, EventArgs e) { + if (optionFile == null) + { + MessageBox.Show("Load the option file first"); + return; + } + foreach (ListViewItem item in listView1.Items) { optionFile.SetRawValue(item.Text, item.SubItems[1].Text); diff --git a/examples/winform/JavaForm.cs b/examples/winform/JavaForm.cs index a679375..e641da4 100644 --- a/examples/winform/JavaForm.cs +++ b/examples/winform/JavaForm.cs @@ -1,13 +1,10 @@ -using System; -using System.Windows.Forms; - -namespace CmlLibWinFormSample +namespace CmlLibWinFormSample { public partial class JavaForm : Form { - public string JavaBinaryPath { get; set; } + public string? JavaBinaryPath { get; set; } - public JavaForm(string javaPath) + public JavaForm(string? javaPath) { this.JavaBinaryPath = javaPath; InitializeComponent(); diff --git a/examples/winform/MainForm.cs b/examples/winform/MainForm.cs index 4bc38dc..86d9791 100644 --- a/examples/winform/MainForm.cs +++ b/examples/winform/MainForm.cs @@ -3,14 +3,14 @@ using System.ComponentModel; using System.Diagnostics; using System.Reflection; -using CmlLib.Core.Downloader; -using CmlLib.Core.FileChecker; using CmlLib.Core.VersionMetadata; +using CmlLib.Core.Installers; namespace CmlLibWinFormSample { public partial class MainForm : Form { + private readonly MSession session; private readonly HttpClient _httpClient = new(); public MainForm(MSession session) @@ -19,47 +19,50 @@ public MainForm(MSession session) InitializeComponent(); } - CMLauncher launcher; - readonly MSession session; - MinecraftPath gamePath; - string javaPath; + MinecraftLauncher? launcher; + string? javaPath; private async void MainForm_Shown(object sender, EventArgs e) { lbLibraryVersion.Text = "CmlLib.Core " + getLibraryVersion(); // Initialize launcher - var defaultPath = new MinecraftPath(MinecraftPath.GetOSDefaultPath()); - await initializeLauncher(defaultPath); + await initializeLauncher(new MinecraftPath()); } private async Task initializeLauncher(MinecraftPath path) { lbUsername.Text = session.Username; txtPath.Text = path.BasePath; - this.gamePath = path; - launcher = new CMLauncher(path, _httpClient); - launcher.FileChanged += Launcher_FileChanged; - launcher.ProgressChanged += Launcher_ProgressChanged; - await refreshVersions(null); + var parameters = MinecraftLauncherParameters.CreateDefault(); + parameters.MinecraftPath = path; + parameters.HttpClient = _httpClient; + + launcher = new MinecraftLauncher(parameters); + await refreshVersions(); } private async void btnRefreshVersion_Click(object sender, EventArgs e) { - await refreshVersions(null); + await refreshVersions(); } - private async Task refreshVersions(string showVersion) + private async Task refreshVersions(string? showVersion=null) { - cbVersion.Items.Clear(); + if (launcher == null) + { + MessageBox.Show("Initialize the launcher first"); + return; + } + cbVersion.Items.Clear(); var versions = await launcher.GetAllVersionsAsync(); bool showVersionExist = false; foreach (var item in versions) { - if (showVersion != null && item.Name == showVersion) + if (item.Name == showVersion) showVersionExist = true; cbVersion.Items.Add(item.Name); } @@ -70,27 +73,35 @@ private async Task refreshVersions(string showVersion) cbVersion.Text = showVersion; } - private void btnSetLastVersion_Click(object sender, EventArgs e) + private void btnSetLastVersion_Click(object? sender, EventArgs? e) { - cbVersion.Text = launcher.Versions?.LatestReleaseName; + cbVersion.Text = launcher?.Versions?.LatestReleaseName; } private void btnSortFilter_Click(object sender, EventArgs e) { + if (launcher == null) + { + MessageBox.Show("Initialize the launcher first"); + return; + } var form = new VersionSortOptionForm(launcher, new MVersionSortOption()); form.ShowDialog(); - } // Start Game private async void Btn_Launch_Click(object sender, EventArgs e) { + if (launcher == null) + { + MessageBox.Show("Initialize the launcher first"); + return; + } if (session == null) { MessageBox.Show("Login First"); return; } - if (cbVersion.Text == "") { MessageBox.Show("Select Version"); @@ -103,7 +114,7 @@ private async void Btn_Launch_Click(object sender, EventArgs e) try { // create LaunchOption - var launchOption = new MLaunchOption() + var launchOption = new CmlLib.Core.ProcessBuilder.MLaunchOption() { MaximumRamMb = int.Parse(TxtXmx.Text), Session = this.session, @@ -138,14 +149,6 @@ private async void Btn_Launch_Click(object sender, EventArgs e) if (!string.IsNullOrEmpty(Txt_JavaArgs.Text)) launchOption.JVMArguments = Txt_JavaArgs.Text.Split(' '); - if (rbParallelDownload.Checked) - { - System.Net.ServicePointManager.DefaultConnectionLimit = 256; - launcher.FileDownloader = new AsyncParallelDownloader(_httpClient); - } - else - launcher.FileDownloader = new SequenceDownloader(_httpClient); - //if (cbSkipAssetsDownload.Checked) // launcher.GameFileCheckers.AssetFileChecker = null; //else if (launcher.GameFileCheckers.AssetFileChecker == null) @@ -171,15 +174,6 @@ private async void Btn_Launch_Click(object sender, EventArgs e) { MessageBox.Show("Failed to create MLaunchOption\n\n" + fex); } - catch (MDownloadFileException mex) // download exception - { - MessageBox.Show( - $"FileName : {mex.ExceptionFile.Name}\n" + - $"FilePath : {mex.ExceptionFile.Path}\n" + - $"FileUrl : {mex.ExceptionFile.Url}\n" + - $"FileType : {mex.ExceptionFile.Type}\n\n" + - mex.ToString()); - } catch (Win32Exception wex) // java exception { MessageBox.Show(wex + "\n\nIt seems your java setting has problem"); @@ -208,21 +202,26 @@ private void Launcher_ProgressChanged(object sender, ProgressChangedEventArgs e) Pb_Progress.Value = e.ProgressPercentage; } - private void Launcher_FileChanged(DownloadFileChangedEventArgs e) + private void Launcher_FileChanged(InstallerProgressChangedEventArgs e) { if (Thread.CurrentThread.ManagedThreadId != uiThreadId) { Debug.WriteLine(e); } - Pb_File.Maximum = e.TotalFileCount; - Pb_File.Value = e.ProgressedFileCount; - Lv_Status.Text = $"{e.FileKind} : {e.FileName} ({e.ProgressedFileCount}/{e.TotalFileCount})"; + Pb_File.Maximum = e.TotalTasks; + Pb_File.Value = e.ProgressedTasks; + Lv_Status.Text = $"[{e.EventType}][{e.ProgressedTasks}/{e.TotalTasks}] {e.Name}"; //Debug.WriteLine(Lv_Status.Text); } private async void btnChangePath_Click(object sender, EventArgs e) { - var form = new PathForm(this.gamePath); + if (launcher == null) + { + MessageBox.Show("Initialize the launcher first"); + return; + } + var form = new PathForm(launcher.MinecraftPath); form.ShowDialog(); await initializeLauncher(form.MinecraftPath); } @@ -295,8 +294,12 @@ private void btnChangelog_Click(object sender, EventArgs e) private void btnOptions_Click(object sender, EventArgs e) { - // options.txt - var path = System.IO.Path.Combine(gamePath.BasePath, "options.txt"); + if (launcher == null) + { + MessageBox.Show("Initialize the launcher first"); + return; + } + var path = Path.Combine(launcher.MinecraftPath.BasePath, "options.txt"); var f = new GameOptions(path); f.Show(); } @@ -323,11 +326,11 @@ private void start(string url) } } - private string getLibraryVersion() + private string? getLibraryVersion() { try { - return Assembly.GetAssembly(typeof(CMLauncher)).GetName().Version.ToString(); + return Assembly.GetAssembly(typeof(MinecraftLauncher))?.GetName().Version?.ToString(); } catch (Exception ex) { diff --git a/examples/winform/PathForm.cs b/examples/winform/PathForm.cs index 3a37c79..65a8246 100644 --- a/examples/winform/PathForm.cs +++ b/examples/winform/PathForm.cs @@ -1,7 +1,4 @@ using CmlLib.Core; -using System; -using System.IO; -using System.Windows.Forms; namespace CmlLibWinFormSample { @@ -11,6 +8,7 @@ public partial class PathForm : Form public PathForm() { + this.MinecraftPath = new MinecraftPath(); InitializeComponent(); } @@ -28,7 +26,7 @@ private void InitializingForm_Load(object sender, EventArgs e) apply(MinecraftPath); } - private void btnSetDefault_Click(object sender, EventArgs e) + private void btnSetDefault_Click(object? sender, EventArgs? e) { var defaultPath = MinecraftPath.GetOSDefaultPath(); apply(new MinecraftPath(defaultPath)); diff --git a/examples/winform/VersionSortOptionForm.cs b/examples/winform/VersionSortOptionForm.cs index b2acfc5..a386155 100644 --- a/examples/winform/VersionSortOptionForm.cs +++ b/examples/winform/VersionSortOptionForm.cs @@ -5,15 +5,15 @@ namespace CmlLibWinFormSample { public partial class VersionSortOptionForm : Form { - public VersionSortOptionForm(CMLauncher launcher, MVersionSortOption sortOption) + public VersionSortOptionForm(MinecraftLauncher launcher, MVersionSortOption sortOption) { this.sortOption = sortOption; this.launcher = launcher; InitializeComponent(); } - private readonly CMLauncher launcher; - private MVersionCollection versions; + private readonly MinecraftLauncher launcher; + private VersionMetadataCollection? versions; public MVersionSortOption sortOption { get; private set; } private async void VersionSortOptionForm_Load(object sender, EventArgs e) @@ -32,7 +32,7 @@ private async void VersionSortOptionForm_Load(object sender, EventArgs e) versions = await launcher.GetAllVersionsAsync(); } - private MVersionSortOption buildSortOption() + private MVersionSortOption? buildSortOption() { var typeList = new List(); foreach (var item in txtTypeOrder.Text.Split('\n')) @@ -89,7 +89,7 @@ private MVersionSortOption buildSortOption() private void btnPreview_Click(object sender, EventArgs e) { var option = buildSortOption(); - if (option == null) + if (option == null || versions == null) return; listPreview.Items.Clear(); @@ -102,7 +102,11 @@ private void btnPreview_Click(object sender, EventArgs e) private void btnClose_Click(object sender, EventArgs e) { - sortOption = buildSortOption(); + var option = buildSortOption(); + if (option == null) + return; + + sortOption = option; this.Close(); } diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 7296ab1..85d8e95 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -8,14 +8,14 @@ namespace CmlLib.Core; -public class LauncherParameters +public class MinecraftLauncherParameters { - public static LauncherParameters CreateDefault() => + public static MinecraftLauncherParameters CreateDefault() => CreateDefault(HttpUtil.SharedHttpClient.Value); - public static LauncherParameters CreateDefault(HttpClient httpClient) + public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) { - var parameters = new LauncherParameters(); + var parameters = new MinecraftLauncherParameters(); parameters.HttpClient = httpClient; parameters.MinecraftPath = new MinecraftPath(); parameters.RulesEvaluator = new RulesEvaluator(); @@ -35,12 +35,12 @@ public static LauncherParameters CreateDefault(HttpClient httpClient) return parameters; } - public static LauncherParameters CreateEmpty() + public static MinecraftLauncherParameters CreateEmpty() { - return new LauncherParameters(); + return new MinecraftLauncherParameters(); } - private LauncherParameters() + private MinecraftLauncherParameters() { } diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 2675bbf..fbce467 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -39,14 +39,14 @@ public MinecraftLauncher(MinecraftPath path) : this(WithMinecraftPath(path)) } - private static LauncherParameters WithMinecraftPath(MinecraftPath path) + private static MinecraftLauncherParameters WithMinecraftPath(MinecraftPath path) { - var parameters = LauncherParameters.CreateDefault(); + var parameters = MinecraftLauncherParameters.CreateDefault(); parameters.MinecraftPath = path; return parameters; } - public MinecraftLauncher(LauncherParameters parameters) + public MinecraftLauncher(MinecraftLauncherParameters parameters) { MinecraftPath = parameters.MinecraftPath ?? throw new ArgumentException(nameof(parameters.MinecraftPath) + " was null"); From 0bee74d9cf5f495a96c3bd486229aa204213c861 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 8 Feb 2024 01:39:35 +0900 Subject: [PATCH 075/137] feat: add LinkedTaskBuilder and TaskFactory --- benchmark/DummyDownloaderExtractor.cs | 2 +- benchmark/DummyTaskExtractor.cs | 2 +- benchmark/Executors/ExecutorBenchmarkBase.cs | 3 +- benchmark/RandomFileExtractor.cs | 2 +- ...askExecutorWithDummyDownloaderBenchmark.cs | 3 + .../TPLTaskExecutorWithDummyTaskBenchmark.cs | 3 + .../TPLTaskExecutorWithRandomFileBenchmark.cs | 3 + src/FileExtractors/AssetFileExtractor.cs | 146 ++++++----- src/FileExtractors/ClientFileExtractor.cs | 18 +- src/FileExtractors/DefaultFileExtractors.cs | 7 +- src/FileExtractors/IFileExtractor.cs | 3 +- src/FileExtractors/JavaFileExtractor.cs | 227 ++++++------------ src/FileExtractors/LegacyJavaFileExtractor.cs | 55 +++-- src/FileExtractors/LibraryFileExtractor.cs | 38 +-- src/FileExtractors/LogFileExtractor.cs | 21 +- src/Files/AssetIndex.cs | 68 ++++++ src/Files/MinecraftJavaFile.cs | 13 + src/Files/MinecraftJavaManifestMetadata.cs | 18 ++ src/Installers/IGameInstaller.cs | 2 + src/Installers/TPLGameInstaller.cs | 11 +- src/Java/IJavaPathResolver.cs | 8 +- src/Java/MinecraftJavaManifestResolver.cs | 149 ++++++++++++ src/Java/MinecraftJavaPathResolver.cs | 29 ++- src/LauncherParameters.cs | 5 +- src/MinecraftLauncher.cs | 8 +- src/Rules/IRulesEvaluator.cs | 1 - src/Rules/LauncherOSRule.cs | 6 +- src/Tasks/ITaskFactory.cs | 9 + src/Tasks/LinkedTaskBuilder.cs | 105 ++++++++ src/Tasks/TaskFactory.cs | 36 +++ test/Installers/TPLGameInstallerTest.cs | 6 + 31 files changed, 660 insertions(+), 347 deletions(-) create mode 100644 src/Files/AssetIndex.cs create mode 100644 src/Files/MinecraftJavaFile.cs create mode 100644 src/Files/MinecraftJavaManifestMetadata.cs create mode 100644 src/Java/MinecraftJavaManifestResolver.cs create mode 100644 src/Tasks/ITaskFactory.cs create mode 100644 src/Tasks/LinkedTaskBuilder.cs create mode 100644 src/Tasks/TaskFactory.cs diff --git a/benchmark/DummyDownloaderExtractor.cs b/benchmark/DummyDownloaderExtractor.cs index 6b5b932..11e17e7 100644 --- a/benchmark/DummyDownloaderExtractor.cs +++ b/benchmark/DummyDownloaderExtractor.cs @@ -14,7 +14,7 @@ public class DummyDownloaderExtractor : IFileExtractor public DummyDownloaderExtractor(string prefix, int count, long size) => (_prefix, _count, _size) = (prefix, count, size); - public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) + public ValueTask> Extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var r = extract(); return new ValueTask>(r); diff --git a/benchmark/DummyTaskExtractor.cs b/benchmark/DummyTaskExtractor.cs index d337e84..545782c 100644 --- a/benchmark/DummyTaskExtractor.cs +++ b/benchmark/DummyTaskExtractor.cs @@ -12,7 +12,7 @@ public class DummyTaskExtractor : IFileExtractor public DummyTaskExtractor(string prefix, int count) => (_prefix, _count) = (prefix, count); - public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) + public ValueTask> Extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var r = extract(); return new ValueTask>(r); diff --git a/benchmark/Executors/ExecutorBenchmarkBase.cs b/benchmark/Executors/ExecutorBenchmarkBase.cs index c6c77ac..ce19b76 100644 --- a/benchmark/Executors/ExecutorBenchmarkBase.cs +++ b/benchmark/Executors/ExecutorBenchmarkBase.cs @@ -9,6 +9,7 @@ public abstract class ExecutorBenchmarkBase { public static ByteProgress LastEvent; + public ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); public RulesEvaluatorContext RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); public int MaxParallelism { get; set; } = 6; public string Name { get; set; } = "D"; @@ -28,7 +29,7 @@ public async Task IterationSetup() var minecraftPath = new MinecraftPath(); var version = new DummyVersion(); var taskExtractor = new DummyDownloaderExtractor(Name, Count, Size); - var result = await taskExtractor.Extract(minecraftPath, version, RulesContext, default); + var result = await taskExtractor.Extract(TaskFactory, minecraftPath, version, RulesContext, default); var list = new List(); foreach (var head in result) diff --git a/benchmark/RandomFileExtractor.cs b/benchmark/RandomFileExtractor.cs index 2b33688..729f8ec 100644 --- a/benchmark/RandomFileExtractor.cs +++ b/benchmark/RandomFileExtractor.cs @@ -51,7 +51,7 @@ public void Cleanup() Directory.Delete(_path); } - public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) + public ValueTask> Extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var result = extract(); return new ValueTask>(result); diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs index 6e4f856..d70fe8c 100644 --- a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs @@ -3,6 +3,7 @@ using CmlLib.Core.Executors; using CmlLib.Core.FileExtractors; using CmlLib.Core.Installers; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; @@ -15,6 +16,7 @@ public class TPLTaskExecutorWithDummyDownloaderBenchmark public static InstallerProgressChangedEventArgs? FileProgressArgs; public static ByteProgress BytesProgressArgs; + private ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private DummyDownloaderExtractor[] Extractors; @@ -33,6 +35,7 @@ public void IterationSetup() public async Task Benchmark() { await Executor.Install( + TaskFactory, Extractors, MinecraftPath, DummyVersion, diff --git a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs index 5105c28..cb03e1b 100644 --- a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs @@ -4,6 +4,7 @@ using CmlLib.Core.FileExtractors; using CmlLib.Core.Installers; using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; @@ -51,6 +52,7 @@ public class TPLTaskExecutorWithDummyTaskBenchmark private int parallelism = 6; private int extractorCount = 8; + private ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private DummyTaskExtractor[] Extractors; @@ -69,6 +71,7 @@ public void IterationSetup() public async Task Benchmark() { await Executor.Install( + TaskFactory, Extractors, MinecraftPath, DummyVersion, diff --git a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs index 9968cea..1c1e1ce 100644 --- a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs +++ b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs @@ -3,6 +3,7 @@ using CmlLib.Core.Executors; using CmlLib.Core.FileExtractors; using CmlLib.Core.Installers; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; @@ -18,6 +19,7 @@ public class TPLTaskExecutorWithRandomFileBenchmark public static InstallerProgressChangedEventArgs? FileProgressArgs; public static ByteProgress BytesProgressArgs; + private ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); private MinecraftPath MinecraftPath = new MinecraftPath(); private IVersion DummyVersion = new DummyVersion(); private RandomFileExtractor[] Extractors; @@ -54,6 +56,7 @@ public void IterationCleanup() public async Task Benchmark() { await Executor.Install( + TaskFactory, Extractors, MinecraftPath, DummyVersion, diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index 1fe5d4c..d5cbe1f 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -1,10 +1,10 @@ -using System.Diagnostics; -using System.Text.Json; -using CmlLib.Core.Files; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; +using CmlLib.Core.Files; using CmlLib.Core.Internals; using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; +using CmlLib.Core.Version; +using System.Diagnostics; +using System.Text.Json; namespace CmlLib.Core.FileExtractors; @@ -17,50 +17,55 @@ public AssetFileExtractor(HttpClient client) this.httpClient = client; } - private string assetServer = MojangServer.ResourceDownload; - public string AssetServer - { - get => assetServer; - set - { - if (value.Last() == '/') - assetServer = value; - else - assetServer = value + "/"; - } - } + public string AssetServer { get; set; } = MojangServer.ResourceDownload; public async ValueTask> Extract( - MinecraftPath path, + ITaskFactory taskFactory, + MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var assets = version.AssetIndex; - if (assets == null || - string.IsNullOrEmpty(assets.Id) || - string.IsNullOrEmpty(assets.Url)) + var assetIndex = await loadAssetIndex(path, version.AssetIndex); + if (assetIndex == null) return Enumerable.Empty(); + return TaskExtractor.ExtractTasksFromAssetIndex( + taskFactory, + assetIndex, + path, + AssetServer, + dispose: true); + } + + private async ValueTask loadAssetIndex(MinecraftPath path, MFileMetadata? assets) + { + if (assets == null || + string.IsNullOrEmpty(assets?.Id)) + return null; + using var assetIndexStream = await createAssetIndexStream(path, assets); - var assetIndexJson = JsonDocument.Parse(assetIndexStream); - return extractFromAssetIndexJson(assetIndexJson, path, version); + if (assetIndexStream == null) + return null; + + var assetIndexJson = await JsonDocument.ParseAsync(assetIndexStream); + return new JsonAssetIndex(assets.Id, assetIndexJson); } - // Check index file validation and download - private async ValueTask createAssetIndexStream(MinecraftPath path, MFileMetadata assets) + private async ValueTask createAssetIndexStream(MinecraftPath path, MFileMetadata assets) { - Debug.Assert(!string.IsNullOrEmpty(assets?.Id)); - Debug.Assert(!string.IsNullOrEmpty(assets?.Url)); + Debug.Assert(!string.IsNullOrEmpty(assets.Id)); var indexFilePath = path.GetIndexFilePath(assets.Id); - if (IOUtil.CheckFileValidation(indexFilePath, assets.Sha1)) { return File.Open(indexFilePath, FileMode.Open); } else { + if (string.IsNullOrEmpty(assets.Url)) + return null; + IOUtil.CreateParentDirectory(indexFilePath); var ms = new MemoryStream(); @@ -80,59 +85,44 @@ private async ValueTask createAssetIndexStream(MinecraftPath path, MFile } } - private IEnumerable extractFromAssetIndexJson( - JsonDocument _json, MinecraftPath path, IVersion version) + public static class TaskExtractor { - Debug.Assert(!string.IsNullOrEmpty(version.AssetIndex?.Id)); - - using var json = _json; - var root = json.RootElement; - - var assetId = version.AssetIndex.Id; - var isVirtual = root.GetPropertyOrNull("virtual")?.GetBoolean() ?? false; - var mapResource = root.GetPropertyOrNull("map_to_resources")?.GetBoolean() ?? false; - - if (!root.TryGetProperty("objects", out var objectsProp)) - yield break; - - var objects = objectsProp.EnumerateObject(); - foreach (var prop in objects) + public static IEnumerable ExtractTasksFromAssetIndex( + ITaskFactory taskFactory, IAssetIndex assetIndex, MinecraftPath path, string assetServer, bool dispose) { - var hash = prop.Value.GetPropertyValue("hash"); - if (hash == null) - continue; - - var hashName = hash.Substring(0, 2) + "/" + hash; - var hashPath = Path.Combine(path.GetAssetObjectPath(assetId), hashName); - long size = prop.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; + if (assetServer.Last() != '/') + assetServer += '/'; - var copyPath = new List(2); - if (isVirtual) - copyPath.Add(Path.Combine(path.GetAssetLegacyPath(assetId), prop.Name)); - if (mapResource) - copyPath.Add(Path.Combine(path.Resource, prop.Name)); - - var file = new TaskFile(prop.Name) + foreach (var assetObject in assetIndex.EnumerateAssetObjects()) { - Path = hashPath, - Hash = hash, - Size = size, - Url = AssetServer + hashName - }; - - var checkTask = new FileCheckTask(file); - var downloadTask = new DownloadTask(file, httpClient); - var progressTask = ProgressTask.CreateDoneTask(file); - var copyTask = new FileCopyTask(prop.Name, hashPath, copyPath.ToArray()); - - checkTask.OnTrue = progressTask; - checkTask.OnFalse = downloadTask; - - if (copyPath.Any()) - downloadTask.InsertNextTask(copyTask); - - var taskHead = new LinkedTaskHead(checkTask, file); - yield return taskHead; + var hashName = assetObject.Hash.Substring(0, 2) + "/" + assetObject.Hash; + var hashPath = Path.Combine(path.GetAssetObjectPath(assetIndex.Id), hashName); + + var copyPath = new List(2); + if (assetIndex.IsVirtual) + copyPath.Add(Path.Combine(path.GetAssetLegacyPath(assetIndex.Id), assetObject.Name)); + if (assetIndex.MapToResources) + copyPath.Add(Path.Combine(path.Resource, assetObject.Name)); + + var file = new TaskFile(assetObject.Name) + { + Path = hashPath, + Hash = assetObject.Hash, + Size = assetObject.Size, + Url = assetServer + hashName + }; + + yield return LinkedTaskBuilder.Create(file, taskFactory) + .CheckFile( + onSuccess => onSuccess.ReportDone(), + onFail => onFail + .Download() + .ThenIf(copyPath.Any()).Then(new FileCopyTask(assetObject.Name, hashPath, copyPath.ToArray()))) + .BuildHead(); + } + + if (dispose && assetIndex is IDisposable disposableAssetIndex) + disposableAssetIndex.Dispose(); } } } diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 74906a2..42fc211 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -6,24 +6,18 @@ namespace CmlLib.Core.FileExtractors; public class ClientFileExtractor : IFileExtractor { - private readonly HttpClient _httpClient; - - public ClientFileExtractor(HttpClient httpClient) - { - _httpClient = httpClient; - } - public ValueTask> Extract( + ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var result = extract(path, version); + var result = extract(taskFactory, path, version); return new ValueTask>(result); } - private IEnumerable extract(MinecraftPath path, IVersion version) + private IEnumerable extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version) { var id = version.JarId; var url = version.Client?.Url; @@ -40,10 +34,6 @@ private IEnumerable extract(MinecraftPath path, IVersion version Size = version.Client?.Size ?? 0 }; - var checkTask = new FileCheckTask(file); - checkTask.OnTrue = ProgressTask.CreateDoneTask(file); - checkTask.OnFalse = new DownloadTask(file, _httpClient); - - yield return new LinkedTaskHead(checkTask, file); + yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } } diff --git a/src/FileExtractors/DefaultFileExtractors.cs b/src/FileExtractors/DefaultFileExtractors.cs index 395299e..072a0db 100644 --- a/src/FileExtractors/DefaultFileExtractors.cs +++ b/src/FileExtractors/DefaultFileExtractors.cs @@ -1,5 +1,6 @@ using CmlLib.Core.Java; using CmlLib.Core.Rules; +using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; @@ -11,11 +12,11 @@ public static DefaultFileExtractors CreateDefault( IJavaPathResolver javaPathResolver) { var extractors = new DefaultFileExtractors(); - extractors.Library = new LibraryFileExtractor(rulesEvaluator, httpClient); + extractors.Library = new LibraryFileExtractor(JsonVersionParserOptions.ClientSide, rulesEvaluator); extractors.Asset = new AssetFileExtractor(httpClient); - extractors.Client = new ClientFileExtractor(httpClient); + extractors.Client = new ClientFileExtractor(); extractors.Java = new JavaFileExtractor(httpClient, javaPathResolver); - extractors.Log = new LogFileExtractor(httpClient); + extractors.Log = new LogFileExtractor(); return extractors; } diff --git a/src/FileExtractors/IFileExtractor.cs b/src/FileExtractors/IFileExtractor.cs index ad1d304..632a938 100644 --- a/src/FileExtractors/IFileExtractor.cs +++ b/src/FileExtractors/IFileExtractor.cs @@ -7,7 +7,8 @@ namespace CmlLib.Core.FileExtractors; public interface IFileExtractor { ValueTask> Extract( - MinecraftPath path, + ITaskFactory taskFactory, + MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken); diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 40cf9b2..1422035 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -1,10 +1,9 @@ -using System.Diagnostics; -using System.Text.Json; +using CmlLib.Core.Files; +using CmlLib.Core.Internals; using CmlLib.Core.Java; using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; -using CmlLib.Core.Internals; namespace CmlLib.Core.FileExtractors; @@ -23,7 +22,8 @@ public JavaFileExtractor( } public async ValueTask> Extract( - MinecraftPath path, + ITaskFactory taskFactory, + MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) @@ -33,171 +33,90 @@ public async ValueTask> Extract( javaVersion = MinecraftJavaPathResolver.JreLegacyVersion; else javaVersion = version.JavaVersion.Value; - return await extractFromJavaVersion( - path, - javaVersion, - version, - rulesContext, - cancellationToken); - } - - private async ValueTask> extractFromJavaVersion( - MinecraftPath minecraftPath, - JavaVersion javaVersion, - IVersion version, - RulesEvaluatorContext rulesContext, - CancellationToken cancellationToken) - { - using var response = await _httpClient.GetStreamAsync(JavaManifestServer); - using var jsonDocument = JsonDocument.Parse(response); - var root = jsonDocument.RootElement; - - var osName = getJavaOSName(); - if (string.IsNullOrEmpty(osName)) - return await createLegacyJavaTask(minecraftPath, version, rulesContext, cancellationToken); - if (!root.TryGetProperty(osName, out var javaVersionsForOS)) - return await createLegacyJavaTask(minecraftPath, version, rulesContext, cancellationToken); - var currentVersionManifestUrl = getManifestUrl(javaVersionsForOS, javaVersion); - - if (!string.IsNullOrEmpty(currentVersionManifestUrl)) - return await extractFromManifestUrl(minecraftPath, currentVersionManifestUrl, javaVersion, rulesContext, cancellationToken); - else if (javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) - { - var legacyVersionManifestUrl = getManifestUrl(javaVersionsForOS, MinecraftJavaPathResolver.JreLegacyVersion); - if (!string.IsNullOrEmpty(legacyVersionManifestUrl)) - return await extractFromManifestUrl(minecraftPath, legacyVersionManifestUrl, javaVersion, rulesContext, cancellationToken); - } + var manifestResolver = new MinecraftJavaManifestResolver(_httpClient); + manifestResolver.ManifestServer = JavaManifestServer; - return await createLegacyJavaTask(minecraftPath, version, rulesContext, cancellationToken); + var extractor = new Extractor( + taskFactory, + _javaPathResolver, + rulesContext, + manifestResolver); + return await extractor.ExtractFromJavaVersion(javaVersion, cancellationToken); } - private string? getJavaOSName() // TODO: find exact version name + public class Extractor { - return (LauncherOSRule.Current.Name, LauncherOSRule.Current.Arch) switch + private readonly ITaskFactory _taskFactory; + private readonly IJavaPathResolver _javaPathResolver; + private readonly RulesEvaluatorContext _rulesContext; + private readonly MinecraftJavaManifestResolver _manifestResolver; + + public Extractor( + ITaskFactory taskFactory, + IJavaPathResolver javaPathResolver, + RulesEvaluatorContext rulesContext, + MinecraftJavaManifestResolver manifestResolver) { - (LauncherOSRule.Windows, "64") => "windows-x64", - (LauncherOSRule.Windows, "32") => "windows-x86", - (LauncherOSRule.Windows, _) => null, - (LauncherOSRule.Linux, "64") => "linux", - (LauncherOSRule.Linux, "32") => "linux-i386", - (LauncherOSRule.Linux, _) => "linux", - (LauncherOSRule.OSX, "64") => "mac-os", - (LauncherOSRule.OSX, "32") => "mac-os", - (LauncherOSRule.OSX, "arm") => "mac-os", // TODO - (LauncherOSRule.OSX, "arm64") => "mac-os", - (LauncherOSRule.OSX, _) => null, - (_, _) => null - }; - } - - private string? getManifestUrl(JsonElement element, JavaVersion version) - { - return element - .GetPropertyOrNull(version.Component)? - .EnumerateArray() - .FirstOrDefault() - .GetPropertyOrNull("manifest")? - .GetPropertyOrNull("url")? - .GetString(); - } + _taskFactory = taskFactory; + _javaPathResolver = javaPathResolver; + _rulesContext = rulesContext; + _manifestResolver = manifestResolver; + } - private async ValueTask> extractFromManifestUrl( - MinecraftPath minecraftPath, - string manifestUrl, - JavaVersion version, - RulesEvaluatorContext rulesContext, - CancellationToken cancellationToken) - { - using var res = await _httpClient.GetAsync(manifestUrl, cancellationToken); - using var stream = await res.Content.ReadAsStreamAsync(); - var json = JsonDocument.Parse(stream); // should be disposed after extraction - return extractFromManifestJson(minecraftPath, json, version, rulesContext); - } + public async ValueTask> ExtractFromJavaVersion( + JavaVersion javaVersion, + CancellationToken cancellationToken) + { + var osName = MinecraftJavaManifestResolver.GetOSNameForJava(_rulesContext.OS) ?? ""; + var manifests = await _manifestResolver.GetManifestsForOS(osName); + var manifestUrl = findManifestUrl(manifests, javaVersion.Component); - private IEnumerable extractFromManifestJson( - MinecraftPath minecraftPath, - JsonDocument _json, - JavaVersion version, - RulesEvaluatorContext rulesContext) - { - using var json = _json; - var manifest = json.RootElement; + if (string.IsNullOrEmpty(manifestUrl) && + javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) + manifestUrl = findManifestUrl(manifests, MinecraftJavaPathResolver.JreLegacyVersion.Component); - var path = _javaPathResolver.GetJavaDirPath(minecraftPath, version, rulesContext); + if (string.IsNullOrEmpty(manifestUrl)) + return Enumerable.Empty(); - if (!manifest.TryGetProperty("files", out var files)) - yield break; + var installPath = _javaPathResolver.GetJavaDirPath(javaVersion, _rulesContext); + var files = await _manifestResolver.GetFilesFromManifest(manifestUrl, cancellationToken); + return iterateFileToTask(installPath, files); + } - var objects = files.EnumerateObject(); - foreach (var prop in objects) + private string? findManifestUrl(IEnumerable metadatas, string component) { - var name = prop.Name; - var value = prop.Value; - - var type = value.GetPropertyValue("type"); - if (type == "file") - { - var filePath = Path.Combine(path, name); - filePath = IOUtil.NormalizePath(filePath); + return metadatas.FirstOrDefault(v => v.Component == component)?.Metadata?.Url; + } - var task = createTask(value, name, filePath); - if (task.HasValue) - yield return task.Value; - } - else + private IEnumerable iterateFileToTask( + string path, + IEnumerable files) + { + foreach (var javaFile in files) { - if (type != "directory") - Debug.WriteLine(type); + if (javaFile.Type == "file") + { + var filePath = Path.Combine(path, javaFile.Name); + filePath = IOUtil.NormalizePath(filePath); + + var taskFile = new TaskFile(javaFile.Name) + { + Hash = javaFile.Sha1, + Path = filePath, + Url = javaFile.Url, + Size = javaFile.Size + }; + + yield return LinkedTaskBuilder.Create(taskFile, _taskFactory) + .CheckFile( + onSuccess => onSuccess.ReportDone(), + onFail => onFail + .Download() + .ThenIf(javaFile.Executable).Then(new ChmodTask(javaFile.Name, filePath))) + .BuildHead(); + } } - } } - - private LinkedTaskHead? createTask(JsonElement value, string name, string filePath) - { - var downloadObj = value.GetPropertyOrNull("downloads")?.GetPropertyOrNull("raw"); - if (downloadObj == null) - return null; - - var url = downloadObj.Value.GetPropertyValue("url"); - if (string.IsNullOrEmpty(url)) - return null; - - var hash = downloadObj.Value.GetPropertyValue("sha1"); - var size = downloadObj.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; - - var executable = value.GetPropertyOrNull("executable")?.GetBoolean() ?? false; - - var file = new TaskFile(name) - { - Hash = hash, - Path = filePath, - Url = url, - Size = size - }; - - var checkTask = new FileCheckTask(file); - var downloadTask = new DownloadTask(file, _httpClient); - var chmodTask = new ChmodTask(file.Name, file.Path); - var progressTask = ProgressTask.CreateDoneTask(file); - - checkTask.OnTrue = progressTask; - checkTask.OnFalse = downloadTask; - - if (executable) - downloadTask.InsertNextTask(chmodTask); - - return new LinkedTaskHead(checkTask, file); - } - - private async ValueTask> createLegacyJavaTask( - MinecraftPath path, - IVersion version, - RulesEvaluatorContext rulesContext, - CancellationToken cancellationToken) - { - var legacyJava = new LegacyJavaFileExtractor(_httpClient, _javaPathResolver); - return await legacyJava.Extract(path, version, rulesContext, cancellationToken); - } } \ No newline at end of file diff --git a/src/FileExtractors/LegacyJavaFileExtractor.cs b/src/FileExtractors/LegacyJavaFileExtractor.cs index 4131103..bb6139e 100644 --- a/src/FileExtractors/LegacyJavaFileExtractor.cs +++ b/src/FileExtractors/LegacyJavaFileExtractor.cs @@ -1,11 +1,9 @@ -using System.ComponentModel; +using CmlLib.Core.Internals; using CmlLib.Core.Java; -using System.Text.Json; -using System.Net; using CmlLib.Core.Rules; -using CmlLib.Core.Internals; using CmlLib.Core.Tasks; using CmlLib.Core.Version; +using System.Text.Json; namespace CmlLib.Core.FileExtractors; @@ -20,16 +18,18 @@ public LegacyJavaFileExtractor( (_httpClient, _javaPathResolver) = (httpClient, resolver); public ValueTask> Extract( + ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var task = createTask(path, rulesContext, cancellationToken); + var task = createTask(taskFactory, path, rulesContext, cancellationToken); return new ValueTask>(new LinkedTaskHead[] { task }); } private LinkedTaskHead createTask( + ITaskFactory taskFactory, MinecraftPath path, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) @@ -40,35 +40,34 @@ private LinkedTaskHead createTask( MajorVersion = 17 }; - var javaBinaryPath = _javaPathResolver.GetJavaBinaryPath(path, javaVersion, rulesContext); - var javaBinaryDir = _javaPathResolver.GetJavaDirPath(path, javaVersion, rulesContext); + var javaBinaryPath = _javaPathResolver.GetJavaBinaryPath(javaVersion, rulesContext); + var javaBinaryDir = _javaPathResolver.GetJavaDirPath(javaVersion, rulesContext); var file = new TaskFile(javaVersion.Component) { Path = javaBinaryPath }; - var checkTask = new FileCheckTask(file); - var javaUrlTask = new ActionTask(file.Name, async task => - { - var javaUrl = await GetJavaUrlAsync(); - var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); - var lzmaFile = new TaskFile("jre.lzma") - { - Path = Path.Combine(Path.GetTempPath(), "jre.lzma"), - Url = javaUrl - }; - - var linkedTask = LinkedTask.LinkTasks( - new DownloadTask(lzmaFile, _httpClient), - new LZMADecompressTask("jre.lzma", lzmaFile.Path, zipPath), - new UnzipTask("jre.zip", zipPath, javaBinaryDir), - new ChmodTask(javaVersion.Component, javaBinaryPath) - ); - return task.InsertNextTask(linkedTask!); - }); + return LinkedTaskBuilder.Create(file, taskFactory) + .CheckFile( + onSuccess => onSuccess.ReportDone(), + onFail => onFail.Then(new ActionTask(file.Name, async task => + { + var javaUrl = await GetJavaUrlAsync(); + var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); + var lzmaFile = new TaskFile("jre.lzma") + { + Path = Path.Combine(Path.GetTempPath(), "jre.lzma"), + Url = javaUrl + }; - checkTask.OnFalse = javaUrlTask; - return new LinkedTaskHead(checkTask, file); + return LinkedTaskBuilder.Create(lzmaFile, taskFactory) + .Download() + .Then(new LZMADecompressTask("jre.lzma", lzmaFile.Path, zipPath)) + .Then(new UnzipTask("jre.zip", zipPath, javaBinaryDir)) + .Then(new ChmodTask(javaVersion.Component, javaBinaryPath)) + .BuildTask(); + }))) + .BuildHead(); } public async Task GetJavaUrlAsync() diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index a49779a..884cb9d 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -6,15 +6,13 @@ namespace CmlLib.Core.FileExtractors; public class LibraryFileExtractor : IFileExtractor { + private readonly string _side; private readonly IRulesEvaluator _rulesEvaluator; - private readonly HttpClient _httpClient; - public LibraryFileExtractor( - IRulesEvaluator rulesEvaluator, - HttpClient httpClient) + public LibraryFileExtractor(string side, IRulesEvaluator rulesEvaluator) { + _side = side; _rulesEvaluator = rulesEvaluator; - _httpClient = httpClient; } private string libServer = MojangServer.Library; @@ -31,27 +29,21 @@ public string LibraryServer } public ValueTask> Extract( + ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var result = extract(path, version, rulesContext); - return new ValueTask>(result); - } - - private IEnumerable extract( - MinecraftPath path, - IVersion version, - RulesEvaluatorContext rulesContext) - { - return version.Libraries - .Where(lib => lib.CheckIsRequired("SIDE")) + var result = version.Libraries + .Where(lib => lib.CheckIsRequired(_side)) .Where(lib => lib.Rules == null || _rulesEvaluator.Match(lib.Rules, rulesContext)) - .SelectMany(lib => createLibraryTasks(path, lib, rulesContext)); + .SelectMany(lib => createLibraryTasks(taskFactory, path, lib, rulesContext)); + return new ValueTask>(result); } private IEnumerable createLibraryTasks( + ITaskFactory taskFactory, MinecraftPath path, MLibrary library, RulesEvaluatorContext rulesContext) @@ -69,7 +61,7 @@ private IEnumerable createLibraryTasks( Size = artifact.Size }; - yield return createTaskFromFile(file); + yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } // native library (*.dll, *.so) @@ -86,19 +78,11 @@ private IEnumerable createLibraryTasks( Hash = native.GetSha1(), Size = native.Size }; - yield return createTaskFromFile(file); + yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } } } - private LinkedTaskHead createTaskFromFile(TaskFile file) - { - var task = new FileCheckTask(file); - task.OnTrue = ProgressTask.CreateDoneTask(file); - task.OnFalse = new DownloadTask(file, _httpClient); - return new LinkedTaskHead(task, file); - } - private string? createDownloadUrl(string? url, string path) { if (string.IsNullOrEmpty(url) && string.IsNullOrEmpty(path)) diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index 8abf87b..9c995b6 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -6,24 +6,21 @@ namespace CmlLib.Core.FileExtractors; public class LogFileExtractor : IFileExtractor { - private readonly HttpClient _httpClient; - - public LogFileExtractor(HttpClient httpClient) - { - _httpClient = httpClient; - } - public ValueTask> Extract( + ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var result = extract(path, version); + var result = extract(taskFactory, path, version); return new ValueTask>(result); } - private IEnumerable extract(MinecraftPath path, IVersion version) + private IEnumerable extract( + ITaskFactory taskFactory, + MinecraftPath path, + IVersion version) { if (version.Logging == null) yield break; @@ -41,9 +38,7 @@ private IEnumerable extract(MinecraftPath path, IVersion version Hash = version.Logging?.LogFile?.GetSha1(), Size = version.Logging?.LogFile?.Size ?? 0 }; - var task = new FileCheckTask(file); - task.OnTrue = ProgressTask.CreateDoneTask(file); - task.OnFalse = new DownloadTask(file, _httpClient); - yield return new LinkedTaskHead(task, file); + + yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } } diff --git a/src/Files/AssetIndex.cs b/src/Files/AssetIndex.cs new file mode 100644 index 0000000..762d913 --- /dev/null +++ b/src/Files/AssetIndex.cs @@ -0,0 +1,68 @@ +using CmlLib.Core.Internals; +using System.Text.Json; + +namespace CmlLib.Core.Files; + +public interface IAssetIndex +{ + string Id { get; } + bool IsVirtual { get; } + bool MapToResources { get; } + IEnumerable EnumerateAssetObjects(); +} + +public class JsonAssetIndex : IAssetIndex, IDisposable +{ + private readonly JsonDocument _json; + private readonly Lazy isVirtualLoader; + private readonly Lazy mapToResourcesLoader; + + public JsonAssetIndex(string id, JsonDocument json) + { + Id = id; + _json = json; + isVirtualLoader = new Lazy(() => + _json.RootElement.GetPropertyOrNull("virtual")?.GetBoolean() ?? false); + mapToResourcesLoader = new Lazy(() => + _json.RootElement.GetPropertyOrNull("map_to_resources")?.GetBoolean() ?? false); + } + + public string Id { get; } + public bool IsVirtual => isVirtualLoader.Value; + public bool MapToResources => mapToResourcesLoader.Value; + + public IEnumerable EnumerateAssetObjects() + { + if (!_json.RootElement.TryGetProperty("objects", out var objectsProp)) + yield break; + + var objects = objectsProp.EnumerateObject(); + foreach (var prop in objects) + { + var name = prop.Name; + var hash = prop.Value.GetPropertyValue("hash"); + var size = prop.Value.GetPropertyOrNull("size")?.GetInt64() ?? 0; + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(hash)) + continue; + + yield return new AssetObject(name, hash, size); + } + } + + public void Dispose() => _json.Dispose(); +} + +public class AssetObject +{ + public AssetObject(string name, string hash, long size) + { + Name = name; + Hash = hash; + Size = size; + } + + public string Name { get; } + public string Hash { get; } + public long Size { get; } +} \ No newline at end of file diff --git a/src/Files/MinecraftJavaFile.cs b/src/Files/MinecraftJavaFile.cs new file mode 100644 index 0000000..da4d1f7 --- /dev/null +++ b/src/Files/MinecraftJavaFile.cs @@ -0,0 +1,13 @@ +namespace CmlLib.Core.Files; + +public class MinecraftJavaFile +{ + public MinecraftJavaFile(string name) => Name = name; + + public string Name { get; } + public string? Type { get; set; } + public bool Executable { get; set; } + public string? Sha1 { get; set; } + public long Size { get; set; } + public string? Url { get; set; } +} diff --git a/src/Files/MinecraftJavaManifestMetadata.cs b/src/Files/MinecraftJavaManifestMetadata.cs new file mode 100644 index 0000000..d077370 --- /dev/null +++ b/src/Files/MinecraftJavaManifestMetadata.cs @@ -0,0 +1,18 @@ +namespace CmlLib.Core.Files; + +public class MinecraftJavaManifestMetadata +{ + public MinecraftJavaManifestMetadata(string os, string component) + { + OS = os; + Component = component; + } + + public string OS { get; set; } + public string Component { get; set; } + public MFileMetadata? Metadata { get; set; } + public string? VersionName { get; set; } + public string? VersionReleased { get; set; } + + public string? GetMajorVersion() => VersionName?.Split('.')?[0]; +} \ No newline at end of file diff --git a/src/Installers/IGameInstaller.cs b/src/Installers/IGameInstaller.cs index 6701bfb..4324451 100644 --- a/src/Installers/IGameInstaller.cs +++ b/src/Installers/IGameInstaller.cs @@ -1,5 +1,6 @@ using CmlLib.Core.FileExtractors; using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; namespace CmlLib.Core.Installers; @@ -7,6 +8,7 @@ namespace CmlLib.Core.Installers; public interface IGameInstaller { ValueTask Install( + ITaskFactory taskFactory, IEnumerable extractors, MinecraftPath path, IVersion version, diff --git a/src/Installers/TPLGameInstaller.cs b/src/Installers/TPLGameInstaller.cs index 6cfef25..1c3581c 100644 --- a/src/Installers/TPLGameInstaller.cs +++ b/src/Installers/TPLGameInstaller.cs @@ -26,6 +26,7 @@ public TPLGameInstaller(int parallelism) => _maxParallelism = parallelism; public async ValueTask Install( + ITaskFactory taskFactory, IEnumerable extractors, MinecraftPath path, IVersion version, @@ -34,7 +35,7 @@ public async ValueTask Install( IProgress? byteProgress, CancellationToken cancellationToken) { - var executor = new TPLGameInstallerExecutor(_maxParallelism, path, version, rulesContext); + var executor = new TPLGameInstallerExecutor(_maxParallelism, taskFactory, path, version, rulesContext); executor.FileProgress += (s, e) => fileProgress?.Report(e); executor.ByteProgress += (s, e) => byteProgress?.Report(e); await executor.Install(extractors, cancellationToken); @@ -51,17 +52,19 @@ public enum GameInstallerExceptionMode class TPLGameInstallerExecutor { private readonly int _maxParallelism; + private readonly ITaskFactory _taskFactory; private readonly MinecraftPath _path; private readonly IVersion _version; private readonly RulesEvaluatorContext _rulesContext; public TPLGameInstallerExecutor( int parallelism, + ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext) => - (_maxParallelism, _path, _version, _rulesContext) = - (parallelism, path, version, rulesContext); + (_maxParallelism, _taskFactory, _path, _version, _rulesContext) = + (parallelism, taskFactory, path, version, rulesContext); public event EventHandler? FileProgress; public event EventHandler? ByteProgress; @@ -222,7 +225,7 @@ private IPropagatorBlock createExtractBlock( { var extractorBlock = new TransformManyBlock(async extractor => { - var tasks = await extractor.Extract(_path, _version, _rulesContext, cancellationToken); + var tasks = await extractor.Extract(_taskFactory, _path, _version, _rulesContext, cancellationToken); return tasks .Where(task => task.First != null) .Where(task => string.IsNullOrEmpty(task.File.Path) || distinctStorage.Add(task.File.Path)) diff --git a/src/Java/IJavaPathResolver.cs b/src/Java/IJavaPathResolver.cs index f90a36c..2196299 100644 --- a/src/Java/IJavaPathResolver.cs +++ b/src/Java/IJavaPathResolver.cs @@ -4,8 +4,8 @@ namespace CmlLib.Core.Java; public interface IJavaPathResolver { - string[] GetInstalledJavaVersions(MinecraftPath path); - string? GetDefaultJavaBinaryPath(MinecraftPath path, RulesEvaluatorContext rules); - string GetJavaBinaryPath(MinecraftPath path, JavaVersion javaVersion, RulesEvaluatorContext rules); - string GetJavaDirPath(MinecraftPath path, JavaVersion javaVersion, RulesEvaluatorContext rules); + string[] GetInstalledJavaVersions(); + string? GetDefaultJavaBinaryPath(RulesEvaluatorContext rules); + string GetJavaBinaryPath(JavaVersion javaVersion, RulesEvaluatorContext rules); + string GetJavaDirPath(JavaVersion javaVersion, RulesEvaluatorContext rules); } \ No newline at end of file diff --git a/src/Java/MinecraftJavaManifestResolver.cs b/src/Java/MinecraftJavaManifestResolver.cs new file mode 100644 index 0000000..e1f8b96 --- /dev/null +++ b/src/Java/MinecraftJavaManifestResolver.cs @@ -0,0 +1,149 @@ +using CmlLib.Core.Files; +using CmlLib.Core.Internals; +using CmlLib.Core.Rules; +using System.Text.Json; + +namespace CmlLib.Core.Java; + +public class MinecraftJavaManifestResolver +{ + public static string? GetOSNameForJava(LauncherOSRule os) + { + return (os.Name, os.Arch) switch + { + (LauncherOSRule.Windows, "64") => "windows-x64", + (LauncherOSRule.Windows, "32") => "windows-x86", + (LauncherOSRule.Windows, "arm64") => "windows-arm64", + (LauncherOSRule.Windows, _) => null, + (LauncherOSRule.Linux, "64") => "linux", + (LauncherOSRule.Linux, "32") => "linux-i386", + (LauncherOSRule.Linux, _) => "linux", + (LauncherOSRule.OSX, "64") => "mac-os", + (LauncherOSRule.OSX, "32") => "mac-os", + (LauncherOSRule.OSX, "arm") => "mac-os-arm64", + (LauncherOSRule.OSX, "arm64") => "mac-os-arm64", + (LauncherOSRule.OSX, _) => null, + (_, _) => null + }; + } + + private readonly HttpClient _httpClient; + public string ManifestServer { get; set; } = MojangServer.JavaManifest; + + public MinecraftJavaManifestResolver(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task> GetAllManifests() + { + using var json = await requestJsonManifest(); + var root = json.RootElement; + + var list = new List(); + foreach (var osProp in root.EnumerateObject()) + { + if (osProp.Value.ValueKind != JsonValueKind.Object) + continue; + + var components = enumerateComponents(osProp.Name, osProp.Value.EnumerateObject()); + foreach (var component in components) + { + list.Add(component); + } + } + return list; + } + + public async Task> GetManifestsForOS(string os) + { + using var json = await requestJsonManifest(); + var components = json.RootElement + .GetPropertyOrNull(os)? + .EnumerateObject(); + + if (components == null) + return Enumerable.Empty(); + + return enumerateComponents(os, components); + } + + private async Task requestJsonManifest() + { + var stream = await _httpClient.GetStreamAsync(ManifestServer); + return await JsonDocument.ParseAsync(stream); + } + + private IEnumerable enumerateComponents(string osName, IEnumerable components) + { + foreach (var componentProp in components) + { + var componentName = componentProp.Name; + var manifest = componentProp.Value; + + if (manifest.ValueKind == JsonValueKind.Object) + yield return parseManifest(osName, componentName, manifest); + else if (manifest.ValueKind == JsonValueKind.Array) + { + var manifestArray = manifest.EnumerateArray(); + if (manifestArray.Any()) + yield return parseManifest(osName, componentName, manifestArray.First()); + } + } + } + + private MinecraftJavaManifestMetadata parseManifest(string os, string component, JsonElement json) + { + return new MinecraftJavaManifestMetadata(os, component) + { + Metadata = json.GetPropertyOrNull("manifest")?.Deserialize(), + VersionName = json.GetPropertyOrNull("version")?.GetPropertyOrNull("name")?.GetString(), + VersionReleased = json.GetPropertyOrNull("version")?.GetPropertyOrNull("released")?.GetString() + }; + } + + public async Task> GetFilesFromManifest( + MinecraftJavaManifestMetadata manifest, + CancellationToken cancellationToken) + { + var url = manifest.Metadata?.Url; + if (string.IsNullOrEmpty(url)) + throw new ArgumentException("Url was null"); + return await GetFilesFromManifest(url, cancellationToken); + } + + public async Task> GetFilesFromManifest( + string manifestUrl, + CancellationToken cancellationToken) + { + using var res = await _httpClient.GetAsync(manifestUrl, cancellationToken); + using var stream = await res.Content.ReadAsStreamAsync(); + var json = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); // should be disposed after extraction + return parseJavaFilesAndDispose(json); + } + + private IEnumerable parseJavaFilesAndDispose(JsonDocument _json) + { + using var json = _json; + + if (!json.RootElement.TryGetProperty("files", out var files)) + yield break; + + var objects = files.EnumerateObject(); + foreach (var prop in objects) + { + var name = prop.Name; + var value = prop.Value; + + var downloadObj = value.GetPropertyOrNull("downloads")?.GetPropertyOrNull("raw"); + yield return new MinecraftJavaFile(name) + { + Type = value.GetPropertyValue("type"), + Executable = value.GetPropertyOrNull("executable")?.GetBoolean() ?? false, + Sha1 = downloadObj?.GetPropertyValue("sha1"), + Size = downloadObj?.GetPropertyOrNull("size")?.GetInt64() ?? 0, + Url = downloadObj?.GetPropertyValue("url") + }; + } + } +} diff --git a/src/Java/MinecraftJavaPathResolver.cs b/src/Java/MinecraftJavaPathResolver.cs index 75b3541..6bb53f4 100644 --- a/src/Java/MinecraftJavaPathResolver.cs +++ b/src/Java/MinecraftJavaPathResolver.cs @@ -7,9 +7,16 @@ public class MinecraftJavaPathResolver : IJavaPathResolver public static readonly JavaVersion JreLegacyVersion = new JavaVersion("jre-legacy"); public static readonly JavaVersion CmlLegacyVersion = new JavaVersion("m-legacy"); - public string[] GetInstalledJavaVersions(MinecraftPath path) + private readonly MinecraftPath _path; + + public MinecraftJavaPathResolver(MinecraftPath path) + { + this._path = path; + } + + public string[] GetInstalledJavaVersions() { - var dir = new DirectoryInfo(path.Runtime); + var dir = new DirectoryInfo(_path.Runtime); if (!dir.Exists) return Array.Empty(); @@ -18,36 +25,36 @@ public string[] GetInstalledJavaVersions(MinecraftPath path) .ToArray(); } - public string? GetDefaultJavaBinaryPath(MinecraftPath path, RulesEvaluatorContext rules) + public string? GetDefaultJavaBinaryPath(RulesEvaluatorContext rules) { - var javaVersions = GetInstalledJavaVersions(path); + var javaVersions = GetInstalledJavaVersions(); string? javaPath = null; if (string.IsNullOrEmpty(javaPath) && javaVersions.Contains(MinecraftJavaPathResolver.JreLegacyVersion.Component)) - javaPath = GetJavaBinaryPath(path, MinecraftJavaPathResolver.JreLegacyVersion, rules); + javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.JreLegacyVersion, rules); if (string.IsNullOrEmpty(javaPath) && javaVersions.Contains(MinecraftJavaPathResolver.CmlLegacyVersion.Component)) - javaPath = GetJavaBinaryPath(path, MinecraftJavaPathResolver.CmlLegacyVersion, rules); + javaPath = GetJavaBinaryPath(MinecraftJavaPathResolver.CmlLegacyVersion, rules); if (string.IsNullOrEmpty(javaPath) && javaVersions.Length > 0) - javaPath = GetJavaBinaryPath(path, new JavaVersion(javaVersions[0]), rules); + javaPath = GetJavaBinaryPath(new JavaVersion(javaVersions[0]), rules); return javaPath; } - public string GetJavaBinaryPath(MinecraftPath path, JavaVersion javaVersionName, RulesEvaluatorContext rules) + public string GetJavaBinaryPath(JavaVersion javaVersionName, RulesEvaluatorContext rules) { return Path.Combine( - GetJavaDirPath(path, javaVersionName, rules), + GetJavaDirPath(javaVersionName, rules), "bin", GetJavaBinaryName(rules.OS)); } - public string GetJavaDirPath(MinecraftPath path, JavaVersion javaVersionName, RulesEvaluatorContext rules) - => Path.Combine(path.Runtime, javaVersionName.Component); + public string GetJavaDirPath(JavaVersion javaVersionName, RulesEvaluatorContext rules) + => Path.Combine(_path.Runtime, javaVersionName.Component); public string GetJavaBinaryName(LauncherOSRule os) { diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 85d8e95..135efd4 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -4,6 +4,7 @@ using CmlLib.Core.Java; using CmlLib.Core.Natives; using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.VersionLoader; namespace CmlLib.Core; @@ -24,7 +25,7 @@ public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) new LocalJsonVersionLoader(parameters.MinecraftPath), new MojangJsonVersionLoader(httpClient) }; - parameters.JavaPathResolver = new MinecraftJavaPathResolver(); + parameters.JavaPathResolver = new MinecraftJavaPathResolver(parameters.MinecraftPath); parameters.GameInstaller = new TPLGameInstaller(TPLGameInstaller.BestMaxParallelism); parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); var extractors = DefaultFileExtractors.CreateDefault( @@ -32,6 +33,7 @@ public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) parameters.RulesEvaluator, parameters.JavaPathResolver); parameters.FileExtractors = extractors.ToExtractorCollection(); + parameters.TaskFactory = new Tasks.TaskFactory(httpClient); return parameters; } @@ -56,6 +58,7 @@ public HttpClient HttpClient public IVersionLoader? VersionLoader { get; set; } public IJavaPathResolver? JavaPathResolver { get; set; } public FileExtractorCollection? FileExtractors { get; set; } + public ITaskFactory? TaskFactory { get; set; } public IGameInstaller? GameInstaller { get; set; } public INativeLibraryExtractor? NativeLibraryExtractor { get; set; } public IRulesEvaluator? RulesEvaluator { get; set; } diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index fbce467..c4c556b 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -5,6 +5,7 @@ using CmlLib.Core.Natives; using CmlLib.Core.ProcessBuilder; using CmlLib.Core.Rules; +using CmlLib.Core.Tasks; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; @@ -21,6 +22,7 @@ public class MinecraftLauncher public MinecraftPath MinecraftPath { get; } public IVersionLoader VersionLoader { get; } public IJavaPathResolver JavaPathResolver { get; } + public ITaskFactory TaskFactory { get; } public FileExtractorCollection FileExtractors { get; } public IGameInstaller GameInstaller { get; } public INativeLibraryExtractor NativeLibraryExtractor { get; } @@ -54,6 +56,8 @@ public MinecraftLauncher(MinecraftLauncherParameters parameters) ?? throw new ArgumentException(nameof(parameters.VersionLoader) + " was null"); JavaPathResolver = parameters.JavaPathResolver ?? throw new ArgumentException(nameof(parameters.JavaPathResolver) + " was null"); + TaskFactory = parameters.TaskFactory + ?? throw new ArgumentException(nameof(parameters.TaskFactory) + " was null"); FileExtractors = parameters.FileExtractors ?? throw new ArgumentException(nameof(parameters.FileExtractors) + " was null"); GameInstaller = parameters.GameInstaller @@ -103,6 +107,7 @@ public async ValueTask InstallAsync( CancellationToken cancellationToken = default) { await GameInstaller.Install( + TaskFactory, FileExtractors, MinecraftPath, version, @@ -155,14 +160,13 @@ public Process BuildProcess( return null; return JavaPathResolver.GetJavaBinaryPath( - MinecraftPath, version.JavaVersion.Value, RulesContext); } public string? GetDefaultJavaPath() { - return JavaPathResolver.GetDefaultJavaBinaryPath(MinecraftPath, RulesContext); + return JavaPathResolver.GetDefaultJavaBinaryPath(RulesContext); } private string createNativePath(IVersion version) diff --git a/src/Rules/IRulesEvaluator.cs b/src/Rules/IRulesEvaluator.cs index 9b4030c..dbebf21 100644 --- a/src/Rules/IRulesEvaluator.cs +++ b/src/Rules/IRulesEvaluator.cs @@ -1,4 +1,3 @@ - namespace CmlLib.Core.Rules; public interface IRulesEvaluator diff --git a/src/Rules/LauncherOSRule.cs b/src/Rules/LauncherOSRule.cs index e18e729..f45d1d0 100644 --- a/src/Rules/LauncherOSRule.cs +++ b/src/Rules/LauncherOSRule.cs @@ -23,12 +23,14 @@ private static LauncherOSRule createCurrent() else name = Linux; - arch = RuntimeInformation.OSArchitecture switch // TODO: find exact value + // ${arch} : 32, 64 + // rules/os/arch: x86 + arch = RuntimeInformation.OSArchitecture switch { Architecture.X86 => "32", Architecture.X64 => "64", Architecture.Arm => "arm", - Architecture.Arm64 => "arm", + Architecture.Arm64 => "arm64", _ => "" }; diff --git a/src/Tasks/ITaskFactory.cs b/src/Tasks/ITaskFactory.cs new file mode 100644 index 0000000..2a6dd10 --- /dev/null +++ b/src/Tasks/ITaskFactory.cs @@ -0,0 +1,9 @@ +namespace CmlLib.Core.Tasks; + +public interface ITaskFactory +{ + LinkedTask CheckFile(TaskFile file, LinkedTask onSuccess, LinkedTask onFail); + LinkedTask Download(TaskFile file); + LinkedTask CheckAndDownload(TaskFile file); + LinkedTask ReportDone(TaskFile file); +} \ No newline at end of file diff --git a/src/Tasks/LinkedTaskBuilder.cs b/src/Tasks/LinkedTaskBuilder.cs new file mode 100644 index 0000000..cdf6561 --- /dev/null +++ b/src/Tasks/LinkedTaskBuilder.cs @@ -0,0 +1,105 @@ +using System.Diagnostics; + +namespace CmlLib.Core.Tasks; + +public class LinkedTaskBuilder +{ + private readonly ITaskFactory _taskFactory; + + public TaskFile TaskFile { get; private set; } + public LinkedTask? Task { get; private set; } + private LinkedTask? Tail { get; set; } + public bool IsReadOnly { get; private set; } + + private LinkedTaskBuilder( + TaskFile taskFile, + ITaskFactory taskFactory, + LinkedTask? head, + LinkedTask? tail, + bool isReadOnly) + { + TaskFile = taskFile; + _taskFactory = taskFactory; + Task = head; + Tail = tail; + IsReadOnly = isReadOnly; + } + + public static LinkedTaskBuilder Create(TaskFile taskFile, ITaskFactory taskFactory) => + new LinkedTaskBuilder(taskFile, taskFactory, null, null, false); + + public static LinkedTaskBuilder CreateReadOnly(TaskFile taskFile, ITaskFactory taskFactory, LinkedTask? head) => + new LinkedTaskBuilder(taskFile, taskFactory, head, null, true); + + public LinkedTaskBuilder Then(LinkedTask task) + { + if (IsReadOnly) + return this; + + if (Task == null) + { + Task = task; + Tail = task; + } + else + { + Debug.Assert(Tail != null); + + Tail.InsertNextTask(task); + Tail = task; + } + + return this; + } + + public LinkedTaskBuilder ThenIf(bool condition) + { + if (condition) + return this; + else + return CreateReadOnly(TaskFile, _taskFactory, Task); + } + + public LinkedTaskBuilder CheckFile(LinkedTask onSuccess, LinkedTask onFail) + { + return Then(_taskFactory.CheckFile(TaskFile, onSuccess, onFail)); + } + + public LinkedTaskBuilder CheckFile( + Func onSuccess, + Func onFail) + { + return CheckFile( + onSuccess.Invoke(Create(TaskFile, _taskFactory)).BuildTask() ?? throw new InvalidOperationException(), + onFail.Invoke(Create(TaskFile, _taskFactory)).BuildTask() ?? throw new InvalidOperationException()); + } + + public LinkedTaskBuilder Download() + { + return Then(_taskFactory.Download(TaskFile)); + } + + public LinkedTaskBuilder ReportDone() + { + return Then(_taskFactory.ReportDone(TaskFile)); + } + + public LinkedTaskBuilder CheckAndDownload() + { + return Then(_taskFactory.CheckAndDownload(TaskFile)); + } + + public LinkedTaskHead BuildHead() + { + if (Task == null) + throw new InvalidOperationException(); + return new LinkedTaskHead(Task, TaskFile); + } + + public LinkedTask BuildTask() + { + if (Task == null) + throw new InvalidOperationException(); + return Task; + } +} \ No newline at end of file diff --git a/src/Tasks/TaskFactory.cs b/src/Tasks/TaskFactory.cs new file mode 100644 index 0000000..a68f345 --- /dev/null +++ b/src/Tasks/TaskFactory.cs @@ -0,0 +1,36 @@ +namespace CmlLib.Core.Tasks; + +public class TaskFactory : ITaskFactory +{ + private readonly HttpClient _httpClient; + + public TaskFactory(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public LinkedTask CheckAndDownload(TaskFile file) + { + return CheckFile(file, + onSuccess: ReportDone(file), + onFail: Download(file)); + } + + public LinkedTask CheckFile(TaskFile file, LinkedTask onSuccess, LinkedTask onFail) + { + var task = new FileCheckTask(file); + task.OnTrue = onSuccess; + task.OnFalse = onFail; + return task; + } + + public LinkedTask Download(TaskFile file) + { + return new DownloadTask(file, _httpClient); + } + + public LinkedTask ReportDone(TaskFile file) + { + return ProgressTask.CreateDoneTask(file); + } +} \ No newline at end of file diff --git a/test/Installers/TPLGameInstallerTest.cs b/test/Installers/TPLGameInstallerTest.cs index bbdae19..9772688 100644 --- a/test/Installers/TPLGameInstallerTest.cs +++ b/test/Installers/TPLGameInstallerTest.cs @@ -14,6 +14,7 @@ public class TPLGameInstallerTest private const int TaskCount = 128; private const int ExtractorCount = 4; private const long TaskSize = 128 * 32; + private readonly ITaskFactory TestTaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); private readonly RulesEvaluatorContext TestRulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); private readonly MinecraftPath TestMinecraftPath = new Mock().Object; private readonly IVersion TestVersion = new Mock().Object; @@ -26,6 +27,7 @@ public async Task TestAllTaskExecuted() var installer = createInstaller(); await installer.Install( + TestTaskFactory, extractors, TestMinecraftPath, TestVersion, @@ -53,6 +55,7 @@ public async Task TestFileProgressReachesTo100() var installer = createInstaller(); await installer.Install( + TestTaskFactory, extractors, TestMinecraftPath, TestVersion, @@ -83,6 +86,7 @@ public async Task TestByteProgressReachesTo100() var installer = createInstaller(); await installer.Install( + TestTaskFactory, extractors, TestMinecraftPath, TestVersion, @@ -117,6 +121,7 @@ public async Task TestFilterDuplicatedFile() var installer = createInstaller(); await installer.Install( + TestTaskFactory, extractors, TestMinecraftPath, TestVersion, @@ -163,6 +168,7 @@ private IEnumerable createMockExtractors(IEnumerable t { var mockExtractor = new Mock(); mockExtractor.Setup(e => e.Extract( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), From 98d7fdf30bbf475f5ac9b99ecc891bcd814c7292 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 8 Feb 2024 14:17:38 +0900 Subject: [PATCH 076/137] BREAKING CHANGE: simplify tasks, extractors, installers --- benchmark/DummyDownloadTask.cs | 32 --- benchmark/DummyDownloaderExtractor.cs | 40 --- benchmark/DummyTask.cs | 19 -- benchmark/DummyTaskExtractor.cs | 40 --- .../ConcurrentDictionaryBenchmark.cs | 69 ----- .../Executors/ConcurrentQueueBenchmark.cs | 70 ----- benchmark/Executors/ExecutorBenchmarkBase.cs | 117 -------- benchmark/Executors/LockBenchmark.cs | 79 ----- benchmark/Executors/NoLockBenchmark.cs | 72 ----- benchmark/Executors/SemaphoreSlimBenchmark.cs | 112 -------- benchmark/Executors/ThreadLocalBenchmark.cs | 75 ----- benchmark/ExecutorsBenchmark.cs | 83 ------ benchmark/HashBenchmark.cs | 36 --- benchmark/Program.cs | 26 +- benchmark/RandomFileExtractor.cs | 14 +- ...askExecutorWithDummyDownloaderBenchmark.cs | 47 --- .../TPLTaskExecutorWithDummyTaskBenchmark.cs | 83 ------ .../TPLTaskExecutorWithRandomFileBenchmark.cs | 68 ----- examples/console/Program.cs | 25 +- src/FileExtractors/AssetFileExtractor.cs | 23 +- src/FileExtractors/ClientFileExtractor.cs | 13 +- src/FileExtractors/IFileExtractor.cs | 3 +- src/FileExtractors/JavaFileExtractor.cs | 26 +- src/FileExtractors/LegacyJavaFileExtractor.cs | 71 ++--- src/FileExtractors/LibraryFileExtractor.cs | 17 +- src/FileExtractors/LogFileExtractor.cs | 15 +- src/Installers/BasicGameInstaller.cs | 115 ++++++++ src/Installers/IGameInstaller.cs | 6 +- .../InstallerProgressChangedEventArgs.cs | 10 +- src/Installers/TPLGameInstaller.cs | 270 ------------------ src/Installers/TaskState.cs | 8 - src/Java/MinecraftJavaManifestResolver.cs | 2 +- src/LauncherParameters.cs | 4 +- src/MinecraftLauncher.cs | 32 ++- src/Tasks/ActionTask.cs | 17 -- src/Tasks/ChmodTask.cs | 17 +- src/Tasks/CompositeUpdateTask.cs | 25 ++ src/Tasks/DownloadTask.cs | 63 ---- src/Tasks/FileCheckTask.cs | 47 --- src/Tasks/FileCopyTask.cs | 17 +- src/Tasks/GameFile.cs | 20 ++ src/Tasks/ITaskFactory.cs | 9 - src/Tasks/IUpdateTask.cs | 6 + src/Tasks/LZMADecompressTask.cs | 23 -- src/Tasks/LegacyJavaExtractionTask.cs | 25 ++ src/Tasks/LinkedTask.cs | 77 ----- src/Tasks/LinkedTaskBuilder.cs | 105 ------- src/Tasks/LinkedTaskHead.cs | 21 -- src/Tasks/ProgressTask.cs | 31 -- src/Tasks/ResultTask.cs | 34 --- src/Tasks/TaskExecutionContext.cs | 12 - src/Tasks/TaskFactory.cs | 36 --- src/Tasks/TaskFile.cs | 13 - src/Tasks/UnzipTask.cs | 23 -- test/CmlLib.Core.Test.csproj | 4 + test/Installers/TPLGameInstallerTest.cs | 197 ------------- test/Tasks/MockDownloadTask.cs | 26 -- test/Tasks/MockResultTask.cs | 17 -- test/Tasks/MockTask.cs | 20 -- test/Tasks/ResultTaskTest.cs | 82 ------ 60 files changed, 327 insertions(+), 2362 deletions(-) delete mode 100644 benchmark/DummyDownloadTask.cs delete mode 100644 benchmark/DummyDownloaderExtractor.cs delete mode 100644 benchmark/DummyTask.cs delete mode 100644 benchmark/DummyTaskExtractor.cs delete mode 100644 benchmark/Executors/ConcurrentDictionaryBenchmark.cs delete mode 100644 benchmark/Executors/ConcurrentQueueBenchmark.cs delete mode 100644 benchmark/Executors/ExecutorBenchmarkBase.cs delete mode 100644 benchmark/Executors/LockBenchmark.cs delete mode 100644 benchmark/Executors/NoLockBenchmark.cs delete mode 100644 benchmark/Executors/SemaphoreSlimBenchmark.cs delete mode 100644 benchmark/Executors/ThreadLocalBenchmark.cs delete mode 100644 benchmark/ExecutorsBenchmark.cs delete mode 100644 benchmark/HashBenchmark.cs delete mode 100644 benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs delete mode 100644 benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs delete mode 100644 benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs create mode 100644 src/Installers/BasicGameInstaller.cs delete mode 100644 src/Installers/TPLGameInstaller.cs delete mode 100644 src/Installers/TaskState.cs delete mode 100644 src/Tasks/ActionTask.cs create mode 100644 src/Tasks/CompositeUpdateTask.cs delete mode 100644 src/Tasks/DownloadTask.cs delete mode 100644 src/Tasks/FileCheckTask.cs create mode 100644 src/Tasks/GameFile.cs delete mode 100644 src/Tasks/ITaskFactory.cs create mode 100644 src/Tasks/IUpdateTask.cs delete mode 100644 src/Tasks/LZMADecompressTask.cs create mode 100644 src/Tasks/LegacyJavaExtractionTask.cs delete mode 100644 src/Tasks/LinkedTask.cs delete mode 100644 src/Tasks/LinkedTaskBuilder.cs delete mode 100644 src/Tasks/LinkedTaskHead.cs delete mode 100644 src/Tasks/ProgressTask.cs delete mode 100644 src/Tasks/ResultTask.cs delete mode 100644 src/Tasks/TaskExecutionContext.cs delete mode 100644 src/Tasks/TaskFactory.cs delete mode 100644 src/Tasks/TaskFile.cs delete mode 100644 src/Tasks/UnzipTask.cs delete mode 100644 test/Installers/TPLGameInstallerTest.cs delete mode 100644 test/Tasks/MockDownloadTask.cs delete mode 100644 test/Tasks/MockResultTask.cs delete mode 100644 test/Tasks/MockTask.cs delete mode 100644 test/Tasks/ResultTaskTest.cs diff --git a/benchmark/DummyDownloadTask.cs b/benchmark/DummyDownloadTask.cs deleted file mode 100644 index 6cfd65a..0000000 --- a/benchmark/DummyDownloadTask.cs +++ /dev/null @@ -1,32 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public class DummyDownloadTask : DownloadTask -{ - public static int Seed = 0; - - public DummyDownloadTask(TaskFile file, HttpClient httpClient) : base(file, httpClient) - { - } - - protected override ValueTask DownloadFile( - IProgress? progress, - CancellationToken cancellationToken) - { - for (int i = 0; i < Size; i += 1) - { - if (Size % 128 == 0) - { - progress?.Report(new ByteProgress - { - TotalBytes = Size, - ProgressedBytes = i - }); - } - Seed += i; - //await Task.Delay(1); - } - return new ValueTask(); - } -} \ No newline at end of file diff --git a/benchmark/DummyDownloaderExtractor.cs b/benchmark/DummyDownloaderExtractor.cs deleted file mode 100644 index 11e17e7..0000000 --- a/benchmark/DummyDownloaderExtractor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -public class DummyDownloaderExtractor : IFileExtractor -{ - private readonly int _count; - private readonly string _prefix; - private readonly long _size; - - public DummyDownloaderExtractor(string prefix, int count, long size) => - (_prefix, _count, _size) = (prefix, count, size); - - public ValueTask> Extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) - { - var r = extract(); - return new ValueTask>(r); - } - - private IEnumerable extract() - { - for (int i = 0; i < _count; i++) - { - var file = new TaskFile(_prefix + "-" + i.ToString()) - { - Size = _size, - Path = "a.dat", - Url = "a.dat" - }; - var task = LinkedTask.LinkTasks( - new DummyTask(file, i), - new DummyDownloadTask(file, null!) - ); - yield return new LinkedTaskHead(task, file); - } - } -} \ No newline at end of file diff --git a/benchmark/DummyTask.cs b/benchmark/DummyTask.cs deleted file mode 100644 index 620582b..0000000 --- a/benchmark/DummyTask.cs +++ /dev/null @@ -1,19 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public class DummyTask : LinkedTask -{ - public static int Seed { get; set; } - - public DummyTask(TaskFile file, int seed) : base(file) => Seed = seed; - - protected override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - for (int j = 0; j < 1024; j++) - Seed += j; - return new ValueTask(NextTask); - } -} \ No newline at end of file diff --git a/benchmark/DummyTaskExtractor.cs b/benchmark/DummyTaskExtractor.cs deleted file mode 100644 index 545782c..0000000 --- a/benchmark/DummyTaskExtractor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -public class DummyTaskExtractor : IFileExtractor -{ - private readonly int _count; - private readonly string _prefix; - public DummyTaskExtractor(string prefix, int count) => - (_prefix, _count) = (prefix, count); - - public ValueTask> Extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) - { - var r = extract(); - return new ValueTask>(r); - } - - private IEnumerable extract() - { - for (int i = 0; i < _count; i++) - { - var file = new TaskFile(_prefix + "-" + i.ToString()) - { - Size = 1024 * 2, - Path = "a.dat", - Url = "a.dat" - }; - var task = LinkedTask.LinkTasks( - new DummyTask(file, i), - new DummyTask(file, i), - new DummyTask(file, i) - //new DummyDownloadTask(file, null!) - ); - yield return new LinkedTaskHead(task, file); - } - } -} \ No newline at end of file diff --git a/benchmark/Executors/ConcurrentDictionaryBenchmark.cs b/benchmark/Executors/ConcurrentDictionaryBenchmark.cs deleted file mode 100644 index 2bd3c1f..0000000 --- a/benchmark/Executors/ConcurrentDictionaryBenchmark.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Concurrent; -using CmlLib.Core.Benchmarks; -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Executors; - -public class ConcurrentDictionaryBenchmark : ExecutorBenchmarkBase -{ - private ConcurrentDictionary progressStorage = null!; - - protected override void Setup() - { - progressStorage = new ConcurrentDictionary(); - } - - protected override void OnTaskAdded(LinkedTaskHead head) - { - progressStorage[head.Name] = new ByteProgress - { - TotalBytes = head.File.Size, - ProgressedBytes = 0 - }; - } - - protected override void OnReportEvent() - { - long totalBytes = 0; - long progressedBytes = 0; - - foreach (var kv in progressStorage) - { - totalBytes += kv.Value.TotalBytes; - progressedBytes += kv.Value.ProgressedBytes; - } - - FireProgress(new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); - } - - protected async override ValueTask Transform(LinkedTask t) - { - var task = t as DownloadTask; - if (task == null) - return t.NextTask; - - var progress = new SyncProgress(e => - { - progressStorage.AddOrUpdate(task.Name, e, (_, _) => e); - }); - - var nextTask = await task.Execute(progress, default); - - if (nextTask == null) - { - var p = new ByteProgress - { - TotalBytes = task.Size, - ProgressedBytes = task.Size - }; - progress.Report(p); - } - return nextTask; - } -} - diff --git a/benchmark/Executors/ConcurrentQueueBenchmark.cs b/benchmark/Executors/ConcurrentQueueBenchmark.cs deleted file mode 100644 index 33f5ad1..0000000 --- a/benchmark/Executors/ConcurrentQueueBenchmark.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Concurrent; -using CmlLib.Core.Tasks; -using CmlLib.Core.Executors; -using CmlLib.Core.Installers; - -namespace CmlLib.Core.Benchmarks; - -public class ConcurrentQueueBenchmark : ExecutorBenchmarkBase -{ - private struct TaskSizeChangedEventArgs - { - public string Name; - public long TaskSize; - } - - private Dictionary taskSizeStorage = null!; - private ConcurrentQueue messageQueue = null!; - private long progressedBytes; - - protected override void Setup() - { - taskSizeStorage = new Dictionary(); - messageQueue = new ConcurrentQueue(); - } - - protected override void OnReportEvent() - { - while (messageQueue.TryDequeue(out var message)) - { - taskSizeStorage[message.Name] = message.TaskSize; - } - - long totalSize = 0; - foreach (var kv in taskSizeStorage) - { - totalSize += kv.Value; - } - - FireProgress(new ByteProgress - { - TotalBytes = totalSize, - ProgressedBytes = Interlocked.Read(ref progressedBytes) - }); - } - - protected override void OnTaskAdded(LinkedTaskHead head) - { - messageQueue.Enqueue(new TaskSizeChangedEventArgs - { - Name = head.Name, - TaskSize = head.File.Size - }); - } - - protected override async ValueTask Transform(LinkedTask task) - { - long prevBytes = 0; - var progress = new SyncProgress(e => - { - var d = e.ProgressedBytes - prevBytes; - //if (d < 1024 * 8) // 8kb - // return; - prevBytes = e.ProgressedBytes; - Interlocked.Add(ref progressedBytes, d); - }); - - var nextTask = await task.Execute(progress, default); - return nextTask; - } -} \ No newline at end of file diff --git a/benchmark/Executors/ExecutorBenchmarkBase.cs b/benchmark/Executors/ExecutorBenchmarkBase.cs deleted file mode 100644 index ce19b76..0000000 --- a/benchmark/Executors/ExecutorBenchmarkBase.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Threading.Tasks.Dataflow; -using BenchmarkDotNet.Attributes; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public abstract class ExecutorBenchmarkBase -{ - public static ByteProgress LastEvent; - - public ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); - public RulesEvaluatorContext RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - public int MaxParallelism { get; set; } = 6; - public string Name { get; set; } = "D"; - public int Count { get; set; } = 1024*2; // 256 - public int Size { get; set; } = 1024*16; // 1024*128 - public bool Verbose { get; set; } = false; - - protected int TotalTasks = 0; - protected int ProceedTasks = 0; - protected LinkedTask[] Tasks { get; private set; } = null!; - public event EventHandler? ByteProgress; - - public async Task IterationSetup() - { - Setup(); - - var minecraftPath = new MinecraftPath(); - var version = new DummyVersion(); - var taskExtractor = new DummyDownloaderExtractor(Name, Count, Size); - var result = await taskExtractor.Extract(TaskFactory, minecraftPath, version, RulesContext, default); - - var list = new List(); - foreach (var head in result) - { - if (head.First == null) - continue; - - OnTaskAdded(head); - Interlocked.Increment(ref TotalTasks); - list.Add(head.First); - } - Tasks = list.ToArray(); - - ByteProgress += (s, e) => LastEvent = e; - if (Verbose) - ByteProgress += (s, e) => Console.WriteLine($"{e.ProgressedBytes} / {e.TotalBytes}"); - } - - protected abstract void Setup(); - - protected virtual void OnTaskAdded(LinkedTaskHead head) - { - - } - - - public async Task Benchmark() - { - var block = CreateExecutorBlock(); - Task? lastSendTask = null; - foreach (var item in Tasks) - { - lastSendTask = block.SendAsync(item); - } - if (lastSendTask == null) - return; - await lastSendTask; - - var executeTask = block.Completion; - while (!executeTask.IsCompleted) - { - OnReportEvent(); - await Task.Delay(100); - } - OnReportEvent(); - await executeTask; - } - - protected virtual BufferBlock CreateExecutorBlock() - { - - var buffer = new BufferBlock(); - var executor = new TransformBlock( - async t => - { - var result = await Transform(t); - if (result == null) - { - Interlocked.Increment(ref ProceedTasks); - if (TotalTasks == ProceedTasks) - buffer.Complete(); - } - return result; - }, - new ExecutionDataflowBlockOptions - { - MaxDegreeOfParallelism = MaxParallelism - }); - buffer.LinkTo(executor); - executor.LinkTo(buffer!, t => t != null); - executor.LinkTo(DataflowBlock.NullTarget()); - return buffer; - } - - protected virtual ValueTask Transform(LinkedTask task) - { - return new ValueTask(task.NextTask); - } - protected abstract void OnReportEvent(); - - protected void FireProgress(ByteProgress progress) - { - ByteProgress?.Invoke(this, progress); - } -} \ No newline at end of file diff --git a/benchmark/Executors/LockBenchmark.cs b/benchmark/Executors/LockBenchmark.cs deleted file mode 100644 index 4cf9f33..0000000 --- a/benchmark/Executors/LockBenchmark.cs +++ /dev/null @@ -1,79 +0,0 @@ -using CmlLib.Core.Executors; -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public class LockBenchmark : ExecutorBenchmarkBase -{ - private Dictionary sizeStorage = null!; - private ThreadLocal> progressStorage = null!; - - protected override void Setup() - { - progressStorage = new ThreadLocal>( - () => new Dictionary(), - true); - sizeStorage = new Dictionary(); - } - - protected override void OnTaskAdded(LinkedTaskHead head) - { - sizeStorage[head.Name] = head.File.Size; - } - - protected override void OnReportEvent() - { - long totalBytes = 0; - long progressedBytes = 0; - - foreach (var dict in progressStorage.Values) - { - lock (dict) - { - foreach (var kv in dict) - { - totalBytes += kv.Value.TotalBytes; - progressedBytes += kv.Value.ProgressedBytes; - sizeStorage.Remove(kv.Key); - } - } - } - - foreach (var kv in sizeStorage) - totalBytes += kv.Value; - - FireProgress(new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); - } - - protected override async ValueTask Transform(LinkedTask t) - { - var task = t as DownloadTask; - if (task == null) - return t.NextTask; - - var progress = new SyncProgress(e => - { - lock (progressStorage.Value!) - { - progressStorage.Value![task.Name] = e; - } - }); - - var nextTask = await task.Execute(progress, default); - - if (nextTask == null) - { - progress.Report(new ByteProgress - { - TotalBytes = task.Size, - ProgressedBytes = task.Size - }); - } - return nextTask; - } -} \ No newline at end of file diff --git a/benchmark/Executors/NoLockBenchmark.cs b/benchmark/Executors/NoLockBenchmark.cs deleted file mode 100644 index 3390f60..0000000 --- a/benchmark/Executors/NoLockBenchmark.cs +++ /dev/null @@ -1,72 +0,0 @@ -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public class NoLockBenchmark : ExecutorBenchmarkBase -{ - private ThreadLocal progressStorage = null!; - - protected override void Setup() - { - progressStorage = new ThreadLocal( - () => new ByteProgress(), - true); - } - - protected override void OnTaskAdded(LinkedTaskHead head) - { - progressStorage.Value = new ByteProgress - { - TotalBytes = progressStorage.Value.TotalBytes + head.File.Size, - ProgressedBytes = 0 - }; - } - - protected override void OnReportEvent() - { - long totalBytes = 0; - long progressedBytes = 0; - foreach (var p in progressStorage.Values) - { - totalBytes += p.TotalBytes; - progressedBytes += p.ProgressedBytes; - } - - FireProgress(new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); - } - - protected override async ValueTask Transform(LinkedTask task) - { - var lastProgress = new ByteProgress - { - TotalBytes = task.Size, - ProgressedBytes = 0 - }; - var progress = new SyncProgress(e => - { - progressStorage.Value = new ByteProgress - { - TotalBytes = progressStorage.Value.TotalBytes + e.TotalBytes - lastProgress.TotalBytes, - ProgressedBytes = progressStorage.Value.ProgressedBytes + e.ProgressedBytes - lastProgress.ProgressedBytes - }; - lastProgress = e; - }); - - var nextTask = await task.Execute(progress, default); - - if (nextTask == null) - { - progress.Report(new ByteProgress - { - TotalBytes = lastProgress.TotalBytes, - ProgressedBytes = lastProgress.TotalBytes - }); - } - return nextTask; - } -} \ No newline at end of file diff --git a/benchmark/Executors/SemaphoreSlimBenchmark.cs b/benchmark/Executors/SemaphoreSlimBenchmark.cs deleted file mode 100644 index 5d86b10..0000000 --- a/benchmark/Executors/SemaphoreSlimBenchmark.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Diagnostics; -using CmlLib.Core.Executors; -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public class SemaphoreSlimBenchmark : ExecutorBenchmarkBase -{ - private Dictionary sizeStorage = null!; - private ThreadLocal> progressStorage = null!; - private SemaphoreSlim semaphore = null!; - - protected override void Setup() - { - sizeStorage = new Dictionary(); - progressStorage = new ThreadLocal>( - () => new Dictionary(), - true); - semaphore = new SemaphoreSlim(1); - } - - protected override void OnTaskAdded(LinkedTaskHead head) - { - sizeStorage[head.Name] = head.File.Size; - } - - protected override void OnReportEvent() - { - try - { - semaphore.Wait(100); - - long totalBytes = 0; - long progressedBytes = 0; - - foreach (var dict in progressStorage.Values) - { - foreach (var kv in dict) - { - totalBytes += kv.Value.TotalBytes; - progressedBytes += kv.Value.ProgressedBytes; - sizeStorage.Remove(kv.Key); - } - } - - foreach (var kv in sizeStorage) - totalBytes += kv.Value; - - FireProgress(new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); - } - finally - { - semaphore.Release(); - } - } - - protected override async ValueTask Transform(LinkedTask task) - { - var progress = new SyncProgress(e => - { - var isLocked = false; - try - { - if (semaphore.Wait(0)) - { - isLocked = true; - progressStorage.Value![task.Name] = e; - //Console.WriteLine("progress"); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex.ToString()); - } - finally - { - if (isLocked) - semaphore.Release(); - } - }); - - var nextTask = await task.Execute(progress, default); - if (nextTask == null) - { - try - { - semaphore.Wait(1000); - var previousProgress = progressStorage.Value![task.Name]; - progressStorage.Value![task.Name] = new ByteProgress - { - TotalBytes = previousProgress.TotalBytes, - ProgressedBytes = previousProgress.TotalBytes - }; - //Console.WriteLine("completed"); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - finally - { - semaphore.Release(); - } - } - return nextTask; - } -} \ No newline at end of file diff --git a/benchmark/Executors/ThreadLocalBenchmark.cs b/benchmark/Executors/ThreadLocalBenchmark.cs deleted file mode 100644 index 2c55c2e..0000000 --- a/benchmark/Executors/ThreadLocalBenchmark.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Collections.Concurrent; -using CmlLib.Core.Executors; -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Benchmarks; - -public class ThreadLocalBenchmark : ExecutorBenchmarkBase -{ - private ConcurrentDictionary totalProgress = null!; - private ThreadLocal lastUpdate = null!; - - protected override void Setup() - { - totalProgress = new ConcurrentDictionary(); - lastUpdate = new ThreadLocal(() => 0); - } - - protected override void OnTaskAdded(LinkedTaskHead head) - { - var p = new ByteProgress - { - TotalBytes = head.File.Size, - ProgressedBytes = 0 - }; - totalProgress.AddOrUpdate(head.Name, p, (_, _) => p); - } - - protected override void OnReportEvent() - { - long totalBytes = 0; - long progressedBytes = 0; - - foreach (var kv in totalProgress) - { - totalBytes += kv.Value.TotalBytes; - progressedBytes += kv.Value.ProgressedBytes; - } - - FireProgress(new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); - } - - protected async override ValueTask Transform(LinkedTask t) - { - var task = t as DownloadTask; - if (task == null) - return t.NextTask; - - var progress = new SyncProgress(e => - { - if (Math.Abs(lastUpdate.Value - Environment.TickCount) > 256) - { - totalProgress.AddOrUpdate(task.Name, e, (_, _) => e); - lastUpdate.Value = Environment.TickCount; - } - }); - - var nextTask = await task.Execute(progress, default); - - if (nextTask == null) - { - var p = new ByteProgress - { - TotalBytes = task.Size, - ProgressedBytes = task.Size - }; - totalProgress.AddOrUpdate(task.Name, p, (_, _) => p); - } - return nextTask; - } -} \ No newline at end of file diff --git a/benchmark/ExecutorsBenchmark.cs b/benchmark/ExecutorsBenchmark.cs deleted file mode 100644 index 7304be7..0000000 --- a/benchmark/ExecutorsBenchmark.cs +++ /dev/null @@ -1,83 +0,0 @@ -using BenchmarkDotNet.Attributes; -using CmlLib.Core.Executors; -using CmlLib.Core.Installers; - -namespace CmlLib.Core.Benchmarks; - -[MemoryDiagnoser(false)] -public class ExecutorsBenchmark -{ - #nullable disable - ConcurrentDictionaryBenchmark _concurrent; - ConcurrentQueueBenchmark _queue; - LockBenchmark _lock; - SemaphoreSlimBenchmark _semaphore; - ThreadLocalBenchmark _thread; - NoLockBenchmark _nolock; - - [IterationSetup] - public void IterationSetup() - { - _concurrent = new ConcurrentDictionaryBenchmark(); - _concurrent.IterationSetup().Wait(); - - _queue = new ConcurrentQueueBenchmark(); - _queue.IterationSetup().Wait(); - - _lock = new LockBenchmark(); - _lock.IterationSetup().Wait(); - - _semaphore = new SemaphoreSlimBenchmark(); - _semaphore.IterationSetup().Wait(); - - _thread = new ThreadLocalBenchmark(); - _thread.IterationSetup().Wait(); - - _nolock = new NoLockBenchmark(); - _nolock.IterationSetup().Wait(); - } - - //[Benchmark(Baseline = true)] - //public async Task StartConcurrent() - //{ - // await _concurrent.Benchmark(); - //} - - [Benchmark] - public async Task StartLock() - { - await _lock.Benchmark(); - } - - [Benchmark] - public async Task StartSemaphore() - { - await _semaphore.Benchmark(); - } - - [Benchmark] - public async Task StartNoLock() - { - await _nolock.Benchmark(); - } - - //[Benchmark(Baseline = true)] - //public async Task StartQueue() - //{ - // await _queue.Benchmark(); - //} - - //[Benchmark] - //public async Task StartThread() - //{ - // await _thread.Benchmark(); - //} - - public static void PrintProgress(InstallerProgressChangedEventArgs e) - { - //if (status != TaskStatus.Done) return; - //if (proceed % 100 != 0) return; - var now = DateTime.Now.ToString("hh:mm:ss.fff"); - Console.WriteLine($"[{now}][{e.ProgressedTasks}/{e.TotalTasks}][{e.EventType}] {e.Name}"); - } -} \ No newline at end of file diff --git a/benchmark/HashBenchmark.cs b/benchmark/HashBenchmark.cs deleted file mode 100644 index c71b5e6..0000000 --- a/benchmark/HashBenchmark.cs +++ /dev/null @@ -1,36 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Standart.Hash.xxHash; - -namespace CmlLib.Core.Benchmarks; - -public class HashBenchmark -{ - private byte[] Data; - - [GlobalSetup] - public void GlobalSetup() - { - Data = new byte[1024*1024]; - Random.Shared.NextBytes(Data); - } - - [Benchmark] - public ulong TestXX() - { - return xxHash64.ComputeHash(Data, Data.Length); - } - - [Benchmark] - public byte[] TestMD5() - { - using var md5 = System.Security.Cryptography.MD5.Create(); - return md5.ComputeHash(Data); - } - - [Benchmark] - public byte[] TestSHA1() - { - using var sha1 = System.Security.Cryptography.SHA1.Create(); - return sha1.ComputeHash(Data); - } -} \ No newline at end of file diff --git a/benchmark/Program.cs b/benchmark/Program.cs index cd36fd5..ab39029 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,27 +1,5 @@ using System.Diagnostics; using BenchmarkDotNet.Running; -using CmlLib.Core.Benchmarks; -using CmlLib.Core.Executors; -var summary = BenchmarkRunner.Run(); -return; - -await once(); -async Task once() -{ - var benchmark = new NoLockBenchmark(); - benchmark.Verbose = true; - //benchmark.Size = 1024 * 1; - //benchmark.Count = 1024 * 256; - //benchmark.MaxParallelism = 1; - //benchmark.MaxParallelism = 12; - //benchmark.Count = 1024 * 16; - - await benchmark.IterationSetup(); - var sw = new Stopwatch(); - sw.Start(); - await benchmark.Benchmark(); - sw.Stop(); - Console.WriteLine("Done"); - Console.WriteLine(sw.Elapsed.ToString()); -} \ No newline at end of file +Console.WriteLine("Hello, World!"); +//var summary = BenchmarkRunner.Run(); \ No newline at end of file diff --git a/benchmark/RandomFileExtractor.cs b/benchmark/RandomFileExtractor.cs index 729f8ec..906c356 100644 --- a/benchmark/RandomFileExtractor.cs +++ b/benchmark/RandomFileExtractor.cs @@ -25,8 +25,6 @@ public void Setup() for (int i = 0; i < _fileCount; i++) { var dummyFilePath = Path.Combine(_path, i + ".dat"); - if (TPLTaskExecutorWithRandomFileBenchmark.Verbose) - Console.WriteLine(dummyFilePath); using var fs = File.Create(dummyFilePath); var bufferSize = 1024 * 8; @@ -51,25 +49,23 @@ public void Cleanup() Directory.Delete(_path); } - public ValueTask> Extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) + public ValueTask> Extract(MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { var result = extract(); - return new ValueTask>(result); + return new ValueTask>(result); } - private IEnumerable extract() + private IEnumerable extract() { foreach (var filePath in Directory.GetFiles(_path)) { - var file = new TaskFile(filePath) + var file = new GameFile(filePath) { Path = filePath, Hash = "-" }; - - var task = new FileCheckTask(file); - yield return new LinkedTaskHead(task, file); + yield return file; } } } \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs deleted file mode 100644 index d70fe8c..0000000 --- a/benchmark/TPLTaskExecutorWithDummyDownloaderBenchmark.cs +++ /dev/null @@ -1,47 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using CmlLib.Core.Executors; -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -public class TPLTaskExecutorWithDummyDownloaderBenchmark -{ - private int parallelism = 6; - private int extractorCount = 8; - - public static InstallerProgressChangedEventArgs? FileProgressArgs; - public static ByteProgress BytesProgressArgs; - - private ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); - private MinecraftPath MinecraftPath = new MinecraftPath(); - private IVersion DummyVersion = new DummyVersion(); - private DummyDownloaderExtractor[] Extractors; - private TPLGameInstaller Executor; - - [IterationSetup] - public void IterationSetup() - { - Extractors = new DummyDownloaderExtractor[extractorCount]; - for (int i = 0; i < extractorCount; i++) - Extractors[i] = new DummyDownloaderExtractor(i.ToString(), 1024, 1024*256); - Executor = new TPLGameInstaller(parallelism); - } - - [Benchmark] - public async Task Benchmark() - { - await Executor.Install( - TaskFactory, - Extractors, - MinecraftPath, - DummyVersion, - TPLTaskExecutorWithDummyTaskBenchmark.RulesContext, - TPLTaskExecutorWithDummyTaskBenchmark.FileProgress, - TPLTaskExecutorWithDummyTaskBenchmark.ByteProgress, - default); - } -} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs b/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs deleted file mode 100644 index cb03e1b..0000000 --- a/benchmark/TPLTaskExecutorWithDummyTaskBenchmark.cs +++ /dev/null @@ -1,83 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using CmlLib.Core.Executors; -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Installers; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -public class TPLTaskExecutorWithDummyTaskBenchmark -{ - public static bool Verbose = false; - public static InstallerProgressChangedEventArgs? FileProgressArgs; - public static ByteProgress BytesProgressArgs; - - private static object consoleLock = new object(); - private static string? bottomMsg; - private static ByteProgress previousEvent; - public static RulesEvaluatorContext RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - public static IProgress FileProgress = new SyncProgress(e => - { - FileProgressArgs = e; - if (Verbose) - { - lock (consoleLock) - { - Console.SetCursorPosition(0, Console.CursorTop - 1); - ExecutorsBenchmark.PrintProgress(e); - Console.WriteLine(bottomMsg); - } - } - }); - public static IProgress ByteProgress = new SyncProgress(e => - { - BytesProgressArgs = e; - if (Verbose) - { - lock (consoleLock) - { - if (previousEvent.ProgressedBytes >= e.ProgressedBytes) - return; - previousEvent = e; - bottomMsg = $"{e.ProgressedBytes} / {e.TotalBytes} "; - Console.SetCursorPosition(0, Console.CursorTop - 1); - Console.WriteLine(bottomMsg); - } - } - }); - - private int parallelism = 6; - private int extractorCount = 8; - - private ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); - private MinecraftPath MinecraftPath = new MinecraftPath(); - private IVersion DummyVersion = new DummyVersion(); - private DummyTaskExtractor[] Extractors; - private TPLGameInstaller Executor; - - [IterationSetup] - public void IterationSetup() - { - Extractors = new DummyTaskExtractor[extractorCount]; - for (int i = 0; i < extractorCount; i++) - Extractors[i] = new DummyTaskExtractor(i.ToString(), 1024); - Executor = new TPLGameInstaller(parallelism); - } - - [Benchmark] - public async Task Benchmark() - { - await Executor.Install( - TaskFactory, - Extractors, - MinecraftPath, - DummyVersion, - RulesContext, - FileProgress, - ByteProgress, - default); - } -} \ No newline at end of file diff --git a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs b/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs deleted file mode 100644 index 1c1e1ce..0000000 --- a/benchmark/TPLTaskExecutorWithRandomFileBenchmark.cs +++ /dev/null @@ -1,68 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using CmlLib.Core.Executors; -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Installers; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -[SimpleJob(RunStrategy.Monitoring, iterationCount: 10)] -public class TPLTaskExecutorWithRandomFileBenchmark -{ - public static bool Verbose { get; set; } = false; - - private int parallelism = 6; - private int extractorCount = 4; - - public static InstallerProgressChangedEventArgs? FileProgressArgs; - public static ByteProgress BytesProgressArgs; - - private ITaskFactory TaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); - private MinecraftPath MinecraftPath = new MinecraftPath(); - private IVersion DummyVersion = new DummyVersion(); - private RandomFileExtractor[] Extractors; - private TPLGameInstaller Executor; - - [GlobalSetup] - public void GlobalSetup() - { - } - - [IterationSetup] - public void IterationSetup() - { - Extractors = new RandomFileExtractor[extractorCount]; - for (int i = 0; i < extractorCount; i++) - { - var path = Path.GetFullPath("./benchmark" + i); - Extractors[i] = new RandomFileExtractor(path, 1024, 1024*1024/2); - Extractors[i].Setup(); - } - Executor = new TPLGameInstaller(parallelism); - } - - [IterationCleanup] - public void IterationCleanup() - { - foreach (var item in Extractors) - { - item.Cleanup(); - } - } - - [Benchmark] - public async Task Benchmark() - { - await Executor.Install( - TaskFactory, - Extractors, - MinecraftPath, - DummyVersion, - TPLTaskExecutorWithDummyTaskBenchmark.RulesContext, - TPLTaskExecutorWithDummyTaskBenchmark.FileProgress, - TPLTaskExecutorWithDummyTaskBenchmark.ByteProgress, - default); - } -} \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index df454eb..26a0e0b 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -43,8 +43,8 @@ private async Task Start() // select version Console.WriteLine("Select the version to launch: "); Console.Write("> "); - //var startVersion = Console.ReadLine(); - var startVersion = "1.20.1"; + var startVersion = Console.ReadLine(); + //var startVersion = "1.20.1"; if (string.IsNullOrEmpty(startVersion)) return; @@ -91,7 +91,7 @@ private void Launcher_FileProgressChanged(object? sender, InstallerProgressChang if (previousProceed > e.ProgressedTasks) return; - var msg = $"[{e.ProgressedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"; + var msg = $"[{e.ProgressedTasks} / {e.TotalTasks}] {e.Name}"; Console.WriteLine(msg.PadRight(lastCursorLeft)); printBottomProgress(); @@ -103,15 +103,18 @@ private void Launcher_ByteProgressChanged(object? sender, ByteProgress e) { lock (consoleLock) { - var percent = (e.ProgressedBytes / (double)e.TotalBytes) * 100; - var total = e.TotalBytes.ToString().PadRight(12); - var progressed = e.ProgressedBytes.ToString().PadLeft(12); - var now = Environment.TickCount; - var speed = (e.ProgressedBytes - lastProgressed) / (double)(now - lastUpdate); - bottomText = $"==> {percent:F2}%, ({progressed} / {total}) bytes, {speed:F2} KB/s"; - lastProgressed = e.ProgressedBytes; - lastUpdate = now; + if (Math.Abs(now - lastUpdate) >= 1000) + { + var percent = (e.ProgressedBytes / (double)e.TotalBytes) * 100; + var total = e.TotalBytes.ToString().PadRight(12); + var progressed = e.ProgressedBytes.ToString().PadLeft(12); + + var speed = (e.ProgressedBytes - lastProgressed) / (double)(now - lastUpdate); + bottomText = $"==> {percent:F2}%, ({progressed} / {total}) bytes, {speed:F2} KB/s"; + lastProgressed = e.ProgressedBytes; + lastUpdate = now; + } printBottomProgress(); } } diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index d5cbe1f..0d7870a 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -19,8 +19,7 @@ public AssetFileExtractor(HttpClient client) public string AssetServer { get; set; } = MojangServer.ResourceDownload; - public async ValueTask> Extract( - ITaskFactory taskFactory, + public async ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, @@ -28,10 +27,9 @@ public async ValueTask> Extract( { var assetIndex = await loadAssetIndex(path, version.AssetIndex); if (assetIndex == null) - return Enumerable.Empty(); + return Enumerable.Empty(); return TaskExtractor.ExtractTasksFromAssetIndex( - taskFactory, assetIndex, path, AssetServer, @@ -87,8 +85,8 @@ public async ValueTask> Extract( public static class TaskExtractor { - public static IEnumerable ExtractTasksFromAssetIndex( - ITaskFactory taskFactory, IAssetIndex assetIndex, MinecraftPath path, string assetServer, bool dispose) + public static IEnumerable ExtractTasksFromAssetIndex( + IAssetIndex assetIndex, MinecraftPath path, string assetServer, bool dispose) { if (assetServer.Last() != '/') assetServer += '/'; @@ -104,7 +102,7 @@ public static IEnumerable ExtractTasksFromAssetIndex( if (assetIndex.MapToResources) copyPath.Add(Path.Combine(path.Resource, assetObject.Name)); - var file = new TaskFile(assetObject.Name) + var file = new GameFile(assetObject.Name) { Path = hashPath, Hash = assetObject.Hash, @@ -112,13 +110,10 @@ public static IEnumerable ExtractTasksFromAssetIndex( Url = assetServer + hashName }; - yield return LinkedTaskBuilder.Create(file, taskFactory) - .CheckFile( - onSuccess => onSuccess.ReportDone(), - onFail => onFail - .Download() - .ThenIf(copyPath.Any()).Then(new FileCopyTask(assetObject.Name, hashPath, copyPath.ToArray()))) - .BuildHead(); + if (copyPath.Any()) + file.UpdateTask = new FileCopyTask(copyPath); + + yield return file; } if (dispose && assetIndex is IDisposable disposableAssetIndex) diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 42fc211..8db020e 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -6,18 +6,17 @@ namespace CmlLib.Core.FileExtractors; public class ClientFileExtractor : IFileExtractor { - public ValueTask> Extract( - ITaskFactory taskFactory, + public ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var result = extract(taskFactory, path, version); - return new ValueTask>(result); + var result = extract(path, version); + return new ValueTask>(result); } - private IEnumerable extract(ITaskFactory taskFactory, MinecraftPath path, IVersion version) + private IEnumerable extract(MinecraftPath path, IVersion version) { var id = version.JarId; var url = version.Client?.Url; @@ -26,14 +25,12 @@ private IEnumerable extract(ITaskFactory taskFactory, MinecraftP yield break; var clientPath = path.GetVersionJarPath(id); - var file = new TaskFile(id) + yield return new GameFile(id) { Path = clientPath, Url = url, Hash = version.Client?.GetSha1(), Size = version.Client?.Size ?? 0 }; - - yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } } diff --git a/src/FileExtractors/IFileExtractor.cs b/src/FileExtractors/IFileExtractor.cs index 632a938..f1050fe 100644 --- a/src/FileExtractors/IFileExtractor.cs +++ b/src/FileExtractors/IFileExtractor.cs @@ -6,8 +6,7 @@ namespace CmlLib.Core.FileExtractors; public interface IFileExtractor { - ValueTask> Extract( - ITaskFactory taskFactory, + ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 1422035..fe3a76c 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -21,8 +21,7 @@ public JavaFileExtractor( _javaPathResolver = javaPathResolver; } - public async ValueTask> Extract( - ITaskFactory taskFactory, + public async ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, @@ -38,7 +37,6 @@ public async ValueTask> Extract( manifestResolver.ManifestServer = JavaManifestServer; var extractor = new Extractor( - taskFactory, _javaPathResolver, rulesContext, manifestResolver); @@ -47,24 +45,21 @@ public async ValueTask> Extract( public class Extractor { - private readonly ITaskFactory _taskFactory; private readonly IJavaPathResolver _javaPathResolver; private readonly RulesEvaluatorContext _rulesContext; private readonly MinecraftJavaManifestResolver _manifestResolver; public Extractor( - ITaskFactory taskFactory, IJavaPathResolver javaPathResolver, RulesEvaluatorContext rulesContext, MinecraftJavaManifestResolver manifestResolver) { - _taskFactory = taskFactory; _javaPathResolver = javaPathResolver; _rulesContext = rulesContext; _manifestResolver = manifestResolver; } - public async ValueTask> ExtractFromJavaVersion( + public async ValueTask> ExtractFromJavaVersion( JavaVersion javaVersion, CancellationToken cancellationToken) { @@ -77,7 +72,7 @@ public async ValueTask> ExtractFromJavaVersion( manifestUrl = findManifestUrl(manifests, MinecraftJavaPathResolver.JreLegacyVersion.Component); if (string.IsNullOrEmpty(manifestUrl)) - return Enumerable.Empty(); + return Enumerable.Empty(); var installPath = _javaPathResolver.GetJavaDirPath(javaVersion, _rulesContext); var files = await _manifestResolver.GetFilesFromManifest(manifestUrl, cancellationToken); @@ -89,7 +84,7 @@ public async ValueTask> ExtractFromJavaVersion( return metadatas.FirstOrDefault(v => v.Component == component)?.Metadata?.Url; } - private IEnumerable iterateFileToTask( + private IEnumerable iterateFileToTask( string path, IEnumerable files) { @@ -100,7 +95,7 @@ private IEnumerable iterateFileToTask( var filePath = Path.Combine(path, javaFile.Name); filePath = IOUtil.NormalizePath(filePath); - var taskFile = new TaskFile(javaFile.Name) + var gameFile = new GameFile(javaFile.Name) { Hash = javaFile.Sha1, Path = filePath, @@ -108,13 +103,10 @@ private IEnumerable iterateFileToTask( Size = javaFile.Size }; - yield return LinkedTaskBuilder.Create(taskFile, _taskFactory) - .CheckFile( - onSuccess => onSuccess.ReportDone(), - onFail => onFail - .Download() - .ThenIf(javaFile.Executable).Then(new ChmodTask(javaFile.Name, filePath))) - .BuildHead(); + if (javaFile.Executable) + gameFile.UpdateTask = new ChmodTask(NativeMethods.Chmod755); + + yield return gameFile; } } } diff --git a/src/FileExtractors/LegacyJavaFileExtractor.cs b/src/FileExtractors/LegacyJavaFileExtractor.cs index bb6139e..85d7ad2 100644 --- a/src/FileExtractors/LegacyJavaFileExtractor.cs +++ b/src/FileExtractors/LegacyJavaFileExtractor.cs @@ -17,64 +17,55 @@ public LegacyJavaFileExtractor( IJavaPathResolver resolver) => (_httpClient, _javaPathResolver) = (httpClient, resolver); - public ValueTask> Extract( - ITaskFactory taskFactory, + public JavaVersion JavaVersion = new JavaVersion + { + Component = "m-legacy", + MajorVersion = 17 + }; + + public async ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var task = createTask(taskFactory, path, rulesContext, cancellationToken); - return new ValueTask>(new LinkedTaskHead[] { task }); + var javaBinaryPath = _javaPathResolver.GetJavaBinaryPath(JavaVersion, rulesContext); + if (!File.Exists(javaBinaryPath)) + { + return new [] { await createTask(rulesContext, cancellationToken) }; + } + else + { + return Enumerable.Empty(); + } } - private LinkedTaskHead createTask( - ITaskFactory taskFactory, - MinecraftPath path, + private async Task createTask( RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var javaVersion = new JavaVersion - { - Component = "m-legacy", - MajorVersion = 17 - }; + var javaBinaryDir = _javaPathResolver.GetJavaDirPath(JavaVersion, rulesContext); - var javaBinaryPath = _javaPathResolver.GetJavaBinaryPath(javaVersion, rulesContext); - var javaBinaryDir = _javaPathResolver.GetJavaDirPath(javaVersion, rulesContext); - var file = new TaskFile(javaVersion.Component) + var javaUrl = await GetJavaUrlAsync(cancellationToken); + var lzmaFile = new GameFile("jre.lzma") { - Path = javaBinaryPath + Path = Path.Combine(Path.GetTempPath(), "jre.lzma"), + Hash = "0", // since the file is temporary it should be always downloaded again + Url = javaUrl }; - return LinkedTaskBuilder.Create(file, taskFactory) - .CheckFile( - onSuccess => onSuccess.ReportDone(), - onFail => onFail.Then(new ActionTask(file.Name, async task => - { - var javaUrl = await GetJavaUrlAsync(); - var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); - var lzmaFile = new TaskFile("jre.lzma") - { - Path = Path.Combine(Path.GetTempPath(), "jre.lzma"), - Url = javaUrl - }; + lzmaFile.UpdateTask = CompositeUpdateTask.Create( + new LegacyJavaExtractionTask(javaBinaryDir), + new ChmodTask(NativeMethods.Chmod755)); - return LinkedTaskBuilder.Create(lzmaFile, taskFactory) - .Download() - .Then(new LZMADecompressTask("jre.lzma", lzmaFile.Path, zipPath)) - .Then(new UnzipTask("jre.zip", zipPath, javaBinaryDir)) - .Then(new ChmodTask(javaVersion.Component, javaBinaryPath)) - .BuildTask(); - }))) - .BuildHead(); + return lzmaFile; } - public async Task GetJavaUrlAsync() + public async Task GetJavaUrlAsync(CancellationToken cancellationToken) { - var json = await _httpClient.GetStringAsync(MojangServer.LauncherMeta) - .ConfigureAwait(false); - return parseLauncherMetadata(json); + var res = await _httpClient.GetAsync(MojangServer.LauncherMeta, cancellationToken); + var resStr = await res.Content.ReadAsStringAsync(); + return parseLauncherMetadata(resStr); } private string parseLauncherMetadata(string json) diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index 884cb9d..7cdfef8 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -28,8 +28,7 @@ public string LibraryServer } } - public ValueTask> Extract( - ITaskFactory taskFactory, + public ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, @@ -38,12 +37,11 @@ public ValueTask> Extract( var result = version.Libraries .Where(lib => lib.CheckIsRequired(_side)) .Where(lib => lib.Rules == null || _rulesEvaluator.Match(lib.Rules, rulesContext)) - .SelectMany(lib => createLibraryTasks(taskFactory, path, lib, rulesContext)); - return new ValueTask>(result); + .SelectMany(lib => createLibraryTasks(path, lib, rulesContext)); + return new ValueTask>(result); } - private IEnumerable createLibraryTasks( - ITaskFactory taskFactory, + private IEnumerable createLibraryTasks( MinecraftPath path, MLibrary library, RulesEvaluatorContext rulesContext) @@ -53,15 +51,13 @@ private IEnumerable createLibraryTasks( if (artifact != null) { var libPath = library.GetLibraryPath(); - var file = new TaskFile(library.Name) + yield return new GameFile(library.Name) { Path = Path.Combine(path.Library, libPath), Url = createDownloadUrl(artifact.Url, libPath), Hash = artifact.GetSha1(), Size = artifact.Size }; - - yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } // native library (*.dll, *.so) @@ -71,14 +67,13 @@ private IEnumerable createLibraryTasks( var libPath = library.GetNativeLibraryPath(rulesContext.OS); if (!string.IsNullOrEmpty(libPath)) { - var file = new TaskFile(library.Name) + yield return new GameFile(library.Name) { Path = Path.Combine(path.Library, libPath), Url = createDownloadUrl(native.Url, libPath), Hash = native.GetSha1(), Size = native.Size }; - yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } } } diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index 9c995b6..76fd6d7 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -6,19 +6,17 @@ namespace CmlLib.Core.FileExtractors; public class LogFileExtractor : IFileExtractor { - public ValueTask> Extract( - ITaskFactory taskFactory, + public ValueTask> Extract( MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { - var result = extract(taskFactory, path, version); - return new ValueTask>(result); + var result = extract(path, version); + return new ValueTask>(result); } - private IEnumerable extract( - ITaskFactory taskFactory, + private IEnumerable extract( MinecraftPath path, IVersion version) { @@ -30,15 +28,12 @@ private IEnumerable extract( yield break; var id = version.Logging?.LogFile?.Id ?? version.Id; - - var file = new TaskFile(id) + yield return new GameFile(id) { Path = path.GetLogConfigFilePath(id), Url = url, Hash = version.Logging?.LogFile?.GetSha1(), Size = version.Logging?.LogFile?.Size ?? 0 }; - - yield return new LinkedTaskHead(taskFactory.CheckAndDownload(file), file); } } diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs new file mode 100644 index 0000000..4900148 --- /dev/null +++ b/src/Installers/BasicGameInstaller.cs @@ -0,0 +1,115 @@ +using CmlLib.Core.Internals; +using CmlLib.Core.Tasks; +using System.Diagnostics; + +namespace CmlLib.Core.Installers; + +public class BasicGameInstaller : IGameInstaller +{ + private readonly HttpClient _httpClient; + + public BasicGameInstaller(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async ValueTask Install( + IEnumerable gameFiles, + IProgress? fileProgress, + IProgress? byteProgress, + CancellationToken cancellationToken) + { + int totalTasks = gameFiles.Count(); + int progressedTasks = 0; + long totalBytes = gameFiles.Select(f => f.Size).Sum(); + long progressedBytes = 0; + + foreach (var gameFile in gameFiles) + { + fileProgress?.Report(new InstallerProgressChangedEventArgs + { + TotalTasks = totalTasks, + ProgressedTasks = progressedTasks, + Name = gameFile.Name + }); + + if (needUpdate(gameFile)) + { + long lastTotal = gameFile.Size; + long lastProgressed = 0; + + var progressIntercepter = new SyncProgress(p => + { + totalBytes += p.TotalBytes - lastTotal; + progressedBytes += p.ProgressedBytes - lastProgressed; + lastTotal = p.TotalBytes; + lastProgressed = p.ProgressedBytes; + + byteProgress?.Report(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + }); + + await download(gameFile, progressIntercepter, cancellationToken); + await gameFile.ExecuteUpdateTask(cancellationToken); + } + + progressedTasks++; + } + + fileProgress?.Report(new InstallerProgressChangedEventArgs + { + TotalTasks = totalTasks, + ProgressedTasks = totalTasks, + Name = gameFiles.LastOrDefault()?.Name + }); + } + + private bool needUpdate(GameFile file) + { + if (string.IsNullOrEmpty(file.Path)) + return false; + + if (!File.Exists(file.Path)) + return true; + + if (string.IsNullOrEmpty(file.Hash)) + return false; + + var realHash = IOUtil.ComputeFileSHA1(file.Path); + return realHash != file.Hash; + } + + private async Task download( + GameFile file, + IProgress? progress, + CancellationToken cancellationToken) + { + Debug.Assert(!string.IsNullOrEmpty(file.Path)); + if (string.IsNullOrEmpty(file.Url)) + return; + + for (int i = 3; i > 0; i--) + { + try + { + await HttpClientDownloadHelper.DownloadFileAsync( + _httpClient, + file.Url, + file.Size, + file.Path, + progress, + cancellationToken); + break; + } + catch (Exception) + { + if (i == 1) + throw; + await Task.Delay(3000); + } + } + } +} \ No newline at end of file diff --git a/src/Installers/IGameInstaller.cs b/src/Installers/IGameInstaller.cs index 4324451..e5a93e0 100644 --- a/src/Installers/IGameInstaller.cs +++ b/src/Installers/IGameInstaller.cs @@ -8,11 +8,7 @@ namespace CmlLib.Core.Installers; public interface IGameInstaller { ValueTask Install( - ITaskFactory taskFactory, - IEnumerable extractors, - MinecraftPath path, - IVersion version, - RulesEvaluatorContext rulesContext, + IEnumerable gameFiles, IProgress? fileProgress, IProgress? byteProgress, CancellationToken cancellationToken); diff --git a/src/Installers/InstallerProgressChangedEventArgs.cs b/src/Installers/InstallerProgressChangedEventArgs.cs index 2f6081b..f8ab59c 100644 --- a/src/Installers/InstallerProgressChangedEventArgs.cs +++ b/src/Installers/InstallerProgressChangedEventArgs.cs @@ -2,11 +2,7 @@ namespace CmlLib.Core.Installers; public struct InstallerProgressChangedEventArgs { - public InstallerProgressChangedEventArgs(string name, TaskStatus status) => - (Name, EventType) = (name, status); - - public int TotalTasks { get; set; } = 0; - public int ProgressedTasks { get; set; } = 0; - public TaskStatus EventType { get; } - public string Name { get; } + public int TotalTasks; + public int ProgressedTasks; + public string? Name; } \ No newline at end of file diff --git a/src/Installers/TPLGameInstaller.cs b/src/Installers/TPLGameInstaller.cs deleted file mode 100644 index 1c3581c..0000000 --- a/src/Installers/TPLGameInstaller.cs +++ /dev/null @@ -1,270 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Version; -using System.Collections.Concurrent; -using System.Threading.Tasks.Dataflow; - -namespace CmlLib.Core.Installers; - -public class TPLGameInstaller : IGameInstaller -{ - private static int? _bestMaxParallelism; - public static int BestMaxParallelism => _bestMaxParallelism ??= getBestMaxParallelism(); - public static int getBestMaxParallelism() - { - // 2 <= p <= 6 - var p = Environment.ProcessorCount; - p = Math.Max(p, 2); - p = Math.Min(p, 6); - return p; - } - - private readonly int _maxParallelism; - - public TPLGameInstaller(int parallelism) => - _maxParallelism = parallelism; - - public async ValueTask Install( - ITaskFactory taskFactory, - IEnumerable extractors, - MinecraftPath path, - IVersion version, - RulesEvaluatorContext rulesContext, - IProgress? fileProgress, - IProgress? byteProgress, - CancellationToken cancellationToken) - { - var executor = new TPLGameInstallerExecutor(_maxParallelism, taskFactory, path, version, rulesContext); - executor.FileProgress += (s, e) => fileProgress?.Report(e); - executor.ByteProgress += (s, e) => byteProgress?.Report(e); - await executor.Install(extractors, cancellationToken); - } -} - -public enum GameInstallerExceptionMode -{ - Ignore, - ThrowException, - ThrowAggreateException -} - -class TPLGameInstallerExecutor -{ - private readonly int _maxParallelism; - private readonly ITaskFactory _taskFactory; - private readonly MinecraftPath _path; - private readonly IVersion _version; - private readonly RulesEvaluatorContext _rulesContext; - - public TPLGameInstallerExecutor( - int parallelism, - ITaskFactory taskFactory, - MinecraftPath path, - IVersion version, - RulesEvaluatorContext rulesContext) => - (_maxParallelism, _taskFactory, _path, _version, _rulesContext) = - (parallelism, taskFactory, path, version, rulesContext); - - public event EventHandler? FileProgress; - public event EventHandler? ByteProgress; - - public GameInstallerExceptionMode ExceptionMode { get; set; } = GameInstallerExceptionMode.ThrowAggreateException; - - private ThreadLocal progressStorage = null!; - private bool isStarted = false; - private CancellationToken CancellationToken; - private int totalTasks = 0; - private int proceed = 0; - private ConcurrentBag exceptions = null!; - - public async ValueTask Install( - IEnumerable extractors, - CancellationToken cancellationToken) - { - if (isStarted) - throw new InvalidOperationException("Already started"); - - isStarted = true; - initializeResources(); - CancellationToken = cancellationToken; - - var distinctStorage = new HashSet(); - var extractorBlock = createExtractBlock(distinctStorage, cancellationToken); - var executeBlock = createExecuteBlock(extractorBlock.Completion); - extractorBlock.LinkTo(executeBlock!, t => t != null); - extractorBlock.LinkTo(DataflowBlock.NullTarget()); - - await Task.WhenAll(extractors.Select(extractor => extractorBlock.SendAsync(extractor))); - extractorBlock.Complete(); - await extractorBlock.Completion; - - distinctStorage.Clear(); - - if (proceed != totalTasks) - { - var executeTask = executeBlock.Completion; - while (!executeTask.IsCompleted) - { - reportByteProgress(); - await Task.WhenAny(Task.Delay(200), executeTask); - } - } - - reportByteProgress(); - disposeResources(); - - if (!exceptions.IsEmpty && - ExceptionMode == GameInstallerExceptionMode.ThrowAggreateException) - { - throw new AggregateException(exceptions); - } - } - - private void initializeResources() - { - progressStorage = new ThreadLocal(() => new ByteProgress(), true); - exceptions = new ConcurrentBag(); - totalTasks = 0; - proceed = 0; - } - - private void disposeResources() - { - progressStorage.Dispose(); - progressStorage = null!; - } - - private void reportByteProgress() - { - long totalBytes = 0; - long progressedBytes = 0; - - foreach (var v in progressStorage.Values) - { - totalBytes += v.TotalBytes; - progressedBytes += v.ProgressedBytes; - } - fireByteProgress(totalBytes, progressedBytes); - } - - private BufferBlock createExecuteBlock(Task extractTask) - { - var buffer = new BufferBlock(); - var executor = createExecuteTransformBlock(extractTask, buffer, _maxParallelism); - buffer.LinkTo(executor); - executor.LinkTo(buffer!, t => t != null); - executor.LinkTo(DataflowBlock.NullTarget()); - - return buffer; - } - - private TransformBlock createExecuteTransformBlock(Task extractTask, IDataflowBlock buffer, int parallelism) - { - return new TransformBlock( - t => execute(t, extractTask, buffer), - new ExecutionDataflowBlockOptions - { - MaxDegreeOfParallelism = parallelism - }); - } - - private async Task execute(LinkedTask task, Task extractTask, IDataflowBlock buffer) - { - ByteProgress lastProgress = new ByteProgress - { - TotalBytes = task.Size, - ProgressedBytes = 0 - }; - - var progress = new SyncProgress(e => - { - progressStorage.Value = new ByteProgress - { - TotalBytes = progressStorage.Value.TotalBytes + (e.TotalBytes - lastProgress.TotalBytes), - ProgressedBytes = progressStorage.Value.ProgressedBytes + (e.ProgressedBytes - lastProgress.ProgressedBytes) - }; - lastProgress = e; - }); - - LinkedTask? nextTask = null; - try - { - await task.Execute(progress, CancellationToken); - } - catch (Exception ex) - { - exceptions.Add(ex); - if (ExceptionMode == GameInstallerExceptionMode.ThrowException) - { - buffer.Complete(); - throw; - } - } - - progress.Report(new ByteProgress - { - TotalBytes = lastProgress.TotalBytes, - ProgressedBytes = lastProgress.TotalBytes - }); - - if (nextTask == null) - { - Interlocked.Increment(ref proceed); - fireFileProgress(task.Name, TaskStatus.Done); - if (totalTasks == proceed && extractTask.IsCompleted) - buffer.Complete(); - } - - return nextTask; - } - - private IPropagatorBlock createExtractBlock( - HashSet distinctStorage, - CancellationToken cancellationToken) - { - var extractorBlock = new TransformManyBlock(async extractor => - { - var tasks = await extractor.Extract(_taskFactory, _path, _version, _rulesContext, cancellationToken); - return tasks - .Where(task => task.First != null) - .Where(task => string.IsNullOrEmpty(task.File.Path) || distinctStorage.Add(task.File.Path)) - .Select(task => - { - totalTasks++; - fireFileProgress(task.Name, TaskStatus.Queued); - progressStorage.Value = new ByteProgress - { - TotalBytes = progressStorage.Value.TotalBytes + task.File.Size, - ProgressedBytes = progressStorage.Value.ProgressedBytes - }; - - return task.First!; - }); - }, new ExecutionDataflowBlockOptions - { - MaxDegreeOfParallelism = 1 - }); - - return extractorBlock; - } - - private void fireFileProgress(string name, TaskStatus status) - { - FileProgress?.Invoke(this, new InstallerProgressChangedEventArgs(name, status) - { - TotalTasks = totalTasks, - ProgressedTasks = proceed - }); - } - - private void fireByteProgress(long totalBytes, long progressedBytes) - { - var progress = new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }; - ByteProgress?.Invoke(this, progress); - } -} \ No newline at end of file diff --git a/src/Installers/TaskState.cs b/src/Installers/TaskState.cs deleted file mode 100644 index 9663323..0000000 --- a/src/Installers/TaskState.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace CmlLib.Core.Installers; - -public enum TaskStatus -{ - Queued, - Processing, - Done -} \ No newline at end of file diff --git a/src/Java/MinecraftJavaManifestResolver.cs b/src/Java/MinecraftJavaManifestResolver.cs index e1f8b96..2af2908 100644 --- a/src/Java/MinecraftJavaManifestResolver.cs +++ b/src/Java/MinecraftJavaManifestResolver.cs @@ -65,7 +65,7 @@ public async Task> GetManifestsForOS( if (components == null) return Enumerable.Empty(); - return enumerateComponents(os, components); + return enumerateComponents(os, components).ToArray(); } private async Task requestJsonManifest() diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 135efd4..a85e223 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -26,14 +26,13 @@ public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) new MojangJsonVersionLoader(httpClient) }; parameters.JavaPathResolver = new MinecraftJavaPathResolver(parameters.MinecraftPath); - parameters.GameInstaller = new TPLGameInstaller(TPLGameInstaller.BestMaxParallelism); + parameters.GameInstaller = new BasicGameInstaller(httpClient); parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); var extractors = DefaultFileExtractors.CreateDefault( parameters.HttpClient, parameters.RulesEvaluator, parameters.JavaPathResolver); parameters.FileExtractors = extractors.ToExtractorCollection(); - parameters.TaskFactory = new Tasks.TaskFactory(httpClient); return parameters; } @@ -58,7 +57,6 @@ public HttpClient HttpClient public IVersionLoader? VersionLoader { get; set; } public IJavaPathResolver? JavaPathResolver { get; set; } public FileExtractorCollection? FileExtractors { get; set; } - public ITaskFactory? TaskFactory { get; set; } public IGameInstaller? GameInstaller { get; set; } public INativeLibraryExtractor? NativeLibraryExtractor { get; set; } public IRulesEvaluator? RulesEvaluator { get; set; } diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index c4c556b..b7ffe31 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -22,7 +22,6 @@ public class MinecraftLauncher public MinecraftPath MinecraftPath { get; } public IVersionLoader VersionLoader { get; } public IJavaPathResolver JavaPathResolver { get; } - public ITaskFactory TaskFactory { get; } public FileExtractorCollection FileExtractors { get; } public IGameInstaller GameInstaller { get; } public INativeLibraryExtractor NativeLibraryExtractor { get; } @@ -56,8 +55,6 @@ public MinecraftLauncher(MinecraftLauncherParameters parameters) ?? throw new ArgumentException(nameof(parameters.VersionLoader) + " was null"); JavaPathResolver = parameters.JavaPathResolver ?? throw new ArgumentException(nameof(parameters.JavaPathResolver) + " was null"); - TaskFactory = parameters.TaskFactory - ?? throw new ArgumentException(nameof(parameters.TaskFactory) + " was null"); FileExtractors = parameters.FileExtractors ?? throw new ArgumentException(nameof(parameters.FileExtractors) + " was null"); GameInstaller = parameters.GameInstaller @@ -94,24 +91,41 @@ public async ValueTask GetVersionAsync(string versionName) } } + public async ValueTask> ExtractFiles( + string versionName, + CancellationToken cancellationToken = default) + { + var version = await GetVersionAsync(versionName); + return await ExtractFiles(version, cancellationToken); + } + + public async ValueTask> ExtractFiles( + IVersion version, + CancellationToken cancellationToken) + { + var files = new List(); + foreach (var extractor in FileExtractors) + { + files.AddRange(await extractor.Extract(MinecraftPath, version, RulesContext, cancellationToken)); + } + return files; + } + public async ValueTask InstallAsync( string versionName, CancellationToken cancellationToken = default) { var version = await GetVersionAsync(versionName); - await InstallAsync(version); + await InstallAsync(version, cancellationToken); } public async ValueTask InstallAsync( IVersion version, CancellationToken cancellationToken = default) { + var files = await ExtractFiles(version, cancellationToken); await GameInstaller.Install( - TaskFactory, - FileExtractors, - MinecraftPath, - version, - RulesContext, + files, _fileProgress, _byteProgress, cancellationToken); diff --git a/src/Tasks/ActionTask.cs b/src/Tasks/ActionTask.cs deleted file mode 100644 index fcdbe27..0000000 --- a/src/Tasks/ActionTask.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public class ActionTask : LinkedTask -{ - private readonly Func> _action; - - public ActionTask(string name, Func> action) - : base(name) => - _action = action; - - protected override async ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - return await _action.Invoke(this); - } -} \ No newline at end of file diff --git a/src/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs index f3badde..cecd7a9 100644 --- a/src/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -3,19 +3,20 @@ namespace CmlLib.Core.Tasks; -public class ChmodTask : LinkedTask +public class ChmodTask : IUpdateTask { - public string Path { get; private set; } + public ChmodTask(int mode) => Mode = mode; - public ChmodTask(string name, string path) : base(name) => - Path = path; + public int Mode { get; } - protected override ValueTask OnExecuted( - IProgress? progress, + public ValueTask Execute( + GameFile file, CancellationToken cancellationToken) { + if (string.IsNullOrEmpty(file.Path)) + throw new InvalidOperationException(); if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) - NativeMethods.Chmod(Path, NativeMethods.Chmod755); - return new ValueTask(NextTask); + NativeMethods.Chmod(file.Path, Mode); + return new ValueTask(); } } \ No newline at end of file diff --git a/src/Tasks/CompositeUpdateTask.cs b/src/Tasks/CompositeUpdateTask.cs new file mode 100644 index 0000000..f851001 --- /dev/null +++ b/src/Tasks/CompositeUpdateTask.cs @@ -0,0 +1,25 @@ + +namespace CmlLib.Core.Tasks; + +public class CompositeUpdateTask : IUpdateTask +{ + public static CompositeUpdateTask Create(params IUpdateTask[] tasks) + { + return new CompositeUpdateTask(tasks); + } + + private readonly IEnumerable _tasks; + + public CompositeUpdateTask(IEnumerable tasks) + { + _tasks = tasks; + } + + public async ValueTask Execute(GameFile file, CancellationToken cancellationToken) + { + foreach (var task in _tasks) + { + await task.Execute(file, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Tasks/DownloadTask.cs b/src/Tasks/DownloadTask.cs deleted file mode 100644 index 1341ed2..0000000 --- a/src/Tasks/DownloadTask.cs +++ /dev/null @@ -1,63 +0,0 @@ -using CmlLib.Core.Installers; - -namespace CmlLib.Core.Tasks; - -public class DownloadTask : LinkedTask -{ - public DownloadTask(TaskFile file, HttpClient httpClient) : base(file) - { - if (string.IsNullOrEmpty(file.Path)) - throw new ArgumentException("file.Path was empty"); - if (string.IsNullOrEmpty(file.Url)) - throw new ArgumentException("file.Url was empty"); - - HttpClient = httpClient; - this.Path = file.Path; - this.Url = file.Url; - this.Size = file.Size; - } - - protected HttpClient HttpClient; - public string Path { get; } - public string Url { get; } - - protected async override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - await DownloadFile(progress, cancellationToken); - return NextTask; - } - - protected async virtual ValueTask DownloadFile( - IProgress? progress, - CancellationToken cancellationToken) - { - var interceptedProgress = new SyncProgress(e => - { - this.Size = e.TotalBytes; - progress?.Report(e); - }); - - for (int i = 3; i > 0; i--) - { - try - { - await HttpClientDownloadHelper.DownloadFileAsync( - HttpClient, - Url, - Size, - Path, - interceptedProgress, - cancellationToken); - break; - } - catch (Exception) - { - if (i == 1) - throw; - await Task.Delay(3000); - } - } - } -} \ No newline at end of file diff --git a/src/Tasks/FileCheckTask.cs b/src/Tasks/FileCheckTask.cs deleted file mode 100644 index cf3d3ce..0000000 --- a/src/Tasks/FileCheckTask.cs +++ /dev/null @@ -1,47 +0,0 @@ -using CmlLib.Core.Internals; - -namespace CmlLib.Core.Tasks; - -public class FileCheckTask : ResultTask -{ - public FileCheckTask(TaskFile file) : base(file) - { - if (string.IsNullOrEmpty(file.Path)) - throw new ArgumentException("file.Path was empty"); - this.Path = file.Path; - this.Hash = file.Hash; - } - - public FileCheckTask(string name, string path, string? hash) : base(name) - { - this.Path = path; - this.Hash = hash; - } - - public string Path { get; } - public string? Hash { get; } - - protected override ValueTask OnExecutedWithResult( - IProgress? progress, - CancellationToken cancellationToken) - { - var result = checkFile(); - return new ValueTask(result); - } - - private bool checkFile() - { - if (File.Exists(Path)) - { - if (string.IsNullOrEmpty(Hash)) - return true; - else - { - var realHash = IOUtil.ComputeFileSHA1(Path); - return realHash == Hash; - } - } - else - return false; - } -} \ No newline at end of file diff --git a/src/Tasks/FileCopyTask.cs b/src/Tasks/FileCopyTask.cs index a5bcb12..db369eb 100644 --- a/src/Tasks/FileCopyTask.cs +++ b/src/Tasks/FileCopyTask.cs @@ -2,22 +2,19 @@ namespace CmlLib.Core.Tasks; -public class FileCopyTask : LinkedTask +public class FileCopyTask : IUpdateTask { - public FileCopyTask(string name, string sourcePath, IEnumerable destPaths) : base(name) => - (SourcePath, DestinationPaths) = (sourcePath, destPaths.ToArray()); + public FileCopyTask(IEnumerable destPaths) => + DestinationPaths = destPaths.ToArray(); - public string SourcePath { get; } public string[] DestinationPaths { get; } - protected override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) + public ValueTask Execute(GameFile file, CancellationToken cancellationToken) { - if (!File.Exists(SourcePath)) + if (string.IsNullOrEmpty(file.Path) || !File.Exists(file.Path)) throw new InvalidOperationException("The source file does not exists"); - var orgFile = new FileInfo(SourcePath); + var orgFile = new FileInfo(file.Path); foreach (var destination in DestinationPaths) { cancellationToken.ThrowIfCancellationRequested(); @@ -30,6 +27,6 @@ public FileCopyTask(string name, string sourcePath, IEnumerable destPath } } - return new ValueTask(NextTask); + return new ValueTask(); } } \ No newline at end of file diff --git a/src/Tasks/GameFile.cs b/src/Tasks/GameFile.cs new file mode 100644 index 0000000..5bbfaf7 --- /dev/null +++ b/src/Tasks/GameFile.cs @@ -0,0 +1,20 @@ +namespace CmlLib.Core.Tasks; + +public class GameFile +{ + public GameFile(string name) => + Name = name; + + public string Name { get; } + public string? Path { get; set; } + public string? Hash { get; set; } + public string? Url { get; set; } + public long Size { get; set; } + public IUpdateTask? UpdateTask { get; set; } + + public async ValueTask ExecuteUpdateTask(CancellationToken cancellationToken) + { + if (UpdateTask != null) + await UpdateTask.Execute(this, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tasks/ITaskFactory.cs b/src/Tasks/ITaskFactory.cs deleted file mode 100644 index 2a6dd10..0000000 --- a/src/Tasks/ITaskFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public interface ITaskFactory -{ - LinkedTask CheckFile(TaskFile file, LinkedTask onSuccess, LinkedTask onFail); - LinkedTask Download(TaskFile file); - LinkedTask CheckAndDownload(TaskFile file); - LinkedTask ReportDone(TaskFile file); -} \ No newline at end of file diff --git a/src/Tasks/IUpdateTask.cs b/src/Tasks/IUpdateTask.cs new file mode 100644 index 0000000..de2ad78 --- /dev/null +++ b/src/Tasks/IUpdateTask.cs @@ -0,0 +1,6 @@ +namespace CmlLib.Core.Tasks; + +public interface IUpdateTask +{ + ValueTask Execute(GameFile file, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Tasks/LZMADecompressTask.cs b/src/Tasks/LZMADecompressTask.cs deleted file mode 100644 index c511e2a..0000000 --- a/src/Tasks/LZMADecompressTask.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CmlLib.Core.Internals; - -namespace CmlLib.Core.Tasks; - -public class LZMADecompressTask : LinkedTask -{ - public string LZMAPath { get; set; } - public string ExtractPath { get; set; } - - public LZMADecompressTask(string name, string lzmaPath, string extractTo) : base(name) - { - LZMAPath = lzmaPath; - ExtractPath = extractTo; - } - - protected override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - SevenZipWrapper.DecompressFileLZMA(LZMAPath, ExtractPath); - return new ValueTask(); - } -} \ No newline at end of file diff --git a/src/Tasks/LegacyJavaExtractionTask.cs b/src/Tasks/LegacyJavaExtractionTask.cs new file mode 100644 index 0000000..8c5a5cb --- /dev/null +++ b/src/Tasks/LegacyJavaExtractionTask.cs @@ -0,0 +1,25 @@ +using CmlLib.Core.Internals; + +namespace CmlLib.Core.Tasks; + +public class LegacyJavaExtractionTask : IUpdateTask +{ + public LegacyJavaExtractionTask(string extractTo) + { + ExtractTo = extractTo; + } + + public string ExtractTo { get; } + + public ValueTask Execute(GameFile file, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(file.Path)) + throw new ArgumentException(); + + var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); + SevenZipWrapper.DecompressFileLZMA(file.Path, zipPath); + SharpZipWrapper.Unzip(zipPath, ExtractTo, null, cancellationToken); + + return new ValueTask(); + } +} \ No newline at end of file diff --git a/src/Tasks/LinkedTask.cs b/src/Tasks/LinkedTask.cs deleted file mode 100644 index c089927..0000000 --- a/src/Tasks/LinkedTask.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public abstract class LinkedTask -{ - public LinkedTask(TaskFile file) - { - if (string.IsNullOrEmpty(file.Name)) - throw new ArgumentException("file.Name was empty"); - this.Name = file.Name; - this.Size = file.Size; - } - - public LinkedTask(string name) - { - this.Name = name; - } - - public string Name { get; set; } - public long Size { get; protected set; } - public LinkedTask? NextTask { get; private set; } - - public async ValueTask Execute( - IProgress? progress, - CancellationToken cancellationToken) - { - var nextTask = await OnExecuted(progress, cancellationToken); - if (nextTask != null) - { - if (nextTask.Name != this.Name) - throw new InvalidOperationException("Name should be same"); - } - - return nextTask; - } - - protected abstract ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken); - - public LinkedTask InsertNextTask(LinkedTask task) - { - if (NextTask == null) - { - NextTask = task; - } - else - { - task.NextTask = this.NextTask; - this.NextTask = task; - } - return NextTask; - } - - public static LinkedTask? LinkTasks(params LinkedTask[] tasks) - { - return internalLinkTasks(tasks); - } - - public static LinkedTask? LinkTasks(IEnumerable tasks) => - internalLinkTasks(tasks); - - private static LinkedTask? internalLinkTasks(IEnumerable tasks) - { - var firstTask = tasks.FirstOrDefault(); - if (firstTask == null) - return null; - - var nextTask = firstTask; - foreach (var task in tasks.Skip(1)) - { - nextTask.InsertNextTask(task); - nextTask = task; - } - - return firstTask; - } -} \ No newline at end of file diff --git a/src/Tasks/LinkedTaskBuilder.cs b/src/Tasks/LinkedTaskBuilder.cs deleted file mode 100644 index cdf6561..0000000 --- a/src/Tasks/LinkedTaskBuilder.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Diagnostics; - -namespace CmlLib.Core.Tasks; - -public class LinkedTaskBuilder -{ - private readonly ITaskFactory _taskFactory; - - public TaskFile TaskFile { get; private set; } - public LinkedTask? Task { get; private set; } - private LinkedTask? Tail { get; set; } - public bool IsReadOnly { get; private set; } - - private LinkedTaskBuilder( - TaskFile taskFile, - ITaskFactory taskFactory, - LinkedTask? head, - LinkedTask? tail, - bool isReadOnly) - { - TaskFile = taskFile; - _taskFactory = taskFactory; - Task = head; - Tail = tail; - IsReadOnly = isReadOnly; - } - - public static LinkedTaskBuilder Create(TaskFile taskFile, ITaskFactory taskFactory) => - new LinkedTaskBuilder(taskFile, taskFactory, null, null, false); - - public static LinkedTaskBuilder CreateReadOnly(TaskFile taskFile, ITaskFactory taskFactory, LinkedTask? head) => - new LinkedTaskBuilder(taskFile, taskFactory, head, null, true); - - public LinkedTaskBuilder Then(LinkedTask task) - { - if (IsReadOnly) - return this; - - if (Task == null) - { - Task = task; - Tail = task; - } - else - { - Debug.Assert(Tail != null); - - Tail.InsertNextTask(task); - Tail = task; - } - - return this; - } - - public LinkedTaskBuilder ThenIf(bool condition) - { - if (condition) - return this; - else - return CreateReadOnly(TaskFile, _taskFactory, Task); - } - - public LinkedTaskBuilder CheckFile(LinkedTask onSuccess, LinkedTask onFail) - { - return Then(_taskFactory.CheckFile(TaskFile, onSuccess, onFail)); - } - - public LinkedTaskBuilder CheckFile( - Func onSuccess, - Func onFail) - { - return CheckFile( - onSuccess.Invoke(Create(TaskFile, _taskFactory)).BuildTask() ?? throw new InvalidOperationException(), - onFail.Invoke(Create(TaskFile, _taskFactory)).BuildTask() ?? throw new InvalidOperationException()); - } - - public LinkedTaskBuilder Download() - { - return Then(_taskFactory.Download(TaskFile)); - } - - public LinkedTaskBuilder ReportDone() - { - return Then(_taskFactory.ReportDone(TaskFile)); - } - - public LinkedTaskBuilder CheckAndDownload() - { - return Then(_taskFactory.CheckAndDownload(TaskFile)); - } - - public LinkedTaskHead BuildHead() - { - if (Task == null) - throw new InvalidOperationException(); - return new LinkedTaskHead(Task, TaskFile); - } - - public LinkedTask BuildTask() - { - if (Task == null) - throw new InvalidOperationException(); - return Task; - } -} \ No newline at end of file diff --git a/src/Tasks/LinkedTaskHead.cs b/src/Tasks/LinkedTaskHead.cs deleted file mode 100644 index 765c3ab..0000000 --- a/src/Tasks/LinkedTaskHead.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public struct LinkedTaskHead -{ - public LinkedTaskHead(LinkedTask? first, TaskFile file) => - (First, File) = (first, file); - - public string Name => File.Name; - public LinkedTask? First { get; } - public TaskFile File { get; } - - public ValueTask Execute( - IProgress? progress = null, - CancellationToken cancellationToken = default) - { - if (First == null) - return new ValueTask(); - - return First.Execute(progress, cancellationToken); - } -} \ No newline at end of file diff --git a/src/Tasks/ProgressTask.cs b/src/Tasks/ProgressTask.cs deleted file mode 100644 index a8a32c2..0000000 --- a/src/Tasks/ProgressTask.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public class ProgressTask : LinkedTask -{ - public static ProgressTask CreateDoneTask(TaskFile file) - { - var task = new ProgressTask(file, new ByteProgress - { - TotalBytes = file.Size, - ProgressedBytes = file.Size - }); - task.Size = file.Size; - return task; - } - - public ProgressTask(TaskFile file, ByteProgress progress) : base(file) => - Progress = progress; - - public ProgressTask(string name, ByteProgress progress) : base(name) => - Progress = progress; - - public ByteProgress Progress { get; } - - protected override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - progress?.Report(Progress); - return new ValueTask(NextTask); - } -} diff --git a/src/Tasks/ResultTask.cs b/src/Tasks/ResultTask.cs deleted file mode 100644 index 6f98c02..0000000 --- a/src/Tasks/ResultTask.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public abstract class ResultTask : LinkedTask -{ - public ResultTask(string name) : base(name) - { - - } - - public ResultTask(TaskFile file) : base(file) - { - - } - - public LinkedTask? OnTrue { get; set; } - public LinkedTask? OnFalse { get; set; } - - protected override async ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - var result = await OnExecutedWithResult(progress, cancellationToken); - var nextTask = result ? OnTrue : OnFalse; - - if (nextTask != null) - return InsertNextTask(nextTask); - else - return NextTask; - } - - protected abstract ValueTask OnExecutedWithResult( - IProgress? progress, - CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/src/Tasks/TaskExecutionContext.cs b/src/Tasks/TaskExecutionContext.cs deleted file mode 100644 index ffdd7bb..0000000 --- a/src/Tasks/TaskExecutionContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public class TaskExecutionContext -{ - public TaskExecutionContext( - IProgress? progress, - CancellationToken cancellationToken) => - (ProgressChanged, CancellationToken) = (progress, cancellationToken); - - public CancellationToken CancellationToken { get; } - public IProgress? ProgressChanged { get; } -} \ No newline at end of file diff --git a/src/Tasks/TaskFactory.cs b/src/Tasks/TaskFactory.cs deleted file mode 100644 index a68f345..0000000 --- a/src/Tasks/TaskFactory.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public class TaskFactory : ITaskFactory -{ - private readonly HttpClient _httpClient; - - public TaskFactory(HttpClient httpClient) - { - _httpClient = httpClient; - } - - public LinkedTask CheckAndDownload(TaskFile file) - { - return CheckFile(file, - onSuccess: ReportDone(file), - onFail: Download(file)); - } - - public LinkedTask CheckFile(TaskFile file, LinkedTask onSuccess, LinkedTask onFail) - { - var task = new FileCheckTask(file); - task.OnTrue = onSuccess; - task.OnFalse = onFail; - return task; - } - - public LinkedTask Download(TaskFile file) - { - return new DownloadTask(file, _httpClient); - } - - public LinkedTask ReportDone(TaskFile file) - { - return ProgressTask.CreateDoneTask(file); - } -} \ No newline at end of file diff --git a/src/Tasks/TaskFile.cs b/src/Tasks/TaskFile.cs deleted file mode 100644 index d330e55..0000000 --- a/src/Tasks/TaskFile.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CmlLib.Core.Tasks; - -public struct TaskFile -{ - public TaskFile(string name) => - Name = name; - - public string Name { get; } - public string? Path { get; set; } = null; - public string? Hash { get; set; } = null; - public string? Url { get; set; } = null; - public long Size { get; set; } = 0; -} \ No newline at end of file diff --git a/src/Tasks/UnzipTask.cs b/src/Tasks/UnzipTask.cs deleted file mode 100644 index c4d3a47..0000000 --- a/src/Tasks/UnzipTask.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CmlLib.Core.Internals; - -namespace CmlLib.Core.Tasks; - -public class UnzipTask : LinkedTask -{ - public string ZipPath { get; set; } - public string ExtractTo { get; set; } - - public UnzipTask(string name, string zipPath, string unzipTo) : base(name) - { - ZipPath = zipPath; - ExtractTo = unzipTo; - } - - protected override ValueTask OnExecuted( - IProgress? progress, - CancellationToken cancellationToken) - { - SharpZipWrapper.Unzip(ZipPath, ExtractTo, null, cancellationToken); - return new ValueTask(NextTask); - } -} \ No newline at end of file diff --git a/test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj index 4560b2e..97543f0 100644 --- a/test/CmlLib.Core.Test.csproj +++ b/test/CmlLib.Core.Test.csproj @@ -27,5 +27,9 @@ + + + + diff --git a/test/Installers/TPLGameInstallerTest.cs b/test/Installers/TPLGameInstallerTest.cs deleted file mode 100644 index 9772688..0000000 --- a/test/Installers/TPLGameInstallerTest.cs +++ /dev/null @@ -1,197 +0,0 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Installers; -using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; -using CmlLib.Core.Test.Tasks; -using CmlLib.Core.Version; -using Moq; -using NUnit.Framework; - -namespace CmlLib.Core.Test.Installers; - -public class TPLGameInstallerTest -{ - private const int TaskCount = 128; - private const int ExtractorCount = 4; - private const long TaskSize = 128 * 32; - private readonly ITaskFactory TestTaskFactory = new CmlLib.Core.Tasks.TaskFactory(new HttpClient()); - private readonly RulesEvaluatorContext TestRulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - private readonly MinecraftPath TestMinecraftPath = new Mock().Object; - private readonly IVersion TestVersion = new Mock().Object; - - [Test] - public async Task TestAllTaskExecuted() - { - var mocks = createMockTasks(); - var extractors = createMockExtractors(mocks); - - var installer = createInstaller(); - await installer.Install( - TestTaskFactory, - extractors, - TestMinecraftPath, - TestVersion, - TestRulesContext, - null, - null, - default); - - assertAllMockTasks(mocks); - } - - [Test] - public async Task TestFileProgressReachesTo100() - { - var mocks = createMockTasks(); - var extractors = createMockExtractors(mocks); - - int totalTasks = 0; - int progressedTasks = 0; - var fileProgress = new SyncProgress(e => - { - totalTasks = Math.Max(totalTasks, e.TotalTasks); - progressedTasks = Math.Max(progressedTasks, e.ProgressedTasks); - }); - - var installer = createInstaller(); - await installer.Install( - TestTaskFactory, - extractors, - TestMinecraftPath, - TestVersion, - TestRulesContext, - fileProgress, - null, - default - ); - - assertAllMockTasks(mocks); - Assert.That(totalTasks, Is.EqualTo(totalTasks), "TotalTasks"); - Assert.That(progressedTasks, Is.EqualTo(totalTasks), "ProgressedTasks"); - } - - [Test] - public async Task TestByteProgressReachesTo100() - { - var mocks = createMockDownloadTasks(); - var extractors = createMockExtractors(mocks); - - long totalBytes = 0; - long progressedBytes = 0; - var byteProgress = new SyncProgress(e => - { - totalBytes = Math.Max(totalBytes, e.TotalBytes); - progressedBytes = Math.Max(progressedBytes, e.ProgressedBytes); - }); - - var installer = createInstaller(); - await installer.Install( - TestTaskFactory, - extractors, - TestMinecraftPath, - TestVersion, - TestRulesContext, - null, - byteProgress, - default - ); - - assertAllMockTasks(mocks); - Assert.That(totalBytes, Is.EqualTo(TaskCount * TaskSize), "TotalBytes"); - Assert.That(progressedBytes, Is.EqualTo(totalBytes), - "ProgressedBytes, d: " + (totalBytes - progressedBytes) / (double)TaskSize); - } - - [Test] - public async Task TestFilterDuplicatedFile() - { - var divide = 2; - var mockTasks = Enumerable.Range(0, TaskCount) - .Select(i => - { - var setId = i / (TaskCount / divide); - return new MockTask(new TaskFile($"{i} {setId}") - { - Path = setId.ToString() - }); - }) - .ToArray(); - - var extractors = createMockExtractors(mockTasks); - - var installer = createInstaller(); - await installer.Install( - TestTaskFactory, - extractors, - TestMinecraftPath, - TestVersion, - TestRulesContext, - null, - null, - default - ); - - var set = new HashSet(); - foreach (var task in mockTasks) - { - var setId = task.Name.Split()[1]; - if (task.IsExecuted && !set.Add(setId)) - Assert.Fail("duplicated tasks are executed: " + setId); - } - - Assert.That(set.Count, Is.EqualTo(divide), "not all tasks are executed"); - } - - [Test] - public void TestExceptionMode() - { - - } - - private MockTask[] createMockTasks() => - Enumerable.Range(0, TaskCount) - .Select(i => new MockTask(new TaskFile(i.ToString()))) - .ToArray(); - - private MockDownloadTask[] createMockDownloadTasks() => - Enumerable.Range(0, TaskCount) - .Select(i => new MockDownloadTask(new TaskFile(i.ToString()) - { - Size = TaskSize, - })) - .ToArray(); - - private IEnumerable createMockExtractors(IEnumerable tasks) => tasks - .Select(task => new LinkedTaskHead(task, task.File)) - .Chunk(TaskCount / ExtractorCount) - .Select(chunkedTasks => - { - var mockExtractor = new Mock(); - mockExtractor.Setup(e => e.Extract( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - default).Result) - .Returns(chunkedTasks); - return mockExtractor.Object; - }); - - private void assertAllMockTasks(IEnumerable mocks) - { - var list = new List(); - foreach (var mock in mocks) - { - if (!mock.IsExecuted) - list.Add(mock.Name); - } - - if (list.Any()) - Assert.Fail("Find tasks not executed: " + string.Join(',', list)); - } - - public IGameInstaller createInstaller() - { - return new TPLGameInstaller(1); - } -} \ No newline at end of file diff --git a/test/Tasks/MockDownloadTask.cs b/test/Tasks/MockDownloadTask.cs deleted file mode 100644 index f046159..0000000 --- a/test/Tasks/MockDownloadTask.cs +++ /dev/null @@ -1,26 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Test.Tasks; - -public class MockDownloadTask : MockTask -{ - public MockDownloadTask(TaskFile file) : base(file) - { - TotalEventSize = file.Size; - } - - public long TotalEventSize { get; set; } - - protected override ValueTask OnExecuted(IProgress? progress, CancellationToken cancellationToken) - { - for (int i = 0; i <= TotalEventSize; i += 128) - { - progress?.Report(new ByteProgress - { - TotalBytes = Size, - ProgressedBytes = i - }); - } - return base.OnExecuted(progress, cancellationToken); - } -} \ No newline at end of file diff --git a/test/Tasks/MockResultTask.cs b/test/Tasks/MockResultTask.cs deleted file mode 100644 index a12fb0d..0000000 --- a/test/Tasks/MockResultTask.cs +++ /dev/null @@ -1,17 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Test.Tasks; - -public class MockResultTask : ResultTask -{ - public bool Result { get; set; } - - public MockResultTask(string name) : base(name) - { - } - - protected override ValueTask OnExecutedWithResult(IProgress? progress, CancellationToken cancellationToken) - { - return new ValueTask(Result); - } -} \ No newline at end of file diff --git a/test/Tasks/MockTask.cs b/test/Tasks/MockTask.cs deleted file mode 100644 index 7b190f8..0000000 --- a/test/Tasks/MockTask.cs +++ /dev/null @@ -1,20 +0,0 @@ -using CmlLib.Core.Tasks; - -namespace CmlLib.Core.Test.Tasks; - -public class MockTask : LinkedTask -{ - public bool IsExecuted { get; private set; } - public TaskFile File { get; } - - public MockTask(TaskFile file) : base(file) - { - File = file; - } - - protected override ValueTask OnExecuted(IProgress? progress, CancellationToken cancellationToken) - { - IsExecuted = true; - return new ValueTask(NextTask); - } -} \ No newline at end of file diff --git a/test/Tasks/ResultTaskTest.cs b/test/Tasks/ResultTaskTest.cs deleted file mode 100644 index ee0d24c..0000000 --- a/test/Tasks/ResultTaskTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -using CmlLib.Core.Tasks; -using NUnit.Framework; - -namespace CmlLib.Core.Test.Tasks; - -public class ResultTaskTest -{ - public void TestTrue() - { - var (task, onTrue, onFalse, next) = createTasks(); - - task.Result = true; - task.OnTrue = onTrue; - task.OnFalse = onFalse; - task.InsertNextTask(next); - - Assert.True(onTrue.IsExecuted); - Assert.False(onFalse.IsExecuted); - Assert.True(next.IsExecuted); - } - - public void TestFalse() - { - var (task, onTrue, onFalse, next) = createTasks(); - - task.Result = false; - task.OnTrue = onTrue; - task.OnFalse = onFalse; - task.InsertNextTask(next); - - Assert.False(onTrue.IsExecuted); - Assert.True(onFalse.IsExecuted); - Assert.True(next.IsExecuted); - } - - public void TestNullTrue() - { - var (task, onTrue, onFalse, next) = createTasks(); - - task.Result = true; - task.OnTrue = null; - task.OnFalse = onFalse; - task.InsertNextTask(next); - - Assert.False(onFalse.IsExecuted); - Assert.True(next.IsExecuted); - } - - public void TestNullFalse() - { - var (task, onTrue, onFalse, next) = createTasks(); - - task.Result = false; - task.OnTrue = onTrue; - task.OnFalse = null; - task.InsertNextTask(next); - - Assert.False(onTrue.IsExecuted); - Assert.True(next.IsExecuted); - } - - public void TestNull() - { - var (task, onTrue, onFalse, next) = createTasks(); - task.Result = true; - task.OnTrue = null; - task.OnFalse = null; - task.InsertNextTask(next); - - Assert.True(next.IsExecuted); - } - - private (MockResultTask, MockTask, MockTask, MockTask) createTasks() - { - var task = new MockResultTask("task"); - var onTrue = new MockTask(new TaskFile("OnTrue")); - var onFalse = new MockTask(new TaskFile("OnFalse")); - var next = new MockTask(new TaskFile("Next")); - - return (task, onTrue, onFalse, next); - } -} \ No newline at end of file From 427a911404efd69d3bda2f44eeb56866e9fba90a Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 8 Feb 2024 23:51:39 +0900 Subject: [PATCH 077/137] feat: IEnumerable to IReadOnlyList --- src/Installers/BasicGameInstaller.cs | 38 +++++++++---------- src/Installers/IGameInstaller.cs | 5 +-- .../InstallerProgressChangedEventArgs.cs | 2 +- src/MinecraftLauncher.cs | 4 +- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 4900148..9c077a2 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -14,30 +14,23 @@ public BasicGameInstaller(HttpClient httpClient) } public async ValueTask Install( - IEnumerable gameFiles, + IReadOnlyList gameFiles, IProgress? fileProgress, IProgress? byteProgress, CancellationToken cancellationToken) { - int totalTasks = gameFiles.Count(); - int progressedTasks = 0; long totalBytes = gameFiles.Select(f => f.Size).Sum(); long progressedBytes = 0; - foreach (var gameFile in gameFiles) + for (int i = 0; i < gameFiles.Count; i++) { - fileProgress?.Report(new InstallerProgressChangedEventArgs - { - TotalTasks = totalTasks, - ProgressedTasks = progressedTasks, - Name = gameFile.Name - }); + var gameFile = gameFiles[i]; + fireFileProgress(fileProgress, gameFiles.Count, i + 1, gameFile.Name); if (needUpdate(gameFile)) { long lastTotal = gameFile.Size; long lastProgressed = 0; - var progressIntercepter = new SyncProgress(p => { totalBytes += p.TotalBytes - lastTotal; @@ -55,16 +48,9 @@ public async ValueTask Install( await download(gameFile, progressIntercepter, cancellationToken); await gameFile.ExecuteUpdateTask(cancellationToken); } - - progressedTasks++; } - fileProgress?.Report(new InstallerProgressChangedEventArgs - { - TotalTasks = totalTasks, - ProgressedTasks = totalTasks, - Name = gameFiles.LastOrDefault()?.Name - }); + fireFileProgress(fileProgress, gameFiles.Count, gameFiles.Count, gameFiles.LastOrDefault()?.Name); } private bool needUpdate(GameFile file) @@ -112,4 +98,18 @@ await HttpClientDownloadHelper.DownloadFileAsync( } } } + + private void fireFileProgress( + IProgress? progress, + int totalTasks, + int progressedTasks, + string? name) + { + progress?.Report(new InstallerProgressChangedEventArgs + { + TotalTasks = totalTasks, + ProgressedTasks = progressedTasks, + Name = name + }); + } } \ No newline at end of file diff --git a/src/Installers/IGameInstaller.cs b/src/Installers/IGameInstaller.cs index e5a93e0..73beda8 100644 --- a/src/Installers/IGameInstaller.cs +++ b/src/Installers/IGameInstaller.cs @@ -1,14 +1,11 @@ -using CmlLib.Core.FileExtractors; -using CmlLib.Core.Rules; using CmlLib.Core.Tasks; -using CmlLib.Core.Version; namespace CmlLib.Core.Installers; public interface IGameInstaller { ValueTask Install( - IEnumerable gameFiles, + IReadOnlyList gameFiles, IProgress? fileProgress, IProgress? byteProgress, CancellationToken cancellationToken); diff --git a/src/Installers/InstallerProgressChangedEventArgs.cs b/src/Installers/InstallerProgressChangedEventArgs.cs index f8ab59c..eb82446 100644 --- a/src/Installers/InstallerProgressChangedEventArgs.cs +++ b/src/Installers/InstallerProgressChangedEventArgs.cs @@ -1,6 +1,6 @@ namespace CmlLib.Core.Installers; -public struct InstallerProgressChangedEventArgs +public class InstallerProgressChangedEventArgs { public int TotalTasks; public int ProgressedTasks; diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index b7ffe31..c2409c5 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -91,7 +91,7 @@ public async ValueTask GetVersionAsync(string versionName) } } - public async ValueTask> ExtractFiles( + public async ValueTask> ExtractFiles( string versionName, CancellationToken cancellationToken = default) { @@ -99,7 +99,7 @@ public async ValueTask> ExtractFiles( return await ExtractFiles(version, cancellationToken); } - public async ValueTask> ExtractFiles( + public async ValueTask> ExtractFiles( IVersion version, CancellationToken cancellationToken) { From 6c69df1478455861ef4f62727c82d49aac30db98 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 9 Feb 2024 20:31:03 +0900 Subject: [PATCH 078/137] feat: add GameInstallerBase and EventType --- examples/console/Program.cs | 2 +- src/Installers/BasicGameInstaller.cs | 94 ++-------------- src/Installers/GameInstallerBase.cs | 106 ++++++++++++++++++ .../InstallerProgressChangedEventArgs.cs | 25 ++++- 4 files changed, 141 insertions(+), 86 deletions(-) create mode 100644 src/Installers/GameInstallerBase.cs diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 26a0e0b..815cdad 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -91,7 +91,7 @@ private void Launcher_FileProgressChanged(object? sender, InstallerProgressChang if (previousProceed > e.ProgressedTasks) return; - var msg = $"[{e.ProgressedTasks} / {e.TotalTasks}] {e.Name}"; + var msg = $"[{e.ProgressedTasks} / {e.TotalTasks}][{e.EventType}] {e.Name}"; Console.WriteLine(msg.PadRight(lastCursorLeft)); printBottomProgress(); diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 9c077a2..1806b2c 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -1,33 +1,27 @@ -using CmlLib.Core.Internals; -using CmlLib.Core.Tasks; -using System.Diagnostics; +using CmlLib.Core.Tasks; namespace CmlLib.Core.Installers; -public class BasicGameInstaller : IGameInstaller +public class BasicGameInstaller : GameInstallerBase { - private readonly HttpClient _httpClient; - - public BasicGameInstaller(HttpClient httpClient) + public BasicGameInstaller(HttpClient httpClient) : base(httpClient) { - _httpClient = httpClient; + } - public async ValueTask Install( - IReadOnlyList gameFiles, - IProgress? fileProgress, - IProgress? byteProgress, + protected override async ValueTask Install( + IReadOnlyList gameFiles, CancellationToken cancellationToken) { long totalBytes = gameFiles.Select(f => f.Size).Sum(); long progressedBytes = 0; - for (int i = 0; i < gameFiles.Count; i++) + for (int i = 0; i < gameFiles.Count; i++) { var gameFile = gameFiles[i]; - fireFileProgress(fileProgress, gameFiles.Count, i + 1, gameFile.Name); + FireFileProgress(gameFiles.Count, i, gameFile.Name, InstallerEventType.Queued); - if (needUpdate(gameFile)) + if (NeedUpdate(gameFile)) { long lastTotal = gameFile.Size; long lastProgressed = 0; @@ -38,78 +32,14 @@ public async ValueTask Install( lastTotal = p.TotalBytes; lastProgressed = p.ProgressedBytes; - byteProgress?.Report(new ByteProgress - { - TotalBytes = totalBytes, - ProgressedBytes = progressedBytes - }); + FireByteProgress(totalBytes, progressedBytes); }); - await download(gameFile, progressIntercepter, cancellationToken); + await Download(gameFile, progressIntercepter, cancellationToken); await gameFile.ExecuteUpdateTask(cancellationToken); } - } - - fireFileProgress(fileProgress, gameFiles.Count, gameFiles.Count, gameFiles.LastOrDefault()?.Name); - } - - private bool needUpdate(GameFile file) - { - if (string.IsNullOrEmpty(file.Path)) - return false; - - if (!File.Exists(file.Path)) - return true; - - if (string.IsNullOrEmpty(file.Hash)) - return false; - - var realHash = IOUtil.ComputeFileSHA1(file.Path); - return realHash != file.Hash; - } - - private async Task download( - GameFile file, - IProgress? progress, - CancellationToken cancellationToken) - { - Debug.Assert(!string.IsNullOrEmpty(file.Path)); - if (string.IsNullOrEmpty(file.Url)) - return; - for (int i = 3; i > 0; i--) - { - try - { - await HttpClientDownloadHelper.DownloadFileAsync( - _httpClient, - file.Url, - file.Size, - file.Path, - progress, - cancellationToken); - break; - } - catch (Exception) - { - if (i == 1) - throw; - await Task.Delay(3000); - } + FireFileProgress(gameFiles.Count, i + 1, gameFile.Name, InstallerEventType.Done); } } - - private void fireFileProgress( - IProgress? progress, - int totalTasks, - int progressedTasks, - string? name) - { - progress?.Report(new InstallerProgressChangedEventArgs - { - TotalTasks = totalTasks, - ProgressedTasks = progressedTasks, - Name = name - }); - } } \ No newline at end of file diff --git a/src/Installers/GameInstallerBase.cs b/src/Installers/GameInstallerBase.cs new file mode 100644 index 0000000..5014221 --- /dev/null +++ b/src/Installers/GameInstallerBase.cs @@ -0,0 +1,106 @@ +using CmlLib.Core.Internals; +using CmlLib.Core.Tasks; +using System.Diagnostics; + +namespace CmlLib.Core.Installers; + +public abstract class GameInstallerBase : IGameInstaller +{ + private readonly HttpClient _httpClient; + + public GameInstallerBase(HttpClient httpClient) + { + _httpClient = httpClient; + } + + private volatile bool IsRunning = false; + public bool CheckFileSize { get; set; } = false; + public bool CheckFileChecksum { get; set; } = true; + + private IProgress? FileProgress; + private IProgress? ByteProgress; + + public async ValueTask Install( + IReadOnlyList gameFiles, + IProgress? fileProgress, + IProgress? byteProgress, + CancellationToken cancellationToken) + { + if (IsRunning) + throw new InvalidOperationException("Already installing"); + + IsRunning = true; + this.FileProgress = fileProgress; + this.ByteProgress = byteProgress; + + await Install(gameFiles, cancellationToken); + IsRunning = false; + } + + protected abstract ValueTask Install(IReadOnlyList gameFiles, CancellationToken cancellationToken); + + protected bool NeedUpdate(GameFile file) + { + if (string.IsNullOrEmpty(file.Path)) + return false; + + if (!File.Exists(file.Path)) + return true; + + if (CheckFileSize && file.Size > 0) + { + var realSize = new FileInfo(file.Path).Length; + if (realSize != file.Size) + return true; + } + + if (CheckFileChecksum && !string.IsNullOrEmpty(file.Hash)) + { + var realHash = IOUtil.ComputeFileSHA1(file.Path); + if (realHash != file.Hash) + return true; + } + + return false; + } + + protected virtual async Task Download( + GameFile file, + IProgress? progress, + CancellationToken cancellationToken) + { + Debug.Assert(!string.IsNullOrEmpty(file.Path)); + if (string.IsNullOrEmpty(file.Url)) + return; + + await HttpClientDownloadHelper.DownloadFileAsync( + _httpClient, + file.Url, + file.Size, + file.Path, + progress, + cancellationToken); + } + + protected void FireFileProgress( + int totalTasks, + int progressedTasks, + string? name, + InstallerEventType type) + { + FileProgress?.Report(new InstallerProgressChangedEventArgs( + totalTasks: totalTasks, + progressedTasks: progressedTasks, + name: name, + type: type)); + } + + protected void FireByteProgress(long totalBytes, long progressedBytes) + { + ByteProgress?.Report(new ByteProgress + { + TotalBytes = totalBytes, + ProgressedBytes = progressedBytes + }); + } +} \ No newline at end of file diff --git a/src/Installers/InstallerProgressChangedEventArgs.cs b/src/Installers/InstallerProgressChangedEventArgs.cs index eb82446..1f6d72b 100644 --- a/src/Installers/InstallerProgressChangedEventArgs.cs +++ b/src/Installers/InstallerProgressChangedEventArgs.cs @@ -1,8 +1,27 @@ namespace CmlLib.Core.Installers; +public enum InstallerEventType +{ + Queued, + Done +} + public class InstallerProgressChangedEventArgs { - public int TotalTasks; - public int ProgressedTasks; - public string? Name; + public InstallerProgressChangedEventArgs( + int totalTasks, + int progressedTasks, + string? name, + InstallerEventType type) + { + TotalTasks = totalTasks; + ProgressedTasks = progressedTasks; + Name = name; + EventType = type; + } + + public int TotalTasks { get; } + public int ProgressedTasks { get; } + public string? Name { get; } + public InstallerEventType EventType { get; } } \ No newline at end of file From 35aa9eb1d51a7b6b65342cf5e679a83861e340ef Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 9 Feb 2024 20:31:15 +0900 Subject: [PATCH 079/137] feat: add ParallelGameInstaller --- src/Installers/ParallelGameInstaller.cs | 139 ++++++++++++++++++++++++ src/LauncherParameters.cs | 3 +- 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/Installers/ParallelGameInstaller.cs diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs new file mode 100644 index 0000000..0f7fcf3 --- /dev/null +++ b/src/Installers/ParallelGameInstaller.cs @@ -0,0 +1,139 @@ +using System.Diagnostics; +using System.Threading.Tasks.Dataflow; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Installers; + +public class ParallelGameInstaller : GameInstallerBase +{ + public ParallelGameInstaller( + int maxChecker, + int maxDownloader, + HttpClient httpClient) : base(httpClient) + { + if (maxChecker <= 0) + throw new ArgumentException(nameof(maxChecker)); + if (maxDownloader <= 0) + throw new ArgumentException(nameof(maxDownloader)); + + MaxChecker = maxChecker; + MaxDownloader = maxDownloader; + } + + public int MaxChecker { get; } + public int MaxDownloader { get; } + + ThreadLocal? progressStorage; + int totalFiles = 0; + int progressedFiles = 0; + long totalBytes = 0; + + protected override async ValueTask Install( + IReadOnlyList gameFiles, + CancellationToken cancellationToken) + { + totalFiles = gameFiles.Count; + progressedFiles = 0; + totalBytes = gameFiles.Select(f => f.Size).Sum(); + progressStorage = new ThreadLocal( + () => new ByteProgress(), true); + + var (firstBlock, lastBlock) = buildBlock(cancellationToken); + for (int i = 0; i < totalFiles; i++) + { + var file = gameFiles[i]; + FireFileProgress(totalFiles, progressedFiles, file.Name, InstallerEventType.Queued); + await firstBlock.SendAsync(file, cancellationToken); + } + firstBlock.Complete(); + + while (!lastBlock.Completion.IsCompleted) + { + aggregateAndReportByteProgress(); + await Task.WhenAny(Task.Delay(500), lastBlock.Completion); + } + aggregateAndReportByteProgress(); + + progressStorage.Dispose(); + progressStorage = null; + } + + private (ITargetBlock, IDataflowBlock) buildBlock(CancellationToken cancellationToken) + { + Debug.Assert(progressStorage != null); + + var checkBlock = new TransformBlock(gameFile => + { + return (gameFile, NeedUpdate(gameFile)); + }, + new ExecutionDataflowBlockOptions + { + CancellationToken = cancellationToken, + MaxDegreeOfParallelism = MaxChecker + }); + + var buffer = new BufferBlock<(GameFile, bool)>(); + + var downloadBlock = new ActionBlock<(GameFile GameFile, bool NeedUpdate)>(async result => + { + var lastProgress = new ByteProgress + { + TotalBytes = result.GameFile.Size, + ProgressedBytes = 0 + }; + + var progressIntercepter = new SyncProgress(p => + { + progressStorage.Value = new ByteProgress + { + TotalBytes = progressStorage.Value.TotalBytes + (p.TotalBytes - lastProgress.TotalBytes), + ProgressedBytes = progressStorage.Value.ProgressedBytes + (p.ProgressedBytes - lastProgress.ProgressedBytes) + }; + lastProgress = p; + }); + + if (result.NeedUpdate) + { + await Download(result.GameFile, progressIntercepter, cancellationToken); + await result.GameFile.ExecuteUpdateTask(cancellationToken); + } + else + { + progressIntercepter.Report(new ByteProgress + { + TotalBytes = result.GameFile.Size, + ProgressedBytes = result.GameFile.Size + }); + } + + Interlocked.Increment(ref progressedFiles); + FireFileProgress(totalFiles, progressedFiles, result.GameFile.Name, InstallerEventType.Done); + }, + new ExecutionDataflowBlockOptions + { + CancellationToken = cancellationToken, + MaxDegreeOfParallelism = MaxDownloader + }); + + var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; + checkBlock.LinkTo(buffer, linkOptions); + buffer.LinkTo(downloadBlock, linkOptions); + + return (checkBlock, downloadBlock); + } + + private void aggregateAndReportByteProgress() + { + Debug.Assert(progressStorage != null); + + long aggregatedTotalBytes = totalBytes; + long aggregatedProgressedBytes = 0; + foreach (var progress in progressStorage.Values) + { + aggregatedTotalBytes += progress.TotalBytes; + aggregatedProgressedBytes += progress.ProgressedBytes; + } + + FireByteProgress(aggregatedTotalBytes, aggregatedProgressedBytes); + } +} \ No newline at end of file diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index a85e223..1f3171f 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -4,7 +4,6 @@ using CmlLib.Core.Java; using CmlLib.Core.Natives; using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; using CmlLib.Core.VersionLoader; namespace CmlLib.Core; @@ -26,7 +25,7 @@ public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) new MojangJsonVersionLoader(httpClient) }; parameters.JavaPathResolver = new MinecraftJavaPathResolver(parameters.MinecraftPath); - parameters.GameInstaller = new BasicGameInstaller(httpClient); + parameters.GameInstaller = new ParallelGameInstaller(4, 4, httpClient); parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); var extractors = DefaultFileExtractors.CreateDefault( parameters.HttpClient, From a61ad1aeee03f48b704bff314545c8f66ebff504 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 10 Feb 2024 15:35:38 +0900 Subject: [PATCH 080/137] fix: BasicGameInstaller reports ByteProgress BasicGameInstaller didn't report ByteProgress when there is no file to download --- src/Installers/BasicGameInstaller.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 1806b2c..7482963 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -21,8 +21,6 @@ protected override async ValueTask Install( var gameFile = gameFiles[i]; FireFileProgress(gameFiles.Count, i, gameFile.Name, InstallerEventType.Queued); - if (NeedUpdate(gameFile)) - { long lastTotal = gameFile.Size; long lastProgressed = 0; var progressIntercepter = new SyncProgress(p => @@ -35,9 +33,19 @@ protected override async ValueTask Install( FireByteProgress(totalBytes, progressedBytes); }); + if (NeedUpdate(gameFile)) + { await Download(gameFile, progressIntercepter, cancellationToken); await gameFile.ExecuteUpdateTask(cancellationToken); } + else + { + progressIntercepter.Report(new ByteProgress + { + TotalBytes = gameFile.Size, + ProgressedBytes = gameFile.Size + }); + } FireFileProgress(gameFiles.Count, i + 1, gameFile.Name, InstallerEventType.Done); } From 7b950cd5839179a81e8eaa1857ab061d62bacd3b Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 10 Feb 2024 15:46:57 +0900 Subject: [PATCH 081/137] feat: add ParallelGameInstaller.CreateAsCoreCount --- src/Installers/ParallelGameInstaller.cs | 15 +++++++++++++++ src/LauncherParameters.cs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index 0f7fcf3..4b15502 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -6,6 +6,21 @@ namespace CmlLib.Core.Installers; public class ParallelGameInstaller : GameInstallerBase { + public static ParallelGameInstaller CreateAsCoreCount(HttpClient httpClient) + { + // 1 <= maxChecker <= 4 + var maxChecker = Environment.ProcessorCount; + maxChecker = Math.Max(1, maxChecker); + maxChecker = Math.Min(4, maxChecker); + + // 4 <= maxDownloader <= 6 + var maxDownloader = Environment.ProcessorCount; + maxDownloader = Math.Max(4, maxDownloader); + maxDownloader = Math.Max(6, maxDownloader); + + return new ParallelGameInstaller(maxChecker, maxDownloader, httpClient); + } + public ParallelGameInstaller( int maxChecker, int maxDownloader, diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 1f3171f..716fe20 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -25,7 +25,7 @@ public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) new MojangJsonVersionLoader(httpClient) }; parameters.JavaPathResolver = new MinecraftJavaPathResolver(parameters.MinecraftPath); - parameters.GameInstaller = new ParallelGameInstaller(4, 4, httpClient); + parameters.GameInstaller = ParallelGameInstaller.CreateAsCoreCount(httpClient); parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); var extractors = DefaultFileExtractors.CreateDefault( parameters.HttpClient, From c0d9a5a8ea8ef5dfca48b57de134bc283434fb22 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 10 Feb 2024 17:33:03 +0900 Subject: [PATCH 082/137] fix: don't ignore exception from ParallelGameInstaller --- TODO.md | 5 +++++ src/Installers/ParallelGameInstaller.cs | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b6e4f67 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +- [] 테스트케이스 더 자세하게 작성 +- [] MLaunchOption 에서 ExtraArguments 같은거 추가 +- [] master 브랜치에서 v3.4.0 으로 cherry-pick +- [] ParallelGameInstaller 예외 처리방식 설정할 수 있게 +- [] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 \ No newline at end of file diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index 4b15502..09bfe13 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -67,7 +67,9 @@ protected override async ValueTask Install( aggregateAndReportByteProgress(); await Task.WhenAny(Task.Delay(500), lastBlock.Completion); } - aggregateAndReportByteProgress(); + + await lastBlock.Completion; // throw exception if exists + aggregateAndReportByteProgress(); // report 100% progressStorage.Dispose(); progressStorage = null; @@ -106,7 +108,7 @@ protected override async ValueTask Install( }; lastProgress = p; }); - + if (result.NeedUpdate) { await Download(result.GameFile, progressIntercepter, cancellationToken); From 94dc10dbddec4735b5c03765e2d96751d265a997 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 13 Feb 2024 11:43:47 +0900 Subject: [PATCH 083/137] refactor: move AddArguments from ProcessArgumentBuilder to MinecraftProcessBuilder --- src/ProcessBuilder/MinecraftProcessBuilder.cs | 59 ++++++++++++++++--- src/ProcessBuilder/ProcessArgumentBuilder.cs | 53 +---------------- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index 87ad131..e9709a1 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -110,11 +110,11 @@ public IEnumerable BuildArguments() private IEnumerable buildJvmArguments(Dictionary argDict) { - var builder = new ProcessArgumentBuilder(rulesEvaluator, rulesContext); + var builder = new ProcessArgumentBuilder(); // version-specific jvm arguments var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); - builder.AddArguments(jvmArgs, argDict); + builder.AddRange(mapArguments(jvmArgs, argDict)); // default jvm arguments if (launchOption.JVMArguments != null) @@ -133,9 +133,9 @@ private IEnumerable buildJvmArguments(Dictionary argDic } // -Xmx, -Xms - if (!builder.CheckKeyAdded("-Xmx") && launchOption.MaximumRamMb > 0) + if (!checkXmxAdded(builder) && launchOption.MaximumRamMb > 0) builder.Add("-Xmx" + launchOption.MaximumRamMb + "m"); - if (!builder.CheckKeyAdded("-Xms") && launchOption.MinimumRamMb > 0) + if (!checkXmsAdded(builder) && launchOption.MinimumRamMb > 0) builder.Add("-Xms" + launchOption.MinimumRamMb + "m"); // for macOS @@ -148,10 +148,10 @@ private IEnumerable buildJvmArguments(Dictionary argDic var logging = version.GetInheritedProperty(v => v.Logging); if (!string.IsNullOrEmpty(logging?.Argument)) { - builder.AddArgument(new MArgument(logging.Argument), new Dictionary() + builder.AddRange(mapArgument(new MArgument(logging.Argument), new Dictionary() { { "path", minecraftPath.GetLogConfigFilePath(logging.LogFile?.Id ?? version.Id) } - }); + })); } // main class @@ -162,13 +162,23 @@ private IEnumerable buildJvmArguments(Dictionary argDic return builder.Build(); } + private bool checkXmxAdded(ProcessArgumentBuilder builder) + { + return builder.Keys.Any(k => k.StartsWith("-Xmx")); + } + + private bool checkXmsAdded(ProcessArgumentBuilder builder) + { + return builder.Keys.Any(k => k.StartsWith("-Xms")); + } + private IEnumerable buildGameArguments(Dictionary argDict) { - var builder = new ProcessArgumentBuilder(rulesEvaluator, rulesContext); + var builder = new ProcessArgumentBuilder(); // game arguments var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); - builder.AddArguments(gameArgs, argDict); + builder.AddRange(mapArguments(gameArgs, argDict)); // options if (!string.IsNullOrEmpty(launchOption.ServerIp)) @@ -197,7 +207,7 @@ private IEnumerable getClasspaths() // libraries var libPaths = version .ConcatInheritedCollection(v => v.Libraries) - .Where(lib => lib.CheckIsRequired("SIDE")) + .Where(lib => lib.CheckIsRequired(JsonVersionParserOptions.ClientSide)) .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) .Where(lib => lib.Artifact != null) .Select(lib => Path.Combine(minecraftPath.Library, lib.GetLibraryPath())); @@ -221,4 +231,35 @@ private IEnumerable getClasspaths() else return input1; } + + private IEnumerable mapArguments(IEnumerable arguments, Dictionary mapper) + { + foreach (var arg in arguments) + { + foreach (var mappedArg in mapArgument(arg, mapper)) + { + yield return mappedArg; + } + } + } + + private IEnumerable mapArgument(MArgument arg, Dictionary mapper) + { + if (arg.Values == null) + yield break; + + if (arg.Rules != null) + { + var isMatch = rulesEvaluator.Match(arg.Rules, rulesContext); + if (!isMatch) + yield break; + } + + foreach (var value in arg.Values) + { + var mappedValue = Mapper.Interpolation(value, mapper); + if (!string.IsNullOrEmpty(mappedValue)) + yield return mappedValue; + } + } } diff --git a/src/ProcessBuilder/ProcessArgumentBuilder.cs b/src/ProcessBuilder/ProcessArgumentBuilder.cs index 8411484..6a5ad3a 100644 --- a/src/ProcessBuilder/ProcessArgumentBuilder.cs +++ b/src/ProcessBuilder/ProcessArgumentBuilder.cs @@ -1,20 +1,11 @@ -using CmlLib.Core.Rules; - namespace CmlLib.Core.ProcessBuilder; public class ProcessArgumentBuilder { - private readonly IRulesEvaluator _evaluator; - private readonly RulesEvaluatorContext _context; private readonly HashSet _keys = new(); private readonly List _args = new(); - public ProcessArgumentBuilder(IRulesEvaluator evaluator, RulesEvaluatorContext context) - { - _evaluator = evaluator; - _context = context; - } - + public IEnumerable Keys => _keys; public bool CheckKeyAdded(string key) => _keys.Contains(key); public void AddRaw(string? value) @@ -66,7 +57,7 @@ public void Add(string? value) if (trimmed.Contains(" ")) throw new FormatException(); result = trimmed; - addKey(k); + _keys.Add(k); } else // contains '=' { @@ -121,48 +112,10 @@ public void AddKeyValue(string key, string? value) else result = $"{key}={escapeValue(value)}"; - addKey(key); + _keys.Add(key); AddRaw(result); } - private void addKey(string key) - { - if (key.StartsWith("-Xmx")) - _keys.Add("-Xmx"); - else if (key.StartsWith("-Xms")) - _keys.Add("-Xms"); - else - _keys.Add(key); - } - - public void AddArguments(IEnumerable arguments, Dictionary mapper) - { - foreach (var arg in arguments) - { - AddArgument(arg, mapper); - } - } - - public void AddArgument(MArgument arg, Dictionary mapper) - { - if (arg.Values == null) - return; - - if (arg.Rules != null) - { - var isMatch = _evaluator.Match(arg.Rules, _context); - if (!isMatch) - return; - } - - foreach (var value in arg.Values) - { - var mappedValue = Mapper.Interpolation(value, mapper); - if (!string.IsNullOrEmpty(mappedValue)) - Add(mappedValue); - } - } - public string[] Build() { return _args.ToArray(); From 0cb0c6060d82c63986df42976ca6e6df60b89211 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 13 Feb 2024 11:46:49 +0900 Subject: [PATCH 084/137] fix: RulesEvaluator match OS.Arch --- src/Rules/LauncherOSRule.cs | 15 +++++++++------ src/Rules/RulesEvaluator.cs | 12 +++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Rules/LauncherOSRule.cs b/src/Rules/LauncherOSRule.cs index f45d1d0..e53d26c 100644 --- a/src/Rules/LauncherOSRule.cs +++ b/src/Rules/LauncherOSRule.cs @@ -9,6 +9,11 @@ public record LauncherOSRule public const string OSX = "osx"; public const string Linux = "linux"; + public const string X86 = "32"; + public const string X64 = "64"; + public const string Arm = "arm"; + public const string Arm64 = "arm64"; + private static LauncherOSRule? _current; public static LauncherOSRule Current => _current ??= createCurrent(); @@ -23,14 +28,12 @@ private static LauncherOSRule createCurrent() else name = Linux; - // ${arch} : 32, 64 - // rules/os/arch: x86 arch = RuntimeInformation.OSArchitecture switch { - Architecture.X86 => "32", - Architecture.X64 => "64", - Architecture.Arm => "arm", - Architecture.Arm64 => "arm64", + Architecture.X86 => X86, + Architecture.X64 => X64, + Architecture.Arm => Arm, + Architecture.Arm64 => Arm64, _ => "" }; diff --git a/src/Rules/RulesEvaluator.cs b/src/Rules/RulesEvaluator.cs index 9ccfc07..2fd37e0 100644 --- a/src/Rules/RulesEvaluator.cs +++ b/src/Rules/RulesEvaluator.cs @@ -27,7 +27,7 @@ private bool matchOS(LauncherRule rule, RulesEvaluatorContext context) bool isArchMatched = true; if (!string.IsNullOrEmpty(rule.OS?.Arch)) - isArchMatched = rule.OS?.Arch == context.OS?.Arch; + isArchMatched = rule.OS?.Arch == getArchForRule(context.OS?.Arch); bool isVersionMatched = true; if (string.IsNullOrEmpty(context.OS?.Version)) @@ -42,6 +42,16 @@ private bool matchOS(LauncherRule rule, RulesEvaluatorContext context) return isNameMatched && isArchMatched && isVersionMatched; } + private string? getArchForRule(string? arch) + { + if (arch == LauncherOSRule.X86) + return "x86"; + else if (arch == LauncherOSRule.X64) + return "x64"; + else + return arch; + } + private bool matchFeature(LauncherRule rule, RulesEvaluatorContext context) { if (rule.Features == null) From 36e8d9e0d0978d23faed1596e4ea817fbb45b737 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 13 Feb 2024 11:49:24 +0900 Subject: [PATCH 085/137] fix: JsonVersion parse minecraftArguments as array now JsonVersion split minecraftArguments and return as an array, instead of returning only one MArgument instance, which contains full argument. --- src/Version/JsonVersion.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index 02f8887..17e95a7 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -153,13 +153,10 @@ private MArgument[] getGameArguments() } else { - return new MArgument[] - { - new MArgument() - { - Values = new string[] { args } - } - }; + return args + .Split(' ') + .Select(arg => new MArgument(arg)) + .ToArray(); } } catch (Exception) @@ -194,7 +191,7 @@ private MArgument[] getJvmArguments() public string? GetProperty(string key) { if (_json.RootElement.TryGetProperty(key, out var prop)) - return prop.GetString(); + return prop.ToString(); else return null; } From f9ac55dfb55a8e69c2a63628e61c1d2ce4dfba0f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 13 Feb 2024 11:52:27 +0900 Subject: [PATCH 086/137] fix: NativeLibraryExtractor extract client side libraries NativeLibraryExtractor didn't extract any libraries --- src/Natives/NativeLibraryExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Natives/NativeLibraryExtractor.cs b/src/Natives/NativeLibraryExtractor.cs index e7546ac..c461b47 100644 --- a/src/Natives/NativeLibraryExtractor.cs +++ b/src/Natives/NativeLibraryExtractor.cs @@ -39,7 +39,7 @@ private IEnumerable getNativeLibraryPaths( { return version .ConcatInheritedCollection(v => v.Libraries) - .Where(lib => lib.CheckIsRequired("SIDE")) + .Where(lib => lib.IsClientRequired) .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) .Select(lib => lib.GetNativeLibraryPath(rulesContext.OS)) .Where(libPath => !string.IsNullOrEmpty(libPath)) From 78dde5bf68fa0de9b16163de8e49b07d8109c1fe Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 13 Feb 2024 11:53:25 +0900 Subject: [PATCH 087/137] misc: add TODO.md --- TODO.md | 39 +++++++++++++++++++++++++-- src/Tasks/CompositeUpdateTask.cs | 3 +-- src/Tasks/HttpClientDownloadHelper.cs | 2 +- src/Tasks/LegacyJavaExtractionTask.cs | 1 + 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index b6e4f67..040fa9c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,40 @@ - [] 테스트케이스 더 자세하게 작성 - [] MLaunchOption 에서 ExtraArguments 같은거 추가 - [] master 브랜치에서 v3.4.0 으로 cherry-pick -- [] ParallelGameInstaller 예외 처리방식 설정할 수 있게 -- [] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 \ No newline at end of file +- [] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 +- [] GameInstaller 에서 중복 파일 확인 +- [] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 +- [] 통합 테스트 작성. 모든 주요 버전 한번씩 실제로 실행해보는 테스트 프로그램 작성 + +# Flow + + { JsonVersionLoader } + | + JsonVersionMetadata + | + JsonVersion + + + + JsonVersion + | +|----------------------------| +| IEnumerable | +|----------------------------| + | + IEnumerable + | +|----------------------------| +| IGameInstaller | +|----------------------------| + + + + JsonVersion + | +|-------------------------| +| INativeLibraryExtractor | +| MinecraftProcessBuilder | +|-------------------------| + | + Process diff --git a/src/Tasks/CompositeUpdateTask.cs b/src/Tasks/CompositeUpdateTask.cs index f851001..4cffaa2 100644 --- a/src/Tasks/CompositeUpdateTask.cs +++ b/src/Tasks/CompositeUpdateTask.cs @@ -1,5 +1,4 @@ - -namespace CmlLib.Core.Tasks; +namespace CmlLib.Core.Tasks; public class CompositeUpdateTask : IUpdateTask { diff --git a/src/Tasks/HttpClientDownloadHelper.cs b/src/Tasks/HttpClientDownloadHelper.cs index fcba45c..ff1b818 100644 --- a/src/Tasks/HttpClientDownloadHelper.cs +++ b/src/Tasks/HttpClientDownloadHelper.cs @@ -33,7 +33,7 @@ public static async Task DownloadFileAsync( string url, long size, Stream destination, - IProgress? progress = null, + IProgress? progress = null, // it must report last progress (100%) CancellationToken cancellationToken = default) { // Get the http headers first to examine the content length diff --git a/src/Tasks/LegacyJavaExtractionTask.cs b/src/Tasks/LegacyJavaExtractionTask.cs index 8c5a5cb..92da5f1 100644 --- a/src/Tasks/LegacyJavaExtractionTask.cs +++ b/src/Tasks/LegacyJavaExtractionTask.cs @@ -16,6 +16,7 @@ public ValueTask Execute(GameFile file, CancellationToken cancellationToken) if (string.IsNullOrEmpty(file.Path)) throw new ArgumentException(); + // jre.lzma (file.Path) -> jre.zip -> /extracTo var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); SevenZipWrapper.DecompressFileLZMA(file.Path, zipPath); SharpZipWrapper.Unzip(zipPath, ExtractTo, null, cancellationToken); From a34dad4043e05d58861f8c649f84bd9b7ba985c4 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Tue, 13 Feb 2024 11:54:58 +0900 Subject: [PATCH 088/137] test: refactoring the entire project, use xunit --- global.json | 2 +- src/Files/MLogFileMetadata.cs | 2 +- test/CmlLib.Core.Test.csproj | 24 +- test/{IOUtilTest.cs => IOUtilTests.cs} | 31 +- test/{MapperTest.cs => MapperTests.cs} | 25 +- test/PackageNameTest.cs | 43 +- .../MinecraftProcessBuilderTests.cs | 10 + .../ProcessArgumentBuilderTest.cs | 87 ---- .../ProcessArgumentBuilderTests.cs | 132 ++++++ test/Program.cs | 14 - test/Rules/RulesEvaluatorFeatureTest.cs | 119 ------ test/Rules/RulesEvaluatorFeatureTests.cs | 201 +++++++++ test/Rules/RulesEvaluatorOSTest.cs | 237 ----------- test/Rules/RulesEvaluatorOSTests.cs | 397 ++++++++++++++++++ test/Usings.cs | 1 + .../{MockVersion.cs => DummyVersion.cs} | 4 +- test/Version/JsonArgumentParserTests.cs | 319 ++++++++++++++ test/Version/JsonLibraryParserTests.cs | 264 ++++++++++++ test/Version/JsonVersionParserTests.cs | 148 +++++++ ...t.cs => VersionMetadataCollectionTests.cs} | 119 +++--- .../VersionMetadataSorterTests.cs | 6 + 21 files changed, 1608 insertions(+), 577 deletions(-) rename test/{IOUtilTest.cs => IOUtilTests.cs} (56%) rename test/{MapperTest.cs => MapperTests.cs} (57%) create mode 100644 test/ProcessBuilder/MinecraftProcessBuilderTests.cs delete mode 100644 test/ProcessBuilder/ProcessArgumentBuilderTest.cs create mode 100644 test/ProcessBuilder/ProcessArgumentBuilderTests.cs delete mode 100644 test/Program.cs delete mode 100644 test/Rules/RulesEvaluatorFeatureTest.cs create mode 100644 test/Rules/RulesEvaluatorFeatureTests.cs delete mode 100644 test/Rules/RulesEvaluatorOSTest.cs create mode 100644 test/Rules/RulesEvaluatorOSTests.cs create mode 100644 test/Usings.cs rename test/Version/{MockVersion.cs => DummyVersion.cs} (92%) create mode 100644 test/Version/JsonArgumentParserTests.cs create mode 100644 test/Version/JsonLibraryParserTests.cs create mode 100644 test/Version/JsonVersionParserTests.cs rename test/VersionMetadata/{VersionMetadataCollectionTest.cs => VersionMetadataCollectionTests.cs} (56%) create mode 100644 test/VersionMetadata/VersionMetadataSorterTests.cs diff --git a/global.json b/global.json index 5932159..3b1d787 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.400", + "version": "8.0.0", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/src/Files/MLogFileMetadata.cs b/src/Files/MLogFileMetadata.cs index c98e162..5eea5cf 100644 --- a/src/Files/MLogFileMetadata.cs +++ b/src/Files/MLogFileMetadata.cs @@ -2,7 +2,7 @@ namespace CmlLib.Core.Files; -public class MLogFileMetadata : MFileMetadata +public class MLogFileMetadata { [JsonPropertyName("file")] public MFileMetadata? LogFile { get; set; } diff --git a/test/CmlLib.Core.Test.csproj b/test/CmlLib.Core.Test.csproj index 97543f0..6fd6008 100644 --- a/test/CmlLib.Core.Test.csproj +++ b/test/CmlLib.Core.Test.csproj @@ -1,7 +1,8 @@ - net6.0 + 12 + net8.0 enable enable false @@ -9,7 +10,7 @@ - + @@ -18,18 +19,19 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - - - - - diff --git a/test/IOUtilTest.cs b/test/IOUtilTests.cs similarity index 56% rename from test/IOUtilTest.cs rename to test/IOUtilTests.cs index 12eb0e0..cc61add 100644 --- a/test/IOUtilTest.cs +++ b/test/IOUtilTests.cs @@ -1,32 +1,30 @@ using CmlLib.Core.Internals; -using NUnit.Framework; namespace CmlLib.Core.Test; public class IOUtilTest { - [Platform("Win")] - [TestCase(@"C:\\a/", @"C:\a")] - [TestCase(@"C:\\a\/b.txt", @"C:\a\b.txt")] - [TestCase(@"C:\/a\\b/\c.txt", @"C:\a\b\c.txt")] - [TestCase(@"/root/f1\.txt", @"C:\root\f1\.txt")] + [Theory(Skip = "Win")] + [InlineData(@"C:\\a/", @"C:\a")] + [InlineData(@"C:\\a\/b.txt", @"C:\a\b.txt")] + [InlineData(@"C:\/a\\b/\c.txt", @"C:\a\b\c.txt")] + [InlineData(@"/root/f1\.txt", @"C:\root\f1\.txt")] public void TestNormalizePathWin(string path, string expected) { var normalizedPath = IOUtil.NormalizePath(path); - Assert.AreEqual(expected, normalizedPath); + Assert.Equal(expected, normalizedPath); } - [Platform("Unix")] - [TestCase(@"/root/f1\.txt", @"/root/f1\.txt")] - [TestCase(@"/root/path1/", @"/root/path1")] + [Theory(Skip = "Unix")] + [InlineData(@"/root/f1\.txt", @"/root/f1\.txt")] + [InlineData(@"/root/path1/", @"/root/path1")] public void TestNormalizePathUnix(string path, string expected) { var normalizedPath = IOUtil.NormalizePath(path); - Assert.AreEqual(expected, normalizedPath); + Assert.Equal(expected, normalizedPath); } - [Platform("Win")] - [Test] + [Fact(Skip = "Win")] public void TestCombinePathWin() { var paths = new[] @@ -35,11 +33,10 @@ public void TestCombinePathWin() }; var exp = @"C:\test\lib1.zip;""C:\test\lib space 2.zip"""; - Assert.AreEqual(exp, IOUtil.CombinePath(paths)); + Assert.Equal(exp, IOUtil.CombinePath(paths)); } - [Platform("Unix")] - [Test] + [Fact(Skip = "Unix")] public void TestCombinePathUnix() { var paths = new[] @@ -48,6 +45,6 @@ public void TestCombinePathUnix() }; var exp = @"/root/f1.zip:""/root/f2 space sss.txt"""; - Assert.AreEqual(exp, IOUtil.CombinePath(paths)); + Assert.Equal(exp, IOUtil.CombinePath(paths)); } } \ No newline at end of file diff --git a/test/MapperTest.cs b/test/MapperTests.cs similarity index 57% rename from test/MapperTest.cs rename to test/MapperTests.cs index 462565d..d1288f2 100644 --- a/test/MapperTest.cs +++ b/test/MapperTests.cs @@ -1,27 +1,26 @@ -using NUnit.Framework; - -namespace CmlLib.Core.Test; +namespace CmlLib.Core.Test; public class MapperTest { - [Platform("win")] - [TestCase( + [Theory(Skip ="Win")] + [InlineData( @"[de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip]", @"C:\libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip")] - [TestCase( + [InlineData( @"[net.minecraft:client:1.16.2-20200812.004259:slim]", @"C:\libraries\net\minecraft\client\1.16.2-20200812.004259\client-1.16.2-20200812.004259-slim.jar")] public void TestFullPath(string input, string exp) { - Assert.AreEqual(exp, Mapper.ToFullPath(input, @"C:\libraries\")); + Assert.Equal(exp, Mapper.ToFullPath(input, @"C:\libraries\")); } - [TestCase("", "")] - [TestCase("key=value", "key=value")] - [TestCase("key=value 1", "key=\"value 1\"")] - [TestCase("key=\"value 2\"", "key=\"value 2\"")] - [TestCase("value 3", "\"value 3\"")] - [TestCase("\"value 4\"", "\"value 4\"")] + [Theory] + [InlineData("", "")] + [InlineData("key=value", "key=value")] + [InlineData("key=value 1", "key=\"value 1\"")] + [InlineData("key=\"value 2\"", "key=\"value 2\"")] + [InlineData("value 3", "\"value 3\"")] + [InlineData("\"value 4\"", "\"value 4\"")] public void TestHandleEmptyArg(string input, string exp) { //Assert.AreEqual(exp, Mapper.HandleEmptyArg(input)); diff --git a/test/PackageNameTest.cs b/test/PackageNameTest.cs index f04603b..a3252d2 100644 --- a/test/PackageNameTest.cs +++ b/test/PackageNameTest.cs @@ -1,60 +1,59 @@ -using NUnit.Framework; - -namespace CmlLib.Core.Test; +namespace CmlLib.Core.Test; public class PackageNameTest { - [TestCase("a:b:c", "a", "b", "c")] - [TestCase("a.b:c.d:e.f", "a.b", "c.d", "e.f")] + [Theory] + [InlineData("a:b:c", "a", "b", "c")] + [InlineData("a.b:c.d:e.f", "a.b", "c.d", "e.f")] public void TestParse(string input, string package, string name, string version) { var packageName = PackageName.Parse(input); - Assert.AreEqual(name, packageName.Name); - Assert.AreEqual(package, packageName.Package); - Assert.AreEqual(version, packageName.Version); + Assert.Equal(name, packageName.Name); + Assert.Equal(package, packageName.Package); + Assert.Equal(version, packageName.Version); } - [Platform("Win")] - [TestCase("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", + [Theory(Skip ="Win")] + [InlineData("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", @"de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259-mappings.jar")] - [TestCase("net.java.dev.jna:platform:3.4.0", + [InlineData("net.java.dev.jna:platform:3.4.0", @"net\java\dev\jna\platform\3.4.0\platform-3.4.0.jar")] public void TestGetPathWin(string input, string exp) { var packageName = PackageName.Parse(input); var result = packageName.GetPath(null, "jar"); - Assert.AreEqual(exp, result); + Assert.Equal(exp, result); } - [Platform("Unix")] - [TestCase("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", + [Theory(Skip ="Unix")] + [InlineData("de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259:mappings", @"de/oceanlabs/mcp/mcp_config/1.16.2-20200812.004259/mcp_config-1.16.2-20200812.004259-mappings.jar")] - [TestCase("net.java.dev.jna:platform:3.4.0", + [InlineData("net.java.dev.jna:platform:3.4.0", @"net/java/dev/jna/platform/3.4.0/platform-3.4.0.jar")] public void TestGetPathUnix(string input, string exp) { var packageName = PackageName.Parse(input); var result = packageName.GetPath(null, "jar"); - Assert.AreEqual(exp, result); + Assert.Equal(exp, result); } - [Platform("Win")] - [TestCase("com.mojang:text2speech:1.10.3", + [Theory(Skip ="Win")] + [InlineData("com.mojang:text2speech:1.10.3", @"com\mojang\text2speech\1.10.3\text2speech-1.10.3-natives-windows.jar")] public void TestGetPathWithNativeWin(string input, string exp) { var packageName = PackageName.Parse(input); var result = packageName.GetPath("natives-windows"); - Assert.AreEqual(exp, result); + Assert.Equal(exp, result); } - [Platform("Unix")] - [TestCase("ca.weblite:java-objc-bridge:1.0.0", + [Theory(Skip ="Unix")] + [InlineData("ca.weblite:java-objc-bridge:1.0.0", "ca/weblite/java-objc-bridge/1.0.0/java-objc-bridge-1.0.0-natives-osx.jar")] public void TestGetPathWithNativeUnix(string input, string exp) { var packageName = PackageName.Parse(input); var result = packageName.GetPath("natives-osx"); - Assert.AreEqual(exp, result); + Assert.Equal(exp, result); } } \ No newline at end of file diff --git a/test/ProcessBuilder/MinecraftProcessBuilderTests.cs b/test/ProcessBuilder/MinecraftProcessBuilderTests.cs new file mode 100644 index 0000000..bd3ac6d --- /dev/null +++ b/test/ProcessBuilder/MinecraftProcessBuilderTests.cs @@ -0,0 +1,10 @@ +namespace CmlLib.Core.Test.ProcessBuilder; + +public class MinecraftProcessBuilderTests +{ + [Fact] + public void build_arguments() + { + + } +} \ No newline at end of file diff --git a/test/ProcessBuilder/ProcessArgumentBuilderTest.cs b/test/ProcessBuilder/ProcessArgumentBuilderTest.cs deleted file mode 100644 index bd0ea0e..0000000 --- a/test/ProcessBuilder/ProcessArgumentBuilderTest.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NUnit.Framework; -using CmlLib.Core.Rules; -using CmlLib.Core.ProcessBuilder; - -namespace CmlLib.Core.Test.ProcessBuilder; - -[TestFixture] -public class ProcessArgumentBuilderTest -{ - [TestCase(null)] - [TestCase("")] - public void TestAddEmptyValue(string test) - { - var builder = createBuilder(); - builder.Add(test); - var actual = builder.Build(); - Assert.That(actual.Length, Is.Zero); - } - - [TestCase("\"\"", "\"\"")] - [TestCase("word", "word")] - [TestCase("\"word\"", "\"word\"")] - [TestCase("double word", "\"double word\"")] - [TestCase("\"double word\"", "\"double word\"")] - [TestCase("\"-k=v\"", "\"-k=v\"")] - [TestCase("\"-k=double word\"", "\"-k=double word\"")] - [TestCase(" - ", "-")] - [TestCase("-k", "-k")] - [TestCase("-k=", "-k=")] - [TestCase("-k=\"\"", "-k=\"\"")] - [TestCase("-k=v", "-k=v")] - [TestCase("-k=\"v\"", "-k=\"v\"")] - [TestCase("-k=\"double word\"", "-k=\"double word\"")] - public void TestAddValue(string test, string expected) - { - var builder = createBuilder(); - builder.Add(test); - var actual = builder.Build(); - Assert.That(actual, Is.EqualTo(new string[] { expected })); - } - - [TestCase("-k", null, "-k")] - [TestCase("-k", "", "-k=\"\"")] - [TestCase("-k", "value", "-k=value")] - [TestCase("-k", "\"value\"", "-k=\"value\"")] - [TestCase("-k", "double word", "-k=\"double word\"")] - [TestCase("-k", "\"double word\"", "-k=\"double word\"")] - [TestCase("a", "b", "a=b")] // key not starts with '-' - public void TestAddKeyValue(string key, string value, string expected) - { - var builder = createBuilder(); - builder.AddKeyValue(key, value); - var actual = builder.Build(); - Assert.That(actual, Is.EqualTo(new string[] { expected })); - } - - [TestCase("-key value")] - [TestCase("-key=value and")] - public void TestAddValueException(string test) - { - var builder = createBuilder(); - Assert.Throws(() => - { - builder.Add(test); - }); - } - - [Test, Combinatorial] - public void TestAddKeyValueException( - [Values(null, "", "-k -v", "-k and", "\"key\"")] string key, - [Values(null, "", "word", "double word")] string value) - { - var builder = createBuilder(); - Assert.Throws(() => - { - builder.AddKeyValue(key, value); - }); - } - - private ProcessArgumentBuilder createBuilder() - { - var evaluator = new RulesEvaluator(); - var context = new RulesEvaluatorContext(LauncherOSRule.Current); - var builder = new ProcessArgumentBuilder(evaluator, context); - return builder; - } -} \ No newline at end of file diff --git a/test/ProcessBuilder/ProcessArgumentBuilderTests.cs b/test/ProcessBuilder/ProcessArgumentBuilderTests.cs new file mode 100644 index 0000000..9f80a6a --- /dev/null +++ b/test/ProcessBuilder/ProcessArgumentBuilderTests.cs @@ -0,0 +1,132 @@ +using CmlLib.Core.ProcessBuilder; + +namespace CmlLib.Core.Test.ProcessBuilder; + +public class ProcessArgumentBuilderTests +{ + [Fact] + public void ignore_null_or_empty() + { + // Given + var sut = createBuilder(); + + // When + sut.Add(null); + sut.Add(""); + + // Then + var actual = sut.Build(); + Assert.Empty(actual); + } + + [Theory] + // value without space + [InlineData("word", "word")] + // escape spaced value + [InlineData("double word", "\"double word\"")] + // do not escape already escaped value + [InlineData("\"word\"", "\"word\"")] + [InlineData("\"\"", "\"\"")] + [InlineData("\"double word\"", "\"double word\"")] + // thread as value (not key-value) + [InlineData("\"-k=v\"", "\"-k=v\"")] + [InlineData("\"-k=double word\"", "\"-k=double word\"")] + [InlineData("-k", "-k")] + // special + [InlineData(" - ", "-")] + public void add_value(string input, string expected) + { + // Given + var sut = createBuilder(); + + // When + sut.Add(input); + + // Then + Assert.Empty(sut.Keys); + Assert.Equal(new []{expected}, sut.Build()); + } + + [Theory] + // key-value + [InlineData("-k=v", "-k=v", "-k")] + [InlineData("-k=", "-k=", "-k")] + // key-value, with space in value + [InlineData("-k=\"\"", "-k=\"\"", "-k")] + [InlineData("-k=\"v\"", "-k=\"v\"", "-k")] + [InlineData("-k=\"double word\"", "-k=\"double word\"", "-k")] + public void add_key_value_by_Add(string input, string expectedArg, string expectedKey) + { + // Given + var sut = createBuilder(); + + // When + sut.Add(input); + + // Then + Assert.Equal(new []{expectedKey}, sut.Keys); + Assert.Equal(new []{expectedArg}, sut.Build()); + } + + [Theory] + // null value + [InlineData("-k", null, "-k")] + // empty value + [InlineData("-k", "", "-k=\"\"")] + // value + [InlineData("-k", "value", "-k=value")] + // value with space + [InlineData("-k", "double word", "-k=\"double word\"")] + // do not escape already escaped value + [InlineData("-k", "\"value\"", "-k=\"value\"")] + [InlineData("-k", "\"double word\"", "-k=\"double word\"")] + // string that don't start with '-' also thread as key + [InlineData("a", "b", "a=b")] + public void add_key_value_by_AddKeyValue(string key, string value, string expectedArg) + { + // Given + var sut = createBuilder(); + + // When + sut.AddKeyValue(key, value); + + // Then + Assert.Equal(new []{key}, sut.Keys); + Assert.Equal(new []{expectedArg}, sut.Build()); + } + + [Theory] + // cannot add key-value with a space in the value + [InlineData("-key value")] + [InlineData("-key=value and")] + public void add_invalid_value(string test) + { + var sut = createBuilder(); + Assert.Throws(() => + { + sut.Add(test); + }); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("a b")] + [InlineData("\"\"")] + [InlineData("\"a b\"")] + public void add_invalid_key(string key) + { + var builder = createBuilder(); + Assert.Throws(() => + { + builder.AddKeyValue(key, "value"); + }); + } + + private ProcessArgumentBuilder createBuilder() + { + var builder = new ProcessArgumentBuilder(); + return builder; + } +} \ No newline at end of file diff --git a/test/Program.cs b/test/Program.cs deleted file mode 100644 index 7f72323..0000000 --- a/test/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -#if TestSdk - -using CmlLib.Core.Test.Installers; - -var tester = new TPLGameInstallerTest(); -int count = 0; -while (true) -{ - Console.WriteLine("try " + count); - await tester.TestByteProgressReachesTo100(); - count++; -} - -#endif \ No newline at end of file diff --git a/test/Rules/RulesEvaluatorFeatureTest.cs b/test/Rules/RulesEvaluatorFeatureTest.cs deleted file mode 100644 index 2c4605a..0000000 --- a/test/Rules/RulesEvaluatorFeatureTest.cs +++ /dev/null @@ -1,119 +0,0 @@ -using CmlLib.Core.Rules; -using NUnit.Framework; - -namespace CmlLib.Core.Test.Rules; - -public class RulesEvaluatorFeatureTest -{ - private LauncherOSRule TestOS = new LauncherOSRule - { - Name = "linux", - Arch = "x86" - }; - - [Test] - public void TestAllowEmptyFeature() - { - testFeatures(true, new string[] {}, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = TestOS, - Features = new Dictionary() - } - }); - } - - [Test] - [TestCase(true, "feature1", "feature1", true)] - [TestCase(false, "feature1", "a", true)] - [TestCase(false, "a", "feature1", true)] - [TestCase(false, "feature1", "feature1", false)] - [TestCase(true, "feature1", "a", false)] - [TestCase(true, "a", "feature1", false)] - public void TestOneFeature( - bool expected, - string inputFeature, - string ruleFeature, - bool ruleValue) - { - testFeatures(expected, new string[] { inputFeature }, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = TestOS, - Features = new Dictionary - { - [ruleFeature] = ruleValue - } - } - }); - } - - [Test] - [TestCase(true, "a", "b", "a", "b", true, true)] - [TestCase(false, "a", "b", "a", "x", true, true)] - [TestCase(false, "a", "x", "a", "b", true, true)] - [TestCase(false, "a", "b", "a", "b", true, false)] - [TestCase(true, "x", "b", "a", "b", false, true)] - [TestCase(true, "a", "b", "x", "y", false, false)] - public void TestTwoMatchFeature( - bool expected, - string inputFeature1, string inputFeature2, - string ruleFeature1, string ruleFeature2, - bool ruleValue1, bool ruleValue2) - { - testFeatures(expected, new string[] { inputFeature1, inputFeature2 }, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = TestOS, - Features = new Dictionary - { - [ruleFeature1] = ruleValue1, - [ruleFeature2] = ruleValue2 - } - } - }); - } - - [Test] - public void TestDisallowMismatchOS() - { - var evaluator = new RulesEvaluator(); - var context = new RulesEvaluatorContext(new LauncherOSRule - { - Name = "windows", Arch = "x86" - }); - context.Features = new string[] { "feature1" }; - var result = evaluator.Match(new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "linux", Arch = "x86" - }, - Features = new Dictionary - { - ["feature1"] = true, - ["feature2"] = false - } - } - }, context); - Assert.False(result); - } - - private void testFeatures(bool expected, string[] features, IEnumerable rules) - { - var evaluator = new RulesEvaluator(); - var context = new RulesEvaluatorContext(TestOS); - context.Features = features; - var result = evaluator.Match(rules, context); - Assert.That(result, Is.EqualTo(expected)); - } -} \ No newline at end of file diff --git a/test/Rules/RulesEvaluatorFeatureTests.cs b/test/Rules/RulesEvaluatorFeatureTests.cs new file mode 100644 index 0000000..25a034d --- /dev/null +++ b/test/Rules/RulesEvaluatorFeatureTests.cs @@ -0,0 +1,201 @@ +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Test.Rules; + +public class RulesEvaluatorFeatureTest +{ + private LauncherOSRule TestOS = new LauncherOSRule + { + Name = "linux", + Arch = "x86" + }; + + [Fact] + public void allow_empty_feature() + { + allow(new string[] {}, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary() + } + }); + } + + [Fact] + public void allow_one_feature() + { + allow(new string[] { "demo" }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["demo"] = true + } + } + }); + } + + [Fact] + public void allow_not_ruled_feature() + { + allow(new string[] { "demo" }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["fullscreen"] = false + } + } + }); + } + + [Fact] + public void disallow_no_feature() + { + disallow(new string[] { "demo" }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["fullscreen"] = true + } + } + }); + } + + [Fact] + public void disallow_disallowed_feature() + { + disallow(new string[] { "demo" }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["demo"] = false + } + } + }); + } + + [Theory] + [InlineData("demo", "cmllib")] + public void allow_two_features(string input1, string input2) + { + allow(new string[] { input1, input2 }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["demo"] = true, + ["fullscreen"] = false, + ["cmllib"] = true + } + } + }); + } + + [Theory] + [InlineData("demo", "not_included_feature")] + public void allow_included_feature_and_not_included_featre(string input1, string input2) + { + allow(new string[] { input1, input2 }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["demo"] = true, + ["fullscreen"] = false, + ["cmllib"] = false + } + } + }); + } + + [Theory] + [InlineData("demo", "fullscreen")] + [InlineData("fullscreen", "cmllib")] + public void disallow_two_features(string input1, string input2) + { + disallow(new string[] { input1, input2 }, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = TestOS, + Features = new Dictionary + { + ["demo"] = true, + ["fullscreen"] = false, + ["cmllib"] = true + } + } + }); + } + + [Fact] + public void TestDisallowMismatchOS() + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(new LauncherOSRule + { + Name = "windows", Arch = "x86" + }); + context.Features = new string[] { "feature1" }; + var result = evaluator.Match(new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux", Arch = "x86" + }, + Features = new Dictionary + { + ["feature1"] = true, + ["feature2"] = false + } + } + }, context); + Assert.False(result); + } + + private void allow(string[] features, IEnumerable rules) + { + Assert.True(testFeatures(features, rules)); + } + + private void disallow(string[] features, IEnumerable rules) + { + Assert.False(testFeatures(features, rules)); + } + + private bool testFeatures(string[] features, IEnumerable rules) + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(TestOS); + context.Features = features; + return evaluator.Match(rules, context); + } +} \ No newline at end of file diff --git a/test/Rules/RulesEvaluatorOSTest.cs b/test/Rules/RulesEvaluatorOSTest.cs deleted file mode 100644 index 093bada..0000000 --- a/test/Rules/RulesEvaluatorOSTest.cs +++ /dev/null @@ -1,237 +0,0 @@ -using CmlLib.Core.Rules; -using NUnit.Framework; - -namespace CmlLib.Core.Test.Rules; - -public class RulesEvaluatorOSTest -{ - [Test] - [TestCase("windows", "x86", true)] - [TestCase("windows", "x64", true)] - [TestCase("linux", "x86", true)] - [TestCase("linux", "x64", true)] - [TestCase("osx", "x86", true)] - [TestCase("osx", "x64", true)] - public void TestAllow(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "allow" - } - }); - } - - [Test] - [TestCase("windows", "x86", false)] - [TestCase("windows", "x64", false)] - [TestCase("linux", "x86", false)] - [TestCase("linux", "x64", false)] - [TestCase("osx", "x86", false)] - [TestCase("osx", "x64", false)] - public void TestDisallow(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "disallow" - } - }); - } - - [Test] - [TestCase("windows", "x86", false)] - [TestCase("windows", "x64", false)] - [TestCase("linux", "x86", true)] - [TestCase("linux", "x64", true)] - [TestCase("osx", "x86", false)] - [TestCase("osx", "x64", false)] - public void TestAllowOSName(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "linux" - } - } - }); - } - - [Test] - [TestCase("windows", "x86", true)] - [TestCase("windows", "x64", false)] - [TestCase("linux", "x86", true)] - [TestCase("linux", "x64", false)] - [TestCase("osx", "x86", true)] - [TestCase("osx", "x64", false)] - public void TestAllowOSArch(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Arch = "x86" - } - } - }); - } - - - [Test] - [TestCase("windows", "10.0", true)] - [TestCase("windows", "7", false)] - [TestCase("linux", "10.0", false)] - public void TestAllowOSVersion(string osname, string version, bool expected) - { - var os = new LauncherOSRule - { - Name = osname, - Arch = "x86", - Version = version - }; - testOSRule(os, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "windows", - Version = "^10\\." - } - } - }); - } - - [Test] - [TestCase("windows", "x86", true)] - [TestCase("windows", "x64", true)] - [TestCase("linux", "x86", true)] - [TestCase("linux", "x64", true)] - [TestCase("osx", "x86", false)] - [TestCase("osx", "x64", false)] - public void TestAllowCompositedOSName(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "allow" - }, - new LauncherRule - { - Action = "disallow", - OS = new LauncherOSRule - { - Name = "osx" - } - } - }); - } - - [Test] - [TestCase("windows", "x86", false)] - [TestCase("windows", "x64", false)] - [TestCase("linux", "x86", false)] - [TestCase("linux", "x64", false)] - [TestCase("osx", "x86", true)] - [TestCase("osx", "x64", true)] - public void TestDisallowCompositedOSName(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "disallow" - }, - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "osx" - } - } - }); - } - - [Test] - [TestCase("windows", "x86", false)] - [TestCase("windows", "x64", true)] - [TestCase("linux", "x86", true)] - [TestCase("linux", "x64", false)] - [TestCase("osx", "x86", false)] - [TestCase("osx", "x64", false)] - public void TestCompositedAllows(string osname, string arch, bool expected) - { - testOSRule(osname, arch, expected, new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "windows", - Arch = "x64" - } - }, - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "linux", - Arch = "x86" - } - } - }); - } - - [Test] - public void TestDisallowMismatchFeatures() - { - var os = new LauncherOSRule - { - Name = "linux", Arch = "x86" - }; - var context = new RulesEvaluatorContext(os); - context.Features = new string[] { "feature1" }; - var evaluator = new RulesEvaluator(); - var result = evaluator.Match(new LauncherRule[] - { - new LauncherRule - { - Action = "allow", - OS = os, - Features = new Dictionary - { - ["feature1"] = false - } - } - }, context); - Assert.False(result); - } - - private void testOSRule(string osname, string arch, bool expected, IEnumerable rules) - { - var os = new LauncherOSRule(osname, arch); - testOSRule(os, expected, rules); - } - - private void testOSRule(LauncherOSRule os, bool expected, IEnumerable rules) - { - var evaluator = new RulesEvaluator(); - var context = new RulesEvaluatorContext(os); - var result = evaluator.Match(rules, context); - Assert.That(result, Is.EqualTo(expected)); - } -} \ No newline at end of file diff --git a/test/Rules/RulesEvaluatorOSTests.cs b/test/Rules/RulesEvaluatorOSTests.cs new file mode 100644 index 0000000..eaf0109 --- /dev/null +++ b/test/Rules/RulesEvaluatorOSTests.cs @@ -0,0 +1,397 @@ +using CmlLib.Core.Rules; + +namespace CmlLib.Core.Test.Rules; + +public class RulesEvaluatorOSTest +{ + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void allow_no_rule(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow" + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void disallow_no_rule(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "disallow" + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + public void allow_linux(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux" + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void disallow_not_linux(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux" + } + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + public void allow_x86(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Arch = "x86" + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.Arm64)] + public void disallow_not_x86(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Arch = "x86" + } + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, "10.0")] + public void allow_windows_10_later(string osname, string version) + { + var os = new LauncherOSRule + { + Name = osname, + Arch = LauncherOSRule.X86, + Version = version + }; + var result = testOSRule(os, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Version = "^10\\." + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, "7")] + [InlineData(LauncherOSRule.Linux, "10.0")] + public void disallow_not_windows_10_later(string osname, string version) + { + var os = new LauncherOSRule + { + Name = osname, + Arch = LauncherOSRule.X86, + Version = version + }; + var result = testOSRule(os, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Version = "^10\\." + } + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.OSX, "10.5.0")] + [InlineData(LauncherOSRule.OSX, "10.5.8")] + public void allow_osx_10_5(string osname, string version) + { + var os = new LauncherOSRule + { + Name = osname, + Arch = LauncherOSRule.X86, + Version = version + }; + var result = testOSRule(os, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx", + Version = "^10\\.5\\.\\d$" + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.OSX, "10.5")] + [InlineData(LauncherOSRule.OSX, "10.6.5")] + [InlineData(LauncherOSRule.Windows, "10.5.0")] + public void disallow_not_osx_10_5(string osname, string version) + { + var os = new LauncherOSRule + { + Name = osname, + Arch = LauncherOSRule.X86, + Version = version + }; + var result = testOSRule(os, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx", + Version = "^10\\.5\\.\\d$" + } + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + public void allow_all_except_one(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow" + }, + new LauncherRule + { + Action = "disallow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void disallow_only_one(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow" + }, + new LauncherRule + { + Action = "disallow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + public void disallow_all_except_one(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "disallow" + }, + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + }); + Assert.False(result); + } + + [Theory] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void allow_only_one(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "disallow" + }, + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + public void allow_only_two(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Arch = "x64" + } + }, + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux", + Arch = "x86" + } + } + }); + Assert.True(result); + } + + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void disallow_all_except_two(string osname, string arch) + { + var result = testOSRule(osname, arch, new LauncherRule[] + { + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Arch = "x64" + } + }, + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "linux", + Arch = "x86" + } + } + }); + Assert.False(result); + } + + private bool testOSRule(string osname, string arch, IEnumerable rules) + { + var os = new LauncherOSRule(osname, arch); + return testOSRule(os, rules); + } + + private bool testOSRule(LauncherOSRule os, IEnumerable rules) + { + var evaluator = new RulesEvaluator(); + var context = new RulesEvaluatorContext(os); + return evaluator.Match(rules, context); + } +} \ No newline at end of file diff --git a/test/Usings.cs b/test/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/test/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/Version/MockVersion.cs b/test/Version/DummyVersion.cs similarity index 92% rename from test/Version/MockVersion.cs rename to test/Version/DummyVersion.cs index c0bcbe3..3fbc6cd 100644 --- a/test/Version/MockVersion.cs +++ b/test/Version/DummyVersion.cs @@ -5,9 +5,9 @@ namespace CmlLib.Core.Test.Version; -public class MockVersion : IVersion +public class DummyVersion : IVersion { - public MockVersion(string id) => Id = id; + public DummyVersion(string id) => Id = id; public string Id { get; set; } diff --git a/test/Version/JsonArgumentParserTests.cs b/test/Version/JsonArgumentParserTests.cs new file mode 100644 index 0000000..c302899 --- /dev/null +++ b/test/Version/JsonArgumentParserTests.cs @@ -0,0 +1,319 @@ +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test.Version; + +public class JsonArgumentParserTests +{ + private static readonly JsonVersionParserOptions options = new JsonVersionParserOptions + { + Side = JsonVersionParserOptions.ClientSide, + SkipError = false + }; + + [Fact] + public void parse_from_vanilla_arg_string() + { + // release 1.0 ~ : ${auth_player_name} ${auth_session} --gameDir ${game_directory} --assetsDir ${game_assets} + // release 1.6.1 ~ : from --username to --assetDir + // release 1.7.2 ~ : add --uuid, --accessToken + // release 1.7.10 ~ : add --assetIndex, --userProperties, --userType + + var json = """ +{ + "id": "1.7.10", + "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userProperties ${user_properties} --userType ${user_type}" +} +"""; + + var version = JsonVersionParser.ParseFromJsonString(json, options); + + var parsedArgs = version.GameArguments + .SelectMany(arg => arg.Values ?? Enumerable.Empty()) + .ToArray(); + Assert.Equal(new[] + { + "--username", + "${auth_player_name}", + "--version", + "${version_name}", + "--gameDir", + "${game_directory}", + "--assetsDir", + "${assets_root}", + "--assetIndex", + "${assets_index_name}", + "--uuid", + "${auth_uuid}", + "--accessToken", + "${auth_access_token}", + "--userProperties", + "${user_properties}", + "--userType", + "${user_type}" + }, parsedArgs); + } + + [Fact] + public void parse_from_vanilla_game_arg_array() // 1.13 ~ + { + // release 1.13 ~ + // release 1.19 ~ : add --clientId ${clientid} --xuid ${auth_xuid} + // release 1.20 ~ : add --quickPlayPath, --quickPlaySingleplayer, --quickPlayMultiplayer, --quickPlayRealms + + var json = """ +{ + "id": "1.13", + "arguments": { + "game": [ + "--username", + "${auth_player_name}", + "--version", + "${version_name}", + "--gameDir", + "${game_directory}", + "--assetsDir", + "${assets_root}", + "--assetIndex", + "${assets_index_name}", + "--uuid", + "${auth_uuid}", + "--accessToken", + "${auth_access_token}", + "--clientId", + "${clientid}", + "--xuid", + "${auth_xuid}", + "--userType", + "${user_type}", + "--versionType", + "${version_type}", + { + "rules": [ + { + "action": "allow", + "features": { + "is_demo_user": true + } + } + ], + "value": "--demo" + }, + { + "rules": [ + { + "action": "allow", + "features": { + "has_custom_resolution": true + } + } + ], + "value": [ + "--width", + "${resolution_width}", + "--height", + "${resolution_height}" + ] + }, + { + "rules": [ + { + "action": "allow", + "features": { + "has_quick_plays_support": true + } + } + ], + "value": [ + "--quickPlayPath", + "${quickPlayPath}" + ] + }, + { + "rules": [ + { + "action": "allow", + "features": { + "is_quick_play_singleplayer": true + } + } + ], + "value": [ + "--quickPlaySingleplayer", + "${quickPlaySingleplayer}" + ] + }, + { + "rules": [ + { + "action": "allow", + "features": { + "is_quick_play_multiplayer": true + } + } + ], + "value": [ + "--quickPlayMultiplayer", + "${quickPlayMultiplayer}" + ] + }, + { + "rules": [ + { + "action": "allow", + "features": { + "is_quick_play_realms": true + } + } + ], + "value": [ + "--quickPlayRealms", + "${quickPlayRealms}" + ] + } + ] + } +} +"""; + + var version = JsonVersionParser.ParseFromJsonString(json, options); + + var parsedArgs = version.JvmArguments + .SelectMany(arg => arg.Values ?? Enumerable.Empty()) + .ToArray(); + Assert.Equal(new [] + { + "--username", + "${auth_player_name}", + "--version", + "${version_name}", + "--gameDir", + "${game_directory}", + "--assetsDir", + "${assets_root}", + "--assetIndex", + "${assets_index_name}", + "--uuid", + "${auth_uuid}", + "--accessToken", + "${auth_access_token}", + "--clientId", + "${clientid}", + "--xuid", + "${auth_xuid}", + "--userType", + "${user_type}", + "--versionType", + "${version_type}", + "--demo", + "--width", + "${resolution_width}", + "--height", + "${resolution_height}", + "--quickPlayPath", + "${quickPlayPath}", + "--quickPlaySingleplayer", + "${quickPlaySingleplayer}", + "--quickPlayMultiplayer", + "${quickPlayMultiplayer}", + "--quickPlayRealms", + "${quickPlayRealms}" + }, parsedArgs); + } + + [Fact] + public void parse_from_vanilla_jvm_arg_array() + { + // release 1.13 ~ + // release 1.20 ~ : add -Djna.tmpdir=, -Dorg.lwjgl.system.SharedLibraryExtractPath, -Dio.netty.native.workdir + + var json = """ +{ + "id": "1.13", + "arguments": { + "jvm": [ + { + "rules": [ + { + "action": "allow", + "os": { + "name": "osx" + } + } + ], + "value": [ + "-XstartOnFirstThread" + ] + }, + { + "rules": [ + { + "action": "allow", + "os": { + "name": "windows" + } + } + ], + "value": "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump" + }, + { + "rules": [ + { + "action": "allow", + "os": { + "name": "windows", + "version": "^10\\." + } + } + ], + "value": [ + "-Dos.name=Windows 10", + "-Dos.version=10.0" + ] + }, + { + "rules": [ + { + "action": "allow", + "os": { + "arch": "x86" + } + } + ], + "value": "-Xss1M" + }, + "-Djava.library.path=${natives_directory}", + "-Djna.tmpdir=${natives_directory}", + "-Dorg.lwjgl.system.SharedLibraryExtractPath=${natives_directory}", + "-Dio.netty.native.workdir=${natives_directory}", + "-Dminecraft.launcher.brand=${launcher_name}", + "-Dminecraft.launcher.version=${launcher_version}", + "-cp", + "${classpath}" + ] + } +} +"""; + + var version = JsonVersionParser.ParseFromJsonString(json, options); + + var parsedArgs = version.JvmArguments + .SelectMany(args => args.Values ?? Enumerable.Empty()) + .ToArray(); + Assert.Equal(new [] + { + "-XstartOnFirstThread", + "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump", + "-Dos.name=Windows 10", + "-Dos.version=10.0", + "-Xss1M", + "-Djava.library.path=${natives_directory}", + "-Djna.tmpdir=${natives_directory}", + "-Dorg.lwjgl.system.SharedLibraryExtractPath=${natives_directory}", + "-Dio.netty.native.workdir=${natives_directory}", + "-Dminecraft.launcher.brand=${launcher_name}", + "-Dminecraft.launcher.version=${launcher_version}", + "-cp", + "${classpath}" + }, parsedArgs); + } +} \ No newline at end of file diff --git a/test/Version/JsonLibraryParserTests.cs b/test/Version/JsonLibraryParserTests.cs new file mode 100644 index 0000000..4734311 --- /dev/null +++ b/test/Version/JsonLibraryParserTests.cs @@ -0,0 +1,264 @@ +using System.Text.Json; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test.Version; + +public class JsonLibraryParserTests +{ + [Fact] + public void parse_simple_java_library() + { + // release 1.0 + var json = """ +{ + "downloads": { + "artifact": { + "path": "net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", + "sha1": "5150b9c2951f0fde987ce9c33496e26add1de224", + "size": 27787, + "url": "https://libraries.minecraft.net/net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar" + } + }, + "name": "net.minecraft:launchwrapper:1.5" +} +"""; + + using var jsonDocument = JsonDocument.Parse(json); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + + Assert.Equal("net.minecraft:launchwrapper:1.5", lib?.Name); + Assert.Equal("net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", lib?.Artifact?.Path); + Assert.Equal("5150b9c2951f0fde987ce9c33496e26add1de224", lib?.Artifact?.Sha1); + Assert.Equal(27787, lib?.Artifact?.Size); + Assert.Equal("https://libraries.minecraft.net/net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", lib?.Artifact?.Url); + } + + [Fact] + public void parse_java_library_with_rules() + { + // release 1.0 + var json = """ + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl/2.9.0/lwjgl-2.9.0.jar", + "sha1": "5654d06e61a1bba7ae1e7f5233e1106be64c91cd", + "size": 994633, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.0/lwjgl-2.9.0.jar" + } + }, + "name": "org.lwjgl.lwjgl:lwjgl:2.9.0", + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ] + } + """; + + using var jsonDocument = JsonDocument.Parse(json); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + + Assert.Equal(2, lib?.Rules?.Length); + } + + [Fact] + public void parse_native_library() + { + // release 1.2.5 + var json = """ + { + "downloads": { + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar", + "sha1": "2ba5dcb11048147f1a74eff2deb192c001321f77", + "size": 569061, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar" + }, + "natives-osx": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-osx.jar", + "sha1": "6621b382cb14cc409b041d8d72829156a87c31aa", + "size": 518924, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-osx.jar" + }, + "natives-windows": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", + "sha1": "3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", + "size": 609967, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.0", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ] + } + """; + + using var jsonDocument = JsonDocument.Parse(json); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "64")); + + Assert.Equal("org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", nativeLib?.Path); + Assert.Equal("3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", nativeLib?.Sha1); + Assert.Equal(609967, nativeLib?.Size); + Assert.Equal("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", nativeLib?.Url); + } + + [Fact] + public void parse_native_library_with_arch() + { + // release 1.7.10 + var json = """ + { + "downloads": { + "classifiers": { + "natives-osx": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", + "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", + "size": 418331, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" + }, + "natives-windows-32": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", + "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", + "size": 386792, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" + }, + "natives-windows-64": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", + "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", + "size": 463390, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "tv.twitch:twitch-platform:5.16", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows-${arch}" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "linux" + } + } + ] + } + """; + + using var jsonDocument = JsonDocument.Parse(json); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + + var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "32")); + Assert.Equal("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", nativeLib?.Path); + } + + [Fact] + public void parse_java_library_and_native_library() + { + // release 1.8.9 + var json = """ + { + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", + "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + "size": 22, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }, + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", + "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", + "size": 578680, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" + }, + "natives-osx": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", + "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", + "size": 426822, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" + }, + "natives-windows": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", + "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", + "size": 613748, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx" + } + } + ] + } + """; + + using var jsonDocument = JsonDocument.Parse(json); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + + var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "32")); + Assert.Equal("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", nativeLib?.Path); + + var javaLib = lib?.Artifact; + Assert.Equal("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", javaLib?.Path); + } +} \ No newline at end of file diff --git a/test/Version/JsonVersionParserTests.cs b/test/Version/JsonVersionParserTests.cs new file mode 100644 index 0000000..f1d550d --- /dev/null +++ b/test/Version/JsonVersionParserTests.cs @@ -0,0 +1,148 @@ +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test.Version; + +public class JsonVersionParserTests +{ + private readonly static JsonVersionParserOptions options = new JsonVersionParserOptions + { + Side = JsonVersionParserOptions.ClientSide, + SkipError = false + }; + + [Fact] + public void parse_basic_properties_from_vanilla() + { + // release 1.0 ~ : same + // release 1.16.5 ~ : complianceLevel 1 + // release 1.17 ~ : java-runtime-alpha, 16 + // release 1.18 ~ : java-runtime-beta, 17 + // release 1.19 ~ : java-runtime-gamma, 17 + var json = """ +{ + "id": "1.0", + "javaVersion": { + "component": "jre-legacy", + "majorVersion": 8 + }, + "complianceLevel": 0, + "mainClass": "net.minecraft.launchwrapper.Launch", + "minimumLauncherVersion": 4, + "releaseTime": "2011-11-17T22:00:00+00:00", + "time": "2011-11-17T22:00:00+00:00", + "type": "release" +} +"""; + var version = JsonVersionParser.ParseFromJsonString(json, options); + + Assert.Equal("1.0", version.Id); + Assert.Null(version.InheritsFrom); + Assert.Equal("jre-legacy", version.JavaVersion?.Component); + Assert.Equal(8, version.JavaVersion?.MajorVersion); + Assert.Null(version.Jar); + Assert.Equal("net.minecraft.launchwrapper.Launch", version.MainClass); + Assert.Equal("release", version.Type); + Assert.Equal(DateTime.Parse("2011-11-17T22:00:00+00:00"), version.ReleaseTime); + Assert.Equal("4", version.GetProperty("minimumLauncherVersion")); + } + + [Fact] + public void parse_asset_from_vanilla() + { + // assetId + // release 1.0 ~ 1.5.2 : pre-1.6 + // release 1.6.1 ~ 1.7.2 : legacy + // release 1.7.3 ~ : inconsistent + var json = """ +{ + "id": "1.0", + "assetIndex": { + "id": "pre-1.6", + "sha1": "3d8e55480977e32acd9844e545177e69a52f594b", + "size": 74091, + "totalSize": 49505710, + "url": "https://launchermeta.mojang.com/v1/packages/3d8e55480977e32acd9844e545177e69a52f594b/pre-1.6.json" + }, + "assets": "pre-1.6" +} +"""; + + var version = JsonVersionParser.ParseFromJsonString(json, options); + + Assert.Equal("pre-1.6", version.AssetIndex?.Id); + Assert.Equal("3d8e55480977e32acd9844e545177e69a52f594b", version.AssetIndex?.Sha1); + Assert.Equal(74091, version.AssetIndex?.Size); + Assert.Equal(49505710, version.AssetIndex?.TotalSize); + Assert.Equal("https://launchermeta.mojang.com/v1/packages/3d8e55480977e32acd9844e545177e69a52f594b/pre-1.6.json", version.AssetIndex?.Url); + } + + [Fact] + public void parse_client_from_vanilla() + { + // release 1.0 ~ : only client + // release 1.2.5 ~ : client, server, some windows_server + // release 1.14.4 ~ : client, client_mappings, server, server_mapping + var json = """ +{ + "id": "1.0", + "downloads": { + "client": { + "sha1": "b679fea27f2284836202e9365e13a82552092e5d", + "size": 2362837, + "url": "https://launcher.mojang.com/v1/objects/b679fea27f2284836202e9365e13a82552092e5d/client.jar" + }, + "server": { + "sha1": "0252918a5f9d47e3c6eb1dfec02134d1374a89b4", + "size": 6132004, + "url": "https://launcher.mojang.com/v1/objects/0252918a5f9d47e3c6eb1dfec02134d1374a89b4/server.jar" + }, + "windows_server": { + "sha1": "f495386d0eded7346e7e77a1c6d7dfc5a5dae068", + "size": 6527780, + "url": "https://launcher.mojang.com/v1/objects/f495386d0eded7346e7e77a1c6d7dfc5a5dae068/windows_server.exe" + } + } +} +"""; + + var version = JsonVersionParser.ParseFromJsonString(json, options); + + Assert.Equal("b679fea27f2284836202e9365e13a82552092e5d", version.Client?.Sha1); + Assert.Equal(2362837, version.Client?.Size); + Assert.Equal("https://launcher.mojang.com/v1/objects/b679fea27f2284836202e9365e13a82552092e5d/client.jar", version.Client?.Url); + } + + [Fact] + public void parse_logging_from_vanilla() + { + // release before 1.7.2 : null + // release 1.7.2 ~ : 1.7 + // release 1.12 ~ : 1.12 + var json = """ +{ + "id": "1.7.2", + "logging": { + "client": { + "argument": "-Dlog4j.configurationFile=${path}", + "file": { + "id": "client-1.7.xml", + "sha1": "50c9cc4af6d853d9fc137c84bcd153e2bd3a9a82", + "size": 966, + "url": "https://launcher.mojang.com/v1/objects/50c9cc4af6d853d9fc137c84bcd153e2bd3a9a82/client-1.7.xml" + }, + "type": "log4j2-xml" + } + } +} +"""; + + var version = JsonVersionParser.ParseFromJsonString(json, options); + + Assert.Equal("-Dlog4j.configurationFile=${path}", version.Logging?.Argument); + Assert.Equal("client-1.7.xml", version.Logging?.LogFile?.Id); + Assert.Equal("50c9cc4af6d853d9fc137c84bcd153e2bd3a9a82", version.Logging?.LogFile?.Sha1); + Assert.Equal(966, version.Logging?.LogFile?.Size); + Assert.Equal("https://launcher.mojang.com/v1/objects/50c9cc4af6d853d9fc137c84bcd153e2bd3a9a82/client-1.7.xml", version.Logging?.LogFile?.Url); + Assert.Equal("log4j2-xml", version.Logging?.Type); + } +} \ No newline at end of file diff --git a/test/VersionMetadata/VersionMetadataCollectionTest.cs b/test/VersionMetadata/VersionMetadataCollectionTests.cs similarity index 56% rename from test/VersionMetadata/VersionMetadataCollectionTest.cs rename to test/VersionMetadata/VersionMetadataCollectionTests.cs index f761db6..c068ace 100644 --- a/test/VersionMetadata/VersionMetadataCollectionTest.cs +++ b/test/VersionMetadata/VersionMetadataCollectionTests.cs @@ -3,23 +3,21 @@ using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using Moq; -using NUnit.Framework; namespace CmlLib.Core.Test.VersionMetadata; public class VersionMetadataCollectionTest { - [Test] - public void TestGetVersionMetadata() + [Fact] + public void get_version_by_id() { var (collection, parent, child) = createMocks(); - var result = collection.GetVersionMetadata(parent.Id); - Assert.That(result.Name, Is.EqualTo("parent")); + Assert.Equal("parent", result.Name); } - [Test] - public void TestGetVersionMetadataException() + [Fact] + public void get_version_by_not_existing_id() { var (collection, parent, child) = createMocks(); @@ -29,34 +27,37 @@ public void TestGetVersionMetadataException() }); } - [Test] - public void TestEnumerationOrder() + [Fact] + public void keep_enumeration_order() { var (collection, parent, child) = createMocks(); - var names = collection.Select(v => v.Name); - Assert.That(names, Is.EqualTo(new string[] { "parent", "child" })); + Assert.Equal(new [] { "parent", "child" }, names); } - [Test] - public async Task TestInheritance() + [Fact] + public async Task get_inherited_property() { var (collection, parent, child) = createMocks(); + var version = await collection.GetVersionAsync("child"); + Assert.Equal("child_assetindex", version.GetInheritedProperty(v => v.AssetIndex)?.Id); + Assert.Equal("parent_mainclass", version.GetInheritedProperty(v => v.MainClass)); + } - var result = await collection.GetVersionAsync("child"); - Assert.That(result, Is.EqualTo(child)); - Assert.That(result.ParentVersion, Is.EqualTo(parent)); - Assert.That(result.GetInheritedProperty(v => v.AssetIndex)?.Id, Is.EqualTo("child_assetindex")); - Assert.That(result.GetInheritedProperty(v => v.MainClass), Is.EqualTo("parent_mainclass")); - - var concatArguments = result.ConcatInheritedCollection(v => v.GameArguments) + public async Task get_inherited_collection() + { + var (collection, parent, child) = createMocks(); + var version = await collection.GetVersionAsync("child"); + var concatArguments = version.ConcatInheritedCollection(v => v.GameArguments) .SelectMany(args => args.Values ?? Enumerable.Empty()) .ToArray(); - Assert.That(concatArguments, Is.EqualTo(new string[] { "parent_arg", "child_arg" })); + + // should keep the order + Assert.Equal(new []{ "parent_arg", "child_arg" }, concatArguments); } - [Test] - public void TestCircularInheritanceException() + [Fact] + public void throw_circular_inheritance() { var (collection, parent, child) = createMocks(); parent.InheritsFrom = child.Id; @@ -67,22 +68,26 @@ public void TestCircularInheritanceException() }); } - [Test] - public async Task TestSelfInheritance() + [Fact] + public async Task handle_self_inheritance() { + // Given var (collection, parent, child) = createMocks(); child.InheritsFrom = child.Id; - var actual = await collection.GetVersionAsync("child"); - Assert.That(actual.InheritsFrom, Is.EqualTo(child.Id)); - Assert.Null(actual.ParentVersion); + // When + var selfInherited = await collection.GetVersionAsync("child"); + + // Then + Assert.Equal("child", selfInherited.InheritsFrom); + Assert.Null(selfInherited.ParentVersion); } - [Test] - public void TestNonExistInheritanceException() + [Fact] + public void throw_non_existent_parent_id() { var (collection, parent, child) = createMocks(); - child.InheritsFrom = "NON_EXISTENCE_VERSION_ID"; + child.InheritsFrom = "NON_EXISTENT_VERSION_ID"; Assert.ThrowsAsync(async () => { @@ -90,13 +95,13 @@ public void TestNonExistInheritanceException() }); } - [Test] - public void TestInheritanceOverflow() + [Fact] + public void throw_too_deep_inheritance() { - var v1 = new MockVersion("v1"); - var v2 = new MockVersion("v2"); - var v3 = new MockVersion("v3"); - var v4 = new MockVersion("v4"); + var v1 = new DummyVersion("v1"); + var v2 = new DummyVersion("v2"); + var v3 = new DummyVersion("v3"); + var v4 = new DummyVersion("v4"); v4.InheritsFrom = v3.Id; v3.InheritsFrom = v2.Id; @@ -118,13 +123,12 @@ public void TestInheritanceOverflow() }); } - [Test] - public void TestMerge() + private (VersionMetadataCollection, VersionMetadataCollection) createTestCollections() { - var v1 = createMockVersionMetadata(new MockVersion("v1")); - var v2 = createMockVersionMetadata(new MockVersion("v2")); - var v3 = createMockVersionMetadata(new MockVersion("v3")); - var v4 = createMockVersionMetadata(new MockVersion("v4")); + var v1 = createMockVersionMetadata(new DummyVersion("v1")); + var v2 = createMockVersionMetadata(new DummyVersion("v2")); + var v3 = createMockVersionMetadata(new DummyVersion("v3")); + var v4 = createMockVersionMetadata(new DummyVersion("v4")); var collection1 = new VersionMetadataCollection(new IVersionMetadata[] { @@ -136,20 +140,29 @@ public void TestMerge() v2, v3, v4 }, "v2", "v4"); + return (collection1, collection2); + } + + [Fact] + public void keep_enumeration_order_after_merging() + { + var (collection1, collection2) = createTestCollections(); collection1.Merge(collection2); + Assert.Equal(new [] { "v1", "v2", "v3", "v4" }, collection1.Select(v => v.Name)); + } - Assert.That( - collection1.Select(v => v.Name), Is.EqualTo( - new string[] { "v1", "v2", "v3", "v4" }), - "Wrong order"); - - Assert.That(collection1.LatestReleaseName, Is.EqualTo("v1"), "Wrong LatestReleaseName"); - Assert.That(collection1.LatestSnapshotName, Is.EqualTo("v4"), "Wrong LatestSnapshotName"); + [Fact] + public void merge_latest_version_names() + { + var (collection1, collection2) = createTestCollections(); + collection1.Merge(collection2); + Assert.Equal("v1", collection1.LatestReleaseName); + Assert.Equal("v4", collection1.LatestSnapshotName); } - private (VersionMetadataCollection, MockVersion, MockVersion) createMocks() + private (VersionMetadataCollection, DummyVersion, DummyVersion) createMocks() { - var parent = new MockVersion("parent"); + var parent = new DummyVersion("parent"); parent.MainClass = "parent_mainclass"; parent.AssetIndex = new Files.AssetMetadata { @@ -160,7 +173,7 @@ public void TestMerge() new MArgument("parent_arg") }; - var child = new MockVersion("child"); + var child = new DummyVersion("child"); child.AssetIndex = new Files.AssetMetadata { Id = "child_assetindex" diff --git a/test/VersionMetadata/VersionMetadataSorterTests.cs b/test/VersionMetadata/VersionMetadataSorterTests.cs new file mode 100644 index 0000000..b8a78fd --- /dev/null +++ b/test/VersionMetadata/VersionMetadataSorterTests.cs @@ -0,0 +1,6 @@ +namespace CmlLib.Core.Test.VersionMetadata; + +public class VersionMetadataSorterTests +{ + +} \ No newline at end of file From c392d4fdeb18e1102777df0031a22eccfd66cbcf Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 14 Feb 2024 09:31:05 +0900 Subject: [PATCH 089/137] feat: add ByteProgressDelta --- TODO.md | 3 + benchmark/Installers/ThreadLocalBenchmark.cs | 77 ++++++++++++++++++++ benchmark/Program.cs | 8 +- src/Installers/BasicGameInstaller.cs | 25 ++----- src/Installers/ByteProgressDelta.cs | 43 +++++++++++ src/Installers/ParallelGameInstaller.cs | 24 ++---- 6 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 benchmark/Installers/ThreadLocalBenchmark.cs create mode 100644 src/Installers/ByteProgressDelta.cs diff --git a/TODO.md b/TODO.md index 040fa9c..32da1d8 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,9 @@ - [] GameInstaller 에서 중복 파일 확인 - [] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 - [] 통합 테스트 작성. 모든 주요 버전 한번씩 실제로 실행해보는 테스트 프로그램 작성 +- [] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 +- [] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 +- [] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성, 이후 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 # Flow diff --git a/benchmark/Installers/ThreadLocalBenchmark.cs b/benchmark/Installers/ThreadLocalBenchmark.cs new file mode 100644 index 0000000..757ae1e --- /dev/null +++ b/benchmark/Installers/ThreadLocalBenchmark.cs @@ -0,0 +1,77 @@ +using BenchmarkDotNet.Attributes; + +namespace CmlLib.Core.Benchmarks; + +public class ThreadLocalBenchmark +{ + public int ThreadCount = 6; + public int IterationCount = 1000; + public int AggregateCount = 10; + + private ThreadLocal smallThreadLocal1 = new(true); + private ThreadLocal smallThreadLocal2 = new(true); + + [Benchmark] + public long OneThreadLocal() + { + var threads = new List(); + for (int i = 0; i < ThreadCount; i++) + { + var thread = new Thread(() => + { + for (int i = 0; i < IterationCount; i++) + { + smallThreadLocal1.Value = smallThreadLocal1.Value + i; + smallThreadLocal2.Value = smallThreadLocal2.Value + IterationCount - i; + } + }); + threads.Add(thread); + } + + foreach (var t in threads) t.Start(); + + long result = 0; + for (int i = 0; i < AggregateCount; i++) + { + result += smallThreadLocal1.Values.Sum() + smallThreadLocal2.Values.Sum(); + } + + foreach (var t in threads) t.Join(); + return result; + } + + private ThreadLocal bigThreadLocal = new(true); + + [Benchmark] + public long TwoThreadLocal() + { + var threads = new List(); + for (int i = 0; i < ThreadCount; i++) + { + var thread = new Thread(() => + { + for (int i = 0; i < IterationCount; i++) + { + var stored = bigThreadLocal.Value; + bigThreadLocal.Value = new ByteProgress + { + TotalBytes = stored.TotalBytes + i, + ProgressedBytes = stored.ProgressedBytes + IterationCount - i + }; + } + }); + threads.Add(thread); + } + + foreach (var t in threads) t.Start(); + + long result = 0; + for (int i = 0; i < AggregateCount; i++) + { + result += bigThreadLocal.Values.Select(v => v.TotalBytes + v.ProgressedBytes).Sum(); + } + + foreach (var t in threads) t.Join(); + return result; + } +} \ No newline at end of file diff --git a/benchmark/Program.cs b/benchmark/Program.cs index ab39029..b1d49e9 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,5 +1,5 @@ -using System.Diagnostics; -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; +using CmlLib.Core.Benchmarks; -Console.WriteLine("Hello, World!"); -//var summary = BenchmarkRunner.Run(); \ No newline at end of file +//Console.WriteLine("Hello, World!"); +var summary = BenchmarkRunner.Run(); \ No newline at end of file diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 7482963..8350ba5 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -21,30 +21,21 @@ protected override async ValueTask Install( var gameFile = gameFiles[i]; FireFileProgress(gameFiles.Count, i, gameFile.Name, InstallerEventType.Queued); - long lastTotal = gameFile.Size; - long lastProgressed = 0; - var progressIntercepter = new SyncProgress(p => - { - totalBytes += p.TotalBytes - lastTotal; - progressedBytes += p.ProgressedBytes - lastProgressed; - lastTotal = p.TotalBytes; - lastProgressed = p.ProgressedBytes; - - FireByteProgress(totalBytes, progressedBytes); - }); + var progress = new ByteProgressDelta(initialSize: gameFile.Size, delta => + { + totalBytes += delta.TotalBytes; + progressedBytes += delta.ProgressedBytes; + FireByteProgress(totalBytes, progressedBytes); + }); if (NeedUpdate(gameFile)) { - await Download(gameFile, progressIntercepter, cancellationToken); + await Download(gameFile, progress, cancellationToken); await gameFile.ExecuteUpdateTask(cancellationToken); } else { - progressIntercepter.Report(new ByteProgress - { - TotalBytes = gameFile.Size, - ProgressedBytes = gameFile.Size - }); + progress.ReportDone(); } FireFileProgress(gameFiles.Count, i + 1, gameFile.Name, InstallerEventType.Done); diff --git a/src/Installers/ByteProgressDelta.cs b/src/Installers/ByteProgressDelta.cs new file mode 100644 index 0000000..15e2fd3 --- /dev/null +++ b/src/Installers/ByteProgressDelta.cs @@ -0,0 +1,43 @@ +namespace CmlLib.Core.Installers; + +public class ByteProgressDelta : IProgress +{ + private readonly Action _action; + private ByteProgress lastProgress; + + public ByteProgressDelta(long initialSize, Action action) + { + lastProgress = new ByteProgress + { + TotalBytes = initialSize, + ProgressedBytes = 0 + }; + _action = action; + } + + public ByteProgressDelta(ByteProgress initial, Action action) + { + lastProgress = initial; + _action = action; + } + + public void Report(ByteProgress value) + { + var delta = new ByteProgress + { + TotalBytes = value.TotalBytes - lastProgress.TotalBytes, + ProgressedBytes = value.ProgressedBytes - lastProgress.ProgressedBytes + }; + lastProgress = value; + _action(delta); + } + + public void ReportDone() + { + Report(new ByteProgress + { + TotalBytes = lastProgress.TotalBytes, + ProgressedBytes = lastProgress.TotalBytes + }); + } +} \ No newline at end of file diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index 09bfe13..b4fd013 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -93,34 +93,24 @@ protected override async ValueTask Install( var downloadBlock = new ActionBlock<(GameFile GameFile, bool NeedUpdate)>(async result => { - var lastProgress = new ByteProgress - { - TotalBytes = result.GameFile.Size, - ProgressedBytes = 0 - }; - - var progressIntercepter = new SyncProgress(p => + var progress = new ByteProgressDelta(initialSize: result.GameFile.Size, delta => { + var storedProgress = progressStorage.Value; progressStorage.Value = new ByteProgress { - TotalBytes = progressStorage.Value.TotalBytes + (p.TotalBytes - lastProgress.TotalBytes), - ProgressedBytes = progressStorage.Value.ProgressedBytes + (p.ProgressedBytes - lastProgress.ProgressedBytes) + TotalBytes = storedProgress.TotalBytes + delta.TotalBytes, + ProgressedBytes = storedProgress.ProgressedBytes + delta.ProgressedBytes }; - lastProgress = p; }); - + if (result.NeedUpdate) { - await Download(result.GameFile, progressIntercepter, cancellationToken); + await Download(result.GameFile, progress, cancellationToken); await result.GameFile.ExecuteUpdateTask(cancellationToken); } else { - progressIntercepter.Report(new ByteProgress - { - TotalBytes = result.GameFile.Size, - ProgressedBytes = result.GameFile.Size - }); + progress.ReportDone(); } Interlocked.Increment(ref progressedFiles); From 74251dee8fcbfce3d0cbb885aa8af3b2aa5cf16b Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 14 Feb 2024 09:36:40 +0900 Subject: [PATCH 090/137] feat: filter duplicated paths from extractors --- TODO.md | 4 ++-- src/MinecraftLauncher.cs | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 32da1d8..2d462d6 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,8 @@ - [] 테스트케이스 더 자세하게 작성 - [] MLaunchOption 에서 ExtraArguments 같은거 추가 - [] master 브랜치에서 v3.4.0 으로 cherry-pick -- [] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 -- [] GameInstaller 에서 중복 파일 확인 +- [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 +- [x] GameInstaller 에서 중복 파일 확인 - [] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 - [] 통합 테스트 작성. 모든 주요 버전 한번씩 실제로 실행해보는 테스트 프로그램 작성 - [] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index c2409c5..d91c9bc 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -103,12 +103,17 @@ public async ValueTask> ExtractFiles( IVersion version, CancellationToken cancellationToken) { - var files = new List(); + // filter duplicated paths + var fileSet = new Dictionary(); foreach (var extractor in FileExtractors) { - files.AddRange(await extractor.Extract(MinecraftPath, version, RulesContext, cancellationToken)); + var files = await extractor.Extract(MinecraftPath, version, RulesContext, cancellationToken); + foreach (var file in files) + { + fileSet[file.Path ?? ""] = file; + } } - return files; + return fileSet.Values.ToList(); } public async ValueTask InstallAsync( From 5aaa5a9de055d30134eb5d7e7e2aa819a1bad948 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Wed, 14 Feb 2024 09:42:32 +0900 Subject: [PATCH 091/137] feat: add ExcludeFiles to GameInstallerBase --- TODO.md | 2 +- src/Installers/BasicGameInstaller.cs | 2 +- src/Installers/GameInstallerBase.cs | 11 +++++++++++ src/Installers/ParallelGameInstaller.cs | 7 +++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 2d462d6..f51b48f 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ - [] master 브랜치에서 v3.4.0 으로 cherry-pick - [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 - [x] GameInstaller 에서 중복 파일 확인 -- [] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 +- [x] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 - [] 통합 테스트 작성. 모든 주요 버전 한번씩 실제로 실행해보는 테스트 프로그램 작성 - [] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 - [] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 8350ba5..e9021be 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -28,7 +28,7 @@ protected override async ValueTask Install( FireByteProgress(totalBytes, progressedBytes); }); - if (NeedUpdate(gameFile)) + if (NeedUpdate(gameFile) && !CheckExcludeFile(gameFile.Path ?? "")) { await Download(gameFile, progress, cancellationToken); await gameFile.ExecuteUpdateTask(cancellationToken); diff --git a/src/Installers/GameInstallerBase.cs b/src/Installers/GameInstallerBase.cs index 5014221..5b0ac00 100644 --- a/src/Installers/GameInstallerBase.cs +++ b/src/Installers/GameInstallerBase.cs @@ -16,7 +16,9 @@ public GameInstallerBase(HttpClient httpClient) private volatile bool IsRunning = false; public bool CheckFileSize { get; set; } = false; public bool CheckFileChecksum { get; set; } = true; + public IEnumerable ExcludeFiles { get; set; } = Enumerable.Empty(); + private HashSet excludeSet = new(); private IProgress? FileProgress; private IProgress? ByteProgress; @@ -33,6 +35,10 @@ public async ValueTask Install( this.FileProgress = fileProgress; this.ByteProgress = byteProgress; + excludeSet.Clear(); + foreach (var excludeFile in ExcludeFiles) + excludeSet.Add(excludeFile); + await Install(gameFiles, cancellationToken); IsRunning = false; } @@ -82,6 +88,11 @@ await HttpClientDownloadHelper.DownloadFileAsync( cancellationToken); } + protected bool CheckExcludeFile(string path) + { + return excludeSet.Contains(path); + } + protected void FireFileProgress( int totalTasks, int progressedTasks, diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index b4fd013..5754fb1 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -57,6 +57,13 @@ protected override async ValueTask Install( for (int i = 0; i < totalFiles; i++) { var file = gameFiles[i]; + if (CheckExcludeFile(file.Path ?? "")) + { + totalFiles--; + totalBytes -= file.Size; + continue; + } + FireFileProgress(totalFiles, progressedFiles, file.Name, InstallerEventType.Queued); await firstBlock.SendAsync(file, cancellationToken); } From 5d10fe00d02b2636453b9d0192793a3c353688f2 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 25 Feb 2024 22:31:11 +0900 Subject: [PATCH 092/137] refactor: replace array to IEnumerable or IReadOnlyCollection --- TODO.md | 9 ++++++--- benchmark/DummyVersion.cs | 6 +++--- src/Files/MFileMetadata.cs | 2 +- src/Files/MLibrary.cs | 2 +- src/ProcessBuilder/MArgument.cs | 4 ++-- src/ProcessBuilder/MLaunchOption.cs | 2 +- src/ProcessBuilder/ProcessArgumentBuilder.cs | 4 ++-- src/Rules/RulesEvaluatorContext.cs | 2 +- src/Version/IVersion.cs | 6 +++--- src/Version/JsonArgumentParser.cs | 4 ++-- src/Version/JsonRulesParser.cs | 2 +- src/Version/JsonVersion.cs | 20 ++++++++++---------- 12 files changed, 33 insertions(+), 30 deletions(-) diff --git a/TODO.md b/TODO.md index f51b48f..defb8a6 100644 --- a/TODO.md +++ b/TODO.md @@ -1,13 +1,16 @@ -- [] 테스트케이스 더 자세하게 작성 - [] MLaunchOption 에서 ExtraArguments 같은거 추가 - [] master 브랜치에서 v3.4.0 으로 cherry-pick - [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 - [x] GameInstaller 에서 중복 파일 확인 - [x] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 -- [] 통합 테스트 작성. 모든 주요 버전 한번씩 실제로 실행해보는 테스트 프로그램 작성 +- [] MArgument 유닛테스트 +- [] Extractors 최대한 유닛테스트 +- [] Extractor 통합 테스트 작성. 주요 버전 파싱, GameFile 확인 +- [] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 +- [] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성, 이후 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 - [] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 - [] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 -- [] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성, 이후 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 +- [] Memory 위에서 mutable 한 IVersion 구현 # Flow diff --git a/benchmark/DummyVersion.cs b/benchmark/DummyVersion.cs index bed30bd..c30530a 100644 --- a/benchmark/DummyVersion.cs +++ b/benchmark/DummyVersion.cs @@ -19,7 +19,7 @@ public class DummyVersion : IVersion public JavaVersion? JavaVersion => throw new NotImplementedException(); - public MLibrary[] Libraries => throw new NotImplementedException(); + public IReadOnlyCollection Libraries => throw new NotImplementedException(); public string? Jar => throw new NotImplementedException(); @@ -27,9 +27,9 @@ public class DummyVersion : IVersion public string? MainClass => throw new NotImplementedException(); - public MArgument[] GameArguments => throw new NotImplementedException(); + public IReadOnlyCollection GameArguments => throw new NotImplementedException(); - public MArgument[] JvmArguments => throw new NotImplementedException(); + public IReadOnlyCollection JvmArguments => throw new NotImplementedException(); public DateTime ReleaseTime => throw new NotImplementedException(); diff --git a/src/Files/MFileMetadata.cs b/src/Files/MFileMetadata.cs index 31ebfcd..eb8c822 100644 --- a/src/Files/MFileMetadata.cs +++ b/src/Files/MFileMetadata.cs @@ -17,7 +17,7 @@ public class MFileMetadata public string? Sha1 { get; set; } [JsonPropertyName("checksums")] - public string[]? Checksums { get; set; } + public IReadOnlyCollection? Checksums { get; set; } [JsonPropertyName("size")] public long Size { get; set; } diff --git a/src/Files/MLibrary.cs b/src/Files/MLibrary.cs index e42b7b2..8bc1b25 100644 --- a/src/Files/MLibrary.cs +++ b/src/Files/MLibrary.cs @@ -11,7 +11,7 @@ public MLibrary(string name) => public MFileMetadata? Artifact { get; set; } public Dictionary? Classifiers { get; set; } public Dictionary? Natives { get; set; } - public LauncherRule[]? Rules { get; set; } + public IReadOnlyCollection Rules { get; set; } = Array.Empty(); public string Name { get; set; } public bool IsServerRequired { get; set; } = true; public bool IsClientRequired { get; set; } = true; diff --git a/src/ProcessBuilder/MArgument.cs b/src/ProcessBuilder/MArgument.cs index 5f3cfd2..9827b16 100644 --- a/src/ProcessBuilder/MArgument.cs +++ b/src/ProcessBuilder/MArgument.cs @@ -14,6 +14,6 @@ public MArgument(string arg) Values = new string[] { arg }; } - public string[]? Values { get; set; } - public LauncherRule[]? Rules { get; set; } + public IReadOnlyCollection Values { get; set; } = Array.Empty(); + public IReadOnlyCollection Rules { get; set; } = Array.Empty(); } \ No newline at end of file diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index 1224dda..49ef98d 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -16,7 +16,7 @@ public class MLaunchOption public string? JavaPath { get; set; } public int MaximumRamMb { get; set; } = 1024; public int MinimumRamMb { get; set; } - public string[]? JVMArguments { get; set; } + public IEnumerable JVMArguments { get; set; } = Enumerable.Empty(); public string? DockName { get; set; } public string? DockIcon { get; set; } diff --git a/src/ProcessBuilder/ProcessArgumentBuilder.cs b/src/ProcessBuilder/ProcessArgumentBuilder.cs index 6a5ad3a..520d4de 100644 --- a/src/ProcessBuilder/ProcessArgumentBuilder.cs +++ b/src/ProcessBuilder/ProcessArgumentBuilder.cs @@ -116,9 +116,9 @@ public void AddKeyValue(string key, string? value) AddRaw(result); } - public string[] Build() + public IReadOnlyCollection Build() { - return _args.ToArray(); + return _args; } private string escapeValue(string value) diff --git a/src/Rules/RulesEvaluatorContext.cs b/src/Rules/RulesEvaluatorContext.cs index 0f06cf5..bc72444 100644 --- a/src/Rules/RulesEvaluatorContext.cs +++ b/src/Rules/RulesEvaluatorContext.cs @@ -8,5 +8,5 @@ public RulesEvaluatorContext(LauncherOSRule os) } public LauncherOSRule OS { get; set; } - public string[]? Features { get; set; } + public IEnumerable Features { get; set; } = Enumerable.Empty(); } \ No newline at end of file diff --git a/src/Version/IVersion.cs b/src/Version/IVersion.cs index add5fe1..b8ce684 100644 --- a/src/Version/IVersion.cs +++ b/src/Version/IVersion.cs @@ -13,12 +13,12 @@ public interface IVersion AssetMetadata? AssetIndex { get; } MFileMetadata? Client { get; } JavaVersion? JavaVersion { get; } - MLibrary[] Libraries { get; } + IReadOnlyCollection Libraries { get; } string? Jar { get; } MLogFileMetadata? Logging { get; } string? MainClass { get; } - MArgument[] GameArguments { get; } - MArgument[] JvmArguments { get; } + IReadOnlyCollection GameArguments { get; } + IReadOnlyCollection JvmArguments { get; } DateTime ReleaseTime { get; } string? Type { get; } diff --git a/src/Version/JsonArgumentParser.cs b/src/Version/JsonArgumentParser.cs index d42752f..f9f3d2b 100644 --- a/src/Version/JsonArgumentParser.cs +++ b/src/Version/JsonArgumentParser.cs @@ -6,7 +6,7 @@ namespace CmlLib.Core.Version; public static class JsonArgumentParser { - public static MArgument[] Parse(JsonElement element) + public static IReadOnlyCollection Parse(JsonElement element) { var list = new List(); foreach (var item in element.EnumerateArray()) @@ -67,7 +67,7 @@ public static MArgument[] Parse(JsonElement element) } } - if (arg.Values == null || arg.Values.Length == 0) + if (arg.Values == null || arg.Values.Count == 0) return null; return arg; } diff --git a/src/Version/JsonRulesParser.cs b/src/Version/JsonRulesParser.cs index 59d9d47..2bae0aa 100644 --- a/src/Version/JsonRulesParser.cs +++ b/src/Version/JsonRulesParser.cs @@ -5,7 +5,7 @@ namespace CmlLib.Core.Version; public static class JsonRulesParser { - public static LauncherRule[] Parse(JsonElement element) + public static IReadOnlyCollection Parse(JsonElement element) { var list = new List(); diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index 17e95a7..e58d678 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -35,8 +35,8 @@ public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) public JavaVersion? JavaVersion => _model.JavaVersion; - private MLibrary[]? _libs = null; - public MLibrary[] Libraries => _libs ??= getLibraries(); + private IReadOnlyCollection? _libs = null; + public IReadOnlyCollection Libraries => _libs ??= getLibraries(); public string? Jar => _model.Jar; @@ -51,12 +51,12 @@ public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) public string? Type => _model.Type; - private MArgument[]? _gameArgs = null; - public MArgument[] GameArguments => _gameArgs ??= getGameArguments(); + private IReadOnlyCollection? _gameArgs = null; + public IReadOnlyCollection GameArguments => _gameArgs ??= getGameArguments(); - private MArgument[]? _jvmArgs = null; + private IReadOnlyCollection? _jvmArgs = null; - public MArgument[] JvmArguments => _jvmArgs ??= getJvmArguments(); + public IReadOnlyCollection JvmArguments => _jvmArgs ??= getJvmArguments(); private string? getJarId() { @@ -96,7 +96,7 @@ public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) } } - private MLibrary[] getLibraries() + private IReadOnlyCollection getLibraries() { try { @@ -108,7 +108,7 @@ private MLibrary[] getLibraries() if (lib != null) libList.Add(lib); } - return libList.ToArray(); + return libList; } catch (Exception) { @@ -135,7 +135,7 @@ private MLibrary[] getLibraries() } } - private MArgument[] getGameArguments() + private IReadOnlyCollection getGameArguments() { try { @@ -167,7 +167,7 @@ private MArgument[] getGameArguments() } } - private MArgument[] getJvmArguments() + private IReadOnlyCollection getJvmArguments() { try { From d2a0141dadffac5b6e9cfeb8c62eaa0d616013ff Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 25 Feb 2024 22:46:03 +0900 Subject: [PATCH 093/137] fix: LegacyJavaFileExtractor apply chmod on wrong file --- TODO.md | 20 +++++++++---------- src/FileExtractors/LegacyJavaFileExtractor.cs | 14 ++++++------- src/Tasks/ChmodTask.cs | 6 +++++- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/TODO.md b/TODO.md index defb8a6..7042ed6 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,16 @@ -- [] MLaunchOption 에서 ExtraArguments 같은거 추가 -- [] master 브랜치에서 v3.4.0 으로 cherry-pick +- [ ] MLaunchOption 에서 ExtraArguments 같은거 추가 +- [ ] master 브랜치에서 v3.4.0 으로 cherry-pick - [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 - [x] GameInstaller 에서 중복 파일 확인 - [x] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 -- [] MArgument 유닛테스트 -- [] Extractors 최대한 유닛테스트 -- [] Extractor 통합 테스트 작성. 주요 버전 파싱, GameFile 확인 -- [] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 -- [] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성, 이후 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 -- [] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 -- [] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 -- [] Memory 위에서 mutable 한 IVersion 구현 +- [ ] MArgument 유닛테스트 +- [ ] Extractors 최대한 유닛테스트 +- [ ] Extractor 통합 테스트 작성. 주요 버전 파싱, GameFile 확인 +- [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 +- [ ] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성, 이후 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 +- [x] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 +- [ ] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 +- [ ] Memory 위에서 mutable 한 IVersion 구현 # Flow diff --git a/src/FileExtractors/LegacyJavaFileExtractor.cs b/src/FileExtractors/LegacyJavaFileExtractor.cs index 85d7ad2..4522cb8 100644 --- a/src/FileExtractors/LegacyJavaFileExtractor.cs +++ b/src/FileExtractors/LegacyJavaFileExtractor.cs @@ -44,21 +44,19 @@ private async Task createTask( RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) { + var javaBinaryPath = _javaPathResolver.GetJavaBinaryPath(JavaVersion, rulesContext); var javaBinaryDir = _javaPathResolver.GetJavaDirPath(JavaVersion, rulesContext); var javaUrl = await GetJavaUrlAsync(cancellationToken); - var lzmaFile = new GameFile("jre.lzma") + return new GameFile("jre.lzma") { Path = Path.Combine(Path.GetTempPath(), "jre.lzma"), Hash = "0", // since the file is temporary it should be always downloaded again - Url = javaUrl + Url = javaUrl, + UpdateTask = CompositeUpdateTask.Create( + new LegacyJavaExtractionTask(javaBinaryDir), + new ChmodTask(NativeMethods.Chmod755, javaBinaryPath)) }; - - lzmaFile.UpdateTask = CompositeUpdateTask.Create( - new LegacyJavaExtractionTask(javaBinaryDir), - new ChmodTask(NativeMethods.Chmod755)); - - return lzmaFile; } public async Task GetJavaUrlAsync(CancellationToken cancellationToken) diff --git a/src/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs index cecd7a9..215e0c5 100644 --- a/src/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -5,7 +5,11 @@ namespace CmlLib.Core.Tasks; public class ChmodTask : IUpdateTask { + private readonly string? _filePath; + public ChmodTask(int mode) => Mode = mode; + public ChmodTask(int mode, string filePath) => + (Mode, _filePath) = (mode, filePath); public int Mode { get; } @@ -16,7 +20,7 @@ public ValueTask Execute( if (string.IsNullOrEmpty(file.Path)) throw new InvalidOperationException(); if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) - NativeMethods.Chmod(file.Path, Mode); + NativeMethods.Chmod(_filePath ?? file.Path, Mode); return new ValueTask(); } } \ No newline at end of file From b77acb7dc18b39abdbfa099411f313d1df564b6b Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 25 Feb 2024 23:22:45 +0900 Subject: [PATCH 094/137] feat: add ExtraArguments options --- TODO.md | 2 +- src/ProcessBuilder/MArgument.cs | 5 ++ src/ProcessBuilder/MLaunchOption.cs | 15 ++-- src/ProcessBuilder/MinecraftProcessBuilder.cs | 73 ++++++++++++------- 4 files changed, 60 insertions(+), 35 deletions(-) diff --git a/TODO.md b/TODO.md index 7042ed6..d8c5db4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -- [ ] MLaunchOption 에서 ExtraArguments 같은거 추가 +- [x] MLaunchOption 에서 ExtraArguments 같은거 추가 - [ ] master 브랜치에서 v3.4.0 으로 cherry-pick - [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 - [x] GameInstaller 에서 중복 파일 확인 diff --git a/src/ProcessBuilder/MArgument.cs b/src/ProcessBuilder/MArgument.cs index 9827b16..9cef676 100644 --- a/src/ProcessBuilder/MArgument.cs +++ b/src/ProcessBuilder/MArgument.cs @@ -4,6 +4,11 @@ namespace CmlLib.Core.ProcessBuilder; public class MArgument { + public static IEnumerable FromStrings(IEnumerable args) + { + return args.Select(arg => new MArgument(arg)); + } + public MArgument() { diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index 49ef98d..782e009 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -6,6 +6,9 @@ namespace CmlLib.Core.ProcessBuilder; public class MLaunchOption { + private static readonly Lazy> EmptyDictionary = + new Lazy>(() => new Dictionary()); + public MinecraftPath? Path { get; set; } public IVersion? StartVersion { get; set; } public MSession? Session { get; set; } @@ -16,7 +19,6 @@ public class MLaunchOption public string? JavaPath { get; set; } public int MaximumRamMb { get; set; } = 1024; public int MinimumRamMb { get; set; } - public IEnumerable JVMArguments { get; set; } = Enumerable.Empty(); public string? DockName { get; set; } public string? DockIcon { get; set; } @@ -35,13 +37,10 @@ public class MLaunchOption public string? UserProperties { get; set; } - public Dictionary? ArgumentDictionary { get; set; } - - internal MinecraftPath GetMinecraftPath() => Path!; - internal IVersion GetStartVersion() => StartVersion!; - internal MSession GetSession() => Session!; - internal string GetJavaPath() => JavaPath!; - internal RulesEvaluatorContext GetRulesContext() => RulesContext!; + public IReadOnlyDictionary ArgumentDictionary { get; set; } = EmptyDictionary.Value; + public IEnumerable? JvmArgumentOverrides { get; set; } + public IEnumerable ExtraJvmArguments { get; set; } = Enumerable.Empty(); + public IEnumerable ExtraGameArguments { get; set; } = Enumerable.Empty(); internal void CheckValid() { diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index e9709a1..beafb92 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -28,10 +28,14 @@ public MinecraftProcessBuilder( { option.CheckValid(); + Debug.Assert(option.StartVersion != null); + Debug.Assert(option.Path != null); + Debug.Assert(option.RulesContext != null); + launchOption = option; - version = option.GetStartVersion(); - minecraftPath = option.GetMinecraftPath(); - rulesContext = option.GetRulesContext(); + version = option.StartVersion; + minecraftPath = option.Path; + rulesContext = option.RulesContext; rulesEvaluator = evaluator; } @@ -66,10 +70,11 @@ public IEnumerable BuildArguments() } private Dictionary buildArgumentDictionary() - { + { + Debug.Assert(launchOption.Session != null); + var classpaths = getClasspaths(); var classpath = IOUtil.CombinePath(classpaths); - var session = launchOption.GetSession(); var assetId = version.GetInheritedProperty(version => version.AssetIndex?.Id) ?? "legacy"; var argDict = new Dictionary @@ -81,19 +86,19 @@ public IEnumerable BuildArguments() { "classpath_separator", Path.PathSeparator.ToString() }, { "classpath" , classpath }, - { "auth_player_name" , session.Username }, + { "auth_player_name" , launchOption.Session.Username }, { "version_name" , version.Id }, { "game_directory" , minecraftPath.BasePath }, { "assets_root" , minecraftPath.Assets }, { "assets_index_name", assetId }, - { "auth_uuid" , session.UUID }, - { "auth_access_token", session.AccessToken }, + { "auth_uuid" , launchOption.Session.UUID }, + { "auth_access_token", launchOption.Session.AccessToken }, { "user_properties" , "{}" }, - { "auth_xuid" , session.Xuid ?? "xuid" }, + { "auth_xuid" , launchOption.Session.Xuid ?? "xuid" }, { "clientid" , launchOption.ClientId ?? "clientId" }, - { "user_type" , session.UserType ?? "Mojang" }, + { "user_type" , launchOption.Session.UserType ?? "Mojang" }, { "game_assets" , minecraftPath.GetAssetLegacyPath(assetId) }, - { "auth_session" , session.AccessToken }, + { "auth_session" , launchOption.Session.AccessToken }, { "version_type" , useNotNull(launchOption.VersionType, version.Type) }, }; @@ -112,17 +117,25 @@ private IEnumerable buildJvmArguments(Dictionary argDic { var builder = new ProcessArgumentBuilder(); - // version-specific jvm arguments - var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); - builder.AddRange(mapArguments(jvmArgs, argDict)); - - // default jvm arguments - if (launchOption.JVMArguments != null) - foreach (var item in launchOption.JVMArguments) - builder.Add(item); + if (launchOption.JvmArgumentOverrides != null) + { + // override all jvm arguments + // even if necessary arguments are missing (-cp, -Djava.library.path), + // the builder will still add the necessary arguments + builder.AddRange(mapArguments(launchOption.JvmArgumentOverrides, argDict)); + } else - foreach (var item in DefaultJavaParameter) - builder.Add(item); + { + // version-specific jvm arguments + var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); + builder.AddRange(mapArguments(jvmArgs, argDict)); + + // default jvm arguments + builder.AddRange(DefaultJavaParameter); + } + + // add extra jvm arguments + builder.AddRange(mapArguments(launchOption.ExtraJvmArguments, argDict)); // libraries builder.TryAddKeyValue("-Djava.library.path", argDict["natives_directory"]); @@ -180,21 +193,29 @@ private IEnumerable buildGameArguments(Dictionary argDi var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); builder.AddRange(mapArguments(gameArgs, argDict)); - // options + // add extra game arguments + builder.AddRange(mapArguments(launchOption.ExtraGameArguments, argDict)); + + // server if (!string.IsNullOrEmpty(launchOption.ServerIp)) { - builder.AddRange("--server", launchOption.ServerIp); + if (!builder.CheckKeyAdded("--server")) + builder.AddRange("--server", launchOption.ServerIp); - if (launchOption.ServerPort != DefaultServerPort) + if (launchOption.ServerPort != DefaultServerPort && !builder.CheckKeyAdded("--port")) builder.AddRange("--port", launchOption.ServerPort.ToString()); } + // screen size if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) { - builder.AddRange("--width", launchOption.ScreenWidth.ToString()); - builder.AddRange("--height", launchOption.ScreenHeight.ToString()); + if (!builder.CheckKeyAdded("--width")) + builder.AddRange("--width", launchOption.ScreenWidth.ToString()); + if (!builder.CheckKeyAdded("--height")) + builder.AddRange("--height", launchOption.ScreenHeight.ToString()); } + // fullscreen if (!builder.CheckKeyAdded("--fullscreen") && launchOption.FullScreen) builder.Add("--fullscreen"); From 429cb86376d8758f6336d8ac86f3841f3cbdeb39 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 2 Mar 2024 17:59:26 +0900 Subject: [PATCH 095/137] refactor: consistent extractor names --- src/FileExtractors/AssetFileExtractor.cs | 10 +- src/FileExtractors/JavaFileExtractor.cs | 68 ++++++++------ src/FileExtractors/LibraryFileExtractor.cs | 103 ++++++++++----------- 3 files changed, 95 insertions(+), 86 deletions(-) diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index 0d7870a..4513530 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -29,7 +29,7 @@ public async ValueTask> Extract( if (assetIndex == null) return Enumerable.Empty(); - return TaskExtractor.ExtractTasksFromAssetIndex( + return Extractor.ExtractTasksFromAssetIndex( assetIndex, path, AssetServer, @@ -83,7 +83,7 @@ public async ValueTask> Extract( } } - public static class TaskExtractor + public static class Extractor { public static IEnumerable ExtractTasksFromAssetIndex( IAssetIndex assetIndex, MinecraftPath path, string assetServer, bool dispose) @@ -94,13 +94,13 @@ public static IEnumerable ExtractTasksFromAssetIndex( foreach (var assetObject in assetIndex.EnumerateAssetObjects()) { var hashName = assetObject.Hash.Substring(0, 2) + "/" + assetObject.Hash; - var hashPath = Path.Combine(path.GetAssetObjectPath(assetIndex.Id), hashName); + var hashPath = IOUtil.NormalizePath(Path.Combine(path.GetAssetObjectPath(assetIndex.Id), hashName)); var copyPath = new List(2); if (assetIndex.IsVirtual) - copyPath.Add(Path.Combine(path.GetAssetLegacyPath(assetIndex.Id), assetObject.Name)); + copyPath.Add(IOUtil.NormalizePath(Path.Combine(path.GetAssetLegacyPath(assetIndex.Id), assetObject.Name))); if (assetIndex.MapToResources) - copyPath.Add(Path.Combine(path.Resource, assetObject.Name)); + copyPath.Add(IOUtil.NormalizePath(Path.Combine(path.Resource, assetObject.Name))); var file = new GameFile(assetObject.Name) { diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index fe3a76c..34c34bd 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -37,8 +37,8 @@ public async ValueTask> Extract( manifestResolver.ManifestServer = JavaManifestServer; var extractor = new Extractor( - _javaPathResolver, - rulesContext, + _javaPathResolver, + rulesContext, manifestResolver); return await extractor.ExtractFromJavaVersion(javaVersion, cancellationToken); } @@ -51,7 +51,7 @@ public class Extractor public Extractor( IJavaPathResolver javaPathResolver, - RulesEvaluatorContext rulesContext, + RulesEvaluatorContext rulesContext, MinecraftJavaManifestResolver manifestResolver) { _javaPathResolver = javaPathResolver; @@ -60,31 +60,37 @@ public Extractor( } public async ValueTask> ExtractFromJavaVersion( - JavaVersion javaVersion, + JavaVersion javaVersion, CancellationToken cancellationToken) + { + var manifestUrl = await findManifestUrl(javaVersion); + if (string.IsNullOrEmpty(manifestUrl)) + return Enumerable.Empty(); + + var installPath = _javaPathResolver.GetJavaDirPath(javaVersion, _rulesContext); + var files = await _manifestResolver.GetFilesFromManifest(manifestUrl, cancellationToken); + return extractFiles(installPath, files); + } + + private async ValueTask findManifestUrl(JavaVersion javaVersion) { var osName = MinecraftJavaManifestResolver.GetOSNameForJava(_rulesContext.OS) ?? ""; var manifests = await _manifestResolver.GetManifestsForOS(osName); - var manifestUrl = findManifestUrl(manifests, javaVersion.Component); + var manifestUrl = findManifestUrlFromMetadatas(manifests, javaVersion.Component); if (string.IsNullOrEmpty(manifestUrl) && javaVersion.Component != MinecraftJavaPathResolver.JreLegacyVersion.Component) - manifestUrl = findManifestUrl(manifests, MinecraftJavaPathResolver.JreLegacyVersion.Component); + manifestUrl = findManifestUrlFromMetadatas(manifests, MinecraftJavaPathResolver.JreLegacyVersion.Component); - if (string.IsNullOrEmpty(manifestUrl)) - return Enumerable.Empty(); - - var installPath = _javaPathResolver.GetJavaDirPath(javaVersion, _rulesContext); - var files = await _manifestResolver.GetFilesFromManifest(manifestUrl, cancellationToken); - return iterateFileToTask(installPath, files); + return manifestUrl; } - private string? findManifestUrl(IEnumerable metadatas, string component) + private string? findManifestUrlFromMetadatas(IEnumerable metadatas, string component) { return metadatas.FirstOrDefault(v => v.Component == component)?.Metadata?.Url; } - private IEnumerable iterateFileToTask( + private IEnumerable extractFiles( string path, IEnumerable files) { @@ -92,23 +98,29 @@ private IEnumerable iterateFileToTask( { if (javaFile.Type == "file") { - var filePath = Path.Combine(path, javaFile.Name); - filePath = IOUtil.NormalizePath(filePath); - - var gameFile = new GameFile(javaFile.Name) - { - Hash = javaFile.Sha1, - Path = filePath, - Url = javaFile.Url, - Size = javaFile.Size - }; - - if (javaFile.Executable) - gameFile.UpdateTask = new ChmodTask(NativeMethods.Chmod755); - + var gameFile = extractFile(path, javaFile); yield return gameFile; } } } + + private GameFile extractFile(string path, MinecraftJavaFile javaFile) + { + var filePath = Path.Combine(path, javaFile.Name); + filePath = IOUtil.NormalizePath(filePath); + + var gameFile = new GameFile(javaFile.Name) + { + Hash = javaFile.Sha1, + Path = filePath, + Url = javaFile.Url, + Size = javaFile.Size + }; + + if (javaFile.Executable) + gameFile.UpdateTask = new ChmodTask(NativeMethods.Chmod755); + + return gameFile; + } } } \ No newline at end of file diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index 7cdfef8..81c1775 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -1,4 +1,5 @@ -using CmlLib.Core.Rules; +using CmlLib.Core.Internals; +using CmlLib.Core.Rules; using CmlLib.Core.Tasks; using CmlLib.Core.Version; @@ -15,21 +16,10 @@ public LibraryFileExtractor(string side, IRulesEvaluator rulesEvaluator) _rulesEvaluator = rulesEvaluator; } - private string libServer = MojangServer.Library; - public string LibraryServer - { - get => libServer; - set - { - if (value.Last() == '/') - libServer = value; - else - libServer = value + "/"; - } - } + public string LibraryServer { get; set; } = MojangServer.Library; public ValueTask> Extract( - MinecraftPath path, + MinecraftPath path, IVersion version, RulesEvaluatorContext rulesContext, CancellationToken cancellationToken) @@ -37,59 +27,66 @@ public ValueTask> Extract( var result = version.Libraries .Where(lib => lib.CheckIsRequired(_side)) .Where(lib => lib.Rules == null || _rulesEvaluator.Match(lib.Rules, rulesContext)) - .SelectMany(lib => createLibraryTasks(path, lib, rulesContext)); + .SelectMany(lib => Extractor.ExtractTasks(LibraryServer, path, lib, rulesContext)); return new ValueTask>(result); } - private IEnumerable createLibraryTasks( - MinecraftPath path, - MLibrary library, - RulesEvaluatorContext rulesContext) + public static class Extractor { - // java library (*.jar) - var artifact = library.Artifact; - if (artifact != null) + public static IEnumerable ExtractTasks( + string libraryServer, + MinecraftPath path, + MLibrary library, + RulesEvaluatorContext rulesContext) { - var libPath = library.GetLibraryPath(); - yield return new GameFile(library.Name) - { - Path = Path.Combine(path.Library, libPath), - Url = createDownloadUrl(artifact.Url, libPath), - Hash = artifact.GetSha1(), - Size = artifact.Size - }; - } + if (!libraryServer.EndsWith("/")) + libraryServer += '/'; - // native library (*.dll, *.so) - var native = library.GetNativeLibrary(rulesContext.OS); - if (native != null) - { - var libPath = library.GetNativeLibraryPath(rulesContext.OS); - if (!string.IsNullOrEmpty(libPath)) + // java library (*.jar) + var artifact = library.Artifact; + if (artifact != null) { + var libPath = library.GetLibraryPath(); yield return new GameFile(library.Name) { - Path = Path.Combine(path.Library, libPath), - Url = createDownloadUrl(native.Url, libPath), - Hash = native.GetSha1(), - Size = native.Size + Path = IOUtil.NormalizePath(Path.Combine(path.Library, libPath)), + Url = createDownloadUrl(libraryServer, artifact.Url, libPath), + Hash = artifact.GetSha1(), + Size = artifact.Size }; } + + // native library (*.dll, *.so) + var native = library.GetNativeLibrary(rulesContext.OS); + if (native != null) + { + var libPath = library.GetNativeLibraryPath(rulesContext.OS); + if (!string.IsNullOrEmpty(libPath)) + { + yield return new GameFile(library.Name) + { + Path = IOUtil.NormalizePath(Path.Combine(path.Library, libPath)), + Url = createDownloadUrl(libraryServer, native.Url, libPath), + Hash = native.GetSha1(), + Size = native.Size + }; + } + } } - } - private string? createDownloadUrl(string? url, string path) - { - if (string.IsNullOrEmpty(url) && string.IsNullOrEmpty(path)) - return null; + private static string? createDownloadUrl(string server, string? url, string path) + { + if (string.IsNullOrEmpty(url) && string.IsNullOrEmpty(path)) + return null; - if (url == null) - url = LibraryServer + path; - else if (url == "") - url = null; - else if (url.Split('/').Last() == "") - url += path.Replace("\\", "/"); + if (url == null) + url = server + path; + else if (url == "") + url = null; + else if (url.Split('/').Last() == "") + url += path.Replace("\\", "/"); - return url; + return url; + } } } From 48140a4871faed341e6db58fa9fca18c18bf6bcd Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 2 Mar 2024 18:00:21 +0900 Subject: [PATCH 096/137] test: fix errors --- test/Version/DummyVersion.cs | 6 +++--- test/Version/JsonArgumentParserTests.cs | 20 ++++++++++---------- test/Version/JsonLibraryParserTests.cs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/Version/DummyVersion.cs b/test/Version/DummyVersion.cs index 3fbc6cd..fe49e3a 100644 --- a/test/Version/DummyVersion.cs +++ b/test/Version/DummyVersion.cs @@ -21,7 +21,7 @@ public class DummyVersion : IVersion public JavaVersion? JavaVersion { get; set; } - public MLibrary[] Libraries { get; set; } = new MLibrary[0]; + public IReadOnlyCollection Libraries { get; set; } = Array.Empty(); public string? Jar { get; set; } @@ -29,9 +29,9 @@ public class DummyVersion : IVersion public string? MainClass { get; set; } - public MArgument[] GameArguments { get; set; } = new MArgument[0]; + public IReadOnlyCollection GameArguments { get; set; } = Array.Empty(); - public MArgument[] JvmArguments { get; set; } = new MArgument[0]; + public IReadOnlyCollection JvmArguments { get; set; } = Array.Empty(); public DateTime ReleaseTime { get; set; } diff --git a/test/Version/JsonArgumentParserTests.cs b/test/Version/JsonArgumentParserTests.cs index c302899..5f789ad 100644 --- a/test/Version/JsonArgumentParserTests.cs +++ b/test/Version/JsonArgumentParserTests.cs @@ -30,8 +30,8 @@ public void parse_from_vanilla_arg_string() var parsedArgs = version.GameArguments .SelectMany(arg => arg.Values ?? Enumerable.Empty()) .ToArray(); - Assert.Equal(new[] - { + Assert.Equal( + [ "--username", "${auth_player_name}", "--version", @@ -50,7 +50,7 @@ public void parse_from_vanilla_arg_string() "${user_properties}", "--userType", "${user_type}" - }, parsedArgs); + ], parsedArgs); } [Fact] @@ -177,11 +177,11 @@ public void parse_from_vanilla_game_arg_array() // 1.13 ~ var version = JsonVersionParser.ParseFromJsonString(json, options); - var parsedArgs = version.JvmArguments + var parsedArgs = version.GameArguments .SelectMany(arg => arg.Values ?? Enumerable.Empty()) .ToArray(); - Assert.Equal(new [] - { + Assert.Equal( + [ "--username", "${auth_player_name}", "--version", @@ -217,7 +217,7 @@ public void parse_from_vanilla_game_arg_array() // 1.13 ~ "${quickPlayMultiplayer}", "--quickPlayRealms", "${quickPlayRealms}" - }, parsedArgs); + ], parsedArgs); } [Fact] @@ -299,8 +299,8 @@ public void parse_from_vanilla_jvm_arg_array() var parsedArgs = version.JvmArguments .SelectMany(args => args.Values ?? Enumerable.Empty()) .ToArray(); - Assert.Equal(new [] - { + Assert.Equal( + [ "-XstartOnFirstThread", "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump", "-Dos.name=Windows 10", @@ -314,6 +314,6 @@ public void parse_from_vanilla_jvm_arg_array() "-Dminecraft.launcher.version=${launcher_version}", "-cp", "${classpath}" - }, parsedArgs); + ], parsedArgs); } } \ No newline at end of file diff --git a/test/Version/JsonLibraryParserTests.cs b/test/Version/JsonLibraryParserTests.cs index 4734311..ff251cf 100644 --- a/test/Version/JsonLibraryParserTests.cs +++ b/test/Version/JsonLibraryParserTests.cs @@ -66,7 +66,7 @@ public void parse_java_library_with_rules() using var jsonDocument = JsonDocument.Parse(json); var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); - Assert.Equal(2, lib?.Rules?.Length); + Assert.Equal(2, lib?.Rules?.Count); } [Fact] From 3696ec4f8dad3dd2b62127b584320e9ba60bbfca Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 2 Mar 2024 18:46:51 +0900 Subject: [PATCH 097/137] refactor: introduce record type --- TODO.md | 5 ++ benchmark/RandomFileExtractor.cs | 2 +- src/CmlLib.Core.csproj | 4 ++ src/FileExtractors/AssetFileExtractor.cs | 10 +--- src/FileExtractors/ClientFileExtractor.cs | 2 +- src/FileExtractors/IFileExtractor.cs | 2 +- src/FileExtractors/JavaFileExtractor.cs | 10 +--- src/FileExtractors/LegacyJavaFileExtractor.cs | 1 + src/FileExtractors/LibraryFileExtractor.cs | 2 +- src/FileExtractors/LogFileExtractor.cs | 2 +- src/Files/AssetIndex.cs | 17 ++---- src/Files/AssetMetadata.cs | 2 +- src/{Tasks => Files}/GameFile.cs | 16 +++--- src/Files/MFileMetadata.cs | 2 +- src/Files/MLibrary.cs | 14 ++--- src/Installers/BasicGameInstaller.cs | 2 +- src/Installers/GameInstallerBase.cs | 3 +- src/Installers/IGameInstaller.cs | 2 +- src/Installers/ParallelGameInstaller.cs | 2 +- src/MinecraftLauncher.cs | 2 +- src/Tasks/ChmodTask.cs | 1 + src/Tasks/CompositeUpdateTask.cs | 4 +- src/Tasks/FileCopyTask.cs | 1 + src/Tasks/IUpdateTask.cs | 4 +- src/Tasks/LegacyJavaExtractionTask.cs | 1 + src/Version/JsonLibraryParser.cs | 39 ++++++++----- test/Rules/RulesEvaluatorFeatureTests.cs | 56 +++++++++---------- test/Version/JsonLibraryParserTests.cs | 33 +++++++---- 28 files changed, 131 insertions(+), 110 deletions(-) rename src/{Tasks => Files}/GameFile.cs (50%) diff --git a/TODO.md b/TODO.md index d8c5db4..fe58f91 100644 --- a/TODO.md +++ b/TODO.md @@ -11,9 +11,13 @@ - [x] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 - [ ] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 - [ ] Memory 위에서 mutable 한 IVersion 구현 +- [ ] JvmArgumentOverrides 에서 띄어쓰기 포함되어있으면 어떡하지? 예시: +-Darg="hi -cp" +- [x] introduce record type: AssetObject # Flow +``` { JsonVersionLoader } | JsonVersionMetadata @@ -44,3 +48,4 @@ |-------------------------| | Process +``` \ No newline at end of file diff --git a/benchmark/RandomFileExtractor.cs b/benchmark/RandomFileExtractor.cs index 906c356..77e1509 100644 --- a/benchmark/RandomFileExtractor.cs +++ b/benchmark/RandomFileExtractor.cs @@ -1,6 +1,6 @@ using CmlLib.Core.FileExtractors; using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; namespace CmlLib.Core.Benchmarks; diff --git a/src/CmlLib.Core.csproj b/src/CmlLib.Core.csproj index 446b54b..27ff8f9 100644 --- a/src/CmlLib.Core.csproj +++ b/src/CmlLib.Core.csproj @@ -40,6 +40,10 @@ Support all version, forge, optifine + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/FileExtractors/AssetFileExtractor.cs b/src/FileExtractors/AssetFileExtractor.cs index 4513530..fd54e62 100644 --- a/src/FileExtractors/AssetFileExtractor.cs +++ b/src/FileExtractors/AssetFileExtractor.cs @@ -102,18 +102,14 @@ public static IEnumerable ExtractTasksFromAssetIndex( if (assetIndex.MapToResources) copyPath.Add(IOUtil.NormalizePath(Path.Combine(path.Resource, assetObject.Name))); - var file = new GameFile(assetObject.Name) + yield return new GameFile(assetObject.Name) { Path = hashPath, Hash = assetObject.Hash, Size = assetObject.Size, - Url = assetServer + hashName + Url = assetServer + hashName, + UpdateTask = copyPath.Any() ? new FileCopyTask(copyPath) : null }; - - if (copyPath.Any()) - file.UpdateTask = new FileCopyTask(copyPath); - - yield return file; } if (dispose && assetIndex is IDisposable disposableAssetIndex) diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 8db020e..488a3fe 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -1,5 +1,5 @@ using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; diff --git a/src/FileExtractors/IFileExtractor.cs b/src/FileExtractors/IFileExtractor.cs index f1050fe..6620279 100644 --- a/src/FileExtractors/IFileExtractor.cs +++ b/src/FileExtractors/IFileExtractor.cs @@ -1,5 +1,5 @@ using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; diff --git a/src/FileExtractors/JavaFileExtractor.cs b/src/FileExtractors/JavaFileExtractor.cs index 34c34bd..c359762 100644 --- a/src/FileExtractors/JavaFileExtractor.cs +++ b/src/FileExtractors/JavaFileExtractor.cs @@ -109,18 +109,14 @@ private GameFile extractFile(string path, MinecraftJavaFile javaFile) var filePath = Path.Combine(path, javaFile.Name); filePath = IOUtil.NormalizePath(filePath); - var gameFile = new GameFile(javaFile.Name) + return new GameFile(javaFile.Name) { Hash = javaFile.Sha1, Path = filePath, Url = javaFile.Url, - Size = javaFile.Size + Size = javaFile.Size, + UpdateTask = javaFile.Executable ? new ChmodTask(NativeMethods.Chmod755) : null }; - - if (javaFile.Executable) - gameFile.UpdateTask = new ChmodTask(NativeMethods.Chmod755); - - return gameFile; } } } \ No newline at end of file diff --git a/src/FileExtractors/LegacyJavaFileExtractor.cs b/src/FileExtractors/LegacyJavaFileExtractor.cs index 4522cb8..41501d0 100644 --- a/src/FileExtractors/LegacyJavaFileExtractor.cs +++ b/src/FileExtractors/LegacyJavaFileExtractor.cs @@ -2,6 +2,7 @@ using CmlLib.Core.Java; using CmlLib.Core.Rules; using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; using System.Text.Json; diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index 81c1775..f7c18d3 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -1,6 +1,6 @@ using CmlLib.Core.Internals; using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; diff --git a/src/FileExtractors/LogFileExtractor.cs b/src/FileExtractors/LogFileExtractor.cs index 76fd6d7..2ff7b7b 100644 --- a/src/FileExtractors/LogFileExtractor.cs +++ b/src/FileExtractors/LogFileExtractor.cs @@ -1,5 +1,5 @@ using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; namespace CmlLib.Core.FileExtractors; diff --git a/src/Files/AssetIndex.cs b/src/Files/AssetIndex.cs index 762d913..0ab5ffd 100644 --- a/src/Files/AssetIndex.cs +++ b/src/Files/AssetIndex.cs @@ -53,16 +53,7 @@ public IEnumerable EnumerateAssetObjects() public void Dispose() => _json.Dispose(); } -public class AssetObject -{ - public AssetObject(string name, string hash, long size) - { - Name = name; - Hash = hash; - Size = size; - } - - public string Name { get; } - public string Hash { get; } - public long Size { get; } -} \ No newline at end of file +public record AssetObject( + string Name, + string Hash, + long Size); \ No newline at end of file diff --git a/src/Files/AssetMetadata.cs b/src/Files/AssetMetadata.cs index 14c8b18..af40fdd 100644 --- a/src/Files/AssetMetadata.cs +++ b/src/Files/AssetMetadata.cs @@ -2,7 +2,7 @@ namespace CmlLib.Core.Files; -public class AssetMetadata : MFileMetadata +public record AssetMetadata : MFileMetadata { [JsonPropertyName("totalSize")] public long TotalSize { get; set; } diff --git a/src/Tasks/GameFile.cs b/src/Files/GameFile.cs similarity index 50% rename from src/Tasks/GameFile.cs rename to src/Files/GameFile.cs index 5bbfaf7..48f08a0 100644 --- a/src/Tasks/GameFile.cs +++ b/src/Files/GameFile.cs @@ -1,16 +1,18 @@ -namespace CmlLib.Core.Tasks; +using CmlLib.Core.Tasks; -public class GameFile +namespace CmlLib.Core.Files; + +public record GameFile { public GameFile(string name) => Name = name; public string Name { get; } - public string? Path { get; set; } - public string? Hash { get; set; } - public string? Url { get; set; } - public long Size { get; set; } - public IUpdateTask? UpdateTask { get; set; } + public string? Path { get; init; } + public string? Hash { get; init; } + public string? Url { get; init; } + public long Size { get; init; } + public IUpdateTask? UpdateTask { get; init; } public async ValueTask ExecuteUpdateTask(CancellationToken cancellationToken) { diff --git a/src/Files/MFileMetadata.cs b/src/Files/MFileMetadata.cs index eb8c822..77b9aa2 100644 --- a/src/Files/MFileMetadata.cs +++ b/src/Files/MFileMetadata.cs @@ -2,7 +2,7 @@ namespace CmlLib.Core.Files; -public class MFileMetadata +public record MFileMetadata { [JsonPropertyName("id")] public string? Id { get; set; } diff --git a/src/Files/MLibrary.cs b/src/Files/MLibrary.cs index 8bc1b25..0317bc6 100644 --- a/src/Files/MLibrary.cs +++ b/src/Files/MLibrary.cs @@ -8,13 +8,13 @@ public record MLibrary public MLibrary(string name) => Name = name; - public MFileMetadata? Artifact { get; set; } - public Dictionary? Classifiers { get; set; } - public Dictionary? Natives { get; set; } - public IReadOnlyCollection Rules { get; set; } = Array.Empty(); - public string Name { get; set; } - public bool IsServerRequired { get; set; } = true; - public bool IsClientRequired { get; set; } = true; + public MFileMetadata? Artifact { get; init; } + public IReadOnlyDictionary? Classifiers { get; init; } + public IReadOnlyDictionary? Natives { get; init; } + public IReadOnlyCollection Rules { get; init; } = Array.Empty(); + public string Name { get; } + public bool IsServerRequired { get; init; } = true; + public bool IsClientRequired { get; init; } = true; public bool CheckIsRequired(string side) { diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index e9021be..6d0b1c1 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -1,4 +1,4 @@ -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; namespace CmlLib.Core.Installers; diff --git a/src/Installers/GameInstallerBase.cs b/src/Installers/GameInstallerBase.cs index 5b0ac00..feb7c0f 100644 --- a/src/Installers/GameInstallerBase.cs +++ b/src/Installers/GameInstallerBase.cs @@ -1,6 +1,7 @@ using CmlLib.Core.Internals; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using System.Diagnostics; +using CmlLib.Core.Tasks; namespace CmlLib.Core.Installers; diff --git a/src/Installers/IGameInstaller.cs b/src/Installers/IGameInstaller.cs index 73beda8..af9f2a7 100644 --- a/src/Installers/IGameInstaller.cs +++ b/src/Installers/IGameInstaller.cs @@ -1,4 +1,4 @@ -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; namespace CmlLib.Core.Installers; diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index 5754fb1..d139606 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -1,6 +1,6 @@ using System.Diagnostics; using System.Threading.Tasks.Dataflow; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; namespace CmlLib.Core.Installers; diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index d91c9bc..8c9d29c 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -5,7 +5,7 @@ using CmlLib.Core.Natives; using CmlLib.Core.ProcessBuilder; using CmlLib.Core.Rules; -using CmlLib.Core.Tasks; +using CmlLib.Core.Files; using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; diff --git a/src/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs index 215e0c5..18ee6f5 100644 --- a/src/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -1,4 +1,5 @@ using CmlLib.Core.Rules; +using CmlLib.Core.Files; using CmlLib.Core.Internals; namespace CmlLib.Core.Tasks; diff --git a/src/Tasks/CompositeUpdateTask.cs b/src/Tasks/CompositeUpdateTask.cs index 4cffaa2..b8ffff4 100644 --- a/src/Tasks/CompositeUpdateTask.cs +++ b/src/Tasks/CompositeUpdateTask.cs @@ -1,4 +1,6 @@ -namespace CmlLib.Core.Tasks; +using CmlLib.Core.Files; + +namespace CmlLib.Core.Tasks; public class CompositeUpdateTask : IUpdateTask { diff --git a/src/Tasks/FileCopyTask.cs b/src/Tasks/FileCopyTask.cs index db369eb..428631b 100644 --- a/src/Tasks/FileCopyTask.cs +++ b/src/Tasks/FileCopyTask.cs @@ -1,4 +1,5 @@ using CmlLib.Core.Internals; +using CmlLib.Core.Files; namespace CmlLib.Core.Tasks; diff --git a/src/Tasks/IUpdateTask.cs b/src/Tasks/IUpdateTask.cs index de2ad78..54da4ce 100644 --- a/src/Tasks/IUpdateTask.cs +++ b/src/Tasks/IUpdateTask.cs @@ -1,4 +1,6 @@ -namespace CmlLib.Core.Tasks; +using CmlLib.Core.Files; + +namespace CmlLib.Core.Tasks; public interface IUpdateTask { diff --git a/src/Tasks/LegacyJavaExtractionTask.cs b/src/Tasks/LegacyJavaExtractionTask.cs index 92da5f1..9634410 100644 --- a/src/Tasks/LegacyJavaExtractionTask.cs +++ b/src/Tasks/LegacyJavaExtractionTask.cs @@ -1,4 +1,5 @@ using CmlLib.Core.Internals; +using CmlLib.Core.Files; namespace CmlLib.Core.Tasks; diff --git a/src/Version/JsonLibraryParser.cs b/src/Version/JsonLibraryParser.cs index dbac825..d8026cf 100644 --- a/src/Version/JsonLibraryParser.cs +++ b/src/Version/JsonLibraryParser.cs @@ -1,6 +1,7 @@ using System.Text.Json; using CmlLib.Core.Files; using CmlLib.Core.Internals; +using CmlLib.Core.Rules; namespace CmlLib.Core.Version; @@ -11,20 +12,21 @@ public static class JsonLibraryParser var name = element.GetPropertyValue("name"); if (string.IsNullOrEmpty(name)) return null; - - var library = new MLibrary(name); // rules + IReadOnlyCollection rules; if (element.TryGetProperty("rules", out var rulesProp)) - library.Rules = JsonRulesParser.Parse(rulesProp); + rules = JsonRulesParser.Parse(rulesProp); + else + rules = Array.Empty(); // forge serverreq, clientreq - library.IsServerRequired = element + var isServerRequired = element .GetPropertyOrNull("serverreq")? .GetBoolean() ?? true; // default value is true - library.IsClientRequired = element + var isClientRequired = element .GetPropertyOrNull("clientreq")? .GetBoolean() ?? true; // default value is true @@ -33,21 +35,28 @@ public static class JsonLibraryParser var artifact = element.GetPropertyOrNull("artifact") ?? element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("artifact") ?? element; - library.Artifact = artifact.Deserialize(); // classifiers - var classifiers = element.GetPropertyOrNull("classifies") ?? + IReadOnlyDictionary? classifiers = null; + var classifiersProp = element.GetPropertyOrNull("classifies") ?? element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("classifiers"); - if (classifiers.HasValue) - library.Classifiers = classifiers.Value.Deserialize>(); + if (classifiersProp.HasValue) + classifiers = classifiersProp.Value.Deserialize>(); // natives - var natives = element.GetPropertyOrNull("natives"); - if (natives.HasValue) - { - library.Natives = natives.Value.Deserialize>(); - } + IReadOnlyDictionary? natives = null; + var nativesProp = element.GetPropertyOrNull("natives"); + if (nativesProp.HasValue) + natives = nativesProp.Value.Deserialize>(); - return library; + return new MLibrary(name) + { + Artifact = artifact.Deserialize(), + Classifiers = classifiers, + Natives = natives, + Rules = rules, + IsClientRequired = isClientRequired, + IsServerRequired = isServerRequired + }; } } diff --git a/test/Rules/RulesEvaluatorFeatureTests.cs b/test/Rules/RulesEvaluatorFeatureTests.cs index 25a034d..cca61f1 100644 --- a/test/Rules/RulesEvaluatorFeatureTests.cs +++ b/test/Rules/RulesEvaluatorFeatureTests.cs @@ -13,22 +13,22 @@ public class RulesEvaluatorFeatureTest [Fact] public void allow_empty_feature() { - allow(new string[] {}, new LauncherRule[] - { + allow([], + [ new LauncherRule { Action = "allow", OS = TestOS, Features = new Dictionary() } - }); + ]); } [Fact] public void allow_one_feature() { - allow(new string[] { "demo" }, new LauncherRule[] - { + allow(["demo"], + [ new LauncherRule { Action = "allow", @@ -38,14 +38,14 @@ public void allow_one_feature() ["demo"] = true } } - }); + ]); } [Fact] public void allow_not_ruled_feature() { - allow(new string[] { "demo" }, new LauncherRule[] - { + allow(["demo"], + [ new LauncherRule { Action = "allow", @@ -55,14 +55,14 @@ public void allow_not_ruled_feature() ["fullscreen"] = false } } - }); + ]); } [Fact] public void disallow_no_feature() { - disallow(new string[] { "demo" }, new LauncherRule[] - { + disallow(["demo"], + [ new LauncherRule { Action = "allow", @@ -72,14 +72,14 @@ public void disallow_no_feature() ["fullscreen"] = true } } - }); + ]); } [Fact] public void disallow_disallowed_feature() { - disallow(new string[] { "demo" }, new LauncherRule[] - { + disallow(["demo"], + [ new LauncherRule { Action = "allow", @@ -89,15 +89,15 @@ public void disallow_disallowed_feature() ["demo"] = false } } - }); + ]); } [Theory] [InlineData("demo", "cmllib")] public void allow_two_features(string input1, string input2) { - allow(new string[] { input1, input2 }, new LauncherRule[] - { + allow([input1, input2], + [ new LauncherRule { Action = "allow", @@ -109,15 +109,15 @@ public void allow_two_features(string input1, string input2) ["cmllib"] = true } } - }); + ]); } [Theory] [InlineData("demo", "not_included_feature")] public void allow_included_feature_and_not_included_featre(string input1, string input2) { - allow(new string[] { input1, input2 }, new LauncherRule[] - { + allow([input1, input2], + [ new LauncherRule { Action = "allow", @@ -129,7 +129,7 @@ public void allow_included_feature_and_not_included_featre(string input1, string ["cmllib"] = false } } - }); + ]); } [Theory] @@ -137,8 +137,8 @@ public void allow_included_feature_and_not_included_featre(string input1, string [InlineData("fullscreen", "cmllib")] public void disallow_two_features(string input1, string input2) { - disallow(new string[] { input1, input2 }, new LauncherRule[] - { + disallow([input1, input2], + [ new LauncherRule { Action = "allow", @@ -150,7 +150,7 @@ public void disallow_two_features(string input1, string input2) ["cmllib"] = true } } - }); + ]); } [Fact] @@ -161,9 +161,9 @@ public void TestDisallowMismatchOS() { Name = "windows", Arch = "x86" }); - context.Features = new string[] { "feature1" }; - var result = evaluator.Match(new LauncherRule[] - { + context.Features = ["feature1"]; + var result = evaluator.Match( + [ new LauncherRule { Action = "allow", @@ -177,7 +177,7 @@ public void TestDisallowMismatchOS() ["feature2"] = false } } - }, context); + ], context); Assert.False(result); } diff --git a/test/Version/JsonLibraryParserTests.cs b/test/Version/JsonLibraryParserTests.cs index ff251cf..03d1162 100644 --- a/test/Version/JsonLibraryParserTests.cs +++ b/test/Version/JsonLibraryParserTests.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using CmlLib.Core.Files; using CmlLib.Core.Version; namespace CmlLib.Core.Test.Version; @@ -24,13 +25,18 @@ public void parse_simple_java_library() """; using var jsonDocument = JsonDocument.Parse(json); - var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); - - Assert.Equal("net.minecraft:launchwrapper:1.5", lib?.Name); - Assert.Equal("net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", lib?.Artifact?.Path); - Assert.Equal("5150b9c2951f0fde987ce9c33496e26add1de224", lib?.Artifact?.Sha1); - Assert.Equal(27787, lib?.Artifact?.Size); - Assert.Equal("https://libraries.minecraft.net/net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", lib?.Artifact?.Url); + var result = JsonLibraryParser.Parse(jsonDocument.RootElement); + var expected = new MLibrary("net.minecraft:launchwrapper:1.5") + { + Artifact = new MFileMetadata + { + Path = "net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", + Sha1 = "5150b9c2951f0fde987ce9c33496e26add1de224", + Size = 27787, + Url = "https://libraries.minecraft.net/net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar", + } + }; + Assert.Equal(expected, result); } [Fact] @@ -126,11 +132,14 @@ public void parse_native_library() using var jsonDocument = JsonDocument.Parse(json); var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "64")); - - Assert.Equal("org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", nativeLib?.Path); - Assert.Equal("3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", nativeLib?.Sha1); - Assert.Equal(609967, nativeLib?.Size); - Assert.Equal("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", nativeLib?.Url); + var expected = new MFileMetadata + { + Path = "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", + Sha1 = "3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", + Size = 609967, + Url = "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar" + }; + Assert.Equal(expected, nativeLib); } [Fact] From 247a6b6a6a209a925b0de18e9985fe720f45463b Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 2 Mar 2024 22:46:31 +0900 Subject: [PATCH 098/137] fix: JsonLibraryParser return wrong artifact --- .vscode/launch.json | 12 +- .vscode/tasks.json | 10 +- src/Version/JsonLibraryParser.cs | 12 +- test/Version/JsonLibraryParserTests.cs | 390 +++++++++++++------------ 4 files changed, 213 insertions(+), 211 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index db9ed1a..209bcc4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,19 +1,17 @@ { + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/test/bin/Debug/net6.0/CmlLib.Core.Test.dll", + "program": "${workspaceFolder}/examples/console/bin/Debug/net6.0/CmlLibCoreSample.dll", "args": [], - "cwd": "${workspaceFolder}/test", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "cwd": "${workspaceFolder}/examples/console", "console": "internalConsole", "stopAtEntry": false }, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 68e1b7e..2a4050e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,9 +7,9 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/test/CmlLib.Core.Test.csproj", + "${workspaceFolder}/CmlLib.Core.sln", "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "/consoleloggerparameters:NoSummary;ForceNoAlign" ], "problemMatcher": "$msCompile" }, @@ -19,9 +19,9 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/test/CmlLib.Core.Test.csproj", + "${workspaceFolder}/CmlLib.Core.sln", "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "/consoleloggerparameters:NoSummary;ForceNoAlign" ], "problemMatcher": "$msCompile" }, @@ -33,7 +33,7 @@ "watch", "run", "--project", - "${workspaceFolder}/test/CmlLib.Core.Test.csproj" + "${workspaceFolder}/CmlLib.Core.sln" ], "problemMatcher": "$msCompile" } diff --git a/src/Version/JsonLibraryParser.cs b/src/Version/JsonLibraryParser.cs index d8026cf..5b8366c 100644 --- a/src/Version/JsonLibraryParser.cs +++ b/src/Version/JsonLibraryParser.cs @@ -32,14 +32,16 @@ public static class JsonLibraryParser true; // default value is true // artifact - var artifact = element.GetPropertyOrNull("artifact") ?? - element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("artifact") ?? - element; + MFileMetadata? artifact = null; + var artifactProp = element.GetPropertyOrNull("artifact") ?? + element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("artifact"); + if (artifactProp.HasValue) + artifact = artifactProp.Value.Deserialize(); // classifiers IReadOnlyDictionary? classifiers = null; var classifiersProp = element.GetPropertyOrNull("classifies") ?? - element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("classifiers"); + element.GetPropertyOrNull("downloads")?.GetPropertyOrNull("classifiers"); if (classifiersProp.HasValue) classifiers = classifiersProp.Value.Deserialize>(); @@ -51,7 +53,7 @@ public static class JsonLibraryParser return new MLibrary(name) { - Artifact = artifact.Deserialize(), + Artifact = artifact, Classifiers = classifiers, Natives = natives, Rules = rules, diff --git a/test/Version/JsonLibraryParserTests.cs b/test/Version/JsonLibraryParserTests.cs index 03d1162..7eff18c 100644 --- a/test/Version/JsonLibraryParserTests.cs +++ b/test/Version/JsonLibraryParserTests.cs @@ -1,16 +1,14 @@ using System.Text.Json; using CmlLib.Core.Files; +using CmlLib.Core.Rules; using CmlLib.Core.Version; namespace CmlLib.Core.Test.Version; public class JsonLibraryParserTests { - [Fact] - public void parse_simple_java_library() - { - // release 1.0 - var json = """ + // release 1.0 + public static readonly string simple_java_library = """ { "downloads": { "artifact": { @@ -24,7 +22,10 @@ public void parse_simple_java_library() } """; - using var jsonDocument = JsonDocument.Parse(json); + [Fact] + public void parse_simple_java_library() + { + using var jsonDocument = JsonDocument.Parse(simple_java_library); var result = JsonLibraryParser.Parse(jsonDocument.RootElement); var expected = new MLibrary("net.minecraft:launchwrapper:1.5") { @@ -39,99 +40,98 @@ public void parse_simple_java_library() Assert.Equal(expected, result); } - [Fact] - public void parse_java_library_with_rules() - { - // release 1.0 - var json = """ + // release 1.0 + public static readonly string java_library_with_rules = """ +{ + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl/2.9.0/lwjgl-2.9.0.jar", + "sha1": "5654d06e61a1bba7ae1e7f5233e1106be64c91cd", + "size": 994633, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.0/lwjgl-2.9.0.jar" + } + }, + "name": "org.lwjgl.lwjgl:lwjgl:2.9.0", + "rules": [ { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl/2.9.0/lwjgl-2.9.0.jar", - "sha1": "5654d06e61a1bba7ae1e7f5233e1106be64c91cd", - "size": 994633, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.0/lwjgl-2.9.0.jar" - } - }, - "name": "org.lwjgl.lwjgl:lwjgl:2.9.0", - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ] + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } } - """; + ] +} +"""; - using var jsonDocument = JsonDocument.Parse(json); + [Fact] + public void parse_java_library_with_rules() + { + using var jsonDocument = JsonDocument.Parse(java_library_with_rules); var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); - Assert.Equal(2, lib?.Rules?.Count); } - [Fact] - public void parse_native_library() - { - // release 1.2.5 - var json = """ - { - "downloads": { - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar", - "sha1": "2ba5dcb11048147f1a74eff2deb192c001321f77", - "size": 569061, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-osx.jar", - "sha1": "6621b382cb14cc409b041d8d72829156a87c31aa", - "size": 518924, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", - "sha1": "3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", - "size": 609967, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] +// release 1.2.5 + public readonly static string native_library = """ +{ + "downloads": { + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar", + "sha1": "2ba5dcb11048147f1a74eff2deb192c001321f77", + "size": 569061, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar" }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.0", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" + "natives-osx": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-osx.jar", + "sha1": "6621b382cb14cc409b041d8d72829156a87c31aa", + "size": 518924, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-osx.jar" }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ] + "natives-windows": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", + "sha1": "3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", + "size": 609967, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar" + } } - """; + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.0", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ] +} +"""; - using var jsonDocument = JsonDocument.Parse(json); + [Fact] + public void parse_native_library() + { + using var jsonDocument = JsonDocument.Parse(native_library); var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); - var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "64")); + var nativeLib = lib?.GetNativeLibrary(new LauncherOSRule(LauncherOSRule.Windows, LauncherOSRule.X64)); var expected = new MFileMetadata { Path = "org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar", @@ -139,129 +139,131 @@ public void parse_native_library() Size = 609967, Url = "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar" }; + Assert.Null(lib?.Artifact); Assert.Equal(expected, nativeLib); } - [Fact] - public void parse_native_library_with_arch() - { - // release 1.7.10 - var json = """ - { - "downloads": { - "classifiers": { - "natives-osx": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", - "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", - "size": 418331, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" - }, - "natives-windows-32": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", - "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", - "size": 386792, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" - }, - "natives-windows-64": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", - "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", - "size": 463390, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] + // release 1.7.10 + public readonly static string native_library_with_arch = """ +{ + "downloads": { + "classifiers": { + "natives-osx": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", + "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", + "size": 418331, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" }, - "name": "tv.twitch:twitch-platform:5.16", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows-${arch}" + "natives-windows-32": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", + "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", + "size": 386792, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "linux" - } - } - ] + "natives-windows-64": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", + "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", + "size": 463390, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" + } } - """; + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "tv.twitch:twitch-platform:5.16", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows-${arch}" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "linux" + } + } + ] +} +"""; - using var jsonDocument = JsonDocument.Parse(json); + [Fact] + public void parse_native_library_with_arch() + { + using var jsonDocument = JsonDocument.Parse(native_library_with_arch); var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + Assert.Null(lib?.Artifact); - var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "32")); + var nativeLib = lib?.GetNativeLibrary(new LauncherOSRule(LauncherOSRule.Windows, LauncherOSRule.X86)); Assert.Equal("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", nativeLib?.Path); } - [Fact] - public void parse_java_library_and_native_library() - { - // release 1.8.9 - var json = """ - { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] + // release 1.8.9 + public readonly static string java_library_and_native_library = """ +{ + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", + "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + "size": 22, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }, + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", + "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", + "size": 578680, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" + "natives-osx": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", + "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", + "size": 426822, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] + "natives-windows": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", + "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", + "size": 613748, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" + } } - """; + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx" + } + } + ] +} +"""; - using var jsonDocument = JsonDocument.Parse(json); + [Fact] + public void parse_java_library_and_native_library() + { + using var jsonDocument = JsonDocument.Parse(java_library_and_native_library); var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); var nativeLib = lib?.GetNativeLibrary(new Core.Rules.LauncherOSRule("windows", "32")); From 17b0630526b555d83c65f5a441e5bf103be4cfee Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 2 Mar 2024 22:47:03 +0900 Subject: [PATCH 099/137] test: add tc for FileExtractors --- TODO.md | 8 +- .../FileExtractors/AssetFileExtractorTests.cs | 96 ++++++++++++++++++ .../LibraryFileExtractorTests.cs | 97 +++++++++++++++++++ test/FileExtractors/MockAssetIndex.cs | 12 +++ 4 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 test/FileExtractors/AssetFileExtractorTests.cs create mode 100644 test/FileExtractors/LibraryFileExtractorTests.cs create mode 100644 test/FileExtractors/MockAssetIndex.cs diff --git a/TODO.md b/TODO.md index fe58f91..0670645 100644 --- a/TODO.md +++ b/TODO.md @@ -4,16 +4,18 @@ - [x] GameInstaller 에서 중복 파일 확인 - [x] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 - [ ] MArgument 유닛테스트 -- [ ] Extractors 최대한 유닛테스트 -- [ ] Extractor 통합 테스트 작성. 주요 버전 파싱, GameFile 확인 +- [x] Extractors 최대한 유닛테스트 +- [x] Extractor 통합 테스트 작성. 주요 버전 파싱, GameFile 확인 - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 -- [ ] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성, 이후 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 +- [x] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성 +- [ ] 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 - [x] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 - [ ] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 - [ ] Memory 위에서 mutable 한 IVersion 구현 - [ ] JvmArgumentOverrides 에서 띄어쓰기 포함되어있으면 어떡하지? 예시: -Darg="hi -cp" - [x] introduce record type: AssetObject +- [ ] GameInstaller 에서 IReadOnlyCollection 아니라 IEnumerable 받도록 # Flow diff --git a/test/FileExtractors/AssetFileExtractorTests.cs b/test/FileExtractors/AssetFileExtractorTests.cs new file mode 100644 index 0000000..75cf52d --- /dev/null +++ b/test/FileExtractors/AssetFileExtractorTests.cs @@ -0,0 +1,96 @@ +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Files; +using CmlLib.Core.Internals; +using CmlLib.Core.Tasks; + +namespace CmlLib.Core.Test.FileExtractors; + +public class AssetFileExtractorTests +{ + public MinecraftPath TestPath = new() + { + Assets = "assets", + Resource = "resources", + }; + + public string TestAssetServer = "https://assetserver"; + + [Fact] + public void create_common_asset_file() + { + var index = new MockAssetIndex() + { + IsVirtual = false, + MapToResources = false, + AssetObjects = + [ + new AssetObject("icons/icon_128x128.png", "b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", 9101) + ] + }; + var result = AssetFileExtractor.Extractor.ExtractTasksFromAssetIndex(index, TestPath, TestAssetServer, false).ToArray(); + var expected = new GameFile("icons/icon_128x128.png") + { + Hash = "b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", + Size = 9101, + Path = IOUtil.NormalizePath("assets/objects/b6/b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356"), + Url = "https://assetserver/b6/b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356" + }; + Assert.Equal([expected], result); + } + + [Fact] + public void create_virtual_asset_file() + { + var index = new MockAssetIndex() + { + IsVirtual = true, + MapToResources = false, + AssetObjects = + [ + new AssetObject("icons/icon_128x128.png", "b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", 9101) + ] + }; + var result = AssetFileExtractor.Extractor.ExtractTasksFromAssetIndex(index, TestPath, TestAssetServer, false).ToArray(); + var expected = new GameFile("icons/icon_128x128.png") + { + Hash = "b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", + Size = 9101, + Path = IOUtil.NormalizePath("assets/objects/b6/b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356"), + Url = "https://assetserver/b6/b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", + UpdateTask = result.First().UpdateTask + }; + Assert.Equal([expected], result); + + var task = result.First().UpdateTask as FileCopyTask; + Assert.NotNull(task); + Assert.Equal([IOUtil.NormalizePath("assets/virtual/legacy/icons/icon_128x128.png")], task!.DestinationPaths); + } + + [Fact] + public void create_map_to_resources_asset_file() + { + var index = new MockAssetIndex() + { + IsVirtual = false, + MapToResources = true, + AssetObjects = + [ + new AssetObject("icons/icon_128x128.png", "b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", 9101) + ] + }; + var result = AssetFileExtractor.Extractor.ExtractTasksFromAssetIndex(index, TestPath, TestAssetServer, false).ToArray(); + var expected = new GameFile("icons/icon_128x128.png") + { + Hash = "b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", + Size = 9101, + Path = IOUtil.NormalizePath("assets/objects/b6/b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356"), + Url = "https://assetserver/b6/b62ca8ec10d07e6bf5ac8dae0c8c1d2e6a1e3356", + UpdateTask = result.First().UpdateTask + }; + Assert.Equal([expected], result); + + var task = result.First().UpdateTask as FileCopyTask; + Assert.NotNull(task); + Assert.Equal([IOUtil.NormalizePath("resources/icons/icon_128x128.png")], task!.DestinationPaths); + } +} \ No newline at end of file diff --git a/test/FileExtractors/LibraryFileExtractorTests.cs b/test/FileExtractors/LibraryFileExtractorTests.cs new file mode 100644 index 0000000..1270992 --- /dev/null +++ b/test/FileExtractors/LibraryFileExtractorTests.cs @@ -0,0 +1,97 @@ +using System.Text.Json; +using CmlLib.Core.FileExtractors; +using CmlLib.Core.Files; +using CmlLib.Core.Internals; +using CmlLib.Core.Rules; +using CmlLib.Core.Test.Version; +using CmlLib.Core.Version; + +namespace CmlLib.Core.Test.FileExtractors; + +public class LibraryFileExtractorTests +{ + MinecraftPath TestPath = new() + { + Library = "libraries" + }; + string TestServer = "https://libraryserver"; + RulesEvaluatorContext TestWindows = new RulesEvaluatorContext(new LauncherOSRule(LauncherOSRule.Windows, LauncherOSRule.X64)); + RulesEvaluatorContext TestOSX = new RulesEvaluatorContext(new LauncherOSRule(LauncherOSRule.OSX, LauncherOSRule.X64)); + RulesEvaluatorContext TestLinux = new RulesEvaluatorContext(new LauncherOSRule(LauncherOSRule.Linux, LauncherOSRule.X64)); + + [Fact] + public void extract_simple_java_library() + { + var library = parseLibrary(JsonLibraryParserTests.simple_java_library); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var expected = new GameFile("net.minecraft:launchwrapper:1.5") + { + Path = IOUtil.NormalizePath("libraries/net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar"), + Hash = "5150b9c2951f0fde987ce9c33496e26add1de224", + Size = 27787, + Url = "https://libraries.minecraft.net/net/minecraft/launchwrapper/1.5/launchwrapper-1.5.jar" + }; + Assert.Equal([expected], result); + } + + [Fact] + public void extract_native_library_on_linux() + { + var library = parseLibrary(JsonLibraryParserTests.native_library); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestLinux); + var expected = new GameFile("org.lwjgl.lwjgl:lwjgl-platform:2.9.0") + { + Path = IOUtil.NormalizePath("libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar"), + Hash = "2ba5dcb11048147f1a74eff2deb192c001321f77", + Size = 569061, + Url = "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-linux.jar" + }; + Assert.Equal([expected], result); + } + + [Fact] + public void extract_native_library_on_windows() + { + var library = parseLibrary(JsonLibraryParserTests.native_library); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var expected = new GameFile("org.lwjgl.lwjgl:lwjgl-platform:2.9.0") + { + Path = IOUtil.NormalizePath("libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar"), + Hash = "3f11873dc8e84c854ec7c5a8fd2e869f8aaef764", + Size = 609967, + Url = "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.0/lwjgl-platform-2.9.0-natives-windows.jar" + }; + Assert.Equal([expected], result); + } + + [Fact] + public void extract_java_library_and_native_library() + { + var library = parseLibrary(JsonLibraryParserTests.java_library_and_native_library); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var javaLibrary = new GameFile("org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209") + { + Path = IOUtil.NormalizePath("libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"), + Hash = "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + Size = 22, + Url = "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }; + var nativeLibrary = new GameFile("org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209") + { + Path = IOUtil.NormalizePath("libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar"), + Hash = "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", + Size = 613748, + Url = "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" + }; + Assert.Equal([javaLibrary, nativeLibrary], result.ToHashSet()); + } + + private static MLibrary parseLibrary(string json) + { + using var jsonDocument = JsonDocument.Parse(json); + var result = JsonLibraryParser.Parse(jsonDocument.RootElement); + if (result == null) + throw new InvalidOperationException("null json"); + return result; + } +} \ No newline at end of file diff --git a/test/FileExtractors/MockAssetIndex.cs b/test/FileExtractors/MockAssetIndex.cs new file mode 100644 index 0000000..13e700a --- /dev/null +++ b/test/FileExtractors/MockAssetIndex.cs @@ -0,0 +1,12 @@ +using CmlLib.Core.Files; + +namespace CmlLib.Core.Test.FileExtractors; + +public class MockAssetIndex : IAssetIndex +{ + public string Id { get; set; } = "mock"; + public bool IsVirtual { get; set; } + public bool MapToResources { get; set; } + public IEnumerable AssetObjects { get; set; } = Enumerable.Empty(); + public IEnumerable EnumerateAssetObjects() => AssetObjects; +} \ No newline at end of file From 4cc2f913d4db7db7c8f36bb489fdc5d24f09214f Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 3 Mar 2024 14:26:04 +0900 Subject: [PATCH 100/137] refactor: GameInstaller accept IEnumerable --- TODO.md | 19 ++++---- examples/console/Program.cs | 25 +++++----- src/Files/GameFilePathComparer.cs | 16 +++++++ src/Installers/BasicGameInstaller.cs | 28 ++++++++--- src/Installers/GameInstallerBase.cs | 8 ++-- src/Installers/IGameInstaller.cs | 2 +- src/Installers/ParallelGameInstaller.cs | 63 +++++++++++++------------ src/MinecraftLauncher.cs | 38 +++++++++------ 8 files changed, 124 insertions(+), 75 deletions(-) create mode 100644 src/Files/GameFilePathComparer.cs diff --git a/TODO.md b/TODO.md index 0670645..7b7e60a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,21 +1,22 @@ -- [x] MLaunchOption 에서 ExtraArguments 같은거 추가 - [ ] master 브랜치에서 v3.4.0 으로 cherry-pick +- [ ] MArgument 유닛테스트 +- [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 +- [ ] 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 +- [ ] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 +- [ ] Memory 위에서 mutable 한 IVersion 구현 +- [ ] JvmArgumentOverrides 에서 띄어쓰기 포함되어있으면 어떡하지? 예시: -Darg="hi -cp" +- [ ] { "name": "name" } 이런식으로 name 만 있는것도 GameFile 로 추출해야하나? 이전버전 어떻게했나 확인해보고 수정 +- [ ] MojangVersionLoader v2 구현 +- [x] GameInstaller 에서 IReadOnlyCollection 아니라 IEnumerable 받도록 +- [x] MLaunchOption 에서 ExtraArguments 같은거 추가 - [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 - [x] GameInstaller 에서 중복 파일 확인 - [x] GameInstaller 에서 UpdateExcludeFiles 같은거 만들기 -- [ ] MArgument 유닛테스트 - [x] Extractors 최대한 유닛테스트 - [x] Extractor 통합 테스트 작성. 주요 버전 파싱, GameFile 확인 -- [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 - [x] 바닐라 버전 우선적으로 완벽하게 처리하도록 테스트 케이스 작성 -- [ ] 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 - [x] LegacyJava 버그: task의 file이 실제 java binary 을 나타내지 않음 -- [ ] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 -- [ ] Memory 위에서 mutable 한 IVersion 구현 -- [ ] JvmArgumentOverrides 에서 띄어쓰기 포함되어있으면 어떡하지? 예시: --Darg="hi -cp" - [x] introduce record type: AssetObject -- [ ] GameInstaller 에서 IReadOnlyCollection 아니라 IEnumerable 받도록 # Flow diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 815cdad..f5d3ad7 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -5,6 +5,7 @@ using CmlLib.Core.Installers; using CmlLib.Core.ProcessBuilder; using CmlLib.Core.VersionLoader; +using CmlLib.Core.VersionMetadata; namespace CmlLibCoreSample; @@ -22,35 +23,37 @@ private async Task Start() // initialize launcher var parameters = MinecraftLauncherParameters.CreateDefault(); - //parameters.GameInstaller = new TPLGameInstaller(1); - //parameters.VersionLoader = new VersionLoaderCollection - //{ - // new LocalVersionLoader(parameters.MinecraftPath!) - //}; var launcher = new MinecraftLauncher(parameters); // add event handler - launcher.FileProgressChanged += Launcher_FileProgressChanged; - launcher.ByteProgressChanged += Launcher_ByteProgressChanged; + //launcher.FileProgressChanged += Launcher_FileProgressChanged; + //launcher.ByteProgressChanged += Launcher_ByteProgressChanged; // list versions var versions = await launcher.GetAllVersionsAsync(); foreach (var v in versions) { - Console.WriteLine($"{v.Name}, {v.Type}, {v.ReleaseTime}"); + //if (v.GetVersionType() == MVersionType.Release) + { + Console.WriteLine($"{v.Name}, {v.Type}, {v.ReleaseTime}"); + //await v.SaveVersionAsync(launcher.MinecraftPath); + } } // select version Console.WriteLine("Select the version to launch: "); Console.Write("> "); - var startVersion = Console.ReadLine(); - //var startVersion = "1.20.1"; + //var startVersion = Console.ReadLine(); + var startVersion = "1.20.1"; if (string.IsNullOrEmpty(startVersion)) return; // install sw.Start(); - await launcher.InstallAsync(startVersion); + await launcher.InstallAsync( + startVersion, + new SyncProgress(e => Launcher_FileProgressChanged(null, e)), + new SyncProgress(e => Launcher_ByteProgressChanged(null, e))); sw.Stop(); // build process diff --git a/src/Files/GameFilePathComparer.cs b/src/Files/GameFilePathComparer.cs new file mode 100644 index 0000000..862d771 --- /dev/null +++ b/src/Files/GameFilePathComparer.cs @@ -0,0 +1,16 @@ +namespace CmlLib.Core.Files; + +public class GameFilePathComparer : IEqualityComparer +{ + public static readonly GameFilePathComparer Default = new GameFilePathComparer(); + + public bool Equals(GameFile? x, GameFile? y) + { + return x?.Path == y?.Path; + } + + public int GetHashCode(GameFile obj) + { + return (obj.Path ?? "").GetHashCode(); + } +} \ No newline at end of file diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 6d0b1c1..1a8f530 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -10,17 +10,30 @@ public BasicGameInstaller(HttpClient httpClient) : base(httpClient) } protected override async ValueTask Install( - IReadOnlyList gameFiles, + IEnumerable gameFiles, CancellationToken cancellationToken) { - long totalBytes = gameFiles.Select(f => f.Size).Sum(); + long totalBytes = 0; long progressedBytes = 0; - for (int i = 0; i < gameFiles.Count; i++) + // queue files + var queue = new HashSet(GameFilePathComparer.Default); + foreach (var gameFile in gameFiles) { - var gameFile = gameFiles[i]; - FireFileProgress(gameFiles.Count, i, gameFile.Name, InstallerEventType.Queued); + if (!queue.Add(gameFile)) + continue; + + if (IsExcludedPath(gameFile.Path ?? "")) + continue; + totalBytes += gameFile.Size; + FireFileProgress(queue.Count, 0, gameFile.Name, InstallerEventType.Queued); + } + + // process files + int progressed = 0; + foreach (var gameFile in queue) + { var progress = new ByteProgressDelta(initialSize: gameFile.Size, delta => { totalBytes += delta.TotalBytes; @@ -28,7 +41,7 @@ protected override async ValueTask Install( FireByteProgress(totalBytes, progressedBytes); }); - if (NeedUpdate(gameFile) && !CheckExcludeFile(gameFile.Path ?? "")) + if (NeedUpdate(gameFile)) { await Download(gameFile, progress, cancellationToken); await gameFile.ExecuteUpdateTask(cancellationToken); @@ -38,7 +51,8 @@ protected override async ValueTask Install( progress.ReportDone(); } - FireFileProgress(gameFiles.Count, i + 1, gameFile.Name, InstallerEventType.Done); + progressed++; + FireFileProgress(queue.Count, progressed, gameFile.Name, InstallerEventType.Done); } } } \ No newline at end of file diff --git a/src/Installers/GameInstallerBase.cs b/src/Installers/GameInstallerBase.cs index feb7c0f..21fd9c2 100644 --- a/src/Installers/GameInstallerBase.cs +++ b/src/Installers/GameInstallerBase.cs @@ -24,7 +24,7 @@ public GameInstallerBase(HttpClient httpClient) private IProgress? ByteProgress; public async ValueTask Install( - IReadOnlyList gameFiles, + IEnumerable gameFiles, IProgress? fileProgress, IProgress? byteProgress, CancellationToken cancellationToken) @@ -38,13 +38,15 @@ public async ValueTask Install( excludeSet.Clear(); foreach (var excludeFile in ExcludeFiles) + { excludeSet.Add(excludeFile); + } await Install(gameFiles, cancellationToken); IsRunning = false; } - protected abstract ValueTask Install(IReadOnlyList gameFiles, CancellationToken cancellationToken); + protected abstract ValueTask Install(IEnumerable gameFiles, CancellationToken cancellationToken); protected bool NeedUpdate(GameFile file) { @@ -89,7 +91,7 @@ await HttpClientDownloadHelper.DownloadFileAsync( cancellationToken); } - protected bool CheckExcludeFile(string path) + protected bool IsExcludedPath(string path) { return excludeSet.Contains(path); } diff --git a/src/Installers/IGameInstaller.cs b/src/Installers/IGameInstaller.cs index af9f2a7..b4d10f2 100644 --- a/src/Installers/IGameInstaller.cs +++ b/src/Installers/IGameInstaller.cs @@ -5,7 +5,7 @@ namespace CmlLib.Core.Installers; public interface IGameInstaller { ValueTask Install( - IReadOnlyList gameFiles, + IEnumerable gameFiles, IProgress? fileProgress, IProgress? byteProgress, CancellationToken cancellationToken); diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index d139606..79b1da3 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -41,31 +41,32 @@ public ParallelGameInstaller( ThreadLocal? progressStorage; int totalFiles = 0; int progressedFiles = 0; - long totalBytes = 0; protected override async ValueTask Install( - IReadOnlyList gameFiles, + IEnumerable gameFiles, CancellationToken cancellationToken) { - totalFiles = gameFiles.Count; - progressedFiles = 0; - totalBytes = gameFiles.Select(f => f.Size).Sum(); progressStorage = new ThreadLocal( () => new ByteProgress(), true); + totalFiles = 0; + progressedFiles = 0; var (firstBlock, lastBlock) = buildBlock(cancellationToken); - for (int i = 0; i < totalFiles; i++) + + var queue = new HashSet(GameFilePathComparer.Default); + foreach (var gameFile in gameFiles) { - var file = gameFiles[i]; - if (CheckExcludeFile(file.Path ?? "")) - { - totalFiles--; - totalBytes -= file.Size; + if (!queue.Add(gameFile)) continue; - } - FireFileProgress(totalFiles, progressedFiles, file.Name, InstallerEventType.Queued); - await firstBlock.SendAsync(file, cancellationToken); + if (IsExcludedPath(gameFile.Path ?? "")) + continue; + + addProgressToStorage(gameFile.Size, 0); + Interlocked.Increment(ref totalFiles); + + FireFileProgress(totalFiles, progressedFiles, gameFile.Name, InstallerEventType.Queued); + await firstBlock.SendAsync(gameFile, cancellationToken); } firstBlock.Complete(); @@ -93,22 +94,13 @@ protected override async ValueTask Install( new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken, - MaxDegreeOfParallelism = MaxChecker + MaxDegreeOfParallelism = MaxChecker, + EnsureOrdered = false }); - - var buffer = new BufferBlock<(GameFile, bool)>(); - var downloadBlock = new ActionBlock<(GameFile GameFile, bool NeedUpdate)>(async result => { var progress = new ByteProgressDelta(initialSize: result.GameFile.Size, delta => - { - var storedProgress = progressStorage.Value; - progressStorage.Value = new ByteProgress - { - TotalBytes = storedProgress.TotalBytes + delta.TotalBytes, - ProgressedBytes = storedProgress.ProgressedBytes + delta.ProgressedBytes - }; - }); + addProgressToStorage(delta.TotalBytes, delta.ProgressedBytes)); if (result.NeedUpdate) { @@ -126,21 +118,32 @@ protected override async ValueTask Install( new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken, - MaxDegreeOfParallelism = MaxDownloader + MaxDegreeOfParallelism = MaxDownloader, + BoundedCapacity = 1000, + EnsureOrdered = false }); var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; - checkBlock.LinkTo(buffer, linkOptions); - buffer.LinkTo(downloadBlock, linkOptions); + checkBlock.LinkTo(downloadBlock, linkOptions); return (checkBlock, downloadBlock); } + private void addProgressToStorage(long totalBytes, long progressedBytes) + { + var storedProgress = progressStorage!.Value; + progressStorage.Value = new ByteProgress + { + TotalBytes = storedProgress.TotalBytes + totalBytes, + ProgressedBytes = storedProgress.ProgressedBytes + progressedBytes + }; + } + private void aggregateAndReportByteProgress() { Debug.Assert(progressStorage != null); - long aggregatedTotalBytes = totalBytes; + long aggregatedTotalBytes = 0; long aggregatedProgressedBytes = 0; foreach (var progress in progressStorage.Values) { diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 8c9d29c..6e362ed 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -14,8 +14,8 @@ namespace CmlLib.Core; public class MinecraftLauncher { - private readonly Progress _fileProgress; - private readonly Progress _byteProgress; + private readonly IProgress _fileProgress; + private readonly IProgress _byteProgress; public event EventHandler? FileProgressChanged; public event EventHandler? ByteProgressChanged; @@ -91,7 +91,7 @@ public async ValueTask GetVersionAsync(string versionName) } } - public async ValueTask> ExtractFiles( + public async ValueTask> ExtractFiles( string versionName, CancellationToken cancellationToken = default) { @@ -99,40 +99,50 @@ public async ValueTask> ExtractFiles( return await ExtractFiles(version, cancellationToken); } - public async ValueTask> ExtractFiles( + public async ValueTask> ExtractFiles( IVersion version, CancellationToken cancellationToken) { - // filter duplicated paths - var fileSet = new Dictionary(); + var allFiles = Enumerable.Empty(); foreach (var extractor in FileExtractors) { var files = await extractor.Extract(MinecraftPath, version, RulesContext, cancellationToken); - foreach (var file in files) - { - fileSet[file.Path ?? ""] = file; - } + allFiles = allFiles.Concat(files); } - return fileSet.Values.ToList(); + return allFiles; } + public ValueTask InstallAsync( + string versionName, + CancellationToken cancellationToken = default) => + InstallAsync(versionName, null, null, cancellationToken); + public async ValueTask InstallAsync( string versionName, + IProgress? fileProgress, + IProgress? byteProgress, CancellationToken cancellationToken = default) { var version = await GetVersionAsync(versionName); - await InstallAsync(version, cancellationToken); + await InstallAsync(version, fileProgress, byteProgress, cancellationToken); } + public ValueTask InstallAsync( + IVersion version, + CancellationToken cancellationToken = default) => + InstallAsync(version, null, null, cancellationToken); + public async ValueTask InstallAsync( IVersion version, + IProgress? fileProgress, + IProgress? byteProgress, CancellationToken cancellationToken = default) { var files = await ExtractFiles(version, cancellationToken); await GameInstaller.Install( files, - _fileProgress, - _byteProgress, + fileProgress ?? _fileProgress, + byteProgress ?? _byteProgress, cancellationToken); } From eec883467022b5171b66ff73b90a09d0cd766b54 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 10 Mar 2024 20:01:34 +0900 Subject: [PATCH 101/137] test: add forge versions --- .../LibraryFileExtractorTests.cs | 55 ++++++++ test/Version/JsonArgumentParserTests.cs | 73 +++++++++++ test/Version/JsonLibraryParserTests.cs | 118 ++++++++++++++++++ 3 files changed, 246 insertions(+) diff --git a/test/FileExtractors/LibraryFileExtractorTests.cs b/test/FileExtractors/LibraryFileExtractorTests.cs index 1270992..aa7d725 100644 --- a/test/FileExtractors/LibraryFileExtractorTests.cs +++ b/test/FileExtractors/LibraryFileExtractorTests.cs @@ -86,6 +86,61 @@ public void extract_java_library_and_native_library() Assert.Equal([javaLibrary, nativeLibrary], result.ToHashSet()); } + [Fact] + public void extract_java_library_with_url() + { + var library = parseLibrary(JsonLibraryParserTests.java_library_with_url); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var expected = new GameFile("net.minecraftforge:forge:1.7.10-10.13.4.1558-1.7.10") + { + Path = IOUtil.NormalizePath("libraries/net/minecraftforge/forge/1.7.10-10.13.4.1558-1.7.10/forge-1.7.10-10.13.4.1558-1.7.10.jar"), + Url = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/1.7.10-10.13.4.1558-1.7.10/forge-1.7.10-10.13.4.1558-1.7.10.jar" + }; + Assert.Equal([expected], result); + } + + [Fact] + public void extract_java_library_with_url_checksums() + { + var library = parseLibrary(JsonLibraryParserTests.java_library_with_url_checksums); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var expected = new GameFile("com.typesafe.akka:akka-actor_2.11:2.3.3") + { + Path = IOUtil.NormalizePath("libraries/com/typesafe/akka/akka-actor_2.11/2.3.3/akka-actor_2.11-2.3.3.jar"), + Url = "http://files.minecraftforge.net/maven/com/typesafe/akka/akka-actor_2.11/2.3.3/akka-actor_2.11-2.3.3.jar", + Hash = "ed62e9fc709ca0f2ff1a3220daa8b70a2870078e", + }; + Assert.Equal([expected], result); + } + + [Fact] + public void extract_java_library_with_only_name() + { + var library = parseLibrary(JsonLibraryParserTests.java_library_with_only_name); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var expected = new GameFile("java3d:vecmath:1.5.2") + { + Path = IOUtil.NormalizePath("libraries/java3d/vecmath/1.5.2/vecmath-1.5.2.jar"), + Url = "https://libraryserver/java3d/vecmath/1.5.2/vecmath-1.5.2.jar" + }; + Assert.Equal([expected], result); + } + + [Fact] + public void extract_java_library_with_empty_url() + { + var library = parseLibrary(JsonLibraryParserTests.java_library_with_empty_url); + var result = LibraryFileExtractor.Extractor.ExtractTasks(TestServer, TestPath, library, TestWindows); + var expected = new GameFile("net.minecraftforge:forge:1.12.2-14.23.5.2854") + { + Path = IOUtil.NormalizePath("libraries/net/minecraftforge/forge/1.12.2-14.23.5.2854/forge-1.12.2-14.23.5.2854.jar"), + Url = null, + Hash = "762f5cb227a8dd88cfd3949033c78f24030b36aa", + Size = 4464068 + }; + Assert.Equal([expected], result); + } + private static MLibrary parseLibrary(string json) { using var jsonDocument = JsonDocument.Parse(json); diff --git a/test/Version/JsonArgumentParserTests.cs b/test/Version/JsonArgumentParserTests.cs index 5f789ad..755f9a1 100644 --- a/test/Version/JsonArgumentParserTests.cs +++ b/test/Version/JsonArgumentParserTests.cs @@ -316,4 +316,77 @@ public void parse_from_vanilla_jvm_arg_array() "${classpath}" ], parsedArgs); } + + public readonly static string forge_arguments = """ +{ + "id": "1.16.5-forge-36.2.0", + "arguments": { + "game": [ + "--launchTarget", + "fmlclient", + "--fml.forgeVersion", + "36.2.0", + "--fml.mcVersion", + "1.16.5", + "--fml.forgeGroup", + "net.minecraftforge", + "--fml.mcpVersion", + "20210115.111550" + ], + "jvm": [ + "-XX:+IgnoreUnrecognizedVMOptions", + "--add-exports=java.base/sun.security.util=ALL-UNNAMED", + "--add-exports=jdk.naming.dns/com.sun.jndi.dns=java.naming", + "--add-opens=java.base/java.util.jar=ALL-UNNAMED" + ] + } +} +"""; + + public readonly static string forge_arguments_2 = """ +{ + "id": "1.17.1-forge-37.0.48", + "arguments": { + "game": [ + "--launchTarget", + "forgeclient", + "--fml.forgeVersion", + "37.0.48", + "--fml.mcVersion", + "1.17.1", + "--fml.forgeGroup", + "net.minecraftforge", + "--fml.mcpVersion", + "20210706.113038" + ], + "jvm": [ + "-DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,client-extra,fmlcore,javafmllanguage,mclanguage,forge-,${version_name}.jar", + "-DmergeModules=jna-5.8.0.jar,jna-platform-58.0.jar,java-objc-bridge-1.0.0.jar", + "-DlibraryDirectory=${library_directory}", + "-p", + "${library_directory}/cpw/mods/bootstraplauncher/0.1.17/bootstraplauncher-0.1.17.jar${classpath_separator}${library_directory}/cpw/mods/securejarhandler/0.9.46/securejarhandler-0.9.46.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm/9.1/asm-9.1.jar", + "--add-modules", + "ALL-MODULE-PATH", + "--add-opens", + "java.base/java.util.jar=cpw.mods.securejarhandler", + "--add-exports", + "java.base/sun.security.util=cpw.mods.securejarhandler", + "--add-exports", + "jdk.naming.dns/com.sun.jndi.dns=java.naming" + ] + }, +} +"""; + + public readonly static string fabric_loader_arguments = """ +{ + "id": "fabric-loader-0.13.3-1.18.2", + "arguments": { + "game": [], + "jvm": [ + "-DFabricMcEmu= net.minecraft.client.main.Main " + ] + }, +} +"""; } \ No newline at end of file diff --git a/test/Version/JsonLibraryParserTests.cs b/test/Version/JsonLibraryParserTests.cs index 7eff18c..7b0ecf9 100644 --- a/test/Version/JsonLibraryParserTests.cs +++ b/test/Version/JsonLibraryParserTests.cs @@ -272,4 +272,122 @@ public void parse_java_library_and_native_library() var javaLib = lib?.Artifact; Assert.Equal("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", javaLib?.Path); } + + // 1.7.10-Forge10.13.4.1558-1.7.10 + public static readonly string java_library_with_url = """ +{ + "name": "net.minecraftforge:forge:1.7.10-10.13.4.1558-1.7.10", + "url": "http://files.minecraftforge.net/maven/" +} +"""; + + [Fact] + public void parse_java_library_with_url() + { + using var jsonDocument = JsonDocument.Parse(java_library_with_url); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + Assert.Null(lib?.Classifiers); + Assert.Equal("net.minecraftforge:forge:1.7.10-10.13.4.1558-1.7.10", lib?.Name); + Assert.Equal("http://files.minecraftforge.net/maven/", lib?.Artifact?.Url); + Assert.Null(lib?.Artifact?.Path); + Assert.Null(lib?.Artifact?.GetSha1()); + } + + // 1.7.10-Forge10.13.4.1558-1.7.10 + public static readonly string java_library_with_url_checksums = """ +{ + "name": "com.typesafe.akka:akka-actor_2.11:2.3.3", + "url": "http://files.minecraftforge.net/maven/", + "checksums": [ + "ed62e9fc709ca0f2ff1a3220daa8b70a2870078e", + "25a86ccfdb6f6dfe08971f4825d0a01be83a6f2e" + ], + "serverreq": true, + "clientreq": true +} +"""; + + public static readonly string java_library_with_url_checksums_2 = """ +{ + "name": "org.ow2.asm:asm-all:5.2", + "url": "https://maven.minecraftforge.net/", + "checksums": [ + "2ea49e08b876bbd33e0a7ce75c8f371d29e1f10a" + ], + "serverreq": true, + "clientreq": true +} +"""; + + [Fact] + public void parse_java_library_with_url_checksums() + { + using var jsonDocument = JsonDocument.Parse(java_library_with_url_checksums); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + Assert.Equal("com.typesafe.akka:akka-actor_2.11:2.3.3", lib?.Name); + Assert.Equal("http://files.minecraftforge.net/maven/", lib?.Artifact?.Url); + Assert.Equal(["ed62e9fc709ca0f2ff1a3220daa8b70a2870078e", "25a86ccfdb6f6dfe08971f4825d0a01be83a6f2e"], lib?.Artifact?.Checksums); + Assert.Null(lib?.Artifact?.Path); + Assert.Null(lib?.Classifiers); + } + + // 1.8.9-forge1.8.9-11.15.1.1722 + public static readonly string java_library_with_only_name = """ +{ + "name": "java3d:vecmath:1.5.2", + "clientreq": true, + "serverreq": true +} +"""; + + // 1.8.9-OptiFine_HD_U_M5 + public static readonly string java_library_with_only_name_2 = """ +{ + "name": "optifine:OptiFine:1.8.9_HD_U_M5" +} +"""; + + [Fact] + public void parse_java_library_with_only_name() + { + using var jsonDocument = JsonDocument.Parse(java_library_with_only_name); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + Assert.Equal("java3d:vecmath:1.5.2", lib?.Name); + Assert.Null(lib?.Artifact?.Url); + Assert.Null(lib?.Artifact?.Path); + Assert.Null(lib?.Classifiers); + } + + // 1.12.2-forge-14.23.5.2854 + public static readonly string java_library_with_empty_url = """ +{ + "name": "net.minecraftforge:forge:1.12.2-14.23.5.2854", + "downloads": { + "artifact": { + "path": "net/minecraftforge/forge/1.12.2-14.23.5.2854/forge-1.12.2-14.23.5.2854.jar", + "url": "", + "sha1": "762f5cb227a8dd88cfd3949033c78f24030b36aa", + "size": 4464068 + } + } +} +"""; + + [Fact] + public void parse_java_library_with_empty_url() + { + using var jsonDocument = JsonDocument.Parse(java_library_with_empty_url); + var lib = JsonLibraryParser.Parse(jsonDocument.RootElement); + var expected = new MLibrary("net.minecraftforge:forge:1.12.2-14.23.5.2854") + { + Artifact = new MFileMetadata + { + Path = "net/minecraftforge/forge/1.12.2-14.23.5.2854/forge-1.12.2-14.23.5.2854.jar", + Url = "", + Sha1 = "762f5cb227a8dd88cfd3949033c78f24030b36aa", + Size = 4464068 + } + }; + Assert.Equal(expected, lib); + } } \ No newline at end of file From 4ab2585b42578df4833f9d64dc4d70de8697fe66 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 10 Mar 2024 20:02:42 +0900 Subject: [PATCH 102/137] fix: parse libraries that have no artifacts and classifiers --- src/Version/JsonLibraryParser.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Version/JsonLibraryParser.cs b/src/Version/JsonLibraryParser.cs index 5b8366c..0989a91 100644 --- a/src/Version/JsonLibraryParser.cs +++ b/src/Version/JsonLibraryParser.cs @@ -51,6 +51,13 @@ public static class JsonLibraryParser if (nativesProp.HasValue) natives = nativesProp.Value.Deserialize>(); + // some libraries (forge, optifine, fabric) lack 'artifacts' or 'classifiers' property; + // instead they have metadata properties directly + if (artifact == null && classifiers == null) + { + artifact = element.Deserialize(); + } + return new MLibrary(name) { Artifact = artifact, From 57444fe7cf0c1778f454b4526b681e908af308d4 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 10 Mar 2024 20:04:53 +0900 Subject: [PATCH 103/137] fix: skip files without url and path --- src/Installers/BasicGameInstaller.cs | 5 ++++- src/Installers/ParallelGameInstaller.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index 1a8f530..e0a4b8e 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -20,10 +20,13 @@ protected override async ValueTask Install( var queue = new HashSet(GameFilePathComparer.Default); foreach (var gameFile in gameFiles) { + if (string.IsNullOrEmpty(gameFile.Url) || string.IsNullOrEmpty(gameFile.Path)) + continue; + if (!queue.Add(gameFile)) continue; - if (IsExcludedPath(gameFile.Path ?? "")) + if (IsExcludedPath(gameFile.Path)) continue; totalBytes += gameFile.Size; diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index 79b1da3..f27df46 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -56,10 +56,13 @@ protected override async ValueTask Install( var queue = new HashSet(GameFilePathComparer.Default); foreach (var gameFile in gameFiles) { + if (string.IsNullOrEmpty(gameFile.Url) || string.IsNullOrEmpty(gameFile.Path)) + continue; + if (!queue.Add(gameFile)) continue; - if (IsExcludedPath(gameFile.Path ?? "")) + if (IsExcludedPath(gameFile.Path)) continue; addProgressToStorage(gameFile.Size, 0); From f7066894c3963653015452f669812b0d9a23c4c6 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 16 Mar 2024 16:13:08 +0900 Subject: [PATCH 104/137] refactor: add CommandLineBuilder and upgrade MArgument --- TODO.md | 17 +- src/CmlLib.Core.csproj | 2 +- src/CommandParser/CommandLineBuilder.cs | 82 +++++++ src/CommandParser/CommandLineStates.cs | 139 +++++++++++ src/CommandParser/Extensions.cs | 14 ++ src/CommandParser/KeyValueArgument.cs | 110 +++++++++ src/CommandParser/Parser.cs | 40 ++++ src/CommandParser/ParserStates.cs | 81 +++++++ src/Internals/IOUtil.cs | 12 +- src/Mapper.cs | 134 ----------- src/MinecraftLauncher.cs | 48 ++-- src/ProcessBuilder/MArgument.cs | 24 +- src/ProcessBuilder/MLaunchOption.cs | 27 ++- src/ProcessBuilder/Mapper.cs | 27 +++ src/ProcessBuilder/MinecraftProcessBuilder.cs | 218 ++++++------------ src/ProcessBuilder/ProcessArgumentBuilder.cs | 140 ----------- test/CommandParser/CommandLineBuilderTests.cs | 78 +++++++ test/CommandParser/CommandLineParserTests.cs | 59 +++++ .../KeyValueArgumentParserTests.cs | 60 +++++ test/CommandParser/KeyValueArgumentTests.cs | 61 +++++ test/MapperTests.cs | 28 --- test/ProcessBuilder/MapperTests.cs | 64 +++++ .../MinecraftProcessBuilderTests.cs | 10 - .../ProcessArgumentBuilderTests.cs | 132 ----------- test/Version/JsonArgumentParserTests.cs | 147 +++++++++--- 25 files changed, 1084 insertions(+), 670 deletions(-) create mode 100644 src/CommandParser/CommandLineBuilder.cs create mode 100644 src/CommandParser/CommandLineStates.cs create mode 100644 src/CommandParser/Extensions.cs create mode 100644 src/CommandParser/KeyValueArgument.cs create mode 100644 src/CommandParser/Parser.cs create mode 100644 src/CommandParser/ParserStates.cs delete mode 100644 src/Mapper.cs create mode 100644 src/ProcessBuilder/Mapper.cs delete mode 100644 src/ProcessBuilder/ProcessArgumentBuilder.cs create mode 100644 test/CommandParser/CommandLineBuilderTests.cs create mode 100644 test/CommandParser/CommandLineParserTests.cs create mode 100644 test/CommandParser/KeyValueArgumentParserTests.cs create mode 100644 test/CommandParser/KeyValueArgumentTests.cs delete mode 100644 test/MapperTests.cs create mode 100644 test/ProcessBuilder/MapperTests.cs delete mode 100644 test/ProcessBuilder/MinecraftProcessBuilderTests.cs delete mode 100644 test/ProcessBuilder/ProcessArgumentBuilderTests.cs diff --git a/TODO.md b/TODO.md index 7b7e60a..24c659b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,17 @@ -- [ ] master 브랜치에서 v3.4.0 으로 cherry-pick -- [ ] MArgument 유닛테스트 +- [ ] 라이트로더 패브릭 tlauncher 등등 확장가능하게 테스트 케이스 작성 +- [ ] LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 -- [ ] 포지 라이트로더 옵티파인 패브릭 tlauncher 커스텀클라이언트 등등 확장가능하게 테스트 케이스 작성 -- [ ] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 - [ ] Memory 위에서 mutable 한 IVersion 구현 -- [ ] JvmArgumentOverrides 에서 띄어쓰기 포함되어있으면 어떡하지? 예시: -Darg="hi -cp" -- [ ] { "name": "name" } 이런식으로 name 만 있는것도 GameFile 로 추출해야하나? 이전버전 어떻게했나 확인해보고 수정 - [ ] MojangVersionLoader v2 구현 +- [ ] master 브랜치에서 v3.4.0 으로 cherry-pick +- [ ] 자동화된 e2e test runner 만들기 +- [ ] quickPlayServer, serverip, 같은 feature 자동설정 +- [x] MArgument 유닛테스트 +- [x] 포지 옵티파인 iconic-mixed vexed +- [x] { "name": "name" } 이런식으로 name 만 있는것도 GameFile 로 추출해야하나? 이전버전 어떻게했나 확인해보고 수정 -> 추출했음 +- [x] Path 가 비어있는 파일, Url 이 비어있는 파일은 큐잉하지 않고 스킵 +- [x] JvmArgumentOverrides 에서 띄어쓰기 포함되어있으면 어떡하지? 예시: -Darg="hi -cp" -> CommandLineParser +- [x] 4.0.0 으로 올릴지 3.4.0 으로 올릴지 결정 -> 4.0.0 으로 올리고 레거시 쓸때없는거 다 지우고 바꾸고 가는게 좋을듯 - [x] GameInstaller 에서 IReadOnlyCollection 아니라 IEnumerable 받도록 - [x] MLaunchOption 에서 ExtraArguments 같은거 추가 - [x] RulesEvalutor 에서 ${arch} 와 os: 에서 쓰이는거 용도따라 바뀌게 diff --git a/src/CmlLib.Core.csproj b/src/CmlLib.Core.csproj index 27ff8f9..af055f5 100644 --- a/src/CmlLib.Core.csproj +++ b/src/CmlLib.Core.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 10.0 + 12.0 enable enable 3.4.0 diff --git a/src/CommandParser/CommandLineBuilder.cs b/src/CommandParser/CommandLineBuilder.cs new file mode 100644 index 0000000..89869fe --- /dev/null +++ b/src/CommandParser/CommandLineBuilder.cs @@ -0,0 +1,82 @@ +using System.Text; + +namespace CmlLib.Core.CommandParser; + +public class CommandLineBuilder +{ + private readonly StringBuilder _sb = new(); + private List _arguments = new(); + + public IEnumerable Arguments => _arguments; + + public void AddCommandLine(string cmd) + { + if (string.IsNullOrEmpty(cmd)) + return; + + var parsedArgs = Parser.ParseCommandLineToArguments(cmd); + var parsedKv = Parser.ParseArgumentsToKeyValue(parsedArgs); + foreach (var kv in parsedKv) + { + _arguments.Add(kv); + } + + _sb.Append(cmd); + _sb.Append(" "); + } + + public void AddArguments(IEnumerable args) + { + args = args.Select(arg => + { + _sb.Append(KeyValueArgument.Escape(arg)); + _sb.Append(" "); + return arg; + }); + + var parsedKv = Parser.ParseArgumentsToKeyValue(args); + foreach (var kv in parsedKv) + { + _arguments.Add(kv); + } + } + + public void AddKeyValueArgument(KeyValueArgument arg) + { + _arguments.Add(arg); + + var argStr = arg.ToString(); + if (!string.IsNullOrEmpty(argStr)) + { + _sb.Append(arg.ToString()); + _sb.Append(" "); + } + } + + public bool ContainsKey(string key) + { + return _arguments.Any(arg => arg.Key == key); + } + + public IEnumerable Find(string key) + { + return _arguments.Where(arg => arg.Key == key); + } + + public string Build() + { + if (_sb.Length > 0) + { + // Add 메서드는 항상 _sb 끝에 공백 문자를 추가하기 때문에, + // 마지막으로 추가된 공백 문자를 지우고 문자열을 만든다. + // 예를 들어 Add("a"); Add("b"); Add("c") 를 하면 _sb 안에는 + // "a b c " 가 들어있기에 마지막 공백을 지우고 "a b c" 를 반환한다. + + return _sb.ToString(0, _sb.Length - 1); + } + else + { + return ""; + } + } +} \ No newline at end of file diff --git a/src/CommandParser/CommandLineStates.cs b/src/CommandParser/CommandLineStates.cs new file mode 100644 index 0000000..5550402 --- /dev/null +++ b/src/CommandParser/CommandLineStates.cs @@ -0,0 +1,139 @@ +using System.Text; + +namespace CmlLib.Core.CommandParser; + +interface ICommandLineStateMachine +{ + void End(ArgumentBuilder current); + ICommandLineStateMachine Put(ArgumentBuilder current, char next); +} + +static class CommandLineStates +{ + public readonly static ICommandLineStateMachine Init = new InitState(); + public readonly static ICommandLineStateMachine Value = new ValueState(); + public readonly static ICommandLineStateMachine Quoted = new QuotedState(); + public readonly static ICommandLineStateMachine EscapedFromValue = new EscapedState(returnTo: Value); + public readonly static ICommandLineStateMachine EscapedFromQuoted = new EscapedState(returnTo: Quoted); + + class InitState : ICommandLineStateMachine + { + public void End(ArgumentBuilder current) + { + + } + + public ICommandLineStateMachine Put(ArgumentBuilder current, char next) + { + if (char.IsWhiteSpace(next)) + { + return CommandLineStates.Init; + } + else if (next == '"') + { + return CommandLineStates.Quoted; + } + else if (next == '\\') + { + return CommandLineStates.EscapedFromValue; + } + else + { + current.Append(next); + return CommandLineStates.Value; + } + } + } + + class ValueState : ICommandLineStateMachine + { + public void End(ArgumentBuilder current) + { + current.Complete(); + } + + public ICommandLineStateMachine Put(ArgumentBuilder current, char next) + { + if (char.IsWhiteSpace(next)) + { + current.Complete(); + return CommandLineStates.Init; + } + else if (next == '"') + { + return CommandLineStates.Quoted; + } + else if (next == '\\') + { + return CommandLineStates.EscapedFromValue; + } + else + { + current.Append(next); + return CommandLineStates.Value; + } + } + } + + class QuotedState : ICommandLineStateMachine + { + public void End(ArgumentBuilder current) + { + current.Complete(); + } + + public ICommandLineStateMachine Put(ArgumentBuilder current, char next) + { + if (next == '"') + { + return CommandLineStates.Value; + } + else if (next == '\\') + { + return CommandLineStates.EscapedFromQuoted; + } + else + { + current.Append(next); + return CommandLineStates.Quoted; + } + } + } + + class EscapedState : ICommandLineStateMachine + { + private readonly ICommandLineStateMachine _returnTo; + public EscapedState(ICommandLineStateMachine returnTo) => _returnTo = returnTo; + + public void End(ArgumentBuilder current) + { + current.Complete(); + } + + public ICommandLineStateMachine Put(ArgumentBuilder current, char next) + { + current.Append(next); + return _returnTo; + } + } +} + +class ArgumentBuilder +{ + private readonly Queue _q = new(); + private readonly StringBuilder _sb = new(); + + public void Append(char c) => _sb.Append(c); + + public void Complete() + { + _q.Enqueue(_sb.ToString()); + _sb.Clear(); + } + + public IEnumerable PopArguments() + { + while (_q.Any()) + yield return _q.Dequeue(); + } +} \ No newline at end of file diff --git a/src/CommandParser/Extensions.cs b/src/CommandParser/Extensions.cs new file mode 100644 index 0000000..7535eb8 --- /dev/null +++ b/src/CommandParser/Extensions.cs @@ -0,0 +1,14 @@ +namespace CmlLib.Core.CommandParser; + +public static class Extensions +{ + public static bool ContainsXmx(this CommandLineBuilder builder) + { + return builder.Arguments.Any(arg => arg.Key.StartsWith("-Xmx")); + } + + public static bool ContainsXms(this CommandLineBuilder builder) + { + return builder.Arguments.Any(arg => arg.Key.StartsWith("-Xms")); + } +} \ No newline at end of file diff --git a/src/CommandParser/KeyValueArgument.cs b/src/CommandParser/KeyValueArgument.cs new file mode 100644 index 0000000..3796b03 --- /dev/null +++ b/src/CommandParser/KeyValueArgument.cs @@ -0,0 +1,110 @@ +namespace CmlLib.Core.CommandParser; + +public class KeyValueArgument +{ + public static KeyValueArgument Create(string key, string? value) + { + if (key != "" && !key.StartsWith("-")) + throw new FormatException("key should start with key prefix (-)"); + return new KeyValueArgument(key, value); + } + + public static KeyValueArgument CreateWithoutValidation(string key, string? value) + { + return new KeyValueArgument(key, value); + } + + public static string Escape(string input) + { + input = input.Replace("\"", "\\\""); + + if (input == "") + { + return "\"\""; + } + else if (input.Any(c => char.IsWhiteSpace(c))) + { + return "\"" + input + "\""; + } + else + { + return input; + } + } + + private KeyValueArgument(string key, string? value) + { + Key = key; + Value = value; + } + + public string Key { get; } + public string? Value { get; } + + public override string ToString() + { + return ToString(true); + } + + public string ToString(bool formatMode) + { + if (Key == "" && Value == null) + return ""; + else if (Value == null) + return Key; + else if (Key == "") + return Value; + else + { + if (formatMode) + return Key + "=" + Escape(Value); + else + return Key + " " + Escape(Value); + } + } + + public override int GetHashCode() + { + return Key.GetHashCode() ^ (Value ?? "").GetHashCode(); + } + + public override bool Equals(object? obj) + { + if (obj is KeyValueArgument arg) + { + return this.Key == arg.Key && this.Value == arg.Value; + } + else + { + return false; + } + } +} + +public class KeyValueArgumentBuilder +{ + private readonly Queue _q = new(); + + public string Key { get; set; } = ""; + public string? Value { get; set; } + + public void Clear() + { + Key = ""; + Value = null; + } + + public void Complete() + { + _q.Enqueue(KeyValueArgument.CreateWithoutValidation(Key, Value)); + Clear(); + } + + public IEnumerable PopArguments() + { + while (_q.Any()) + { + yield return _q.Dequeue(); + } + } +} \ No newline at end of file diff --git a/src/CommandParser/Parser.cs b/src/CommandParser/Parser.cs new file mode 100644 index 0000000..ee8d174 --- /dev/null +++ b/src/CommandParser/Parser.cs @@ -0,0 +1,40 @@ +namespace CmlLib.Core.CommandParser; + +public static class Parser +{ + public static IEnumerable ParseArgumentsToKeyValue(IEnumerable args) + { + IParserStateMachine state = ParserStates.Init; + KeyValueArgumentBuilder current = new(); + + foreach (var arg in args) + { + state = state.Put(current, arg); + + foreach (var parsed in current.PopArguments()) + yield return parsed; + } + + state.End(current); + foreach (var parsed in current.PopArguments()) + yield return parsed; + } + + public static IEnumerable ParseCommandLineToArguments(string input) + { + ICommandLineStateMachine state = CommandLineStates.Init; + ArgumentBuilder current = new(); + + foreach (char c in input) + { + state = state.Put(current, c); + + foreach (var parsed in current.PopArguments()) + yield return parsed; + } + + state.End(current); + foreach (var parsed in current.PopArguments()) + yield return parsed; + } +} \ No newline at end of file diff --git a/src/CommandParser/ParserStates.cs b/src/CommandParser/ParserStates.cs new file mode 100644 index 0000000..c27fb94 --- /dev/null +++ b/src/CommandParser/ParserStates.cs @@ -0,0 +1,81 @@ +namespace CmlLib.Core.CommandParser; + +interface IParserStateMachine +{ + IParserStateMachine Put(KeyValueArgumentBuilder current, string next); + void End(KeyValueArgumentBuilder current); +} + +static class ParserStates +{ + public readonly static IParserStateMachine Init = new InitState(); + public readonly static IParserStateMachine Key = new KeyState(); + + public static IParserStateMachine PutKey(KeyValueArgumentBuilder current, string next) + { + var separatorIndex = next.IndexOf("="); + if (separatorIndex >= 0) // key=value + { + var key = next.Substring(0, separatorIndex); + var value = (separatorIndex < next.Length - 1) + ? next.Substring(separatorIndex + 1, next.Length - separatorIndex - 1) + : ""; + + current.Key = key; + current.Value = value; + current.Complete(); + return ParserStates.Init; + } + else // key + { + current.Key = next; + return ParserStates.Key; + } + } + + class InitState : IParserStateMachine + { + public void End(KeyValueArgumentBuilder current) + { + + } + + public IParserStateMachine Put(KeyValueArgumentBuilder current, string next) + { + if (next.StartsWith("-")) // key + { + return PutKey(current, next); + } + else // value + { + current.Key = ""; + current.Value = next; + current.Complete(); + return ParserStates.Init; + } + } + } + + class KeyState : IParserStateMachine + { + public void End(KeyValueArgumentBuilder current) + { + current.Complete(); + } + + public IParserStateMachine Put(KeyValueArgumentBuilder current, string next) + { + if (next.StartsWith("-")) // key + { + current.Complete(); + return PutKey(current, next); + } + else // value + { + current.Value = next; + current.Complete(); + return ParserStates.Init; + } + } + } +} \ No newline at end of file diff --git a/src/Internals/IOUtil.cs b/src/Internals/IOUtil.cs index 520fbf2..9945db6 100644 --- a/src/Internals/IOUtil.cs +++ b/src/Internals/IOUtil.cs @@ -18,15 +18,9 @@ public static string NormalizePath(string path) public static string CombinePath(IEnumerable paths) { - return string.Join(Path.PathSeparator.ToString(), - paths.Select(x => - { - string path = Path.GetFullPath(x); - if (path.Contains(' ')) - return "\"" + path + "\""; - else - return path; - })); + return string.Join( + Path.PathSeparator.ToString(), + paths.Select(Path.GetFullPath)); } public static bool CheckFileValidation(string path, string? compareHash) diff --git a/src/Mapper.cs b/src/Mapper.cs deleted file mode 100644 index 8783c1c..0000000 --- a/src/Mapper.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; - -namespace CmlLib.Core; - -public static class Mapper -{ - private static readonly Regex argBracket = new Regex(@"\$?\{(.*?)}"); - - public static string[] Map(string[] arg, Dictionary dicts, string prepath) - { - var checkPath = !string.IsNullOrEmpty(prepath); - - var args = new List(arg.Length); - foreach (string item in arg) - { - var a = Interpolation(item, dicts); - if (checkPath) - a = ToFullPath(a, prepath); - args.Add(HandleEmptyArg(a, out _)); - } - - return args.ToArray(); - } - - public static string[] MapInterpolation(string[] arg, Dictionary dicts) - { - var args = new List(arg.Length); - foreach (string item in arg) - { - var a = Interpolation(item, dicts); - if (!string.IsNullOrEmpty(a)) - args.Add(HandleEmptyArg(a, out _)); - } - - return args.ToArray(); - } - - public static string[] MapPathString(string[] arg, string prepath) - { - var args = new List(arg.Length); - foreach (string item in arg) - { - var a = ToFullPath(item, prepath); - args.Add(HandleEmptyArg(a, out _)); - } - - return args.ToArray(); - } - - public static string Interpolation(string str, Dictionary dicts) - { - return argBracket.Replace(str, match => - { - if (match.Groups.Count < 2) - return match.Value; - - var key = match.Groups[1].Value; - if (dicts.TryGetValue(key, out string? value)) - { - return value ?? ""; - } - - return match.Value; - }); - } - - public static string ToFullPath(string str, string prepath) - { - if (str.StartsWith("[") && str.EndsWith("]") && !string.IsNullOrEmpty(prepath)) - { - var innerStr = str.TrimStart('[').TrimEnd(']').Split('@'); - var pathName = innerStr[0]; - var extension = "jar"; - - if (innerStr.Length > 1) - extension = innerStr[1]; - - return Path.Combine(prepath, - PackageName.Parse(pathName).GetPath(null, extension)); - } - else if (str.StartsWith("\'") && str.EndsWith("\'")) - return str.Trim('\''); - else - return str; - } - - static string replaceByPos(string input, string replace, int startIndex, int length) - { - var sb = new StringBuilder(input); - return replaceByPos(sb, replace, startIndex, length); - } - - static string replaceByPos(StringBuilder sb, string replace, int startIndex, int length) - { - sb.Remove(startIndex, length); - sb.Insert(startIndex, replace); - return sb.ToString(); - } - - // key=value 1 => key="value 1" - // key="va l" => key="va l" - // va lue => "va lue" - // "va lue" => "va lue" - public static string HandleEmptyArg(string input, out string key) - { - var seperatorIndex = input.IndexOf('='); - if (seperatorIndex != -1) - { - key = input.Substring(0, seperatorIndex); - var value = input.Substring(seperatorIndex + 1); - - if ((key.Contains(" ") && !checkEmptyHandled(key)) || string.IsNullOrWhiteSpace(key)) - return key + "=\"" + value + "\""; - else - return input; - } - else if ((input.Contains(" ") && !checkEmptyHandled(input)) || string.IsNullOrWhiteSpace(input)) - { - key = ""; - return "\"" + input + "\""; - } - else - { - key = ""; - return input; - } - } - - static bool checkEmptyHandled(string str) - { - return str.StartsWith("\"") || str.EndsWith("\""); - } -} diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 6e362ed..a1cf0f9 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -57,7 +57,7 @@ public MinecraftLauncher(MinecraftLauncherParameters parameters) ?? throw new ArgumentException(nameof(parameters.JavaPathResolver) + " was null"); FileExtractors = parameters.FileExtractors ?? throw new ArgumentException(nameof(parameters.FileExtractors) + " was null"); - GameInstaller = parameters.GameInstaller + GameInstaller = parameters.GameInstaller ?? throw new ArgumentException(nameof(parameters.GameInstaller) + " was null"); NativeLibraryExtractor = parameters.NativeLibraryExtractor ?? throw new ArgumentException(nameof(parameters.NativeLibraryExtractor) + " was null"); @@ -113,12 +113,12 @@ public async ValueTask> ExtractFiles( } public ValueTask InstallAsync( - string versionName, + string versionName, CancellationToken cancellationToken = default) => InstallAsync(versionName, null, null, cancellationToken); public async ValueTask InstallAsync( - string versionName, + string versionName, IProgress? fileProgress, IProgress? byteProgress, CancellationToken cancellationToken = default) @@ -146,22 +146,8 @@ await GameInstaller.Install( cancellationToken); } - // Install and build game process - public async ValueTask CreateProcessAsync( - string versionName, - MLaunchOption launchOption, - bool checkAndDownload = true) - { - if (checkAndDownload) - { - await InstallAsync(versionName, default); - } - - return await BuildProcessAsync(versionName, launchOption); - } - public async ValueTask BuildProcessAsync( - string versionName, + string versionName, MLaunchOption launchOption) { var version = await GetVersionAsync(versionName); @@ -169,7 +155,7 @@ public async ValueTask BuildProcessAsync( } public Process BuildProcess( - IVersion version, + IVersion version, MLaunchOption launchOption) { launchOption.NativesDirectory ??= createNativePath(version); @@ -197,10 +183,32 @@ public Process BuildProcess( { return JavaPathResolver.GetDefaultJavaBinaryPath(RulesContext); } - + private string createNativePath(IVersion version) { NativeLibraryExtractor.Clean(MinecraftPath, version); return NativeLibraryExtractor.Extract(MinecraftPath, version, RulesContext); } + + public ValueTask InstallAndBuildProcessAsync( + string versionName, + MLaunchOption launchOption, + CancellationToken cancellationToken = default) => + InstallAndBuildProcessAsync(versionName, launchOption, null, null, cancellationToken); + + public async ValueTask InstallAndBuildProcessAsync( + string versionName, + MLaunchOption launchOption, + IProgress? fileProgress, + IProgress? byteProgress, + CancellationToken cancellationToken = default) + { + var version = await GetVersionAsync(versionName); + await InstallAsync(version, fileProgress, byteProgress, cancellationToken); + return BuildProcess(version, launchOption); + } + + // legacy api + public ValueTask CreateProcessAsync(string versionName, MLaunchOption launchOption) => + InstallAndBuildProcessAsync(versionName, launchOption, null, null, default); } diff --git a/src/ProcessBuilder/MArgument.cs b/src/ProcessBuilder/MArgument.cs index 9cef676..aeefa8b 100644 --- a/src/ProcessBuilder/MArgument.cs +++ b/src/ProcessBuilder/MArgument.cs @@ -1,12 +1,14 @@ +using CmlLib.Core.CommandParser; using CmlLib.Core.Rules; namespace CmlLib.Core.ProcessBuilder; public class MArgument { - public static IEnumerable FromStrings(IEnumerable args) + public static MArgument FromCommandLine(string cmd) { - return args.Select(arg => new MArgument(arg)); + var args = Parser.ParseCommandLineToArguments(cmd).ToList(); + return new MArgument(args); } public MArgument() @@ -16,9 +18,25 @@ public MArgument() public MArgument(string arg) { - Values = new string[] { arg }; + Values = [arg]; + } + + public MArgument(IReadOnlyCollection args) + { + Values = args; } public IReadOnlyCollection Values { get; set; } = Array.Empty(); public IReadOnlyCollection Rules { get; set; } = Array.Empty(); + + public IEnumerable InterpolateValues(IReadOnlyDictionary varDict) + { + if (Values == null) + yield break; + + foreach (var value in Values) + { + yield return Mapper.InterpolateVariables(value, varDict); + } + } } \ No newline at end of file diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index 782e009..dd15e5b 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -9,11 +9,29 @@ public class MLaunchOption private static readonly Lazy> EmptyDictionary = new Lazy>(() => new Dictionary()); + public readonly static MArgument[] DefaultJvmArguments = + [ + new MArgument + { + Values = + [ + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=16M", + "-Dlog4j2.formatMsgNoLookups=true" + ] + } + ]; + public MinecraftPath? Path { get; set; } public IVersion? StartVersion { get; set; } public MSession? Session { get; set; } public string? NativesDirectory { get; set; } public RulesEvaluatorContext? RulesContext { get; set; } + public string PathSeparator { get; set; } = System.IO.Path.PathSeparator.ToString(); public string? JavaVersion { get; set; } public string? JavaPath { get; set; } @@ -32,14 +50,13 @@ public class MLaunchOption public string? ClientId { get; set; } public string? VersionType { get; set; } - public string? GameLauncherName { get; set; } - public string? GameLauncherVersion { get; set; } - - public string? UserProperties { get; set; } + public string? GameLauncherName { get; set; } = "minecraft-launcher"; + public string? GameLauncherVersion { get; set; } = "2"; + public string? UserProperties { get; set; } = "{}"; public IReadOnlyDictionary ArgumentDictionary { get; set; } = EmptyDictionary.Value; public IEnumerable? JvmArgumentOverrides { get; set; } - public IEnumerable ExtraJvmArguments { get; set; } = Enumerable.Empty(); + public IEnumerable ExtraJvmArguments { get; set; } = DefaultJvmArguments; public IEnumerable ExtraGameArguments { get; set; } = Enumerable.Empty(); internal void CheckValid() diff --git a/src/ProcessBuilder/Mapper.cs b/src/ProcessBuilder/Mapper.cs new file mode 100644 index 0000000..4e7c4cf --- /dev/null +++ b/src/ProcessBuilder/Mapper.cs @@ -0,0 +1,27 @@ +using System.Text.RegularExpressions; + +namespace CmlLib.Core.ProcessBuilder; + +public static class Mapper +{ + private static readonly Regex argBracket = new Regex(@"\$?\{(.*?)}"); + + public static string InterpolateVariables(string str, IReadOnlyDictionary dicts) + { + return argBracket.Replace(str, match => + { + if (match.Groups.Count < 2) + return match.Value; + + var key = match.Groups[1].Value; + if (dicts.TryGetValue(key, out string? value)) + { + return value ?? ""; + } + else + { + return match.Value; + } + }); + } +} diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index beafb92..7ce3930 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -1,6 +1,7 @@ using CmlLib.Core.Rules; using CmlLib.Core.Version; using CmlLib.Core.Internals; +using CmlLib.Core.CommandParser; using System.Diagnostics; namespace CmlLib.Core.ProcessBuilder; @@ -9,19 +10,6 @@ public class MinecraftProcessBuilder { private const int DefaultServerPort = 25565; - public static readonly string SupportVersion = "1.20.1"; - public readonly static string[] DefaultJavaParameter = - { - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=16M", - "-Dlog4j2.formatMsgNoLookups=true" - // "-Xss1M" - }; - public MinecraftProcessBuilder( IRulesEvaluator evaluator, MLaunchOption option) @@ -47,26 +35,22 @@ public MinecraftProcessBuilder( public Process CreateProcess() { - var arg = string.Join(" ", BuildArguments()); + Debug.Assert(!string.IsNullOrEmpty(launchOption.JavaPath)); + var mc = new Process(); - mc.StartInfo.FileName = launchOption.JavaPath!; - mc.StartInfo.Arguments = arg; + mc.StartInfo.FileName = launchOption.JavaPath; + mc.StartInfo.Arguments = BuildArguments(); mc.StartInfo.WorkingDirectory = minecraftPath.BasePath; - return mc; } - public IEnumerable BuildArguments() + public string BuildArguments() { + var builder = new CommandLineBuilder(); var argDict = buildArgumentDictionary(); - - var jvmArgs = buildJvmArguments(argDict); - foreach (var item in jvmArgs) - yield return item; - - var gameArgs = buildGameArguments(argDict); - foreach (var item in gameArgs) - yield return item; + addJvmArguments(builder, argDict); + addGameArguments(builder, argDict); + return builder.Build(); } private Dictionary buildArgumentDictionary() @@ -81,9 +65,9 @@ public IEnumerable BuildArguments() { { "library_directory" , minecraftPath.Library }, { "natives_directory" , launchOption.NativesDirectory }, - { "launcher_name" , useNotNull(launchOption.GameLauncherName, "minecraft-launcher") }, - { "launcher_version" , useNotNull(launchOption.GameLauncherVersion, "2") }, - { "classpath_separator", Path.PathSeparator.ToString() }, + { "launcher_name" , launchOption.GameLauncherName }, + { "launcher_version" , launchOption.GameLauncherVersion }, + { "classpath_separator", launchOption.PathSeparator }, { "classpath" , classpath }, { "auth_player_name" , launchOption.Session.Username }, @@ -93,13 +77,13 @@ public IEnumerable BuildArguments() { "assets_index_name", assetId }, { "auth_uuid" , launchOption.Session.UUID }, { "auth_access_token", launchOption.Session.AccessToken }, - { "user_properties" , "{}" }, + { "user_properties" , launchOption.UserProperties }, { "auth_xuid" , launchOption.Session.Xuid ?? "xuid" }, { "clientid" , launchOption.ClientId ?? "clientId" }, { "user_type" , launchOption.Session.UserType ?? "Mojang" }, { "game_assets" , minecraftPath.GetAssetLegacyPath(assetId) }, { "auth_session" , launchOption.Session.AccessToken }, - { "version_type" , useNotNull(launchOption.VersionType, version.Type) }, + { "version_type" , launchOption.VersionType ?? version.Type }, }; if (launchOption.ArgumentDictionary != null) @@ -113,55 +97,72 @@ public IEnumerable BuildArguments() return argDict; } - private IEnumerable buildJvmArguments(Dictionary argDict) + // make library files into jvm classpath string + private IEnumerable getClasspaths() { - var builder = new ProcessArgumentBuilder(); + // libraries + var libPaths = version + .ConcatInheritedCollection(v => v.Libraries) + .Where(lib => lib.CheckIsRequired(JsonVersionParserOptions.ClientSide)) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) + .Where(lib => lib.Artifact != null) + .Select(lib => Path.Combine(minecraftPath.Library, lib.GetLibraryPath())); + + foreach (var item in libPaths) + yield return item; + + // .jar file + // TODO: decide what Jar file should be used. current jar or parent jar + var jar = version.GetInheritedProperty(v => v.Jar); + if (string.IsNullOrEmpty(jar)) + jar = version.Id; + yield return minecraftPath.GetVersionJarPath(jar); + } + private void addJvmArguments(CommandLineBuilder builder, Dictionary argDict) + { if (launchOption.JvmArgumentOverrides != null) { // override all jvm arguments // even if necessary arguments are missing (-cp, -Djava.library.path), // the builder will still add the necessary arguments - builder.AddRange(mapArguments(launchOption.JvmArgumentOverrides, argDict)); + builder.AddArguments(getArguments(launchOption.JvmArgumentOverrides, argDict)); } else { // version-specific jvm arguments var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); - builder.AddRange(mapArguments(jvmArgs, argDict)); - - // default jvm arguments - builder.AddRange(DefaultJavaParameter); + builder.AddArguments(getArguments(jvmArgs, argDict)); } // add extra jvm arguments - builder.AddRange(mapArguments(launchOption.ExtraJvmArguments, argDict)); + builder.AddArguments(getArguments(launchOption.ExtraJvmArguments, argDict)); - // libraries - builder.TryAddKeyValue("-Djava.library.path", argDict["natives_directory"]); - if (!builder.CheckKeyAdded("-cp")) - { - builder.Add("-cp"); - builder.AddRaw(argDict["classpath"]); - } + // native library + if (!builder.ContainsKey("-Djava.library.path")) + builder.AddArguments(["-Djava.library.path", argDict["natives_directory"] ?? ""]); + + // classpath + if (!builder.ContainsKey("-cp")) + builder.AddArguments(["-cp", argDict["classpath"] ?? ""]); // -Xmx, -Xms - if (!checkXmxAdded(builder) && launchOption.MaximumRamMb > 0) - builder.Add("-Xmx" + launchOption.MaximumRamMb + "m"); - if (!checkXmsAdded(builder) && launchOption.MinimumRamMb > 0) - builder.Add("-Xms" + launchOption.MinimumRamMb + "m"); + if (!builder.ContainsXmx() && launchOption.MaximumRamMb > 0) + builder.AddArguments(["-Xmx" + launchOption.MaximumRamMb + "m"]); + if (!builder.ContainsXms() && launchOption.MinimumRamMb > 0) + builder.AddArguments(["-Xms" + launchOption.MinimumRamMb + "m"]); // for macOS - if (!string.IsNullOrEmpty(launchOption.DockName)) - builder.TryAddKeyValue("-Xdock:name", launchOption.DockName); - if (!string.IsNullOrEmpty(launchOption.DockIcon)) - builder.TryAddKeyValue("-Xdock:icon", launchOption.DockIcon); + if (!string.IsNullOrEmpty(launchOption.DockName) && !builder.ContainsKey("-Xdock:name")) + builder.AddArguments(["-Xdock:name", launchOption.DockName]); + if (!string.IsNullOrEmpty(launchOption.DockIcon) && !builder.ContainsKey("-Xdock:icon")) + builder.AddArguments(["-Xdock:icon", launchOption.DockIcon]); // logging var logging = version.GetInheritedProperty(v => v.Logging); if (!string.IsNullOrEmpty(logging?.Argument)) { - builder.AddRange(mapArgument(new MArgument(logging.Argument), new Dictionary() + builder.AddArguments(getArguments([new MArgument(logging.Argument)], new Dictionary() { { "path", minecraftPath.GetLogConfigFilePath(logging.LogFile?.Id ?? version.Id) } })); @@ -170,117 +171,46 @@ private IEnumerable buildJvmArguments(Dictionary argDic // main class var mainClass = version.GetInheritedProperty(v => v.MainClass); if (!string.IsNullOrEmpty(mainClass)) - builder.Add(mainClass); - - return builder.Build(); - } - - private bool checkXmxAdded(ProcessArgumentBuilder builder) - { - return builder.Keys.Any(k => k.StartsWith("-Xmx")); - } - - private bool checkXmsAdded(ProcessArgumentBuilder builder) - { - return builder.Keys.Any(k => k.StartsWith("-Xms")); + builder.AddArguments([mainClass]); } - private IEnumerable buildGameArguments(Dictionary argDict) + private void addGameArguments(CommandLineBuilder builder, Dictionary argDict) { - var builder = new ProcessArgumentBuilder(); - // game arguments var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); - builder.AddRange(mapArguments(gameArgs, argDict)); + builder.AddArguments(getArguments(gameArgs, argDict)); // add extra game arguments - builder.AddRange(mapArguments(launchOption.ExtraGameArguments, argDict)); + builder.AddArguments(getArguments(launchOption.ExtraGameArguments, argDict)); // server if (!string.IsNullOrEmpty(launchOption.ServerIp)) { - if (!builder.CheckKeyAdded("--server")) - builder.AddRange("--server", launchOption.ServerIp); + if (!builder.ContainsKey("--server")) + builder.AddArguments(["--server", launchOption.ServerIp]); - if (launchOption.ServerPort != DefaultServerPort && !builder.CheckKeyAdded("--port")) - builder.AddRange("--port", launchOption.ServerPort.ToString()); + if (launchOption.ServerPort != DefaultServerPort && !builder.ContainsKey("--port")) + builder.AddArguments(["--port", launchOption.ServerPort.ToString()]); } // screen size if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) { - if (!builder.CheckKeyAdded("--width")) - builder.AddRange("--width", launchOption.ScreenWidth.ToString()); - if (!builder.CheckKeyAdded("--height")) - builder.AddRange("--height", launchOption.ScreenHeight.ToString()); + if (!builder.ContainsKey("--width")) + builder.AddArguments(["--width", launchOption.ScreenWidth.ToString()]); + if (!builder.ContainsKey("--height")) + builder.AddArguments(["--height", launchOption.ScreenHeight.ToString()]); } // fullscreen - if (!builder.CheckKeyAdded("--fullscreen") && launchOption.FullScreen) - builder.Add("--fullscreen"); - - return builder.Build(); - } - - // make library files into jvm classpath string - private IEnumerable getClasspaths() - { - // libraries - var libPaths = version - .ConcatInheritedCollection(v => v.Libraries) - .Where(lib => lib.CheckIsRequired(JsonVersionParserOptions.ClientSide)) - .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) - .Where(lib => lib.Artifact != null) - .Select(lib => Path.Combine(minecraftPath.Library, lib.GetLibraryPath())); - - foreach (var item in libPaths) - yield return item; - - // .jar file - // TODO: decide what Jar file should be used. current jar or parent jar - var jar = version.GetInheritedProperty(v => v.Jar); - if (string.IsNullOrEmpty(jar)) - jar = version.Id; - yield return (minecraftPath.GetVersionJarPath(jar)); + if (!builder.ContainsKey("--fullscreen") && launchOption.FullScreen) + builder.AddArguments(["--fullscreen"]); } - // if input1 is null, return input2 - private string? useNotNull(string? input1, string? input2) + private IEnumerable getArguments(IEnumerable args, IReadOnlyDictionary varDict) { - if (string.IsNullOrEmpty(input1)) - return input2; - else - return input1; - } - - private IEnumerable mapArguments(IEnumerable arguments, Dictionary mapper) - { - foreach (var arg in arguments) - { - foreach (var mappedArg in mapArgument(arg, mapper)) - { - yield return mappedArg; - } - } - } - - private IEnumerable mapArgument(MArgument arg, Dictionary mapper) - { - if (arg.Values == null) - yield break; - - if (arg.Rules != null) - { - var isMatch = rulesEvaluator.Match(arg.Rules, rulesContext); - if (!isMatch) - yield break; - } - - foreach (var value in arg.Values) - { - var mappedValue = Mapper.Interpolation(value, mapper); - if (!string.IsNullOrEmpty(mappedValue)) - yield return mappedValue; - } + return args + .Where(arg => rulesEvaluator.Match(arg.Rules, rulesContext)) + .SelectMany(arg => arg.InterpolateValues(varDict)); } } diff --git a/src/ProcessBuilder/ProcessArgumentBuilder.cs b/src/ProcessBuilder/ProcessArgumentBuilder.cs deleted file mode 100644 index 520d4de..0000000 --- a/src/ProcessBuilder/ProcessArgumentBuilder.cs +++ /dev/null @@ -1,140 +0,0 @@ -namespace CmlLib.Core.ProcessBuilder; - -public class ProcessArgumentBuilder -{ - private readonly HashSet _keys = new(); - private readonly List _args = new(); - - public IEnumerable Keys => _keys; - public bool CheckKeyAdded(string key) => _keys.Contains(key); - - public void AddRaw(string? value) - { - if (string.IsNullOrEmpty(value)) - return; - _args.Add(value); - } - - public void AddRange(IEnumerable values) - { - foreach (var item in values) - Add(item); - } - - public void AddRange(params string?[] values) - { - foreach (var item in values) - Add(item); - } - - public void Add(string? value) - { - if (string.IsNullOrEmpty(value)) - return; - - string result; - var trimmed = value.Trim(); - if (trimmed.StartsWith("-")) - { - if (trimmed == "-") - { - result = "-"; - } - else - { - var seperator = trimmed.IndexOf('='); - if (seperator == -1) // does not contain '=' - { - if (trimmed.Contains(" ")) - throw new FormatException(); - result = trimmed; - } - else - { - var k = trimmed.Substring(0, seperator); - if (seperator == trimmed.Length - 1) // last character was '=' - { - if (trimmed.Contains(" ")) - throw new FormatException(); - result = trimmed; - _keys.Add(k); - } - else // contains '=' - { - var v = trimmed.Substring(seperator + 1); - if (!isEscaped(v) && v.Contains(" ")) - throw new FormatException(); - AddKeyValue(k, v); - return; - } - } - } - } - else if (isEscaped(value)) - { - result = value; - } - else - { - result = escapeValue(value); - } - - AddRaw(result); - } - - public bool TryAddKeyValue(string key, string? value) - { - if (CheckKeyAdded(key)) - return false; - else - { - AddKeyValue(key, value); - return true; - } - } - - public void AddKeyValue(string key, string? value) - { - if (string.IsNullOrEmpty(key) || - isEscaped(key) || - key.Contains(" ")) - throw new FormatException(); - - string result; - if (value == null) - result = key; - else if (value == "") - result = $"{key}=\"\""; - else if (value == "\"\"") - result = $"{key}=\"\""; - else if (isEscaped(value)) - result = $"{key}={value}"; - else - result = $"{key}={escapeValue(value)}"; - - _keys.Add(key); - AddRaw(result); - } - - public IReadOnlyCollection Build() - { - return _args; - } - - private string escapeValue(string value) - { - if (value.Contains(' ') && !isEscaped(value)) - { - return $"\"{value}\""; - } - else - { - return value; - } - } - - private bool isEscaped(string value) - { - return value.StartsWith("\"") && value.EndsWith("\""); - } -} \ No newline at end of file diff --git a/test/CommandParser/CommandLineBuilderTests.cs b/test/CommandParser/CommandLineBuilderTests.cs new file mode 100644 index 0000000..f56ffa0 --- /dev/null +++ b/test/CommandParser/CommandLineBuilderTests.cs @@ -0,0 +1,78 @@ +using CmlLib.Core.CommandParser; + +namespace CmlLib.Core.Test.CommandParser; + +public class CommandLineBuilderTests +{ + [Fact] + public void add_command_line() + { + var builder = new CommandLineBuilder(); + builder.AddCommandLine("--key"); + builder.AddCommandLine("value"); + builder.AddCommandLine("-a=b"); + builder.AddCommandLine("-c d"); + builder.AddCommandLine("--key value1 value2 -x=y value3"); + Assert.Equal("--key value -a=b -c d --key value1 value2 -x=y value3", builder.Build()); + } + + [Fact] + public void add_arguments() + { + var builder = new CommandLineBuilder(); + builder.AddArguments(["-key1", "value 1", "-key2=value 2", "default value"]); + builder.AddArguments(["-key3"]); + builder.AddArguments(["value 3"]); + builder.AddArguments(["-key4", "\"value 4\""]); + Assert.Equal("-key1 \"value 1\" \"-key2=value 2\" \"default value\" -key3 \"value 3\" -key4 \"\\\"value 4\\\"\"", builder.Build()); + } + + [Fact] + public void check_key_is_contained() + { + var builder = new CommandLineBuilder(); + builder.AddArguments(["--key", "value"]); + builder.AddKeyValueArgument(KeyValueArgument.Create("-a", "b")); + builder.AddCommandLine("-c d"); + builder.AddCommandLine("--key \"value1 -key2\" value2 -x=y value3"); + + Assert.True(builder.ContainsKey("--key")); + Assert.True(builder.ContainsKey("-a")); + Assert.True(builder.ContainsKey("-c")); + Assert.True(builder.ContainsKey("-x")); + Assert.False(builder.ContainsKey("value")); + Assert.False(builder.ContainsKey("value1")); + Assert.False(builder.ContainsKey("-key2")); + Assert.False(builder.ContainsKey("value2")); + Assert.False(builder.ContainsKey("value3")); + Assert.False(builder.ContainsKey("b")); + Assert.False(builder.ContainsKey("d")); + Assert.False(builder.ContainsKey("y")); + } + + [Fact] + public void find_arguments_with_key() + { + var builder = new CommandLineBuilder(); + builder.AddCommandLine("--key1"); + builder.AddCommandLine("--key1=b"); + builder.AddCommandLine("--key1 c d"); + + KeyValueArgument[] expected = + [ + KeyValueArgument.CreateWithoutValidation("--key1", null), + KeyValueArgument.CreateWithoutValidation("--key1", "b"), + KeyValueArgument.CreateWithoutValidation("--key1", "c"), + ]; + Assert.Equal(expected, builder.Find("--key1")); + } + + [Fact] + public void find_arguments_with_no_matched_key() + { + var builder = new CommandLineBuilder(); + builder.AddCommandLine("--key1=value"); + + Assert.Empty(builder.Find("--no-key")); + } +} \ No newline at end of file diff --git a/test/CommandParser/CommandLineParserTests.cs b/test/CommandParser/CommandLineParserTests.cs new file mode 100644 index 0000000..5568f01 --- /dev/null +++ b/test/CommandParser/CommandLineParserTests.cs @@ -0,0 +1,59 @@ +using CmlLib.Core.CommandParser; + +namespace CmlLib.Core.Test.CommandParser; + +public class ArgumentParserTests +{ + [Fact] + public void parse_empty() + { + Assert.Empty(Parser.ParseCommandLineToArguments("")); + Assert.Empty(Parser.ParseCommandLineToArguments(" \n ")); + } + + [Fact] + public void parse_single_word() + { + Assert.Equal(["word"], Parser.ParseCommandLineToArguments("word")); + } + + [Fact] + public void parse_multiple_word() + { + Assert.Equal(["aa", "bb", "cc"], Parser.ParseCommandLineToArguments("aa bb cc")); + } + + [Fact] + public void ignore_spaces() + { + Assert.Equal(["a", "b", "c"], Parser.ParseCommandLineToArguments(" a \t\r b\n c ")); + } + + [Fact] + public void escape_with_quotes() + { + Assert.Equal( + ["-key=1 2 3", "word", "-1 + 3 = 2"], + Parser.ParseCommandLineToArguments("\"-key=1 2 3\" word \"-1 + 3 = 2\"")); + } + + [Fact] + public void quotes_in_word() + { + Assert.Equal( + ["-key=1 2 3"], + Parser.ParseCommandLineToArguments("-key=\"1 2 3\"")); + + Assert.Equal( + ["123456", "ab cd"], + Parser.ParseCommandLineToArguments("12\"34\"56 a\"b c\"d")); + } + + [Fact] + public void escape() + { + Assert.Equal(["\""], Parser.ParseCommandLineToArguments("\\\"")); + Assert.Equal(["hi\""], Parser.ParseCommandLineToArguments("hi\\\"")); + Assert.Equal(["-key=0\"0"], Parser.ParseCommandLineToArguments("-key=\"0\\\"0\"")); + } +} \ No newline at end of file diff --git a/test/CommandParser/KeyValueArgumentParserTests.cs b/test/CommandParser/KeyValueArgumentParserTests.cs new file mode 100644 index 0000000..1dffed6 --- /dev/null +++ b/test/CommandParser/KeyValueArgumentParserTests.cs @@ -0,0 +1,60 @@ +using CmlLib.Core.CommandParser; + +namespace CmlLib.Core.Test.CommandParser; + +public class KeyValueArgumentParserTests +{ + private static void assert((string, string?)[] expected, string[] input) + { + var result = Parser.ParseArgumentsToKeyValue(input); + var expectedArgs = expected.Select(tuple => KeyValueArgument.CreateWithoutValidation(tuple.Item1, tuple.Item2)); + Assert.Equal(expectedArgs, result); + } + + [Fact] + public void parse_empty_args() + { + assert([], []); + } + + [Fact] + public void parse_empty_string() + { + assert([("", "")], [""]); + assert([("", " ")], [" "]); + } + + [Fact] + public void parse_empty_value() + { + assert([("-key", "")], ["-key="]); + assert([("-key", null)], ["-key"]); + } + + [Fact] + public void parse_single_key_value() + { + assert([("-key", "value")], ["-key=value"]); + assert([("-key", "value and value")], ["-key=value and value"]); + assert([("-key", "value -key")], ["-key=value -key"]); + assert([("-key", "\\value \"\"")], ["-key=\\value \"\""]); + assert([("-key", "value -key=value")], ["-key=value -key=value"]); + assert([("-key", "value -key value")], ["-key=value -key value"]); + } + + [Fact] + public void parse_value_without_key() + { + assert([("", "value2")], ["value2"]); + assert([("", "value1"), ("", "value 2")], ["value1", "value 2"]); + + } + + [Fact] + public void parse_continous_key_values() + { + assert( + [("-key", "value"), ("-key", "value"), ("", "value"), ("-key", null)], + ["-key", "value", "-key=value", "value", "-key"]); + } +} \ No newline at end of file diff --git a/test/CommandParser/KeyValueArgumentTests.cs b/test/CommandParser/KeyValueArgumentTests.cs new file mode 100644 index 0000000..9bf34e8 --- /dev/null +++ b/test/CommandParser/KeyValueArgumentTests.cs @@ -0,0 +1,61 @@ +using CmlLib.Core.CommandParser; + +namespace CmlLib.Core.Test.CommandParser; + +public class KeyValueArgumentTests +{ + [Fact] + public void disallow_key_without_prefix() + { + Assert.Throws(() => + KeyValueArgument.Create("abc", null)); + } + + [Fact] + public void key_and_value_to_string() + { + Assert.Equal("-key=value", KeyValueArgument.Create("-key", "value").ToString()); + } + + [Fact] + public void key_and_null_value_to_string() + { + Assert.Equal("-key", KeyValueArgument.Create("-key", null).ToString()); + } + + [Fact] + public void key_and_empty_value_to_string() + { + Assert.Equal("-key=\"\"", KeyValueArgument.Create("-key", "").ToString(true)); + Assert.Equal("-key \"\"", KeyValueArgument.Create("-key", "").ToString(false)); + } + + [Fact] + public void empty_key_and_value_to_string() + { + Assert.Equal("value", KeyValueArgument.Create("", "value").ToString()); + } + + [Fact] + public void empty_key_and_empty_value_to_string() + { + Assert.Equal("", KeyValueArgument.Create("", null).ToString()); + Assert.Equal("", KeyValueArgument.Create("", "").ToString()); + } + + [Fact] + public void escape_spaces() + { + Assert.Equal("-key=\"a b\"", KeyValueArgument.Create("-key", "a b").ToString(true)); + Assert.Equal("-key \"a b\"", KeyValueArgument.Create("-key", "a b").ToString(false)); + } + + [Fact] + public void escape_quotes() + { + // (-key, " ") -> -key="\"\"" + Assert.Equal("-key=\"\\\" \\\"\"", KeyValueArgument.Create("-key", "\" \"").ToString(true)); + // (-key, " ") -> -key "\" \"" + Assert.Equal("-key \"\\\" \\\"\"", KeyValueArgument.Create("-key", "\" \"").ToString(false)); + } +} \ No newline at end of file diff --git a/test/MapperTests.cs b/test/MapperTests.cs deleted file mode 100644 index d1288f2..0000000 --- a/test/MapperTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CmlLib.Core.Test; - -public class MapperTest -{ - [Theory(Skip ="Win")] - [InlineData( - @"[de.oceanlabs.mcp:mcp_config:1.16.2-20200812.004259@zip]", - @"C:\libraries\de\oceanlabs\mcp\mcp_config\1.16.2-20200812.004259\mcp_config-1.16.2-20200812.004259.zip")] - [InlineData( - @"[net.minecraft:client:1.16.2-20200812.004259:slim]", - @"C:\libraries\net\minecraft\client\1.16.2-20200812.004259\client-1.16.2-20200812.004259-slim.jar")] - public void TestFullPath(string input, string exp) - { - Assert.Equal(exp, Mapper.ToFullPath(input, @"C:\libraries\")); - } - - [Theory] - [InlineData("", "")] - [InlineData("key=value", "key=value")] - [InlineData("key=value 1", "key=\"value 1\"")] - [InlineData("key=\"value 2\"", "key=\"value 2\"")] - [InlineData("value 3", "\"value 3\"")] - [InlineData("\"value 4\"", "\"value 4\"")] - public void TestHandleEmptyArg(string input, string exp) - { - //Assert.AreEqual(exp, Mapper.HandleEmptyArg(input)); - } -} \ No newline at end of file diff --git a/test/ProcessBuilder/MapperTests.cs b/test/ProcessBuilder/MapperTests.cs new file mode 100644 index 0000000..cb04327 --- /dev/null +++ b/test/ProcessBuilder/MapperTests.cs @@ -0,0 +1,64 @@ +using CmlLib.Core.ProcessBuilder; + +namespace CmlLib.Core.Test; + +public class MapperTest +{ + private readonly static Dictionary TestVarDict = new() + { + { "word1", "value 1" }, + { "word 2", "value 2" }, + { "empty", "" }, + { "null", null } + }; + + [Theory] + [InlineData("${word1}", "value 1")] + [InlineData("insert${word1}here", "insertvalue 1here")] + [InlineData("insert here ${word1} and ${word 2}...", "insert here value 1 and value 2...")] + [InlineData("...${word1}${word 2}...", "...value 1value 2...")] + public void interpolate_variables(string input, string expected) + { + var result = Mapper.InterpolateVariables(input, TestVarDict); + Assert.Equal(expected, result); + } + + [Fact] + public void interpolate_empty_value() + { + var result = Mapper.InterpolateVariables("insert${empty}here", TestVarDict); + Assert.Equal("inserthere", result); + } + + [Fact] + public void interpolate_null_value() + { + var result = Mapper.InterpolateVariables("insert${null}here", TestVarDict); + Assert.Equal("inserthere", result); + } + + [Theory] + [InlineData("${}", "${}")] + [InlineData("insert${}here", "insert${}here")] + public void interpolate_empty_variable(string input, string expected) + { + var result = Mapper.InterpolateVariables(input, TestVarDict); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("${not_found}", "${not_found}")] + [InlineData("insert${not_found}here", "insert${not_found}here")] + public void variable_not_found(string input, string expected) + { + var result = Mapper.InterpolateVariables(input, TestVarDict); + Assert.Equal(expected, result); + } + + [Fact] + public void bracket_inside_bracket() + { + var result = Mapper.InterpolateVariables("insert${insert_${word1}_here}here", TestVarDict); + Assert.Equal("insert${insert_${word1}_here}here", result); + } +} \ No newline at end of file diff --git a/test/ProcessBuilder/MinecraftProcessBuilderTests.cs b/test/ProcessBuilder/MinecraftProcessBuilderTests.cs deleted file mode 100644 index bd3ac6d..0000000 --- a/test/ProcessBuilder/MinecraftProcessBuilderTests.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CmlLib.Core.Test.ProcessBuilder; - -public class MinecraftProcessBuilderTests -{ - [Fact] - public void build_arguments() - { - - } -} \ No newline at end of file diff --git a/test/ProcessBuilder/ProcessArgumentBuilderTests.cs b/test/ProcessBuilder/ProcessArgumentBuilderTests.cs deleted file mode 100644 index 9f80a6a..0000000 --- a/test/ProcessBuilder/ProcessArgumentBuilderTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using CmlLib.Core.ProcessBuilder; - -namespace CmlLib.Core.Test.ProcessBuilder; - -public class ProcessArgumentBuilderTests -{ - [Fact] - public void ignore_null_or_empty() - { - // Given - var sut = createBuilder(); - - // When - sut.Add(null); - sut.Add(""); - - // Then - var actual = sut.Build(); - Assert.Empty(actual); - } - - [Theory] - // value without space - [InlineData("word", "word")] - // escape spaced value - [InlineData("double word", "\"double word\"")] - // do not escape already escaped value - [InlineData("\"word\"", "\"word\"")] - [InlineData("\"\"", "\"\"")] - [InlineData("\"double word\"", "\"double word\"")] - // thread as value (not key-value) - [InlineData("\"-k=v\"", "\"-k=v\"")] - [InlineData("\"-k=double word\"", "\"-k=double word\"")] - [InlineData("-k", "-k")] - // special - [InlineData(" - ", "-")] - public void add_value(string input, string expected) - { - // Given - var sut = createBuilder(); - - // When - sut.Add(input); - - // Then - Assert.Empty(sut.Keys); - Assert.Equal(new []{expected}, sut.Build()); - } - - [Theory] - // key-value - [InlineData("-k=v", "-k=v", "-k")] - [InlineData("-k=", "-k=", "-k")] - // key-value, with space in value - [InlineData("-k=\"\"", "-k=\"\"", "-k")] - [InlineData("-k=\"v\"", "-k=\"v\"", "-k")] - [InlineData("-k=\"double word\"", "-k=\"double word\"", "-k")] - public void add_key_value_by_Add(string input, string expectedArg, string expectedKey) - { - // Given - var sut = createBuilder(); - - // When - sut.Add(input); - - // Then - Assert.Equal(new []{expectedKey}, sut.Keys); - Assert.Equal(new []{expectedArg}, sut.Build()); - } - - [Theory] - // null value - [InlineData("-k", null, "-k")] - // empty value - [InlineData("-k", "", "-k=\"\"")] - // value - [InlineData("-k", "value", "-k=value")] - // value with space - [InlineData("-k", "double word", "-k=\"double word\"")] - // do not escape already escaped value - [InlineData("-k", "\"value\"", "-k=\"value\"")] - [InlineData("-k", "\"double word\"", "-k=\"double word\"")] - // string that don't start with '-' also thread as key - [InlineData("a", "b", "a=b")] - public void add_key_value_by_AddKeyValue(string key, string value, string expectedArg) - { - // Given - var sut = createBuilder(); - - // When - sut.AddKeyValue(key, value); - - // Then - Assert.Equal(new []{key}, sut.Keys); - Assert.Equal(new []{expectedArg}, sut.Build()); - } - - [Theory] - // cannot add key-value with a space in the value - [InlineData("-key value")] - [InlineData("-key=value and")] - public void add_invalid_value(string test) - { - var sut = createBuilder(); - Assert.Throws(() => - { - sut.Add(test); - }); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - [InlineData("a b")] - [InlineData("\"\"")] - [InlineData("\"a b\"")] - public void add_invalid_key(string key) - { - var builder = createBuilder(); - Assert.Throws(() => - { - builder.AddKeyValue(key, "value"); - }); - } - - private ProcessArgumentBuilder createBuilder() - { - var builder = new ProcessArgumentBuilder(); - return builder; - } -} \ No newline at end of file diff --git a/test/Version/JsonArgumentParserTests.cs b/test/Version/JsonArgumentParserTests.cs index 755f9a1..9847ed7 100644 --- a/test/Version/JsonArgumentParserTests.cs +++ b/test/Version/JsonArgumentParserTests.cs @@ -1,3 +1,5 @@ +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Rules; using CmlLib.Core.Version; namespace CmlLib.Core.Test.Version; @@ -10,26 +12,31 @@ public class JsonArgumentParserTests SkipError = false }; - [Fact] - public void parse_from_vanilla_arg_string() + private static string[] getArguments(IEnumerable args) { - // release 1.0 ~ : ${auth_player_name} ${auth_session} --gameDir ${game_directory} --assetsDir ${game_assets} - // release 1.6.1 ~ : from --username to --assetDir - // release 1.7.2 ~ : add --uuid, --accessToken - // release 1.7.10 ~ : add --assetIndex, --userProperties, --userType + var dict = new Dictionary(); + return args + .SelectMany(arg => arg.InterpolateValues(dict)) + .ToArray(); + } - var json = """ + public static readonly string vanilla_arg_string = """ { "id": "1.7.10", "minecraftArguments": "--username ${auth_player_name} --version ${version_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userProperties ${user_properties} --userType ${user_type}" } """; - var version = JsonVersionParser.ParseFromJsonString(json, options); + [Fact] + public void parse_from_vanilla_arg_string() + { + // release 1.0 ~ : ${auth_player_name} ${auth_session} --gameDir ${game_directory} --assetsDir ${game_assets} + // release 1.6.1 ~ : from --username to --assetDir + // release 1.7.2 ~ : add --uuid, --accessToken + // release 1.7.10 ~ : add --assetIndex, --userProperties, --userType - var parsedArgs = version.GameArguments - .SelectMany(arg => arg.Values ?? Enumerable.Empty()) - .ToArray(); + var version = JsonVersionParser.ParseFromJsonString(vanilla_arg_string, options); + var parsedArgs = getArguments(version.GameArguments); Assert.Equal( [ "--username", @@ -53,14 +60,7 @@ public void parse_from_vanilla_arg_string() ], parsedArgs); } - [Fact] - public void parse_from_vanilla_game_arg_array() // 1.13 ~ - { - // release 1.13 ~ - // release 1.19 ~ : add --clientId ${clientid} --xuid ${auth_xuid} - // release 1.20 ~ : add --quickPlayPath, --quickPlaySingleplayer, --quickPlayMultiplayer, --quickPlayRealms - - var json = """ + public static readonly string vanilla_game_arg_array = """ { "id": "1.13", "arguments": { @@ -175,11 +175,15 @@ public void parse_from_vanilla_game_arg_array() // 1.13 ~ } """; - var version = JsonVersionParser.ParseFromJsonString(json, options); - - var parsedArgs = version.GameArguments - .SelectMany(arg => arg.Values ?? Enumerable.Empty()) - .ToArray(); + [Fact] + public void parse_from_vanilla_game_arg_array() // 1.13 ~ + { + // release 1.13 ~ + // release 1.19 ~ : add --clientId ${clientid} --xuid ${auth_xuid} + // release 1.20 ~ : add --quickPlayPath, --quickPlaySingleplayer, --quickPlayMultiplayer, --quickPlayRealms + + var version = JsonVersionParser.ParseFromJsonString(vanilla_game_arg_array, options); + var parsedArgs = getArguments(version.GameArguments); Assert.Equal( [ "--username", @@ -220,13 +224,7 @@ public void parse_from_vanilla_game_arg_array() // 1.13 ~ ], parsedArgs); } - [Fact] - public void parse_from_vanilla_jvm_arg_array() - { - // release 1.13 ~ - // release 1.20 ~ : add -Djna.tmpdir=, -Dorg.lwjgl.system.SharedLibraryExtractPath, -Dio.netty.native.workdir - - var json = """ + public static readonly string vanilla_jvm_arg_array = """ { "id": "1.13", "arguments": { @@ -294,11 +292,15 @@ public void parse_from_vanilla_jvm_arg_array() } """; - var version = JsonVersionParser.ParseFromJsonString(json, options); + [Fact] + public void parse_from_vanilla_jvm_arg_array() + { + // release 1.13 ~ + // release 1.20 ~ : add -Djna.tmpdir=, -Dorg.lwjgl.system.SharedLibraryExtractPath, -Dio.netty.native.workdir + + var version = JsonVersionParser.ParseFromJsonString(vanilla_jvm_arg_array, options); - var parsedArgs = version.JvmArguments - .SelectMany(args => args.Values ?? Enumerable.Empty()) - .ToArray(); + var parsedArgs = getArguments(version.JvmArguments); Assert.Equal( [ "-XstartOnFirstThread", @@ -313,7 +315,7 @@ public void parse_from_vanilla_jvm_arg_array() "-Dminecraft.launcher.brand=${launcher_name}", "-Dminecraft.launcher.version=${launcher_version}", "-cp", - "${classpath}" + "${classpath}", ], parsedArgs); } @@ -343,6 +345,32 @@ public void parse_from_vanilla_jvm_arg_array() } """; + [Fact] + public void parse_forge_arguments() + { + var version = JsonVersionParser.ParseFromJsonString(forge_arguments, options); + Assert.Equal( + [ + "--launchTarget", + "fmlclient", + "--fml.forgeVersion", + "36.2.0", + "--fml.mcVersion", + "1.16.5", + "--fml.forgeGroup", + "net.minecraftforge", + "--fml.mcpVersion", + "20210115.111550" + ], getArguments(version.GameArguments)); + Assert.Equal( + [ + "-XX:+IgnoreUnrecognizedVMOptions", + "--add-exports=java.base/sun.security.util=ALL-UNNAMED", + "--add-exports=jdk.naming.dns/com.sun.jndi.dns=java.naming", + "--add-opens=java.base/java.util.jar=ALL-UNNAMED" + ], getArguments(version.JvmArguments)); + } + public readonly static string forge_arguments_2 = """ { "id": "1.17.1-forge-37.0.48", @@ -374,10 +402,45 @@ public void parse_from_vanilla_jvm_arg_array() "--add-exports", "jdk.naming.dns/com.sun.jndi.dns=java.naming" ] - }, + } } """; + [Fact] + public void parse_forge_arguments_2() + { + var version = JsonVersionParser.ParseFromJsonString(forge_arguments_2, options); + Assert.Equal( + [ + "--launchTarget", + "forgeclient", + "--fml.forgeVersion", + "37.0.48", + "--fml.mcVersion", + "1.17.1", + "--fml.forgeGroup", + "net.minecraftforge", + "--fml.mcpVersion", + "20210706.113038" + ], getArguments(version.GameArguments)); + Assert.Equal( + [ + "-DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,client-extra,fmlcore,javafmllanguage,mclanguage,forge-,${version_name}.jar", + "-DmergeModules=jna-5.8.0.jar,jna-platform-58.0.jar,java-objc-bridge-1.0.0.jar", + "-DlibraryDirectory=${library_directory}", + "-p", + "${library_directory}/cpw/mods/bootstraplauncher/0.1.17/bootstraplauncher-0.1.17.jar${classpath_separator}${library_directory}/cpw/mods/securejarhandler/0.9.46/securejarhandler-0.9.46.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar${classpath_separator}${library_directory}/org/ow2/asm/asm/9.1/asm-9.1.jar", + "--add-modules", + "ALL-MODULE-PATH", + "--add-opens", + "java.base/java.util.jar=cpw.mods.securejarhandler", + "--add-exports", + "java.base/sun.security.util=cpw.mods.securejarhandler", + "--add-exports", + "jdk.naming.dns/com.sun.jndi.dns=java.naming" + ], getArguments(version.JvmArguments)); + } + public readonly static string fabric_loader_arguments = """ { "id": "fabric-loader-0.13.3-1.18.2", @@ -386,7 +449,15 @@ public void parse_from_vanilla_jvm_arg_array() "jvm": [ "-DFabricMcEmu= net.minecraft.client.main.Main " ] - }, + } } """; + + [Fact] + public void parse_fabric_loader_arguments() + { + var version = JsonVersionParser.ParseFromJsonString(fabric_loader_arguments, options); + Assert.Empty(getArguments(version.GameArguments)); + Assert.Equal(["-DFabricMcEmu= net.minecraft.client.main.Main "], getArguments(version.JvmArguments)); + } } \ No newline at end of file From 4550faf569a460316c640c3095aae525a4ad13ca Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 16 Mar 2024 16:13:38 +0900 Subject: [PATCH 105/137] feat: allow empty rules --- src/Rules/RulesEvaluator.cs | 4 +++- test/Rules/RulesEvaluatorOSTests.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Rules/RulesEvaluator.cs b/src/Rules/RulesEvaluator.cs index 2fd37e0..341fc95 100644 --- a/src/Rules/RulesEvaluator.cs +++ b/src/Rules/RulesEvaluator.cs @@ -7,8 +7,10 @@ public class RulesEvaluator : IRulesEvaluator public bool Match(IEnumerable rules, RulesEvaluatorContext context) { var finalResult = false; + var isAny = false; foreach (var rule in rules) { + isAny = true; var isAllow = rule.Action == "allow"; var isOSMatch = matchOS(rule, context); var isFeatureMatch = matchFeature(rule, context); @@ -16,7 +18,7 @@ public bool Match(IEnumerable rules, RulesEvaluatorContext context if (isOSMatch && isFeatureMatch) finalResult = isAllow; } - return finalResult; + return isAny ? finalResult : true; } private bool matchOS(LauncherRule rule, RulesEvaluatorContext context) diff --git a/test/Rules/RulesEvaluatorOSTests.cs b/test/Rules/RulesEvaluatorOSTests.cs index eaf0109..113a1c7 100644 --- a/test/Rules/RulesEvaluatorOSTests.cs +++ b/test/Rules/RulesEvaluatorOSTests.cs @@ -4,6 +4,19 @@ namespace CmlLib.Core.Test.Rules; public class RulesEvaluatorOSTest { + [Theory] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.Linux, LauncherOSRule.X64)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X86)] + [InlineData(LauncherOSRule.OSX, LauncherOSRule.X64)] + public void allow_empty(string osname, string arch) + { + var result = testOSRule(osname, arch, []); + Assert.True(result); + } + [Theory] [InlineData(LauncherOSRule.Windows, LauncherOSRule.X86)] [InlineData(LauncherOSRule.Windows, LauncherOSRule.X64)] From 6430ddd458a79f1fd1dfca85cef97fe70eca311c Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sat, 16 Mar 2024 20:02:05 +0900 Subject: [PATCH 106/137] feat: add MojangJsonVersionLoaderV2 --- examples/console/Program.cs | 4 +- examples/winform/MainForm.cs | 3 +- src/LauncherParameters.cs | 7 +- src/MojangServer.cs | 1 + src/VersionLoader/LocalJsonVersionLoader.cs | 25 ++-- src/VersionLoader/MojangJsonVersionLoader.cs | 34 ++---- .../MojangJsonVersionLoaderV2.cs | 115 ++++++++++++++++++ src/VersionLoader/VersionLoaderCollection.cs | 2 +- src/VersionMetadata/IVersionMetadata.cs | 2 +- .../JsonVersionManifestModel.cs | 37 ++++++ src/VersionMetadata/JsonVersionMetadata.cs | 12 +- .../JsonVersionMetadataModel.cs | 18 --- src/VersionMetadata/VersionMetadataSorter.cs | 15 +-- 13 files changed, 195 insertions(+), 80 deletions(-) create mode 100644 src/VersionLoader/MojangJsonVersionLoaderV2.cs create mode 100644 src/VersionMetadata/JsonVersionManifestModel.cs delete mode 100644 src/VersionMetadata/JsonVersionMetadataModel.cs diff --git a/examples/console/Program.cs b/examples/console/Program.cs index f5d3ad7..a8772af 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -43,8 +43,8 @@ private async Task Start() // select version Console.WriteLine("Select the version to launch: "); Console.Write("> "); - //var startVersion = Console.ReadLine(); - var startVersion = "1.20.1"; + var startVersion = Console.ReadLine(); + //var startVersion = "1.20.1"; if (string.IsNullOrEmpty(startVersion)) return; diff --git a/examples/winform/MainForm.cs b/examples/winform/MainForm.cs index 86d9791..3a34bc9 100644 --- a/examples/winform/MainForm.cs +++ b/examples/winform/MainForm.cs @@ -5,6 +5,7 @@ using System.Reflection; using CmlLib.Core.VersionMetadata; using CmlLib.Core.Installers; +using CmlLib.Core.ProcessBuilder; namespace CmlLibWinFormSample { @@ -147,7 +148,7 @@ private async void Btn_Launch_Click(object sender, EventArgs e) } if (!string.IsNullOrEmpty(Txt_JavaArgs.Text)) - launchOption.JVMArguments = Txt_JavaArgs.Text.Split(' '); + launchOption.JvmArgumentOverrides = new [] { MArgument.FromCommandLine(Txt_JavaArgs.Text) }; //if (cbSkipAssetsDownload.Checked) // launcher.GameFileCheckers.AssetFileChecker = null; diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 716fe20..170b2ea 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -5,6 +5,7 @@ using CmlLib.Core.Natives; using CmlLib.Core.Rules; using CmlLib.Core.VersionLoader; +using CmlLib.Core.VersionMetadata; namespace CmlLib.Core; @@ -19,11 +20,7 @@ public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) parameters.HttpClient = httpClient; parameters.MinecraftPath = new MinecraftPath(); parameters.RulesEvaluator = new RulesEvaluator(); - parameters.VersionLoader = new VersionLoaderCollection - { - new LocalJsonVersionLoader(parameters.MinecraftPath), - new MojangJsonVersionLoader(httpClient) - }; + parameters.VersionLoader = new MojangJsonVersionLoaderV2(parameters.MinecraftPath, httpClient); parameters.JavaPathResolver = new MinecraftJavaPathResolver(parameters.MinecraftPath); parameters.GameInstaller = ParallelGameInstaller.CreateAsCoreCount(httpClient); parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); diff --git a/src/MojangServer.cs b/src/MojangServer.cs index 0d3edcb..24b5b74 100644 --- a/src/MojangServer.cs +++ b/src/MojangServer.cs @@ -5,6 +5,7 @@ public static class MojangServer public static readonly string Auth = "https://authserver.mojang.com/", Version = "https://launchermeta.mojang.com/mc/game/version_manifest.json", + VersionV2 = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json", Library = "https://libraries.minecraft.net/", ResourceDownload = "https://resources.download.minecraft.net/", LauncherMeta = "https://launchermeta.mojang.com/mc/launcher.json", diff --git a/src/VersionLoader/LocalJsonVersionLoader.cs b/src/VersionLoader/LocalJsonVersionLoader.cs index 6743763..d64afa4 100644 --- a/src/VersionLoader/LocalJsonVersionLoader.cs +++ b/src/VersionLoader/LocalJsonVersionLoader.cs @@ -13,14 +13,25 @@ public LocalJsonVersionLoader(MinecraftPath path) public ValueTask GetVersionMetadatasAsync() { - var versions = getFromLocal(minecraftPath); + var versions = GetVersionNameAndPaths() + .Select(nameAndPath => toMetadata(nameAndPath.Item1, nameAndPath.Item2)); var collection = new VersionMetadataCollection(versions, null, null); return new ValueTask(collection); } - private IEnumerable getFromLocal(MinecraftPath path) + private IVersionMetadata toMetadata(string name, string path) { - var versionDirectory = new DirectoryInfo(path.Versions); + var model = new JsonVersionMetadataModel + { + Id = name, + Type = "local", + }; + return new LocalVersionMetadata(model, path); + } + + public IEnumerable<(string, string)> GetVersionNameAndPaths() + { + var versionDirectory = new DirectoryInfo(minecraftPath.Versions); if (!versionDirectory.Exists) yield break; @@ -29,13 +40,7 @@ private IEnumerable getFromLocal(MinecraftPath path) { var filepath = Path.Combine(dir.FullName, dir.Name + ".json"); if (!File.Exists(filepath)) continue; - - var model = new JsonVersionMetadataModel - { - Name = dir.Name, - Type = "local", - }; - yield return new LocalVersionMetadata(model, filepath); + yield return (dir.Name, filepath); } } } diff --git a/src/VersionLoader/MojangJsonVersionLoader.cs b/src/VersionLoader/MojangJsonVersionLoader.cs index 926a1ad..45a692f 100644 --- a/src/VersionLoader/MojangJsonVersionLoader.cs +++ b/src/VersionLoader/MojangJsonVersionLoader.cs @@ -17,30 +17,16 @@ public MojangJsonVersionLoader(HttpClient httpClient, string endpoint) => public async ValueTask GetVersionMetadatasAsync() { - var res = await _httpClient.GetStreamAsync(_endpoint) - .ConfigureAwait(false); - - using var jsonDocument = await JsonDocument.ParseAsync(res); - var root = jsonDocument.RootElement; - - var latestReleaseId = root.GetPropertyOrNull("latest")?.GetPropertyValue("release"); - var latestSnapshotId = root.GetPropertyOrNull("latest")?.GetPropertyValue("snapshot"); - - var metadatas = new List(); - if (root.TryGetProperty("versions", out var versions) && - versions.ValueKind == JsonValueKind.Array) - { - foreach (var t in versions.EnumerateArray()) - { - var metadataModel = t.Deserialize(); - if (metadataModel == null) - continue; - - var metadata = new MojangVersionMetadata(metadataModel, _httpClient); - metadatas.Add(metadata); - } - } + var manifest = await GetManifestAsync(); + return new VersionMetadataCollection( + manifest?.Versions?.Select(v => new MojangVersionMetadata(v, _httpClient)) ?? [], + manifest?.Latest?.Release, + manifest?.Latest?.Snapshot); + } - return new VersionMetadataCollection(metadatas, latestReleaseId, latestSnapshotId); + public async Task GetManifestAsync() + { + using var res = await _httpClient.GetStreamAsync(_endpoint); + return await JsonSerializer.DeserializeAsync(res); } } diff --git a/src/VersionLoader/MojangJsonVersionLoaderV2.cs b/src/VersionLoader/MojangJsonVersionLoaderV2.cs new file mode 100644 index 0000000..7dc7494 --- /dev/null +++ b/src/VersionLoader/MojangJsonVersionLoaderV2.cs @@ -0,0 +1,115 @@ +using System.Text.Json; +using CmlLib.Core.Internals; +using CmlLib.Core.VersionMetadata; + +namespace CmlLib.Core.VersionLoader; + +public class MojangJsonVersionLoaderV2 : IVersionLoader +{ + private readonly HttpClient _httpClient; + private readonly string _endpoint; + private readonly LocalJsonVersionLoader _localLoader; + private readonly string _localManifestPath; + + public MojangJsonVersionLoaderV2(MinecraftPath path, HttpClient httpClient) : + this(path, httpClient, MojangServer.VersionV2) + { + + } + + public MojangJsonVersionLoaderV2(MinecraftPath path, HttpClient httpClient, string endpoint) + { + _httpClient = httpClient; + _endpoint = endpoint; + _localLoader = new LocalJsonVersionLoader(path); + _localManifestPath = Path.Combine(path.Versions, "version_manifest_v2.json"); + } + + public bool UseLocalManifestWhenError { get; set; } = false; + + public async ValueTask GetVersionMetadatasAsync() + { + var localVersions = _localLoader.GetVersionNameAndPaths(); + var localVersionDict = localVersions.ToDictionary(nameAndPath => nameAndPath.Item1, nameAndPath => nameAndPath.Item2); + var mojangVersions = await getManifest(); + + var vanillaVersions = new List(); + foreach (var mojangVersion in mojangVersions?.Versions ?? []) + { + string id; + if (!string.IsNullOrEmpty(mojangVersion.Id)) + id = mojangVersion.Id; + else if (!string.IsNullOrEmpty(mojangVersion.Name)) + id = mojangVersion.Name; + else + continue; + + if (localVersionDict.TryGetValue(id, out var localVersion)) + { + localVersionDict.Remove(id); + var valid = IOUtil.CheckFileValidation(localVersion, mojangVersion.Sha1); + if (valid) + { + vanillaVersions.Add(new LocalVersionMetadata(mojangVersion, localVersion)); + } + else + { + vanillaVersions.Add(new MojangVersionMetadata(mojangVersion, _httpClient)); + } + } + else + { + vanillaVersions.Add(new MojangVersionMetadata(mojangVersion, _httpClient)); + } + } + + var remainLocalVersions = localVersionDict + .Where(kv => !string.IsNullOrEmpty(kv.Key)) + .Select(nameAndPath => + { + var model = new JsonVersionMetadataModel + { + Id = nameAndPath.Key, + Type = "local" + }; + return new LocalVersionMetadata(model, nameAndPath.Value); + }); + + // 1. custom local versions + // 2. vanilla versions + return new VersionMetadataCollection( + remainLocalVersions.Concat(vanillaVersions), + mojangVersions?.Latest?.Release, + mojangVersions?.Latest?.Snapshot + ); + } + + private async Task getManifest() + { + using var stream = await getManifestStream(); + return await JsonSerializer.DeserializeAsync(stream); + } + + private async Task getManifestStream() + { + try + { + using var res = await _httpClient.GetStreamAsync(_endpoint); + var buffer = new MemoryStream(); + await res.CopyToAsync(buffer); + + using var saveTo = File.Create(_localManifestPath); + await buffer.CopyToAsync(saveTo); + + buffer.Position = 0; + return buffer; + } + catch (HttpRequestException) + { + if (!UseLocalManifestWhenError) + throw; + + return File.OpenRead(_localManifestPath); + } + } +} \ No newline at end of file diff --git a/src/VersionLoader/VersionLoaderCollection.cs b/src/VersionLoader/VersionLoaderCollection.cs index cc695ed..99796c2 100644 --- a/src/VersionLoader/VersionLoaderCollection.cs +++ b/src/VersionLoader/VersionLoaderCollection.cs @@ -20,7 +20,7 @@ public async ValueTask GetVersionMetadatasAsync() // ICollection implementation public int Count => _collection.Count(); - public bool IsReadOnly => throw new NotImplementedException(); + public bool IsReadOnly => false; public void Add(IVersionLoader item) => _collection.Add(item); public void Clear() => _collection.Clear(); public bool Contains(IVersionLoader item) => _collection.Contains(item); diff --git a/src/VersionMetadata/IVersionMetadata.cs b/src/VersionMetadata/IVersionMetadata.cs index cb945d5..159d693 100644 --- a/src/VersionMetadata/IVersionMetadata.cs +++ b/src/VersionMetadata/IVersionMetadata.cs @@ -10,7 +10,7 @@ public interface IVersionMetadata { string Name { get; } string? Type { get; } - DateTime? ReleaseTime { get; } + DateTimeOffset ReleaseTime { get; } Task GetVersionAsync(); Task GetAndSaveVersionAsync(MinecraftPath minecraftPath); diff --git a/src/VersionMetadata/JsonVersionManifestModel.cs b/src/VersionMetadata/JsonVersionManifestModel.cs new file mode 100644 index 0000000..973f514 --- /dev/null +++ b/src/VersionMetadata/JsonVersionManifestModel.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; +using CmlLib.Core.Files; + +namespace CmlLib.Core.VersionMetadata; + +public class JsonVersionManifestModel +{ + [JsonPropertyName("latest")] + public LatestVersion? Latest { get; set; } + + [JsonPropertyName("versions")] + public IEnumerable Versions { get; set; } = Enumerable.Empty(); +} + +public class LatestVersion +{ + [JsonPropertyName("release")] + public string? Release { get; set; } + + [JsonPropertyName("snapshot")] + public string? Snapshot { get; set; } +} + +public record JsonVersionMetadataModel : MFileMetadata +{ + [JsonPropertyName("type")] + public string? Type { get; set; } + + [JsonPropertyName("time")] + public DateTimeOffset Time { get; set; } + + [JsonPropertyName("releaseTime")] + public DateTimeOffset ReleaseTime { get; set; } + + [JsonPropertyName("complianceLevel")] + public int ComplianceLevel { get; set; } +} \ No newline at end of file diff --git a/src/VersionMetadata/JsonVersionMetadata.cs b/src/VersionMetadata/JsonVersionMetadata.cs index 4079f39..9875f07 100644 --- a/src/VersionMetadata/JsonVersionMetadata.cs +++ b/src/VersionMetadata/JsonVersionMetadata.cs @@ -10,9 +10,13 @@ public abstract class JsonVersionMetadata : IVersionMetadata { public JsonVersionMetadata(JsonVersionMetadataModel model) { - if (string.IsNullOrEmpty(model.Name)) - throw new ArgumentNullException(nameof(model.Name)); - Name = model.Name; + if (!string.IsNullOrEmpty(model.Id)) + Name = model.Id; + else if (!string.IsNullOrEmpty(model.Name)) + Name = model.Name; + else + throw new ArgumentNullException(nameof(model.Id)); + Type = model.Type; ReleaseTime = model.ReleaseTime; } @@ -20,7 +24,7 @@ public JsonVersionMetadata(JsonVersionMetadataModel model) public bool IsSaved { get; set; } public string Name { get; } public string? Type { get; } - public DateTime? ReleaseTime { get; } + public DateTimeOffset ReleaseTime { get; } public override bool Equals(object? obj) diff --git a/src/VersionMetadata/JsonVersionMetadataModel.cs b/src/VersionMetadata/JsonVersionMetadataModel.cs deleted file mode 100644 index a8ad437..0000000 --- a/src/VersionMetadata/JsonVersionMetadataModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; - -namespace CmlLib.Core.VersionMetadata; - -public class JsonVersionMetadataModel -{ - [JsonPropertyName("id")] - public string? Name { get; set; } - - [JsonPropertyName("type")] - public string? Type { get; set; } - - [JsonPropertyName("releaseTime")] - public DateTime? ReleaseTime { get; set; } - - [JsonPropertyName("url")] - public string? Url { get; set; } -} \ No newline at end of file diff --git a/src/VersionMetadata/VersionMetadataSorter.cs b/src/VersionMetadata/VersionMetadataSorter.cs index 699038e..c909492 100644 --- a/src/VersionMetadata/VersionMetadataSorter.cs +++ b/src/VersionMetadata/VersionMetadataSorter.cs @@ -25,20 +25,9 @@ public VersionMetadataSorter(MVersionSortOption option) } propertyOptions = propertyList.ToArray(); - - switch (option.NullReleaseDateSortOption) - { - case MVersionNullReleaseDateSortOption.AsLatest: - defaultDateTime = DateTime.MaxValue; - break; - case MVersionNullReleaseDateSortOption.AsOldest: - defaultDateTime = DateTime.MinValue; - break; - } } private readonly MVersionSortPropertyOption[] propertyOptions; - private readonly DateTime defaultDateTime; private readonly Dictionary typePriority; private readonly MVersionSortOption option; @@ -96,9 +85,7 @@ private int compareVersion(IVersionMetadata v1, IVersionMetadata v2) private int compareReleaseDate(IVersionMetadata v1, IVersionMetadata v2) { - var v1DateTime = v1.ReleaseTime ?? defaultDateTime; - var v2DateTime = v2.ReleaseTime ?? defaultDateTime; - var result = DateTime.Compare(v1DateTime, v2DateTime); + var result = DateTimeOffset.Compare(v1.ReleaseTime, v2.ReleaseTime); if (!option.AscendingPropertyOrder) result *= -1; return result; From 48cbd4bef3a1bda97331199bd0cf67405b0b3821 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 18:34:37 +0900 Subject: [PATCH 107/137] feat: add LauncherTester --- examples/console/LauncherTester.cs | 141 +++++++++++++++++++++++++++++ examples/console/Program.cs | 31 ++++++- 2 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 examples/console/LauncherTester.cs diff --git a/examples/console/LauncherTester.cs b/examples/console/LauncherTester.cs new file mode 100644 index 0000000..02734de --- /dev/null +++ b/examples/console/LauncherTester.cs @@ -0,0 +1,141 @@ +using CmlLib.Core; +using CmlLib.Core.Installers; +using CmlLib.Core.ProcessBuilder; + +namespace CmlLibCoreSample; + +public class LauncherTester +{ + private readonly MinecraftLauncher _launcher; + + public LauncherTester(string id) + { + Id = id; + var path = new MinecraftPath(); + _launcher = new MinecraftLauncher(path); + } + + public string Id { get; } + public int AliveTimeSec = 30; + public string OutputDirectory = "./outputs"; + + private StreamWriter? currentOutputStream; + private string? currentOutputPath; + + public async Task Start(IEnumerable targetVersions) + { + prepareOutputs(); + foreach (var version in targetVersions) + { + if (checkTested(version)) + { + Console.WriteLine($"Skip {version}, already tested"); + } + else + { + Console.WriteLine($"Start {version}"); + await startTest(version); + } + } + } + + private void prepareOutputs() + { + var output = Path.Combine(OutputDirectory, Id); + Console.WriteLine($"Output: {output}"); + Directory.CreateDirectory(output); + } + + private bool checkTested(string targetVersion) + { + return File.Exists(getOutputPath(targetVersion)); + } + + private string getOutputPath(string targetVersion) + { + return Path.Combine(OutputDirectory, Id, targetVersion); + } + + private async Task startTest(string version) + { + prepareOutput(version); + + InstallerProgressChangedEventArgs? lastFileProgress = null; + ByteProgress lastByteProgress = new(); + + var launcherTask = _launcher.InstallAndBuildProcessAsync( + version, + new MLaunchOption + { + JavaPath = "java" + }, + new SyncProgress(f => lastFileProgress = f), + new SyncProgress(f => lastByteProgress = f)); + + Console.WriteLine(); + while (!launcherTask.IsCompleted) + { + await Task.Delay(3000); + var percent = lastByteProgress.ProgressedBytes / (double)lastByteProgress.TotalBytes * 100; + //Console.SetCursorPosition(0, Math.Max(0, Console.CursorTop - 1)); + Console.WriteLine($"Progress: {version} {lastFileProgress?.ProgressedTasks} / {lastFileProgress?.TotalTasks}, {percent}%"); + } + + var process = await launcherTask; + Console.WriteLine(process.StartInfo.Arguments); + writeOutput(process.StartInfo.Arguments); + + Console.WriteLine($"Launch: {version}"); + var processWrapper = new ProcessWrapper(process); + processWrapper.OutputReceived += (sender, msg) => + { + Console.WriteLine(msg); + writeOutput(msg); + }; + processWrapper.StartWithEvents(); + var processTask = processWrapper.WaitForExitTaskAsync(); + await Task.WhenAny(processTask, Task.Delay(AliveTimeSec * 1000)); + + if (process.HasExited) + { + Console.WriteLine($"Crashed: {version}"); + } + else + { + process.Kill(); + writeOutput($"!!! Success: has been alive for {AliveTimeSec}, kill it"); + Console.WriteLine($"Success: {version}, has been alive for {AliveTimeSec} seconds"); + } + + completeOutput(version); + } + + private void prepareOutput(string version) + { + currentOutputPath = Path.GetTempFileName(); + Directory.CreateDirectory(Path.GetDirectoryName(currentOutputPath)!); + currentOutputStream = new StreamWriter(File.Create(currentOutputPath)); + + writeOutput($"!!! Test: {Id}/{version}, {DateTime.Now}"); + } + + private void writeOutput(string msg) + { + if (currentOutputStream == null) + return; + currentOutputStream.WriteLine(msg); + } + + private void completeOutput(string version) + { + if (string.IsNullOrEmpty(currentOutputPath)) + return; + if (currentOutputStream == null) + return; + + currentOutputStream.Flush(); + currentOutputStream.Dispose(); + Directory.CreateDirectory(Path.GetDirectoryName(getOutputPath(version))!); + File.Move(currentOutputPath, getOutputPath(version) + ".txt"); + } +} \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index a8772af..82a78cd 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -1,11 +1,8 @@ using System.Diagnostics; using CmlLib.Core; using CmlLib.Core.Auth; -using CmlLib.Core.FileExtractors; using CmlLib.Core.Installers; using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; namespace CmlLibCoreSample; @@ -13,6 +10,34 @@ class Program { public static async Task Main() { + var tester = new LauncherTester("a"); + await tester.Start(new [] + { + "1.0", + "1.2.5", + "1.3.2", + "1.4.7", + "1.5.2", + "1.6.4", + "1.7.2", + "1.7.4", + "1.7.10", + "1.8.9", + "1.9.4", + "1.10.2", + "1.11.2", + "1.12.2", + "1.13.2", + "1.14.4", + "1.15.2", + "1.16.5", + "1.17.1", + "1.18.2", + "1.19.4", + "1.20.4" + }); + return; + var p = new Program(); await p.Start(); } From 5d163c1a13975e50e3334deb89065be4211b71a3 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 18:35:20 +0900 Subject: [PATCH 108/137] feat: add MinecraftArgumentBuilder --- TODO.md | 8 +- src/CommandParser/Extensions.cs | 14 -- src/ProcessBuilder/MLaunchOption.cs | 56 +++++- .../MinecraftArgumentBuilder.cs | 123 +++++++++++++ src/ProcessBuilder/MinecraftProcessBuilder.cs | 166 +++++++++++------- 5 files changed, 284 insertions(+), 83 deletions(-) delete mode 100644 src/CommandParser/Extensions.cs create mode 100644 src/ProcessBuilder/MinecraftArgumentBuilder.cs diff --git a/TODO.md b/TODO.md index 24c659b..1ad43db 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,11 @@ - [ ] 라이트로더 패브릭 tlauncher 등등 확장가능하게 테스트 케이스 작성 -- [ ] LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 +- [ ] MojangLauncher 와 호환성 확인: LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 - [ ] Memory 위에서 mutable 한 IVersion 구현 -- [ ] MojangVersionLoader v2 구현 - [ ] master 브랜치에서 v3.4.0 으로 cherry-pick -- [ ] 자동화된 e2e test runner 만들기 -- [ ] quickPlayServer, serverip, 같은 feature 자동설정 +- [x] 자동화된 e2e test runner 만들기 +- [x] quickPlayServer, serverip, 같은 feature 자동설정 +- [x] MojangVersionLoader v2 구현 - [x] MArgument 유닛테스트 - [x] 포지 옵티파인 iconic-mixed vexed - [x] { "name": "name" } 이런식으로 name 만 있는것도 GameFile 로 추출해야하나? 이전버전 어떻게했나 확인해보고 수정 -> 추출했음 diff --git a/src/CommandParser/Extensions.cs b/src/CommandParser/Extensions.cs deleted file mode 100644 index 7535eb8..0000000 --- a/src/CommandParser/Extensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace CmlLib.Core.CommandParser; - -public static class Extensions -{ - public static bool ContainsXmx(this CommandLineBuilder builder) - { - return builder.Arguments.Any(arg => arg.Key.StartsWith("-Xmx")); - } - - public static bool ContainsXms(this CommandLineBuilder builder) - { - return builder.Arguments.Any(arg => arg.Key.StartsWith("-Xms")); - } -} \ No newline at end of file diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index dd15e5b..cc3d0e6 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -23,6 +23,51 @@ public class MLaunchOption "-XX:G1HeapRegionSize=16M", "-Dlog4j2.formatMsgNoLookups=true" ] + }, + new MArgument + { + Values = ["-XstartOnFirstThread"], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + ] + }, + new MArgument + { + Values = ["-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows" + } + } + ] + }, + new MArgument + { + Values = ["-Xss1M"], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Arch = "x86" + } + } + ] } ]; @@ -41,12 +86,17 @@ public class MLaunchOption public string? DockName { get; set; } public string? DockIcon { get; set; } - public string? ServerIp { get; set; } - public int ServerPort { get; set; } = 25565; - + public bool IsDemo { get; set; } public int ScreenWidth { get; set; } public int ScreenHeight { get; set; } public bool FullScreen { get; set; } + public string? QuickPlayPath { get; set; } + public string? QuickPlaySingleplayer { get; set; } + public string? QuickPlayRealms { get; set; } + + // QuickPlayMultiplayer + public string? ServerIp { get; set; } + public int ServerPort { get; set; } = 25565; public string? ClientId { get; set; } public string? VersionType { get; set; } diff --git a/src/ProcessBuilder/MinecraftArgumentBuilder.cs b/src/ProcessBuilder/MinecraftArgumentBuilder.cs new file mode 100644 index 0000000..07d718e --- /dev/null +++ b/src/ProcessBuilder/MinecraftArgumentBuilder.cs @@ -0,0 +1,123 @@ +using CmlLib.Core.CommandParser; +using CmlLib.Core.Rules; + +namespace CmlLib.Core.ProcessBuilder; + +public class MinecraftArgumentBuilder +{ + public const int DefaultServerPort = 25565; + + private readonly CommandLineBuilder _builder; + private readonly IReadOnlyDictionary _varDict; + private readonly IRulesEvaluator _evaluator; + private readonly RulesEvaluatorContext _context; + + public MinecraftArgumentBuilder( + IRulesEvaluator evaluator, + RulesEvaluatorContext context, + IReadOnlyDictionary varDict) + { + _builder = new CommandLineBuilder(); + _varDict = varDict; + _evaluator = evaluator; + _context = context; + } + + + public void AddArguments(IEnumerable args) => + _builder.AddArguments(args); + + public void AddArguments(IEnumerable args) => + _builder.AddArguments(getArguments(args, _varDict)); + + public void AddArguments(IEnumerable args, IReadOnlyDictionary varDict) => + _builder.AddArguments(getArguments(args, varDict)); + + public bool ContainsKey(string key) => _builder.ContainsKey(key); + public string Build() => _builder.Build(); + + private IEnumerable getArguments(IEnumerable args, IReadOnlyDictionary varDict) + { + return args + .Where(arg => _evaluator.Match(arg.Rules, _context)) + .SelectMany(arg => arg.InterpolateValues(varDict)); + } + + public bool ContainsXmx() + { + return _builder.Arguments.Any(arg => arg.Key.StartsWith("-Xmx")); + } + + public bool ContainsXms() + { + return _builder.Arguments.Any(arg => arg.Key.StartsWith("-Xms")); + } + + public void TryAddNativesDirectory() + { + if (!ContainsKey("-Djava.library.path")) + AddArguments(["-Djava.library.path=${natives_directory}"]); + } + + public void TryAddClassPath() + { + if (!ContainsKey("-cp")) + AddArguments(["-cp", "${classpath}"]); + } + + public void TryAddXmx(int xmx) + { + if (!ContainsXmx()) + AddArguments([$"-Xmx{xmx}m"]); + } + + public void TryAddXms(int xms) + { + if (!ContainsXms()) + AddArguments([$"-Xms{xms}m"]); + } + + public void TryAddDockName(string dockName) + { + if (!ContainsKey("-Xdock:name")) + AddArguments(["-Xdock:name", dockName]); + } + + public void TryAddDockIcon(string dockIcon) + { + if (!ContainsKey("-Xdock:icon")) + AddArguments(["-Xdock:icon", dockIcon]); + } + + public void SetDemo() + { + if (!ContainsKey("--demo")) + AddArguments(["--demo"]); + } + + public void TryAddScreenResolution(int width, int height) + { + if (!ContainsKey("--width")) + AddArguments(["--width", width.ToString()]); + if (!ContainsKey("--height")) + AddArguments(["--height", height.ToString()]); + } + + public void TryAddQuickPlayMultiplayer(string ip, int port) + { + if (!ContainsKey("--quickPlayMultiplayer")) + { + if (!ContainsKey("--server")) + AddArguments(["--server", ip]); + + if (port != DefaultServerPort && !ContainsKey("--port")) + AddArguments(["--port", port.ToString()]); + } + } + + public void SetFullscreen() + { + if (!ContainsKey("--fullscreen")) + AddArguments(["--fullscreen"]); + } +} \ No newline at end of file diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index 7ce3930..aab25a4 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -1,15 +1,12 @@ using CmlLib.Core.Rules; using CmlLib.Core.Version; using CmlLib.Core.Internals; -using CmlLib.Core.CommandParser; using System.Diagnostics; namespace CmlLib.Core.ProcessBuilder; public class MinecraftProcessBuilder { - private const int DefaultServerPort = 25565; - public MinecraftProcessBuilder( IRulesEvaluator evaluator, MLaunchOption option) @@ -23,12 +20,10 @@ public MinecraftProcessBuilder( launchOption = option; version = option.StartVersion; minecraftPath = option.Path; - rulesContext = option.RulesContext; rulesEvaluator = evaluator; } private readonly IVersion version; - private readonly RulesEvaluatorContext rulesContext; private readonly IRulesEvaluator rulesEvaluator; private readonly MinecraftPath minecraftPath; private readonly MLaunchOption launchOption; @@ -46,18 +41,63 @@ public Process CreateProcess() public string BuildArguments() { - var builder = new CommandLineBuilder(); - var argDict = buildArgumentDictionary(); - addJvmArguments(builder, argDict); - addGameArguments(builder, argDict); + Debug.Assert(launchOption.RulesContext != null); + + var context = addFeatures(launchOption.RulesContext); + var argDict = buildArgumentDictionary(context); + + var builder = new MinecraftArgumentBuilder(rulesEvaluator, context, argDict); + addJvmArguments(builder); + addGameArguments(builder); return builder.Build(); } - private Dictionary buildArgumentDictionary() + private RulesEvaluatorContext addFeatures(RulesEvaluatorContext context) + { + var featureSet = new HashSet(context.Features); + + if (launchOption.IsDemo) + { + featureSet.Add("is_demo_user"); + } + + if (launchOption.ScreenWidth > 0 && + launchOption.ScreenHeight > 0) + { + featureSet.Add("has_custom_resolution"); + } + + if (!string.IsNullOrEmpty(launchOption.QuickPlayPath)) + { + featureSet.Add("has_quick_plays_support"); + } + + if (!string.IsNullOrEmpty(launchOption.QuickPlaySingleplayer)) + { + featureSet.Add("is_quick_play_singleplayer"); + } + + if (!string.IsNullOrEmpty(launchOption.ServerIp)) + { + featureSet.Add("is_quick_play_multiplayer"); + } + + if (!string.IsNullOrEmpty(launchOption.QuickPlayRealms)) + { + featureSet.Add("is_quick_play_realms"); + } + + return new RulesEvaluatorContext(context.OS) + { + Features = featureSet + }; + } + + private IReadOnlyDictionary buildArgumentDictionary(RulesEvaluatorContext context) { Debug.Assert(launchOption.Session != null); - var classpaths = getClasspaths(); + var classpaths = getClasspaths(context); var classpath = IOUtil.CombinePath(classpaths); var assetId = version.GetInheritedProperty(version => version.AssetIndex?.Id) ?? "legacy"; @@ -84,6 +124,13 @@ public string BuildArguments() { "game_assets" , minecraftPath.GetAssetLegacyPath(assetId) }, { "auth_session" , launchOption.Session.AccessToken }, { "version_type" , launchOption.VersionType ?? version.Type }, + + { "resolution_width" , launchOption.ScreenWidth.ToString() }, + { "resolution_height" , launchOption.ScreenHeight.ToString() }, + { "quickPlayPath" , launchOption.QuickPlayPath }, + { "quickPlaySingleplayer", launchOption.QuickPlaySingleplayer }, + { "quickPlayMultiplayer" , createAddress(launchOption.ServerIp, launchOption.ServerPort) }, + { "quickPlayRealms" , launchOption.QuickPlayRealms } }; if (launchOption.ArgumentDictionary != null) @@ -98,13 +145,13 @@ public string BuildArguments() } // make library files into jvm classpath string - private IEnumerable getClasspaths() + private IEnumerable getClasspaths(RulesEvaluatorContext context) { // libraries var libPaths = version .ConcatInheritedCollection(v => v.Libraries) .Where(lib => lib.CheckIsRequired(JsonVersionParserOptions.ClientSide)) - .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, rulesContext)) + .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, context)) .Where(lib => lib.Artifact != null) .Select(lib => Path.Combine(minecraftPath.Library, lib.GetLibraryPath())); @@ -119,53 +166,62 @@ private IEnumerable getClasspaths() yield return minecraftPath.GetVersionJarPath(jar); } - private void addJvmArguments(CommandLineBuilder builder, Dictionary argDict) + private string? createAddress(string? ip, int port) + { + if (port == MinecraftArgumentBuilder.DefaultServerPort) + return ip; + else + return ip + ":" + port; + } + + private void addJvmArguments(MinecraftArgumentBuilder builder) { if (launchOption.JvmArgumentOverrides != null) { // override all jvm arguments // even if necessary arguments are missing (-cp, -Djava.library.path), // the builder will still add the necessary arguments - builder.AddArguments(getArguments(launchOption.JvmArgumentOverrides, argDict)); + builder.AddArguments(launchOption.JvmArgumentOverrides); } else { // version-specific jvm arguments var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); - builder.AddArguments(getArguments(jvmArgs, argDict)); - } + builder.AddArguments(jvmArgs); - // add extra jvm arguments - builder.AddArguments(getArguments(launchOption.ExtraJvmArguments, argDict)); + // add extra jvm arguments + builder.AddArguments(launchOption.ExtraJvmArguments); + } // native library - if (!builder.ContainsKey("-Djava.library.path")) - builder.AddArguments(["-Djava.library.path", argDict["natives_directory"] ?? ""]); + builder.TryAddNativesDirectory(); // classpath - if (!builder.ContainsKey("-cp")) - builder.AddArguments(["-cp", argDict["classpath"] ?? ""]); - - // -Xmx, -Xms - if (!builder.ContainsXmx() && launchOption.MaximumRamMb > 0) - builder.AddArguments(["-Xmx" + launchOption.MaximumRamMb + "m"]); - if (!builder.ContainsXms() && launchOption.MinimumRamMb > 0) - builder.AddArguments(["-Xms" + launchOption.MinimumRamMb + "m"]); + builder.TryAddClassPath(); + + // -Xmx + if (launchOption.MaximumRamMb > 0) + builder.TryAddXmx(launchOption.MaximumRamMb); + + // -Xms + if (launchOption.MinimumRamMb > 0) + builder.TryAddXms(launchOption.MinimumRamMb); // for macOS - if (!string.IsNullOrEmpty(launchOption.DockName) && !builder.ContainsKey("-Xdock:name")) - builder.AddArguments(["-Xdock:name", launchOption.DockName]); - if (!string.IsNullOrEmpty(launchOption.DockIcon) && !builder.ContainsKey("-Xdock:icon")) - builder.AddArguments(["-Xdock:icon", launchOption.DockIcon]); + if (!string.IsNullOrEmpty(launchOption.DockName)) + builder.TryAddDockName(launchOption.DockName); + if (!string.IsNullOrEmpty(launchOption.DockIcon)) + builder.TryAddDockIcon(launchOption.DockIcon); // logging var logging = version.GetInheritedProperty(v => v.Logging); if (!string.IsNullOrEmpty(logging?.Argument)) { - builder.AddArguments(getArguments([new MArgument(logging.Argument)], new Dictionary() + var logArguments = MArgument.FromCommandLine(logging.Argument); + builder.AddArguments([logArguments], new Dictionary() { { "path", minecraftPath.GetLogConfigFilePath(logging.LogFile?.Id ?? version.Id) } - })); + }); } // main class @@ -174,43 +230,29 @@ private void addJvmArguments(CommandLineBuilder builder, Dictionary argDict) + private void addGameArguments(MinecraftArgumentBuilder builder) { // game arguments var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); - builder.AddArguments(getArguments(gameArgs, argDict)); + builder.AddArguments(gameArgs); // add extra game arguments - builder.AddArguments(getArguments(launchOption.ExtraGameArguments, argDict)); - - // server - if (!string.IsNullOrEmpty(launchOption.ServerIp)) - { - if (!builder.ContainsKey("--server")) - builder.AddArguments(["--server", launchOption.ServerIp]); + builder.AddArguments(launchOption.ExtraGameArguments); - if (launchOption.ServerPort != DefaultServerPort && !builder.ContainsKey("--port")) - builder.AddArguments(["--port", launchOption.ServerPort.ToString()]); - } + // demo + if (launchOption.IsDemo) + builder.SetDemo(); // screen size if (launchOption.ScreenWidth > 0 && launchOption.ScreenHeight > 0) - { - if (!builder.ContainsKey("--width")) - builder.AddArguments(["--width", launchOption.ScreenWidth.ToString()]); - if (!builder.ContainsKey("--height")) - builder.AddArguments(["--height", launchOption.ScreenHeight.ToString()]); - } + builder.TryAddScreenResolution(launchOption.ScreenWidth, launchOption.ScreenHeight); - // fullscreen - if (!builder.ContainsKey("--fullscreen") && launchOption.FullScreen) - builder.AddArguments(["--fullscreen"]); - } + // quickPlayMultiplayer + if (!string.IsNullOrEmpty(launchOption.ServerIp)) + builder.TryAddQuickPlayMultiplayer(launchOption.ServerIp, launchOption.ServerPort); - private IEnumerable getArguments(IEnumerable args, IReadOnlyDictionary varDict) - { - return args - .Where(arg => rulesEvaluator.Match(arg.Rules, rulesContext)) - .SelectMany(arg => arg.InterpolateValues(varDict)); + // fullscreen + if (launchOption.FullScreen) + builder.SetFullscreen(); } } From 6483becb94482a40a4715d7b457bbe91a9bbcdda Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 18:36:26 +0900 Subject: [PATCH 109/137] fix: skip exceptions when extracting zip file --- src/Internals/SharpZipWrapper.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Internals/SharpZipWrapper.cs b/src/Internals/SharpZipWrapper.cs index 005781c..c93f3d1 100644 --- a/src/Internals/SharpZipWrapper.cs +++ b/src/Internals/SharpZipWrapper.cs @@ -23,8 +23,19 @@ public static void Unzip( IOUtil.CreateParentDirectory(fullPath); var fileName = Path.GetFileName(fullPath); - using var output = File.Create(fullPath); - s.CopyTo(output); + try + { + using var output = File.Create(fullPath); + s.CopyTo(output); + } + catch (IOException) + { + // just skip + } + catch (UnauthorizedAccessException) + { + // just skip + } progress?.Report(new ByteProgress { From f4476472fa81311083df213a499bbcbec0115bac Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 19:42:22 +0900 Subject: [PATCH 110/137] fix: use default java if resolver can't find java for game version --- TODO.md | 5 +++++ src/MinecraftLauncher.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 1ad43db..f546ed6 100644 --- a/TODO.md +++ b/TODO.md @@ -3,6 +3,11 @@ - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 - [ ] Memory 위에서 mutable 한 IVersion 구현 - [ ] master 브랜치에서 v3.4.0 으로 cherry-pick +- [ ] IUpdateTask 항상 실행해야 할듯 +- [ ] -Dos.name= 이거 추가하기 +- [ ] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 +- [ ] DefaultJvmArguments 이거 필요할듯 +- [ ] disableMultiplayer 같은 option 도 추가 - [x] 자동화된 e2e test runner 만들기 - [x] quickPlayServer, serverip, 같은 feature 자동설정 - [x] MojangVersionLoader v2 구현 diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index a1cf0f9..8a1360a 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -161,7 +161,7 @@ public Process BuildProcess( launchOption.NativesDirectory ??= createNativePath(version); launchOption.Path ??= MinecraftPath; launchOption.StartVersion ??= version; - launchOption.JavaPath ??= GetJavaPath(version); + launchOption.JavaPath ??= GetJavaPath(version) ?? GetDefaultJavaPath(); launchOption.RulesContext ??= RulesContext; var processBuilder = new MinecraftProcessBuilder(RulesEvaluator, launchOption); From 8193d7386a37f0c261cd1977996fc3a94100269c Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 19:46:41 +0900 Subject: [PATCH 111/137] fix: fix AddArguments(IEnumerable args) didn't interpolate variables --- src/ProcessBuilder/MinecraftArgumentBuilder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ProcessBuilder/MinecraftArgumentBuilder.cs b/src/ProcessBuilder/MinecraftArgumentBuilder.cs index 07d718e..84ee5c8 100644 --- a/src/ProcessBuilder/MinecraftArgumentBuilder.cs +++ b/src/ProcessBuilder/MinecraftArgumentBuilder.cs @@ -25,7 +25,10 @@ public MinecraftArgumentBuilder( public void AddArguments(IEnumerable args) => - _builder.AddArguments(args); + _builder.AddArguments(getArguments(args, _varDict)); + + public void AddArguments(IEnumerable args, IReadOnlyDictionary varDict) => + _builder.AddArguments(getArguments(args, varDict)); public void AddArguments(IEnumerable args) => _builder.AddArguments(getArguments(args, _varDict)); @@ -43,6 +46,11 @@ private IEnumerable getArguments(IEnumerable args, IReadOnlyD .SelectMany(arg => arg.InterpolateValues(varDict)); } + private IEnumerable getArguments(IEnumerable args, IReadOnlyDictionary varDict) + { + return args.Select(arg => Mapper.InterpolateVariables(arg, varDict)); + } + public bool ContainsXmx() { return _builder.Arguments.Any(arg => arg.Key.StartsWith("-Xmx")); From 22766460196d90c69d1b1ba7be4fd3cc324df28e Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 19:47:22 +0900 Subject: [PATCH 112/137] feat: add DefaultJvmArguments, DefaultExtraJvmArguments --- .../MLaunchOption.DefaultJvmArguments.cs | 92 +++++++++++++++++++ src/ProcessBuilder/MLaunchOption.cs | 69 +------------- src/ProcessBuilder/MinecraftProcessBuilder.cs | 7 +- 3 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 src/ProcessBuilder/MLaunchOption.DefaultJvmArguments.cs diff --git a/src/ProcessBuilder/MLaunchOption.DefaultJvmArguments.cs b/src/ProcessBuilder/MLaunchOption.DefaultJvmArguments.cs new file mode 100644 index 0000000..480457f --- /dev/null +++ b/src/ProcessBuilder/MLaunchOption.DefaultJvmArguments.cs @@ -0,0 +1,92 @@ +using CmlLib.Core.Rules; + +namespace CmlLib.Core.ProcessBuilder; + +public partial class MLaunchOption +{ + public readonly static MArgument[] DefaultJvmArguments = + [ + new MArgument + { + Values = ["-XstartOnFirstThread"], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "osx" + } + } + ] + }, + new MArgument + { + Values = ["-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows" + } + } + ] + }, + new MArgument + { + Values = ["-Xss1M"], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Arch = "x86" + } + } + ] + }, + new MArgument + { + Values = + [ + "-Dos.name=Windows 10", + "-Dos.version=10.0" + ], + Rules = + [ + new LauncherRule + { + Action = "allow", + OS = new LauncherOSRule + { + Name = "windows", + Version = "^10\\." + } + } + ] + } + ]; + + public readonly static MArgument[] DefaultExtraJvmArguments = + [ + new MArgument + { + Values = + [ + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=16M", + "-Dlog4j2.formatMsgNoLookups=true" + ] + }, + ]; +} \ No newline at end of file diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index cc3d0e6..3c75bba 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -4,73 +4,11 @@ namespace CmlLib.Core.ProcessBuilder; -public class MLaunchOption +public partial class MLaunchOption { private static readonly Lazy> EmptyDictionary = new Lazy>(() => new Dictionary()); - public readonly static MArgument[] DefaultJvmArguments = - [ - new MArgument - { - Values = - [ - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=16M", - "-Dlog4j2.formatMsgNoLookups=true" - ] - }, - new MArgument - { - Values = ["-XstartOnFirstThread"], - Rules = - [ - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "osx" - } - } - ] - }, - new MArgument - { - Values = ["-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"], - Rules = - [ - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Name = "windows" - } - } - ] - }, - new MArgument - { - Values = ["-Xss1M"], - Rules = - [ - new LauncherRule - { - Action = "allow", - OS = new LauncherOSRule - { - Arch = "x86" - } - } - ] - } - ]; - public MinecraftPath? Path { get; set; } public IVersion? StartVersion { get; set; } public MSession? Session { get; set; } @@ -106,13 +44,16 @@ public class MLaunchOption public IReadOnlyDictionary ArgumentDictionary { get; set; } = EmptyDictionary.Value; public IEnumerable? JvmArgumentOverrides { get; set; } - public IEnumerable ExtraJvmArguments { get; set; } = DefaultJvmArguments; + public IEnumerable ExtraJvmArguments { get; set; } = DefaultExtraJvmArguments; public IEnumerable ExtraGameArguments { get; set; } = Enumerable.Empty(); internal void CheckValid() { string? exMsg = null; // error message + if (string.IsNullOrEmpty(JavaPath)) + exMsg = "JavaPath is null"; + if (RulesContext == null) exMsg = "RulesContext is null"; diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index aab25a4..b3d784e 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -186,8 +186,11 @@ private void addJvmArguments(MinecraftArgumentBuilder builder) else { // version-specific jvm arguments - var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments); - builder.AddArguments(jvmArgs); + var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments).ToList(); + if (jvmArgs.Any()) + builder.AddArguments(jvmArgs); + else + builder.AddArguments(MLaunchOption.DefaultJvmArguments); // add extra jvm arguments builder.AddArguments(launchOption.ExtraJvmArguments); From fbe9ec9729ad6a35caf86d5d2f91261980aa6a08 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 20:19:37 +0900 Subject: [PATCH 113/137] test: update LauncherTester --- TODO.md | 4 ++-- examples/console/LauncherTester.cs | 10 ++++++---- examples/console/Program.cs | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index f546ed6..6194cbd 100644 --- a/TODO.md +++ b/TODO.md @@ -4,10 +4,10 @@ - [ ] Memory 위에서 mutable 한 IVersion 구현 - [ ] master 브랜치에서 v3.4.0 으로 cherry-pick - [ ] IUpdateTask 항상 실행해야 할듯 -- [ ] -Dos.name= 이거 추가하기 - [ ] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 -- [ ] DefaultJvmArguments 이거 필요할듯 - [ ] disableMultiplayer 같은 option 도 추가 +- [x] -Dos.name= 이거 추가하기 +- [x] DefaultJvmArguments 이거 필요할듯 - [x] 자동화된 e2e test runner 만들기 - [x] quickPlayServer, serverip, 같은 feature 자동설정 - [x] MojangVersionLoader v2 구현 diff --git a/examples/console/LauncherTester.cs b/examples/console/LauncherTester.cs index 02734de..0fb0778 100644 --- a/examples/console/LauncherTester.cs +++ b/examples/console/LauncherTester.cs @@ -16,7 +16,7 @@ public LauncherTester(string id) } public string Id { get; } - public int AliveTimeSec = 30; + public int AliveTimeSec = 10; public string OutputDirectory = "./outputs"; private StreamWriter? currentOutputStream; @@ -53,7 +53,7 @@ private bool checkTested(string targetVersion) private string getOutputPath(string targetVersion) { - return Path.Combine(OutputDirectory, Id, targetVersion); + return Path.Combine(OutputDirectory, Id, targetVersion + ".log"); } private async Task startTest(string version) @@ -67,7 +67,7 @@ private async Task startTest(string version) version, new MLaunchOption { - JavaPath = "java" + //JavaPath = "java" }, new SyncProgress(f => lastFileProgress = f), new SyncProgress(f => lastByteProgress = f)); @@ -82,7 +82,9 @@ private async Task startTest(string version) } var process = await launcherTask; + Console.WriteLine(process.StartInfo.FileName); Console.WriteLine(process.StartInfo.Arguments); + writeOutput(process.StartInfo.FileName); writeOutput(process.StartInfo.Arguments); Console.WriteLine($"Launch: {version}"); @@ -136,6 +138,6 @@ private void completeOutput(string version) currentOutputStream.Flush(); currentOutputStream.Dispose(); Directory.CreateDirectory(Path.GetDirectoryName(getOutputPath(version))!); - File.Move(currentOutputPath, getOutputPath(version) + ".txt"); + File.Move(currentOutputPath, getOutputPath(version)); } } \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 82a78cd..68a474c 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -10,6 +10,10 @@ class Program { public static async Task Main() { + var p = new Program(); + await p.Start(); + return; + var tester = new LauncherTester("a"); await tester.Start(new [] { @@ -37,9 +41,6 @@ await tester.Start(new [] "1.20.4" }); return; - - var p = new Program(); - await p.Start(); } private async Task Start() From fdc2852dc93ab2310a742b88c62ee2a520850af0 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 20:30:25 +0900 Subject: [PATCH 114/137] feat: add CreateOfflineSession --- examples/console/Program.cs | 2 +- examples/winform/LoginForm.cs | 2 +- src/Auth/MSession.cs | 19 ++++++++++++++++--- src/ProcessBuilder/MLaunchOption.cs | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 68a474c..da9d0c3 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -85,7 +85,7 @@ await launcher.InstallAsync( // build process var process = await launcher.BuildProcessAsync(startVersion, new MLaunchOption { - Session = MSession.GetOfflineSession("username"), + Session = MSession.CreateOfflineSession("username"), JavaPath = "java" }); diff --git a/examples/winform/LoginForm.cs b/examples/winform/LoginForm.cs index 38766d3..41b5857 100644 --- a/examples/winform/LoginForm.cs +++ b/examples/winform/LoginForm.cs @@ -40,7 +40,7 @@ private void btnDeleteToken_Click(object sender, EventArgs e) private void btnOfflineLogin_Click(object sender, EventArgs e) { - UpdateSession(MSession.GetOfflineSession(txtUsername.Text)); + UpdateSession(MSession.CreateOfflineSession(txtUsername.Text)); } private void UpdateSession(MSession session) diff --git a/src/Auth/MSession.cs b/src/Auth/MSession.cs index bdde90b..cca3dd2 100644 --- a/src/Auth/MSession.cs +++ b/src/Auth/MSession.cs @@ -33,15 +33,28 @@ public bool CheckIsValid() && !string.IsNullOrEmpty(UUID); } - public static MSession GetOfflineSession(string username) + public static MSession CreateLegacyOfflineSession(string username) { return new MSession { - Username = username, - AccessToken = "access_token", + Username = username, + AccessToken = "access_token", UUID = "user_uuid", UserType = "Mojang", ClientToken = null }; } + + public static MSession CreateOfflineSession(string username) + { + return new MSession + { + Username = username, + AccessToken = "access_token", + UUID = Guid.NewGuid().ToString().Replace("-", ""), // create random valid UUID + UserType = "msa", + ClientToken = null + }; + } + } diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index 3c75bba..7430874 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -64,7 +64,7 @@ internal void CheckValid() exMsg = "StartVersion is null"; if (Session == null) - Session = MSession.GetOfflineSession("tester123"); + Session = MSession.CreateOfflineSession("tester123"); if (!Session.CheckIsValid()) exMsg = "Invalid Session"; From a740d0a760339e19066292ac66a181ef1a7b702d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 20:31:36 +0900 Subject: [PATCH 115/137] fix: filter duplicated library path when building -cp 1.20.2~ forge version file has duplicated library paths. it crashes the game --- src/ProcessBuilder/MinecraftProcessBuilder.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index b3d784e..279efb8 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -155,8 +155,13 @@ private IEnumerable getClasspaths(RulesEvaluatorContext context) .Where(lib => lib.Artifact != null) .Select(lib => Path.Combine(minecraftPath.Library, lib.GetLibraryPath())); + // filter duplicated paths + var pathSet = new HashSet(); foreach (var item in libPaths) - yield return item; + { + if (pathSet.Add(item)) + yield return item; + } // .jar file // TODO: decide what Jar file should be used. current jar or parent jar From a2e54bc558d60f61c28d30e0676ea525ec72a5f1 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 20:43:43 +0900 Subject: [PATCH 116/137] feat: increase default MaximumRamMb value to 2048 on X64 --- src/ProcessBuilder/MLaunchOption.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ProcessBuilder/MLaunchOption.cs b/src/ProcessBuilder/MLaunchOption.cs index 7430874..488d29c 100644 --- a/src/ProcessBuilder/MLaunchOption.cs +++ b/src/ProcessBuilder/MLaunchOption.cs @@ -9,6 +9,14 @@ public partial class MLaunchOption private static readonly Lazy> EmptyDictionary = new Lazy>(() => new Dictionary()); + public MLaunchOption() + { + if (LauncherOSRule.Current.Arch == LauncherOSRule.X64) + MaximumRamMb = 2048; + else + MaximumRamMb = 1024; + } + public MinecraftPath? Path { get; set; } public IVersion? StartVersion { get; set; } public MSession? Session { get; set; } @@ -18,7 +26,7 @@ public partial class MLaunchOption public string? JavaVersion { get; set; } public string? JavaPath { get; set; } - public int MaximumRamMb { get; set; } = 1024; + public int MaximumRamMb { get; set; } public int MinimumRamMb { get; set; } public string? DockName { get; set; } @@ -75,6 +83,12 @@ internal void CheckValid() if (ScreenWidth < 0 || ScreenHeight < 0) exMsg = "Screen Size must be greater than or equal to zero."; + if (MaximumRamMb < 0) + exMsg = "MaximumRamMb must be greater than or equal to zero."; + + if (MinimumRamMb < 0) + exMsg = "MinimumRamMb must be greater than or equal to zero."; + if (exMsg != null) // if launch option is invalid, throw exception throw new ArgumentException(exMsg); } From f72fd8377bfcbacfaed5ae50d4e90130413aeeb1 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 21:55:27 +0900 Subject: [PATCH 117/137] fix: always execute UpdateTask --- src/Installers/BasicGameInstaller.cs | 1 + src/Installers/ParallelGameInstaller.cs | 1 + src/Tasks/ChmodTask.cs | 5 +++-- src/Tasks/LegacyJavaExtractionTask.cs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Installers/BasicGameInstaller.cs b/src/Installers/BasicGameInstaller.cs index e0a4b8e..09b3503 100644 --- a/src/Installers/BasicGameInstaller.cs +++ b/src/Installers/BasicGameInstaller.cs @@ -51,6 +51,7 @@ protected override async ValueTask Install( } else { + await gameFile.ExecuteUpdateTask(cancellationToken); progress.ReportDone(); } diff --git a/src/Installers/ParallelGameInstaller.cs b/src/Installers/ParallelGameInstaller.cs index f27df46..3c371c0 100644 --- a/src/Installers/ParallelGameInstaller.cs +++ b/src/Installers/ParallelGameInstaller.cs @@ -112,6 +112,7 @@ protected override async ValueTask Install( } else { + await result.GameFile.ExecuteUpdateTask(cancellationToken); progress.ReportDone(); } diff --git a/src/Tasks/ChmodTask.cs b/src/Tasks/ChmodTask.cs index 18ee6f5..066c48b 100644 --- a/src/Tasks/ChmodTask.cs +++ b/src/Tasks/ChmodTask.cs @@ -18,10 +18,11 @@ public ValueTask Execute( GameFile file, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(file.Path)) + var target = _filePath ?? file.Path; + if (string.IsNullOrEmpty(target)) throw new InvalidOperationException(); if (LauncherOSRule.Current.Name != LauncherOSRule.Windows) - NativeMethods.Chmod(_filePath ?? file.Path, Mode); + NativeMethods.Chmod(target, Mode); return new ValueTask(); } } \ No newline at end of file diff --git a/src/Tasks/LegacyJavaExtractionTask.cs b/src/Tasks/LegacyJavaExtractionTask.cs index 9634410..093594e 100644 --- a/src/Tasks/LegacyJavaExtractionTask.cs +++ b/src/Tasks/LegacyJavaExtractionTask.cs @@ -15,7 +15,7 @@ public LegacyJavaExtractionTask(string extractTo) public ValueTask Execute(GameFile file, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(file.Path)) - throw new ArgumentException(); + throw new InvalidOperationException(); // jre.lzma (file.Path) -> jre.zip -> /extracTo var zipPath = Path.Combine(Path.GetTempPath(), "jre.zip"); From e9cd7283ff2f78980436c95fa8ffd2ae7efa48c7 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Sun, 17 Mar 2024 21:55:56 +0900 Subject: [PATCH 118/137] refactor: move HttpClientDownloadHelper to Internals --- src/Installers/GameInstallerBase.cs | 1 - src/{Tasks => Internals}/HttpClientDownloadHelper.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) rename src/{Tasks => Internals}/HttpClientDownloadHelper.cs (97%) diff --git a/src/Installers/GameInstallerBase.cs b/src/Installers/GameInstallerBase.cs index 21fd9c2..77dde84 100644 --- a/src/Installers/GameInstallerBase.cs +++ b/src/Installers/GameInstallerBase.cs @@ -1,7 +1,6 @@ using CmlLib.Core.Internals; using CmlLib.Core.Files; using System.Diagnostics; -using CmlLib.Core.Tasks; namespace CmlLib.Core.Installers; diff --git a/src/Tasks/HttpClientDownloadHelper.cs b/src/Internals/HttpClientDownloadHelper.cs similarity index 97% rename from src/Tasks/HttpClientDownloadHelper.cs rename to src/Internals/HttpClientDownloadHelper.cs index ff1b818..26e4a89 100644 --- a/src/Tasks/HttpClientDownloadHelper.cs +++ b/src/Internals/HttpClientDownloadHelper.cs @@ -1,6 +1,4 @@ -using CmlLib.Core.Internals; - -namespace CmlLib.Core.Tasks; +namespace CmlLib.Core.Internals; // To implement System.Net.WebClient.DownloadFileTaskAsync // https://source.dot.net/#System.Net.WebClient/System/Net/WebClient.cs,c2224e40a6960929 From 287a58252318efe73349fe39918e7d3c8b645b12 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Mon, 18 Mar 2024 21:53:07 +0900 Subject: [PATCH 119/137] refactor: GetVersionJsonString to GetVersionJsonStream --- TODO.md | 10 +++-- src/Internals/AsyncIO.cs | 44 ++----------------- .../LiteLoader/LiteLoaderVersionMetadata.cs | 9 ++-- src/Version/JsonVersionParser.cs | 6 +++ src/VersionMetadata/JsonVersionMetadata.cs | 24 +++++----- src/VersionMetadata/LocalVersionMetadata.cs | 4 +- src/VersionMetadata/MojangVersionMetadata.cs | 4 +- 7 files changed, 34 insertions(+), 67 deletions(-) diff --git a/TODO.md b/TODO.md index 6194cbd..f736ff7 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,13 @@ +- [ ] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 +- [ ] 여기까지 하고 4.0.0-beta.1 내놓기 + - [ ] 라이트로더 패브릭 tlauncher 등등 확장가능하게 테스트 케이스 작성 - [ ] MojangLauncher 와 호환성 확인: LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 - [ ] Memory 위에서 mutable 한 IVersion 구현 -- [ ] master 브랜치에서 v3.4.0 으로 cherry-pick -- [ ] IUpdateTask 항상 실행해야 할듯 -- [ ] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 -- [ ] disableMultiplayer 같은 option 도 추가 +- [x] master 브랜치에서 v3.4.0 으로 cherry-pick +- [x] disableMultiplayer 같은 option 도 추가 -> ExtraGameArguments 로 추가해라 +- [x] IUpdateTask 항상 실행해야 할듯 - [x] -Dos.name= 이거 추가하기 - [x] DefaultJvmArguments 이거 필요할듯 - [x] 자동화된 e2e test runner 만들기 diff --git a/src/Internals/AsyncIO.cs b/src/Internals/AsyncIO.cs index 58edb78..4b4c506 100644 --- a/src/Internals/AsyncIO.cs +++ b/src/Internals/AsyncIO.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace CmlLib.Core.Internals; internal static class AsyncIO @@ -30,45 +28,9 @@ public static FileStream AsyncWriteStream(string path, bool append) return stream; } - public static StreamReader AsyncStreamReader(string path, Encoding encoding) - { - FileStream stream = AsyncReadStream(path); - return new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: false); - } - - public static StreamWriter AsyncStreamWriter(string path, Encoding encoding, bool append) + public static async Task WriteFileAsync(string path, Stream stream) { - FileStream stream = AsyncWriteStream(path, append); - return new StreamWriter(stream, encoding); - } - - // In .NET Standard 2.0, There is no File.ReadFileTextAsync. so I copied it from .NET Core source code - public static async Task ReadFileAsync(string path) - { - using var reader = AsyncStreamReader(path, Encoding.UTF8); - var content = await reader.ReadToEndAsync() - .ConfigureAwait(false); // **MUST be awaited in this scope** - await reader.BaseStream.FlushAsync().ConfigureAwait(false); - return content; - } - - // In .NET Standard 2.0, There is no File.WriteFileTextAsync. so I copied it from .NET Core source code - public static async Task WriteFileAsync(string path, string content) - { - // UTF8 with BOM might not be recognized by minecraft. not tested - var encoder = new UTF8Encoding(false); - using var writer = AsyncStreamWriter(path, encoder, false); - await writer.WriteAsync(content).ConfigureAwait(false); // **MUST be awaited in this scope** - await writer.FlushAsync().ConfigureAwait(false); - } - - public static async Task CopyFileAsync(string sourceFile, string destinationFile) - { - using var sourceStream = AsyncReadStream(sourceFile); - using var destinationStream = AsyncWriteStream(destinationFile, false); - - await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); - - await destinationStream.FlushAsync().ConfigureAwait(false); + using var writer = AsyncWriteStream(path, false); + await stream.CopyToAsync(writer); } } \ No newline at end of file diff --git a/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs index 5905011..8285529 100644 --- a/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs @@ -118,11 +118,10 @@ public async Task InstallAsync(MinecraftPath path, IVersion baseVersion) return versionName; } - protected override ValueTask GetVersionJsonString() + protected override ValueTask GetVersionJsonStream() { - using var ms = new MemoryStream(); - using var writer = new Utf8JsonWriter(ms); - var json = System.Text.Encoding.UTF8.GetString(ms.ToArray()); - return new ValueTask(json); + var ms = new MemoryStream(); + using var writer = new Utf8JsonWriter(ms); // TODO + return new ValueTask(ms); } } \ No newline at end of file diff --git a/src/Version/JsonVersionParser.cs b/src/Version/JsonVersionParser.cs index ae12824..beb1288 100644 --- a/src/Version/JsonVersionParser.cs +++ b/src/Version/JsonVersionParser.cs @@ -10,6 +10,12 @@ public static IVersion ParseFromJsonString(string json, JsonVersionParserOptions return ParseFromJson(document, options); } + public static IVersion ParseFromJsonStream(Stream stream, JsonVersionParserOptions options) + { + var document = JsonDocument.Parse(stream); + return ParseFromJson(document, options); + } + public static IVersion ParseFromJson(JsonDocument json, JsonVersionParserOptions options) { try diff --git a/src/VersionMetadata/JsonVersionMetadata.cs b/src/VersionMetadata/JsonVersionMetadata.cs index 9875f07..3dd273b 100644 --- a/src/VersionMetadata/JsonVersionMetadata.cs +++ b/src/VersionMetadata/JsonVersionMetadata.cs @@ -26,7 +26,6 @@ public JsonVersionMetadata(JsonVersionMetadataModel model) public string? Type { get; } public DateTimeOffset ReleaseTime { get; } - public override bool Equals(object? obj) { if (obj == null) @@ -56,34 +55,33 @@ public override int GetHashCode() /// Get actual version data as string /// /// Version metadata - protected abstract ValueTask GetVersionJsonString(); + protected abstract ValueTask GetVersionJsonStream(); public async Task GetVersionAsync() { - var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - return JsonVersionParser.ParseFromJsonString(metadataJson, new JsonVersionParserOptions()); + using var versionStream = await GetVersionJsonStream().ConfigureAwait(false); + return JsonVersionParser.ParseFromJsonStream(versionStream, new JsonVersionParserOptions()); } public async Task GetAndSaveVersionAsync(MinecraftPath path) { - var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - var version = JsonVersionParser.ParseFromJsonString(metadataJson, new JsonVersionParserOptions()); - await saveMetdataJson(path, metadataJson); + using var versionStream = await GetVersionJsonStream().ConfigureAwait(false); + var version = JsonVersionParser.ParseFromJsonStream(versionStream, new JsonVersionParserOptions()); + await saveMetdataJson(path, versionStream); return version; } public async Task SaveVersionAsync(MinecraftPath path) { - var metadataJson = await GetVersionJsonString().ConfigureAwait(false); - await saveMetdataJson(path, metadataJson); + using var versionStream = await GetVersionJsonStream().ConfigureAwait(false); + await saveMetdataJson(path, versionStream); } - private async Task saveMetdataJson(MinecraftPath path, string json) + private async Task saveMetdataJson(MinecraftPath path, Stream stream) { - if (IsSaved) - return; + if (IsSaved) return; var metadataPath = path.GetVersionJsonPath(Name); IOUtil.CreateParentDirectory(metadataPath); - await AsyncIO.WriteFileAsync(metadataPath, json).ConfigureAwait(false); + await AsyncIO.WriteFileAsync(metadataPath, stream); } } \ No newline at end of file diff --git a/src/VersionMetadata/LocalVersionMetadata.cs b/src/VersionMetadata/LocalVersionMetadata.cs index 4ad1a3e..ebe5144 100644 --- a/src/VersionMetadata/LocalVersionMetadata.cs +++ b/src/VersionMetadata/LocalVersionMetadata.cs @@ -15,12 +15,12 @@ public LocalVersionMetadata(JsonVersionMetadataModel model, string path) : base( Path = path; } - protected override async ValueTask GetVersionJsonString() + protected override ValueTask GetVersionJsonStream() { if (string.IsNullOrEmpty(Path)) throw new InvalidOperationException("Path property was null"); // FileNotFoundException will be thrown if Path does not exist. - return await AsyncIO.ReadFileAsync(Path); + return new ValueTask(File.OpenRead(Path)); } } \ No newline at end of file diff --git a/src/VersionMetadata/MojangVersionMetadata.cs b/src/VersionMetadata/MojangVersionMetadata.cs index 93d869d..e1e9438 100644 --- a/src/VersionMetadata/MojangVersionMetadata.cs +++ b/src/VersionMetadata/MojangVersionMetadata.cs @@ -17,9 +17,9 @@ public MojangVersionMetadata(JsonVersionMetadataModel model, HttpClient httpClie Url = model.Url; } - protected override async ValueTask GetVersionJsonString() + protected override async ValueTask GetVersionJsonStream() { - return await _httpClient.GetStringAsync(Url) + return await _httpClient.GetStreamAsync(Url) .ConfigureAwait(false); } } \ No newline at end of file From 662b2444fec45b0c996b3f4c4121e28a8501f117 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Mon, 18 Mar 2024 21:54:03 +0900 Subject: [PATCH 120/137] feat: add legacy apis --- src/Auth/MSession.cs | 3 +++ src/MinecraftLauncher.cs | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Auth/MSession.cs b/src/Auth/MSession.cs index cca3dd2..ae852c9 100644 --- a/src/Auth/MSession.cs +++ b/src/Auth/MSession.cs @@ -57,4 +57,7 @@ public static MSession CreateOfflineSession(string username) }; } + // legacy api + [Obsolete("Use CreateOfflineSession(\"username\") instead.")] + public static MSession GetOfflineSession(string username) => CreateOfflineSession(username); } diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 8a1360a..0871d1f 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -30,6 +30,11 @@ public class MinecraftLauncher public RulesEvaluatorContext RulesContext { get; set; } public VersionMetadataCollection? Versions { get; private set; } + public MinecraftLauncher() : this(WithMinecraftPath(new MinecraftPath())) + { + + } + public MinecraftLauncher(string path) : this(WithMinecraftPath(new MinecraftPath(path))) { @@ -65,8 +70,8 @@ public MinecraftLauncher(MinecraftLauncherParameters parameters) ?? throw new ArgumentException(nameof(parameters.RulesEvaluator) + " was null"); RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - _fileProgress = new Progress(e => FileProgressChanged?.Invoke(this, e)); - _byteProgress = new Progress(e => ByteProgressChanged?.Invoke(this, e)); + _fileProgress = new SyncProgress(e => FileProgressChanged?.Invoke(this, e)); + _byteProgress = new SyncProgress(e => ByteProgressChanged?.Invoke(this, e)); } public async ValueTask GetAllVersionsAsync() From b91512c6fd273803d8584b98d8570bdce87ebf03 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Mon, 18 Mar 2024 21:54:09 +0900 Subject: [PATCH 121/137] doc: add QuickStart --- README.md | 63 ++++++++++++++++++++++++++++++---- examples/console/QuickStart.cs | 50 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 examples/console/QuickStart.cs diff --git a/README.md b/README.md index a94a077..c91a28f 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,14 @@ Supports all versions, Forge, and any custom version ## Features -* Asynchronous APIs -* Mojang Authentication -* Microsoft Xbox Live Login -* Download the game files from the Mojang file server -* Launch any version (tested up to 1.18.2) +* Authenticate with Microsoft Xbox account +* Get vanilla versions and installed versions +* Install vanilla versions +* Launch any vanilla version (tested up to 1.20.4) * Launch Forge, Optifine, FabricMC, LiteLoader or any other custom version * Install Java runtime * Install LiteLoader, FabricMC -* Launch with options (direct server connecting, screen resolution) +* Launch with options (direct server connecting, screen resolution, JVM arguments) * Cross-platform (Windows, Linux, macOS) [Go to the wiki for all features](https://alphabs.gitbook.io/cmllib/cmllib.core/cmllib) @@ -36,11 +35,61 @@ Supports all versions, Forge, and any custom version Install the [CmlLib.Core Nuget package](https://www.nuget.org/packages/CmlLib.Core) -Write this at the top of your source code: +## QuickStart + +### Get All Versions + +```csharp +using CmlLib.Core; + +var launcher = new MinecraftLauncher(); +var versions = await launcher.GetAllVersionsAsync(); +foreach (var version in versions) +{ + Console.WriteLine($"{version.Type} {version.Name}"); +} +``` + +### Launch the Game + +```csharp +using CmlLib.Core; +using CmlLib.Core.ProcessBuilder; + +var launcher = new MinecraftLauncher(); +var process = await launcher.InstallAndBuildProcessAsync("1.20.4", new MLaunchOption()); +process.Start(); +``` + +### Launch the Game with Options ```csharp using CmlLib.Core; using CmlLib.Core.Auth; +using CmlLib.Core.ProcessBuilder; + +var path = new MinecraftPath("./my_game_dir"); +var launcher = new MinecraftLauncher(path); + +launcher.FileProgressChanged += (sender, args) => +{ + Console.WriteLine($"Name: {args.Name}"); + Console.WriteLine($"Type: {args.EventType}"); + Console.WriteLine($"Total: {args.TotalTasks}"); + Console.WriteLine($"Progressed: {args.ProgressedTasks}"); +}; +launcher.ByteProgressChanged += (sender, args) => +{ + Console.WriteLine($"{args.ProgressedBytes} bytes / {args.TotalBytes} bytes"); +}; + +await launcher.InstallAsync("1.20.4"); +var process = await launcher.BuildProcessAsync("1.20.4", new MLaunchOption +{ + Session = MSession.CreateOfflineSession("CmllibGamer123"), + MaximumRamMb = 4096 +}); +process.Start(); ``` ## Documentation diff --git a/examples/console/QuickStart.cs b/examples/console/QuickStart.cs new file mode 100644 index 0000000..bde1fef --- /dev/null +++ b/examples/console/QuickStart.cs @@ -0,0 +1,50 @@ +using CmlLib.Core; +using CmlLib.Core.Auth; +using CmlLib.Core.ProcessBuilder; + +namespace CmlLibCoreSample; + +public class QuickStart +{ + public async Task Basic() + { + var launcher = new MinecraftLauncher(); + var process = await launcher.InstallAndBuildProcessAsync("1.20.4", new MLaunchOption()); + process.Start(); + } + + public async Task Fancy() + { + var path = new MinecraftPath("./my_game_dir"); + var launcher = new MinecraftLauncher(path); + launcher.FileProgressChanged += (sender, args) => + { + Console.WriteLine($"Name: {args.Name}"); + Console.WriteLine($"Type: {args.EventType}"); + Console.WriteLine($"Total: {args.TotalTasks}"); + Console.WriteLine($"Progressed: {args.ProgressedTasks}"); + }; + launcher.ByteProgressChanged += (sender, args) => + { + Console.WriteLine($"{args.ProgressedBytes} bytes / {args.TotalBytes} bytes"); + }; + + await launcher.InstallAsync("1.20.4"); + var process = await launcher.BuildProcessAsync("1.20.4", new MLaunchOption + { + Session = MSession.CreateOfflineSession("Gamer123"), + MaximumRamMb = 4096 + }); + process.Start(); + } + + public async Task GetAllVersions() + { + var launcher = new MinecraftLauncher(); + var versions = await launcher.GetAllVersionsAsync(); + foreach (var version in versions) + { + Console.WriteLine($"{version.Type} {version.Name}"); + } + } +} \ No newline at end of file From dc89299c01de35f20790eee714abf236094821ea Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 19:53:42 +0900 Subject: [PATCH 122/137] test: update LauncherTester --- examples/console/LauncherTester.cs | 26 +- examples/console/Program.cs | 39 +- examples/winform/MainForm.Designer.cs | 1008 ++++++++++++------------- examples/winform/MainForm.cs | 31 +- examples/winform/MainForm.resx | 50 +- 5 files changed, 595 insertions(+), 559 deletions(-) diff --git a/examples/console/LauncherTester.cs b/examples/console/LauncherTester.cs index 0fb0778..fbde59b 100644 --- a/examples/console/LauncherTester.cs +++ b/examples/console/LauncherTester.cs @@ -11,7 +11,7 @@ public class LauncherTester public LauncherTester(string id) { Id = id; - var path = new MinecraftPath(); + var path = new MinecraftPath("C:\\Users\\ksi12\\AppData\\Roaming\\minecraft test"); _launcher = new MinecraftLauncher(path); } @@ -56,6 +56,11 @@ private string getOutputPath(string targetVersion) return Path.Combine(OutputDirectory, Id, targetVersion + ".log"); } + private string getCrashedOutputPath(string targetVersion) + { + return Path.Combine(OutputDirectory, Id, targetVersion + ".crash.log"); + } + private async Task startTest(string version) { prepareOutput(version); @@ -101,15 +106,15 @@ private async Task startTest(string version) if (process.HasExited) { Console.WriteLine($"Crashed: {version}"); + completeCrashedOutput(version); } else { process.Kill(); writeOutput($"!!! Success: has been alive for {AliveTimeSec}, kill it"); Console.WriteLine($"Success: {version}, has been alive for {AliveTimeSec} seconds"); + completeSuccessfulOutput(version); } - - completeOutput(version); } private void prepareOutput(string version) @@ -128,7 +133,20 @@ private void writeOutput(string msg) currentOutputStream.WriteLine(msg); } - private void completeOutput(string version) + private void completeSuccessfulOutput(string version) + { + if (string.IsNullOrEmpty(currentOutputPath)) + return; + if (currentOutputStream == null) + return; + + currentOutputStream.Flush(); + currentOutputStream.Dispose(); + Directory.CreateDirectory(Path.GetDirectoryName(getOutputPath(version))!); + File.Move(currentOutputPath, getOutputPath(version)); + } + + private void completeCrashedOutput(string version) { if (string.IsNullOrEmpty(currentOutputPath)) return; diff --git a/examples/console/Program.cs b/examples/console/Program.cs index da9d0c3..2eb884d 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -10,12 +10,12 @@ class Program { public static async Task Main() { - var p = new Program(); - await p.Start(); - return; + //var p = new Program(); + //await p.Start(); + //return; - var tester = new LauncherTester("a"); - await tester.Start(new [] + var tester = new LauncherTester("b"); + await tester.Start(new[] { "1.0", "1.2.5", @@ -38,7 +38,34 @@ await tester.Start(new [] "1.17.1", "1.18.2", "1.19.4", - "1.20.4" + "1.20.4", + "1.7.10-Forge10.13.4.1558-1.7.10", + "1.7.10-Forge10.13.4.1614-1.7.10", + "1.8.9-forge1.8.9-11.15.1.1722", + "1.8.9-OptiFine_HD_U_M5", + "1.12.2-forge1.12.2-14.23.4.2705", + "1.12.2-forge1.12.2-14.23.5.2847", + "1.12.2-forge-14.23.5.2854", + "1.12.2-forge-14.23.5.2860", + "1.12.2-OptiFine_HD_U_G5", + "1.16.1-forge-32.0.47", + "1.16.5-forge-36.2.0", + "1.16.5-forge-36.2.34", + "1.16.5-OptiFine_HD_U_G8", + "1.17.1-forge-37.0.48", + "1.17.1-forge-37.0.75", + "1.17.1-OptiFine_HD_U_G9", + "1.18.1-forge-39.0.5", + "1.18.1-forge-39.1.2", + "1.18.1-OptiFine_HD_U_H4", + "1.18.2-forge-40.0.41", + "1.19.4-forge-45.1.0", + "1.20.1-forge-47.1.0", + "1.20.2-forge-48.0.40", + "1.20.4-forge-49.0.31", + "3D Shareware v1.34", + "fabric-loader-0.13.3-1.18.2", + "neoforge-20.4.196" }); return; } diff --git a/examples/winform/MainForm.Designer.cs b/examples/winform/MainForm.Designer.cs index eab12f8..ebdfeb2 100644 --- a/examples/winform/MainForm.Designer.cs +++ b/examples/winform/MainForm.Designer.cs @@ -28,686 +28,676 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.cbFullscreen = new System.Windows.Forms.CheckBox(); - this.btnAutoRamSet = new System.Windows.Forms.Button(); - this.Txt_DockIcon = new System.Windows.Forms.TextBox(); - this.txtXms = new System.Windows.Forms.TextBox(); - this.label17 = new System.Windows.Forms.Label(); - this.Txt_DockName = new System.Windows.Forms.TextBox(); - this.label21 = new System.Windows.Forms.Label(); - this.label18 = new System.Windows.Forms.Label(); - this.Txt_GLauncherVersion = new System.Windows.Forms.TextBox(); - this.label16 = new System.Windows.Forms.Label(); - this.Txt_GLauncherName = new System.Windows.Forms.TextBox(); - this.label15 = new System.Windows.Forms.Label(); - this.Txt_ServerPort = new System.Windows.Forms.TextBox(); - this.TxtXmx = new System.Windows.Forms.TextBox(); - this.label14 = new System.Windows.Forms.Label(); - this.Txt_JavaArgs = new System.Windows.Forms.TextBox(); - this.Xmx_RAM = new System.Windows.Forms.Label(); - this.Txt_ScHt = new System.Windows.Forms.TextBox(); - this.Txt_ScWd = new System.Windows.Forms.TextBox(); - this.label11 = new System.Windows.Forms.Label(); - this.label10 = new System.Windows.Forms.Label(); - this.label9 = new System.Windows.Forms.Label(); - this.Txt_ServerIp = new System.Windows.Forms.TextBox(); - this.Txt_VersionType = new System.Windows.Forms.TextBox(); - this.label8 = new System.Windows.Forms.Label(); - this.label7 = new System.Windows.Forms.Label(); - this.Pb_Progress = new System.Windows.Forms.ProgressBar(); - this.Pb_File = new System.Windows.Forms.ProgressBar(); - this.Lv_Status = new System.Windows.Forms.Label(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.btnChangeJava = new System.Windows.Forms.Button(); - this.lbJavaPath = new System.Windows.Forms.Label(); - this.lbUsername = new System.Windows.Forms.Label(); - this.label6 = new System.Windows.Forms.Label(); - this.btnChangePath = new System.Windows.Forms.Button(); - this.txtPath = new System.Windows.Forms.TextBox(); - this.label4 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.btnLaunch = new System.Windows.Forms.Button(); - this.cbVersion = new System.Windows.Forms.ComboBox(); - this.label1 = new System.Windows.Forms.Label(); - this.label12 = new System.Windows.Forms.Label(); - this.btnGithub = new System.Windows.Forms.Button(); - this.btnWiki = new System.Windows.Forms.Button(); - this.btnChangelog = new System.Windows.Forms.Button(); - this.rbSequenceDownload = new System.Windows.Forms.RadioButton(); - this.rbParallelDownload = new System.Windows.Forms.RadioButton(); - this.groupBox3 = new System.Windows.Forms.GroupBox(); - this.cbSkipHashCheck = new System.Windows.Forms.CheckBox(); - this.cbSkipAssetsDownload = new System.Windows.Forms.CheckBox(); - this.groupBox4 = new System.Windows.Forms.GroupBox(); - this.btnSortFilter = new System.Windows.Forms.Button(); - this.btnRefreshVersion = new System.Windows.Forms.Button(); - this.btnSetLastVersion = new System.Windows.Forms.Button(); - this.btnOptions = new System.Windows.Forms.Button(); - this.lbLibraryVersion = new System.Windows.Forms.Label(); - this.groupBox2.SuspendLayout(); - this.groupBox1.SuspendLayout(); - this.groupBox3.SuspendLayout(); - this.groupBox4.SuspendLayout(); - this.SuspendLayout(); + groupBox2 = new GroupBox(); + cbFullscreen = new CheckBox(); + btnAutoRamSet = new Button(); + Txt_DockIcon = new TextBox(); + txtXms = new TextBox(); + label17 = new Label(); + Txt_DockName = new TextBox(); + label21 = new Label(); + label18 = new Label(); + Txt_GLauncherVersion = new TextBox(); + label16 = new Label(); + Txt_GLauncherName = new TextBox(); + label15 = new Label(); + Txt_ServerPort = new TextBox(); + TxtXmx = new TextBox(); + label14 = new Label(); + Txt_JavaArgs = new TextBox(); + Xmx_RAM = new Label(); + Txt_ScHt = new TextBox(); + Txt_ScWd = new TextBox(); + label11 = new Label(); + label10 = new Label(); + label9 = new Label(); + Txt_ServerIp = new TextBox(); + Txt_VersionType = new TextBox(); + label8 = new Label(); + label7 = new Label(); + Pb_Progress = new ProgressBar(); + Lv_Status = new Label(); + groupBox1 = new GroupBox(); + btnChangeJava = new Button(); + lbJavaPath = new Label(); + lbUsername = new Label(); + label6 = new Label(); + btnChangePath = new Button(); + txtPath = new TextBox(); + label4 = new Label(); + label2 = new Label(); + btnLaunch = new Button(); + cbVersion = new ComboBox(); + label1 = new Label(); + label12 = new Label(); + btnGithub = new Button(); + btnWiki = new Button(); + btnChangelog = new Button(); + rbSequenceDownload = new RadioButton(); + rbParallelDownload = new RadioButton(); + groupBox3 = new GroupBox(); + cbSkipHashCheck = new CheckBox(); + cbSkipAssetsDownload = new CheckBox(); + groupBox4 = new GroupBox(); + btnSortFilter = new Button(); + btnRefreshVersion = new Button(); + btnSetLastVersion = new Button(); + btnOptions = new Button(); + lbLibraryVersion = new Label(); + groupBox2.SuspendLayout(); + groupBox1.SuspendLayout(); + groupBox3.SuspendLayout(); + groupBox4.SuspendLayout(); + SuspendLayout(); // // groupBox2 // - this.groupBox2.Controls.Add(this.cbFullscreen); - this.groupBox2.Controls.Add(this.btnAutoRamSet); - this.groupBox2.Controls.Add(this.Txt_DockIcon); - this.groupBox2.Controls.Add(this.txtXms); - this.groupBox2.Controls.Add(this.label17); - this.groupBox2.Controls.Add(this.Txt_DockName); - this.groupBox2.Controls.Add(this.label21); - this.groupBox2.Controls.Add(this.label18); - this.groupBox2.Controls.Add(this.Txt_GLauncherVersion); - this.groupBox2.Controls.Add(this.label16); - this.groupBox2.Controls.Add(this.Txt_GLauncherName); - this.groupBox2.Controls.Add(this.label15); - this.groupBox2.Controls.Add(this.Txt_ServerPort); - this.groupBox2.Controls.Add(this.TxtXmx); - this.groupBox2.Controls.Add(this.label14); - this.groupBox2.Controls.Add(this.Txt_JavaArgs); - this.groupBox2.Controls.Add(this.Xmx_RAM); - this.groupBox2.Controls.Add(this.Txt_ScHt); - this.groupBox2.Controls.Add(this.Txt_ScWd); - this.groupBox2.Controls.Add(this.label11); - this.groupBox2.Controls.Add(this.label10); - this.groupBox2.Controls.Add(this.label9); - this.groupBox2.Controls.Add(this.Txt_ServerIp); - this.groupBox2.Controls.Add(this.Txt_VersionType); - this.groupBox2.Controls.Add(this.label8); - this.groupBox2.Controls.Add(this.label7); - this.groupBox2.Location = new System.Drawing.Point(463, 15); - this.groupBox2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox2.Size = new System.Drawing.Size(440, 447); - this.groupBox2.TabIndex = 20; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Options (Empty textbox means using default option)"; + groupBox2.Controls.Add(cbFullscreen); + groupBox2.Controls.Add(btnAutoRamSet); + groupBox2.Controls.Add(Txt_DockIcon); + groupBox2.Controls.Add(txtXms); + groupBox2.Controls.Add(label17); + groupBox2.Controls.Add(Txt_DockName); + groupBox2.Controls.Add(label21); + groupBox2.Controls.Add(label18); + groupBox2.Controls.Add(Txt_GLauncherVersion); + groupBox2.Controls.Add(label16); + groupBox2.Controls.Add(Txt_GLauncherName); + groupBox2.Controls.Add(label15); + groupBox2.Controls.Add(Txt_ServerPort); + groupBox2.Controls.Add(TxtXmx); + groupBox2.Controls.Add(label14); + groupBox2.Controls.Add(Txt_JavaArgs); + groupBox2.Controls.Add(Xmx_RAM); + groupBox2.Controls.Add(Txt_ScHt); + groupBox2.Controls.Add(Txt_ScWd); + groupBox2.Controls.Add(label11); + groupBox2.Controls.Add(label10); + groupBox2.Controls.Add(label9); + groupBox2.Controls.Add(Txt_ServerIp); + groupBox2.Controls.Add(Txt_VersionType); + groupBox2.Controls.Add(label8); + groupBox2.Controls.Add(label7); + groupBox2.Location = new Point(405, 15); + groupBox2.Margin = new Padding(3, 4, 3, 4); + groupBox2.Name = "groupBox2"; + groupBox2.Padding = new Padding(3, 4, 3, 4); + groupBox2.Size = new Size(385, 447); + groupBox2.TabIndex = 20; + groupBox2.TabStop = false; + groupBox2.Text = "Options (Empty textbox means using default option)"; // // cbFullscreen // - this.cbFullscreen.AutoSize = true; - this.cbFullscreen.Location = new System.Drawing.Point(152, 359); - this.cbFullscreen.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.cbFullscreen.Name = "cbFullscreen"; - this.cbFullscreen.Size = new System.Drawing.Size(95, 19); - this.cbFullscreen.TabIndex = 25; - this.cbFullscreen.Text = "Fullscreen"; - this.cbFullscreen.UseVisualStyleBackColor = true; + cbFullscreen.AutoSize = true; + cbFullscreen.Location = new Point(133, 359); + cbFullscreen.Margin = new Padding(3, 4, 3, 4); + cbFullscreen.Name = "cbFullscreen"; + cbFullscreen.Size = new Size(79, 19); + cbFullscreen.TabIndex = 25; + cbFullscreen.Text = "Fullscreen"; + cbFullscreen.UseVisualStyleBackColor = true; // // btnAutoRamSet // - this.btnAutoRamSet.Location = new System.Drawing.Point(337, 400); - this.btnAutoRamSet.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnAutoRamSet.Name = "btnAutoRamSet"; - this.btnAutoRamSet.Size = new System.Drawing.Size(86, 29); - this.btnAutoRamSet.TabIndex = 24; - this.btnAutoRamSet.Text = "Auto Set"; - this.btnAutoRamSet.UseVisualStyleBackColor = true; - this.btnAutoRamSet.Click += new System.EventHandler(this.btnAutoRamSet_Click); + btnAutoRamSet.Location = new Point(295, 400); + btnAutoRamSet.Margin = new Padding(3, 4, 3, 4); + btnAutoRamSet.Name = "btnAutoRamSet"; + btnAutoRamSet.Size = new Size(75, 29); + btnAutoRamSet.TabIndex = 24; + btnAutoRamSet.Text = "Auto Set"; + btnAutoRamSet.UseVisualStyleBackColor = true; + btnAutoRamSet.Click += btnAutoRamSet_Click; // // Txt_DockIcon // - this.Txt_DockIcon.Location = new System.Drawing.Point(152, 332); - this.Txt_DockIcon.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_DockIcon.Name = "Txt_DockIcon"; - this.Txt_DockIcon.Size = new System.Drawing.Size(255, 25); - this.Txt_DockIcon.TabIndex = 17; + Txt_DockIcon.Location = new Point(133, 332); + Txt_DockIcon.Margin = new Padding(3, 4, 3, 4); + Txt_DockIcon.Name = "Txt_DockIcon"; + Txt_DockIcon.Size = new Size(224, 23); + Txt_DockIcon.TabIndex = 17; // // txtXms // - this.txtXms.Location = new System.Drawing.Point(119, 382); - this.txtXms.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.txtXms.Name = "txtXms"; - this.txtXms.Size = new System.Drawing.Size(207, 25); - this.txtXms.TabIndex = 23; + txtXms.Location = new Point(104, 382); + txtXms.Margin = new Padding(3, 4, 3, 4); + txtXms.Name = "txtXms"; + txtXms.Size = new Size(182, 23); + txtXms.TabIndex = 23; // // label17 // - this.label17.AutoSize = true; - this.label17.Location = new System.Drawing.Point(65, 336); - this.label17.Name = "label17"; - this.label17.Size = new System.Drawing.Size(84, 15); - this.label17.TabIndex = 16; - this.label17.Text = "DockIcon : "; + label17.AutoSize = true; + label17.Location = new Point(57, 336); + label17.Name = "label17"; + label17.Size = new Size(66, 15); + label17.TabIndex = 16; + label17.Text = "DockIcon : "; // // Txt_DockName // - this.Txt_DockName.Location = new System.Drawing.Point(152, 299); - this.Txt_DockName.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_DockName.Name = "Txt_DockName"; - this.Txt_DockName.Size = new System.Drawing.Size(255, 25); - this.Txt_DockName.TabIndex = 15; + Txt_DockName.Location = new Point(133, 299); + Txt_DockName.Margin = new Padding(3, 4, 3, 4); + Txt_DockName.Name = "Txt_DockName"; + Txt_DockName.Size = new Size(224, 23); + Txt_DockName.TabIndex = 15; // // label21 // - this.label21.AutoSize = true; - this.label21.Location = new System.Drawing.Point(11, 388); - this.label21.Name = "label21"; - this.label21.Size = new System.Drawing.Size(105, 15); - this.label21.TabIndex = 22; - this.label21.Text = "Xms(MinMb) : "; + label21.AutoSize = true; + label21.Location = new Point(10, 388); + label21.Name = "label21"; + label21.Size = new Size(86, 15); + label21.TabIndex = 22; + label21.Text = "Xms(MinMb) : "; // // label18 // - this.label18.AutoSize = true; - this.label18.Location = new System.Drawing.Point(54, 302); - this.label18.Name = "label18"; - this.label18.Size = new System.Drawing.Size(92, 15); - this.label18.TabIndex = 14; - this.label18.Text = "DockName : "; + label18.AutoSize = true; + label18.Location = new Point(47, 302); + label18.Name = "label18"; + label18.Size = new Size(75, 15); + label18.TabIndex = 14; + label18.Text = "DockName : "; // // Txt_GLauncherVersion // - this.Txt_GLauncherVersion.Location = new System.Drawing.Point(152, 265); - this.Txt_GLauncherVersion.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_GLauncherVersion.Name = "Txt_GLauncherVersion"; - this.Txt_GLauncherVersion.Size = new System.Drawing.Size(255, 25); - this.Txt_GLauncherVersion.TabIndex = 13; + Txt_GLauncherVersion.Location = new Point(133, 265); + Txt_GLauncherVersion.Margin = new Padding(3, 4, 3, 4); + Txt_GLauncherVersion.Name = "Txt_GLauncherVersion"; + Txt_GLauncherVersion.Size = new Size(224, 23); + Txt_GLauncherVersion.TabIndex = 13; // // label16 // - this.label16.AutoSize = true; - this.label16.Location = new System.Drawing.Point(7, 269); - this.label16.Name = "label16"; - this.label16.Size = new System.Drawing.Size(141, 15); - this.label16.TabIndex = 12; - this.label16.Text = "GLauncherVersion : "; + label16.AutoSize = true; + label16.Location = new Point(6, 269); + label16.Name = "label16"; + label16.Size = new Size(111, 15); + label16.TabIndex = 12; + label16.Text = "GLauncherVersion : "; // // Txt_GLauncherName // - this.Txt_GLauncherName.Location = new System.Drawing.Point(152, 231); - this.Txt_GLauncherName.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_GLauncherName.Name = "Txt_GLauncherName"; - this.Txt_GLauncherName.Size = new System.Drawing.Size(255, 25); - this.Txt_GLauncherName.TabIndex = 11; + Txt_GLauncherName.Location = new Point(133, 231); + Txt_GLauncherName.Margin = new Padding(3, 4, 3, 4); + Txt_GLauncherName.Name = "Txt_GLauncherName"; + Txt_GLauncherName.Size = new Size(224, 23); + Txt_GLauncherName.TabIndex = 11; // // label15 // - this.label15.AutoSize = true; - this.label15.Location = new System.Drawing.Point(16, 235); - this.label15.Name = "label15"; - this.label15.Size = new System.Drawing.Size(129, 15); - this.label15.TabIndex = 10; - this.label15.Text = "GLauncherName : "; + label15.AutoSize = true; + label15.Location = new Point(14, 235); + label15.Name = "label15"; + label15.Size = new Size(105, 15); + label15.TabIndex = 10; + label15.Text = "GLauncherName : "; // // Txt_ServerPort // - this.Txt_ServerPort.Location = new System.Drawing.Point(152, 61); - this.Txt_ServerPort.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_ServerPort.Name = "Txt_ServerPort"; - this.Txt_ServerPort.Size = new System.Drawing.Size(255, 25); - this.Txt_ServerPort.TabIndex = 9; + Txt_ServerPort.Location = new Point(133, 61); + Txt_ServerPort.Margin = new Padding(3, 4, 3, 4); + Txt_ServerPort.Name = "Txt_ServerPort"; + Txt_ServerPort.Size = new Size(224, 23); + Txt_ServerPort.TabIndex = 9; // // TxtXmx // - this.TxtXmx.Location = new System.Drawing.Point(119, 416); - this.TxtXmx.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.TxtXmx.Name = "TxtXmx"; - this.TxtXmx.Size = new System.Drawing.Size(207, 25); - this.TxtXmx.TabIndex = 11; - this.TxtXmx.Text = "1024"; + TxtXmx.Location = new Point(104, 416); + TxtXmx.Margin = new Padding(3, 4, 3, 4); + TxtXmx.Name = "TxtXmx"; + TxtXmx.Size = new Size(182, 23); + TxtXmx.TabIndex = 11; + TxtXmx.Text = "1024"; // // label14 // - this.label14.AutoSize = true; - this.label14.Location = new System.Drawing.Point(55, 65); - this.label14.Name = "label14"; - this.label14.Size = new System.Drawing.Size(96, 15); - this.label14.TabIndex = 8; - this.label14.Text = "Server Port : "; + label14.AutoSize = true; + label14.Location = new Point(48, 65); + label14.Name = "label14"; + label14.Size = new Size(73, 15); + label14.TabIndex = 8; + label14.Text = "Server Port : "; // // Txt_JavaArgs // - this.Txt_JavaArgs.Location = new System.Drawing.Point(152, 164); - this.Txt_JavaArgs.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_JavaArgs.Name = "Txt_JavaArgs"; - this.Txt_JavaArgs.Size = new System.Drawing.Size(255, 25); - this.Txt_JavaArgs.TabIndex = 7; + Txt_JavaArgs.Location = new Point(133, 164); + Txt_JavaArgs.Margin = new Padding(3, 4, 3, 4); + Txt_JavaArgs.Name = "Txt_JavaArgs"; + Txt_JavaArgs.Size = new Size(224, 23); + Txt_JavaArgs.TabIndex = 7; // // Xmx_RAM // - this.Xmx_RAM.AutoSize = true; - this.Xmx_RAM.Location = new System.Drawing.Point(7, 419); - this.Xmx_RAM.Name = "Xmx_RAM"; - this.Xmx_RAM.Size = new System.Drawing.Size(112, 15); - this.Xmx_RAM.TabIndex = 10; - this.Xmx_RAM.Text = "Xmx(MaxMb) : "; + Xmx_RAM.AutoSize = true; + Xmx_RAM.Location = new Point(6, 419); + Xmx_RAM.Name = "Xmx_RAM"; + Xmx_RAM.Size = new Size(89, 15); + Xmx_RAM.TabIndex = 10; + Xmx_RAM.Text = "Xmx(MaxMb) : "; // // Txt_ScHt // - this.Txt_ScHt.Location = new System.Drawing.Point(152, 130); - this.Txt_ScHt.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_ScHt.Name = "Txt_ScHt"; - this.Txt_ScHt.Size = new System.Drawing.Size(255, 25); - this.Txt_ScHt.TabIndex = 6; + Txt_ScHt.Location = new Point(133, 130); + Txt_ScHt.Margin = new Padding(3, 4, 3, 4); + Txt_ScHt.Name = "Txt_ScHt"; + Txt_ScHt.Size = new Size(224, 23); + Txt_ScHt.TabIndex = 6; // // Txt_ScWd // - this.Txt_ScWd.Location = new System.Drawing.Point(152, 96); - this.Txt_ScWd.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_ScWd.Name = "Txt_ScWd"; - this.Txt_ScWd.Size = new System.Drawing.Size(255, 25); - this.Txt_ScWd.TabIndex = 5; + Txt_ScWd.Location = new Point(133, 96); + Txt_ScWd.Margin = new Padding(3, 4, 3, 4); + Txt_ScWd.Name = "Txt_ScWd"; + Txt_ScWd.Size = new Size(224, 23); + Txt_ScWd.TabIndex = 5; // // label11 // - this.label11.AutoSize = true; - this.label11.Location = new System.Drawing.Point(22, 168); - this.label11.Name = "label11"; - this.label11.Size = new System.Drawing.Size(121, 15); - this.label11.TabIndex = 4; - this.label11.Text = "JVM Arguments : "; + label11.AutoSize = true; + label11.Location = new Point(19, 168); + label11.Name = "label11"; + label11.Size = new Size(100, 15); + label11.TabIndex = 4; + label11.Text = "JVM Arguments : "; // // label10 // - this.label10.AutoSize = true; - this.label10.Location = new System.Drawing.Point(35, 134); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(114, 15); - this.label10.TabIndex = 3; - this.label10.Text = "Screen Height : "; + label10.AutoSize = true; + label10.Location = new Point(31, 134); + label10.Name = "label10"; + label10.Size = new Size(90, 15); + label10.TabIndex = 3; + label10.Text = "Screen Height : "; // // label9 // - this.label9.AutoSize = true; - this.label9.Location = new System.Drawing.Point(41, 100); - this.label9.Name = "label9"; - this.label9.Size = new System.Drawing.Size(110, 15); - this.label9.TabIndex = 2; - this.label9.Text = "Screen Width : "; + label9.AutoSize = true; + label9.Location = new Point(36, 100); + label9.Name = "label9"; + label9.Size = new Size(86, 15); + label9.TabIndex = 2; + label9.Text = "Screen Width : "; // // Txt_ServerIp // - this.Txt_ServerIp.Location = new System.Drawing.Point(152, 28); - this.Txt_ServerIp.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_ServerIp.Name = "Txt_ServerIp"; - this.Txt_ServerIp.Size = new System.Drawing.Size(255, 25); - this.Txt_ServerIp.TabIndex = 1; + Txt_ServerIp.Location = new Point(133, 28); + Txt_ServerIp.Margin = new Padding(3, 4, 3, 4); + Txt_ServerIp.Name = "Txt_ServerIp"; + Txt_ServerIp.Size = new Size(224, 23); + Txt_ServerIp.TabIndex = 1; // // Txt_VersionType // - this.Txt_VersionType.Location = new System.Drawing.Point(152, 198); - this.Txt_VersionType.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Txt_VersionType.Name = "Txt_VersionType"; - this.Txt_VersionType.Size = new System.Drawing.Size(255, 25); - this.Txt_VersionType.TabIndex = 1; + Txt_VersionType.Location = new Point(133, 198); + Txt_VersionType.Margin = new Padding(3, 4, 3, 4); + Txt_VersionType.Name = "Txt_VersionType"; + Txt_VersionType.Size = new Size(224, 23); + Txt_VersionType.TabIndex = 1; // // label8 // - this.label8.AutoSize = true; - this.label8.Location = new System.Drawing.Point(67, 31); - this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(82, 15); - this.label8.TabIndex = 0; - this.label8.Text = "Server IP : "; + label8.AutoSize = true; + label8.Location = new Point(59, 31); + label8.Name = "label8"; + label8.Size = new Size(61, 15); + label8.TabIndex = 0; + label8.Text = "Server IP : "; // // label7 // - this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(43, 201); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(102, 15); - this.label7.TabIndex = 0; - this.label7.Text = "VersionType : "; + label7.AutoSize = true; + label7.Location = new Point(38, 201); + label7.Name = "label7"; + label7.Size = new Size(78, 15); + label7.TabIndex = 0; + label7.Text = "VersionType : "; // // Pb_Progress // - this.Pb_Progress.Location = new System.Drawing.Point(16, 521); - this.Pb_Progress.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Pb_Progress.Name = "Pb_Progress"; - this.Pb_Progress.Size = new System.Drawing.Size(887, 29); - this.Pb_Progress.TabIndex = 19; - // - // Pb_File - // - this.Pb_File.Location = new System.Drawing.Point(16, 485); - this.Pb_File.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Pb_File.Name = "Pb_File"; - this.Pb_File.Size = new System.Drawing.Size(887, 29); - this.Pb_File.TabIndex = 18; + Pb_Progress.Location = new Point(14, 489); + Pb_Progress.Margin = new Padding(3, 4, 3, 4); + Pb_Progress.Name = "Pb_Progress"; + Pb_Progress.Size = new Size(776, 29); + Pb_Progress.TabIndex = 19; // // Lv_Status // - this.Lv_Status.AutoSize = true; - this.Lv_Status.Location = new System.Drawing.Point(14, 466); - this.Lv_Status.Name = "Lv_Status"; - this.Lv_Status.Size = new System.Drawing.Size(49, 15); - this.Lv_Status.TabIndex = 17; - this.Lv_Status.Text = "Ready"; + Lv_Status.AutoSize = true; + Lv_Status.Location = new Point(12, 466); + Lv_Status.Name = "Lv_Status"; + Lv_Status.Size = new Size(39, 15); + Lv_Status.TabIndex = 17; + Lv_Status.Text = "Ready"; // // groupBox1 // - this.groupBox1.Controls.Add(this.btnChangeJava); - this.groupBox1.Controls.Add(this.lbJavaPath); - this.groupBox1.Controls.Add(this.lbUsername); - this.groupBox1.Controls.Add(this.label6); - this.groupBox1.Controls.Add(this.btnChangePath); - this.groupBox1.Controls.Add(this.txtPath); - this.groupBox1.Controls.Add(this.label4); - this.groupBox1.Controls.Add(this.label2); - this.groupBox1.Location = new System.Drawing.Point(16, 15); - this.groupBox1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox1.Size = new System.Drawing.Size(440, 145); - this.groupBox1.TabIndex = 16; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "CmlLib Sample Launcher"; + groupBox1.Controls.Add(btnChangeJava); + groupBox1.Controls.Add(lbJavaPath); + groupBox1.Controls.Add(lbUsername); + groupBox1.Controls.Add(label6); + groupBox1.Controls.Add(btnChangePath); + groupBox1.Controls.Add(txtPath); + groupBox1.Controls.Add(label4); + groupBox1.Controls.Add(label2); + groupBox1.Location = new Point(14, 15); + groupBox1.Margin = new Padding(3, 4, 3, 4); + groupBox1.Name = "groupBox1"; + groupBox1.Padding = new Padding(3, 4, 3, 4); + groupBox1.Size = new Size(385, 145); + groupBox1.TabIndex = 16; + groupBox1.TabStop = false; + groupBox1.Text = "CmlLib Sample Launcher"; // // btnChangeJava // - this.btnChangeJava.Location = new System.Drawing.Point(362, 112); - this.btnChangeJava.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnChangeJava.Name = "btnChangeJava"; - this.btnChangeJava.Size = new System.Drawing.Size(66, 29); - this.btnChangeJava.TabIndex = 21; - this.btnChangeJava.Text = "Change"; - this.btnChangeJava.UseVisualStyleBackColor = true; - this.btnChangeJava.Click += new System.EventHandler(this.btnChangeJava_Click); + btnChangeJava.Location = new Point(317, 112); + btnChangeJava.Margin = new Padding(3, 4, 3, 4); + btnChangeJava.Name = "btnChangeJava"; + btnChangeJava.Size = new Size(58, 29); + btnChangeJava.TabIndex = 21; + btnChangeJava.Text = "Change"; + btnChangeJava.UseVisualStyleBackColor = true; + btnChangeJava.Click += btnChangeJava_Click; // // lbJavaPath // - this.lbJavaPath.AutoSize = true; - this.lbJavaPath.Location = new System.Drawing.Point(101, 115); - this.lbJavaPath.Name = "lbJavaPath"; - this.lbJavaPath.Size = new System.Drawing.Size(113, 15); - this.lbJavaPath.TabIndex = 20; - this.lbJavaPath.Text = "Use default java"; + lbJavaPath.AutoSize = true; + lbJavaPath.Location = new Point(88, 115); + lbJavaPath.Name = "lbJavaPath"; + lbJavaPath.Size = new Size(90, 15); + lbJavaPath.TabIndex = 20; + lbJavaPath.Text = "Use default java"; // // lbUsername // - this.lbUsername.AutoSize = true; - this.lbUsername.Location = new System.Drawing.Point(101, 84); - this.lbUsername.Name = "lbUsername"; - this.lbUsername.Size = new System.Drawing.Size(67, 15); - this.lbUsername.TabIndex = 18; - this.lbUsername.Text = "test_user"; + lbUsername.AutoSize = true; + lbUsername.Location = new Point(88, 84); + lbUsername.Name = "lbUsername"; + lbUsername.Size = new Size(53, 15); + lbUsername.TabIndex = 18; + lbUsername.Text = "test_user"; // // label6 // - this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(47, 115); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(52, 15); - this.label6.TabIndex = 12; - this.label6.Text = "Java : "; + label6.AutoSize = true; + label6.Location = new Point(41, 115); + label6.Name = "label6"; + label6.Size = new Size(38, 15); + label6.TabIndex = 12; + label6.Text = "Java : "; // // btnChangePath // - this.btnChangePath.Location = new System.Drawing.Point(362, 45); - this.btnChangePath.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnChangePath.Name = "btnChangePath"; - this.btnChangePath.Size = new System.Drawing.Size(66, 29); - this.btnChangePath.TabIndex = 9; - this.btnChangePath.Text = "Change"; - this.btnChangePath.UseVisualStyleBackColor = true; - this.btnChangePath.Click += new System.EventHandler(this.btnChangePath_Click); + btnChangePath.Location = new Point(317, 45); + btnChangePath.Margin = new Padding(3, 4, 3, 4); + btnChangePath.Name = "btnChangePath"; + btnChangePath.Size = new Size(58, 29); + btnChangePath.TabIndex = 9; + btnChangePath.Text = "Change"; + btnChangePath.UseVisualStyleBackColor = true; + btnChangePath.Click += btnChangePath_Click; // // txtPath // - this.txtPath.Location = new System.Drawing.Point(19, 46); - this.txtPath.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.txtPath.Name = "txtPath"; - this.txtPath.ReadOnly = true; - this.txtPath.Size = new System.Drawing.Size(335, 25); - this.txtPath.TabIndex = 8; + txtPath.Location = new Point(17, 46); + txtPath.Margin = new Padding(3, 4, 3, 4); + txtPath.Name = "txtPath"; + txtPath.ReadOnly = true; + txtPath.Size = new Size(294, 23); + txtPath.TabIndex = 8; // // label4 // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(17, 28); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(95, 15); - this.label4.TabIndex = 7; - this.label4.Text = "Game Path : "; + label4.AutoSize = true; + label4.Location = new Point(15, 28); + label4.Name = "label4"; + label4.Size = new Size(74, 15); + label4.TabIndex = 7; + label4.Text = "Game Path : "; // // label2 // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(24, 84); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(76, 15); - this.label2.TabIndex = 3; - this.label2.Text = "Account : "; + label2.AutoSize = true; + label2.Location = new Point(21, 84); + label2.Name = "label2"; + label2.Size = new Size(61, 15); + label2.TabIndex = 3; + label2.Text = "Account : "; // // btnLaunch // - this.btnLaunch.Location = new System.Drawing.Point(32, 102); - this.btnLaunch.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnLaunch.Name = "btnLaunch"; - this.btnLaunch.Size = new System.Drawing.Size(377, 69); - this.btnLaunch.TabIndex = 2; - this.btnLaunch.Text = "Download and Launch"; - this.btnLaunch.UseVisualStyleBackColor = true; - this.btnLaunch.Click += new System.EventHandler(this.Btn_Launch_Click); + btnLaunch.Location = new Point(28, 102); + btnLaunch.Margin = new Padding(3, 4, 3, 4); + btnLaunch.Name = "btnLaunch"; + btnLaunch.Size = new Size(330, 69); + btnLaunch.TabIndex = 2; + btnLaunch.Text = "Download and Launch"; + btnLaunch.UseVisualStyleBackColor = true; + btnLaunch.Click += Btn_Launch_Click; // // cbVersion // - this.cbVersion.FormattingEnabled = true; - this.cbVersion.Location = new System.Drawing.Point(105, 34); - this.cbVersion.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.cbVersion.Name = "cbVersion"; - this.cbVersion.Size = new System.Drawing.Size(207, 23); - this.cbVersion.TabIndex = 1; + cbVersion.FormattingEnabled = true; + cbVersion.Location = new Point(92, 34); + cbVersion.Margin = new Padding(3, 4, 3, 4); + cbVersion.Name = "cbVersion"; + cbVersion.Size = new Size(182, 23); + cbVersion.TabIndex = 1; // // label1 // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(30, 38); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(70, 15); - this.label1.TabIndex = 0; - this.label1.Text = "Version : "; + label1.AutoSize = true; + label1.Location = new Point(26, 38); + label1.Name = "label1"; + label1.Size = new Size(54, 15); + label1.TabIndex = 0; + label1.Text = "Version : "; // // label12 // - this.label12.AutoSize = true; - this.label12.Location = new System.Drawing.Point(485, 559); - this.label12.Name = "label12"; - this.label12.Size = new System.Drawing.Size(242, 15); - this.label12.TabIndex = 23; - this.label12.Text = "AlphaBs (ksi123456ab@naver.com)"; + label12.AutoSize = true; + label12.Location = new Point(424, 525); + label12.Name = "label12"; + label12.Size = new Size(191, 15); + label12.TabIndex = 23; + label12.Text = "AlphaBs (ksi123456ab@naver.com)"; // // btnGithub // - this.btnGithub.Location = new System.Drawing.Point(725, 558); - this.btnGithub.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnGithub.Name = "btnGithub"; - this.btnGithub.Size = new System.Drawing.Size(86, 29); - this.btnGithub.TabIndex = 24; - this.btnGithub.Text = "GitHub"; - this.btnGithub.UseVisualStyleBackColor = true; - this.btnGithub.Click += new System.EventHandler(this.btnGithub_Click); + btnGithub.Location = new Point(634, 524); + btnGithub.Margin = new Padding(3, 4, 3, 4); + btnGithub.Name = "btnGithub"; + btnGithub.Size = new Size(75, 29); + btnGithub.TabIndex = 24; + btnGithub.Text = "GitHub"; + btnGithub.UseVisualStyleBackColor = true; + btnGithub.Click += btnGithub_Click; // // btnWiki // - this.btnWiki.Location = new System.Drawing.Point(817, 558); - this.btnWiki.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnWiki.Name = "btnWiki"; - this.btnWiki.Size = new System.Drawing.Size(86, 29); - this.btnWiki.TabIndex = 25; - this.btnWiki.Text = "Wiki"; - this.btnWiki.UseVisualStyleBackColor = true; - this.btnWiki.Click += new System.EventHandler(this.btnWiki_Click); + btnWiki.Location = new Point(715, 524); + btnWiki.Margin = new Padding(3, 4, 3, 4); + btnWiki.Name = "btnWiki"; + btnWiki.Size = new Size(75, 29); + btnWiki.TabIndex = 25; + btnWiki.Text = "Wiki"; + btnWiki.UseVisualStyleBackColor = true; + btnWiki.Click += btnWiki_Click; // // btnChangelog // - this.btnChangelog.Location = new System.Drawing.Point(14, 559); - this.btnChangelog.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnChangelog.Name = "btnChangelog"; - this.btnChangelog.Size = new System.Drawing.Size(145, 29); - this.btnChangelog.TabIndex = 26; - this.btnChangelog.Text = "GameChangelog"; - this.btnChangelog.UseVisualStyleBackColor = true; - this.btnChangelog.Click += new System.EventHandler(this.btnChangelog_Click); + btnChangelog.Location = new Point(12, 525); + btnChangelog.Margin = new Padding(3, 4, 3, 4); + btnChangelog.Name = "btnChangelog"; + btnChangelog.Size = new Size(127, 29); + btnChangelog.TabIndex = 26; + btnChangelog.Text = "GameChangelog"; + btnChangelog.UseVisualStyleBackColor = true; + btnChangelog.Click += btnChangelog_Click; // // rbSequenceDownload // - this.rbSequenceDownload.AutoSize = true; - this.rbSequenceDownload.Location = new System.Drawing.Point(44, 30); - this.rbSequenceDownload.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.rbSequenceDownload.Name = "rbSequenceDownload"; - this.rbSequenceDownload.Size = new System.Drawing.Size(171, 19); - this.rbSequenceDownload.TabIndex = 22; - this.rbSequenceDownload.Text = "SequenceDownloader"; - this.rbSequenceDownload.UseVisualStyleBackColor = true; + rbSequenceDownload.AutoSize = true; + rbSequenceDownload.Location = new Point(38, 30); + rbSequenceDownload.Margin = new Padding(3, 4, 3, 4); + rbSequenceDownload.Name = "rbSequenceDownload"; + rbSequenceDownload.Size = new Size(140, 19); + rbSequenceDownload.TabIndex = 22; + rbSequenceDownload.Text = "SequenceDownloader"; + rbSequenceDownload.UseVisualStyleBackColor = true; // // rbParallelDownload // - this.rbParallelDownload.AutoSize = true; - this.rbParallelDownload.Checked = true; - this.rbParallelDownload.Location = new System.Drawing.Point(221, 30); - this.rbParallelDownload.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.rbParallelDownload.Name = "rbParallelDownload"; - this.rbParallelDownload.Size = new System.Drawing.Size(193, 19); - this.rbParallelDownload.TabIndex = 23; - this.rbParallelDownload.TabStop = true; - this.rbParallelDownload.Text = "AsyncParallelDownloader"; - this.rbParallelDownload.UseVisualStyleBackColor = true; + rbParallelDownload.AutoSize = true; + rbParallelDownload.Checked = true; + rbParallelDownload.Location = new Point(193, 30); + rbParallelDownload.Margin = new Padding(3, 4, 3, 4); + rbParallelDownload.Name = "rbParallelDownload"; + rbParallelDownload.Size = new Size(159, 19); + rbParallelDownload.TabIndex = 23; + rbParallelDownload.TabStop = true; + rbParallelDownload.Text = "AsyncParallelDownloader"; + rbParallelDownload.UseVisualStyleBackColor = true; // // groupBox3 // - this.groupBox3.Controls.Add(this.cbSkipHashCheck); - this.groupBox3.Controls.Add(this.cbSkipAssetsDownload); - this.groupBox3.Controls.Add(this.rbSequenceDownload); - this.groupBox3.Controls.Add(this.rbParallelDownload); - this.groupBox3.Location = new System.Drawing.Point(16, 168); - this.groupBox3.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox3.Name = "groupBox3"; - this.groupBox3.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox3.Size = new System.Drawing.Size(440, 98); - this.groupBox3.TabIndex = 27; - this.groupBox3.TabStop = false; - this.groupBox3.Text = "Download Options"; + groupBox3.Controls.Add(cbSkipHashCheck); + groupBox3.Controls.Add(cbSkipAssetsDownload); + groupBox3.Controls.Add(rbSequenceDownload); + groupBox3.Controls.Add(rbParallelDownload); + groupBox3.Location = new Point(14, 168); + groupBox3.Margin = new Padding(3, 4, 3, 4); + groupBox3.Name = "groupBox3"; + groupBox3.Padding = new Padding(3, 4, 3, 4); + groupBox3.Size = new Size(385, 98); + groupBox3.TabIndex = 27; + groupBox3.TabStop = false; + groupBox3.Text = "Download Options"; // // cbSkipHashCheck // - this.cbSkipHashCheck.AutoSize = true; - this.cbSkipHashCheck.Location = new System.Drawing.Point(229, 57); - this.cbSkipHashCheck.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.cbSkipHashCheck.Name = "cbSkipHashCheck"; - this.cbSkipHashCheck.Size = new System.Drawing.Size(157, 19); - this.cbSkipHashCheck.TabIndex = 26; - this.cbSkipHashCheck.Text = "Skip hash checking"; - this.cbSkipHashCheck.UseVisualStyleBackColor = true; + cbSkipHashCheck.AutoSize = true; + cbSkipHashCheck.Location = new Point(200, 57); + cbSkipHashCheck.Margin = new Padding(3, 4, 3, 4); + cbSkipHashCheck.Name = "cbSkipHashCheck"; + cbSkipHashCheck.Size = new Size(127, 19); + cbSkipHashCheck.TabIndex = 26; + cbSkipHashCheck.Text = "Skip hash checking"; + cbSkipHashCheck.UseVisualStyleBackColor = true; // // cbSkipAssetsDownload // - this.cbSkipAssetsDownload.AutoSize = true; - this.cbSkipAssetsDownload.Location = new System.Drawing.Point(50, 57); - this.cbSkipAssetsDownload.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.cbSkipAssetsDownload.Name = "cbSkipAssetsDownload"; - this.cbSkipAssetsDownload.Size = new System.Drawing.Size(166, 19); - this.cbSkipAssetsDownload.TabIndex = 25; - this.cbSkipAssetsDownload.Text = "Skip asset download"; - this.cbSkipAssetsDownload.UseVisualStyleBackColor = true; + cbSkipAssetsDownload.AutoSize = true; + cbSkipAssetsDownload.Location = new Point(44, 57); + cbSkipAssetsDownload.Margin = new Padding(3, 4, 3, 4); + cbSkipAssetsDownload.Name = "cbSkipAssetsDownload"; + cbSkipAssetsDownload.Size = new Size(133, 19); + cbSkipAssetsDownload.TabIndex = 25; + cbSkipAssetsDownload.Text = "Skip asset download"; + cbSkipAssetsDownload.UseVisualStyleBackColor = true; // // groupBox4 // - this.groupBox4.Controls.Add(this.btnSortFilter); - this.groupBox4.Controls.Add(this.btnRefreshVersion); - this.groupBox4.Controls.Add(this.btnSetLastVersion); - this.groupBox4.Controls.Add(this.cbVersion); - this.groupBox4.Controls.Add(this.label1); - this.groupBox4.Controls.Add(this.btnLaunch); - this.groupBox4.Location = new System.Drawing.Point(16, 272); - this.groupBox4.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox4.Name = "groupBox4"; - this.groupBox4.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.groupBox4.Size = new System.Drawing.Size(440, 190); - this.groupBox4.TabIndex = 28; - this.groupBox4.TabStop = false; - this.groupBox4.Text = "Launch"; + groupBox4.Controls.Add(btnSortFilter); + groupBox4.Controls.Add(btnRefreshVersion); + groupBox4.Controls.Add(btnSetLastVersion); + groupBox4.Controls.Add(cbVersion); + groupBox4.Controls.Add(label1); + groupBox4.Controls.Add(btnLaunch); + groupBox4.Location = new Point(14, 272); + groupBox4.Margin = new Padding(3, 4, 3, 4); + groupBox4.Name = "groupBox4"; + groupBox4.Padding = new Padding(3, 4, 3, 4); + groupBox4.Size = new Size(385, 190); + groupBox4.TabIndex = 28; + groupBox4.TabStop = false; + groupBox4.Text = "Launch"; // // btnSortFilter // - this.btnSortFilter.Location = new System.Drawing.Point(149, 65); - this.btnSortFilter.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnSortFilter.Name = "btnSortFilter"; - this.btnSortFilter.Size = new System.Drawing.Size(163, 29); - this.btnSortFilter.TabIndex = 5; - this.btnSortFilter.Text = "Sort option"; - this.btnSortFilter.UseVisualStyleBackColor = true; - this.btnSortFilter.Click += new System.EventHandler(this.btnSortFilter_Click); + btnSortFilter.Location = new Point(130, 65); + btnSortFilter.Margin = new Padding(3, 4, 3, 4); + btnSortFilter.Name = "btnSortFilter"; + btnSortFilter.Size = new Size(143, 29); + btnSortFilter.TabIndex = 5; + btnSortFilter.Text = "Sort option"; + btnSortFilter.UseVisualStyleBackColor = true; + btnSortFilter.Click += btnSortFilter_Click; // // btnRefreshVersion // - this.btnRefreshVersion.Location = new System.Drawing.Point(323, 65); - this.btnRefreshVersion.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnRefreshVersion.Name = "btnRefreshVersion"; - this.btnRefreshVersion.Size = new System.Drawing.Size(86, 29); - this.btnRefreshVersion.TabIndex = 4; - this.btnRefreshVersion.Text = "Refresh"; - this.btnRefreshVersion.UseVisualStyleBackColor = true; - this.btnRefreshVersion.Click += new System.EventHandler(this.btnRefreshVersion_Click); + btnRefreshVersion.Location = new Point(283, 65); + btnRefreshVersion.Margin = new Padding(3, 4, 3, 4); + btnRefreshVersion.Name = "btnRefreshVersion"; + btnRefreshVersion.Size = new Size(75, 29); + btnRefreshVersion.TabIndex = 4; + btnRefreshVersion.Text = "Refresh"; + btnRefreshVersion.UseVisualStyleBackColor = true; + btnRefreshVersion.Click += btnRefreshVersion_Click; // // btnSetLastVersion // - this.btnSetLastVersion.Location = new System.Drawing.Point(323, 31); - this.btnSetLastVersion.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnSetLastVersion.Name = "btnSetLastVersion"; - this.btnSetLastVersion.Size = new System.Drawing.Size(86, 29); - this.btnSetLastVersion.TabIndex = 2; - this.btnSetLastVersion.Text = "Lastest\r\n"; - this.btnSetLastVersion.UseVisualStyleBackColor = true; - this.btnSetLastVersion.Click += new System.EventHandler(this.btnSetLastVersion_Click); + btnSetLastVersion.Location = new Point(283, 31); + btnSetLastVersion.Margin = new Padding(3, 4, 3, 4); + btnSetLastVersion.Name = "btnSetLastVersion"; + btnSetLastVersion.Size = new Size(75, 29); + btnSetLastVersion.TabIndex = 2; + btnSetLastVersion.Text = "Lastest\r\n"; + btnSetLastVersion.UseVisualStyleBackColor = true; + btnSetLastVersion.Click += btnSetLastVersion_Click; // // btnOptions // - this.btnOptions.Location = new System.Drawing.Point(318, 559); - this.btnOptions.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnOptions.Name = "btnOptions"; - this.btnOptions.Size = new System.Drawing.Size(138, 29); - this.btnOptions.TabIndex = 30; - this.btnOptions.Text = "options.txt"; - this.btnOptions.UseVisualStyleBackColor = true; - this.btnOptions.Click += new System.EventHandler(this.btnOptions_Click); + btnOptions.Location = new Point(278, 525); + btnOptions.Margin = new Padding(3, 4, 3, 4); + btnOptions.Name = "btnOptions"; + btnOptions.Size = new Size(121, 29); + btnOptions.TabIndex = 30; + btnOptions.Text = "options.txt"; + btnOptions.UseVisualStyleBackColor = true; + btnOptions.Click += btnOptions_Click; // // lbLibraryVersion // - this.lbLibraryVersion.Location = new System.Drawing.Point(485, 576); - this.lbLibraryVersion.Name = "lbLibraryVersion"; - this.lbLibraryVersion.Size = new System.Drawing.Size(234, 23); - this.lbLibraryVersion.TabIndex = 31; - this.lbLibraryVersion.Text = "CmlLib.Core"; + lbLibraryVersion.Location = new Point(424, 542); + lbLibraryVersion.Name = "lbLibraryVersion"; + lbLibraryVersion.Size = new Size(205, 23); + lbLibraryVersion.TabIndex = 31; + lbLibraryVersion.Text = "CmlLib.Core"; // // MainForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(918, 608); - this.Controls.Add(this.lbLibraryVersion); - this.Controls.Add(this.btnOptions); - this.Controls.Add(this.groupBox4); - this.Controls.Add(this.groupBox3); - this.Controls.Add(this.btnChangelog); - this.Controls.Add(this.btnWiki); - this.Controls.Add(this.btnGithub); - this.Controls.Add(this.label12); - this.Controls.Add(this.groupBox2); - this.Controls.Add(this.Pb_Progress); - this.Controls.Add(this.Pb_File); - this.Controls.Add(this.Lv_Status); - this.Controls.Add(this.groupBox1); - this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Name = "MainForm"; - this.Text = "MainForm"; - this.Shown += new System.EventHandler(this.MainForm_Shown); - this.groupBox2.ResumeLayout(false); - this.groupBox2.PerformLayout(); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.groupBox3.ResumeLayout(false); - this.groupBox3.PerformLayout(); - this.groupBox4.ResumeLayout(false); - this.groupBox4.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(803, 564); + Controls.Add(lbLibraryVersion); + Controls.Add(btnOptions); + Controls.Add(groupBox4); + Controls.Add(groupBox3); + Controls.Add(btnChangelog); + Controls.Add(btnWiki); + Controls.Add(btnGithub); + Controls.Add(label12); + Controls.Add(groupBox2); + Controls.Add(Pb_Progress); + Controls.Add(Lv_Status); + Controls.Add(groupBox1); + Margin = new Padding(3, 4, 3, 4); + Name = "MainForm"; + Text = "MainForm"; + Shown += MainForm_Shown; + groupBox2.ResumeLayout(false); + groupBox2.PerformLayout(); + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + groupBox3.ResumeLayout(false); + groupBox3.PerformLayout(); + groupBox4.ResumeLayout(false); + groupBox4.PerformLayout(); + ResumeLayout(false); + PerformLayout(); } private System.Windows.Forms.Button btnSortFilter; diff --git a/examples/winform/MainForm.cs b/examples/winform/MainForm.cs index 3a34bc9..d14976f 100644 --- a/examples/winform/MainForm.cs +++ b/examples/winform/MainForm.cs @@ -20,6 +20,7 @@ public MainForm(MSession session) InitializeComponent(); } + CancellationToken cancellationToken = default; MinecraftLauncher? launcher; string? javaPath; @@ -163,7 +164,12 @@ private async void Btn_Launch_Click(object sender, EventArgs e) //if (launcher.GameFileCheckers.LibraryFileChecker != null) // launcher.GameFileCheckers.LibraryFileChecker.CheckHash = !cbSkipHashCheck.Checked; - var process = await launcher.CreateProcessAsync(cbVersion.Text, launchOption); // Create Arguments and Process + var process = await launcher.InstallAndBuildProcessAsync( + cbVersion.Text, + launchOption, + new Progress(Launcher_FileChanged), + new Progress(Launcher_ProgressChanged), + cancellationToken); // Create Arguments and Process // process.Start(); // Just start game, or StartProcess(process); // Start Process with debug options @@ -189,30 +195,25 @@ private async void Btn_Launch_Click(object sender, EventArgs e) setUIEnabled(true); } } - - private int uiThreadId = Thread.CurrentThread.ManagedThreadId; - // Event Handler. Show download progress - private void Launcher_ProgressChanged(object sender, ProgressChangedEventArgs e) + int lastProgress = 0; + private void Launcher_ProgressChanged(ByteProgress e) { - if (Thread.CurrentThread.ManagedThreadId != uiThreadId) + var progress = (int)(e.ProgressedBytes / (double)e.TotalBytes * 100); + if (progress >= 0 && progress <= 100 && progress != lastProgress) { - Debug.WriteLine(e); + lastProgress = progress; + Pb_Progress.Value = progress; + Pb_Progress.Maximum = 100; } - Pb_Progress.Maximum = 100; - Pb_Progress.Value = e.ProgressPercentage; } private void Launcher_FileChanged(InstallerProgressChangedEventArgs e) { - if (Thread.CurrentThread.ManagedThreadId != uiThreadId) + if (e.EventType == InstallerEventType.Done) { - Debug.WriteLine(e); + Lv_Status.Text = $"[{e.ProgressedTasks}/{e.TotalTasks}] {e.Name}"; } - Pb_File.Maximum = e.TotalTasks; - Pb_File.Value = e.ProgressedTasks; - Lv_Status.Text = $"[{e.EventType}][{e.ProgressedTasks}/{e.TotalTasks}] {e.Name}"; - //Debug.WriteLine(Lv_Status.Text); } private async void btnChangePath_Click(object sender, EventArgs e) diff --git a/examples/winform/MainForm.resx b/examples/winform/MainForm.resx index 1af7de1..af32865 100644 --- a/examples/winform/MainForm.resx +++ b/examples/winform/MainForm.resx @@ -1,17 +1,17 @@  - From beb0ee55af93a451e34143636b66922b2f278848 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 19:57:53 +0900 Subject: [PATCH 123/137] fix: handle directories in zip files --- src/Internals/SharpZipWrapper.cs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Internals/SharpZipWrapper.cs b/src/Internals/SharpZipWrapper.cs index c93f3d1..54c9e85 100644 --- a/src/Internals/SharpZipWrapper.cs +++ b/src/Internals/SharpZipWrapper.cs @@ -20,21 +20,28 @@ public static void Unzip( cancellationToken.ThrowIfCancellationRequested(); var fullPath = Path.Combine(extractTo, e.Name); - IOUtil.CreateParentDirectory(fullPath); - var fileName = Path.GetFileName(fullPath); - - try - { - using var output = File.Create(fullPath); - s.CopyTo(output); - } - catch (IOException) + if (e.IsFile) { - // just skip + IOUtil.CreateParentDirectory(fullPath); + var fileName = Path.GetFileName(fullPath); + + try + { + using var output = File.Create(fullPath); + s.CopyTo(output); + } + catch (IOException) + { + // just skip + } + catch (UnauthorizedAccessException) + { + // just skip + } } - catch (UnauthorizedAccessException) + else { - // just skip + Directory.CreateDirectory(fullPath); } progress?.Report(new ByteProgress From b4b62e2f2ec581263a09ed174555064a32698b91 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 20:00:34 +0900 Subject: [PATCH 124/137] fix: prevent `minecraftArguments` from being concatenated --- .../LiteLoader/LiteLoaderVersionMetadata.cs | 4 +- src/ProcessBuilder/MinecraftProcessBuilder.cs | 7 ++- src/Version/Extensions.cs | 41 ++++++++++++++-- src/Version/IVersion.cs | 4 +- src/Version/JsonVersion.cs | 49 ++++++++++++++----- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs index 5905011..3af6918 100644 --- a/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs @@ -101,7 +101,7 @@ public async Task InstallAsync(MinecraftPath path, IVersion baseVersion) var newArguments = $"--tweakClass {tweakClass} {minecraftArguments}"; writeVersion(writer, versionName, baseVersion.Id, newArguments, null); } - else if (baseVersion.GameArguments.Any()) + else if (baseVersion.GetGameArguments(true).Any()) { var tweakArg = new MArgument[] { @@ -109,7 +109,7 @@ public async Task InstallAsync(MinecraftPath path, IVersion baseVersion) new MArgument(tweakClass!) }; - var newArguments = tweakArg.Concat(baseVersion.GameArguments); + var newArguments = tweakArg.Concat(baseVersion.GetGameArguments(true)); writeVersion(writer, versionName, baseVersion.Id, null, newArguments); } diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index 279efb8..3774e5c 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -1,4 +1,4 @@ -using CmlLib.Core.Rules; +using CmlLib.Core.Rules; using CmlLib.Core.Version; using CmlLib.Core.Internals; using System.Diagnostics; @@ -191,7 +191,7 @@ private void addJvmArguments(MinecraftArgumentBuilder builder) else { // version-specific jvm arguments - var jvmArgs = version.ConcatInheritedCollection(v => v.JvmArguments).ToList(); + var jvmArgs = version.ConcatInheritedJvmArguments().ToList(); if (jvmArgs.Any()) builder.AddArguments(jvmArgs); else @@ -241,8 +241,7 @@ private void addJvmArguments(MinecraftArgumentBuilder builder) private void addGameArguments(MinecraftArgumentBuilder builder) { // game arguments - var gameArgs = version.ConcatInheritedCollection(v => v.GameArguments); - builder.AddArguments(gameArgs); + builder.AddArguments(version.ConcatInheritedGameArguments()); // add extra game arguments builder.AddArguments(launchOption.ExtraGameArguments); diff --git a/src/Version/Extensions.cs b/src/Version/Extensions.cs index a939434..4762ed0 100644 --- a/src/Version/Extensions.cs +++ b/src/Version/Extensions.cs @@ -1,3 +1,4 @@ +using CmlLib.Core.ProcessBuilder; using CmlLib.Core.VersionMetadata; namespace CmlLib.Core.Version; @@ -11,15 +12,13 @@ public static MVersionType GetVersionType(this IVersion version) public static T? GetInheritedProperty(this IVersion self, Func prop) { - IVersion? version = self; - while (version != null) + foreach (var version in self.EnumerateToParent()) { var value = prop.Invoke(version); if (value is string valueStr && !string.IsNullOrEmpty(valueStr)) return value; else if (value != null) return value; - version = version.ParentVersion; } return default; } @@ -29,7 +28,7 @@ public static IEnumerable ConcatInheritedCollection( Func> prop, int maxDepth = 10) { - foreach (var version in enumerateFromParent(self, maxDepth)) + foreach (var version in self.EnumerateFromParent(maxDepth)) { foreach (var item in prop(version)) { @@ -37,8 +36,40 @@ public static IEnumerable ConcatInheritedCollection( } } } + + public static IEnumerable ConcatInheritedGameArguments(this IVersion self, int maxDepth = 10) + { + foreach (var version in self.EnumerateFromParent(maxDepth)) + { + foreach (var item in version.GetGameArguments(version != self)) + { + yield return item; + } + } + } + + public static IEnumerable ConcatInheritedJvmArguments(this IVersion self, int maxDepth = 10) + { + foreach (var version in self.EnumerateFromParent(maxDepth)) + { + foreach (var item in version.GetJvmArguments(version != self)) + { + yield return item; + } + } + } + + public static IEnumerable EnumerateToParent(this IVersion version) + { + IVersion? current = version; + while (current != null) + { + yield return current; + current = current.ParentVersion; + } + } - private static IEnumerable enumerateFromParent(IVersion version, int maxDepth) + public static IEnumerable EnumerateFromParent(this IVersion version, int maxDepth) { var stack = new Stack(); diff --git a/src/Version/IVersion.cs b/src/Version/IVersion.cs index b8ce684..7bb40aa 100644 --- a/src/Version/IVersion.cs +++ b/src/Version/IVersion.cs @@ -17,8 +17,8 @@ public interface IVersion string? Jar { get; } MLogFileMetadata? Logging { get; } string? MainClass { get; } - IReadOnlyCollection GameArguments { get; } - IReadOnlyCollection JvmArguments { get; } + IReadOnlyCollection GetGameArguments(bool isBaseVersion); + IReadOnlyCollection GetJvmArguments(bool isBaseVersion); DateTime ReleaseTime { get; } string? Type { get; } diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index e58d678..7959160 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -52,11 +52,24 @@ public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) public string? Type => _model.Type; private IReadOnlyCollection? _gameArgs = null; - public IReadOnlyCollection GameArguments => _gameArgs ??= getGameArguments(); + private IReadOnlyCollection? _gameArgsForBase = null; + public IReadOnlyCollection GetGameArguments(bool isBaseVersion) + { + if (isBaseVersion) + return _gameArgsForBase ??= getGameArguments(true); + else + return _gameArgs ??= getGameArguments(false); + } private IReadOnlyCollection? _jvmArgs = null; - - public IReadOnlyCollection JvmArguments => _jvmArgs ??= getJvmArguments(); + private IReadOnlyCollection? _jvmArgsForBase = null; + public IReadOnlyCollection GetJvmArguments(bool isBaseVersion) + { + if (isBaseVersion) + return _jvmArgsForBase ??= getJvmArguments(true); + else + return _jvmArgs ?? getJvmArguments(false); + } private string? getJarId() { @@ -135,57 +148,69 @@ private IReadOnlyCollection getLibraries() } } - private IReadOnlyCollection getGameArguments() + private IReadOnlyCollection getGameArguments(bool isBaseVersion) { try { var prop = _json.RootElement .GetProperty("arguments") .GetProperty("game"); - return JsonArgumentParser.Parse(prop); + var args = JsonArgumentParser.Parse(prop); + _gameArgs = args; + _gameArgsForBase = args; } catch (KeyNotFoundException) { var args = GetProperty("minecraftArguments"); if (string.IsNullOrEmpty(args)) { - return Array.Empty(); + _gameArgs = Array.Empty(); + _gameArgsForBase = Array.Empty(); } else { - return args + _gameArgs = args .Split(' ') .Select(arg => new MArgument(arg)) .ToArray(); + _gameArgsForBase = Array.Empty(); } } catch (Exception) { if (!_options.SkipError) throw; - return Array.Empty(); + _gameArgs = Array.Empty(); + _gameArgsForBase = Array.Empty(); } + + return isBaseVersion ? _gameArgsForBase : _gameArgs; } - private IReadOnlyCollection getJvmArguments() + private IReadOnlyCollection getJvmArguments(bool isBaseVersion) { try { var prop = _json.RootElement .GetProperty("arguments") .GetProperty("jvm"); - return JsonArgumentParser.Parse(prop); + var args = JsonArgumentParser.Parse(prop); + _jvmArgs = args; + _jvmArgsForBase = args; } catch (KeyNotFoundException) { - return Array.Empty(); + _jvmArgs = Array.Empty(); + _jvmArgsForBase = Array.Empty(); } catch (Exception) { if (!_options.SkipError) throw; - return Array.Empty(); + _jvmArgs = Array.Empty(); + _jvmArgsForBase = Array.Empty(); } + return isBaseVersion ? _jvmArgsForBase : _jvmArgs; } public string? GetProperty(string key) From 227692f8874911620ceae1f27ba03ee3400e2f99 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 20:08:20 +0900 Subject: [PATCH 125/137] fix: MinecraftPath can be set to a value other than the default --- src/LauncherParameters.cs | 16 +++++++++------- src/MinecraftLauncher.cs | 13 ++++--------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/LauncherParameters.cs b/src/LauncherParameters.cs index 170b2ea..e994208 100644 --- a/src/LauncherParameters.cs +++ b/src/LauncherParameters.cs @@ -5,27 +5,29 @@ using CmlLib.Core.Natives; using CmlLib.Core.Rules; using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; namespace CmlLib.Core; public class MinecraftLauncherParameters { public static MinecraftLauncherParameters CreateDefault() => - CreateDefault(HttpUtil.SharedHttpClient.Value); + CreateDefault(new MinecraftPath(), HttpUtil.SharedHttpClient.Value); - public static MinecraftLauncherParameters CreateDefault(HttpClient httpClient) + public static MinecraftLauncherParameters CreateDefault(MinecraftPath path) => + CreateDefault(path, HttpUtil.SharedHttpClient.Value); + + public static MinecraftLauncherParameters CreateDefault(MinecraftPath path, HttpClient httpClient) { var parameters = new MinecraftLauncherParameters(); parameters.HttpClient = httpClient; - parameters.MinecraftPath = new MinecraftPath(); + parameters.MinecraftPath = path; parameters.RulesEvaluator = new RulesEvaluator(); - parameters.VersionLoader = new MojangJsonVersionLoaderV2(parameters.MinecraftPath, httpClient); - parameters.JavaPathResolver = new MinecraftJavaPathResolver(parameters.MinecraftPath); + parameters.VersionLoader = new MojangJsonVersionLoaderV2(path, httpClient); + parameters.JavaPathResolver = new MinecraftJavaPathResolver(path); parameters.GameInstaller = ParallelGameInstaller.CreateAsCoreCount(httpClient); parameters.NativeLibraryExtractor = new NativeLibraryExtractor(parameters.RulesEvaluator); var extractors = DefaultFileExtractors.CreateDefault( - parameters.HttpClient, + httpClient, parameters.RulesEvaluator, parameters.JavaPathResolver); parameters.FileExtractors = extractors.ToExtractorCollection(); diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 8a1360a..93391c7 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -30,23 +30,18 @@ public class MinecraftLauncher public RulesEvaluatorContext RulesContext { get; set; } public VersionMetadataCollection? Versions { get; private set; } - public MinecraftLauncher(string path) : this(WithMinecraftPath(new MinecraftPath(path))) + public MinecraftLauncher(string path) : + this(MinecraftLauncherParameters.CreateDefault(new MinecraftPath(path))) { } - public MinecraftLauncher(MinecraftPath path) : this(WithMinecraftPath(path)) + public MinecraftLauncher(MinecraftPath path) : + this(MinecraftLauncherParameters.CreateDefault(path)) { } - private static MinecraftLauncherParameters WithMinecraftPath(MinecraftPath path) - { - var parameters = MinecraftLauncherParameters.CreateDefault(); - parameters.MinecraftPath = path; - return parameters; - } - public MinecraftLauncher(MinecraftLauncherParameters parameters) { MinecraftPath = parameters.MinecraftPath From 4aaa1cc20c054dec0db330f523053fbb179b46ee Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 20:09:18 +0900 Subject: [PATCH 126/137] fix: extract files from parent versions also --- src/MinecraftLauncher.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 93391c7..77b702d 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -133,7 +133,13 @@ public async ValueTask InstallAsync( IProgress? byteProgress, CancellationToken cancellationToken = default) { - var files = await ExtractFiles(version, cancellationToken); + var files = Enumerable.Empty(); + foreach (var v in version.EnumerateToParent()) + { + var f = await ExtractFiles(v, cancellationToken); + files = files.Concat(f); + } + await GameInstaller.Install( files, fileProgress ?? _fileProgress, From d042e59fddce664d1749a91f80193940417be07b Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 20:10:30 +0900 Subject: [PATCH 127/137] fix: decide which jar file to use --- src/ProcessBuilder/MinecraftProcessBuilder.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index 3774e5c..d26e5f3 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -1,4 +1,4 @@ -using CmlLib.Core.Rules; +using CmlLib.Core.Rules; using CmlLib.Core.Version; using CmlLib.Core.Internals; using System.Diagnostics; @@ -162,15 +162,26 @@ private IEnumerable getClasspaths(RulesEvaluatorContext context) if (pathSet.Add(item)) yield return item; } - + // .jar file - // TODO: decide what Jar file should be used. current jar or parent jar - var jar = version.GetInheritedProperty(v => v.Jar); - if (string.IsNullOrEmpty(jar)) - jar = version.Id; + var jar = getJar(version); yield return minecraftPath.GetVersionJarPath(jar); } + private string getJar(IVersion version) + { + IVersion? currentVersion = version; + IVersion rootVersion = version; + while (currentVersion != null) + { + if (!string.IsNullOrEmpty(currentVersion.Jar)) + return currentVersion.Jar; + rootVersion = currentVersion; + currentVersion = currentVersion.ParentVersion; + } + return rootVersion.Id; + } + private string? createAddress(string? ip, int port) { if (port == MinecraftArgumentBuilder.DefaultServerPort) From b111a1ba0ce1502c1e67c2aab9c93f0b51465db8 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 20:11:35 +0900 Subject: [PATCH 128/137] fix: GameInstaller can be run again after an exception is thrown --- src/Installers/GameInstallerBase.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Installers/GameInstallerBase.cs b/src/Installers/GameInstallerBase.cs index 21fd9c2..b217e79 100644 --- a/src/Installers/GameInstallerBase.cs +++ b/src/Installers/GameInstallerBase.cs @@ -42,8 +42,14 @@ public async ValueTask Install( excludeSet.Add(excludeFile); } - await Install(gameFiles, cancellationToken); - IsRunning = false; + try + { + await Install(gameFiles, cancellationToken); + } + finally + { + IsRunning = false; + } } protected abstract ValueTask Install(IEnumerable gameFiles, CancellationToken cancellationToken); From 1e59bc94fa9475e9060949f95b1515c2c1ed7070 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Thu, 28 Mar 2024 20:12:09 +0900 Subject: [PATCH 129/137] fix: create parent directory for version_manifest_v2.json --- src/VersionLoader/MojangJsonVersionLoaderV2.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/VersionLoader/MojangJsonVersionLoaderV2.cs b/src/VersionLoader/MojangJsonVersionLoaderV2.cs index 7dc7494..6fd768c 100644 --- a/src/VersionLoader/MojangJsonVersionLoaderV2.cs +++ b/src/VersionLoader/MojangJsonVersionLoaderV2.cs @@ -98,6 +98,7 @@ private async Task getManifestStream() var buffer = new MemoryStream(); await res.CopyToAsync(buffer); + IOUtil.CreateParentDirectory(_localManifestPath); using var saveTo = File.Create(_localManifestPath); await buffer.CopyToAsync(saveTo); From 0eba6f9ef17658ce56a7fb3764a78c337341586d Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 14:20:24 +0900 Subject: [PATCH 130/137] fix: fix the order of libraries in -cp arguments and also main jar --- benchmark/DummyVersion.cs | 2 +- src/FileExtractors/ClientFileExtractor.cs | 12 +++++------ .../SafeDateTimeOffsetJsonConverter.cs | 20 +++++++++++++++++++ src/ProcessBuilder/MinecraftProcessBuilder.cs | 20 +++---------------- src/Version/IVersion.cs | 4 ++-- src/Version/JsonVersion.cs | 9 +++++---- src/Version/JsonVersionDTO.cs | 7 +++++-- .../JsonVersionManifestModel.cs | 5 ++++- test/Version/DummyVersion.cs | 2 +- 9 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 src/Internals/SafeDateTimeOffsetJsonConverter.cs diff --git a/benchmark/DummyVersion.cs b/benchmark/DummyVersion.cs index c30530a..31b72c3 100644 --- a/benchmark/DummyVersion.cs +++ b/benchmark/DummyVersion.cs @@ -35,7 +35,7 @@ public class DummyVersion : IVersion public string? Type => throw new NotImplementedException(); - public string? JarId => throw new NotImplementedException(); + public string MainJarId => throw new NotImplementedException(); public string? GetProperty(string key) { diff --git a/src/FileExtractors/ClientFileExtractor.cs b/src/FileExtractors/ClientFileExtractor.cs index 488a3fe..33838f7 100644 --- a/src/FileExtractors/ClientFileExtractor.cs +++ b/src/FileExtractors/ClientFileExtractor.cs @@ -18,19 +18,19 @@ public ValueTask> Extract( private IEnumerable extract(MinecraftPath path, IVersion version) { - var id = version.JarId; - var url = version.Client?.Url; + var id = version.MainJarId; + var client = version.GetInheritedProperty(v => v.Client); - if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(id)) + if (string.IsNullOrEmpty(client?.Url) || string.IsNullOrEmpty(id)) yield break; var clientPath = path.GetVersionJarPath(id); yield return new GameFile(id) { Path = clientPath, - Url = url, - Hash = version.Client?.GetSha1(), - Size = version.Client?.Size ?? 0 + Url = client.Url, + Hash = client.GetSha1(), + Size = client.Size }; } } diff --git a/src/Internals/SafeDateTimeOffsetJsonConverter.cs b/src/Internals/SafeDateTimeOffsetJsonConverter.cs new file mode 100644 index 0000000..840be97 --- /dev/null +++ b/src/Internals/SafeDateTimeOffsetJsonConverter.cs @@ -0,0 +1,20 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CmlLib.Core.Internals; + +internal class SafeDateTimeOffsetJsonConverter : JsonConverter +{ + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (DateTimeOffset.TryParse(reader.GetString(), out var result)) + return result; + else + return DateTimeOffset.MinValue; + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("o")); + } +} \ No newline at end of file diff --git a/src/ProcessBuilder/MinecraftProcessBuilder.cs b/src/ProcessBuilder/MinecraftProcessBuilder.cs index d26e5f3..43ce104 100644 --- a/src/ProcessBuilder/MinecraftProcessBuilder.cs +++ b/src/ProcessBuilder/MinecraftProcessBuilder.cs @@ -149,7 +149,8 @@ private IEnumerable getClasspaths(RulesEvaluatorContext context) { // libraries var libPaths = version - .ConcatInheritedCollection(v => v.Libraries) + .EnumerateToParent() + .SelectMany(v => v.Libraries) .Where(lib => lib.CheckIsRequired(JsonVersionParserOptions.ClientSide)) .Where(lib => lib.Rules == null || rulesEvaluator.Match(lib.Rules, context)) .Where(lib => lib.Artifact != null) @@ -164,22 +165,7 @@ private IEnumerable getClasspaths(RulesEvaluatorContext context) } // .jar file - var jar = getJar(version); - yield return minecraftPath.GetVersionJarPath(jar); - } - - private string getJar(IVersion version) - { - IVersion? currentVersion = version; - IVersion rootVersion = version; - while (currentVersion != null) - { - if (!string.IsNullOrEmpty(currentVersion.Jar)) - return currentVersion.Jar; - rootVersion = currentVersion; - currentVersion = currentVersion.ParentVersion; - } - return rootVersion.Id; + yield return minecraftPath.GetVersionJarPath(version.MainJarId); } private string? createAddress(string? ip, int port) diff --git a/src/Version/IVersion.cs b/src/Version/IVersion.cs index 7bb40aa..a9a90da 100644 --- a/src/Version/IVersion.cs +++ b/src/Version/IVersion.cs @@ -7,7 +7,7 @@ namespace CmlLib.Core.Version; public interface IVersion { string Id { get; } - string? JarId { get; } + string MainJarId { get; } string? InheritsFrom { get; } IVersion? ParentVersion { get; set; } AssetMetadata? AssetIndex { get; } @@ -19,7 +19,7 @@ public interface IVersion string? MainClass { get; } IReadOnlyCollection GetGameArguments(bool isBaseVersion); IReadOnlyCollection GetJvmArguments(bool isBaseVersion); - DateTime ReleaseTime { get; } + DateTimeOffset ReleaseTime { get; } string? Type { get; } string? GetProperty(string key); diff --git a/src/Version/JsonVersion.cs b/src/Version/JsonVersion.cs index 7959160..12cba84 100644 --- a/src/Version/JsonVersion.cs +++ b/src/Version/JsonVersion.cs @@ -21,7 +21,7 @@ public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) } public string Id { get; } - public string? JarId => getJarId(); + public string MainJarId => getJarId(); public string? InheritsFrom => _model.InheritsFrom; @@ -47,7 +47,7 @@ public JsonVersion(JsonDocument jsonDocument, JsonVersionParserOptions options) public string? MinecraftArguments => _model.MinecraftArguments; - public DateTime ReleaseTime => _model.ReleaseTime; + public DateTimeOffset ReleaseTime => _model.ReleaseTime; public string? Type => _model.Type; @@ -71,12 +71,13 @@ public IReadOnlyCollection GetJvmArguments(bool isBaseVersion) return _jvmArgs ?? getJvmArguments(false); } - private string? getJarId() + private string getJarId() { var jar = this.GetInheritedProperty(v => v.Jar); if (string.IsNullOrEmpty(jar)) return Id; - return jar; + else + return jar; } private AssetMetadata? getAssetIndex() diff --git a/src/Version/JsonVersionDTO.cs b/src/Version/JsonVersionDTO.cs index c47f6b6..b460023 100644 --- a/src/Version/JsonVersionDTO.cs +++ b/src/Version/JsonVersionDTO.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using CmlLib.Core.Files; +using CmlLib.Core.Internals; using CmlLib.Core.Java; namespace CmlLib.Core.Version; @@ -37,10 +38,12 @@ public class JsonVersionDTO public int MinimumLauncherVersion { get; set; } [JsonPropertyName("releaseTime")] - public DateTime ReleaseTime { get; set; } + [JsonConverter(typeof(SafeDateTimeOffsetJsonConverter))] + public DateTimeOffset ReleaseTime { get; set; } [JsonPropertyName("time")] - public DateTime Time { get; set; } + [JsonConverter(typeof(SafeDateTimeOffsetJsonConverter))] + public DateTimeOffset Time { get; set; } [JsonPropertyName("type")] public string? Type { get; set; } diff --git a/src/VersionMetadata/JsonVersionManifestModel.cs b/src/VersionMetadata/JsonVersionManifestModel.cs index 973f514..bf611e4 100644 --- a/src/VersionMetadata/JsonVersionManifestModel.cs +++ b/src/VersionMetadata/JsonVersionManifestModel.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using CmlLib.Core.Files; +using CmlLib.Core.Internals; namespace CmlLib.Core.VersionMetadata; @@ -27,9 +28,11 @@ public record JsonVersionMetadataModel : MFileMetadata public string? Type { get; set; } [JsonPropertyName("time")] + [JsonConverter(typeof(SafeDateTimeOffsetJsonConverter))] public DateTimeOffset Time { get; set; } - [JsonPropertyName("releaseTime")] + [JsonPropertyName("releaseTime")] + [JsonConverter(typeof(SafeDateTimeOffsetJsonConverter))] public DateTimeOffset ReleaseTime { get; set; } [JsonPropertyName("complianceLevel")] diff --git a/test/Version/DummyVersion.cs b/test/Version/DummyVersion.cs index fe49e3a..e08b3dd 100644 --- a/test/Version/DummyVersion.cs +++ b/test/Version/DummyVersion.cs @@ -37,7 +37,7 @@ public class DummyVersion : IVersion public string? Type { get; set; } - public string? JarId { get; set; } + public string MainJarId { get; set; } public string? GetProperty(string key) { From 500ea1b5949e1228bb537eeb8aa59acf72196369 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 14:22:03 +0900 Subject: [PATCH 131/137] fix: use parent java version if not specified on child --- src/MinecraftLauncher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 2d6f7a5..7a90a89 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -178,11 +178,11 @@ public Process BuildProcess( public string? GetJavaPath(IVersion version) { - if (!version.JavaVersion.HasValue || string.IsNullOrEmpty(version.JavaVersion?.Component)) + var javaVersion = version.GetInheritedProperty(v => v.JavaVersion); + if (!javaVersion.HasValue) return null; - return JavaPathResolver.GetJavaBinaryPath( - version.JavaVersion.Value, + javaVersion.Value, RulesContext); } From 88d6bbf44315f5fa09a30e2837cb6f245a805e20 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 14:22:37 +0900 Subject: [PATCH 132/137] refactor: update FabricInstaller, LiteLoaderInstaller, QuiltInstaller --- src/ModLoaders/FabricMC/FabricInstaller.cs | 119 +++++++++++++++ src/ModLoaders/FabricMC/FabricLoader.cs | 8 +- .../FabricMC/FabricVersionLoader.cs | 88 ----------- .../LiteLoader/LiteLoaderInstaller.cs | 139 +++++++++++++----- .../LiteLoader/LiteLoaderLibrary.cs | 12 ++ .../LiteLoader/LiteLoaderVersion.cs | 16 ++ .../LiteLoader/LiteLoaderVersionLoader.cs | 63 -------- .../LiteLoader/LiteLoaderVersionMetadata.cs | 127 ---------------- src/ModLoaders/QuiltMC/QuiltInstaller.cs | 119 +++++++++++++++ src/ModLoaders/QuiltMC/QuiltLoader.cs | 2 + src/ModLoaders/QuiltMC/QuiltVersionLoader.cs | 99 ------------- 11 files changed, 373 insertions(+), 419 deletions(-) create mode 100644 src/ModLoaders/FabricMC/FabricInstaller.cs delete mode 100644 src/ModLoaders/FabricMC/FabricVersionLoader.cs create mode 100644 src/ModLoaders/LiteLoader/LiteLoaderLibrary.cs create mode 100644 src/ModLoaders/LiteLoader/LiteLoaderVersion.cs delete mode 100644 src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs delete mode 100644 src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs create mode 100644 src/ModLoaders/QuiltMC/QuiltInstaller.cs delete mode 100644 src/ModLoaders/QuiltMC/QuiltVersionLoader.cs diff --git a/src/ModLoaders/FabricMC/FabricInstaller.cs b/src/ModLoaders/FabricMC/FabricInstaller.cs new file mode 100644 index 0000000..966fd6f --- /dev/null +++ b/src/ModLoaders/FabricMC/FabricInstaller.cs @@ -0,0 +1,119 @@ +using System.Text.Json; +using CmlLib.Core.Internals; + +namespace CmlLib.Core.ModLoaders.FabricMC; + +public class FabricInstaller +{ + public static readonly string DefaultApiServerHost = "https://meta.fabricmc.net"; + private readonly HttpClient _httpClient; + private readonly string _host; + + public FabricInstaller(HttpClient httpClient) => + (_httpClient, _host) = (httpClient, DefaultApiServerHost); + + public FabricInstaller(HttpClient httpClient, string host) => + (_httpClient, _host) = (httpClient, host); + + public static string GetVersionName(string gameVersion, string loaderVersion) + { + return $"fabric-loader-{loaderVersion}-{gameVersion}"; + } + + public async Task> GetSupportedVersionNames() + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v2/versions/game"); + var list = await JsonSerializer.DeserializeAsync>(res); + if (list == null) + return Array.Empty(); + + return list + .Where(item => item.Stable && !string.IsNullOrEmpty(item.Version)) + .Select(item => item.Version!) + .ToList(); + } + + public async Task> GetLoaders() + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v2/versions/loader"); + var list = await JsonSerializer.DeserializeAsync>(res); + if (list == null) + return Array.Empty(); + else + return list; + } + + public async Task> GetLoaders(string gameVersion) + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v2/versions/loader/{gameVersion}"); + using var json = await JsonDocument.ParseAsync(res); + return parseLoaders(json.RootElement).ToList(); + } + + public async Task GetFirstLoader(string gameVersion) + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v2/versions/loader/{gameVersion}"); + using var json = await JsonDocument.ParseAsync(res); + return parseLoaders(json.RootElement).FirstOrDefault(); + } + + public async Task GetProfileJson(string gameVersion, string loaderVersion) + { + return await _httpClient.GetStreamAsync($"{DefaultApiServerHost}/v2/versions/loader/{gameVersion}/{loaderVersion}/profile/json"); + } + + private IEnumerable parseLoaders(JsonElement root) + { + if (root.ValueKind != JsonValueKind.Array) + Enumerable.Empty(); + + foreach (var item in root.EnumerateArray()) + { + if (item.ValueKind != JsonValueKind.Object) + continue; + var loader = item.GetPropertyOrNull("loader")?.Deserialize(); + if (loader != null) + yield return loader; + } + } + + public async Task Install(string gameVersion, MinecraftPath installTo) + { + var loader = await GetFirstLoader(gameVersion); + if (string.IsNullOrEmpty(loader?.Version)) + throw new KeyNotFoundException("Cannot find any loader for " + gameVersion); + + var versionName = GetVersionName(gameVersion, loader.Version); + return await Install(gameVersion, loader.Version, installTo, versionName); + } + + public async Task Install(string gameVersion, MinecraftPath installTo, string versionName) + { + var loader = await GetFirstLoader(gameVersion); + if (string.IsNullOrEmpty(loader?.Version)) + throw new KeyNotFoundException("Cannot find any loader for " + gameVersion); + + return await Install(gameVersion, loader.Version, installTo, versionName); + } + + public async Task Install(string gameVersion, string loaderVersion, MinecraftPath installTo) + { + var versionName = GetVersionName(gameVersion, loaderVersion); + return await Install(gameVersion, loaderVersion, installTo, versionName); + } + + public async Task Install(string gameVersion, string loaderVersion, MinecraftPath installTo, string versionName) + { + using var profileStream = await GetProfileJson(gameVersion, loaderVersion); + return await installProfile(profileStream, installTo, versionName); + } + + private async Task installProfile(Stream profileStream, MinecraftPath installTo, string versionName) + { + var versionJsonPath = installTo.GetVersionJsonPath(versionName); + IOUtil.CreateParentDirectory(versionJsonPath); + using var versionJsonFileStream = File.Create(versionJsonPath); + await profileStream.CopyToAsync(versionJsonFileStream); + return versionName; + } +} diff --git a/src/ModLoaders/FabricMC/FabricLoader.cs b/src/ModLoaders/FabricMC/FabricLoader.cs index dd5c788..481619c 100644 --- a/src/ModLoaders/FabricMC/FabricLoader.cs +++ b/src/ModLoaders/FabricMC/FabricLoader.cs @@ -6,13 +6,17 @@ public class FabricLoader { [JsonPropertyName("separator")] public string? Separator { get; set; } + [JsonPropertyName("build")] - public string? Build { get; set; } + public int Build { get; set; } + [JsonPropertyName("maven")] public string? Maven { get; set; } + [JsonPropertyName("version")] public string? Version { get; set; } + [JsonPropertyName("stable")] - public bool Stable { get; set; } + public bool Stable { get; set; } = true; } } diff --git a/src/ModLoaders/FabricMC/FabricVersionLoader.cs b/src/ModLoaders/FabricMC/FabricVersionLoader.cs deleted file mode 100644 index af5c0f2..0000000 --- a/src/ModLoaders/FabricMC/FabricVersionLoader.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Text.Json; -using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; -using CmlLib.Core.Internals; - -namespace CmlLib.Core.ModLoaders.FabricMC; - -public class FabricVersionLoader : IVersionLoader -{ - public static readonly string ApiServer = "https://meta.fabricmc.net"; - private static readonly string LoaderUrl = ApiServer + "/v2/versions/loader"; - - private readonly HttpClient _httpClient; - - public FabricVersionLoader(HttpClient httpClient) => - _httpClient = httpClient; - - public string? LoaderVersion { get; set; } - - protected string GetVersionName(string version, string loaderVersion) - { - return $"fabric-loader-{loaderVersion}-{version}"; - } - - public async ValueTask GetVersionMetadatasAsync() - { - if (string.IsNullOrEmpty(LoaderVersion)) - { - var loaders = await GetFabricLoadersAsync().ConfigureAwait(false); - - if (loaders.Length == 0 || string.IsNullOrEmpty(loaders[0].Version)) - throw new KeyNotFoundException("can't find loaders"); - - LoaderVersion = loaders[0].Version; - } - - var url = "https://meta.fabricmc.net/v2/versions/game/intermediary"; - var res = await _httpClient.GetStringAsync(url) - .ConfigureAwait(false); - - var versions = parseVersions(res, LoaderVersion!); - return new VersionMetadataCollection(versions, null, null); - } - - private IEnumerable parseVersions(string res, string loader) - { - using var jsonDocument = JsonDocument.Parse(res); - var root = jsonDocument.RootElement; - - foreach (var item in root.EnumerateArray()) - { - var versionName = item.GetPropertyValue("version"); - if (string.IsNullOrEmpty(versionName)) - continue; - - var jsonUrl = $"{ApiServer}/v2/versions/loader/{versionName}/{loader}/profile/json"; - - var id = GetVersionName(versionName, loader); - var model = new JsonVersionMetadataModel - { - Name = id, - Url = jsonUrl, - Type = "fabric" - }; - yield return new MojangVersionMetadata(model, _httpClient); - } - } - - public async Task GetFabricLoadersAsync() - { - var res = await _httpClient.GetStringAsync(LoaderUrl); - return parseLoaders(res); - } - - private FabricLoader[] parseLoaders(string res) - { - using var jsonDocument = JsonDocument.Parse(res); - var loaderList = new List(); - foreach (var item in jsonDocument.RootElement.EnumerateArray()) - { - var obj = item.Deserialize(); - if (obj != null) - loaderList.Add(obj); - } - - return loaderList.ToArray(); - } -} diff --git a/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs b/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs index c2e25d8..e35f0da 100644 --- a/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs +++ b/src/ModLoaders/LiteLoader/LiteLoaderInstaller.cs @@ -1,74 +1,133 @@ -using CmlLib.Core.Version; +using CmlLib.Core.CommandParser; +using CmlLib.Core.Files; +using CmlLib.Core.Internals; +using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Version; using CmlLib.Core.VersionLoader; using CmlLib.Core.VersionMetadata; +using System; +using System.IO; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Xml.Linq; namespace CmlLib.Core.ModLoaders.LiteLoader; // 1.8.9 freezing public class LiteLoaderInstaller { + public static readonly string LiteLoaderLibName = "com.mumfrey:liteloader"; + public static readonly string ManifestServer = "http://dl.liteloader.com/versions/versions.json"; + public static readonly string DownloadServer = "http://dl.liteloader.com/versions/"; + private readonly HttpClient _httpClient; + private readonly string _manifestServer; - public LiteLoaderInstaller(MinecraftPath path, HttpClient httpClient) + public LiteLoaderInstaller(HttpClient httpClient) { _httpClient = httpClient; - this.minecraftPath = path; + _manifestServer = ManifestServer; } - private readonly MinecraftPath minecraftPath; - private VersionMetadataCollection? liteLoaderVersions; + public LiteLoaderInstaller(HttpClient httpClient, string manifestServer) + { + _httpClient = httpClient; + _manifestServer = manifestServer; + } - public static string GetVersionName(string loaderVersion, string baseVersion) + public static string GetVersionName(string loaderVersion, string gameVersion) { loaderVersion = loaderVersion.Replace("LiteLoader", ""); - return $"{loaderVersion}-LiteLoader{baseVersion}"; + return $"{loaderVersion}-LiteLoader{gameVersion}"; } - public async Task GetAllLiteLoaderVersions() + public async Task> GetAllLiteLoaders() { - var llVersionLoader = new LiteLoaderVersionLoader(_httpClient); - liteLoaderVersions = await llVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - - return liteLoaderVersions - .Select(x => x as LiteLoaderVersionMetadata) - .Where(x => x != null) - .ToArray()!; + using var res = await _httpClient.GetStreamAsync(_manifestServer); + using var json = await JsonDocument.ParseAsync(res); + var versions = parseVersions(json.RootElement); + return versions.ToList(); } - // vanilla - public async Task InstallAsync(string liteLoaderVersion) + private IEnumerable parseVersions(JsonElement root) { - var localVersionLoader = new LocalJsonVersionLoader(minecraftPath); - var localVersions = await localVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); - - liteLoaderVersions = await localVersionLoader.GetVersionMetadatasAsync() - .ConfigureAwait(false); + if (root.TryGetProperty("versions", out var versions)) + { + foreach (var item in versions.EnumerateObject()) + { + var baseVersion = item.Name; + var versionObj = item.Value; - var liteLoader = liteLoaderVersions?.GetVersionMetadata(liteLoaderVersion) as LiteLoaderVersionMetadata; - if (liteLoader == null) - throw new KeyNotFoundException(liteLoaderVersion); + var libObj = + versionObj.GetPropertyOrNull("artefacts") ?? + versionObj.GetPropertyOrNull("snapshots"); - var vanillaVersionName = liteLoader.VanillaVersionName; - var vanillaVersion = await localVersions.GetVersionAsync(vanillaVersionName) - .ConfigureAwait(false); + var latestLiteLoader = libObj? + .GetPropertyOrNull(LiteLoaderLibName)? + .GetPropertyOrNull("latest")? + .Deserialize(); - if (vanillaVersion == null) - throw new KeyNotFoundException(vanillaVersionName); + if (latestLiteLoader == null) + continue; - return await liteLoader.InstallAsync(minecraftPath, vanillaVersion); + latestLiteLoader.BaseVersion = baseVersion; + yield return latestLiteLoader; + } + } } - public async Task InstallAsync(string liteLoaderVersion, IVersion target) + public async Task Install(LiteLoaderVersion loader, IVersion baseVersion, MinecraftPath path) { - if (liteLoaderVersions == null) - await GetAllLiteLoaderVersions().ConfigureAwait(false); + if (string.IsNullOrEmpty(loader.Version)) + throw new ArgumentException("invalid loader"); + + var versionName = GetVersionName(loader.Version, baseVersion.Id); + var versionJsonFilePath = path.GetVersionJsonPath(versionName); + IOUtil.CreateParentDirectory(versionJsonFilePath); + using var versionJsonFile = File.Create(versionJsonFilePath); + + IEnumerable createLibraries() + { + yield return new LiteLoaderLibrary + { + Name = $"{LiteLoaderLibName}:{loader.Version}", + Url = DownloadServer + }; + + foreach (var lib in loader.Libraries ?? Enumerable.Empty()) + { + // asm-all:5.2 is only available on LiteLoader server + if (lib.Name == "org.ow2.asm:asm-all:5.2") + lib.Url = "http://repo.liteloader.com/"; + yield return lib; + } + } + + IEnumerable createGameArguments() + { + if (!string.IsNullOrEmpty(loader.TweakClass)) + { + yield return "--tweakClass"; + yield return loader.TweakClass; + } - var liteLoader = liteLoaderVersions?.GetVersionMetadata(liteLoaderVersion) as LiteLoaderVersionMetadata; - if (liteLoader == null) - throw new KeyNotFoundException(liteLoaderVersion); + var args = baseVersion.GetGameArguments(false).SelectMany(arg => arg.Values); + foreach (var item in args) + yield return item; + } - return await liteLoader.InstallAsync(minecraftPath, target); + await JsonSerializer.SerializeAsync(versionJsonFile, new + { + id = versionName, + type = "release", + mainClass = "net.minecraft.launchwrapper.Launch", + inheritsFrom = baseVersion.Id, + jar = baseVersion.Id, + libraries = createLibraries(), + minecraftArguments = string.Join(" ", createGameArguments()) + }); + return versionName; } + } \ No newline at end of file diff --git a/src/ModLoaders/LiteLoader/LiteLoaderLibrary.cs b/src/ModLoaders/LiteLoader/LiteLoaderLibrary.cs new file mode 100644 index 0000000..0516936 --- /dev/null +++ b/src/ModLoaders/LiteLoader/LiteLoaderLibrary.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.ModLoaders.LiteLoader; + +public class LiteLoaderLibrary +{ + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("url")] + public string? Url { get; set; } +} \ No newline at end of file diff --git a/src/ModLoaders/LiteLoader/LiteLoaderVersion.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersion.cs new file mode 100644 index 0000000..40c83e0 --- /dev/null +++ b/src/ModLoaders/LiteLoader/LiteLoaderVersion.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace CmlLib.Core.ModLoaders.LiteLoader; + +public class LiteLoaderVersion +{ + public string? BaseVersion { get; set; } + [JsonPropertyName("tweakClass")] + public string? TweakClass { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + + [JsonPropertyName("libraries")] + public IEnumerable? Libraries { get; set; } +} \ No newline at end of file diff --git a/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs deleted file mode 100644 index 01b0b40..0000000 --- a/src/ModLoaders/LiteLoader/LiteLoaderVersionLoader.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Text.Json; -using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; -using CmlLib.Core.Internals; - -namespace CmlLib.Core.ModLoaders.LiteLoader; - -public class LiteLoaderVersionLoader : IVersionLoader -{ - public const string LiteLoaderLibName = "com.mumfrey:liteloader"; - public const string ManifestServer = "http://dl.liteloader.com/versions/versions.json"; - - private readonly HttpClient _httpClient; - - public LiteLoaderVersionLoader(HttpClient httpClient) => - _httpClient = httpClient; - - public async ValueTask GetVersionMetadatasAsync() - { - var res = await _httpClient.GetStringAsync(ManifestServer) - .ConfigureAwait(false); - - return parseVersions(res); - } - - private VersionMetadataCollection parseVersions(string json) - { - using var jsonDocument = JsonDocument.Parse(json); - var root = jsonDocument.RootElement; - - var metadataList = new List(); - if (root.TryGetProperty("versions", out var versions)) - { - foreach (var item in versions.EnumerateObject()) - { - var vanillaVersion = item.Name; - var versionObj = item.Value; - - var libObj = - versionObj.GetPropertyOrNull("artefacts") ?? - versionObj.GetPropertyOrNull("snapshots"); - - var latestLiteLoader = libObj? - .GetPropertyOrNull(LiteLoaderLibName)? - .GetPropertyOrNull("latest"); - - if (latestLiteLoader == null) - continue; - - var model = new JsonVersionMetadataModel - { - Name = $"LiteLoader-{vanillaVersion}", - Type = versionObj.GetPropertyOrNull("repo")?.GetPropertyValue("stream") - }; - var metadata = new LiteLoaderVersionMetadata(model, vanillaVersion, latestLiteLoader.Value); - - metadataList.Add(metadata); - } - } - - return new VersionMetadataCollection(metadataList, null, null); - } -} \ No newline at end of file diff --git a/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs b/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs deleted file mode 100644 index dacfea1..0000000 --- a/src/ModLoaders/LiteLoader/LiteLoaderVersionMetadata.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Text.Json; -using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.Version; -using CmlLib.Core.VersionMetadata; -using CmlLib.Core.Internals; - -namespace CmlLib.Core.ModLoaders.LiteLoader; - -public class LiteLoaderVersionMetadata : JsonVersionMetadata -{ - private const string LiteLoaderDl = "http://dl.liteloader.com/versions/"; // should be https - - public LiteLoaderVersionMetadata( - JsonVersionMetadataModel model, - string vanillaVersionName, - JsonElement element) : base(model) - { - IsSaved = false; - VanillaVersionName = vanillaVersionName; - _element = element; - } - - private readonly JsonElement? _element; - public string VanillaVersionName { get; private set; } - - private void writeVersion(Utf8JsonWriter writer, - string versionName, string baseVersionName, string? strArgs, IEnumerable? arrArgs) - { - var llVersion = _element?.GetPropertyValue("version"); - var libraries = _element?.GetPropertyOrNull("libraries"); - - writer.WriteStartObject(); - writer.WriteString("id", versionName); - writer.WriteString("type", "release"); - writer.WriteString("mainClass", "net.minecraft.launchwrapper.Launch"); - writer.WriteString("inheritsFrom", baseVersionName); - writer.WriteString("jar", baseVersionName); - - writer.WriteStartArray("libraries"); - writer.WriteStartObject(); - writer.WriteString("name", $"{LiteLoaderVersionLoader.LiteLoaderLibName}:{llVersion}"); - writer.WriteString("url", LiteLoaderDl); - writer.WriteEndObject(); - - if (libraries != null) - { - foreach (var lib in libraries.Value.EnumerateArray()) - { - // asm-all:5.2 is only available on LiteLoader server - var libName = lib.GetPropertyValue("name"); - var libUrl = lib.GetPropertyValue("url"); - if (libName == "org.ow2.asm:asm-all:5.2") - libUrl = "http://repo.liteloader.com/"; - - writer.WriteStartObject(); - writer.WriteString("name", libName); - writer.WriteString("url", libUrl); - writer.WriteEndObject(); - } - } - - writer.WriteEndArray(); - - if (!string.IsNullOrEmpty(strArgs)) - writer.WriteString("minecraftArguments", strArgs); - if (arrArgs != null) - { - writer.WriteStartObject("arguments"); - writer.WriteStartArray(); - foreach (var item in arrArgs) - JsonSerializer.Serialize(writer, item); - writer.WriteEndArray(); - writer.WriteEndObject(); - } - - writer.WriteEndObject(); - } - - private Stream createVersionWriteStream(MinecraftPath path, string name) - { - var metadataPath = path.GetVersionJsonPath(name); - - var directoryPath = System.IO.Path.GetDirectoryName(metadataPath); - if (!string.IsNullOrEmpty(directoryPath)) - Directory.CreateDirectory(directoryPath); - return File.Create(metadataPath); - } - - public async Task InstallAsync(MinecraftPath path, IVersion baseVersion) - { - var versionName = LiteLoaderInstaller.GetVersionName(VanillaVersionName, baseVersion.Id); - var tweakClass = _element?.GetPropertyValue("tweakClass"); - - using var fs = createVersionWriteStream(path, versionName); - using var writer = new Utf8JsonWriter(fs); - - var minecraftArguments = baseVersion.GetProperty("minecraftArguments"); - if (!string.IsNullOrEmpty(minecraftArguments)) - { - // com.mumfrey.liteloader.launch.LiteLoaderTweaker - var newArguments = $"--tweakClass {tweakClass} {minecraftArguments}"; - writeVersion(writer, versionName, baseVersion.Id, newArguments, null); - } - else if (baseVersion.GetGameArguments(true).Any()) - { - var tweakArg = new MArgument[] - { - new MArgument("--tweakClass"), - new MArgument(tweakClass!) - }; - - var newArguments = tweakArg.Concat(baseVersion.GetGameArguments(true)); - writeVersion(writer, versionName, baseVersion.Id, null, newArguments); - } - - await fs.FlushAsync(); - await writer.FlushAsync(); - return versionName; - } - - protected override ValueTask GetVersionJsonStream() - { - var ms = new MemoryStream(); - using var writer = new Utf8JsonWriter(ms); // TODO - return new ValueTask(ms); - } -} \ No newline at end of file diff --git a/src/ModLoaders/QuiltMC/QuiltInstaller.cs b/src/ModLoaders/QuiltMC/QuiltInstaller.cs new file mode 100644 index 0000000..3279d16 --- /dev/null +++ b/src/ModLoaders/QuiltMC/QuiltInstaller.cs @@ -0,0 +1,119 @@ +using System.Text.Json; +using CmlLib.Core.Internals; + +namespace CmlLib.Core.ModLoaders.QuiltMC; + +public class QuiltInstaller +{ + public static readonly string DefaultApiServerHost = "https://meta.Quiltmc.net"; + private readonly HttpClient _httpClient; + private readonly string _host; + + public QuiltInstaller(HttpClient httpClient) => + (_httpClient, _host) = (httpClient, DefaultApiServerHost); + + public QuiltInstaller(HttpClient httpClient, string host) => + (_httpClient, _host) = (httpClient, host); + + public static string GetVersionName(string gameVersion, string loaderVersion) + { + return $"quilt-loader-{loaderVersion}-{gameVersion}"; + } + + public async Task> GetSupportedVersionNames() + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v3/versions/game"); + var list = await JsonSerializer.DeserializeAsync>(res); + if (list == null) + return Array.Empty(); + + return list + .Where(item => item.Stable && !string.IsNullOrEmpty(item.Version)) + .Select(item => item.Version!) + .ToList(); + } + + public async Task> GetLoaders() + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v3/versions/loader"); + var list = await JsonSerializer.DeserializeAsync>(res); + if (list == null) + return Array.Empty(); + else + return list; + } + + public async Task> GetLoaders(string gameVersion) + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v3/versions/loader/{gameVersion}"); + using var json = await JsonDocument.ParseAsync(res); + return parseLoaders(json.RootElement).ToList(); + } + + public async Task GetFirstLoader(string gameVersion) + { + using var res = await _httpClient.GetStreamAsync($"{_host}/v3/versions/loader/{gameVersion}"); + using var json = await JsonDocument.ParseAsync(res); + return parseLoaders(json.RootElement).FirstOrDefault(); + } + + public async Task GetProfileJson(string gameVersion, string loaderVersion) + { + return await _httpClient.GetStreamAsync($"{DefaultApiServerHost}/v3/versions/loader/{gameVersion}/{loaderVersion}/profile/json"); + } + + private IEnumerable parseLoaders(JsonElement root) + { + if (root.ValueKind != JsonValueKind.Array) + Enumerable.Empty(); + + foreach (var item in root.EnumerateArray()) + { + if (item.ValueKind != JsonValueKind.Object) + continue; + var loader = item.GetPropertyOrNull("loader")?.Deserialize(); + if (loader != null) + yield return loader; + } + } + + public async Task Install(string gameVersion, MinecraftPath installTo) + { + var loader = await GetFirstLoader(gameVersion); + if (string.IsNullOrEmpty(loader?.Version)) + throw new KeyNotFoundException("Cannot find any loader for " + gameVersion); + + var versionName = GetVersionName(gameVersion, loader.Version); + return await Install(gameVersion, loader.Version, installTo, versionName); + } + + public async Task Install(string gameVersion, MinecraftPath installTo, string versionName) + { + var loader = await GetFirstLoader(gameVersion); + if (string.IsNullOrEmpty(loader?.Version)) + throw new KeyNotFoundException("Cannot find any loader for " + gameVersion); + + return await Install(gameVersion, loader.Version, installTo, versionName); + } + + public async Task Install(string gameVersion, string loaderVersion, MinecraftPath installTo) + { + var versionName = GetVersionName(gameVersion, loaderVersion); + return await Install(gameVersion, loaderVersion, installTo, versionName); + } + + public async Task Install(string gameVersion, string loaderVersion, MinecraftPath installTo, string versionName) + { + using var profileStream = await GetProfileJson(gameVersion, loaderVersion); + return await installProfile(profileStream, installTo, versionName); + } + + private async Task installProfile(Stream profileStream, MinecraftPath installTo, string versionName) + { + var versionJsonPath = installTo.GetVersionJsonPath(versionName); + IOUtil.CreateParentDirectory(versionJsonPath); + using var versionJsonFileStream = File.Create(versionJsonPath); + await profileStream.CopyToAsync(versionJsonFileStream); + return versionName; + } +} diff --git a/src/ModLoaders/QuiltMC/QuiltLoader.cs b/src/ModLoaders/QuiltMC/QuiltLoader.cs index b93aacc..3c4af19 100644 --- a/src/ModLoaders/QuiltMC/QuiltLoader.cs +++ b/src/ModLoaders/QuiltMC/QuiltLoader.cs @@ -12,5 +12,7 @@ public class QuiltLoader public string? Maven { get; set; } [JsonPropertyName("version")] public string? Version { get; set; } + [JsonPropertyName("stable")] + public bool Stable { get; set; } = true; } } diff --git a/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs b/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs deleted file mode 100644 index 921a66b..0000000 --- a/src/ModLoaders/QuiltMC/QuiltVersionLoader.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Net; -using System.Text.Json; -using CmlLib.Core.VersionLoader; -using CmlLib.Core.VersionMetadata; - -namespace CmlLib.Core.ModLoaders.QuiltMC; - -public class QuiltVersionLoader : IVersionLoader -{ - public static readonly string ApiServer = "https://meta.quiltmc.org"; - private static readonly string LoaderUrl = ApiServer + "/v3/versions/loader"; - - private readonly HttpClient _httpClient; - - public QuiltVersionLoader(HttpClient httpClient) => _httpClient = httpClient; - - public string? LoaderVersion { get; set; } - - protected string GetVersionName(string version, string loaderVersion) - { - return $"quilt-loader-{loaderVersion}-{version}"; - } - - public async ValueTask GetVersionMetadatasAsync() - { - if (string.IsNullOrEmpty(LoaderVersion)) - { - var loaders = await GetQuiltLoadersAsync().ConfigureAwait(false); - - if (loaders.Length == 0 || string.IsNullOrEmpty(loaders[0].Version)) - throw new KeyNotFoundException("can't find loaders"); - - LoaderVersion = loaders[0].Version; - } - - var url = "https://meta.quiltmc.org/v3/versions/game"; - var res = await _httpClient.GetStringAsync(url); - - var versions = parseVersions(res, LoaderVersion!); - return new VersionMetadataCollection(versions, null, null); - } - - private IEnumerable parseVersions(string res, string loader) - { - using var json = JsonDocument.Parse(res); - var jarr = json.RootElement.EnumerateArray(); - - foreach (var item in jarr) - { - if (!item.TryGetProperty("version", out var versionProp)) - continue; - var versionName = versionProp.GetString(); - if (string.IsNullOrEmpty(versionName)) - continue; - - string jsonUrl = $"{ApiServer}/v3/versions/loader/{versionName}/{loader}/profile/json"; - - string id = GetVersionName(versionName, loader); - var model = new JsonVersionMetadataModel - { - Type = "quilt", - Url = jsonUrl, - }; - yield return new MojangVersionMetadata(model, _httpClient); - } - } - - public QuiltLoader[] GetQuiltLoaders() - { - using var wc = new WebClient(); - var res = wc.DownloadString(LoaderUrl); - - return parseLoaders(res); - } - - public async Task GetQuiltLoadersAsync() - { - using var wc = new WebClient(); - var res = await wc.DownloadStringTaskAsync(LoaderUrl) - .ConfigureAwait(false); - - return parseLoaders(res); - } - - private QuiltLoader[] parseLoaders(string res) - { - using var json = JsonDocument.Parse(res); - var jarr = json.RootElement.EnumerateArray(); - var loaderList = new List(); - foreach (var item in jarr) - { - var obj = item.Deserialize(); - if (obj != null) - loaderList.Add(obj); - } - - return loaderList.ToArray(); - } -} From 92440fb359abf6556468100e4333ea01f28a8949 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 15:19:10 +0900 Subject: [PATCH 133/137] feat: add MinecraftVersion, mutable IVersion implementation --- benchmark/DummyVersion.cs | 44 --------------------------------- src/Version/Extensions.cs | 23 +++++++++++++++++ src/Version/MinecraftVersion.cs | 41 ++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 44 deletions(-) delete mode 100644 benchmark/DummyVersion.cs create mode 100644 src/Version/MinecraftVersion.cs diff --git a/benchmark/DummyVersion.cs b/benchmark/DummyVersion.cs deleted file mode 100644 index 31b72c3..0000000 --- a/benchmark/DummyVersion.cs +++ /dev/null @@ -1,44 +0,0 @@ -using CmlLib.Core.Files; -using CmlLib.Core.Java; -using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Benchmarks; - -public class DummyVersion : IVersion -{ - public string Id => throw new NotImplementedException(); - - public string? InheritsFrom => throw new NotImplementedException(); - - public IVersion? ParentVersion { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public AssetMetadata? AssetIndex => throw new NotImplementedException(); - - public MFileMetadata? Client => throw new NotImplementedException(); - - public JavaVersion? JavaVersion => throw new NotImplementedException(); - - public IReadOnlyCollection Libraries => throw new NotImplementedException(); - - public string? Jar => throw new NotImplementedException(); - - public MLogFileMetadata? Logging => throw new NotImplementedException(); - - public string? MainClass => throw new NotImplementedException(); - - public IReadOnlyCollection GameArguments => throw new NotImplementedException(); - - public IReadOnlyCollection JvmArguments => throw new NotImplementedException(); - - public DateTime ReleaseTime => throw new NotImplementedException(); - - public string? Type => throw new NotImplementedException(); - - public string MainJarId => throw new NotImplementedException(); - - public string? GetProperty(string key) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/Version/Extensions.cs b/src/Version/Extensions.cs index 4762ed0..3751a67 100644 --- a/src/Version/Extensions.cs +++ b/src/Version/Extensions.cs @@ -88,4 +88,27 @@ public static IEnumerable EnumerateFromParent(this IVersion version, i yield return stack.Pop(); } } + + public static MinecraftVersion ToMutableVersion(this IVersion self) + { + return new MinecraftVersion(self.Id) + { + MainJarId = self.MainJarId, + InheritsFrom = self.InheritsFrom, + ParentVersion = self.ParentVersion, + AssetIndex = self.AssetIndex, + Client = self.Client, + JavaVersion = self.JavaVersion, + Libraries = self.Libraries, + Jar = self.Jar, + Logging = self.Logging, + MainClass = self.MainClass, + ReleaseTime = self.ReleaseTime, + Type = self.Type, + GameArguments = self.GetGameArguments(false), + GameArgumentsForBaseVersion = self.GetGameArguments(true), + JvmArguments = self.GetJvmArguments(false), + JvmArgumentsForBaseVersion = self.GetJvmArguments(true) + }; + } } \ No newline at end of file diff --git a/src/Version/MinecraftVersion.cs b/src/Version/MinecraftVersion.cs new file mode 100644 index 0000000..a2a6360 --- /dev/null +++ b/src/Version/MinecraftVersion.cs @@ -0,0 +1,41 @@ +using CmlLib.Core.Files; +using CmlLib.Core.Java; +using CmlLib.Core.ProcessBuilder; + +namespace CmlLib.Core.Version; + +public class MinecraftVersion : IVersion +{ + public MinecraftVersion(string id) + { + Id = id; + MainJarId = id; + } + + public string Id { get; } + public string MainJarId { get; set; } + public string? InheritsFrom { get; set; } + public IVersion? ParentVersion { get; set; } + public AssetMetadata? AssetIndex { get; set; } + public MFileMetadata? Client { get; set; } + public JavaVersion? JavaVersion { get; set; } + public IReadOnlyCollection Libraries { get; set; } = Array.Empty(); + public string? Jar { get; set; } + public MLogFileMetadata? Logging { get; set; } + public string? MainClass { get; set; } + public DateTimeOffset ReleaseTime { get; set; } + public string? Type { get; set; } + + public IReadOnlyCollection GameArguments { get; set; } = Array.Empty(); + public IReadOnlyCollection GameArgumentsForBaseVersion { get; set; } = Array.Empty(); + public IReadOnlyCollection JvmArguments { get; set; } = Array.Empty(); + public IReadOnlyCollection JvmArgumentsForBaseVersion { get; set; } = Array.Empty(); + + public IReadOnlyCollection GetGameArguments(bool isBaseVersion) => + isBaseVersion ? GameArgumentsForBaseVersion : GameArguments; + + public IReadOnlyCollection GetJvmArguments(bool isBaseVersion) => + isBaseVersion ? JvmArgumentsForBaseVersion : JvmArguments; + + public string? GetProperty(string key) => null; +} \ No newline at end of file From 1c1fc6bd48ec4073d4c9836fa885828cf9c56c88 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 15:19:35 +0900 Subject: [PATCH 134/137] test: update tests --- TODO.md | 8 +- test/Version/DummyVersion.cs | 46 ---------- test/Version/JsonArgumentParserTests.cs | 20 ++--- .../VersionMetadataCollectionTests.cs | 90 ++++++++++++------- 4 files changed, 75 insertions(+), 89 deletions(-) delete mode 100644 test/Version/DummyVersion.cs diff --git a/TODO.md b/TODO.md index f736ff7..4d82d6f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,12 @@ -- [ ] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 - [ ] 여기까지 하고 4.0.0-beta.1 내놓기 +- [x] Memory 위에서 mutable 한 IVersion 구현 - [ ] 라이트로더 패브릭 tlauncher 등등 확장가능하게 테스트 케이스 작성 -- [ ] MojangLauncher 와 호환성 확인: LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 -- [ ] Memory 위에서 mutable 한 IVersion 구현 +- [ ] excludes 구현 +- [ ] XML 로거 파서 +- [x] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 +- [x] MojangLauncher 와 호환성 확인: LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 - [x] master 브랜치에서 v3.4.0 으로 cherry-pick - [x] disableMultiplayer 같은 option 도 추가 -> ExtraGameArguments 로 추가해라 - [x] IUpdateTask 항상 실행해야 할듯 diff --git a/test/Version/DummyVersion.cs b/test/Version/DummyVersion.cs deleted file mode 100644 index e08b3dd..0000000 --- a/test/Version/DummyVersion.cs +++ /dev/null @@ -1,46 +0,0 @@ -using CmlLib.Core.Files; -using CmlLib.Core.Java; -using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.Version; - -namespace CmlLib.Core.Test.Version; - -public class DummyVersion : IVersion -{ - public DummyVersion(string id) => Id = id; - - public string Id { get; set; } - - public string? InheritsFrom { get; set; } - - public IVersion? ParentVersion { get; set; } - - public AssetMetadata? AssetIndex { get; set; } - - public MFileMetadata? Client { get; set; } - - public JavaVersion? JavaVersion { get; set; } - - public IReadOnlyCollection Libraries { get; set; } = Array.Empty(); - - public string? Jar { get; set; } - - public MLogFileMetadata? Logging { get; set; } - - public string? MainClass { get; set; } - - public IReadOnlyCollection GameArguments { get; set; } = Array.Empty(); - - public IReadOnlyCollection JvmArguments { get; set; } = Array.Empty(); - - public DateTime ReleaseTime { get; set; } - - public string? Type { get; set; } - - public string MainJarId { get; set; } - - public string? GetProperty(string key) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/test/Version/JsonArgumentParserTests.cs b/test/Version/JsonArgumentParserTests.cs index 9847ed7..ae8324e 100644 --- a/test/Version/JsonArgumentParserTests.cs +++ b/test/Version/JsonArgumentParserTests.cs @@ -9,7 +9,7 @@ public class JsonArgumentParserTests private static readonly JsonVersionParserOptions options = new JsonVersionParserOptions { Side = JsonVersionParserOptions.ClientSide, - SkipError = false + SkipError = true }; private static string[] getArguments(IEnumerable args) @@ -36,7 +36,7 @@ public void parse_from_vanilla_arg_string() // release 1.7.10 ~ : add --assetIndex, --userProperties, --userType var version = JsonVersionParser.ParseFromJsonString(vanilla_arg_string, options); - var parsedArgs = getArguments(version.GameArguments); + var parsedArgs = getArguments(version.GetGameArguments(false)); Assert.Equal( [ "--username", @@ -183,7 +183,7 @@ public void parse_from_vanilla_game_arg_array() // 1.13 ~ // release 1.20 ~ : add --quickPlayPath, --quickPlaySingleplayer, --quickPlayMultiplayer, --quickPlayRealms var version = JsonVersionParser.ParseFromJsonString(vanilla_game_arg_array, options); - var parsedArgs = getArguments(version.GameArguments); + var parsedArgs = getArguments(version.GetGameArguments(false)); Assert.Equal( [ "--username", @@ -300,7 +300,7 @@ public void parse_from_vanilla_jvm_arg_array() var version = JsonVersionParser.ParseFromJsonString(vanilla_jvm_arg_array, options); - var parsedArgs = getArguments(version.JvmArguments); + var parsedArgs = getArguments(version.GetJvmArguments(false)); Assert.Equal( [ "-XstartOnFirstThread", @@ -361,14 +361,14 @@ public void parse_forge_arguments() "net.minecraftforge", "--fml.mcpVersion", "20210115.111550" - ], getArguments(version.GameArguments)); + ], getArguments(version.GetGameArguments(false))); Assert.Equal( [ "-XX:+IgnoreUnrecognizedVMOptions", "--add-exports=java.base/sun.security.util=ALL-UNNAMED", "--add-exports=jdk.naming.dns/com.sun.jndi.dns=java.naming", "--add-opens=java.base/java.util.jar=ALL-UNNAMED" - ], getArguments(version.JvmArguments)); + ], getArguments(version.GetJvmArguments(false))); } public readonly static string forge_arguments_2 = """ @@ -422,7 +422,7 @@ public void parse_forge_arguments_2() "net.minecraftforge", "--fml.mcpVersion", "20210706.113038" - ], getArguments(version.GameArguments)); + ], getArguments(version.GetGameArguments(false))); Assert.Equal( [ "-DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,client-extra,fmlcore,javafmllanguage,mclanguage,forge-,${version_name}.jar", @@ -438,7 +438,7 @@ public void parse_forge_arguments_2() "java.base/sun.security.util=cpw.mods.securejarhandler", "--add-exports", "jdk.naming.dns/com.sun.jndi.dns=java.naming" - ], getArguments(version.JvmArguments)); + ], getArguments(version.GetJvmArguments(false))); } public readonly static string fabric_loader_arguments = """ @@ -457,7 +457,7 @@ public void parse_forge_arguments_2() public void parse_fabric_loader_arguments() { var version = JsonVersionParser.ParseFromJsonString(fabric_loader_arguments, options); - Assert.Empty(getArguments(version.GameArguments)); - Assert.Equal(["-DFabricMcEmu= net.minecraft.client.main.Main "], getArguments(version.JvmArguments)); + Assert.Empty(getArguments(version.GetGameArguments(false))); + Assert.Equal(["-DFabricMcEmu= net.minecraft.client.main.Main "], getArguments(version.GetJvmArguments(false))); } } \ No newline at end of file diff --git a/test/VersionMetadata/VersionMetadataCollectionTests.cs b/test/VersionMetadata/VersionMetadataCollectionTests.cs index c068ace..a0188ce 100644 --- a/test/VersionMetadata/VersionMetadataCollectionTests.cs +++ b/test/VersionMetadata/VersionMetadataCollectionTests.cs @@ -1,5 +1,4 @@ using CmlLib.Core.ProcessBuilder; -using CmlLib.Core.Test.Version; using CmlLib.Core.Version; using CmlLib.Core.VersionMetadata; using Moq; @@ -44,16 +43,41 @@ public async Task get_inherited_property() Assert.Equal("parent_mainclass", version.GetInheritedProperty(v => v.MainClass)); } + [Fact] public async Task get_inherited_collection() { var (collection, parent, child) = createMocks(); var version = await collection.GetVersionAsync("child"); - var concatArguments = version.ConcatInheritedCollection(v => v.GameArguments) - .SelectMany(args => args.Values ?? Enumerable.Empty()) + var concatLibraries = version.ConcatInheritedCollection(v => v.Libraries).Select(lib => lib.Name); + + // should keep the order + Assert.Equal(["parent_lib", "child_lib"], concatLibraries); + } + + [Fact] + public async Task get_game_arguments_with_base_arguments() + { + var (collection, parent, child) = createMocks(); + var version = await collection.GetVersionAsync("child"); + var gameArgs = version.ConcatInheritedGameArguments() + .SelectMany(arg => arg.Values) .ToArray(); // should keep the order - Assert.Equal(new []{ "parent_arg", "child_arg" }, concatArguments); + Assert.Equal(["parent_base_arg", "child_arg"], gameArgs); + } + + [Fact] + public async Task get_jvm_arguments_with_base_arguments() + { + var (collection, parent, child) = createMocks(); + var version = await collection.GetVersionAsync("child"); + var gameArgs = version.ConcatInheritedJvmArguments() + .SelectMany(arg => arg.Values) + .ToArray(); + + // should keep the order + Assert.Equal(["parent_base_arg", "child_arg"], gameArgs); } [Fact] @@ -98,10 +122,10 @@ public void throw_non_existent_parent_id() [Fact] public void throw_too_deep_inheritance() { - var v1 = new DummyVersion("v1"); - var v2 = new DummyVersion("v2"); - var v3 = new DummyVersion("v3"); - var v4 = new DummyVersion("v4"); + var v1 = new MinecraftVersion("v1"); + var v2 = new MinecraftVersion("v2"); + var v3 = new MinecraftVersion("v3"); + var v4 = new MinecraftVersion("v4"); v4.InheritsFrom = v3.Id; v3.InheritsFrom = v2.Id; @@ -125,10 +149,10 @@ public void throw_too_deep_inheritance() private (VersionMetadataCollection, VersionMetadataCollection) createTestCollections() { - var v1 = createMockVersionMetadata(new DummyVersion("v1")); - var v2 = createMockVersionMetadata(new DummyVersion("v2")); - var v3 = createMockVersionMetadata(new DummyVersion("v3")); - var v4 = createMockVersionMetadata(new DummyVersion("v4")); + var v1 = createMockVersionMetadata(new MinecraftVersion("v1")); + var v2 = createMockVersionMetadata(new MinecraftVersion("v2")); + var v3 = createMockVersionMetadata(new MinecraftVersion("v3")); + var v4 = createMockVersionMetadata(new MinecraftVersion("v4")); var collection1 = new VersionMetadataCollection(new IVersionMetadata[] { @@ -160,31 +184,37 @@ public void merge_latest_version_names() Assert.Equal("v4", collection1.LatestSnapshotName); } - private (VersionMetadataCollection, DummyVersion, DummyVersion) createMocks() + private (VersionMetadataCollection, MinecraftVersion, MinecraftVersion) createMocks() { - var parent = new DummyVersion("parent"); - parent.MainClass = "parent_mainclass"; - parent.AssetIndex = new Files.AssetMetadata - { - Id = "parent_assetindex" - }; - parent.GameArguments = new MArgument[] + var parent = new MinecraftVersion("parent") { - new MArgument("parent_arg") + MainClass = "parent_mainclass", + AssetIndex = new Files.AssetMetadata + { + Id = "parent_assetindex" + }, + GameArguments = [new MArgument("parent_arg")], + GameArgumentsForBaseVersion = [new MArgument("parent_base_arg")], + JvmArguments = [new MArgument("parent_arg")], + JvmArgumentsForBaseVersion = [new MArgument("parent_base_arg")], + Libraries = [new MLibrary("parent_lib")] }; - var child = new DummyVersion("child"); - child.AssetIndex = new Files.AssetMetadata - { - Id = "child_assetindex" - }; - child.GameArguments = new MArgument[] + var child = new MinecraftVersion("child") { - new MArgument("child_arg") + AssetIndex = new Files.AssetMetadata + { + Id = "child_assetindex" + }, + GameArguments = [new MArgument("child_arg")], + GameArgumentsForBaseVersion = [new MArgument("child_base_arg")], + JvmArguments = [new MArgument("child_arg")], + JvmArgumentsForBaseVersion = [new MArgument("child_base_arg")], + Libraries = [new MLibrary("child_lib")], + + InheritsFrom = parent.Id }; - child.InheritsFrom = parent.Id; - var collection = new VersionMetadataCollection( new IVersionMetadata[] { From 948d6d89042d6111076d85f5e19dafdc80e46b50 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 15:20:16 +0900 Subject: [PATCH 135/137] fix: path separator of library url should be /, not \ --- src/FileExtractors/LibraryFileExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FileExtractors/LibraryFileExtractor.cs b/src/FileExtractors/LibraryFileExtractor.cs index f7c18d3..12c932f 100644 --- a/src/FileExtractors/LibraryFileExtractor.cs +++ b/src/FileExtractors/LibraryFileExtractor.cs @@ -80,7 +80,7 @@ public static IEnumerable ExtractTasks( return null; if (url == null) - url = server + path; + url = server + path.Replace("\\", "/"); else if (url == "") url = null; else if (url.Split('/').Last() == "") From d1ce5f72eaa946db47354ee740ae1bd131e1e161 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 20:51:37 +0900 Subject: [PATCH 136/137] misc: update examples --- examples/console/FabricTester.cs | 24 + examples/console/LauncherTester.cs | 119 +++- examples/console/LiteLoaderTester.cs | 27 + examples/console/Program.cs | 77 +-- examples/winform/JavaForm.Designer.cs | 113 ---- examples/winform/JavaForm.cs | 37 -- examples/winform/JavaForm.resx | 120 ---- examples/winform/LoginForm.Designer.cs | 287 ---------- examples/winform/LoginForm.cs | 56 -- examples/winform/LoginForm.resx | 120 ---- examples/winform/MainForm.Designer.cs | 749 ++++++++++++++++--------- examples/winform/MainForm.cs | 247 ++++---- examples/winform/MainForm.resx | 3 + examples/winform/Program.cs | 2 +- examples/winform/Util.cs | 30 +- src/MinecraftLauncher.cs | 4 +- 16 files changed, 845 insertions(+), 1170 deletions(-) create mode 100644 examples/console/FabricTester.cs create mode 100644 examples/console/LiteLoaderTester.cs delete mode 100644 examples/winform/JavaForm.Designer.cs delete mode 100644 examples/winform/JavaForm.cs delete mode 100644 examples/winform/JavaForm.resx delete mode 100644 examples/winform/LoginForm.Designer.cs delete mode 100644 examples/winform/LoginForm.cs delete mode 100644 examples/winform/LoginForm.resx diff --git a/examples/console/FabricTester.cs b/examples/console/FabricTester.cs new file mode 100644 index 0000000..2b36ad6 --- /dev/null +++ b/examples/console/FabricTester.cs @@ -0,0 +1,24 @@ +using CmlLib.Core; +using CmlLib.Core.ModLoaders.FabricMC; + +namespace CmlLibCoreSample; + +public class FabricTester +{ + private readonly MinecraftPath _minecraftPath; + + public FabricTester(MinecraftPath minecraftPath) => + _minecraftPath = minecraftPath; + + public async Task Test() + { + var fabricInstaller = new FabricInstaller(new HttpClient()); + var versions = await fabricInstaller.GetSupportedVersionNames(); + + foreach (var version in versions) + { + Console.WriteLine("Install " + version); + await fabricInstaller.Install(version, _minecraftPath); + } + } +} \ No newline at end of file diff --git a/examples/console/LauncherTester.cs b/examples/console/LauncherTester.cs index fbde59b..0009ecf 100644 --- a/examples/console/LauncherTester.cs +++ b/examples/console/LauncherTester.cs @@ -6,26 +6,129 @@ namespace CmlLibCoreSample; public class LauncherTester { + static readonly string[] TargetVersions = new[] + { + "rd-132211", + + "1.0", + "1.2.5", + "1.3.2", + "1.4.7", + "1.5.2", + "1.6.4", + "1.7.2", + "1.7.4", + "1.7.10", + "1.8.9", + "1.9.4", + "1.10.2", + "1.11.2", + "1.12.2", + "1.13.2", + "1.14.4", + "1.15.2", + "1.16.5", + "1.17.1", + "1.18.2", + "1.19.4", + "1.20.4", + "3D Shareware v1.34", + + "1.7.10-Forge10.13.4.1558-1.7.10", + "1.7.10-Forge10.13.4.1614-1.7.10", + "1.8.9-forge1.8.9-11.15.1.1722", + "1.8.9-OptiFine_HD_U_M5", + "1.12.2-forge1.12.2-14.23.4.2705", + "1.12.2-forge1.12.2-14.23.5.2847", + "1.12.2-forge-14.23.5.2854", + "1.12.2-forge-14.23.5.2860", + "1.12.2-OptiFine_HD_U_G5", + "1.16.1-forge-32.0.47", + "1.16.5-forge-36.2.0", + "1.16.5-forge-36.2.34", + "1.16.5-OptiFine_HD_U_G8", + "1.17.1-forge-37.0.48", + "1.17.1-forge-37.0.75", + "1.17.1-OptiFine_HD_U_G9", + "1.18.1-forge-39.0.5", + "1.18.1-forge-39.1.2", + "1.18.1-OptiFine_HD_U_H4", + "1.18.2-forge-40.0.41", + "1.19.4-forge-45.1.0", + "1.20.1-forge-47.1.0", + "1.20.2-forge-48.0.40", + "1.20.4-forge-49.0.31", + + "fabric-loader-0.13.3-1.18.2", + "fabric-loader-0.15.7-1.14", + "fabric-loader-0.15.7-1.14.1", + "fabric-loader-0.15.7-1.14.2", + "fabric-loader-0.15.7-1.14.3", + "fabric-loader-0.15.7-1.14.4", + "fabric-loader-0.15.7-1.15", + "fabric-loader-0.15.7-1.15.1", + "fabric-loader-0.15.7-1.15.2", + "fabric-loader-0.15.7-1.16", + "fabric-loader-0.15.7-1.16.1", + "fabric-loader-0.15.7-1.16.2", + "fabric-loader-0.15.7-1.16.3", + "fabric-loader-0.15.7-1.16.4", + "fabric-loader-0.15.7-1.16.5", + "fabric-loader-0.15.7-1.17", + "fabric-loader-0.15.7-1.17.1", + "fabric-loader-0.15.7-1.18", + "fabric-loader-0.15.7-1.18.1", + "fabric-loader-0.15.7-1.18.2", + "fabric-loader-0.15.7-1.19", + "fabric-loader-0.15.7-1.19.1", + "fabric-loader-0.15.7-1.19.2", + "fabric-loader-0.15.7-1.19.3", + "fabric-loader-0.15.7-1.19.4", + "fabric-loader-0.15.7-1.20", + "fabric-loader-0.15.7-1.20.1", + "fabric-loader-0.15.7-1.20.2", + "fabric-loader-0.15.7-1.20.3", + "fabric-loader-0.15.7-1.20.4", + + "neoforge-20.4.196", + + "1.9.4-LiteLoader1.9.4", + "1.9-SNAPSHOT-LiteLoader1.9", + "1.8.9-SNAPSHOT-LiteLoader1.8.9", + "1.8-LiteLoader1.8", + "1.7.2_05-LiteLoader1.7.2", + "1.7.10_04-LiteLoader1.7.10", + "1.6.4_01-LiteLoader1.6.4", + "1.6.2_04-LiteLoader1.6.2", + "1.5.2_01-LiteLoader1.5.2", + "1.12.2-SNAPSHOT-LiteLoader1.12.2", + "1.12.1-SNAPSHOT-LiteLoader1.12.1", + "1.12-SNAPSHOT-LiteLoader1.12", + "1.11.2-SNAPSHOT-LiteLoader1.11.2", + "1.11-SNAPSHOT-LiteLoader1.11", + "1.10.2-LiteLoader1.10.2", + "1.10-SNAPSHOT-LiteLoader1.10" + }; + private readonly MinecraftLauncher _launcher; - public LauncherTester(string id) + public LauncherTester(string id, MinecraftLauncher launcher) { Id = id; - var path = new MinecraftPath("C:\\Users\\ksi12\\AppData\\Roaming\\minecraft test"); - _launcher = new MinecraftLauncher(path); + _launcher = launcher; } public string Id { get; } - public int AliveTimeSec = 10; + public int AliveTimeSec = 30; public string OutputDirectory = "./outputs"; private StreamWriter? currentOutputStream; private string? currentOutputPath; - public async Task Start(IEnumerable targetVersions) + public async Task Start() { prepareOutputs(); - foreach (var version in targetVersions) + foreach (var version in TargetVersions) { if (checkTested(version)) { @@ -155,7 +258,7 @@ private void completeCrashedOutput(string version) currentOutputStream.Flush(); currentOutputStream.Dispose(); - Directory.CreateDirectory(Path.GetDirectoryName(getOutputPath(version))!); - File.Move(currentOutputPath, getOutputPath(version)); + Directory.CreateDirectory(Path.GetDirectoryName(getCrashedOutputPath(version))!); + File.Move(currentOutputPath, getCrashedOutputPath(version), true); } } \ No newline at end of file diff --git a/examples/console/LiteLoaderTester.cs b/examples/console/LiteLoaderTester.cs new file mode 100644 index 0000000..f2e63d0 --- /dev/null +++ b/examples/console/LiteLoaderTester.cs @@ -0,0 +1,27 @@ +using CmlLib.Core; +using CmlLib.Core.ModLoaders.LiteLoader; + +namespace CmlLibCoreSample; + +public class LiteLoaderTester +{ + private readonly MinecraftLauncher _launcher; + + public LiteLoaderTester(MinecraftLauncher launcher) + { + _launcher = launcher; + } + + public async Task Test() + { + var liteLoaderInstaller = new LiteLoaderInstaller(new HttpClient()); + var loaders = await liteLoaderInstaller.GetAllLiteLoaders(); + + foreach (var loader in loaders) + { + Console.WriteLine(loader.Version); + var baseVersion = await _launcher.GetVersionAsync(loader.BaseVersion!); + await liteLoaderInstaller.Install(loader, baseVersion, _launcher.MinecraftPath); + } + } +} \ No newline at end of file diff --git a/examples/console/Program.cs b/examples/console/Program.cs index 2eb884d..e37f0ee 100644 --- a/examples/console/Program.cs +++ b/examples/console/Program.cs @@ -10,64 +10,23 @@ class Program { public static async Task Main() { - //var p = new Program(); - //await p.Start(); + var p = new Program(); + await p.Start(); + return; + + //var path = new MinecraftPath("C:\\Users\\ksi12\\AppData\\Roaming\\minecraft test"); + //var launcher = new MinecraftLauncher(path); + + //var fabricTester = new FabricTester(path); + //await fabricTester.Test(); //return; - var tester = new LauncherTester("b"); - await tester.Start(new[] - { - "1.0", - "1.2.5", - "1.3.2", - "1.4.7", - "1.5.2", - "1.6.4", - "1.7.2", - "1.7.4", - "1.7.10", - "1.8.9", - "1.9.4", - "1.10.2", - "1.11.2", - "1.12.2", - "1.13.2", - "1.14.4", - "1.15.2", - "1.16.5", - "1.17.1", - "1.18.2", - "1.19.4", - "1.20.4", - "1.7.10-Forge10.13.4.1558-1.7.10", - "1.7.10-Forge10.13.4.1614-1.7.10", - "1.8.9-forge1.8.9-11.15.1.1722", - "1.8.9-OptiFine_HD_U_M5", - "1.12.2-forge1.12.2-14.23.4.2705", - "1.12.2-forge1.12.2-14.23.5.2847", - "1.12.2-forge-14.23.5.2854", - "1.12.2-forge-14.23.5.2860", - "1.12.2-OptiFine_HD_U_G5", - "1.16.1-forge-32.0.47", - "1.16.5-forge-36.2.0", - "1.16.5-forge-36.2.34", - "1.16.5-OptiFine_HD_U_G8", - "1.17.1-forge-37.0.48", - "1.17.1-forge-37.0.75", - "1.17.1-OptiFine_HD_U_G9", - "1.18.1-forge-39.0.5", - "1.18.1-forge-39.1.2", - "1.18.1-OptiFine_HD_U_H4", - "1.18.2-forge-40.0.41", - "1.19.4-forge-45.1.0", - "1.20.1-forge-47.1.0", - "1.20.2-forge-48.0.40", - "1.20.4-forge-49.0.31", - "3D Shareware v1.34", - "fabric-loader-0.13.3-1.18.2", - "neoforge-20.4.196" - }); - return; + //var llTester = new LiteLoaderTester(launcher); + //await llTester.Test(); + //return; + + //var tester = new LauncherTester("b", launcher); + //await tester.Start(); } private async Task Start() @@ -77,10 +36,6 @@ private async Task Start() // initialize launcher var parameters = MinecraftLauncherParameters.CreateDefault(); var launcher = new MinecraftLauncher(parameters); - - // add event handler - //launcher.FileProgressChanged += Launcher_FileProgressChanged; - //launcher.ByteProgressChanged += Launcher_ByteProgressChanged; // list versions var versions = await launcher.GetAllVersionsAsync(); @@ -123,7 +78,7 @@ await launcher.InstallAsync( Console.WriteLine(process.StartInfo.FileName); Console.WriteLine("Arguments:"); Console.WriteLine(process.StartInfo.Arguments); - return; + var processWrapper = new ProcessWrapper(process); processWrapper.OutputReceived += (s, e) => Console.WriteLine(e); processWrapper.StartWithEvents(); diff --git a/examples/winform/JavaForm.Designer.cs b/examples/winform/JavaForm.Designer.cs deleted file mode 100644 index 23393d8..0000000 --- a/examples/winform/JavaForm.Designer.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace CmlLibWinFormSample -{ - partial class JavaForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.label4 = new System.Windows.Forms.Label(); - this.txtUserJava = new System.Windows.Forms.TextBox(); - this.label3 = new System.Windows.Forms.Label(); - this.btnClose = new System.Windows.Forms.Button(); - this.cbAutoJava = new System.Windows.Forms.CheckBox(); - this.SuspendLayout(); - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(14, 96); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(175, 30); - this.label4.TabIndex = 5; - this.label4.Text = "Input java binary file path.\r\nex) java.exe, javaw.exe"; - // - // txtUserJava - // - this.txtUserJava.Location = new System.Drawing.Point(107, 52); - this.txtUserJava.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.txtUserJava.Name = "txtUserJava"; - this.txtUserJava.Size = new System.Drawing.Size(357, 25); - this.txtUserJava.TabIndex = 6; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(14, 55); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(87, 15); - this.label3.TabIndex = 5; - this.label3.Text = "Java Path : "; - // - // btnClose - // - this.btnClose.Location = new System.Drawing.Point(14, 130); - this.btnClose.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(450, 45); - this.btnClose.TabIndex = 6; - this.btnClose.Text = "Close"; - this.btnClose.UseVisualStyleBackColor = true; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // cbAutoJava - // - this.cbAutoJava.Location = new System.Drawing.Point(12, 21); - this.cbAutoJava.Name = "cbAutoJava"; - this.cbAutoJava.Size = new System.Drawing.Size(215, 24); - this.cbAutoJava.TabIndex = 7; - this.cbAutoJava.Text = "Set java path automatically"; - this.cbAutoJava.UseVisualStyleBackColor = true; - this.cbAutoJava.CheckedChanged += new System.EventHandler(this.cbAutoJava_CheckedChanged); - // - // JavaForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(479, 188); - this.Controls.Add(this.cbAutoJava); - this.Controls.Add(this.label4); - this.Controls.Add(this.btnClose); - this.Controls.Add(this.label3); - this.Controls.Add(this.txtUserJava); - this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Name = "JavaForm"; - this.Text = "JavaForm"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.JavaForm_FormClosing); - this.Load += new System.EventHandler(this.JavaForm_Load); - this.ResumeLayout(false); - this.PerformLayout(); - } - - private System.Windows.Forms.CheckBox cbAutoJava; - - #endregion - - private System.Windows.Forms.Label label4; - private System.Windows.Forms.TextBox txtUserJava; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.Button btnClose; - } -} \ No newline at end of file diff --git a/examples/winform/JavaForm.cs b/examples/winform/JavaForm.cs deleted file mode 100644 index e641da4..0000000 --- a/examples/winform/JavaForm.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace CmlLibWinFormSample -{ - public partial class JavaForm : Form - { - public string? JavaBinaryPath { get; set; } - - public JavaForm(string? javaPath) - { - this.JavaBinaryPath = javaPath; - InitializeComponent(); - } - - private void JavaForm_Load(object sender, EventArgs e) - { - txtUserJava.Text = JavaBinaryPath; - if (string.IsNullOrEmpty(JavaBinaryPath)) - cbAutoJava.Checked = true; - } - - private void btnClose_Click(object sender, EventArgs e) - { - this.Close(); - } - - private void JavaForm_FormClosing(object sender, FormClosingEventArgs e) - { - this.JavaBinaryPath = txtUserJava.Text; - } - - private void cbAutoJava_CheckedChanged(object sender, EventArgs e) - { - if (cbAutoJava.Checked) - txtUserJava.Clear(); - txtUserJava.Enabled = !cbAutoJava.Checked; - } - } -} diff --git a/examples/winform/JavaForm.resx b/examples/winform/JavaForm.resx deleted file mode 100644 index 1af7de1..0000000 --- a/examples/winform/JavaForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/examples/winform/LoginForm.Designer.cs b/examples/winform/LoginForm.Designer.cs deleted file mode 100644 index 3b9926a..0000000 --- a/examples/winform/LoginForm.Designer.cs +++ /dev/null @@ -1,287 +0,0 @@ -namespace CmlLibWinFormSample -{ - partial class LoginForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.label5 = new System.Windows.Forms.Label(); - this.label6 = new System.Windows.Forms.Label(); - this.txtEmail = new System.Windows.Forms.TextBox(); - this.txtPassword = new System.Windows.Forms.TextBox(); - this.btnLogin = new System.Windows.Forms.Button(); - this.btnSignout = new System.Windows.Forms.Button(); - this.btnInvalidate = new System.Windows.Forms.Button(); - this.btnDeleteToken = new System.Windows.Forms.Button(); - this.label7 = new System.Windows.Forms.Label(); - this.gMojangLogin = new System.Windows.Forms.GroupBox(); - this.btnAutoLogin = new System.Windows.Forms.Button(); - this.gOfflineLogin = new System.Windows.Forms.GroupBox(); - this.btnOfflineLogin = new System.Windows.Forms.Button(); - this.txtUsername = new System.Windows.Forms.TextBox(); - this.label8 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.gMojangLogin.SuspendLayout(); - this.gOfflineLogin.SuspendLayout(); - this.SuspendLayout(); - // - // label5 - // - this.label5.AutoSize = true; - this.label5.Location = new System.Drawing.Point(41, 105); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(56, 15); - this.label5.TabIndex = 5; - this.label5.Text = "Email : "; - // - // label6 - // - this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(13, 135); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(87, 15); - this.label6.TabIndex = 6; - this.label6.Text = "Password : "; - // - // txtEmail - // - this.txtEmail.Location = new System.Drawing.Point(104, 101); - this.txtEmail.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.txtEmail.Name = "txtEmail"; - this.txtEmail.Size = new System.Drawing.Size(325, 25); - this.txtEmail.TabIndex = 7; - // - // txtPassword - // - this.txtPassword.Location = new System.Drawing.Point(104, 131); - this.txtPassword.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.txtPassword.Name = "txtPassword"; - this.txtPassword.Size = new System.Drawing.Size(325, 25); - this.txtPassword.TabIndex = 8; - this.txtPassword.UseSystemPasswordChar = true; - // - // btnLogin - // - this.btnLogin.Location = new System.Drawing.Point(435, 100); - this.btnLogin.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnLogin.Name = "btnLogin"; - this.btnLogin.Size = new System.Drawing.Size(86, 59); - this.btnLogin.TabIndex = 9; - this.btnLogin.Text = "Login"; - this.btnLogin.UseVisualStyleBackColor = true; - this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click); - // - // btnSignout - // - this.btnSignout.Location = new System.Drawing.Point(104, 179); - this.btnSignout.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnSignout.Name = "btnSignout"; - this.btnSignout.Size = new System.Drawing.Size(86, 29); - this.btnSignout.TabIndex = 10; - this.btnSignout.Text = "Signout"; - this.btnSignout.UseVisualStyleBackColor = true; - this.btnSignout.Click += new System.EventHandler(this.btnSignout_Click); - // - // btnInvalidate - // - this.btnInvalidate.Location = new System.Drawing.Point(197, 179); - this.btnInvalidate.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnInvalidate.Name = "btnInvalidate"; - this.btnInvalidate.Size = new System.Drawing.Size(86, 29); - this.btnInvalidate.TabIndex = 11; - this.btnInvalidate.Text = "Invalidate"; - this.btnInvalidate.UseVisualStyleBackColor = true; - this.btnInvalidate.Click += new System.EventHandler(this.btnInvalidate_Click); - // - // btnDeleteToken - // - this.btnDeleteToken.Location = new System.Drawing.Point(289, 179); - this.btnDeleteToken.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnDeleteToken.Name = "btnDeleteToken"; - this.btnDeleteToken.Size = new System.Drawing.Size(112, 29); - this.btnDeleteToken.TabIndex = 12; - this.btnDeleteToken.Text = "Delete Token"; - this.btnDeleteToken.UseVisualStyleBackColor = true; - this.btnDeleteToken.Click += new System.EventHandler(this.btnDeleteToken_Click); - // - // label7 - // - this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(34, 185); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(68, 15); - this.label7.TabIndex = 13; - this.label7.Text = "Logout : "; - // - // gMojangLogin - // - this.gMojangLogin.Controls.Add(this.btnAutoLogin); - this.gMojangLogin.Controls.Add(this.btnLogin); - this.gMojangLogin.Controls.Add(this.label7); - this.gMojangLogin.Controls.Add(this.label5); - this.gMojangLogin.Controls.Add(this.btnDeleteToken); - this.gMojangLogin.Controls.Add(this.label6); - this.gMojangLogin.Controls.Add(this.btnInvalidate); - this.gMojangLogin.Controls.Add(this.txtEmail); - this.gMojangLogin.Controls.Add(this.btnSignout); - this.gMojangLogin.Controls.Add(this.txtPassword); - this.gMojangLogin.Location = new System.Drawing.Point(12, 65); - this.gMojangLogin.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.gMojangLogin.Name = "gMojangLogin"; - this.gMojangLogin.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.gMojangLogin.Size = new System.Drawing.Size(527, 220); - this.gMojangLogin.TabIndex = 14; - this.gMojangLogin.TabStop = false; - this.gMojangLogin.Text = "Mojang Login"; - // - // btnAutoLogin - // - this.btnAutoLogin.Location = new System.Drawing.Point(107, 64); - this.btnAutoLogin.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnAutoLogin.Name = "btnAutoLogin"; - this.btnAutoLogin.Size = new System.Drawing.Size(322, 29); - this.btnAutoLogin.TabIndex = 14; - this.btnAutoLogin.Text = "TryAutoLogin (CmlLib.Core cache file)"; - this.btnAutoLogin.UseVisualStyleBackColor = true; - this.btnAutoLogin.Click += new System.EventHandler(this.btnAutoLogin_Click); - // - // gOfflineLogin - // - this.gOfflineLogin.Controls.Add(this.btnOfflineLogin); - this.gOfflineLogin.Controls.Add(this.txtUsername); - this.gOfflineLogin.Controls.Add(this.label8); - this.gOfflineLogin.Location = new System.Drawing.Point(12, 326); - this.gOfflineLogin.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.gOfflineLogin.Name = "gOfflineLogin"; - this.gOfflineLogin.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.gOfflineLogin.Size = new System.Drawing.Size(527, 72); - this.gOfflineLogin.TabIndex = 15; - this.gOfflineLogin.TabStop = false; - this.gOfflineLogin.Text = "Offline Login"; - // - // btnOfflineLogin - // - this.btnOfflineLogin.Location = new System.Drawing.Point(435, 25); - this.btnOfflineLogin.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.btnOfflineLogin.Name = "btnOfflineLogin"; - this.btnOfflineLogin.Size = new System.Drawing.Size(86, 26); - this.btnOfflineLogin.TabIndex = 14; - this.btnOfflineLogin.Text = "Login"; - this.btnOfflineLogin.UseVisualStyleBackColor = true; - this.btnOfflineLogin.Click += new System.EventHandler(this.btnOfflineLogin_Click); - // - // txtUsername - // - this.txtUsername.Location = new System.Drawing.Point(104, 25); - this.txtUsername.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.txtUsername.Name = "txtUsername"; - this.txtUsername.Size = new System.Drawing.Size(322, 25); - this.txtUsername.TabIndex = 1; - // - // label8 - // - this.label8.AutoSize = true; - this.label8.Location = new System.Drawing.Point(13, 29); - this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(87, 15); - this.label8.TabIndex = 0; - this.label8.Text = "Username : "; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Font = new System.Drawing.Font("굴림", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); - this.label2.Location = new System.Drawing.Point(8, 15); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(532, 19); - this.label2.TabIndex = 17; - this.label2.Text = "CmlLib.Core (.NET Framework WinForm) Sample Launcher"; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); - this.label1.Location = new System.Drawing.Point(19, 42); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(317, 15); - this.label1.TabIndex = 18; - this.label1.Text = "Please login with your mojang account : "; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.Font = new System.Drawing.Font("굴림", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129))); - this.label3.Location = new System.Drawing.Point(12, 298); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(280, 15); - this.label3.TabIndex = 19; - this.label3.Text = "or, Just type a username you want :"; - // - // LoginForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(551, 410); - this.Controls.Add(this.label3); - this.Controls.Add(this.label1); - this.Controls.Add(this.label2); - this.Controls.Add(this.gOfflineLogin); - this.Controls.Add(this.gMojangLogin); - this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.Name = "LoginForm"; - this.Text = "LoginForm"; - this.Load += new System.EventHandler(this.LoginForm_Load); - this.gMojangLogin.ResumeLayout(false); - this.gMojangLogin.PerformLayout(); - this.gOfflineLogin.ResumeLayout(false); - this.gOfflineLogin.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - } - - #endregion - private System.Windows.Forms.Label label5; - private System.Windows.Forms.Label label6; - private System.Windows.Forms.TextBox txtEmail; - private System.Windows.Forms.TextBox txtPassword; - private System.Windows.Forms.Button btnLogin; - private System.Windows.Forms.Button btnSignout; - private System.Windows.Forms.Button btnInvalidate; - private System.Windows.Forms.Button btnDeleteToken; - private System.Windows.Forms.Label label7; - private System.Windows.Forms.GroupBox gMojangLogin; - private System.Windows.Forms.Button btnAutoLogin; - private System.Windows.Forms.GroupBox gOfflineLogin; - private System.Windows.Forms.Button btnOfflineLogin; - private System.Windows.Forms.TextBox txtUsername; - private System.Windows.Forms.Label label8; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label label3; - } -} \ No newline at end of file diff --git a/examples/winform/LoginForm.cs b/examples/winform/LoginForm.cs deleted file mode 100644 index 41b5857..0000000 --- a/examples/winform/LoginForm.cs +++ /dev/null @@ -1,56 +0,0 @@ -using CmlLib.Core.Auth; - -namespace CmlLibWinFormSample -{ - public partial class LoginForm : Form - { - public LoginForm() - { - InitializeComponent(); - } - - private void LoginForm_Load(object sender, EventArgs e) - { - } - - private void btnAutoLogin_Click(object sender, EventArgs e) - { - - } - - private void btnLogin_Click(object sender, EventArgs e) - { - - } - - private void btnSignout_Click(object sender, EventArgs e) - { - - } - - private void btnInvalidate_Click(object sender, EventArgs e) - { - - } - - private void btnDeleteToken_Click(object sender, EventArgs e) - { - - } - - private void btnOfflineLogin_Click(object sender, EventArgs e) - { - UpdateSession(MSession.CreateOfflineSession(txtUsername.Text)); - } - - private void UpdateSession(MSession session) - { - // Success to login! - - var mainForm = new MainForm(session); - mainForm.FormClosed += (s, e) => this.Close(); - mainForm.Show(); - this.Hide(); - } - } -} diff --git a/examples/winform/LoginForm.resx b/examples/winform/LoginForm.resx deleted file mode 100644 index 1af7de1..0000000 --- a/examples/winform/LoginForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/examples/winform/MainForm.Designer.cs b/examples/winform/MainForm.Designer.cs index ebdfeb2..4db0217 100644 --- a/examples/winform/MainForm.Designer.cs +++ b/examples/winform/MainForm.Designer.cs @@ -28,44 +28,58 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + components = new System.ComponentModel.Container(); groupBox2 = new GroupBox(); + txtFeatures = new TextBox(); + label26 = new Label(); + txtExtraGameArguments = new TextBox(); + label25 = new Label(); + txtExtraJVMArguments = new TextBox(); + label24 = new Label(); + txtJVMArgumentOverrides = new TextBox(); + label11 = new Label(); + cbDemo = new CheckBox(); + txtClientId = new TextBox(); + label23 = new Label(); + txtQuickPlayReamls = new TextBox(); + label22 = new Label(); + txtQuickPlaySingleplay = new TextBox(); + label20 = new Label(); + txtQuickPlayPath = new TextBox(); + label19 = new Label(); cbFullscreen = new CheckBox(); btnAutoRamSet = new Button(); - Txt_DockIcon = new TextBox(); + txtDockIcon = new TextBox(); txtXms = new TextBox(); label17 = new Label(); - Txt_DockName = new TextBox(); + txtDockName = new TextBox(); label21 = new Label(); label18 = new Label(); - Txt_GLauncherVersion = new TextBox(); + txtGLauncherVersion = new TextBox(); label16 = new Label(); - Txt_GLauncherName = new TextBox(); + txtGLauncherName = new TextBox(); label15 = new Label(); - Txt_ServerPort = new TextBox(); - TxtXmx = new TextBox(); + txtServerPort = new TextBox(); + txtXmx = new TextBox(); label14 = new Label(); - Txt_JavaArgs = new TextBox(); Xmx_RAM = new Label(); - Txt_ScHt = new TextBox(); - Txt_ScWd = new TextBox(); - label11 = new Label(); + txtScreenHeight = new TextBox(); + txtScreenWidth = new TextBox(); label10 = new Label(); label9 = new Label(); - Txt_ServerIp = new TextBox(); - Txt_VersionType = new TextBox(); + txtServerIP = new TextBox(); + txtVersionType = new TextBox(); label8 = new Label(); label7 = new Label(); Pb_Progress = new ProgressBar(); Lv_Status = new Label(); groupBox1 = new GroupBox(); - btnChangeJava = new Button(); - lbJavaPath = new Label(); - lbUsername = new Label(); + cbJavaUseDefault = new CheckBox(); + txtJava = new TextBox(); label6 = new Label(); btnChangePath = new Button(); txtPath = new TextBox(); label4 = new Label(); - label2 = new Label(); btnLaunch = new Button(); cbVersion = new ComboBox(); label1 = new Label(); @@ -73,64 +87,230 @@ private void InitializeComponent() btnGithub = new Button(); btnWiki = new Button(); btnChangelog = new Button(); - rbSequenceDownload = new RadioButton(); - rbParallelDownload = new RadioButton(); - groupBox3 = new GroupBox(); - cbSkipHashCheck = new CheckBox(); - cbSkipAssetsDownload = new CheckBox(); groupBox4 = new GroupBox(); + btnCancel = new Button(); btnSortFilter = new Button(); btnRefreshVersion = new Button(); btnSetLastVersion = new Button(); btnOptions = new Button(); lbLibraryVersion = new Label(); + groupBox3 = new GroupBox(); + btnLogout = new Button(); + btnLogin = new Button(); + label13 = new Label(); + label5 = new Label(); + label3 = new Label(); + label2 = new Label(); + txtXUID = new TextBox(); + txtUUID = new TextBox(); + txtAccessToken = new TextBox(); + txtUsername = new TextBox(); + lbTime = new Label(); + eventTimer = new System.Windows.Forms.Timer(components); groupBox2.SuspendLayout(); groupBox1.SuspendLayout(); - groupBox3.SuspendLayout(); groupBox4.SuspendLayout(); + groupBox3.SuspendLayout(); SuspendLayout(); // // groupBox2 // + groupBox2.Controls.Add(txtFeatures); + groupBox2.Controls.Add(label26); + groupBox2.Controls.Add(txtExtraGameArguments); + groupBox2.Controls.Add(label25); + groupBox2.Controls.Add(txtExtraJVMArguments); + groupBox2.Controls.Add(label24); + groupBox2.Controls.Add(txtJVMArgumentOverrides); + groupBox2.Controls.Add(label11); + groupBox2.Controls.Add(cbDemo); + groupBox2.Controls.Add(txtClientId); + groupBox2.Controls.Add(label23); + groupBox2.Controls.Add(txtQuickPlayReamls); + groupBox2.Controls.Add(label22); + groupBox2.Controls.Add(txtQuickPlaySingleplay); + groupBox2.Controls.Add(label20); + groupBox2.Controls.Add(txtQuickPlayPath); + groupBox2.Controls.Add(label19); groupBox2.Controls.Add(cbFullscreen); groupBox2.Controls.Add(btnAutoRamSet); - groupBox2.Controls.Add(Txt_DockIcon); + groupBox2.Controls.Add(txtDockIcon); groupBox2.Controls.Add(txtXms); groupBox2.Controls.Add(label17); - groupBox2.Controls.Add(Txt_DockName); + groupBox2.Controls.Add(txtDockName); groupBox2.Controls.Add(label21); groupBox2.Controls.Add(label18); - groupBox2.Controls.Add(Txt_GLauncherVersion); + groupBox2.Controls.Add(txtGLauncherVersion); groupBox2.Controls.Add(label16); - groupBox2.Controls.Add(Txt_GLauncherName); + groupBox2.Controls.Add(txtGLauncherName); groupBox2.Controls.Add(label15); - groupBox2.Controls.Add(Txt_ServerPort); - groupBox2.Controls.Add(TxtXmx); + groupBox2.Controls.Add(txtServerPort); + groupBox2.Controls.Add(txtXmx); groupBox2.Controls.Add(label14); - groupBox2.Controls.Add(Txt_JavaArgs); groupBox2.Controls.Add(Xmx_RAM); - groupBox2.Controls.Add(Txt_ScHt); - groupBox2.Controls.Add(Txt_ScWd); - groupBox2.Controls.Add(label11); + groupBox2.Controls.Add(txtScreenHeight); + groupBox2.Controls.Add(txtScreenWidth); groupBox2.Controls.Add(label10); groupBox2.Controls.Add(label9); - groupBox2.Controls.Add(Txt_ServerIp); - groupBox2.Controls.Add(Txt_VersionType); + groupBox2.Controls.Add(txtServerIP); + groupBox2.Controls.Add(txtVersionType); groupBox2.Controls.Add(label8); groupBox2.Controls.Add(label7); groupBox2.Location = new Point(405, 15); groupBox2.Margin = new Padding(3, 4, 3, 4); groupBox2.Name = "groupBox2"; groupBox2.Padding = new Padding(3, 4, 3, 4); - groupBox2.Size = new Size(385, 447); + groupBox2.Size = new Size(720, 447); groupBox2.TabIndex = 20; groupBox2.TabStop = false; groupBox2.Text = "Options (Empty textbox means using default option)"; // + // txtFeatures + // + txtFeatures.Location = new Point(383, 319); + txtFeatures.Name = "txtFeatures"; + txtFeatures.Size = new Size(331, 23); + txtFeatures.TabIndex = 42; + // + // label26 + // + label26.AutoSize = true; + label26.Location = new Point(383, 301); + label26.Name = "label26"; + label26.Size = new Size(174, 15); + label26.TabIndex = 41; + label26.Text = "Features: (separate by commas)"; + // + // txtExtraGameArguments + // + txtExtraGameArguments.Location = new Point(383, 275); + txtExtraGameArguments.Name = "txtExtraGameArguments"; + txtExtraGameArguments.Size = new Size(331, 23); + txtExtraGameArguments.TabIndex = 40; + // + // label25 + // + label25.AutoSize = true; + label25.Location = new Point(383, 257); + label25.Name = "label25"; + label25.Size = new Size(126, 15); + label25.TabIndex = 39; + label25.Text = "ExtraGameArguments:"; + // + // txtExtraJVMArguments + // + txtExtraJVMArguments.Location = new Point(383, 231); + txtExtraJVMArguments.Name = "txtExtraJVMArguments"; + txtExtraJVMArguments.Size = new Size(331, 23); + txtExtraJVMArguments.TabIndex = 38; + // + // label24 + // + label24.AutoSize = true; + label24.Location = new Point(383, 214); + label24.Name = "label24"; + label24.Size = new Size(117, 15); + label24.TabIndex = 37; + label24.Text = "ExtraJVMArguments:"; + // + // txtJVMArgumentOverrides + // + txtJVMArgumentOverrides.Location = new Point(383, 188); + txtJVMArgumentOverrides.Name = "txtJVMArgumentOverrides"; + txtJVMArgumentOverrides.Size = new Size(331, 23); + txtJVMArgumentOverrides.TabIndex = 36; + // + // label11 + // + label11.AutoSize = true; + label11.Location = new Point(383, 170); + label11.Name = "label11"; + label11.Size = new Size(139, 15); + label11.TabIndex = 35; + label11.Text = "JVMArgumentOverrides: "; + // + // cbDemo + // + cbDemo.AutoSize = true; + cbDemo.Location = new Point(383, 130); + cbDemo.Name = "cbDemo"; + cbDemo.Size = new Size(58, 19); + cbDemo.TabIndex = 34; + cbDemo.Text = "Demo"; + cbDemo.UseVisualStyleBackColor = true; + // + // txtClientId + // + txtClientId.Location = new Point(133, 167); + txtClientId.Margin = new Padding(3, 4, 3, 4); + txtClientId.Name = "txtClientId"; + txtClientId.Size = new Size(224, 23); + txtClientId.TabIndex = 33; + // + // label23 + // + label23.AutoSize = true; + label23.Location = new Point(62, 170); + label23.Name = "label23"; + label23.Size = new Size(54, 15); + label23.TabIndex = 32; + label23.Text = "ClientId: "; + // + // txtQuickPlayReamls + // + txtQuickPlayReamls.Location = new Point(490, 96); + txtQuickPlayReamls.Margin = new Padding(3, 4, 3, 4); + txtQuickPlayReamls.Name = "txtQuickPlayReamls"; + txtQuickPlayReamls.Size = new Size(224, 23); + txtQuickPlayReamls.TabIndex = 31; + // + // label22 + // + label22.AutoSize = true; + label22.Location = new Point(383, 99); + label22.Name = "label22"; + label22.Size = new Size(101, 15); + label22.TabIndex = 30; + label22.Text = "QuickPlayRealms:"; + // + // txtQuickPlaySingleplay + // + txtQuickPlaySingleplay.Location = new Point(490, 62); + txtQuickPlaySingleplay.Margin = new Padding(3, 4, 3, 4); + txtQuickPlaySingleplay.Name = "txtQuickPlaySingleplay"; + txtQuickPlaySingleplay.Size = new Size(224, 23); + txtQuickPlaySingleplay.TabIndex = 29; + // + // label20 + // + label20.AutoSize = true; + label20.Location = new Point(364, 66); + label20.Name = "label20"; + label20.Size = new Size(120, 15); + label20.TabIndex = 28; + label20.Text = "QuickPlaySingleplay: "; + // + // txtQuickPlayPath + // + txtQuickPlayPath.Location = new Point(490, 28); + txtQuickPlayPath.Margin = new Padding(3, 4, 3, 4); + txtQuickPlayPath.Name = "txtQuickPlayPath"; + txtQuickPlayPath.Size = new Size(224, 23); + txtQuickPlayPath.TabIndex = 27; + // + // label19 + // + label19.AutoSize = true; + label19.Location = new Point(394, 33); + label19.Name = "label19"; + label19.Size = new Size(90, 15); + label19.TabIndex = 26; + label19.Text = "QuickPlayPath: "; + // // cbFullscreen // cbFullscreen.AutoSize = true; - cbFullscreen.Location = new Point(133, 359); + cbFullscreen.Location = new Point(447, 130); cbFullscreen.Margin = new Padding(3, 4, 3, 4); cbFullscreen.Name = "cbFullscreen"; cbFullscreen.Size = new Size(79, 19); @@ -140,7 +320,7 @@ private void InitializeComponent() // // btnAutoRamSet // - btnAutoRamSet.Location = new Point(295, 400); + btnAutoRamSet.Location = new Point(325, 381); btnAutoRamSet.Margin = new Padding(3, 4, 3, 4); btnAutoRamSet.Name = "btnAutoRamSet"; btnAutoRamSet.Size = new Size(75, 29); @@ -149,17 +329,17 @@ private void InitializeComponent() btnAutoRamSet.UseVisualStyleBackColor = true; btnAutoRamSet.Click += btnAutoRamSet_Click; // - // Txt_DockIcon + // txtDockIcon // - Txt_DockIcon.Location = new Point(133, 332); - Txt_DockIcon.Margin = new Padding(3, 4, 3, 4); - Txt_DockIcon.Name = "Txt_DockIcon"; - Txt_DockIcon.Size = new Size(224, 23); - Txt_DockIcon.TabIndex = 17; + txtDockIcon.Location = new Point(133, 332); + txtDockIcon.Margin = new Padding(3, 4, 3, 4); + txtDockIcon.Name = "txtDockIcon"; + txtDockIcon.Size = new Size(224, 23); + txtDockIcon.TabIndex = 17; // // txtXms // - txtXms.Location = new Point(104, 382); + txtXms.Location = new Point(134, 363); txtXms.Margin = new Padding(3, 4, 3, 4); txtXms.Name = "txtXms"; txtXms.Size = new Size(182, 23); @@ -174,18 +354,18 @@ private void InitializeComponent() label17.TabIndex = 16; label17.Text = "DockIcon : "; // - // Txt_DockName + // txtDockName // - Txt_DockName.Location = new Point(133, 299); - Txt_DockName.Margin = new Padding(3, 4, 3, 4); - Txt_DockName.Name = "Txt_DockName"; - Txt_DockName.Size = new Size(224, 23); - Txt_DockName.TabIndex = 15; + txtDockName.Location = new Point(133, 299); + txtDockName.Margin = new Padding(3, 4, 3, 4); + txtDockName.Name = "txtDockName"; + txtDockName.Size = new Size(224, 23); + txtDockName.TabIndex = 15; // // label21 // label21.AutoSize = true; - label21.Location = new Point(10, 388); + label21.Location = new Point(40, 369); label21.Name = "label21"; label21.Size = new Size(86, 15); label21.TabIndex = 22; @@ -200,13 +380,13 @@ private void InitializeComponent() label18.TabIndex = 14; label18.Text = "DockName : "; // - // Txt_GLauncherVersion + // txtGLauncherVersion // - Txt_GLauncherVersion.Location = new Point(133, 265); - Txt_GLauncherVersion.Margin = new Padding(3, 4, 3, 4); - Txt_GLauncherVersion.Name = "Txt_GLauncherVersion"; - Txt_GLauncherVersion.Size = new Size(224, 23); - Txt_GLauncherVersion.TabIndex = 13; + txtGLauncherVersion.Location = new Point(133, 265); + txtGLauncherVersion.Margin = new Padding(3, 4, 3, 4); + txtGLauncherVersion.Name = "txtGLauncherVersion"; + txtGLauncherVersion.Size = new Size(224, 23); + txtGLauncherVersion.TabIndex = 13; // // label16 // @@ -217,13 +397,13 @@ private void InitializeComponent() label16.TabIndex = 12; label16.Text = "GLauncherVersion : "; // - // Txt_GLauncherName + // txtGLauncherName // - Txt_GLauncherName.Location = new Point(133, 231); - Txt_GLauncherName.Margin = new Padding(3, 4, 3, 4); - Txt_GLauncherName.Name = "Txt_GLauncherName"; - Txt_GLauncherName.Size = new Size(224, 23); - Txt_GLauncherName.TabIndex = 11; + txtGLauncherName.Location = new Point(133, 231); + txtGLauncherName.Margin = new Padding(3, 4, 3, 4); + txtGLauncherName.Name = "txtGLauncherName"; + txtGLauncherName.Size = new Size(224, 23); + txtGLauncherName.TabIndex = 11; // // label15 // @@ -234,22 +414,22 @@ private void InitializeComponent() label15.TabIndex = 10; label15.Text = "GLauncherName : "; // - // Txt_ServerPort + // txtServerPort // - Txt_ServerPort.Location = new Point(133, 61); - Txt_ServerPort.Margin = new Padding(3, 4, 3, 4); - Txt_ServerPort.Name = "Txt_ServerPort"; - Txt_ServerPort.Size = new Size(224, 23); - Txt_ServerPort.TabIndex = 9; + txtServerPort.Location = new Point(133, 61); + txtServerPort.Margin = new Padding(3, 4, 3, 4); + txtServerPort.Name = "txtServerPort"; + txtServerPort.Size = new Size(224, 23); + txtServerPort.TabIndex = 9; // - // TxtXmx + // txtXmx // - TxtXmx.Location = new Point(104, 416); - TxtXmx.Margin = new Padding(3, 4, 3, 4); - TxtXmx.Name = "TxtXmx"; - TxtXmx.Size = new Size(182, 23); - TxtXmx.TabIndex = 11; - TxtXmx.Text = "1024"; + txtXmx.Location = new Point(134, 397); + txtXmx.Margin = new Padding(3, 4, 3, 4); + txtXmx.Name = "txtXmx"; + txtXmx.Size = new Size(182, 23); + txtXmx.TabIndex = 11; + txtXmx.Text = "1024"; // // label14 // @@ -260,47 +440,30 @@ private void InitializeComponent() label14.TabIndex = 8; label14.Text = "Server Port : "; // - // Txt_JavaArgs - // - Txt_JavaArgs.Location = new Point(133, 164); - Txt_JavaArgs.Margin = new Padding(3, 4, 3, 4); - Txt_JavaArgs.Name = "Txt_JavaArgs"; - Txt_JavaArgs.Size = new Size(224, 23); - Txt_JavaArgs.TabIndex = 7; - // // Xmx_RAM // Xmx_RAM.AutoSize = true; - Xmx_RAM.Location = new Point(6, 419); + Xmx_RAM.Location = new Point(36, 400); Xmx_RAM.Name = "Xmx_RAM"; Xmx_RAM.Size = new Size(89, 15); Xmx_RAM.TabIndex = 10; Xmx_RAM.Text = "Xmx(MaxMb) : "; // - // Txt_ScHt - // - Txt_ScHt.Location = new Point(133, 130); - Txt_ScHt.Margin = new Padding(3, 4, 3, 4); - Txt_ScHt.Name = "Txt_ScHt"; - Txt_ScHt.Size = new Size(224, 23); - Txt_ScHt.TabIndex = 6; + // txtScreenHeight // - // Txt_ScWd + txtScreenHeight.Location = new Point(133, 130); + txtScreenHeight.Margin = new Padding(3, 4, 3, 4); + txtScreenHeight.Name = "txtScreenHeight"; + txtScreenHeight.Size = new Size(224, 23); + txtScreenHeight.TabIndex = 6; // - Txt_ScWd.Location = new Point(133, 96); - Txt_ScWd.Margin = new Padding(3, 4, 3, 4); - Txt_ScWd.Name = "Txt_ScWd"; - Txt_ScWd.Size = new Size(224, 23); - Txt_ScWd.TabIndex = 5; + // txtScreenWidth // - // label11 - // - label11.AutoSize = true; - label11.Location = new Point(19, 168); - label11.Name = "label11"; - label11.Size = new Size(100, 15); - label11.TabIndex = 4; - label11.Text = "JVM Arguments : "; + txtScreenWidth.Location = new Point(133, 96); + txtScreenWidth.Margin = new Padding(3, 4, 3, 4); + txtScreenWidth.Name = "txtScreenWidth"; + txtScreenWidth.Size = new Size(224, 23); + txtScreenWidth.TabIndex = 5; // // label10 // @@ -320,21 +483,21 @@ private void InitializeComponent() label9.TabIndex = 2; label9.Text = "Screen Width : "; // - // Txt_ServerIp + // txtServerIP // - Txt_ServerIp.Location = new Point(133, 28); - Txt_ServerIp.Margin = new Padding(3, 4, 3, 4); - Txt_ServerIp.Name = "Txt_ServerIp"; - Txt_ServerIp.Size = new Size(224, 23); - Txt_ServerIp.TabIndex = 1; + txtServerIP.Location = new Point(133, 28); + txtServerIP.Margin = new Padding(3, 4, 3, 4); + txtServerIP.Name = "txtServerIP"; + txtServerIP.Size = new Size(224, 23); + txtServerIP.TabIndex = 1; // - // Txt_VersionType + // txtVersionType // - Txt_VersionType.Location = new Point(133, 198); - Txt_VersionType.Margin = new Padding(3, 4, 3, 4); - Txt_VersionType.Name = "Txt_VersionType"; - Txt_VersionType.Size = new Size(224, 23); - Txt_VersionType.TabIndex = 1; + txtVersionType.Location = new Point(133, 198); + txtVersionType.Margin = new Padding(3, 4, 3, 4); + txtVersionType.Name = "txtVersionType"; + txtVersionType.Size = new Size(224, 23); + txtVersionType.TabIndex = 1; // // label8 // @@ -359,7 +522,7 @@ private void InitializeComponent() Pb_Progress.Location = new Point(14, 489); Pb_Progress.Margin = new Padding(3, 4, 3, 4); Pb_Progress.Name = "Pb_Progress"; - Pb_Progress.Size = new Size(776, 29); + Pb_Progress.Size = new Size(1105, 29); Pb_Progress.TabIndex = 19; // // Lv_Status @@ -373,56 +536,47 @@ private void InitializeComponent() // // groupBox1 // - groupBox1.Controls.Add(btnChangeJava); - groupBox1.Controls.Add(lbJavaPath); - groupBox1.Controls.Add(lbUsername); + groupBox1.Controls.Add(cbJavaUseDefault); + groupBox1.Controls.Add(txtJava); groupBox1.Controls.Add(label6); groupBox1.Controls.Add(btnChangePath); groupBox1.Controls.Add(txtPath); groupBox1.Controls.Add(label4); - groupBox1.Controls.Add(label2); groupBox1.Location = new Point(14, 15); groupBox1.Margin = new Padding(3, 4, 3, 4); groupBox1.Name = "groupBox1"; groupBox1.Padding = new Padding(3, 4, 3, 4); - groupBox1.Size = new Size(385, 145); + groupBox1.Size = new Size(385, 99); groupBox1.TabIndex = 16; groupBox1.TabStop = false; groupBox1.Text = "CmlLib Sample Launcher"; // - // btnChangeJava - // - btnChangeJava.Location = new Point(317, 112); - btnChangeJava.Margin = new Padding(3, 4, 3, 4); - btnChangeJava.Name = "btnChangeJava"; - btnChangeJava.Size = new Size(58, 29); - btnChangeJava.TabIndex = 21; - btnChangeJava.Text = "Change"; - btnChangeJava.UseVisualStyleBackColor = true; - btnChangeJava.Click += btnChangeJava_Click; - // - // lbJavaPath + // cbJavaUseDefault // - lbJavaPath.AutoSize = true; - lbJavaPath.Location = new Point(88, 115); - lbJavaPath.Name = "lbJavaPath"; - lbJavaPath.Size = new Size(90, 15); - lbJavaPath.TabIndex = 20; - lbJavaPath.Text = "Use default java"; + cbJavaUseDefault.AutoSize = true; + cbJavaUseDefault.Checked = true; + cbJavaUseDefault.CheckState = CheckState.Checked; + cbJavaUseDefault.Location = new Point(290, 60); + cbJavaUseDefault.Name = "cbJavaUseDefault"; + cbJavaUseDefault.Size = new Size(85, 19); + cbJavaUseDefault.TabIndex = 14; + cbJavaUseDefault.Text = "Use default"; + cbJavaUseDefault.UseVisualStyleBackColor = true; + cbJavaUseDefault.CheckedChanged += cbJavaUseDefault_CheckedChanged; // - // lbUsername + // txtJava // - lbUsername.AutoSize = true; - lbUsername.Location = new Point(88, 84); - lbUsername.Name = "lbUsername"; - lbUsername.Size = new Size(53, 15); - lbUsername.TabIndex = 18; - lbUsername.Text = "test_user"; + txtJava.Location = new Point(52, 58); + txtJava.Margin = new Padding(3, 4, 3, 4); + txtJava.Name = "txtJava"; + txtJava.ReadOnly = true; + txtJava.Size = new Size(226, 23); + txtJava.TabIndex = 13; // // label6 // label6.AutoSize = true; - label6.Location = new Point(41, 115); + label6.Location = new Point(15, 61); label6.Name = "label6"; label6.Size = new Size(38, 15); label6.TabIndex = 12; @@ -430,7 +584,7 @@ private void InitializeComponent() // // btnChangePath // - btnChangePath.Location = new Point(317, 45); + btnChangePath.Location = new Point(317, 20); btnChangePath.Margin = new Padding(3, 4, 3, 4); btnChangePath.Name = "btnChangePath"; btnChangePath.Size = new Size(58, 29); @@ -441,11 +595,11 @@ private void InitializeComponent() // // txtPath // - txtPath.Location = new Point(17, 46); + txtPath.Location = new Point(85, 25); txtPath.Margin = new Padding(3, 4, 3, 4); txtPath.Name = "txtPath"; txtPath.ReadOnly = true; - txtPath.Size = new Size(294, 23); + txtPath.Size = new Size(226, 23); txtPath.TabIndex = 8; // // label4 @@ -457,23 +611,14 @@ private void InitializeComponent() label4.TabIndex = 7; label4.Text = "Game Path : "; // - // label2 - // - label2.AutoSize = true; - label2.Location = new Point(21, 84); - label2.Name = "label2"; - label2.Size = new Size(61, 15); - label2.TabIndex = 3; - label2.Text = "Account : "; - // // btnLaunch // btnLaunch.Location = new Point(28, 102); btnLaunch.Margin = new Padding(3, 4, 3, 4); btnLaunch.Name = "btnLaunch"; - btnLaunch.Size = new Size(330, 69); + btnLaunch.Size = new Size(250, 69); btnLaunch.TabIndex = 2; - btnLaunch.Text = "Download and Launch"; + btnLaunch.Text = "Install and Launch"; btnLaunch.UseVisualStyleBackColor = true; btnLaunch.Click += Btn_Launch_Click; // @@ -498,7 +643,7 @@ private void InitializeComponent() // label12 // label12.AutoSize = true; - label12.Location = new Point(424, 525); + label12.Location = new Point(756, 525); label12.Name = "label12"; label12.Size = new Size(191, 15); label12.TabIndex = 23; @@ -506,7 +651,7 @@ private void InitializeComponent() // // btnGithub // - btnGithub.Location = new Point(634, 524); + btnGithub.Location = new Point(966, 524); btnGithub.Margin = new Padding(3, 4, 3, 4); btnGithub.Name = "btnGithub"; btnGithub.Size = new Size(75, 29); @@ -517,7 +662,7 @@ private void InitializeComponent() // // btnWiki // - btnWiki.Location = new Point(715, 524); + btnWiki.Location = new Point(1047, 524); btnWiki.Margin = new Padding(3, 4, 3, 4); btnWiki.Name = "btnWiki"; btnWiki.Size = new Size(75, 29); @@ -537,69 +682,9 @@ private void InitializeComponent() btnChangelog.UseVisualStyleBackColor = true; btnChangelog.Click += btnChangelog_Click; // - // rbSequenceDownload - // - rbSequenceDownload.AutoSize = true; - rbSequenceDownload.Location = new Point(38, 30); - rbSequenceDownload.Margin = new Padding(3, 4, 3, 4); - rbSequenceDownload.Name = "rbSequenceDownload"; - rbSequenceDownload.Size = new Size(140, 19); - rbSequenceDownload.TabIndex = 22; - rbSequenceDownload.Text = "SequenceDownloader"; - rbSequenceDownload.UseVisualStyleBackColor = true; - // - // rbParallelDownload - // - rbParallelDownload.AutoSize = true; - rbParallelDownload.Checked = true; - rbParallelDownload.Location = new Point(193, 30); - rbParallelDownload.Margin = new Padding(3, 4, 3, 4); - rbParallelDownload.Name = "rbParallelDownload"; - rbParallelDownload.Size = new Size(159, 19); - rbParallelDownload.TabIndex = 23; - rbParallelDownload.TabStop = true; - rbParallelDownload.Text = "AsyncParallelDownloader"; - rbParallelDownload.UseVisualStyleBackColor = true; - // - // groupBox3 - // - groupBox3.Controls.Add(cbSkipHashCheck); - groupBox3.Controls.Add(cbSkipAssetsDownload); - groupBox3.Controls.Add(rbSequenceDownload); - groupBox3.Controls.Add(rbParallelDownload); - groupBox3.Location = new Point(14, 168); - groupBox3.Margin = new Padding(3, 4, 3, 4); - groupBox3.Name = "groupBox3"; - groupBox3.Padding = new Padding(3, 4, 3, 4); - groupBox3.Size = new Size(385, 98); - groupBox3.TabIndex = 27; - groupBox3.TabStop = false; - groupBox3.Text = "Download Options"; - // - // cbSkipHashCheck - // - cbSkipHashCheck.AutoSize = true; - cbSkipHashCheck.Location = new Point(200, 57); - cbSkipHashCheck.Margin = new Padding(3, 4, 3, 4); - cbSkipHashCheck.Name = "cbSkipHashCheck"; - cbSkipHashCheck.Size = new Size(127, 19); - cbSkipHashCheck.TabIndex = 26; - cbSkipHashCheck.Text = "Skip hash checking"; - cbSkipHashCheck.UseVisualStyleBackColor = true; - // - // cbSkipAssetsDownload - // - cbSkipAssetsDownload.AutoSize = true; - cbSkipAssetsDownload.Location = new Point(44, 57); - cbSkipAssetsDownload.Margin = new Padding(3, 4, 3, 4); - cbSkipAssetsDownload.Name = "cbSkipAssetsDownload"; - cbSkipAssetsDownload.Size = new Size(133, 19); - cbSkipAssetsDownload.TabIndex = 25; - cbSkipAssetsDownload.Text = "Skip asset download"; - cbSkipAssetsDownload.UseVisualStyleBackColor = true; - // // groupBox4 // + groupBox4.Controls.Add(btnCancel); groupBox4.Controls.Add(btnSortFilter); groupBox4.Controls.Add(btnRefreshVersion); groupBox4.Controls.Add(btnSetLastVersion); @@ -615,12 +700,22 @@ private void InitializeComponent() groupBox4.TabStop = false; groupBox4.Text = "Launch"; // + // btnCancel + // + btnCancel.Location = new Point(284, 104); + btnCancel.Name = "btnCancel"; + btnCancel.Size = new Size(75, 67); + btnCancel.TabIndex = 6; + btnCancel.Text = "Cancel"; + btnCancel.UseVisualStyleBackColor = true; + btnCancel.Click += btnCancel_Click; + // // btnSortFilter // - btnSortFilter.Location = new Point(130, 65); + btnSortFilter.Location = new Point(92, 65); btnSortFilter.Margin = new Padding(3, 4, 3, 4); btnSortFilter.Name = "btnSortFilter"; - btnSortFilter.Size = new Size(143, 29); + btnSortFilter.Size = new Size(182, 29); btnSortFilter.TabIndex = 5; btnSortFilter.Text = "Sort option"; btnSortFilter.UseVisualStyleBackColor = true; @@ -650,7 +745,7 @@ private void InitializeComponent() // // btnOptions // - btnOptions.Location = new Point(278, 525); + btnOptions.Location = new Point(145, 526); btnOptions.Margin = new Padding(3, 4, 3, 4); btnOptions.Name = "btnOptions"; btnOptions.Size = new Size(121, 29); @@ -661,21 +756,139 @@ private void InitializeComponent() // // lbLibraryVersion // - lbLibraryVersion.Location = new Point(424, 542); + lbLibraryVersion.Location = new Point(756, 542); lbLibraryVersion.Name = "lbLibraryVersion"; lbLibraryVersion.Size = new Size(205, 23); lbLibraryVersion.TabIndex = 31; lbLibraryVersion.Text = "CmlLib.Core"; // + // groupBox3 + // + groupBox3.Controls.Add(btnLogout); + groupBox3.Controls.Add(btnLogin); + groupBox3.Controls.Add(label13); + groupBox3.Controls.Add(label5); + groupBox3.Controls.Add(label3); + groupBox3.Controls.Add(label2); + groupBox3.Controls.Add(txtXUID); + groupBox3.Controls.Add(txtUUID); + groupBox3.Controls.Add(txtAccessToken); + groupBox3.Controls.Add(txtUsername); + groupBox3.Location = new Point(14, 121); + groupBox3.Name = "groupBox3"; + groupBox3.Size = new Size(385, 144); + groupBox3.TabIndex = 32; + groupBox3.TabStop = false; + groupBox3.Text = "groupBox3"; + // + // btnLogout + // + btnLogout.Location = new Point(317, 82); + btnLogout.Name = "btnLogout"; + btnLogout.Size = new Size(58, 48); + btnLogout.TabIndex = 9; + btnLogout.Text = "Logout"; + btnLogout.UseVisualStyleBackColor = true; + btnLogout.Click += btnLogout_Click; + // + // btnLogin + // + btnLogin.Location = new Point(317, 28); + btnLogin.Name = "btnLogin"; + btnLogin.Size = new Size(58, 48); + btnLogin.TabIndex = 8; + btnLogin.Text = "Login"; + btnLogin.UseVisualStyleBackColor = true; + btnLogin.Click += btnLogin_Click; + // + // label13 + // + label13.AutoSize = true; + label13.Location = new Point(52, 114); + label13.Name = "label13"; + label13.Size = new Size(33, 15); + label13.TabIndex = 7; + label13.Text = "XUID"; + // + // label5 + // + label5.AutoSize = true; + label5.Location = new Point(52, 85); + label5.Name = "label5"; + label5.Size = new Size(34, 15); + label5.TabIndex = 6; + label5.Text = "UUID"; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new Point(15, 56); + label3.Name = "label3"; + label3.Size = new Size(74, 15); + label3.TabIndex = 5; + label3.Text = "AccessToken"; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(31, 28); + label2.Name = "label2"; + label2.Size = new Size(60, 15); + label2.TabIndex = 4; + label2.Text = "Username"; + // + // txtXUID + // + txtXUID.Location = new Point(92, 111); + txtXUID.Name = "txtXUID"; + txtXUID.Size = new Size(219, 23); + txtXUID.TabIndex = 3; + // + // txtUUID + // + txtUUID.Location = new Point(92, 82); + txtUUID.Name = "txtUUID"; + txtUUID.Size = new Size(219, 23); + txtUUID.TabIndex = 2; + // + // txtAccessToken + // + txtAccessToken.Location = new Point(92, 53); + txtAccessToken.Name = "txtAccessToken"; + txtAccessToken.Size = new Size(219, 23); + txtAccessToken.TabIndex = 1; + // + // txtUsername + // + txtUsername.Location = new Point(92, 24); + txtUsername.Name = "txtUsername"; + txtUsername.Size = new Size(219, 23); + txtUsername.TabIndex = 0; + // + // lbTime + // + lbTime.AutoSize = true; + lbTime.Location = new Point(1002, 470); + lbTime.Name = "lbTime"; + lbTime.Size = new Size(12, 15); + lbTime.TabIndex = 33; + lbTime.Text = "?"; + // + // eventTimer + // + eventTimer.Enabled = true; + eventTimer.Tick += eventTimer_Tick; + // // MainForm // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(803, 564); + ClientSize = new Size(1137, 564); + Controls.Add(lbTime); + Controls.Add(groupBox3); Controls.Add(lbLibraryVersion); Controls.Add(btnOptions); Controls.Add(groupBox4); - Controls.Add(groupBox3); Controls.Add(btnChangelog); Controls.Add(btnWiki); Controls.Add(btnGithub); @@ -692,10 +905,10 @@ private void InitializeComponent() groupBox2.PerformLayout(); groupBox1.ResumeLayout(false); groupBox1.PerformLayout(); - groupBox3.ResumeLayout(false); - groupBox3.PerformLayout(); groupBox4.ResumeLayout(false); groupBox4.PerformLayout(); + groupBox3.ResumeLayout(false); + groupBox3.PerformLayout(); ResumeLayout(false); PerformLayout(); } @@ -706,14 +919,12 @@ private void InitializeComponent() #endregion private System.Windows.Forms.GroupBox groupBox2; - private System.Windows.Forms.TextBox Txt_JavaArgs; - private System.Windows.Forms.TextBox Txt_ScHt; - private System.Windows.Forms.TextBox Txt_ScWd; - private System.Windows.Forms.Label label11; + private System.Windows.Forms.TextBox txtScreenHeight; + private System.Windows.Forms.TextBox txtScreenWidth; private System.Windows.Forms.Label label10; private System.Windows.Forms.Label label9; - private System.Windows.Forms.TextBox Txt_ServerIp; - private System.Windows.Forms.TextBox Txt_VersionType; + private System.Windows.Forms.TextBox txtServerIP; + private System.Windows.Forms.TextBox txtVersionType; private System.Windows.Forms.Label label8; private System.Windows.Forms.Label label7; private System.Windows.Forms.ProgressBar Pb_Progress; @@ -721,44 +932,68 @@ private void InitializeComponent() private System.Windows.Forms.Label Lv_Status; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.Label label6; - private System.Windows.Forms.TextBox TxtXmx; + private System.Windows.Forms.TextBox txtXmx; private System.Windows.Forms.Label Xmx_RAM; private System.Windows.Forms.Button btnChangePath; private System.Windows.Forms.TextBox txtPath; private System.Windows.Forms.Label label4; - private System.Windows.Forms.Label label2; private System.Windows.Forms.Button btnLaunch; private System.Windows.Forms.ComboBox cbVersion; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label12; private System.Windows.Forms.Button btnGithub; - private System.Windows.Forms.TextBox Txt_ServerPort; + private System.Windows.Forms.TextBox txtServerPort; private System.Windows.Forms.Label label14; - private System.Windows.Forms.TextBox Txt_GLauncherVersion; + private System.Windows.Forms.TextBox txtGLauncherVersion; private System.Windows.Forms.Label label16; - private System.Windows.Forms.TextBox Txt_GLauncherName; + private System.Windows.Forms.TextBox txtGLauncherName; private System.Windows.Forms.Label label15; - private System.Windows.Forms.TextBox Txt_DockIcon; + private System.Windows.Forms.TextBox txtDockIcon; private System.Windows.Forms.Label label17; - private System.Windows.Forms.TextBox Txt_DockName; + private System.Windows.Forms.TextBox txtDockName; private System.Windows.Forms.Label label18; private System.Windows.Forms.Button btnWiki; private System.Windows.Forms.Button btnChangelog; private System.Windows.Forms.Button btnAutoRamSet; private System.Windows.Forms.TextBox txtXms; private System.Windows.Forms.Label label21; - private System.Windows.Forms.Button btnChangeJava; - private System.Windows.Forms.Label lbJavaPath; - private System.Windows.Forms.Label lbUsername; - private System.Windows.Forms.RadioButton rbSequenceDownload; - private System.Windows.Forms.RadioButton rbParallelDownload; - private System.Windows.Forms.GroupBox groupBox3; - private System.Windows.Forms.CheckBox cbSkipAssetsDownload; private System.Windows.Forms.GroupBox groupBox4; private System.Windows.Forms.Button btnSetLastVersion; private System.Windows.Forms.Button btnOptions; private System.Windows.Forms.Button btnRefreshVersion; private System.Windows.Forms.CheckBox cbFullscreen; - private System.Windows.Forms.CheckBox cbSkipHashCheck; + private GroupBox groupBox3; + private Button btnLogout; + private Button btnLogin; + private Label label13; + private Label label5; + private Label label3; + private Label label2; + private TextBox txtXUID; + private TextBox txtUUID; + private TextBox txtAccessToken; + private TextBox txtUsername; + private CheckBox cbJavaUseDefault; + private TextBox txtJava; + private TextBox txtQuickPlayPath; + private Label label19; + private TextBox txtQuickPlaySingleplay; + private Label label20; + private TextBox txtQuickPlayReamls; + private Label label22; + private TextBox txtClientId; + private Label label23; + private CheckBox cbDemo; + private Label label11; + private TextBox txtJVMArgumentOverrides; + private TextBox txtExtraJVMArguments; + private Label label24; + private TextBox txtExtraGameArguments; + private Label label25; + private TextBox txtFeatures; + private Label label26; + private Button btnCancel; + private Label lbTime; + private System.Windows.Forms.Timer eventTimer; } } \ No newline at end of file diff --git a/examples/winform/MainForm.cs b/examples/winform/MainForm.cs index d14976f..1bc99f3 100644 --- a/examples/winform/MainForm.cs +++ b/examples/winform/MainForm.cs @@ -2,45 +2,45 @@ using CmlLib.Core.Auth; using System.ComponentModel; using System.Diagnostics; -using System.Reflection; using CmlLib.Core.VersionMetadata; using CmlLib.Core.Installers; using CmlLib.Core.ProcessBuilder; +using CmlLib.Core.Rules; namespace CmlLibWinFormSample { public partial class MainForm : Form { - private readonly MSession session; private readonly HttpClient _httpClient = new(); - public MainForm(MSession session) + public MainForm() { - this.session = session; InitializeComponent(); } - CancellationToken cancellationToken = default; + CancellationTokenSource? cancellationToken; MinecraftLauncher? launcher; - string? javaPath; private async void MainForm_Shown(object sender, EventArgs e) { - lbLibraryVersion.Text = "CmlLib.Core " + getLibraryVersion(); - + lbLibraryVersion.Text = "CmlLib.Core " + Util.GetLibraryVersion(); + txtExtraJVMArguments.Text = string.Join(' ', MLaunchOption.DefaultExtraJvmArguments.SelectMany(arg => arg.Values)); + + var defaultSession = MSession.CreateOfflineSession("cmltester123"); + txtUsername.Text = defaultSession.Username; + txtUUID.Text = defaultSession.UUID; + txtAccessToken.Text = defaultSession.AccessToken; + txtXUID.Text = defaultSession.Xuid; + // Initialize launcher await initializeLauncher(new MinecraftPath()); } private async Task initializeLauncher(MinecraftPath path) { - lbUsername.Text = session.Username; txtPath.Text = path.BasePath; - - var parameters = MinecraftLauncherParameters.CreateDefault(); - parameters.MinecraftPath = path; - parameters.HttpClient = _httpClient; + var parameters = MinecraftLauncherParameters.CreateDefault(path, _httpClient); launcher = new MinecraftLauncher(parameters); await refreshVersions(); } @@ -50,7 +50,7 @@ private async void btnRefreshVersion_Click(object sender, EventArgs e) await refreshVersions(); } - private async Task refreshVersions(string? showVersion=null) + private async Task refreshVersions(string? showVersion = null) { if (launcher == null) { @@ -79,7 +79,7 @@ private void btnSetLastVersion_Click(object? sender, EventArgs? e) { cbVersion.Text = launcher?.Versions?.LatestReleaseName; } - + private void btnSortFilter_Click(object sender, EventArgs e) { if (launcher == null) @@ -99,11 +99,6 @@ private async void Btn_Launch_Click(object sender, EventArgs e) MessageBox.Show("Initialize the launcher first"); return; } - if (session == null) - { - MessageBox.Show("Login First"); - return; - } if (cbVersion.Text == "") { MessageBox.Show("Select Version"); @@ -116,62 +111,103 @@ private async void Btn_Launch_Click(object sender, EventArgs e) try { // create LaunchOption - var launchOption = new CmlLib.Core.ProcessBuilder.MLaunchOption() + var launchOption = new MLaunchOption() { - MaximumRamMb = int.Parse(TxtXmx.Text), - Session = this.session, + Session = new MSession + { + Username = txtUsername.Text, + AccessToken = txtAccessToken.Text, + UUID = txtUUID.Text, + Xuid = txtXUID.Text + }, + IsDemo = cbDemo.Checked, + FullScreen = cbFullscreen.Checked, + JvmArgumentOverrides = new[] { MArgument.FromCommandLine(txtJVMArgumentOverrides.Text) }, + ExtraJvmArguments = new[] { MArgument.FromCommandLine(txtExtraJVMArguments.Text) }, + ExtraGameArguments = new[] { MArgument.FromCommandLine(txtExtraGameArguments.Text) }, + }; - VersionType = Txt_VersionType.Text, - GameLauncherName = Txt_GLauncherName.Text, - GameLauncherVersion = Txt_GLauncherVersion.Text, + if (!cbJavaUseDefault.Checked) + launchOption.JavaPath = txtJava.Text; - FullScreen = cbFullscreen.Checked, + if (!string.IsNullOrEmpty(txtClientId.Text)) + launchOption.ClientId = txtClientId.Text; - ServerIp = Txt_ServerIp.Text, + if (!string.IsNullOrEmpty(txtVersionType.Text)) + launchOption.VersionType = txtVersionType.Text; - DockName = Txt_DockName.Text, - DockIcon = Txt_DockIcon.Text - }; + if (!string.IsNullOrEmpty(txtGLauncherName.Text)) + launchOption.GameLauncherName = txtGLauncherName.Text; + + if (!string.IsNullOrEmpty(txtGLauncherVersion.Text)) + launchOption.GameLauncherVersion = txtGLauncherVersion.Text; + + if (!string.IsNullOrEmpty(txtDockName.Text)) + launchOption.DockName = txtDockName.Text; + + if (!string.IsNullOrEmpty(txtDockIcon.Text)) + launchOption.DockIcon = txtDockIcon.Text; + + if (!string.IsNullOrEmpty(txtQuickPlayPath.Text)) + launchOption.QuickPlayPath = txtQuickPlayPath.Text; + + if (!string.IsNullOrEmpty(txtQuickPlaySingleplay.Text)) + launchOption.QuickPlaySingleplayer = txtQuickPlaySingleplay.Text; + + if (!string.IsNullOrEmpty(txtQuickPlayReamls.Text)) + launchOption.QuickPlayRealms = txtQuickPlayReamls.Text; + + if (!string.IsNullOrEmpty(txtXmx.Text)) + launchOption.MaximumRamMb = int.Parse(txtXmx.Text); - if (!string.IsNullOrEmpty(javaPath)) - launchOption.JavaPath = javaPath; - if (!string.IsNullOrEmpty(txtXms.Text)) launchOption.MinimumRamMb = int.Parse(txtXms.Text); - if (!string.IsNullOrEmpty(Txt_ServerPort.Text)) - launchOption.ServerPort = int.Parse(Txt_ServerPort.Text); + if (!string.IsNullOrEmpty(txtServerIP.Text)) + launchOption.ServerIp = txtServerIP.Text; + + if (!string.IsNullOrEmpty(txtServerPort.Text)) + launchOption.ServerPort = int.Parse(txtServerPort.Text); + + if (!string.IsNullOrEmpty(txtScreenWidth.Text) && !string.IsNullOrEmpty(txtScreenHeight.Text)) + { + launchOption.ScreenHeight = int.Parse(txtScreenHeight.Text); + launchOption.ScreenWidth = int.Parse(txtScreenWidth.Text); + } - if (!string.IsNullOrEmpty(Txt_ScWd.Text) && !string.IsNullOrEmpty(Txt_ScHt.Text)) + if (!string.IsNullOrEmpty(txtFeatures.Text)) { - launchOption.ScreenHeight = int.Parse(Txt_ScHt.Text); - launchOption.ScreenWidth = int.Parse(Txt_ScWd.Text); + launchOption.RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current) + { + Features = txtFeatures.Text + .Split(',') + .Select(f => f.Trim()) + .Where(f => !string.IsNullOrWhiteSpace(f)) + .ToList() + }; } - if (!string.IsNullOrEmpty(Txt_JavaArgs.Text)) - launchOption.JvmArgumentOverrides = new [] { MArgument.FromCommandLine(Txt_JavaArgs.Text) }; - - //if (cbSkipAssetsDownload.Checked) - // launcher.GameFileCheckers.AssetFileChecker = null; - //else if (launcher.GameFileCheckers.AssetFileChecker == null) - // launcher.GameFileCheckers.AssetFileChecker = new AssetChecker(); - - // check file hash or don't check - //if (launcher.GameFileCheckers.AssetFileChecker != null) - // launcher.GameFileCheckers.AssetFileChecker.CheckHash = !cbSkipHashCheck.Checked; - //if (launcher.GameFileCheckers.ClientFileChecker != null) - // launcher.GameFileCheckers.ClientFileChecker.CheckHash = !cbSkipHashCheck.Checked; - //if (launcher.GameFileCheckers.LibraryFileChecker != null) - // launcher.GameFileCheckers.LibraryFileChecker.CheckHash = !cbSkipHashCheck.Checked; - - var process = await launcher.InstallAndBuildProcessAsync( - cbVersion.Text, - launchOption, - new Progress(Launcher_FileChanged), - new Progress(Launcher_ProgressChanged), - cancellationToken); // Create Arguments and Process - - // process.Start(); // Just start game, or + cancellationToken = new CancellationTokenSource(); + + var version = cbVersion.Text; + var fileProgress = new SyncProgress(Launcher_FileChanged); + var byteProgress = new SyncProgress(Launcher_ProgressChanged); + var stopwatch = new Stopwatch(); + + var process = await Task.Run(async () => + { + stopwatch.Start(); + var result = await launcher.InstallAndBuildProcessAsync( + version, + launchOption, + fileProgress, + byteProgress, + cancellationToken.Token); + stopwatch.Stop(); + return result; + }); // Create Arguments and Process + + lbTime.Text = stopwatch.Elapsed.ToString(); StartProcess(process); // Start Process with debug options var gameLog = new GameLog(process); @@ -196,23 +232,41 @@ private async void Btn_Launch_Click(object sender, EventArgs e) } } - int lastProgress = 0; + ByteProgress byteProgress; private void Launcher_ProgressChanged(ByteProgress e) { - var progress = (int)(e.ProgressedBytes / (double)e.TotalBytes * 100); - if (progress >= 0 && progress <= 100 && progress != lastProgress) + byteProgress = e; + } + + InstallerProgressChangedEventArgs? fileProgress; + private void Launcher_FileChanged(InstallerProgressChangedEventArgs e) + { + if (e.EventType == InstallerEventType.Done) + fileProgress = e; + } + + private void eventTimer_Tick(object sender, EventArgs e) + { + var bytePercentage = (int)(byteProgress.ProgressedBytes / (double)byteProgress.TotalBytes * 100); + if (bytePercentage >= 0 && bytePercentage <= 100) { - lastProgress = progress; - Pb_Progress.Value = progress; + Pb_Progress.Value = bytePercentage; Pb_Progress.Maximum = 100; } + + if (fileProgress != null) + Lv_Status.Text = $"[{fileProgress.ProgressedTasks}/{fileProgress.TotalTasks}] {fileProgress.Name}"; } - private void Launcher_FileChanged(InstallerProgressChangedEventArgs e) + private void cbJavaUseDefault_CheckedChanged(object sender, EventArgs e) { - if (e.EventType == InstallerEventType.Done) + if (cbJavaUseDefault.Checked) + { + txtJava.ReadOnly = true; + } + else { - Lv_Status.Text = $"[{e.ProgressedTasks}/{e.TotalTasks}] {e.Name}"; + txtJava.ReadOnly = false; } } @@ -228,18 +282,6 @@ private async void btnChangePath_Click(object sender, EventArgs e) await initializeLauncher(form.MinecraftPath); } - private void btnChangeJava_Click(object sender, EventArgs e) - { - var form = new JavaForm(javaPath); - form.ShowDialog(); - javaPath = form.JavaBinaryPath; - - if (string.IsNullOrEmpty(javaPath)) - lbJavaPath.Text = "Use default java"; - else - lbJavaPath.Text = javaPath; - } - private void btnAutoRamSet_Click(object sender, EventArgs e) { var computerMemory = Util.GetMemoryMb(); @@ -257,7 +299,7 @@ private void btnAutoRamSet_Click(object sender, EventArgs e) var min = max / 10; - TxtXmx.Text = max.ToString(); + txtXmx.Text = max.ToString(); txtXms.Text = min.ToString(); } @@ -266,13 +308,14 @@ private void setUIEnabled(bool value) groupBox1.Enabled = value; groupBox2.Enabled = value; groupBox3.Enabled = value; - groupBox4.Enabled = value; + btnLaunch.Enabled = value; + btnCancel.Enabled = !value; } private void StartProcess(Process process) { File.WriteAllText("launcher.txt", process.StartInfo.Arguments); - + // process options to display game log process.StartInfo.UseShellExecute = false; @@ -308,37 +351,27 @@ private void btnOptions_Click(object sender, EventArgs e) private void btnGithub_Click(object sender, EventArgs e) { - start("https://github.com/AlphaBs/CmlLib.Core"); + Util.OpenUrl("https://github.com/AlphaBs/CmlLib.Core"); } private void btnWiki_Click(object sender, EventArgs e) { - start("https://github.com/AlphaBs/CmlLib.Core/wiki/"); + Util.OpenUrl("https://github.com/AlphaBs/CmlLib.Core/wiki/"); } - private void start(string url) + private void btnCancel_Click(object sender, EventArgs e) { - try - { - Process.Start(url); - } - catch (Exception ex) - { - MessageBox.Show(ex.ToString()); - } + cancellationToken?.Cancel(); } - private string? getLibraryVersion() + private void btnLogin_Click(object sender, EventArgs e) { - try - { - return Assembly.GetAssembly(typeof(MinecraftLauncher))?.GetName().Version?.ToString(); - } - catch (Exception ex) - { - Console.WriteLine(ex); - return null; - } + + } + + private void btnLogout_Click(object sender, EventArgs e) + { + } } } diff --git a/examples/winform/MainForm.resx b/examples/winform/MainForm.resx index af32865..d452706 100644 --- a/examples/winform/MainForm.resx +++ b/examples/winform/MainForm.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/examples/winform/Program.cs b/examples/winform/Program.cs index 9468bb2..1394e0c 100644 --- a/examples/winform/Program.cs +++ b/examples/winform/Program.cs @@ -13,7 +13,7 @@ static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new LoginForm()); + Application.Run(new MainForm()); } } } diff --git a/examples/winform/Util.cs b/examples/winform/Util.cs index 7349c12..65c7741 100644 --- a/examples/winform/Util.cs +++ b/examples/winform/Util.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +using CmlLib.Core; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; namespace CmlLibWinFormSample { @@ -23,5 +26,30 @@ public static class Util return null; } } + + public static void OpenUrl(string url) + { + try + { + Process.Start(url); + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString()); + } + } + + public static string? GetLibraryVersion() + { + try + { + return Assembly.GetAssembly(typeof(MinecraftLauncher))?.GetName().Version?.ToString(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + return null; + } + } } } diff --git a/src/MinecraftLauncher.cs b/src/MinecraftLauncher.cs index 7a90a89..84cb8c0 100644 --- a/src/MinecraftLauncher.cs +++ b/src/MinecraftLauncher.cs @@ -66,8 +66,8 @@ public MinecraftLauncher(MinecraftLauncherParameters parameters) ?? throw new ArgumentException(nameof(parameters.RulesEvaluator) + " was null"); RulesContext = new RulesEvaluatorContext(LauncherOSRule.Current); - _fileProgress = new SyncProgress(e => FileProgressChanged?.Invoke(this, e)); - _byteProgress = new SyncProgress(e => ByteProgressChanged?.Invoke(this, e)); + _fileProgress = new Progress(e => FileProgressChanged?.Invoke(this, e)); + _byteProgress = new Progress(e => ByteProgressChanged?.Invoke(this, e)); } public async ValueTask GetAllVersionsAsync() From f524163c625f3180f9ea268e9905cd8f5a207d77 Mon Sep 17 00:00:00 2001 From: AlphaBs Date: Fri, 29 Mar 2024 21:01:25 +0900 Subject: [PATCH 137/137] release 4.0.0-beta.1 --- TODO.md | 6 +++--- src/CmlLib.Core.csproj | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 4d82d6f..e955e8e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,10 @@ -- [ ] 여기까지 하고 4.0.0-beta.1 내놓기 - -- [x] Memory 위에서 mutable 한 IVersion 구현 - [ ] 라이트로더 패브릭 tlauncher 등등 확장가능하게 테스트 케이스 작성 - [ ] MLaunch 통합 테스트 작성. 주요 버전 파싱, 최종 argument 확인 - [ ] excludes 구현 - [ ] XML 로거 파서 +- [x] features, MLaunchOption 테스트 아직 안됨 +- [x] 여기까지 하고 4.0.0-beta.1 내놓기 +- [x] Memory 위에서 mutable 한 IVersion 구현 - [x] LauncherTester 에서 자주 쓰는 버전들 미리추가 예를들면 1.12.2 1.14.4 1.16.5 1.20.4 그리고 3D shockwave 같은것들도 - [x] MojangLauncher 와 호환성 확인: LibraryFileExtractorTests 에서 실제 url 에 파일 존재하는지, 실제 인스톨러는 어디다 설치하는지 확인 - [x] master 브랜치에서 v3.4.0 으로 cherry-pick diff --git a/src/CmlLib.Core.csproj b/src/CmlLib.Core.csproj index af055f5..4069ab4 100644 --- a/src/CmlLib.Core.csproj +++ b/src/CmlLib.Core.csproj @@ -5,7 +5,7 @@ 12.0 enable enable - 3.4.0 + 4.0.0-beta.1 Minecraft Launcher Library for .NET Support all version, forge, optifine @@ -26,8 +26,8 @@ Support all version, forge, optifine - - + +