using AzuresPackager; using Lentia.Core.Auth.OAuth2; using Lentia.Core.Constants; using Lentia.Core.Game; using Lentia.Utils; using Photino.NET; using System.Reflection; using System.Text; using System.Text.Json; using LentRules = Lentia.Core.Auth.Yggdrasil; namespace Lentia; class Program { private static readonly string __dirname = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; private static LentRules.AuthenticateResponse? _authenticatedPlayer; private static readonly string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); private static readonly string gameRoot = Path.Combine(appData, ".lentia2"); private static readonly LentRules.Authenticator _ygg = new(); [STAThread] static void Main(string[] args) { SettingsManager.InitSettings(gameRoot); CreateLoginWindow().WaitForClose(); } public static PhotinoWindow CreateLoginWindow() { AzuresPackage azp = new AzuresPackage(Path.Combine(__dirname, "root.dat"), 66); List entries = azp.ListEntries(); var window = new PhotinoWindow() .SetUseOsDefaultLocation(false) .SetUseOsDefaultSize(false) .SetContextMenuEnabled(false) .SetChromeless(true) .RegisterCustomSchemeHandler("http", (object sender, string scheme, string url, out string contentType) =>{ Uri uri = new Uri(url); if (uri.Host == "internal") { string internalPath = uri.AbsolutePath; contentType = MimeHelper.GetMimeType(uri.AbsolutePath); byte[] fileData = azp.GetFileBytes(internalPath.TrimStart('/')); return fileData != null ? new MemoryStream(fileData) : null; } contentType = null!; return null; }); window.WindowCreated += (sender, e) => { SetupBridge(window); WindowHelper.MakeLoginWindow(window); }; window.Load("http://internal/login.html"); return window; } public static void SetupBridge(PhotinoWindow window) { window.RegisterWebMessageReceivedHandler(async (object? sender, string message) => { try { var json = JsonDocument.Parse(message); string requestId = json.RootElement.GetProperty("requestId").GetString()!; string method = json.RootElement.GetProperty("functionName").GetString()!; var jsonPayload = json.RootElement.GetProperty("payload"); var pw = (PhotinoWindow)sender!; object? payload = null; switch (method) { case "launcher::version": payload = "v1.0.0-beta"; break; case "window::close": pw.Close(); break; case "lentia::dynmap": payload = "http://azures.fr:8123/"; break; case "hardware::ram": payload = Hardware.GetRamUsage(); break; case "auth::lentia": string user = jsonPayload.GetProperty("username").GetString()!; string pass = jsonPayload.GetProperty("password").GetString()!; var lentiaAuthResult = await _ygg.Login(user, pass); if (lentiaAuthResult.Success) { _authenticatedPlayer = lentiaAuthResult.Player; WindowHelper.MakeStandardWindow(pw, 1356, 720, "http://internal/logged.html"); payload = new { success = true }; } else { payload = new { success = false, error = lentiaAuthResult.Error }; } break; case "auth::logout": _authenticatedPlayer = null; pw.Load("http://internal/login.html"); WindowHelper.MakeLoginWindow(pw); payload = true; break; case "dialog::error": string title = jsonPayload.TryGetProperty("title", out var t) ? t.GetString()! : "Lentia"; string msg = jsonPayload.GetProperty("message").GetString()!; pw.ShowMessage(title, msg, PhotinoDialogButtons.Ok, PhotinoDialogIcon.Error); payload = new { success = true }; break; case "settings::read": payload = SettingsManager.ReadSettings(); break; case "settings::set": if (jsonPayload.TryGetProperty("key", out var keyElement) && jsonPayload.TryGetProperty("value", out var valueElement)) { string key = keyElement.GetString()!; object? valueToSet = valueElement.ValueKind switch { JsonValueKind.String => valueElement.GetString(), JsonValueKind.Number => valueElement.TryGetInt32(out int i) ? i : valueElement.GetDouble(), JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Null => null, _ => valueElement }; if (valueToSet != null) { SettingsManager.Set(key, valueToSet); payload = new { success = true }; } else { payload = new { success = false, error = "Incorrect or null value" }; } } break; case "dialog::selectSkin": var imageFilter = new (string Name, string[] Extensions)[] { ("Portable Network Graphic Image", new[] { "*.png" }) }; string[] files = window.ShowOpenFile("Choisis ton plus beau skin mon reuf", null, false, imageFilter); payload = new { success = files != null && files.Length > 0, path = (files != null && files.Length > 0) ? files[0] : null }; break; case "network::uploadSkin": try { string path = jsonPayload.GetProperty("path").GetString()!; string variant = jsonPayload.GetProperty("variant").GetString()!; string token = _authenticatedPlayer?.AccessToken!; string skinResult = await MojangAPI.UploadSkinAsync(path, variant, token); payload = new { success = true }; } catch (Exception ex) { payload = new { success = false, error = ex.Message }; } break; case "network::getPlayer": try { var profileData = await MojangAPI.GetPlayerProfileAsync(_authenticatedPlayer!.AccessToken); payload = profileData; } catch (Exception ex) { payload = new { success = false, error = ex.Message }; } break; case "network::hideCape": try { var hideCapeResult = await MojangAPI.HideCapeAsync(_authenticatedPlayer!.AccessToken); payload = new { success = true, data = hideCapeResult }; } catch (Exception ex) { payload = new { success = false, error = ex.Message }; } break; case "network::showCape": try { string token = _authenticatedPlayer?.AccessToken!; string capeId = jsonPayload.GetProperty("capeId").GetString()!; var showCapeResult = await MojangAPI.ShowCapeAsync(token, capeId); payload = new { success = true, data = showCapeResult }; } catch (Exception ex) { payload = new { success = false, error = ex.Message }; } break; case "network::changeUsername": try { string newName = jsonPayload.GetProperty("username").GetString()!; string token = _authenticatedPlayer?.AccessToken!; if (string.IsNullOrEmpty(token)) throw new Exception("Session expirée ou invalide."); var updatedProfile = await MojangAPI.ChangeUsernameAsync(newName, token); if (_authenticatedPlayer != null) { _authenticatedPlayer.User.Username = newName; } payload = new { success = true, profile = updatedProfile }; } catch (Exception ex) { payload = new { success = false, error = ex.Message }; } break; case "oauth2::discord": BashUtils.OpenUrl("https://yggdrasil.azures.fr/auth/provider/discord/login"); string? code = await Discord.ListenForCode(); var discordLoginResult = await Discord.LoginWithDiscordOAuth2(code!); if (discordLoginResult.Success) { _authenticatedPlayer = discordLoginResult.Player; WindowHelper.MakeStandardWindow(pw, 1356, 720, "http://internal/logged.html"); payload = new { success = true }; } else { Console.WriteLine(discordLoginResult); payload = new { success = false, error = discordLoginResult.Error }; } break; case "launcher::game": try { await GenericFilesService.FetchAndSyncGenericFiles(gameRoot); var options = new LaunchOptions { 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, 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) => { var logMessage = new { requestId = "game::log", payload = new { message = logLine.ToString() } }; window.SendWebMessage(JsonSerializer.Serialize(logMessage, LauncherConstants._jsonOptions)); }); payload = new { success = true }; } catch (Exception) { payload = new { success = false }; throw; } break; } var response = new { requestId, payload }; window.SendWebMessage(JsonSerializer.Serialize(response, LauncherConstants._jsonOptions)); } catch (Exception ex) { Console.WriteLine($"Error stacktrace: {ex.StackTrace}"); Console.WriteLine($"Bridge error: {ex.Message}"); } }); } }