2023-11-26 16:17:59 +00: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-07-29 17:40:58 +00:00
|
|
|
if check_1 != b'\x01' or check_2 != b'\x01':
|
2023-11-26 16:17:59 +00: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)
|