Skip to content

Commit

Permalink
Merge pull request #23 from LittleBigRefresh/fix-rpcs3-patching
Browse files Browse the repository at this point in the history
Fix various patching issues
  • Loading branch information
jvyden authored Aug 29, 2023
2 parents 46d74cf + aa33838 commit f2751c7
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 52 deletions.
92 changes: 45 additions & 47 deletions Refresher/Patching/Patcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ public Patcher(Stream stream)
/// <returns>A list of the URLs and Digest keys</returns>
private static List<PatchTargetInfo> 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);

Expand All @@ -56,65 +60,56 @@ private static List<PatchTargetInfo> FindPatchableElements(Stream file)
//Found positions of `cook` in the binary
List<long> 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<byte> arr = new byte[8];
while (reader.Read(arr) == arr.Length)
{
// Assume a word size of 4, i would use `ProgBitsSection<T>.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<ulong> progBitsSectionUlong:
file.Position = (long)progBitsSectionUlong.Offset;
sectionLength = progBitsSectionUlong.Size;
break;
case ProgBitsSection<uint> 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<PatchTargetInfo> 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<long> foundPossibleUrlPositions, BinaryReader reader, List<PatchTargetInfo> foundItems)
private static void FilterValidUrls(BinaryReader reader, List<long> foundPossibleUrlPositions, List<PatchTargetInfo> 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)
Expand All @@ -137,13 +132,16 @@ private static void FilterValidUrls(Stream file, List<long> 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
Expand All @@ -159,11 +157,11 @@ private static void FilterValidUrls(Stream file, List<long> foundPossibleUrlPosi
}
}

private static void FindDigestAroundCookie(Stream file, List<long> foundPossibleCookiePositions, BinaryReader reader, List<PatchTargetInfo> foundItems)
private static void FindDigestAroundCookie(BinaryReader reader, List<long> foundPossibleCookiePositions, List<PatchTargetInfo> 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"
Expand All @@ -176,7 +174,7 @@ private static void FindDigestAroundCookie(Stream file, List<long> 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<byte> toCheck = checkArr.AsSpan().Slice(0, reader.Read(checkArr));
Expand Down Expand Up @@ -209,7 +207,7 @@ private static void FindDigestAroundCookie(Stream file, List<long> foundPossible
foundItems.Add(new PatchTargetInfo
{
Length = len,
Offset = file.Position - checkSize + start,
Offset = reader.BaseStream.Position - checkSize + start,
Data = str,
Type = PatchTargetType.Digest,
});
Expand Down
12 changes: 7 additions & 5 deletions Refresher/UI/IntegratedPatchForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ public abstract class IntegratedPatchForm : PatchForm<Patcher>
[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
protected IntegratedPatchForm(string subtitle) : base(subtitle)

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (ubuntu-latest)

Non-nullable field '_tempFile' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (ubuntu-latest)

Non-nullable field '_usrDir' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (windows-latest)

Non-nullable field '_tempFile' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (windows-latest)

Non-nullable field '_usrDir' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (ubuntu-latest)

Non-nullable field '_tempFile' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (ubuntu-latest)

Non-nullable field '_usrDir' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (windows-latest)

Non-nullable field '_tempFile' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 26 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (windows-latest)

Non-nullable field '_usrDir' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
{
this.FormPanel = new TableLayout(new List<TableRow>
List<TableRow> 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.<value>.elf)", out this._outputField));
rows.Add(AddField("Identifier (EBOOT.<value>.elf)", out this._outputField));

Check warning on line 37 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (ubuntu-latest)

The type 'Eto.Forms.TextBox?' cannot be used as type parameter 'TControl' in the generic type or method 'PatchForm<Patcher>.AddField<TControl>(string, out TControl, Button?, int)'. Nullability of type argument 'Eto.Forms.TextBox?' doesn't match constraint type 'Eto.Forms.Control'.

Check warning on line 37 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (windows-latest)

The type 'Eto.Forms.TextBox?' cannot be used as type parameter 'TControl' in the generic type or method 'PatchForm<Patcher>.AddField<TControl>(string, out TControl, Button?, int)'. Nullability of type argument 'Eto.Forms.TextBox?' doesn't match constraint type 'Eto.Forms.Control'.

Check warning on line 37 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (ubuntu-latest)

The type 'Eto.Forms.TextBox?' cannot be used as type parameter 'TControl' in the generic type or method 'PatchForm<Patcher>.AddField<TControl>(string, out TControl, Button?, int)'. Nullability of type argument 'Eto.Forms.TextBox?' doesn't match constraint type 'Eto.Forms.Control'.

Check warning on line 37 in Refresher/UI/IntegratedPatchForm.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds (windows-latest)

The type 'Eto.Forms.TextBox?' cannot be used as type parameter 'TControl' in the generic type or method 'PatchForm<Patcher>.AddField<TControl>(string, out TControl, Button?, int)'. Nullability of type argument 'Eto.Forms.TextBox?' doesn't match constraint type 'Eto.Forms.Control'.
this._outputField!.PlaceholderText = "refresh";
}

this.FormPanel = new TableLayout(rows);

this._gameDropdown.SelectedValueChanged += this.GameChanged;

this.InitializePatcher();
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f2751c7

Please sign in to comment.