* Revert reset protection * Fix reset protection --------- Co-authored-by: alchav <alchav@jalchavware.com>
		
			
				
	
	
		
			120 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			120 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
 | 
						|
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)
 | 
						|
        if check_1 != b'\x01' or check_2 != b'\x01':
 | 
						|
            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)
 |