| 
									
										
											  
											
												Mega Man 2: Implement New Game (#3256)
* initial (broken) commit
* small work on init
* Update Items.py
* beginning work, some rom patches
* commit progress from bh branch
* deathlink, fix soft-reset kill, e-tank loss
* begin work on targeting new bhclient
* write font
* definitely didn't forget to add the other two hashes no
* update to modern options, begin colors
* fix 6th letter bug
* palette shuffle + logic rewrite
* fix a bunch of pointers
* fix color changes, deathlink, and add wily 5 req
* adjust weapon weakness generation
* Update Rules.py
* attempt wily 5 softlock fix
* add explicit test for rbm weaknesses
* fix difficulty and hard reset
* fix connect deathlink and off by one item color
* fix atomic fire again
* de-jank deathlink
* rewrite wily5 rule
* fix rare solo-gen fill issue, hopefully
* Update Client.py
* fix wily 5 requirements
* undo fill hook
* fix picopico-kun rules
* for real this time
* update minimum damage requirement
* begin move to procedure patch
* finish move to APPP, allow rando boobeam, color updates
* fix color bug, UT support?
* what do you mean I forgot the procedure
* fix UT?
* plando weakness and fixes
* sfx when item received, more time stopper edge cases
* Update test_weakness.py
* fix rules and color bug
* fix color bug, support reduced flashing
* major world overhaul
* Update Locations.py
* fix first found bugs
* mypy cleanup
* headerless roms
* Update Rom.py
* further cleanup
* work on energylink
* el fixes
* update to energylink 2.0 packet
* energylink balancing
* potentially break other clients, more balancing
* Update Items.py
* remove startup change from basepatch
we write that in patch, since we also need to clean the area before applying
* el balancing and feedback
* hopefully less test failures?
* implement world version check
* add weapon/health option
* Update Rom.py
* x/x2
* specials
* Update Color.py
* Update Options.py
* finally apply location groups
* bump minor version number instead
* fix duplicate stage sends
* validate wily 5, tests
* see if renaming fixes
* add shuffled weakness
* remove passwords
* refresh rbm select, fix wily 5 validation
* forgot we can't check 0
* oops I broke the basepatch (remove failing test later)
* fix solo gen fill error?
* fix webhost patch recognition
* fix imports, basepatch
* move to flexibility metric for boss validation
* special case boobeam trap
* block strobe on stage select init
* more energylink balancing
* bump world version
* wily HP inaccurate in validation
* fix validation edge case
* save last completed wily to data storage
* mypy and pep8 cleanup
* fix file browse validation
* fix test failure, add enemy weakness
* remove test seed
* update enemy damage
* inno setup
* Update en_Mega Man 2.md
* setup guide
* Update en_Mega Man 2.md
* finish plando weakness section
* starting rbm edge case
* remove * imports
* properly wrap later weakness additions in regen playthrough
* fix import
* forgot readme
* remove time stopper special casing
since we moved to proper wily 5 validation, this special casing is no longer important
* properly type added locations
* Update CODEOWNERS
* add animation reduction
* deprioritize Time Stopper in rush checks
* special case wily phase 1
* fix key error
* forgot the test
* music and general cleanup
* the great rename
* fix import
* thanks pycharm
* reorder palette shuffle
* account for alien on shuffled weakness
* apply suggestions
* fix seedbleed
* fix invalid buster passthrough
* fix weakness landing beneath required amount
* fix failsafe
* finish music
* fix Time Stopper on Flash/Alien
* asar pls
* Apply suggestions from code review
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
* world helpers
* init cleanup
* apostrophes
* clearer wording
* mypy and cleanup
* options doc cleanup
* Update rom.py
* rules cleanup
* Update __init__.py
* Update __init__.py
* move to defaultdict
* cleanup world helpers
* Update __init__.py
* remove unnecessary line from fill hook
* forgot the other one
* apply code review
* remove collect
* Update rules.py
* forgot another
---------
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
											
										 
											2024-08-19 21:59:29 -05:00
										 |  |  | import pkgutil | 
					
						
							|  |  |  | from typing import Optional, TYPE_CHECKING, Iterable, Dict, Sequence | 
					
						
							|  |  |  | import hashlib | 
					
						
							|  |  |  | import Utils | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import settings | 
					
						
							|  |  |  | from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes | 
					
						
							|  |  |  | from . import names | 
					
						
							|  |  |  | from .rules import minimum_weakness_requirement | 
					
						
							|  |  |  | from .text import MM2TextEntry | 
					
						
							|  |  |  | from .color import get_colors_for_item, write_palette_shuffle | 
					
						
							|  |  |  | from .options import Consumables, ReduceFlashing, RandomMusic | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from . import MM2World | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MM2LCHASH = "37f2c36ce7592f1e16b3434b3985c497" | 
					
						
							|  |  |  | PROTEUSHASH = "9ff045a3ca30018b6e874c749abb3ec4" | 
					
						
							|  |  |  | MM2NESHASH = "0527a0ee512f69e08b8db6dc97964632" | 
					
						
							|  |  |  | MM2VCHASH = "0c78dfe8e90fb8f3eed022ff01126ad3" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enemy_weakness_ptrs: Dict[int, int] = { | 
					
						
							|  |  |  |     0: 0x3E9A8, | 
					
						
							|  |  |  |     1: 0x3EA24, | 
					
						
							|  |  |  |     2: 0x3EA9C, | 
					
						
							|  |  |  |     3: 0x3EB14, | 
					
						
							|  |  |  |     4: 0x3EB8C, | 
					
						
							|  |  |  |     5: 0x3EC04, | 
					
						
							|  |  |  |     6: 0x3EC7C, | 
					
						
							|  |  |  |     7: 0x3ECF4, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enemy_addresses: Dict[str, int] = { | 
					
						
							|  |  |  |     "Shrink": 0x00, | 
					
						
							|  |  |  |     "M-445": 0x04, | 
					
						
							|  |  |  |     "Claw": 0x08, | 
					
						
							|  |  |  |     "Tanishi": 0x0A, | 
					
						
							|  |  |  |     "Kerog": 0x0C, | 
					
						
							|  |  |  |     "Petit Kerog": 0x0D, | 
					
						
							|  |  |  |     "Anko": 0x0F, | 
					
						
							|  |  |  |     "Batton": 0x16, | 
					
						
							|  |  |  |     "Robitto": 0x17, | 
					
						
							|  |  |  |     "Friender": 0x1C, | 
					
						
							|  |  |  |     "Monking": 0x1D, | 
					
						
							|  |  |  |     "Kukku": 0x1F, | 
					
						
							|  |  |  |     "Telly": 0x22, | 
					
						
							|  |  |  |     "Changkey Maker": 0x23, | 
					
						
							|  |  |  |     "Changkey": 0x24, | 
					
						
							|  |  |  |     "Pierrobot": 0x29, | 
					
						
							|  |  |  |     "Fly Boy": 0x2C, | 
					
						
							|  |  |  |     # "Crash Wall": 0x2D | 
					
						
							|  |  |  |     # "Friender Wall": 0x2E | 
					
						
							|  |  |  |     "Blocky": 0x31, | 
					
						
							|  |  |  |     "Neo Metall": 0x34, | 
					
						
							|  |  |  |     "Matasaburo": 0x36, | 
					
						
							|  |  |  |     "Pipi": 0x38, | 
					
						
							|  |  |  |     "Pipi Egg": 0x3A, | 
					
						
							|  |  |  |     "Copipi": 0x3C, | 
					
						
							|  |  |  |     "Kaminari Goro": 0x3E, | 
					
						
							|  |  |  |     "Petit Goblin": 0x45, | 
					
						
							|  |  |  |     "Springer": 0x46, | 
					
						
							|  |  |  |     "Mole (Up)": 0x48, | 
					
						
							|  |  |  |     "Mole (Down)": 0x49, | 
					
						
							|  |  |  |     "Shotman (Left)": 0x4B, | 
					
						
							|  |  |  |     "Shotman (Right)": 0x4C, | 
					
						
							|  |  |  |     "Sniper Armor": 0x4E, | 
					
						
							|  |  |  |     "Sniper Joe": 0x4F, | 
					
						
							|  |  |  |     "Scworm": 0x50, | 
					
						
							|  |  |  |     "Scworm Worm": 0x51, | 
					
						
							|  |  |  |     "Picopico-kun": 0x6A, | 
					
						
							|  |  |  |     "Boobeam Trap": 0x6D, | 
					
						
							|  |  |  |     "Big Fish": 0x71 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # addresses printed when assembling basepatch | 
					
						
							|  |  |  | consumables_ptr: int = 0x3F2FE | 
					
						
							|  |  |  | quickswap_ptr: int = 0x3F363 | 
					
						
							|  |  |  | wily_5_ptr: int = 0x3F3A1 | 
					
						
							|  |  |  | energylink_ptr: int = 0x3F46B | 
					
						
							|  |  |  | get_equipped_sound_ptr: int = 0x3F384 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RomData: | 
					
						
							|  |  |  |     def __init__(self, file: bytes, name: str = "") -> None: | 
					
						
							|  |  |  |         self.file = bytearray(file) | 
					
						
							|  |  |  |         self.name = name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def read_byte(self, offset: int) -> int: | 
					
						
							|  |  |  |         return self.file[offset] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def read_bytes(self, offset: int, length: int) -> bytearray: | 
					
						
							|  |  |  |         return self.file[offset:offset + length] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write_byte(self, offset: int, value: int) -> None: | 
					
						
							|  |  |  |         self.file[offset] = value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write_bytes(self, offset: int, values: Sequence[int]) -> None: | 
					
						
							|  |  |  |         self.file[offset:offset + len(values)] = values | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write_to_file(self, file: str) -> None: | 
					
						
							|  |  |  |         with open(file, 'wb') as outfile: | 
					
						
							|  |  |  |             outfile.write(self.file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MM2ProcedurePatch(APProcedurePatch, APTokenMixin): | 
					
						
							|  |  |  |     hash = [MM2LCHASH, MM2NESHASH, MM2VCHASH] | 
					
						
							|  |  |  |     game = "Mega Man 2" | 
					
						
							|  |  |  |     patch_file_ending = ".apmm2" | 
					
						
							|  |  |  |     result_file_ending = ".nes" | 
					
						
							|  |  |  |     name: bytearray | 
					
						
							|  |  |  |     procedure = [ | 
					
						
							|  |  |  |         ("apply_bsdiff4", ["mm2_basepatch.bsdiff4"]), | 
					
						
							|  |  |  |         ("apply_tokens", ["token_patch.bin"]), | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def get_source_data(cls) -> bytes: | 
					
						
							|  |  |  |         return get_base_rom_bytes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write_byte(self, offset: int, value: int) -> None: | 
					
						
							|  |  |  |         self.write_token(APTokenTypes.WRITE, offset, value.to_bytes(1, "little")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write_bytes(self, offset: int, value: Iterable[int]) -> None: | 
					
						
							|  |  |  |         self.write_token(APTokenTypes.WRITE, offset, bytes(value)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def patch_rom(world: "MM2World", patch: MM2ProcedurePatch) -> None: | 
					
						
							| 
									
										
										
										
											2024-12-08 19:59:40 -05:00
										 |  |  |     patch.write_file("mm2_basepatch.bsdiff4", pkgutil.get_data(__name__, "data/mm2_basepatch.bsdiff4")) | 
					
						
							| 
									
										
											  
											
												Mega Man 2: Implement New Game (#3256)
* initial (broken) commit
* small work on init
* Update Items.py
* beginning work, some rom patches
* commit progress from bh branch
* deathlink, fix soft-reset kill, e-tank loss
* begin work on targeting new bhclient
* write font
* definitely didn't forget to add the other two hashes no
* update to modern options, begin colors
* fix 6th letter bug
* palette shuffle + logic rewrite
* fix a bunch of pointers
* fix color changes, deathlink, and add wily 5 req
* adjust weapon weakness generation
* Update Rules.py
* attempt wily 5 softlock fix
* add explicit test for rbm weaknesses
* fix difficulty and hard reset
* fix connect deathlink and off by one item color
* fix atomic fire again
* de-jank deathlink
* rewrite wily5 rule
* fix rare solo-gen fill issue, hopefully
* Update Client.py
* fix wily 5 requirements
* undo fill hook
* fix picopico-kun rules
* for real this time
* update minimum damage requirement
* begin move to procedure patch
* finish move to APPP, allow rando boobeam, color updates
* fix color bug, UT support?
* what do you mean I forgot the procedure
* fix UT?
* plando weakness and fixes
* sfx when item received, more time stopper edge cases
* Update test_weakness.py
* fix rules and color bug
* fix color bug, support reduced flashing
* major world overhaul
* Update Locations.py
* fix first found bugs
* mypy cleanup
* headerless roms
* Update Rom.py
* further cleanup
* work on energylink
* el fixes
* update to energylink 2.0 packet
* energylink balancing
* potentially break other clients, more balancing
* Update Items.py
* remove startup change from basepatch
we write that in patch, since we also need to clean the area before applying
* el balancing and feedback
* hopefully less test failures?
* implement world version check
* add weapon/health option
* Update Rom.py
* x/x2
* specials
* Update Color.py
* Update Options.py
* finally apply location groups
* bump minor version number instead
* fix duplicate stage sends
* validate wily 5, tests
* see if renaming fixes
* add shuffled weakness
* remove passwords
* refresh rbm select, fix wily 5 validation
* forgot we can't check 0
* oops I broke the basepatch (remove failing test later)
* fix solo gen fill error?
* fix webhost patch recognition
* fix imports, basepatch
* move to flexibility metric for boss validation
* special case boobeam trap
* block strobe on stage select init
* more energylink balancing
* bump world version
* wily HP inaccurate in validation
* fix validation edge case
* save last completed wily to data storage
* mypy and pep8 cleanup
* fix file browse validation
* fix test failure, add enemy weakness
* remove test seed
* update enemy damage
* inno setup
* Update en_Mega Man 2.md
* setup guide
* Update en_Mega Man 2.md
* finish plando weakness section
* starting rbm edge case
* remove * imports
* properly wrap later weakness additions in regen playthrough
* fix import
* forgot readme
* remove time stopper special casing
since we moved to proper wily 5 validation, this special casing is no longer important
* properly type added locations
* Update CODEOWNERS
* add animation reduction
* deprioritize Time Stopper in rush checks
* special case wily phase 1
* fix key error
* forgot the test
* music and general cleanup
* the great rename
* fix import
* thanks pycharm
* reorder palette shuffle
* account for alien on shuffled weakness
* apply suggestions
* fix seedbleed
* fix invalid buster passthrough
* fix weakness landing beneath required amount
* fix failsafe
* finish music
* fix Time Stopper on Flash/Alien
* asar pls
* Apply suggestions from code review
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
* world helpers
* init cleanup
* apostrophes
* clearer wording
* mypy and cleanup
* options doc cleanup
* Update rom.py
* rules cleanup
* Update __init__.py
* Update __init__.py
* move to defaultdict
* cleanup world helpers
* Update __init__.py
* remove unnecessary line from fill hook
* forgot the other one
* apply code review
* remove collect
* Update rules.py
* forgot another
---------
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
											
										 
											2024-08-19 21:59:29 -05:00
										 |  |  |     # text writing | 
					
						
							|  |  |  |     patch.write_bytes(0x37E2A, MM2TextEntry("FOR           ", 0xCB).resolve()) | 
					
						
							|  |  |  |     patch.write_bytes(0x37EAA, MM2TextEntry("GET EQUIPPED  ", 0x0B).resolve()) | 
					
						
							|  |  |  |     patch.write_bytes(0x37EBA, MM2TextEntry("WITH          ", 0x2B).resolve()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     base_address = 0x3F650 | 
					
						
							|  |  |  |     color_address = 0x37F6C | 
					
						
							|  |  |  |     for i, location in zip(range(11), [ | 
					
						
							|  |  |  |         names.atomic_fire_get, | 
					
						
							|  |  |  |         names.air_shooter_get, | 
					
						
							|  |  |  |         names.leaf_shield_get, | 
					
						
							|  |  |  |         names.bubble_lead_get, | 
					
						
							|  |  |  |         names.quick_boomerang_get, | 
					
						
							|  |  |  |         names.time_stopper_get, | 
					
						
							|  |  |  |         names.metal_blade_get, | 
					
						
							|  |  |  |         names.crash_bomber_get, | 
					
						
							|  |  |  |         names.item_1_get, | 
					
						
							|  |  |  |         names.item_2_get, | 
					
						
							|  |  |  |         names.item_3_get | 
					
						
							|  |  |  |     ]): | 
					
						
							|  |  |  |         item = world.multiworld.get_location(location, world.player).item | 
					
						
							|  |  |  |         if item: | 
					
						
							|  |  |  |             if len(item.name) <= 14: | 
					
						
							|  |  |  |                 # we want to just place it in the center | 
					
						
							|  |  |  |                 first_str = "" | 
					
						
							|  |  |  |                 second_str = item.name | 
					
						
							|  |  |  |                 third_str = "" | 
					
						
							|  |  |  |             elif len(item.name) <= 28: | 
					
						
							|  |  |  |                 # spread across second and third | 
					
						
							|  |  |  |                 first_str = "" | 
					
						
							|  |  |  |                 second_str = item.name[:14] | 
					
						
							|  |  |  |                 third_str = item.name[14:] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 # all three | 
					
						
							|  |  |  |                 first_str = item.name[:14] | 
					
						
							|  |  |  |                 second_str = item.name[14:28] | 
					
						
							|  |  |  |                 third_str = item.name[28:] | 
					
						
							|  |  |  |                 if len(third_str) > 16: | 
					
						
							|  |  |  |                     third_str = third_str[:16] | 
					
						
							|  |  |  |             player_str = world.multiworld.get_player_name(item.player) | 
					
						
							|  |  |  |             if len(player_str) > 14: | 
					
						
							|  |  |  |                 player_str = player_str[:14] | 
					
						
							|  |  |  |             patch.write_bytes(base_address + (64 * i), MM2TextEntry(first_str, 0x4B).resolve()) | 
					
						
							|  |  |  |             patch.write_bytes(base_address + (64 * i) + 16, MM2TextEntry(second_str, 0x6B).resolve()) | 
					
						
							|  |  |  |             patch.write_bytes(base_address + (64 * i) + 32, MM2TextEntry(third_str, 0x8B).resolve()) | 
					
						
							|  |  |  |             patch.write_bytes(base_address + (64 * i) + 48, MM2TextEntry(player_str, 0xEB).resolve()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             colors = get_colors_for_item(item.name) | 
					
						
							|  |  |  |             if i > 7: | 
					
						
							|  |  |  |                 patch.write_bytes(color_address + 27 + ((i - 8) * 2), colors) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 patch.write_bytes(color_address + (i * 2), colors) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     write_palette_shuffle(world, patch) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     enemy_weaknesses: Dict[str, Dict[int, int]] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.strict_weakness or world.options.random_weakness or world.options.plando_weakness: | 
					
						
							|  |  |  |         # we need to write boss weaknesses | 
					
						
							|  |  |  |         output = bytearray() | 
					
						
							|  |  |  |         for weapon in world.weapon_damage: | 
					
						
							|  |  |  |             if weapon == 8: | 
					
						
							|  |  |  |                 continue  # Time Stopper is a special case | 
					
						
							|  |  |  |             weapon_damage = [world.weapon_damage[weapon][i] | 
					
						
							|  |  |  |                              if world.weapon_damage[weapon][i] >= 0 | 
					
						
							|  |  |  |                              else 256 + world.weapon_damage[weapon][i] | 
					
						
							|  |  |  |                              for i in range(14)] | 
					
						
							|  |  |  |             output.extend(weapon_damage) | 
					
						
							|  |  |  |         patch.write_bytes(0x2E952, bytes(output)) | 
					
						
							|  |  |  |         time_stopper_damage = world.weapon_damage[8] | 
					
						
							|  |  |  |         time_offset = 0x2C03B | 
					
						
							|  |  |  |         damage_table = { | 
					
						
							|  |  |  |             4: 0xF, | 
					
						
							|  |  |  |             3: 0x17, | 
					
						
							|  |  |  |             2: 0x1E, | 
					
						
							|  |  |  |             1: 0x25 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for boss, damage in enumerate(time_stopper_damage): | 
					
						
							|  |  |  |             if damage > 4: | 
					
						
							|  |  |  |                 damage = 4  # 4 is a guaranteed kill, no need to exceed | 
					
						
							|  |  |  |             if damage <= 0: | 
					
						
							|  |  |  |                 patch.write_byte(time_offset + 14 + boss, 0) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 patch.write_byte(time_offset + 14 + boss, 1) | 
					
						
							|  |  |  |                 patch.write_byte(time_offset + boss, damage_table[damage]) | 
					
						
							|  |  |  |         if world.options.random_weakness: | 
					
						
							|  |  |  |             wily_5_weaknesses = [i for i in range(8) if world.weapon_damage[i][12] > minimum_weakness_requirement[i]] | 
					
						
							|  |  |  |             world.random.shuffle(wily_5_weaknesses) | 
					
						
							|  |  |  |             if len(wily_5_weaknesses) >= 3: | 
					
						
							|  |  |  |                 weak1 = wily_5_weaknesses.pop() | 
					
						
							|  |  |  |                 weak2 = wily_5_weaknesses.pop() | 
					
						
							|  |  |  |                 weak3 = wily_5_weaknesses.pop() | 
					
						
							|  |  |  |             elif len(wily_5_weaknesses) == 2: | 
					
						
							|  |  |  |                 weak1 = weak2 = wily_5_weaknesses.pop() | 
					
						
							|  |  |  |                 weak3 = wily_5_weaknesses.pop() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 weak1 = weak2 = weak3 = 0 | 
					
						
							|  |  |  |             patch.write_byte(0x2DA2E, weak1) | 
					
						
							|  |  |  |             patch.write_byte(0x2DA32, weak2) | 
					
						
							|  |  |  |             patch.write_byte(0x2DA3A, weak3) | 
					
						
							|  |  |  |         enemy_weaknesses["Picopico-kun"] = {weapon: world.weapon_damage[weapon][9] for weapon in range(8)} | 
					
						
							|  |  |  |         enemy_weaknesses["Boobeam Trap"] = {weapon: world.weapon_damage[weapon][11] for weapon in range(8)} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.enemy_weakness: | 
					
						
							|  |  |  |         for enemy in enemy_addresses: | 
					
						
							|  |  |  |             if enemy in ("Picopico-kun", "Boobeam Trap"): | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             enemy_weaknesses[enemy] = {weapon: world.random.randint(-4, 4) for weapon in enemy_weakness_ptrs} | 
					
						
							|  |  |  |             if enemy == "Friender": | 
					
						
							|  |  |  |                 # Friender has to be killed, need buster damage to not break logic | 
					
						
							|  |  |  |                 enemy_weaknesses[enemy][0] = max(enemy_weaknesses[enemy][0], 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for enemy, damage_table in enemy_weaknesses.items(): | 
					
						
							|  |  |  |         for weapon in enemy_weakness_ptrs: | 
					
						
							|  |  |  |             if damage_table[weapon] < 0: | 
					
						
							|  |  |  |                 damage_table[weapon] = 256 + damage_table[weapon] | 
					
						
							|  |  |  |             patch.write_byte(enemy_weakness_ptrs[weapon] + enemy_addresses[enemy], damage_table[weapon]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.quickswap: | 
					
						
							|  |  |  |         patch.write_byte(quickswap_ptr + 1, 0x01) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.consumables != Consumables.option_all: | 
					
						
							|  |  |  |         value_a = 0x7C | 
					
						
							|  |  |  |         value_b = 0x76 | 
					
						
							|  |  |  |         if world.options.consumables == Consumables.option_1up_etank: | 
					
						
							|  |  |  |             value_b = 0x7A | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             value_a = 0x7A | 
					
						
							|  |  |  |         patch.write_byte(consumables_ptr - 3, value_a) | 
					
						
							|  |  |  |         patch.write_byte(consumables_ptr + 1, value_b) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     patch.write_byte(wily_5_ptr + 1, world.options.wily_5_requirement.value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.energy_link: | 
					
						
							|  |  |  |         patch.write_byte(energylink_ptr + 1, 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.reduce_flashing: | 
					
						
							|  |  |  |         if world.options.reduce_flashing.value == ReduceFlashing.option_virtual_console: | 
					
						
							|  |  |  |             color = 0x2D  # Dark Gray | 
					
						
							|  |  |  |             speed = -1 | 
					
						
							|  |  |  |         elif world.options.reduce_flashing.value == ReduceFlashing.option_minor: | 
					
						
							|  |  |  |             color = 0x2D | 
					
						
							|  |  |  |             speed = 0x08 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             color = 0x0F | 
					
						
							|  |  |  |             speed = 0x00 | 
					
						
							|  |  |  |         patch.write_byte(0x2D1B0, color)  # Change white to a dark gray, Mecha Dragon | 
					
						
							|  |  |  |         patch.write_byte(0x2D397, 0x0F)  # Longer flash time, Mecha Dragon kill | 
					
						
							|  |  |  |         patch.write_byte(0x2D3A0, color)  # Change white to a dark gray, Picopico-kun/Boobeam Trap | 
					
						
							|  |  |  |         patch.write_byte(0x2D65F, color)  # Change white to a dark gray, Guts Tank | 
					
						
							|  |  |  |         patch.write_byte(0x2DA94, color)  # Change white to a dark gray, Wily Machine | 
					
						
							|  |  |  |         patch.write_byte(0x2DC97, color)  # Change white to a dark gray, Alien | 
					
						
							|  |  |  |         patch.write_byte(0x2DD68, 0x10)  # Longer flash time, Alien kill | 
					
						
							|  |  |  |         patch.write_bytes(0x2DF14, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])  # Reduce final Alien flash to 1 big flash | 
					
						
							|  |  |  |         patch.write_byte(0x34132, 0x08)  # Longer flash time, Stage Select | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if world.options.reduce_flashing.value == ReduceFlashing.option_full: | 
					
						
							|  |  |  |             # reduce color of stage flashing | 
					
						
							|  |  |  |             patch.write_bytes(0x344C9, [0x2D, 0x10, 0x00, 0x2D, | 
					
						
							|  |  |  |                                         0x0F, 0x10, 0x2D, 0x00, | 
					
						
							|  |  |  |                                         0x0F, 0x10, 0x2D, 0x00, | 
					
						
							|  |  |  |                                         0x0F, 0x10, 0x2D, 0x00, | 
					
						
							|  |  |  |                                         0x2D, 0x10, 0x2D, 0x00, | 
					
						
							|  |  |  |                                         0x0F, 0x10, 0x2D, 0x00, | 
					
						
							|  |  |  |                                         0x0F, 0x10, 0x2D, 0x00, | 
					
						
							|  |  |  |                                         0x0F, 0x10, 0x2D, 0x00]) | 
					
						
							|  |  |  |             # remove wily castle flash | 
					
						
							|  |  |  |             patch.write_byte(0x3596D, 0x0F) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if speed != -1: | 
					
						
							|  |  |  |             patch.write_byte(0xFE01, speed)  # Bubble Man Stage | 
					
						
							|  |  |  |             patch.write_byte(0x1BE01, speed)  # Metal Man Stage | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if world.options.random_music: | 
					
						
							|  |  |  |         if world.options.random_music == RandomMusic.option_none: | 
					
						
							|  |  |  |             pool = [0xFF] * 20 | 
					
						
							|  |  |  |             # A couple of additional mutes we want here | 
					
						
							|  |  |  |             patch.write_byte(0x37819, 0xFF)  # Credits | 
					
						
							|  |  |  |             patch.write_byte(0x378A4, 0xFF)  # Credits #2 | 
					
						
							|  |  |  |             patch.write_byte(0x37149, 0xFF)  # Game Over Jingle | 
					
						
							|  |  |  |             patch.write_byte(0x341BA, 0xFF)  # Robot Master Jingle | 
					
						
							|  |  |  |             patch.write_byte(0x2E0B4, 0xFF)  # Robot Master Defeated | 
					
						
							|  |  |  |             patch.write_byte(0x35B78, 0xFF)  # Wily Castle | 
					
						
							|  |  |  |             patch.write_byte(0x2DFA5, 0xFF)  # Wily Defeated | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         elif world.options.random_music == RandomMusic.option_shuffled: | 
					
						
							|  |  |  |             pool = [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 9, 0x10, 0xC, 0xB, 0x17, 0x13, 0xE, 0xD] | 
					
						
							|  |  |  |             world.random.shuffle(pool) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             pool = world.random.choices([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xB, 0xC, 0xD, 0xE, 0x10, 0x13, 0x17], k=20) | 
					
						
							|  |  |  |         patch.write_bytes(0x381E0, pool[:13]) | 
					
						
							|  |  |  |         patch.write_byte(0x36318, pool[13])  # Game Start | 
					
						
							|  |  |  |         patch.write_byte(0x37181, pool[13])  # Game Over | 
					
						
							|  |  |  |         patch.write_byte(0x340AE, pool[14])  # RBM Select | 
					
						
							|  |  |  |         patch.write_byte(0x39005, pool[15])  # Robot Master Battle | 
					
						
							|  |  |  |         patch.write_byte(get_equipped_sound_ptr + 1, pool[16])  # Get Equipped, we actually hook this already lmao | 
					
						
							|  |  |  |         patch.write_byte(0x3775A, pool[17])  # Epilogue | 
					
						
							|  |  |  |         patch.write_byte(0x36089, pool[18])  # Intro | 
					
						
							|  |  |  |         patch.write_byte(0x361F1, pool[19])  # Title | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     from Utils import __version__ | 
					
						
							|  |  |  |     patch.name = bytearray(f'MM2{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', | 
					
						
							|  |  |  |                            'utf8')[:21] | 
					
						
							|  |  |  |     patch.name.extend([0] * (21 - len(patch.name))) | 
					
						
							|  |  |  |     patch.write_bytes(0x3FFC0, patch.name) | 
					
						
							|  |  |  |     deathlink_byte = world.options.death_link.value | (world.options.energy_link.value << 1) | 
					
						
							|  |  |  |     patch.write_byte(0x3FFD5, deathlink_byte) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     patch.write_bytes(0x3FFD8, world.world_version) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     version_map = { | 
					
						
							|  |  |  |         "0": 0x90, | 
					
						
							|  |  |  |         "1": 0x91, | 
					
						
							|  |  |  |         "2": 0x92, | 
					
						
							|  |  |  |         "3": 0x93, | 
					
						
							|  |  |  |         "4": 0x94, | 
					
						
							|  |  |  |         "5": 0x95, | 
					
						
							|  |  |  |         "6": 0x96, | 
					
						
							|  |  |  |         "7": 0x97, | 
					
						
							|  |  |  |         "8": 0x98, | 
					
						
							|  |  |  |         "9": 0x99, | 
					
						
							|  |  |  |         ".": 0xDC | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     patch.write_token(APTokenTypes.RLE, 0x36EE0, (11, 0)) | 
					
						
							|  |  |  |     patch.write_token(APTokenTypes.RLE, 0x36EEE, (25, 0)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # BY SILVRIS | 
					
						
							|  |  |  |     patch.write_bytes(0x36EE0, [0xC2, 0xD9, 0xC0, 0xD3, 0xC9, 0xCC, 0xD6, 0xD2, 0xC9, 0xD3]) | 
					
						
							|  |  |  |     # ARCHIPELAGO x.x.x | 
					
						
							|  |  |  |     patch.write_bytes(0x36EF2, [0xC1, 0xD2, 0xC3, 0xC8, 0xC9, 0xD0, 0xC5, 0xCC, 0xC1, 0xC7, 0xCF, 0xC0]) | 
					
						
							|  |  |  |     patch.write_bytes(0x36EFE, list(map(lambda c: version_map[c], __version__))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     patch.write_file("token_patch.bin", patch.get_token_binary()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | header = b"\x4E\x45\x53\x1A\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def read_headerless_nes_rom(rom: bytes) -> bytes: | 
					
						
							|  |  |  |     if rom[:4] == b"NES\x1A": | 
					
						
							|  |  |  |         return rom[16:] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         return rom | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_base_rom_bytes(file_name: str = "") -> bytes: | 
					
						
							|  |  |  |     base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None) | 
					
						
							|  |  |  |     if not base_rom_bytes: | 
					
						
							|  |  |  |         file_name = get_base_rom_path(file_name) | 
					
						
							|  |  |  |         base_rom_bytes = read_headerless_nes_rom(bytes(open(file_name, "rb").read())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         basemd5 = hashlib.md5() | 
					
						
							|  |  |  |         basemd5.update(base_rom_bytes) | 
					
						
							|  |  |  |         if basemd5.hexdigest() == PROTEUSHASH: | 
					
						
							|  |  |  |             base_rom_bytes = extract_mm2(base_rom_bytes) | 
					
						
							|  |  |  |             basemd5 = hashlib.md5() | 
					
						
							|  |  |  |             basemd5.update(base_rom_bytes) | 
					
						
							|  |  |  |         if basemd5.hexdigest() not in {MM2LCHASH, MM2NESHASH, MM2VCHASH}: | 
					
						
							|  |  |  |             print(basemd5.hexdigest()) | 
					
						
							|  |  |  |             raise Exception("Supplied Base Rom does not match known MD5 for US, LC, or US VC release. " | 
					
						
							|  |  |  |                             "Get the correct game and version, then dump it") | 
					
						
							|  |  |  |         headered_rom = bytearray(base_rom_bytes) | 
					
						
							|  |  |  |         headered_rom[0:0] = header | 
					
						
							|  |  |  |         setattr(get_base_rom_bytes, "base_rom_bytes", bytes(headered_rom)) | 
					
						
							|  |  |  |         return bytes(headered_rom) | 
					
						
							|  |  |  |     return base_rom_bytes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_base_rom_path(file_name: str = "") -> str: | 
					
						
							|  |  |  |     options: settings.Settings = settings.get_settings() | 
					
						
							|  |  |  |     if not file_name: | 
					
						
							|  |  |  |         file_name = options["mm2_options"]["rom_file"] | 
					
						
							|  |  |  |     if not os.path.exists(file_name): | 
					
						
							|  |  |  |         file_name = Utils.user_path(file_name) | 
					
						
							|  |  |  |     return file_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PRG_OFFSET = 0x8ED70 | 
					
						
							|  |  |  | PRG_SIZE = 0x40000 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def extract_mm2(proteus: bytes) -> bytes: | 
					
						
							|  |  |  |     mm2 = bytearray(proteus[PRG_OFFSET:PRG_OFFSET + PRG_SIZE]) | 
					
						
							|  |  |  |     return bytes(mm2) |