diff --git a/CurseForge.Minecraft.Serverpack.Launcher/Classes/CurseForgeFile.cs b/CurseForge.Minecraft.Serverpack.Launcher/Classes/CurseForgeFile.cs index 84e6c27..97922f3 100644 --- a/CurseForge.Minecraft.Serverpack.Launcher/Classes/CurseForgeFile.cs +++ b/CurseForge.Minecraft.Serverpack.Launcher/Classes/CurseForgeFile.cs @@ -5,9 +5,9 @@ namespace CurseForge.Minecraft.Serverpack.Launcher public class CurseForgeFile { [JsonPropertyName("projectID")] - public long ProjectId { get; set; } + public uint ProjectId { get; set; } [JsonPropertyName("fileID")] - public long FileId { get; set; } + public uint FileId { get; set; } [JsonPropertyName("required")] public bool Required { get; set; } } diff --git a/CurseForge.Minecraft.Serverpack.Launcher/CommandArguments.cs b/CurseForge.Minecraft.Serverpack.Launcher/CommandArguments.cs index e3abe5f..b29a917 100644 --- a/CurseForge.Minecraft.Serverpack.Launcher/CommandArguments.cs +++ b/CurseForge.Minecraft.Serverpack.Launcher/CommandArguments.cs @@ -19,7 +19,7 @@ private static RootCommand SetupCommand() SetupArguments(command); SetupOptions(command); - command.Handler = CommandHandler.Create(async (projectid, fileid, serverPath, javaArgs, startServer) => { + command.Handler = CommandHandler.Create(async (projectid, fileid, serverPath, javaArgs, startServer) => { return await InstallServer(projectid, fileid, serverPath, javaArgs, startServer); }); @@ -32,8 +32,29 @@ private static void SetupSubcommand(RootCommand command) description: @"The interactive mode lets you search and select what modpack you want to use. This will search for modpacks from CurseForge."); - interactive.Handler = CommandHandler.Create(async () => { - return await InteractiveInstallation(); + interactive.AddArgument(new("automaticInstaller") + { + ArgumentType = typeof(bool), + Arity = ArgumentArity.ZeroOrOne, + Description = "Runs the installer even more automatic" + }); + + interactive.AddArgument(new("projectId") + { + ArgumentType = typeof(uint), + Arity = ArgumentArity.ZeroOrOne, + Description = "ProjectId for the modpack" + }); + + interactive.AddArgument(new("fileId") + { + ArgumentType = typeof(string), + Arity = ArgumentArity.ZeroOrOne, + Description = "FileId (or \"latest\") for the modpack" + }); + + interactive.Handler = CommandHandler.Create(async (automaticInstaller, projectId, fileId) => { + return await InteractiveInstallation(automaticInstaller, projectId, fileId); }); command.Add(interactive); @@ -102,14 +123,14 @@ private static void SetupArguments(RootCommand command) { command.AddArgument(new("projectid") { - ArgumentType = typeof(int), + ArgumentType = typeof(uint), Arity = ArgumentArity.ZeroOrOne, Description = "Sets the project id / modpack id to use", }); command.AddArgument(new("fileid") { - ArgumentType = typeof(int), + ArgumentType = typeof(uint), Description = "Sets the file id to use" }); diff --git a/CurseForge.Minecraft.Serverpack.Launcher/InteractiveInstaller.cs b/CurseForge.Minecraft.Serverpack.Launcher/InteractiveInstaller.cs index 2c9c797..4d49bb7 100644 --- a/CurseForge.Minecraft.Serverpack.Launcher/InteractiveInstaller.cs +++ b/CurseForge.Minecraft.Serverpack.Launcher/InteractiveInstaller.cs @@ -10,13 +10,19 @@ namespace CurseForge.Minecraft.Serverpack.Launcher { partial class Program { - private static async Task InteractiveInstallation() + private static async Task InteractiveInstallation(bool? automaticInstaller, uint? projectId, string fileId) { if (!CheckRequiredDependencies()) { return -1; } + if (automaticInstaller.HasValue && automaticInstaller.Value) + { + Console.WriteLine("Automatic modpack server installer activated"); + Console.WriteLine("ProjectId: {0}, FileId: {1}", projectId, fileId); + } + Console.WriteLine("Activating interactive mode. Please follow the instructions."); Console.WriteLine("If you want to know other ways to use this, please use the argument --help"); Console.WriteLine(); @@ -51,48 +57,88 @@ private static async Task InteractiveInstallation() Console.WriteLine(); - AnsiConsole.Write(new Rule("Search modpack to install")); - - var searchType = AnsiConsole.Prompt(new SelectionPrompt() - .Title("Do you want to search by [orange1 bold]project id[/] or [orange1 bold]project name[/]?") - .AddChoices(new[] - { - "Project Id", - "Project Name" - }) - .HighlightStyle(new Style(Color.Orange1))); - - Console.WriteLine($"Searching with {searchType}"); - GetCfApiInformation(out var cfApiKey, out var cfPartnerId, out var cfContactEmail, out var errors); if (errors.Count > 0) { - AnsiConsole.WriteLine("[bold red]Please resolve the errors before continuing.[/]"); + AnsiConsole.MarkupLine("[bold red]Please resolve the errors before continuing.[/]"); return -1; } using ApiClient cfApiClient = new(cfApiKey, cfPartnerId, cfContactEmail); - try + if (!projectId.HasValue) { - await cfApiClient.GetGamesAsync(); - } - catch - { - Console.WriteLine("Error: Could not contact the CurseForge API, please check your API key"); - return -1; - } + AnsiConsole.Write(new Rule("Search modpack to install")); - if (searchType == "Project Id") - { - while (!await HandleProjectIdSearch(cfApiClient)) - { } + var searchType = AnsiConsole.Prompt(new SelectionPrompt() + .Title("Do you want to search by [orange1 bold]project id[/] or [orange1 bold]project name[/]?") + .AddChoices(new[] + { + "Project Id", + "Project Name" + }) + .HighlightStyle(new Style(Color.Orange1))); + + Console.WriteLine($"Searching with {searchType}"); + + try + { + await cfApiClient.GetGamesAsync(); + } + catch + { + Console.WriteLine("Error: Could not contact the CurseForge API, please check your API key"); + return -1; + } + + if (searchType == "Project Id") + { + while (!await HandleProjectIdSearch(cfApiClient)) + { } + } + else + { + while (!await HandleProjectSearch(cfApiClient)) + { } + } } else { - while (!await HandleProjectSearch(cfApiClient)) - { } + var _selectedMod = await cfApiClient.GetModAsync(projectId.Value); + if (_selectedMod?.Data == null) + { + Console.Write($"Error: Project {projectId} does not exist"); + return -1; + } + + selectedMod = _selectedMod.Data; + + if (fileId == "latest") + { + var versions = await cfApiClient.GetModFilesAsync(selectedMod.Id); + var validVersions = versions.Data.Where(v => v.FileStatus == APIClient.Models.Files.FileStatus.Approved); + + var latestVersion = validVersions.OrderByDescending(c => c.FileDate).First(); + + selectedVersion = latestVersion; + } + else + { + if (!uint.TryParse(fileId, out var _fileId)) + { + Console.WriteLine("Error: Use either \"latest\" or a file id for the version"); + return -1; + } + var _selectedFile = await cfApiClient.GetModFileAsync(projectId.Value, _fileId); + if (_selectedFile?.Data == null) + { + Console.Write($"Error: File {fileId} does not exist"); + return -1; + } + + selectedVersion = _selectedFile.Data; + } } if (selectedMod == null) @@ -101,8 +147,11 @@ private static async Task InteractiveInstallation() return -1; } - while (!await HandleProjectVersionSearch(cfApiClient, selectedMod)) - { } + if (selectedVersion == null) + { + while (!await HandleProjectVersionSearch(cfApiClient, selectedMod)) + { } + } if (selectedVersion == null) { @@ -116,13 +165,9 @@ private static async Task InteractiveInstallation() return 1; } - var javaArgs = string.Empty; var startServer = AnsiConsole.Confirm("Do you want to start the server directly?"); - if (startServer) - { - javaArgs = AnsiConsole.Ask("Do you want any [orange1 bold]java arguments[/] for the server?"); - } + var javaArgs = AnsiConsole.Ask("Do you want any [orange1 bold]java arguments[/] for the server?", "-Xms4G -Xmx4G"); await InstallServer(selectedMod.Id, selectedVersion.Id, serverPath, javaArgs, startServer); @@ -178,7 +223,7 @@ private static async Task HandleProjectSearch(ApiClient cfApiClient) if (modResults.Pagination.TotalCount > modResults.Pagination.ResultCount) { - int index = modResults.Pagination.Index; + uint index = modResults.Pagination.Index; while (modsFound.Count < modResults.Pagination.TotalCount) { ctx.Status($"Fetching more results ({modResults.Pagination.PageSize * (index + 1)} / {modResults.Pagination.TotalCount})"); @@ -216,7 +261,7 @@ private static async Task HandleProjectSearch(ApiClient cfApiClient) private static async Task HandleProjectIdSearch(ApiClient cfApiClient) { var projectId = AnsiConsole.Prompt( - new TextPrompt( + new TextPrompt( "Enter [orange1 bold]Project Id[/] of the modpack" ).ValidationErrorMessage("Please enter a valid [orange1 bold]Project Id[/] for a modpack from CurseForge") .Validate(l => l > 0 ? ValidationResult.Success() : ValidationResult.Error("[orange1 bold]Project Ids[/] cannot be negative")) diff --git a/CurseForge.Minecraft.Serverpack.Launcher/Program.cs b/CurseForge.Minecraft.Serverpack.Launcher/Program.cs index 46ebabd..3815173 100644 --- a/CurseForge.Minecraft.Serverpack.Launcher/Program.cs +++ b/CurseForge.Minecraft.Serverpack.Launcher/Program.cs @@ -4,7 +4,6 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; @@ -34,15 +33,50 @@ private static async Task Main(params string[] args) var command = SetupCommand(); if (args.Length == 0) { - await command.InvokeAsync("interactive"); - Console.ReadKey(); + var automaticInstall = await CheckProcessNameForAutomaticInstall(command); + if (!automaticInstall) + { + await command.InvokeAsync("interactive"); + Console.ReadKey(); - return 0; + return 0; + } + else + { + Console.ReadKey(); + return 0; + } } return await command.InvokeAsync(args); } + private async static Task CheckProcessNameForAutomaticInstall(RootCommand command) + { + var currentProcess = Process.GetCurrentProcess().ProcessName; + + Console.WriteLine(currentProcess); + + if (currentProcess.Equals("cf-mc-server", StringComparison.InvariantCultureIgnoreCase)) + { + return false; + } + + if (!currentProcess.StartsWith("cf-", StringComparison.InvariantCultureIgnoreCase) || !currentProcess.EndsWith("-server", StringComparison.InvariantCultureIgnoreCase)) + { + return false; + } + + var processNameArguments = currentProcess.Split('-'); + + var projectId = processNameArguments[2]; + var fileId = processNameArguments[3]; + + await command.InvokeAsync($"interactive true {projectId} {fileId}"); + + return true; + } + private static bool TryDirectoryPath(string path) { try @@ -60,7 +94,7 @@ private static bool TryDirectoryPath(string path) } } - private static async Task InstallServer(int modId, int fileId, string path, string javaArgs, bool startServer) + private static async Task InstallServer(uint modId, uint fileId, string path, string javaArgs, bool startServer) { GetCfApiInformation(out var cfApiKey, out var cfPartnerId, out var cfContactEmail, out var errors); @@ -118,13 +152,13 @@ private static async Task InstallServer(int modId, int fileId, string path, if (!modInfo.Data.Categories.Any(c => c.ClassId == 4471)) { // Not a modpack - AnsiConsole.WriteLine("[bold red]Error: Project is not a modpack, not allowed in current version of server launcher[/]"); + AnsiConsole.MarkupLine("[bold red]Error: Project is not a modpack, not allowed in current version of server launcher[/]"); return -1; } if (!(modInfo.Data.AllowModDistribution ?? true) || !modInfo.Data.IsAvailable) { - AnsiConsole.WriteLine("[bold red]The author of this modpack has not made it available for download through third party tools.[/]"); + AnsiConsole.MarkupLine("[bold red]The author of this modpack has not made it available for download through third party tools.[/]"); return -1; } @@ -134,12 +168,14 @@ private static async Task InstallServer(int modId, int fileId, string path, var installPath = Path.Combine(path, "installed", modInfo.Data.Slug); var manifestPath = Path.Combine(installPath, "manifest.json"); - if (!File.Exists(dlPath)) + if (!File.Exists(dlPath) || modFile.Data.FileLength != (ulong)new FileInfo(dlPath).Length) { -#pragma warning disable SYSLIB0014 // Type or member is obsolete - using WebClient wc = new(); -#pragma warning restore SYSLIB0014 // Type or member is obsolete - await wc.DownloadFileTaskAsync(modFile.Data.DownloadUrl, dlPath); + // Removes the file, if we have a unfinished download (or if the size differs) + File.Delete(dlPath); + + using HttpClient wc = new(); + var dlBytes = await wc.GetByteArrayAsync(modFile.Data.DownloadUrl); + await File.WriteAllBytesAsync(dlPath, dlBytes); } if (!Directory.Exists(installPath)) diff --git a/CurseForge.Minecraft.Serverpack.Launcher/Properties/launchSettings.json b/CurseForge.Minecraft.Serverpack.Launcher/Properties/launchSettings.json index db99bce..37f9797 100644 --- a/CurseForge.Minecraft.Serverpack.Launcher/Properties/launchSettings.json +++ b/CurseForge.Minecraft.Serverpack.Launcher/Properties/launchSettings.json @@ -7,6 +7,13 @@ "Specific modpack": { "commandName": "Project", "commandLineArgs": "477455 3295539 \"c:\\mc-server\"" + }, + "No arguments": { + "commandName": "Project" + }, + "Automatic installer": { + "commandName": "Project", + "commandLineArgs": "interactive true 542763 latest" } } } \ No newline at end of file