public static void WriteToROM(int Addr, ushort val) { int f = RomUtils.GetFileIndexForWriting(Addr); int dest = Addr - RomData.MMFileList[f].Addr; var data = new byte[] { (byte)((val & 0xFF00) >> 8), (byte)(val & 0xFF) }; Arr_Insert(data, 0, data.Length, RomData.MMFileList[f].Data, dest); }
public static int AddNewFile(string path, string filename) { byte[] buffer; using (BinaryReader data = new BinaryReader(File.OpenRead(Path.Combine(path, filename)))) { int len = (int)data.BaseStream.Length; buffer = new byte[len]; data.Read(buffer, 0, len); } int index = RomUtils.AppendFile(buffer); return(RomData.MMFileList[index].Addr); }
public static void ReenableNightBGMSingle(int SceneFileID, byte NewMusicByte = 0x13) { // search for the bgm music header in the scene headers and replace the night sfx with a value that plays day BGM RomUtils.CheckCompressed(SceneFileID); for (int Byte = 0; Byte < 0x10 * 70; Byte += 8) { if (RomData.MMFileList[SceneFileID].Data[Byte] == 0x15) // header command starts with 0x15 { RomData.MMFileList[SceneFileID].Data[Byte + 0x6] = NewMusicByte; // 6th/8 byte is night BGM behavior, 0x13 is daytime BGM return; } } }
public static void ReassignSkulltulaHousesMusic(byte replacement_slot = 0x75) { // changes the skulltulla house BGM to a separate slot so it plays a new music that isn't generic cave music (overused) // the BGM for a scene is specified by a single byte in the scene headers // to modify the scene header, which is in the scene, we need the scene as a file // we can get this from the Romdata.SceneList but this only gets populated on enemizer // and we don't NEED to populate it since vanilla scenes are static, we can just hard code it here // at re-encode, we'll have fewer decoded files to re-encode too int swamp_spider_house_fid = 1284; // taken from ultimate MM spreadsheet (US File list -> A column) // scan the files for the header that contains scene music (0x15 first byte) // 15xx0000 0000yyzz where zz is the sequence pointer byte RomUtils.CheckCompressed(swamp_spider_house_fid); for (int b = 0; b < 0x10 * 70; b += 8) { if (RomData.MMFileList[swamp_spider_house_fid].Data[b] == 0x15 && RomData.MMFileList[swamp_spider_house_fid].Data[b + 0x7] == 0x3B) { RomData.MMFileList[swamp_spider_house_fid].Data[b + 0x7] = replacement_slot; break; } } int ocean_spider_house_fid = 1291; // taken from ultimate MM spreadsheet RomUtils.CheckCompressed(ocean_spider_house_fid); for (int b = 0; b < 0x10 * 70; b += 8) { if (RomData.MMFileList[ocean_spider_house_fid].Data[b] == 0x15 && RomData.MMFileList[ocean_spider_house_fid].Data[b + 0x7] == 0x3B) { RomData.MMFileList[ocean_spider_house_fid].Data[b + 0x7] = replacement_slot; break; } } SequenceInfo new_music_slot = new SequenceInfo { Name = "mm-spiderhouse-replacement", MM_seq = replacement_slot, Replaces = replacement_slot, Type = new List <int> { 2 }, Instrument = 3 }; RomData.TargetSequences.Add(new_music_slot); }
public static void RebuildAudioBank(List <InstrumentSetInfo> InstrumentSetList) { // get index for the old audiobank, we're putting it back int the same spot but letting it expand into audioseq's spot, because the later is no longer there int f = RomUtils.GetFileIndexForWriting(Addresses.AudiobankTable); // the DMA table doesn't point directly to the table on the rom, its part of a larger yaz0 file, we have to use an offset to get the address in the file int audiobank_table_adjusted_addr = Addresses.AudiobankTable - RomData.MMFileList[RomUtils.GetFileIndexForWriting(Addresses.AudiobankTable)].Addr; int current_audiobank_addr = 0; byte[] new_audiobank = new byte[0]; // for each bank, concat onto the new bank byte object, update the table to match the new instrument sets for (int inst_set_num = 0; inst_set_num <= 0x28; ++inst_set_num) { InstrumentSetInfo current_bank = InstrumentSetList[inst_set_num]; // not sure lists are sequential in C# int bank_len = current_bank.BankBinary.Length; new_audiobank = new_audiobank.Concat(current_bank.BankBinary).ToArray(); // update address of the bank in the table RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16] = (byte)((current_audiobank_addr & 0xFF000000) >> 24); RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 1] = (byte)((current_audiobank_addr & 0xFF0000) >> 16); RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 2] = (byte)((current_audiobank_addr & 0xFF00) >> 8); RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 3] = (byte)(current_audiobank_addr & 0xFF); // adjust the address for the next sequence to use current_audiobank_addr += bank_len; int remainder = bank_len % 16; if (remainder > 0) // in the event the user made an audiobank instrument set that isn't padded { new_audiobank = new_audiobank.Concat(new byte[remainder + 0x10]).ToArray(); // padding with a spare 16 byte line sounds cheap enough to try Debug.WriteLine("Bank " + inst_set_num + " wasn't padded to 16 bytes:" + bank_len.ToString("X")); current_audiobank_addr += remainder; } // update length of the bank in the table RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 4] = (byte)((bank_len & 0xFF000000) >> 24); RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 5] = (byte)((bank_len & 0xFF0000) >> 16); RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 6] = (byte)((bank_len & 0xFF00) >> 8); RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + inst_set_num * 16 + 7] = (byte)(bank_len & 0xFF); // update metadata of the bank in the table for (int meta_iter = 0; meta_iter < 8; ++meta_iter) { RomData.MMFileList[f].Data[audiobank_table_adjusted_addr + (inst_set_num * 16) + 8 + meta_iter] = current_bank.BankMetaData[meta_iter]; } } // write audioseq as a new file f = RomUtils.GetFileIndexForWriting(Addresses.Audiobank); RomData.MMFileList[f].Data = new_audiobank; }
public static void ApplyHack(string path, string name) { BinaryReader hack_file = new BinaryReader(File.Open(Path.Combine(path, name), FileMode.Open)); int hack_len = (int)hack_file.BaseStream.Length; byte[] hack_content = new byte[hack_len]; hack_file.Read(hack_content, 0, hack_len); hack_file.Close(); if (name.EndsWith("title-screen")) { Random R = new Random(); int rot = R.Next(360); Color l; float h; for (int i = 0; i < 144 * 64; i++) { int p = (i * 4) + 8; l = Color.FromArgb(hack_content[p + 3], hack_content[p], hack_content[p + 1], hack_content[p + 2]); h = l.GetHue(); h += rot; h %= 360f; l = ColorUtils.FromAHSB(l.A, h, l.GetSaturation(), l.GetBrightness()); hack_content[p] = l.R; hack_content[p + 1] = l.G; hack_content[p + 2] = l.B; hack_content[p + 3] = l.A; } l = Color.FromArgb(hack_content[0x1FE72], hack_content[0x1FE73], hack_content[0x1FE76]); h = l.GetHue(); h += rot; h %= 360f; l = ColorUtils.FromAHSB(255, h, l.GetSaturation(), l.GetBrightness()); hack_content[0x1FE72] = l.R; hack_content[0x1FE73] = l.G; hack_content[0x1FE76] = l.B; } int addr = 0; while (hack_content[addr] != 0xFF) { //Debug.WriteLine(addr.ToString("X4")); uint dest = ReadWriteUtils.Arr_ReadU32(hack_content, addr); addr += 4; uint len = ReadWriteUtils.Arr_ReadU32(hack_content, addr); addr += 4; int f = RomUtils.GetFileIndexForWriting((int)dest); dest -= (uint)RomData.MMFileList[f].Addr; ReadWriteUtils.Arr_Insert(hack_content, addr, (int)len, RomData.MMFileList[f].Data, (int)dest); addr += (int)len; } }
public static byte[] GetObjectData(int objectIndex) { var objectTableFileIndex = RomUtils.GetFileIndexForWriting(OBJECT_TABLE); var baseAddress = OBJECT_TABLE - RomData.MMFileList[objectTableFileIndex].Addr; var objectAddress = ReadWriteUtils.Arr_ReadU32(RomData.MMFileList[objectTableFileIndex].Data, baseAddress + (objectIndex * 8)); var objectFileIndex = RomData.MMFileList.FindIndex(f => f.Addr == objectAddress); if (objectFileIndex == -1) { return(null); } RomUtils.CheckCompressed(objectFileIndex); return(RomData.MMFileList[objectFileIndex].Data); }
public static void UpdateFormTunics(List <int[]> addresses, Color targetColor) { for (int i = 0; i < addresses.Count; i++) { for (int j = 0; j < addresses[i].Length; j++) { int fileInRom = RomUtils.GetFileIndexForWriting(addresses[i][j]); int addressInFile = addresses[i][j] - RomData.MMFileList[fileInRom].Addr; Color[] colorArray = ReadColours(fileInRom, addressInFile, paletteSize[i]); colorArray = ShiftHue(colorArray, targetColor, paletteSize[i], isZora[i], isGradientImage[i], isFierceDeity[i]); WriteColours(fileInRom, addressInFile, paletteSize[i], colorArray); } } }
public static void UpdateFormTunics(List <int[]> addresses, Color target) { for (int i = 0; i < addresses.Count; i++) { for (int j = 0; j < addresses[i].Length; j++) { int f = RomUtils.GetFileIndexForWriting(addresses[i][j]); int a = addresses[i][j] - RomData.MMFileList[f].Addr; Color[] c = ReadColours(f, a, sizes[i]); c = ShiftHue(c, target, sizes[i], zora[i], grad[i], fd[i]); WriteColours(f, a, sizes[i], c); } } }
public static void RebuildAudioBank(List <InstrumentSetInfo> InstrumentSetList) { // get index for the old audiobank, we're putting it back in the same spot but letting it expand into audioseq's spot, which was moved to the end int fid = RomUtils.GetFileIndexForWriting(Addresses.AudiobankTable); // the DMA table doesn't point directly to the indextable on the rom, its part of a larger yaz0 file, we have to use an offset to get the address in the file int audiobankIndexOffset = Addresses.AudiobankTable - RomData.MMFileList[RomUtils.GetFileIndexForWriting(Addresses.AudiobankTable)].Addr; int audiobankBankOffset = 0; var audiobankData = new byte[0]; // for each bank, concat onto the new bank byte object, update the table to match the new instrument sets for (int audiobankIndex = 0; audiobankIndex <= 0x28; ++audiobankIndex) { var currentBank = InstrumentSetList[audiobankIndex]; audiobankData = audiobankData.Concat(currentBank.BankBinary).ToArray(); // update address of the bank in the index table RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 0] = (byte)((audiobankBankOffset & 0xFF000000) >> 24); RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 1] = (byte)((audiobankBankOffset & 0xFF0000) >> 16); RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 2] = (byte)((audiobankBankOffset & 0xFF00) >> 8); RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 3] = (byte)(audiobankBankOffset & 0xFF); // update length of the bank in the table int currentBankLength = currentBank.BankBinary.Length; RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 4] = (byte)((currentBankLength & 0xFF000000) >> 24); RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 5] = (byte)((currentBankLength & 0xFF0000) >> 16); RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 6] = (byte)((currentBankLength & 0xFF00) >> 8); RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 7] = (byte)(currentBankLength & 0xFF); // update metadata of the bank in the table for (int metadataIter = 0; metadataIter < 8; ++metadataIter) { RomData.MMFileList[fid].Data[audiobankIndexOffset + (audiobankIndex * 16) + 8 + metadataIter] = currentBank.BankMetaData[metadataIter]; } // adjust the address for the next bank to use audiobankBankOffset += currentBankLength; int paddingRemainder = currentBankLength % 0x10; if (paddingRemainder > 0) // in the event the user made an audiobank instrument set that isn't padded { audiobankData = audiobankData.Concat(new byte[paddingRemainder + 0x10]).ToArray(); // padding with a spare 16 byte line sounds cheap enough to try audiobankBankOffset += paddingRemainder; } } // write new audiobank back to file RomData.MMFileList[RomUtils.GetFileIndexForWriting(Addresses.Audiobank)].Data = audiobankData; }
public static void WriteROMAddr(int[] Addr, byte[] data) { for (int i = 0; i < Addr.Length; i++) { int var = (int)(Addr[i] & 0xF0000000) >> 28; int rAddr = Addr[i] & 0xFFFFFFF; byte[] rdata = data; if (var == 1) { rdata[0] += 0xA; rdata[1] -= 0x70; } int f = RomUtils.GetFileIndexForWriting(rAddr); int dest = rAddr - RomData.MMFileList[f].Addr; Arr_Insert(rdata, 0, rdata.Length, RomData.MMFileList[f].Data, dest); } }
public static void ApplyHack(byte[] hack_content) { int addr = 0; while (hack_content[addr] != 0xFF) { //Debug.WriteLine(addr.ToString("X4")); uint dest = ReadWriteUtils.Arr_ReadU32(hack_content, addr); addr += 4; uint len = ReadWriteUtils.Arr_ReadU32(hack_content, addr); addr += 4; int f = RomUtils.GetFileIndexForWriting((int)dest); dest -= (uint)RomData.MMFileList[f].Addr; ReadWriteUtils.Arr_Insert(hack_content, addr, (int)len, RomData.MMFileList[f].Data, (int)dest); addr += (int)len; } }
public static void WriteNewSoundSamples(List <InstrumentSetInfo> InstrumentSetList) { /// Writing all of our new samples in a single file at the end // in the event we run out of MMFile DMA indexes: These files dont need to be a hard file in the filesystem // they can be placed anywhere after the soundbank starting address on rom, instrument sample lookup doesnt use file system // adding to the file system is just useful for shifting in BuildROM() // issue: we don't know right now where the samples will be written to, because BuildRom will shift the files // for now we write samples, after audiobank/soundbank/samples are written, we'll update audiobank pointers // save extra soundsamples fid, and the samples with their data, for later (UpdateBankInstrumentPointers()) int fid = RomData.SamplesFileID = RomUtils.AppendFile(new byte[0x0]); RomData.ListOfSamples = new List <SequenceSoundSampleBinaryData>(); // for each custom instrument set that needs a custom sample foreach (InstrumentSetInfo instrumentSet in InstrumentSetList) { if (instrumentSet.InstrumentSamples != null && instrumentSet.InstrumentSamples.Count > 0) { foreach (SequenceSoundSampleBinaryData sample in instrumentSet.InstrumentSamples) { // test if sample was already added by another song (OOT instruments for instance) var previouslyWrittenSample = RomData.ListOfSamples.Find(u => sample.Hash == u.Hash); if (previouslyWrittenSample == null) // if sample not already written once before { // get the rom addr of our new file, our file will start at the end of the last file sample.Addr = (uint)RomData.MMFileList[fid].Data.Length; // concat our sample to sample collection file RomData.MMFileList[fid].Data = RomData.MMFileList[fid].Data.Concat(sample.BinaryData).ToArray(); // I don't know if samples need to be padded to 0x10 like sequences but might as well int paddingRemainder = RomData.MMFileList[fid].Data.Length % 0x10; if (paddingRemainder > 0) { RomData.MMFileList[fid].Data = RomData.MMFileList[fid].Data.Concat(new byte[paddingRemainder]).ToArray(); } RomData.ListOfSamples.Add(sample); } else // get address of previously used sample { sample.Addr = previouslyWrittenSample.Addr; } } } } }
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 }
private static void InitGetBottleList() { RomData.BottleList = new Dictionary <int, BottleCatchEntry>(); int f = RomUtils.GetFileIndexForWriting(BOTTLE_CATCH_TABLE); int baseaddr = BOTTLE_CATCH_TABLE - RomData.MMFileList[f].Addr; var fileData = RomData.MMFileList[f].Data; foreach (var getBottleItemIndex in ItemUtils.AllGetBottleItemIndices()) { int offset = getBottleItemIndex * 6 + baseaddr; RomData.BottleList[getBottleItemIndex] = new BottleCatchEntry { ItemGained = fileData[offset + 3], Index = fileData[offset + 4], Message = fileData[offset + 5] }; } }
/// <summary> /// Get image data for the 4 stray fairy icons. /// </summary> /// <returns>Array of image bytes.</returns> public static byte[][] GetStrayFairyIcons() { // Stray Fairy icons start at: 0xA0A000 + 0x1B80 RomUtils.CheckCompressed(11); var fileData = RomData.MMFileList[11].Data; // Extract HUD icon for Stray Fairy icons. var fairies = new List <byte[]>(4); for (int i = 0; i < 4; i++) { var imageData = new byte[0xC00]; var offset = 0x1B80 + (imageData.Length * i); Buffer.BlockCopy(fileData, offset, imageData, 0, imageData.Length); fairies.Add(imageData); } return(fairies.ToArray()); }
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 }
public static void UpdateSceneFlagMask(int num) { int offset = num >> 3; int mod = offset % 16; if (mod < 4) { offset += 8; } else if (mod < 12) { offset -= 4; } int bit = 1 << (num & 7); int f = RomUtils.GetFileIndexForWriting(SCENE_FLAG_MASKS); int addr = SCENE_FLAG_MASKS - RomData.MMFileList[f].Addr + offset; RomData.MMFileList[f].Data[addr] |= (byte)bit; }
private static void InitGetItemList() { RomData.GetItemList = new Dictionary <int, GetItemEntry>(); int f = RomUtils.GetFileIndexForWriting(GET_ITEM_TABLE); var fileData = RomData.MMFileList[f].Data; for (var i = 0; i < fileData.Length; i += 8) { var getItemIndex = (i / 8) + 1; RomData.GetItemList[getItemIndex] = new GetItemEntry { ItemGained = fileData[i], Flag = fileData[i + 1], Index = fileData[i + 2], Type = fileData[i + 3], Message = (short)((fileData[i + 4] << 8) | fileData[i + 5]), Object = (short)((fileData[i + 6] << 8) | fileData[i + 7]) }; } }
public static void WriteNewBottle(Item location, Item item) { System.Diagnostics.Debug.WriteLine($"Writing {item.Name()} --> {location.Location()}"); int f = RomUtils.GetFileIndexForWriting(BOTTLE_CATCH_TABLE); int baseaddr = BOTTLE_CATCH_TABLE - RomData.MMFileList[f].Addr; var fileData = RomData.MMFileList[f].Data; foreach (var index in location.GetBottleItemIndices()) { var offset = index * 6 + baseaddr; var newBottle = RomData.BottleList[item.GetBottleItemIndices()[0]]; var data = new byte[] { newBottle.ItemGained, newBottle.Index, newBottle.Message, }; ReadWriteUtils.Arr_Insert(data, 0, data.Length, fileData, offset + 3); } }
private static void InitGetItemList() { RomData.GetItemList = new Dictionary <int, GetItemEntry>(); int f = RomUtils.GetFileIndexForWriting(GET_ITEM_TABLE); int baseaddr = GET_ITEM_TABLE - RomData.MMFileList[f].Addr; var fileData = RomData.MMFileList[f].Data; foreach (var getItemIndex in ItemUtils.AllGetItemIndices()) { int offset = (getItemIndex - 1) * 8 + baseaddr; RomData.GetItemList[getItemIndex] = new GetItemEntry { ItemGained = fileData[offset], Flag = fileData[offset + 1], Index = fileData[offset + 2], Type = fileData[offset + 3], Message = (short)((fileData[offset + 4] << 8) | fileData[offset + 5]), Object = (short)((fileData[offset + 6] << 8) | fileData[offset + 7]) }; } }
public static void GetMaps() { foreach (var scene in RomData.SceneList) { int f = scene.File; RomUtils.CheckCompressed(f); int j = 0; while (true) { byte cmd = RomData.MMFileList[f].Data[j]; if (cmd == 0x04) { byte mapcount = RomData.MMFileList[f].Data[j + 1]; int mapsaddr = (int)ReadWriteUtils.Arr_ReadU32(RomData.MMFileList[f].Data, j + 4) & 0xFFFFFF; for (int k = 0; k < mapcount; k++) { Map m = new Map(); m.File = RomUtils.AddrToFile((int)ReadWriteUtils.Arr_ReadU32(RomData.MMFileList[f].Data, mapsaddr)); scene.Maps.Add(m); mapsaddr += 8; } break; } if (cmd == 0x14) { break; } j += 8; } CheckHeaderForExits(f, 0, scene); if (scene.Number == 108) // avoid modifying unused setup in East Clock Town. doesn't seem to actually affect anything in-game, but best not to touch it. { scene.Setups.RemoveAt(2); } } }
public static void ReadSceneTable() { RomData.SceneList = new List <Scene>(); int f = RomUtils.GetFileIndexForWriting(SCENE_TABLE); int _SceneTable = SCENE_TABLE - RomData.MMFileList[f].Addr; int i = 0; while (true) { Scene s = new Scene(); uint saddr = ReadWriteUtils.Arr_ReadU32(RomData.MMFileList[f].Data, _SceneTable + i); if (saddr > 0x4000000) { break; } if (saddr != 0) { s.File = RomUtils.AddrToFile((int)saddr); s.Number = i >> 4; RomData.SceneList.Add(s); } i += 16; } }
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 int AddNewFile(byte[] content) { int index = RomUtils.AppendFile(content); return(RomData.MMFileList[index].Addr); }
/// <summary> /// Get the <see cref="MMFile"/> at a <see cref="FileIndex"/>. /// </summary> /// <param name="index">File index.</param> /// <returns></returns> static MMFile GetFile(FileIndex index) { RomUtils.CheckCompressed((int)index); return(RomData.MMFileList[(int)index]); }
public static void ReenableNightBGM() { // summary: there is a scene header which has a single byte that determines what plays at night, setting to 13 re-enables BGM at night // since scene table is only read previously on enemizer, if not loaded we have to load now if (RomData.SceneList == null) { ReadSceneTable(); } // TODO since this is static, it can be moved var TargetSceneEnums = new GameObjects.Scene[] { GameObjects.Scene.TerminaField, GameObjects.Scene.RoadToSouthernSwamp, GameObjects.Scene.SouthernSwamp, GameObjects.Scene.SouthernSwampClear, GameObjects.Scene.PathToMountainVillage, GameObjects.Scene.MountainVillage, GameObjects.Scene.MountainVillageSpring, GameObjects.Scene.TwinIslands, GameObjects.Scene.TwinIslandsSpring, GameObjects.Scene.GoronRacetrack, GameObjects.Scene.GoronVillage, GameObjects.Scene.GoronVillageSpring, GameObjects.Scene.PathToSnowhead, GameObjects.Scene.Snowhead, GameObjects.Scene.MilkRoad, GameObjects.Scene.GreatBayCoast, GameObjects.Scene.PinnacleRock, GameObjects.Scene.ZoraCape, GameObjects.Scene.WaterfallRapids, GameObjects.Scene.RoadToIkana, GameObjects.Scene.IkanaCanyon, GameObjects.Scene.EastClockTown, GameObjects.Scene.WestClockTown, GameObjects.Scene.NorthClockTown, GameObjects.Scene.SouthClockTown, GameObjects.Scene.LaundryPool, GameObjects.Scene.Woodfall, }.ToList(); foreach (var SceneEnum in TargetSceneEnums) { ReenableNightBGMSingle(RomData.SceneList.Find(u => u.Number == SceneEnum.Id()).File); } // Kamaro the dancing ghost in Termina Field breaks night music // he calls a function that sets an unknown actor flag unk39 & 20, he calls this function per frame from multiple places // if we nop it his music never plays, and might music is never interupted by him var kamaroFID = 593; RomUtils.CheckCompressed(kamaroFID); var kamaroData = RomData.MMFileList[kamaroFID].Data; // null function call to func_800B9084 -> NOP ReadWriteUtils.Arr_WriteU32(kamaroData, 0x618, 0x00000000); // Sakon the Bomb Bag theif breaks night music // on first night, after he's supposed to have stolen the bag // his actor will spawn, check the time, and self-destroy, and take out BGM with it // if we nop that kill music command it will stop him from stopping BGM var sakonFID = 526; RomUtils.CheckCompressed(sakonFID); var sakonData = RomData.MMFileList[sakonFID].Data; // null function call to Audio_QueueSeqCmd -> NOP ReadWriteUtils.Arr_WriteU32(sakonData, 0x3A0C, 0x00000000); }
// 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("X2") + " -> " + SequenceList[j].Name); } else if (SequenceList[j].SequenceBinaryList != null && SequenceList[j].SequenceBinaryList.Count > 0) { 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("X2") + " := " + 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." + SequenceList[j].Filename); } // 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("X2") + " := " + SequenceList[j].Name); } } else // not found, song wasn't touched by rando, just transfer over { newentry.Data = OldSeq[i].Data; } // 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 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); //} }
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 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 graphics = extendedObjects.ResolveGraphics(newItem); if (graphics.HasValue) { newItem.Object = graphics.Value.objectId; newItem.Index = graphics.Value.graphicId; } 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); int?refillGetItemIndex = item switch { Item.ItemBottleMadameAroma => 0x91, Item.ItemBottleAliens => 0x92, _ => null, }; if (refillGetItemIndex.HasValue) { var refillItem = RomData.GetItemList[refillGetItemIndex.Value]; var refillGraphics = extendedObjects.ResolveGraphics(refillItem); if (refillGraphics.HasValue) { refillItem.Object = refillGraphics.Value.objectId; refillItem.Index = refillGraphics.Value.graphicId; } var refillData = new byte[] { refillItem.ItemGained, refillItem.Flag, refillItem.Index, refillItem.Type, (byte)(refillItem.Message >> 8), (byte)(refillItem.Message & 0xFF), (byte)(refillItem.Object >> 8), (byte)(refillItem.Object & 0xFF), }; var refillOffset = (refillGetItemIndex.Value - 1) * 8 + baseaddr; ReadWriteUtils.Arr_Insert(refillData, 0, refillData.Length, fileData, refillOffset); } if (location.IsRupeeRepeatable()) { settings.AsmOptions.MMRConfig.RupeeRepeatableLocations.Add(getItemIndex); } var isRepeatable = item.IsRepeatable(settings) || (!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); } } }