Example #1
0
        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);
                }
            }
        }
Example #2
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);
        }
Example #3
0
 public static void ResetSceneFlagMask()
 {
     ReadWriteUtils.WriteToROM(SCENE_FLAG_MASKS, (uint)0);
     ReadWriteUtils.WriteToROM(SCENE_FLAG_MASKS + 0xC, (uint)0);
 }
Example #4
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)
                });
            }
        }
Example #5
0
        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++;
                    }
                }
            }
        }
Example #6
0
        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()
                                );
            }
        }
Example #7
0
        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");
                }
            }
        }
Example #8
0
        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);
                }
            }
        }
Example #9
0
        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;
        }
Example #10
0
        // 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);
            //}
        }
Example #11
0
        /// <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.");
            }
        }
Example #12
0
        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);
                    }
        }