public static byte[] BuildROM() { // yaz0 encode all of the files for the rom Parallel.ForEach(RomData.MMFileList, file => { if (file.IsCompressed && file.WasEdited) { // lower priority so that the rando can't lock a badly scheduled CPU by using 100% var previousThreadPriority = Thread.CurrentThread.Priority; Thread.CurrentThread.Priority = ThreadPriority.Lowest; byte[] result; var newSize = Yaz.Encode(file.Data, file.Data.Length, out result); if (newSize >= 0) { file.Data = new byte[newSize]; ReadWriteUtils.Arr_Insert(result, 0, newSize, file.Data, 0); } // this thread is borrowed, we don't want it to always be the lowest priority, return to previous state Thread.CurrentThread.Priority = previousThreadPriority; } }); byte[] ROM = new byte[0x2000000]; int ROMAddr = 0; // write all files to rom for (int i = 0; i < RomData.MMFileList.Count; i++) { if (RomData.MMFileList[i].Cmp_Addr == -1) { continue; } RomData.MMFileList[i].Cmp_Addr = ROMAddr; int fileLength = RomData.MMFileList[i].Data.Length; if (RomData.MMFileList[i].IsCompressed) { RomData.MMFileList[i].Cmp_End = ROMAddr + fileLength; } if (ROMAddr + fileLength > ROM.Length) // rom too small { // assuming the largest file isn't the last one, we still want some extra space for further files // padding will reduce the requirements for further resizes int expansionIncrementSize = 0x40000; // 1mb might be too large, not sure if there is a hardware compatiblity issue here int expansionLength = (((ROMAddr + fileLength - ROM.Length) / expansionIncrementSize) + 1) * expansionIncrementSize; byte[] newROM = new byte[ROM.Length + expansionLength]; Buffer.BlockCopy(ROM, 0, newROM, 0, ROM.Length); Buffer.BlockCopy(new byte[expansionLength], 0, newROM, ROM.Length, expansionLength); ROM = newROM; Debug.WriteLine("*** Expanding rom to size 0x" + ROM.Length.ToString("X2") + "***"); } ReadWriteUtils.Arr_Insert(RomData.MMFileList[i].Data, 0, fileLength, ROM, ROMAddr); ROMAddr += fileLength; } SequenceUtils.UpdateBankInstrumentPointers(ROM); UpdateFileTable(ROM); SignROM(ROM); FixCRC(ROM); return(ROM); }
/// <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) { var hashAlg = new SHA256Managed(); using (var filestream = File.OpenRead(filename)) using (var cryptoStream = new CryptoStream(filestream, hashAlg, CryptoStreamMode.Read)) using (var decompressStream = new GZipStream(cryptoStream, CompressionMode.Decompress)) using (var memoryStream = new MemoryStream()) { decompressStream.CopyTo(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); using (var reader = new BinaryReader(memoryStream)) { var magic = ReadWriteUtils.ReadU32(reader); var version = ReadWriteUtils.ReadU32(reader); // Validate patch magic and version values PatchUtils.Validate(magic, version); while (reader.BaseStream.Position != reader.BaseStream.Length) { var fileIndex = ReadWriteUtils.ReadS32(reader); var fileAddr = ReadWriteUtils.ReadS32(reader); var index = ReadWriteUtils.ReadS32(reader); var isStatic = ReadWriteUtils.ReadS32(reader) != 0 ? true : false; var length = ReadWriteUtils.ReadS32(reader); 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); } }
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; } }
private static void WriteColours(int fileNumber, int addressInFile, int count, Color[] colorArray) { for (int i = 0; i < count; i++) { int colorAddress = addressInFile + (i * 2); ushort rgba = ColorUtils.ToRGBA5551(colorArray[i]); var data = new byte[] { (byte)(rgba >> 8), (byte)(rgba & 0xFF), }; ReadWriteUtils.Arr_Insert(data, 0, data.Length, RomData.MMFileList[fileNumber].Data, colorAddress); } }
private static void WriteColours(int file, int addr, int count, Color[] c) { for (int i = 0; i < count; i++) { int ca = addr + (i * 2); ushort rgba = ToRGBA5551(c[i]); var data = new byte[] { (byte)(rgba >> 8), (byte)(rgba & 0xFF), }; ReadWriteUtils.Arr_Insert(data, 0, data.Length, RomData.MMFileList[file].Data, ca); } }
public static byte[] BuildROM() { // yaz0 encode all of the files for the rom Parallel.ForEach(RomData.MMFileList, file => { if (file.IsCompressed && file.WasEdited) { // lower priority so that the rando can't lock a badly scheduled CPU by using 100% var previous_thread_priority = Thread.CurrentThread.Priority; Thread.CurrentThread.Priority = ThreadPriority.Lowest; byte[] result; var newSize = Yaz.Encode(file.Data, file.Data.Length, out result); if (newSize >= 0) { file.Data = new byte[newSize]; ReadWriteUtils.Arr_Insert(result, 0, newSize, file.Data, 0); } // this thread is borrowed, we don't want it to always be the lowest priority, return to previous state Thread.CurrentThread.Priority = previous_thread_priority; } }); byte[] ROM = new byte[0x2000000]; int ROMAddr = 0; // write all files to rom for (int i = 0; i < RomData.MMFileList.Count; i++) { if (RomData.MMFileList[i].Cmp_Addr == -1) { continue; } RomData.MMFileList[i].Cmp_Addr = ROMAddr; int file_len = RomData.MMFileList[i].Data.Length; if (RomData.MMFileList[i].IsCompressed) { RomData.MMFileList[i].Cmp_End = ROMAddr + file_len; } ReadWriteUtils.Arr_Insert(RomData.MMFileList[i].Data, 0, file_len, ROM, ROMAddr); ROMAddr += file_len; } UpdateFileTable(ROM); SignROM(ROM); FixCRC(ROM); return(ROM); }
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 byte[] BuildROM() { CompressMMFiles(); byte[] ROM = new byte[0x2000000]; int ROMAddr = 0; // write all files to rom for (int i = 0; i < RomData.MMFileList.Count; i++) { if (RomData.MMFileList[i].Cmp_Addr == -1) { continue; } RomData.MMFileList[i].Cmp_Addr = ROMAddr; int fileLength = RomData.MMFileList[i].Data.Length; if (RomData.MMFileList[i].IsCompressed) { RomData.MMFileList[i].Cmp_End = ROMAddr + fileLength; } if (ROMAddr + fileLength > ROM.Length) // rom too small { // assuming the largest file isn't the last one, we still want some extra space for further files // padding will reduce the requirements for further resizes int expansionIncrementSize = 0x40000; // 1mb might be too large, not sure if there is a hardware compatiblity issue here int expansionLength = (((ROMAddr + fileLength - ROM.Length) / expansionIncrementSize) + 1) * expansionIncrementSize; byte[] newROM = new byte[ROM.Length + expansionLength]; Buffer.BlockCopy(ROM, 0, newROM, 0, ROM.Length); Buffer.BlockCopy(new byte[expansionLength], 0, newROM, ROM.Length, expansionLength); ROM = newROM; Debug.WriteLine("*** Expanding rom to size 0x" + ROM.Length.ToString("X2") + "***"); } ReadWriteUtils.Arr_Insert(RomData.MMFileList[i].Data, 0, fileLength, ROM, ROMAddr); ROMAddr += fileLength; } SequenceUtils.UpdateBankInstrumentPointers(ROM); UpdateFileTable(ROM); SignROM(ROM); FixCRC(ROM); return(ROM); }
public static void SetStrings(string path, string filename, string ver, string setting) { ResourceUtils.ApplyHack(path, filename); int veraddr = 0xC44E30; int settingaddr = 0xC44E70; string verstring = $"MM Rando {ver}\x00"; string settingstring = $"{setting}\x00"; int f = GetFileIndexForWriting(veraddr); var file = RomData.MMFileList[f]; byte[] buffer = Encoding.ASCII.GetBytes(verstring); int addr = veraddr - file.Addr; ReadWriteUtils.Arr_Insert(buffer, 0, buffer.Length, file.Data, addr); buffer = Encoding.ASCII.GetBytes(settingstring); addr = settingaddr - file.Addr; ReadWriteUtils.Arr_Insert(buffer, 0, buffer.Length, file.Data, addr); }
public static List <byte[]> GetFilesFromArchive(int fileIndex) { CheckCompressed(fileIndex); var data = RomData.MMFileList[fileIndex].Data; var headerLength = ReadWriteUtils.Arr_ReadS32(data, 0); var pointer = headerLength; var files = new List <byte[]>(); for (var i = 4; i < headerLength; i += 4) { var nextFileOffset = headerLength + ReadWriteUtils.Arr_ReadS32(data, i); var fileLength = nextFileOffset - pointer; var dest = new byte[fileLength]; ReadWriteUtils.Arr_Insert(data, pointer, fileLength, dest, 0); pointer += fileLength; var decompressed = Yaz.Decode(dest); files.Add(decompressed); } return(files); }
public static void ApplyHack_File(string name, byte[] data) { BinaryReader hack_file = new BinaryReader(File.Open(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(); 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; ReadWriteUtils.Arr_Insert(hack_content, addr, (int)len, data, (int)dest); addr += (int)len; } }
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); } }
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); } } }
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) { 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); } } }