-
Posts
2 -
Joined
-
Last visited
-
Days Won
1
air6ornepig last won the day on July 25
air6ornepig had the most liked content!
Reputation
1 NeutralRecent Profile Visitors
The recent visitors block is disabled and is not being shown to other users.
-
Good evening, I've been spending some time romhacking soul silver for fun. I used some unused item slots to add new items to the game, but needed a way to make them accessible to the player so I've been reverse-engineering the game's shops and I created a list of their offsets. Also in this post will include a short tutorial on editing the shops in arm9.bin Find the offset list here (google sheet) Before I begin, I want to credit TheShadyRelapse. I used their method to find the offsets in arm9.bin, and my discoveries are built on their knowledge. Generally, you should read their tutorial before reading mine, as my tutorial won't be very in-depth and the methodology is essentially the same. The reason I created a new thread instead of responding to their old post is: I was concerned it might be considered necroposting, I'm offering a little bit more than just PokéMart edits, and TheShadyRelapse's tutorial explicitly states you can't use their method to edit dynamic shop inventories. Because I am explicitly offering dynamic shops, I don't want people to read that and leave before reaching my post. * * * * * Requirements: A method to extract/import and decompress/compress arm9.bin. I recommend NitroPacker. Other people seem to report success using CrystalTile2 and blz.exe, but I personally experience rom breakage with these.* A Hex Editor of any sort. I prefer HxD. A HGSS ROM, of course. DS Pokémon ROM Editor Reloaded (DSPRE). Not a hard requirement, but very helpful and strongly recommended. Required for some tasks. PtHGSS's index of items. Not a downloadable app nor hard requirement, just a list you might need for item IDs. * * * * * *UPDATE: Apparently DSPRE automatically unpacks and decompresses some files like the arm9 binary (arm9.bin) that we're editing. I had no idea when I made this tutorial, and I haven't tried using it, but I encourage people to try it. You can find the folder of unpacked files in the same location as your ROM after opening it with DSPRE. This means you should be able to skip step 1 and 3 as DSPRE seems to automatically recompress arm9.bin as well. 1. Unpack your ROM (instructions for NitroPacker on Windows) If you know how to unpack your ROM, or extract/import arm9.bin and uncompress/compress it using other means, feel free to skip this step if you prefer that method. Nitropacker's instructions are also on their github page; I'm basically just ripping from them. 2. Open arm9.bin with a hex editor of your choice, and start editing! I have provided a table of offsets for each of the shops that are hard-coded in the arm9 binary. Simply find on the table what shop you want to edit, choose to search by offset or by the shop's inventory code (dubbed 'full associated code' on the table) by copying it from the table and pasting it into the search function of your hex editor. In HxD: CTRL+G to search by offset, CTRL+F to search code (don't forget to select the "Hex-values" tab). Most shops or clerks that have a static inventory: Items are listed simply and in order by their 16-bit (little endian) item IDs. The hex value FFFF terminates the shop list. Once you've arrived at the shop code, use either the bulbapedia index or the hex values provided in the table (already in little endian form) to locate the shop item you want to replace. When you replace items, make sure it's in little endian form! I won't go into much detail over editing static inventory shops, you can read ShadyRelapse's post. Because the list won't terminate until it reaches FFFF, you can use this to do things like combine and expand shop inventories. I included the location of mart script files and the variable that the common script uses to call which inventory to load, and I'll explain more on that later. Dynamic PokéMart shop: Items have an added 16-bit hex value in their code that corresponds to the amount of badges you've earned. They are are arranged as: [Item hex ID][Story Variable]. Where [Item hex ID] is its 16-bit hex ID in a little endian form, which can be found in the same bulbapedia index from earlier. [Story Variable] is a 16-bit variable that changes based on the amount of badges you have: 0001 = 0 badges 0002 = 1 badge 0003 = 3 badges 0004 = 5 badges 0005 = 7 badges 0006 = 8 badges So a poke ball's code would be (always in little endian of course) 04000100, potion = 11000100, full restore = 17000600, etc.. The offset and full associated code with the pokemart is already provided in the google sheet. Change the first 4 hexes to change the item in the inventory, and the last 4 hexes to change when in the story it becomes available. Example: If you wanted to do something goofy and replace Great Balls with Master Balls and make them available ASAP, you would go to the correct offset and replace the code 03000300 ([Great Ball][3 Badges]) with 01000100 ([Master Ball][0 Badges]). The dynamic shop uniquely terminates at 0000, and not FFFF. This means the inventory count is defined somewhere else. Unfortunately this means I don't know how to expand it. Pokéathlon shop: Items are also arranged as two 16-bit values: [Item hex ID][Cost in Pokéathlon Points (AP)]. [Cost in AP] is a little endian hex value corresponding to the item's AP cost in that shop (obviously). So the code for a red apricorn would be E501C800 (01e5 = red apricorn, 00c8 = 200), moomoo milk is 21006400 (0021 = moomoo milk, 0064 = 100), and something like a fire stone would be 5200C409 (0052 = fire stone, 09c4 = 2500). Replacing this code is just like any other, except you also have control of the AP price. The Pokéathlon shop also has 14 different inventories, two for each day of the week. While most shop inventory calls are controlled by a common script, the call for the pokeathlon shop inventory seems to be an engine-level command, so I'm not sure how to change which inventory it loads based on the day of the week. Still, I have the offsets for each day listed on the google sheet. 3. Repack your ROM (instructions for NitroPacker on Windows) Again, if you have your own preferred method for compressing and importing arm9.bin, feel free to use that. * * * * * Script-based Shops (DSPRE required): A number of shops are actually script-based instead of stored on the arm9 binary. This makes modifying them much easier and less constrained. You can open the "Script-based Shops" tab in the google sheets table to find the list of script-based shops and their associated script files. Open your rom with DSPRE and select the "Script Editor" tab to access and edit the script files safely. People already familiar with IDEs and code editors probably won't need help editing scripts. For an example I'm going to add the Return TM to the Game Corner shop. To edit the Game Corner shops, load script file 910. The Pokemon Shop starts at script 5 and the Item/TM Shop starts at script 4. To add an item to the TM shop, navigate to script 4. You can mouse-over message lines of code to see what they say and their text archive location. Sometimes menu items are drawn from different text archives then their map headers would suggest. I've listed locations for known text strings in the google sheets table. In this case both messages and menu items are drawn from archive 603. This is the code for the Held Item/TM shop: ShowSpecialCurrency [par1] [par2] [par3] shows either BP or Coins. I haven't done any testing to figure out what exactly the first 2 parameters specify, but I know for par3, 1 displays BP and 2 displays current Coins. MultiTouchLocalText [par1] [par2] [par3] [par4] [par5] seems to enable an interactable text list that assigns its input into a variable set in [par5] (Var 0x4000 in this case). I haven't tested what the first 4 parameters do, probably something to do with either box alignment or colour. CreateMultiTouchBox [par1] [par2] [par3] creates the actual text box that can be selected. [par1] = box text. [par2] = selection description (set to 255 in this case which is out of bounds for the text archive, which I believe the game interprets to exclude a description). [par3] = value between 0-7 assigned to that text box which is stored in variable [par5] from MultiTouchLocalText. So "CreateMultiTouchBox 12 255 0", which is the first option in the game shop, has par1 set to message 12 which is "TECHNICAL MACHINE", par2 set to 255 which is nothing, and par3 set to 0, so when TECHNICAL MACHINE is selected, Var 0x4000 is set to 0. SetVarFromVariable [par1] [par2] copies a variable from another variable. Comparing variable values is pretty much never done directly from the MultiTouchLocalText par5 variable for some reason. I don't know why the developers decided to code like this. Maybe the variable from MultiTouchLocalText isn't stored for long, or it prevents the variable from being unintentionally overwritten by similar script functions. I haven't done the testing to confirm this. In this case it just assigns Var 0x8008 the same value as was assigned to Var 0x4000 (0). CompareVarValue [par1] [par2] loads a variable and a value for the context of the next command JumpIf [operator] [function] jumps to a specified function if par1 from CompareVarValue is equal to, lesser to, or greater than (depending on the operator) par2. In this case it jumps to function 8 if Var 0x8008 = 0 (which is assigned after a player selects a text box from CreateMultiTouchBox), so having selected the first option (TECHNICAL MACHINE), the script jumps to function 8 which handles the TMs menu. Jump [function] as you can probably tell jumps to a function without a condition. Function #8, aka the TM shop's code looks like this: To add a TM to this shop, simply add an extra line "CreateMultiTouchBox XX YYY Z" between the last two CreateMultiTouchBox's, and change the value of CreateMultiTouchBox 11 255 6 to CreateMultiTouchBox 11 255 7. For "CreateMultiTouchBox XX YYY Z", XX and YYY correspond to string indices in text archive 603. Your TM won't automatically be in text archive 603, so you'll have to navigate to the Text Editor tab and open Text Archive 603. Messages 29 and 30 are free to use, so this example will use 29 (The DSPRE text editor allows you to safely add new strings of text to archives, so if you have no free spaces, add strings with the "Append Line" button). I'm going to make it cost 1000 coins, so I'll type "RETURN\n1,000 Coins" into index 29 and save the text archive. Our extra line from earlier will actually look like "CreateMultiTouchBox 29 255 6". Now we need to create a command that jumps to an all-new function if Return is selected. Add "CompareVarValue 0x8008 6" and "JumpIf EQUAL Function#72". Function #8 should now look like: Now, we've added a jump to a function that doesn't exist yet, so we still have to create Function #72. We can just copy any of the other TM functions 20-25 and paste it at the very end of the function editor. I'm going to copy Function #25 (which handles checking your inventory space and choosing to buy the Thunderbolt TM): Edit "Function 25:" to read "Function 72:". The item ID for TM27 (Return) is 354. We don't need to translate it into a hex value, just replace "SetVar 0x8004 351" with "SetVar 0x8004 354" (hex values are prefixed with 0x). "JumpIf EQUAL Function#44" must be changed to "JumpIf EQUAL Function#73", as this is the function that actually handles checking your coins and adding the item to our inventory. Even though I think this part of the code is redundant (because both yes/no inputs jump to another function so this code never runs), it's probably wise to ensure the cost here for Return is correct; "CompareVarValue 0x8006 0x2710" and "TakeCoins 0x2710" should be changed to "CompareVarValue 0x8006 0x03E8" and "TakeCoins 0x03E8". If you know how to code you can absolutely fix this function to be less redundant if you want! I didn't bother, so for me Function 72 looks like: Next we must copy Function 44: As mentioned before, this function checks if you have enough coins to buy the item and then adds it to your inventory. Change "CompareVarValue 0x8006 0x2710" (0x2710 = 10000) to "CompareVarValue 0x8006 0x03E8" (0x03E8 = 1000). Function 73 should look like: We're not actually done yet! There's one more subtle step we need to consider that most script shops do when they have multiple pages; we need to account for Functions #37 & #39, which handle when the player doesn't have enough coins or not enough space for the item (i.e. they already have 999 or 99 of that item), respectively. They contain a message ("not enough coins/space") and then check Variable 0x8004 (the index # of the item) and are used commonly by other functions to correctly navigate back to the item page you were browsing before. They look like: As you can see these functions check if Var 0x8004 is 417 (TM90 (Substitute)), so if you selected substitute in the browsing window, the script knows to send you back to that same TM browsing window (Function #8). F#39 jumps to F#52 before checking but that's an unimportant detail. Both functions funnel into F#51, which chain into more functions that just check Var 0x8004 before sending you to an appropriate window: If it's not already apparent, the script stops bothering to check variables after the last TM on the list and just sends you to the item page (Function#9). This means if you attempted to buy the new Return TM but didn't have the coins or space, the script would mistakenly send you back to the items page instead of the TMs page. This is a simple fix. You can create a new function and redirect F#66 to it, or you can edit F#70 to check Var 0x8004. I find the former easier. So what I would do is copy F#66, append it to the end of the script and relabel it as "Function 74:" then have it check if Var 0x8004 = 354 (TM (Return)) and jump to an appropriate function: Don't forget to redirect F#66 to F#74!! Now don't forget to "Save Current File" in the editor! DSPRE will automatically re-arrange and re-name any functions as it deems fit to, so the functions we added won't be labelled the same anymore when we reload the script file or ROM. This is why we didn't save mid-progress, having the script re-arranged mid-editing is a hassle. Next you can save your ROM and voila! You should see changes reflected in the Game Corner TM shop! Keep in mind that not every script-based shop handles its window navigation the exact same way. Some shops, instead of checking every possibility Var 0x8004 could be, they might set a variable before the item/tm menu loads (usually Var 0x8007) and check that variable instead to redirect you back to the correct menu. Battle Frontier shops do this. If you want to edit another script shop, you're just going to have to read the script yourself and figure it out! I can't make examples for everything! Important notes to consider when script editing shops: You can only create a maximum of 8 selection boxes with CreateMultiTouchBox at a time. If you have more than 8 lines of CreateMultiTouchBox at once, the game doesn't know how to display it and will hardlock. If you want to add more than just 1 TM to the Game Corner shop like in my example, you'll have to get a little creative and add a second page of options. I accomplish this by copying Function 8 and pasting it as a new function, then adding a "Next Page" button in Function 8 that jumps to the newly pasted function. The new function will need appropriate edits. Once you figure out how to do this, your script shop editing options are essentially limitless. Creating a MultiTouchLocalText with only 4 or less options will automatically create wide selection boxes, so you must format your text appropriately. The new line text function "\n" doesn't work on these wide boxes, so you have to use another command to create a space between an item/pokemon name and its cost. Just copy what they do with the Pokemon Shop text and append "{CURSOR_X, 0, ###}" to your text followed by its cost. Some examples from the game: * * * * * Tips, Tricks, Suggestions, Etc.: In case you haven't noticed, Goldenrod and Celadon City Dept. Stores possess their own space in the arm9 code, but most of their stores have the exact same inventory. The engine command for the static inventory shop overlay is called by a Common Script 2052, and I don't know where common scripts are stored right now so I can't edit them. However, Common Script 2052 retrieves a variable (Var 0x8004) which decides what inventory to load, and it is set just before the common script is called. This means we can change what shops load what inventories by changing SetVar 0x8004 in the script. You can use this to set Celadon to load Goldenrod inventories, freeing space on arm9.bin for custom marts! Each shop's associated script as well as what variable it's associated with is recorded on the google sheet. For example, Celadon Dept. Store's medicine shop script is located in script file 790, it is the first script in the file: Change "SetVar 0x8004 18" to "SetVar 0x8004 3" to end up with: Save your changes and you'll observe no tangible changes in-game, but you've freed offset 000FBC02 in the arm9 binary, which can now be used for a custom shop with up to 11 items! You'll notice the 2F Item shop, the vitamin shops, and the battle item shops also take up unnecessary space and can also be repurposed the same way. Because we know which offset each variable points Common Script 2052 to in arm9.bin, we can effectively remap the entire game's mart system. Shop inventories can also by expanded if you remap them wisely. Shop inventories in arm9.bin terminate at FFFF. If you overwrite the termination code FFFF, the shop inventory will 'bleed' into the next mart inventory code, effectively 'combining' the inventories (the second shop being bled into should be unaffected, but the first shop would have its own inventory plus the second shop's inventory now). Or rather, with careful remapping, you could use the space freed from the redundant Celadon Dept. Store medicine shop code to expand the inventory in the shop preceding its code (which by default is the Goldenrod medicine shop, so you would be expanding both medicine shop inventories). * * * * * I think that is everything I wanted to share. Thank you for reading, I hope this helps other people with their romhacking. Please inform me of any errors or typos I've made.
-
- 1
-
-
air6ornepig changed their profile photo
-
PKHeX new update legality errors (contribution page)
air6ornepig replied to theSLAYER's topic in PKHeX
Hello, I found a Rotom that's giving me an illegal check for some reason ("Invalid: Encounter Type PID mismatch") I caught it on the Island where you find the TR for Overheat in the Isle of Armor expansion. It was level 60 and wandering outside the grass. I'd completed the whole base game before I even bought the DLC so I had all 8 badges and did the Sordward/Shielbert side-quest already. I've made no changes to it whatsoever. I encountered it, caught it, and sent it to the box. I played through the dlc a bit but i never touched the rotom. I'm using PKHex 2022.01.01 Just thought I should submit it in case it helps. Thank you all for your hard work on PKHex! 479 - Rotom - 9EB2FD73CD29.pk8