The patch store was a std::vector<IniPatch> exported to Counters and
VMManager, each walking it for the places it wanted; a 'bytes'
payload was a std::vector<u8> inside each patch; the dynamic patches
were copies of the game database's vectors; the parser split lines
into std::vector<std::string_view>; the loaders took std::string and
went through std::string buffers, std::optional and FileSystem's
std::vector of results.
Now:
- The patches are a plain array in Patch.cpp, read through
LoadedPatchCount/LoadedPatch, and applied by ApplyLoadedPatches with
a mask of places, in load order as before. A payload is a malloc'd
run with its length, freed by ForgetLoadedPatches, and only
allocated once the line has passed every check.
- The dynamic patches' entries are copied into one array of their
own, a pattern run then a replacement run per patch, so a reload of
the game database cannot leave them dangling.
- The parser splits into five (pointer, length) fields on the stack,
with the stripping and empty-field rules SplitString had, and the
command table takes the same.
- LoadPatchesFromZip and LoadPatchesFromDir take C strings. The zip
member is inflated into a malloc'd buffer. The folder is walked with
retro_opendir directly, with FindFiles's filter (files, hidden ones
included, "*.pnach", sizable) and its order, and each file is read
with filestream_read_file, which terminates it. The "not found"
message no longer has a newline in the middle of the path.
tests/patch gains a lane that links Patch.cpp against the real
libretro-common directory, file and inflate code: pnach files for the
CRC in either case and with suffixes beside another CRC's, a .txt, a
hidden file, a directory named like a pnach and an empty folder; and
archives written by Python's zipfile with the member deflated, stored
under an upper-case name, only under a subfolder, and behind a
comment, plus every truncation and every one-byte corruption of one.
It fails if the CRC match turns case-sensitive.