using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Serialization; namespace ManifestGenerator; /// /// Um arquivo dentro do manifesto: caminho relativo, tamanho e hash MD5. /// O launcher vai comparar esses três dados com os arquivos que já tem /// localmente para decidir o que precisa baixar. /// public class ManifestFileEntry { [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; [JsonPropertyName("size")] public long Size { get; set; } [JsonPropertyName("md5")] public string Md5 { get; set; } = string.Empty; } /// /// O manifesto completo que fica hospedado no servidor (ex: via XAMPP, /// no mesmo esquema que você já usa em C:\xampp\htdocs\download). /// public class Manifest { [JsonPropertyName("version")] public string Version { get; set; } = string.Empty; [JsonPropertyName("generatedAt")] public string GeneratedAt { get; set; } = string.Empty; [JsonPropertyName("totalFiles")] public int TotalFiles { get; set; } [JsonPropertyName("totalSize")] public long TotalSize { get; set; } [JsonPropertyName("files")] public List Files { get; set; } = new(); } internal class Program { // Arquivos/pastas que nunca devem entrar no manifesto. private static readonly string[] ExcludedNames = { "manifest.json", ".git", "Thumbs.db", "desktop.ini" }; private static int Main(string[] args) { Console.OutputEncoding = System.Text.Encoding.UTF8; Console.WriteLine("=== Gerador de Manifesto (Auto-Update) ===\n"); // --- 1. Ler argumentos ou perguntar interativamente --- string sourceFolder = GetArg(args, "--source") ?? AskFolder("Pasta com os arquivos do jogo/patch"); string outputFile = GetArg(args, "--output") ?? System.IO.Path.Combine(sourceFolder, "manifest.json"); string? version = GetArg(args, "--version"); if (!Directory.Exists(sourceFolder)) { Console.WriteLine($"[ERRO] Pasta não encontrada: {sourceFolder}"); return 1; } version ??= DateTime.UtcNow.ToString("yyyyMMddHHmmss"); // versão automática se não for passada Console.WriteLine($"Pasta de origem : {sourceFolder}"); Console.WriteLine($"Arquivo de saída: {outputFile}"); Console.WriteLine($"Versão : {version}\n"); // --- 2. Varrer os arquivos recursivamente --- var allFiles = Directory .EnumerateFiles(sourceFolder, "*", SearchOption.AllDirectories) .Where(f => !ExcludedNames.Contains(System.IO.Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)) .ToList(); Console.WriteLine($"Encontrados {allFiles.Count} arquivo(s). Calculando hashes...\n"); var manifest = new Manifest { Version = version, GeneratedAt = DateTime.UtcNow.ToString("o") }; int processed = 0; foreach (var fullPath in allFiles) { processed++; string relativePath = System.IO.Path.GetRelativePath(sourceFolder, fullPath) .Replace('\\', '/'); // sempre usar "/" no manifesto, independente do SO long size = new FileInfo(fullPath).Length; string hash = ComputeMd5(fullPath); manifest.Files.Add(new ManifestFileEntry { Path = relativePath, Size = size, Md5 = hash }); manifest.TotalSize += size; // Barra de progresso simples Console.Write($"\r[{processed}/{allFiles.Count}] {relativePath.PadRight(60).Substring(0, Math.Min(60, relativePath.Length))}"); } manifest.TotalFiles = manifest.Files.Count; // --- 3. Salvar o manifesto em JSON --- var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; string json = JsonSerializer.Serialize(manifest, jsonOptions); File.WriteAllText(outputFile, json); Console.WriteLine("\n\n=== Concluído ==="); Console.WriteLine($"Total de arquivos : {manifest.TotalFiles}"); Console.WriteLine($"Tamanho total : {FormatBytes(manifest.TotalSize)}"); Console.WriteLine($"Manifesto salvo em: {outputFile}"); return 0; } private static string ComputeMd5(string filePath) { using var md5 = MD5.Create(); using var stream = File.OpenRead(filePath); byte[] hashBytes = md5.ComputeHash(stream); return Convert.ToHexString(hashBytes).ToLowerInvariant(); } private static string? GetArg(string[] args, string name) { for (int i = 0; i < args.Length - 1; i++) { if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) return args[i + 1]; } return null; } private static string AskFolder(string prompt) { Console.Write($"{prompt}: "); return Console.ReadLine()?.Trim().Trim('"') ?? string.Empty; } private static string FormatBytes(long bytes) { string[] sizes = { "B", "KB", "MB", "GB" }; double len = bytes; int order = 0; while (len >= 1024 && order < sizes.Length - 1) { order++; len /= 1024; } return $"{len:0.##} {sizes[order]}"; } }