public static void ReplaceGetItemTable() { ResourceUtils.ApplyHack(Resources.mods.replace_gi_table); int last_file = RomData.MMFileList.Count - 1; GET_ITEM_TABLE = RomUtils.AddNewFile(Resources.mods.gi_table); ReadWriteUtils.WriteToROM(0xBDAEAC, (uint)last_file + 1); ResourceUtils.ApplyHack(Resources.mods.update_chests); RomUtils.AddNewFile(Resources.mods.chest_table); ReadWriteUtils.WriteToROM(0xBDAEA8, (uint)last_file + 2); ResourceUtils.ApplyHack(Resources.mods.standing_hearts); ResourceUtils.ApplyHack(Resources.mods.fix_item_checks); SceneUtils.ResetSceneFlagMask(); SceneUtils.UpdateSceneFlagMask(0x5B); // red potion SceneUtils.UpdateSceneFlagMask(0x91); // chateau romani SceneUtils.UpdateSceneFlagMask(0x92); // milk SceneUtils.UpdateSceneFlagMask(0x93); // gold dust }
public static void ReplaceGetItemTable() { ResourceUtils.ApplyHack(Values.ModsDirectory, "replace-gi-table"); int last_file = RomData.MMFileList.Count - 1; GET_ITEM_TABLE = RomUtils.AddNewFile(Values.ModsDirectory, "gi-table"); ReadWriteUtils.WriteToROM(0xBDAEAC, (uint)last_file + 1); ResourceUtils.ApplyHack(Values.ModsDirectory, "update-chests"); RomUtils.AddNewFile(Values.ModsDirectory, "chest-table"); ReadWriteUtils.WriteToROM(0xBDAEA8, (uint)last_file + 2); ResourceUtils.ApplyHack(Values.ModsDirectory, "standing-hearts"); ResourceUtils.ApplyHack(Values.ModsDirectory, "fix-item-checks"); cycle_repeat = 0xC72DF4; SceneUtils.ResetSceneFlagMask(); SceneUtils.UpdateSceneFlagMask(0x5B); // red potion SceneUtils.UpdateSceneFlagMask(0x91); // chateau romani SceneUtils.UpdateSceneFlagMask(0x92); // milk SceneUtils.UpdateSceneFlagMask(0x93); // gold dust }
private static void UpdateChest(Item location, Item item, ChestTypeAttribute.ChestType?overrideChestType) { var chestType = item.GetAttribute <ChestTypeAttribute>().Type; if (overrideChestType.HasValue) { chestType = overrideChestType.Value; } var chestAttribute = location.GetAttribute <ChestAttribute>(); if (chestAttribute != null) { foreach (var address in chestAttribute.Addresses) { var chestVariable = ReadWriteUtils.Read(address); chestVariable &= 0x0F; // remove existing chest type var newChestType = ChestAttribute.GetType(chestType, chestAttribute.Type); newChestType <<= 4; chestVariable |= newChestType; ReadWriteUtils.WriteToROM(address, chestVariable); } } var grottoChestAttribute = location.GetAttribute <GrottoChestAttribute>(); if (grottoChestAttribute != null) { foreach (var address in grottoChestAttribute.Addresses) { var grottoVariable = ReadWriteUtils.Read(address); grottoVariable &= 0x1F; // remove existing chest type var newChestType = (byte)chestType; newChestType <<= 5; grottoVariable |= newChestType; // add new chest type ReadWriteUtils.WriteToROM(address, grottoVariable); } } }
private static void UpdateShop(Item location, Item item, List <MessageEntry> newMessages) { var newItem = RomData.GetItemList[item.GetItemIndex().Value]; var shopRooms = location.GetAttributes <ShopRoomAttribute>(); foreach (var shopRoom in shopRooms) { ReadWriteUtils.WriteToROM(shopRoom.RoomObjectAddress, (ushort)newItem.Object); } var shopInventories = location.GetAttributes <ShopInventoryAttribute>(); foreach (var shopInventory in shopInventories) { ReadWriteUtils.WriteToROM(shopInventory.ShopItemAddress, (ushort)newItem.Object); var index = newItem.Index > 0x7F ? (byte)(0xFF - newItem.Index) : (byte)(newItem.Index - 1); ReadWriteUtils.WriteToROM(shopInventory.ShopItemAddress + 0x03, index); var shopTexts = item.ShopTexts(); string description; switch (shopInventory.Keeper) { case ShopInventoryAttribute.ShopKeeper.WitchShop: description = shopTexts.WitchShop; break; case ShopInventoryAttribute.ShopKeeper.TradingPostMain: description = shopTexts.TradingPostMain; break; case ShopInventoryAttribute.ShopKeeper.TradingPostPartTimer: description = shopTexts.TradingPostPartTimer; break; case ShopInventoryAttribute.ShopKeeper.CuriosityShop: description = shopTexts.CuriosityShop; break; case ShopInventoryAttribute.ShopKeeper.BombShop: description = shopTexts.BombShop; break; case ShopInventoryAttribute.ShopKeeper.ZoraShop: description = shopTexts.ZoraShop; break; case ShopInventoryAttribute.ShopKeeper.GoronShop: description = shopTexts.GoronShop; break; case ShopInventoryAttribute.ShopKeeper.GoronShopSpring: description = shopTexts.GoronShopSpring; break; default: description = null; break; } if (description == null) { description = shopTexts.Default; } var messageId = ReadWriteUtils.ReadU16(shopInventory.ShopItemAddress + 0x0A); newMessages.Add(new MessageEntry { Id = messageId, Header = null, Message = MessageUtils.BuildShopDescriptionMessage(item.Name(), 20, description) }); newMessages.Add(new MessageEntry { Id = (ushort)(messageId + 1), Header = null, Message = MessageUtils.BuildShopPurchaseMessage(item.Name(), 20, item) }); } }
public static void WriteNewItem(Item location, Item item, List <MessageEntry> newMessages, bool updateShop, bool preventDowngrades, bool updateChest, ChestTypeAttribute.ChestType?overrideChestType, bool isExtraStartingItem) { System.Diagnostics.Debug.WriteLine($"Writing {item.Name()} --> {location.Location()}"); int f = RomUtils.GetFileIndexForWriting(GET_ITEM_TABLE); int baseaddr = GET_ITEM_TABLE - RomData.MMFileList[f].Addr; var getItemIndex = location.GetItemIndex().Value; int offset = (getItemIndex - 1) * 8 + baseaddr; var newItem = isExtraStartingItem ? Items.RecoveryHeart // Warning: this will not work well for starting with Bottle contents (currently impossible), because you'll first have to acquire the Recovery Heart before getting the bottle-less version. Also may interfere with future implementation of progressive upgrades. : RomData.GetItemList[item.GetItemIndex().Value]; var fileData = RomData.MMFileList[f].Data; var data = new byte[] { newItem.ItemGained, newItem.Flag, newItem.Index, newItem.Type, (byte)(newItem.Message >> 8), (byte)(newItem.Message & 0xFF), (byte)(newItem.Object >> 8), (byte)(newItem.Object & 0xFF), }; ReadWriteUtils.Arr_Insert(data, 0, data.Length, fileData, offset); // todo use Logic Editor to handle which locations should be repeatable and which shouldn't. if ((item.IsCycleRepeatable() && location != Item.HeartPieceNotebookMayor) || (item.Name().Contains("Rupee") && location.IsRupeeRepeatable())) { ReadWriteUtils.WriteToROM(cycle_repeat, (ushort)getItemIndex); cycle_repeat += 2; cycle_repeat_count += 2; ReadWriteUtils.WriteToROM(cycle_repeat_count_address, cycle_repeat_count); } var isRepeatable = item.IsRepeatable() || (!preventDowngrades && item.IsDowngradable()); if (!isRepeatable) { SceneUtils.UpdateSceneFlagMask(getItemIndex); } if (item == Item.ItemBottleWitch) { ReadWriteUtils.WriteToROM(0xB4997E, (ushort)getItemIndex); ReadWriteUtils.WriteToROM(0xC72B42, (ushort)getItemIndex); } if (item == Item.ItemBottleMadameAroma) { ReadWriteUtils.WriteToROM(0xB4998A, (ushort)getItemIndex); ReadWriteUtils.WriteToROM(0xC72B4E, (ushort)getItemIndex); } if (item == Item.ItemBottleAliens) { ReadWriteUtils.WriteToROM(0xB49996, (ushort)getItemIndex); ReadWriteUtils.WriteToROM(0xC72B5A, (ushort)getItemIndex); } if (item == Item.ItemBottleGoronRace) { ReadWriteUtils.WriteToROM(0xB499A2, (ushort)getItemIndex); ReadWriteUtils.WriteToROM(0xC72B66, (ushort)getItemIndex); } if (updateChest) { UpdateChest(location, item, overrideChestType); } if (location != item) { if (updateShop) { UpdateShop(location, item, newMessages); } if (location == Item.StartingSword) { ResourceUtils.ApplyHack(Values.ModsDirectory, "fix-sword-song-of-time"); } if (location == Item.MundaneItemSeahorse) { ResourceUtils.ApplyHack(Values.ModsDirectory, "fix-fisherman"); } if (location == Item.MaskFierceDeity) { ResourceUtils.ApplyHack(Values.ModsDirectory, "fix-fd-mask-reset"); } } }
public static void ResetSceneFlagMask() { ReadWriteUtils.WriteToROM(SCENE_FLAG_MASKS, (uint)0); ReadWriteUtils.WriteToROM(SCENE_FLAG_MASKS + 0xC, (uint)0); }
/// <summary> /// Apply all patches for Adult Link mod provided by SkilarsArt. /// </summary> public static void ApplyAdultLinkPatches() { // Overwrite segmented address: 0x0601E244 ReadWriteUtils.WriteU32ToROM(0xC56350, 0x060122C4); // Physical: 0xBA5E70 // ??? ReadWriteUtils.WriteU16ToROM(0xC5637E, 0x02BC); ReadWriteUtils.WriteU16ToROM(0xC56380, 0x0226); ReadWriteUtils.WriteU16ToROM(0xC56382, 0x010E); ReadWriteUtils.WriteU16ToROM(0xC56384, 0x02BC); ReadWriteUtils.WriteU16ToROM(0xC56386, 0x012C); ReadWriteUtils.WriteU16ToROM(0xC5638C, 0x0258); ReadWriteUtils.WriteU16ToROM(0xC56392, 0x024E); ReadWriteUtils.WriteU16ToROM(0xC56398, 0x00C8); ReadWriteUtils.WriteU16ToROM(0xC5639A, 0x0082); // Write chunk of segmented addresses for player model. ReadWriteUtils.WriteToROM(0xC5653C, Resources.models.adult_link_code_segaddrs); // Update sword hitbox? { // Kokiri Sword: 3000.0 => 4000.0 ReadWriteUtils.WriteU32ToROM(0xC572BC, 0x457A0000); // Razor Sword: 3000.0 => 4000.0 ReadWriteUtils.WriteU32ToROM(0xC572C0, 0x457A0000); // Gilded Sword: 4000.0 => 5500.0 ReadWriteUtils.WriteU32ToROM(0xC572C4, 0x45ABE000); // Great Fairy's Sword: 5500.0 => 5500.0 ReadWriteUtils.WriteU32ToROM(0xC572C8, 0x45ABE000); } // Overwrite segmented address: 0x06017818 => 0x0601BE60 ReadWriteUtils.WriteU32ToROM(0xC572D4, 0x0601BE60); // Overwrite function pointer: 0x800B7078 => 0x800B7058 // Last function in function pointer array (length 5) at RDRAM: 0x801DCA58 // Old function: F0 = F2 + 44.0 // New function: F0 = F2 + 68.0 ReadWriteUtils.WriteU32ToROM(0xC72FA8, 0x800B7058); // Physical: 0xBC2AC8 // Patch player_actor. { // Replace floats? Physical: 0xC25D38 ReadWriteUtils.WriteU32ToROM(0xCD6218, 0x42600000); // 40.0 => 56.0 ReadWriteUtils.WriteU32ToROM(0xCD621C, 0x42B40000); // 60.0 => 90.0 ReadWriteUtils.WriteU32ToROM(0xCD6220, 0x3F800000); // 0.647 => 1.0 ReadWriteUtils.WriteU32ToROM(0xCD6224, 0x42DE0000); // 71.0 => 111.0 ReadWriteUtils.WriteU32ToROM(0xCD6228, 0x428C0000); // 50.0 => 70.0 ReadWriteUtils.WriteU32ToROM(0xCD622C, 0x429ECCCD); // 49.0 => 79.4 ReadWriteUtils.WriteU32ToROM(0xCD6230, 0x426C0000); // 39.0 => 59.0 ReadWriteUtils.WriteU32ToROM(0xCD6234, 0x42240000); // 27.0 => 41.0 ReadWriteUtils.WriteU32ToROM(0xCD6238, 0x41980000); // 19.0 ReadWriteUtils.WriteU32ToROM(0xCD623C, 0x42100000); // 22.0 => 36.0 ReadWriteUtils.WriteU32ToROM(0xCD6240, 0x42480000); // 32.4 => 50.0 ReadWriteUtils.WriteU32ToROM(0xCD6244, 0x42600000); // 32.0 => 56.0 ReadWriteUtils.WriteU32ToROM(0xCD6248, 0x42880000); // 48.0 => 68.0 ReadWriteUtils.WriteU32ToROM(0xCD624C, 0x428C0000); // 45.294 => 70.0 ReadWriteUtils.WriteU32ToROM(0xCD6250, 0x41900000); // 14.0 => 18.0 ReadWriteUtils.WriteU32ToROM(0xCD6254, 0x41B80000); // 12.0 => 23.0 ReadWriteUtils.WriteU32ToROM(0xCD6258, 0x428C0000); // 55.0 => 70.0 // Replace unknown data chunk immediately following floats. Physical: 0xC25D7C // This affects Link's voice (child voice to adult voice). ReadWriteUtils.WriteToROM(0xCD625C, Resources.models.adult_link_player_actor_data_1); // Replace segmented addresses following previous chunk. Physical: 0xC25DD8 ReadWriteUtils.WriteToROM(0xCD62B8, Resources.models.adult_link_player_actor_data_2); } // Patch Arms_Hook (Hookshot) actor. { // Update segmented address in code: 0x0602D960 => 0x06029D60 // Old: addiu t0, 0x0602 // addiu t0, t0, 0xD960 // New: addiu t0, 0x0602 // addiu t0, t0, 0x9D60 ReadWriteUtils.WriteU32ToROM(0xD3BC50, 0x25089D60); // Physical: 0xC8B770 } // Patch En_Zog (Mikau) actor. { // Update instruction to use new distance to allow pushing Mikau from: 40.0 => 96.0 // Old: lui at, 0x4220 // New: lui at, 0x42C0 ReadWriteUtils.WriteU32ToROM(0xFFA188, 0x3C0142C0); // Physical: 0xF49CA8 } // Patch vertex data and DLists in gameplay_keep. { // Write vertex buffer (1). ReadWriteUtils.WriteToROM(0x10B0510, Resources.models.adult_link_gameplay_keep_vtx_1); // Physical: 0xFFFFB0 // Write vertex buffer (2). ReadWriteUtils.WriteToROM(0x10B0A90, Resources.models.adult_link_gameplay_keep_vtx_2); // Physical: 0x1000530 // Write vertex buffer (3). ReadWriteUtils.WriteToROM(0x10B1810, Resources.models.adult_link_gameplay_keep_vtx_3); // Physical: 0x10012B0 // Replace DList instruction: G_RDPPIPESYNC => G_DL ReadWriteUtils.WriteU64ToROM(0x10B18F0, 0xDE0000000405A2E0); // gsSPDisplayList(0x0405A2E0); // Write vertex buffer (4). ReadWriteUtils.WriteToROM(0x10E52A0, Resources.models.adult_link_gameplay_keep_vtx_4); // Physical: 0x1034D40 // Replace vertex data with small DList (offset 0x5A2E0). ReadWriteUtils.WriteU64ToROM(0x10E52E0, 0xE700000000000000); // gsDPPipeSync(); ReadWriteUtils.WriteU64ToROM(0x10E52E8, 0xDA3800010405A2A0); // gsSPMatrix(G_MTX_NOPUSH, 0x0405A2A0); ReadWriteUtils.WriteU64ToROM(0x10E52F0, 0xDF00000000000000); // gsSPEndDisplayList(); } // Patch DLists for masks (field models). { // Update Keaton Mask to call gameplay_keep DList: gsDPPipeSync() => gsSPDisplayList(0x0405A2E0) ReadWriteUtils.WriteU64ToROM(0x11B14A8, 0xDE0000000405A2E0); // Physical: 0x10FB618 // Update Bunny Hood to call gameplay_keep DLists. ReadWriteUtils.WriteU64ToROM(0x11B2620, 0xDE0000000405A2E0); // Physical: 0x10FC260 ReadWriteUtils.WriteU64ToROM(0x11B2780, 0xDE0000000405A2E8); // Physical: 0x10FC3C0 ReadWriteUtils.WriteU64ToROM(0x11B27B0, 0xDE0000000405A2E0); // Physical: 0x10FC3F0 ReadWriteUtils.WriteU64ToROM(0x11B2890, 0xDE0000000405A2E8); // Physical: 0x10FC4D0 ReadWriteUtils.WriteU64ToROM(0x11B28C0, 0xDE0000000405A2E0); // Physical: 0x10FC500 // Update Mask of Scents to call gameplay_keep DList: gsDPPipeSync() => gsSPDisplayList(0x0405A2E0) ReadWriteUtils.WriteU64ToROM(0x11D6718, 0xDE0000000405A2E0); // Physical: 0x11175B8 } // Replace En_Horse actor (Epona). ReadWriteUtils.WriteToROM(0xCF5950, Resources.models.adult_en_horse); // Insert player model object. ObjUtils.InsertObj(Resources.models.adult_obj_link, 0x11); // Insert adult Epona model object. ObjUtils.InsertObj(Resources.models.adult_obj_epona, 0x7D); }
private static void UpdateShop(ItemObject itemObject, List <MessageEntry> newMessages) { var location = itemObject.NewLocation.Value; GetItemEntry newItem; if (itemObject.Mimic != null) { newItem = RomData.GetItemList[itemObject.Mimic.Item.GetItemIndex().Value]; } else if (itemObject.Item.IsExclusiveItem()) { newItem = itemObject.Item.ExclusiveItemEntry(); } else { newItem = RomData.GetItemList[itemObject.Item.GetItemIndex().Value]; } var shopRooms = location.GetAttributes <ShopRoomAttribute>(); foreach (var shopRoom in shopRooms) { ReadWriteUtils.WriteToROM(shopRoom.RoomObjectAddress, (ushort)newItem.Object); } var shopInventories = location.GetAttributes <ShopInventoryAttribute>(); foreach (var shopInventory in shopInventories) { ReadWriteUtils.WriteToROM(shopInventory.ShopItemAddress, (ushort)newItem.Object); var index = newItem.Index > 0x7F ? (byte)(0xFF - newItem.Index) : (byte)(newItem.Index - 1); ReadWriteUtils.WriteToROM(shopInventory.ShopItemAddress + 0x03, index); var messageId = ReadWriteUtils.ReadU16(shopInventory.ShopItemAddress + 0x0A); newMessages.Add(new MessageEntryBuilder() .Id(messageId) .Message(it => { it.Red(() => { it.RuntimeItemName(itemObject.DisplayName(), location).Text(": ").Text("20 Rupees").NewLine(); }) .RuntimeWrap(() => { it.RuntimeItemDescription(itemObject.DisplayItem, shopInventory.Keeper, location); }) .DisableTextBoxClose() .EndFinalTextBox(); }) .Build() ); newMessages.Add(new MessageEntryBuilder() .Id((ushort)(messageId + 1)) .Message(it => { it.RuntimeItemName(itemObject.DisplayName(), location).Text(": ").Text("20 Rupees").NewLine() .Text(" ").NewLine() .StartGreenText() .TwoChoices() .Text("I'll buy ").RuntimePronoun(itemObject.DisplayItem, location).NewLine() .Text("No thanks") .EndFinalTextBox(); }) .Build() ); } }