public static void WriteNewItem(ItemObject itemObject, List <MessageEntry> newMessages, GameplaySettings settings, ChestTypeAttribute.ChestType?overrideChestType, MessageTable messageTable, ExtendedObjects extendedObjects) { var item = itemObject.Item; var location = itemObject.NewLocation.Value; System.Diagnostics.Debug.WriteLine($"Writing {item.Name()} --> {location.Location()}"); if (!itemObject.IsRandomized) { var indices = location.GetCollectableIndices(); if (indices.Any()) { foreach (var collectableIndex in location.GetCollectableIndices()) { ReadWriteUtils.Arr_WriteU16(RomData.MMFileList[COLLECTABLE_TABLE_FILE_INDEX].Data, collectableIndex * 2, 0); } return; } } 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 fileData = RomData.MMFileList[f].Data; GetItemEntry newItem; if (!itemObject.IsRandomized && location.IsNullableItem()) { newItem = new GetItemEntry(); } else if (item.IsExclusiveItem()) { newItem = item.ExclusiveItemEntry(); } else { newItem = RomData.GetItemList[item.GetItemIndex().Value]; } // Attempt to resolve extended object Id, which should affect "Exclusive Items" as well. var objectId = extendedObjects.ResolveObjectId(newItem); if (objectId.HasValue) { newItem.Object = objectId.Value; } 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); if (location.IsRupeeRepeatable()) { settings.AsmOptions.MMRConfig.RupeeRepeatableLocations.Add(getItemIndex); } var isRepeatable = item.IsRepeatable() || (!settings.PreventDowngrades && item.IsDowngradable()); if (settings.ProgressiveUpgrades && item.HasAttribute <ProgressiveAttribute>()) { isRepeatable = false; } if (item.IsReturnable(settings)) { isRepeatable = false; settings.AsmOptions.MMRConfig.ItemsToReturnIds.Add(getItemIndex); } if (!isRepeatable) { SceneUtils.UpdateSceneFlagMask(getItemIndex); } if (settings.UpdateChests) { UpdateChest(location, item, overrideChestType); } if (settings.UpdateShopAppearance) { UpdateShop(itemObject, newMessages, messageTable); } if (itemObject.IsRandomized) { var hackContentAttributes = location.GetAttributes <HackContentAttribute>(); if (location == item) { hackContentAttributes = hackContentAttributes.Where(h => !h.ApplyOnlyIfItemIsDifferent); } foreach (var hackContent in hackContentAttributes.Select(h => h.HackContent)) { ResourceUtils.ApplyHack(hackContent); } } }
/// <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); }
public static void ResetSceneFlagMask() { ReadWriteUtils.WriteToROM(SCENE_FLAG_MASKS, (uint)0); ReadWriteUtils.WriteToROM(SCENE_FLAG_MASKS + 0xC, (uint)0); }
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 UpdateKafeiTunic(ref byte[] objectData, Color targetColor) { int[] kafeiPaletteAddress = new int[] { 0xB538, 0xDF68, 0xDF68, 0xD1B0 }; int[] hairPaletteAvoid = new int[] { 0, 4, 5, 6, 7, 9, 0xB, 0x14, 0x17, 0x18, 0x22, 0x23, 0x31, 0x32, 0x3E, 0x6B, 0x71, 0x8E, 0x100 }; var averageColour = new Color(); for (int i = 0; i < 4; i++) { var colourMask = new bool[0x100]; var coloursFound = new Color[0x100]; var k = 0; for (int j = 0; j < 0x100; j++) { var thisColour = ColorUtils.FromRGBA5551(ReadWriteUtils.Arr_ReadU16(objectData, kafeiPaletteAddress[i] + (j << 1))); // separate palette colours used for hair/clothes or they won't adjust very well if (i == 1) { if (j == hairPaletteAvoid[k]) { colourMask[j] = true; coloursFound[k] = thisColour; k++; } ; } else if (i == 2) { if (j != hairPaletteAvoid[j - k]) { colourMask[j] = true; coloursFound[k] = thisColour; k++; } ; } else if ((thisColour.B != 0) && ((thisColour.G == 0) || ((i != 0) && (thisColour.B > thisColour.G) && (thisColour.B > thisColour.R) && (thisColour.B + thisColour.R > 2.2 * thisColour.G)))) { colourMask[j] = true; coloursFound[k] = thisColour; k++; } ; } ; if (i != 1) { averageColour = ColorUtils.GetAverageColour(coloursFound, k); } ; coloursFound = ShiftHue(coloursFound, targetColor, k, averageColour); k = 0; for (int j = 0; j < 0x100; j++) { if (colourMask[j]) { ReadWriteUtils.Arr_WriteU16(objectData, kafeiPaletteAddress[i] + (j << 1), ColorUtils.ToRGBA5551(coloursFound[k])); k++; } } } }
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() ); } }
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); } if (item == Item.ItemBottleMadameAroma) { ReadWriteUtils.WriteToROM(0xB4998A, (ushort)getItemIndex); } if (item == Item.ItemBottleAliens) { ReadWriteUtils.WriteToROM(0xB49996, (ushort)getItemIndex); } if (item == Item.ItemBottleGoronRace) { ReadWriteUtils.WriteToROM(0xB499A2, (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 WriteNewItem(ItemObject itemObject, List <MessageEntry> newMessages, GameplaySettings settings, ChestTypeAttribute.ChestType?overrideChestType) { var item = itemObject.Item; var location = itemObject.NewLocation.Value; 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 fileData = RomData.MMFileList[f].Data; GetItemEntry newItem; if (item.IsExclusiveItem()) { newItem = item.ExclusiveItemEntry(); } else { newItem = RomData.GetItemList[item.GetItemIndex().Value]; } 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. var isCycleRepeatable = item.IsCycleRepeatable(); if (item.Name().Contains("Rupee") && location.IsRupeeRepeatable()) { isCycleRepeatable = true; } if (item.ToString().StartsWith("Trade") && settings.QuestItemStorage) { isCycleRepeatable = false; } if (isCycleRepeatable) { settings.AsmOptions.MMRConfig.CycleRepeatableLocations.Add(getItemIndex); } var isRepeatable = item.IsRepeatable() || (!settings.PreventDowngrades && item.IsDowngradable()); if (settings.ProgressiveUpgrades && item.HasAttribute <ProgressiveAttribute>()) { isRepeatable = false; } if (!isRepeatable) { SceneUtils.UpdateSceneFlagMask(getItemIndex); } if (settings.UpdateChests) { UpdateChest(location, item, overrideChestType); } if (settings.UpdateShopAppearance) { UpdateShop(itemObject, newMessages); } if (location != item) { if (location == Item.StartingSword) { ResourceUtils.ApplyHack(Resources.mods.fix_sword_song_of_time); } if (location == Item.MundaneItemSeahorse) { ResourceUtils.ApplyHack(Resources.mods.fix_fisherman); } if (location == Item.MaskFierceDeity) { ResourceUtils.ApplyHack(Resources.mods.fix_fd_mask_reset); } } }
public static void ReadInstrumentSetList() { // traverse the whole audiobank index and grab details about every bank // use those details to generate a list from the vanilla game that we can modify as needed List <InstrumentSetInfo> InstrumentSets = new List <InstrumentSetInfo>(); for (int inst_set_num = 0; inst_set_num <= 0x28; ++inst_set_num) { // each bank has one 16 byte sentence of data, first word is address, second length, last 8 bytes metadata int table_pointer_addr = Addresses.AudiobankTable + (16 * inst_set_num); int bank_location = (ReadWriteUtils.ReadU16(table_pointer_addr) << 16) + ReadWriteUtils.ReadU16(table_pointer_addr + 2); int bank_length = (ReadWriteUtils.ReadU16(table_pointer_addr + 4) << 16) + ReadWriteUtils.ReadU16(table_pointer_addr + 6); byte[] metadata = new byte[8]; for (int b = 0; b < 8; ++b) { metadata[b] = ReadWriteUtils.Read(table_pointer_addr + 8 + b); } byte[] bank_data = new byte[bank_length]; for (int b = 0; b < bank_length; ++b) { bank_data[b] = ReadWriteUtils.Read(Addresses.Audiobank + bank_location + b); } InstrumentSetInfo new_bank = new InstrumentSetInfo { BankSlot = inst_set_num, BankMetaData = metadata, BankBinary = bank_data }; InstrumentSets.Add(new_bank); } RomData.InstrumentSetList = InstrumentSets; }
// gets passed RomData.SequenceList in Builder.cs::WriteAudioSeq public static void RebuildAudioSeq(List <SequenceInfo> SequenceList, OutputSettings _settings) { // spoiler log output DEBUG StringBuilder log = new StringBuilder(); void WriteOutput(string str) { Debug.WriteLine(str); // we still want debug output though log.AppendLine(str); } List <MMSequence> OldSeq = new List <MMSequence>(); int f = RomUtils.GetFileIndexForWriting(Addresses.SeqTable); int basea = RomData.MMFileList[f].Addr; for (int i = 0; i < 128; i++) { MMSequence entry = new MMSequence(); if (i == 0x1E) // intro music when link gets ambushed { entry.Addr = 2; OldSeq.Add(entry); continue; } int entryaddr = Addresses.SeqTable + (i * 16); entry.Addr = (int)ReadWriteUtils.Arr_ReadU32(RomData.MMFileList[f].Data, entryaddr - basea); var size = (int)ReadWriteUtils.Arr_ReadU32(RomData.MMFileList[f].Data, (entryaddr - basea) + 4); if (size > 0) { entry.Data = new byte[size]; Array.Copy(RomData.MMFileList[4].Data, entry.Addr, entry.Data, 0, entry.Size); } else { int j = SequenceList.FindIndex(u => u.Replaces == i); if (j != -1) { if ((entry.Addr > 0) && (entry.Addr < 128)) { if (SequenceList[j].Replaces != 0x28) // 28 (fairy fountain) { SequenceList[j].Replaces = entry.Addr; } else { entry.Data = OldSeq[0x18].Data; } } } } OldSeq.Add(entry); } List <MMSequence> NewSeq = new List <MMSequence>(); int addr = 0; byte[] NewAudioSeq = new byte[0]; for (int i = 0; i < 128; i++) { MMSequence newentry = new MMSequence(); if (OldSeq[i].Size == 0) { newentry.Addr = OldSeq[i].Addr; } else { newentry.Addr = addr; } if (SequenceList.FindAll(u => u.Replaces == i).Count > 1) { WriteOutput("Error: Slot " + i.ToString("X") + " has multiple songs pointing at it!"); } int p = RomData.PointerizedSequences.FindIndex(u => u.PreviousSlot == i); int j = SequenceList.FindIndex(u => u.Replaces == i); if (p != -1) { // found song we want to pointerize newentry.Addr = RomData.PointerizedSequences[p].Replaces; } else if (j != -1) { // new song to replace old slot found if (SequenceList[j].MM_seq != -1) { newentry.Data = OldSeq[SequenceList[j].MM_seq].Data; WriteOutput("Slot " + i.ToString("X") + " -> " + SequenceList[j].Name); } else if (SequenceList[j].SequenceBinaryList != null && SequenceList[j].SequenceBinaryList[0] != null) { if (SequenceList[j].SequenceBinaryList.Count == 0) { throw new Exception("Reached music write without a song to write"); } if (SequenceList[j].SequenceBinaryList.Count > 1) { WriteOutput("Warning: writing song with multiple sequence/bank combos, selecting first available"); } newentry.Data = SequenceList[j].SequenceBinaryList[0].SequenceBinary; WriteOutput("Slot " + i.ToString("X") + " := " + SequenceList[j].Name + " *"); } else // non mm, load file and add { byte[] data; if (File.Exists(SequenceList[j].Filename)) { using (var reader = new BinaryReader(File.OpenRead(SequenceList[j].Filename))) { data = new byte[(int)reader.BaseStream.Length]; reader.Read(data, 0, data.Length); } } else if (SequenceList[j].Name == nameof(Properties.Resources.mmr_f_sot)) { data = Properties.Resources.mmr_f_sot; } else { throw new Exception("Music not found as file or built-in resource."); } // if the sequence is not padded to 16 bytes, the DMA fails // music can stop from playing and on hardware it will just straight crash if (data.Length % 0x10 != 0) { data = data.Concat(new byte[0x10 - (data.Length % 0x10)]).ToArray(); } // I think this checks if the sequence type is correct for MM // because DB ripped sequences from SF64/SM64/MK64 without modifying them if (data[1] != 0x20) { data[1] = 0x20; } newentry.Data = data; WriteOutput("Slot " + i.ToString("X") + " := " + SequenceList[j].Name); } } else // not found, song wasn't touched by rando, just transfer over { newentry.Data = OldSeq[i].Data; } var padding = 0x10 - newentry.Size % 0x10; if (padding != 0x10) { newentry.Data = newentry.Data.Concat(new byte[padding]).ToArray(); } NewSeq.Add(newentry); // TODO is there not a better way to write this? if (newentry.Data != null) { NewAudioSeq = NewAudioSeq.Concat(newentry.Data).ToArray(); } addr += newentry.Size; } // discovered when MM-only music was fixed, if the audioseq is left in it's old spot // audio quality is garbage, sounds like static //if (addr > (RomData.MMFileList[4].End - RomData.MMFileList[4].Addr)) //else //RomData.MMFileList[4].Data = NewAudioSeq; int index = RomUtils.AppendFile(NewAudioSeq); ResourceUtils.ApplyHack(Values.ModsDirectory, "reloc-audio"); RelocateSeq(index); RomData.MMFileList[4].Data = new byte[0]; RomData.MMFileList[4].Cmp_Addr = -1; RomData.MMFileList[4].Cmp_End = -1; //update sequence index pointer table f = RomUtils.GetFileIndexForWriting(Addresses.SeqTable); for (int i = 0; i < 128; i++) { ReadWriteUtils.Arr_WriteU32(RomData.MMFileList[f].Data, (Addresses.SeqTable + (i * 16)) - basea, (uint)NewSeq[i].Addr); ReadWriteUtils.Arr_WriteU32(RomData.MMFileList[f].Data, 4 + (Addresses.SeqTable + (i * 16)) - basea, (uint)NewSeq[i].Size); } //update inst sets used by each new seq // this is NOT the audiobank, its the complementary instrument set value for each sequence // IE, sequence 7 uses instrument set "10", we replaced it with sequnece ae which needs bank "23" f = RomUtils.GetFileIndexForWriting(Addresses.InstSetMap); basea = RomData.MMFileList[f].Addr; for (int i = 0; i < 128; i++) { // huh? paddr? pointer? padding? int paddr = (Addresses.InstSetMap - basea) + (i * 2) + 2; int j = -1; if (NewSeq[i].Size == 0) // pointer, we need to copy the instrumnet set from the destination { j = SequenceList.FindIndex(u => u.Replaces == NewSeq[i].Addr); } else { j = SequenceList.FindIndex(u => u.Replaces == i); } if (j != -1) { RomData.MMFileList[f].Data[paddr] = (byte)SequenceList[j].Instrument; } } //// DEBUG spoiler log output //String dir = Path.GetDirectoryName(_settings.OutputROMFilename); //String path = $"{Path.GetFileNameWithoutExtension(_settings.OutputROMFilename)}"; //// spoiler log should already be written by the time we reach this far //if (File.Exists(Path.Combine(dir, path + "_SpoilerLog.txt"))) // path += "_SpoilerLog.txt"; //else // TODO add HTML log compatibility // path += "_SongLog.txt"; //using (StreamWriter sw = new StreamWriter(Path.Combine(dir, path), append: true)) //{ // sw.WriteLine(""); // spacer // sw.Write(log); //} }
/// <summary> /// Applies the given filename patch to the in-memory RomData /// </summary> /// <param name="filename"></param> /// <returns>SHA256 hash of the patch.</returns> public static byte[] ApplyPatch(string filename) { try { var aes = Aes.Create(); var hashAlg = new SHA256Managed(); using (var filestream = File.OpenRead(filename)) using (var cryptoStream = new CryptoStream(filestream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read)) using (var hashStream = new CryptoStream(cryptoStream, hashAlg, CryptoStreamMode.Read)) using (var decompressStream = new GZipStream(hashStream, CompressionMode.Decompress)) using (var memoryStream = new MemoryStream()) { decompressStream.CopyTo(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); using (var reader = new BeBinaryReader(memoryStream)) { var magic = reader.ReadUInt32(); var version = reader.ReadUInt32(); // Validate patch magic and version values PatchUtils.Validate(magic, version); while (reader.BaseStream.Position != reader.BaseStream.Length) { var fileIndex = reader.ReadInt32(); var fileAddr = reader.ReadInt32(); var index = reader.ReadInt32(); var isStatic = reader.ReadInt32() != 0 ? true : false; var length = reader.ReadInt32(); var data = reader.ReadBytes(length); if (fileIndex >= RomData.MMFileList.Count) { var newFile = new MMFile { Addr = fileAddr, IsCompressed = false, Data = data, End = fileAddr + data.Length, IsStatic = isStatic, }; RomUtils.AppendFile(newFile); } if (index == -1) { RomData.MMFileList[fileIndex].Data = data; if (data.Length == 0) { RomData.MMFileList[fileIndex].Cmp_Addr = -1; RomData.MMFileList[fileIndex].Cmp_End = -1; } } else { CheckCompressed(fileIndex); ReadWriteUtils.Arr_Insert(data, 0, data.Length, RomData.MMFileList[fileIndex].Data, index); } } } return(hashAlg.Hash); } } catch { throw new IOException("Failed to apply patch. Patch may be invalid."); } }
public static byte[] CreatePatch(string filename, List <MMFile> originalMMFiles) { var aes = Aes.Create(); var hashAlg = new SHA256Managed(); using (var outStream = filename != null ? (Stream)File.Open(Path.ChangeExtension(filename, "mmr"), FileMode.Create) : new MemoryStream()) using (var cryptoStream = new CryptoStream(outStream, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write)) using (var hashStream = new CryptoStream(cryptoStream, hashAlg, CryptoStreamMode.Write)) { using (var compressStream = new GZipStream(hashStream, CompressionMode.Compress)) using (var writer = new BinaryWriter(compressStream)) { writer.Write(ReadWriteUtils.Byteswap32(PatchUtils.PATCH_MAGIC)); writer.Write(ReadWriteUtils.Byteswap32((uint)PatchUtils.PATCH_VERSION)); for (var fileIndex = 0; fileIndex < RomData.MMFileList.Count; fileIndex++) { var file = RomData.MMFileList[fileIndex]; var fileIsStatic = file.IsStatic ? 1 : 0; if (file.Data == null || (file.IsCompressed && !file.WasEdited)) { continue; } if (fileIndex >= originalMMFiles.Count) { writer.Write(ReadWriteUtils.Byteswap32((uint)fileIndex)); writer.Write(ReadWriteUtils.Byteswap32((uint)file.Addr)); writer.Write(ReadWriteUtils.Byteswap32((uint)0)); writer.Write(ReadWriteUtils.Byteswap32((uint)fileIsStatic)); writer.Write(ReadWriteUtils.Byteswap32((uint)file.Data.Length)); writer.Write(file.Data); continue; } CheckCompressed(fileIndex, originalMMFiles); var originalFile = originalMMFiles[fileIndex]; if (file.Data.Length != originalFile.Data.Length) { writer.Write(ReadWriteUtils.Byteswap32((uint)fileIndex)); writer.Write(ReadWriteUtils.Byteswap32((uint)file.Addr)); writer.Write(-1); writer.Write(ReadWriteUtils.Byteswap32((uint)fileIsStatic)); writer.Write(ReadWriteUtils.Byteswap32((uint)file.Data.Length)); writer.Write(file.Data); continue; } int?modifiedIndex = null; var modifiedBuffer = new List <byte>(); for (var i = 0; i <= file.Data.Length; i++) { if (i == file.Data.Length || file.Data[i] == originalFile.Data[i]) { if (modifiedBuffer.Any()) { writer.Write(ReadWriteUtils.Byteswap32((uint)fileIndex)); writer.Write(ReadWriteUtils.Byteswap32((uint)file.Addr)); writer.Write(ReadWriteUtils.Byteswap32((uint)modifiedIndex.Value)); writer.Write(ReadWriteUtils.Byteswap32((uint)fileIsStatic)); writer.Write(ReadWriteUtils.Byteswap32((uint)modifiedBuffer.Count)); writer.Write(modifiedBuffer.ToArray()); modifiedBuffer.Clear(); modifiedIndex = null; continue; } } else { if (!modifiedIndex.HasValue) { modifiedIndex = i; } modifiedBuffer.Add(file.Data[i]); } } } } return(hashAlg.Hash); } }