add per-account skins, skin preview and code refactor

- 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
This commit is contained in:
GatoWare
2026-03-09 20:18:13 -03:00
parent 0fdae7f8aa
commit 9153341cc2
13 changed files with 1482 additions and 856 deletions

110
Form1.Accounts.cs Normal file
View File

@@ -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<string> lines = new List<string>();
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]);
}
}
}

67
Form1.Designer.cs generated
View File

@@ -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;
}
}

173
Form1.Events.cs Normal file
View File

@@ -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)
{
}
}
}

281
Form1.Game.cs Normal file
View File

@@ -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<string>(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();
});
}
}
}

156
Form1.SkinPreview.cs Normal file
View File

@@ -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;
}
}
}

235
Form1.Skins.cs Normal file
View File

@@ -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();
}
}
}

326
Form1.Updates.cs Normal file
View File

@@ -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<string> 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<string> 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
);
}
}
}
}

855
Form1.cs
View File

@@ -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<string> lines = new List<string>();
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<string> updatedLines = new List<string>(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<string> 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<string> 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
);
}
}
}
}

39
Form3.Designer.cs generated Normal file
View File

@@ -0,0 +1,39 @@
namespace LegacyConsoleLauncher
{
partial class Form3
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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
}
}

20
Form3.cs Normal file
View File

@@ -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();
}
}
}

View File

@@ -36,6 +36,9 @@
<ApplicationIcon>icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System">
<HintPath>..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.dll</HintPath>
</Reference>
@@ -53,18 +56,42 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.Accounts.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Form1.Events.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Game.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.SkinPreview.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Skins.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Updates.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form2.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form2.Designer.cs">
<DependentUpon>Form2.cs</DependentUpon>
</Compile>
<Compile Include="Form3.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form3.Designer.cs">
<DependentUpon>Form3.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
@@ -82,6 +109,7 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>

View File

@@ -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

4
packages.config Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net472" />
</packages>