From 960dfadd75781ca31680e03b24e5b5737aa83e53 Mon Sep 17 00:00:00 2001 From: naralan Date: Sat, 4 Apr 2026 18:19:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B0=83=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=EF=BC=9A=E5=91=BD=E4=BB=A4=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ExternalInvocation.md | 90 ++++++ docs/UrlProtocol.md | 47 --- .../Features/CommandLine/GameCliRunner.cs | 268 ++++++++++++++++++ .../GameInstallProgressFormatter.cs | 102 +++++++ .../GameLauncher/StartGameButton.xaml.cs | 90 +----- src/Starward/Program.cs | 16 +- 6 files changed, 482 insertions(+), 131 deletions(-) create mode 100644 docs/ExternalInvocation.md delete mode 100644 docs/UrlProtocol.md create mode 100644 src/Starward/Features/CommandLine/GameCliRunner.cs create mode 100644 src/Starward/Features/GameInstall/GameInstallProgressFormatter.cs diff --git a/docs/ExternalInvocation.md b/docs/ExternalInvocation.md new file mode 100644 index 000000000..24710a437 --- /dev/null +++ b/docs/ExternalInvocation.md @@ -0,0 +1,90 @@ +# External Invocation + +Starward currently supports two external invocation methods: + +- URL Protocol +- Command Line + +The parameter `game_biz` in the following is game region identifier and can be viewed in [GameBiz.cs](https://github.com/Scighost/Starward/blob/main/src/Starward.Core/GameBiz.cs). + +| game_biz (string) | Description | +| ----------------- | ------------------------------- | +| hk4e_cn | Genshin Impact (Mainland China) | +| hk4e_global | Genshin Impact (Global) | +| hk4e_bilibili | Genshin Impact (Bilibili) | +| hkrpg_cn | Star Rail (Mainland China) | +| hkrpg_global | Star Rail (Global) | +| hkrpg_bilibili | Star Rail (Bilibili) | +| bh3_cn | Honkai 3rd (Mainland China) | +| bh3_global | Honkai 3rd (Global) | + +## URL Protocol + +Other software or websites can use the `starward` URL protocol to call some features of Starward. The URL protocol is registered only when the user enables this feature in the settings page. + +![URL Protocol](https://user-images.githubusercontent.com/61003590/278273851-7c614cde-d8c4-403b-876e-cecc3570f684.png) + +### Start game + +``` +starward://startgame/{game_biz}?install_path={install_path} +``` + +**Acceptable query arguments** + +| Key | Type | Description | +| --- | --- | --- | +| install_path | `string` (Optional) | Full folder path of the game executable location. | + +### Record playtime + +``` +starward://playtime/{game_biz}?pid={pid} +``` + +**Acceptable query arguments** + +| Key | Type | Description | +| --- | --- | --- | +| pid | `int` (Optional) | Game process id. | + +## Command Line + +Starward also supports direct Command Line invocation through command arguments handled in the application entry point. + +> [!NOTE] +> Ensure that the `Starward.exe` you invoke is the one in the same directory as `version.ini`. + +### Start game + +``` +Starward.exe startgame --biz={game_biz} --install_path="{install_path}" +``` + +**Acceptable arguments** + +| Key | Type | Description | +| --- | --- | --- | +| `--biz` | `string` (Required) | Game region identifier. Refer to `game_biz` | +| `--install_path` | `string` (Optional) | Full folder path of the game executable location. | + +If install path is not provided, Starward will try to use the locally stored install path. + +### Update game + +``` +Starward.exe updategame --action={action} --biz={game_biz} --install_path="{install_path}" +``` + +**Acceptable arguments** + +| Key | Type | Description | +| --- | --- | --- | +| `--action` | `string` (Required) | One of: `check`, `update`, `repair`. | +| `--biz` | `string` (Required) | Game region identifier. Refer to `game_biz` | +| `--install_path` | `string` (Optional) | Full folder path of the game executable location. | + +> [!NOTE] +> When `--action` is `repair` or `update`, Starward will run silently in the background until the task finishes. + + diff --git a/docs/UrlProtocol.md b/docs/UrlProtocol.md deleted file mode 100644 index 3a08e2853..000000000 --- a/docs/UrlProtocol.md +++ /dev/null @@ -1,47 +0,0 @@ -# URL Protocol - -Other software even website could use url protocol `starward` to call some features of Starward. The url protocol is registered only when the user enables this feature in setting page. - -![URL Protocol](https://user-images.githubusercontent.com/61003590/278273851-7c614cde-d8c4-403b-876e-cecc3570f684.png) - - -## Available features - -The parameter `game_biz` in the following is game region identifier and can be viewed in [GameBiz.cs](https://github.com/Scighost/Starward/blob/main/src/Starward.Core/GameBiz.cs). - -| game_biz (string) | Description | -| ----------------- | --------------------------------------- | -| hk4e_cn | Genshin Impact (Mainland China) | -| hk4e_global | Genshin Impact (Global) | -| hk4e_bilibili | Genshin Impact (Bilibili) | -| hkrpg_cn | Star Rail (Mainland China) | -| hkrpg_global | Star Rail (Global) | -| hkrpg_bilibili | Star Rail (Bilibili) | -| bh3_cn | Honkai 3rd (Mainland China) | -| bh3_global | Honkai 3rd (Global) | - - -### Start game - -``` -starward://startgame/{game_biz}?install_path={install_path} -``` - -**Acceptable query arguments** - -|Key|Type|Description| -|---|---|---| -|install_path| `string` (Option) | Folder full path of game executable. | - - -### Record playtime - -``` -starward://playtime/{game_biz}?pid={pid} -``` - -**Acceptable query arguments** - -|Key|Type|Description| -|---|---|---| -|pid| `int` (Option) | Game process id. | diff --git a/src/Starward/Features/CommandLine/GameCliRunner.cs b/src/Starward/Features/CommandLine/GameCliRunner.cs new file mode 100644 index 000000000..f74742fb0 --- /dev/null +++ b/src/Starward/Features/CommandLine/GameCliRunner.cs @@ -0,0 +1,268 @@ +using Microsoft.Extensions.Configuration; +using Starward.Core; +using Starward.Core.HoYoPlay; +using Starward.Features.GameInstall; +using Starward.Features.GameLauncher; +using Starward.RPC.GameInstall; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Text; + +namespace Starward.Features.CommandLine; + +internal static class GameCliRunner +{ + + + private enum UpdateGameAction + { + Check, + Update, + Repair, + } + + + public static async Task StartGameAsync(string[] args) + { + try + { + IConfiguration config = new ConfigurationBuilder().AddCommandLine(args).Build(); + GameBiz biz = ParseGameBiz(config.GetValue("biz")); + string? installPath = GetInstallPath(config) ?? GameLauncherService.GetGameInstallPath(biz); + GameId gameId = GameId.FromGameBiz(biz) ?? throw new ArgumentException($"Unknown game biz: {biz}."); + Process? process = await AppConfig.GetService().StartGameAsync(gameId, installPath); + if (process is null) + { + Log($"启动游戏失败: biz={biz}, reason=未获取到进程"); + } + else + { + Log($"启动游戏成功: biz={biz}, pid={process.Id}, install_path={installPath ?? ""}"); + } + } + catch (Exception ex) + { + Log($"启动游戏失败: {ex.Message}"); + } + } + + + + public static async Task UpdateGameAsync(string[] args) + { + try + { + IConfiguration config = new ConfigurationBuilder().AddCommandLine(args).Build(); + UpdateGameAction action = ParseAction(config.GetValue("action")); + + GameBiz biz = ParseGameBiz(config.GetValue("biz")); + GameId gameId = GameId.FromGameBiz(biz) ?? throw new ArgumentException($"Unknown game biz: {biz}."); + string? installPath = GetInstallPath(config) ?? GameLauncherService.GetGameInstallPath(biz); + + GameLauncherService gameLauncherService = AppConfig.GetService(); + GamePackageService gamePackageService = AppConfig.GetService(); + GameInstallService gameInstallService = AppConfig.GetService(); + + switch (action) + { + case UpdateGameAction.Check: + { + await CheckUpdateStateAsync(gameLauncherService, gamePackageService, gameId, biz, installPath); + return; + } + + case UpdateGameAction.Update: + { + await RunUpdateOrRepairAsync(gameLauncherService, gamePackageService, gameInstallService, gameId, biz, installPath, isRepair: false); + return; + } + + case UpdateGameAction.Repair: + { + await RunUpdateOrRepairAsync(gameLauncherService, gamePackageService, gameInstallService, gameId, biz, installPath, isRepair: true); + return; + } + } + } + catch (Exception ex) + { + Log($"updategame 执行失败: {ex.Message}"); + } + } + + + + private static async Task CheckUpdateStateAsync(GameLauncherService gameLauncherService, GamePackageService gamePackageService, GameId gameId, GameBiz biz, string? installPath) + { + if (string.IsNullOrWhiteSpace(installPath)) + { + Log($"检查预下载/更新失败: biz={biz}, reason=install_path_not_found"); + return; + } + + Version? localVersion = await gameLauncherService.GetLocalGameVersionAsync(gameId, installPath).ConfigureAwait(false); + (Version? latestVersion, Version? predownloadVersion) = await gameLauncherService.GetLatestGameVersionAsync(gameId).ConfigureAwait(false); + + bool needUpdate = localVersion is not null && latestVersion > localVersion; + bool hasPredownload = localVersion is not null && predownloadVersion > localVersion; + bool predownloadFinished = false; + if (hasPredownload) + { + predownloadFinished = await gamePackageService.CheckPreDownloadFinishedAsync(gameId, installPath).ConfigureAwait(false); + } + + Log($"检查预下载/更新成功: biz={biz}, local={localVersion?.ToString() ?? ""}, latest={latestVersion?.ToString() ?? ""}, predownload={predownloadVersion?.ToString() ?? ""}, need_update={needUpdate}, need_predownload={hasPredownload && !predownloadFinished}, predownload_finished={predownloadFinished}"); + } + + + + private static async Task RunUpdateOrRepairAsync(GameLauncherService gameLauncherService, GamePackageService gamePackageService, GameInstallService gameInstallService, GameId gameId, GameBiz biz, string? installPath, bool isRepair) + { + string actionName = isRepair ? "修复" : "更新"; + + if (string.IsNullOrWhiteSpace(installPath)) + { + Log($"{actionName}游戏失败: biz={biz}, reason=install_path_not_found"); + return; + } + + if (!isRepair) + { + Version? localVersion = await gameLauncherService.GetLocalGameVersionAsync(gameId, installPath).ConfigureAwait(false); + (Version? latestVersion, _) = await gameLauncherService.GetLatestGameVersionAsync(gameId).ConfigureAwait(false); + if (localVersion is null || latestVersion is null || latestVersion <= localVersion) + { + Log($"更新游戏无需执行: biz={biz}, local={localVersion?.ToString() ?? ""}, latest={latestVersion?.ToString() ?? ""}"); + return; + } + } + + AudioLanguage audio = await gamePackageService.GetAudioLanguageAsync(gameId, installPath).ConfigureAwait(false); + GameInstallContext? task = isRepair + ? await gameInstallService.StartRepairAsync(gameId, installPath, audio).ConfigureAwait(false) + : await gameInstallService.StartUpdateAsync(gameId, installPath, audio).ConfigureAwait(false); + + if (task is null) + { + Log($"{actionName}游戏失败: biz={biz}, reason=start_{(isRepair ? "repair" : "update")}_failed"); + return; + } + + Log($"{actionName}任务已启动: biz={biz}, state={task.State}, install_path={installPath}"); + await TrackInstallTaskAsync(gameInstallService, gameId, task, actionName).ConfigureAwait(false); + } + + + + private static async Task TrackInstallTaskAsync(GameInstallService gameInstallService, GameId gameId, GameInstallContext task, string action) + { + using var cts = new CancellationTokenSource(TimeSpan.FromHours(12)); + + GameInstallState? lastState = null; + string? lastLine = null; + + while (!cts.IsCancellationRequested) + { + GameInstallContext current = gameInstallService.GetGameInstallTask(gameId) ?? task; + task = current; + + string line = BuildProgressLine(action, task); + if (task.State != lastState || line != lastLine) + { + Log(line); + lastState = task.State; + lastLine = line; + } + + if (task.State is GameInstallState.Finish) + { + Log($"{action}完成: state={task.State}"); + return true; + } + + if (task.State is GameInstallState.Stop or GameInstallState.Error) + { + Log($"{action}失败: state={task.State}, error={task.ErrorMessage ?? ""}"); + return false; + } + + await Task.Delay(1000, cts.Token).ConfigureAwait(false); + } + + Log($"{action}超时: state={task.State}, error={task.ErrorMessage ?? ""}"); + return false; + } + + + + private static string BuildProgressLine(string action, GameInstallContext task) + { + double progress = GameInstallProgressFormatter.GetProgressPercent(task); + string percent = $"{progress:P1}"; + string downloadBytes = GameInstallProgressFormatter.ToBytesText(task.Progress_DownloadFinishBytes, task.Progress_DownloadTotalBytes) ?? "-"; + string installBytes = GameInstallProgressFormatter.ToBytesText(task.Progress_WriteFinishBytes, task.Progress_WriteTotalBytes) ?? "-"; + string downloadSpeed = GameInstallProgressFormatter.ToSpeedText(task.NetworkDownloadSpeed); + string installSpeed = GameInstallProgressFormatter.ToSpeedText(task.StorageWriteSpeed); + string verifySpeed = GameInstallProgressFormatter.ToSpeedText(task.StorageReadSpeed); + string remain = task.State is GameInstallState.Finish + ? "00:00:00" + : GameInstallProgressFormatter.ToRemainTimeText(task.RemainTimeSeconds); + string stateText = GameInstallProgressFormatter.GetInstallStateText(task.State); + + return $"{action}进度: state={task.State}({stateText}), percent={percent}, download={downloadBytes}, write={installBytes}, net={downloadSpeed}, write_speed={installSpeed}, verify_speed={verifySpeed}, remain={remain}"; + } + + + + private static UpdateGameAction ParseAction(string? value) + { + return value?.Trim().ToLowerInvariant() switch + { + "check" => UpdateGameAction.Check, + "update" => UpdateGameAction.Update, + "repair" => UpdateGameAction.Repair, + _ => throw new ArgumentException("Missing or invalid argument --action, valid values: check | update | repair."), + }; + } + + + + private static GameBiz ParseGameBiz(string? value) + { + if (!GameBiz.TryParse(value, out GameBiz biz)) + { + throw new ArgumentException("Missing or invalid argument --biz."); + } + return biz; + } + + + + private static string? GetInstallPath(IConfiguration config) + { + return config.GetValue("install_path") + ?? config.GetValue("install-path") + ?? config.GetValue("path"); + } + + + + static GameCliRunner() + { + var utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + Console.OutputEncoding = utf8; + Console.SetOut(new System.IO.StreamWriter(Console.OpenStandardOutput(), utf8) { AutoFlush = true }); + Console.SetError(new System.IO.StreamWriter(Console.OpenStandardError(), utf8) { AutoFlush = true }); + } + + + + private static void Log(string message) + { + Console.Write($"[{DateTime.Now:HH:mm:ss.fff}] {message}{Environment.NewLine}"); + Console.Out.Flush(); + } +} + diff --git a/src/Starward/Features/GameInstall/GameInstallProgressFormatter.cs b/src/Starward/Features/GameInstall/GameInstallProgressFormatter.cs new file mode 100644 index 000000000..55f23ff11 --- /dev/null +++ b/src/Starward/Features/GameInstall/GameInstallProgressFormatter.cs @@ -0,0 +1,102 @@ +using Starward.RPC.GameInstall; +using System; + +namespace Starward.Features.GameInstall; + +internal static class GameInstallProgressFormatter +{ + + + public static string GetInstallStateText(GameInstallState state) + { + return state switch + { + GameInstallState.Waiting => Lang.StartGameButton_Waiting, + GameInstallState.Downloading => Lang.DownloadGamePage_Downloading, + GameInstallState.Decompressing => Lang.DownloadGamePage_Decompressing, + GameInstallState.Merging => Lang.DownloadGamePage_Merging, + GameInstallState.Verifying => Lang.DownloadGamePage_Verifying, + GameInstallState.Paused => Lang.DownloadGamePage_Paused, + GameInstallState.Finish => Lang.DownloadGamePage_Finished, + GameInstallState.Error => Lang.DownloadGamePage_SomethingError, + GameInstallState.Queueing => Lang.StartGameButton_InQueue, + _ => "State Error", + }; + } + + + public static double GetProgressPercent(GameInstallContext task) + { + if (task.State is GameInstallState.Downloading) + { + double progress = task.Progress_DownloadTotalBytes > 0 + ? (double)task.Progress_DownloadFinishBytes / task.Progress_DownloadTotalBytes + : 0d; + + if (task.Operation is GameInstallOperation.Update && task.DownloadMode is GameInstallDownloadMode.Chunk) + { + progress = task.Progress_WriteTotalBytes > 0 + ? (double)task.Progress_WriteFinishBytes / task.Progress_WriteTotalBytes + : progress; + } + + return progress; + } + + if (task.State is GameInstallState.Decompressing or GameInstallState.Merging) + { + return task.Progress_Percent; + } + + if (task.State is GameInstallState.Finish) + { + return 1d; + } + + return task.Progress_Percent > 0 ? task.Progress_Percent : 0d; + } + + + public static string? ToBytesText(long finish, long total) + { + const double MB = 1 << 20; + const double GB = 1 << 30; + if (total == 0) + { + return null; + } + + if (total >= GB) + { + return $"{finish / GB:F2}/{total / GB:F2} GB"; + } + + return $"{finish / MB:F2}/{total / MB:F2} MB"; + } + + + public static string ToSpeedText(long bytes) + { + const double KB = 1 << 10; + const double MB = 1 << 20; + if (bytes >= MB) + { + return $"{bytes / MB:F2} MB/s"; + } + + return $"{bytes / KB:F2} KB/s"; + } + + + public static string ToRemainTimeText(long seconds) + { + if (seconds <= 0) + { + return "--:--:--"; + } + + var timeSpan = TimeSpan.FromSeconds(seconds); + return $"{(int)timeSpan.TotalHours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}"; + } +} + diff --git a/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs b/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs index 8dfae0a12..7e896b1db 100644 --- a/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs +++ b/src/Starward/Features/GameLauncher/StartGameButton.xaml.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; +using Starward.Features.GameInstall; using Starward.RPC.GameInstall; using System; using System.Windows.Input; @@ -198,16 +199,7 @@ private void Control_PointerExited(object sender, PointerRoutedEventArgs e) public string InstallStateText => InstallState switch { - GameInstallState.Waiting => Lang.StartGameButton_Waiting, - GameInstallState.Downloading => Lang.DownloadGamePage_Downloading, - GameInstallState.Decompressing => Lang.DownloadGamePage_Decompressing, - GameInstallState.Merging => Lang.DownloadGamePage_Merging, - GameInstallState.Verifying => Lang.DownloadGamePage_Verifying, - GameInstallState.Paused => Lang.DownloadGamePage_Paused, - GameInstallState.Finish => Lang.DownloadGamePage_Finished, - GameInstallState.Error => Lang.DownloadGamePage_SomethingError, - GameInstallState.Queueing => Lang.StartGameButton_InQueue, - _ => "State Error" + _ => GameInstallProgressFormatter.GetInstallStateText(InstallState), }; @@ -250,29 +242,18 @@ public void UpdateActionButtonStateWhenInstalling() public void UpdateGameInstallTaskState(GameInstallContext task) { InstallState = task.State; - DownloadBytesText = ToBytesText(task.Progress_DownloadFinishBytes, task.Progress_DownloadTotalBytes); - InstallBytesText = ToBytesText(task.Progress_WriteFinishBytes, task.Progress_WriteTotalBytes); + DownloadBytesText = GameInstallProgressFormatter.ToBytesText(task.Progress_DownloadFinishBytes, task.Progress_DownloadTotalBytes); + InstallBytesText = GameInstallProgressFormatter.ToBytesText(task.Progress_WriteFinishBytes, task.Progress_WriteTotalBytes); ErrorMessage = task.ErrorMessage; if (InstallState is GameInstallState.Downloading) { - long total = task.Progress_DownloadTotalBytes; - long finish = task.Progress_DownloadFinishBytes; - double progress = (double)finish / total; - DownloadSpeedText = ToSpeedText(task.NetworkDownloadSpeed); - InstallSpeedText = ToSpeedText(task.StorageWriteSpeed); - VerifySpeedText = ToSpeedText(task.StorageReadSpeed); - RemainTimeText = ToRemainTimeText(task.RemainTimeSeconds); - if (task.Operation is GameInstallOperation.Update && task.DownloadMode is GameInstallDownloadMode.Chunk) - { - progress = (double)task.Progress_WriteFinishBytes / task.Progress_WriteTotalBytes; - ProgressRingValue = (int)(progress * 100); - ProgressPercentText = $"{progress:P1}"; - } - else - { - ProgressRingValue = (int)(progress * 100); - ProgressPercentText = $"{progress:P1}"; - } + double progress = GameInstallProgressFormatter.GetProgressPercent(task); + DownloadSpeedText = GameInstallProgressFormatter.ToSpeedText(task.NetworkDownloadSpeed); + InstallSpeedText = GameInstallProgressFormatter.ToSpeedText(task.StorageWriteSpeed); + VerifySpeedText = GameInstallProgressFormatter.ToSpeedText(task.StorageReadSpeed); + RemainTimeText = GameInstallProgressFormatter.ToRemainTimeText(task.RemainTimeSeconds); + ProgressRingValue = (int)(progress * 100); + ProgressPercentText = $"{progress:P1}"; } else if (InstallState is GameInstallState.Decompressing or GameInstallState.Merging) { @@ -280,8 +261,9 @@ public void UpdateGameInstallTaskState(GameInstallContext task) InstallSpeedText = null; VerifySpeedText = null; RemainTimeText = "--:--:--"; - ProgressRingValue = (int)(task.Progress_Percent * 100); - ProgressPercentText = $"{task.Progress_Percent:P1}"; + double progress = GameInstallProgressFormatter.GetProgressPercent(task); + ProgressRingValue = (int)(progress * 100); + ProgressPercentText = $"{progress:P1}"; } else if (InstallState is GameInstallState.Finish) { @@ -306,50 +288,6 @@ public void UpdateGameInstallTaskState(GameInstallContext task) } - private static string? ToBytesText(long finish, long total) - { - const double MB = 1 << 20; - const double GB = 1 << 30; - if (total == 0) - { - return null; - } - if (total >= GB) - { - return $"{finish / GB:F2}/{total / GB:F2} GB"; - } - else - { - return $"{finish / MB:F2}/{total / MB:F2} MB"; - } - } - - - private static string ToSpeedText(long bytes) - { - const double KB = 1 << 10; - const double MB = 1 << 20; - if (bytes >= MB) - { - return $"{bytes / MB:F2} MB/s"; - } - else - { - return $"{bytes / KB:F2} KB/s"; - } - } - - - private static string? ToRemainTimeText(long seconds) - { - if (seconds == 0) - { - return "--:--:--"; - } - return TimeSpan.FromSeconds(seconds).ToString(@"hh\:mm\:ss"); - } - - private void StartGameButton_ActualThemeChanged(FrameworkElement sender, object args) { diff --git a/src/Starward/Program.cs b/src/Starward/Program.cs index 728b774a2..c29b38e2d 100644 --- a/src/Starward/Program.cs +++ b/src/Starward/Program.cs @@ -1,8 +1,7 @@ global using Starward.Language; using Microsoft.Extensions.Configuration; using Starward.Core; -using Starward.Core.HoYoPlay; -using Starward.Features.GameLauncher; +using Starward.Features.CommandLine; using Starward.Features.UrlProtocol; using Starward.RPC; using System; @@ -50,12 +49,13 @@ static void Main(string[] args) if (args[0].ToLower() is "startgame") { - GameBiz biz = (GameBiz)config.GetValue("biz"); - GameId? gameId = GameId.FromGameBiz(biz); - if (gameId is not null) - { - AppConfig.GetService().StartGameAsync(gameId).GetAwaiter().GetResult(); - } + GameCliRunner.StartGameAsync(args).GetAwaiter().GetResult(); + return; + } + + if (args[0].ToLower() is "updategame") + { + GameCliRunner.UpdateGameAsync(args).GetAwaiter().GetResult(); return; }