From 6f2e5f81a8da48c00a34886607bd15817808751e Mon Sep 17 00:00:00 2001 From: azures04 Date: Sun, 25 Jan 2026 23:22:08 +0100 Subject: [PATCH] Add generic file sync and improve UI for game launch Introduced GenericFilesService to fetch and validate required game files before launching. Updated launcher version to beta and added support for AuthlibInjector agent in launch options. Improved avatar rendering and button states in the UI, including pixelated avatar display and disabled state handling for logout during game launch. --- src/main/Program.cs | 7 ++- src/main/core/Constants.cs | 13 +++-- src/main/utils/LentiaFiles.cs | 92 +++++++++++++++++++++++++++++++++++ wwwroot/assets/css/logged.css | 9 +++- wwwroot/assets/js/logged.js | 31 +++++++++++- wwwroot/logged.html | 4 +- 6 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 src/main/utils/LentiaFiles.cs diff --git a/src/main/Program.cs b/src/main/Program.cs index 52496f8..172a433 100644 --- a/src/main/Program.cs +++ b/src/main/Program.cs @@ -1,7 +1,6 @@ using Lentia.Core.Auth.OAuth2; using Lentia.Core.Constants; using Lentia.Core.Game; -using Lentia.Core.Game.Extra; using Lentia.Utils; using Photino.NET; using System.Text.Json; @@ -50,7 +49,7 @@ class Program { switch (method) { case "launcher::version": - payload = "v1.0.0-alpha"; + payload = "v1.0.0-beta"; break; case "window::close": @@ -212,6 +211,7 @@ class Program { case "launcher::game": try { + await GenericFilesService.FetchAndSyncGenericFiles(gameRoot); var options = new LaunchOptions { Version = new LaunchOptions.VersionOptions { Number = "1.12.2", @@ -222,6 +222,9 @@ class Program { Min = "512M" }, ModLoader = LaunchOptions.ModLoaderType.Forge, + CustomArgs = new List { + $"-javaagent:{Path.Combine(gameRoot, LauncherConstants.AgentsPath.AuthlibInjector)}={LauncherConstants.Urls.YggdrasilServer}" + } }; MinecraftVersion version = await GameHelper.PrepareGame(options, gameRoot); GameHelper.Launch(version, _authenticatedPlayer!, gameRoot, options, (logLine) => { diff --git a/src/main/core/Constants.cs b/src/main/core/Constants.cs index 7203259..6282445 100644 --- a/src/main/core/Constants.cs +++ b/src/main/core/Constants.cs @@ -1,13 +1,18 @@ -using System.Net.Http; using System.Text.Json; namespace Lentia.Core.Constants { public static class LauncherConstants { public static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - public static class Urls - { + public static class Urls { public const string MojangManifest = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; - public const string MojangAuthServer = "https://yggdrasil.azures.fr/authserver"; + public const string YggdrasilServer = "https://yggdrasil.azures.fr/"; + public const string MojangAuthServer = YggdrasilServer + "/authserver"; + + public const string ApiUrl = "https://lentia-api.azures.fr"; + } + + public static class AgentsPath { + public const string AuthlibInjector = "libraries/moe/yushi/authlib-injector/1.2.7/authlib-injector-1.2.7.jar"; } } } \ No newline at end of file diff --git a/src/main/utils/LentiaFiles.cs b/src/main/utils/LentiaFiles.cs new file mode 100644 index 0000000..f7c9a8a --- /dev/null +++ b/src/main/utils/LentiaFiles.cs @@ -0,0 +1,92 @@ +using System.Net.Http.Json; +using System.Security.Cryptography; +using Lentia.Core.Constants; +using Lentia.Core.Utils; + +namespace Lentia.Core.Game; + +public class GenericArtifact { + public string Path { get; set; } = string.Empty; + public string Sha1 { get; set; } = string.Empty; + public long Size { get; set; } + public string Url { get; set; } = string.Empty; +} + +public class GameFilesIndex { + public List Root { get; set; } = new(); +} + +public class DownloadsWrapper { + public GenericArtifact Artifact { get; set; } = null!; +} + +public class ArtifactWrapper { + public DownloadsWrapper Downloads { get; set; } = null!; + public string Name { get; set; } = string.Empty; +} + +public static class GenericFilesService { + + public static async Task FetchAndSyncGenericFiles(string gameRoot) { + try { + var response = await HttpHelper.FetchAsync($"{LauncherConstants.Urls.ApiUrl}/api/v2/gamefiles", HttpMethod.Get); + response.EnsureSuccessStatusCode(); + + var index = await response.Content.ReadFromJsonAsync(LauncherConstants._jsonOptions); + + if (index?.Root != null) { + var artifacts = index.Root.Select(r => r.Downloads.Artifact).ToList(); + await DownloadFiles(artifacts, gameRoot); + } + } + catch (Exception ex) { + Console.WriteLine($"[API Error] Impossible de récupérer les fichiers : {ex.Message}"); + } + } + + public static async Task DownloadFiles(List artifacts, string gameRoot) { + var downloadQueue = new List(); + + foreach (var artifact in artifacts) { + string localPath = Path.Combine(gameRoot, artifact.Path); + + if (!IsFileValid(localPath, artifact.Size, artifact.Sha1)) { + downloadQueue.Add(artifact); + } + } + + if (downloadQueue.Count == 0) return; + + var options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; + await Parallel.ForEachAsync(downloadQueue, options, async (artifact, token) => { + string localPath = Path.Combine(gameRoot, artifact.Path); + + try { + await FileHelper.DownloadFileAsync(artifact.Url, localPath); + + if (!IsFileValid(localPath, artifact.Size, artifact.Sha1)) { + throw new Exception($"Échec de la validation post-téléchargement pour : {artifact.Path}"); + } + } catch (Exception ex) { + Console.WriteLine($"[DownloadError] {artifact.Path} : {ex.Message}"); + } + }); + } + + public static bool IsFileValid(string localPath, long expectedSize, string expectedSha1) { + if (!File.Exists(localPath)) return false; + + var fileInfo = new FileInfo(localPath); + if (fileInfo.Length != expectedSize) return false; + + string localHash = GetFileSha1(localPath); + return localHash.Equals(expectedSha1, StringComparison.OrdinalIgnoreCase); + } + + private static string GetFileSha1(string filePath) { + using var stream = File.OpenRead(filePath); + using var sha1 = SHA1.Create(); + byte[] hashBytes = sha1.ComputeHash(stream); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + } +} \ No newline at end of file diff --git a/wwwroot/assets/css/logged.css b/wwwroot/assets/css/logged.css index 1841468..137a6c5 100644 --- a/wwwroot/assets/css/logged.css +++ b/wwwroot/assets/css/logged.css @@ -54,6 +54,8 @@ main > aside > nav > button:first-of-type > img { width: 60px; height: 60px; border-radius: 5px; + image-rendering: pixelated; + image-rendering: crisp-edges; } main > aside > nav > button:nth-child(2) { @@ -70,7 +72,7 @@ main > aside > nav > button:last-child { margin-bottom: 0; } -main > aside > nav > button:has(> i.fas.fa-sign-out-alt):hover { +main > aside > nav > button:has(> i.fas.fa-sign-out-alt):not(:disabled):hover { color: #ff4747; } @@ -124,6 +126,11 @@ button.play:disabled { filter: grayscale(100%); } +button.logout:disabled { + cursor: not-allowed; + filter: brightness(0.70); +} + button.play > i { margin-right: 5px; font-size: 14px; diff --git a/wwwroot/assets/js/logged.js b/wwwroot/assets/js/logged.js index 436d561..75f0e13 100644 --- a/wwwroot/assets/js/logged.js +++ b/wwwroot/assets/js/logged.js @@ -3,6 +3,7 @@ const dynmapFrame = document.querySelector("article.frame.dynmap > iframe") const capesSelector = document.querySelector("article.capes > div.capes") const logsContainer = document.querySelector("div.container.logs") const playButton = document.querySelector("button.play") +const logoutButton = document.querySelector("button.logout") let viewerInstance = new skinview3d.SkinViewer({ canvas: document.getElementById("skin"), @@ -43,7 +44,29 @@ window.getPlayer = async function getPlayer() { return result } +window.displayMinecraftHead = function displayMinecraftHead(skinUrl, targetImg) { + const canvas = document.createElement("canvas") + const ctx = canvas.getContext("2d") + const skinImg = new Image() + + skinImg.crossOrigin = "anonymous" + + skinImg.onload = function() { + canvas.width = 8 + canvas.height = 8 + + ctx.imageSmoothingEnabled = false + + ctx.drawImage(skinImg, 8, 8, 8, 8, 0, 0, 8, 8) + ctx.drawImage(skinImg, 40, 8, 8, 8, 0, 0, 8, 8) + document.querySelector(targetImg).src = canvas.toDataURL() + } + + skinImg.src = skinUrl +} + window.profile = await getPlayer() +displayMinecraftHead(`https://yggdrasil.azures.fr/textures/${profile.skins.find(skin => skin.state == "ACTIVE").url.replace(/^\//, "")}`, "img.avatar") window.refreshProfile = async function refreshProfile() { window.profile = await getPlayer() @@ -97,13 +120,16 @@ window.initSkin = async function initSkin() { return } + const skinUrl = `https://yggdrasil.azures.fr/textures/${profile.skins.find(skin => skin.state == "ACTIVE").url.replace(/^\//, "")}` viewerInstance = new skinview3d.SkinViewer({ canvas: document.getElementById("skin"), width: container.clientWidth, height: container.clientHeight, - skin: `https://yggdrasil.azures.fr/textures/${profile.skins.find(skin => skin.state == "ACTIVE").url.replace(/^\//, "")}` + skin: skinUrl }) + displayMinecraftHead(skinUrl, "img.avatar") + const activeCape = window.profile.capes.find(s => s.state === "ACTIVE") || null const capeUrl = activeCape == null ? null : `https://yggdrasil.azures.fr/textures/${activeCape.url.replace(/^\//, "")}?t=${Date.now()}` @@ -147,6 +173,7 @@ window.validateSkinSelection = async function validateSkinSelection(localPath) { if (activeSkin) { const skinUrl = `https://yggdrasil.azures.fr/textures/${activeSkin.url.replace(/^\//, "")}?t=${Date.now()}` const capeUrl = activeCape == null ? null : `https://yggdrasil.azures.fr/textures/${activeCape.url.replace(/^\//, "")}?t=${Date.now()}` + displayMinecraftHead(skinUrl, "img.avatar") await viewerInstance.loadSkin(skinUrl, { model: variant.toLowerCase() === "slim" ? "slim" : "default" }) @@ -246,12 +273,14 @@ window.play = async function play() { gamelog.clear() showLoadingBar() playButton.setAttribute("disabled", "") + logoutButton.setAttribute("disabled", "") const gameLaunch = await system.call("launcher::game") if (gameLaunch.success) { hideLoadingBar() } else { hideLoadingBar() playButton.removeAttribute("disabled") + logoutButton.removeAttribute("disabled") } } diff --git a/wwwroot/logged.html b/wwwroot/logged.html index 0638f1f..5ef7c10 100644 --- a/wwwroot/logged.html +++ b/wwwroot/logged.html @@ -11,7 +11,7 @@