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

TOTK: no more hardcoded offsets, using real hash offsets now :)

This commit is contained in:
Marc Robledo 2023-05-21 09:26:06 +02:00
parent 15f401771c
commit 624ee098d8
9 changed files with 546 additions and 436 deletions

View File

@ -398,7 +398,7 @@ function select(id,options,func,def){
select.appendChild(options[i]);
}
if(unknownValue && options[i].value!=def){
if(unknownValue && options[i].value==def){
unknownValue=false;
}
}
@ -410,7 +410,7 @@ function select(id,options,func,def){
if(unknownValue){
var option=document.createElement('option');
option.value=def;
option.innerHTML='Unknown: '+def;
option.innerHTML='['+(typeof def==='number'? def.toString(16) : def)+']';
select.appendChild(option);
}

View File

@ -30,14 +30,16 @@ caches.keys().then(function(cacheNames){
});
var PRECACHE_ID='zelda-totk-editor';
var PRECACHE_VERSION='v0a';
var PRECACHE_VERSION='v0';
var PRECACHE_URLS=[
//is hashes file too big for cacheing?
'/savegame-editors/zelda-totk/','/savegame-editors/zelda-totk/index.html',
'/savegame-editors/zelda-totk/zelda-totk.css',
'/savegame-editors/zelda-totk/zelda-totk.js',
'/savegame-editors/zelda-totk/zelda-totk.data.js',
'/savegame-editors/zelda-totk/zelda-totk.locations.js',
'/savegame-editors/zelda-totk/zelda-totk.class.equipment.js',
'/savegame-editors/zelda-totk/zelda-totk.class.armor.js',
'/savegame-editors/zelda-totk/zelda-totk.class.item.js',
'/savegame-editors/zelda-totk/zelda-totk.class.horse.js',
'/savegame-editors/zelda-totk/zelda-totk.master.js',
'/savegame-editors/zelda-totk/favicon.png',
'/savegame-editors/zelda-totk/assets/_blank.png',

View File

@ -108,7 +108,7 @@
<div class="ten columns"><label for="select-max-hearts">Max. hearts</label></div>
<div class="two columns">
<select id="select-max-hearts" class="full-width">
<option value="4">1</option>
<option value="4">1 heart</option>
<option value="8">2 hearts</option>
<option value="12">3 hearts</option>
<option value="16">4 hearts</option>
@ -152,7 +152,7 @@
</div>
</div>
<div class="row mb-5">
<div class="ten columns"><label for="select-max-stamina">Max. stamina </label></div>
<div class="ten columns"><label for="select-max-stamina">Max. stamina</label></div>
<div class="two columns">
<select id="select-max-stamina" class="full-width">
<option value="1148846080">1 wheel</option>
@ -169,6 +169,10 @@
</select>
</div>
</div>
<div class="row mb-5">
<div class="ten columns"><label for="number-pony-points">Pony points</label></div>
<div class="two columns"><input id="number-pony-points" class="full-width" type="text" /></div>
</div>
<!-- Pouch size -->
<h3 class="border-green">Pouch size</h3>
@ -268,7 +272,7 @@
<div id="tab-weapons">
<h3>Weapons</h3><div id="container-weapons"></div>
<div class="text-center">
<!-- <button onclick="SavegameEditor.restoreDurability('weapons')">Restore durability</button> -->
<button onclick="SavegameEditor.restoreDurability('weapons')">Restore durability</button>
<button class="with-icon icon1" onclick="SavegameEditor.addItem('weapons')">Add weapon</button>
</div>
</div>
@ -277,6 +281,7 @@
<div id="tab-bows">
<h3>Bows</h3><div id="container-bows"></div>
<div class="text-center">
<button onclick="SavegameEditor.restoreDurability('bows')">Restore durability</button>
<button class="with-icon icon1" onclick="SavegameEditor.addItem('bows')">Add bow</button>
</div>
<h3>Arrows</h3><div id="container-arrows"></div>
@ -286,6 +291,7 @@
<div id="tab-shields">
<h3>Shields</h3><div id="container-shields"></div>
<div class="text-center">
<button onclick="SavegameEditor.restoreDurability('shields')">Restore durability</button>
<button class="with-icon icon1" onclick="SavegameEditor.addItem('shields')">Add shield</button>
</div>
</div>
@ -309,6 +315,9 @@
<!-- TAB: FOOD -->
<div id="tab-food">
<h3>Food</h3><div id="container-food"></div>
<div class="text-center">
<button class="with-icon icon1" onclick="SavegameEditor.addItem('food')">Add food</button>
</div>
</div>
<!-- TAB: ZONAI DEVICES -->

View File

@ -1,31 +1,32 @@
/*
The legend of Zelda: Tears of the Kingdom Savegame Editor (Armor class) v20230519
The legend of Zelda: Tears of the Kingdom Savegame Editor (Armor class) v20230521
by Marc Robledo 2023
item names compiled by Echocolat, Exincracci, HylianLZ and Karlos007
*/
function Armor(index, read){
function Armor(index, id, dye){
this.category='armors';
this.index=index;
this._offsets=Armor.Offsets.Armor;
if(read){
this.id=SavegameEditor.readString64Array(this._offsets.ID, index);
this.dyeColor=0xdeadbeef;
}else{
this.id='\0';
this.dyeColor=0xdeadbeef;
}
this.id=id;
this.dye=dye || '';
Armor.buildHtmlElements(this);
}
Armor.prototype.getItemTranslation=function(){
return Armor.TRANSLATIONS[this.id] || this.id;
}
Armor.prototype.copy=function(index, newId){
return new Armor(
index,
typeof newId==='string'? newId : this.id,
this.dye
);
}
Armor.prototype.save=function(){
SavegameEditor.writeString64Array(this._offsets.ID, this.index, this.id);
//SavegameEditor.writeString64Array(this._offsets.DYE, this.dyeColor, this.index);
SavegameEditor.writeString64('ArrayArmorIds', this.index, this.id);
//SavegameEditor.writeString64('ArrayArmorDyeColors', this.index, this.dyeColor);
}
@ -34,18 +35,19 @@ Armor.buildHtmlElements=function(item){
//to-do: add dye selector
}
Armor.readMaxCapacity=function(catId){
return SavegameEditor.readArraySize(Armor.Offsets.Armor.ID);
}
Armor.readAll=function(){
var items=[];
var maxItems=Armor.readMaxCapacity();
for(var i=0; i<maxItems; i++){
var item=new Armor(i, true);
if(item.id)
items.push(item);
var armorIds=SavegameEditor.readString64Array('ArrayArmorIds');
var validArmors=[];
for(var i=0; i<armorIds.length; i++){
if(armorIds[i]){
validArmors.push(new Armor(
i,
armorIds[i],
null
));
}
}
return items;
return validArmors;
}
Armor.remove=function(index){
if(typeof index==='object')
@ -58,12 +60,6 @@ Armor.remove=function(index){
SavegameEditor.writeString64Array(this._offsets.ID, '\0', Armor.items.length);
}
Armor.Offsets={ //v1.0 offsets, v1.1=v1.0 + 0x38
Armor:{
ID: 0x00061bbc,
DYE: 0xdeadbeef
}
}
//Armor.DYE_COLORS:['-default-','Blue','Red','Yellow','White','Black','Purple','Green','Light Blue','Navy','Orange','Peach','Crimson','Light Yellow','Brown','Gray',{value:0xffffffff,name:'locked color'}],

View File

@ -1,31 +1,23 @@
/*
The legend of Zelda: Tears of the Kingdom Savegame Editor (Equipment class) v20230519
The legend of Zelda: Tears of the Kingdom Savegame Editor (Equipment class) v20230521
by Marc Robledo 2023
item names compiled by Echocolat, Exincracci, HylianLZ and Karlos007
*/
function Equipment(catId, index, read){ //Weapon, Bow or Shield
function Equipment(catId, index, id, durability, modifier, modifierValue, fuseId){ //Weapon, Bow or Shield
this.category=catId;
this.index=index;
this._offsets=Equipment.getOffsetsByCategoryId(catId);
if(read){
this.id=SavegameEditor.readString64Array(this._offsets.ID, index);
this.durability=SavegameEditor.readU32Array(this._offsets.DURABILITY, index);
this.modifier=SavegameEditor.readU32Array(this._offsets.MODIFIER, index);
this.modifierValue=SavegameEditor.readU32Array(this._offsets.MODIFIER_VALUE, index);
if(catId==='weapons' || catId==='shields')
this.fuseId=SavegameEditor.readString64Array(this._offsets.FUSE_ID, index);
}else{
this.id='\0';
this.durability=70;
this.modifier=Equipment.MODIFIER_NO_BONUS;
this.modifierValue=0;
if(catId==='weapons' || catId==='shields')
this.fuseId='\0';
this.id=id;
this.durability=durability || 70;
this.modifier=modifier || Equipment.MODIFIER_NO_BONUS;
this.modifierValue=modifierValue || 0;
if(this.isFusable()){
this.fuseId=fuseId || '';
if(this.fuseId && Equipment.FUSABLE_WITH.indexOf(this.fuseId)===-1){
console.warn('unknown fusable item['+catId+','+index+']: '+this.fuseId);
}
}
Equipment.buildHtmlElements(this);
@ -33,23 +25,44 @@ function Equipment(catId, index, read){ //Weapon, Bow or Shield
Equipment.prototype.getItemTranslation=function(){
return Equipment.TRANSLATIONS[this.category][this.id] || this.id;
}
Equipment.prototype.isFusable=function(){
return (this.category==='weapons' || this.category==='shields')
}
Equipment.prototype.getFusableTranslation=function(){
if(!this.fuseId)
return null;
//to-do
}
Equipment.prototype.restoreDurability=function(){
this.durability=this.getMaximumDurability();
this._htmlInputDurability.value=this.durability;
}
Equipment.prototype.getMaximumDurability=function(){
var defaultDurability=Equipment.DEFAULT_DURABILITY[this.id] || 70;
if(this.modifier===Equipment.MODIFIER_DURABILITY || this.modifier===Equipment.MODIFIER_DURABILITY2) //Durability ↑/↑↑
return 2100000000;
return Equipment.DEFAULT_DURABILITY[this.id] || 70;
return defaultDurability + this.modifierValue;
return defaultDurability;
}
Equipment.prototype.copy=function(index, newId){
return new Equipment(
this.category,
index,
typeof newId==='string'? newId : this.id,
this.durability,
this.modifier,
this.modifierValue,
this.isFusable()? this.fuseId : null
);
}
Equipment.prototype.save=function(){
SavegameEditor.writeString64Array(this._offsets.ID, this.index, this.id);
SavegameEditor.writeU32Array(this._offsets.DURABILITY, this.index, this.durability);
SavegameEditor.writeU32Array(this._offsets.MODIFIER, this.index, this.modifier);
SavegameEditor.writeU32Array(this._offsets.MODIFIER_VALUE, this.index, this.modifierValue);
var categoryHash=capitalizeCategoryId(this.category);
SavegameEditor.writeString64('Array'+categoryHash+'Ids', this.index, this.id);
SavegameEditor.writeU32('Array'+categoryHash+'Durabilities', this.index, this.durability);
SavegameEditor.writeU32('Array'+categoryHash+'Modifiers', this.index, this.modifier);
SavegameEditor.writeU32('Array'+categoryHash+'ModifierValue', this.index, this.modifierValue);
/*if(this.category==='weapons' || this.category==='shields')
SavegameEditor.writeString64Array(this._offsets.FUSE_ID, this.index, this.fuseId);*/
/*if(this.isFusable())
SavegameEditor.writeString64('ArrayWeaponFuseIds', this.index, this.fuseId);*/
}
@ -110,14 +123,14 @@ Equipment.buildHtmlElements=function(item){
get('number-item-durability-'+item.category+'-'+item.index).maxValue=maximumDurability;
if(item.modifier===Equipment.MODIFIER_NO_BONUS){
item.restoreDurability();
item.modifierValue=0;
item._htmlInputModifierValue.value=0;
}else if(fromNoBonus && (item.modifier===Equipment.MODIFIER_DURABILITY || item.modifier===Equipment.MODIFIER_DURABILITY2)){
item.restoreDurability();
item.modifierValue=maximumDurability;
item._htmlInputModifierValue.value=maximumDurability;
}else if(fromNoBonus || fromModifierDurability){
if(fromNoBonus && (item.modifier===Equipment.MODIFIER_DURABILITY || item.modifier===Equipment.MODIFIER_DURABILITY2)){
item.modifierValue=2100000000;
item._htmlInputModifierValue.value=2100000000;
}
item.restoreDurability();
}
}, item.modifier);
@ -136,33 +149,33 @@ Equipment.buildHtmlElements=function(item){
}
});
item._htmlInputModifierValue.title='Modifier value';
}
Equipment.readMaxCapacity=function(catId){
return SavegameEditor.readArraySize(Equipment.getOffsetsByCategoryId(catId).ID);
}
Equipment.readAll=function(catId){
var offsets=Equipment.getOffsetsByCategoryId(catId);
var items=[];
var maxItems=Equipment.readMaxCapacity(catId);
for(var i=0; i<maxItems; i++){
var item=new Equipment(catId, i, true);
if(item.id)
items.push(item);
if(item.isFusable()){
}
return items;
}
Equipment.getOffsetsByCategoryId=function(catId){
if(catId==='weapons')
return Equipment.Offsets.Weapons;
else if(catId==='bows')
return Equipment.Offsets.Bows;
else if(catId==='shields')
return Equipment.Offsets.Shields;
return null;
Equipment.readAll=function(catId){
var categoryHash=capitalizeCategoryId(catId);
var equipmentIds=SavegameEditor.readString64Array('Array'+categoryHash+'Ids');
var isFusable=(catId==='weapons' || catId==='shields');
var validEquipment=[];
for(var i=0; i<equipmentIds.length; i++){
if(equipmentIds[i]){
validEquipment.push(new Equipment(
catId,
i,
equipmentIds[i],
SavegameEditor.readU32('Array'+categoryHash+'Durabilities', i),
SavegameEditor.readU32('Array'+categoryHash+'Modifiers', i),
SavegameEditor.readU32('Array'+categoryHash+'ModifierValues', i),
isFusable? SavegameEditor.readString64('Array'+categoryHash+'FuseIds', i) : null
));
}
}
return validEquipment;
}
Equipment.remove=function(index){
if(typeof index==='object')
index=Equipment.items.indexOf(index);
@ -174,28 +187,7 @@ Equipment.remove=function(index){
SavegameEditor.writeString64Array(this._offsets.ID, '\0', Equipment.items.length);
}
Equipment.Offsets={ //v1.0 offsets, v1.1=v1.0 + 0x38
Weapons:{
ID: 0x000c3b58,
DURABILITY: 0x0004d1c0,
MODIFIER: 0x000515bc,
MODIFIER_VALUE: 0x0004eed4,
FUSE_ID: 0x000a65d4
},
Bows:{
ID: 0x0007b4e4,
DURABILITY: 0x0004aab8,
MODIFIER: 0x0005252c,
MODIFIER_VALUE: 0x0004cafc
},
Shields:{
ID: 0x000760f0,
DURABILITY: 0x0004a3b0,
MODIFIER: 0x00051070,
MODIFIER_VALUE: 0x0004ba54,
FUSE_ID: 0x000aa07c
}
}
Equipment.MODIFIER_NO_BONUS=0xb6eede09;
Equipment.MODIFIER_ATTACK=0xa9384c6c;
Equipment.MODIFIER_ATTACK2=0xdad10617;
@ -254,6 +246,8 @@ Equipment.DEFAULT_DURABILITY={
Weapon_Sword_166:15,
Weapon_Sword_167:4,
Weapon_Sword_168:12,
Weapon_Sword_077:30,
Weapon_Lsword_001:20,
Weapon_Lsword_002:25,
Weapon_Lsword_003:30,
@ -419,25 +413,26 @@ Weapon_Sword_070:'Master Sword',
Weapon_Sword_101:'Zonaite Sword',
Weapon_Sword_103:'Wooden Stick',
Weapon_Sword_105:'Boomerang',
Weapon_Sword_106:'Traveler\'s Sword *',
Weapon_Sword_106:'Traveler\'s Sword (decayed)',
Weapon_Sword_107:'Lizal Boomerang',
Weapon_Sword_108:'Sturdy Wooden Stick',
Weapon_Sword_109:'Gnarled Wooden Stick',
Weapon_Sword_112:'Soldier\'s Broadsword *',
Weapon_Sword_113:'Knight\'s Broadsword *',
Weapon_Sword_114:'Eightfold Blade *',
Weapon_Sword_124:'Royal Broadsword *',
Weapon_Sword_125:'Forest Dweller\'s Sword *',
Weapon_Sword_127:'Zora Sword *',
Weapon_Sword_129:'Gerudo Scimitar *',
Weapon_Sword_131:'Feathered Edge *',
Weapon_Sword_147:'Royal Guard\'s Sword *',
Weapon_Sword_112:'Soldier\'s Broadsword (decayed)',
Weapon_Sword_113:'Knight\'s Broadsword (decayed)',
Weapon_Sword_114:'Eightfold Blade (decayed)',
Weapon_Sword_124:'Royal Broadsword (decayed)',
Weapon_Sword_125:'Forest Dweller\'s Sword (decayed)',
Weapon_Sword_127:'Zora Sword (decayed)',
Weapon_Sword_129:'Gerudo Scimitar (decayed)',
Weapon_Sword_131:'Feathered Edge (decayed)',
Weapon_Sword_147:'Royal Guard\'s Sword (decayed)',
Weapon_Sword_161:'Magic Rod',
Weapon_Sword_163:'Strong Zonaite Sword',
Weapon_Sword_164:'Mighty Zonaite Sword',
Weapon_Sword_166:'Gloom Sword',
Weapon_Sword_167:'Tree Branch *',
Weapon_Sword_168:'Wooden Stick *',
Weapon_Sword_167:'Tree Branch (sky)',
Weapon_Sword_168:'Wooden Stick (decayed)',
Weapon_Sword_077:'Master Sword (glitched)',
Weapon_Lsword_001:'Traveler\'s Claymore',
Weapon_Lsword_002:'Soldier\'s Claymore',
@ -459,23 +454,23 @@ Weapon_Lsword_059:'Biggoron\'s Sword',
Weapon_Lsword_060:'Fierce Deity Sword',
Weapon_Lsword_101:'Zonaite Longsword',
Weapon_Lsword_103:'Thick Stick',
Weapon_Lsword_106:'Traveler\'s Claymore *',
Weapon_Lsword_106:'Traveler\'s Claymore (decayed)',
Weapon_Lsword_108:'Sturdy Thick Stick',
Weapon_Lsword_109:'Gnarled Thick Stick',
Weapon_Lsword_112:'Soldier\'s Claymore *',
Weapon_Lsword_113:'Knight\'s Claymore *',
Weapon_Lsword_114:'Eightfold Longblade *',
Weapon_Lsword_124:'Royal Claymore *',
Weapon_Lsword_127:'Zora Longsword *',
Weapon_Lsword_129:'Gerudo Claymore *',
Weapon_Lsword_136:'Cobble Crusher *',
Weapon_Lsword_147:'Royal Guard\'s Claymore *',
Weapon_Lsword_112:'Soldier\'s Claymore (decayed)',
Weapon_Lsword_113:'Knight\'s Claymore (decayed)',
Weapon_Lsword_114:'Eightfold Longblade (decayed)',
Weapon_Lsword_124:'Royal Claymore (decayed)',
Weapon_Lsword_127:'Zora Longsword (decayed)',
Weapon_Lsword_129:'Gerudo Claymore (decayed)',
Weapon_Lsword_136:'Cobble Crusher (decayed)',
Weapon_Lsword_147:'Royal Guard\'s Claymore (decayed)',
Weapon_Lsword_161:'Magic Scepter',
Weapon_Lsword_163:'Strong Zonaite Longsword',
Weapon_Lsword_164:'Mighty Zonaite Longsword',
Weapon_Lsword_166:'Gloom Club',
Weapon_Lsword_168:'Thick Stick *',
Weapon_Lsword_174:'Giant Boomerang *',
Weapon_Lsword_168:'Thick Stick (decayed)',
Weapon_Lsword_174:'Giant Boomerang (decayed)',
Weapon_Spear_001:'Traveler\'s Spear',
Weapon_Spear_002:'Soldier\'s Spear',
@ -494,23 +489,23 @@ Weapon_Spear_047:'Royal Guard\'s Spear',
Weapon_Spear_050:'Lightscale Trident',
Weapon_Spear_101:'Zonaite Spear',
Weapon_Spear_103:'Long Stick',
Weapon_Spear_106:'Traveler\'s Spear *',
Weapon_Spear_106:'Traveler\'s Spear (decayed)',
Weapon_Spear_108:'Sturdy Long Stick',
Weapon_Spear_109:'Gnarled Long Stick',
Weapon_Spear_112:'Soldier\'s Spear *',
Weapon_Spear_113:'Knight\'s Halberd *',
Weapon_Spear_124:'Royal Halberd *',
Weapon_Spear_125:'Forest Dweller\'s Spear *',
Weapon_Spear_127:'Zora Spear *',
Weapon_Spear_129:'Gerudo Spear *',
Weapon_Spear_132:'Feathered Spear *',
Weapon_Spear_147:'Royal Guard\'s Spear *',
Weapon_Spear_112:'Soldier\'s Spear (decayed)',
Weapon_Spear_113:'Knight\'s Halberd (decayed)',
Weapon_Spear_124:'Royal Halberd (decayed)',
Weapon_Spear_125:'Forest Dweller\'s Spear (decayed)',
Weapon_Spear_127:'Zora Spear (decayed)',
Weapon_Spear_129:'Gerudo Spear (decayed)',
Weapon_Spear_132:'Feathered Spear (decayed)',
Weapon_Spear_147:'Royal Guard\'s Spear (decayed)',
Weapon_Spear_161:'Magic Staff',
Weapon_Spear_163:'Strong Zonaite Spear',
Weapon_Spear_164:'Mighty Zonaite Spear',
Weapon_Spear_166:'Gloom Spear',
Weapon_Spear_168:'Long Stick *',
Weapon_Spear_173:'Throwing Spear *'
Weapon_Spear_168:'Long Stick (decayed)',
Weapon_Spear_173:'Throwing Spear (decayed)'
},
'bows':{
@ -581,4 +576,59 @@ Weapon_Shield_102:'Strong Zonaite Shield',
Weapon_Shield_103:'Mighty Zonaite Shield',
Weapon_Shield_107:'Old Wooden Shield'
}
};
};
Equipment.TRANSLATIONS_FUSE_ONLY={
}
Equipment.FUSABLE_WITH=[
'AsbObj_RockParts_C_S_01',
'AsbObj_SharpRock_A_S_01',
'AsbObj_WhiteWoodRectangle_A_LL_01',
'Barrel_SkyObj',
'DgnObj_BoardIron_E',
'DgnObj_SpikeBallWood_A',
'IceWall_Piece',
'Item_Enemy_106',
'Item_Enemy_109',
'Item_Enemy_134',
'Item_Enemy_137',
'Item_Enemy_138',
'Item_Enemy_139',
'Item_Enemy_141',
'Item_Enemy_142',
'Item_Enemy_149',
'Item_Enemy_150',
'Item_Enemy_151',
'Item_Enemy_153',
'Item_Enemy_166',
'Item_Enemy_168',
'Item_Enemy_192',
'Item_Enemy_193',
'Item_Enemy_225',
'Item_Enemy_227',
'Item_Enemy_58',
'Item_Enemy_59',
'Item_Enemy_60',
'Item_Enemy_77',
'Item_Ore_B',
'Item_Ore_C',
'Item_Ore_D',
'Obj_GerudoHoleCover_A_03',
'Obj_LiftRockWhite_A_01',
'Obj_SpikeBall_B',
'SpObj_Cannon_A_01',
'SpObj_ElectricBoxGenerator',
'SpObj_FlameThrower_A_01',
'SpObj_SlipBoard_A_01',
'SpObj_SpringPiston_A_01',
'Weapon_Bow_036',
'Weapon_Lsword_041',
'Weapon_Shield_002',
'Weapon_Shield_006',
'Weapon_Shield_103',
'Weapon_Sword_019',
'Weapon_Sword_101',
'Weapon_Sword_112',
'Weapon_Sword_124'
];

View File

@ -1,20 +1,19 @@
/*
The legend of Zelda: Tears of the Kingdom Savegame Editor (Horse class) v20230519
The legend of Zelda: Tears of the Kingdom Savegame Editor (Horse class) v20230521
by Marc Robledo 2023
item names compiled by Echocolat, Exincracci, HylianLZ and Karlos007
*/
function Horse(index){
function Horse(index, id, name){
this.category='horses';
this.index=index;
this._offsets=Horses.Offsets;
this.id=SavegameEditor.readString64Array(this._offsets.ID, index);
this.name=SavegameEditor.readUTF8String64Array(this._offsets.NAME, index);
this.id=id;
this.name=name;
}
Horse.prototype.save=function(){
SavegameEditor.writeString64Array(this._offsets.ID, this.index, this.id);
SavegameEditor.writeString64('ArrayHorseIds', this.index, this.id);
}
@ -23,57 +22,22 @@ Horse.buildHtmlElements=function(horse){
//to-do
}
/*setHorseName:function(i,val){
if(i<5)
this._writeString64(this.Offsets.HORSE_NAMES, val, i);
},
setHorseSaddle:function(i,val){
if(i<5)
this._writeString64(this.Offsets.HORSE_SADDLES, val, i);
},
setHorseReins:function(i,val){
if(i<5)
this._writeString64(this.Offsets.HORSE_REINS, val, i);
},
setHorseType:function(i,val){
if(currentEditingItem<6){
this._writeString64(this.Offsets.HORSE_TYPES, val, i);
// fix mane
this._writeString64(this.Offsets.HORSE_MANES, (val==='GameRomHorse00L'?'Horse_Link_Mane_00L':'Horse_Link_Mane'), i);
Horse.readAll=function(){
var horsesIds=SavegameEditor.readString64Array('ArrayHorseIds');
var validHorses=[];
for(var i=0; i<horsesIds.length; i++){
if(horsesIds[i]){
validHorses.push(new Horse(
i,
horsesIds[i],
SavegameEditor.readStringUTF8('ArrayHorseNames', i)
));
}
}
},
for(var i=0; i<6; i++){
if(i<6){
//get('select-horse'+i+'-saddles').horseIndex=i;
//get('select-horse'+i+'-saddles').addEventListener('change', function(){SavegameEditor.setHorseSaddle(this.horseIndex, this.value)}, false);
//get('select-horse'+i+'-reins').horseIndex=i;
//get('select-horse'+i+'-reins').addEventListener('change', function(){SavegameEditor.setHorseReins(this.horseIndex, this.value)}, false);
}
get('select-horse'+i+'-type').horseIndex=i;
get('select-horse'+i+'-type').addEventListener('change', function(){SavegameEditor.setHorseType(this.horseIndex, this.value)}, false);
//select('horse'+i+'-saddles', this._arrayToSelectOpts(TOTK_Data.HORSE_SADDLES));
//select('horse'+i+'-reins', this._arrayToSelectOpts(TOTK_Data.HORSE_REINS));
//select('horse'+i+'-type', this._arrayToSelectOpts(i===6?TOTK_Data.HORSE_TYPES.concat(TOTK_Data.HORSE_TYPES_UNTAMMED):TOTK_Data.HORSE_TYPES));
}
*/
var Horses={};
Horses.readAll=function(){
var horses=[];
var maxHorses=SavegameEditor.readArraySize(Horses.Offsets.ID);
for(var i=0; i<maxHorses; i++){
var horse=new Horse(i);
if(horse.id)
horses.push(horse);
}
return horses;
}
Horses.Offsets={ //v1.0 offsets, v1.1=v1.0 + 0x38
ID: 0x0008a0ec,
NAME: 0x0010a148
return validHorses;
}
Horses.HORSE_TYPES=[
Horse.HORSE_TYPES=[
'GameRomHorse00','GameRomHorse01','GameRomHorse02','GameRomHorse03','GameRomHorse04','GameRomHorse05','GameRomHorse06','GameRomHorse07','GameRomHorse08','GameRomHorse09','GameRomHorse10','GameRomHorse11','GameRomHorse12','GameRomHorse13','GameRomHorse14','GameRomHorse15','GameRomHorse16','GameRomHorse17','GameRomHorse18','GameRomHorse19','GameRomHorse20','GameRomHorse21','GameRomHorse22','GameRomHorse23','GameRomHorse25','GameRomHorse26','GameRomHorseEpona','GameRomHorseZelda','GameRomHorse00L','GameRomHorse01L','GameRomHorseGold',
//untested, posible freeze
@ -82,13 +46,11 @@ Horses.HORSE_TYPES=[
'GameRomHorseForStreetVender',
'GameRomHorseNushi'
];
Horses.HORSE_REINS=['GameRomHorseReins_00','GameRomHorseReins_01','GameRomHorseReins_02','GameRomHorseReins_03','GameRomHorseReins_04','GameRomHorseReins_05','GameRomHorseReins_06','GameRomHorseReins_00L','GameRomHorseReins_10'];
Horses.HORSE_SADDLES=['GameRomHorseSaddle_00','GameRomHorseSaddle_01','GameRomHorseSaddle_02','GameRomHorseSaddle_03','GameRomHorseSaddle_04','GameRomHorseSaddle_05','GameRomHorseSaddle_06','GameRomHorseSaddle_00L','GameRomHorseSaddle_00S','GameRomHorseSaddle_10'];
Horses.HORSE_TYPES_UNTAMMED=[];
Horse.HORSE_REINS=['GameRomHorseReins_00','GameRomHorseReins_01','GameRomHorseReins_02','GameRomHorseReins_03','GameRomHorseReins_04','GameRomHorseReins_05','GameRomHorseReins_06','GameRomHorseReins_00L','GameRomHorseReins_10'];
Horse.HORSE_SADDLES=['GameRomHorseSaddle_00','GameRomHorseSaddle_01','GameRomHorseSaddle_02','GameRomHorseSaddle_03','GameRomHorseSaddle_04','GameRomHorseSaddle_05','GameRomHorseSaddle_06','GameRomHorseSaddle_00L','GameRomHorseSaddle_00S','GameRomHorseSaddle_10'];
Horse.HORSE_TYPES_UNTAMMED=[];
Horses.TRANSLATIONS={
Horse.TRANSLATIONS={
//to-do
}
};

View File

@ -1,39 +1,62 @@
/*
The legend of Zelda: Tears of the Kingdom Savegame Editor (Item class) v20230519
The legend of Zelda: Tears of the Kingdom Savegame Editor (Item class) v20230521
by Marc Robledo 2023
item names compiled by Echocolat, Exincracci, HylianLZ and Karlos007
*/
function Item(catId, index, read){
function Item(catId, index, id, quantity, foodEffect, foodEffectHearts, foodEffectMultiplier, foodEffectTime, foodEffectUnknown1){
this.category=catId;
this.index=index;
this._offsets=Items.getOffsetsByCategoryId(catId);
if(read){
this.id=SavegameEditor.readString64Array(this._offsets.ID, index);
this.quantity=SavegameEditor.readU32Array(this._offsets.QUANTITY, index);
}else{
this.id='\0';
this.quantity=1;
this.id=id;
this.quantity=quantity || 1;
if(catId==='food'){
this.foodEffect=typeof foodEffect==='number'? foodEffect : Item.FOOD_NO_EFFECT;
this.foodEffectHearts=typeof foodEffectHearts==='number'? foodEffectHearts : 4;
this.foodEffectMultiplier=typeof foodEffectMultiplier==='number'? foodEffectMultiplier : 0;
this.foodEffectTime=typeof foodEffectTime==='number'? foodEffectTime : 0;
this.foodEffectUnknown1=typeof foodEffectUnknown1==='number'? foodEffectUnknown1 : 1;
}
Items.buildHtmlElements(this);
Item.buildHtmlElements(this);
}
Item.prototype.getItemTranslation=function(){
return Items.TRANSLATIONS[this.category][this.id] || this.id;
return Item.TRANSLATIONS[this.category][this.id] || this.id;
}
Item.prototype.copy=function(index, newId){
return new Item(
this.category,
index,
typeof newId==='string'? newId : this.id,
this.quantity,
this.category==='food'? this.foodEffect : null,
this.category==='food'? this.foodEffectHearts : null,
this.category==='food'? this.foodEffectMultiplier : null,
this.category==='food'? this.foodEffectTime : null,
this.category==='food'? this.foodEffectUnknown1 : null
);
}
Item.prototype.save=function(){
SavegameEditor.writeString64Array(this._offsets.ID, this.index, this.id);
SavegameEditor.writeU32Array(this._offsets.QUANTITY, this.index, this.quantity);
var categoryHash=capitalizeCategoryId(this.category);
SavegameEditor.writeString64('Array'+categoryHash+'Ids', this.index, this.id);
SavegameEditor.writeU32('Array'+categoryHash+'Quantities', this.index, this.quantity);
if(this.category==='food'){
SavegameEditor.writeU32('ArrayFoodEffects', this.index, this.foodEffect);
SavegameEditor.writeU32('ArrayFoodEffectsHearts', this.index, this.foodEffectHearts);
SavegameEditor.writeU32('ArrayFoodEffectsMultiplier', this.index, this.foodEffectMultiplier);
SavegameEditor.writeU32('ArrayFoodEffectsTime', this.index, this.foodEffectTime);
SavegameEditor.writeU32('ArrayFoodEffectsUnknown1', this.index, this.foodEffectUnknown1);
}
}
var Items={};
Items.buildHtmlElements=function(item){
Item.buildHtmlElements=function(item){
//build html elements
var maxValue=Items.MAXIMUM_QUANTITY[item.id] || 999;
var maxValue=Item.MAXIMUM_QUANTITY[item.id] || 999;
item._htmlInputQuantity=inputNumber('item-quantity-'+item.category+'-'+item.index, 1, maxValue, item.quantity);
item._htmlInputQuantity.addEventListener('change', function(){
var newVal=parseInt(this.value);
@ -41,72 +64,78 @@ Items.buildHtmlElements=function(item){
item.quantity=newVal;
});
item._htmlInputQuantity.title='Quantity';
}
Items.readMaxCapacity=function(catId){
return SavegameEditor.readArraySize(Items.getOffsetsByCategoryId(catId).ID);
}
Items.readAll=function(catId){
var offsets=Items.getOffsetsByCategoryId(catId);
var items=[];
var maxItems=Items.readMaxCapacity(catId);
for(var i=0; i<maxItems; i++){
var item=new Item(catId, i, true);
if(item.id)
items.push(item);
if(item.category==='food'){
var foodEffects=[
{name:'none', value:Item.FOOD_NO_EFFECT},
{name:'Stamina', value:0xe9a30056},
{name:'Attack+', value:0xa9384c6c},
{name:'Attack+ with cold', value:0x4a3e58f6},
{name:'Attack+ with heat', value:0x4c6a85d2},
{name:'Defense+', value:0xa0a00c0e},
{name:'Speed+', value:0xb3f6b87a},
{name:'Brightness', value:0x4939dca1}
];
item._htmlSelectFoodEffect=select('item-food-effects-'+item.category+'-'+item.index, foodEffects, function(){
item.foodEffect=parseInt(this.value);
}, item.foodEffect);
item._htmlSelectFoodEffect.title='Food effect';
item._htmlSelectFoodEffectHearts=inputNumber('item-food-effects-hearts-'+item.category+'-'+item.index, 0, 40*4, item.foodEffectHearts);
item._htmlSelectFoodEffectHearts.addEventListener('change', function(){
var newVal=parseInt(this.value);
if(!isNaN(newVal) && newVal>0)
item.foodEffectHearts=newVal;
});
item._htmlSelectFoodEffectHearts.title='Heart quarters heal';
item._htmlSelectFoodEffectMultiplier=inputNumber('item-food-effects-multiplier-'+item.category+'-'+item.index, 1, 8, item.foodEffectMultiplier);
item._htmlSelectFoodEffectMultiplier.addEventListener('change', function(){
var newVal=parseInt(this.value);
if(!isNaN(newVal) && newVal>0)
item.foodEffectMultiplier=newVal;
});
item._htmlSelectFoodEffectMultiplier.title='Multiplier';
item._htmlSelectFoodEffectTime=inputNumber('item-food-effects-time-'+item.category+'-'+item.index, 30, 4800, item.foodEffectTime);
item._htmlSelectFoodEffectTime.addEventListener('change', function(){
var newVal=parseInt(this.value);
if(!isNaN(newVal) && newVal>0)
item.foodEffectTime=newVal;
});
item._htmlSelectFoodEffectTime.title='Duration (in seconds)';
}
return items;
}
Items.getOffsetsByCategoryId=function(catId){
if(catId==='arrows')
return Items.Offsets.Arrows;
else if(catId==='materials')
return Items.Offsets.Materials;
else if(catId==='food')
return Items.Offsets.Food;
else if(catId==='devices')
return Items.Offsets.Devices;
else if(catId==='key')
return Items.Offsets.Key;
return null;
}
Items.remove=function(index){
if(typeof index==='object')
index=Items.items.indexOf(index);
Items.items.splice(index, 1);
for(var i=index; i<Items.items.length; i++){
Items.items[i].index--;
Item.readAll=function(catId){
var categoryHash=capitalizeCategoryId(catId);
var itemIds=SavegameEditor.readString64Array('Array'+categoryHash+'Ids');
var isFood=(catId==='food');
var validItems=[];
for(var i=0; i<itemIds.length; i++){
if(itemIds[i]){
validItems.push(new Item(
catId,
i,
itemIds[i],
SavegameEditor.readU32('Array'+categoryHash+'Quantities', i),
isFood? SavegameEditor.readU32('ArrayFoodEffects', i) : null,
isFood? SavegameEditor.readU32('ArrayFoodEffectsHearts', i) : null,
isFood? SavegameEditor.readU32('ArrayFoodEffectsMultiplier', i) : null,
isFood? SavegameEditor.readU32('ArrayFoodEffectsTime', i) : null,
isFood? SavegameEditor.readU32('ArrayFoodEffectsUnknown1', i) : null
));
}
}
SavegameEditor.writeString64Array(this._offsets.ID, '\0', Items.items.length);
return validItems;
}
Item.remove=function(index){
//to-do
}
Items.Offsets={ //v1.0 offsets, v1.1=v1.0 + 0x38
Arrows:{
ID: 0x000820ec,
QUANTITY: 0x00046ff4
},
Materials:{
ID: 0x000afbf4,
QUANTITY: 0x000477a4
},
Food:{
ID: 0x00087ca4,
QUANTITY: 0x0004e984
},
Devices:{
ID: 0x0009cb70,
QUANTITY: 0x00046148
},
Key:{
ID: 0x000b9488,
QUANTITY: 0x0004eb98
}
};
Items.MAXIMUM_QUANTITY={
Item.MAXIMUM_QUANTITY={
Item_Ore_L:999999, //Zonaite
Item_Ore_M:999999, //Large Zonaite
Energy_Material_01:99999, //Crystallized Charge
@ -119,8 +148,9 @@ MinusRupee_00:99999 //Poe
};
Item.FOOD_NO_EFFECT=0xb6eede09;
Items.TRANSLATIONS={
Item.TRANSLATIONS={
'arrows':{
NormalArrow:'Arrow'
},
@ -830,6 +860,9 @@ Obj_SubstituteCloth_56:"Addison's Fabric"
}
};
Item.TRANSLATIONS_FUSE_ONLY={
}
/*

View File

@ -1,5 +1,5 @@
/*
The legend of Zelda: Tears of the Kingdom savegame editor v20230519
The legend of Zelda: Tears of the Kingdom savegame editor v20230521
by Marc Robledo 2017-2020
*/
var currentEditingItem;
@ -7,14 +7,11 @@ var currentEditingItem;
SavegameEditor={
Name:'The legend of Zelda: Tears of the Kingdom',
Filename:['progress.sav','caption.sav'],
Version:20230519,
Version:20230521,
noDemo:true,
/* Constants */
Constants:{
STRING_SIZE:0x20,
STRING64_SIZE:0x40,
VERSION: ['v1.0', 'v1.1'],
FILESIZE: [2307552, 2307656],
HEADER: [0x0046c3c8, 0x0047e0f4],
@ -23,94 +20,144 @@ SavegameEditor={
BLANK_ICON_PATH:'./assets/_blank.png'
},
currentItemOffset:null,
/* Hashes */
Hashes:[
0xfbe01da1, 'TempMaxHearts',
0xa77921d7, 'TempRupees',
//0x31ab5580, 'TempHearts',
0xf9212c74, 'TempStamina',
0xe573f564, 'TempPlaytime',
0xa3db7114, 'TempItemData' //???
0xfbe01da1, 'MaxHearts',
0xa77921d7, 'CurrentRupees',
//0x31ab5580, 'CurrentHearts',
0xf9212c74, 'MaxStamina',
0x15ec5858, 'PonyPoints',
0xe573f564, 'Playtime',
0xd7a3f6ba, 'ArrayPouchSwords',
0xc61785c2, 'ArrayPouchBows',
0x05271e7d, 'ArrayPouchShields',
0x65efd0be, 'ArrayWeaponIds',
0x8b12d062, 'ArrayWeaponDurabilities',
0xdd846288, 'ArrayWeaponModifiers',
0xfda1d214, 'ArrayWeaponModifierValue',
0x80707cad, 'ArrayWeaponFuseIds',
0x791c4a0b, 'ArrayBowIds',
0x60589200, 'ArrayBowDurabilities',
0xd59aeed5, 'ArrayBowModifiers',
0xdfd216f2, 'ArrayBowModifierValue',
0x273190f4, 'ArrayShieldIds',
0xc3416d19, 'ArrayShieldDurabilities',
0x464b410c, 'ArrayShieldModifiers',
0xa6a38304, 'ArrayShieldModifierValue',
0xc95833d9, 'ArrayShieldFuseIds',
0x754e8549, 'ArrayArmorIds',
0x24dd3262, 'ArrayArrowIds',
0x53b27d94, 'ArrayArrowQuantities',
0xd96ebf12, 'ArrayMaterialIds',
0xde2d8500, 'ArrayMaterialQuantities',
0x7c0f89ad, 'ArrayFoodIds',
0x2a952a60, 'ArrayFoodQuantities',
0x6f743e98, 'ArrayFoodEffects',
0x9d3b5847, 'ArrayFoodEffectsHearts',
0x904a7213, 'ArrayFoodEffectsMultiplier',
0xf097cefa, 'ArrayFoodEffectsTime', // in seconds
0x2e470848, 'ArrayFoodEffectsUnknown1', //???
0xa86f2f10, 'ArrayDeviceIds',
0x60d16ab0, 'ArrayDeviceQuantities',
0x22c6530a, 'ArrayKeyIds',
0x60fac288, 'ArrayKeyQuantities',
0x7bde80e9, 'ArrayHorseIds',
0xd2ddb868, 'ArrayHorseNames'
],
OffsetsItems:{
'pouchSword':0x04aa64,
'pouchBow':0x047630,
'pouchShield':0x04d080
},
/* read/write data */
readArraySize:function(arrayOffset){
return tempFile.readU32(this.currentItemOffset + arrayOffset);
readU32:function(hashKey, arrayIndex){
if(typeof arrayIndex==='number')
return tempFile.readU32(SavegameEditor.Offsets[hashKey] + 0x04 + arrayIndex*0x04);
return tempFile.readU32(SavegameEditor.Offsets[hashKey]);
},
readU32Array:function(arrayOffset, index){
return tempFile.readU32(this.currentItemOffset + 0x04 + arrayOffset + index*4);
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+$/,'');
},
readString64Array:function(arrayOffset, index){
return tempFile.readString(this.currentItemOffset + 0x04 + arrayOffset + index*this.Constants.STRING64_SIZE, this.Constants.STRING64_SIZE);
},
readUTF8String64Array:function(arrayOffset, index){
readStringUTF8:function(hashKey, arrayIndex){
var offset=this.Offsets[hashKey];
if(typeof arrayIndex==='number')
offset+=0x04 + arrayIndex*0x20;
var str='';
var offset=this.currentItemOffset + 0x04 + arrayOffset + index*this.Constants.STRING_SIZE;
for(var i=0; i<this.Constants.STRING_SIZE/2; i++){
for(var i=0; i<0x20; i+=2){
var charCode=tempFile.readU16(offset);
if(!charCode)
break;
str+=String.fromCharCode(charCode);
offset+=2;
}
return str;
},
writeU32Array:function(arrayOffset, index, value){
tempFile.writeU32(this.currentItemOffset + 0x04 + arrayOffset + index*4, value);
},
writeString64Array:function(arrayOffset, index, value){
tempFile.writeString(this.currentItemOffset + 0x04 + arrayOffset + index*this.Constants.STRING64_SIZE, value, this.Constants.STRING64_SIZE);
return str.replace(/\u0000+$/,'');
},
_toHexInt:function(i){var s=i.toString(16);while(s.length<8)s='0'+s;return '0x'+s},
_writeBoolean:function(offset,val,arrayIndex){if(arrayIndex)tempFile.writeU32(offset+8*arrayIndex,val?1:0);else tempFile.writeU32(offset,val?1:0)},
_writeValue:function(offset,val,arrayIndex){if(arrayIndex)tempFile.writeU32(offset+8*arrayIndex,val);else tempFile.writeU32(offset,val)},
/*_writeFloat32:function(offset,val,arrayIndex){if(arrayIndex)tempFile.writeF32(offset+8*arrayIndex,val);else tempFile.writeF32(offset,val)},*/
_readArray:function(hashKey, arrayIndex, callback){
var arraySize=SavegameEditor.readU32(hashKey);
if(typeof arrayIndex==='number'){
if(arrayIndex>=0 && arrayIndex<arraySize)
return callback(hashKey, arrayIndex);
return null;
}
var elems=[];
for(var i=0; i<arraySize; i++){
elems.push(callback(hashKey, i));
}
return elems;
},
readU32Array:function(hashKey, arrayIndex){
return this._readArray(hashKey, arrayIndex, this.readU32);
},
readString64Array:function(hashKey, arrayIndex){
return this._readArray(hashKey, arrayIndex, this.readString64);
},
readStringUTF8Array:function(hashKey, arrayIndex){
return this._readArray(hashKey, arrayIndex, this.readStringUTF8);
},
writeU32:function(hashKey, arrayIndex, value){
if(typeof arrayIndex==='number')
tempFile.writeU32(this.Offsets[hashKey] + 0x04 + arrayIndex*0x04, value);
else
tempFile.writeU32(this.Offsets[hashKey], value);
},
writeString64:function(hashKey, arrayIndex, value){
if(typeof arrayIndex==='number')
tempFile.writeString(this.Offsets[hashKey] + 0x04 + arrayIndex*0x40, value, 0x40);
else
tempFile.writeString(this.Offsets[hashKey], value, 0x40);
},
writeF32:function(hashKey, arrayIndex, value){
if(typeof arrayIndex==='number')
tempFile.writeF32(this.Offsets[hashKey] + 0x04 + arrayIndex*0x04, value);
else
tempFile.writeF32(this.Offsets[hashKey], value);
},
/* private functions */
_searchHash:function(hash){
for(var i=0x0c; i<tempFile.fileSize; i+=8)
if(hash===tempFile.readU32(i))
return i;
return false;
},
_readFromHash:function(hash){
var offset=this._searchHash(hash);
if(typeof offset === 'number')
return tempFile.readU32(offset+4);
return false;
},
_writeValueAtHash:function(hash,val){
var offset=this._searchHash(hash);
if(typeof offset==='number')
this._writeValue(offset+4,val);
},
_toHexInt:function(i){var s=i.toString(16);while(s.length<8)s='0'+s;return '0x'+s},
_getOffsets:function(){
this.Offsets={};
this.Headers={};
var startSearchOffset=0x28;
for(var i=0; i<this.Hashes.length; i+=2){
for(var j=startSearchOffset; j<tempFile.fileSize; j+=8){
if(this.Hashes[i]===tempFile.readU32(j)){
this.Offsets[this.Hashes[i+1]]=j+4;
this.Headers[this.Hashes[i+1]]=this.Hashes[i];
startSearchOffset=j+8;
break;
}
for(var i=0x000028; i<0x03c800; i+=8){
var hash=tempFile.readU32(i);
var foundHashIndex=this.Hashes.indexOf(hash);
if(hash===0xa3db7114){ //looks like this hash is always the final one (at least in v1.0 and v1.1)
break;
}else if(foundHashIndex!==-1){
if(/^Array/.test(this.Hashes[foundHashIndex+1]))
this.Offsets[this.Hashes[foundHashIndex+1]]=tempFile.readU32(i+4);
else
this.Offsets[this.Hashes[foundHashIndex+1]]=i+4;
}
}
for(var i=0; i<this.Hashes.length; i+=2){
if(typeof this.Offsets[this.Hashes[i+1]] === 'undefined'){
console.log(this.Hashes[i+1]+' not found');
console.error('hash '+this.Hashes[i+1]+' not found');
}
}
},
@ -137,6 +184,9 @@ SavegameEditor={
spanItemId.className='item-name clickable';
spanItemId.id='item-name-'+item.category+'-'+item.index;
spanItemId.innerHTML=item.getItemTranslation();
if(item.getItemTranslation()===item.id){
spanItemId.style.color='red';
}
spanItemId.addEventListener('click', function(){
SavegameEditor.editItem(item);
}, false);
@ -152,6 +202,12 @@ SavegameEditor={
lastColumn.appendChild(item._htmlInputModifierValue);
}else if(item.quantity!==0xffffffff && (item.category==='arrows' || item.category==='materials' || item.category==='food' || item.category==='devices' || item.category==='key')){
lastColumn.appendChild(item._htmlInputQuantity);
if(item.category==='food'){
lastColumn.appendChild(item._htmlSelectFoodEffect);
lastColumn.appendChild(item._htmlSelectFoodEffectHearts);
lastColumn.appendChild(item._htmlSelectFoodEffectMultiplier);
lastColumn.appendChild(item._htmlSelectFoodEffectTime);
}
}
var r=row([1,6,3,2],
@ -168,46 +224,44 @@ SavegameEditor={
},
addItem:function(catId){
var categoryHash=capitalizeCategoryId(catId);
var maxItems=SavegameEditor.readU32('Array'+categoryHash+'Ids');
var lastItem=this.getLastItem(catId);
var newItem, maxItems;
if(catId==='weapons' || catId==='bows' || catId==='shields'){
newItem=new Equipment(catId, lastItem? lastItem.index+1 : 0);
if(lastItem){
newItem.durability=lastItem.durability;
newItem.modifier=lastItem.modifier;
newItem.modifierValue=lastItem.modifierValue;
newItem.fuseId=lastItem.fuseId;
}
maxItems=Equipment.readMaxCapacity(catId);
}else if(catId==='armors'){
newItem=new Armor(lastItem? lastItem.index+1 : 0);
if(lastItem){
newItem.dyeColor=lastItem.dyeColor;
}
maxItems=Armor.readMaxCapacity();
}else if(catId==='arrows' || catId==='materials' || catId==='food' || catId==='devices' || catId==='key'){
newItem=new Item(catId, lastItem? lastItem.index+1 : 0);
if(lastItem){
newItem.quantity=lastItem.quantity;
}
maxItems=Items.readMaxCapacity(catId);
var newId;
if(lastItem && lastItem.index===(maxItems -1)){
console.warn('not enough space in '+catId);
return false;
}
if(lastItem.index===(maxItems -1))
return false;
var itemList=this.getTranslationHash(newItem);
var itemList=this.getTranslationHash(catId);
var itemListArray=[];
for(var id in itemList){
itemListArray.push(id);
}
if(lastItem){
var nextIndexId=itemListArray.indexOf(lastItem.id)+1;
if(nextIndexId===itemListArray.length)
nextIndexId=0;
newId=itemListArray[nextIndexId];
}else{
newId=itemListArray[0];
}
var nextIndexId=itemListArray.indexOf(lastItem.id)+1;
if(nextIndexId===itemListArray.length)
nextIndexId=0;
newItem.id=itemListArray[nextIndexId];
var newItem, maxItems;
if(catId==='weapons' || catId==='bows' || catId==='shields'){
newItem=lastItem? lastItem.copy(lastItem.index+1, newId) : new Equipment(catId, 0, newId);
if(lastItem)
newItem.restoreDurability();
}else if(catId==='armors'){
newItem=lastItem? lastItem.copy(lastItem.index+1, newId) : new Armor(0, newId);
}else if(catId==='arrows' || catId==='materials' || catId==='food' || catId==='devices' || catId==='key'){
newItem=lastItem? lastItem.copy(lastItem.index+1, newId) : new Item(catId, 0, newId);
}
this.currentItems[catId].push(newItem);
var row=this._createItemRow(newItem);
@ -228,13 +282,13 @@ SavegameEditor={
return null;
},
getTranslationHash:function(item){
if(typeof item.durability==='number') //weapons, bows or shields
return Equipment.TRANSLATIONS[item.category];
else if(item.category==='armors')
getTranslationHash:function(catId){
if(catId==='weapons' || catId==='bows' || catId==='shields')
return Equipment.TRANSLATIONS[catId];
else if(catId==='armors')
return Armor.TRANSLATIONS;
else if(typeof item.quantity==='number') //arrows, materials, food, devices or key
return Items.TRANSLATIONS[item.category];
else if(catId==='arrows' || catId==='materials' || catId==='food' || catId==='devices' || catId==='key')
return Item.TRANSLATIONS[catId];
return null;
},
@ -244,28 +298,24 @@ SavegameEditor={
/* prepare edit item selector */
if(this.selectItem.lastCategory !== item.category){
this.selectItem.innerHTML='';
var foundItemId=false;
var itemList=this.getTranslationHash(item);
var itemList=this.getTranslationHash(item.category);
for(var itemId in itemList){
var opt=document.createElement('option');
opt.value=itemId;
opt.innerHTML=itemList[itemId];
if(itemId===item.id)
foundItemId=true;
this.selectItem.appendChild(opt);
}
if(!foundItemId){
var opt=document.createElement('option');
opt.value=item.id;
opt.innerHTML=item.id;
this.selectItem.appendChild(opt);
}
this.selectItem.lastCategory=item.category;
}
this.selectItem.value=item.id;
if(!this.selectItem.value){
var opt=document.createElement('option');
opt.value=item.id;
opt.innerHTML='Unknown: '+item.id;
this.selectItem.appendChild(opt);
this.selectItem.value=item.id;
}
document.getElementById('item-name-'+item.category+'-'+item.index).innerHTML='';
document.getElementById('item-name-'+item.category+'-'+item.index).parentElement.appendChild(this.selectItem);
@ -284,6 +334,12 @@ SavegameEditor={
// document.getElementById('number-item'+i).maxValue=this._getItemMaximumQuantity(newId);
},
restoreDurability:function(catId){
this.currentItems[catId].forEach(function(equipment, i){
equipment.restoreDurability();
});
},
/* check if savegame is valid */
checkValidSavegame:function(){
tempFile.littleEndian=true;
@ -321,7 +377,6 @@ SavegameEditor={
if(tempFile.fileSize===this.Constants.FILESIZE[i] && dummyHeader===0x01020304 && versionHash===this.Constants.HEADER[i]){
this._getOffsets();
this.currentItemOffset=(SavegameEditor.Offsets.TempItemData - 0x04) - 0x3c048;
setValue('version', this.Constants.VERSION[i]);
return true;
}
@ -343,6 +398,7 @@ SavegameEditor={
}, false);
setNumericRange('rupees', 0, 999999);
setNumericRange('pony-points', 0, 999999);
setNumericRange('pouch-size-swords', 9, 18);
setNumericRange('pouch-size-bows', 5, 13);
@ -364,7 +420,7 @@ SavegameEditor={
});
var horseTypes=[];
Horses.HORSE_TYPES.forEach(function(id){
Horse.HORSE_TYPES.forEach(function(id){
horseTypes.push({name:id, value:id});
});
for(var i=0; i<6; i++){
@ -452,6 +508,8 @@ SavegameEditor={
load:function(){
tempFile.fileName='progress.sav';
this.selectItem.lastCategory=null;
/* empty item containers */
var ITEM_CATS=['weapons','bows','shields','armors','arrows','materials','food','devices','key'];
ITEM_CATS.forEach(function(catId, i){
@ -464,30 +522,27 @@ SavegameEditor={
'bows':Equipment.readAll('bows'),
'shields':Equipment.readAll('shields'),
'armors':Armor.readAll(),
'arrows':Items.readAll('arrows'),
'materials':Items.readAll('materials'),
'food':Items.readAll('food'),
'devices':Items.readAll('devices'),
'key':Items.readAll('key'),
'arrows':Item.readAll('arrows'),
'materials':Item.readAll('materials'),
'food':Item.readAll('food'),
'devices':Item.readAll('devices'),
'key':Item.readAll('key'),
'horses':Horses.readAll(),
'pouchSword':SavegameEditor.readU32Array(SavegameEditor.OffsetsItems.pouchSword, 0),
'pouchBow':SavegameEditor.readU32Array(SavegameEditor.OffsetsItems.pouchBow, 0),
'pouchShield':SavegameEditor.readU32Array(SavegameEditor.OffsetsItems.pouchShield, 0)
'horses':Horse.readAll()
};
/* prepare editor */
setValue('rupees', tempFile.readU32(this.Offsets.TempRupees));
/*setValue('mons', tempFile.readU32(this.Offsets.MONS));*/
setValue('max-hearts', tempFile.readU32(this.Offsets.TempMaxHearts));
setValue('max-stamina', tempFile.readU32(this.Offsets.TempStamina));
setValue('rupees', this.readU32('CurrentRupees'));
/*setValue('mons', this.readU32(this.Offsets.MONS));*/
setValue('max-hearts', this.readU32('MaxHearts'));
setValue('max-stamina', this.readU32('MaxStamina'));
setValue('pony-points', this.readU32('PonyPoints'));
setValue('number-pouch-size-swords', this.currentItems.pouchSword);
setValue('number-pouch-size-bows', this.currentItems.pouchBow);
setValue('number-pouch-size-shields', this.currentItems.pouchShield);
setValue('number-pouch-size-swords', this.readU32Array('ArrayPouchSwords', 0));
setValue('number-pouch-size-bows', this.readU32Array('ArrayPouchBows', 0));
setValue('number-pouch-size-shields', this.readU32Array('ArrayPouchShields', 0));
setValue('playtime', this._timeToString(tempFile.readU32(this.Offsets.TempPlaytime)));
setValue('playtime', this._timeToString(this.readU32('Playtime')));
/*setValue('relic-gerudo', tempFile.readU32(this.Offsets.RELIC_GERUDO));
setValue('relic-goron', tempFile.readU32(this.Offsets.RELIC_GORON));
@ -570,14 +625,15 @@ SavegameEditor={
/* save function */
save:function(){
/* STATS */
tempFile.writeU32(this.Offsets.TempRupees, getValue('rupees'));
/*tempFile.writeU32(this.Offsets.MONS, getValue('mons'));*/
tempFile.writeU32(this.Offsets.TempMaxHearts, getValue('max-hearts'));
tempFile.writeU32(this.Offsets.TempStamina, getValue('max-stamina'));
this.writeU32('CurrentRupees', null, getValue('rupees'));
/*this.writeU32('Mons', getValue('mons'));*/
this.writeU32('MaxHearts', null, getValue('max-hearts'));
this.writeU32('MaxStamina', null, getValue('max-stamina'));
this.writeU32('PonyPoints', null, getValue('pony-points'));
SavegameEditor.writeU32Array(this.OffsetsItems.pouchSword, 0, this.currentItems.pouchSword);
SavegameEditor.writeU32Array(this.OffsetsItems.pouchBow, 0, this.currentItems.pouchBow);
SavegameEditor.writeU32Array(this.OffsetsItems.pouchShield, 0, this.currentItems.pouchShield);
this.writeU32('ArrayPouchSwords', 0, getValue('pouch-size-swords'));
this.writeU32('ArrayPouchBows', 0, getValue('pouch-size-bows'));
this.writeU32('ArrayPouchShields', 0, getValue('pouch-size-shields'));
/*tempFile.writeU32(this.Offsets.RELIC_GERUDO, getValue('relic-gerudo'));
tempFile.writeU32(this.Offsets.RELIC_GORON, getValue('relic-goron'));
@ -611,9 +667,9 @@ SavegameEditor={
/* HORSES */
SavegameEditor.currentItems.horses.forEach(function(horse, j){
horse.save();
});
for(var i=0; i<SavegameEditor.currentItems.horses.length; i++){
SavegameEditor.currentItems.horses[i].save();
}
}
}
@ -662,7 +718,9 @@ window.addEventListener('scroll', onScroll, false);
function capitalizeCategoryId(catId){
return (catId.charAt(0).toUpperCase() + catId.substr(1)).replace(/s$/, '')
}

View File

@ -119,12 +119,12 @@ function createHashInput(container, hashId, type, offset){
function setBoolean(){
SavegameEditor._writeBoolean(this.offset, this.checked, 0);
tempFile.writeU32(this.offset, this.checked? 1: 0);
}
function setS32(){
SavegameEditor._writeValue(this.offset, this.value, 0);
tempFile.writeU32(this.offset, parseInt(this.value));
}
function setF32(){
/*function setF32(){
SavegameEditor._writeFloat32(this.offset, this.value, 0);
}
function setString(){
@ -135,7 +135,7 @@ function setString64(){
}
function setString256(){
SavegameEditor._writeString256(this.offset, this.value);
}
}*/
var TOTKMasterEditor=(function(){
var HASHES_PER_PAGE=100;