How to set up automatic patching via launcher in MU Online
Build an end-to-end automatic patching flow for the MU Online client — from packaging the changed files to publishing the manifest, with versioning, rollback, and distribution through the launcher.
Distributing an MU Online patch seems trivial until the day you change three files and need to guarantee that hundreds of players, on different machines and connections, have exactly the same bytes you do. That is when an automatic patching flow stops being a luxury and becomes infrastructure. In th
Distributing an MU Online patch seems trivial until the day you change three files and need to guarantee that hundreds of players, on different machines and connections, have exactly the same bytes you do. That is when an automatic patching flow stops being a luxury and becomes infrastructure. In this guide you will build the complete server-side process — change detection, packaging, versioning, publishing, and rollback — that feeds the launcher running on the player's machine.
This is a client tutorial: it covers the files the player downloads and runs, and how the launcher keeps them in sync. We do not cover the game server configuration itself — if you are still setting up the server, start with the guide on how to create an MU Online server. The focus here is the patch pipeline.
What "automatic patching" really means
Many people call it automatic patching just because the launcher downloads files. That is only half of it. Complete automatic patching has two sides:
- Server side (build/deploy): a process that compares the current client folder against the last published version, identifies what changed, generates a manifest with hashes, and uploads the changed files to the distribution server.
- Client side (launcher): the tool that reads the manifest, compares it with what is installed, and downloads only the differences.
The link between the two is the manifest, a JSON file with the list of files and their hashes. When you automate the generation of that manifest, you eliminate the biggest source of bugs on MU servers: the "half-published" patch, with the manifest pointing to a file that was never uploaded or with an outdated hash.
YOU CHANGE FILES
│
▼
[ PATCH SCRIPT ] ── scans, computes hashes, compares with the previous version
│ generates the manifest + isolates the changed files
▼
[ DISTRIBUTION SERVER ] (Apache/Nginx with HTTPS)
│ version.json + /patches/<version>/...
▼
[ PLAYER'S LAUNCHER ] ── downloads only the diff, validates the hash, applies it
│
▼
Updated Main.exe
Prerequisites
| Item | Recommendation | Role in the flow |
|---|---|---|
| Intact base client | Complete MU folder for your season | Source of truth for the files |
| Working launcher | .NET launcher that consumes the manifest | Applies the patch on the player |
| Distribution server | Apache/Nginx + HTTPS, dedicated subdomain | Hosts the manifest and patches |
| PowerShell 5+ | Ships with Windows | Runs the patch script |
| Compression tool | 7-Zip (optional) | Package large patches |
| Version control | Git or a versioned folder | History for rollback |
One conceptual prerequisite: define what is distributable. Not everything in the client folder should go into the patch. Logs, the player's local settings (chosen resolution, volume), and temporary files should stay out. Create an exclusion list from the start.
Step 1 — Structure versioning on the server
Before automating, define the folder structure on the distribution server. I recommend versioning by folder, keeping the history:
/var/www/cdn/
├── version.json ← CURRENT manifest (what the launcher reads)
├── manifests/
│ ├── 3.1.0.json ← manifest history (for rollback)
│ ├── 3.2.0.json
│ └── 3.3.0.json
└── patches/
├── Data/
│ ├── Item.bmd
│ └── Local/Text.bmd
├── Interface/
│ └── loading01.jpg
└── Main.exe
Here we use a scheme where patches/ always holds the latest version of each file (a mirror of the distributable client), and manifests/ keeps the history. The version.json at the root is simply a copy of the current version's manifest. This simplifies the launcher: it downloads any file at patch_base_url + path and always gets the current version.
> The Data/, Interface/, Main.exe structure is a typical Season 6 example. The actual folder layout and which BMD files exist vary by season/client. Adjust the exclusion list and the paths to your distribution.
Step 2 — The automatic patch script
The heart of the system is a script that does it all: scans, computes hashes, compares with the previous version, assembles the manifest, and reports what changed. This PowerShell script is the template:
# publicar-patch.ps1
param(
[Parameter(Mandatory)] [string] $NovaVersao # e.g.: "3.3.0"
)
$clienteDir = "C:\Build\ClienteMU" # distributable client (source)
$saidaDir = "C:\Build\Publicar" # what will be uploaded to the server
$baseUrl = "https://cdn.meuservidor.com/patches/"
$exclusoes = @("*.log", "config.local.ini", "Screenshots\*", "*.tmp")
# --- 1. Scan files, applying exclusions ---
$arquivos = Get-ChildItem $clienteDir -Recurse -File | Where-Object {
$rel = $_.FullName.Substring($clienteDir.Length + 1)
-not ($exclusoes | Where-Object { $rel -like $_ })
}
# --- 2. Compute the hash of each file ---
$lista = foreach ($f in $arquivos) {
$rel = $f.FullName.Substring($clienteDir.Length + 1).Replace('\','/')
$hash = (Get-FileHash $f.FullName -Algorithm SHA256).Hash.ToLower()
[ordered]@{ path = $rel; hash = $hash; size = $f.Length }
}
# --- 3. Compare with the previous manifest (diff) ---
$anterior = @{}
if (Test-Path ".\version.json") {
(Get-Content ".\version.json" -Raw | ConvertFrom-Json).files |
ForEach-Object { $anterior[$_.path] = $_.hash }
}
$alterados = $lista | Where-Object { $anterior[$_.path] -ne $_.hash }
Write-Host "Changed/new files: $($alterados.Count)"
# --- 4. Copy only the changed files to the publish folder ---
if (Test-Path $saidaDir) { Remove-Item $saidaDir -Recurse -Force }
foreach ($a in $alterados) {
$origem = Join-Path $clienteDir ($a.path -replace '/','\')
$destino = Join-Path $saidaDir ($a.path -replace '/','\')
New-Item -ItemType Directory -Force -Path (Split-Path $destino) | Out-Null
Copy-Item $origem $destino
}
# --- 5. Generate the new manifest ---
$manifesto = [ordered]@{
version = $NovaVersao
patch_base_url = $baseUrl
main_exe = "Main.exe"
files = $lista
}
$json = $manifesto | ConvertTo-Json -Depth 4
$json | Out-File ".\version.json" -Encoding utf8
$json | Out-File ".\manifests\$NovaVersao.json" -Encoding utf8
Write-Host "Patch $NovaVersao ready. Upload:"
Write-Host " - contents of $saidaDir -> /var/www/cdn/patches/"
Write-Host " - version.json -> /var/www/cdn/version.json"
What this script guarantees:
- Automatic detection of what changed, by comparing hashes with the previous manifest — no more manually remembering which files you edited.
- Incremental packaging: only the changed files go into the publish folder, reducing the upload.
- Always-consistent manifest: the hashes come from the same scan that generated the list, so they are never out of sync.
- Versioned history in
manifests/, a prerequisite for rollback.
Step 3 — Publishing order (the golden rule)
The number one cause of a broken client during a patch is the wrong upload order. The launcher may fetch the version.json at the exact moment you are uploading the files. If it grabs the new manifest before the files exist, it gets a 404 and fails.
The correct order is always:
- Upload all the patch files to
/patches/first. - Confirm they are accessible (
curl -Ion a few of them). - Only then upload the updated
version.json.
# in your terminal, after running the script:
# 1) files first
rsync -avz C:/Build/Publicar/ usuario@servidor:/var/www/cdn/patches/
# 2) verify
curl -I https://cdn.meuservidor.com/patches/Main.exe # expect 200
# 3) manifest last
scp version.json usuario@servidor:/var/www/cdn/version.json
That way, throughout the whole process players see the old (consistent) manifest until the moment the new one goes live with everything already available.
Step 4 — Configuring the distribution server
The web server needs to serve the files as static content, with two things to watch out for: no cache on the manifest and cache on the patches (which are immutable by hash). Example Nginx configuration:
server {
listen 443 ssl;
server_name cdn.meuservidor.com;
root /var/www/cdn;
# the manifest must never be cached
location = /version.json {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
# patches can be cached for a long time
location /patches/ {
add_header Cache-Control "public, max-age=604800";
}
}
If you use a CDN in front, respect the same logic: the version.json should have a very low TTL, and the files in patches/ can have a long TTL, since any content change alters the hash and therefore the launcher's behavior.
Step 5 — Rollback when a patch breaks
Sooner or later a patch will go out bad: a corrupted texture, a Main.exe from the wrong season, a Data file with an invalid format. Since the system is hash-based and you kept the history, rollback is quick.
Rollback strategy in three steps:
- Identify the last stable version (e.g.:
3.2.0). - Ensure the files from that version still exist in
patches/— if the bad patch overwrote any, restore them from your versioned build. - Republish the stable manifest as
version.json:
# revert to 3.2.0
cp /var/www/cdn/manifests/3.2.0.json /var/www/cdn/version.json
On the next launch, the launcher compares the players' current hashes with the 3.2.0 manifest, notices the divergence in the affected files, and downloads the stable versions back. The player never even needs to know there was a problem.
> Always keep at least the last two or three complete versions of the distributable client. Rollback only works if the old bytes still exist somewhere.
Step 6 — Communicating the patch to the player
A good patch flow also informs. Publish a news.json that the launcher displays, with the changelog:
{
"version": "3.3.0",
"date": "2026-07-10",
"highlights": [
"New seasonal event enabled",
"Texture fix on the Kanturu map",
"Drop balance adjustment"
]
}
Showing the changelog reduces support tickets ("what changed?") and conveys the sense of an active, well-maintained server — which retains players.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Launcher gets a 404 during the patch | Manifest uploaded before the files | Upload files first, version.json last |
| Patch downloads files that did not change | Hash comparison fails (case or line endings) | Normalize hashes to lowercase; do not accidentally alter binary files |
| Players stuck on an old version | version.json cached by CDN/proxy | Cache-Control: no-cache on the manifest; purge the CDN |
| Client breaks after a patch | Corrupted or wrong-season file published | Roll back to the previous stable manifest |
| Slow upload on every patch | Sending the entire client instead of the diff | Use the script that copies only the changed files |
| Rollback does not restore files | Old bytes were overwritten and lost | Keep versioned builds of the latest versions |
| Manifest and files out of sync | Manifest generated before finishing the edits | Always generate the manifest last, in the same scan |
Patch flow best practices
- Automate everything into one command:
publicar-patch.ps1 3.3.0should perform the complete build. Manual steps are where errors are born. - Test in a mirror folder before publishing to production. Point a test launcher at a staging manifest.
- HTTPS all the way, from the manifest to the patches. A patch without TLS is a vector for injecting malware into the client.
- Version the distributable client (Git LFS or a dated folder) to enable reliable rollback.
- Never include the player's local files in the patch (video config, sound, screenshots). Use the exclusion list.
- Log every publish: version, date, changed files. That log saves investigations when something breaks.
Release checklist
- Server folder structure defined (
version.json,manifests/,patches/) publicar-patch.ps1script tested and generating a correct diff- Exclusion list covering logs and the player's local configs
- Upload order respected (files before the manifest)
Cache-Control: no-cacheonversion.json; long cache onpatches/- HTTPS active on the distribution subdomain
- Manifest history kept for rollback
- Versioned build of the latest client versions
- Rollback procedure tested in a staging environment
news.json/changelog published alongside the patch- Test launcher validating the patch before releasing it to everyone
- Publish log updated after each patch
Frequently asked questions
What is the difference between automatic patching and an ordinary launcher?
The launcher is the tool that runs on the player's machine. Automatic patching is the complete server-side process: detecting what changed, packaging, versioning, and publishing the manifest that the launcher consumes. One without the other is incomplete.
Do I need to resend the entire client on every patch?
No. The whole point of automatic patching is to send only the changed files. The manifest lists the hash of each file and the launcher downloads only the differences, saving the player's bandwidth and time.
How do I roll back if a patch breaks the client?
Keep previous versions of the manifest and the files. To revert, republish the manifest pointing to the hashes of the previous stable version. Since the launcher trusts the hash, it restores the old files automatically.
Can I automate everything with a script?
Yes, and it is recommended. A script scans the client folder, computes hashes, compares against the last version, packages the changed ones, and uploads the manifest and files. That turns patching into a single command and eliminates human error.
Does automatic patching depend on the server's season?
The logic is the same for any season. What changes is which files exist in the client folder and the folder structure (Data, BMD textures, Main.exe). Treat these paths as an example, since they vary by season/client.