From 5ba108979414f7f3c82c4b82bdbc1fe7fef875d2 Mon Sep 17 00:00:00 2001 From: Marc Robledo Date: Fri, 9 Aug 2024 19:21:59 +0200 Subject: [PATCH] Created Embedding Rom Patcher JS (markdown) --- Embedding-Rom-Patcher-JS.md | 209 ++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 Embedding-Rom-Patcher-JS.md diff --git a/Embedding-Rom-Patcher-JS.md b/Embedding-Rom-Patcher-JS.md new file mode 100644 index 0000000..86beeb5 --- /dev/null +++ b/Embedding-Rom-Patcher-JS.md @@ -0,0 +1,209 @@ +# Embedding Rom Patcher JS in your site +If you are self-hosting your own translations and/or hack patches, you can embed Rom Patcher JS and provide a nice solution for your users, allowing them to easily apply your patches to their ROMs without requiring them to download a single file, all thanks to the magic of the web! + +Take a look at [`index_template.html`](https://github.com/marcrobledo/RomPatcher.js/blob/master/index_template.html) for a template. Basically you need to: + +1. [Download the latest version](https://github.com/marcrobledo/RomPatcher.js/releases) and copy the entire `rom-patcher-js/` folder to your site +2. Include **all** file imports from the `
` into yours: + ```html + + + ... + + + ``` +3. Copy the needed HTML structure from `index_template.html`: + ```html +
+ ... +
+ ``` +4. Build a quick patcher with a simple call to `RomPatcherWeb.initialize`: + ```html + + ``` + + +  +## Providing additional patch information +If you want to improve the user experience, you can provide additional information of your patches.
+Instead of a simple file path, you can build a JSON object like this: +```js +RomPatcherWeb.initialize(myPatcherSettings, { + file: 'my_hack.ips', + name: 'My game improvement (v2.0)', //patch name that will appear in the dropdown button + inputCrc32: 0xdeadbeef, //add checksum validation to patch, will warn user if the ROM is not valid + description: 'This patch translates the game and fixes bugs!', //short description that will appear when user picks this patch + outputName: 'My game (Hack)' //patched ROM name +}); +``` +It is recommended, though, to store the patch or patches in a single zip file: +```js +RomPatcherWeb.initialize(myPatcherSettings, { + file: 'my_patches.zip', //zip containing patches + patches: [ //information about patches inside the zip + { + file: 'my_hack.ips', //required, must match the patch file name in the zip + name: 'My game improvement hack (v2.0)', + inputCrc32: [0xdeadbeef, 0xd0d0cafe], //an array of valid checksums + description: 'This patch translates the game and fixes bugs! (Recommended)', + outputName: 'My game improvement' + }, + { + file: 'my_hack_vanilla.ips', + name: 'My game improvement hack (v2.0, no bugfixes)', + inputCrc32: [0xdeadbeef, 0xd0d0cafe], + description: 'This patch translates the game but keeps the original game\'s bugs untouched for a vanilla experience', + outputName: 'My game improvement' + }, + /* ... */ + ] +}); +``` + + +  +## Events +If you want to go further, you can run your own code via events: +```js +const myPatcherSettings={ + language: 'en', + + onloadrom: function (romFile) { + /* this event is triggered when user provides a ROM and before validating it */ + /* can be used to: */ + /* - modify it before validation (e.g. add/remove/fix header, change endianness...) */ + /* - to switch to another patch file in the dropdown selector */ + }, + onvalidaterom: function (romFile, isRomValid) { + /* this event is triggered after ROM is validated */ + /* can be used to show a custom error message if provided ROM is not valid */ + }, + onpatch: function (romFile) { + /* this event is triggered a ROM is patched */ + /* can be used to modify it before the patched ROM file is saved */ + } +}; +``` +In all events, the `romFile` parameter is an instance of [BinFile.js](https://github.com/marcrobledo/RomPatcher.js/blob/master/rom-patcher-js/modules/BinFile.js), which allows easy file data and name manipulation. + + + +  +### Example: Fix ROM endianness +N64 ROM files are usually stored in `.z64` (Big Endian) or `.n64` (Little Endian) formats. You can make your patcher compatible with both formats by changing the provided ROM file endianness before patching (and then restoring it after patching).
+```js + const SSB_LITTE_ENDIAN_CRC32 = 0xf0b7c200; + var romIsLittleEndian; + + RomPatcher.initialize( + { + onloadrom:function(romFile, patch){ + /* if ROM is little endian */ + if(romFile.getExtension()==='n64' && romFile.hashMD5()===SSB_LITTE_ENDIAN_CRC32){ + /* change endianness to big */ + romFile.swapBytes(4); + /* change extension */ + romFile.setExtension('z64'); + + romIsLittleEndian=true; + }else{ + romIsLittleEndian=false; + } + }, + onpatch:function(patchedRomFile){ + /* if ROM was little endian */ + if(romIsLittleEndian){ + /* revert endianness to little */ + romFile.swapBytes(4); + /* revert extension */ + romFile.setExtension('n64'); + } + } + }, { + file: 'smashremix1.5.2.xdelta', + inputCrc32: 0xeb97929e, + name: 'Smash Remix v1.5.2', + outputName: 'Smash Remix v1.5.2 (Hack)' + } + ); +``` + +  +### Example: Choose correct patch automatically +If your zip file contains several patches for different ROMs, you can make Rom Patcher JS so it picks automatically the patch for the provided ROM. +```js + const SML2_CHECKSUM_WORLD = 0xd5ec24e4; + const SML2_CHECKSUM_JAPAN = 0xa715daf5; + const SML2_CHECKSUMS_INVALID = [0xe6f886e5, 0x635a9112, 0xbf733e10, 0x29e0911a]; + + RomPatcher.initialize( + { + onloadrom: function (romFile) { + const crc32 = romFile.hashCRC32(); + /* if ROM region is USA/Europe */ + if (crc32 === SML2_CHECKSUM_WORLD) + RomPatcherWeb.pickEmbededFile('SML2DXv181.ips'); + /* if ROM region is Japan */ + else if (crc32 === SML2_CHECKSUM_JAPAN) + RomPatcherWeb.pickEmbededFile('SML2DXv181_jap.ips'); + }, + onvalidaterom: function (romFile, isRomValid) { + const crc32 = romFile.hashCRC32(); + /* if ROM is a known SML2 revision that is not compatible with the patch*/ + if (!isRomValid && SML2_CHECKSUMS_INVALID.indexOf(crc32) !== -1) + RomPatcherWeb.setErrorMessage('Please provide a v1.0 SML2 ROM'); + } + }, { + file: 'SML2DXv181.zip', + patches: [ + { + file: 'SML2DXv181.ips', + name: 'Super Mario Land 2 DX - 6 Golden Coins (v1.8.1)', + inputCrc32: SML2_CHECKSUM_WORLD, + description: 'SML2 colorization hack (USA/Europe) by toruzz', + outputName: 'Super Mario Land 2 DX - 6 Golden Coins (Hack)', + outputExtension: 'gbc' + }, + { + file: 'SML2DXv181_jap.ips', + name: 'Super Mario Land 2 DX - 6-tsu no Kinka (v1.8.1)', + inputCrc32: SML2_CHECKSUM_JAPAN, + description: 'SML2 colorization hack (Japan) by toruzz', + outputName: 'Super Mario Land 2 DX - 6-tsu no Kinka (Hack)', + outputExtension: 'gbc' + } + ] + } + ); +``` + +  +### Example: Remove header +SNES ROM files are usually stored in `.sfc` (headerless) or `.smc` (headered) formats. You can make your patcher compatible with both formats by detecting if the ROM is headered and remove the header before the ROM is validated.
+```js + RomPatcher.initialize( + { + onloadrom: function (romFile) { + const SMC_HEADER_SIZE=512; + if(romFile.getExtension()==='smc' && ((romFile.fileSize - SMC_HEADER_SIZE) % 0x20000) === 0){ + romFile.removeLeadingBytes(SMC_HEADER_SIZE); + romFile.setExtension('sfc'); + console.log('removed SMC header'); + } + } + }, { + file: 'Tekkaman Blade v1.0.rup', + name: 'English translation by D and Near', + outputName: 'Uchuu no Kishi Tekkaman Blade (English v1.0)', + } + ); +```