コード例 #1
0
        public void UpdatePatch()
        {
            // deserialize patch.json
            string          json      = File.ReadAllText(PatchLocation);
            JObject         o         = JObject.Parse(json);
            TowerclimbPatch patchData = o.ToObject <TowerclimbPatch>();
            // update stringmap

            var alteredStrings = new List <PatchableString>();

            for (int j = 0; j < PatchableStrings.Length; j++)
            {
                if (PatchableStrings[j].Override != PatchableStrings[j].Original)
                {
                    alteredStrings.Add(PatchableStrings[j]);
                }
            }
            patchData.LastTcrVersion = TcrVersion;
            patchData.StringMap      = alteredStrings.ToArray();
            patchData.Fonts          = PatchableFonts;
            patchData.Menus          = PatchableMenus;
            // reserialize patch.json
            byte[] jsonBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(patchData));
            File.WriteAllBytes(PatchLocation, jsonBytes);
        }
コード例 #2
0
        public void ExportPatch(string exportDir)
        {
            byte[] bytes = File.ReadAllBytes(PatchTarget);


            DelimitedData[] outputData = bytes.LocateDelimited(PNG_BEGIN, PNG_IEND);


            for (int i = 0; i < outputData.Length; i++)
            {
                string finalExportPath = exportDir + @"\" + i.ToString("00000") + ".png";
                if (bgw.IsBusy)
                {
                    int percents = (i * 100) / outputData.Length;
                    bgw.ReportProgress(percents, finalExportPath);
                }
                // if size is negative for some reason (meaning iend is before beginning which shouldn't happen) just write a file with a single null byte and continue
                if (Math.Sign(outputData[i].Size) == -1)
                {
                    File.WriteAllBytes(finalExportPath, new byte[] { 0x00 });
                    continue;
                }
                File.WriteAllBytes(finalExportPath, bytes.Skip(outputData[i].Start).Take(outputData[i].Size).ToArray());

                // add any altered strings to final patch export
                var alteredStrings = new List <PatchableString>();
                for (int j = 0; j < PatchableStrings.Length; j++)
                {
                    if (PatchableStrings[j].Override != PatchableStrings[j].Original)
                    {
                        alteredStrings.Add(PatchableStrings[j]);
                    }
                }
                TowerclimbPatch patchData = new TowerclimbPatch
                {
                    CreatedTcrVersion = TcrVersion,
                    LastTcrVersion    = TcrVersion,
                    GameVersion       = GameVersion,
                    ImageCount        = ImageCount,
                    StringMap         = alteredStrings.ToArray(),
                    PngMap            = outputData,
                    Menus             = PatchableMenus,
                    Fonts             = PatchableFonts
                };

                byte[] jsonBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(patchData));
                File.WriteAllBytes(exportDir + @"\patch.json", jsonBytes);
            }
            InfoNotice(this, new NotificationEventArgs($"Patch exported successfully to {exportDir}"));
        }
