using System.IO.Compression; namespace AM2RPortHelperLib; public abstract class LauncherModsBase : ModsBase { /// /// Ports a Mod zip intended to be installed via the AM2RLauncher to other operating systems. /// /// The path to the AM2RLauncher mod zip that should be ported. /// The target operating system to port the /// Whether Android should be inlcuded in the port. /// The path where the ported AM2RLauncher mod zip should be saved. /// The path to an AM2R 1.1 zip path. This is *required* if the input launcher zip is for Mac and will be ignored if the input zip is for anything else. /// The function that should handle in-progress output messages. /// WIP /// TODO: other exceptions 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 filesToCopyDir = extractDirectory + "/files_to_copy"; // Check if temp folder exists, delete if yes, extract zip to there if (Directory.Exists(extractDirectory)) Directory.Delete(extractDirectory, true); SendOutput("Extracting Launcher mod..."); ZipFile.ExtractToDirectory(inputLauncherZipPath, extractDirectory); var profile = Serializer.Deserialize(File.ReadAllText(extractDirectory + "/profile.xml")); if (profile.UsesYYC) throw new NotSupportedException("Launcher Mod is YYC, cannot port!"); string currentOS = profile.OperatingSystem; bool isAndroidIncluded = profile.SupportsAndroid; if (modTarget.ToString() == profile.OperatingSystem) { SendOutput("Target OS and Launcher OS are the same; exiting."); return; } // Run sha256 hash on runner to see if it's supported! string runnerHash = HelperMethods.CalculateSHA256(extractDirectory + "/AM2R.xdelta"); string[] allowedHashes = new[] { "b78c4fd2dc481f97b60440a5c89786da284b4aaeeba9fb2e3b48ac369cfe50d5", "243509f4270f448411c8405b71d7bc4f5d4fe5f3ecc1638d9c1218bf76b69f1f", "852b9a9466f99a53260b8147c6d286b81c145b2c10b00bb5c392b40b035811b5"}; // Don't check has on Windows, because the runner there has icons embedded in it, screwing off the hashes // TODO: find a way around that if (!(profile.OperatingSystem == "Windows" || allowedHashes.Contains(runnerHash))) throw new NotSupportedException("Invalid GM:S version! Porting Launcher mods is only supported for mods build with GM:S 1.4.1763!"); // TODO: Not sure if this is ever gonna be possible, since it requires one to shift back the patch. // We'd need a 1.1 file to apply the patch to, run that with umtlib to shift it back, and then apply a new patch. if (profile.OperatingSystem == "Mac") throw new NotSupportedException("Porting Mac mods is currently not supported!"); switch (modTarget) { case Core.ModOS.Windows: { // We have a non-windows launcher mod, where data file patch is guaranteed to be game.xdelta File.Move(extractDirectory + "/game.xdelta", extractDirectory + "/data.xdelta"); // get proper runner File.Delete(extractDirectory + "/AM2R.xdelta"); File.Copy(UtilDir + "/windowsRunner.xdelta", extractDirectory + "/AM2R.xdelta"); // Windows doesn't care about capitalization and because I can't predict how it originally was, I'm going to ignore it. // Windows doesn't have icons/splashes, so we remove those if they exist. if (!File.Exists(filesToCopyDir + "/icon.png")) File.Delete(filesToCopyDir + "/icon.png"); if (!File.Exists(filesToCopyDir + "/splash.png")) File.Delete(filesToCopyDir + "/splash.png"); // Properly set profile.xml variables. profile.OperatingSystem = "Windows"; profile.SaveLocation = currentOS switch { "Linux" => profile.SaveLocation.Replace("~/.config", "%localappdata%"), "Mac" => profile.SaveLocation.Replace("~/Library/Application Support", "%localappdata%"), _ => throw new NotSupportedException("Unsupported OS: " + currentOS) }; File.WriteAllText(extractDirectory + "/profile.xml",Serializer.Serialize(profile)); break; } case Core.ModOS.Linux: { if (currentOS == "Windows") File.Move(extractDirectory + "/data.xdelta", extractDirectory + "/game.xdelta"); // get proper runner File.Delete(extractDirectory + "/AM2R.xdelta"); File.Copy(UtilDir + "/linuxRunner.xdelta", extractDirectory + "/AM2R.xdelta"); // Linux needs everything lowercased. Only needed if we're coming from Windows if (currentOS == "Windows") HelperMethods.LowercaseFolder(extractDirectory + "/files_to_copy"); // Windows doesn't have icon/splash, so we copy them over from here if (!File.Exists(filesToCopyDir + "/icon.png")) File.Copy(UtilDir + "/icon.png", filesToCopyDir + "/icon.png"); if (!File.Exists(filesToCopyDir + "/splash.png")) File.Copy(UtilDir + "/splash.png", filesToCopyDir + "/splash.png"); // Properly set profile.xml variables profile.OperatingSystem = "Linux"; profile.SaveLocation = currentOS switch { "Windows" => profile.SaveLocation.Replace("%localappdata%", "~/.config"), "Mac" => profile.SaveLocation.Replace("~/Library/Application Support", "~/.config"), _ => throw new NotSupportedException("Unsupported OS " + currentOS) }; File.WriteAllText(extractDirectory + "/profile.xml",Serializer.Serialize(profile)); break; } case Core.ModOS.Mac: { // TODO: Not sure if this is ever gonna be possible, since it requires one to shift up the patch. // We'd need a 1.1 file to apply the patch to, run that with umtlib to shift it up, and then apply a new patch. throw new NotSupportedException("Porting Mac mods is currently not supported!"); } default: throw new ArgumentOutOfRangeException(nameof(modTarget), modTarget, "Unknown target to port to!"); } if (!includeAndroid) { if (File.Exists(extractDirectory + "/droid.xdelta")) File.Delete(extractDirectory + "/droid.xdelta"); if (Directory.Exists(extractDirectory + "/android")) Directory.Delete(extractDirectory + "/android", true); profile.SupportsAndroid = false; } else { // If APK is not there, we need to create the APK ourselves. if (!isAndroidIncluded) { //TODO: see above } profile.SupportsAndroid = true; } //zip the result SendOutput($"Creating Launcher zip for {modTarget}..."); ZipFile.CreateFromDirectory(extractDirectory, outputLauncherZipPath); // Clean up Directory.Delete(extractDirectory, true); } }