mirror of
https://github.com/two-torial/webpatcher.git
synced 2025-02-01 04:15:47 +01: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:
parent
7ee6560d4b
commit
c87d450c46
@ -312,3 +312,15 @@ input[type=radio] {
|
||||
input[type=checkbox]:checked + .patchPreview {
|
||||
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;
|
||||
}
|
260
js/dllpatcher.js
260
js/dllpatcher.js
@ -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,
|
||||
// updateUI
|
||||
|
||||
@ -447,6 +704,9 @@ class Patcher {
|
||||
if(mod.type === "number") {
|
||||
this.mods.push(new NumberPatch(mod));
|
||||
}
|
||||
if(mod.type === "dynamic") {
|
||||
this.mods.push(new DynamicPatch(mod));
|
||||
}
|
||||
} else { // standard patch
|
||||
this.mods.push(new StandardPatch(mod));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user