Beispiel #1
0
    /// <summary>
    /// Creates an APK of the selected <paramref name="profile"/>.
    /// </summary>
    /// <param name="profile"><see cref="ProfileXML"/> to be compiled into an APK.</param>
    /// <param name="useHqMusic">Wether to create the APK with high quality music or not.</param>
    /// <param name="progress">Provides the current progress of this method.</param>
    public static void CreateAPK(ProfileXML profile, bool useHqMusic, IProgress <int> progress)
    {
        // Overall safety check just in case of bad situations
        if (!profile.SupportsAndroid)
        {
            progress.Report(100);
            return;
        }

        log.Info("Creating Android APK for profile " + profile.Name + ".");

        // Create working dir after some cleanup
        string apktoolPath = CrossPlatformOperations.CURRENTPATH + "/PatchData/utilities/android/apktool.jar",
               uberPath    = CrossPlatformOperations.CURRENTPATH + "/PatchData/utilities/android/uber-apk-signer.jar",
               tempDir     = new DirectoryInfo(CrossPlatformOperations.CURRENTPATH + "/temp").FullName,
               dataPath    = CrossPlatformOperations.CURRENTPATH + profile.DataPath;

        if (Directory.Exists(tempDir))
        {
            Directory.Delete(tempDir, true);
        }
        Directory.CreateDirectory(tempDir);

        log.Info("Cleanup, variables, and working directory created.");
        progress.Report(14);

        // Decompile AM2RWrapper.apk
        CrossPlatformOperations.RunJavaJar("\"" + apktoolPath + "\" d \"" + dataPath + "/android/AM2RWrapper.apk\"", tempDir);
        log.Info("AM2RWrapper decompiled.");
        progress.Report(28);

        // Add datafiles: 1.1, new datafiles, hq music, am2r.ini
        string workingDir = tempDir + "/AM2RWrapper/assets";

        ZipFile.ExtractToDirectory(CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip", workingDir);
        HelperMethods.DirectoryCopy(dataPath + "/files_to_copy", workingDir);

        if (useHqMusic)
        {
            HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/HDR_HQ_in-game_music", workingDir);
        }
        // Yes, I'm aware this is dumb. If you've got any better ideas for how to copy a seemingly randomly named .ini from this folder to the APK, please let me know.
        foreach (FileInfo file in new DirectoryInfo(dataPath).GetFiles().Where(f => f.Name.EndsWith("ini")))
        {
            File.Copy(file.FullName, workingDir + "/" + file.Name);
        }

        log.Info("AM2R_11.zip extracted and datafiles copied into AM2RWrapper.");
        progress.Report(42);

        // Patch data.win to game.droid
        CrossPlatformOperations.ApplyXdeltaPatch(workingDir + "/data.win", dataPath + "/droid.xdelta", workingDir + "/game.droid");
        log.Info("game.droid successfully patched.");
        progress.Report(56);

        // Delete unnecessary files
        File.Delete(workingDir + "/AM2R.exe");
        File.Delete(workingDir + "/D3DX9_43.dll");
        File.Delete(workingDir + "/explanations.txt");
        File.Delete(workingDir + "/modifiers.ini");
        File.Delete(workingDir + "/readme.txt");
        File.Delete(workingDir + "/data.win");
        Directory.Delete(workingDir + "/mods", true);
        Directory.Delete(workingDir + "/lang/headers", true);
        if (OS.IsLinux)
        {
            File.Delete(workingDir + "/icon.png");
        }

        // Modify apktool.yml to NOT compress ogg files
        string apktoolText = File.ReadAllText(workingDir + "/../apktool.yml");

        apktoolText = apktoolText.Replace("doNotCompress:", "doNotCompress:\n- ogg");
        File.WriteAllText(workingDir + "/../apktool.yml", apktoolText);
        log.Info("Unnecessary files removed, apktool.yml modified to prevent ogg compression.");
        progress.Report(70);

        // Rebuild APK
        CrossPlatformOperations.RunJavaJar("\"" + apktoolPath + "\" b AM2RWrapper -o \"" + profile.Name + ".apk\"", tempDir);
        log.Info("AM2RWrapper rebuilt into " + profile.Name + ".apk.");
        progress.Report(84);

        // Debug-sign APK
        CrossPlatformOperations.RunJavaJar("\"" + uberPath + "\" -a \"" + profile.Name + ".apk\"", tempDir);

        // Extra file cleanup
        File.Copy(tempDir + "/" + profile.Name + "-aligned-debugSigned.apk", CrossPlatformOperations.CURRENTPATH + "/" + profile.Name + ".apk", true);
        log.Info(profile.Name + ".apk signed and moved to " + CrossPlatformOperations.CURRENTPATH + "/" + profile.Name + ".apk.");
        HelperMethods.DeleteDirectory(tempDir);

        // Done
        progress.Report(100);
        log.Info("Successfully created Android APK for profile " + profile.Name + ".");
        CrossPlatformOperations.OpenFolderAndSelectFile(CrossPlatformOperations.CURRENTPATH + "/" + profile.Name + ".apk");
    }
Beispiel #2
0
    /// <summary>
    /// Installs <paramref name="profile"/>.
    /// </summary>
    /// <param name="profile"><see cref="ProfileXML"/> to be installed.</param>
    /// <param name="useHqMusic">Wether to patch this with high quality music or not.</param>
    /// <param name="progress">Provides the current progress of this method.</param>
    public static void InstallProfile(ProfileXML profile, bool useHqMusic, IProgress <int> progress)
    {
        log.Info("Installing profile " + profile.Name + "...");

        string profilesHomePath = CrossPlatformOperations.CURRENTPATH + "/Profiles";
        string profilePath      = profilesHomePath + "/" + profile.Name;

        // Failsafe for Profiles directory
        if (!Directory.Exists(profilesHomePath))
        {
            Directory.CreateDirectory(profilesHomePath);
        }


        // This failsafe should NEVER get triggered, but Miepee's broken this too much for me to trust it otherwise.
        if (Directory.Exists(profilePath))
        {
            Directory.Delete(profilePath, true);
        }

        // Create profile directory
        Directory.CreateDirectory(profilePath);

        // Switch profilePath on Gtk
        if (OS.IsLinux)
        {
            profilePath += "/assets";
            Directory.CreateDirectory(profilePath);
        }
        else if (OS.IsMac)
        {
            // Folder structure for mac is like this:
            // am2r.app -> Contents
            //     -Frameworks (some libs)
            //     -MacOS (runner)
            //     -Resources (asset path)
            profilePath += "/AM2R.app/Contents";
            Directory.CreateDirectory(profilePath);
            Directory.CreateDirectory(profilePath + "/MacOS");
            Directory.CreateDirectory(profilePath + "/Resources");
            profilePath += "/Resources";

            log.Info("ProfileInstallation: Created folder structure.");
        }

        // Extract 1.1
        ZipFile.ExtractToDirectory(CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip", profilePath);

        // Extracted 1.1
        progress.Report(33);
        log.Info("Profile folder created and AM2R_11.zip extracted.");

        // Set local datapath for installation files
        var dataPath = CrossPlatformOperations.CURRENTPATH + profile.DataPath;

        string datawin = null, exe = null;

        if (OS.IsWindows)
        {
            datawin = "data.win";
            exe     = "AM2R.exe";
        }
        else if (OS.IsLinux)
        {
            datawin = "game.unx";
            // Use the exe name based on the desktop file in the appimage, rather than hardcoding it.
            string desktopContents = File.ReadAllText(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/AM2R.AppDir/AM2R.desktop");
            exe = Regex.Match(desktopContents, @"(?<=Exec=).*").Value;
            log.Info("According to AppImage desktop file, using \"" + exe + "\" as game name.");
        }
        else if (OS.IsMac)
        {
            datawin = "game.ios";
            exe     = "Mac_Runner";
        }
        else
        {
            log.Error(OS.Name + " does not have valid runner / data.win names!");
        }

        log.Info("Attempting to patch in " + profilePath);

        if (OS.IsWindows)
        {
            // Patch game executable
            if (profile.UsesYYC)
            {
                CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/data.win", dataPath + "/AM2R.xdelta", profilePath + "/" + exe);

                // Delete 1.1's data.win, we don't need it anymore!
                File.Delete(profilePath + "/data.win");
            }
            else
            {
                CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/data.win", dataPath + "/data.xdelta", profilePath + "/" + datawin);
                CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/AM2R.exe", dataPath + "/AM2R.xdelta", profilePath + "/" + exe);
            }
        }
        else if (OS.IsUnix) // YYC and VM look exactly the same on Linux and Mac so we're all good here.
        {
            CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/data.win", dataPath + "/game.xdelta", profilePath + "/" + datawin);
            CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/AM2R.exe", dataPath + "/AM2R.xdelta", profilePath + "/" + exe);
            // Just in case the resulting file isn't chmoddded...
            Process.Start("chmod", "+x  \"" + profilePath + "/" + exe + "\"")?.WaitForExit();

            // These are not needed by linux or Mac at all, so we delete them
            File.Delete(profilePath + "/data.win");
            File.Delete(profilePath + "/AM2R.exe");
            File.Delete(profilePath + "/D3DX9_43.dll");

            // Move exe one directory out on Linux, move to MacOS folder instead on Mac
            if (OS.IsLinux)
            {
                File.Move(profilePath + "/" + exe, profilePath.Substring(0, profilePath.LastIndexOf("/")) + "/" + exe);
            }
            else
            {
                File.Move(profilePath + "/" + exe, profilePath.Replace("Resources", "MacOS") + "/" + exe);
            }
        }
        else
        {
            log.Error(OS.Name + " does not have patching methods!");
        }

        // Applied patch
        if (OS.IsWindows || OS.IsMac)
        {
            progress.Report(66);
        }
        else if (OS.IsLinux)
        {
            progress.Report(44);                  // Linux will take a bit longer, due to appimage creation
        }
        log.Info("xdelta patch(es) applied.");

        // Install new datafiles
        HelperMethods.DirectoryCopy(dataPath + "/files_to_copy", profilePath);

        // HQ music
        if (!profile.UsesCustomMusic && useHqMusic)
        {
            HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/HDR_HQ_in-game_music", profilePath);
        }


        // Linux post-process
        if (OS.IsLinux)
        {
            string assetsPath = profilePath;
            profilePath = profilePath.Substring(0, profilePath.LastIndexOf("/"));

            // Rename all songs to lowercase
            foreach (var file in new DirectoryInfo(assetsPath).GetFiles())
            {
                if (file.Name.EndsWith(".ogg") && !File.Exists(file.DirectoryName + "/" + file.Name.ToLower()))
                {
                    File.Move(file.FullName, file.DirectoryName + "/" + file.Name.ToLower());
                }
            }

            // Copy AppImage template to here
            HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/AM2R.AppDir", profilePath + "/AM2R.AppDir/");

            // Safety checks, in case the folders don't exist
            Directory.CreateDirectory(profilePath + "/AM2R.AppDir/usr/bin/");
            Directory.CreateDirectory(profilePath + "/AM2R.AppDir/usr/bin/assets/");

            // Copy game assets to the appimageDir
            HelperMethods.DirectoryCopy(assetsPath, profilePath + "/AM2R.AppDir/usr/bin/assets/");
            File.Copy(profilePath + "/" + exe, profilePath + "/AM2R.AppDir/usr/bin/" + exe);

            progress.Report(66);
            log.Info("Gtk-specific formatting finished.");

            // Temp save the currentWorkingDirectory and console.error, change it to profilePath and null, call the script, and change it back.
            string     workingDir = Directory.GetCurrentDirectory();
            TextWriter cliError   = Console.Error;
            Directory.SetCurrentDirectory(profilePath);
            Console.SetError(new StreamWriter(Stream.Null));
            Environment.SetEnvironmentVariable("ARCH", "x86_64");
            Process.Start(CrossPlatformOperations.CURRENTPATH + "/PatchData/utilities/appimagetool-x86_64.AppImage", "-n AM2R.AppDir")?.WaitForExit();
            Directory.SetCurrentDirectory(workingDir);
            Console.SetError(cliError);

            // Clean files
            Directory.Delete(profilePath + "/AM2R.AppDir", true);
            Directory.Delete(assetsPath, true);
            File.Delete(profilePath + "/" + exe);
            if (File.Exists(profilePath + "/AM2R.AppImage"))
            {
                File.Delete(profilePath + "/AM2R.AppImage");
            }
            File.Move(profilePath + "/" + "AM2R-x86_64.AppImage", profilePath + "/AM2R.AppImage");
        }
        // Mac post-process
        else if (OS.IsMac)
        {
            // Rename all songs to lowercase
            foreach (var file in new DirectoryInfo(profilePath).GetFiles())
            {
                if (file.Name.EndsWith(".ogg") && !File.Exists(file.DirectoryName + "/" + file.Name.ToLower()))
                {
                    File.Move(file.FullName, file.DirectoryName + "/" + file.Name.ToLower());
                }
            }
            // Loading custom fonts crashes on Mac, so we delete those if they exist
            if (Directory.Exists(profilePath + "/lang/fonts"))
            {
                Directory.Delete(profilePath + "/lang/fonts", true);
            }
            // Move Frameworks, Info.plist and PkgInfo over
            HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/Frameworks", profilePath.Replace("Resources", "Frameworks"));
            File.Copy(dataPath + "/Info.plist", profilePath.Replace("Resources", "") + "/Info.plist", true);
            File.Copy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/PkgInfo", profilePath.Replace("Resources", "") + "/PkgInfo", true);
            //Put profilePath back to what it was before
            profilePath = profilesHomePath + "/" + profile.Name;
        }

        // Copy profile.xml so we can grab data to compare for updates later!
        // tldr; check if we're in PatchData or not
        if (new DirectoryInfo(dataPath).Parent?.Name == "PatchData")
        {
            File.Copy(dataPath + "/../profile.xml", profilePath + "/profile.xml");
        }
        else
        {
            File.Copy(dataPath + "/profile.xml", profilePath + "/profile.xml");
        }

        // Installed datafiles
        progress.Report(100);
        log.Info("Successfully installed profile " + profile.Name + ".");
    }