コード例 #3
0
        public void ApplyMemoryPatch(string patchFile)
        {
            // exit immediately if the process was closed
            if (!ProcessSharp.IsRunning)
            {
                InfoNotice(this, new NotificationEventArgs($"Process is not running."));
                ProcessSharp = null;
                return;
            }

            // read the game's entire mainmodule image into a big byte array (threads should have hopefully been frozen before everything gets copied into private memory, but after steam has already set things in motion)
            IntPtr processHandle = OpenProcess(0x0008 | 0x0020 | 0x0010, false, ProcessSharp.Native.Id);
            IntPtr offset        = ProcessSharp.Native.MainModule.BaseAddress;
            IntPtr endOffset     = IntPtr.Add(offset, ProcessSharp.Native.MainModule.ModuleMemorySize);

            byte[] bytes     = new byte[ProcessSharp.Native.MainModule.ModuleMemorySize];
            int    bytesRead = 0;

            ReadProcessMemory((int)processHandle, (int)offset, bytes, bytes.Length, ref bytesRead);


            string patchDir;

            if (Path.GetExtension(patchFile) == ".zip")
            {
                // clear amd (re)create temp folder
                patchDir = Directory.CreateDirectory($"{Path.GetTempPath()}\\tcrepainter_").FullName;
                Directory.Delete(patchDir, true);
                patchDir = Directory.CreateDirectory($"{Path.GetTempPath()}\\tcrepainter_").FullName;
                // extract to temp folder
                ZipFile.ExtractToDirectory(patchFile, patchDir);
                // navigate down directories to find patch.json if neccesary
                while (true)
                {
                    if (File.Exists(patchDir + @"\patch.json"))
                    {
                        break;
                    }
                    if (Directory.GetDirectories(patchDir).Length > 0)
                    {
                        patchDir = Directory.GetDirectories(patchDir)[0];
                    }
                    else
                    {
                        throw new Exception("Could not find patch.json file in the selected patch.");
                    }
                }
            }
            else
            {
                patchDir = Path.GetDirectoryName(patchFile);
            }

            // get list of images from patchdir
            string[] images = Directory.GetFiles(patchDir, "*.png");
            // deserialize patch.json
            string          json      = File.ReadAllText(patchDir + @"\patch.json");
            JObject         o         = JObject.Parse(json);
            TowerclimbPatch patchData = o.ToObject <TowerclimbPatch>();

            // locate png headers in process memory
            DelimitedData[] imageLocations = bytes.LocateDelimited(PNG_BEGIN, PNG_IEND);

            // break and explain to user if patch version does not match the version they are trying to patch
            if (patchData.ImageCount != imageLocations.Length)
            {
                InfoNotice(this, new NotificationEventArgs($"You are attempting to patch a version of the game containing {imageLocations.Length} images with a patch made for version \"{patchData.GameVersion}\" which contains {patchData.ImageCount} images. This isn't going to work."));
                return;
            }

            // start reading images into memory
            byte[][] imagesData = new byte[patchData.PngMap.Length][];
            for (int i = 0; i < images.Length; i++)
            {
                bgw.ReportProgress((i * 100) / images.Length, $"reading {images[i]}");
                // get id of image from filename to make sure they are array'd in the order they appear in the executable
                int    idx    = images[i].LastIndexOf('\\');
                string imgNum = images[i].Substring(idx + 1, 5);
                if (!imgNum.All(Char.IsDigit))
                {
                    continue;
                }
                int id = int.Parse(imgNum);
                imagesData[id] = (File.ReadAllBytes(images[i]));
            }

            // the relative positions of everything of concern in the mainmodule the same as the exe, but the byte arrays aren't neccesarily identical in size --
            // save the difference so that it can be factored in to the saved Start and End values to determine exactly where strings should go in the module
            int moduleOffset = imageLocations[0].Start - patchData.PngMap[0].Start;


            // write images to corresponding found locations
            bool ignoreSizeWarnings = false;

            for (int i = 0; i < patchData.PngMap.Length; i++)
            {
                bgw.ReportProgress((i * 100) / images.Length, $"overwriting image {i}");

                // if index was never filled (nothing in patch folder for this image number) then skip
                if (imagesData[i] == null)
                {
                    continue;
                }
                // if size is too big to patch back in, skip file and warn user unless they elect not to hear
                if (imagesData[i].Length > patchData.PngMap[i].Size)
                {
                    if (!ignoreSizeWarnings)
                    {
                        Action <bool> setIgnoreSizeWarnings = (bool newSetting) => { ignoreSizeWarnings = newSetting; };
                        YesNoPromptNotice(this, new RequestEventArgs <bool>($"Image {i} (size {imagesData[i].Length}) is larger than the original file it is replacing (size {patchData.PngMap[i].Size}) and will not be patched in.\nClick no to ignore further warnings.", setIgnoreSizeWarnings));
                    }
                    continue;
                }

                // overwrite source image bytes with new image bytes
                ProcessSharp.Write((IntPtr)imageLocations[i].Start, imagesData[i]);
            }

            // patch in strings
            PatchableString[] totalPatchableStrings = patchData.StringMap
                                                      .Concat(patchData.Fonts ?? new PatchableString[0])
                                                      .Concat(patchData.Menus != null ? (from menu in patchData.Menus select menu.Body) : new PatchableString[0])
                                                      .Concat(patchData.Menus != null ? (from menu in patchData.Menus where menu.Recipe != null select menu.Recipe) : new PatchableString[0])
                                                      .ToArray();
            foreach (PatchableString patchString in totalPatchableStrings)
            {
                ProcessSharp.WriteString((IntPtr)(patchString.Start + moduleOffset), patchString.Override, Encoding.Default);
            }

            // clean up and resume game
            InfoNotice(this, new NotificationEventArgs($"Memory patched successfully.\nGame may take a moment to display."));
            ProcessSharp.Threads.ResumeAll();
            ProcessSharp = null;
        }
