mirror of
https://github.com/mon/BemaniPatcher.git
synced 2026-08-01 22:10:41 -07:00
support dynamic patcher (#224)
* support dynamic patcher * fix typo * fix createUI() * delete sample html * merge dynamicHex and dynamicString patch * fix dynamic patch constructor * use strict equality operator
This commit is contained in:
@@ -312,3 +312,15 @@ input[type=radio] {
|
|||||||
input[type=checkbox]:checked + .patchPreview {
|
input[type=checkbox]:checked + .patchPreview {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
input[type=checkbox] ~ ul > li.patch-off {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
input[type=checkbox] ~ ul > li.patch-on {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
input[type=checkbox]:checked ~ ul > li.patch-on {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
input[type=checkbox]:checked ~ ul > li.patch-off {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
@@ -103,6 +103,263 @@ class StandardPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DynamicPatch {
|
||||||
|
constructor(options) {
|
||||||
|
this.name = options.name;
|
||||||
|
this.patches = options.patches;
|
||||||
|
this.tooltip = options.tooltip;
|
||||||
|
this.mode = options.mode;
|
||||||
|
this.target = options.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
createUI(parent) {
|
||||||
|
var id = createID();
|
||||||
|
var label = this.name;
|
||||||
|
this.ui = $('<div>', {'class' : 'patch'});
|
||||||
|
this.checkbox = $('<input type="checkbox" id="' + id + '">')[0];
|
||||||
|
this.ui.append(this.checkbox);
|
||||||
|
this.ui.append('<label for="' + id + '">' + label + '</label>');
|
||||||
|
if(this.tooltip) {
|
||||||
|
this.ui.append('<div class="tooltip">' + this.tooltip + '</div>');
|
||||||
|
}
|
||||||
|
parent.append(this.ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI(file) {
|
||||||
|
if (this.mode === 'all') {
|
||||||
|
this.checkbox.checked = this.checkPatchAll(file, true) === "on";
|
||||||
|
} else {
|
||||||
|
this.checkbox.checked = this.checkPatch(file, true) === "on";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validatePatch(file) {
|
||||||
|
var status = this.mode === 'all' ? this.checkPatchAll(file) : this.checkPatch(file);
|
||||||
|
|
||||||
|
if(status === "on") {
|
||||||
|
console.log('"' + this.name + '"', "is enabled!");
|
||||||
|
} else if(status === "off") {
|
||||||
|
console.log('"' + this.name + '"', "is disabled!");
|
||||||
|
} else {
|
||||||
|
return '"' + this.name + '" is neither on nor off! Have you got the right file?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyPatch(file) {
|
||||||
|
this.replaceAll(file, this.checkbox.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceAll(file, featureOn) {
|
||||||
|
for(var i = 0; i < this.patches.length; i++) {
|
||||||
|
if (Array.isArray(this.patches[i].offset)) {
|
||||||
|
this.patches[i].offset.forEach((offset) => {
|
||||||
|
if (this.target === 'string') {
|
||||||
|
replace(file, offset,
|
||||||
|
new TextEncoder().encode(featureOn? this.patches[i].on : this.patches[i].off));
|
||||||
|
} else {
|
||||||
|
this.patches[i].on = this.patches[i].on.map((patch, idx) => patch === 'XX' ? file[offset + idx] : patch);
|
||||||
|
this.patches[i].off = this.patches[i].off.map((patch, idx) => patch === 'XX' ? file[offset + idx] : patch);
|
||||||
|
replace(file, offset,
|
||||||
|
featureOn? this.patches[i].on : this.patches[i].off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (this.target === 'string') {
|
||||||
|
replace(file, this.patches[i].offset,
|
||||||
|
new TextEncoder().encode(featureOn? this.patches[i].on : this.patches[i].off));
|
||||||
|
} else {
|
||||||
|
this.patches[i].on = this.patches[i].on.map((patch, idx) => patch === 'XX' ? file[this.patches[i].offset + idx] : patch);
|
||||||
|
this.patches[i].off = this.patches[i].off.map((patch, idx) => patch === 'XX' ? file[this.patches[i].offset + idx] : patch);
|
||||||
|
replace(file, this.patches[i].offset,
|
||||||
|
featureOn? this.patches[i].on : this.patches[i].off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPatch(file, updateUiFlag = false) {
|
||||||
|
var patchStatus = "";
|
||||||
|
if (updateUiFlag) {
|
||||||
|
var listUi = $('<ul />');
|
||||||
|
this.ui.append(listUi);
|
||||||
|
}
|
||||||
|
for(var i = 0; i < this.patches.length; i++) {
|
||||||
|
var patch = this.patches[i];
|
||||||
|
var offOffset = this.searchPatchOffset(file, patch.off, i);
|
||||||
|
var onOffset = this.searchPatchOffset(file, patch.on, i);
|
||||||
|
this.patches[i].offset = offOffset === -1 ? onOffset : offOffset;
|
||||||
|
if(offOffset > 0) {
|
||||||
|
if (updateUiFlag) {
|
||||||
|
if (this.target === 'string') {
|
||||||
|
listUi.append('<li class="patch-off">0x' + offOffset.toString(16) + ' <b>' + patch.off + '</b> will be replaced with <b>'+ patch.on +'</b></li>');
|
||||||
|
} else {
|
||||||
|
listUi.append('<li class="patch-off">0x' + offOffset.toString(16) + ' will be replaced</li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(patchStatus === "") {
|
||||||
|
patchStatus = "off";
|
||||||
|
}
|
||||||
|
} else if(onOffset > 0) {
|
||||||
|
if (updateUiFlag) {
|
||||||
|
if (this.target === 'string') {
|
||||||
|
listUi.append('<li class="patch-on">0x' + onOffset.toString(16) + ' <b>' + patch.on + '</b> will be replaced with <b>'+ patch.off +'</b></li>');
|
||||||
|
} else {
|
||||||
|
listUi.append('<li class="patch-on">0x' + onOffset.toString(16) + ' will be replaced</li>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(patchStatus === "") {
|
||||||
|
patchStatus = "on";
|
||||||
|
}
|
||||||
|
} else if (this.mode === 'all') {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return "patch string not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patchStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPatchAll(file, updateUiFlag = false) {
|
||||||
|
var patchStatus = "";
|
||||||
|
if (updateUiFlag) {
|
||||||
|
var listUi = $('<ul />');
|
||||||
|
this.ui.append(listUi);
|
||||||
|
}
|
||||||
|
for(var i = 0; i < this.patches.length; i++) {
|
||||||
|
var patch = this.patches[i];
|
||||||
|
var offOffset = this.searchPatchOffsetAll(file, patch.off);
|
||||||
|
var onOffset = this.searchPatchOffsetAll(file, patch.on);
|
||||||
|
this.patches[i].offset = offOffset.length === 0 ? onOffset : offOffset;
|
||||||
|
|
||||||
|
if(offOffset.length > 0) {
|
||||||
|
if (updateUiFlag) {
|
||||||
|
offOffset.forEach((offset) => {
|
||||||
|
listUi.append('<li class="patch-off">0x' + offset.toString(16) + ' will be replaced</li>');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(patchStatus === "") {
|
||||||
|
patchStatus = "off";
|
||||||
|
}
|
||||||
|
} else if(onOffset.length > 0) {
|
||||||
|
if (updateUiFlag) {
|
||||||
|
onOffset.forEach((offset) => {
|
||||||
|
listUi.append('<li class="patch-on">0x' + offset.toString(16) + ' will be replaced</li>');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(patchStatus === "") {
|
||||||
|
patchStatus = "on";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "patch string not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patchStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchPatchOffset(file, search, offset) {
|
||||||
|
if (this.target === 'string') {
|
||||||
|
var searchBytes = new TextEncoder().encode(search);
|
||||||
|
} else {
|
||||||
|
var searchBytes = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8Array.prototype.indexOfArr = function(searchElements, fromIndex) {
|
||||||
|
fromIndex = fromIndex || 0;
|
||||||
|
|
||||||
|
var index = Array.prototype.indexOf.call(this, searchElements[0], fromIndex);
|
||||||
|
if(searchElements.length === 1 || index === -1) {
|
||||||
|
return {
|
||||||
|
match: false,
|
||||||
|
index: -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = index, j = 0; j < searchElements.length && i < this.length; i++, j++) {
|
||||||
|
if (this.target !== 'string' && searchElements[j] === 'XX') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(this[i] !== searchElements[j]) {
|
||||||
|
return {
|
||||||
|
match: false,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
match: true,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
var foundCount = 0;
|
||||||
|
for (var i = 0; i < file.length; i++) {
|
||||||
|
var result = file.indexOfArr(searchBytes, idx);
|
||||||
|
if (result.match) {
|
||||||
|
if (offset === foundCount) {
|
||||||
|
return result.index;
|
||||||
|
}
|
||||||
|
foundCount++;
|
||||||
|
} else if (result.index === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
idx = result.index + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchPatchOffsetAll(file, search) {
|
||||||
|
if (this.target === 'string') {
|
||||||
|
var searchBytes = new TextEncoder().encode(search);
|
||||||
|
} else {
|
||||||
|
var searchBytes = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8Array.prototype.indexOfArr = function(searchElements, fromIndex) {
|
||||||
|
fromIndex = fromIndex || 0;
|
||||||
|
|
||||||
|
var index = Array.prototype.indexOf.call(this, searchElements[0], fromIndex);
|
||||||
|
if(searchElements.length === 1 || index === -1) {
|
||||||
|
return {
|
||||||
|
match: false,
|
||||||
|
index: -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = index, j = 0; j < searchElements.length && i < this.length; i++, j++) {
|
||||||
|
if (this.target !== 'string' && searchElements[j] === 'XX') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(this[i] !== searchElements[j]) {
|
||||||
|
return {
|
||||||
|
match: false,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
match: true,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
var foundOffsetArray = [];
|
||||||
|
for (var i = 0; i < file.length; i++) {
|
||||||
|
var result = file.indexOfArr(searchBytes, idx);
|
||||||
|
if (result.match) {
|
||||||
|
foundOffsetArray.push(result.index);
|
||||||
|
} else if (result.index === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
idx = result.index + 1;
|
||||||
|
}
|
||||||
|
return foundOffsetArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
|
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
|
||||||
// updateUI
|
// updateUI
|
||||||
|
|
||||||
@@ -447,6 +704,9 @@ class Patcher {
|
|||||||
if(mod.type === "number") {
|
if(mod.type === "number") {
|
||||||
this.mods.push(new NumberPatch(mod));
|
this.mods.push(new NumberPatch(mod));
|
||||||
}
|
}
|
||||||
|
if(mod.type === "dynamic") {
|
||||||
|
this.mods.push(new DynamicPatch(mod));
|
||||||
|
}
|
||||||
} else { // standard patch
|
} else { // standard patch
|
||||||
this.mods.push(new StandardPatch(mod));
|
this.mods.push(new StandardPatch(mod));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user