예제 #1
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;
        }