Clean BC converter, implement port to windows function

mac
Miepee 3 years ago
parent 9847521e70
commit 3c95489227

@ -20,6 +20,7 @@ internal static class Program
var interactiveOption = new Option<bool>(new[] { "-i", "--interactive" }, "Use an interactive mode. This will ignore all other options."); var interactiveOption = new Option<bool>(new[] { "-i", "--interactive" }, "Use an interactive mode. This will ignore all other options.");
var fileOption = new Option<FileInfo>(new[] { "-f", "--file" }, "The file path to the raw mod that should be ported. *REQUIRED IN NON-INTERACTIVE*"); var fileOption = new Option<FileInfo>(new[] { "-f", "--file" }, "The file path to the raw mod that should be ported. *REQUIRED IN NON-INTERACTIVE*");
var windowsOption = new Option<FileInfo>(new[] { "-w", "--windows" }, "The output file path for the Windows mod. None given equals to no Windows port.");
var linuxOption = new Option<FileInfo>(new[] { "-l", "--linux" }, "The output file path for the Linux mod. None given equals to no Linux port."); var linuxOption = new Option<FileInfo>(new[] { "-l", "--linux" }, "The output file path for the Linux mod. None given equals to no Linux port.");
var androidOption = new Option<FileInfo>(new[] { "-a", "--android" }, "The output file path for the Android mod. None given equals to no Android port."); var androidOption = new Option<FileInfo>(new[] { "-a", "--android" }, "The output file path for the Android mod. None given equals to no Android port.");
var macOption = new Option<FileInfo>(new[] { "-m", "--mac" }, "The output file path for the Mac mod. None given equals to no Mac port."); var macOption = new Option<FileInfo>(new[] { "-m", "--mac" }, "The output file path for the Mac mod. None given equals to no Mac port.");
@ -32,13 +33,14 @@ internal static class Program
// TODO: double check whether its not possible to have the same splash screen for both desktop and mobile // TODO: double check whether its not possible to have the same splash screen for both desktop and mobile
var customSaveOption = new Option<bool>(new[] { "-s", "--customsave" }, "Whether the Android Port should use a custom save location. Has no effect on other OS."); var customSaveOption = new Option<bool>(new[] { "-s", "--customsave" }, "Whether the Android Port should use a custom save location. Has no effect on other OS.");
var internetOption = new Option<bool>(new[] { "-w", "--internet" }, "Add internet usage permissions to the Android mod. Has no effect on other OS."); var internetOption = new Option<bool>(new[] { "-n", "--internet" }, "Add internet usage permissions to the Android mod. Has no effect on other OS.");
var verboseOption = new Option<bool>(new[] { "-v", "--verbose" }, "Whether to show verbose output."); var verboseOption = new Option<bool>(new[] { "-v", "--verbose" }, "Whether to show verbose output.");
RootCommand rootCommand = new RootCommand("A utility to port Windows AM2R Mods to other operating systems.") RootCommand rootCommand = new RootCommand("A utility to port Windows AM2R Mods to other operating systems.")
{ {
interactiveOption, interactiveOption,
fileOption, fileOption,
windowsOption,
linuxOption, linuxOption,
androidOption, androidOption,
macOption, macOption,
@ -52,6 +54,7 @@ internal static class Program
{ {
bool interactive = context.ParseResult.GetValueForOption(interactiveOption); bool interactive = context.ParseResult.GetValueForOption(interactiveOption);
FileInfo inputModPath = context.ParseResult.GetValueForOption(fileOption); FileInfo inputModPath = context.ParseResult.GetValueForOption(fileOption);
FileInfo windowsPath = context.ParseResult.GetValueForOption(windowsOption);
FileInfo linuxPath = context.ParseResult.GetValueForOption(linuxOption); FileInfo linuxPath = context.ParseResult.GetValueForOption(linuxOption);
FileInfo androidPath = context.ParseResult.GetValueForOption(androidOption); FileInfo androidPath = context.ParseResult.GetValueForOption(androidOption);
FileInfo macPath = context.ParseResult.GetValueForOption(macOption); FileInfo macPath = context.ParseResult.GetValueForOption(macOption);
@ -61,13 +64,13 @@ internal static class Program
bool beVerbose = context.ParseResult.GetValueForOption(verboseOption); bool beVerbose = context.ParseResult.GetValueForOption(verboseOption);
FileInfo iconPath = context.ParseResult.GetValueForOption(iconOption); FileInfo iconPath = context.ParseResult.GetValueForOption(iconOption);
return RootMethod(interactive, inputModPath, linuxPath, androidPath, macPath, splashPath, iconPath, useCustomSave, usesInternet, beVerbose); return RootMethod(interactive, inputModPath, windowsPath, linuxPath, androidPath, macPath, splashPath, iconPath, useCustomSave, usesInternet, beVerbose);
}); });
return rootCommand.InvokeAsync(args).Result; return rootCommand.InvokeAsync(args).Result;
} }
#pragma warning disable CS1998 #pragma warning disable CS1998
private static async Task<int> RootMethod(bool interactive, FileInfo inputModPath, FileInfo linuxPath, FileInfo androidPath, FileInfo macPath, private static async Task<int> RootMethod(bool interactive, FileInfo inputModPath, FileInfo windowsPath, FileInfo linuxPath, FileInfo androidPath, FileInfo macPath,
FileInfo iconPath, FileInfo splashPath, bool useCustomSave, bool usesInternet, bool beVerbose) FileInfo iconPath, FileInfo splashPath, bool useCustomSave, bool usesInternet, bool beVerbose)
#pragma warning restore CS1998 #pragma warning restore CS1998
{ {
@ -92,6 +95,10 @@ internal static class Program
OutputHandlerDelegate(output); OutputHandlerDelegate(output);
} }
if (windowsPath is not null)
{
RawMods.PortToWindows(inputModPath.FullName, windowsPath.FullName, LocalOutput);
}
if (linuxPath is not null) if (linuxPath is not null)
{ {
RawMods.PortToLinux(inputModPath.FullName, linuxPath.FullName, iconPath?.FullName, splashPath?.FullName, LocalOutput); RawMods.PortToLinux(inputModPath.FullName, linuxPath.FullName, iconPath?.FullName, splashPath?.FullName, LocalOutput);

@ -47,7 +47,7 @@ public partial class MainForm : Form
mainLayout.AddRow(labelOSHeader); mainLayout.AddRow(labelOSHeader);
mainLayout.EndCentered(); mainLayout.EndCentered();
mainLayout.BeginCentered(); mainLayout.BeginCentered();
mainLayout.AddRow(checkboxLinux, checkboxAndroid, checkboxMac); mainLayout.AddRow(checkboxWindows, checkboxLinux, checkboxAndroid, checkboxMac);
mainLayout.AddSpace(); mainLayout.AddSpace();
mainLayout.EndCentered(); mainLayout.EndCentered();
mainLayout.BeginCentered(); mainLayout.BeginCentered();
@ -146,6 +146,7 @@ public partial class MainForm : Form
void OutputHandlerDelegate(string output) => Application.Instance.Invoke(() => labelProgress.Text = $"Info: {output}"); void OutputHandlerDelegate(string output) => Application.Instance.Invoke(() => labelProgress.Text = $"Info: {output}");
string modZipPath = filePicker.FilePath; string modZipPath = filePicker.FilePath;
string currentDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); string currentDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string windowsPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_WINDOWS.zip";
string linuxPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_LINUX.zip"; string linuxPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_LINUX.zip";
string androidPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_ANDROID.apk"; string androidPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_ANDROID.apk";
string macPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_MACOS.zip"; string macPath = $"{currentDir}/{Path.GetFileNameWithoutExtension(modZipPath)}_MACOS.zip";
@ -155,6 +156,13 @@ public partial class MainForm : Form
try try
{ {
if (checkboxWindows.Checked.Value)
{
if (File.Exists(windowsPath))
File.Delete(windowsPath);
await Task.Run(() => RawMods.PortToWindows(modZipPath, windowsPath, OutputHandlerDelegate));
}
if (checkboxLinux.Checked.Value) if (checkboxLinux.Checked.Value)
{ {
if (File.Exists(linuxPath)) if (File.Exists(linuxPath))
@ -205,7 +213,7 @@ public partial class MainForm : Form
{ {
// there needs to be a selected mod + any checkbox // there needs to be a selected mod + any checkbox
if (!String.IsNullOrWhiteSpace(filePicker.FilePath) && if (!String.IsNullOrWhiteSpace(filePicker.FilePath) &&
(checkboxAndroid.Checked.Value || checkboxLinux.Checked.Value || checkboxMac.Checked.Value)) (checkboxWindows.Checked.Value || checkboxLinux.Checked.Value || checkboxAndroid.Checked.Value || checkboxMac.Checked.Value))
buttonPort.Enabled = true; buttonPort.Enabled = true;
else else
buttonPort.Enabled = false; buttonPort.Enabled = false;
@ -242,6 +250,10 @@ public partial class MainForm : Form
Text = "Choose the OS to port to", Text = "Choose the OS to port to",
Font = new Font(SystemFont.Bold) Font = new Font(SystemFont.Bold)
}; };
private readonly CheckBox checkboxWindows = new CheckBox
{
Text = "Windows"
};
private readonly CheckBox checkboxLinux = new CheckBox private readonly CheckBox checkboxLinux = new CheckBox
{ {
Text = "Linux" Text = "Linux"

@ -0,0 +1,11 @@
using UndertaleModLib.Compiler;
namespace AM2RPortHelperLib;
public static class ExtensionMethods
{
public static void SendOutput(this ModsBase.OutputHandlerDelegate outputDelegate, string output)
{
outputDelegate?.Invoke(output);
}
}

@ -15,16 +15,16 @@ public abstract class LauncherMods : ModsBase
/// <param name="outputDelegate">The function that should handle in-progress output messages.</param> /// <param name="outputDelegate">The function that should handle in-progress output messages.</param>
/// <exception cref="NotSupportedException">WIP</exception> /// <exception cref="NotSupportedException">WIP</exception>
/// TODO: other exceptions /// TODO: other exceptions
public static void PortLauncherMod(string inputLauncherZipPath, Core.ModOS modTarget, bool includeAndroid, string outputLauncherZipPath, string am2r11ZipPath = null, OutputHandlerDelegate outputDelegate = null) public static void PortLauncherMod(string inputLauncherZipPath, Core.ModOS modTarget, bool includeAndroid, string outputLauncherZipPath, string am2r11ZipPath = null,
OutputHandlerDelegate outputDelegate = null)
{ {
OutputHandler = outputDelegate;
string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputLauncherZipPath); string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputLauncherZipPath);
string filesToCopyDir = extractDirectory + "/files_to_copy"; string filesToCopyDir = extractDirectory + "/files_to_copy";
// Check if temp folder exists, delete if yes, extract zip to there // Check if temp folder exists, delete if yes, extract zip to there
if (Directory.Exists(extractDirectory)) if (Directory.Exists(extractDirectory))
Directory.Delete(extractDirectory, true); Directory.Delete(extractDirectory, true);
SendOutput("Extracting Launcher mod..."); outputDelegate.SendOutput("Extracting Launcher mod...");
ZipFile.ExtractToDirectory(inputLauncherZipPath, extractDirectory); ZipFile.ExtractToDirectory(inputLauncherZipPath, extractDirectory);
var profile = Serializer.Deserialize<ProfileXML>(File.ReadAllText(extractDirectory + "/profile.xml")); var profile = Serializer.Deserialize<ProfileXML>(File.ReadAllText(extractDirectory + "/profile.xml"));
@ -36,7 +36,7 @@ public abstract class LauncherMods : ModsBase
if (modTarget.ToString() == profile.OperatingSystem) if (modTarget.ToString() == profile.OperatingSystem)
{ {
SendOutput("Target OS and Launcher OS are the same; exiting."); outputDelegate.SendOutput("Target OS and Launcher OS are the same; exiting.");
return; return;
} }
@ -143,7 +143,7 @@ public abstract class LauncherMods : ModsBase
} }
//zip the result //zip the result
SendOutput($"Creating Launcher zip for {modTarget}..."); outputDelegate.SendOutput($"Creating Launcher zip for {modTarget}...");
ZipFile.CreateFromDirectory(extractDirectory, outputLauncherZipPath); ZipFile.CreateFromDirectory(extractDirectory, outputLauncherZipPath);
// Clean up // Clean up

@ -4,13 +4,6 @@ public abstract class ModsBase
{ {
public delegate void OutputHandlerDelegate(string output); public delegate void OutputHandlerDelegate(string output);
protected static OutputHandlerDelegate OutputHandler;
protected static void SendOutput(string output)
{
OutputHandler?.Invoke(output);
}
// Create before accessing anything // Create before accessing anything
static ModsBase() static ModsBase()
{ {

@ -85,10 +85,64 @@ public abstract class RawMods : ModsBase
} }
} }
// TODO: Port to Windows /// <summary>
public static void PortToWindows(string inputRawZipPath, string outputRawZipPath, OutputHandlerDelegate outputHandlerDelegate = null) /// Ports a raw AM2R mod zip for Linux.
/// </summary>
/// <param name="inputRawZipPath">The path to the raw mod zip.</param>
/// <param name="outputRawZipPath">The path where the ported Windows mod zip should be saved to.</param>
/// <param name="outputDelegate">A delegate to post output info to.</param>
/// <exception cref="NotSupportedException">The raw mod zip was made for an OS that can't be determined.</exception>
public static void PortToWindows(string inputRawZipPath, string outputRawZipPath, OutputHandlerDelegate outputDelegate = null)
{ {
ModOS currentOS = GetModOSOfRawZip(inputRawZipPath);
outputDelegate.SendOutput("Zip Recognized as " + currentOS);
if (currentOS == ModOS.Windows)
{
outputDelegate.SendOutput("Zip is already a raw Windows zip. Copying to output directory...");
File.Copy(inputRawZipPath, outputRawZipPath, true);
return;
}
string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
// Check if temp folder exists, delete if yes, extract zip to there
if (Directory.Exists(extractDirectory))
Directory.Delete(extractDirectory, true);
outputDelegate.SendOutput("Extracting for Raw Windows...");
Directory.CreateDirectory(extractDirectory);
ZipFile.ExtractToDirectory(inputRawZipPath, extractDirectory);
// Delete unnecessary files, rename data.win, move in the new runner
outputDelegate.SendOutput("Delete unnecessary files for Windows...");
switch (currentOS)
{
case ModOS.Linux:
File.Delete(extractDirectory + "/runner");
HelperMethods.DirectoryCopy(extractDirectory + "/assets", extractDirectory);
Directory.Delete(extractDirectory + "/assets", true);
File.Move(extractDirectory + "/game.unx", extractDirectory + "/data.win");
break;
case ModOS.Mac:
var appDir = new DirectoryInfo(extractDirectory).GetDirectories().First(n => n.Name.EndsWith(".app"));
HelperMethods.DirectoryCopy(extractDirectory + "/" + appDir.Name + "/Contents/Resources", extractDirectory);
File.Delete(extractDirectory + "/gamecontrollerdb.txt");
File.Delete(extractDirectory + "/yoyorunner.config");
Directory.Delete(extractDirectory + "/English.lproj", true);
Directory.Delete(extractDirectory + "/" + appDir.Name, true);
File.Move(extractDirectory + "/game.ios", extractDirectory + "/data.win");
break;
default: throw new NotSupportedException("The OS of the mod zip is unknown and thus not supported.");
}
File.Copy(UtilDir + "/executable.exe", extractDirectory + "/AM2R.exe");
//zip the result
outputDelegate.SendOutput("Creating raw Windows zip...");
ZipFile.CreateFromDirectory(extractDirectory, outputRawZipPath);
// Clean up
Directory.Delete(TempDir, true);
} }
/// <summary> /// <summary>
@ -106,28 +160,27 @@ public abstract class RawMods : ModsBase
OutputHandlerDelegate outputDelegate = null) OutputHandlerDelegate outputDelegate = null)
{ {
ModOS currentOS = GetModOSOfRawZip(inputRawZipPath); ModOS currentOS = GetModOSOfRawZip(inputRawZipPath);
SendOutput("Zip Recognized as " + currentOS); outputDelegate.SendOutput("Zip Recognized as " + currentOS);
if (currentOS == ModOS.Linux) if (currentOS == ModOS.Linux)
{ {
SendOutput("Zip is already a raw Linux zip. Copying to output directory..."); outputDelegate.SendOutput("Zip is already a raw Linux zip. Copying to output directory...");
File.Copy(inputRawZipPath, outputRawZipPath, true); File.Copy(inputRawZipPath, outputRawZipPath, true);
return; return;
} }
OutputHandler = outputDelegate;
string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath); string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
string assetsDir = extractDirectory + "/assets"; string assetsDir = extractDirectory + "/assets";
// Check if temp folder exists, delete if yes, extract zip to there // Check if temp folder exists, delete if yes, extract zip to there
if (Directory.Exists(extractDirectory)) if (Directory.Exists(extractDirectory))
Directory.Delete(extractDirectory, true); Directory.Delete(extractDirectory, true);
SendOutput("Extracting for Raw Linux..."); outputDelegate.SendOutput("Extracting for Raw Linux...");
Directory.CreateDirectory(assetsDir); Directory.CreateDirectory(assetsDir);
ZipFile.ExtractToDirectory(inputRawZipPath, assetsDir); ZipFile.ExtractToDirectory(inputRawZipPath, assetsDir);
// Delete unnecessary files, rename data.win, move in the new runner // Delete unnecessary files, rename data.win, move in the new runner
SendOutput("Delete unnecessary files for Linux and lowercase them..."); outputDelegate.SendOutput("Delete unnecessary files for Linux...");
switch (currentOS) switch (currentOS)
{ {
case ModOS.Windows: case ModOS.Windows:
@ -153,10 +206,11 @@ public abstract class RawMods : ModsBase
File.Copy(GetProperPathToBuiltinIcons(nameof(Resources.splash), pathToSplashScreen), assetsDir + "/splash.png"); File.Copy(GetProperPathToBuiltinIcons(nameof(Resources.splash), pathToSplashScreen), assetsDir + "/splash.png");
//recursively lowercase everything in the assets folder //recursively lowercase everything in the assets folder
outputDelegate.SendOutput("Lowercase everything in the assets folder...");
HelperMethods.LowercaseFolder(assetsDir); HelperMethods.LowercaseFolder(assetsDir);
//zip the result //zip the result
SendOutput("Creating raw Linux zip..."); outputDelegate.SendOutput("Creating raw Linux zip...");
ZipFile.CreateFromDirectory(extractDirectory, outputRawZipPath); ZipFile.CreateFromDirectory(extractDirectory, outputRawZipPath);
// Clean up // Clean up
@ -181,9 +235,8 @@ public abstract class RawMods : ModsBase
bool useCustomSaveDirectory = false, bool usesInternet = false, OutputHandlerDelegate outputDelegate = null) bool useCustomSaveDirectory = false, bool usesInternet = false, OutputHandlerDelegate outputDelegate = null)
{ {
ModOS currentOS = GetModOSOfRawZip(inputRawZipPath); ModOS currentOS = GetModOSOfRawZip(inputRawZipPath);
SendOutput("Zip Recognized as " + currentOS); outputDelegate.SendOutput("Zip Recognized as " + currentOS);
OutputHandler = outputDelegate;
string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath); string extractDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
string apkDir = extractDirectory + "/apk"; string apkDir = extractDirectory + "/apk";
string apkAssetsDir = apkDir + "/assets"; string apkAssetsDir = apkDir + "/assets";
@ -199,7 +252,7 @@ public abstract class RawMods : ModsBase
Directory.CreateDirectory(extractDirectory); Directory.CreateDirectory(extractDirectory);
// Run APKTOOL and decompress the file // Run APKTOOL and decompress the file
SendOutput("Decompiling apk..."); outputDelegate.SendOutput("Decompiling apk...");
ProcessStartInfo pStartInfo = new ProcessStartInfo ProcessStartInfo pStartInfo = new ProcessStartInfo
{ {
FileName = bin, FileName = bin,
@ -210,11 +263,11 @@ public abstract class RawMods : ModsBase
p.Start(); p.Start();
p.WaitForExit(); p.WaitForExit();
SendOutput("Extracting for Raw Android..."); outputDelegate.SendOutput("Extracting for Raw Android...");
ZipFile.ExtractToDirectory(inputRawZipPath, apkAssetsDir); ZipFile.ExtractToDirectory(inputRawZipPath, apkAssetsDir);
// Delete unnecessary files, rename data.win, move in the new runner // Delete unnecessary files, rename data.win, move in the new runner
SendOutput("Delete unnecessary files for Android and lowercase them..."); outputDelegate.SendOutput("Delete unnecessary files for Android...");
switch (currentOS) switch (currentOS)
{ {
case ModOS.Windows: case ModOS.Windows:
@ -245,15 +298,16 @@ public abstract class RawMods : ModsBase
File.Copy(GetProperPathToBuiltinIcons(nameof(Resources.splashAndroid), pathToSplashScreen), apkAssetsDir + "/splash.png", true); File.Copy(GetProperPathToBuiltinIcons(nameof(Resources.splashAndroid), pathToSplashScreen), apkAssetsDir + "/splash.png", true);
//recursively lowercase everything in the assets folder //recursively lowercase everything in the assets folder
outputDelegate.SendOutput("Lowercase everything in the assets folder...");
HelperMethods.LowercaseFolder(apkAssetsDir); HelperMethods.LowercaseFolder(apkAssetsDir);
// Edit apktool.yml to not compress music // Edit apktool.yml to not compress music
SendOutput("Edit settings file to not compress OGGs..."); outputDelegate.SendOutput("Edit settings file to not compress OGGs...");
string yamlFile = File.ReadAllText(apkDir + "/apktool.yml"); string yamlFile = File.ReadAllText(apkDir + "/apktool.yml");
yamlFile = yamlFile.Replace("doNotCompress:", "doNotCompress:\n- ogg"); yamlFile = yamlFile.Replace("doNotCompress:", "doNotCompress:\n- ogg");
File.WriteAllText(apkDir + "/apktool.yml", yamlFile); File.WriteAllText(apkDir + "/apktool.yml", yamlFile);
SendOutput("Save new icons"); outputDelegate.SendOutput("Save new icons");
// Edit the icons in the apk. Wrapper always has these, so we need to overwrite these too. // Edit the icons in the apk. Wrapper always has these, so we need to overwrite these too.
string resPath = apkDir + "/res"; string resPath = apkDir + "/res";
// Icon should only be read from if its there, otherwise default frog icon should be in the assembly // Icon should only be read from if its there, otherwise default frog icon should be in the assembly
@ -274,12 +328,12 @@ public abstract class RawMods : ModsBase
// If a custom name was given, replace it everywhere. // If a custom name was given, replace it everywhere.
if (useCustomSaveDirectory) if (useCustomSaveDirectory)
{ {
SendOutput("Get display name..."); outputDelegate.SendOutput("Get display name...");
string modName; string modName;
FileInfo datafile = new FileInfo(apkAssetsDir + "/game.droid"); FileInfo datafile = new FileInfo(apkAssetsDir + "/game.droid");
using (FileStream fs = datafile.OpenRead()) using (FileStream fs = datafile.OpenRead())
{ {
UndertaleData gmData = UndertaleIO.Read(fs, SendOutput, SendOutput); UndertaleData gmData = UndertaleIO.Read(fs, outputDelegate.SendOutput, outputDelegate.SendOutput);
modName = gmData.GeneralInfo.DisplayName.Content; modName = gmData.GeneralInfo.DisplayName.Content;
} }
modName = modName.Replace(" ", "").Replace(":", ""); modName = modName.Replace(" ", "").Replace(":", "");
@ -289,7 +343,7 @@ public abstract class RawMods : ModsBase
if (!nameReg.Match(modName).Success) if (!nameReg.Match(modName).Success)
throw new InvalidDataException("The display name " + modName + " is invalid! The name has to start with letters (a-z), and can only contain letters, digits, space, colon and underscore!"); throw new InvalidDataException("The display name " + modName + " is invalid! The name has to start with letters (a-z), and can only contain letters, digits, space, colon and underscore!");
SendOutput("Replace Android save directory..."); outputDelegate.SendOutput("Replace Android save directory...");
// first in the manifest // first in the manifest
manifestFile = manifestFile.Replace("com.companyname.AM2RWrapper", $"com.companyname.{modName}"); manifestFile = manifestFile.Replace("com.companyname.AM2RWrapper", $"com.companyname.{modName}");
@ -324,7 +378,7 @@ public abstract class RawMods : ModsBase
// Add internet permission, keying off the Bluetooth permission. // Add internet permission, keying off the Bluetooth permission.
if (usesInternet) if (usesInternet)
{ {
SendOutput("Replace Internet permission..."); outputDelegate.SendOutput("Replace Internet permission...");
const string bluetoothPermission = "<uses-permission android:name=\"android.permission.BLUETOOTH\"/>"; const string bluetoothPermission = "<uses-permission android:name=\"android.permission.BLUETOOTH\"/>";
const string internetPermission = "<uses-permission android:name=\"android.permission.INTERNET\"/>"; const string internetPermission = "<uses-permission android:name=\"android.permission.INTERNET\"/>";
manifestFile = manifestFile.Replace(bluetoothPermission, internetPermission + "\n " + bluetoothPermission); manifestFile = manifestFile.Replace(bluetoothPermission, internetPermission + "\n " + bluetoothPermission);
@ -333,7 +387,7 @@ public abstract class RawMods : ModsBase
} }
// Run APKTOOL and build the apk // Run APKTOOL and build the apk
SendOutput("Rebuild apk..."); outputDelegate.SendOutput("Rebuild apk...");
pStartInfo = new ProcessStartInfo pStartInfo = new ProcessStartInfo
{ {
FileName = bin, FileName = bin,
@ -345,7 +399,7 @@ public abstract class RawMods : ModsBase
p.WaitForExit(); p.WaitForExit();
// Sign the apk // Sign the apk
SendOutput("Sign apk..."); outputDelegate.SendOutput("Sign apk...");
pStartInfo = new ProcessStartInfo pStartInfo = new ProcessStartInfo
{ {
FileName = bin, FileName = bin,
@ -378,16 +432,15 @@ public abstract class RawMods : ModsBase
OutputHandlerDelegate outputDelegate = null) OutputHandlerDelegate outputDelegate = null)
{ {
ModOS currentOS = GetModOSOfRawZip(inputRawZipPath); ModOS currentOS = GetModOSOfRawZip(inputRawZipPath);
SendOutput("Zip Recognized as " + currentOS); outputDelegate.SendOutput("Zip Recognized as " + currentOS);
if (currentOS == ModOS.Mac) if (currentOS == ModOS.Mac)
{ {
SendOutput("Zip is already a raw Mac zip. Copying to output dir..."); outputDelegate.SendOutput("Zip is already a raw Mac zip. Copying to output dir...");
File.Copy(inputRawZipPath, outputRawZipPath, true); File.Copy(inputRawZipPath, outputRawZipPath, true);
return; return;
} }
OutputHandler = outputDelegate;
string baseTempDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath); string baseTempDirectory = TempDir + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
string extractDirectory = baseTempDirectory + "/extract"; string extractDirectory = baseTempDirectory + "/extract";
string appDirectory = baseTempDirectory + "/AM2R.app"; string appDirectory = baseTempDirectory + "/AM2R.app";
@ -397,16 +450,16 @@ public abstract class RawMods : ModsBase
// Check if temp folder exists, delete if yes, copy bare runner to there // Check if temp folder exists, delete if yes, copy bare runner to there
if (Directory.Exists(baseTempDirectory)) if (Directory.Exists(baseTempDirectory))
Directory.Delete(baseTempDirectory, true); Directory.Delete(baseTempDirectory, true);
SendOutput("Copying Mac Runner..."); outputDelegate.SendOutput("Copying Mac Runner...");
Directory.CreateDirectory(contentsDir); Directory.CreateDirectory(contentsDir);
HelperMethods.DirectoryCopy(UtilDir + "/Contents", contentsDir); HelperMethods.DirectoryCopy(UtilDir + "/Contents", contentsDir);
// Extract mod to temp location // Extract mod to temp location
SendOutput("Extracting Mac..."); outputDelegate.SendOutput("Extracting Mac...");
ZipFile.ExtractToDirectory(inputRawZipPath, extractDirectory); ZipFile.ExtractToDirectory(inputRawZipPath, extractDirectory);
// Delete unnecessary files, rename data.win, move in the new runner // Delete unnecessary files, rename data.win, move in the new runner
SendOutput("Delete unnecessary files for Mac and lowercase them..."); outputDelegate.SendOutput("Delete unnecessary files for Mac...");
switch (currentOS) switch (currentOS)
{ {
case ModOS.Windows: case ModOS.Windows:
@ -432,10 +485,11 @@ public abstract class RawMods : ModsBase
Directory.Delete(extractDirectory + "/lang/fonts", true); Directory.Delete(extractDirectory + "/lang/fonts", true);
// Lowercase every file first // Lowercase every file first
outputDelegate.SendOutput("Lowercase everything in the assets folder...");
HelperMethods.LowercaseFolder(extractDirectory); HelperMethods.LowercaseFolder(extractDirectory);
// Convert data.win to BC16 and get rid of not needed functions anymore // Convert data.win to BC16 and get rid of not needed functions anymore
SendOutput("Editing data.win to change ByteCode version and functions..."); outputDelegate.SendOutput("Editing data.win to change ByteCode version and functions...");
string modName; string modName;
FileInfo datafile = new FileInfo(extractDirectory + "/game.ios"); FileInfo datafile = new FileInfo(extractDirectory + "/game.ios");
@ -445,15 +499,15 @@ public abstract class RawMods : ModsBase
UndertaleData gmData; UndertaleData gmData;
using (FileStream fs = datafile.OpenRead()) using (FileStream fs = datafile.OpenRead())
{ {
gmData = UndertaleIO.Read(fs, SendOutput, SendOutput); gmData = UndertaleIO.Read(fs, outputDelegate.SendOutput, outputDelegate.SendOutput);
modName = gmData.GeneralInfo.DisplayName.Content; modName = gmData.GeneralInfo.DisplayName.Content;
ChangeToByteCode16(gmData); ChangeToByteCode16(gmData, outputDelegate.SendOutput);
} }
using (FileStream fs = new FileInfo(extractDirectory + "/game.ios").OpenWrite()) using (FileStream fs = new FileInfo(extractDirectory + "/game.ios").OpenWrite())
{ {
UndertaleIO.Write(fs, gmData, SendOutput); UndertaleIO.Write(fs, gmData, outputDelegate.SendOutput);
} }
} }
@ -462,13 +516,13 @@ public abstract class RawMods : ModsBase
Process.Start("chmod", "+x \"" + contentsDir + "/MacOS/Mac_Runner"); Process.Start("chmod", "+x \"" + contentsDir + "/MacOS/Mac_Runner");
// Copy assets to the place where they belong to // Copy assets to the place where they belong to
SendOutput("Copy files over..."); outputDelegate.SendOutput("Copy files over...");
HelperMethods.DirectoryCopy(extractDirectory, assetsDir); HelperMethods.DirectoryCopy(extractDirectory, assetsDir);
// Edit config and plist to change display name // Edit config and plist to change display name
// Escape invalid xml characters // Escape invalid xml characters
modName = SecurityElement.Escape(modName); modName = SecurityElement.Escape(modName);
SendOutput("Editing Runner references to AM2R..."); outputDelegate.SendOutput("Editing Runner references to AM2R...");
string textFile = File.ReadAllText(assetsDir + "/yoyorunner.config"); string textFile = File.ReadAllText(assetsDir + "/yoyorunner.config");
textFile = textFile.Replace("YoYo Runner", modName); textFile = textFile.Replace("YoYo Runner", modName);
File.WriteAllText(assetsDir + "/yoyorunner.config", textFile); File.WriteAllText(assetsDir + "/yoyorunner.config", textFile);
@ -484,44 +538,55 @@ public abstract class RawMods : ModsBase
Directory.Delete(extractDirectory, true); Directory.Delete(extractDirectory, true);
//zip the result //zip the result
SendOutput("Creating Mac zip..."); outputDelegate.SendOutput("Creating Mac zip...");
ZipFile.CreateFromDirectory(baseTempDirectory, outputRawZipPath); ZipFile.CreateFromDirectory(baseTempDirectory, outputRawZipPath);
// Clean up // Clean up
Directory.Delete(TempDir, true); Directory.Delete(TempDir, true);
} }
// TODO: clean this /// <summary>
private static void ChangeToByteCode16(UndertaleData Data) /// Converts a GameMaker data file to bytecode version 16
/// </summary>
/// <param name="Data">The GameMaker data file.</param>
/// <param name="output">Delegate on where to send output messages to.</param>
/// <exception cref="NotSupportedException"><paramref name="Data"/> has a not supported Bytecode version.</exception>
private static void ChangeToByteCode16(UndertaleData Data, OutputHandlerDelegate output)
{ {
if (Data is null) return; if (Data is null) return;
string currentBytecodeVersion = Data?.GeneralInfo.BytecodeVersion.ToString();
string game_name = Data.GeneralInfo.Name.Content; string game_name = Data.GeneralInfo.Name.Content;
void ScriptError(string s) => Console.WriteLine(s, ConsoleColor.Red); void ScriptMessage(string s) => output(s);
void ScriptMessage(string s) => Console.WriteLine(s);
if (!Data.FORM.Chunks.ContainsKey("AGRP")) if (!Data.FORM.Chunks.ContainsKey("AGRP"))
{ {
ScriptError("Bytecode 13 is not supported."); throw new NotSupportedException("Bytecode 13 is not supported.");
return;
} }
if (Data?.GeneralInfo.BytecodeVersion == 14) byte? bcVersion = Data?.GeneralInfo.BytecodeVersion;
if (bcVersion == 14)
{ {
ScriptError("Bytecode 14 is not supported."); throw new NotSupportedException("Bytecode 14 is not supported.");
return; }
if (bcVersion == 17)
{
throw new NotSupportedException("Bytecode 17 is not supported.");
} }
if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false))) if (!((Data.GMS2_3 == false) && (Data.GMS2_3_1 == false) && (Data.GMS2_3_2 == false)))
{ {
ScriptError(game_name + "is GMS 2.3+ and is ineligible"); throw new NotSupportedException(game_name + "is GMS 2.3+ and is ineligible");
return; }
if (bcVersion != 14 && bcVersion != 15 && bcVersion != 16)
{
throw new NotSupportedException("Unknown Bytecode version!");
} }
if ((Data?.GeneralInfo.BytecodeVersion == 14) || (Data?.GeneralInfo.BytecodeVersion == 15))
if ((bcVersion == 14) || (bcVersion == 15))
{ {
if (Data?.GeneralInfo.BytecodeVersion <= 14) // For BC 14
if (bcVersion <= 14)
{ {
foreach (UndertaleCode code in Data.Code) foreach (UndertaleCode code in Data.Code)
{ {
@ -539,7 +604,8 @@ public abstract class RawMods : ModsBase
Data.CodeLocals.Add(locals); Data.CodeLocals.Add(locals);
} }
} }
if (!(Data.FORM.Chunks.ContainsKey("AGRP"))) // For BC 13
if (!Data.FORM.Chunks.ContainsKey("AGRP"))
{ {
Data.FORM.Chunks["AGRP"] = new UndertaleChunkAGRP(); Data.FORM.Chunks["AGRP"] = new UndertaleChunkAGRP();
var previous = -1; var previous = -1;
@ -598,43 +664,16 @@ public abstract class RawMods : ModsBase
newChunks[name] = Data.FORM.Chunks[name]; newChunks[name] = Data.FORM.Chunks[name];
Data.FORM.Chunks = newChunks; Data.FORM.Chunks = newChunks;
Data.GeneralInfo.BytecodeVersion = 16; Data.GeneralInfo.BytecodeVersion = 16;
ScriptMessage("Upgraded from " + currentBytecodeVersion + " to 16 successfully. Save the game to apply the changes."); ScriptMessage("Upgraded from " + bcVersion + " to 16 successfully.");
ScriptMessage("Trying to remove functions \"immersion_play_effect\", \"immersion_stop\" and \"font_replace\"!");
RemoveFunctions();
}
else if (Data?.GeneralInfo.BytecodeVersion == 17)
{
ScriptMessage("Cancelled.");
return;
} }
else if (Data?.GeneralInfo.BytecodeVersion == 16) else if (bcVersion == 16)
{ {
ScriptMessage("This is already bytecode 16."); ScriptMessage("This is already bytecode 16.");
ScriptMessage("Trying to remove functions \"immersion_play_effect\", \"immersion_stop\" and \"font_replace\"!");
RemoveFunctions();
return;
}
else
{
string error = @"This game is not bytecode 13,
14, 15, 16, or 17, and is not made in GameMaker 2.3
or greater. Please report this game to Grossley#2869
on Discord and provide the name of the game, where
you obtained it from, and additionally send the
data.win file of the game." + @"
Current status of game '" + game_name + @"':
GMS 2.3 == " + Data.GMS2_3 + @"
GMS 2.3.1 == " + Data.GMS2_3_1 + @"
GMS 2.3.2 == " + Data.GMS2_3_2 + @"
Bytecode == " + (Data?.GeneralInfo.BytecodeVersion);
ScriptError(error);
return;
} }
ScriptMessage("Trying to remove functions \"immersion_play_effect\", \"immersion_stop\" and \"font_replace\"!");
RemoveFunctions();
void RemoveFunctions() void RemoveFunctions()
{ {
List<UndertaleFunction> funcsToRemove = new List<UndertaleFunction>(); List<UndertaleFunction> funcsToRemove = new List<UndertaleFunction>();

Loading…
Cancel
Save