コード例 #4
0
        public void ApplyFilePatch(string patchFile)
        {
            // are you sure
            bool          continueFilePatch    = true;
            Action <bool> setContinueFilePatch = newSetting => continueFilePatch = newSetting;

            YesNoPromptNotice(this, new RequestEventArgs <bool>("Directly patching the executable will not work with versions of the game that run though Steam\nAre you sure you want to do this?", setContinueFilePatch));
            if (!continueFilePatch)
            {
                return;
            }
            string patchDir;

            if (Path.GetExtension(patchFile) == ".zip")
            {
                // clear amd (re)create temp folder
                patchDir = Directory.CreateDirectory($"{Path.GetTempPath()}\\tcrepainter_").FullName;
                Directory.Delete(patchDir, true);
                patchDir = Directory.CreateDirectory($"{Path.GetTempPath()}\\tcrepainter_").FullName;
                // extract to temp folder
                ZipFile.ExtractToDirectory(patchFile, patchDir);
                // navigate down directories to find patch.json if neccesary
                while (true)
                {
                    if (File.Exists(patchDir + @"\patch.json"))
                    {
                        break;
                    }
                    if (Directory.GetDirectories(patchDir).Length > 0)
                    {
                        patchDir = Directory.GetDirectories(patchDir)[0];
                    }
                    else
                    {
                        throw new Exception("Could not find patch.json file in the selected patch.");
                    }
                }
            }
            else
            {
                patchDir = Path.GetDirectoryName(patchFile);
            }
            byte[] gameBytes = File.ReadAllBytes(PatchTarget);
            // get list of images from patchdir
            string[] images = Directory.GetFiles(patchDir, "*.png");
            // deserialize patch.json
            string          json      = File.ReadAllText(patchDir + @"\patch.json");
            JObject         o         = JObject.Parse(json);
            TowerclimbPatch patchData = o.ToObject <TowerclimbPatch>();

            // break and explain to user if patch version does not match the exe they are trying to patch
            if (patchData.ImageCount != ImageCount)
            {
                InfoNotice(this, new NotificationEventArgs($"You are attempting to patch a game of version \"{GameVersion}\" ({ImageCount} images) with a patch made for version \"{patchData.GameVersion}\" ({patchData.ImageCount} images). This isn't going to work."));
                return;
            }

            // start reading images into memory
            byte[][] imagesData = new byte[patchData.PngMap.Length][];
            for (int i = 0; i < images.Length; i++)
            {
                if (bgw.IsBusy)
                {
                    bgw.ReportProgress((i * 100) / images.Length, $"reading {images[i]}");
                }
                // get id of image from filename to make sure they are array'd in the order they appear in the executable
                int    idx    = images[i].LastIndexOf('\\');
                string imgNum = images[i].Substring(idx + 1, 5);
                if (!imgNum.All(Char.IsDigit))
                {
                    continue;
                }
                int id = int.Parse(imgNum);
                imagesData[id] = (File.ReadAllBytes(images[i]));
            }
            // write images to locations specified in exe by patch.json's PngMap
            bool ignoreSizeWarnings = false;

            for (int i = 0; i < patchData.PngMap.Length; i++)
            {
                if (bgw.IsBusy)
                {
                    bgw.ReportProgress((i * 100) / images.Length, $"overwriting image {i}");
                }

                // if index was never filled (nothing in patch folder for this image number) then skip
                if (imagesData[i] == null)
                {
                    continue;
                }
                // if size is too big to patch back in, skip file and warn user unless they elect not to hear
                if (imagesData[i].Length > patchData.PngMap[i].Size)
                {
                    if (!ignoreSizeWarnings)
                    {
                        Action <bool> setIgnoreSizeWarnings = (bool newSetting) => { ignoreSizeWarnings = newSetting; };
                        YesNoPromptNotice(this, new RequestEventArgs <bool>($"Image {i} (size {imagesData[i].Length}) is larger than the original file it is replacing (size {patchData.PngMap[i].Size}) and will not be patched in.\nClick no to ignore further warnings.", setIgnoreSizeWarnings));
                    }
                    continue;
                }

                for (int j = 0; j < imagesData[i].Length; j++)
                {
                    gameBytes[j + patchData.PngMap[i].Start] = imagesData[i][j];
                }
            }

            // patch in all string things
            PatchableString[] totalPatchableStrings = patchData.StringMap
                                                      .Concat(patchData.Fonts ?? new PatchableString[0])
                                                      .Concat(patchData.Menus != null ? (from menu in patchData.Menus select menu.Body) : new PatchableString[0])
                                                      .Concat(patchData.Menus != null ? (from menu in patchData.Menus where menu.Recipe != null select menu.Recipe) : new PatchableString[0])
                                                      .ToArray();
            for (int i = 0; i < totalPatchableStrings.Length; i++)
            {
                PatchableString patchString      = totalPatchableStrings[i];
                List <byte>     patchStringBytes = Encoding.Default.GetBytes(patchString.Override).Append <byte>(0x00).ToList();
                for (int j = 0; j < patchString.Override.Length + 1; j++)
                {
                    gameBytes[patchString.Start + j] = patchStringBytes[j];
                }
            }



            File.WriteAllBytes(PatchTarget, gameBytes);
            InfoNotice(this, new NotificationEventArgs("Patched game successfully."));
        }