| 
									
										
										
										
											2023-11-26 11:17:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | from NetUtils import ClientStatus, color | 
					
						
							|  |  |  | from worlds.AutoSNIClient import SNIClient | 
					
						
							|  |  |  | from .Regions import offset | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | snes_logger = logging.getLogger("SNES") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ROM_NAME = (0x7FC0, 0x7FD4 + 1 - 0x7FC0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | READ_DATA_START = 0xF50EA8 | 
					
						
							|  |  |  | READ_DATA_END = 0xF50FE7 + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GAME_FLAGS = (0xF50EA8, 64) | 
					
						
							|  |  |  | COMPLETED_GAME = (0xF50F22, 1) | 
					
						
							|  |  |  | BATTLEFIELD_DATA = (0xF50FD4, 20) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RECEIVED_DATA = (0xE01FF0, 3) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ITEM_CODE_START = 0x420000 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | IN_GAME_FLAG = (4 * 8) + 2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | NPC_CHECKS = { | 
					
						
							|  |  |  |     4325676: ((6 * 8) + 4, False),  # Old Man Level Forest | 
					
						
							|  |  |  |     4325677: ((3 * 8) + 6, True),  # Kaeli Level Forest | 
					
						
							|  |  |  |     4325678: ((25 * 8) + 1, True),  # Tristam | 
					
						
							|  |  |  |     4325680: ((26 * 8) + 0, True),  # Aquaria Vendor Girl | 
					
						
							|  |  |  |     4325681: ((29 * 8) + 2, True),  # Phoebe Wintry Cave | 
					
						
							|  |  |  |     4325682: ((25 * 8) + 6, False),  # Mysterious Man (Life Temple) | 
					
						
							|  |  |  |     4325683: ((29 * 8) + 3, True),  # Reuben Mine | 
					
						
							|  |  |  |     4325684: ((29 * 8) + 7, True),  # Spencer | 
					
						
							|  |  |  |     4325685: ((29 * 8) + 6, False),  # Venus Chest | 
					
						
							|  |  |  |     4325686: ((29 * 8) + 1, True),  # Fireburg Tristam | 
					
						
							|  |  |  |     4325687: ((26 * 8) + 1, True),  # Fireburg Vendor Girl | 
					
						
							|  |  |  |     4325688: ((14 * 8) + 4, True),  # MegaGrenade Dude | 
					
						
							|  |  |  |     4325689: ((29 * 8) + 5, False),  # Tristam's Chest | 
					
						
							|  |  |  |     4325690: ((29 * 8) + 4, True),  # Arion | 
					
						
							|  |  |  |     4325691: ((29 * 8) + 0, True),  # Windia Kaeli | 
					
						
							|  |  |  |     4325692: ((26 * 8) + 2, True),  # Windia Vendor Girl | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_flag(data, flag): | 
					
						
							|  |  |  |     byte = int(flag / 8) | 
					
						
							|  |  |  |     bit = int(0x80 / (2 ** (flag % 8))) | 
					
						
							|  |  |  |     return (data[byte] & bit) > 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FFMQClient(SNIClient): | 
					
						
							|  |  |  |     game = "Final Fantasy Mystic Quest" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async def validate_rom(self, ctx): | 
					
						
							|  |  |  |         from SNIClient import snes_read | 
					
						
							|  |  |  |         rom_name = await snes_read(ctx, *ROM_NAME) | 
					
						
							|  |  |  |         if rom_name is None: | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |         if rom_name[:2] != b"MQ": | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ctx.rom = rom_name | 
					
						
							|  |  |  |         ctx.game = self.game | 
					
						
							|  |  |  |         ctx.items_handling = 0b001 | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async def game_watcher(self, ctx): | 
					
						
							|  |  |  |         from SNIClient import snes_buffered_write, snes_flush_writes, snes_read | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         check_1 = await snes_read(ctx, 0xF53749, 1) | 
					
						
							|  |  |  |         received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1]) | 
					
						
							|  |  |  |         data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START) | 
					
						
							|  |  |  |         check_2 = await snes_read(ctx, 0xF53749, 1) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:21:02 -05:00
										 |  |  |         if check_1 in (b'\x00', b'\x55') or check_2 in (b'\x00', b'\x55'): | 
					
						
							| 
									
										
										
										
											2023-11-26 11:17:59 -05:00
										 |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def get_range(data_range): | 
					
						
							|  |  |  |             return data[data_range[0] - READ_DATA_START:data_range[0] + data_range[1] - READ_DATA_START] | 
					
						
							|  |  |  |         completed_game = get_range(COMPLETED_GAME) | 
					
						
							|  |  |  |         battlefield_data = get_range(BATTLEFIELD_DATA) | 
					
						
							|  |  |  |         game_flags = get_range(GAME_FLAGS) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if game_flags is None: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         if not get_flag(game_flags, IN_GAME_FLAG): | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not ctx.finished_game: | 
					
						
							|  |  |  |             if completed_game[0] & 0x80 and game_flags[30] & 0x18: | 
					
						
							|  |  |  |                 await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) | 
					
						
							|  |  |  |                 ctx.finished_game = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         old_locations_checked = ctx.locations_checked.copy() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for container in range(256): | 
					
						
							|  |  |  |             if get_flag(game_flags, (0x20 * 8) + container): | 
					
						
							|  |  |  |                 ctx.locations_checked.add(offset["Chest"] + container) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for location, data in NPC_CHECKS.items(): | 
					
						
							|  |  |  |             if get_flag(game_flags, data[0]) is data[1]: | 
					
						
							|  |  |  |                 ctx.locations_checked.add(location) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for battlefield in range(20): | 
					
						
							|  |  |  |             if battlefield_data[battlefield] == 0: | 
					
						
							|  |  |  |                 ctx.locations_checked.add(offset["BattlefieldItem"] + battlefield + 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if old_locations_checked != ctx.locations_checked: | 
					
						
							|  |  |  |             await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": ctx.locations_checked}]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if received[0] == 0: | 
					
						
							|  |  |  |             received_index = int.from_bytes(received[1:], "big") | 
					
						
							|  |  |  |             if received_index < len(ctx.items_received): | 
					
						
							|  |  |  |                 item = ctx.items_received[received_index] | 
					
						
							|  |  |  |                 received_index += 1 | 
					
						
							|  |  |  |                 code = (item.item - ITEM_CODE_START) + 1 | 
					
						
							|  |  |  |                 if code > 256: | 
					
						
							|  |  |  |                     code -= 256 | 
					
						
							|  |  |  |                 snes_buffered_write(ctx, RECEIVED_DATA[0], bytes([code, *received_index.to_bytes(2, "big")])) | 
					
						
							|  |  |  |         await snes_flush_writes(ctx) |