A solid Metin2 patcher build is one of the most important pieces of infrastructure when you run your own server: instead of downloading the client by hand and copying files around, players expect the launcher they open to handle updates quietly. A well-designed launcher compares the version on the server with the one on the player's disk, downloads only the files that changed, and starts the game with the right parameters. In this guide we build a simple but robust auto-updating architecture from start to finish.
How a launcher works: the architecture in brief
Patcher logic is really just a synchronization problem. There are three parts:
- A server-side manifest: a file listing the path, size and
hash(for example SHA-256) of every file in the published client. - A file store: an HTTP(S) server or CDN from which the current client files can be downloaded.
- The client app (launcher): it downloads the manifest, compares against local files, pulls the missing/changed ones and runs the game.
The key idea here is content-based comparison. Instead of trusting a date or version number, you verify each file's hash; that way half-downloaded or corrupted files are repaired automatically on the next launch.
Generating the manifest file
First, write a small script that scans the client folder you're about to publish and produces a manifest. Below is a simple PowerShell example; you can set up the same logic in Python or as a CI step:
$root = "C:\dist\metin2-client"
$items = Get-ChildItem $root -Recurse -File | ForEach-Object {
$rel = $_.FullName.Substring($root.Length + 1).Replace('\','/')
[pscustomobject]@{
path = $rel
size = $_.Length
hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower()
}
}
$items | ConvertTo-Json -Depth 3 | Out-File "$root\manifest.json" -Encoding utf8
The resulting manifest.json looks like this:
[
{ "path": "metin2client.exe", "size": 5242880, "hash": "9f2c..." },
{ "path": "pack/root.eix", "size": 184320, "hash": "ab17..." },
{ "path": "pack/root.epk", "size": 9437184, "hash": "5e0d..." }
]
Place the manifest in the same directory as the client files, under a fixed URL (for example https://cdn.myserver.com/client/manifest.json). For every new patch you simply rerun this script and republish the manifest.
The client side: comparison and download
Since you're writing the launcher for Windows, C# (.NET) is a practical choice; HttpClient, SHA256 and WPF/WinForms come built in. The core flow is: download the manifest, compute the hash of each local file, and download the ones that don't match.
using System.Security.Cryptography;
string Sha256(string file)
{
using var stream = File.OpenRead(file);
using var sha = SHA256.Create();
return Convert.ToHexString(sha.ComputeHash(stream)).ToLowerInvariant();
}
async Task SyncAsync(string baseUrl, string localRoot, List<Entry> manifest)
{
using var http = new HttpClient();
foreach (var entry in manifest)
{
var local = Path.Combine(localRoot, entry.path);
bool needs = !File.Exists(local) || Sha256(local) != entry.hash;
if (!needs) continue;
Directory.CreateDirectory(Path.GetDirectoryName(local)!);
var bytes = await http.GetByteArrayAsync($"{baseUrl}/{entry.path}");
await File.WriteAllBytesAsync(local, bytes);
}
}
For large .epk/.eix packages, prefer streaming to disk instead of GetByteArrayAsync so you don't exhaust memory and can feed a progress bar. You show a percentage by ratioing the bytes downloaded per file against the total size.
Launching the game correctly
Once syncing is done, the launcher runs the client. Setting the working directory to the client folder is important, otherwise the game won't find its packages:
var psi = new ProcessStartInfo
{
FileName = Path.Combine(localRoot, "metin2client.exe"),
WorkingDirectory = localRoot,
UseShellExecute = false
};
Process.Start(psi);
If you want, the launcher can pass an argument to hand a session/token to the client; but avoid sending sensitive data in plain text on the command line — use a temporary file or a named pipe instead.
Update and security notes
- Use HTTPS. Serving the manifest and files over plain HTTP opens the door to man-in-the-middle (MITM) tampering.
- Sign the manifest. For a sturdier setup, sign the manifest with a private key and verify it with a public key embedded in the launcher, so a forged manifest can't be injected.
- Self-update. The launcher itself needs updating too. A common approach: download the new version as
launcher.exe.newand swap the file on exit with a tiny helper script. - Retry and repair. If a download is interrupted, the hash won't match on the next launch, so the file is pulled again — repair is a natural by-product of this architecture.
Common mistakes in a Metin2 patcher build
Most problems come not from the architecture but from the details. The most frequent: mixing slashes in relative paths (Windows \ vs URL /), comparing hashes with inconsistent letter casing, and bloating the launcher by loading huge packages fully into memory. Always generate the manifest as the last step of distribution; otherwise a file you just added won't be in the manifest and will never reach players.
Frequently Asked Questions
Which language/technology should I pick?
If you're targeting Windows, C# (.NET) is the lowest-friction path; you ship it as a single-file publish. If you want cross-platform support or a sleeker UI, C++/Qt or Electron also work, but Electron carries a size cost.
Why SHA-256 instead of MD5?
MD5 is discouraged for security because it's collision-prone. While MD5 is fast for plain integrity checks, once you add a security layer like manifest signing it's cleaner to stay consistent with SHA-256.
Should I distribute the whole client or just the diff?
In this architecture only files whose hash changed are downloaded, so you effectively get incremental updates. For the first install, also offering a link to download the full client once makes life easier for players.
Want a solid patcher for your own server? I build the full launcher stack, from manifest generation to self-update, and deliver it with a UI that matches your brand. Let's talk about your project: get in touch with me.