From 9f3ad79320865b728782e00b67bf1addc878cfaa Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 14 Apr 2026 11:12:04 +0200 Subject: [PATCH] launcher: scaffold csproj and solution --- .gitignore | 402 +----------------- Launcher.sln | 36 ++ src/Metin2Launcher/Config/LauncherConfig.cs | 37 ++ src/Metin2Launcher/Logging/Log.cs | 89 ++++ src/Metin2Launcher/Metin2Launcher.csproj | 18 + src/Metin2Launcher/Program.cs | 16 + .../Metin2Launcher.Tests.csproj | 30 ++ 7 files changed, 229 insertions(+), 399 deletions(-) create mode 100644 Launcher.sln create mode 100644 src/Metin2Launcher/Config/LauncherConfig.cs create mode 100644 src/Metin2Launcher/Logging/Log.cs create mode 100644 src/Metin2Launcher/Metin2Launcher.csproj create mode 100644 src/Metin2Launcher/Program.cs create mode 100644 tests/Metin2Launcher.Tests/Metin2Launcher.Tests.csproj diff --git a/.gitignore b/.gitignore index ed6d1d2..df7b1b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,402 +1,6 @@ -# ---> VisualStudio -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo +bin/ +obj/ *.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory +*.suo .vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -# but not Directory.Build.rsp, as it configures directory-level build defaults -!Directory.Build.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml - diff --git a/Launcher.sln b/Launcher.sln new file mode 100644 index 0000000..faf9488 --- /dev/null +++ b/Launcher.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{55F52404-C1C7-4E04-8A4A-F0AF9616D6BE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metin2Launcher", "src\Metin2Launcher\Metin2Launcher.csproj", "{6FF284A9-68DE-4423-91A4-03B20606C486}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C5AD0C44-A658-4FF0-BBAE-DD5EF0D2F55F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Metin2Launcher.Tests", "tests\Metin2Launcher.Tests\Metin2Launcher.Tests.csproj", "{15D6EB4F-5BC5-4801-AD36-ABEA56027973}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6FF284A9-68DE-4423-91A4-03B20606C486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FF284A9-68DE-4423-91A4-03B20606C486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FF284A9-68DE-4423-91A4-03B20606C486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FF284A9-68DE-4423-91A4-03B20606C486}.Release|Any CPU.Build.0 = Release|Any CPU + {15D6EB4F-5BC5-4801-AD36-ABEA56027973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15D6EB4F-5BC5-4801-AD36-ABEA56027973}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15D6EB4F-5BC5-4801-AD36-ABEA56027973}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15D6EB4F-5BC5-4801-AD36-ABEA56027973}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6FF284A9-68DE-4423-91A4-03B20606C486} = {55F52404-C1C7-4E04-8A4A-F0AF9616D6BE} + {15D6EB4F-5BC5-4801-AD36-ABEA56027973} = {C5AD0C44-A658-4FF0-BBAE-DD5EF0D2F55F} + EndGlobalSection +EndGlobal diff --git a/src/Metin2Launcher/Config/LauncherConfig.cs b/src/Metin2Launcher/Config/LauncherConfig.cs new file mode 100644 index 0000000..afc8c53 --- /dev/null +++ b/src/Metin2Launcher/Config/LauncherConfig.cs @@ -0,0 +1,37 @@ +namespace Metin2Launcher.Config; + +/// +/// Hardcoded configuration for the launcher MVP. +/// These values are deliberately not loaded from a config file — they are compile-time +/// constants so a tampered local config cannot redirect updates. +/// +public static class LauncherConfig +{ + public const string ManifestUrl = "https://updates.jakubkadlec.dev/manifest.json"; + public const string SignatureUrl = "https://updates.jakubkadlec.dev/manifest.json.sig"; + public const string BlobBaseUrl = "https://updates.jakubkadlec.dev/files"; + public const string VelopackFeedUrl = "https://updates.jakubkadlec.dev/launcher"; + public const string GameExecutable = "Metin2.exe"; + + /// + /// Ed25519 public key used to verify the manifest signature. + /// + /// PLACEHOLDER — generated with NSec.Cryptography during MVP scaffolding. + /// Must be swapped for the real production key before first public release. + /// The matching private key lives only in the test-generator used locally and + /// IS NOT used to sign any real release. + /// + public const string PublicKeyHex = "a8619c11348cb26cdcad9467c5dd44e3fcb40017e9fbcf225ac76ad824422c46"; + + public static readonly TimeSpan ManifestFetchTimeout = TimeSpan.FromSeconds(10); + public static readonly TimeSpan BlobFetchTimeout = TimeSpan.FromSeconds(60); + public static readonly TimeSpan TotalUpdateTimeout = TimeSpan.FromMinutes(30); + + public const int MaxBlobRetries = 3; + + /// Name of the directory under the client root that holds launcher state. + public const string StateDirName = ".updates"; + + /// Name of the staging directory under the state directory. + public const string StagingDirName = "staging"; +} diff --git a/src/Metin2Launcher/Logging/Log.cs b/src/Metin2Launcher/Logging/Log.cs new file mode 100644 index 0000000..149bd5e --- /dev/null +++ b/src/Metin2Launcher/Logging/Log.cs @@ -0,0 +1,89 @@ +using System.Globalization; + +namespace Metin2Launcher.Logging; + +/// +/// Hand-rolled logger: writes to stdout and a rolling (well, appending) log file. +/// Thread-safe via a lock. Intentionally simple — no Serilog/NLog for MVP. +/// +public static class Log +{ + private static readonly object Gate = new(); + private static string? _logFilePath; + + public static string LogFilePath + { + get + { + if (_logFilePath is null) + { + _logFilePath = DefaultLogFilePath(); + } + return _logFilePath; + } + } + + public static void SetLogFilePath(string path) + { + lock (Gate) + { + _logFilePath = path; + var dir = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + } + } + + public static void Info(string message) => Write("INFO ", message); + public static void Warn(string message) => Write("WARN ", message); + public static void Error(string message) => Write("ERROR", message); + + public static void Error(string message, Exception ex) + => Write("ERROR", message + " :: " + ex); + + private static void Write(string level, string message) + { + var line = string.Format( + CultureInfo.InvariantCulture, + "{0:yyyy-MM-dd HH:mm:ss.fff} {1} {2}", + DateTime.UtcNow, level, message); + + lock (Gate) + { + Console.WriteLine(line); + try + { + var path = LogFilePath; + var dir = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(dir)) + Directory.CreateDirectory(dir); + File.AppendAllText(path, line + Environment.NewLine); + } + catch + { + // Deliberately swallow — logging must never take the process down. + } + } + } + + private static string DefaultLogFilePath() + { + string baseDir; + if (OperatingSystem.IsWindows()) + { + baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + } + else + { + var xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + if (string.IsNullOrEmpty(xdg)) + { + xdg = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".local", "share"); + } + baseDir = xdg; + } + return Path.Combine(baseDir, "Metin2Launcher", "launcher.log"); + } +} diff --git a/src/Metin2Launcher/Metin2Launcher.csproj b/src/Metin2Launcher/Metin2Launcher.csproj new file mode 100644 index 0000000..83d150d --- /dev/null +++ b/src/Metin2Launcher/Metin2Launcher.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + Metin2Launcher + Metin2Launcher + true + + + + + + + + diff --git a/src/Metin2Launcher/Program.cs b/src/Metin2Launcher/Program.cs new file mode 100644 index 0000000..87627d4 --- /dev/null +++ b/src/Metin2Launcher/Program.cs @@ -0,0 +1,16 @@ +using Metin2Launcher.Logging; + +namespace Metin2Launcher; + +/// +/// Launcher entry point. Wired to a minimal scaffold in the first commit; +/// later commits extend this into the full update + game launch flow. +/// +public static class Program +{ + public static int Main(string[] args) + { + Log.Info("metin2 launcher scaffold: " + string.Join(' ', args)); + return 0; + } +} diff --git a/tests/Metin2Launcher.Tests/Metin2Launcher.Tests.csproj b/tests/Metin2Launcher.Tests/Metin2Launcher.Tests.csproj new file mode 100644 index 0000000..7292abf --- /dev/null +++ b/tests/Metin2Launcher.Tests/Metin2Launcher.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +