Skip to content

Instantly share code, notes, and snippets.

@EvidentlyCube
Created October 21, 2024 14:10
Show Gist options
  • Save EvidentlyCube/d851bd7bf0f87081b3a49c1e511c38d2 to your computer and use it in GitHub Desktop.
Save EvidentlyCube/d851bd7bf0f87081b3a49c1e511c38d2 to your computer and use it in GitHub Desktop.
RPG Maker 2k3 Protection Remover in Node
/**
* AUTHOR: Maurycy Zarzycki
* WEBSITE: www.evidentlycube.com
* DESCRIPTION:
* This is a Node script that removes protection from RPG Maker 2000 and 2003
* games via RM2k3 General Utility Tool.
* It was not thoroughly tested, I used it so I could fix a very old game of mine,
* Diamentowa Przygoda, where I "accidentally" released the game with the start
* position in the wrong place.
*
* HOW TO USE:
* 1. Make sure you have node installed
* 2. Copy the script to the game's directory
* 3. Run `node Rm2k3ProtectionRemover.js`
*/
const fs = require('fs');
const { join } = require('path');
const extensionToFirstBytes = {
'.lmu': [0x0a, 0x4c, 0x63, 0x66, 0x4d, 0x61, 0x70, 0x55, 0x6e, 0x69, 0x74],
'.ldb': [0x0b, 0x4c, 0x63, 0x66, 0x44, 0x61, 0x74, 0x61, 0x42, 0x61, 0x73, 0x65],
'.lmt': [0x0a, 0x4c, 0x63, 0x66, 0x4d, 0x61, 0x70, 0x54, 0x72, 0x65, 0x65],
}
const encodedPattern = determinePattern();
fixExe(encodedPattern);
for (const file of fs.readdirSync(__dirname)) {
const path = join(__dirname, file);
const stat = fs.statSync(path);
if (stat.isDirectory()) {
continue;
}
for (const [extension, bytes] of Object.entries(extensionToFirstBytes)) {
if (!path.endsWith(extension)) {
continue;
}
let changedBytes = 0;
const data = fs.readFileSync(path);
for (let i = 0; i < bytes.length; i++) {
const expectedByte = bytes[i];
if (data[i] !== expectedByte) {
changedBytes++;
data[i] = bytes[i];
}
}
if (changedBytes) {
console.log(`Fixed ${file}, changed ${changedBytes} byte${changedBytes !== 1 ? 's' : ''}`);
} else {
console.log(`Unchanged ${file}`);
}
fs.writeFileSync(path, data);
}
}
function fixExe(encodedPattern) {
const encodedPatternString = encodedPattern.map(s => String.fromCharCode(s)).join('');
const data = fs.readFileSync(join(__dirname, 'RPG_RT.exe'));
const positions = getBufferPositions(data, encodedPatternString.substring(0, 7));
if (positions.length === 0) {
console.log("No encoded patterns found in EXE, checking if it has all the expected patterns...");
if (getBufferPositions(data, 'LcfDataBase').length !== 2) {
console.error(`Expected 2 'LcfDataBase' but got ${getBufferPositions(data, 'LcfDataBase').length} instead`);
process.exit(1);
}
if (getBufferPositions(data, 'LcfMapTreeItem').length !== 1) {
console.error(`Expected 1 'LcfMapTreeItem' but got ${getBufferPositions(data, 'LcfMapTreeItem').length} instead`);
process.exit(1);
}
if (getBufferPositions(data, 'LcfMapTree').length !== 3) {
console.error(`Expected 3 'LcfMapTree' but got ${getBufferPositions(data, 'LcfMapTree').length} instead`);
process.exit(1);
}
if (getBufferPositions(data, 'TestPlay').length !== 3) {
console.error(`Expected 3 'TestPlay' but got ${getBufferPositions(data, 'TestPlay').length} instead`);
process.exit(1);
}
if (getBufferPositions(data, 'LcfMapUnit').length !== 2) {
console.error(`Expected 2 'LcfMapUnit' but got ${getBufferPositions(data, 'LcfMapUnit').length} instead`);
process.exit(1);
}
console.log("Valid!");
return;
} else if (positions.length !== 8) {
console.log(`Expected to find 8 encoded patterns but found ${positions.length}`);
process.exit(1);
}
console.log("Fixing EXE");
replaceStringInBuffer(data, positions[0], 'LcfDataBase');
replaceStringInBuffer(data, positions[1], 'LcfDataBase');
replaceStringInBuffer(data, positions[2], 'LcfMapTreeItem');
replaceStringInBuffer(data, positions[3], 'LcfMapTree');
replaceStringInBuffer(data, positions[4], 'LcfMapTree');
replaceStringInBuffer(data, positions[5], 'LcfMapUnit');
replaceStringInBuffer(data, positions[6], 'LcfMapUnit');
replaceStringInBuffer(data, positions[7], 'TestPlay');
console.log("Fixed EXE");
fs.writeFileSync(join(__dirname, 'RPG_RT.exe'), data);
}
function replaceStringInBuffer(buffer, at, string) {
for (let i = 0; i < string.length; i++) {
buffer[at + i] = string.charCodeAt(i);
}
}
function getBufferPositions(buffer, string) {
const positions = [];
let lastPosition = buffer.indexOf(string);
if (lastPosition === -1) {
return [];
}
do {
positions.push(lastPosition);
lastPosition = buffer.indexOf(string, lastPosition + 1);
} while (lastPosition !== -1);
return positions;
}
function determinePattern() {
const data = fs.readFileSync(join(__dirname, 'RPG_RT.ldb'));
const patternBytes = data.subarray(1, 12);
return Array.from(patternBytes);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment