Skip to content

Commit

Permalink
v1.6.2
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesbrq committed Sep 20, 2024
1 parent a14041d commit 5abcf8f
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 55 deletions.
122 changes: 71 additions & 51 deletions worlds/gl/GauntletLegendsClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,21 @@ def send(self, message: str):
except Exception as e:
raise Exception("An error occurred while sending a message.")


async def read(self, message: str) -> Optional[bytes]:
# Send the message
self.send(message)
response = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(self.socket, 30000), 1.0)
data = response.decode().split(" ")
b = b""
for s in data[2:]:
if "-1" in s:
logger.info("-1 response")
raise Exception("Client tried to read from an invalid address or ROM is not open.")
b += bytes.fromhex(s)
return b
try:
response = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(self.socket, 30000), 1.0)
data = response.decode().split(" ")
b = b""
for s in data[2:]:
if "-1" in s:
logger.info("-1 response")
raise Exception("Client tried to read from an invalid address or ROM is not open.")
b += bytes.fromhex(s)
return b
except asyncio.TimeoutError:
logger.error("Timeout while waiting for socket response")
return None

async def status(self) -> str:
message = "GET_STATUS"
Expand Down Expand Up @@ -184,17 +186,16 @@ def __init__(self, server_address, password):
self.difficulty: int = 0
self.players: int = 1
self.gl_sync_task = None
self.received_index: int = 0
self.glslotdata = None
self.crc32 = None
self.socket = RetroSocket()
self.rom_loaded: bool = False
self.locations_checked: List[int] = []
self.inventory: List[List[InventoryEntry]] = []
self.inventory_raw: List[RamChunk] = []
self.item_objects: List[ObjectEntry] = []
self.obelisk_objects: List[ObjectEntry] = []
self.chest_objects: List[ObjectEntry] = []
self.item_objects_init: List[ObjectEntry] = []
self.chest_objects_init: List[ObjectEntry] = []
self.retro_connected: bool = False
self.level_loading: bool = False
self.in_game: bool = False
Expand All @@ -212,10 +213,11 @@ def __init__(self, server_address, password):
self.offset: int = -1
self.clear_counts = None
self.current_level: bytes = b""
self.logged: bool = False
self.output_file: str = ""
self.movement: int = 0
self.init_refactor: bool = False
self.location_scouts: list[NetworkItem] = []
self.character_loaded: bool = False

