smrpgpatchbuilder 1.5.8
Patchable base class support for some SMRPG concepts similar to their definitions in Lazy Shell
Requires Python: >=3.12

# SMRPG Patch Builder

Freeform editing of various things in Super Mario RPG.

This is not a decomp.

## How this works

Convert some contents of your ROM into Python code so you can freely edit it, then create a patch to get your changes into your ROM. You can do this for:
- ✅ [event scripts](#event-and-action-scripts)
- ✅ [battle animation scripts](#battle-animation-scripts)
- ✅ [monsters](#monsters)
- ✅ [monster AI scripts](#monster-ai-scripts)
- ✅ [monster attacks](#monster-attacks)
- ✅ [monster and ally spells](#monster-and-ally-spells)
- ✅ [allies](#allies)
- ✅ [items](#items)
- ✅ [sprites](#sprites)
- ✅ [shops](#shops)
- ✅ [overworld dialogs](#overworld-dialogs)
- ✅ [battle dialogs and battle messages](#battle-dialogs-and-messages)
- ✅ [battle packs and formations](#battle-packs-and-formations)
- ✅ [packet NPCs](#packet-npcs)
- ✅ [rooms and NPCs](#rooms-and-npcs)
- ✅ [overworld location data](#overworld-location-data)

(✅ = roundtrip tested and working. Roundtrips will not be 100% identical bytewise because monster pointers are rearranged, dialogs are optimized, etc, but they will look the same in Lazy Shell.)

You can also take the contents of your disassembler_output folder and stick it in whatever Python project you want, as long as that project imports this package. (example: SMRPG randomizer)

## Getting started

These instructions are for bash terminals (no powershell). On Windows, you can use WSL (note that your ROM will be in `/mnt/some_drive_letter/path/to/your/smrpg/rom`) but setup for WSL is beyond the scope of this guide.

### Step 0

- Install the Python runtime for your operating system. All further steps assume that your PATH variable will execute python 3 when you use `python` in the command line (and that you don't have that pointing to a separate python 2 install for example)
- Create a virtual environment: `python -m venv MyVirtualEnvironmentNameWhateverIWant`
- Activate the virtual environment: `source ~/.venvs/MyVirtualEnvironmentNameWhateverIWant/bin/activate` (might differ depending on your setup, use google if that's the case)
- Install required packages: `pip install -r requirements.txt`

Everything you do beyond this point must be in your venv. You'll know you're in your venv when your terminal prompts have your venv name in them, like this if you use vscode:

```
(MyVirtualEnvironmentNameWhateverIWant) stef@Stefs-MBP smrpgpatchbuilder %
```

### Step 1

This patch builder helps you remember the context of the things you're building. The first thing you should do is copy the contents of the config.example folder into the config folder. 

Then inside the config folder, you'll see a bunch of plaintext files. They are:
- `action_script_names.input`: Describe your 1024 action scripts.
- `animationdata_write.input`: If you want to move animation pack data for sprites to anywhere else in the ROM beyond where SMRPG keeps it by default (which you could do if you want to free up space for sprite tile data in 0x280000-0x370000), indicate your desired ranges here. Be sure to break them up by upper byte so that they work correctly.
- `animationpack_read.input`: If you have moved the pointer table for sprite animation pack data to anywhere else in the ROM besides where SMRPG keeps it by default, indicate the range here.
- `battle_effect_names.input`: These are the things you edit in the `Effects` editor in Lazy Shell (128 total).
- `battle_event_names.input`: There are 103 battle events in the game, you can describe them here if you have modified them.
- `battle_sfx_names.input`: If you have changed any of the 211 sound effects accessible during battle, you can name them here.
- `battle_variable_names.input`: If you've modified monster AI scripts in a way that reserves certain $7EE00x variables for certain purposes, you can describe them here.
- `battlefield_names.input`: If you've changed any of the 64 battlefields, rename them here.
- `dialog_names.input`: If you want to remember what certain overworld dialogs are being used for, you can give them names here. There's 4096 of them, so it can be handy.
- `event_script_names.input`: Describe your 4096 event scripts.
- `imagepack_read.input`: If you have moved image pack data for sprites to anywhere else in the ROM besides where SMRPG keeps it by default, indicate the range here.
- `item_prefixes.input`: Items and ally spells can begin with a special symbol or a blank space. If you've changed what any of those symbols look like in your ROM's alphabet, indicate them here. (i.e. if you've replaced the fan symbol, normally 0x26, wholesale with some other symbol, rename it here)
- `music_names.input`: Rename the game's music tracks if you've changed them.
- `overworld_area_names`: Rename the 56 world map destinations here if you need to.
- `overworld_sfx_names`: If you have changed any of the 163 sound effects accessible in the overworld, you can name them here. (There are actually 256 rows because there is one instance in the original game of a SFX command trying to use ID 255, so ID 255 acts as a placeholder)
- `pack_names.input`: Describe any of the 256 battle pack definitions here (these are referenced by overworld scripts when loading a specific battle)
- `packet_names.input`: A packet is a NPC created on the fly by an event script or action script (as opposed to a NPC that exists in the room definition), such as the mushrooms/flowers/etc that appear temporarily when you open a treasure chest. You can disassemble and edit those. In this file, put descriptive names for them.
- `room_names.input`: You can name the 510 levels in the game. By default most of these are derived from the level descriptions in Lazy Shell.
- `screen_effect_names.input`: If you've changed any of the 21 screen effects used in battle, you can describe them here.
- `shop_names.input`: If you've changed any of the 33 shops, rename them here.
- `sprite_names.input`: Give names to the 1024 NPC sprites in your ROM.
- `tiles_read.input`: The list of ranges where uncompressed NPC tiles are in the ROM
- `tiles_write.input`: The list of ranges where uncompressed NPC tiles in the ROM should be written to (if you've moved animation packs somewhere else, for instance, you could expand this to accommodate more tiles). Be sure to break them up by upper byte so that pointers work correctly, see example file. This should be the same as tiles_read.input if you want to read and write tile data to the same place.
- `toplevelsprite_read.input`: The addresses where sprite containers live (which hold image pack data and animation pack data to make a whole sprite). This should only be one line. You can change it if you've moved that data in your ROM to somewhere else.
- `variable_names.input`: Every $7000 var and $7000 var bit that can be used by Lazy Shell's event script editor is in this file. Rename the variables according to what you're using them for in your ROM.

Make sure that even if you don't rename any variables, **do not delete any lines**, it will mess up your code that uses it. 

<sup>Note: There are no files here to name your items, enemies, attacks, or spells. This is on purpose! Those will be retrieved from your ROM in a different way.</sup>

### Step 2

Run 
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py variableparser
```
in the root folder of this repo. It will create files in `./src/disassembler_output/variables` that define Python variables according to the names you gave in Step 1. Everything else you disassemble will be using these.

### Step 3

Disassemble items, spells, monster attacks, and packets by running:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py itemdisassembler --rom "/path/to/your/smrpg/rom"
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py enemyattackdisassembler --rom "/path/to/your/smrpg/rom"
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py spelldisassembler --rom "/path/to/your/smrpg/rom"
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py packetdisassembler --rom "/path/to/your/smrpg/rom"
```

These will produce: 
- `./src/disassembler_output/items.items.py`
- `./src/disassembler_output/enemy_attacks/attacks.py`
- `./src/disassembler_output/spells/spells.py`
- `./src/disassembler_output/packets/packets.py`

It is important to do these next because other things will use these. Event scripts and monster definitions (ie loot drops) will need to reference items, monster AI scripts use attacks and spells, allies use spells, and event/action scripts use packets.

### Step 4

Disassemble monsters:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py enemydisassembler --rom "/path/to/your/smrpg/rom"
```

These will produce `./src/disassembler_output/enemies/enemies.py`. It is important to do this now because other data types (battle animations, monster AI, battle packs) depend on this data.

### Step 5

Now you can disassemble whatever else you want. There are no more dependencies.

## Details on the things you can disassemble and patch

### Event and action scripts

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py eventconverter --rom "path/to/your/smrpg/rom" # be warned, this will probably take a few hours
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py eventassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/overworld_scripts/event
./src/disassembler_output/overworld_scripts/animation
```

For an example of an overworld script, if your ROM has this:
![alt text](image-6.png)
you would get this:
```python
# E0313_MUSHROOM_KINGDOM_OCCUPIED_GRANDMA

script = EventScript([
	RunDialog(dialog_id=DI0676_GRANDMA_DURING_MK_INVASION, above_object=MEM_70A8, closable=True, sync=False, multiline=True, use_background=True),
	Return()
])
```

You can then edit that however you like. For example you could set a variable:
```python
# E0313_MUSHROOM_KINGDOM_OCCUPIED_GRANDMA

script = EventScript([
	RunDialog(dialog_id=DI0676_GRANDMA_DURING_MK_INVASION, above_object=MEM_70A8, closable=True, sync=False, multiline=True, use_background=True),
	AddConstToVar(COIN_COUNTER_1, 1),
	Return()
])
```

You can also use `Jump to address` commands without having to worry about pointer addresses at all*. In this example, `JmpIfBitClear` is looking for a name instead of a $xxxx pointer, and the `SetBit` command further below has the name it's looking for:
```python
script = EventScript([
	JmpIfBitClear(UNKNOWN_7049_4, ["LabelGoesHere"]), # This will skip the RunDialog command by jumping to the SetBit command
	RunDialog(
		dialog_id=DI0520_LITTLE_SHORT_ON_COINS,
		above_object=MEM_70A8,
		closable=True,
		sync=False,
		multiline=True,
		use_background=True,
	),
	SetBit(INSUFFICIENT_COINS, identifier="LabelGoesHere"),
	JmpToEvent(E3072_FLOWER_STAR_FC_OR_MUSHROOM_CHEST)
])
```

This disassembler/assembler always processes event scripts and action scripts together. Action scripts look like this:
![alt text](image-3.png)
```python
#A0002_FLASH_AFTER_RUNNING_AWAY_IFRAMES

script = ActionScript([
	A_ObjectMemorySetBit(arg_1=0x30, bits=[4]),
	A_JmpIfBitClear(TEMP_707C_1, ["ACTION_2_start_loop_n_times_3"]),
	A_ClearSolidityBits(bit_4=True, cant_walk_through=True),
	A_StartLoopNTimes(15, identifier="ACTION_2_start_loop_n_times_3"),
	A_Pause(2),
	A_VisibilityOff(),
	A_Pause(2),
	A_VisibilityOn(),
	A_EndLoop(),
	A_SetSolidityBits(bit_4=True, cant_walk_through=True),
	A_ObjectMemoryClearBit(arg_1=0x30, bits=[4]),
	A_ReturnQueue()
])
```
<sup>(All action script commands are prefixed with `A_` to distinguish them from event script commands.)</sup>

Embedded action queues within event scripts are also supported. They use the same contents as action scripts:
![alt text](image-2.png)
```python
# E0255_EXP_STAR_HIT

script = EventScript([
	DisableObjectTrigger(MEM_70A8),
	StartSyncEmbeddedActionScript(target=MEM_70A8, prefix=0xF1, subscript=[
		A_SetObjectMemoryBits(arg_1=0x0B, bits=[0, 1]),
		A_Db(bytearray(b'\xfd\xf2'))
	]),
	SetSyncActionScript(MEM_70A8, A1022_HIT_BY_EXP_STAR),
	IncEXPByPacket(),
	JmpIfVarEqualsConst(PRIMARY_TEMP_7000, 0, ["EVENT_255_ret_13"]),
	SetBit(UNKNOWN_MIMIC_BIT, identifier="EVENT_255_set_bit_5"),
	SetBit(EXP_STAR_BIT_6),
	UnfreezeAllNPCs(),
	Pause(3),
	CreatePacketAtObjectCoords(packet=P031_LEVELUP_TEXT, target_npc=MARIO, destinations=["EVENT_255_set_bit_5"]),
	PlaySound(sound=SO095_LEVEL_UP_WITH_STAR, channel=6),
	SetVarToConst(TIMER_701E, 64),
	RunBackgroundEventWithPauseReturnOnExit(event_id=E0254_EXP_STAR_HIT_SUBROUTINE, timer_var=TIMER_701E),
	Return(identifier="EVENT_255_ret_13")
])
```

When it comes time to build your scripts into ROM patches, the builder code will convert names into pointers for you.

<sup>*caveat: you still need to be conscious about the script ID because two-byte pointers are still a thing. Jump commands in scripts 0-1535 can only jump to other scripts in 0-1535 (0x1Exxxx), same with 1536-3071 (0x1Fxxxx), same with 3072-4095 (0x20xxxx).</sup>

The `__init__.py` in each of the two output folders will export either a `ActionScriptBank` or three `EventScriptBank`s. If you want to use these in another Python project, it should import these. For both types, `bank.render()` returns a bytearray that when building your ROM should be patched at `bank.pointer_table_start`.

Notes:
- You can Ctrl+F the name of a command in Lazy Shell in src/smrpgpatchbuilder/datatypes/overworld_scripts to find the documentation for how to use that command in Python.
- The disassembler will produce scripts that add up to exactly the amount of bytes each bank can contain. To free up space, go to the final script (event 4095, action 1023) and delete all of the trailing `EndAll` class instantiators.
- Yo'ster Isle (events 470, 1837, 1839, 3329, 3729) has some weird implementation of this called "non-embedded action queues." That's event script code that's supposed to be read as NPC animation script code, despite having no header to indicate that. Non-embedded action queues are expected to begin at a certain offset relative to the start of the event. These are the five events that editing is disabled for in Lazy Shell. With disassembled scripts, you can edit these scripts if you like, but if you add too much code such that the NEAQ is pushed to a greater offset than it's expected to be, you'll get an error when building your patch. (Removing code before the NEAQ is fine, the assembler will fill in space to adjust it.)
- There's a couple of overrides that are technically legal but that you should probably never do. Normally, when you're using commands that jump to other commands, you specify another command's `identifier` property as the destination, so that this code will take care of filling these in with ROM addresses for you. However, event 580 in the original game issues a jump to an address that isn't associated to an event. I've flagged this with `ILLEGAL_JUMP` in the identifier, which means you're allowed in general to use a destination of `ILLEGAL_JUMP_XXXX` where `XXXX` is a four-digit hex int indicating the offset you want to jump to and is unassociated with any other command. I *strongly* recommend against ever doing this.
- Similarly, queue 53 and queue 137 use sound ID 255. I have no idea what that is, but it's accommodated for. This is another thing that's technically legal but that you shouldn't do.

<sub>([back to top](#how-this-works))</sub>

### Battle animation scripts

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py animationdisassembler --rom "path/to/your/smrpg/rom"
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py animationassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/battle_animation/scripts.py
```
which exports three folders, each with its own `AnimationScriptBank` class (one for $02xxxx, one for $35xxxx, one for $3Axxxx) - if you want to use your disassembled scripts in another Python project, these are what your project should import. `bank.render()` return type is `list[tuple[int, bytearray]]`, when building your ROM patch, each bytearray should be patched at its corresponding int (address).

For an example of a battle animation script, if your ROM has:
![alt text](image.png)
you would get this:
```python
# BE0022_YARIDOVICH_MIRAGE_ATTACK

script = BattleAnimationScript(header=["command_0x3a647c"], script = [
	RunSubroutine(["command_0x3a7531"]),
	SpriteQueue(field_object=1, destinations=["queuestart_0x3ac3b2"], bit_2=True, bit_4=True),
	RunSubroutine(["command_0x3a7729"]),
	PauseScriptUntil(condition=FRAMES_ELAPSED, frames=90),
	ClearAMEM8Bit(0x68),
	SetAMEMToRandomByte(amem=0x68, upper_bound=7),
	JmpIfAMEM8BitLessThanConst(0x68, 3, ["command_0x3a646c"]),
	SpriteQueue(field_object=0, destinations=["queuestart_0x3ac345"], bit_2=True, bit_4=True),
	SpriteQueue(field_object=1, destinations=["queuestart_0x3ac35f"], bit_2=True, bit_4=True),
	Jmp(["command_0x3a6476"]),
	SpriteQueue(field_object=1, destinations=["queuestart_0x3ac345"], bit_2=True, bit_4=True, identifier="command_0x3a646c"),
	SpriteQueue(field_object=0, destinations=["queuestart_0x3ac35f"], bit_2=True, bit_4=True),
	RunSubroutine(["command_0x3a771e"], identifier="command_0x3a6476"),
	Jmp(["command_0x3a7550"]),
	SetAMEM32ToXYZCoords(origin=ABSOLUTE_POSITION, x=183, y=127, z=0, set_x=True, set_y=True, set_z=True, identifier="command_0x3a647c"),
	NewSpriteAtCoords(sprite_id=SPR0482_YARIDOVICH, sequence=0, priority=2, vram_address=0x7800, palette_row=12, overwrite_vram=True, looping=True, overwrite_palette=True, behind_all_sprites=True, overlap_all_sprites=True),
	RunSubroutine(["command_0x3a756c"]),
	SummonMonster(monster=Yaridovich, position=1, bit_6=True, bit_7=True)
])
```
<sup>(note you can see how your custom battle event names and sprite names are used here!)</sup>

Battle animations are typically the most restrictive type of script to edit because of how sensitive pointers to things like object queues and sprite queues are, but those restrictions are accounted for to give you more freedom. This is accomplished by the disassembler reading all of the scripts it can find in your ROM and recursively tracing through them to find subroutines, object queues, sprites queues, etc to build a profile of how much code it can find and where it lives. Like with event and action scripts, pointers (including object/sprite queue pointers) are replaced with name strings. 

This allows you to freely edit your battle animation scripts however you like, just as if they were event scripts or animation scripts. For example, there's no longer any need to worry about whether a command you're trying to replace will be the same size or not. The assembler will warn you if your code is too long and will go out of bounds, and if it's not, it'll proceed to build your patch and calculate pointers on its own.*

Battle animations however are NOT like event scripts and action scripts in that script files are not separated by pointer. When the disassembler recursively traces your code, it builds a profile of which ranges within the ROM it can verify are being used, and then create one script file per contiguous range. That means in the above Yaridovich script, you can see the code that comes after what would be the final "Jump to $3A7550" command in Lazy Shell.

<sup>*caveat: you still need to be conscious about where in the ROM your script is going. Commands in 0x35xxxx can't jump to 0x02xxxx or 0x3Axxxx or vice versa.</sup>

This is still easily the most volatile part of the game that this repo lets you edit, so here are some considerations to be aware of:

- You can Ctrl+F the name of a command in Lazy Shell in src/smrpgpatchbuilder/datatypes/battle_animation_scripts to find the documentation for how to use that command in Python.
- This is still very, very much in alpha. The recursive tracer will find every branch of every object queue it can detect as used to the best of my understanding. It is probably still an imperfect approximation and not completely comprehensive.
- Every script file you get will include an address that it intends to patch to and an expected size that it shouldn't exceed. **Do not change these.** This is determined by what parts of the ROM the disassembler was able to access when reading your code. Changing this value means you might be overwriting something that wasn't hit by the recursive trace, aka something the assembler doesn't know exists and might be bad to overwrite.
- If your changed script is shorter than the script's expected size, that's okay! It just can't be longer than the expected size.
- I've done my best to make sure that pointers referenced outside of battle animations (such as monster definitions including pointers to where their sprite behaviours live) will stay intact, however it's possible that there are some I missed, which means those external pointers will no longer work if your code changes shape too much. Let me know if you come across anything like that and I can add it to the disassembler.
- Pointer tables for things like weapons, battle events, etc are treated as object queues, because that's basically what they are. For reasons above, don't try to add or remove pointers from these unless you really know what you're doing.
- If you've changed the start or end of any top-level pointer tables in your ROM, then this will not work correctly and you will need to fork this repo and modify the `banks` dict of animationdisassembler.py. (If you're doing this, note the `end` address of a bank dict entry is **inclusive**, not exclusive. Don't ask me why I did it that way because I don't remember).
- Object queues always assume that their pointers point only toward code that comes AFTER it. For example if you define an object queue at $3A1234 and the first pointer is `0x23 0x01`, it might compile correctly, but it will never decompile correctly after that. Be careful to avoid doing this.
- The "Ally tries to run" animation in the original game does something weird, as in it jumps to a byte that is actually an argument of another command and not a command on its own. The disassembler forcibly changes this so that it points instead to a real command. This required some educated guesses about what opcodes 0x02 and 0x47 do, so you'll see those command classes named with the word "EXPERIMENTAL" in them. No other scripts in the original game do this as far as I could find.
- `UnknownCommand`s produced by the disassembler are raw bytes whose opcode we don't know what it does. It's possible that some of these undocumented commands might be pointers, which means the code will break if the destination these raw bytes were meant to point to changes. If this happens, let me know and I'll see if you might have discovered an opcode that uses a pointer, and will integrate it into this code (for example I had to do this for 0x47).

<sub>([back to top](#how-this-works))</sub>

### Monsters

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py enemydisassembler --rom "path/to/your/smrpg/rom" 
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py enemyassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/enemies/enemies.py
```

Using Mokura as an example:
![alt text](image-7.png)
The disassembler produces this Python class:
```python
class MOKURA(Enemy):
    """MOKURA enemy class"""
    _monster_id: int = 148
    _name: str = "MOKURA"

    _hp: int = 620
    _fp: int = 100
    _attack: int = 120
    _defense: int = 75
    _magic_attack: int = 80
    _magic_defense: int = 90
    _speed: int = 25
    _evade: int = 20
    _magic_evade: int = 10
    _status_immunities: List[Status] = [Status.MUTE, Status.SLEEP, Status.POISON, Status.FEAR]
    _resistances: List[Element] = [Element.THUNDER, Element.JUMP]
    _xp: int = 90
    _coins: int = 0
    _yoshi_cookie_item = MushroomItem
    _rare_item_drop = RoyalSyrupItem
    _common_item_drop = KerokeroColaItem
    _flower_bonus_type: FlowerBonusType = FlowerBonusType.ATTACK_UP
    _flower_bonus_chance: int = 20
    _sound_on_hit: HitSound = HitSound.CLAW
    _sound_on_approach: ApproachSound = ApproachSound.NONE
    _coin_sprite: CoinSprite = CoinSprite.NONE
    _entrance_style: EntranceStyle = EntranceStyle.NONE
    _monster_behaviour: str = "unknown_0x350ADF"
    _elevate: int = 2
    _ohko_immune: bool = True
    _psychopath_message: str = " Mwa ha ha...[await]"
	# TODO cursor positions
```

The disassembler creates an `EnemyCollection` containing all 256 enemies:

```python
ALL_ENEMIES = EnemyCollection([
    Goomba(),
    Spikey(),
    ...
])
```

Other things like battle packs will import these classes.

If you want to use these in another Python project, you'll need to import the `EnemyCollection`. To build a patch for it, you will _also_ need an `AnimationScriptBank` (so that it can figure out what pointers to use for your monsters' sprite behaviours). `enemycollection.render(animationscriptbank.build_command_address_mapping())` produces a `dict[int, bytearray]` where each `int` is a ROM address where its corresponding `bytearray` should be patched.

NOTE: You can't change the Sprite Behaviour property in these Enemy classes. Those are completely taken care of by the battle animation assembler. When disassembling battle animations, you will see a file (usually with 0x350202 in the filename) that has an object queue of 256 pointer names. These match up to the enemy's ID and they indicate where their sprite behaviour table starts. If you want to change an entrance style then you will just have to change which pointer to use at that index.

<sub>([back to top](#how-this-works))</sub>

### Monster AI scripts

This was based off of the battle disassembler that patcdr made in smrpg randomizer that enabled spell randomization!

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py battledisassembler --rom "path/to/your/smrpg/rom" 
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py battleassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/monster_ai/monster_scripts.py
```
which exports a `MonsterScriptBank` - if you want to use your disassembled scripts in another Python project, this is what your project should import. `bank.render()` returns `tuple[bytearray, bytearray]` - when building your ROM patch, the first `bytearray` should go to `bank.range_1_start` and the second to `bank.range_2_start`.

For individual scripts, if you have this in your ROM:
![alt text](image-1.png)
the disassembler will give you:
```python
# 204 - Megasmilax

script = MonsterScript([
	IfVarBitsClear(BV7EE00A, [0]),
	SetVarBits(BV7EE00A, [0]),
	CastSpell(PetalBlastSpell),
	Wait1TurnandRestartScript(),
	IfTurnCounterEquals(4),
	CastSpell(PetalBlastSpell),
	ClearVar(BV7EE005_ATTACK_PHASE_COUNTER),
	Wait1TurnandRestartScript(),
	ClearVar(BV7EE005_DESIGNATED_RANDOM_NUM_VAR),
	Set7EE005ToRandomNumber(upper_bound=7),
	IfVarLessThan(BV7EE005_DESIGNATED_RANDOM_NUM_VAR, 4),
	SetVarBits(BV7EE00F, [0]),
	Attack(Attack0, Attack0, ScrowDustAttack),
	ClearVarBits(BV7EE00F, [0]),
	Wait1TurnandRestartScript(),
	CastSpell(DrainSpell, FlameWallSpell, FlameWallSpell),
	StartCounterCommands(),
	IfHPBelow(0),
	RunObjectSequence(3),
	IncreaseVarBy1(BV7EE00E),
	RemoveTarget(SELF),
	Wait1TurnandRestartScript(),
	IfTargetedByCommand([COMMAND_ATTACK]),
	SetVarBits(BV7EE00F, [0]),
	Attack(Attack0, DoNothing, DoNothing),
	ClearVarBits(BV7EE00F, [0]),
	Wait1TurnandRestartScript()
])
```

You can Ctrl+F the name of a command in Lazy Shell in src/smrpgpatchbuilder/datatypes/monster_scripts to find the documentation for how to use that command in Python.

<sub>([back to top](#how-this-works))</sub>

### Monster attacks

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py enemyattackdisassembler --rom "path/to/your/smrpg/rom"
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py enemyattackassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/enemy_attacks/attacks.py
```

The disassembler creates an `EnemyAttackCollection` (in the same file) that instantiates all of your EnemyAttack classes. If you want to use these attacks in another Python project, you'll need to import the `EnemyAttackCollection`. To prepare the patch data, run `collection.render()` to get a `dict[int, bytearray]` where each `int` is a ROM address to which the `bytearray` should be patched.

<sub>([back to top](#how-this-works))</sub>

### Monster and ally spells

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py spelldisassembler --rom "path/to/your/smrpg/rom"
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py spellassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/spells/spells.py
```

The disassembler creates a `SpellCollection` (in the same file) that instantiates all of your Spell classes. If you want to use these spells in another Python project, you'll need to import the `SpellCollection`. To prepare the patch data, run `collection.render()` to get a `dict[int, bytearray]` where each `int` is a ROM address to which the `bytearray` should be patched.

<sub>([back to top](#how-this-works))</sub>

### Allies

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py spelldisassembler --rom "path/to/your/smrpg/rom"
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py spellassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/allies/allies.py
```

The disassembler creates an `AllyCollection` that instantiates all five of your allies. If you want to use these characters in another Python project, you'll need to import the `AllyCollection`. To prepare the patch data, run `collection.render()` to get a `dict[int, bytearray]` where each `int` is a ROM address to which the `bytearray` should be patched.

Allies look like this:
![alt text](image-15.png)

```python
MARIO_Ally = Ally(
    index=0,
    name="Mario",
    starting_level=1,
    starting_current_hp=20,
    starting_max_hp=20,
    starting_speed=20,
    starting_attack=20,
    starting_defense=0,
    starting_mg_attack=10,
    starting_mg_defense=2,
    starting_experience=0,
    starting_weapon=None,
    starting_armor=None,
    starting_accessory=None,
    starting_magic=[
        JumpSpell,
    ],
    levels=[
        LevelUp(
            level=2,
            exp_needed=16,
            spell_learned=None,
            hp_plus=5,
            attack_plus=3,
            defense_plus=2,
            mg_attack_plus=2,
            mg_defense_plus=2,
            hp_plus_bonus=3,
            attack_plus_bonus=1,
            defense_plus_bonus=1,
            mg_attack_plus_bonus=3,
            mg_defense_plus_bonus=1,
        ),
        ...
    ],
    coordinates=AllyCoordinate(
        cursor_x=1,
        cursor_y=3,
        sprite_abxy_y=191,
        cursor_x_scarecrow=1,
        cursor_y_scarecrow=3,
        sprite_abxy_y_scarecrow=192,
    )
)
```

where `coordinates` controls where the cursor and ABXY buttons will appear relative to their sprites in battle (the rest of the properties should be self explanatory).

The assembler can rename your characters, but be aware that it will not change the point at which their names get cut off in the shop menu (such as when looking at who can equip an item). The renaming will also apply to the levelup screen, but their names will be written to 0x2F9B0 instead of 0x2D3AF (to be more forgiving with space for longer names). This assumes that you haven't added anything to the ROM at 0x2F9B0.

<sub>([back to top](#how-this-works))</sub>

### Items

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py itemdisassembler --rom "path/to/your/smrpg/rom" 
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py itemassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/items.items.py
```

Examples - NokNokShell, Muku Cookie, and Fire Bomb:
![alt text](image-8.png) ![alt text](image-9.png) ![alt text](image-10.png)

```python
class NokNokShellItem(Weapon):
    """NokNok Shell item class"""
    _item_name: str = "NokNok Shell"
    _prefix = ItemPrefix.SHELL

    _item_id: int = 7
    _description: str = " Kick to attack"
    _equip_chars: List[PartyCharacter] = [MARIO]
    _attack: int = 20
    _variance: int = 2
    _price: int = 20
    _hide_damage: bool = True
    _half_time_window_begins = UInt8(20)
    _perfect_window_begins = UInt8(25)
    _perfect_window_ends = UInt8(31)
    _half_time_window_ends = UInt8(36)


class MukuCookieItem(RegularItem):
    """Muku Cookie item class"""
    _item_name: str = "Muku Cookie"
    _prefix = ItemPrefix.DOT

    _item_id: int = 120
    _description: str = " Muku! Muku-\n muku! Muka?"
    _inflict: int = 69
    _price: int = 69
    _effect_type = EffectType.NULLIFICATION
    _hide_damage: bool = True
    _usable_battle: bool = True
    _overworld_menu_fill_fp: bool = True
    _target_all: bool = True
    _one_side_only: bool = True
    _status_immunities: List[Status] = [Status.MUTE, Status.SLEEP, Status.POISON, Status.FEAR, Status.BERSERK, Status.MUSHROOM, Status.SCARECROW]


class FireBombItem(RegularItem):
    """Fire Bomb item class"""
    _item_name: str = "Fire Bomb"
    _prefix = ItemPrefix.BOMB

    _item_id: int = 113
    _description: str = " Hit all\n enemies w/fire"
    _inflict: int = 120
    _price: int = 200
    _inflict_element = Element.FIRE
    _hide_damage: bool = True
    _usable_battle: bool = True
    _target_enemies: bool = True
    _target_all: bool = True
    _one_side_only: bool = True
```

It is assumed (for now) that the item subclass ranges are as follows:
- 0-36: weapons
- 37-73: armor
- 74-95: accessory
- 96-255: regular items

Almost everything that can be disassembled will need access to your item classes, so it's important for this to be one of the first things you do.

The disassembler creates an `ItemCollection` (in the same file) that instantiates all of your Item classes (this is necessary to create a ROM patch). If you want to use these items in another Python project, you'll need to import the `ItemCollection`. To prepare the patch data, run `itemcollection.render()` to get a `dict[int, bytearray]` where each `int` is a ROM address to which the `bytearray` should be patched.

<sub>([back to top](#how-this-works))</sub>

### Sprites

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py graphicsdisassembler --rom "path/to/your/smrpg/rom" 
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py graphicsassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/sprites/sprites.py
```

NPC sprites use uncompressed tiles, an image pack, an animation pack, and a container that references all three of those things. These all exist in different parts of the ROM. The sprite disassembler combines all of these things into a single object (which is why you won't see animation or image pack IDs) so that the assembler can create and write that info to the rom in an optimized manner that prioritizes clearing up empty space for more tiles.

Palettes are not covered by the disassembler, you still need to use IDs for those.

Here is an example of a disassembled sprite:

```python
# SPR0524_EMPTY

from smrpgpatchbuilder.datatypes.graphics.classes import CompleteSprite, AnimationPack, AnimationPackProperties, AnimationSequence, AnimationSequenceFrame, Mold, Tile, Clone
sprite = CompleteSprite(
    animation=AnimationPack(0, length=31, unknown=0x0002,
        properties=AnimationPackProperties(vram_size=2048,
            molds=[
                Mold(0, gridplane=False,
                    tiles=[
                        Tile(mirror=False, invert=False, format=0, length=7, subtile_bytes=[
                            bytearray(b'\xff\xf0\xff\xc0\xff\x80\xff\x80\xff\x00\xff\x00\xff\x00\xff\x00\x0f\xff?\xff\x7f\xff~\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xe0\xe0'),
                            bytearray(b'\xff\x0f\xff\x03\xff\x01\xff\x01\xff\x00\xff\x00\xff\x00\xff\x00\xf0\xff\xfc\xff\xfe\xff~\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x07\x07'),
                            bytearray(b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x80\xff\x80\xff\xc0\xff\xf0\xe0\xe0\xfe\xfe\xfe\xfe\xfe\xfe~\xfe\x7f\xff?\xff\x0f\xff'),
                            bytearray(b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x01\xff\x01\xff\x03\xff\x0f\x07\x07\x7f\x7f\x7f\x7f\x7f\x7f~\x7f\xfe\xff\xfc\xff\xf0\xff'),
                        ], is_16bit=False, y_plus=0, y_minus=0, x=120, y=120),
                    ]
                ),
            ],
            sequences=[
                AnimationSequence(
                    frames=[
                        AnimationSequenceFrame(duration=16, mold_id=0),
                    ]
                ),
            ]
        )
    ),
    palette_id=0,
    palette_offset=0,
    unknown_num=8
)
```

Disassembly will produce a `SpriteCollection`. If you want to import your sprites into another Python project, this is what you should import. `collection.render()` produces a `list[tuple[int, bytearray]]` where each tuple is a ROM address and the bytes to patch to that address.

<sub>([back to top](#how-this-works))</sub>

### Shops

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py shopdisassembler --rom "path/to/your/smrpg/rom" 
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py shopassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/shops/shops.py
```
which produces a `ShopCollection` of 33 shops. If you want to use your disassembled shops in another project, this is what your project should import. `ShopCollection.render()` produces a `dict[int, bytearray]` where each `int` is a ROM address where the corresponding `bytearray` is supposed to be patched.

Example shop:

```python
shops[SH06_FROG_COIN_EMPORIUM] = Shop(
    index=6,
    items=[
        SleepyBombItem,
        BracerItem,
        EnergizerItem,
        CrystallineItem,
        PowerBlastItem,
    ],
    buy_frog_coin=True,
)
```


<sub>([back to top](#how-this-works))</sub>

### Overworld dialogs

Disassemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py dialogdisassembler --rom "path/to/your/smrpg/rom" 
```
Assemble:
```bash
PYTHONPATH=src python src/smrpgpatchbuilder/manage.py dialogassembler -r path/to/your/smrpg/rom -t -b
# -r generates a ROM patch, -t generates a text file, -b generates a FlexHEX .bin
```
Writes to:
```
./src/disassembler_output/dialogs/dialogs.py
```
which produces a `DialogCollection`. If you want to use your disassembled dialogs in another project, this is what your project should import. `DialogCollection.render()` produces a `dict[int, bytearray]` where each `int` is a ROM address where the corresponding `bytearray` is supposed to be patched.

Dialogs in SMRPG are split in