From 9153341cc21c127d6d9d54ea5227482d406878b2 Mon Sep 17 00:00:00 2001 From: GatoWare Date: Mon, 9 Mar 2026 20:18:13 -0300 Subject: [PATCH] add per-account skins, skin preview and code refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add per-account skin system - add automatic 64x64 → 64x32 skin conversion - add built-in skin preview in launcher UI - apply selected skin automatically on game launch - add original skin backup and restore system - improve launcher layout with integrated skin preview - refactor Form1 logic into multiple partial class files --- Form1.Accounts.cs | 110 +++++ Form1.Designer.cs | 67 ++- Form1.Events.cs | 173 +++++++ Form1.Game.cs | 281 ++++++++++++ Form1.SkinPreview.cs | 156 +++++++ Form1.Skins.cs | 235 ++++++++++ Form1.Updates.cs | 326 +++++++++++++ Form1.cs | 855 +---------------------------------- Form3.Designer.cs | 39 ++ Form3.cs | 20 + LegacyConsoleLauncher.csproj | 28 ++ README.md | 44 ++ packages.config | 4 + 13 files changed, 1482 insertions(+), 856 deletions(-) create mode 100644 Form1.Accounts.cs create mode 100644 Form1.Events.cs create mode 100644 Form1.Game.cs create mode 100644 Form1.SkinPreview.cs create mode 100644 Form1.Skins.cs create mode 100644 Form1.Updates.cs create mode 100644 Form3.Designer.cs create mode 100644 Form3.cs create mode 100644 packages.config diff --git a/Form1.Accounts.cs b/Form1.Accounts.cs new file mode 100644 index 0000000..cb584de --- /dev/null +++ b/Form1.Accounts.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form1 + { + private void LoadAccounts() + { + usernameComboBox.Items.Clear(); + playtimeData.Clear(); + + if (!File.Exists(accountsFile)) + { + return; + } + + string[] lines = File.ReadAllLines(accountsFile); + + foreach (string line in lines) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + string[] parts = line.Split('|'); + string username = parts[0].Trim(); + + if (string.IsNullOrWhiteSpace(username)) + { + continue; + } + + int seconds = 0; + + if (parts.Length > 1) + { + int.TryParse(parts[1], out seconds); + } + + if (!playtimeData.ContainsKey(username)) + { + playtimeData[username] = seconds; + usernameComboBox.Items.Add(username); + } + } + + if (usernameComboBox.Items.Count > 0) + { + usernameComboBox.SelectedIndex = 0; + } + } + + private void SaveAccounts() + { + List lines = new List(); + + foreach (var entry in playtimeData) + { + lines.Add(entry.Key + "|" + entry.Value); + } + + File.WriteAllLines(accountsFile, lines); + } + + private void AddAccount(string username) + { + if (string.IsNullOrWhiteSpace(username)) + { + return; + } + + if (!playtimeData.ContainsKey(username)) + { + playtimeData[username] = 0; + usernameComboBox.Items.Add(username); + } + + usernameComboBox.SelectedItem = username; + SaveAccounts(); + } + + private string FormatPlaytime(int totalSeconds) + { + TimeSpan time = TimeSpan.FromSeconds(totalSeconds); + return ((int)time.TotalHours) + "h " + time.Minutes + "m"; + } + + private void UpdatePlaytimeLabel() + { + string username = usernameComboBox.Text.Trim(); + + if (string.IsNullOrWhiteSpace(username)) + { + playtimeLabel.Text = "Playtime: 0h 0m"; + return; + } + + if (!playtimeData.ContainsKey(username)) + { + playtimeData[username] = 0; + } + + playtimeLabel.Text = "Playtime: " + FormatPlaytime(playtimeData[username]); + } + } +} \ No newline at end of file diff --git a/Form1.Designer.cs b/Form1.Designer.cs index 77c9e6a..dedcdc6 100644 --- a/Form1.Designer.cs +++ b/Form1.Designer.cs @@ -37,17 +37,24 @@ this.resetLink = new System.Windows.Forms.LinkLabel(); this.playtimeLabel = new System.Windows.Forms.Label(); this.checkforLink = new System.Windows.Forms.LinkLabel(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.skinPreviewPictureBox = new System.Windows.Forms.PictureBox(); ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.skinPreviewPictureBox)).BeginInit(); this.SuspendLayout(); // // launchButton // + this.launchButton.BackColor = System.Drawing.SystemColors.GradientActiveCaption; + this.launchButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.launchButton.Location = new System.Drawing.Point(246, 224); this.launchButton.Name = "launchButton"; - this.launchButton.Size = new System.Drawing.Size(111, 32); + this.launchButton.Size = new System.Drawing.Size(111, 31); this.launchButton.TabIndex = 1; this.launchButton.Text = "Launch Game"; - this.launchButton.UseVisualStyleBackColor = true; + this.launchButton.UseVisualStyleBackColor = false; this.launchButton.Click += new System.EventHandler(this.launchButton_Click); // // fullscreenCheckBox @@ -93,9 +100,9 @@ // // setFolderButton // - this.setFolderButton.Location = new System.Drawing.Point(129, 224); + this.setFolderButton.Location = new System.Drawing.Point(96, 198); this.setFolderButton.Name = "setFolderButton"; - this.setFolderButton.Size = new System.Drawing.Size(111, 32); + this.setFolderButton.Size = new System.Drawing.Size(95, 21); this.setFolderButton.TabIndex = 6; this.setFolderButton.Text = "Set Game Folder"; this.setFolderButton.UseVisualStyleBackColor = true; @@ -120,9 +127,9 @@ // // openFolderButton // - this.openFolderButton.Location = new System.Drawing.Point(12, 224); + this.openFolderButton.Location = new System.Drawing.Point(12, 198); this.openFolderButton.Name = "openFolderButton"; - this.openFolderButton.Size = new System.Drawing.Size(111, 32); + this.openFolderButton.Size = new System.Drawing.Size(78, 21); this.openFolderButton.TabIndex = 9; this.openFolderButton.Text = "Open Folder"; this.openFolderButton.UseVisualStyleBackColor = true; @@ -159,11 +166,52 @@ this.checkforLink.Text = "Check for updates"; this.checkforLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.checkforLink_LinkClicked); // + // button1 + // + this.button1.Location = new System.Drawing.Point(129, 224); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(111, 32); + this.button1.TabIndex = 13; + this.button1.Text = "Choose Skin"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + this.button2.Location = new System.Drawing.Point(12, 224); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(111, 32); + this.button2.TabIndex = 14; + this.button2.Text = "Settings (WIP)"; + this.button2.UseVisualStyleBackColor = true; + // + // button3 + // + this.button3.Location = new System.Drawing.Point(262, 198); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(95, 20); + this.button3.TabIndex = 15; + this.button3.Text = "Servers (WIP)"; + this.button3.UseVisualStyleBackColor = true; + // + // skinPreviewPictureBox + // + this.skinPreviewPictureBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.skinPreviewPictureBox.Location = new System.Drawing.Point(381, 12); + this.skinPreviewPictureBox.Name = "skinPreviewPictureBox"; + this.skinPreviewPictureBox.Size = new System.Drawing.Size(138, 281); + this.skinPreviewPictureBox.TabIndex = 16; + this.skinPreviewPictureBox.TabStop = false; + // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(364, 302); + this.ClientSize = new System.Drawing.Size(531, 302); + this.Controls.Add(this.skinPreviewPictureBox); + this.Controls.Add(this.button3); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); this.Controls.Add(this.checkforLink); this.Controls.Add(this.resetLink); this.Controls.Add(this.openFolderButton); @@ -183,6 +231,7 @@ this.Text = "LegacyConsoleLauncher"; this.Load += new System.EventHandler(this.Form1_Load); ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.skinPreviewPictureBox)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -201,5 +250,9 @@ private System.Windows.Forms.LinkLabel resetLink; private System.Windows.Forms.Label playtimeLabel; private System.Windows.Forms.LinkLabel checkforLink; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.PictureBox skinPreviewPictureBox; } } \ No newline at end of file diff --git a/Form1.Events.cs b/Form1.Events.cs new file mode 100644 index 0000000..a6c0534 --- /dev/null +++ b/Form1.Events.cs @@ -0,0 +1,173 @@ +using System; +using System.IO; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form1 + { + private void Form1_DragEnter(object sender, DragEventArgs e) + { + if (!e.Data.GetDataPresent(DataFormats.FileDrop)) + { + e.Effect = DragDropEffects.None; + return; + } + + string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop); + + if (paths.Length == 0) + { + e.Effect = DragDropEffects.None; + return; + } + + string droppedPath = paths[0]; + + if (File.Exists(droppedPath)) + { + if (Path.GetFileName(droppedPath).Equals("Minecraft.Client.exe", StringComparison.OrdinalIgnoreCase)) + { + e.Effect = DragDropEffects.Copy; + return; + } + } + + if (Directory.Exists(droppedPath)) + { + string possibleExe = Path.Combine(droppedPath, "Minecraft.Client.exe"); + + if (File.Exists(possibleExe)) + { + e.Effect = DragDropEffects.Copy; + return; + } + } + + e.Effect = DragDropEffects.None; + } + + private void Form1_DragDrop(object sender, DragEventArgs e) + { + string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop); + + if (paths.Length == 0) + { + return; + } + + string droppedPath = paths[0]; + + if (File.Exists(droppedPath)) + { + if (Path.GetFileName(droppedPath).Equals("Minecraft.Client.exe", StringComparison.OrdinalIgnoreCase)) + { + SetGamePath(droppedPath); + + MessageBox.Show( + "Minecraft.Client.exe loaded successfully.", + "Game Selected", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + } + + if (Directory.Exists(droppedPath)) + { + string possibleExe = Path.Combine(droppedPath, "Minecraft.Client.exe"); + + if (File.Exists(possibleExe)) + { + SetGamePath(possibleExe); + + MessageBox.Show( + "Game folder loaded successfully.", + "Game Selected", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + return; + } + } + + MessageBox.Show( + "Please drag and drop either Minecraft.Client.exe or the folder containing it.", + "Invalid Drop", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + } + + private void resetLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + DialogResult result = MessageBox.Show( + "This will reset the saved accounts and game path. Continue?", + "Reset Launcher Settings", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question + ); + + if (result != DialogResult.Yes) + { + return; + } + + if (File.Exists(accountsFile)) + { + File.Delete(accountsFile); + } + + if (File.Exists(gamePathFile)) + { + File.Delete(gamePathFile); + } + + if (File.Exists(releaseInfoFile)) + { + File.Delete(releaseInfoFile); + } + + usernameComboBox.Items.Clear(); + usernameComboBox.Text = string.Empty; + playtimeData.Clear(); + + exePath = Path.Combine(Application.StartupPath, "Minecraft.Client.exe"); + fullscreenCheckBox.Checked = false; + + AutoDetectGame(); + UpdateGamePathDisplay(); + UpdatePlaytimeLabel(); + + MessageBox.Show( + "Launcher settings have been reset.", + "Done", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + + private void usernameComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + UpdatePlaytimeLabel(); + UpdateSkinPreview(); + } + + private void fullscreenCheckBox_CheckedChanged(object sender, EventArgs e) + { + } + + private void usernameLabel_Click(object sender, EventArgs e) + { + } + + private void logoPictureBox_Click(object sender, EventArgs e) + { + } + + private void gamePathLabel_Click(object sender, EventArgs e) + { + } + } +} \ No newline at end of file diff --git a/Form1.Game.cs b/Form1.Game.cs new file mode 100644 index 0000000..b655ca6 --- /dev/null +++ b/Form1.Game.cs @@ -0,0 +1,281 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form1 + { + private void LoadSavedGamePath() + { + if (!File.Exists(gamePathFile)) + { + return; + } + + string savedPath = File.ReadAllText(gamePathFile).Trim(); + + if (File.Exists(savedPath)) + { + exePath = savedPath; + } + } + + private void AutoDetectGame() + { + if (File.Exists(exePath)) + { + return; + } + + string[] possiblePaths = + { + Path.Combine(Application.StartupPath, "Minecraft.Client.exe"), + Path.Combine(Application.StartupPath, "build", "Minecraft.Client.exe"), + Path.Combine(Application.StartupPath, "bin", "Minecraft.Client.exe"), + Path.Combine(Application.StartupPath, "game", "Minecraft.Client.exe"), + Path.Combine(gameInstallDir, "Minecraft.Client.exe") + }; + + foreach (string path in possiblePaths) + { + if (File.Exists(path)) + { + exePath = path; + File.WriteAllText(gamePathFile, exePath); + break; + } + } + } + + private void UpdateGamePathDisplay() + { + if (File.Exists(exePath)) + { + gamePathTextBox.Text = exePath; + } + else + { + gamePathTextBox.Text = "Not set"; + } + + bool gameFound = File.Exists(exePath); + openFolderButton.Enabled = gameFound; + launchButton.Enabled = gameFound; + } + + private void SetGamePath(string newPath) + { + exePath = newPath; + File.WriteAllText(gamePathFile, exePath); + UpdateGamePathDisplay(); + LoadFullscreenSetting(); + } + + private string FindGameExe(string rootFolder) + { + if (!Directory.Exists(rootFolder)) + { + return string.Empty; + } + + string[] files = Directory.GetFiles(rootFolder, "Minecraft.Client.exe", SearchOption.AllDirectories); + return files.Length > 0 ? files[0] : string.Empty; + } + + private void LoadFullscreenSetting() + { + if (!File.Exists(exePath)) + { + return; + } + + string gameFolder = Path.GetDirectoryName(exePath); + string optionsPath = Path.Combine(gameFolder, "options.txt"); + + if (!File.Exists(optionsPath)) + { + return; + } + + string[] lines = File.ReadAllLines(optionsPath); + + foreach (string line in lines) + { + if (line.StartsWith("fullscreen=")) + { + fullscreenCheckBox.Checked = line.Trim() == "fullscreen=1"; + break; + } + } + } + + private void SaveFullscreenSetting() + { + if (!File.Exists(exePath)) + { + return; + } + + string gameFolder = Path.GetDirectoryName(exePath); + string optionsPath = Path.Combine(gameFolder, "options.txt"); + + if (!File.Exists(optionsPath)) + { + return; + } + + string[] lines = File.ReadAllLines(optionsPath); + bool foundFullscreen = false; + + for (int i = 0; i < lines.Length; i++) + { + if (lines[i].StartsWith("fullscreen=")) + { + lines[i] = "fullscreen=" + (fullscreenCheckBox.Checked ? "1" : "0"); + foundFullscreen = true; + break; + } + } + + if (!foundFullscreen) + { + var updatedLines = new System.Collections.Generic.List(lines); + updatedLines.Add("fullscreen=" + (fullscreenCheckBox.Checked ? "1" : "0")); + File.WriteAllLines(optionsPath, updatedLines.ToArray()); + return; + } + + File.WriteAllLines(optionsPath, lines); + } + + private void openFolderButton_Click(object sender, EventArgs e) + { + if (!File.Exists(exePath)) + { + MessageBox.Show( + "Minecraft.Client.exe was not found.", + "Game Not Found", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + return; + } + + string gameFolder = Path.GetDirectoryName(exePath); + Process.Start("explorer.exe", gameFolder); + } + + private void setFolderButton_Click(object sender, EventArgs e) + { + using (FolderBrowserDialog dialog = new FolderBrowserDialog()) + { + dialog.Description = "Select the folder containing Minecraft.Client.exe"; + + if (dialog.ShowDialog() == DialogResult.OK) + { + string selectedFolder = dialog.SelectedPath; + string possibleExe = Path.Combine(selectedFolder, "Minecraft.Client.exe"); + + if (File.Exists(possibleExe)) + { + SetGamePath(possibleExe); + + MessageBox.Show( + "Game folder set successfully.", + "Success", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + else + { + MessageBox.Show( + "Minecraft.Client.exe was not found in that folder.", + "Game Not Found", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + } + } + } + } + + private void launchButton_Click(object sender, EventArgs e) + { + if (!File.Exists(exePath)) + { + MessageBox.Show( + "Minecraft.Client.exe was not found.", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + return; + } + + string username = usernameComboBox.Text.Trim(); + + AddAccount(username); + File.WriteAllText(gamePathFile, exePath); + SaveFullscreenSetting(); + ApplySkinForAccount(username); + + string args = string.Empty; + + if (!string.IsNullOrWhiteSpace(username)) + { + args += "-name \"" + username + "\" "; + } + + gameProcess = Process.Start(new ProcessStartInfo + { + FileName = exePath, + Arguments = args.Trim(), + WorkingDirectory = Path.GetDirectoryName(exePath), + UseShellExecute = true + }); + + if (gameProcess == null) + { + MessageBox.Show( + "Failed to start the game.", + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + return; + } + + sessionStart = DateTime.Now; + gameProcess.EnableRaisingEvents = true; + gameProcess.Exited += GameProcess_Exited; + + Hide(); + } + + private void GameProcess_Exited(object sender, EventArgs e) + { + int sessionSeconds = (int)(DateTime.Now - sessionStart).TotalSeconds; + + Invoke((MethodInvoker)delegate + { + string username = usernameComboBox.Text.Trim(); + + if (!string.IsNullOrWhiteSpace(username)) + { + if (!playtimeData.ContainsKey(username)) + { + playtimeData[username] = 0; + } + + playtimeData[username] += sessionSeconds; + SaveAccounts(); + } + + Application.Exit(); + }); + } + } +} \ No newline at end of file diff --git a/Form1.SkinPreview.cs b/Form1.SkinPreview.cs new file mode 100644 index 0000000..40a1137 --- /dev/null +++ b/Form1.SkinPreview.cs @@ -0,0 +1,156 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form1 + { + private void InitializeSkinPreview() + { + if (skinPreviewPictureBox == null) + { + return; + } + + skinPreviewPictureBox.SizeMode = PictureBoxSizeMode.CenterImage; + UpdateSkinPreview(); + } + + private void UpdateSkinPreview() + { + if (skinPreviewPictureBox == null) + { + return; + } + + string username = usernameComboBox.Text.Trim(); + + if (string.IsNullOrWhiteSpace(username)) + { + ClearSkinPreview(); + return; + } + + string skinPath = GetAccountSkinPath(username); + + if (!File.Exists(skinPath)) + { + ClearSkinPreview(); + return; + } + + try + { + using (Bitmap skin = new Bitmap(skinPath)) + { + Bitmap preview = CreateSkinPreviewBitmap(skin); + ReplaceSkinPreviewImage(preview); + } + } + catch + { + ClearSkinPreview(); + } + } + + private void ClearSkinPreview() + { + if (skinPreviewPictureBox.Image != null) + { + skinPreviewPictureBox.Image.Dispose(); + skinPreviewPictureBox.Image = null; + } + } + + private void ReplaceSkinPreviewImage(Image newImage) + { + if (skinPreviewPictureBox.Image != null) + { + skinPreviewPictureBox.Image.Dispose(); + } + + skinPreviewPictureBox.Image = newImage; + } + + private Bitmap CreateSkinPreviewBitmap(Bitmap skin) + { + // base paper doll canvas + Bitmap preview = new Bitmap(16, 32, PixelFormat.Format32bppArgb); + + using (Graphics g = Graphics.FromImage(preview)) + { + g.Clear(Color.Transparent); + g.InterpolationMode = InterpolationMode.NearestNeighbor; + g.PixelOffsetMode = PixelOffsetMode.Half; + g.SmoothingMode = SmoothingMode.None; + + // Head front: source (8,8) size 8x8 + g.DrawImage( + skin, + new Rectangle(4, 0, 8, 8), + new Rectangle(8, 8, 8, 8), + GraphicsUnit.Pixel + ); + + // Body front: source (20,20) size 8x12 + g.DrawImage( + skin, + new Rectangle(4, 8, 8, 12), + new Rectangle(20, 20, 8, 12), + GraphicsUnit.Pixel + ); + + // Right arm front: source (44,20) size 4x12 + g.DrawImage( + skin, + new Rectangle(0, 8, 4, 12), + new Rectangle(44, 20, 4, 12), + GraphicsUnit.Pixel + ); + + // Left arm front (legacy format reuses arm texture) + g.DrawImage( + skin, + new Rectangle(12, 8, 4, 12), + new Rectangle(44, 20, 4, 12), + GraphicsUnit.Pixel + ); + + // Right leg front: source (4,20) size 4x12 + g.DrawImage( + skin, + new Rectangle(4, 20, 4, 12), + new Rectangle(4, 20, 4, 12), + GraphicsUnit.Pixel + ); + + // Left leg front (legacy format reuses leg texture) + g.DrawImage( + skin, + new Rectangle(8, 20, 4, 12), + new Rectangle(4, 20, 4, 12), + GraphicsUnit.Pixel + ); + } + + // upscale for cleaner display + Bitmap scaled = new Bitmap(96, 192, PixelFormat.Format32bppArgb); + + using (Graphics g = Graphics.FromImage(scaled)) + { + g.Clear(Color.Transparent); + g.InterpolationMode = InterpolationMode.NearestNeighbor; + g.PixelOffsetMode = PixelOffsetMode.Half; + g.SmoothingMode = SmoothingMode.None; + g.DrawImage(preview, new Rectangle(0, 0, scaled.Width, scaled.Height)); + } + + preview.Dispose(); + return scaled; + } + } +} \ No newline at end of file diff --git a/Form1.Skins.cs b/Form1.Skins.cs new file mode 100644 index 0000000..294fcb2 --- /dev/null +++ b/Form1.Skins.cs @@ -0,0 +1,235 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form1 + { + private string GetSkinFolder() + { + return skinsDir; + } + + private string GetAccountSkinPath(string username) + { + if (string.IsNullOrWhiteSpace(username)) + { + return string.Empty; + } + + foreach (char c in Path.GetInvalidFileNameChars()) + { + username = username.Replace(c, '_'); + } + + return Path.Combine(GetSkinFolder(), username + ".png"); + } + + private string GetGameRootFolder() + { + if (!File.Exists(exePath)) + { + return string.Empty; + } + + return Path.GetDirectoryName(exePath); + } + + private string GetGameSkinFolder() + { + string gameRoot = GetGameRootFolder(); + + if (string.IsNullOrWhiteSpace(gameRoot)) + { + return string.Empty; + } + + return Path.Combine(gameRoot, "Common", "res", "mob"); + } + + private string GetGameSkinPath() + { + string skinFolder = GetGameSkinFolder(); + + if (string.IsNullOrWhiteSpace(skinFolder)) + { + return string.Empty; + } + + return Path.Combine(skinFolder, "char.png"); + } + + private string GetOriginalSkinBackupPath() + { + string skinFolder = GetGameSkinFolder(); + + if (string.IsNullOrWhiteSpace(skinFolder)) + { + return string.Empty; + } + + return Path.Combine(skinFolder, "char_original_backup.png"); + } + + private void EnsureOriginalSkinBackup() + { + string gameSkinPath = GetGameSkinPath(); + string backupPath = GetOriginalSkinBackupPath(); + + if (string.IsNullOrWhiteSpace(gameSkinPath) || string.IsNullOrWhiteSpace(backupPath)) + { + return; + } + + if (!File.Exists(gameSkinPath)) + { + return; + } + + if (!File.Exists(backupPath)) + { + Directory.CreateDirectory(Path.GetDirectoryName(backupPath)); + File.Copy(gameSkinPath, backupPath); + } + } + + private Bitmap ConvertSkinToLegacyFormat(Bitmap original) + { + if (original.Width != 64 || (original.Height != 32 && original.Height != 64)) + { + return null; + } + + if (original.Height == 32) + { + return new Bitmap(original); + } + + Bitmap converted = new Bitmap(64, 32, PixelFormat.Format32bppArgb); + + using (Graphics g = Graphics.FromImage(converted)) + { + g.Clear(Color.Transparent); + g.DrawImage( + original, + new Rectangle(0, 0, 64, 32), + new Rectangle(0, 0, 64, 32), + GraphicsUnit.Pixel + ); + } + + return converted; + } + + private void SaveSkinForCurrentAccount() + { + string username = usernameComboBox.Text.Trim(); + + if (string.IsNullOrWhiteSpace(username)) + { + MessageBox.Show( + "Please enter or select an account first.", + "Skin Manager", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + UpdateSkinPreview(); + return; + } + + using (OpenFileDialog dialog = new OpenFileDialog()) + { + dialog.Filter = "PNG Files (*.png)|*.png"; + dialog.Title = "Select a Minecraft skin"; + + if (dialog.ShowDialog() != DialogResult.OK) + { + return; + } + + using (Bitmap original = new Bitmap(dialog.FileName)) + using (Bitmap converted = ConvertSkinToLegacyFormat(original)) + { + if (converted == null) + { + MessageBox.Show( + "Invalid skin format. Only 64x32 and 64x64 PNG skins are supported.", + "Invalid Skin", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + return; + } + + Directory.CreateDirectory(GetSkinFolder()); + string accountSkinPath = GetAccountSkinPath(username); + converted.Save(accountSkinPath, ImageFormat.Png); + + string formatMessage = original.Height == 64 + ? "Skin saved for this account.\n\n64x64 skin was converted to 64x32." + : "Skin saved for this account."; + + MessageBox.Show( + formatMessage, + "Skin Manager", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + } + } + + private void ApplySkinForAccount(string username) + { + if (string.IsNullOrWhiteSpace(username)) + { + RestoreOriginalSkin(); + return; + } + + string accountSkinPath = GetAccountSkinPath(username); + string gameSkinPath = GetGameSkinPath(); + + if (string.IsNullOrWhiteSpace(gameSkinPath)) + { + return; + } + + if (!File.Exists(accountSkinPath)) + { + RestoreOriginalSkin(); + return; + } + + Directory.CreateDirectory(Path.GetDirectoryName(gameSkinPath)); + EnsureOriginalSkinBackup(); + File.Copy(accountSkinPath, gameSkinPath, true); + } + + private void RestoreOriginalSkin() + { + string backupPath = GetOriginalSkinBackupPath(); + string gameSkinPath = GetGameSkinPath(); + + if (string.IsNullOrWhiteSpace(backupPath) || string.IsNullOrWhiteSpace(gameSkinPath)) + { + return; + } + + if (!File.Exists(backupPath)) + { + return; + } + + File.Copy(backupPath, gameSkinPath, true); + } + + private void button1_Click(object sender, EventArgs e) + { + SaveSkinForCurrentAccount(); + } + } +} \ No newline at end of file diff --git a/Form1.Updates.cs b/Form1.Updates.cs new file mode 100644 index 0000000..3b3826f --- /dev/null +++ b/Form1.Updates.cs @@ -0,0 +1,326 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form1 + { + private async Task DownloadStringWithUserAgentAsync(string url) + { + using (WebClient client = new WebClient()) + { + client.Headers.Add("User-Agent", "LegacyConsoleLauncher"); + return await client.DownloadStringTaskAsync(url); + } + } + + private async Task DownloadFileWithProgressAsync(string url, string outputPath, string statusText) + { + ShowProgressForm(statusText); + + using (WebClient client = new WebClient()) + { + client.Headers.Add("User-Agent", "LegacyConsoleLauncher"); + + client.DownloadProgressChanged += (s, e) => + { + if (progressForm != null && !progressForm.IsDisposed) + { + progressForm.SetStatus(statusText + " " + e.ProgressPercentage + "%"); + progressForm.SetProgress(e.ProgressPercentage); + } + }; + + await client.DownloadFileTaskAsync(new Uri(url), outputPath); + } + + if (progressForm != null && !progressForm.IsDisposed) + { + progressForm.SetProgress(100); + } + } + + private void ShowProgressForm(string status) + { + if (progressForm == null || progressForm.IsDisposed) + { + progressForm = new Form2(); + } + + progressForm.Show(); + progressForm.BringToFront(); + progressForm.SetStatus(status); + progressForm.SetProgress(0); + } + + private void CloseProgressForm() + { + if (progressForm != null && !progressForm.IsDisposed) + { + progressForm.Close(); + } + } + + private async Task GetNightlyCommitAsync() + { + string html = await DownloadStringWithUserAgentAsync(nightlyReleaseUrl); + + Match match = Regex.Match( + html, + @"\b[0-9a-f]{7,40}\b", + RegexOptions.IgnoreCase + ); + + return match.Success ? match.Value : string.Empty; + } + + private string GetInstalledCommit() + { + if (!File.Exists(releaseInfoFile)) + { + return string.Empty; + } + + foreach (string line in File.ReadAllLines(releaseInfoFile)) + { + if (line.StartsWith("commit=")) + { + return line.Substring("commit=".Length).Trim(); + } + } + + return string.Empty; + } + + private void SaveInstalledCommit(string commit) + { + if (string.IsNullOrWhiteSpace(commit)) + { + return; + } + + File.WriteAllText(releaseInfoFile, "commit=" + commit); + } + + private async Task InstallGameAsync() + { + string zipPath = Path.Combine(Application.StartupPath, "LCEWindows64.zip"); + string tempExtractDir = Path.Combine(Application.StartupPath, "Game_Temp"); + + try + { + await DownloadFileWithProgressAsync(nightlyZipUrl, zipPath, "Installing..."); + ShowProgressForm("Extracting..."); + + if (Directory.Exists(tempExtractDir)) + { + Directory.Delete(tempExtractDir, true); + } + + Directory.CreateDirectory(tempExtractDir); + ZipFile.ExtractToDirectory(zipPath, tempExtractDir); + + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + if (Directory.Exists(gameInstallDir)) + { + Directory.Delete(gameInstallDir, true); + } + + Directory.Move(tempExtractDir, gameInstallDir); + + string detectedExe = FindGameExe(gameInstallDir); + + if (string.IsNullOrWhiteSpace(detectedExe) || !File.Exists(detectedExe)) + { + throw new FileNotFoundException("Minecraft.Client.exe could not be found after installation."); + } + + SetGamePath(detectedExe); + + string latestCommit = await GetNightlyCommitAsync(); + SaveInstalledCommit(latestCommit); + + CloseProgressForm(); + + MessageBox.Show( + "Game installed successfully.", + "Install Complete", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + catch + { + CloseProgressForm(); + + if (Directory.Exists(tempExtractDir)) + { + Directory.Delete(tempExtractDir, true); + } + + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + throw; + } + } + + private async Task UpdateGameExeAsync() + { + if (!File.Exists(exePath)) + { + throw new FileNotFoundException("Game executable was not found."); + } + + string tempExePath = Path.Combine(Application.StartupPath, "Minecraft.Client.new.exe"); + + try + { + await DownloadFileWithProgressAsync(nightlyExeUrl, tempExePath, "Updating..."); + File.Copy(tempExePath, exePath, true); + + if (File.Exists(tempExePath)) + { + File.Delete(tempExePath); + } + + string latestCommit = await GetNightlyCommitAsync(); + SaveInstalledCommit(latestCommit); + + CloseProgressForm(); + + MessageBox.Show( + "Game updated successfully.", + "Update Complete", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + catch + { + CloseProgressForm(); + + if (File.Exists(tempExePath)) + { + File.Delete(tempExePath); + } + + throw; + } + } + + private async Task CheckForUpdatesOnStartupAsync() + { + checkforLink.Visible = false; + + try + { + string latestCommit = await GetNightlyCommitAsync(); + string installedCommit = GetInstalledCommit(); + + if (string.IsNullOrWhiteSpace(latestCommit)) + { + return; + } + + if (!File.Exists(exePath)) + { + DialogResult installResult = MessageBox.Show( + "Game not installed.\n\nDo you want to download and install it now?", + "Install Game", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question + ); + + if (installResult == DialogResult.Yes) + { + await InstallGameAsync(); + } + + checkforLink.Visible = true; + return; + } + + if (!string.Equals(latestCommit, installedCommit, StringComparison.OrdinalIgnoreCase)) + { + checkforLink.Visible = true; + + DialogResult updateResult = MessageBox.Show( + "A new nightly build is available.\n\nDo you want to update now?", + "Update Available", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information + ); + + if (updateResult == DialogResult.Yes) + { + await UpdateGameExeAsync(); + checkforLink.Visible = false; + } + } + } + catch (Exception ex) + { + checkforLink.Visible = true; + + MessageBox.Show( + "Failed to check for updates.\n\n" + ex.Message, + "Update Check Error", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + } + } + + private async void checkforLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + try + { + string latestCommit = await GetNightlyCommitAsync(); + string installedCommit = GetInstalledCommit(); + + if (!File.Exists(exePath)) + { + await InstallGameAsync(); + checkforLink.Visible = false; + return; + } + + if (!string.Equals(latestCommit, installedCommit, StringComparison.OrdinalIgnoreCase)) + { + await UpdateGameExeAsync(); + checkforLink.Visible = false; + } + else + { + MessageBox.Show( + "You are already using the latest nightly build.", + "No Updates", + MessageBoxButtons.OK, + MessageBoxIcon.Information + ); + } + } + catch (Exception ex) + { + MessageBox.Show( + "Failed to check for updates.\n\n" + ex.Message, + "Error", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + } + } + } +} \ No newline at end of file diff --git a/Form1.cs b/Form1.cs index ebe54cb..47ebfc5 100644 --- a/Form1.cs +++ b/Form1.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.IO; -using System.IO.Compression; -using System.Net; -using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; @@ -16,6 +14,7 @@ namespace LegacyConsoleLauncher private readonly string gamePathFile = Path.Combine(Application.StartupPath, "gamepath.txt"); private readonly string releaseInfoFile = Path.Combine(Application.StartupPath, "releaseinfo.txt"); private readonly string gameInstallDir = Path.Combine(Application.StartupPath, "Game"); + private readonly string skinsDir = Path.Combine(Application.StartupPath, "skins"); private string exePath = Path.Combine(Application.StartupPath, "Minecraft.Client.exe"); @@ -38,6 +37,7 @@ namespace LegacyConsoleLauncher openFolderButton.Text = "Open Folder"; launchButton.Text = "Launch Game"; setFolderButton.Text = "Set Game Folder"; + button1.Text = "Choose Skin"; fullscreenCheckBox.Text = "Fullscreen"; usernameLabel.Text = "Username:"; @@ -47,12 +47,15 @@ namespace LegacyConsoleLauncher AcceptButton = launchButton; checkforLink.Visible = false; + Directory.CreateDirectory(skinsDir); + LoadAccounts(); LoadSavedGamePath(); AutoDetectGame(); UpdateGamePathDisplay(); LoadFullscreenSetting(); UpdatePlaytimeLabel(); + InitializeSkinPreview(); AllowDrop = true; DragEnter += Form1_DragEnter; @@ -60,851 +63,5 @@ namespace LegacyConsoleLauncher await CheckForUpdatesOnStartupAsync(); } - - private void LoadSavedGamePath() - { - if (!File.Exists(gamePathFile)) - { - return; - } - - string savedPath = File.ReadAllText(gamePathFile).Trim(); - - if (File.Exists(savedPath)) - { - exePath = savedPath; - } - } - - private void LoadAccounts() - { - usernameComboBox.Items.Clear(); - playtimeData.Clear(); - - if (!File.Exists(accountsFile)) - { - return; - } - - string[] lines = File.ReadAllLines(accountsFile); - - foreach (string line in lines) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - string[] parts = line.Split('|'); - string username = parts[0].Trim(); - - if (string.IsNullOrWhiteSpace(username)) - { - continue; - } - - int seconds = 0; - - if (parts.Length > 1) - { - int.TryParse(parts[1], out seconds); - } - - if (!playtimeData.ContainsKey(username)) - { - playtimeData[username] = seconds; - usernameComboBox.Items.Add(username); - } - } - - if (usernameComboBox.Items.Count > 0) - { - usernameComboBox.SelectedIndex = 0; - } - } - - private void SaveAccounts() - { - List lines = new List(); - - foreach (var entry in playtimeData) - { - lines.Add(entry.Key + "|" + entry.Value); - } - - File.WriteAllLines(accountsFile, lines); - } - - private void AddAccount(string username) - { - if (string.IsNullOrWhiteSpace(username)) - { - return; - } - - if (!playtimeData.ContainsKey(username)) - { - playtimeData[username] = 0; - usernameComboBox.Items.Add(username); - } - - usernameComboBox.SelectedItem = username; - SaveAccounts(); - } - - private void AutoDetectGame() - { - if (File.Exists(exePath)) - { - return; - } - - string[] possiblePaths = - { - Path.Combine(Application.StartupPath, "Minecraft.Client.exe"), - Path.Combine(Application.StartupPath, "build", "Minecraft.Client.exe"), - Path.Combine(Application.StartupPath, "bin", "Minecraft.Client.exe"), - Path.Combine(Application.StartupPath, "game", "Minecraft.Client.exe"), - Path.Combine(gameInstallDir, "Minecraft.Client.exe") - }; - - foreach (string path in possiblePaths) - { - if (File.Exists(path)) - { - exePath = path; - File.WriteAllText(gamePathFile, exePath); - break; - } - } - } - - private void UpdateGamePathDisplay() - { - if (File.Exists(exePath)) - { - gamePathTextBox.Text = exePath; - } - else - { - gamePathTextBox.Text = "Not set"; - } - - bool gameFound = File.Exists(exePath); - openFolderButton.Enabled = gameFound; - launchButton.Enabled = gameFound; - } - - private void SetGamePath(string newPath) - { - exePath = newPath; - File.WriteAllText(gamePathFile, exePath); - UpdateGamePathDisplay(); - LoadFullscreenSetting(); - } - - private string FormatPlaytime(int totalSeconds) - { - TimeSpan time = TimeSpan.FromSeconds(totalSeconds); - return ((int)time.TotalHours) + "h " + time.Minutes + "m"; - } - - private void UpdatePlaytimeLabel() - { - string username = usernameComboBox.Text.Trim(); - - if (string.IsNullOrWhiteSpace(username)) - { - playtimeLabel.Text = "Playtime: 0h 0m"; - return; - } - - if (!playtimeData.ContainsKey(username)) - { - playtimeData[username] = 0; - } - - playtimeLabel.Text = "Playtime: " + FormatPlaytime(playtimeData[username]); - } - - private void LoadFullscreenSetting() - { - if (!File.Exists(exePath)) - { - return; - } - - string gameFolder = Path.GetDirectoryName(exePath); - string optionsPath = Path.Combine(gameFolder, "options.txt"); - - if (!File.Exists(optionsPath)) - { - return; - } - - string[] lines = File.ReadAllLines(optionsPath); - - foreach (string line in lines) - { - if (line.StartsWith("fullscreen=")) - { - fullscreenCheckBox.Checked = line.Trim() == "fullscreen=1"; - break; - } - } - } - - private void SaveFullscreenSetting() - { - if (!File.Exists(exePath)) - { - return; - } - - string gameFolder = Path.GetDirectoryName(exePath); - string optionsPath = Path.Combine(gameFolder, "options.txt"); - - if (!File.Exists(optionsPath)) - { - return; - } - - string[] lines = File.ReadAllLines(optionsPath); - bool foundFullscreen = false; - - for (int i = 0; i < lines.Length; i++) - { - if (lines[i].StartsWith("fullscreen=")) - { - lines[i] = "fullscreen=" + (fullscreenCheckBox.Checked ? "1" : "0"); - foundFullscreen = true; - break; - } - } - - if (!foundFullscreen) - { - List updatedLines = new List(lines); - updatedLines.Add("fullscreen=" + (fullscreenCheckBox.Checked ? "1" : "0")); - File.WriteAllLines(optionsPath, updatedLines.ToArray()); - return; - } - - File.WriteAllLines(optionsPath, lines); - } - - private string FindGameExe(string rootFolder) - { - if (!Directory.Exists(rootFolder)) - { - return string.Empty; - } - - string[] files = Directory.GetFiles(rootFolder, "Minecraft.Client.exe", SearchOption.AllDirectories); - return files.Length > 0 ? files[0] : string.Empty; - } - - private async Task DownloadStringWithUserAgentAsync(string url) - { - using (WebClient client = new WebClient()) - { - client.Headers.Add("User-Agent", "LegacyConsoleLauncher"); - return await client.DownloadStringTaskAsync(url); - } - } - - private async Task DownloadFileWithProgressAsync(string url, string outputPath, string statusText) - { - ShowProgressForm(statusText); - - using (WebClient client = new WebClient()) - { - client.Headers.Add("User-Agent", "LegacyConsoleLauncher"); - - client.DownloadProgressChanged += (s, e) => - { - if (progressForm != null && !progressForm.IsDisposed) - { - progressForm.SetStatus(statusText + " " + e.ProgressPercentage + "%"); - progressForm.SetProgress(e.ProgressPercentage); - } - }; - - await client.DownloadFileTaskAsync(new Uri(url), outputPath); - } - - if (progressForm != null && !progressForm.IsDisposed) - { - progressForm.SetProgress(100); - } - } - - private void ShowProgressForm(string status) - { - if (progressForm == null || progressForm.IsDisposed) - { - progressForm = new Form2(); - } - - progressForm.Show(); - progressForm.BringToFront(); - progressForm.SetStatus(status); - progressForm.SetProgress(0); - } - - private void CloseProgressForm() - { - if (progressForm != null && !progressForm.IsDisposed) - { - progressForm.Close(); - } - } - - private async Task GetNightlyCommitAsync() - { - string html = await DownloadStringWithUserAgentAsync(nightlyReleaseUrl); - - Match match = Regex.Match( - html, - @"\b[0-9a-f]{7,40}\b", - RegexOptions.IgnoreCase - ); - - return match.Success ? match.Value : string.Empty; - } - - private string GetInstalledCommit() - { - if (!File.Exists(releaseInfoFile)) - { - return string.Empty; - } - - foreach (string line in File.ReadAllLines(releaseInfoFile)) - { - if (line.StartsWith("commit=")) - { - return line.Substring("commit=".Length).Trim(); - } - } - - return string.Empty; - } - - private void SaveInstalledCommit(string commit) - { - if (string.IsNullOrWhiteSpace(commit)) - { - return; - } - - File.WriteAllText(releaseInfoFile, "commit=" + commit); - } - - private async Task InstallGameAsync() - { - string zipPath = Path.Combine(Application.StartupPath, "LCEWindows64.zip"); - string tempExtractDir = Path.Combine(Application.StartupPath, "Game_Temp"); - - try - { - await DownloadFileWithProgressAsync(nightlyZipUrl, zipPath, "Installing..."); - ShowProgressForm("Extracting..."); - - if (Directory.Exists(tempExtractDir)) - { - Directory.Delete(tempExtractDir, true); - } - - Directory.CreateDirectory(tempExtractDir); - ZipFile.ExtractToDirectory(zipPath, tempExtractDir); - - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - if (Directory.Exists(gameInstallDir)) - { - Directory.Delete(gameInstallDir, true); - } - - Directory.Move(tempExtractDir, gameInstallDir); - - string detectedExe = FindGameExe(gameInstallDir); - - if (string.IsNullOrWhiteSpace(detectedExe) || !File.Exists(detectedExe)) - { - throw new FileNotFoundException("Minecraft.Client.exe could not be found after installation."); - } - - SetGamePath(detectedExe); - - string latestCommit = await GetNightlyCommitAsync(); - SaveInstalledCommit(latestCommit); - - CloseProgressForm(); - - MessageBox.Show( - "Game installed successfully.", - "Install Complete", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - catch - { - CloseProgressForm(); - - if (Directory.Exists(tempExtractDir)) - { - Directory.Delete(tempExtractDir, true); - } - - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - throw; - } - } - - private async Task UpdateGameExeAsync() - { - if (!File.Exists(exePath)) - { - throw new FileNotFoundException("Game executable was not found."); - } - - string tempExePath = Path.Combine(Application.StartupPath, "Minecraft.Client.new.exe"); - - try - { - await DownloadFileWithProgressAsync(nightlyExeUrl, tempExePath, "Updating..."); - File.Copy(tempExePath, exePath, true); - - if (File.Exists(tempExePath)) - { - File.Delete(tempExePath); - } - - string latestCommit = await GetNightlyCommitAsync(); - SaveInstalledCommit(latestCommit); - - CloseProgressForm(); - - MessageBox.Show( - "Game updated successfully.", - "Update Complete", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - catch - { - CloseProgressForm(); - - if (File.Exists(tempExePath)) - { - File.Delete(tempExePath); - } - - throw; - } - } - - private async Task CheckForUpdatesOnStartupAsync() - { - checkforLink.Visible = false; - - try - { - string latestCommit = await GetNightlyCommitAsync(); - string installedCommit = GetInstalledCommit(); - - if (string.IsNullOrWhiteSpace(latestCommit)) - { - return; - } - - if (!File.Exists(exePath)) - { - DialogResult installResult = MessageBox.Show( - "Game not installed.\n\nDo you want to download and install it now?", - "Install Game", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question - ); - - if (installResult == DialogResult.Yes) - { - await InstallGameAsync(); - } - - checkforLink.Visible = true; - return; - } - - if (!string.Equals(latestCommit, installedCommit, StringComparison.OrdinalIgnoreCase)) - { - checkforLink.Visible = true; - - DialogResult updateResult = MessageBox.Show( - "A new nightly build is available.\n\nDo you want to update now?", - "Update Available", - MessageBoxButtons.YesNo, - MessageBoxIcon.Information - ); - - if (updateResult == DialogResult.Yes) - { - await UpdateGameExeAsync(); - checkforLink.Visible = false; - } - } - } - catch (Exception ex) - { - checkforLink.Visible = true; - - MessageBox.Show( - "Failed to check for updates.\n\n" + ex.Message, - "Update Check Error", - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - } - } - - private void openFolderButton_Click(object sender, EventArgs e) - { - if (!File.Exists(exePath)) - { - MessageBox.Show( - "Minecraft.Client.exe was not found.", - "Game Not Found", - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - return; - } - - string gameFolder = Path.GetDirectoryName(exePath); - Process.Start("explorer.exe", gameFolder); - } - - private void launchButton_Click(object sender, EventArgs e) - { - if (!File.Exists(exePath)) - { - MessageBox.Show( - "Minecraft.Client.exe was not found.", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - return; - } - - string username = usernameComboBox.Text.Trim(); - - AddAccount(username); - File.WriteAllText(gamePathFile, exePath); - SaveFullscreenSetting(); - - string args = string.Empty; - - if (!string.IsNullOrWhiteSpace(username)) - { - args += "-name \"" + username + "\" "; - } - - gameProcess = Process.Start(new ProcessStartInfo - { - FileName = exePath, - Arguments = args.Trim(), - WorkingDirectory = Path.GetDirectoryName(exePath), - UseShellExecute = true - }); - - if (gameProcess == null) - { - MessageBox.Show( - "Failed to start the game.", - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - return; - } - - sessionStart = DateTime.Now; - gameProcess.EnableRaisingEvents = true; - gameProcess.Exited += GameProcess_Exited; - - Hide(); - } - - private void GameProcess_Exited(object sender, EventArgs e) - { - int sessionSeconds = (int)(DateTime.Now - sessionStart).TotalSeconds; - - Invoke((MethodInvoker)delegate - { - string username = usernameComboBox.Text.Trim(); - - if (!string.IsNullOrWhiteSpace(username)) - { - if (!playtimeData.ContainsKey(username)) - { - playtimeData[username] = 0; - } - - playtimeData[username] += sessionSeconds; - SaveAccounts(); - } - - Application.Exit(); - }); - } - - private void setFolderButton_Click(object sender, EventArgs e) - { - using (FolderBrowserDialog dialog = new FolderBrowserDialog()) - { - dialog.Description = "Select the folder containing Minecraft.Client.exe"; - - if (dialog.ShowDialog() == DialogResult.OK) - { - string selectedFolder = dialog.SelectedPath; - string possibleExe = Path.Combine(selectedFolder, "Minecraft.Client.exe"); - - if (File.Exists(possibleExe)) - { - SetGamePath(possibleExe); - - MessageBox.Show( - "Game folder set successfully.", - "Success", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - else - { - MessageBox.Show( - "Minecraft.Client.exe was not found in that folder.", - "Game Not Found", - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - } - } - } - } - - private void Form1_DragEnter(object sender, DragEventArgs e) - { - if (!e.Data.GetDataPresent(DataFormats.FileDrop)) - { - e.Effect = DragDropEffects.None; - return; - } - - string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop); - - if (paths.Length == 0) - { - e.Effect = DragDropEffects.None; - return; - } - - string droppedPath = paths[0]; - - if (File.Exists(droppedPath)) - { - if (Path.GetFileName(droppedPath).Equals("Minecraft.Client.exe", StringComparison.OrdinalIgnoreCase)) - { - e.Effect = DragDropEffects.Copy; - return; - } - } - - if (Directory.Exists(droppedPath)) - { - string possibleExe = Path.Combine(droppedPath, "Minecraft.Client.exe"); - - if (File.Exists(possibleExe)) - { - e.Effect = DragDropEffects.Copy; - return; - } - } - - e.Effect = DragDropEffects.None; - } - - private void Form1_DragDrop(object sender, DragEventArgs e) - { - string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop); - - if (paths.Length == 0) - { - return; - } - - string droppedPath = paths[0]; - - if (File.Exists(droppedPath)) - { - if (Path.GetFileName(droppedPath).Equals("Minecraft.Client.exe", StringComparison.OrdinalIgnoreCase)) - { - SetGamePath(droppedPath); - - MessageBox.Show( - "Minecraft.Client.exe loaded successfully.", - "Game Selected", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - } - - if (Directory.Exists(droppedPath)) - { - string possibleExe = Path.Combine(droppedPath, "Minecraft.Client.exe"); - - if (File.Exists(possibleExe)) - { - SetGamePath(possibleExe); - - MessageBox.Show( - "Game folder loaded successfully.", - "Game Selected", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - return; - } - } - - MessageBox.Show( - "Please drag and drop either Minecraft.Client.exe or the folder containing it.", - "Invalid Drop", - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - } - - private void resetLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - DialogResult result = MessageBox.Show( - "This will reset the saved accounts and game path. Continue?", - "Reset Launcher Settings", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question - ); - - if (result != DialogResult.Yes) - { - return; - } - - if (File.Exists(accountsFile)) - { - File.Delete(accountsFile); - } - - if (File.Exists(gamePathFile)) - { - File.Delete(gamePathFile); - } - - if (File.Exists(releaseInfoFile)) - { - File.Delete(releaseInfoFile); - } - - usernameComboBox.Items.Clear(); - usernameComboBox.Text = string.Empty; - playtimeData.Clear(); - - exePath = Path.Combine(Application.StartupPath, "Minecraft.Client.exe"); - fullscreenCheckBox.Checked = false; - - AutoDetectGame(); - UpdateGamePathDisplay(); - UpdatePlaytimeLabel(); - - MessageBox.Show( - "Launcher settings have been reset.", - "Done", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - - private void usernameComboBox_SelectedIndexChanged(object sender, EventArgs e) - { - UpdatePlaytimeLabel(); - } - - private void fullscreenCheckBox_CheckedChanged(object sender, EventArgs e) - { - } - - private void usernameLabel_Click(object sender, EventArgs e) - { - } - - private void logoPictureBox_Click(object sender, EventArgs e) - { - } - - private void gamePathLabel_Click(object sender, EventArgs e) - { - } - - private async void checkforLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - try - { - string latestCommit = await GetNightlyCommitAsync(); - string installedCommit = GetInstalledCommit(); - - if (!File.Exists(exePath)) - { - await InstallGameAsync(); - checkforLink.Visible = false; - return; - } - - if (!string.Equals(latestCommit, installedCommit, StringComparison.OrdinalIgnoreCase)) - { - await UpdateGameExeAsync(); - checkforLink.Visible = false; - } - else - { - MessageBox.Show( - "You are already using the latest nightly build.", - "No Updates", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); - } - } - catch (Exception ex) - { - MessageBox.Show( - "Failed to check for updates.\n\n" + ex.Message, - "Error", - MessageBoxButtons.OK, - MessageBoxIcon.Warning - ); - } - } } } \ No newline at end of file diff --git a/Form3.Designer.cs b/Form3.Designer.cs new file mode 100644 index 0000000..afb8c94 --- /dev/null +++ b/Form3.Designer.cs @@ -0,0 +1,39 @@ +namespace LegacyConsoleLauncher +{ + partial class Form3 + { + /// + /// 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.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form3"; + } + + #endregion + } +} \ No newline at end of file diff --git a/Form3.cs b/Form3.cs new file mode 100644 index 0000000..3a08a21 --- /dev/null +++ b/Form3.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LegacyConsoleLauncher +{ + public partial class Form3 : Form + { + public Form3() + { + InitializeComponent(); + } + } +} diff --git a/LegacyConsoleLauncher.csproj b/LegacyConsoleLauncher.csproj index f835283..dfadf37 100644 --- a/LegacyConsoleLauncher.csproj +++ b/LegacyConsoleLauncher.csproj @@ -36,6 +36,9 @@ icon.ico + + packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + ..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.dll @@ -53,18 +56,42 @@ + + Form + Form Form1.cs + + Form + + + Form + + + Form + + + Form + + + Form + Form Form2.cs + + Form + + + Form3.cs + @@ -82,6 +109,7 @@ True Resources.resx + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/README.md b/README.md index e7ba414..c050ef9 100644 --- a/README.md +++ b/README.md @@ -116,5 +116,49 @@ Stores the location of `Minecraft.Client.exe`. ## License +## Updates + +This file summarizes the update-related changes added to the project (see `Form1.Updates.cs`). + +What was added + +- Automatic nightly-check helpers + - `GetNightlyCommitAsync()` downloads the release page and extracts the commit hash. + - `GetInstalledCommit()` / `SaveInstalledCommit()` read/write the installed commit from the release info file. + +- Download helpers with custom User-Agent and progress + - `DownloadStringWithUserAgentAsync(string url)` uses `WebClient` and sets a `User-Agent` header. + - `DownloadFileWithProgressAsync(string url, string outputPath, string statusText)` shows a progress UI while downloading and reports percentage updates. + +- Install and update flows + - `InstallGameAsync()` downloads the nightly ZIP, extracts it to a temporary folder, moves it into the install folder, detects the game executable and saves the installed commit. + - `UpdateGameExeAsync()` downloads the updated game executable, replaces the existing exe and saves the installed commit. + +- UI integration + - `ShowProgressForm()` / `CloseProgressForm()` manage a `Form2` progress dialog (`progressForm`). + - `CheckForUpdatesOnStartupAsync()` checks for the latest nightly commit on startup and prompts the user to install/update (also toggles `checkforLink` visibility). + - `checkforLink_LinkClicked` is a manual trigger bound to a `LinkLabel` that installs/updates when clicked. + +Error handling and cleanup + +- Both install and update flows perform cleanup of temporary files/directories and close the progress UI on exceptions. Exceptions are re-thrown to let calling UI show user-facing messages. + +Notes for maintainers + +- The update code expects several fields to be present elsewhere in `Form1` (declared in other partial files): + - `nightlyReleaseUrl`, `nightlyZipUrl`, `nightlyExeUrl` + - `releaseInfoFile`, `gameInstallDir`, `exePath` + - `progressForm` (type `Form2`) and `checkforLink` (type `LinkLabel`) + +- The project targets .NET Framework 4.7.2 and uses C# 7.3 language features. + +How to test + +1. Build the project in Visual Studio (target .NET Framework 4.7.2). +2. Run the launcher and confirm the startup update check logic runs (or trigger the `checkforLink` link). +3. Observe the progress dialog while downloads happen and verify that the game installs/updates and `releaseInfoFile` is written. + +If you want, I can expand this README with examples of the `releaseInfoFile` format, how to configure nightly URLs, or include screenshots of the progress dialog. + MIT License © 2026 GatoWare diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..5eaa239 --- /dev/null +++ b/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file