From fae606a8bbeb488d736cf13064cf0f2440df0cc5 Mon Sep 17 00:00:00 2001 From: navaroli Date: Sun, 18 Jun 2023 20:44:51 -0700 Subject: [PATCH] Support HexPatch (#320) Enables direct manipulation of hex values in the binary. eg: changing hard-coded values or strings that may not necessarily be numerical. --- js/dllpatcher.js | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/js/dllpatcher.js b/js/dllpatcher.js index 853165f..1066dab 100644 --- a/js/dllpatcher.js +++ b/js/dllpatcher.js @@ -17,6 +17,22 @@ var bytesMatch = function(buffer, offset, bytes) { return true; }; +var bytesToHex = function(bytes) { + var s = '' + for(var i = 0; i < bytes.length; i++) { + s += bytes[i].toString(16).toUpperCase().padStart(2, '0'); + } + return s; +} + +var hexToBytes = function(hex) { + var bytes = []; + for(var i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.substr(i, 2), 16)); + } + return bytes; +} + var replace = function(buffer, offset, bytes) { for(var i = 0; i < bytes.length; i++) { buffer[offset+i] = bytes[i]; @@ -562,6 +578,103 @@ class NumberPatch { } } +// Each unique kind of patch should have createUI, validatePatch, applyPatch, +// updateUI +class HexPatch { + constructor(options) { + this.name = options.name; + this.tooltip = options.tooltip; + this.danger = options.danger; + this.offset = options.offset; + + this.off = options.off; + } + + createUI(parent) { + this.radios = []; + var radio_id = createID(); + + // Title of the radio option. + var container = createElementClass('div', 'patch-union'); + container.appendChild(createElementClass('span', 'patch-union-title', this.name + ':')); + if(this.tooltip) { + container.appendChild(createElementClass('div', 'tooltip', this.tooltip)); + } + if(this.danger) { + container.appendChild(createElementClass('div', 'danger tooltip', this.danger)); + } + container.appendChild(document.createElement('span')); + + // Default option; tooltip shows default hex value. + var id = createID(); + var patchDiv = createElementClass('div', 'patch'); + var radio = createInput('radio', id); + radio.name = radio_id; + this.radios.push(radio); + + patchDiv.appendChild(radio); + patchDiv.appendChild(createLabel('Default', id)); + patchDiv.appendChild(createElementClass('div', 'tooltip', 'Value ' + bytesToHex(this.off))); + container.appendChild(patchDiv); + + // Custom option. + id = createID(); + patchDiv = createElementClass('div', 'patch'); + radio = createInput('radio', id); + radio.name = radio_id; + this.radios.push(radio); + + patchDiv.appendChild(radio); + patchDiv.appendChild(createLabel('Custom ' + this.off.length + '-byte hex value: ', id)); + this.valueHex = document.createElement('input'); + this.valueHex.type = 'text'; + this.valueHex.id = id; + patchDiv.appendChild(this.valueHex); + + patchDiv.appendChild(createElementClass('div', 'danger tooltip', 'Invalid values will not be applied.')); + container.appendChild(patchDiv); + + parent.appendChild(container); + + } + + updateUI(file) { + if(bytesMatch(file, this.offset, this.off)) { + this.radios[0].checked = true; + return; + } + this.valueHex.value = bytesToHex(file.slice(this.offset, this.offset + this.off.length)); + this.radios[1].checked = true; + } + + validatePatch(file) { + if(bytesMatch(file, this.offset, this.off)) { + console.log(this.name, "has default hex value"); + return; + } + console.log(this.name, "has custom hex value"); + } + + applyPatch(file) { + if(this.radios[0].checked) { + replace(file, this.offset, this.off); + return; + } + if(this.radios[1].checked) { + if(!this.valueHex.value.match(/^[0-9a-fA-F]+$/)) { + alert('Patch "' + this.name + '" not applied - invalid hex!'); + return; + } + if(this.valueHex.value.length != this.off.length * 2) { + alert('Patch "' + this.name + '" not applied - invalid length!!'); + return; + } + replace(file, this.offset, hexToBytes(this.valueHex.value)); + return; + } + } +} + var loadPatch = function(_this, self, patcher) { patcher.loadPatchUI(); patcher.updatePatchUI(); @@ -747,6 +860,9 @@ class Patcher { if(mod.type === "dynamic") { this.mods.push(new DynamicPatch(mod)); } + if(mod.type === "hex") { + this.mods.push(new HexPatch(mod)); + } } else { // standard patch this.mods.push(new StandardPatch(mod)); }