Refactor HTTP requests to use HttpHelper utility

Introduced a new HttpHelper utility to centralize and standardize HTTP requests across the codebase, replacing direct usage of HttpClient. Updated authentication, asset, game, and Mojang API logic to use the new helper, improving maintainability and consistency. Also refactored game launch options for greater flexibility and configurability.
This commit is contained in:
Gilles Lazures 2026-01-25 18:54:11 +01:00
parent 1ae8ee00db
commit 15adb39115
13 changed files with 279 additions and 102 deletions

View File

@ -212,8 +212,19 @@ class Program {
case "launcher::game": case "launcher::game":
try { try {
MinecraftVersion version = await GameHelper.PrepareGame("1.12.2", gameRoot, true); var options = new LaunchOptions {
GameHelper.Launch(version, _authenticatedPlayer!, gameRoot, (logLine) => { Version = new LaunchOptions.VersionOptions {
Number = "1.12.2",
Type = "release"
},
Memory = new LaunchOptions.MemoryOptions {
Max = $"{SettingsManager.ReadSettings().Ram.Max}M",
Min = "512M"
},
ModLoader = LaunchOptions.ModLoaderType.Forge,
};
MinecraftVersion version = await GameHelper.PrepareGame(options, gameRoot);
GameHelper.Launch(version, _authenticatedPlayer!, gameRoot, options, (logLine) => {
var logMessage = new { var logMessage = new {
requestId = "game::log", requestId = "game::log",
payload = new { message = logLine.ToString() } payload = new { message = logLine.ToString() }
@ -231,6 +242,7 @@ class Program {
var response = new { requestId, payload }; var response = new { requestId, payload };
window.SendWebMessage(JsonSerializer.Serialize(response, LauncherConstants._jsonOptions)); window.SendWebMessage(JsonSerializer.Serialize(response, LauncherConstants._jsonOptions));
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine($"Error stacktrace: {ex.StackTrace}");
Console.WriteLine($"Bridge error: {ex.Message}"); Console.WriteLine($"Bridge error: {ex.Message}");
} }
}); });

View File

@ -1,11 +1,8 @@
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
namespace Lentia.Core.Constants namespace Lentia.Core.Constants {
{ public static class LauncherConstants {
public static class LauncherConstants
{
public static readonly HttpClient Http = new HttpClient();
public static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
public static class Urls public static class Urls
{ {

View File

@ -1,6 +1,7 @@
using Lentia.Core.Constants; using Lentia.Core.Constants;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Net.Http.Json; using System.Net.Http.Json;
using Lentia.Core.Utils;
namespace Lentia.Core.Auth.Yggdrasil; namespace Lentia.Core.Auth.Yggdrasil;
@ -76,7 +77,7 @@ public class Authenticator {
}; };
try { try {
var response = await LauncherConstants.Http.PostAsJsonAsync(LauncherConstants.Urls.MojangAuthServer + "/authenticate", payload); var response = await HttpHelper.FetchAsync(LauncherConstants.Urls.MojangAuthServer + "/authenticate", HttpMethod.Post, payload);
if (!response.IsSuccessStatusCode) { if (!response.IsSuccessStatusCode) {
var errorData = await response.Content.ReadFromJsonAsync<YggdrasilError>(); var errorData = await response.Content.ReadFromJsonAsync<YggdrasilError>();
return AuthResult.Fail( return AuthResult.Fail(

View File

@ -3,6 +3,7 @@ using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using Lentia.Core.Auth.Yggdrasil; using Lentia.Core.Auth.Yggdrasil;
using Lentia.Core.Constants; using Lentia.Core.Constants;
using Lentia.Core.Utils;
namespace Lentia.Core.Auth.OAuth2; namespace Lentia.Core.Auth.OAuth2;
@ -36,7 +37,7 @@ public class Discord {
public static async Task<AuthResult> LoginWithDiscordOAuth2(string authCode) { public static async Task<AuthResult> LoginWithDiscordOAuth2(string authCode) {
var url = $"https://yggdrasil.azures.fr/auth/provider/discord/login/callback?code={authCode}&requestUser=true"; var url = $"https://yggdrasil.azures.fr/auth/provider/discord/login/callback?code={authCode}&requestUser=true";
try { try {
var response = await LauncherConstants.Http.GetAsync(url); var response = await HttpHelper.FetchAsync(url, HttpMethod.Get);
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
var data = await response.Content.ReadFromJsonAsync<AuthenticateResponse>(); var data = await response.Content.ReadFromJsonAsync<AuthenticateResponse>();
return data != null ? AuthResult.Ok(data) : AuthResult.Fail("Erreur de lecture des données."); return data != null ? AuthResult.Ok(data) : AuthResult.Fail("Erreur de lecture des données.");

View File

@ -25,7 +25,7 @@ public class AssetsHelper {
} }
public static async Task<AssetsIndex> GetAssetsIndexAsync(string url) { public static async Task<AssetsIndex> GetAssetsIndexAsync(string url) {
var response = await LauncherConstants.Http.GetAsync(url); var response = await HttpHelper.FetchAsync(url, HttpMethod.Get);
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
string json = await response.Content.ReadAsStringAsync(); string json = await response.Content.ReadAsStringAsync();
@ -86,6 +86,7 @@ public class AssetsHelper {
try { try {
await FileHelper.DownloadFileAsync(url, localPath); await FileHelper.DownloadFileAsync(url, localPath);
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine(url);
Console.WriteLine($"Échec : {hash} - {ex.Message}"); Console.WriteLine($"Échec : {hash} - {ex.Message}");
} }
}); });

View File

@ -1,55 +1,164 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json;
using Lentia.Core.Auth.Yggdrasil; using Lentia.Core.Auth.Yggdrasil;
using Lentia.Core.Constants;
using Lentia.Core.Game.Extra; using Lentia.Core.Game.Extra;
using Lentia.Core.Utils; using Lentia.Core.Utils;
using Lentia.Utils; using Lentia.Utils;
namespace Lentia.Core.Game; namespace Lentia.Core.Game;
public class LaunchOptions {
public enum ModLoaderType {
Vanilla,
Forge,
Fabric,
NeoForge
}
public VersionOptions Version { get; set; } = new();
public ModLoaderType ModLoader { get; set; } = ModLoaderType.Vanilla;
public string? Forge { get; set; }
public MemoryOptions Memory { get; set; } = new();
public string JavaPath { get; set; } = "java";
public int Timeout { get; set; } = 10000;
public List<string> CustomArgs { get; set; } = new();
public List<string> CustomLaunchArgs { get; set; } = new();
public List<string> Features { get; set; } = new();
public WindowOptions Window { get; set; } = new();
public QuickPlayOptions? QuickPlay { get; set; }
public ProxyOptions? Proxy { get; set; }
public OverridesOptions Overrides { get; set; } = new();
public class VersionOptions {
public string Number { get; set; } = "1.12.2";
public string Type { get; set; } = "release";
public string? Custom { get; set; }
}
public class MemoryOptions {
public string Max { get; set; } = "2048M";
public string Min { get; set; } = "512M";
}
public class WindowOptions {
public int? Width { get; set; }
public int? Height { get; set; }
public bool Fullscreen { get; set; } = false;
}
public class QuickPlayOptions {
public string Type { get; set; } = "singleplayer";
public string? Identifier { get; set; }
public string? Path { get; set; }
}
public class ProxyOptions {
public string? Host { get; set; }
public string Port { get; set; } = "8080";
public string? Username { get; set; }
public string? Password { get; set; }
}
public class OverridesOptions {
public string? GameDirectory { get; set; }
public string? MinecraftJar { get; set; }
public string? VersionName { get; set; }
public string? VersionJson { get; set; }
public string? Directory { get; set; }
public string? Natives { get; set; }
public string? AssetRoot { get; set; }
public string? AssetIndex { get; set; }
public string? LibraryRoot { get; set; }
public string? Cwd { get; set; }
public bool Detached { get; set; } = false;
public List<string>? Classes { get; set; }
public string? Log4jConfigurationFile { get; set; }
public UrlOverrides Url { get; set; } = new();
}
public class UrlOverrides {
public string Meta { get; set; } = "https://launchermeta.mojang.com";
public string Resource { get; set; } = "https://resources.download.minecraft.net";
public string MavenForge { get; set; } = "https://maven.minecraftforge.net/";
public string DefaultRepoForge { get; set; } = "https://libraries.minecraft.net/";
public string FallbackMaven { get; set; } = "https://search.maven.org/remotecontent?filepath=";
}
}
public static class GameHelper { public static class GameHelper {
public static async Task<MinecraftVersion> PrepareGame(string targetVersion, string gameRoot, bool installForge) { public static async Task<MinecraftVersion> PrepareGame(LaunchOptions options, string gameRoot) {
try { string root = options.Overrides.GameDirectory ?? gameRoot;
string versionUrl = await VersionsHelper.GetVersionUrlAsync(targetVersion);
MinecraftVersion version = await VersionsHelper.GetVersionDetailsAsync(versionUrl);
MinecraftVersion workingVersion = version;
if (installForge) { MinecraftVersion baseVersion;
MinecraftVersion? forgeMeta = await ForgeHelper.ProcessForge(version, gameRoot); if (!string.IsNullOrEmpty(options.Overrides.VersionJson)) {
if (forgeMeta != null) { string jsonContent = await File.ReadAllTextAsync(options.Overrides.VersionJson);
workingVersion = version; baseVersion = JsonSerializer.Deserialize<MinecraftVersion>(jsonContent, LauncherConstants._jsonOptions)!;
} } else {
} string versionUrl = await VersionsHelper.GetVersionUrlAsync(options.Version.Number);
baseVersion = await VersionsHelper.GetVersionDetailsAsync(versionUrl);
AssetsIndex assetsIndex = await AssetsHelper.GetAssetsIndexAsync(version.AssetIndex!.Url!);
await AssetsHelper.DownloadAssetsParallel(assetsIndex, gameRoot);
await JavaHelper.GetJavaExecutablePath(version, gameRoot);
await VersionsHelper.DownloadClientJar(version, gameRoot);
await LibrariesHelper.DownloadLibraries(workingVersion.Libraries!, gameRoot);
LibrariesHelper.ExtractNatives(workingVersion, gameRoot);
await VersionsHelper.DownloadLoggingConfig(version, gameRoot);
return workingVersion;
} catch (Exception) {
throw;
} }
MinecraftVersion workingVersion = baseVersion;
switch (options.ModLoader) {
case LaunchOptions.ModLoaderType.Forge:
var forgeMeta = await ForgeHelper.ProcessForge(baseVersion, root);
if (forgeMeta != null) {
workingVersion = baseVersion;
}
break;
}
if (baseVersion.AssetIndex == null)
throw new Exception("Erreur : L'index des assets est manquant dans le manifest de version.");
string assetRoot = options.Overrides.AssetRoot ?? Path.Combine(root, "assets");
AssetsIndex assetsIndex = await AssetsHelper.GetAssetsIndexAsync(baseVersion.AssetIndex.Url!);
await AssetsHelper.DownloadAssetsParallel(assetsIndex, assetRoot);
string libRoot = options.Overrides.LibraryRoot ?? Path.Combine(root, "libraries");
await LibrariesHelper.DownloadLibraries(workingVersion.Libraries!, libRoot);
await VersionsHelper.DownloadClientJar(baseVersion, root);
LibrariesHelper.ExtractNatives(workingVersion, root);
return workingVersion;
} }
public static string BuildClasspath(MinecraftVersion version, string gameRoot) { public static string BuildClasspath(MinecraftVersion version, string gameRoot) {
var paths = new List<string>(); var paths = new List<string>();
string libRoot = Path.Combine(gameRoot, "libraries"); string libRoot = Path.Combine(gameRoot, "libraries");
paths.Add(Path.Combine(gameRoot, "versions", version.Id!, $"{version.Id}.jar")); string clientJar = Path.Combine(gameRoot, "versions", version.Id!, $"{version.Id}.jar");
if (File.Exists(clientJar)) paths.Add(clientJar);
foreach (var lib in version.Libraries!) { if (version.Libraries != null) {
if (LibrariesHelper.IsAllowed(lib.Rules, OsHelper.GetOSName())) { foreach (var lib in version.Libraries) {
string relPath = ForgeHelper.GeneratePathFromArtifactName(lib.Name!); if (!LibrariesHelper.IsAllowed(lib.Rules, OsHelper.GetOSName())) continue;
paths.Add(Path.Combine(libRoot, relPath));
string? relPath = lib.Downloads?.Artifact?.Path ?? ForgeHelper.GeneratePathFromArtifactName(lib.Name!);
string fullPath = Path.Combine(libRoot, relPath);
if (File.Exists(fullPath)) {
paths.Add(fullPath);
} else {
Console.WriteLine($"[Classpath] Manquant: {fullPath}");
}
} }
} }
return string.Join(Path.PathSeparator.ToString(), paths);
string separator = Path.PathSeparator.ToString();
return string.Join(separator, paths);
} }
public static List<string> GenerateGameArguments(MinecraftVersion version, AuthenticateResponse auth, string gameRoot) { public static List<string> GenerateGameArguments(MinecraftVersion version, AuthenticateResponse auth, string gameRoot) {
@ -73,52 +182,89 @@ public static class GameHelper {
return rawArgs.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList(); return rawArgs.Split(" ", StringSplitOptions.RemoveEmptyEntries).ToList();
} }
public static void Launch(MinecraftVersion version, AuthenticateResponse auth, string gameRoot, Action<string> logHandler) { public static void Launch(MinecraftVersion version, AuthenticateResponse auth, string gameRoot, LaunchOptions options, Action<string> logHandler) {
string javaPath = SettingsManager.ReadSettings().JavaPath; string root = options.Overrides.GameDirectory ?? gameRoot;
string nativesDir = options.Overrides.Natives ?? Path.Combine(root, "versions", version.Id!, "natives");
string nativesDir = Path.Combine(gameRoot, "versions", version.Id!, "natives"); string javaPath = options.JavaPath ?? SettingsManager.ReadSettings().JavaPath;
var jvmArgs = new List<string> { var jvmArgs = new List<string> {
$"-Xmx{SettingsManager.ReadSettings().Ram.Max}M", $"-Xms{options.Memory.Min}",
$"-Xmx{options.Memory.Max}",
$"-Djava.library.path={nativesDir}", $"-Djava.library.path={nativesDir}",
"-Dminecraft.launcher.brand=Lentia", "-Dminecraft.launcher.brand=Lentia",
"-Dminecraft.launcher.version=1.0.0" "-Dminecraft.launcher.version=1.0.0"
}; };
// if (version.Logging?.Client?.Argument != null) { // string? logConfigFile = options.Overrides.Log4jConfigurationFile;
// string logConfig = Path.Combine(gameRoot, "assets", "log_configs", version.Logging.Client.File?.Id ?? "client-log4j2.xml"); // if (string.IsNullOrEmpty(logConfigFile) && version.Logging?.Client?.Argument != null) {
// jvmArgs.Add(version.Logging.Client.Argument.Replace("${path}", logConfig)); // logConfigFile = Path.Combine(root, "assets", "log_configs", version.Logging.Client.File?.Id ?? "client-log4j2.xml");
// jvmArgs.Add(version.Logging.Client.Argument.Replace("${path}", logConfigFile));
// } else if (!string.IsNullOrEmpty(logConfigFile)) {
// jvmArgs.Add($"-Dlog4j.configurationFile={logConfigFile}");
// } // }
string classpath = BuildClasspath(version, gameRoot); if (options.CustomArgs != null && options.CustomArgs.Any()) {
jvmArgs.AddRange(options.CustomArgs);
}
string classpath = (options.Overrides.Classes != null && options.Overrides.Classes.Any())
? string.Join(Path.PathSeparator.ToString(), options.Overrides.Classes)
: BuildClasspath(version, root);
jvmArgs.Add("-cp"); jvmArgs.Add("-cp");
jvmArgs.Add(classpath); jvmArgs.Add(classpath);
jvmArgs.Add(version.MainClass ?? "net.minecraft.client.main.Main"); jvmArgs.Add(version.MainClass ?? "net.minecraft.client.main.Main");
var gameArgs = GenerateGameArguments(version, auth, gameRoot); var gameArgs = GenerateGameArguments(version, auth, root);
jvmArgs.AddRange(gameArgs);
if (options.QuickPlay != null) {
gameArgs.Add($"--quickPlayPath {options.QuickPlay.Path}");
gameArgs.Add($"--quickPlay{options.QuickPlay.Type} {options.QuickPlay.Identifier}");
}
if (options.Window.Fullscreen) {
gameArgs.Add("--fullscreen");
}
if (options.Window.Width.HasValue) {
gameArgs.Add("--width"); gameArgs.Add(options.Window.Width.Value.ToString());
}
if (options.Window.Height.HasValue) {
gameArgs.Add("--height"); gameArgs.Add(options.Window.Height.Value.ToString());
}
if (options.CustomLaunchArgs != null && options.CustomLaunchArgs.Any()) {
gameArgs.AddRange(options.CustomLaunchArgs);
}
var startInfo = new ProcessStartInfo(javaPath) { var startInfo = new ProcessStartInfo(javaPath) {
WorkingDirectory = gameRoot, WorkingDirectory = options.Overrides.Cwd ?? root,
Arguments = string.Join(" ", jvmArgs.Select(a => a.Contains(" ") ? $"\"{a}\"" : a)), Arguments = string.Join(" ", jvmArgs.Concat(gameArgs).Select(a => a.Contains(" ") ? $"\"{a}\"" : a)),
UseShellExecute = false, UseShellExecute = options.Overrides.Detached,
RedirectStandardOutput = true, RedirectStandardOutput = !options.Overrides.Detached,
RedirectStandardError = true, RedirectStandardError = !options.Overrides.Detached,
CreateNoWindow = true CreateNoWindow = true
}; };
Console.WriteLine($"Start command: {javaPath} {startInfo.Arguments}");
var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true };
process.OutputDataReceived += (s, e) => { if (e.Data != null) logHandler(e.Data); }; if (!options.Overrides.Detached) {
process.ErrorDataReceived += (s, e) => { if (e.Data != null) logHandler($"[ERROR] {e.Data}"); }; process.OutputDataReceived += (s, e) => { if (e.Data != null) logHandler(e.Data); };
process.ErrorDataReceived += (s, e) => { if (e.Data != null) logHandler($"[ERROR] {e.Data}"); };
}
process.Exited += (sender, e) => { process.Exited += (sender, e) => {
logHandler("GAME_CLOSED"); logHandler("GAME_CLOSED");
}; };
process.Start(); if (process.Start()) {
process.BeginOutputReadLine(); if (!options.Overrides.Detached) {
process.BeginErrorReadLine(); process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
}
} }
} }

View File

@ -70,7 +70,10 @@ public static class JavaHelper {
private static async Task DownloadJavaRuntime(string root, string platform, string component) { private static async Task DownloadJavaRuntime(string root, string platform, string component) {
string allJsonUrl = "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc6c0dba9f635e09f3900331038590632291/all.json"; string allJsonUrl = "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc6c0dba9f635e09f3900331038590632291/all.json";
var allRuntimes = await LauncherConstants.Http.GetFromJsonAsync<JsonElement>(allJsonUrl);
var allRuntimesResponse = await HttpHelper.FetchAsync(allJsonUrl);
allRuntimesResponse.EnsureSuccessStatusCode();
var allRuntimes = await allRuntimesResponse.Content.ReadFromJsonAsync<JsonElement>(LauncherConstants._jsonOptions);
if (!allRuntimes.TryGetProperty(platform, out var platformData) || if (!allRuntimes.TryGetProperty(platform, out var platformData) ||
!platformData.TryGetProperty(component, out var componentVersions)) { !platformData.TryGetProperty(component, out var componentVersions)) {
@ -78,15 +81,19 @@ public static class JavaHelper {
} }
string manifestUrl = componentVersions[0].GetProperty("manifest").GetProperty("url").GetString()!; string manifestUrl = componentVersions[0].GetProperty("manifest").GetProperty("url").GetString()!;
var manifest = await LauncherConstants.Http.GetFromJsonAsync<JavaRuntimeManifest>(manifestUrl); var manifestResponse = await HttpHelper.FetchAsync(manifestUrl);
manifestResponse.EnsureSuccessStatusCode();
var manifest = await manifestResponse.Content.ReadFromJsonAsync<JavaRuntimeManifest>(LauncherConstants._jsonOptions);
string javaDir = Path.Combine(root, "runtime", component, platform); string javaDir = Path.Combine(root, "runtime", component, platform);
await Parallel.ForEachAsync(manifest!.Files!, new ParallelOptions { MaxDegreeOfParallelism = 8 }, async (entry, token) => { await Parallel.ForEachAsync(manifest!.Files!, new ParallelOptions { MaxDegreeOfParallelism = 8 }, async (entry, token) => {
string localPath = Path.Combine(javaDir, entry.Key); string localPath = Path.Combine(javaDir, entry.Key);
if (entry.Value.Type == "directory") { if (entry.Value.Type == "directory") {
Directory.CreateDirectory(localPath); Directory.CreateDirectory(localPath);
} else if (entry.Value.Downloads?.Raw != null) { } else if (entry.Value.Downloads?.Raw != null) {
var download = entry.Value.Downloads.Raw; var download = entry.Value.Downloads.Raw;
if (!File.Exists(localPath) || new FileInfo(localPath).Length != download.Size) { if (!File.Exists(localPath) || new FileInfo(localPath).Length != download.Size) {
await FileHelper.DownloadFileAsync(download.Url!, localPath); await FileHelper.DownloadFileAsync(download.Url!, localPath);
} }

View File

@ -7,7 +7,7 @@ public static class LibrariesHelper {
public static async Task DownloadLibraries(List<Library> libraries, string root) { public static async Task DownloadLibraries(List<Library> libraries, string root) {
string osName = OsHelper.GetOSName(); string osName = OsHelper.GetOSName();
string librariesDir = Path.Combine(root, "libraries"); string librariesDir = Path.Combine(root);
var downloadsToProcess = new List<DownloadInfo>(); var downloadsToProcess = new List<DownloadInfo>();

View File

@ -196,7 +196,7 @@ public class JavaVersionInfo {
public static class VersionsHelper { public static class VersionsHelper {
public static async Task<string> GetVersionUrlAsync(string targetVersion) { public static async Task<string> GetVersionUrlAsync(string targetVersion) {
var response = await LauncherConstants.Http.GetAsync("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"); var response = await HttpHelper.FetchAsync("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", HttpMethod.Get);
var manifest = await response.Content.ReadFromJsonAsync<VersionManifestV2>(); var manifest = await response.Content.ReadFromJsonAsync<VersionManifestV2>();
var versionEntry = manifest?.Versions?.FirstOrDefault(v => v.Id == targetVersion); var versionEntry = manifest?.Versions?.FirstOrDefault(v => v.Id == targetVersion);
@ -206,7 +206,7 @@ public static class VersionsHelper {
} }
public static async Task<MinecraftVersion> GetVersionDetailsAsync(string versionUrl) { public static async Task<MinecraftVersion> GetVersionDetailsAsync(string versionUrl) {
var response = await LauncherConstants.Http.GetAsync(versionUrl); var response = await HttpHelper.FetchAsync(versionUrl, HttpMethod.Get);
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
string jsonResponse = await response.Content.ReadAsStringAsync(); string jsonResponse = await response.Content.ReadAsStringAsync();

View File

@ -16,9 +16,11 @@ public static class ForgeHelper {
public static async Task<string> FetchAndDownloadForge(string mcVersion, string root, bool recommended = true) { public static async Task<string> FetchAndDownloadForge(string mcVersion, string root, bool recommended = true) {
string promoUrl = "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"; string promoUrl = "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json";
LauncherConstants.Http.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); var response = await HttpHelper.FetchAsync(promoUrl, HttpMethod.Get);
var promoData = await LauncherConstants.Http.GetFromJsonAsync<ForgePromotions>(promoUrl); response.EnsureSuccessStatusCode();
var promoData = await response.Content.ReadFromJsonAsync<ForgePromotions>(LauncherConstants._jsonOptions);
string promoKey = $"{mcVersion}-{(recommended ? "recommended" : "latest")}"; string promoKey = $"{mcVersion}-{(recommended ? "recommended" : "latest")}";
if (promoData?.Promos == null || !promoData.Promos.TryGetValue(promoKey, out string? buildVersion)) { if (promoData?.Promos == null || !promoData.Promos.TryGetValue(promoKey, out string? buildVersion)) {

View File

@ -1,5 +1,3 @@
using Lentia.Core.Constants;
namespace Lentia.Core.Utils; namespace Lentia.Core.Utils;
public static class FileHelper { public static class FileHelper {
@ -13,7 +11,7 @@ public static class FileHelper {
File.Delete(destinationPath); File.Delete(destinationPath);
} }
using var response = await LauncherConstants.Http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); using var response = await HttpHelper.FetchAsync(url);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
using var streamToReadFrom = await response.Content.ReadAsStreamAsync(); using var streamToReadFrom = await response.Content.ReadAsStreamAsync();

View File

@ -0,0 +1,32 @@
using System.Net.Http.Json;
using Lentia.Core.Constants;
namespace Lentia.Core.Utils;
public static class HttpHelper {
private static readonly HttpClient Http = new HttpClient();
public static async Task<HttpResponseMessage> FetchAsync(string url, HttpMethod? method = null, object? body = null, Dictionary<string, string>? headers = null) {
var request = new HttpRequestMessage(method ?? HttpMethod.Get, url);
request.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
if (headers != null) {
foreach (var header in headers) {
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
if (body != null) {
if (body is string s) {
request.Content = new StringContent(s, System.Text.Encoding.UTF8, "application/json");
} else {
request.Content = JsonContent.Create(body, options: LauncherConstants._jsonOptions);
}
}
return await Http.SendAsync(request);
}
}

View File

@ -1,26 +1,22 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text;
using System.Text.Json; using System.Text.Json;
using Lentia.Core.Constants; using Lentia.Core.Constants;
using Lentia.Core.Utils;
namespace Lentia.Utils; namespace Lentia.Utils;
public static class MojangAPI { public static class MojangAPI {
public static async Task<string> UploadSkinAsync(string filePath, string variant, string token) { public static async Task<string> UploadSkinAsync(string filePath, string variant, string token) {
LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
using var content = new MultipartFormDataContent(); using var content = new MultipartFormDataContent();
content.Add(new StringContent(variant), "variant"); content.Add(new StringContent(variant), "variant");
byte[] fileBytes = await File.ReadAllBytesAsync(filePath); byte[] fileBytes = await File.ReadAllBytesAsync(filePath);
var fileContent = new ByteArrayContent(fileBytes); var fileContent = new ByteArrayContent(fileBytes);
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png"); fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
content.Add(fileContent, "file", Path.GetFileName(filePath)); content.Add(fileContent, "file", Path.GetFileName(filePath));
var response = await LauncherConstants.Http.PostAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/skins", content); var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/skins", method: HttpMethod.Post, body: content, headers: new() { { "Authorization", $"Bearer {token}" } } );
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
return await response.Content.ReadAsStringAsync(); return await response.Content.ReadAsStringAsync();
@ -29,12 +25,8 @@ public static class MojangAPI {
} }
} }
public static async Task<object> GetPlayerProfileAsync(string accessToken) { public static async Task<object> GetPlayerProfileAsync(string accessToken) {
LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile", headers: new() { { "Authorization", $"Bearer {accessToken}" }, { "Accept", "application/json" } } );
LauncherConstants.Http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await LauncherConstants.Http.GetAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile");
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
string jsonResponse = await response.Content.ReadAsStringAsync(); string jsonResponse = await response.Content.ReadAsStringAsync();
@ -45,9 +37,7 @@ public static class MojangAPI {
} }
public static async Task<object> HideCapeAsync(string accessToken) { public static async Task<object> HideCapeAsync(string accessToken) {
LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active", method: HttpMethod.Delete, headers: new() { { "Authorization", $"Bearer {accessToken}" } } );
var response = await LauncherConstants.Http.DeleteAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active");
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
string jsonResponse = await response.Content.ReadAsStringAsync(); string jsonResponse = await response.Content.ReadAsStringAsync();
@ -58,13 +48,7 @@ public static class MojangAPI {
} }
public static async Task<object> ShowCapeAsync(string accessToken, string capeId) { public static async Task<object> ShowCapeAsync(string accessToken, string capeId) {
LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await HttpHelper.FetchAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active", method: HttpMethod.Put, body: new { capeId = capeId }, headers: new() { { "Authorization", $"Bearer {accessToken}" } } );
var body = new { capeId = capeId };
string jsonBody = JsonSerializer.Serialize(body);
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = await LauncherConstants.Http.PutAsync("https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/capes/active", content);
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
string jsonResponse = await response.Content.ReadAsStringAsync(); string jsonResponse = await response.Content.ReadAsStringAsync();
@ -75,18 +59,14 @@ public static class MojangAPI {
} }
public static async Task<object> ChangeUsernameAsync(string newName, string accessToken) { public static async Task<object> ChangeUsernameAsync(string newName, string accessToken) {
LauncherConstants.Http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var url = $"https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/name/{newName}"; var url = $"https://yggdrasil.azures.fr/minecraftservices/minecraft/profile/name/{newName}";
var request = new HttpRequestMessage(HttpMethod.Put, url); var response = await HttpHelper.FetchAsync(url, method: HttpMethod.Put, headers: new() { { "Authorization", $"Bearer {accessToken}" } } );
var response = await LauncherConstants.Http.SendAsync(request);
if (response.IsSuccessStatusCode) { if (response.IsSuccessStatusCode) {
string jsonResponse = await response.Content.ReadAsStringAsync(); string jsonResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<object>(jsonResponse, LauncherConstants._jsonOptions)!; return JsonSerializer.Deserialize<object>(jsonResponse, LauncherConstants._jsonOptions)!;
} } else {
else {
throw new Exception($"Erreur lors du changement de pseudo (Code: {response.StatusCode})"); throw new Exception($"Erreur lors du changement de pseudo (Code: {response.StatusCode})");
} }
} }