private List <UInt32> writeSubroutineMakeNoneMapIconsInvisible(AddressMapper addressMapper, VAVAddr entryAddr, VAVAddr returnContinueAddr, VAVAddr returnMakeInvisibleAddr)
        {
            var Scene_Layout_Obj_SetVisible = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8006f854);
            // precondition:  r31  MapIconButton*
            //                 r5  is unused
            // postcondition:  r0  is map icon type
            //                 r5  is 0
            var asm = new List <UInt32>();

            asm.Add(PowerPcAsm.lwz(5, 0x188, 31));                                     // get current map id into r5
            asm.Add(PowerPcAsm.cmpwi(5, -1));                                          // map id == -1 ?
            asm.Add(PowerPcAsm.bne(8));                                                // {
            asm.Add(PowerPcAsm.lwz(3, 0x28, 31));                                      //   \ 
            asm.Add(PowerPcAsm.li(5, 0));                                              //   |
            asm.Add(PowerPcAsm.lwz(4, -0x6600, 13));                                   //   | make "NEW" text invisible
            asm.Add(PowerPcAsm.bl(entryAddr, asm.Count, Scene_Layout_Obj_SetVisible)); //   /
            asm.Add(PowerPcAsm.lwz(3, 0x28, 31));                                      //   \ 
            asm.Add(PowerPcAsm.li(5, 0));                                              //   / make Locked Map Icon "(?)" invisible
            asm.Add(PowerPcAsm.b(entryAddr, asm.Count, returnMakeInvisibleAddr));      //   returnMakeInvisibleAddr
                                                                                       // } else {
            asm.Add(PowerPcAsm.lwz(0, 0x184, 3));                                      //   get map icon type (replaced opcode)
            asm.Add(PowerPcAsm.b(entryAddr, asm.Count, returnContinueAddr));           //   returnContinueAddr
                                                                                       // }
            return(asm);
        }
        protected override void writeAsm(EndianBinaryWriter stream, AddressMapper addressMapper, List <MapDescriptor> mapDescriptors)
        {
            var tableAddr = writeTable(mapDescriptors);

            PowerPcAsm.Pair16Bit v = PowerPcAsm.make16bitValuePair((UInt32)tableAddr);

            // --- Update Table Addr ---
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x801cca98), SeekOrigin.Begin);
            // mulli r0,r3,0x38 ->  mulli r0,r3,0x04
            stream.Write(PowerPcAsm.mulli(0, 3, 0x04));
            // r3 <- 0x80428e50 ->  r3 <- tableAddr
            stream.Write(PowerPcAsm.lis(3, v.upper16Bit)); stream.Write(PowerPcAsm.addi(3, 3, v.lower16Bit));
            stream.Seek(0x4, SeekOrigin.Current);
            // lwz r3,0x10(r3)  ->  lwz r3,0x0(r3)
            stream.Write(PowerPcAsm.lwz(3, 0, 3));

            // --- ASM hack: Use the rule set from map instead of from global setting ---
            var ruleSetFromMapRoutine = allocate(writeRuleSetFromMapRoutine(addressMapper, (VAVAddr)0), "writeRuleSetFromMapRoutine");

            stream.Seek(addressMapper.toFileAddress(ruleSetFromMapRoutine), SeekOrigin.Begin);
            stream.Write(writeRuleSetFromMapRoutine(addressMapper, ruleSetFromMapRoutine)); // re-write the routine again since now we know where it is located in the main dol

            var virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8007e13c);

            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // lha r3,0x3c(r30)  -> bl ruleSetFromMapRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, ruleSetFromMapRoutine));
            // cmpwi r23,0x0     -> lha r3,0x3c(r30)
            stream.Write(PowerPcAsm.lha(3, 0x3c, 30));
            // lha r0,0x28(r30)  -> cmpwi r23,0x0
            stream.Write(PowerPcAsm.cmpwi(23, 0x0));
            // stw r25,0x53f4(r29) -> lha r0,0x28(r30)
            stream.Write(PowerPcAsm.lha(0, 0x28, 30));
        }
        private List <UInt32> writeProcStopEventSquareRoutine(AddressMapper addressMapper, VAVAddr forceVentureCardVariable, VAVAddr routineStartAddress)
        {
            PowerPcAsm.Pair16Bit v            = PowerPcAsm.make16bitValuePair((UInt32)forceVentureCardVariable);
            var gameProgressChangeModeRoutine = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800c093c);
            var endOfSwitchCase = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800fac38);

            var asm = new List <UInt32>();

            asm.Add(PowerPcAsm.lwz(3, 0x188, 28));                                                 // \
            asm.Add(PowerPcAsm.lwz(3, 0x74, 3));                                                   // / r3_place = gameChara.currentPlace
            asm.Add(PowerPcAsm.lbz(6, 0x18, 3));                                                   // | r6_ventureCardId = r3_place.districtId

            asm.Add(PowerPcAsm.lis(3, v.upper16Bit));                                              // \
            asm.Add(PowerPcAsm.addi(3, 3, v.lower16Bit));                                          // | forceVentureCardVariable <- r6_ventureCardId
            asm.Add(PowerPcAsm.stw(6, 0x0, 3));                                                    // /

            asm.Add(PowerPcAsm.lwz(3, 0x18, 20));                                                  // \ lwz r3,0x18(r20)
            asm.Add(PowerPcAsm.li(4, 0x1f));                                                       // | li r4,0x1f  (the GameProgress mode id 0x1f is for executing a venture card)
            asm.Add(PowerPcAsm.li(5, -0x1));                                                       // | li r5,-0x1
            asm.Add(PowerPcAsm.li(6, -0x1));                                                       // | li r6,-0x1
            asm.Add(PowerPcAsm.li(7, 0x0));                                                        // | li r7,0x0
            asm.Add(PowerPcAsm.bl(routineStartAddress, asm.Count, gameProgressChangeModeRoutine)); // | bl Game::GameProgress::changeMode
            asm.Add(PowerPcAsm.b(routineStartAddress, asm.Count, endOfSwitchCase));                // / goto end of switch case
            return(asm);
        }
        private List <UInt32> writeRuleSetFromMapRoutine(AddressMapper addressMapper, VAVAddr routineStartAddress)
        {
            var Game_GetRuleFlag = addressMapper.toVersionAgnosticAddress((BSVAddr)0x801cca98);

            // precondition: r24 is mapId
            // precondition: r25 is global rule set which we are gonna use to store the linkreturn
            var asm = new List <UInt32>();

            asm.Add(PowerPcAsm.mflr(25));
            asm.Add(PowerPcAsm.mr(3, 24));                                             // r3 <- r24
            asm.Add(PowerPcAsm.bl(routineStartAddress, asm.Count, Game_GetRuleFlag));  // r3 <- bl Game_GetRuleFlag(r3)
            asm.Add(PowerPcAsm.stw(3, 0x53f4, 29));                                    // gameRule <- r3
            asm.Add(PowerPcAsm.mtlr(25));
            asm.Add(PowerPcAsm.blr());                                                 // return
            return(asm);
        }
        private List <UInt32> writeSubroutineInitMapIdsForMapIcons(AddressMapper addressMapper, VAVAddr entryAddr)
        {
            var JUtility_memset = addressMapper.toVersionAgnosticAddress((BSVAddr)0x80004714);
            // precondition: r3 is newly created map icon array
            //               r16 is the amount of map ids in the array (size / 4)
            //               r24 is unused
            // postcondition: r24 is the map icon array
            var asm = new List <UInt32>();

            asm.Add(PowerPcAsm.mflr(24));                                     // save the link register
            asm.Add(PowerPcAsm.li(4, -1));                                    // fill with 0xff
            asm.Add(PowerPcAsm.rlwinm(5, 16, 0x3, 0x0, 0x1d));                // get the size of the array
            asm.Add(PowerPcAsm.bl(entryAddr, asm.Count, JUtility_memset));    // call JUtility_memset(array*, 0xff, array.size)
            asm.Add(PowerPcAsm.mtlr(24));                                     // restore the link register
            asm.Add(PowerPcAsm.mr(24, 3));                                    // move array* to r24
            asm.Add(PowerPcAsm.blr());                                        // return
            return(asm);
        }
