0
0
mirror of https://github.com/marcrobledo/savegame-editors.git synced 2025-04-28 09:05:10 +00:00

TOTK: updated master editor with thousands of hashes!

This commit is contained in:
Marc Robledo 2023-06-13 00:14:10 +02:00
parent 5677a48e62
commit babb490b20
6 changed files with 30967 additions and 531 deletions

View File

@ -514,15 +514,18 @@
<div class="mb-10">
<label><span data-translate="Search">Search</span> <input id="input-custom-filter" type="text" list="knownHashes" placeholder="Search variables" onchange="TOTKMasterEditor.refreshResults()" /></label>
<datalist id="knownHashes">
<option value="paraglider fabric">Usable paraglider fabrics</option>
<option value="HorseList">Horses</option>
<option value="AutoBuilder.">Autobuilder</option>
<option value="OwnedParasailPattern.">Usable paraglider fabrics</option>
<option value="tower">Hyrule tower activation</option>
<option value="dragon coordinates">Dragon coordinates</option>
<option value="korok found">Korok found</option>
<option value="korok carried">Korok carried</option>
<option value="shrine">Shrines</option>
<option value="lightroot">Lighroots</option>
<option value="compendium">Compendium status</option>
<option value="Dragon_">Dragon coordinates</option>
<option value="IsAppearKorok.">Korok found</option>
<option value="KorokCarryProgress.">Korok carried</option>
<option value="DungeonState.Dungeon">Shrines</option>
<option value="ArrivalPointState.">Lighroots</option>
<option value="PictureBookData.">Compendium status</option>
<option value="event">Quests</option>
<option value="amiibo">Amiibo</option>
</datalist>
</div>

View File

@ -1,5 +1,5 @@
/*
The legend of Zelda: Tears of the Kingdom Savegame Editor (Item class) v20230608
The legend of Zelda: Tears of the Kingdom Savegame Editor (Item class) v20230612
by Marc Robledo 2023
item names compiled by Echocolat, Exincracci, HylianLZ and Karlos007
@ -267,17 +267,15 @@ Item.fixKeyAvailabilityFlags=function(){
});
if(changes){
//console.warn(changes+' paraglider fabric availability flags have been fixed');
MarcDialogs.alert(changes+' paraglider fabric availability flags have been fixed');
if(currentTab==='master' && TOTKMasterEditor.isLoaded()){
MarcDialogs.alert(changes+' some key flags have been fixed');
if(currentTab==='master' && TOTKMasterEditor.isLoaded())
TOTKMasterEditor.refreshResults();
}
}
return changes;
}
Item.AvailabilityFlags={
/* paraglider fabrics */
/* paraglider fabrics (OwnedParasailPattern.*) */
Obj_SubstituteCloth_Default:0x7ff848d1,
Obj_SubstituteCloth_00:0xb65bc9d7,
Obj_SubstituteCloth_01:0x41929f49,
@ -330,9 +328,24 @@ Item.AvailabilityFlags={
Obj_SubstituteCloth_52:0xeaae73a3,
Obj_SubstituteCloth_53:0xa5ecfeec,
Obj_SubstituteCloth_55:0x93e7260f,
Obj_SubstituteCloth_56:0x6688319b
}
Obj_SubstituteCloth_56:0x6688319b,
/* horse reins (OwnedCustomizableHorseTack_Reins.*) */
GameRomHorseReins_01:0x4a5fd1ed, //Traveler's Bridle
GameRomHorseReins_02:0x2ab85fc5, //Royal Reins
GameRomHorseReins_03:0x7ed8afec, //Knight's Bridle
GameRomHorseReins_04:0x02b23771, //Monster Bridle
GameRomHorseReins_05:0xeee51703, //Extravagant Bridle
/* horse saddles (OwnedCustomizableHorseTack_Saddle.*) */
GameRomHorseSaddle_01:0xac64346a, //Traveler's Saddle
GameRomHorseSaddle_02:0x5e6d9a72, //Royal Saddle
GameRomHorseSaddle_03:0x7a50263f, //Knight's Saddle
GameRomHorseSaddle_04:0x38033224, //Monster Saddle
GameRomHorseSaddle_05:0xe9220149, //Extravagant Saddle
GameRomHorseSaddle_07:0x9dda81a1 //Towing Harness
//GameRomHorseSaddle_07:0xecbbc704, //GameRomHorseSaddle_07_ExternalCoupler
//GameRomHorseSaddle_07:0x663f0238 //GameRomHorseSaddle_07_WithWagon
}

