You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
14 KiB
325 lines
14 KiB
using System.Diagnostics;
|
|
using System.IO.Compression;
|
|
using System.Runtime.InteropServices;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.Processing;
|
|
|
|
namespace AM2RPortHelperLib;
|
|
|
|
public static class PortHelper
|
|
{
|
|
public const string Version = "1.3";
|
|
public delegate void OutputHandlerDelegate(string output);
|
|
|
|
private static OutputHandlerDelegate outputHandler;
|
|
|
|
private static void SendOutput(string output)
|
|
{
|
|
if (outputHandler is not null)
|
|
outputHandler.Invoke(output);
|
|
else
|
|
Console.WriteLine(output);
|
|
}
|
|
|
|
private static readonly string tmp = Path.GetTempPath();
|
|
private static readonly string currentDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
|
|
private static readonly string utilDir = currentDir + "/utils";
|
|
|
|
public static void PortWindowsToLinux(string inputRawZipPath, string outputRawZipPath, OutputHandlerDelegate outputDelegate = null)
|
|
{
|
|
outputHandler = outputDelegate;
|
|
string extractDirectory = tmp + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
|
|
string assetsDir = extractDirectory + "/assets";
|
|
|
|
// Check if temp folder exists, delete if yes, extract zip to there
|
|
if (Directory.Exists(extractDirectory))
|
|
Directory.Delete(extractDirectory, true);
|
|
SendOutput("Extracting Linux...");
|
|
ZipFile.ExtractToDirectory(inputRawZipPath, extractDirectory);
|
|
|
|
// Move everything into assets folder
|
|
SendOutput("Moving into Linux assets folder...");
|
|
Directory.CreateDirectory(assetsDir);
|
|
foreach (var file in new DirectoryInfo(extractDirectory).GetFiles())
|
|
file.MoveTo(assetsDir + "/" + file.Name);
|
|
|
|
foreach (var dir in new DirectoryInfo(extractDirectory).GetDirectories())
|
|
{
|
|
if (dir.Name == "assets") continue;
|
|
dir.MoveTo(assetsDir + "/" + dir.Name);
|
|
}
|
|
|
|
// Delete unnecessary files, rename data.win, move in the new runner
|
|
SendOutput("Delete unnecessary files for Linux and lowercase them...");
|
|
File.Delete(assetsDir + "/AM2R.exe");
|
|
File.Delete(assetsDir + "/D3DX9_43.dll");
|
|
File.Move(assetsDir + "/data.win", assetsDir + "/game.unx");
|
|
File.Copy(utilDir + "/runner", extractDirectory + "/runner");
|
|
if (!File.Exists(assetsDir + "/icon.png"))
|
|
File.Copy(utilDir + "/icon.png", assetsDir + "/icon.png");
|
|
if (!File.Exists(assetsDir + "/splash.png"))
|
|
File.Copy(utilDir + "/splash.png", assetsDir + "/splash.png");
|
|
|
|
//recursively lowercase everything in the assets folder
|
|
LowercaseFolder(assetsDir);
|
|
|
|
//zip the result if no
|
|
SendOutput("Creating Linux zip...");
|
|
ZipFile.CreateFromDirectory(extractDirectory, outputRawZipPath);
|
|
|
|
// Clean up
|
|
Directory.Delete(assetsDir, true);
|
|
}
|
|
|
|
public static void PortWindowsToAndroid(string inputRawZipPath, string outputRawApkPath, OutputHandlerDelegate outputDelegate = null)
|
|
{
|
|
outputHandler = outputDelegate;
|
|
string extractDirectory = tmp + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
|
|
string unzipDir = extractDirectory + "/zip";
|
|
string apkDir = extractDirectory + "/apk";
|
|
string apkAssetsDir = apkDir + "/assets";
|
|
string bin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "cmd.exe" : "java";
|
|
string args = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/C java -jar " : "-jar ";
|
|
string apktool = currentDir + "/utils/apktool.jar";
|
|
string signer = currentDir + "/utils/uber-apk-signer.jar";
|
|
string finalApkBuild = extractDirectory + "/build-aligned-debugSigned.apk";
|
|
|
|
// Check if temp folder exists, delete if yes, extract zip to there
|
|
if (Directory.Exists(extractDirectory))
|
|
Directory.Delete(extractDirectory, true);
|
|
Directory.CreateDirectory(extractDirectory);
|
|
SendOutput("Extracting...");
|
|
ZipFile.ExtractToDirectory(inputRawZipPath, unzipDir);
|
|
|
|
// Run APKTOOL and decompress the file
|
|
SendOutput("Decompiling apk...");
|
|
ProcessStartInfo pStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = bin,
|
|
Arguments = args + "\"" + apktool + "\" d -f -o \"" + apkDir + "\" \"" + currentDir + "/utils/AM2RWrapper.apk" + "\"",
|
|
CreateNoWindow = true
|
|
};
|
|
Process p = new Process { StartInfo = pStartInfo };
|
|
p.Start();
|
|
p.WaitForExit();
|
|
|
|
// Move everything into assets folder
|
|
SendOutput("Move into Android assets folder...");
|
|
foreach (var file in new DirectoryInfo(unzipDir).GetFiles())
|
|
file.MoveTo(apkAssetsDir + "/" + file.Name);
|
|
|
|
foreach (var dir in new DirectoryInfo(unzipDir).GetDirectories())
|
|
dir.MoveTo(apkAssetsDir + "/" + dir.Name);
|
|
|
|
// Delete unnecessary files, rename data.win, move in the new runner
|
|
SendOutput("Delete unnecessary files for Android and lowercase them...");
|
|
File.Delete(apkAssetsDir + "/AM2R.exe");
|
|
File.Delete(apkAssetsDir + "/D3DX9_43.dll");
|
|
File.Move(apkAssetsDir + "/data.win", apkAssetsDir + "/game.droid");
|
|
File.Copy(utilDir + "/splashAndroid.png", apkAssetsDir + "/splash.png", true);
|
|
|
|
//recursively lowercase everything in the assets folder
|
|
LowercaseFolder(apkAssetsDir);
|
|
|
|
// Edit apktool.yml to not compress music
|
|
string yamlFile = File.ReadAllText(apkDir + "/apktool.yml");
|
|
yamlFile = yamlFile.Replace("doNotCompress:", "doNotCompress:\n- ogg");
|
|
File.WriteAllText(apkDir + "/apktool.yml", yamlFile);
|
|
|
|
// Edit the icons in the apk
|
|
string resPath = apkDir + "/res";
|
|
Image orig = Image.Load(utilDir + "/icon.png");
|
|
SaveAndroidIcon(orig, 96, resPath + "/drawable/icon.png");
|
|
SaveAndroidIcon(orig, 72, resPath + "/drawable-hdpi-v4/icon.png");
|
|
SaveAndroidIcon(orig, 36, resPath + "/drawable-ldpi-v4/icon.png");
|
|
SaveAndroidIcon(orig, 48, resPath + "/drawable-mdpi-v4/icon.png");
|
|
SaveAndroidIcon(orig, 96, resPath + "/drawable-xhdpi-v4/icon.png");
|
|
SaveAndroidIcon(orig, 144, resPath + "/drawable-xxhdpi-v4/icon.png");
|
|
SaveAndroidIcon(orig, 192, resPath + "/drawable-xxxhdpi-v4/icon.png");
|
|
|
|
// Run APKTOOL and build the apk
|
|
SendOutput("Rebuild apk...");
|
|
pStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = bin,
|
|
Arguments = args + "\"" + apktool + "\" b \"" + apkDir + "\" -o \"" + extractDirectory + "/build.apk" + "\"",
|
|
CreateNoWindow = true
|
|
};
|
|
p = new Process { StartInfo = pStartInfo };
|
|
p.Start();
|
|
p.WaitForExit();
|
|
|
|
// Sign the apk
|
|
SendOutput("Sign apk...");
|
|
pStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = bin,
|
|
Arguments = args + "\"" + signer + "\" -a \"" + extractDirectory + "/build.apk" + "\"",
|
|
CreateNoWindow = true
|
|
};
|
|
p = new Process { StartInfo = pStartInfo };
|
|
p.Start();
|
|
p.WaitForExit();
|
|
|
|
//Move apk if it doesn't exist already
|
|
File.Move(finalApkBuild, outputRawApkPath);
|
|
|
|
// Clean up
|
|
Directory.Delete(extractDirectory, true);
|
|
}
|
|
|
|
//TODO: try to figure out if its possible to extract the name from the data.win file? They do have a displayname option last time I checked...
|
|
public static void PortWindowsToMac(string inputRawZipPath, string outputRawZipPath, string modName, OutputHandlerDelegate outputDelegate = null)
|
|
{
|
|
outputHandler = outputDelegate;
|
|
string baseTempDirectory = tmp + "/" + Path.GetFileNameWithoutExtension(inputRawZipPath);
|
|
string extractDirectory = baseTempDirectory + "/extract";
|
|
string appDirectory = baseTempDirectory + "/AM2R.app";
|
|
string contentsDir = baseTempDirectory + "/Contents";
|
|
string assetsDir = contentsDir + "/Resources";
|
|
|
|
// Get name from user
|
|
//TODO: handle error on special characters
|
|
|
|
// Check if temp folder exists, delete if yes, copy bare runner to there
|
|
if (Directory.Exists(baseTempDirectory))
|
|
Directory.Delete(baseTempDirectory, true);
|
|
SendOutput("Copying Runner...");
|
|
Directory.CreateDirectory(contentsDir);
|
|
DirectoryCopy(utilDir + "/Contents", contentsDir, true);
|
|
|
|
// Extract mod to temp location
|
|
SendOutput("Extracting Mac...");
|
|
ZipFile.ExtractToDirectory(inputRawZipPath, extractDirectory);
|
|
|
|
// Delete unnecessary files, rename data.win, move in the new runner
|
|
SendOutput("Delete unnecessary files for Mac and lowercase them...");
|
|
File.Delete(extractDirectory + "/AM2R.exe");
|
|
File.Delete(extractDirectory + "/D3DX9_43.dll");
|
|
File.Move(extractDirectory + "/data.win", extractDirectory + "/game.ios");
|
|
if (!File.Exists(assetsDir + "/icon.png"))
|
|
File.Copy(utilDir + "/icon.png", extractDirectory + "/icon.png");
|
|
if (!File.Exists(assetsDir + "/splash.png"))
|
|
File.Copy(utilDir + "/splash.png", extractDirectory + "/splash.png");
|
|
// Delete fonts folder if it exists, because I need to convert bytecode version from game and newer version doesn't support font loading
|
|
if (Directory.Exists(extractDirectory + "/lang/fonts"))
|
|
Directory.Delete(extractDirectory + "/lang/fonts", true);
|
|
|
|
// Lowercase every file first
|
|
LowercaseFolder(extractDirectory);
|
|
|
|
// Convert data.win to BC16 and get rid of not needed functions anymore
|
|
SendOutput("Editing data.win to change data.win BC version and functions...");
|
|
string bin;
|
|
string args;
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
bin = "\"" + utilDir + "/UTMTCli/UndertaleModCli.exe\"";
|
|
args = "";
|
|
}
|
|
else
|
|
{
|
|
// First chmod the file, just in case
|
|
Process.Start("chmod", "+x \"" + utilDir + "/UTMTCli/UndertaleModCli.dll\"");
|
|
bin = "dotnet";
|
|
args = "\"" + utilDir + "/UTMTCli/UndertaleModCli.dll\" ";
|
|
// Also chmod the runner. Just in case.
|
|
Process.Start("chmod", "+x \"" + contentsDir + "/MacOS/Mac_Runner");
|
|
}
|
|
|
|
ProcessStartInfo pStartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = bin,
|
|
Arguments = args + "load \"" + extractDirectory + "/game.ios\" -s \"" + utilDir + "/bc16AndRemoveFunctions.csx\" -o \"" + extractDirectory + "/game.ios\"",
|
|
CreateNoWindow = false
|
|
};
|
|
Process p = new Process { StartInfo = pStartInfo };
|
|
p.Start();
|
|
p.WaitForExit();
|
|
|
|
// Copy assets to the place where they belong to
|
|
SendOutput("Copy files over...");
|
|
DirectoryCopy(extractDirectory, assetsDir, true);
|
|
|
|
// Edit config and plist to change display name
|
|
SendOutput("Editing Runner references to AM2R...");
|
|
string textFile = File.ReadAllText(assetsDir + "/yoyorunner.config");
|
|
textFile = textFile.Replace("YoYo Runner", modName);
|
|
File.WriteAllText(assetsDir + "/yoyorunner.config", textFile);
|
|
|
|
textFile = File.ReadAllText(contentsDir + "/Info.plist");
|
|
textFile = textFile.Replace("YoYo Runner", modName);
|
|
File.WriteAllText(contentsDir + "/Info.plist", textFile);
|
|
|
|
// Create a .app directory and move contents in there
|
|
Directory.CreateDirectory(appDirectory);
|
|
Directory.Move(contentsDir, appDirectory + "/Contents");
|
|
|
|
Directory.Delete(extractDirectory, true);
|
|
|
|
//zip the result if no
|
|
SendOutput("Creating Mac zip...");
|
|
ZipFile.CreateFromDirectory(baseTempDirectory, outputRawZipPath);
|
|
|
|
// Clean up
|
|
Directory.Delete(baseTempDirectory, true);
|
|
}
|
|
|
|
private static void LowercaseFolder(string directory)
|
|
{
|
|
DirectoryInfo dir = new DirectoryInfo(directory);
|
|
|
|
foreach(var file in dir.GetFiles())
|
|
{
|
|
if (file.Name == file.Name.ToLower()) continue;
|
|
file.MoveTo(file.DirectoryName + "/" + file.Name.ToLower());
|
|
}
|
|
|
|
foreach(var subDir in dir.GetDirectories())
|
|
{
|
|
if (subDir.Name == subDir.Name.ToLower()) continue;
|
|
subDir.MoveTo(subDir.Parent.FullName + "/" + subDir.Name.ToLower());
|
|
LowercaseFolder(subDir.FullName);
|
|
}
|
|
}
|
|
|
|
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
|
|
{
|
|
// Get the subdirectories for the specified directory.
|
|
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
|
|
|
|
if (!dir.Exists)
|
|
throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDirName}");
|
|
|
|
DirectoryInfo[] dirs = dir.GetDirectories();
|
|
|
|
// If the destination directory doesn't exist, create it.
|
|
Directory.CreateDirectory(destDirName);
|
|
|
|
// Get the files in the directory and copy them to the new location.
|
|
FileInfo[] files = dir.GetFiles();
|
|
foreach (FileInfo file in files)
|
|
{
|
|
string tempPath = Path.Combine(destDirName, file.Name);
|
|
file.CopyTo(tempPath, true);
|
|
}
|
|
|
|
if (!copySubDirs)
|
|
return;
|
|
|
|
// If copying subdirectories, copy them and their contents to new location.
|
|
foreach (DirectoryInfo subDir in dirs)
|
|
{
|
|
string tempPath = Path.Combine(destDirName, subDir.Name);
|
|
DirectoryCopy(subDir.FullName, tempPath, true);
|
|
}
|
|
}
|
|
|
|
private static void SaveAndroidIcon(Image icon, int dimensions, string filePath)
|
|
{
|
|
Image picture = icon;
|
|
picture.Mutate(x => x.Resize(dimensions, dimensions, KnownResamplers.NearestNeighbor));
|
|
picture.SaveAsPng(filePath);
|
|
}
|
|
} |