Esempio n. 6
0
        /// <summary>
        /// Hijack the LoadBoard() routine. Intercept the moment when the (now compressed) ventureCardTable is passed on to the InitChanceBoard() routine.
        /// Call the decompressVentureCardTable routine and pass the resulting decompressed ventureCardTable (located at ventureCardDecompressedTableAddr) to the InitChanceBoard() routine instead.
        /// </summary>
        protected override void writeAsm(EndianBinaryWriter stream, AddressMapper addressMapper, List <MapDescriptor> mapDescriptors)
        {
            var tableRowCount = mapDescriptors.Count;
            var ventureCardCompressedTableAddr = writeTable(mapDescriptors);

            PowerPcAsm.Pair16Bit v = PowerPcAsm.make16bitValuePair((UInt32)ventureCardCompressedTableAddr);

            // Allocate working memory space for a single uncompressed venture card table which is passed on for the game to use. We will use it to store the result of decompressing a compressed venture card table
            var ventureCardDecompressedTableAddr  = allocate(new byte[130], "VentureCardReservedMemoryForDecompressedTable");
            var ventureCardDecompressTableRoutine = allocate(writeSubroutine(ventureCardDecompressedTableAddr), "DecompressVentureCardSubroutine");

            // cmplwi r24,0x29                                     -> cmplwi r24,ventureCardTableCount-1
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8007e104), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmplwi(24, (UInt16)(tableRowCount - 1)));
            // mulli r0,r24,0x82                                   -> mulli r0,r24,0x10
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8007e11c), SeekOrigin.Begin); stream.Write(PowerPcAsm.mulli(0, 24, 0x10));
            // r4 <- 0x80410648                                    -> r4 <- ventureCardCompressedTableAddr
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8007e120), SeekOrigin.Begin); stream.Write(PowerPcAsm.lis(4, v.upper16Bit)); stream.Seek(4, SeekOrigin.Current); stream.Write(PowerPcAsm.addi(4, 4, v.lower16Bit));
            // li r5,0x0                                           -> bl ventureCardDecompressTableRoutine
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8007e130), SeekOrigin.Begin); stream.Write(PowerPcAsm.bl((UInt32)addressMapper.toVersionAgnosticAddress((BSVAddr)0x8007e130), (UInt32)ventureCardDecompressTableRoutine));
        }
        protected override void writeAsm(EndianBinaryWriter stream, AddressMapper addressMapper, List <MapDescriptor> mapDescriptors)
        {
            var mapIcons = writeIconStrings(mapDescriptors);
            Dictionary <string, VAVAddr> iconTableMap;
            VAVAddr iconTableAddr       = writeIconTable(mapIcons, out iconTableMap);
            VAVAddr mapIconPointerTable = writeMapIconPointerTable(mapDescriptors, iconTableMap);
            ushort  iconCount           = (ushort)iconTableMap.Count;
            short   tableRowCount       = (short)mapDescriptors.Count;

            PowerPcAsm.Pair16Bit v = PowerPcAsm.make16bitValuePair((UInt32)iconTableAddr);
            PowerPcAsm.Pair16Bit w = PowerPcAsm.make16bitValuePair((UInt32)mapIconPointerTable);
            // note: To add custom icons, the following files need to be editted as well:
            // - ui_menu_19_00a.brlyt within game_sequence.arc and within game_sequence_wifi.arc
            // - ui_menu_19_00a_Tag_*.brlan within game_sequence.arc and within game_sequence_wifi.arc

            // custom map icon hack (change it that way that it will call the GetMapDifficulty routine instead of the GetMapOrigin routine
            // the GetMapDifficulty routine is mostly unused by the game and we repurpose it to return the pointer to the pointer of the string of the map icon instead
            // then we go through all map icon pointer pointers and check if it is the same as the one retrieved. If it is then we make it visible, otherwise we set the visibility to false.
            var GetMapDifficulty = addressMapper.toVersionAgnosticAddress((BSVAddr)0x80211da4);

            // bl GetMapOrigin                                     -> bl GetMapDifficulty
            var offset = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e77c);

            stream.Seek(addressMapper.toFileAddress(offset), SeekOrigin.Begin); stream.Write(PowerPcAsm.bl(offset, GetMapDifficulty));
            // cmpw r28,r30                                        -> cmpw r29,r30
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e790), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmpw(29, 30));
            // cmplwi r28,0x12                                     -> cmplwi r28,iconCount
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e7c0), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmplwi(28, iconCount));
            // bl GetMapOrigin                                     -> bl GetMapDifficulty
            offset = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e8a4);
            stream.Seek(addressMapper.toFileAddress(offset), SeekOrigin.Begin); stream.Write(PowerPcAsm.bl(offset, GetMapDifficulty));
            // cmpw r29,r28                                        -> cmpw r30,r28
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e8b8), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmpw(30, 28));
            // cmplwi r29,0x12                                     -> cmplwi r29,iconCount
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e8e8), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmplwi(29, iconCount));
            // bl GetMapOrigin                                     -> bl GetMapDifficulty
            offset = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e824);
            stream.Seek(addressMapper.toFileAddress(offset), SeekOrigin.Begin); stream.Write(PowerPcAsm.bl(offset, GetMapDifficulty));
            // cmplwi r28,0x12                                     -> cmplwi r28,iconCount
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e84c), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmplwi(28, iconCount));
            // r29 <- 0x8047f5c0                                   -> r29 <- iconTableAddr
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e780), SeekOrigin.Begin); stream.Write(PowerPcAsm.lis(29, v.upper16Bit)); stream.Seek(4, SeekOrigin.Current); stream.Write(PowerPcAsm.addi(29, 29, v.lower16Bit));
            // r30 <- 0x8047f5c0                                   -> r30 <- iconTableAddr
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e8a8), SeekOrigin.Begin); stream.Write(PowerPcAsm.lis(30, v.upper16Bit)); stream.Seek(4, SeekOrigin.Current); stream.Write(PowerPcAsm.addi(30, 30, v.lower16Bit));
            // r30 <- 0x8047f5c0                                   -> r30 <- iconTableAddr
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e828), SeekOrigin.Begin); stream.Write(PowerPcAsm.lis(30, v.upper16Bit)); stream.Seek(4, SeekOrigin.Current); stream.Write(PowerPcAsm.addi(30, 30, v.lower16Bit));
            // mr r3,r28                                           -> mr r3,r26
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e94c), SeekOrigin.Begin); stream.Write(PowerPcAsm.mr(3, 26));
            // mr r3,r28                                           -> mr r3,r26
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x8021e968), SeekOrigin.Begin); stream.Write(PowerPcAsm.mr(3, 26));

            // Modify the GetMapDifficulty routine to retrieve the current map icon addr addr
            // subi r31,r3,0x15                                   ->  nop
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211dc8), SeekOrigin.Begin); stream.Write(PowerPcAsm.nop());
            // cmpwi r31,0x12                                     ->  cmpwi r31,tableRowCount
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211dd4), SeekOrigin.Begin); stream.Write(PowerPcAsm.cmpwi(31, tableRowCount));
            // li r3,0x15                                         ->  nop
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211e4c), SeekOrigin.Begin); stream.Write(PowerPcAsm.nop());
            // mulli r4,r3,0x24                                   ->  mulli r4,r3,0x04
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211e58), SeekOrigin.Begin); stream.Write(PowerPcAsm.mulli(4, 3, 0x04));
            // r3 <- 804363c8                                     ->  r3 <- mapIconPointerTable
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211e5c), SeekOrigin.Begin); stream.Write(PowerPcAsm.lis(3, w.upper16Bit)); stream.Write(PowerPcAsm.addi(3, 3, w.lower16Bit));
            // mulli r0,r31,0x24                                  ->  mulli r0,r31,0x04
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211e64), SeekOrigin.Begin); stream.Write(PowerPcAsm.mulli(0, 31, 0x04));
            // lwz r3,0x1c(r3)                                    ->  lwz r3,0x0(r3)
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80211e78), SeekOrigin.Begin); stream.Write(PowerPcAsm.lwz(3, 0x0, 3));

            // --- Hack to make icons invisible which do not have a map ---
            // -- Init Maps in the map array with -1 --
            var subroutineInitMapIdsForMapIcons = allocate(writeSubroutineInitMapIdsForMapIcons(addressMapper, VAVAddr.NullAddress), "SubroutineInitMapIdsForMapIcons");

            stream.Seek(addressMapper.toFileAddress(subroutineInitMapIdsForMapIcons), SeekOrigin.Begin);
            stream.Write(writeSubroutineInitMapIdsForMapIcons(addressMapper, subroutineInitMapIdsForMapIcons)); // re-write the routine again since now we know where it is located in the main dol
            // increase the array size
            // rlwinm r3,r16,0x2,0x0,0x1d                            -> r3,r16,0x3,0x0,0x1d
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80187794), SeekOrigin.Begin); stream.Write(PowerPcAsm.rlwinm(3, 16, 0x3, 0x0, 0x1d));
            var hijackAddr = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8018779c);

            // cmpwi r3,0x0                                          ->  bl subroutineInitMapIdsForMapIcons
            stream.Seek(addressMapper.toFileAddress(hijackAddr), SeekOrigin.Begin); stream.Write(PowerPcAsm.bl(hijackAddr, subroutineInitMapIdsForMapIcons));
            // mr r24,r3                                             ->  cmpwi r3,0x0
            stream.Write(PowerPcAsm.cmpwi(3, 0));
            // increase the array size
            // rlwinm r3,r16,0x2,0x0,0x1d                            -> r3,r16,0x3,0x0,0x1d
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80187aa4), SeekOrigin.Begin); stream.Write(PowerPcAsm.rlwinm(3, 16, 0x3, 0x0, 0x1d));
            hijackAddr = addressMapper.toVersionAgnosticAddress((BSVAddr)0x80187aac);
            // cmpwi r3,0x0                                          ->  bl subroutineInitMapIdsForMapIcons
            stream.Seek(addressMapper.toFileAddress(hijackAddr), SeekOrigin.Begin); stream.Write(PowerPcAsm.bl(hijackAddr, subroutineInitMapIdsForMapIcons));
            // mr r24,r3                                             ->  cmpwi r3,0x0
            stream.Write(PowerPcAsm.cmpwi(3, 0));
            // -- if a map id is -1, make the map icon invisible --
            hijackAddr = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e73c);
            var returnContinueAddr                  = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e740);
            var returnMakeInvisibleAddr             = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e808);
            var subroutineMakeNoneMapIconsInvisible = allocate(writeSubroutineMakeNoneMapIconsInvisible(addressMapper, VAVAddr.NullAddress, returnContinueAddr, returnMakeInvisibleAddr), "SubroutineMakeNoneMapIconsInvisibleMultiplayer");

            stream.Seek(addressMapper.toFileAddress(subroutineMakeNoneMapIconsInvisible), SeekOrigin.Begin);
            stream.Write(writeSubroutineMakeNoneMapIconsInvisible(addressMapper, subroutineMakeNoneMapIconsInvisible, returnContinueAddr, returnMakeInvisibleAddr)); // re-write the routine again since now we know where it is located in the main dol
            // lwz r0,0x184(r3)                                      ->  b subroutineMakeNoneMapIconsInvisible
            stream.Seek(addressMapper.toFileAddress(hijackAddr), SeekOrigin.Begin); stream.Write(PowerPcAsm.b(hijackAddr, subroutineMakeNoneMapIconsInvisible));

            // -- if the map id is -1, do not check if it has been unlocked or not ---
            hijackAddr         = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e570);
            returnContinueAddr = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e574);
            var returnSkipMapUnlockedCheck = addressMapper.toVersionAgnosticAddress((BSVAddr)0x8021e5a8);
            var subroutineWriteSubroutineSkipMapUnlockCheck = allocate(writeSubroutineSkipMapUnlockCheck(addressMapper, VAVAddr.NullAddress, returnContinueAddr, returnSkipMapUnlockedCheck), "SubroutineWriteSubroutineSkipMapUnlockCheck");

            stream.Seek(addressMapper.toFileAddress(subroutineWriteSubroutineSkipMapUnlockCheck), SeekOrigin.Begin);
            stream.Write(writeSubroutineSkipMapUnlockCheck(addressMapper, subroutineWriteSubroutineSkipMapUnlockCheck, returnContinueAddr, returnSkipMapUnlockedCheck)); // re-write the routine again since now we know where it is located in the main dol
            // or r3,r26,r26                                      ->  b subroutineWriteSubroutineSkipMapUnlockCheck
            stream.Seek(addressMapper.toFileAddress(hijackAddr), SeekOrigin.Begin); stream.Write(PowerPcAsm.b(hijackAddr, subroutineWriteSubroutineSkipMapUnlockCheck));

            // --- Various Map UI Fixes ---
            // -- if the map index is over the map array size, do not loop around to the first map index again --
            // ble 0x80187e1c                                        ->  b 0x80187e1c
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80187dfc), SeekOrigin.Begin); stream.Write(PowerPcAsm.b(8));
            // -- fix map selection going out of bounds in tour mode --
            // bne 0x80188258                                        ->  nop
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80188230), SeekOrigin.Begin); stream.Write(PowerPcAsm.nop());
        }
        protected override void writeAsm(EndianBinaryWriter stream, AddressMapper addressMapper, List <MapDescriptor> mapDescriptors)
        {
            // this is the virtual address for the ForceVentureCardVariable: 0x804363c4
            // instead of allocating space for the venture card variable with the free space manager, we use a fixed virtual address
            // so that this variable can be reused by other hacks outside of CSMM.
            var forceVentureCardVariable = addressMapper.toVersionAgnosticAddress((BSVAddr)0x804363c4);

            stream.Seek(addressMapper.toFileAddress(forceVentureCardVariable), SeekOrigin.Begin);
            // write zeroes to it
            stream.Write(new byte[4]);

            // --- Model ---
            // some examples:
            //   80087f88 = Property
            //   80088040 = Bank
            //   80088100 = Default
            //   80088050 = Take a break
            //   80088068 = Stockbroker
            //   80088048 = Arcade
            //   80088060 = Switch
            //   80088058 = Cannon
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80453330), SeekOrigin.Begin);
            stream.Write((UInt32)addressMapper.toVersionAgnosticAddress((BSVAddr)0x80088060));

            // --- Texture ---
            var customTextureHandler = allocate(writeGetTextureForCustomSquareRoutine(15, 14), "GetTextureForCustomSquareRoutine");
            var virtualPos           = addressMapper.toVersionAgnosticAddress((BSVAddr)0x80086d98);

            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // li r15,0x1        -> bl customTextureHandler
            stream.Write(PowerPcAsm.bl(virtualPos, customTextureHandler));

            customTextureHandler = allocate(writeGetTextureForCustomSquareRoutine(31, 30), "GetTextureForCustomSquareRoutine2");
            virtualPos           = addressMapper.toVersionAgnosticAddress((BSVAddr)0x80087a24);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // li r31,0x1        -> bl customTextureHandler
            stream.Write(PowerPcAsm.bl(virtualPos, customTextureHandler));

            // --- Icon ---
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x804160c8), SeekOrigin.Begin);
            stream.Write((uint)addressMapper.toVersionAgnosticAddress((BSVAddr)0x80415ee0)); // pointer to the texture name (0x80415ee0 points to the string "p_mark_21" which is the switch icon texture
            stream.Write((uint)addressMapper.toVersionAgnosticAddress((BSVAddr)0x80415ee0)); // we need to write it twice: once for each design type (Mario and DragonQuest)

            // --- Name ---
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80475580), SeekOrigin.Begin);
            stream.Write(3336); // id of the message in ui_message.csv (3336 = "Switch square")

            // --- Description ---
            var customDescriptionRoutine = allocate(writeGetDescriptionForCustomSquareRoutine(addressMapper, (VAVAddr)0), "GetDescriptionForCustomSquareRoutine");

            stream.Seek(addressMapper.toFileAddress(customDescriptionRoutine), SeekOrigin.Begin);
            stream.Write(writeGetDescriptionForCustomSquareRoutine(addressMapper, customDescriptionRoutine)); // re-write the routine again since now we know where it is located in the main dol

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800f8ce4);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // bl Game::uitext::get_string   -> bl customDescriptionRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, customDescriptionRoutine));

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800f8d6c);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // bl Game::uitext::get_string   -> bl customDescriptionRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, customDescriptionRoutine));

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800f8dd8);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // bl Game::uitext::get_string   -> bl customDescriptionRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, customDescriptionRoutine));

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800f8e5c);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // bl Game::uitext::get_string   -> bl customDescriptionRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, customDescriptionRoutine));

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800f8ee4);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // bl Game::uitext::get_string   -> bl customDescriptionRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, customDescriptionRoutine));

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x800f8f4c);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // bl Game::uitext::get_string   -> bl customDescriptionRoutine
            stream.Write(PowerPcAsm.bl(virtualPos, customDescriptionRoutine));

            // --- Behavior ---
            // the idea is that whenever someone stops at the event square, it sets our custom variable "ForceVentureCardVariable" to the id of the venture card and runs the Venture Card Mode (0x1c).
            // The custom variable is used to remember which venture card should be played the next time a venture card is executed.
            var procStopEventSquareRoutine = allocate(writeProcStopEventSquareRoutine(addressMapper, forceVentureCardVariable, (VAVAddr)0), "procStopEventSquareRoutine");

            stream.Seek(addressMapper.toFileAddress(procStopEventSquareRoutine), SeekOrigin.Begin);
            stream.Write(writeProcStopEventSquareRoutine(addressMapper, forceVentureCardVariable, procStopEventSquareRoutine)); // re-write the routine again since now we know where it is located in the main dol

            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x80475838), SeekOrigin.Begin);
            stream.Write((UInt32)procStopEventSquareRoutine);

            // --- Hijack Venture Card Mode ---
            // We are hijacking the execute venture card mode (0x1f) to check if our custom variable "ForceVentureCardVariable" has been set to anything other than 0.
            // If it was, then setup that specific venture card to be executed. Also reset our custom variable "ForceVentureCardVariable" so that normal venture cards still work.
            var forceFetchFakeVentureCard = allocate(writeSubroutineForceFetchFakeVentureCard(forceVentureCardVariable), "forceFetchFakeVentureCard");

            virtualPos = addressMapper.toVersionAgnosticAddress((BSVAddr)0x801b7f44);
            stream.Seek(addressMapper.toFileAddress(virtualPos), SeekOrigin.Begin);
            // li r4,-0x1   -> bl forceFetchFakeVentureCard
            stream.Write(PowerPcAsm.bl(virtualPos, forceFetchFakeVentureCard));
            // li r8,0x3    -> nop
            stream.Seek(addressMapper.toFileAddress((BSVAddr)0x801b7f74), SeekOrigin.Begin);
            stream.Write(PowerPcAsm.nop());
        }