diff --git a/Refresher/Patching/Patcher.cs b/Refresher/Patching/Patcher.cs index fd90705..bdba022 100644 --- a/Refresher/Patching/Patcher.cs +++ b/Refresher/Patching/Patcher.cs @@ -39,12 +39,16 @@ public Patcher(Stream stream) /// A list of the URLs and Digest keys private static List FindPatchableElements(Stream file) { + long start = Stopwatch.GetTimestamp(); + file.Position = 0; using IELF? elf = ELFReader.Load(file, false); file.Position = 0; - BinaryReader reader = new(file); - + //Buffer the stream with a size of 4096 + BufferedStream bufferedStream = new(file, 4096); + BinaryReader reader = new(bufferedStream); + // The string "http" in ASCII, as an int int httpInt = BitConverter.ToInt32("http"u8); @@ -56,65 +60,56 @@ private static List FindPatchableElements(Stream file) //Found positions of `cook` in the binary List cookiePositions = new(); - foreach (ISection section in elf.Sections) + long read = 0; + + //Create an array twice the size of the data we are wanting to check + Span arr = new byte[8]; + while (reader.Read(arr) == arr.Length) { - // Assume a word size of 4, i would use `ProgBitsSection.Alignment` - // but that seems to be unrelated to the actual alignment and offset chosen for the string constants specifically (the section with all the strings is marked as 32-byte alignment[?????]) - // so we'll just assume 4 byte alignment since that seems universal here - int wordSize = 4; - ulong sectionLength; - switch (section) - { - case ProgBitsSection progBitsSectionUlong: - file.Position = (long)progBitsSectionUlong.Offset; - sectionLength = progBitsSectionUlong.Size; - break; - case ProgBitsSection progBitsSectionUint: - file.Position = progBitsSectionUint.Offset; - sectionLength = progBitsSectionUint.Size; - break; - default: - continue; - } - - int read = 0; - - byte[] buf = new byte[wordSize]; - //While we are not at the end of the file, read each word in - while (read < (int)sectionLength - wordSize) + long? found = null; + for (int i = 0; i < 5; i++) { - read += file.Read(buf); + int check = BitConverter.ToInt32(arr[i..(i + 4)]); - int bufInt = BitConverter.ToInt32(buf); - - //If they are equal, we found an instance of HTTP - if (bufInt == httpInt) + if (check == httpInt) { - //Mark the position of the found instance - possibleUrls.Add(reader.BaseStream.Position - wordSize); + possibleUrls.Add(read + i - 4); + found = read + i - 4; + break; } - if (bufInt == cookieStartInt) + // ReSharper disable once InvertIf + if (check == cookieStartInt) { - cookiePositions.Add(reader.BaseStream.Position - wordSize); + cookiePositions.Add(read + i - 4); + found = read + i - 4; + break; } } - } + //Seek 4 bytes after the position we started at, or 4 bytes after the starting index of a match + reader.BaseStream.Seek(found == null ? read + 4 : found.Value + 4, SeekOrigin.Begin); + + read += arr.Length; + } + List foundItems = new(); - FilterValidUrls(file, possibleUrls, reader, foundItems); - FindDigestAroundCookie(file, cookiePositions, reader, foundItems); + FilterValidUrls(reader, possibleUrls, foundItems); + FindDigestAroundCookie(reader, cookiePositions, foundItems); + + long end = Stopwatch.GetTimestamp(); + Console.WriteLine($"Detecting patchables took {(double)(end - start) / (double)Stopwatch.Frequency} seconds!"); return foundItems; } - private static void FilterValidUrls(Stream file, List foundPossibleUrlPositions, BinaryReader reader, List foundItems) + private static void FilterValidUrls(BinaryReader reader, List foundPossibleUrlPositions, List foundItems) { bool tooLong = false; foreach (long foundPosition in foundPossibleUrlPositions) { int len = 0; - file.Position = foundPosition; + reader.BaseStream.Position = foundPosition; //Find the first null byte while (reader.ReadByte() != 0) @@ -137,13 +132,16 @@ private static void FilterValidUrls(Stream file, List foundPossibleUrlPosi //Keep reading until we arent at a null byte while (reader.ReadByte() == 0) len++; - file.Position = foundPosition; + //Remove one from length to make sure to leave a single null byte after + len -= 1; + + reader.BaseStream.Position = foundPosition; //`len` at this point is the amount of bytes that are actually available to repurpose //This includes all extra null bytes except for the last one byte[] match = new byte[len]; - if (file.Read(match) < len) continue; + if (reader.Read(match) < len) continue; string str = Encoding.UTF8.GetString(match).TrimEnd('\0'); if (str.Contains('%')) continue; // Ignore printf strings, e.g. %s @@ -159,11 +157,11 @@ private static void FilterValidUrls(Stream file, List foundPossibleUrlPosi } } - private static void FindDigestAroundCookie(Stream file, List foundPossibleCookiePositions, BinaryReader reader, List foundItems) + private static void FindDigestAroundCookie(BinaryReader reader, List foundPossibleCookiePositions, List foundItems) { foreach (long foundPosition in foundPossibleCookiePositions) { - file.Position = foundPosition; + reader.BaseStream.Position = foundPosition; byte[] cookieBuf = new byte[8]; //If we didnt read enough or what we read isnt "cookie\0\0" @@ -176,7 +174,7 @@ private static void FindDigestAroundCookie(Stream file, List foundPossible const int checkSize = 1000; //Go back half the check size in bytes (so that `cookie` is in the middle) - file.Position -= checkSize / 2; + reader.BaseStream.Position -= checkSize / 2; byte[] checkArr = new byte[checkSize]; Span toCheck = checkArr.AsSpan().Slice(0, reader.Read(checkArr)); @@ -209,7 +207,7 @@ private static void FindDigestAroundCookie(Stream file, List foundPossible foundItems.Add(new PatchTargetInfo { Length = len, - Offset = file.Position - checkSize + start, + Offset = reader.BaseStream.Position - checkSize + start, Data = str, Type = PatchTargetType.Digest, }); diff --git a/Refresher/UI/IntegratedPatchForm.cs b/Refresher/UI/IntegratedPatchForm.cs index e88beaf..49011ae 100644 --- a/Refresher/UI/IntegratedPatchForm.cs +++ b/Refresher/UI/IntegratedPatchForm.cs @@ -25,19 +25,21 @@ public abstract class IntegratedPatchForm : PatchForm [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] protected IntegratedPatchForm(string subtitle) : base(subtitle) { - this.FormPanel = new TableLayout(new List + List rows = new() { this.AddRemoteField(), AddField("Game to patch", out this._gameDropdown, forceHeight: 56), - AddField("Server URL", out this.UrlField), - }); + AddField("Server URL", out this.UrlField), + }; if (!this.ShouldReplaceExecutable) { - this.FormPanel.Rows.Add(AddField("Identifier (EBOOT..elf)", out this._outputField)); + rows.Add(AddField("Identifier (EBOOT..elf)", out this._outputField)); this._outputField!.PlaceholderText = "refresh"; } + this.FormPanel = new TableLayout(rows); + this._gameDropdown.SelectedValueChanged += this.GameChanged; this.InitializePatcher(); @@ -171,7 +173,7 @@ public override void CompletePatch(object? sender, EventArgs e) { fileToUpload = this._tempFile; } - string destinationFile = this.ShouldReplaceExecutable ? "EBOOT.BIN" : $"EBOOT.{identifier}.BIN"; + string destinationFile = this.ShouldReplaceExecutable ? "EBOOT.BIN" : this.NeedsResign ? $"EBOOT.{identifier}.BIN" : $"EBOOT.{identifier}.elf"; string destination = Path.Combine(this._usrDir, destinationFile); // if we're replacing the executable, back it up to EBOOT.BIN.ORIG before we do so