diff --git a/ProjBobcat/ProjBobcat.Sample/Commands/Game/GameLaunchCommands.cs b/ProjBobcat/ProjBobcat.Sample/Commands/Game/GameLaunchCommands.cs new file mode 100644 index 0000000..c19ba0b --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/Commands/Game/GameLaunchCommands.cs @@ -0,0 +1,109 @@ +using System.ComponentModel.DataAnnotations; +using Cocona; +using Microsoft.Extensions.Logging; +using ProjBobcat.Class.Helper; +using ProjBobcat.Class.Model; +using ProjBobcat.DefaultComponent.Authenticator; + +namespace ProjBobcat.Sample.Commands.Game; + +public class GameLaunchCommands(ILogger logger) +{ + [Command("launch", Aliases = ["run", "start", "play"], Description = "Launch the game.")] + public async Task LaunchGameAsync( + [Argument(Description = "Game Id")] string gameId, + [Argument(Description = "Offline account name")] string displayName, + [Argument(Description = "Java executable path")] string javaPath, + [Range(0, 5)] [Option("gc", Description = "Garbage collection type")] int gcType = 0, + [Range(1024, 1024 * 1024 * 10)] [Option("max-memory", Description = "Max memory size in MB")] uint maxMemory = 1024, + [Option("title", Description = "Custom game window title (Windows only)")] string windowTitle = "Minecraft - By ProjBobcat") + { + var version = GameHelper.GameCore.VersionLocator.GetGame(gameId); + + if (version == null) + { + logger.LogCritical( + "Game {GameId} not found. Please check if the game id is correct.", + gameId); + + return; + } + + var auth = new OfflineAuthenticator + { + LauncherAccountParser = GameHelper.GameCore.VersionLocator.LauncherAccountParser!, + Username = displayName + }; + var authResult = await auth.AuthTaskAsync(false); + + var args = new GameArguments + { + GcType = (GcType)gcType, + JavaExecutable = javaPath, + MaxMemory = maxMemory + }; + var ls = new LaunchSettings + { + Authenticator = auth, + Version = gameId, + GameArguments = args, + GameName = version.Name, + GamePath = GamePathHelper.GetVersionPath(GameHelper.GameCore.RootPath), + GameResourcePath = GameHelper.GameCore.RootPath, + LauncherName = "LauncherX (By ProjBobcat)", + VersionInsulation = true, + VersionLocator = GameHelper.GameCore.VersionLocator, + WindowTitle = windowTitle, + SelectedProfile = authResult.SelectedProfile + }; + + GameHelper.GameCore.LaunchLogEventDelegate += (_, eventArgs) => + { + logger.LogInformation("[{Time}] {Message}", eventArgs.ItemRunTime, eventArgs.Item); + }; + + GameHelper.GameCore.GameExitEventDelegate += (_, eventArgs) => + { + logger.LogInformation("Game exit with code {ExitCode}.", eventArgs.ExitCode); + }; + + GameHelper.GameCore.GameLogEventDelegate += (_, eventArgs) => + { + logger.LogInformation(eventArgs.RawContent); + }; + + var result = await GameHelper.GameCore.LaunchTaskAsync(ls); + var isSucceed = result.Error == null; + var errorReason = result.ErrorType switch + { + LaunchErrorType.AuthFailed => "AuthFailed", + LaunchErrorType.DecompressFailed => "DecompressFailed", + LaunchErrorType.IncompleteArguments => "IncompleteArgument", + LaunchErrorType.NoJava => "NoJava", + LaunchErrorType.None => "None", + LaunchErrorType.OperationFailed => "OperationFailed", + LaunchErrorType.Unknown => "Unknown", + var x => throw new ArgumentOutOfRangeException(x.ToString()) + }; + var detail = isSucceed + ? $"Duration: {result.RunTime:g}" + : $"{result.Error?.ErrorMessage ?? "Unknown"}, Reason: {errorReason}"; + + logger.Log(isSucceed ? LogLevel.Information : LogLevel.Critical, detail); + + if (!isSucceed && result.Error?.Exception != null) + { + logger.LogError(result.Error?.Exception, "Exception occurred when launching game."); + + return; + } + + if (result.GameProcess == null) return; + + logger.LogInformation("Game process started, wait for game to exit."); + + await result.GameProcess.WaitForExitAsync(); + + logger.LogInformation("Game process exited."); + } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/Commands/Game/GameManageCommands.cs b/ProjBobcat/ProjBobcat.Sample/Commands/Game/GameManageCommands.cs new file mode 100644 index 0000000..4715897 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/Commands/Game/GameManageCommands.cs @@ -0,0 +1,185 @@ +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using Cocona; +using Microsoft.Extensions.Logging; +using ProjBobcat.Class.Helper; +using ProjBobcat.Class.Model; +using ProjBobcat.Interface; +using ProjBobcat.Sample.DownloadMirrors; + +namespace ProjBobcat.Sample.Commands.Game; + +public class GameManageCommands(ILogger logger) +{ + [Command("search", Aliases = ["list", "find"], Description = "Search games on the local machine")] + public void GetLocalGames() + { + logger.LogInformation("Start searching games..."); + + var games = GameHelper.GameCore.VersionLocator.GetAllGames().ToImmutableList(); + + if (games.Count == 0) + { + logger.LogWarning("No games found."); + return; + } + + foreach (var game in games) + logger.LogInformation("Found game: {GameId}[{GameName}]", game.Id, game.Name); + } + + [Command("mirrors", Description = "Get download mirrors")] + public void GetDownloadMirrors() + { + string[] mirrors = + [ + nameof(OfficialDownloadMirror), + nameof(BangBangDownloadMirror), + nameof(McBbsDownloadMirror) + ]; + + for (var i = 0; i < mirrors.Length; i++) + { + var mirror = mirrors[i]; + var mirrorName = mirror[..^"DownloadMirror".Length]; + + logger.LogInformation("[{Index}] {MirrorName}", i, mirrorName); + } + } + + [Command("gc", Description = "Get garbage collection types")] + public void GetGcTypes() + { + var arr = Enum.GetNames(); + + for (var i = 0; i < arr.Length; i++) + { + var name = arr[i]; + + logger.LogInformation("[{Index}] {Name}", i, name); + } + } + + [Command("repair", Description = "Check game files and repair them if needed")] + public async Task RepairGameAsync( + [Argument(Description = "Game id")] string gameId, + [Range(0, 2)] + [Argument("mirror", Description = "Download mirror index, use [game mirrors] to reveal mirror indexes")] + int mirrorIndex = 0, + [Range(1, 32)] [Argument("parallel", Description = "Parallel tasks")] + int parallel = 8, + [Range(1, 128)] [Argument("download-threads", Description = "Download threads")] + int downloadThreads = 32, + [Range(1, 16)] [Argument("download-parts", Description = "Download parts")] + int downloadParts = 8, + [Option("verbose", Description = "Enable verbose mode")] + bool verbose = false, + [Option("disable-verify", Description = "Disable file verification")] + bool disableVerify = false, + [Option("uncheck-version", Description = "Disable Version Info checker")] + bool uncheckVersion = false, + [Option("uncheck-asset", Description = "Disable Asset Info checker")] + bool uncheckAsset = false, + [Option("uncheck-lib", Description = "Disable Library checker")] + bool uncheckLib = false) + { + logger.LogInformation("Start repairing game {GameId}...", gameId); + + var versionInfo = GameHelper.GameCore.VersionLocator.GetGame(gameId); + IDownloadMirror mirror = mirrorIndex switch + { + 0 => new OfficialDownloadMirror(), + 1 => new BangBangDownloadMirror(), + 2 => new McBbsDownloadMirror(), + _ => throw new ArgumentOutOfRangeException(nameof(mirrorIndex)) + }; + + if (versionInfo == null) + { + logger.LogCritical( + "Game {GameId} not found. Please check if the game id is correct.", + gameId); + + return; + } + + var resolvers = new List(); + + if (!uncheckVersion) + resolvers.Add(GameHelper.GetVersionInfoResolver(versionInfo)); + if (!uncheckAsset) + { + logger.LogInformation("Start to fetch Version Manifest from remote..."); + + var vm = await GameHelper.GetVersionManifestAsync(mirror); + + if (vm == null) + { + logger.LogCritical( + "Failed to fetch Version Manifest from remote. Please check your network connection."); + return; + } + + resolvers.Add(GameHelper.GetAssetInfoResolver(versionInfo, vm, mirror)); + } + + if (!uncheckLib) + resolvers.Add(GameHelper.GetLibraryInfoResolver(versionInfo, mirror)); + + if (resolvers.Count == 0) + { + logger.LogWarning("No resolver enabled. Please check your command."); + return; + } + + foreach (var resolver in resolvers) + logger.LogInformation("Resolver enabled: {Resolver}", resolver.GetType().Name); + + var completer = GameHelper.GetResourceCompleter( + resolvers, + parallel, + downloadThreads, + downloadParts, + !disableVerify); + + if (verbose) + { + completer.GameResourceInfoResolveStatus += (_, args) => + { + logger.LogInformation("[{Progress:P2}] {Message}", args.Progress, args.Status); + }; + } + + var progressVal = 0d; + + completer.DownloadFileChangedEvent += (_, args) => { progressVal = args.ProgressPercentage; }; + + completer.DownloadFileCompletedEvent += (sender, args) => + { + if (sender is not DownloadFile file) return; + + if (args.Error != null) + logger.LogError(args.Error, "Failed to download file {FileName}", file.FileName); + + + var isSuccess = args.Success == true ? "Success" : "Failed"; + var retry = file.RetryCount == 0 + ? null + : $""; + var fileName = file.FileName; + var speed = DownloadHelper.AutoFormatSpeedString(args.AverageSpeed); + var pD = $"<{file.FileType}>{retry}{isSuccess} {fileName} [{speed}]"; + + logger.LogInformation("[{Progress:P}] {ProgressDetail}", progressVal, pD); + }; + + logger.LogInformation("Start to check and download game {GameId}...", gameId); + + var rCResult = await completer.CheckAndDownloadTaskAsync(); + + if (rCResult.TaskStatus == TaskResultStatus.Error || (rCResult.Value?.IsLibDownloadFailed ?? false)) + logger.LogCritical("Failed to repair game {GameId}.", gameId); + else + logger.LogInformation("Game {GameId} repaired.", gameId); + } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/Commands/SystemInfoCommands.cs b/ProjBobcat/ProjBobcat.Sample/Commands/SystemInfoCommands.cs new file mode 100644 index 0000000..9d42963 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/Commands/SystemInfoCommands.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Cocona; +using Microsoft.Extensions.Logging; +using ProjBobcat.Class.Helper; + +namespace ProjBobcat.Sample.Commands; + +public class SystemInfoCommands(ILogger logger) +{ + [SupportedOSPlatform("windows10.0.10586")] + [SupportedOSPlatform(nameof(OSPlatform.OSX))] + [Command("running-native", Description = "Check if the current process is running on native platform.")] + public void CheckCpuTranslation() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + logger.LogError("This command is not supported on Linux."); + return; + } + + logger.LogInformation("Is running under translation: {IsRunningUnderTranslation}", + SystemInfoHelper.IsRunningUnderTranslation()); + } + + [SupportedOSPlatform(nameof(OSPlatform.Windows))] + [SupportedOSPlatform(nameof(OSPlatform.OSX))] + [SupportedOSPlatform(nameof(OSPlatform.Linux))] + [Command("find-java", Description = "Search Java on the local machine")] + public async Task SearchJavaAsync( + [Option("d", Description = "Enable deep search")] bool deepSearch) + { + logger.LogInformation("Searching Java..."); + + await foreach(var java in SystemInfoHelper.FindJava(deepSearch)) + logger.LogInformation("Found Java: {JavaPath}", java); + } + + [Command("arch", Description = "Get system arch")] + public void CheckSystemArch() + { + logger.LogInformation("System arch: {SystemArch}", SystemInfoHelper.GetSystemArch()); + } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/Commands/SystemMetricsCommands.cs b/ProjBobcat/ProjBobcat.Sample/Commands/SystemMetricsCommands.cs new file mode 100644 index 0000000..2188c68 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/Commands/SystemMetricsCommands.cs @@ -0,0 +1,34 @@ +using Cocona; +using Microsoft.Extensions.Logging; +using ProjBobcat.Class.Helper; + +namespace ProjBobcat.Sample.Commands; + +public class SystemMetricsCommands(ILogger logger) +{ + [Command("cpu", Description = "Get CPU usage")] + public async Task GetCpuUsageAsync( + [Argument(Description = "Sample count")] int sampleCount = 6) + { + for (var i = 0; i < sampleCount; i++) + { + var cpuUsage = SystemInfoHelper.GetProcessorUsage(); + logger.LogInformation("CPU usage: {CpuUsage}", cpuUsage); + + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } + + [Command("mem", Description = "Get memory usage")] + public async Task GetMemoryUsageAsync( + [Argument(Description = "Sample count")] int sampleCount = 6) + { + for (var i = 0; i < sampleCount; i++) + { + var memUsage = SystemInfoHelper.GetMemoryUsage(); + logger.LogInformation("Memory usage: {MemoryUsage}", memUsage); + + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/Commands/TestCommand.cs b/ProjBobcat/ProjBobcat.Sample/Commands/TestCommand.cs new file mode 100644 index 0000000..c666b44 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/Commands/TestCommand.cs @@ -0,0 +1,15 @@ +using Cocona; +using Microsoft.Extensions.Logging; + +namespace ProjBobcat.Sample.Commands; + +public class TestCommand(ILogger logger) +{ + readonly ILogger _logger = logger; + + [Command("hello")] + public void Hello() + { + _logger.LogInformation("Hello from Cocona."); + } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/BangBangDownloadMirror.cs b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/BangBangDownloadMirror.cs new file mode 100644 index 0000000..e07a2e3 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/BangBangDownloadMirror.cs @@ -0,0 +1,13 @@ +namespace ProjBobcat.Sample.DownloadMirrors; + +public record BangBangDownloadMirror : IDownloadMirror +{ + public string RootUri => "https://bmclapi2.bangbang93.com"; + public string VersionManifestJson => $"{RootUri}/mc/game/version_manifest.json"; + public string Assets => $"{RootUri}/assets/"; + public string Libraries => $"{RootUri}/maven/"; + public string Forge => $"{RootUri}/maven/"; + public string ForgeMaven => Forge; + public string ForgeMavenOld => Forge; + public string FabricMaven => $"{RootUri}/maven/"; +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/IDownloadMirror.cs b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/IDownloadMirror.cs new file mode 100644 index 0000000..614221a --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/IDownloadMirror.cs @@ -0,0 +1,13 @@ +namespace ProjBobcat.Sample.DownloadMirrors; + +public interface IDownloadMirror +{ + string? RootUri { get; } + string VersionManifestJson { get; } + string Assets { get; } + string Libraries { get; } + string Forge { get; } + string ForgeMaven { get; } + string ForgeMavenOld { get; } + string FabricMaven { get; } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/McBbsDownloadMirror.cs b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/McBbsDownloadMirror.cs new file mode 100644 index 0000000..be76a66 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/McBbsDownloadMirror.cs @@ -0,0 +1,13 @@ +namespace ProjBobcat.Sample.DownloadMirrors; + +public record McBbsDownloadMirror : IDownloadMirror +{ + public string RootUri => "https://download.mcbbs.net"; + public string VersionManifestJson => $"{RootUri}/mc/game/version_manifest.json"; + public string Assets => $"{RootUri}/assets/"; + public string Libraries => $"{RootUri}/maven/"; + public string Forge => $"{RootUri}/maven/"; + public string ForgeMaven => Forge; + public string ForgeMavenOld => Forge; + public string FabricMaven => $"{RootUri}/maven/"; +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/OfficialDownloadMirror.cs b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/OfficialDownloadMirror.cs new file mode 100644 index 0000000..26049ef --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/DownloadMirrors/OfficialDownloadMirror.cs @@ -0,0 +1,13 @@ +namespace ProjBobcat.Sample.DownloadMirrors; + +public record OfficialDownloadMirror : IDownloadMirror +{ + public string? RootUri => null; + public string VersionManifestJson => "https://launchermeta.mojang.com/mc/game/version_manifest.json"; + public string Assets => "https://resources.download.minecraft.net/"; + public string Libraries => "https://libraries.minecraft.net/"; + public string Forge => "https://files.minecraftforge.net/maven/"; + public string ForgeMaven => "https://maven.minecraftforge.net/"; + public string ForgeMavenOld => "https://files.minecraftforge.net/maven/"; + public string FabricMaven => "https://maven.fabricmc.net/"; +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/GameHelper.cs b/ProjBobcat/ProjBobcat.Sample/GameHelper.cs new file mode 100644 index 0000000..9218868 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/GameHelper.cs @@ -0,0 +1,144 @@ +using System.Net.Http.Json; +using ProjBobcat.Class.Helper; +using ProjBobcat.Class.Model; +using ProjBobcat.Class.Model.Mojang; +using ProjBobcat.DefaultComponent; +using ProjBobcat.DefaultComponent.Launch; +using ProjBobcat.DefaultComponent.Launch.GameCore; +using ProjBobcat.DefaultComponent.Logging; +using ProjBobcat.DefaultComponent.ResourceInfoResolver; +using ProjBobcat.Interface; +using ProjBobcat.Sample.DownloadMirrors; + +namespace ProjBobcat.Sample; + +public static class GameHelper +{ + static GameHelper() + { + GameCore = GetGameCore(Path.GetFullPath(".minecraft")); + } + + public static IGameCore GameCore { get; private set; } + static HttpClient Client => HttpClientHelper.DefaultClient; + + static DefaultGameCore GetGameCore(string rootPath) + { + var clientToken = Guid.Parse("ea6a16d6-2d03-4280-9b0b-279816834993"); + var fullRootPath = Path.GetFullPath(rootPath); + + return new DefaultGameCore + { + ClientToken = clientToken, + RootPath = rootPath, + VersionLocator = new DefaultVersionLocator(fullRootPath, clientToken) + { + LauncherProfileParser = + new DefaultLauncherProfileParser(fullRootPath, clientToken), + LauncherAccountParser = new DefaultLauncherAccountParser(fullRootPath, clientToken) + }, + GameLogResolver = new DefaultGameLogResolver() + }; + } + + public static async Task GetVersionManifestAsync( + IDownloadMirror downloadMirror) + { + VersionManifest? result = null; + var retryCount = 0; + + do + { + try + { + var vmUrl = downloadMirror.VersionManifestJson; + using var req = new HttpRequestMessage(HttpMethod.Get, vmUrl); + using var cts = new CancellationTokenSource(5000); + using var res = await Client.SendAsync(req, cts.Token); + + result = await res.Content.ReadFromJsonAsync( + VersionManifestContext.Default.VersionManifest, + cts.Token); + } + catch (TaskCanceledException) + { + // ignored + } + catch (Exception e) + { + Console.WriteLine(e); + } + finally + { + retryCount++; + await Task.Delay(1500 * retryCount); + } + } while (result == null && retryCount != 3); + + return result; + } + + public static IResourceInfoResolver GetVersionInfoResolver( + VersionInfo versionInfo) + { + return new VersionInfoResolver + { + BasePath = GameCore.RootPath, + VersionInfo = versionInfo, + CheckLocalFiles = true + }; + } + + public static IResourceInfoResolver GetAssetInfoResolver( + VersionInfo versionInfo, + VersionManifest versionManifest, + IDownloadMirror downloadMirror) + { + return new AssetInfoResolver + { + AssetIndexUriRoot = string.IsNullOrEmpty(downloadMirror.RootUri) + ? "https://launchermeta.mojang.com/" + : $"{downloadMirror.RootUri}/", + AssetUriRoot = downloadMirror.Assets, + BasePath = GameCore.RootPath, + VersionInfo = versionInfo, + CheckLocalFiles = true, + Versions = versionManifest.Versions + }; + } + + public static IResourceInfoResolver GetLibraryInfoResolver( + VersionInfo versionInfo, + IDownloadMirror downloadMirror) + { + return new LibraryInfoResolver + { + BasePath = GameCore.RootPath, + ForgeUriRoot = downloadMirror.Forge, + ForgeMavenUriRoot = downloadMirror.ForgeMaven, + ForgeMavenOldUriRoot = downloadMirror.ForgeMavenOld, + FabricMavenUriRoot = downloadMirror.FabricMaven, + LibraryUriRoot = downloadMirror.Libraries, + VersionInfo = versionInfo, + CheckLocalFiles = true + }; + } + + public static IResourceCompleter GetResourceCompleter( + IReadOnlyList resourceInfoResolvers, + int maxDegreeOfParallelism, + int downloadThreads, + int downloadParts, + bool verifyDownloadedFiles) + { + return new DefaultResourceCompleter + { + MaxDegreeOfParallelism = maxDegreeOfParallelism, + ResourceInfoResolvers = resourceInfoResolvers, + TotalRetry = 2, + CheckFile = verifyDownloadedFiles, + DownloadParts = downloadParts, + DownloadThread = downloadThreads + }; + } +} \ No newline at end of file diff --git a/ProjBobcat/ProjBobcat.Sample/Program.cs b/ProjBobcat/ProjBobcat.Sample/Program.cs new file mode 100644 index 0000000..ff5e959 --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/Program.cs @@ -0,0 +1,31 @@ +using Cocona; +using ProjBobcat.Sample.Commands; +using ProjBobcat.Sample.Commands.Game; +using Serilog; + +namespace ProjBobcat.Sample +{ + [HasSubCommands(typeof(GameManageCommands), "game", Description = "Game management commands")] + [HasSubCommands(typeof(SystemMetricsCommands), "usage", Description = "System usage metrics commands")] + [HasSubCommands(typeof(SystemInfoCommands), "utils", Description = "System information utilities")] + class Program + { + static void Main(string[] args) + { + var builder = CoconaApp.CreateBuilder(); + builder + .Host.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration + .ReadFrom.Configuration(hostingContext.Configuration) + .Enrich.FromLogContext() + .WriteTo.Console()); + + var app = builder.Build(); + + app.AddCommands(); + app.AddCommands(); + app.AddCommands(); + + app.Run(); + } + } +} diff --git a/ProjBobcat/ProjBobcat.Sample/ProjBobcat.Sample.csproj b/ProjBobcat/ProjBobcat.Sample/ProjBobcat.Sample.csproj new file mode 100644 index 0000000..87c39fa --- /dev/null +++ b/ProjBobcat/ProjBobcat.Sample/ProjBobcat.Sample.csproj @@ -0,0 +1,46 @@ + + + + Exe + net8.0 + enable + enable + true + en + + + + true + false + + + + + + + + + + + true + true + full + true + true + true + false + true + + + + + + + + + + + + + + diff --git a/ProjBobcat/ProjBobcat.sln b/ProjBobcat/ProjBobcat.sln index 2ab3e35..aead6d5 100644 --- a/ProjBobcat/ProjBobcat.sln +++ b/ProjBobcat/ProjBobcat.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29728.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjBobcat", "ProjBobcat\ProjBobcat.csproj", "{9777E280-8601-4A6C-BD75-0D29FD4F5D6F}" EndProject @@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjBobcat.Sample", "ProjBobcat.Sample\ProjBobcat.Sample.csproj", "{65C4CA49-E835-4AFD-A46E-7E188A821B0D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,6 +22,10 @@ Global {9777E280-8601-4A6C-BD75-0D29FD4F5D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9777E280-8601-4A6C-BD75-0D29FD4F5D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9777E280-8601-4A6C-BD75-0D29FD4F5D6F}.Release|Any CPU.Build.0 = Release|Any CPU + {65C4CA49-E835-4AFD-A46E-7E188A821B0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65C4CA49-E835-4AFD-A46E-7E188A821B0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65C4CA49-E835-4AFD-A46E-7E188A821B0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65C4CA49-E835-4AFD-A46E-7E188A821B0D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ProjBobcat/ProjBobcat/ProjBobcat.csproj b/ProjBobcat/ProjBobcat/ProjBobcat.csproj index 17758ec..e92b107 100644 --- a/ProjBobcat/ProjBobcat/ProjBobcat.csproj +++ b/ProjBobcat/ProjBobcat/ProjBobcat.csproj @@ -66,7 +66,7 @@ resolved the issue that LaunchWrapper may not return the correct exit code - 0.34.2 + 0.35.0