Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions Tools/AuthPatcher/PatreonPatcher/Cli/CliHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@

using PatreonPatcher.Core;
using PatreonPatcher.Core.Helpers;
using PatreonPatcher.Core.Logging;
using System.CommandLine;
using System.CommandLine.IO;
using System.Runtime.InteropServices;

internal static class CliHelpers
{
public static bool IsValidUnityDirectory(string path)
{
if (File.Exists(path))
{
path = Path.GetDirectoryName(path) ?? string.Empty;
}

return File.Exists(Path.Combine(path, Constants.UnityPlayerAssembly));
}

public static async Task<string?> WaitUserSelectGameExecutable(CancellationToken ct = default)
{
// check if OS is windows
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Log.Error("This feature is only available on Windows.");
throw new PlatformNotSupportedException("This feature is only available on Windows.");
}
Task<string?> task = Task.Run(() =>
{
string? gameExecutable;
do
{
gameExecutable = WindowsUtils.ShowOpenFileDialog($"AntroHeat.exe\0*.exe\0");
if (gameExecutable == null || !IsValidUnityDirectory(gameExecutable))
{
if (!WindowsUtils.ShowOkCancelMessageBox("Please select the game executable or press cancel.", "Invalid game executable"))
{
return null;
}
gameExecutable = null;
}
} while (gameExecutable == null);
return gameExecutable;
}, ct);
return await task;
}

public static Task WaitForUserInputAsync()
{
return Console.IsOutputRedirected || Console.IsErrorRedirected
? Task.CompletedTask
: Task.Run(() =>
{
Console.WriteLine("Press any key to continue...");
_ = Console.ReadKey(true);
});
}

