ryujinx/Ryujinx.HLE/Loaders/Mods/MemPatch.cs
mageven 189c0c9c72
Implement modding support (#1249)
* Implement Modding Support

* Executables: Rewrite to use contiguous mem and Spans

* Reorder ExeFs, Npdm, ControlData and SaveData calls

After discussion with gdkchan, it was decided it's best to call
LoadExeFs after all other loads are done as it starts the guest process.

* Build RomFs manually instead of Layering FS

Layered FS approach has considerable latency when building the final
romfs. So, we manually replace files in a single romfs instance.

* Add RomFs modding via storage file

* Fix and cleanup MemPatch

* Add dynamically loaded NRO patching

* Support exefs file replacement

* Rewrite ModLoader to use mods-search architecture

* Disable PPTC when exefs patches are detected

Disable PPTC on exefs replacements too

* Rewrite ModLoader, again

* Increased maintainability and matches Atmosphere closely
* Creates base mods structure if it doesn't exist
* Add Exefs partition replacement
* IPSwitch: Fix nsobid parsing

* Move mod logs to new LogClass

* Allow custom suffixes to title dirs again

* Address nits

* Add a per-App "Open Mods Directory" context menu item

Creates the path if not present.

* Normalize tooltips verbiage

* Use LocalStorage and remove unused namespaces
2020-07-09 14:31:15 +10:00

98 lines
3.2 KiB
C#

using Ryujinx.Cpu;
using System;
using System.Collections.Generic;
using System.Linq;
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.Loaders.Mods
{
public class MemPatch
{
readonly Dictionary<uint, byte[]> _patches = new Dictionary<uint, byte[]>();
/// <summary>
/// Adds a patch to specified offset. Overwrites if already present.
/// </summary>
/// <param name="offset">Memory offset</param>
/// <param name="patch">The patch to add</param>
public void Add(uint offset, byte[] patch)
{
_patches[offset] = patch;
}
/// <summary>
/// Adds a patch in the form of an RLE (Fill mode).
/// </summary>
/// <param name="offset">Memory offset</param>
/// <param name="length"The fill length</param>
/// <param name="filler">The byte to fill</param>
public void AddFill(uint offset, int length, byte filler)
{
// TODO: Can be made space efficient by changing `_patches`
// Should suffice for now
byte[] patch = new byte[length];
patch.AsSpan().Fill(filler);
_patches[offset] = patch;
}
/// <summary>
/// Adds all patches from an existing MemPatch
/// </summary>
/// <param name="patches">The patches to add</param>
public void AddFrom(MemPatch patches)
{
if (patches == null)
{
return;
}
foreach (var (patchOffset, patch) in patches._patches)
{
_patches[patchOffset] = patch;
}
}
/// <summary>
/// Applies all the patches added to this instance.
/// </summary>
/// <remarks>
/// Patches are applied in ascending order of offsets to guarantee
/// overlapping patches always apply the same way.
/// </remarks>
/// <param name="memory">The span of bytes to patch</param>
/// <param name="maxSize">The maximum size of the slice of patchable memory</param>
/// <param name="protectedOffset">A secondary offset used in special cases (NSO header)</param>
/// <returns>Successful patches count</returns>
public int Patch(Span<byte> memory, int protectedOffset = 0)
{
int count = 0;
foreach (var (offset, patch) in _patches.OrderBy(item => item.Key))
{
int patchOffset = (int)offset;
int patchSize = patch.Length;
if (patchOffset < protectedOffset || patchOffset > memory.Length)
{
continue; // Add warning?
}
patchOffset -= protectedOffset;
if (patchOffset + patchSize > memory.Length)
{
patchSize = memory.Length - (int)patchOffset; // Add warning?
}
Logger.PrintInfo(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");
patch.AsSpan().Slice(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize));
count++;
}
return count;
}
}
}