/* The legend of Zelda: Tears of the Kingdom savegame editor (last update 2023-07-29) by Marc Robledo 2023 */ var currentEditingItem; SavegameEditor={ Name:'The legend of Zelda: Tears of the Kingdom', Filename:['progress.sav','caption.sav'], Version:20230729, /* Settings */ Settings:{ lang:'en' }, /* Constants */ Constants:{ GAME_VERSIONS:[ {version:'v1.0', fileSize:2307552, header:0x0046c3c8, metaDataStart:0x0003c050}, {version:'v1.1.x/v1.2.0', fileSize:2307656, header:0x0047e0f4, metaDataStart:0x0003c088} ] }, /* Hashes */ Hashes:[ 0xfbe01da1, 'PlayerStatus.MaxLife', false, 0xa77921d7, 'PlayerStatus.CurrentRupee', false, //0x31ab5580, 'PlayerStatus.Life', 0xf9212c74, 'PlayerStatus.MaxStamina', false, 0x15ec5858, 'HorseInnMemberPoint', false, 0xe573f564, 'Playtime', false, //unknown key 0xafd01d68, 'PlayerStatus.MaxEnergy', false, 0xc884818d, 'PlayerStatus.SavePos', true, //Vector3F 0x1d6189da, 'Sequence_CurrentBanc', true, //String64 0xd7a3f6ba, 'Pouch.Weapon.ValidNum', true, 0xc61785c2, 'Pouch.Bow.ValidNum', true, 0x05271e7d, 'Pouch.Shield.ValidNum', true, 0x14d7f4c4, 'MapData.IconData.StampData.Type', true, 0xf24fc2e7, 'MapData.IconData.StampData.Pos', true, 0xd2025694, 'MapData.IconData.StampData.Layer', true, 0xd27f8651, 'AutoBuilder.Draft.Content.Index', true, //S32, array length=30 0xa56722b6, 'AutoBuilder.Draft.Content.CombinedActorInfo', true, //binary data (size=6688) 0xc5bf2815, 'AutoBuilder.Draft.Content.CameraPos', true, //Vector3F 0xef74dca7, 'AutoBuilder.Draft.Content.CameraAt', true, //Vector3F 0x67f4b46b, 'AutoBuilder.Draft.Content.IsFavorite', true //S32 ], /* read/write data */ readU32:function(hashKey, arrayIndex){ if(typeof arrayIndex==='number') return tempFile.readU32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x04); return tempFile.readU32(SavegameEditor.Offsets[hashKey]); }, readS32:function(hashKey, arrayIndex){ if(typeof arrayIndex==='number') return tempFile.readS32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x04); return tempFile.readS32(SavegameEditor.Offsets[hashKey]); }, readF32:function(hashKey, arrayIndex){ if(typeof arrayIndex==='number') return tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x04); return tempFile.readF32(SavegameEditor.Offsets[hashKey]); }, readVector2F:function(hashKey, arrayIndex){ if(typeof arrayIndex==='number'){ return { x: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x08), y: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x08 + 0x04) } } return { x: tempFile.readF32(SavegameEditor.Offsets[hashKey]), y: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04) } }, readVector3F:function(hashKey, arrayIndex){ if(typeof arrayIndex==='number'){ return { x: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x0c), y: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x0c + 0x04), z: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x0c + 0x08) } } return { x: tempFile.readF32(SavegameEditor.Offsets[hashKey]), y: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x04), z: tempFile.readF32(SavegameEditor.Offsets[hashKey] + 0x08) } }, readString64:function(hashKey, arrayIndex){ if(typeof arrayIndex==='number') return tempFile.readString(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x40, 0x40).replace(/\u0000+$/,''); return tempFile.readString(SavegameEditor.Offsets[hashKey], 0x40).replace(/\u0000+$/,''); }, readStringUTF8:function(hashKey, arrayIndex){ var offset=this.Offsets[hashKey]; if(typeof arrayIndex==='number') offset+=0x04 + arrayIndex*0x20; var str=''; for(var i=0; i<0x20; i+=2){ var charCode=tempFile.readU16(offset); if(!charCode) break; str+=String.fromCharCode(charCode); offset+=2; } return str.replace(/\u0000+$/,''); }, _readArray:function(hashKey, arrayIndex, callback){ var arraySize=SavegameEditor.readU32(hashKey); if(typeof arrayIndex==='number'){ if(arrayIndex>=0 && arrayIndex>> 8; } for(i=i*2; i b) return 1; else if(a < b) return -1; else return 0; }); var offset=this.guidsArrayOffset; for(var i=0; i0? quantity : 1; var foundItem=pouch.findItemById(itemId); if(foundItem){ foundItem.quantity+=quantity; Pouch.updateItemRow(foundItem); }else if(!pouch.isFull()){ var newItem=pouch.add({id:itemId, quantity:quantity}); document.getElementById('container-'+catId).appendChild(Pouch.updateItemRow(newItem)); } return quantity; } if(pouch.isFull()){ console.warn('not enough space in '+catId); return false; } var lastItem=pouch.getLast(); var itemListArray=this.getAvailableItems(catId); var newId; if(lastItem){ var nextIndexId=itemListArray.indexOf(lastItem.id)+1; if(nextIndexId===itemListArray.length) nextIndexId=0; newId=itemListArray[nextIndexId]; if(catId==='armors') while(Armor.INFO[newId].base !== newId){ newId=itemListArray[nextIndexId++]; } }else{ newId=itemListArray[0]; } var newItem; if(lastItem){ var newItemData=lastItem.export(); newItemData.id=newId; newItem=pouch.add(newItemData); }else{ newItem=pouch.add({id:newId}); } var row=Pouch.updateItemRow(newItem); document.getElementById('container-'+newItem.category).appendChild(row); Pouch.scrollToItem(newItem); if(catId==='arrows'){ var equipIndex=new Variable('Pouch.Arrow.EquipIndex', 'IntArray'); if(equipIndex.value[0]===-1){ equipIndex.value[0]=0; equipIndex.save(); UI.toast('Fixed arrows equip index'); } this.refreshAddArrowsButton(); } SavegameEditor.fixItemAvailabilityFlag(newItem); return true; }, fixItemAvailabilityFlag:function(item){ if(item.category==='key'){ var fixed=false; var variable; if(/^Obj_SubstituteCloth_/.test(item.id)){ variable=new Variable('OwnedParasailPattern.'+item.id.replace('Obj_SubstituteCloth_','Pattern').replace('PatternDefault','Default'), 'Bool'); }else if(/^GameRomHorseReins_/.test(item.id)){ variable=new Variable('OwnedCustomizableHorseTack_Reins.'+item.id, 'Bool'); }else if(/^GameRomHorseSaddle_/.test(item.id)){ variable=new Variable('OwnedCustomizableHorseTack_Saddle.'+item.id, 'Bool'); } if(variable && !variable.value){ variable.value=true; variable.save(); UI.toast(_('Fixed necessary usability flags for %s').replace('%s', ''+_(item.getItemTranslation())+''), 'flags-fixed'); } } }, getAvailableItems:function(catId){ if(catId==='weapons' || catId==='bows' || catId==='shields') return Equipment.AVAILABILITY[catId]; else if(catId==='armors') return Armor.AVAILABILITY; else if(catId==='arrows' || catId==='materials' || catId==='food' || catId==='devices' || catId==='key') return Item.AVAILABILITY[catId]; else if(catId==='horses') return Horse.AVAILABILITY; return null; }, editItem:function(item){ currentEditingItem=item; /* prepare edit item selector */ if(this.selectItem.lastCategory !== item.category){ this.selectItem.innerHTML=''; var itemList=this.getAvailableItems(item.category); for(var i=0; i z->y count++; if(count===limit) break; } } }else{ var offsets=this._getOffsetsByHashes(flags); for(var i=0; i z->y count++; if(count===limit) break; } } } if(count){ this.refreshCounterMapPins(); UI.toast(_('%s map pins added').replace('%s', ''+count+'')); }else{ UI.toast(_('No map pins added'), 'map-pins-none'); } return count; }, addPinsTowers:function(){ return this.addLocationPins(CompletismHashes.TOWERS_FOUND, Coordinates.TOWERS, MapPin.ICON_CRYSTAL, 15); }, addPinsShrines:function(){ return this.addLocationPins(CompletismHashes.SHRINES_FOUND, Coordinates.SHRINES, MapPin.ICON_CRYSTAL, 50); }, addPinsLightroots:function(){ return this.addLocationPins(CompletismHashes.LIGHTROOTS_FOUND, Coordinates.LIGHTROOTS, MapPin.ICON_CRYSTAL, 50); }, addPinsKoroksHidden:function(){ return this.addLocationPins(CompletismHashes.KOROKS_HIDDEN, Coordinates.KOROKS_HIDDEN, MapPin.ICON_LEAF, 50); }, addPinsKoroksCarry:function(){ return this.addLocationPins(CompletismHashes.KOROKS_CARRY, Coordinates.KOROKS_CARRY, MapPin.ICON_LEAF, 25, 'NotClear'); }, addPinsBubbuls:function(){ return this.addLocationPins(CompletismHashes.BUBBULS_GUIDS, Coordinates.LOCATION_BUBBULS, MapPin.ICON_HEART, 50); }, addPinsLocations:function(){ return this.addLocationPins(CompletismHashes.LOCATIONS_VISITED, Coordinates.LOCATIONS, MapPin.ICON_DIAMOND, 50); }, addPinsLocationsCaves:function(){ return this.addLocationPins(CompletismHashes.LOCATION_CAVES_VISITED2, Coordinates.LOCATION_CAVES, MapPin.ICON_DIAMOND, 50); }, addPinsLocationsWells:function(){ return this.addLocationPins(CompletismHashes.LOCATION_WELLS_VISITED2, Coordinates.LOCATION_WELLS, MapPin.ICON_DIAMOND, 25); }, addPinsLocationsChasms:function(){ return this.addLocationPins(CompletismHashes.LOCATION_CHASMS_VISITED2, Coordinates.LOCATION_CHASMS, MapPin.ICON_DIAMOND, 20); }, addPinsBossesHinox:function(){ return this.addLocationPins(CompletismHashes.BOSSES_HINOXES_DEFEATED, Coordinates.BOSSES_HINOXES, MapPin.ICON_SKULL, 25); }, addPinsBossesTalus:function(){ return this.addLocationPins(CompletismHashes.BOSSES_TALUSES_DEFEATED, Coordinates.BOSSES_TALUSES, MapPin.ICON_SKULL, 25); }, addPinsBossesMolduga:function(){ return this.addLocationPins(CompletismHashes.BOSSES_MOLDUGAS_DEFEATED, Coordinates.BOSSES_MOLDUGAS, MapPin.ICON_SKULL); }, addPinsBossesFlux:function(){ return this.addLocationPins(CompletismHashes.BOSSES_FLUX_CONSTRUCT_DEFEATED, Coordinates.BOSSES_FLUX_CONSTRUCT, MapPin.ICON_SKULL, 20); }, addPinsBossesFrox:function(){ return this.addLocationPins(CompletismHashes.BOSSES_FROXS_DEFEATED, Coordinates.BOSSES_FROXS, MapPin.ICON_SKULL, 20); }, addPinsBossesGleeok:function(){ return this.addLocationPins(CompletismHashes.BOSSES_GLEEOKS_DEFEATED, Coordinates.BOSSES_GLEEOKS, MapPin.ICON_SKULL, 5); }, addPinsSageWills:function(){ return this.addLocationPins(CompletismHashes.SAGE_WILLS_FOUND, Coordinates.SAGE_WILLS, MapPin.ICON_CHEST); }, addPinsOldMaps:function(){ return this.addLocationPins(CompletismHashes.TREASURE_MAPS_FOUND, Coordinates.TREASURE_MAPS, MapPin.ICON_CHEST); }, addPinsAddison:function(){ return this.addLocationPins(CompletismHashes.ADDISON_COMPLETED, Coordinates.ADDISON, MapPin.ICON_HUMAN, 25); }, addPinsSchematicsStone:function(){ return this.addLocationPins(CompletismHashes.SCHEMATICS_STONE_FOUND, Coordinates.SCHEMATICS_STONE, MapPin.ICON_CHEST); }, addPinsSchematicsYiga:function(){ return this.addLocationPins(CompletismHashes.SCHEMATICS_YIGA_FOUND, Coordinates.SCHEMATICS_YIGA, MapPin.ICON_CHEST); }, _refreshCounter:function(container, val, max){ setValue(container+'-counter', val+'/'+max+''); var percentage=((val/max) * 100); var progressBar=document.createElement('div'); progressBar.className='progress-bar'; progressBar.title=Math.floor(percentage)+'%'; var progress=document.createElement('div'); if(percentage===100) progress.className='progress complete'; else progress.className='progress'; progress.style.width=percentage+'%'; progressBar.appendChild(progress); getField(container+'-counter').appendChild(progressBar); }, refreshCounterMapPins:function(){ SavegameEditor._refreshCounter('pin', MapPin.count(SavegameEditor.currentItems.mapPins), MapPin.MAX); }, refreshCounterTowersFound:function(){ this._refreshCounter('towers-found', Completism.countTowersFound(), CompletismHashes.TOWERS_FOUND.length); }, refreshCounterTowersClear:function(){ this._refreshCounter('towers-clear', Completism.countTowersClear(), CompletismHashes.TOWERS_ACTIVATED.length); }, refreshCounterShrinesFound:function(){ this._refreshCounter('shrines-found', Completism.countShrinesFound(), CompletismHashes.SHRINES_FOUND.length); }, refreshCounterShrinesClear:function(){ this._refreshCounter('shrines-clear', Completism.countShrinesClear(), CompletismHashes.SHRINES_STATUS.length); }, refreshCounterLighrootsFound:function(){ this._refreshCounter('lightroots-found', Completism.countLightrootsFound(), CompletismHashes.LIGHTROOTS_FOUND.length); }, refreshCounterLighrootsClear:function(){ this._refreshCounter('lightroots-clear', Completism.countLightrootsClear(), CompletismHashes.LIGHTROOTS_STATUS.length); }, refreshCounterKoroksHidden:function(){ this._refreshCounter('korok-hidden', Completism.countKoroksHidden(), CompletismHashes.KOROKS_HIDDEN.length); }, refreshCounterKoroksCarry:function(){ this._refreshCounter('korok-carry', Completism.countKoroksCarry(), CompletismHashes.KOROKS_CARRY.length); }, refreshCounterBubbuls:function(){ this._refreshCounter('bubbuls', Completism.countBubbuls(), CompletismHashes.BUBBULS_DEFEATED.length); }, refreshCounterLocations:function(){ this._refreshCounter('locations', Completism.countLocations(), CompletismHashes.LOCATIONS_VISITED.length); }, refreshCounterLocationCaves:function(){ this._refreshCounter('location-caves', Completism.countLocationCaves(), CompletismHashes.LOCATION_CAVES_VISITED.length); }, refreshCounterLocationWells:function(){ this._refreshCounter('location-wells', Completism.countLocationWells(), CompletismHashes.LOCATION_WELLS_VISITED.length); }, refreshCounterLocationChasms:function(){ this._refreshCounter('location-chasms', Completism.countLocationChasms(), CompletismHashes.LOCATION_CHASMS_VISITED.length); }, refreshCounterBossesHinox:function(){ this._refreshCounter('boss-hinox', Completism.countBossesHinox(), CompletismHashes.BOSSES_HINOXES_DEFEATED.length); }, refreshCounterBossesTalus:function(){ this._refreshCounter('boss-talus', Completism.countBossesTalus(), CompletismHashes.BOSSES_TALUSES_DEFEATED.length); }, refreshCounterBossesMolduga:function(){ this._refreshCounter('boss-molduga', Completism.countBossesMolduga(), CompletismHashes.BOSSES_MOLDUGAS_DEFEATED.length); }, refreshCounterBossesFlux:function(){ this._refreshCounter('boss-flux', Completism.countBossesFlux(), CompletismHashes.BOSSES_FLUX_CONSTRUCT_DEFEATED.length); }, refreshCounterBossesFrox:function(){ this._refreshCounter('boss-frox', Completism.countBossesFrox(), CompletismHashes.BOSSES_FROXS_DEFEATED.length); }, refreshCounterBossesGleeok:function(){ this._refreshCounter('boss-gleeok', Completism.countBossesGleeok(), CompletismHashes.BOSSES_GLEEOKS_DEFEATED.length); }, refreshCounterSageWills:function(){ this._refreshCounter('sage-wills', Completism.countSageWills(), CompletismHashes.SAGE_WILLS_FOUND.length); }, refreshCounterOldMaps:function(){ this._refreshCounter('old-maps', Completism.countOldMaps(), CompletismHashes.TREASURE_MAPS_FOUND.length); }, refreshCounterAddison:function(){ this._refreshCounter('addison', Completism.countAddison(), CompletismHashes.ADDISON_COMPLETED.length); }, refreshCounterSchematicsStone:function(){ this._refreshCounter('schematics-stone', Completism.countSchematicsStone(), CompletismHashes.SCHEMATICS_STONE_FOUND.length); }, refreshCounterSchematicsYiga:function(){ this._refreshCounter('schematics-yiga', Completism.countSchematicsYiga(), CompletismHashes.SCHEMATICS_YIGA_FOUND.length); }, refreshCounterCompendium:function(){ this._refreshCounter('compendium', Completism.countCompendium(), CompletismHashes.COMPENDIUM_STATUS.length); }, refreshCounterPristineWeapons:function(){ this._refreshCounter('pristine-weapons', ExperienceCalculator.countPristineWeapons(), ExperienceCalculator.BROKEN_WEAPON_HASHES.length); }, refreshCounterAll:function(){ this.refreshCounterTowersFound(); this.refreshCounterTowersClear(); this.refreshCounterShrinesFound(); this.refreshCounterShrinesClear(); this.refreshCounterLighrootsFound(); this.refreshCounterLighrootsClear(); this.refreshCounterKoroksHidden(); this.refreshCounterKoroksCarry(); this.refreshCounterBubbuls(); this.refreshCounterLocations(); this.refreshCounterLocationCaves(); this.refreshCounterLocationWells(); this.refreshCounterLocationChasms(); this.refreshCounterBossesHinox(); this.refreshCounterBossesTalus(); this.refreshCounterBossesMolduga(); this.refreshCounterBossesFlux(); this.refreshCounterBossesFrox(); this.refreshCounterBossesGleeok(); this.refreshCounterSageWills(); this.refreshCounterOldMaps(); this.refreshCounterAddison(); this.refreshCounterSchematicsStone(); this.refreshCounterSchematicsYiga(); this.refreshCounterCompendium(); this.refreshCounterPristineWeapons(); this.refreshMissingPristineWeapons(); }, experienceCalculate:function(){ var totalExperience=ExperienceCalculator.calculate(); setValue('span-experience', totalExperience); document.getElementById('experience-enemy-tiers').innerHTML=''; ExperienceCalculator.getEnemyTiers(totalExperience).forEach(function(enemy, i){ var span=document.createElement('span'); span.innerHTML=_(enemy); span.className='text-center'; span.style.display='inline-block'; span.style.minWidth='33%'; document.getElementById('experience-enemy-tiers').appendChild(span); }); }, refreshMissingPristineWeapons:function(){ var missingPristineWeapons=ExperienceCalculator.getMissingPristineWeapons(); $('#experience-pristine-weapons').empty(); if(missingPristineWeapons.length){ missingPristineWeapons.forEach(function(weaponId){ var span=document.createElement('span'); span.innerHTML=_(weaponId); span.className='text-center'; span.style.display='inline-block'; span.style.minWidth='33%'; document.getElementById('experience-pristine-weapons').appendChild(span); }); }else{ $('
').addClass('text-center').html(_('None')).appendTo($('#experience-pristine-weapons')); } }, refreshItemTab:function(catId){ empty('container-'+catId); SavegameEditor.pouches[catId].items.forEach(function(item, j){ Pouch.updateItemRow(item); document.getElementById('container-'+item.category).appendChild(item._htmlRow); }); MarcTooltips.add('#container-'+catId+' select',{position:'left'}); MarcTooltips.add('#container-'+catId+' input',{position:'left',align:'center'}); }, /* check if savegame is valid */ checkValidSavegame:function(){ tempFile.littleEndian=true; //if(tempFile.fileName==='caption.sav'){ if(/caption/.test(tempFile.fileName)){ for(var i=0x000028; i<0x000001c0; i+=8){ var hash=tempFile.readU32(i); if(hash===0x63696a32){ //found JPG hash var jpgOffset=tempFile.readU32(i+4); var jpgSize=tempFile.readU32(jpgOffset); var arrayBuffer=tempFile._u8array.buffer.slice(jpgOffset+4, jpgOffset+4+jpgSize); var blob=new Blob([arrayBuffer], {type:'image/jpeg'}); var imageUrl=(window.URL || window.webkitURL).createObjectURL(blob); var img=new Image(); img.src=imageUrl; document.getElementById('dialog-caption').innerHTML=''; document.getElementById('dialog-caption').appendChild(img); window.setTimeout(function(){ MarcDialogs.open('caption') }, 100); break; } } }else if(tempFile.readU32(0)===0x01020304 && tempFile.fileSize>=2307552 && tempFile.fileSize<4194304){ var foundAllHashes=this._getOffsets(); if(foundAllHashes){ var header=tempFile.readU32(4); var metaDataStart=tempFile.readU32(8); var knownSavegameVersion=false; for(var i=0; i'+currentEditingItem.getItemTranslation()+'')) UI.modal('delete-item'); }else{ SavegameEditor._removeItem(currentEditingItem.category, currentEditingItem); } }); $('#btn-delete-item-confirm').on('click', function(evt){ SavegameEditor._removeItem(currentEditingItem.category, currentEditingItem); }); this.selectItem=document.createElement('select'); this.selectItem.addEventListener('change', function(){ //console.log('change'); currentEditingItem.id=this.value; Pouch.updateItemIcon(currentEditingItem); }, false); this.selectItem.addEventListener('blur', function(){ //console.log('blur'); for(var prop in currentEditingItem._htmlInputs){ currentEditingItem._htmlInputs[prop].disabled=false; } Pouch.updateItemRow(currentEditingItem); SavegameEditor.fixItemAvailabilityFlag(currentEditingItem); currentEditingItem._htmlItemId.style.display='inline'; this.parentElement.removeChild(this); currentEditingItem=null; }, false); setNumericRange('rupees', 0, 999999); setNumericRange('pony-points', 0, 999999); setNumericRange('pouch-size-swords', 9, 20); setNumericRange('pouch-size-bows', 5, 14); setNumericRange('pouch-size-shields', 4, 20); getField('pouch-size-swords').addEventListener('change', function(evt){ var newVal=parseInt(this.value); if(!isNaN(newVal) && newVal>=9) SavegameEditor.currentItems.pouchSword=newVal; }); getField('pouch-size-bows').addEventListener('change', function(evt){ var newVal=parseInt(this.value); if(!isNaN(newVal) && newVal>=5) SavegameEditor.currentItems.pouchBow=newVal; }); getField('pouch-size-shields').addEventListener('change', function(evt){ var newVal=parseInt(this.value); if(!isNaN(newVal) && newVal>=4) SavegameEditor.currentItems.pouchShield=newVal; }); /* autobuilder */ get('input-file-autobuilder-import').addEventListener('change', function(evt){ autobuilderTempFile=new MarcFile(this.files[0], function(){ var selectedIndex=parseInt(getValue('select-autobuilder-index')); var autobuilderOld=AutoBuilder.readSingle(selectedIndex); if(autobuilderOld){ var importedAutobuilder=AutoBuilder.fromFile(autobuilderTempFile); if(importedAutobuilder){ importedAutobuilder._index=autobuilderOld._index; importedAutobuilder.index=autobuilderOld.index; importedAutobuilder.isFavorite=autobuilderOld.isFavorite; importedAutobuilder.save(); UI.toast('Successfully imported schema at '+(selectedIndex+1)); }else{ UI.toast('Error while importing schema at '+(selectedIndex+1)); } } }); }); get('button-autobuilder-export').addEventListener('click', function(evt){ var selectedIndex=parseInt(getValue('select-autobuilder-index')); var autobuilder=AutoBuilder.readSingle(selectedIndex); if(autobuilder) autobuilder.export().save(); }); get('button-autobuilder-import').addEventListener('click', function(evt){ get('input-file-autobuilder-import').click(); }); /* experience */ get('map-pins-edit').addEventListener('click', function(){ TOTKMasterEditor.mini( new Struct('mapPins', [ { structArray:'markers', variablesInfo:[ {hash:'MapData.IconData.MapPinData.Type', type:'EnumArray', label:'Marker color', propertyName:'color', enumValues:['Invalid','Red','Blue','Yellow','Green','Purple','LightBlue']}, {hash:'MapData.IconData.MapPinData.Pos', type:'Vector3Array', label:'Marker position', propertyName:'position'} ] },{ structArray:'teleporters', variablesInfo:[ {hash:'MapData.IconData.WarpMarkerData.Index', type:'IntArray', label:'Teleporter index', propertyName:'index'}, {hash:'MapData.IconData.WarpMarkerData.Pos', type:'Vector3Array', label:'Teleporter position', propertyName:'position'}, {hash:'MapData.IconData.WarpMarkerData.Rot', type:'Vector3Array', label:'Teleporter rotation', propertyName:'rotation'} ] },{ structArray:'pins', variablesInfo:[ {hash:'MapData.IconData.StampData.Type', type:'EnumArray', label:'Pin icon', propertyName:'icon', enumValues:['Invalid','Sword','Pot','Human','Rhombus','Heart','Star','TreasureBox','Skull','Leaf','Ore']}, {hash:'MapData.IconData.StampData.Layer', type:'EnumArray', label:'Pin map', propertyName:'layer', enumValues:['Sky','Ground','Underground']}, {hash:'MapData.IconData.StampData.Pos', type:'Vector2Array', label:'Pin position', propertyName:'position'} ] } ]), null, _('Map pins editor'), SavegameEditor.refreshCounterMapPins ); }); get('pristine-weapons-edit').addEventListener('click', function(){ TOTKMasterEditor.mini( new Struct('brokenWeapons', Object.keys(Equipment.WEAPONS_DECAYED_TO_PRISTINE).map(function(weaponId){ return { hash:'EquipmentDeathCount.'+weaponId, label:_('Broken times')+' '+_(weaponId), type:'Int' }; })), [ {label:'Unlock all', action:TOTKMasterEditor.miniSetAllToOneAtLeast}, { label:'Reset ghost seeds', action:function(){ var nChanges=ExperienceCalculator.resetGhostStatuesSeeds(); if(nChanges) UI.alert(nChanges+' ghost seeds were reset'); } }, ], _('Broken weapons editor'), function(){ SavegameEditor.refreshCounterPristineWeapons(); SavegameEditor.refreshMissingPristineWeapons(); } ); }); get('span-experience-edit').addEventListener('click', function(){ TOTKMasterEditor.mini( new Struct('experience', ExperienceCalculator.generateHashesTextAll().map(function(hashText){ return { hash:hashText, label:ExperienceCalculator.getPrettifiedHashLabel(hashText), type:'Int' }; })), [{label:'Reset', action:TOTKMasterEditor.miniResetAll}], _('Experience editor'), SavegameEditor.experienceCalculate ); }); /* master editor mini */ $('#button-hash-editor-export').on('click', TOTKMasterEditor.miniExport); $('#button-hash-editor-import').on('click', function(evt){ $('#input-file-hash-editor-import').trigger('click'); }); $('#input-file-hash-editor-import').on('change', function(evt){ var fileReader=new FileReader(); fileReader.onload=function(evt){ try{ var jsonObject=JSON.parse(evt.target.result); TOTKMasterEditor.miniImport(jsonObject); }catch(err){ console.error(err); } }; fileReader.readAsText(event.target.files[0]); }); MarcTooltips.add('#nav button',{className:'dark',fixed:true}); }, _timeToString:function(timeVal){ var seconds=timeVal%60; if(seconds<10)seconds='0'+seconds; var minutes=parseInt(timeVal/60)%60; if(minutes<10)minutes='0'+minutes; return parseInt(timeVal/3600)+':'+minutes+':'+seconds; }, refreshAddArrowsButton:function(){ document.getElementById('button-add-arrows').disabled=!!this.pouches.arrows.items.length; }, retranslateSelectOptions:function(options){ options.forEach(function(option){ if(typeof option.originalName==='string'){ if(typeof option.originalNamePrefix==='string'){ option.name=_(option.originalNamePrefix)+': '+_(option.originalName); }else{ option.name=_(option.originalName); } } }); }, /* load function */ load:function(){ tempFile.fileName='progress.sav'; $('#container-startup').hide(); Variable.resetCache(); UI.reset(); this.retranslateSelectOptions(Equipment.OPTIONS_MODIFIERS.weapons); this.retranslateSelectOptions(Equipment.OPTIONS_MODIFIERS.bows); this.retranslateSelectOptions(Equipment.OPTIONS_MODIFIERS.shields); this.retranslateSelectOptions(Equipment.FUSABLE_ITEMS); this.retranslateSelectOptions(Armor.OPTIONS_DYE_COLORS); this.retranslateSelectOptions(Item.FOOD_EFFECTS); this.retranslateSelectOptions(Horse.OPTIONS_STATS_STAMINA); this.retranslateSelectOptions(Horse.MANES); this.retranslateSelectOptions(Horse.SADDLES); this.retranslateSelectOptions(Horse.REINS); this.selectItem.lastCategory=null; /* prepare editor */ setValue('playtime', this._timeToString(this.readU32('Playtime'))); setValue('rupees', this.readU32('PlayerStatus.CurrentRupee')); setValue('max-hearts', this.readU32('PlayerStatus.MaxLife')); setValue('max-stamina', this.readU32('PlayerStatus.MaxStamina')); setValue('max-battery', this.readF32('PlayerStatus.MaxEnergy')); setValue('pony-points', this.readU32('HorseInnMemberPoint')); setValue('number-pouch-size-swords', this.readU32Array('Pouch.Weapon.ValidNum', 0)); setValue('number-pouch-size-bows', this.readU32Array('Pouch.Bow.ValidNum', 0)); setValue('number-pouch-size-shields', this.readU32Array('Pouch.Shield.ValidNum', 0)); /* parasail pattern */ this.parasailPattern=new Variable('PlayerStatus.ParasailPattern', 'Enum', ['Default','Pattern00','Pattern01','Pattern02','Pattern03','Pattern04','Pattern05','Pattern06','Pattern07','Pattern08','Pattern09','Pattern10','Pattern11','Pattern12','Pattern13','Pattern14','Pattern15','Pattern16','Pattern17','Pattern18','Pattern19','Pattern20','Pattern21','Pattern22','Pattern23','Pattern24','Pattern25','Pattern26','Pattern27','Pattern28','Pattern29','Pattern30','Pattern31','Pattern32','Pattern33','Pattern34','Pattern35','Pattern36','Pattern37','Pattern38','Pattern39','Pattern40','Pattern41','Pattern43','Pattern45','Pattern46','Pattern48','Pattern49','Pattern51','Pattern52','Pattern53','Pattern55','Pattern56']); this._htmlSelectParasailPattern=this.parasailPattern.buildHtmlInputs(true); for(var i=1; i').attr('id', toastId).addClass('toast').appendTo($('#toasts-container')).get(0); if(msg) $(toast).html(msg); else $(toast).remove(); if(typeof timeoutClose==='undefined') timeoutClose=3000; if(msg && timeoutClose){ toast.closeTimeout=window.setTimeout(function(){ $(toast).remove(); }, timeoutClose) } }, octicon:function(iconId){ var img=new Image(); img.src='assets/octicons/octicon_'+iconId+'.svg' img.className='octicon'; return img; }, modal:function(id){ _setLockScroll(true); var dialog=document.getElementById('modal-'+id); if(!dialog.initialized){ dialog.addEventListener('close', function(){ _setLockScroll(false); }); dialog.initialized=true; } dialog.showModal(); }, alert:function(text){ $('') .addClass('modal') .append($('
').addClass('modal-body text-center').html(text)) .append( $('
') .addClass('modal-footer text-center') .append($('').addClass('btn').html(_('Accept')).on('click', _closeMyModal)) ) .appendTo(document.body) .on('close', function(evt){ $(this).remove(); }) .get(0).showModal(); }, confirm:function(text, onConfirm){ $('') .addClass('modal') .append($('
').addClass('modal-body text-center').html(text)) .append( $('
') .addClass('modal-footer text-center') .append($('').addClass('btn').html(_('Cancel')).on('click', _closeMyModal)) .append($('').addClass('btn btn-primary').html(_('Accept')).on('click', function(evt){ _closeMyModal(evt); onConfirm.call(); })) ) .appendTo(document.body) .on('close', function(evt){ $(this).remove(); }) .get(0).showModal(); } } }(SavegameEditor)); function getInternalCategoryId(catId){ catId=catId.toLowerCase().replace(/s$/, ''); if(catId==='device') return 'SpecialParts'; else if(catId==='key') return 'KeyItem'; //else: weapon,bow,arrow,armor,material,food return (catId.charAt(0).toUpperCase() + catId.substr(1)).replace(/s$/, '') }