Skip to content

Commit

Permalink
feat: Ability to have a specially named executable to install a certa…
Browse files Browse the repository at this point in the history
…in modpack and version (or latest)

fixes #18, fixes #19
  • Loading branch information
itssimple committed Jun 23, 2022
1 parent 296e3cf commit f4a2f66
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
Expand Down
31 changes: 26 additions & 5 deletions CurseForge.Minecraft.Serverpack.Launcher/CommandArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private static RootCommand SetupCommand()
SetupArguments(command);
SetupOptions(command);

command.Handler = CommandHandler.Create<int, int, string, string, bool>(async (projectid, fileid, serverPath, javaArgs, startServer) => {
command.Handler = CommandHandler.Create<uint, uint, string, string, bool>(async (projectid, fileid, serverPath, javaArgs, startServer) => {
return await InstallServer(projectid, fileid, serverPath, javaArgs, startServer);
});

Expand All @@ -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<bool?, uint?, string>(async (automaticInstaller, projectId, fileId) => {
return await InteractiveInstallation(automaticInstaller, projectId, fileId);
});

command.Add(interactive);
Expand Down Expand Up @@ -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"
});

Expand Down
121 changes: 83 additions & 38 deletions CurseForge.Minecraft.Serverpack.Launcher/InteractiveInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ namespace CurseForge.Minecraft.Serverpack.Launcher
{
partial class Program
{
private static async Task<int> InteractiveInstallation()
private static async Task<int> 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();
Expand Down Expand Up @@ -51,48 +57,88 @@ private static async Task<int> InteractiveInstallation()

Console.WriteLine();

AnsiConsole.Write(new Rule("Search modpack to install"));

var searchType = AnsiConsole.Prompt(new SelectionPrompt<string>()
.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<string>()
.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)
Expand All @@ -101,8 +147,11 @@ private static async Task<int> InteractiveInstallation()
return -1;
}

while (!await HandleProjectVersionSearch(cfApiClient, selectedMod))
{ }
if (selectedVersion == null)
{
while (!await HandleProjectVersionSearch(cfApiClient, selectedMod))
{ }
}

if (selectedVersion == null)
{
Expand All @@ -116,13 +165,9 @@ private static async Task<int> InteractiveInstallation()
return 1;
}

var javaArgs = string.Empty;
var startServer = AnsiConsole.Confirm("Do you want to start the server directly?");

if (startServer)
{
javaArgs = AnsiConsole.Ask<string>("Do you want any [orange1 bold]java arguments[/] for the server?");
}
var javaArgs = AnsiConsole.Ask<string>("Do you want any [orange1 bold]java arguments[/] for the server?", "-Xms4G -Xmx4G");

await InstallServer(selectedMod.Id, selectedVersion.Id, serverPath, javaArgs, startServer);

Expand Down Expand Up @@ -178,7 +223,7 @@ private static async Task<bool> 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})");
Expand Down Expand Up @@ -216,7 +261,7 @@ private static async Task<bool> HandleProjectSearch(ApiClient cfApiClient)
private static async Task<bool> HandleProjectIdSearch(ApiClient cfApiClient)
{
var projectId = AnsiConsole.Prompt(
new TextPrompt<int>(
new TextPrompt<uint>(
"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"))
Expand Down
60 changes: 48 additions & 12 deletions CurseForge.Minecraft.Serverpack.Launcher/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -34,15 +33,50 @@ private static async Task<int> 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<bool> 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
Expand All @@ -60,7 +94,7 @@ private static bool TryDirectoryPath(string path)
}
}

private static async Task<int> InstallServer(int modId, int fileId, string path, string javaArgs, bool startServer)
private static async Task<int> InstallServer(uint modId, uint fileId, string path, string javaArgs, bool startServer)
{
GetCfApiInformation(out var cfApiKey, out var cfPartnerId, out var cfContactEmail, out var errors);

Expand Down Expand Up @@ -118,13 +152,13 @@ private static async Task<int> 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;
}

Expand All @@ -134,12 +168,14 @@ private static async Task<int> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}

0 comments on commit f4a2f66

Please sign in to comment.