slap | nyuu.page
Skip to content
font:
textured<br>textured<br>smooth<br>smooth
slap
context
A rom patcher takes a game file and applies a “patch” file to it. The patch is a recipe for turning the original file into some sort of variant of itself, such as a translation, a bugfix, or some other romhack thing. By convention, the way to distribute your game-modding work is to make and distribute a patch meant to be applied to the original.
I made a rom patcher. It’s good. It’s free/FOSS, multiplatform, supports twenty formats, cute, fast, and makes small patches.
I built it because I couldn’t find a CLI tool, available on Linux, that I cared for, that would play nice with applying the patches I needed to prep my FXPak. So it is in part built for scripting nicely. But it is also very chatty. It has structured errors/warnings/observations for all sorts of things.
It assumes nothing about a patch. I am fairly confident that it’ll handle anything that isn’t invalid gracefully; if something is malformed, or coherent but about to do something surprising, slap says exactly what and where.
Try it: cli version, web version. It is written in Haskell and Rust and this made getting it to work nicely in the browser, interesting.
It knows six verbs:
apply — patch + original rom → modified rom
create — two ROMs → patch
undo — patch + modified rom → original rom
convert — patch in one format → patch in another
explain — patch → human-readable statement of what it does
info — patch → its metadata
appendix
correctness
For any realistically likely to actually exist patch, “we can apply it, correctly” is of course a given. But what are the outer edges of what is acceptable? For each format we had to ask, for “property X”:
is property x a ‘spec’ thing?
an idiosyncratic up-to-the-applier kind of thing?
a wire capability that is nonetheless not part of ‘the spec’?
some:
What counts as a well-formed IPS patch? Can the records overlap? Can they be non-monotonic? If the answer to both is “yes” then this makes applying it at least less obvious than it might have at first seemed. What does it mean to honor the truncation marker if it is saying to “truncate” to a size that is larger than the input file?
IPS can’t address past 16MiB, so barring some weird edge case about only modifying the start of a file: IPS can only describe changes in files up to 16MiB. Ish. It can’t see past 16MiB, but it can describe a record that begins in-bounds but writes moderately past that point, beyond 16MiB. This is coherent, has predictable results, and is super weird. Is it “in spec”? Of course we’ll apply these but do we agree to create them?
EBP supports JSON metadata, which it uses to store four strings. The expressive power is much greater than what is actually being done with it. Is “the spec” something is wrong if there is anything present in addition to the strings? Is it the reverse: arbitrary infinite nesting? Can we at least figure that it must be UTF-8?
NINJA2 has this cool “normalization” thing, where if the input rom is not already in a “normal form” (deinterleaved, no header, z64 byte order if N64, etc), the patcher is supposed to put it in that form. If the patch says to apply normalization procedure foo, and the patcher doesn’t know procedure foo (this is actually not contrived), it refuses to apply. The issue is: this format does also store checksums, of the input. The checksum is against the normalized form of that input. It seems to me like this is too conservative and we should at least check whether the input file is already in the expected shape, before giving up. Can I have my tool do that or is that me doing “embrace / extend / extinguish”?
BPS appears to allow “copying stuff from other parts of the output, before anything has been put there”. Is this coherent or no?
PPF3 doesn’t track file sizes, and allows undo. The wire can describe growth, but in a way where if you do so, undoing becomes incoherent. The OG tool looks to me like it intends to block actually doing growth or shrinking, but in a way that I suspect didn’t work. What do we do if the user creates a size-changing undo-bearing PPF3? What if they want to use the PPF3 undo feature; for format-structural reasons it is impossible to detect that the user is trying to truncate-via-undo.
and so on. On creation we are conservative: emit things any tool could apply just fine. On application we have attempted to support the entire “expressive range” of each format. Doing this means figuring out where “the line” is.
This meant a lot of time spent thinking about how something could go wrong: the wire able to represent incoherent instructions, for example. An outsized amount of that time went into making sure every way a patch can be malformed gets called by its name. If curious see Error.hs, ApplyError.hs, and VCDIFF.hs for the sorts of things we watch for.
text encoding stuff
Some formats can store text metadata. I went in expecting to find evidence in...