O maior portal de MU Online do Brasil — desde 2003
Tutorial Avançado Servidor

Como criar um Launcher de atualização para MU Online

Crie um Launcher de auto-atualização para seu servidor de MU Online — com checklist de versão via JSON, download de patches, barra de progresso e lançamento automático do Main.exe.

EQ Equipe ViciadosMU · Atualizado em 1 jul 2026 · ⏱ 22 min de leitura
Resposta rápida

Um Launcher profissional melhora muito a experiência dos jogadores — ao abrir o Launcher, ele verifica automaticamente se o cliente está atualizado, baixa os patches necessários com barra de progresso, e só então abre o Main.exe. Você nunca mais precisará pedir para jogadores baixar e extrair ZIPs d

Um Launcher profissional melhora muito a experiência dos jogadores — ao abrir o Launcher, ele verifica automaticamente se o cliente está atualizado, baixa os patches necessários com barra de progresso, e só então abre o Main.exe. Você nunca mais precisará pedir para jogadores baixar e extrair ZIPs de patch manualmente.

Arquitetura do sistema de Launcher

JOGADOR (Launcher.exe)
      │
      │ 1. HTTP GET: /version.json
      │    {"version": "1.5", "files": [...]}
      ▼
SERVIDOR DE ATUALIZAÇÃO (Apache/Nginx)
      │
      │ 2. Se versão diferente: baixar arquivos de patch
      │    GET /patches/1.5/Data/MonsterSetBase.txt
      ▼
PASTA LOCAL DO CLIENTE
      │
      │ 3. Arquivos atualizados
      ▼
Main.exe ← Launcher inicia o jogo

Passo 1 — Configurar o servidor de atualização

O servidor de atualização é um servidor web que serve os arquivos de patch. Pode ser a mesma VPS do servidor de MU ou a hospedagem do site.

Estrutura de pastas no servidor web

public_html/atualização/
├── version.json           ← arquivo de controle de versão
└── patches/
    ├── 1.0/               ← patch da versão 1.0 (instalação inicial)
    │   └── Data/
    │       └── Monster.txt
    ├── 1.1/               ← patch com novos conteúdos
    │   └── Data/
    │       └── MonsterSetBase.txt
    └── 1.5/               ← versão atual
        ├── Data/
        │   └── Drop.txt
        └── Main.exe

version.json — arquivo de controle de versão

{
  "version": "1.5",
  "min_version": "1.0",
  "server_name": "MeuServidor",
  "patch_url": "https://www.meuservidor.com.br/atualizacao/patches/",
  "news_url": "https://www.meuservidor.com.br/api/news.json",
  "files": [
    {
      "path": "Data/MonsterSetBase.txt",
      "version": "1.3",
      "size": 45678,
      "hash": "a3b4c5d6e7f8901234..."
    },
    {
      "path": "Data/Drop.txt",
      "version": "1.5",
      "size": 89012,
      "hash": "b4c5d6e7f8901234ab..."
    },
    {
      "path": "Main.exe",
      "version": "1.5",
      "size": 2345678,
      "hash": "c5d6e7f890123456bc..."
    }
  ]
}

Gerar hash SHA256 dos arquivos