public static void WriteErrorMessage(IConsole console)
{
console.WriteLine(
@"
_____ _ _ _ _
/ ____| | | | | (_) | |
| (___ ___ _ __ ___ ___| |_| |__ _ _ __ __ _ __ _____ _ __ | |_ __ ___ __ ___ _ __ __ _
\___ \ / _ \| '_ ` _ \ / _ \ __| '_ \| | '_ \ / _` | \ \ /\ / / _ \ '_ \| __| \ \ /\ / / '__/ _ \| '_ \ / _` |
____) | (_) | | | | | | __/ |_| | | | | | | | (_| | \ V V / __/ | | | |_ \ V V /| | | (_) | | | | (_| |
|_____/ \___/|_| |_| |_|\___|\__|_| |_|_|_| |_|\__, | \_/\_/ \___|_| |_|\__| \_/\_/ |_| \___/|_| |_|\__, |
__/ | __/ |
|___/ |___/
", ConsoleColor.Red);
console.WriteLine("Sorry for the inconvenience, but the game could not be patched.");
console.WriteLine("If you believe this is an error, please open an issue at:");
console.WriteLine("https://github.com/OpenYiffGames/HeatGame/issues", ConsoleColor.Blue);
}

public static void WriteDoneMessage(IConsole console)
{
console.WriteLine(
@"
____ _
| _ \ ___ _ __ ___| |
| | | |/ _ \| '_ \ / _ \ |
| |_| | (_) | | | | __/_|
|____/ \___/|_| |_|\___(_)
", ConsoleColor.Green);
console.WriteLine("Patching successful!");
}
}
113 changes: 113 additions & 0 deletions Tools/AuthPatcher/PatreonPatcher/Cli/Commands/PatchCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using PatreonPatcher.Core;
using PatreonPatcher.Core.Logging;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Diagnostics.CodeAnalysis;

namespace PatreonPatcher.Cli.Commands;

[RequiresDynamicCode("Calls PatreonPatcher.src.Patcher.PatchAsync()")]
internal class PatchCommand : RootCommand, ICommandHandler
{
public const string CliModeOptionName = "--cli";

private readonly Option<bool> cliModeOption = new(
name: CliModeOptionName,
description: "Used internally on windows to signal if not running in CLI mode (no GUI)",
getDefaultValue: () => true)
{
IsRequired = false,
IsHidden = true
};

private readonly Argument<FileInfo?> gameExecutableArgument = new()
{
Name = "gameExecutable",
Description = "The path to the game executable.",
};

public PatchCommand(PlatformID platformID) : base("")
{
AddArgument(gameExecutableArgument);
AddOption(cliModeOption);
AddValidator(CommandValidator);
Handler = this;

ArgumentArity arity = platformID switch
{
PlatformID.Win32NT => ArgumentArity.ZeroOrOne,
PlatformID.Unix => ArgumentArity.ExactlyOne,
_ => ArgumentArity.ExactlyOne
};
string workingDirectory = Environment.CurrentDirectory;
gameExecutableArgument.Arity = arity;
gameExecutableArgument.Completions.Add(new DirectoryFilesCompletionSource(new DirectoryInfo(workingDirectory))
{
MatchFilterPredicate = (file) =>
CliHelpers.IsValidUnityDirectory(file.FullName),
FileFilter = platformID switch
{
PlatformID.Win32NT => "*.exe",
PlatformID.Unix => "*",
_ => "*"
}
});
}

public int Invoke(InvocationContext context)
{
return SyncWithAsync(context);
}

public async Task<int> InvokeAsync(InvocationContext context)
{
FileInfo gameExecutable = context.ParseResult.GetValueForArgument(gameExecutableArgument)
?? throw new InvalidOperationException("Game executable is null.");

Log.Debug("running [PatchCommand] Game executable: " + gameExecutable.FullName);

IConsole console = context.Console;
Patcher patcher = Patcher.Create(gameExecutable.FullName);
bool ok = await patcher.PatchAsync();
if (ok)
{
Log.Info("Patch completed successfully.");
CliHelpers.WriteDoneMessage(console);
}
else
{
Log.Error("Patch failed.");
CliHelpers.WriteErrorMessage(console);
}
bool cliMode = context.ParseResult.GetValueForOption(cliModeOption);
if (!cliMode)
{
await CliHelpers.WaitForUserInputAsync();
}
return ok ? 0 : 1;
}

private void CommandValidator(CommandResult command)
{
FileInfo? gameExecutable = command.GetValueForArgument(gameExecutableArgument);
if (gameExecutable is null)
{
return;
}
if (!gameExecutable.Exists)
{
command.ErrorMessage = $"The file '{gameExecutable.FullName}' does not exist.";
}
if (!CliHelpers.IsValidUnityDirectory(gameExecutable.DirectoryName ?? gameExecutable.FullName))
{
command.ErrorMessage = $"The file '{gameExecutable.FullName}' is not a valid Unity executable.";
}
}

private int SyncWithAsync(InvocationContext context)
{
Task<int> task = InvokeAsync(context);
return task.GetAwaiter().GetResult();
}
}
78 changes: 78 additions & 0 deletions Tools/AuthPatcher/PatreonPatcher/Cli/ConsoleExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

using System.CommandLine;
using System.CommandLine.IO;
using System.Runtime.InteropServices;

internal static class ConsoleExtensions
{
public static void Write(this IConsole console, string value, ConsoleColor color)
{
if (NoColor || console.IsOutputRedirected || !PlataformSupportsAnsi())
{
console.Write(value);
return;
}
string ansiColorCode = GetAnsiColorCode(color);
console.Write(ansiColorCode);
console.Write(value);
console.Write(AnsiReset);
}

public static void WriteLine(this IConsole console, string value, ConsoleColor color)
{
if (NoColor || console.IsOutputRedirected || !PlataformSupportsAnsi())
{
console.WriteLine(value);
return;
}
string ansiColorCode = GetAnsiColorCode(color);
console.Write(ansiColorCode);
console.Write(value);
console.WriteLine(AnsiReset);
}

private const string AnsiReset = "\u001b[0m";

public static bool NoColor => Environment.GetEnvironmentVariable("NO_COLOR") != null;

private static bool PlataformSupportsAnsi()
{
var currentPlatform = Environment.OSVersion.Platform;
if (currentPlatform == PlatformID.Win32NT)
{
return IsWindows10OrGreater();
}
return currentPlatform is PlatformID.Unix or PlatformID.MacOSX;
}

private static bool IsWindows10OrGreater()
{
_ = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
return Environment.OSVersion.Version.Major >= 10;
}

private static string GetAnsiColorCode(ConsoleColor color)
{
const string commandBase = $"\u001b[";
return color switch
{
ConsoleColor.Black => $"{commandBase}30m",
ConsoleColor.DarkRed => $"{commandBase}31m",
ConsoleColor.DarkGreen => $"{commandBase}32m",
ConsoleColor.DarkYellow => $"{commandBase}33m",
ConsoleColor.DarkBlue => $"{commandBase}34m",
ConsoleColor.DarkMagenta => $"{commandBase}35m",
ConsoleColor.DarkCyan => $"{commandBase}36m",
ConsoleColor.Gray => $"{commandBase}37m",
ConsoleColor.DarkGray => $"{commandBase}90m",
ConsoleColor.Red => $"{commandBase}91m",
ConsoleColor.Green => $"{commandBase}92m",
ConsoleColor.Yellow => $"{commandBase}93m",
ConsoleColor.Blue => $"{commandBase}94m",
ConsoleColor.Magenta => $"{commandBase}95m",
ConsoleColor.Cyan => $"{commandBase}96m",
ConsoleColor.White => $"{commandBase}97m",
_ => $"{commandBase}0m" // reset
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.CommandLine.Completions;

namespace PatreonPatcher.Cli
{
internal class DirectoryFilesCompletionSource : ICompletionSource
{
private readonly DirectoryInfo _directoryInfo;
public string? FileFilter { get; set; } = null;
public MatchType MatchType { get; set; } = MatchType.Win32;
public MatchCasing MatchCasing { get; set; } = MatchCasing.CaseInsensitive;
public Func<FileInfo, bool> MatchFilterPredicate { get; set; } = _ => true;

private string AllFilesFilter => MatchType switch
{
MatchType.Win32 => "*.*",
MatchType.Simple => "*",
_ => "*.*"
};

public DirectoryFilesCompletionSource(DirectoryInfo directoryInfo)
{
_directoryInfo = directoryInfo;
}

public IEnumerable<CompletionItem> GetCompletions(CompletionContext context)
{
string possibleFileName = context.WordToComplete;

IEnumerable<CompletionItem> matchingFiles = _directoryInfo.GetFiles(FileFilter ?? AllFilesFilter, new EnumerationOptions()
{
MatchType = MatchType,
MatchCasing = MatchCasing,
MaxRecursionDepth = 1,
})
.Where(file => file.Name.StartsWith(possibleFileName, StringComparison.OrdinalIgnoreCase))
.Where(MatchFilterPredicate)
.Select(file => new CompletionItem(file.Name));

return matchingFiles;
}
}
}
Loading
Loading