2022-05-28 13:48:36 +10:00
/*jshint esversion: 6 */
2017-02-08 16:22:19 +10:00
( function ( window , document ) {
"use strict" ;
2017-01-28 14:41:42 +10:00
2018-01-07 22:27:01 +10:00
// form labels often need unique IDs - this can be used to generate some
2019-11-17 20:58:28 +10:00
window . Patcher_uniqueid = 0 ;
2018-01-07 22:27:01 +10:00
var createID = function () {
2019-11-17 20:58:28 +10:00
window . Patcher_uniqueid ++ ;
return "dllpatch_" + window . Patcher_uniqueid ;
2017-01-30 00:37:14 +10:00
};
2019-11-17 20:58:28 +10:00
var bytesMatch = function ( buffer , offset , bytes ) {
for ( var i = 0 ; i < bytes . length ; i ++ ) {
if ( buffer [ offset + i ] != bytes [ i ])
return false ;
2017-01-30 00:37:14 +10:00
}
2019-11-17 20:58:28 +10:00
return true ;
2017-01-30 00:37:14 +10:00
};
2019-11-17 20:58:28 +10:00
var replace = function ( buffer , offset , bytes ) {
for ( var i = 0 ; i < bytes . length ; i ++ ) {
buffer [ offset + i ] = bytes [ i ];
2017-01-30 00:37:14 +10:00
}
2022-05-28 13:48:36 +10:00
};
2017-01-30 00:37:14 +10:00
2019-11-17 20:58:28 +10:00
var whichBytesMatch = function ( buffer , offset , bytesArray ) {
for ( var i = 0 ; i < bytesArray . length ; i ++ ) {
if ( bytesMatch ( buffer , offset , bytesArray [ i ]))
return i ;
2017-01-30 00:37:14 +10:00
}
2019-11-17 20:58:28 +10:00
return - 1 ;
2022-05-28 13:48:36 +10:00
};
// shorthand functions
var createElementClass = function ( elName , className , textContent , innerHTML ) {
var el = document . createElement ( elName );
el . className = className || '' ;
el . textContent = textContent || '' ; // optional
// overrides textContent with HTML if provided
if ( innerHTML ) {
el . innerHTML = innerHTML ;
}
return el ;
};
var createInput = function ( type , id , className ) {
var el = document . createElement ( 'input' );
el . type = type ;
el . id = id ;
el . className = className || '' ;
return el ;
};
var createLabel = function ( labelText , htmlFor , className ) {
var el = document . createElement ( 'label' );
el . textContent = labelText ;
el . htmlFor = htmlFor ;
el . className = className || '' ;
return el ;
};
2017-01-30 00:37:14 +10:00
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
// updateUI
2019-11-17 20:58:28 +10:00
class StandardPatch {
constructor ( options ) {
this . name = options . name ;
this . patches = options . patches ;
this . tooltip = options . tooltip ;
}
2018-06-23 11:59:16 +08:00
2019-11-17 20:58:28 +10:00
createUI ( parent ) {
2018-01-07 22:27:01 +10:00
var id = createID ();
2019-11-17 20:58:28 +10:00
var label = this . name ;
2022-05-28 13:48:36 +10:00
var patch = createElementClass ( 'div' , 'patch' );
this . checkbox = createInput ( 'checkbox' , id );
patch . appendChild ( this . checkbox );
patch . appendChild ( createLabel ( label , id ));
2019-11-17 20:58:28 +10:00
if ( this . tooltip ) {
2022-05-28 13:48:36 +10:00
patch . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ));
2017-09-04 20:30:08 +10:00
}
2022-05-28 13:48:36 +10:00
parent . appendChild ( patch );
2017-01-30 00:37:14 +10:00
}
2019-11-17 20:58:28 +10:00
updateUI ( file ) {
this . checkbox . checked = this . checkPatchBytes ( file ) === "on" ;
2017-01-30 00:37:14 +10:00
}
2019-11-17 20:58:28 +10:00
validatePatch ( file ) {
var status = this . checkPatchBytes ( file );
if ( status === "on" ) {
console . log ( '"' + this . name + '"' , "is enabled!" );
} else if ( status === "off" ) {
console . log ( '"' + this . name + '"' , "is disabled!" );
} else {
2019-11-17 21:03:44 +10:00
return '"' + this . name + '" is neither on nor off! Have you got the right file?' ;
2017-01-30 00:37:14 +10:00
}
}
2019-11-17 20:58:28 +10:00
applyPatch ( file ) {
this . replaceAll ( file , this . checkbox . checked );
}
replaceAll ( file , featureOn ) {
for ( var i = 0 ; i < this . patches . length ; i ++ ) {
replace ( file , this . patches [ i ]. offset ,
featureOn ? this . patches [ i ]. on : this . patches [ i ]. off );
}
}
2017-01-30 00:37:14 +10:00
2019-11-17 20:58:28 +10:00
checkPatchBytes ( file ) {
var patchStatus = "" ;
for ( var i = 0 ; i < this . patches . length ; i ++ ) {
var patch = this . patches [ i ];
if ( bytesMatch ( file , patch . offset , patch . off )) {
if ( patchStatus === "" ) {
patchStatus = "off" ;
} else if ( patchStatus != "off" ){
return "on/off mismatch within patch" ;
}
} else if ( bytesMatch ( file , patch . offset , patch . on )) {
if ( patchStatus === "" ) {
patchStatus = "on" ;
} else if ( patchStatus != "on" ){
return "on/off mismatch within patch" ;
}
} else {
return "patch neither on nor off" ;
}
2017-01-30 00:37:14 +10:00
}
2019-11-17 20:58:28 +10:00
return patchStatus ;
2017-01-30 00:37:14 +10:00
}
}
2021-09-20 20:26:04 +09:00
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 ;
2022-05-28 13:48:36 +10:00
this . ui = createElementClass ( 'div' , 'patch' );
this . checkbox = createInput ( 'checkbox' , id );
this . ui . appendChild ( this . checkbox );
this . ui . appendChild ( createLabel ( label , id ));
2021-09-20 20:26:04 +09:00
if ( this . tooltip ) {
2022-05-28 13:48:36 +10:00
this . ui . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ));
2021-09-20 20:26:04 +09:00
}
2022-05-28 13:48:36 +10:00
parent . appendChild ( this . ui );
2021-09-20 20:26:04 +09:00
}
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 ) {
2022-05-28 13:48:36 +10:00
for ( let patch of this . patches ) {
if ( Array . isArray ( patch . offset )) {
for ( const offset of patch . offset ) {
if ( this . target === 'string' ) {
replace ( file , offset ,
new TextEncoder (). encode ( featureOn ? patch . on : patch . off ));
} else {
patch . on = patch . on . map (( patch , idx ) => patch === 'XX' ? file [ offset + idx ] : patch );
patch . off = patch . off . map (( patch , idx ) => patch === 'XX' ? file [ offset + idx ] : patch );
replace ( file , offset ,
featureOn ? patch . on : patch . off );
2021-09-20 20:26:04 +09:00
}
2022-05-28 13:48:36 +10:00
}
2021-09-20 20:26:04 +09:00
} else {
if ( this . target === 'string' ) {
2022-05-28 13:48:36 +10:00
replace ( file , patch . offset ,
new TextEncoder (). encode ( featureOn ? patch . on : patch . off ));
2021-09-20 20:26:04 +09:00
} else {
2022-05-28 13:48:36 +10:00
patch . on = patch . on . map (( patch , idx ) => patch === 'XX' ? file [ patch . offset + idx ] : patch );
patch . off = patch . off . map (( patch , idx ) => patch === 'XX' ? file [ patch . offset + idx ] : patch );
replace ( file , patch . offset ,
featureOn ? patch . on : patch . off );
2021-09-20 20:26:04 +09:00
}
}
}
}
checkPatch ( file , updateUiFlag = false ) {
var patchStatus = "" ;
2022-05-28 13:48:36 +10:00
let listUi ;
2021-09-20 20:26:04 +09:00
if ( updateUiFlag ) {
2022-05-28 13:48:36 +10:00
listUi = document . createElement ( 'ul' );
this . ui . appendChild ( listUi );
2021-09-20 20:26:04 +09:00
}
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' ) {
2022-05-28 13:48:36 +10:00
listUi . appendChild ( createElementClass ( 'li' , 'patch-off' , null , '0x' + offOffset . toString ( 16 ) + ' <b>' + patch . off + '</b> will be replaced with <b>' + patch . on + '</b>' ));
2021-09-20 20:26:04 +09:00
} else {
2022-05-28 13:48:36 +10:00
listUi . appendChild ( createElementClass ( 'li' , 'patch-off' , '0x' + offOffset . toString ( 16 ) + ' will be replaced' ));
2021-09-20 20:26:04 +09:00
}
}
if ( patchStatus === "" ) {
patchStatus = "off" ;
}
} else if ( onOffset > 0 ) {
if ( updateUiFlag ) {
if ( this . target === 'string' ) {
2022-05-28 13:48:36 +10:00
listUi . appendChild ( createElementClass ( 'li' , 'patch-on' , null , '0x' + onOffset . toString ( 16 ) + ' <b>' + patch . on + '</b> will be replaced with <b>' + patch . off + '</b>' ));
2021-09-20 20:26:04 +09:00
} else {
2022-05-28 13:48:36 +10:00
listUi . appendChild ( createElementClass ( 'li' , 'patch-on' , '0x' + onOffset . toString ( 16 ) + ' will be replaced' ));
2021-09-20 20:26:04 +09:00
}
}
if ( patchStatus === "" ) {
patchStatus = "on" ;
}
} else if ( this . mode === 'all' ) {
continue ;
} else {
return "patch string not found" ;
}
}
return patchStatus ;
}
checkPatchAll ( file , updateUiFlag = false ) {
var patchStatus = "" ;
2022-05-28 13:48:36 +10:00
let listUi ;
2021-09-20 20:26:04 +09:00
if ( updateUiFlag ) {
2022-05-28 13:48:36 +10:00
listUi = document . createElement ( 'ul' );
this . ui . appendChild ( listUi );
2021-09-20 20:26:04 +09:00
}
2022-05-28 13:48:36 +10:00
for ( let patch of this . patches ) {
2021-09-20 20:26:04 +09:00
var offOffset = this . searchPatchOffsetAll ( file , patch . off );
var onOffset = this . searchPatchOffsetAll ( file , patch . on );
2022-05-28 13:48:36 +10:00
patch . offset = offOffset . length === 0 ? onOffset : offOffset ;
2021-09-20 20:26:04 +09:00
if ( offOffset . length > 0 ) {
if ( updateUiFlag ) {
2022-05-28 13:48:36 +10:00
for ( const offset of offOffset ) {
listUi . appendChild ( createElementClass ( 'li' , 'patch-off' , '0x' + offset . toString ( 16 ) + ' will be replaced' ));
}
2021-09-20 20:26:04 +09:00
}
if ( patchStatus === "" ) {
patchStatus = "off" ;
}
} else if ( onOffset . length > 0 ) {
if ( updateUiFlag ) {
2022-05-28 13:48:36 +10:00
for ( const offset of onOffset ) {
listUi . appendChild ( createElementClass ( 'li' , 'patch-on' , '0x' + offset . toString ( 16 ) + ' will be replaced' ));
}
2021-09-20 20:26:04 +09:00
}
if ( patchStatus === "" ) {
patchStatus = "on" ;
}
} else {
return "patch string not found" ;
}
}
return patchStatus ;
}
2022-05-28 13:48:36 +10:00
2021-09-20 20:26:04 +09:00
searchPatchOffset ( file , search , offset ) {
2022-05-28 13:48:36 +10:00
let searchBytes ;
2021-09-20 20:26:04 +09:00
if ( this . target === 'string' ) {
2022-05-28 13:48:36 +10:00
searchBytes = new TextEncoder (). encode ( search );
2021-09-20 20:26:04 +09:00
} else {
2022-05-28 13:48:36 +10:00
searchBytes = search ;
2021-09-20 20:26:04 +09:00
}
2022-05-28 13:48:36 +10:00
2021-09-20 20:26:04 +09:00
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 ,
};
}
2022-05-28 13:48:36 +10:00
2021-09-20 20:26:04 +09:00
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 ,
};
};
2022-05-28 13:48:36 +10:00
var idx = 0 ;
2021-09-20 20:26:04 +09:00
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 ) {
2022-05-28 13:48:36 +10:00
let searchBytes ;
2021-09-20 20:26:04 +09:00
if ( this . target === 'string' ) {
2022-05-28 13:48:36 +10:00
searchBytes = new TextEncoder (). encode ( search );
2021-09-20 20:26:04 +09:00
} else {
2022-05-28 13:48:36 +10:00
searchBytes = search ;
2021-09-20 20:26:04 +09:00
}
Uint8Array . prototype . indexOfArr = function ( searchElements , fromIndex ) {
fromIndex = fromIndex || 0 ;
2022-05-28 13:48:36 +10:00
2021-09-20 20:26:04 +09:00
var index = Array . prototype . indexOf . call ( this , searchElements [ 0 ], fromIndex );
if ( searchElements . length === 1 || index === - 1 ) {
return {
match : false ,
index : - 1 ,
};
}
2022-05-28 13:48:36 +10:00
2021-09-20 20:26:04 +09:00
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 ,
};
}
}
2022-05-28 13:48:36 +10:00
2021-09-20 20:26:04 +09:00
return {
match : true ,
index ,
};
};
2022-05-28 13:48:36 +10:00
var idx = 0 ;
2021-09-20 20:26:04 +09:00
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 ;
}
}
2019-11-17 20:58:28 +10:00
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
// updateUI
2018-06-23 11:59:16 +08:00
2019-11-17 20:58:28 +10:00
// The DEFAULT state is always the 1st element in the patches array
class UnionPatch {
constructor ( options ) {
this . name = options . name ;
this . offset = options . offset ;
this . patches = options . patches ;
2021-09-11 21:37:14 +00:00
this . tooltip = options . tooltip ;
2018-06-23 11:59:16 +08:00
}
2019-11-17 20:58:28 +10:00
createUI ( parent ) {
this . radios = [];
var radio_id = createID ();
2022-05-28 13:48:36 +10:00
var container = createElementClass ( 'div' , 'patch-union' );
container . appendChild ( createElementClass ( 'span' , 'patch-union-title' , this . name + ':' ));
2021-09-11 21:37:14 +00:00
if ( this . tooltip ) {
2022-05-28 13:48:36 +10:00
container . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ));
2021-09-11 21:37:14 +00:00
}
2022-05-28 13:48:36 +10:00
container . appendChild ( document . createElement ( 'span' ));
2021-09-11 21:37:14 +00:00
2019-11-17 20:58:28 +10:00
for ( var i = 0 ; i < this . patches . length ; i ++ ) {
var patch = this . patches [ i ];
var id = createID ();
var label = patch . name ;
2022-05-28 13:48:36 +10:00
var patchDiv = createElementClass ( 'div' , 'patch' );
var radio = createInput ( 'radio' , id );
radio . name = radio_id ;
2019-11-17 20:58:28 +10:00
this . radios . push ( radio );
2022-05-28 13:48:36 +10:00
patchDiv . appendChild ( radio );
patchDiv . appendChild ( createLabel ( label , id ));
2019-11-17 20:58:28 +10:00
if ( patch . tooltip ) {
2022-05-28 13:48:36 +10:00
patchDiv . appendChild ( createElementClass ( 'div' , 'tooltip' , patch . tooltip ));
2019-11-17 20:58:28 +10:00
}
2022-05-28 13:48:36 +10:00
container . appendChild ( patchDiv );
2018-06-23 11:59:16 +08:00
}
2022-05-28 13:48:36 +10:00
parent . appendChild ( container );
2018-06-23 11:59:16 +08:00
}
2019-11-17 20:58:28 +10:00
updateUI ( file ) {
for ( var i = 0 ; i < this . patches . length ; i ++ ) {
if ( bytesMatch ( file , this . offset , this . patches [ i ]. patch )) {
this . radios [ i ]. checked = true ;
return ;
}
}
// Default fallback
this . radios [ 0 ]. checked = true ;
2018-06-23 11:59:16 +08:00
}
2019-11-17 20:58:28 +10:00
validatePatch ( file ) {
for ( var i = 0 ; i < this . patches . length ; i ++ ) {
if ( bytesMatch ( file , this . offset , this . patches [ i ]. patch )) {
console . log ( this . name , "has" , this . patches [ i ]. name , "enabled" );
return ;
}
}
2019-11-17 21:03:44 +10:00
return '"' + this . name + '" doesn\'t have a valid patch! Have you got the right file?' ;
2019-11-17 20:58:28 +10:00
}
2018-06-23 11:59:16 +08:00
2019-11-17 20:58:28 +10:00
applyPatch ( file ) {
var patch = this . getSelected ();
replace ( file , this . offset , patch . patch );
}
2018-06-23 11:59:16 +08:00
2019-11-17 20:58:28 +10:00
getSelected () {
for ( var i = 0 ; i < this . patches . length ; i ++ ) {
if ( this . radios [ i ]. checked ) {
return this . patches [ i ];
}
}
return null ;
2018-06-23 11:59:16 +08:00
}
2019-11-17 20:58:28 +10:00
}
2018-06-23 11:59:16 +08:00
2021-09-11 21:37:14 +00:00
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
// updateUI
class NumberPatch {
constructor ( options ) {
this . name = options . name ;
this . tooltip = options . tooltip ;
this . offset = options . offset ;
this . size = options . size ;
this . min = options . min ;
this . max = options . max ;
}
createUI ( parent ) {
var id = createID ();
var label = this . name ;
2022-05-28 13:48:36 +10:00
var patch = createElementClass ( 'div' , 'patch' );
2021-09-11 21:37:14 +00:00
2022-05-28 13:48:36 +10:00
patch . appendChild ( createLabel ( label , id ));
2021-09-11 21:37:14 +00:00
2022-05-28 13:48:36 +10:00
this . number = createInput ( 'number' , id );
2021-09-11 21:37:14 +00:00
if ( this . min !== null ) {
2022-05-28 13:48:36 +10:00
this . number . min = this . min ;
2021-09-11 21:37:14 +00:00
}
if ( this . max ) {
2022-05-28 13:48:36 +10:00
this . number . max = this . max ;
2021-09-11 21:37:14 +00:00
}
2022-05-28 13:48:36 +10:00
patch . appendChild ( this . number );
2021-09-11 21:37:14 +00:00
if ( this . tooltip ) {
2022-05-28 13:48:36 +10:00
patch . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ));
2021-09-11 21:37:14 +00:00
}
2022-05-28 13:48:36 +10:00
parent . appendChild ( patch );
2021-09-11 21:37:14 +00:00
}
updateUI ( file ) {
// This converts bytes from the file to big endian by shifting each
// byte `i` bytes to the left then doing a bitwise OR to add the less
// significant bytes that were gathered at earlier iterations of loop
var val = 0 ;
for ( var i = 0 ; i < this . size ; i ++ ) {
val = ( file [ this . offset + i ] << ( 8 * i )) | val ;
}
this . number . value = val ;
}
validatePatch ( file ) {
return ;
}
applyPatch ( file ) {
// Convert user inputted number to little endian
const view = new DataView ( new ArrayBuffer ( this . size * 2 ));
view . setInt32 ( 1 , this . number . value , true );
for ( var i = 0 ; i < this . size ; i ++ ) {
var val = view . getInt32 ( 1 );
// Shift off less significant bytes
val = val >> (( this . size - 1 - i ) * 8 );
// Mask off more significant bytes
val = val & 0xFF ;
// Write this byte
file [ this . offset + i ] = val ;
}
}
}
2019-02-17 14:31:02 +10:00
var loadPatch = function ( _this , self , patcher ) {
patcher . loadPatchUI ();
patcher . updatePatchUI ();
2022-05-28 13:48:36 +10:00
patcher . container . style . display = '' ;
2019-11-17 20:58:28 +10:00
var successStr = patcher . filename ;
2022-05-28 13:48:36 +10:00
if ( typeof _this . description === "string" ) {
2019-02-17 14:31:02 +10:00
successStr += "(" + patcher . description + ")" ;
}
2022-05-28 13:48:36 +10:00
self . successDiv . innerHTML = successStr + " loaded successfully!" ;
2018-06-23 11:59:16 +08:00
};
2019-11-17 20:58:28 +10:00
class PatchContainer {
constructor ( patchers ) {
this . patchers = patchers ;
2018-06-23 11:59:16 +08:00
this . createUI ();
}
2017-01-28 14:41:42 +10:00
2019-11-17 20:58:28 +10:00
getSupportedDLLs () {
var dlls = [];
for ( var i = 0 ; i < this . patchers . length ; i ++ ) {
var name = this . patchers [ i ]. filename ;
if ( dlls . indexOf ( name ) === - 1 ) {
dlls . push ( name );
}
}
return dlls ;
2018-01-02 23:06:23 +08:00
}
2017-12-16 13:58:59 +08:00
2019-11-17 20:58:28 +10:00
createUI () {
var self = this ;
2022-05-28 13:48:36 +10:00
var container = createElementClass ( 'div' , 'patchContainer' );
2019-11-17 20:58:28 +10:00
var header = this . getSupportedDLLs (). join ( ", " );
2022-05-28 13:48:36 +10:00
container . innerHTML = "<h3>" + header + "</h3>" ;
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
var supportedDlls = document . createElement ( 'ul' );
2019-11-17 20:58:28 +10:00
this . forceLoadTexts = [];
this . forceLoadButtons = [];
2021-03-26 03:53:45 -07:00
this . matchSuccessText = [];
2021-02-23 23:34:40 +10:00
for ( var i = 0 ; i < this . patchers . length ; i ++ ) {
var checkboxId = createID ();
2022-05-28 13:48:36 +10:00
var listItem = document . createElement ( 'li' );
listItem . appendChild ( createLabel ( this . patchers [ i ]. description , checkboxId , 'patchPreviewLabel' ));
var matchPercent = createElementClass ( 'span' , 'matchPercent' );
2019-11-17 20:58:28 +10:00
this . forceLoadTexts . push ( matchPercent );
2022-05-28 13:48:36 +10:00
listItem . appendChild ( matchPercent );
var matchSuccess = createElementClass ( 'span' , 'matchSuccess' );
2021-03-26 03:53:45 -07:00
this . matchSuccessText . push ( matchSuccess );
2022-05-28 13:48:36 +10:00
listItem . appendChild ( matchSuccess );
var forceButton = createElementClass ( 'button' , '' , 'Force load?' );
forceButton . style . display = 'none' ;
2019-11-17 20:58:28 +10:00
this . forceLoadButtons . push ( forceButton );
2022-05-28 13:48:36 +10:00
listItem . appendChild ( forceButton );
var input = createInput ( 'checkbox' , checkboxId , 'patchPreviewToggle' );
listItem . appendChild ( input );
var patchPreviews = createElementClass ( 'ul' , 'patchPreview' );
2021-02-23 23:34:40 +10:00
for ( var j = 0 ; j < this . patchers [ i ]. mods . length ; j ++ ) {
var patchName = this . patchers [ i ]. mods [ j ]. name ;
2022-05-28 13:48:36 +10:00
patchPreviews . appendChild ( createElementClass ( 'li' , null , patchName ));
2021-02-23 23:34:40 +10:00
}
2022-05-28 13:48:36 +10:00
listItem . appendChild ( patchPreviews );
2021-02-23 23:34:40 +10:00
2022-05-28 13:48:36 +10:00
supportedDlls . appendChild ( listItem );
2019-11-17 20:58:28 +10:00
}
2017-12-16 13:58:59 +08:00
2022-05-28 13:48:36 +10:00
[ "dragover" , "dragenter" ]. forEach ( function ( n ){
document . documentElement . addEventListener ( n , function ( e ) {
container . classList . add ( "dragover" );
2019-11-17 20:58:28 +10:00
e . preventDefault ();
2020-05-01 09:02:15 +08:00
e . stopPropagation ();
2019-11-17 20:58:28 +10:00
});
2022-05-28 13:48:36 +10:00
});
[ "dragleave" , "dragend" , "drop" ]. forEach ( function ( n ){
document . documentElement . addEventListener ( n , function ( e ) {
container . classList . remove ( "dragover" );
e . preventDefault ();
e . stopPropagation ();
});
});
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
container . addEventListener ( "drop" , function ( e ) {
var files = e . dataTransfer . files ;
2019-11-17 20:58:28 +10:00
if ( files && files . length > 0 )
2018-06-23 11:59:16 +08:00
self . loadFile ( files [ 0 ]);
2019-11-17 20:58:28 +10:00
});
2018-06-23 11:59:16 +08:00
2021-02-23 22:45:16 +10:00
var filepickerId = createID ();
2022-05-28 13:48:36 +10:00
this . fileInput = createInput ( 'file' , filepickerId , 'fileInput' );
var label = createLabel ( '' , filepickerId , 'fileLabel' );
label . innerHTML = "<strong>Choose a file</strong> or drag and drop." ;
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
this . fileInput . addEventListener ( "change" , function ( e ) {
2019-11-17 20:58:28 +10:00
if ( this . files && this . files . length > 0 )
2018-06-23 11:59:16 +08:00
self . loadFile ( this . files [ 0 ]);
});
2022-05-28 13:48:36 +10:00
this . successDiv = createElementClass ( 'div' , 'success' );
this . errorDiv = createElementClass ( 'div' , 'error' );
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
container . appendChild ( this . fileInput );
container . appendChild ( label );
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
container . appendChild ( createElementClass ( 'h4' , null , 'Supported Versions:' ));
container . appendChild ( createElementClass ( 'h5' , null , 'Click name to preview patches' ));
container . appendChild ( supportedDlls );
container . appendChild ( this . successDiv );
container . appendChild ( this . errorDiv );
document . body . appendChild ( container );
2018-06-23 11:59:16 +08:00
}
2019-11-17 20:58:28 +10:00
loadFile ( file ) {
var reader = new FileReader ();
var self = this ;
2018-06-23 11:59:16 +08:00
2019-11-17 20:58:28 +10:00
reader . onload = function ( e ) {
var found = false ;
// clear logs
2022-05-28 13:48:36 +10:00
self . errorDiv . textContent = '' ;
self . successDiv . textContent = '' ;
2019-11-17 20:58:28 +10:00
for ( var i = 0 ; i < self . patchers . length ; i ++ ) {
2021-03-26 03:53:45 -07:00
// reset text and buttons
2022-05-28 13:48:36 +10:00
self . forceLoadButtons [ i ]. style . display = 'none' ;
self . forceLoadTexts [ i ]. textContent = '' ;
self . matchSuccessText [ i ]. textContent = '' ;
2019-11-17 20:58:28 +10:00
var patcher = self . patchers [ i ];
// remove the previous UI to clear the page
patcher . destroyUI ();
// patcher UI elements have to exist to load the file
patcher . createUI ();
2022-05-28 13:48:36 +10:00
patcher . container . style . display = 'none' ;
2019-11-17 20:58:28 +10:00
patcher . loadBuffer ( e . target . result );
if ( patcher . validatePatches ()) {
found = true ;
loadPatch ( this , self , patcher );
2021-03-26 03:53:45 -07:00
// show patches matched for 100% - helps identify which version is loaded
var valid = patcher . validPatches ;
2022-05-28 13:48:36 +10:00
self . matchSuccessText [ i ]. textContent = ' ' + valid + ' of ' + valid + ' patches matched (100%) ' ;
2019-11-17 20:58:28 +10:00
}
}
2018-06-23 11:59:16 +08:00
2019-11-17 20:58:28 +10:00
if ( ! found ) {
// let the user force a match
2022-05-28 13:48:36 +10:00
for ( let i = 0 ; i < self . patchers . length ; i ++ ) {
const patcher = self . patchers [ i ];
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
const valid = patcher . validPatches ;
const percent = ( valid / patcher . totalPatches * 100 ). toFixed ( 1 );
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
self . forceLoadTexts [ i ]. textContent = ' ' + valid + ' of ' + patcher . totalPatches + ' patches matched (' + percent + '%) ' ;
self . forceLoadButtons [ i ]. style . display = '' ;
self . forceLoadButtons [ i ]. onclick = function ( i ) {
2019-11-17 20:58:28 +10:00
// reset old text
for ( var j = 0 ; j < self . patchers . length ; j ++ ) {
2022-05-28 13:48:36 +10:00
self . forceLoadButtons [ j ]. style . display = 'none' ;
self . forceLoadTexts [ j ]. textContent = '' ;
2019-11-17 20:58:28 +10:00
}
loadPatch ( this , self , self . patchers [ i ]);
2022-05-28 13:48:36 +10:00
}. bind ( this , i );
2019-11-17 20:58:28 +10:00
}
2022-05-28 13:48:36 +10:00
self . errorDiv . innerHTML = "No patch set was a 100% match." ;
2019-11-17 20:58:28 +10:00
}
};
reader . readAsArrayBuffer ( file );
2018-06-23 11:59:16 +08:00
}
2017-02-08 16:22:19 +10:00
}
2017-01-28 14:41:42 +10:00
2019-11-17 20:58:28 +10:00
class Patcher {
constructor ( fname , description , args ) {
this . mods = [];
for ( var i = 0 ; i < args . length ; i ++ ) {
var mod = args [ i ];
if ( mod . type ) {
if ( mod . type === "union" ) {
this . mods . push ( new UnionPatch ( mod ));
}
2021-09-11 21:37:14 +00:00
if ( mod . type === "number" ) {
this . mods . push ( new NumberPatch ( mod ));
}
2021-09-20 20:26:04 +09:00
if ( mod . type === "dynamic" ) {
this . mods . push ( new DynamicPatch ( mod ));
}
2019-11-17 20:58:28 +10:00
} else { // standard patch
this . mods . push ( new StandardPatch ( mod ));
}
}
2017-12-16 13:58:59 +08:00
2019-11-17 20:58:28 +10:00
this . filename = fname ;
this . description = description ;
this . multiPatcher = true ;
2017-01-28 14:41:42 +10:00
2019-11-17 20:58:28 +10:00
if ( ! this . description ) {
// old style patcher, use the old method to generate the UI
this . multiPatcher = false ;
this . createUI ();
this . loadPatchUI ();
}
}
2017-01-28 14:41:42 +10:00
2019-11-17 20:58:28 +10:00
createUI () {
var self = this ;
2022-05-28 13:48:36 +10:00
this . container = createElementClass ( 'div' , 'patchContainer' );
2019-11-17 20:58:28 +10:00
var header = this . filename ;
if ( this . description === "string" ) {
header += ' (' + this . description + ')' ;
}
2022-05-28 13:48:36 +10:00
this . container . innerHTML = '<h3>' + header + '</h3>' ;
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
this . successDiv = createElementClass ( 'div' , 'success' );
this . errorDiv = createElementClass ( 'div' , 'error' );
this . patchDiv = createElementClass ( 'div' , 'patches' );
2019-11-17 20:58:28 +10:00
2022-05-28 13:48:36 +10:00
var saveButton = document . createElement ( 'button' );
saveButton . disabled = true ;
saveButton . textContent = 'Load file First' ;
saveButton . addEventListener ( 'click' , this . saveDll . bind ( this ));
2019-11-17 20:58:28 +10:00
this . saveButton = saveButton ;
if ( ! this . multiPatcher ) {
2022-05-28 13:48:36 +10:00
[ "dragover" , "dragenter" ]. forEach ( function ( n ){
document . documentElement . addEventListener ( n , function ( e ) {
self . container . classList . add ( 'dragover' );
e . preventDefault ();
return true ;
});
});
[ "dragleave" , "dragend" , "drop" ]. forEach ( function ( n ){
document . documentElement . addEventListener ( n , function ( e ) {
self . container . classList . remove ( 'dragover' );
e . preventDefault ();
return true ;
});
2019-11-17 20:58:28 +10:00
});
2022-05-28 13:48:36 +10:00
this . container . addEventListener ( 'drop' , function ( e ) {
var files = e . dataTransfer . files ;
2019-11-17 20:58:28 +10:00
if ( files && files . length > 0 )
self . loadFile ( files [ 0 ]);
});
2021-02-23 22:45:16 +10:00
var filepickerId = createID ();
2022-05-28 13:48:36 +10:00
this . fileInput = createInput ( 'file' , filepickerId , 'fileInput' );
var label = createLabel ( '' , filepickerId , 'fileLabel' );
label . innerHTML = '<strong>Choose a file</strong> or drag and drop.' ;
this . fileInput . addEventListener ( 'change' , function ( e ) {
2019-11-17 20:58:28 +10:00
if ( this . files && this . files . length > 0 )
self . loadFile ( this . files [ 0 ]);
});
2022-05-28 13:48:36 +10:00
this . container . appendChild ( this . fileInput );
this . container . appendChild ( label );
2019-11-17 20:58:28 +10:00
}
2017-12-16 13:58:59 +08:00
2022-05-28 13:48:36 +10:00
this . container . appendChild ( this . successDiv );
this . container . appendChild ( this . errorDiv );
this . container . appendChild ( this . patchDiv );
this . container . appendChild ( saveButton );
document . body . appendChild ( this . container );
2017-03-13 14:32:53 +10:00
}
2017-12-16 13:58:59 +08:00
2019-11-17 20:58:28 +10:00
destroyUI () {
if ( this . hasOwnProperty ( "container" ))
this . container . remove ();
}
2017-01-28 14:41:42 +10:00
2019-11-17 20:58:28 +10:00
loadBuffer ( buffer ) {
this . dllFile = new Uint8Array ( buffer );
if ( this . validatePatches ()) {
2022-05-28 13:48:36 +10:00
this . successDiv . classList . remove ( "hidden" );
this . successDiv . innerHTML = "File loaded successfully!" ;
2019-11-17 20:58:28 +10:00
} else {
2022-05-28 13:48:36 +10:00
this . successDiv . classList . add ( "hidden" );
2019-11-17 20:58:28 +10:00
}
// Update save button regardless
2022-05-28 13:48:36 +10:00
this . saveButton . disabled = false ;
this . saveButton . textContent = 'Save Patched File' ;
this . errorDiv . innerHTML = this . errorLog ;
2017-01-28 14:41:42 +10:00
}
2019-11-17 20:58:28 +10:00
loadFile ( file ) {
var reader = new FileReader ();
var self = this ;
reader . onload = function ( e ) {
self . loadBuffer ( e . target . result );
self . updatePatchUI ();
};
reader . readAsArrayBuffer ( file );
2017-01-28 14:41:42 +10:00
}
2022-05-28 13:48:36 +10:00
downloadURI ( uri , filename ) {
// http://stackoverflow.com/a/18197341
var element = document . createElement ( 'a' );
element . setAttribute ( 'href' , uri );
element . setAttribute ( 'download' , filename );
element . style . display = 'none' ;
document . body . appendChild ( element );
element . click ();
document . body . removeChild ( element );
}
2019-11-17 20:58:28 +10:00
saveDll () {
if ( ! this . dllFile || ! this . mods || ! this . filename )
return ;
for ( var i = 0 ; i < this . mods . length ; i ++ ) {
this . mods [ i ]. applyPatch ( this . dllFile );
2017-01-28 14:41:42 +10:00
}
2019-11-17 20:58:28 +10:00
var blob = new Blob ([ this . dllFile ], { type : "application/octet-stream" });
2022-05-28 13:48:36 +10:00
var uri = URL . createObjectURL ( blob );
this . downloadURI ( uri , this . filename );
URL . revokeObjectURL ( uri );
2017-01-28 14:41:42 +10:00
}
2019-11-17 20:58:28 +10:00
loadPatchUI () {
for ( var i = 0 ; i < this . mods . length ; i ++ ) {
this . mods [ i ]. createUI ( this . patchDiv );
}
2017-01-28 14:41:42 +10:00
}
2019-11-17 20:58:28 +10:00
updatePatchUI () {
for ( var i = 0 ; i < this . mods . length ; i ++ ) {
this . mods [ i ]. updateUI ( this . dllFile );
}
2017-01-28 14:41:42 +10:00
}
2019-11-17 20:58:28 +10:00
validatePatches () {
this . errorLog = "" ;
var success = true ;
this . validPatches = 0 ;
this . totalPatches = this . mods . length ;
for ( var i = 0 ; i < this . mods . length ; i ++ ) {
var error = this . mods [ i ]. validatePatch ( this . dllFile );
if ( error ) {
this . errorLog += error + "<br/>" ;
success = false ;
} else {
this . validPatches ++ ;
}
}
return success ;
2017-01-28 14:41:42 +10:00
}
}
2019-11-17 20:58:28 +10:00
window . Patcher = Patcher ;
window . PatchContainer = PatchContainer ;
2017-02-08 16:22:19 +10:00
2021-02-23 22:45:16 +10:00
})( window , document );