# PowerShell — gerar hash de todos os arquivos do patch
$files = Get-ChildItem -Recurse "C:\ClienteSource\patches\1.5\"
foreach ($file in $files) {
    $hash = (Get-FileHash $file.FullName -Algorithm SHA256).Hash.ToLower()
    $rel  = $file.FullName.Replace("C:\ClienteSource\patches\1.5\", "")
    Write-Output "'$rel': '$hash'"
}

Passo 2 — Criar o Launcher em C# (WinForms)

Requisitos

  • Visual Studio 2022 Community (gratuito)
  • .NET Framework 4.7.2 ou .NET 6+

Estrutura do projeto

MuLauncher/
├── Form1.cs          ← tela principal do Launcher
├── Form1.Designer.cs ← design da janela
├── Updater.cs        ← lógica de atualização
├── Program.cs        ← ponto de entrada
└── Resources/
    ├── background.jpg
    └── logo.png

Program.cs — ponto de entrada

using System;
using System.Windows.Forms;

namespace MuLauncher
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.cs — janela principal

using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json;  // NuGet: Newtonsoft.Json

namespace MuLauncher
{
    public partial class Form1 : Form
    {
        private const string VERSION_URL = "https://www.meuservidor.com.br/atualizacao/version.json";
        private const string LOCAL_VERSION_FILE = "version.txt";
        private const string MAIN_EXE = "Main.exe";

        private HttpClient _http = new HttpClient();

        public Form1()
        {
            InitializeComponent();
            Load += Form1_Load;
        }

        private async void Form1_Load(object sender, EventArgs e)
        {
            lblStatus.Text = "Verificando atualizações...";
            btnJogar.Enabled = false;

            try
            {
                await CheckAndUpdate();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Erro ao verificar atualizações: {ex.Message}\n\nVocê pode tentar jogar mesmo assim.",
                    "Aviso", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

            btnJogar.Enabled = true;
            lblStatus.Text = "Pronto para jogar!";
        }

        private async Task CheckAndUpdate()
        {
            // Baixar version.json do servidor
            string json = await _http.GetStringAsync(VERSION_URL);
            var serverVersion = JsonConvert.DeserializeObject<VersionInfo>(json);

            // Ler versão local
            string localVersion = "0.0";
            if (File.Exists(LOCAL_VERSION_FILE))
                localVersion = File.ReadAllText(LOCAL_VERSION_FILE).Trim();

            // Comparar versões
            if (localVersion == serverVersion.Version)
            {
                lblStatus.Text = $"Cliente atualizado (v{localVersion})";
                return;
            }

            // Atualizar
            lblStatus.Text = $"Atualizando de v{localVersion} para v{serverVersion.Version}...";
            
            var updater = new Updater(_http, serverVersion.PatchUrl);
            updater.ProgressChanged += (s, p) =>
            {
                progressBar.Value = p.Percentage;
                lblStatus.Text = $"Baixando: {p.FileName} ({p.Percentage}%)";
            };

            await updater.DownloadFiles(serverVersion.Files);

            // Salvar versão atualizada
            File.WriteAllText(LOCAL_VERSION_FILE, serverVersion.Version);
            lblStatus.Text = $"Atualizado para v{serverVersion.Version}!";
        }

        private void btnJogar_Click(object sender, EventArgs e)
        {
            if (!File.Exists(MAIN_EXE))
            {
                MessageBox.Show("Main.exe não encontrado!", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            System.Diagnostics.Process.Start(MAIN_EXE);
            Application.Exit();
        }
    }

    // Modelo do version.json
    public class VersionInfo
    {
        [JsonProperty("version")]    public string Version { get; set; }
        [JsonProperty("patch_url")]  public string PatchUrl { get; set; }
        [JsonProperty("files")]      public FileEntry[] Files { get; set; }
    }

    public class FileEntry
    {
        [JsonProperty("path")]    public string Path { get; set; }
        [JsonProperty("version")] public string Version { get; set; }
        [JsonProperty("hash")]    public string Hash { get; set; }
        [JsonProperty("size")]    public long Size { get; set; }
    }
}

Updater.cs — lógica de download

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace MuLauncher
{
    public class ProgressEventArgs : EventArgs
    {
        public string FileName { get; set; }
        public int Percentage { get; set; }
    }

    public class Updater
    {
        private HttpClient _http;
        private string _patchBaseUrl;
        public event EventHandler<ProgressEventArgs> ProgressChanged;

        public Updater(HttpClient http, string patchBaseUrl)
        {
            _http = http;
            _patchBaseUrl = patchBaseUrl.TrimEnd('/');
        }

        public async Task DownloadFiles(FileEntry[] files)
        {
            for (int i = 0; i < files.Length; i++)
            {
                var file = files[i];
                string localPath = file.Path.Replace('/', '\\');

                // Verificar se precisa atualizar (comparar hash)
                if (File.Exists(localPath) && GetFileHash(localPath) == file.Hash)
                    continue;

                // Criar diretório se não existir
                string dir = Path.GetDirectoryName(localPath);
                if (!string.IsNullOrEmpty(dir))
                    Directory.CreateDirectory(dir);

                // Baixar o arquivo
                int percent = (int)((double)(i + 1) / files.Length * 100);
                ProgressChanged?.Invoke(this, new ProgressEventArgs
                {
                    FileName = Path.GetFileName(localPath),
                    Percentage = percent
                });

                string url = $"{_patchBaseUrl}/{file.Path}";
                byte[] data = await _http.GetByteArrayAsync(url);
                File.WriteAllBytes(localPath, data);
            }
        }

        private string GetFileHash(string path)
        {
            using var sha256 = SHA256.Create();
            using var stream = File.OpenRead(path);
            byte[] hash = sha256.ComputeHash(stream);
            return BitConverter.ToString(hash).Replace("-", "").ToLower();
        }
    }
}

Form1.Designer.cs — layout básico da janela

// Controles básicos para adicionar no Designer do Visual Studio:
// - Label: lblStatus (status atual)
// - ProgressBar: progressBar (0-100)
// - Button: btnJogar ("JOGAR")
// - PictureBox: picLogo (logo do servidor)
// - WebBrowser ou RichTextBox: notícias (opcional)

Passo 3 — Alternativa: Launcher em Python

Para quem prefere Python (requer Python instalado no PC do jogador):

# launcher.py
import tkinter as tk
from tkinter import ttk
import requests, json, hashlib, os, subprocess, threading

VERSION_URL = "https://www.meuservidor.com.br/atualizacao/version.json"
LOCAL_VERSION_FILE = "version.txt"

class Launcher:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("MeuServidor Launcher")
        self.root.geometry("600x400")
        self.root.resizable(False, False)

        self.status_var = tk.StringVar(value="Verificando atualizações...")
        tk.Label(self.root, textvariable=self.status_var).pack(pady=10)

        self.progress = ttk.Progressbar(self.root, length=500, mode='determinate')
        self.progress.pack(pady=10)

        self.play_btn = tk.Button(self.root, text="JOGAR", state=tk.DISABLED,
                                   command=self.launch_game, width=20, height=2)
        self.play_btn.pack(pady=20)

        # Iniciar verificação em thread separada
        thread = threading.Thread(target=self.check_updates)
        thread.daemon = True
        thread.start()

        self.root.mainloop()

    def check_updates(self):
        try:
            resp = requests.get(VERSION_URL, timeout=10)
            server_info = resp.json()
            server_version = server_info["version"]

            local_version = "0.0"
            if os.path.exists(LOCAL_VERSION_FILE):
                with open(LOCAL_VERSION_FILE) as f:
                    local_version = f.read().strip()

            if local_version == server_version:
                self.status_var.set(f"Atualizado (v{local_version})")
                self.play_btn.config(state=tk.NORMAL)
                return

            files = server_info["files"]
            patch_url = server_info["patch_url"]

            for i, file_info in enumerate(files):
                path = file_info["path"]
                url = patch_url + path

                # Verificar hash
                if os.path.exists(path):
                    sha256 = hashlib.sha256(open(path, 'rb').read()).hexdigest()
                    if sha256 == file_info["hash"]:
                        continue

                os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
                
                self.status_var.set(f"Baixando: {os.path.basename(path)}...")
                data = requests.get(url).content
                with open(path, 'wb') as f:
                    f.write(data)

                percent = int((i + 1) / len(files) * 100)
                self.progress["value"] = percent
                self.root.update_idletasks()

            with open(LOCAL_VERSION_FILE, 'w') as f:
                f.write(server_version)

            self.status_var.set(f"Pronto! (v{server_version})")
            self.play_btn.config(state=tk.NORMAL)

        except Exception as e:
            self.status_var.set(f"Erro: {e}")
            self.play_btn.config(state=tk.NORMAL)  # permitir jogar mesmo com erro

    def launch_game(self):
        if os.path.exists("Main.exe"):
            subprocess.Popen(["Main.exe"])
            self.root.destroy()

if __name__ == "__main__":
    Launcher()

Passo 4 — Deploy do servidor de atualização

# No servidor web (VPS Ubuntu)
# Criar pasta para os patches
sudo mkdir -p /var/www/mu-site/atualizacao/patches/1.5

# Fazer upload dos arquivos de patch via FTP/SFTP
# (use FileZilla ou rsync)

# Permissões corretas
sudo chown -R www-data:www-data /var/www/mu-site/atualizacao/
sudo chmod -R 755 /var/www/mu-site/atualizacao/

# Certificar que os arquivos são acessíveis via HTTP
curl https://www.meuservidor.com.br/atualizacao/version.json

Processo de publicar um patch

  1. Modifique os arquivos do cliente (ex: novo Monster.txt);
  2. Calcule o hash SHA256 do novo arquivo;
  3. Copie o arquivo para /patches/[versão]/pasta/arquivo.txt no servidor;
  4. Atualize o version.json com a nova versão e o novo hash;
  5. O Launcher dos jogadores detectará automaticamente na próxima abertura.
# Script para gerar version.json automaticamente
$patchFolder = "C:\Patches\1.6"
$files = Get-ChildItem $patchFolder -Recurse -File

$fileList = @()
foreach ($f in $files) {
    $hash = (Get-FileHash $f.FullName -Algorithm SHA256).Hash.ToLower()
    $rel = $f.FullName.Replace("$patchFolder\", "").Replace("\", "/")
    $fileList += @{
        path    = $rel
        version = "1.6"
        hash    = $hash
        size    = $f.Length
    }
}

$versionJson = @{
    version   = "1.6"
    patch_url = "https://www.meuservidor.com.br/atualizacao/patches/1.6/"
    files     = $fileList
} | ConvertTo-Json -Depth 3

$versionJson | Out-File "version.json" -Encoding utf8
Write-Host "version.json gerado para v1.6!"

Perguntas frequentes

O Launcher é obrigatório?

Não. Você pode distribuir patches manualmente (ZIP). O Launcher automatiza esse processo e melhora muito a experiência do jogador — eles não precisam baixar e extrair patches manualmente.

Em qual linguagem criar o Launcher?

C# (WPF ou WinForms) é a opção mais comum e resulta em um executável nativo rápido. Python com tkinter funciona, mas requer instalação do Python. Electron (JavaScript) cria launchers modernos mas é mais pesado.

O servidor de atualização é o mesmo do jogo?

Não necessariamente. O servidor de atualização é um servidor web (Apache/Nginx) que serve arquivos de patch. Pode estar no mesmo VPS ou em hospedagem separada.

Como funciona o sistema de versão?

O Launcher lê um arquivo JSON (ou XML) no servidor com a versão atual e a lista de arquivos. Compara com a versão instalada. Se houver diferença, baixa apenas os arquivos novos.

EQ

Equipe editorial do ViciadosMU — portal de MU Online no ar desde 2003.

Continue lendo

Artigos relacionados