2022-05-28 05:48:36 +02:00
/*jshint esversion: 6 */
2017-02-08 07:22:19 +01:00
( function ( window , document ) {
"use strict" ;
2017-01-28 05:41:42 +01:00
2018-01-07 13:27:01 +01:00
// form labels often need unique IDs - this can be used to generate some
2019-11-17 11:58:28 +01:00
window . Patcher _uniqueid = 0 ;
2018-01-07 13:27:01 +01:00
var createID = function ( ) {
2019-11-17 11:58:28 +01:00
window . Patcher _uniqueid ++ ;
return "dllpatch_" + window . Patcher _uniqueid ;
2017-01-29 15:37:14 +01:00
} ;
2019-11-17 11:58:28 +01: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-29 15:37:14 +01:00
}
2019-11-17 11:58:28 +01:00
return true ;
2017-01-29 15:37:14 +01:00
} ;
2023-06-19 05:44:51 +02:00
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 ;
}
2019-11-17 11:58:28 +01:00
var replace = function ( buffer , offset , bytes ) {
for ( var i = 0 ; i < bytes . length ; i ++ ) {
buffer [ offset + i ] = bytes [ i ] ;
2017-01-29 15:37:14 +01:00
}
2022-05-28 05:48:36 +02:00
} ;
2017-01-29 15:37:14 +01:00
2019-11-17 11:58:28 +01: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-29 15:37:14 +01:00
}
2019-11-17 11:58:28 +01:00
return - 1 ;
2022-05-28 05:48:36 +02: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-29 15:37:14 +01:00
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
// updateUI
2019-11-17 11:58:28 +01:00
class StandardPatch {
constructor ( options ) {
this . name = options . name ;
this . patches = options . patches ;
this . tooltip = options . tooltip ;
2022-09-13 12:39:39 +02:00
this . danger = options . danger ;
2019-11-17 11:58:28 +01:00
}
2018-06-23 05:59:16 +02:00
2019-11-17 11:58:28 +01:00
createUI ( parent ) {
2018-01-07 13:27:01 +01:00
var id = createID ( ) ;
2019-11-17 11:58:28 +01:00
var label = this . name ;
2022-05-28 05:48:36 +02:00
var patch = createElementClass ( 'div' , 'patch' ) ;
this . checkbox = createInput ( 'checkbox' , id ) ;
patch . appendChild ( this . checkbox ) ;
patch . appendChild ( createLabel ( label , id ) ) ;
2019-11-17 11:58:28 +01:00
if ( this . tooltip ) {
2022-05-28 05:48:36 +02:00
patch . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ) ) ;
2017-09-04 12:30:08 +02:00
}
2022-09-13 12:39:39 +02:00
if ( this . danger ) {
patch . appendChild ( createElementClass ( 'div' , 'danger tooltip' , this . danger ) ) ;
}
2022-05-28 05:48:36 +02:00
parent . appendChild ( patch ) ;
2017-01-29 15:37:14 +01:00
}
2019-11-17 11:58:28 +01:00
updateUI ( file ) {
this . checkbox . checked = this . checkPatchBytes ( file ) === "on" ;
2017-01-29 15:37:14 +01:00
}
2019-11-17 11:58:28 +01: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 12:03:44 +01:00
return '"' + this . name + '" is neither on nor off! Have you got the right file?' ;
2017-01-29 15:37:14 +01:00
}
}
2019-11-17 11:58:28 +01: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-29 15:37:14 +01:00
2019-11-17 11:58:28 +01: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-29 15:37:14 +01:00
}
2019-11-17 11:58:28 +01:00
return patchStatus ;
2017-01-29 15:37:14 +01:00
}
}
2021-09-20 13:26:04 +02:00
class DynamicPatch {
constructor ( options ) {
this . name = options . name ;
this . patches = options . patches ;
this . tooltip = options . tooltip ;
2022-09-13 12:39:39 +02:00
this . danger = options . danger ;
2021-09-20 13:26:04 +02:00
this . mode = options . mode ;
this . target = options . target ;
}
createUI ( parent ) {
var id = createID ( ) ;
var label = this . name ;
2022-05-28 05:48:36 +02: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 13:26:04 +02:00
if ( this . tooltip ) {
2022-05-28 05:48:36 +02:00
this . ui . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ) ) ;
2021-09-20 13:26:04 +02:00
}
2022-09-13 12:39:39 +02:00
if ( this . danger ) {
this . ui . appendChild ( createElementClass ( 'div' , 'danger tooltip' , this . danger ) ) ;
}
2022-05-28 05:48:36 +02:00
parent . appendChild ( this . ui ) ;
2021-09-20 13:26:04 +02: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 05:48:36 +02: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 {
2024-07-01 11:14:40 +02:00
patch . on = patch . on . map ( ( byte , idx ) => byte === 'XX' ? file [ offset + idx ] : byte ) ;
patch . off = patch . off . map ( ( byte , idx ) => byte === 'XX' ? file [ offset + idx ] : byte ) ;
2022-05-28 05:48:36 +02:00
replace ( file , offset ,
featureOn ? patch . on : patch . off ) ;
2021-09-20 13:26:04 +02:00
}
2022-05-28 05:48:36 +02:00
}
2021-09-20 13:26:04 +02:00
} else {
if ( this . target === 'string' ) {
2022-05-28 05:48:36 +02:00
replace ( file , patch . offset ,
new TextEncoder ( ) . encode ( featureOn ? patch . on : patch . off ) ) ;
2021-09-20 13:26:04 +02:00
} else {
2024-07-01 11:14:40 +02:00
patch . on = patch . on . map ( ( byte , idx ) => byte === 'XX' ? file [ patch . offset + idx ] : byte ) ;
patch . off = patch . off . map ( ( byte , idx ) => byte === 'XX' ? file [ patch . offset + idx ] : byte ) ;
2022-05-28 05:48:36 +02:00
replace ( file , patch . offset ,
featureOn ? patch . on : patch . off ) ;
2021-09-20 13:26:04 +02:00
}
}
}
}
checkPatch ( file , updateUiFlag = false ) {
var patchStatus = "" ;
2022-05-28 05:48:36 +02:00
let listUi ;
2021-09-20 13:26:04 +02:00
if ( updateUiFlag ) {
2022-05-28 05:48:36 +02:00
listUi = document . createElement ( 'ul' ) ;
this . ui . appendChild ( listUi ) ;
2021-09-20 13:26:04 +02: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 05:48:36 +02: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 13:26:04 +02:00
} else {
2022-05-28 05:48:36 +02:00
listUi . appendChild ( createElementClass ( 'li' , 'patch-off' , '0x' + offOffset . toString ( 16 ) + ' will be replaced' ) ) ;
2021-09-20 13:26:04 +02:00
}
}
if ( patchStatus === "" ) {
patchStatus = "off" ;
}
} else if ( onOffset > 0 ) {
if ( updateUiFlag ) {
if ( this . target === 'string' ) {
2022-05-28 05:48:36 +02: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 13:26:04 +02:00
} else {
2022-05-28 05:48:36 +02:00
listUi . appendChild ( createElementClass ( 'li' , 'patch-on' , '0x' + onOffset . toString ( 16 ) + ' will be replaced' ) ) ;
2021-09-20 13:26:04 +02: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 05:48:36 +02:00
let listUi ;
2021-09-20 13:26:04 +02:00
if ( updateUiFlag ) {
2022-05-28 05:48:36 +02:00
listUi = document . createElement ( 'ul' ) ;
this . ui . appendChild ( listUi ) ;
2021-09-20 13:26:04 +02:00
}
2022-05-28 05:48:36 +02:00
for ( let patch of this . patches ) {
2021-09-20 13:26:04 +02:00
var offOffset = this . searchPatchOffsetAll ( file , patch . off ) ;
var onOffset = this . searchPatchOffsetAll ( file , patch . on ) ;
2022-05-28 05:48:36 +02:00
patch . offset = offOffset . length === 0 ? onOffset : offOffset ;
2021-09-20 13:26:04 +02:00
if ( offOffset . length > 0 ) {
if ( updateUiFlag ) {
2022-05-28 05:48:36 +02:00
for ( const offset of offOffset ) {
listUi . appendChild ( createElementClass ( 'li' , 'patch-off' , '0x' + offset . toString ( 16 ) + ' will be replaced' ) ) ;
}
2021-09-20 13:26:04 +02:00
}
if ( patchStatus === "" ) {
patchStatus = "off" ;
}
} else if ( onOffset . length > 0 ) {
if ( updateUiFlag ) {
2022-05-28 05:48:36 +02:00
for ( const offset of onOffset ) {
listUi . appendChild ( createElementClass ( 'li' , 'patch-on' , '0x' + offset . toString ( 16 ) + ' will be replaced' ) ) ;
}
2021-09-20 13:26:04 +02:00
}
if ( patchStatus === "" ) {
patchStatus = "on" ;
}
} else {
return "patch string not found" ;
}
}
return patchStatus ;
}
2022-05-28 05:48:36 +02:00
2021-09-20 13:26:04 +02:00
searchPatchOffset ( file , search , offset ) {
2022-05-28 05:48:36 +02:00
let searchBytes ;
2021-09-20 13:26:04 +02:00
if ( this . target === 'string' ) {
2022-05-28 05:48:36 +02:00
searchBytes = new TextEncoder ( ) . encode ( search ) ;
2021-09-20 13:26:04 +02:00
} else {
2022-05-28 05:48:36 +02:00
searchBytes = search ;
2021-09-20 13:26:04 +02:00
}
2022-05-28 05:48:36 +02:00
2021-09-20 13:26:04 +02: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 05:48:36 +02:00
2021-09-20 13:26:04 +02: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 05:48:36 +02:00
var idx = 0 ;
2021-09-20 13:26:04 +02: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 05:48:36 +02:00
let searchBytes ;
2021-09-20 13:26:04 +02:00
if ( this . target === 'string' ) {
2022-05-28 05:48:36 +02:00
searchBytes = new TextEncoder ( ) . encode ( search ) ;
2021-09-20 13:26:04 +02:00
} else {
2022-05-28 05:48:36 +02:00
searchBytes = search ;
2021-09-20 13:26:04 +02:00
}
Uint8Array . prototype . indexOfArr = function ( searchElements , fromIndex ) {
fromIndex = fromIndex || 0 ;
2022-05-28 05:48:36 +02:00
2021-09-20 13:26:04 +02: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 05:48:36 +02:00
2021-09-20 13:26:04 +02: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 05:48:36 +02:00
2021-09-20 13:26:04 +02:00
return {
match : true ,
index ,
} ;
} ;
2022-05-28 05:48:36 +02:00
var idx = 0 ;
2021-09-20 13:26:04 +02: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 11:58:28 +01:00
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
// updateUI
2018-06-23 05:59:16 +02:00
2019-11-17 11:58:28 +01: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 23:37:14 +02:00
this . tooltip = options . tooltip ;
2022-09-13 12:39:39 +02:00
this . danger = options . danger ;
2018-06-23 05:59:16 +02:00
}
2019-11-17 11:58:28 +01:00
createUI ( parent ) {
this . radios = [ ] ;
var radio _id = createID ( ) ;
2022-05-28 05:48:36 +02:00
var container = createElementClass ( 'div' , 'patch-union' ) ;
container . appendChild ( createElementClass ( 'span' , 'patch-union-title' , this . name + ':' ) ) ;
2021-09-11 23:37:14 +02:00
if ( this . tooltip ) {
2022-05-28 05:48:36 +02:00
container . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ) ) ;
2021-09-11 23:37:14 +02:00
}
2022-09-13 12:39:39 +02:00
if ( this . danger ) {
container . appendChild ( createElementClass ( 'div' , 'danger tooltip' , this . danger ) ) ;
}
2022-05-28 05:48:36 +02:00
container . appendChild ( document . createElement ( 'span' ) ) ;
2021-09-11 23:37:14 +02:00
2019-11-17 11:58:28 +01: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 05:48:36 +02:00
var patchDiv = createElementClass ( 'div' , 'patch' ) ;
var radio = createInput ( 'radio' , id ) ;
radio . name = radio _id ;
2019-11-17 11:58:28 +01:00
this . radios . push ( radio ) ;
2022-05-28 05:48:36 +02:00
patchDiv . appendChild ( radio ) ;
patchDiv . appendChild ( createLabel ( label , id ) ) ;
2019-11-17 11:58:28 +01:00
if ( patch . tooltip ) {
2022-05-28 05:48:36 +02:00
patchDiv . appendChild ( createElementClass ( 'div' , 'tooltip' , patch . tooltip ) ) ;
2019-11-17 11:58:28 +01:00
}
2022-09-13 12:39:39 +02:00
if ( patch . danger ) {
patchDiv . appendChild ( createElementClass ( 'div' , 'danger tooltip' , patch . danger ) ) ;
}
2022-05-28 05:48:36 +02:00
container . appendChild ( patchDiv ) ;
2018-06-23 05:59:16 +02:00
}
2022-05-28 05:48:36 +02:00
parent . appendChild ( container ) ;
2018-06-23 05:59:16 +02:00
}
2019-11-17 11:58:28 +01: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 05:59:16 +02:00
}
2019-11-17 11:58:28 +01: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 12:03:44 +01:00
return '"' + this . name + '" doesn\'t have a valid patch! Have you got the right file?' ;
2019-11-17 11:58:28 +01:00
}
2018-06-23 05:59:16 +02:00
2019-11-17 11:58:28 +01:00
applyPatch ( file ) {
var patch = this . getSelected ( ) ;
replace ( file , this . offset , patch . patch ) ;
}
2018-06-23 05:59:16 +02:00
2019-11-17 11:58:28 +01: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 05:59:16 +02:00
}
2019-11-17 11:58:28 +01:00
}
2018-06-23 05:59:16 +02:00
2021-09-11 23:37:14 +02:00
// Each unique kind of patch should have createUI, validatePatch, applyPatch,
// updateUI
class NumberPatch {
constructor ( options ) {
this . name = options . name ;
this . tooltip = options . tooltip ;
2022-09-13 12:39:39 +02:00
this . danger = options . danger ;
2021-09-11 23:37:14 +02:00
this . offset = options . offset ;
this . size = options . size ;
this . min = options . min ;
this . max = options . max ;
2024-07-04 12:09:54 +02:00
this . littleEndian = options . littleEndian ? ? true ;
if ( ! [ 1 , 2 , 4 ] . includes ( this . size ) ) {
throw new Error ( ` Unsupported number size: ${ this . size } . ` ) ;
}
2021-09-11 23:37:14 +02:00
}
createUI ( parent ) {
var id = createID ( ) ;
var label = this . name ;
2022-05-28 05:48:36 +02:00
var patch = createElementClass ( 'div' , 'patch' ) ;
2021-09-11 23:37:14 +02:00
2022-05-28 05:48:36 +02:00
patch . appendChild ( createLabel ( label , id ) ) ;
2021-09-11 23:37:14 +02:00
2022-05-28 05:48:36 +02:00
this . number = createInput ( 'number' , id ) ;
2024-07-04 12:09:54 +02:00
this . number . style . width = "3rem" ;
2021-09-11 23:37:14 +02:00
if ( this . min !== null ) {
2022-05-28 05:48:36 +02:00
this . number . min = this . min ;
2021-09-11 23:37:14 +02:00
}
if ( this . max ) {
2022-05-28 05:48:36 +02:00
this . number . max = this . max ;
2021-09-11 23:37:14 +02:00
}
2022-05-28 05:48:36 +02:00
patch . appendChild ( this . number ) ;
2021-09-11 23:37:14 +02:00
if ( this . tooltip ) {
2022-05-28 05:48:36 +02:00
patch . appendChild ( createElementClass ( 'div' , 'tooltip' , this . tooltip ) ) ;
2021-09-11 23:37:14 +02:00
}
2022-09-13 12:39:39 +02:00
if ( this . danger ) {
patch . appendChild ( createElementClass ( 'div' , 'danger tooltip' , this . danger ) ) ;
}
2022-05-28 05:48:36 +02:00
parent . appendChild ( patch ) ;
2021-09-11 23:37:14 +02:00
}
updateUI ( file ) {
2024-07-04 12:09:54 +02:00
const arr = new Uint8Array ( this . size ) ;
const view = new DataView ( arr . buffer ) ;
2021-09-11 23:37:14 +02:00
for ( var i = 0 ; i < this . size ; i ++ ) {
2024-07-04 12:09:54 +02:00
arr [ i ] = file [ this . offset + i ] ;
2021-09-11 23:37:14 +02:00
}
2024-07-04 12:09:54 +02:00
if ( this . size === 1 ) {
this . number . value = view . getInt8 ( 0 ) ;
} else if ( this . size === 2 ) {
this . number . value = view . getInt16 ( 0 , this . littleEndian ) ;
} else if ( this . size === 4 ) {
this . number . value = view . getInt32 ( 0 , this . littleEndian ) ;
}
2021-09-11 23:37:14 +02:00
}
validatePatch ( file ) {
return ;
}
applyPatch ( file ) {
// Convert user inputted number to little endian
2024-07-04 12:09:54 +02:00
const arr = new Uint8Array ( this . size ) ;
const view = new DataView ( arr . buffer ) ;
if ( this . size === 1 ) {
view . setInt8 ( 0 , this . number . value ) ;
} else if ( this . size === 2 ) {
view . setInt16 ( 0 , this . number . value , this . littleEndian ) ;
} else if ( this . size === 4 ) {
view . setInt32 ( 0 , this . number . value , this . littleEndian ) ;
}
2021-09-11 23:37:14 +02:00
for ( var i = 0 ; i < this . size ; i ++ ) {
2024-07-04 12:09:54 +02:00
file [ this . offset + i ] = arr [ i ] ;
2021-09-11 23:37:14 +02:00
}
}
}
2023-06-19 05:44:51 +02:00
// 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 ;
}
}
}
2019-02-17 05:31:02 +01:00
var loadPatch = function ( _this , self , patcher ) {
patcher . loadPatchUI ( ) ;
patcher . updatePatchUI ( ) ;
2022-05-28 05:48:36 +02:00
patcher . container . style . display = '' ;
2019-11-17 11:58:28 +01:00
var successStr = patcher . filename ;
2022-05-28 05:48:36 +02:00
if ( typeof _this . description === "string" ) {
2019-02-17 05:31:02 +01:00
successStr += "(" + patcher . description + ")" ;
}
2022-05-28 05:48:36 +02:00
self . successDiv . innerHTML = successStr + " loaded successfully!" ;
2018-06-23 05:59:16 +02:00
} ;
2019-11-17 11:58:28 +01:00
class PatchContainer {
constructor ( patchers ) {
this . patchers = patchers ;
2018-06-23 05:59:16 +02:00
this . createUI ( ) ;
}
2017-01-28 05:41:42 +01:00
2019-11-17 11:58:28 +01: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 16:06:23 +01:00
}
2017-12-16 06:58:59 +01:00
2019-11-17 11:58:28 +01:00
createUI ( ) {
var self = this ;
2022-05-28 05:48:36 +02:00
var container = createElementClass ( 'div' , 'patchContainer' ) ;
2019-11-17 11:58:28 +01:00
var header = this . getSupportedDLLs ( ) . join ( ", " ) ;
2022-05-28 05:48:36 +02:00
container . innerHTML = "<h3>" + header + "</h3>" ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
var supportedDlls = document . createElement ( 'ul' ) ;
2019-11-17 11:58:28 +01:00
this . forceLoadTexts = [ ] ;
this . forceLoadButtons = [ ] ;
2021-03-26 11:53:45 +01:00
this . matchSuccessText = [ ] ;
2021-02-23 14:34:40 +01:00
for ( var i = 0 ; i < this . patchers . length ; i ++ ) {
var checkboxId = createID ( ) ;
2022-05-28 05:48:36 +02:00
var listItem = document . createElement ( 'li' ) ;
listItem . appendChild ( createLabel ( this . patchers [ i ] . description , checkboxId , 'patchPreviewLabel' ) ) ;
var matchPercent = createElementClass ( 'span' , 'matchPercent' ) ;
2019-11-17 11:58:28 +01:00
this . forceLoadTexts . push ( matchPercent ) ;
2022-05-28 05:48:36 +02:00
listItem . appendChild ( matchPercent ) ;
var matchSuccess = createElementClass ( 'span' , 'matchSuccess' ) ;
2021-03-26 11:53:45 +01:00
this . matchSuccessText . push ( matchSuccess ) ;
2022-05-28 05:48:36 +02:00
listItem . appendChild ( matchSuccess ) ;
var forceButton = createElementClass ( 'button' , '' , 'Force load?' ) ;
forceButton . style . display = 'none' ;
2019-11-17 11:58:28 +01:00
this . forceLoadButtons . push ( forceButton ) ;
2022-05-28 05:48:36 +02:00
listItem . appendChild ( forceButton ) ;
var input = createInput ( 'checkbox' , checkboxId , 'patchPreviewToggle' ) ;
listItem . appendChild ( input ) ;
var patchPreviews = createElementClass ( 'ul' , 'patchPreview' ) ;
2021-02-23 14:34:40 +01:00
for ( var j = 0 ; j < this . patchers [ i ] . mods . length ; j ++ ) {
var patchName = this . patchers [ i ] . mods [ j ] . name ;
2022-05-28 05:48:36 +02:00
patchPreviews . appendChild ( createElementClass ( 'li' , null , patchName ) ) ;
2021-02-23 14:34:40 +01:00
}
2022-05-28 05:48:36 +02:00
listItem . appendChild ( patchPreviews ) ;
2021-02-23 14:34:40 +01:00
2022-05-28 05:48:36 +02:00
supportedDlls . appendChild ( listItem ) ;
2019-11-17 11:58:28 +01:00
}
2017-12-16 06:58:59 +01:00
2022-05-28 05:48:36 +02:00
[ "dragover" , "dragenter" ] . forEach ( function ( n ) {
document . documentElement . addEventListener ( n , function ( e ) {
container . classList . add ( "dragover" ) ;
2019-11-17 11:58:28 +01:00
e . preventDefault ( ) ;
2020-05-01 03:02:15 +02:00
e . stopPropagation ( ) ;
2019-11-17 11:58:28 +01:00
} ) ;
2022-05-28 05:48:36 +02:00
} ) ;
[ "dragleave" , "dragend" , "drop" ] . forEach ( function ( n ) {
document . documentElement . addEventListener ( n , function ( e ) {
container . classList . remove ( "dragover" ) ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
} ) ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
container . addEventListener ( "drop" , function ( e ) {
var files = e . dataTransfer . files ;
2019-11-17 11:58:28 +01:00
if ( files && files . length > 0 )
2018-06-23 05:59:16 +02:00
self . loadFile ( files [ 0 ] ) ;
2019-11-17 11:58:28 +01:00
} ) ;
2018-06-23 05:59:16 +02:00
2021-02-23 13:45:16 +01:00
var filepickerId = createID ( ) ;
2022-05-28 05:48:36 +02: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 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
this . fileInput . addEventListener ( "change" , function ( e ) {
2019-11-17 11:58:28 +01:00
if ( this . files && this . files . length > 0 )
2018-06-23 05:59:16 +02:00
self . loadFile ( this . files [ 0 ] ) ;
} ) ;
2022-05-28 05:48:36 +02:00
this . successDiv = createElementClass ( 'div' , 'success' ) ;
this . errorDiv = createElementClass ( 'div' , 'error' ) ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
container . appendChild ( this . fileInput ) ;
container . appendChild ( label ) ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02: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 05:59:16 +02:00
}
2019-11-17 11:58:28 +01:00
loadFile ( file ) {
var reader = new FileReader ( ) ;
var self = this ;
2018-06-23 05:59:16 +02:00
2019-11-17 11:58:28 +01:00
reader . onload = function ( e ) {
var found = false ;
// clear logs
2022-05-28 05:48:36 +02:00
self . errorDiv . textContent = '' ;
self . successDiv . textContent = '' ;
2019-11-17 11:58:28 +01:00
for ( var i = 0 ; i < self . patchers . length ; i ++ ) {
2021-03-26 11:53:45 +01:00
// reset text and buttons
2022-05-28 05:48:36 +02:00
self . forceLoadButtons [ i ] . style . display = 'none' ;
self . forceLoadTexts [ i ] . textContent = '' ;
self . matchSuccessText [ i ] . textContent = '' ;
2019-11-17 11:58:28 +01: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 05:48:36 +02:00
patcher . container . style . display = 'none' ;
2019-11-17 11:58:28 +01:00
patcher . loadBuffer ( e . target . result ) ;
if ( patcher . validatePatches ( ) ) {
found = true ;
loadPatch ( this , self , patcher ) ;
2021-03-26 11:53:45 +01:00
// show patches matched for 100% - helps identify which version is loaded
var valid = patcher . validPatches ;
2022-05-28 05:48:36 +02:00
self . matchSuccessText [ i ] . textContent = ' ' + valid + ' of ' + valid + ' patches matched (100%) ' ;
2019-11-17 11:58:28 +01:00
}
}
2018-06-23 05:59:16 +02:00
2019-11-17 11:58:28 +01:00
if ( ! found ) {
// let the user force a match
2022-05-28 05:48:36 +02:00
for ( let i = 0 ; i < self . patchers . length ; i ++ ) {
const patcher = self . patchers [ i ] ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
const valid = patcher . validPatches ;
const percent = ( valid / patcher . totalPatches * 100 ) . toFixed ( 1 ) ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02: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 11:58:28 +01:00
// reset old text
for ( var j = 0 ; j < self . patchers . length ; j ++ ) {
2022-05-28 05:48:36 +02:00
self . forceLoadButtons [ j ] . style . display = 'none' ;
self . forceLoadTexts [ j ] . textContent = '' ;
2019-11-17 11:58:28 +01:00
}
loadPatch ( this , self , self . patchers [ i ] ) ;
2022-05-28 05:48:36 +02:00
} . bind ( this , i ) ;
2019-11-17 11:58:28 +01:00
}
2022-05-28 05:48:36 +02:00
self . errorDiv . innerHTML = "No patch set was a 100% match." ;
2019-11-17 11:58:28 +01:00
}
} ;
reader . readAsArrayBuffer ( file ) ;
2018-06-23 05:59:16 +02:00
}
2017-02-08 07:22:19 +01:00
}
2017-01-28 05:41:42 +01:00
2019-11-17 11:58:28 +01: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 23:37:14 +02:00
if ( mod . type === "number" ) {
this . mods . push ( new NumberPatch ( mod ) ) ;
}
2021-09-20 13:26:04 +02:00
if ( mod . type === "dynamic" ) {
this . mods . push ( new DynamicPatch ( mod ) ) ;
}
2023-06-19 05:44:51 +02:00
if ( mod . type === "hex" ) {
this . mods . push ( new HexPatch ( mod ) ) ;
}
2019-11-17 11:58:28 +01:00
} else { // standard patch
this . mods . push ( new StandardPatch ( mod ) ) ;
}
}
2017-12-16 06:58:59 +01:00
2019-11-17 11:58:28 +01:00
this . filename = fname ;
this . description = description ;
this . multiPatcher = true ;
2017-01-28 05:41:42 +01:00
2019-11-17 11:58:28 +01: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 05:41:42 +01:00
2019-11-17 11:58:28 +01:00
createUI ( ) {
var self = this ;
2022-05-28 05:48:36 +02:00
this . container = createElementClass ( 'div' , 'patchContainer' ) ;
2019-11-17 11:58:28 +01:00
var header = this . filename ;
if ( this . description === "string" ) {
header += ' (' + this . description + ')' ;
}
2022-05-28 05:48:36 +02:00
this . container . innerHTML = '<h3>' + header + '</h3>' ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
this . successDiv = createElementClass ( 'div' , 'success' ) ;
this . errorDiv = createElementClass ( 'div' , 'error' ) ;
this . patchDiv = createElementClass ( 'div' , 'patches' ) ;
2019-11-17 11:58:28 +01:00
2022-05-28 05:48:36 +02:00
var saveButton = document . createElement ( 'button' ) ;
saveButton . disabled = true ;
saveButton . textContent = 'Load file First' ;
saveButton . addEventListener ( 'click' , this . saveDll . bind ( this ) ) ;
2019-11-17 11:58:28 +01:00
this . saveButton = saveButton ;
if ( ! this . multiPatcher ) {
2022-05-28 05:48:36 +02: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 11:58:28 +01:00
} ) ;
2022-05-28 05:48:36 +02:00
this . container . addEventListener ( 'drop' , function ( e ) {
var files = e . dataTransfer . files ;
2019-11-17 11:58:28 +01:00
if ( files && files . length > 0 )
self . loadFile ( files [ 0 ] ) ;
} ) ;
2021-02-23 13:45:16 +01:00
var filepickerId = createID ( ) ;
2022-05-28 05:48:36 +02: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 11:58:28 +01:00
if ( this . files && this . files . length > 0 )
self . loadFile ( this . files [ 0 ] ) ;
} ) ;
2022-05-28 05:48:36 +02:00
this . container . appendChild ( this . fileInput ) ;
this . container . appendChild ( label ) ;
2019-11-17 11:58:28 +01:00
}
2017-12-16 06:58:59 +01:00
2022-05-28 05:48:36 +02: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 05:32:53 +01:00
}
2017-12-16 06:58:59 +01:00
2019-11-17 11:58:28 +01:00
destroyUI ( ) {
if ( this . hasOwnProperty ( "container" ) )
this . container . remove ( ) ;
}
2017-01-28 05:41:42 +01:00
2019-11-17 11:58:28 +01:00
loadBuffer ( buffer ) {
this . dllFile = new Uint8Array ( buffer ) ;
if ( this . validatePatches ( ) ) {
2022-05-28 05:48:36 +02:00
this . successDiv . classList . remove ( "hidden" ) ;
this . successDiv . innerHTML = "File loaded successfully!" ;
2019-11-17 11:58:28 +01:00
} else {
2022-05-28 05:48:36 +02:00
this . successDiv . classList . add ( "hidden" ) ;
2019-11-17 11:58:28 +01:00
}
// Update save button regardless
2022-05-28 05:48:36 +02:00
this . saveButton . disabled = false ;
this . saveButton . textContent = 'Save Patched File' ;
this . errorDiv . innerHTML = this . errorLog ;
2017-01-28 05:41:42 +01:00
}
2019-11-17 11:58:28 +01: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 05:41:42 +01:00
}
2022-05-28 05:48:36 +02: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 11:58:28 +01: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 05:41:42 +01:00
}
2019-11-17 11:58:28 +01:00
var blob = new Blob ( [ this . dllFile ] , { type : "application/octet-stream" } ) ;
2022-05-28 05:48:36 +02:00
var uri = URL . createObjectURL ( blob ) ;
this . downloadURI ( uri , this . filename ) ;
URL . revokeObjectURL ( uri ) ;
2017-01-28 05:41:42 +01:00
}
2019-11-17 11:58:28 +01:00
loadPatchUI ( ) {
for ( var i = 0 ; i < this . mods . length ; i ++ ) {
this . mods [ i ] . createUI ( this . patchDiv ) ;
}
2017-01-28 05:41:42 +01:00
}
2019-11-17 11:58:28 +01:00
updatePatchUI ( ) {
for ( var i = 0 ; i < this . mods . length ; i ++ ) {
this . mods [ i ] . updateUI ( this . dllFile ) ;
}
2017-01-28 05:41:42 +01:00
}
2019-11-17 11:58:28 +01: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 05:41:42 +01:00
}
}
2019-11-17 11:58:28 +01:00
window . Patcher = Patcher ;
window . PatchContainer = PatchContainer ;
2017-02-08 07:22:19 +01:00
2024-07-04 12:09:54 +02:00
} ) ( window , document ) ;