def on_deathlink(self, data: dict):
self.deathlink_pending = True
Expand Down Expand Up @@ -277,19 +279,19 @@ async def get_obj_addr(self) -> int:
# Objects are 0x3C bytes long
# Modes: 0 = items, 1 = chests/barrels
async def obj_read(self, mode=0):
_obj = []
_obj: List[ObjectEntry] = []
b: RamChunk
if self.offset == -1:
log_arr = []
for i in range(5):
b = RamChunk(await self.socket.read(message_format(READ, f"0x{format(OBJ_ADDR, 'x')} {100}")))
b = RamChunk(await self.socket.read(message_format(READ, f"0x{format(OBJ_ADDR + ((i * 100) * 0x3C), 'x')} {100 * 0x3C}")))
b.iterate(0x3C)
log_arr += [b.split]
log_arr += [arr for arr in b.split]
output_folder = 'logs'
output_file = os.path.join(output_folder, f"[{datetime.datetime.now()}] Gauntlet Legends RAMSTATE: {level_names[self.current_level[1] << 0xF + self.current_level[0]]}.txt")
with open(output_file, 'w') as f:
for arr in log_arr:
f.write(" ".join(f"{byte:02x}" for byte in arr) + '\n')
self.output_file = os.path.join(output_folder, f"({datetime.datetime.now().strftime('%Y-%m-%d - %I-%M-%S-%p')}) Gauntlet Legends RAMSTATE - {level_names[(self.current_level[1] << 4) + self.current_level[0]]}.txt")
with open(self.output_file, 'w') as f:
for i, arr in enumerate(log_arr):
f.write(f"0x{format(OBJ_ADDR + (0x3C * i), 'x')}: " + " ".join(f"{int(byte):02x}" for byte in arr) + '\n')
b = RamChunk(await self.socket.read(message_format(READ, f"0x{format(OBJ_ADDR, 'x')} {0x40 * 0x3C}")))
b.iterate(0x3C)
for i, obj in enumerate(b.split):
Expand All @@ -311,6 +313,10 @@ async def obj_read(self, mode=0):
if count <= 0:
break
self.extra_items += count
if not self.logged:
with open(self.output_file, 'a') as f:
f.write(f"Item Address: 0x{format(OBJ_ADDR + (self.offset * 0x3C), 'x')}\n")
f.write(f"Extra Items: {self.extra_items}\n")
else:
spawner_count = len([spawner for spawner in spawners[(self.current_level[1] << 4) + (
self.current_level[0] if self.current_level[1] != 1 else castle_id.index(self.current_level[0]) + 1)]
Expand All @@ -328,6 +334,10 @@ async def obj_read(self, mode=0):
)
b.iterate(0x3C)
count += sum(1 for obj in b.split if obj[1] == 0xFF)
if not self.logged:
with open(self.output_file, 'a') as f:
f.write(f"Spawner Address {i + 1}: 0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + (i * 100)) * 0x3C), 'x')}\n")
f.write(f"Count: {count}\n")
self.extra_spawners += count
count = 0
if self.extra_spawners != 0:
Expand All @@ -346,6 +356,9 @@ async def obj_read(self, mode=0):
break
count += current_count
self.extra_spawners += count
if not self.logged:
with open(self.output_file, 'a') as f:
f.write(f"Total Extra Spawners: {self.extra_spawners}\n")
while True:
b = RamChunk(
await self.socket.read(
Expand All @@ -360,13 +373,24 @@ async def obj_read(self, mode=0):
if count <= 0:
break
self.extra_chests += count
if not self.logged:
with open(self.output_file, 'a') as f:
f.write(
f"Chest Address: 0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + self.extra_spawners + spawner_count) * 0x3C), 'x')}\n")
f.write(f"Extra Chests: {self.extra_chests}\n")
self.logged = True
for arr in b.split:
_obj += [ObjectEntry(arr)]
_obj = [obj for obj in _obj if obj.raw[1] != 0xFF]
if mode == 1:
self.chest_objects = _obj[:len(self.chest_locations)]
if self.chest_objects_init == []:
self.chest_objects_init = self.chest_objects
else:
self.item_objects = _obj[:len(self.item_locations)]
if self.item_objects_init == []:
self.item_objects_init = self.item_objects


# Update item count of an item.
# If the item is new, add it to your inventory
Expand Down Expand Up @@ -499,22 +523,14 @@ async def server_auth(self, password_requested: bool = False):
await self.get_username()
await self.send_connect()

async def connection_closed(self):
self.retro_connected = False
await self.var_reset()
await super(GauntletLegendsContext, self).connection_closed()

async def disconnect(self, allow_autoreconnect: bool = False):
self.retro_connected = False
await self.var_reset()
await super(GauntletLegendsContext, self).disconnect()

def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
self.slot = args["slot"]
self.glslotdata = args["slot_data"]
self.players = self.glslotdata["players"]
self.var_reset()
logger.info(f"Players set to {self.players}.")
logger.info("If this is incorrect, Use /players to set the number of people playing locally.")
elif cmd == "Retrieved":
if "keys" not in args:
logger.warning(f"invalid Retrieved packet to GLClient: {args}")
Expand Down Expand Up @@ -614,7 +630,7 @@ async def scout_locations(self, ctx: "GauntletLegendsContext") -> None:
message_format(
WRITE,
param_format(
BOSS_ADDR, bytes([self.glslotdata["shards"][i][1], 0x0, self.glslotdata["shards"][i][0]]),
BOSS_ADDR + (0x10 * i), bytes([self.glslotdata["shards"][i][1], 0x0, self.glslotdata["shards"][i][0]]),
),
),
)
Expand Down Expand Up @@ -660,6 +676,7 @@ async def scout_locations(self, ctx: "GauntletLegendsContext") -> None:
if "Obelisk" in items_by_id.get(item.item, ItemData(0, "", ItemClassification.filler)).item_name
and item.player == self.slot
]
logger.info(f"Obelisks: {len(self.obelisks)}")
self.useful = [
item
for item in self.location_scouts
Expand All @@ -671,16 +688,19 @@ async def scout_locations(self, ctx: "GauntletLegendsContext") -> None:
self.obelisk_locations = [
location for location in raw_locations if location.id in [item.location for item in self.obelisks]
]
logger.info(f"Obelisk Locations: {len(self.obelisk_locations)}")
self.item_locations = [
location for location in raw_locations
if ("Chest" not in location.name
and ("Barrel" not in location.name or "Barrel of Gold" in location.name))
and location not in self.obelisk_locations
or location.id in [item.location for item in self.useful]
]
logger.info(f"Item Locations: {len(self.item_locations)}")
self.chest_locations = [
location for location in raw_locations
if location not in self.obelisk_locations and location not in self.item_locations]
logger.info(f"Chest Locations: {len(self.chest_locations)}")
max_value: int = self.glslotdata['max']
logger.info(f"Items: {len(self.item_locations)} Chests: {len(self.chest_locations)} Obelisks: {len(self.obelisk_locations)}")
logger.info(
Expand All @@ -691,28 +711,26 @@ async def scout_locations(self, ctx: "GauntletLegendsContext") -> None:
# Sends locations out to server based on object lists read in obj_read()
# Local obelisks and mirror shards have special cases
async def location_loop(self) -> List[int]:
if not self.logged:
await asyncio.sleep(0.5)
await self.obj_read()
await self.obj_read(1)
acquired = []
for i, obj in enumerate(self.item_objects):
if obj.raw[24] != 0x0:
if int.from_bytes(obj.raw[8:12], "little") != int.from_bytes(self.item_objects_init[i].raw[8:12], "little"):
if self.item_locations[i].id not in self.locations_checked:
acquired += [self.item_locations[i].id]
for j in range(len(self.obelisk_locations)):
ob = await self.inv_bitwise("Obelisk", base_count[items_by_id[self.obelisks[j].item].item_name], 0)
if ob:
acquired += [self.obelisk_locations[j].id]
for k, obj in enumerate(self.chest_objects):
if "Chest" in self.chest_locations[k].name:
if obj.raw[24] != 0x0:
if self.chest_locations[k].id not in self.locations_checked:
acquired += [self.chest_locations[k].id]
else:
if obj.raw[0x33] != 0:
if self.chest_locations[k].id not in self.locations_checked:
acquired += [self.chest_locations[k].id]
if int.from_bytes(obj.raw[8:12], "little") != int.from_bytes(self.chest_objects_init[k].raw[8:12], "little"):
if self.chest_locations[k].id not in self.locations_checked:
acquired += [self.chest_locations[k].id]
paused = await self.paused()
if paused:
dead = await self.dead()
if paused or dead:
return []
return acquired

Expand Down Expand Up @@ -776,12 +794,14 @@ async def level_status(self, ctx: "GauntletLegendsContext") -> bool:
else:
if self.deathlink_enabled:
await ctx.send_death(f"{ctx.auth} didn't eat enough meat.")
await self.var_reset()
self.var_reset()
return True
return False

async def var_reset(self):
def var_reset(self):
self.objects_loaded = False
self.logged = False
self.output_file = ""
self.extra_items = 0
self.extra_spawners = 0
self.extra_chests = 0
Expand Down Expand Up @@ -839,7 +859,6 @@ async def _patch_and_run_game(patch_file: str):
# Checks location status inside of levels
async def gl_sync_task(ctx: GauntletLegendsContext):
logger.info("Starting N64 connector...")
logger.info("Use /players to set the number of people playing locally. This is required for the client to function.")
while not ctx.exit_event.is_set():
if ctx.retro_connected:
try:
Expand All @@ -848,9 +867,10 @@ async def gl_sync_task(ctx: GauntletLegendsContext):
status = status.split(" ")
if status[1] == "CONTENTLESS":
logger.info("No ROM loaded, waiting...")
await asyncio.sleep(1)
await asyncio.sleep(3)
continue
else:
logger.info("ROM Loaded")
ctx.rom_loaded = True
cc_str: str = f"gl_cc_T{ctx.team}_P{ctx.slot}"
pl_str: str = f"gl_pl_T{ctx.team}_P{ctx.slot}"
Expand Down Expand Up @@ -943,20 +963,20 @@ async def gl_sync_task(ctx: GauntletLegendsContext):
await asyncio.sleep(0.1)
except TimeoutError:
logger.info("Connection Timed Out, Reconnecting")
await ctx.var_reset()
ctx.var_reset()
ctx.socket = RetroSocket()
ctx.retro_connected = False
await asyncio.sleep(2)
except ConnectionResetError:
logger.info("Connection Lost, Reconnecting")
await ctx.var_reset()
ctx.var_reset()
ctx.socket = RetroSocket()
ctx.retro_connected = False
await asyncio.sleep(2)
except Exception as e:
logger.error(f"Unknown Error Occurred: {e}")
logger.info(traceback.format_exc())
await ctx.var_reset()
ctx.var_reset()
ctx.socket = RetroSocket()
ctx.retro_connected = False
await asyncio.sleep(2)
Expand All @@ -968,7 +988,7 @@ async def gl_sync_task(ctx: GauntletLegendsContext):
status = status.split(" ")
if status[1] == "CONTENTLESS":
ctx.rom_loaded = False
await asyncio.sleep(2)
logger.info("Connected to Retroarch")
continue
except TimeoutError:
logger.info("Connection Timed Out, Trying Again")
Expand Down
8 changes: 4 additions & 4 deletions worlds/gl/Rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def patch_items(caller: APProcedurePatch, rom: bytes):
"Barrel" in location_name and "Barrel of Gold" not in location_name
):
data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][12:14] = [0x27, 0x4]
if "Chest" in location_name:
data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][9] = 0x1
else:
data.items[j - data.items_replaced_by_obelisks][6:8] = [0x27, 0x4]
else:
Expand Down Expand Up @@ -179,11 +181,9 @@ def patch_items(caller: APProcedurePatch, rom: bytes):
data.chests_replaced_by_items += 1
else:
if chest_barrel(location_name):
data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][12] = item_dict[item[0]][0]
data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][13] = item_dict[item[0]][1]
data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][12:13] = item_dict[item[0]]
else:
data.items[j - data.items_replaced_by_obelisks][6] = item_dict[item[0]][0]
data.items[j - data.items_replaced_by_obelisks][7] = item_dict[item[0]][1]
data.items[j - data.items_replaced_by_obelisks][6:7] = item_dict[item[0]]
uncompressed = level_data_reformat(data)
compressed = zenc(uncompressed)
stream.seek(level_header[i] + 4, 0)
Expand Down

0 comments on commit 5abcf8f

Please sign in to comment.