Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PatchEngine: Allow patching of files on disk #7982

Closed
wants to merge 6 commits into from

Conversation

phire
Copy link
Member

@phire phire commented Apr 11, 2019

Resident Evil 2/3 require patching code which live in .rel files (Relocatable Executable)

Our current memory patching system makes this difficult, we would have to patch the rel loader to detect which .rel was loaded and apply the correct patch.

So I've implemented file based patches instead.
Here is the patch for Resident Evil 2 (U)

[FilePatch_enabled]
$fix dcache issue
[FilePatch]
$fix dcache issue
/leon.rel
0x1903f0:dword:0x42800010
/claire.rel
0x190288:dword:0x42800010

Current limitations:

  • Can't patch main.dol
  • Can't patch files not on the main partition
  • Can't patch data outside of files
  • Not hooked up to editor UI

@phire phire added the WIP / do not merge Work in progress (do not merge) label Apr 11, 2019
Copy link
Member

@JosJuice JosJuice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked a little on IRC about having this code in the Volume classes instead. I think that would make sense, as long as we make sure that the patches only are applied when the disc is being accessed by emulation and not the game list, maybe by having a function Volume::ApplyPatches that is called by the boot code or PatchEngine that stores the patches in the Volume object.

Source/Core/Core/HW/DVD/DVDThread.cpp Outdated Show resolved Hide resolved
const auto& file = fs->FindFileInfo(patch.path);
if (file) {
for (const auto& entry : patch.entries) {
u64 disc_offset = disc.PartitionOffsetToRawOffset(file->GetOffset(), partition) + entry.address;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong for Wii discs, since entry.address doesn't account for the hashes at the beginning of each cluster. (Unless you expect the person writing the patch to correct for the hashes manually?) Patching a Wii disc using the current code is inconvenient anyway, since you would need to handle the encryption manually...

If the code isn't going to support Wii discs properly, you might as well skip the call to PartitionOffsetToRawOffset.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard the previous comment. You can get Wii discs to work, but you would need to remove this call to PartitionOffsetToRawOffset, and PatchReadRequest would need to check which partition it's applying patches to.

Source/Core/Core/HW/DVD/DVDThread.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.cpp Outdated Show resolved Hide resolved
Source/Core/Core/HW/DVD/DVDThread.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.cpp Outdated Show resolved Hide resolved
Source/Core/Core/PatchEngine.h Outdated Show resolved Hide resolved
@@ -386,15 +386,15 @@ static void PatchReadRequest(std::vector<u8>& buffer, u64 offset)
u64 start = it->first - offset;
std::memmove(buffer.data() + start, it->second.data(),
std::min(it->second.size(), buffer.size() - start));
INFO_LOG(DVDINTERFACE, "patch applied at %08lx", offset + start);
INFO_LOG(DVDINTERFACE, "patch applied at %08llux", offset + start);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use PRIx64 for u64.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Current versions of Visual studio support %llux

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%llu works for u64 without warnings on all compilers now? I guess you could use that then.

DVDInterface::SetDisc(std::move(volume), auto_disc_change_paths);
return pointer;
return DVDThread::GetDiscVolume();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really ugly situation here. It might actually be cleaner to return to the state before PR #4241

Right now I'm just making it clearer that we are grabbing a pointer internal state of DVDThread (and grabbing it after DVDThread has applyed the patch overlay).

I think the cleaner solution might be to split the EmulatedBS2 into two parts. Part one which sets up the memory/state based on the volume which runs before passing the volume to DVDThread. Part 2 will just load and run apploader, directly calling DVDThread to process read requests (adding a new DVDThread::HLE_Read() which is only intended to be used during boot)

But I'm not sure, I welcome feedback on this issue.

@i30817
Copy link

i30817 commented Apr 10, 2020

Since this is stalled, do you mind if i create a romhacking.net patch for the discs using xdelta using your offsets? And is this for both games or just one? And the offset is inside the files, not the whole disc or partition right?

@mbc07
Copy link
Contributor

mbc07 commented Apr 10, 2020

Just RE2...

@phire
Copy link
Member Author

phire commented Apr 11, 2020

Coincidentally, I'm rebasing this PR right now.

Those offsets work only for the US version of RE2. Offset is within the file.

@i30817
Copy link

i30817 commented Apr 11, 2020

If it's happening i'll wait since this only emulator for the gamecube worth a damn. I'd prefer to do/get/replay both at once anyway.

We also switch from keeping two lists in sync to a system with a single
list.
@i30817
Copy link

i30817 commented Apr 26, 2020

Do you happen to have already researched the offsets for Resident Evil 3 too?

@JosJuice
Copy link
Member

I would like to go with PR #9308 instead of this PR. Since it seems like that PR's approach works for fixing RE2/3, this PR's more complicated solution seems like overkill for just fixing those two games. And I don't feel like this PR is useful for a lot more than fixing RE2/3, as large-scale game patching (like adding new levels to a game) requires features that can't be provided by this PR's approach, like changing the size of files.

@JosJuice
Copy link
Member

The Unifiy AR/Gecko/Patch GUI dialogs commit seems like a good idea regardless, though. If you can split it out to a separate PR, I'd like to try to get it merged.

@phire
Copy link
Member Author

phire commented Dec 16, 2020 via email

@smurf3tte
Copy link
Contributor

I would like to go with PR #9308 instead of this PR.

I don't think this is an either-or situation. The RE 2&3 patches will function identically no matter which application mechanism is used. They can be trivially converted to use either.

That said, I would still like to go ahead with my change. It's just a dozen lines of code in the patch engine; the bulk of the source changes are in the Qt UI, and that code will be thrown away entirely if this PR is completed, which wouldn't bother me in the slightest.

@phire
Copy link
Member Author

phire commented Dec 16, 2020

Yeah, it will function near identically. I chose this option because:

  1. Checking to see if static data has changed every frame and then patching it seems like a massive hack to me.
    Patching on load is a lot cleaner because it only happens once, and there is no edge-case between loading and vblank.

  2. I couldn't actually find a guarantee in the REed code that the executable would always be loaded in the same place. The memory address to load the REL comes from a memory allocater and would change if there is a different order of allocations.
    Though experimental testing has so-far proved my fears wrong.

  3. Generic file seems like it will be very useful for a lot of other modding use cases. Especially once there is support for changing the length of files and adding new files.

Conditional patches are also useful in their own way. Ideally dolphin wants both.

@smurf3tte
Copy link
Contributor

Agreed on all points.

@JosJuice
Copy link
Member

The plan was that the backend disc patching architecture here is generic.
It simply takes lists of modified ranges in disc address space.

A later PR could add an improved frontend that would understand more
advanced patch types (such as deleting files, adding new files, replacing
files with bigger versions, rearranging the filesystem, maybe even creating
oversized discs) in more specialized formats and generate the "list of
modified ranges" for the disc patching backend to consume.

Wouldn't this make the list of modified ranges absolutely huge once you start moving files around on the disc?

@phire
Copy link
Member Author

phire commented Dec 17, 2020

Wouldn't this make the list of modified ranges absolutely huge once you start moving files around on the disc?

Really depends on how easy it is to extend the disc beyond 1.2GB (something I haven't looked into). If the Nintendo SDK library routines simply fall apart, then we will be forced to pack everything into 1.2GB, deleting and packing files down. A HLE approach might be preferable.

If it's easy to extend discs, then it's super simple.
Leave all deleted/replaced data in-place. A few ranges to patch the filesystem data and one big range at the end of the disc to hold all the new data.

@JosJuice
Copy link
Member

I'd like to close this now that we have PR #10127. Any objections?

@JosJuice JosJuice closed this Jan 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
WIP / do not merge Work in progress (do not merge)
7 participants