View File

@ -1,4 +1,4 @@
/* Zelda TOTK Savegame editor by Marc Robledo v20230608 */
/* Zelda TOTK Savegame editor by Marc Robledo v20230612 */
/* minify at https://cssminifier.com/ + https://www.base64-image.de/ (sprites */
/* @FONT-FACES */
@ -151,7 +151,7 @@ tr:hover{background-color:#121710}
/* useful classes */
.help:hover{cursor:help}
.hidden{display:none}
.mono{}
.mono{font-family:monospace;}
.clickable{cursor:pointer} /* also Safari iOS fix for clickable elements */
.text-ellipsis{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.text-left{text-align:left}

File diff suppressed because it is too large Load Diff

View File

@ -1047,6 +1047,9 @@ SavegameEditor={
/* save function */
save:function(){
if(currentTab==='master')
return false;
/* STATS */
this.writeU32('PlayerStatus.CurrentRupee', null, getValue('rupees'));
/*this.writeU32('Mons', getValue('mons'));*/

View File

@ -1,65 +1,35 @@
/*
The legend of Zelda: Tears of the Kingdom - Master editor v20230608
by Marc Robledo 2023
The legend of Zelda: Tears of the Kingdom - Master editor v20230612
by Marc Robledo 2023
thanks to the immeasurable work, hash crack and research of MacSpazzy, MrCheeze and Karlos007
*/
var TOTKMasterEditor=(function(){
const HASHES_PER_PAGE=100;
const buildEnumOptions=function(value){
return {value:murmurHash3.x86.hash32(value), name:value};
};
const COMMON_ENUMS={
ShrineStatus:['Hidden','Appear','Open','Enter','Clear'],
ShrineCrystalStatus:['Hidden','Point','Unlock','Open','Enter','Clear','UnlockToOpen','PointAndActiveWarp','ChangeToKeyStone','PresentedKeyCrystal'],
LightrootStatus:['Close','Open'],
KorokCarry:['NotClear','Clear'],
CompendiumPhotos:['Unopened','TakePhoto','Buy'],
HorseListBodyEyeColor:['Black','Blue'],
HorseListBodyPattern:['00','01','02','03','04','05','06'],
HorseListMane:['None','Horse_Link_Mane','Horse_Link_Mane_01','Horse_Link_Mane_02','Horse_Link_Mane_03','Horse_Link_Mane_04','Horse_Link_Mane_05','Horse_Link_Mane_06','Horse_Link_Mane_07','Horse_Link_Mane_08','Horse_Link_Mane_09','Horse_Link_Mane_00L','Horse_Link_Mane_00S','Horse_Link_Mane_10','Horse_Link_Mane_11','Horse_Link_Mane_12','Horse_Link_Mane_01L'],
HorseListRein:['None','GameRomHorseReins_00','GameRomHorseReins_01','GameRomHorseReins_02','GameRomHorseReins_03','GameRomHorseReins_04','GameRomHorseReins_05','GameRomHorseReins_06','GameRomHorseReins_00L','GameRomHorseReins_00S'],
HorseListSaddle:['None','GameRomHorseSaddle_00','GameRomHorseSaddle_01','GameRomHorseSaddle_02','GameRomHorseSaddle_03','GameRomHorseSaddle_04','GameRomHorseSaddle_05','GameRomHorseSaddle_06','GameRomHorseSaddle_00L','GameRomHorseSaddle_00S','GameRomHorseSaddle_07']
};
var loaded=false;
var hashes=[];
var allHashes=[];
var filteredHashes;
const BOOL=1;
//const BOOL_ARRAY=2;
const S32=3;
//const S32_ARRAY=4;
//const F32=5;
//const F32_ARRAY=6;
//const VECTOR2F=7;
//const VECTOR2F_ARRAY=8;
const VECTOR3F=9;
//const VECTOR3F_ARRAY=10;
//const VECTOR4F=11;
//const VECTOR4F_ARRAY=12;
//const STRING=13;
//const STRING_ARRAY=14;
//const STRING64=15;
//const STRING64_ARRAY=16;
//const STRING256=17;
//const STRING256_ARRAY=18;
var _addCustomHashesBoolean=function(customHashes, label){
for(var i=0; i<customHashes.length; i++){
hashes.push({
hash:customHashes[i],
hashHex:customHashes[i].toString(16),
type:BOOL,
id: label+' ['+i+']',
enumValues:null
})
allHashes.push(customHashes[i]);
}
};
var _addCustomHashesEnum=function(customHashes, label, options){
options=options.map(function(value){
return {value:murmurHash3.x86.hash32(value), name:value};
});
for(var i=0; i<customHashes.length; i++){
hashes.push({
hash:customHashes[i],
hashHex:customHashes[i].toString(16),
type:S32,
id: label+' ['+i+']',
enumValues:options
})
allHashes.push(customHashes[i]);
}
};
var _parseHashFile=function(responseText){
var lines=responseText.split('\n');
@ -68,125 +38,265 @@ var TOTKMasterEditor=(function(){
var data=lines[i].split(';');
var hashInt=parseInt(data[0], 16);
var options=null;
if(data[3]){
options=data[3].split(',').map(function(value){
return {value:murmurHash3.x86.hash32(value), name:value};
});
if(data[1]==='Enum' || data[1]==='EnumArray'){
if(data[3]){
options=data[3].split(',').map(buildEnumOptions);
}else if(/^KeyCrystalDungeonState\.Dungeon/.test(data[2])){
options=COMMON_ENUMS.ShrineCrystalStatus;
}else if(/^DungeonState\.Dungeon/.test(data[2])){
options=COMMON_ENUMS.ShrineStatus;
}else if(/^ArrivalPointState\.CheckPoint/.test(data[2])){
options=COMMON_ENUMS.LightrootStatus;
}else if(/^KorokCarryProgress\./.test(data[2])){
options=COMMON_ENUMS.KorokCarry;
}else if(/^PictureBookData\.(.*?)\.State/.test(data[2])){
options=COMMON_ENUMS.CompendiumPhotos;
}else if(/HorseList\.Body\.EyeColor/.test(data[2])){
options=COMMON_ENUMS.HorseListBodyEyeColor;
}else if(/HorseList\.Body\.Pattern/.test(data[2])){
options=COMMON_ENUMS.HorseListBodyPattern;
}else if(/HorseList\.Mane/.test(data[2])){
options=COMMON_ENUMS.HorseListMane;
}else if(/HorseList\.Rein/.test(data[2])){
options=COMMON_ENUMS.HorseListRein;
}else if(/HorseList\.Saddle/.test(data[2])){
options=COMMON_ENUMS.HorseListSaddle;
}
}
if(data[2]==='Unknown')
data[2]+=' <span class="mono">'+data[0]+'</span>';
hashes.push({
hash:hashInt,
hashHex:data[0],
type:parseInt(data[1]),
type:data[1],
id:data[2],
enumValues:options
});
allHashes.push(hashInt);
}
}
_addCustomHashesBoolean(CompletismHashes.SHRINES_FOUND, 'Shrine location found');
_addCustomHashesEnum(CompletismHashes.SHRINES_STATUS, 'Shrine status', ['Hidden','Appear','Open','Enter','Clear']);
_addCustomHashesBoolean(CompletismHashes.LIGHTROOTS_FOUND, 'Lightroot location found');
_addCustomHashesEnum(CompletismHashes.LIGHTROOTS_STATUS, 'Lightroot status', ['Close','Open']);
_addCustomHashesBoolean(CompletismHashes.KOROKS_HIDDEN, 'Korok found');
_addCustomHashesEnum(CompletismHashes.KOROKS_CARRY, 'Korok carried', ['NotClear','Clear']);
_addCustomHashesBoolean(CompletismHashes.LOCATION_CAVES_VISITED, 'IsVisitLocation.Cave_*');
_addCustomHashesBoolean(CompletismHashes.LOCATION_WELLS_VISITED, 'IsVisitLocation.Well_*');
_addCustomHashesBoolean(CompletismHashes.LOCATION_WELLS_VISITED2, 'IsVisitLocationArea_CaveEntrance.*');
_addCustomHashesEnum(CompletismHashes.COMPENDIUM_STATUS, 'Compendium picture status', ['Unopened','TakePhoto','Buy']);
};
var _setBoolean=function(){
tempFile.writeU32(this.offset, this.checked? 1: 0);
}
var _setS32=function(){
var _setU32=function(){
tempFile.writeU32(this.offset, parseInt(this.value));
}
var _setS32=function(){
tempFile.writeS32(this.offset, parseInt(this.value));
}
var _setF32=function(){
tempFile.writeF32(this.offset, parseFloat(this.value));
}
var _setString64=function(){
tempFile.writeString(this.offset, this.value, 0x40);
}
var _setWString16=function(){
var bytes=new Array(0x20);
for(var i=0; i<this.value.length; i++){
var charCode=this.value.charCodeAt(i);
bytes[i*2 + 0]=charCode & 0xff;
bytes[i*2 + 1]=charCode >>> 8;
}
for(i=i*2; i<bytes.length; i++){
bytes[i]=0;
}
tempFile.writeBytes(this.offset, bytes);
}
var _setF32Negative=function(){
var val=parseFloat(this.value);
tempFile.writeF32(this.offset, val? -val : 0); //just in case, avoid storing -0
}
var _addEditorRow=function(container, left, right1, right2, right3){
var _createHashInputRow=function(container, hash, arrayIndex){
var tr=document.createElement('tr');
tr.appendChild(document.createElement('td'));
tr.appendChild(document.createElement('td'));
tr.children[1].className='text-right';
if(right1){
tr.children[0].appendChild(label(right1.id, left));
tr.children[1].appendChild(right1);
if(right2)
tr.children[1].appendChild(right2);
if(right3)
tr.children[1].appendChild(right3);
}else{
tr.children[0].innerHTML=left;
}
container.appendChild(tr);
}
var _createHashInput=function(container, hash){
if(hash.type===BOOL){
var c=checkbox(hash.hashHex);
c.offset=hash.offset;
c.addEventListener('change', _setBoolean);
if(tempFile.readU32(hash.offset))
c.checked=true;
_addEditorRow(container, hash.id, c);
}else if(hash.type===S32){
if(hash.enumValues){
var s=select(hash.hashHex, hash.enumValues, null, tempFile.readU32(hash.offset));
s.offset=hash.offset;
s.addEventListener('change', _setS32);
_addEditorRow(container, hash.id, s);
}else{
var inp=inputNumber(hash.hashHex, -2147483648, 2147483647, tempFile.readU32(hash.offset));
inp.className='text-right';
inp.offset=hash.offset;
inp.addEventListener('change', _setS32);
_addEditorRow(container, hash.id, inp);
var offset=hash.offset;
if(!offset)
return;
var hashType=hash.type;
if(/String|Vector|Array/.test(hashType)){
offset=tempFile.readU32(hash.offset);
if(/Array$/.test(hashType)){
if(typeof arrayIndex==='number'){
offset+=0x04;
if(/Vector2/.test(hashType)){
offset+=arrayIndex*0x08;
}else if(/Vector3/.test(hashType)){
offset+=arrayIndex*0x0c;
}else if(/String64/.test(hashType)){
offset+=arrayIndex*0x40;
}else if(/WString16/.test(hashType)){
offset+=arrayIndex*0x20;
}else{
offset+=arrayIndex*0x04;
}
hashType=hashType.replace(/(ean)?Array$/, '');
}else{
var len=tempFile.readU32(offset);
for(var i=0; i<len; i++){
_createHashInputRow(container, hash, i);
}
return;
}
}
}else if(hash.type===VECTOR3F){
var metadataOffset=tempFile.readU32(hash.offset);
var inputX=inputFloat(hash.hashHex+'x', -10000, 10000, tempFile.readF32(metadataOffset));
}
var fieldId=hash.hashHex+(typeof arrayIndex==='number'? '_'+arrayIndex:'');
tr.children[0].appendChild(label(fieldId, hash.id+(typeof arrayIndex==='number'? ' ['+arrayIndex+']':'')));
tr.children[1].className='text-right';
if(hashType==='Bool'){
var field=checkbox(fieldId);
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setBoolean);
if(tempFile.readU32(offset))
field.checked=true;
tr.children[1].appendChild(field);
}else if(hashType==='Int' || hashType==='UInt'){
var field;
if(hashType==='UInt'){
field=inputNumber(fieldId, 0, 4294967295, tempFile.readU32(offset));
field.className='text-right';
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setU32);
}else{
field=inputNumber(fieldId, -2147483648, 2147483647, tempFile.readU32(offset));
field.className='text-right';
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setS32);
}
tr.children[1].appendChild(field);
}else if(hashType==='Enum' && hash.enumValues){
var field=select(fieldId, hash.enumValues, null, tempFile.readU32(offset));
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setU32);
tr.children[1].appendChild(field);
}else if(hashType==='Float'){
var field=inputFloat(fieldId, -2147483648, 2147483647, tempFile.readF32(offset));
field.className='text-right';
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setF32);
tr.children[1].appendChild(field);
}else if(hashType==='Vector2'){
var inputX=inputFloat(fieldId, -2147483648, 2147483647, tempFile.readF32(offset));
inputX.className='text-right';
inputX.style='width:160px';
inputX.offset=metadataOffset;
inputX.offset=offset;
inputX.arrayIndex=arrayIndex;
inputX.addEventListener('change', _setF32);
inputX.title='X';
var inputY=inputFloat(hash.hashHex+'y', -10000, 10000, -tempFile.readF32(metadataOffset+8));
var inputY=inputFloat(fieldId+'y', -2147483648, 2147483647, tempFile.readF32(offset+8));
inputY.className='text-right';
inputY.style='width:160px';
inputY.offset=metadataOffset+8;
inputY.offset=offset+8;
inputY.arrayIndex=arrayIndex;
inputY.addEventListener('change', _setF32);
inputY.title='Y';
tr.children[1].appendChild(inputX);
tr.children[1].appendChild(inputY);
}else if(hashType==='Vector3'){
var inputX=inputFloat(fieldId, -2147483648, 2147483647, tempFile.readF32(offset));
inputX.className='text-right';
inputX.style='width:160px';
inputX.offset=offset;
inputX.arrayIndex=arrayIndex;
inputX.addEventListener('change', _setF32);
inputX.title='X';
var inputY=inputFloat(fieldId+'y', -2147483648, 2147483647, -tempFile.readF32(offset+8));
inputY.className='text-right';
inputY.style='width:160px';
inputY.offset=offset+8;
inputY.arrayIndex=arrayIndex;
inputY.addEventListener('change', _setF32Negative);
inputY.title='Y';
var inputZ=inputFloat(hash.hashHex+'z', -10000, 10000, tempFile.readF32(metadataOffset+4));
var inputZ=inputFloat(fieldId+'z', -2147483648, 2147483647, tempFile.readF32(offset+4));
inputZ.className='text-right';
inputZ.style='width:160px';
inputZ.offset=metadataOffset+4;
inputZ.offset=offset+4;
inputZ.arrayIndex=arrayIndex;
inputZ.addEventListener('change', _setF32);
inputZ.title='Z';
_addEditorRow(container, hash.id, inputX, inputY, inputZ);
tr.children[1].appendChild(inputX);
tr.children[1].appendChild(inputY);
tr.children[1].appendChild(inputZ);
}else if(hashType==='String64'){
var field=input(fieldId, tempFile.readString(offset, 0x40));
field.className='text-right';
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setString64);
tr.children[1].appendChild(field);
}else if(hashType==='WString16'){
var str='';
for(var i=0; i<0x20; i+=2){
var charCode=tempFile.readU16(offset+i);
if(!charCode)
break;
str+=String.fromCharCode(charCode);
}
var field=input(fieldId, str.replace(/\u0000+$/,''));
field.className='text-right';
field.offset=offset;
field.arrayIndex=arrayIndex;
field.addEventListener('change', _setWString16);
tr.children[1].appendChild(field);
}else{
_addEditorRow(container, hash.id+' (unknown)');
var span=document.createElement('span');
span.innerHTML='Type: '+hashType;
tr.children[1].appendChild(span);
}
container.appendChild(tr);
}
return{
isLoaded:function(){
return loaded;
},
findOffsets:function(){
//var debugText=''; //remove not found hashes
var offsets=SavegameEditor._getOffsetsByHashes(allHashes);
for(var i=0; i<hashes.length; i++){
hashes[i].offset=offsets[hashes[i].hash];
/*if(hashes[i].offset){
debugText+=hashes[i].hashHex+';'+hashes[i].type+';'+hashes[i].id;
if(hashes[i].enumValues)
debugText+=';'+hashes[i].enumValues.map(function(a){return a.name}).join(',');
debugText+='\n<br/>';
}*/
}
this.forceFindOffsets=false;
//document.body.innerHTML=debugText;
},
focus:function(){
this.refreshResults();
@ -200,6 +310,10 @@ var TOTKMasterEditor=(function(){
.then(res => res.text()) // Gets the response and returns it as a blob
.then(responseText => {
loaded=true;
for(var field in COMMON_ENUMS){
COMMON_ENUMS[field]=COMMON_ENUMS[field].map(buildEnumOptions);
}
_parseHashFile(responseText);
TOTKMasterEditor.findOffsets();
@ -251,11 +365,7 @@ var TOTKMasterEditor=(function(){
for(var i=0; i<paginatedHashes.length; i++){
var hash=paginatedHashes[i];
if(hash.type){
_createHashInput(container, hash)
}else{
_addEditorRow(container, hash.hash);
}
_createHashInputRow(container, hash, null);
}
get('table').appendChild(container);
}