Slap ROM Patcher

Hacker News Top 工具

摘要

Slap is a free, open-source, multiplatform ROM patcher that supports twenty patch formats, with a CLI and web version, designed for scripting and robust error reporting.

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/10 08:33

# slap | nyuu.page Source: [https://nyuu.page/projects/slap/](https://nyuu.page/projects/slap/) ![](https://nyuu.page/images/icons/blobfingerguns.webp)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](https://retrorgb.com/fxpakpro.html)\. 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](https://github.com/nyuutsu/slap/releases),[web version](https://slap.nyuu.page/)\. 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](https://nyuu.page/projects/slap/#appendix) ## [correctness](https://nyuu.page/projects/slap/#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](https://github.com/nyuutsu/slap/blob/main/src/Slap/Status/Error.hs),[ApplyError\.hs](https://github.com/nyuutsu/slap/blob/main/src/Slap/Status/ApplyError.hs), and[VCDIFF\.hs](https://github.com/nyuutsu/slap/blob/main/src/Slap/Status/VCDIFF.hs)for the sorts of things we watch for\. ## [text encoding stuff](https://nyuu.page/projects/slap/#text-encoding-stuff) Some formats can store text metadata\. I went in expecting to find evidence in favor of some if not all of these being ASCII\-only\. Not so\! We output UTF\-8 and feel fairly confident that this is in all cases “not*wrong*”\. The two things we see a lot of are either “in this format, the rule is use UTF\-8” \(great\!\), or “in this format, the rule is use ‘system codepage’” \(whoa, what?\)\. The latter still permits UTF\-8: on a modern system, UTF\-8*is*the system codepage\. On the read/display side it is more involved\. We provide a bunch of alternate decoding options in case UTF\-8 is*not*how to read the patch\. We guard/sanitize what we decode/read\. A few formats have metadata fields for**arbitrary data**\. Not arbitrary text; arbitrary data\. In practice if these fields are used at all, they are being used to store text\. So we don’t want to firewall these fields off from the usual display and preservation\-during\-format\-conversion machinery, but we also don’t want to read and then act on a bunch of control characters out of a suspicious embedded file\. What we actually do: show everything, act on nothing\. Unprintable codepoints are displayed escaped \(`<U\+0007\>`, not a beep\), and a byte sequence the chosen encoding cannot decode becomes U\+FFFD, with a warning saying where\. ## [benchmarks](https://nyuu.page/projects/slap/#benchmarks) ![](https://nyuu.page/images/icons/blobfingerguns.webp)to be clear Every number in the CLI tables includes process startup, ours included\. The smallest races are decided by a 10–25ms exec\-and\-runtime floor, not by patching\. Have some tables\. These are sourced from the[benchmark thing](https://github.com/nyuutsu/slap/tree/main/bench)in the repo, which you can \(in principle\) run yourself\. We have four before/after pairs: a 4MiB GBC rom, a 16MiB GBA rom, a 64MiB N64 rom, a 520MiB disc image\. For each format*where we have a good way to script the reference tool*, each tool creates a patch from the reference pair, and each tool applies\. These are timed; times are medians of warm runs\. Every output is checked byte\-for\-byte against the reference pair before its time counts\. - Each cell reads**slap / reference**\. - Anything that ran past fifteen seconds is written as**15s\+**\. - A blank cell is a pairing that wasn’t benched at that size\. IPS and EBP cap out at 16MiB, hence\. - A — means a run happened and produced nothing to measure\. - ppf3 is compared with undo data off on both sides; the reference tool’s own mode\. ### [creating](https://nyuu.page/projects/slap/#creating) Time to make a patch from the pair,**slap / reference**format / reference4MiB16MiB64MiB520MiBips / flips18ms / 12ms9ms / 21msips32 / sips15ms / 8ms11ms / 17ms38ms / 68ms150ms / 413msups / goUps9ms / 12ms11ms / 24ms44ms / 99ms198ms / 588msbps / flips57ms / 327ms103ms / 1\.29s1\.55s / 11\.94s1\.88s / 15s\+ppf3 / makeppf311ms / 10ms19ms / 14ms74ms / 57ms487ms / 333msninja1 / ninjaPhp17ms / 41ms45ms / 53ms130ms / 241msninja2 / ninja2Php19ms / 145ms50ms / 103ms204ms / 685ms1\.47s / 1\.59sgdiff / javaxdelta18ms / 136ms12ms / 249ms239ms / 1\.27s733ms / 10\.76sbsdiff / bsdiff74ms / 560ms110ms / 2\.65s682ms / 14\.79s3\.57s / 15s\+xdelta1 / xdelta144ms / 41ms45ms / 49ms386ms / 270ms1\.62s / 1\.33sxdelta3 / xdelta376ms / 47ms21ms / 34ms398ms / 383ms596ms / 326ms ### [the patches themselves](https://nyuu.page/projects/slap/#the-patches-themselves) What those creates wrote,**slap / reference**format / reference4MiB16MiB64MiB520MiBips / flips655KiB / 654KiB16KiB / 16KiBips32 / sips672KiB / 899KiB18KiB / 23KiB4\.4MiB / 4\.7MiB388KiB / 613KiBups / goUps888KiB / 888KiB11KiB / 11KiB4\.5MiB / 4\.5MiB467KiB / 467KiBbps / flips276KiB / 272KiB10KiB / 12KiB1\.9MiB / 1\.8MiB280KiB / —ppf3 / makeppf3922KiB / 937KiB24KiB / 33KiB4\.6MiB / 4\.9MiB481KiB / 728KiBninja1 / ninjaPhp888KiB / 899KiB18KiB / 23KiB4\.4MiB / 4\.7MiBninja2 / ninja2Php891KiB / 903KiB22KiB / 28KiB4\.4MiB / 4\.7MiB467KiB / 679KiBgdiff / javaxdelta600KiB / 747KiB21KiB / 33KiB1\.9MiB / 1\.9MiB367KiB / 425KiBbsdiff / bsdiff204KiB / 215KiB8KiB / 8KiB1\.9MiB / 1\.8MiB287KiB / —xdelta1 / xdelta1239KiB / 239KiB13KiB / 17KiB1\.7MiB / 1\.8MiB275KiB / 279KiBxdelta3 / xdelta3218KiB / 236KiB10KiB / 13KiB1\.7MiB / 1\.7MiB263KiB / 272KiB ### [applying](https://nyuu.page/projects/slap/#applying) Applying the patch slap made,**slap / the format’s reference applier**format / applier4MiB16MiB64MiB520MiBips / flips20ms / 10ms33ms / 30msips32 / atmosphereIps21ms / 9ms32ms / 27ms103ms / 103ms835ms / 776msups / goUps15ms / 12ms35ms / 30ms126ms / 123ms902ms / 918msbps / flips34ms / 38ms35ms / 136ms122ms / 532ms896ms / 4\.2sppf3 / applyppf316ms / 30ms31ms / 19ms112ms / 109ms837ms / 109msninja1 / ninjaPhp21ms / 15s\+60ms / 15s\+163ms / 15s\+ninja2 / ninja2Php23ms / 65ms64ms / 51ms232ms / 232ms1\.82s / 1\.48sgdiff / javaxdelta18ms / 48ms33ms / 55ms110ms / 165ms830ms / 1\.01sbsdiff / bsdiff44ms / 27ms112ms / 59ms471ms / 279ms3\.41s / 1\.72sxdelta1 / xdelta130ms / 19ms65ms / 46ms238ms / 174ms1\.84s / 1\.18sxdelta3 / xdelta335ms / 16ms35ms / 32ms152ms / 134ms848ms / 843ms sips and makeppf3 only create, so in this table ips32 and ppf3 are compared against their format’s reference applier instead\. On application we lose more cells than we win; at cartridge sizes the gaps are tens of milliseconds\. Every applier here takes slap’s patches byte\-perfectly\. ![](https://nyuu.page/images/icons/note.webp)in fairness - javaxdelta boots a JVM inside every timed call\. That cost dominates its 4MiB numbers and fades to noise by 520MiB; the library itself is quick\. - applyppf3 and the ninja appliers modify a file in place instead of writing a fresh output, so each timed run is handed a pre\-made copy, and making the copy is not timed\. At 520MiB most of slap’s apply time is writing the 520MiB output; an in\-place applier writes only the changed bytes\. That row is two different jobs sharing a table\. ### [in the browser](https://nyuu.page/projects/slap/#in-the-browser) Web slap’s natural comparison is[RomPatcher\.js](https://www.marcrobledo.com/RomPatcher.js/), the community’s longtime standard\. Three browser\-sized pairs: the 4MiB and 64MiB roms from above, and an 8MiB pair for ips and ebp, which cannot address 64MiB\. Both sides apply slap’s patch; a blank cell means that pairing wasn’t benched at that size\. Both sides are timed warm: booted once, files already in memory\. Unlike the CLI tables, startup is in none of these numbers\. Creating,**web slap / RomPatcher\.js**format4MiB8MiB64MiBips45ms / 36ms17ms / 219msups6ms / 43ms75ms / 620msbps60ms / 15s\+2\.0s / 560msppf5ms / 31ms72ms / 421msebp45ms / 37ms17ms / 220msaps6ms / 33ms74ms / 412msrup14ms / 61ms214ms / 789ms At 4MiB, RomPatcher\.js walks the same search our Rust differ walks, in pure JavaScript; that is the 15s\+, and its patch comes out nearly as small\. Above 4MiB \(a threshold in its source\) it switches to a linear pass, which is much quicker and writes a bigger patch\. The trade lands in the patch table below\. The patches each create wrote,**web slap / RomPatcher\.js**format4MiB8MiB64MiBips655KiB / 886KiB554KiB / 556KiBups888KiB / 888KiB4\.5MiB / 4\.5MiBbps276KiB / 308KiB1\.9MiB / 4\.4MiBppf922KiB / 936KiB4\.6MiB / 4\.9MiBebp655KiB / 886KiB554KiB / 556KiBaps905KiB / 908KiB4\.5MiB / 4\.6MiBrup891KiB / 903KiB4\.4MiB / 4\.7MiB Applying,**web slap / RomPatcher\.js**format4MiB8MiB64MiBips22ms / 4ms9ms / 14msups5ms / 12ms82ms / 98msbps29ms / 10ms68ms / 48msppf3ms / 10ms43ms / 59msebp22ms / 3ms8ms / 19msaps24ms / 11ms37ms / 66msrup12ms / 17ms176ms / 198ms The full grids, the harness itself, and the six CLI formats with no scriptable reference tool to race against are all in[the results directory](https://github.com/nyuutsu/slap/tree/main/bench/results)\.

相似文章

rommapp/romm

GitHub Trending (daily)

RomM 是一款自托管的 ROM 管理器,允许用户通过网页界面扫描、增强、浏览和游玩其游戏收藏,支持多平台、元数据增强以及社区开发的应用程序。

Slisp: 简单的Lisp编译器 (Linux/amd64)

Hacker News Top

Slisp 是一个简单的编译器,它读取 Lisp 程序并生成适用于 Linux/AMD64 的独立汇编表示,支持基本原语、闭包和标准库。

Patchrooms

Product Hunt

Patchrooms 是一款工具,可将 AI 应用反馈转化为可供智能体直接使用的补丁上下文。