mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 07:03:44 -08:00
Jak and Daxter: Replace Pymem, Add Linux Support (#5850)
* Replace pymem with PyMemoryEditor (nonworking) * Add back pymem for faster windows address searching. * Replace other uses of pymem, parameterize executable names. * Updated to add linux and potential MacOS support to launching gk and … (#84) * Updated to add linux and potential MacOS support to launching gk and goalc. Still needs tested on MacOS. * Switched to using x-terminal-emulator instead of trying to find gnome-terminal or konsole Made argument building for suprocessing goalc easier to read Fixed OS X support to use osascript instead of attempting to run Terminal directly * Changed Terminal usage to use Archipelago's Launh utility, which handles terminal launching for me for both linux and OS X * Added try/except to re-connect the memory process. The process file/id changes over time on linux, and this works to re-connect without needing to restart * Removed Unsetting env var in favor of reporting to the source authors * Putting PyMemoryEditor local. (#85) * Putting PyMemoryEditor local --------- Co-authored-by: massimilianodelliubaldini <8584296+massimilianodelliubaldini@users.noreply.github.com> * Fixing minor problems (#87) * Refactor away circular launcher import. * Push latest PyMemoryEditor scan utility (#91) Co-authored-by: Louis M <Louis M> * Remove Pymem, rely solely on PyMemoryEditor. Add konsole support. * Jak 1: Remove vendored copy of PME, update imports, requirements, and manifest. * Jak 1: Prevent server connect until game is properly setup. * Jak 1: reduce REPL/Compiler confusion, small updates to setup guide. * Write hack for Konsole on AppImage to avoid OpenSSL error. * Refactor LD_LIBRARY_PATH hack. * Update worlds/jakanddaxter/agents/memory_reader.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update worlds/jakanddaxter/agents/memory_reader.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: Morgan <morgan07kelley@gmail.com> Co-authored-by: Louis M <prog@tioui.com> Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3b1971be66
commit
f3389f5d8b
@@ -1,13 +1,14 @@
|
||||
import logging
|
||||
import random
|
||||
import struct
|
||||
import sys
|
||||
from typing import ByteString, Callable
|
||||
import json
|
||||
import pymem
|
||||
from pymem import pattern
|
||||
from pymem.exception import ProcessNotFound, ProcessError, MemoryReadError, WinAPIError
|
||||
from PyMemoryEditor import OpenProcess, ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess
|
||||
from dataclasses import dataclass
|
||||
|
||||
import Utils
|
||||
from ..game_id import jak1_gk
|
||||
from ..locs import (orb_locations as orbs,
|
||||
cell_locations as cells,
|
||||
scout_locations as flies,
|
||||
@@ -166,7 +167,7 @@ class JakAndDaxterMemoryReader:
|
||||
initiated_connect: bool = False
|
||||
|
||||
# The memory reader just needs the game running.
|
||||
gk_process: pymem.process = None
|
||||
gk_process: OpenProcess | None = None
|
||||
|
||||
location_outbox: list[int] = []
|
||||
outbox_index: int = 0
|
||||
@@ -227,8 +228,10 @@ class JakAndDaxterMemoryReader:
|
||||
|
||||
if self.connected:
|
||||
try:
|
||||
self.gk_process.read_bool(self.gk_process.base_address) # Ping to see if it's alive.
|
||||
except (ProcessError, MemoryReadError, WinAPIError):
|
||||
# TODO - When PyMemoryEditor issue #15 is resolved, swap out this line for the commented one.
|
||||
# self.gk_process.read_process_memory(0, bytes, 1) # Ping to see if it's alive.
|
||||
OpenProcess(process_name=jak1_gk)
|
||||
except (ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess):
|
||||
msg = (f"Error reading game memory! (Did the game crash?)\n"
|
||||
f"Please close all open windows and reopen the Jak and Daxter Client "
|
||||
f"from the Archipelago Launcher.\n"
|
||||
@@ -272,28 +275,35 @@ class JakAndDaxterMemoryReader:
|
||||
|
||||
async def connect(self):
|
||||
try:
|
||||
self.gk_process = pymem.Pymem("gk.exe") # The GOAL Kernel
|
||||
logger.debug("Found the gk process: " + str(self.gk_process.process_id))
|
||||
except ProcessNotFound:
|
||||
self.gk_process = OpenProcess(process_name=jak1_gk) # The GOAL Kernel
|
||||
logger.debug(f"Found the gk process: {self.gk_process.pid}")
|
||||
except ProcessNotFoundError:
|
||||
self.log_error(logger, "Could not find the game process.")
|
||||
self.connected = False
|
||||
return
|
||||
|
||||
# If we don't find the marker in the first loaded module, we've failed.
|
||||
modules = list(self.gk_process.list_modules())
|
||||
marker_address = pattern.pattern_scan_module(self.gk_process.process_handle, modules[0], self.marker)
|
||||
if marker_address:
|
||||
# At this address is another address that contains the struct we're looking for: the game's state.
|
||||
# From here we need to add the length in bytes for the marker and 4 bytes of padding,
|
||||
# and the struct address is 8 bytes long (it's an uint64).
|
||||
goal_pointer = marker_address + len(self.marker) + 4
|
||||
self.goal_address = int.from_bytes(self.gk_process.read_bytes(goal_pointer, sizeof_uint64),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
logger.debug("Found the archipelago memory address: " + str(self.goal_address))
|
||||
await self.verify_memory_version()
|
||||
if Utils.is_windows or Utils.is_linux:
|
||||
marker_addresses = list(self.gk_process.search_by_value(bytes, len(self.marker), self.marker,
|
||||
writeable_only=True))
|
||||
if len(marker_addresses) > 0:
|
||||
# If we don't find the marker in the first loaded module, we've failed.
|
||||
goal_pointer = marker_addresses[0] + len(self.marker) + 4
|
||||
|
||||
# At this address is another address that contains the struct we're looking for: the game's state.
|
||||
# From here we need to add the length in bytes for the marker and 4 bytes of padding,
|
||||
# and the struct address is 8 bytes long (it's an uint64).
|
||||
self.goal_address = int.from_bytes(
|
||||
self.gk_process.read_process_memory(goal_pointer, bytes, sizeof_uint64),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
logger.debug("Found the archipelago memory address: " + str(self.goal_address))
|
||||
await self.verify_memory_version()
|
||||
else:
|
||||
self.log_error(logger, "Could not find the Archipelago marker address!")
|
||||
self.connected = False
|
||||
|
||||
else:
|
||||
self.log_error(logger, "Could not find the Archipelago marker address!")
|
||||
self.log_error(logger, f"Unsupported operating system: {sys.platform}!")
|
||||
self.connected = False
|
||||
|
||||
async def verify_memory_version(self):
|
||||
@@ -309,8 +319,8 @@ class JakAndDaxterMemoryReader:
|
||||
self.log_success(logger, "The Memory Reader is ready!")
|
||||
self.connected = True
|
||||
else:
|
||||
raise MemoryReadError(memory_version_offset, sizeof_uint32)
|
||||
except (ProcessError, MemoryReadError, WinAPIError):
|
||||
raise Exception(memory_version_offset, sizeof_uint32)
|
||||
except (ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess, Exception):
|
||||
if memory_version is None:
|
||||
msg = (f"Could not find a version number in the OpenGOAL memory structure!\n"
|
||||
f" Expected Version: {str(expected_memory_version)}\n"
|
||||
@@ -336,7 +346,7 @@ class JakAndDaxterMemoryReader:
|
||||
self.connected = False
|
||||
|
||||
async def print_status(self):
|
||||
proc_id = str(self.gk_process.process_id) if self.gk_process else "None"
|
||||
proc_id = str(self.gk_process.pid) if self.gk_process else "None"
|
||||
last_loc = str(self.location_outbox[self.outbox_index - 1] if self.outbox_index else "None")
|
||||
msg = (f"Memory Reader Status:\n"
|
||||
f" Game process ID: {proc_id}\n"
|
||||
@@ -451,7 +461,7 @@ class JakAndDaxterMemoryReader:
|
||||
self.finished_game = True
|
||||
self.log_success(logger, "Congratulations! You finished the game!")
|
||||
|
||||
except (ProcessError, MemoryReadError, WinAPIError):
|
||||
except (ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess):
|
||||
msg = (f"Error reading game memory! (Did the game crash?)\n"
|
||||
f"Please close all open windows and reopen the Jak and Daxter Client "
|
||||
f"from the Archipelago Launcher.\n"
|
||||
@@ -467,7 +477,7 @@ class JakAndDaxterMemoryReader:
|
||||
|
||||
def read_goal_address(self, offset: int, length: int) -> int:
|
||||
return int.from_bytes(
|
||||
self.gk_process.read_bytes(self.goal_address + offset, length),
|
||||
self.gk_process.read_process_memory(self.goal_address + offset, bytes, length),
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
|
||||
|
||||
@@ -8,14 +8,13 @@ from dataclasses import dataclass
|
||||
from queue import Queue
|
||||
from typing import Callable
|
||||
|
||||
import pymem
|
||||
from pymem.exception import ProcessNotFound, ProcessError
|
||||
from PyMemoryEditor import OpenProcess, ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess
|
||||
|
||||
import asyncio
|
||||
from asyncio import StreamReader, StreamWriter, Lock
|
||||
|
||||
from NetUtils import NetworkItem
|
||||
from ..game_id import jak1_id, jak1_max
|
||||
from ..game_id import jak1_id, jak1_max, jak1_gk, jak1_goalc
|
||||
from ..items import item_table, trap_item_table
|
||||
from ..locs import (
|
||||
orb_locations as orbs,
|
||||
@@ -66,8 +65,8 @@ class JakAndDaxterReplClient:
|
||||
|
||||
# The REPL client needs the REPL/compiler process running, but that process
|
||||
# also needs the game running. Therefore, the REPL client needs both running.
|
||||
gk_process: pymem.process = None
|
||||
goalc_process: pymem.process = None
|
||||
gk_process: OpenProcess = None
|
||||
goalc_process: OpenProcess = None
|
||||
|
||||
item_inbox: dict[int, NetworkItem] = {}
|
||||
inbox_index = 0
|
||||
@@ -102,8 +101,10 @@ class JakAndDaxterReplClient:
|
||||
|
||||
if self.connected:
|
||||
try:
|
||||
self.gk_process.read_bool(self.gk_process.base_address) # Ping to see if it's alive.
|
||||
except ProcessError:
|
||||
# TODO - When PyMemoryEditor issue #15 is resolved, swap out this line for the commented one.
|
||||
# self.gk_process.read_process_memory(0, bytes, 1) # Ping to see if it's alive.
|
||||
OpenProcess(process_name=jak1_gk)
|
||||
except (ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess):
|
||||
msg = (f"Error reading game memory! (Did the game crash?)\n"
|
||||
f"Please close all open windows and reopen the Jak and Daxter Client "
|
||||
f"from the Archipelago Launcher.\n"
|
||||
@@ -115,8 +116,10 @@ class JakAndDaxterReplClient:
|
||||
self.log_error(logger, msg)
|
||||
self.connected = False
|
||||
try:
|
||||
self.goalc_process.read_bool(self.goalc_process.base_address) # Ping to see if it's alive.
|
||||
except ProcessError:
|
||||
# TODO - When PyMemoryEditor issue #15 is resolved, swap out this line for the commented one.
|
||||
# self.goalc_process.read_process_memory(0, bytes, 1) # Ping to see if it's alive.
|
||||
OpenProcess(process_name=jak1_goalc)
|
||||
except (ProcessNotFoundError, ProcessIDNotExistsError, ClosedProcess):
|
||||
msg = (f"Error sending data to compiler! (Did the compiler crash?)\n"
|
||||
f"Please close all open windows and reopen the Jak and Daxter Client "
|
||||
f"from the Archipelago Launcher.\n"
|
||||
@@ -172,21 +175,21 @@ class JakAndDaxterReplClient:
|
||||
logger.debug(response)
|
||||
return True
|
||||
else:
|
||||
self.log_error(logger, f"Unexpected response from REPL: {response}")
|
||||
self.log_error(logger, f"Unexpected response from Compiler: {response}")
|
||||
return False
|
||||
|
||||
async def connect(self):
|
||||
try:
|
||||
self.gk_process = pymem.Pymem("gk.exe") # The GOAL Kernel
|
||||
logger.debug("Found the gk process: " + str(self.gk_process.process_id))
|
||||
except ProcessNotFound:
|
||||
self.gk_process = OpenProcess(process_name=jak1_gk) # The GOAL Kernel
|
||||
logger.debug("Found the gk process: " + str(self.gk_process.pid))
|
||||
except ProcessNotFoundError:
|
||||
self.log_error(logger, "Could not find the game process.")
|
||||
return
|
||||
|
||||
try:
|
||||
self.goalc_process = pymem.Pymem("goalc.exe") # The GOAL Compiler and REPL
|
||||
logger.debug("Found the goalc process: " + str(self.goalc_process.process_id))
|
||||
except ProcessNotFound:
|
||||
self.goalc_process = OpenProcess(process_name=jak1_goalc) # The GOAL Compiler and REPL
|
||||
logger.debug("Found the goalc process: " + str(self.goalc_process.pid))
|
||||
except ProcessNotFoundError:
|
||||
self.log_error(logger, "Could not find the compiler process.")
|
||||
return
|
||||
|
||||
@@ -201,9 +204,10 @@ class JakAndDaxterReplClient:
|
||||
logger.debug(welcome_message)
|
||||
else:
|
||||
self.log_error(logger,
|
||||
f"Unable to connect to REPL websocket: unexpected welcome message \"{welcome_message}\"")
|
||||
f"Unable to connect to Compiler websocket: unexpected welcome message "
|
||||
f"\"{welcome_message}\"")
|
||||
except ConnectionRefusedError as e:
|
||||
self.log_error(logger, f"Unable to connect to REPL websocket: {e.strerror}")
|
||||
self.log_error(logger, f"Unable to connect to Compiler websocket: {e.strerror}")
|
||||
return
|
||||
|
||||
ok_count = 0
|
||||
@@ -248,13 +252,13 @@ class JakAndDaxterReplClient:
|
||||
self.connected = False
|
||||
|
||||
if self.connected:
|
||||
self.log_success(logger, "The REPL is ready!")
|
||||
self.log_success(logger, "The Compiler is ready!")
|
||||
|
||||
async def print_status(self):
|
||||
gc_proc_id = str(self.goalc_process.process_id) if self.goalc_process else "None"
|
||||
gk_proc_id = str(self.gk_process.process_id) if self.gk_process else "None"
|
||||
msg = (f"REPL Status:\n"
|
||||
f" REPL process ID: {gc_proc_id}\n"
|
||||
gc_proc_id = str(self.goalc_process.pid) if self.goalc_process else "None"
|
||||
gk_proc_id = str(self.gk_process.pid) if self.gk_process else "None"
|
||||
msg = (f"Compiler Status:\n"
|
||||
f" Compiler process ID: {gc_proc_id}\n"
|
||||
f" Game process ID: {gk_proc_id}\n")
|
||||
try:
|
||||
if self.reader and self.writer:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"game": "Jak and Daxter: The Precursor Legacy",
|
||||
"world_version": "1.0.0",
|
||||
"world_version": "1.1.5",
|
||||
"minimum_ap_version": "0.6.2",
|
||||
"authors": ["markustulliuscicero"]
|
||||
}
|
||||
|
||||
@@ -5,27 +5,27 @@ import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import shlex
|
||||
|
||||
from asyncio import Task
|
||||
from datetime import datetime
|
||||
from logging import Logger
|
||||
from shutil import which
|
||||
from typing import Awaitable
|
||||
|
||||
# Misc imports
|
||||
import colorama
|
||||
import pymem
|
||||
|
||||
from pymem.exception import ProcessNotFound
|
||||
from psutil import NoSuchProcess
|
||||
|
||||
# Archipelago imports
|
||||
import ModuleUpdate
|
||||
import Utils
|
||||
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, server_loop, gui_enabled
|
||||
from NetUtils import ClientStatus
|
||||
from PyMemoryEditor import OpenProcess, ProcessNotFoundError
|
||||
|
||||
# Jak imports
|
||||
from .game_id import jak1_name
|
||||
from .game_id import jak1_name, jak1_gk, jak1_goalc
|
||||
from .options import EnableOrbsanity
|
||||
from .agents.memory_reader import JakAndDaxterMemoryReader
|
||||
from .agents.repl_client import JakAndDaxterReplClient
|
||||
@@ -57,9 +57,9 @@ class JakAndDaxterClientCommandProcessor(ClientCommandProcessor):
|
||||
# (which takes 10-15 seconds to compile the game) have to be requested with user-initiated flags.
|
||||
# The flags are checked by the agents every main_tick.
|
||||
def _cmd_repl(self, *arguments: str):
|
||||
"""Sends a command to the OpenGOAL REPL. Arguments:
|
||||
- connect : connect the client to the REPL (goalc).
|
||||
- status : check internal status of the REPL."""
|
||||
"""Sends a command to the OpenGOAL Compiler. Arguments:
|
||||
- connect : connect the client to the compiler (goalc).
|
||||
- status : check internal status of the Compiler."""
|
||||
if arguments:
|
||||
if arguments[0] == "connect":
|
||||
self.ctx.on_log_info(logger, "This may take a bit... Wait for the success audio cue before continuing!")
|
||||
@@ -135,6 +135,12 @@ class JakAndDaxterContext(CommonContext):
|
||||
self.tags = set()
|
||||
await self.send_connect()
|
||||
|
||||
async def connect(self, address: str | None = None) -> None:
|
||||
if not self.repl.connected or not self.memr.connected:
|
||||
self.on_log_warn(logger, "Please wait for the Compiler and Memory Reader to connect to the game!")
|
||||
return
|
||||
await super(JakAndDaxterContext, self).connect(address)
|
||||
|
||||
async def disconnect(self, allow_autoreconnect: bool = False):
|
||||
self.locations_checked = set() # Clear this set to gracefully handle server disconnects.
|
||||
await super(JakAndDaxterContext, self).disconnect(allow_autoreconnect)
|
||||
@@ -336,13 +342,21 @@ class JakAndDaxterContext(CommonContext):
|
||||
|
||||
async def run_repl_loop(self):
|
||||
while True:
|
||||
await self.repl.main_tick()
|
||||
await asyncio.sleep(0.1)
|
||||
try:
|
||||
await self.repl.main_tick()
|
||||
await asyncio.sleep(0.1)
|
||||
# This catch re-engages the memr loop, enabling the client to re-connect on losing the process
|
||||
except NoSuchProcess:
|
||||
self.on_log_info(logger, "Compiler process lost. Restarting Compiler loop.")
|
||||
|
||||
async def run_memr_loop(self):
|
||||
while True:
|
||||
await self.memr.main_tick()
|
||||
await asyncio.sleep(0.1)
|
||||
try:
|
||||
await self.memr.main_tick()
|
||||
await asyncio.sleep(0.1)
|
||||
# This catch re-engages the memr loop, enabling the client to re-connect on losing the process
|
||||
except NoSuchProcess:
|
||||
self.on_log_info(logger, "Memory reader process lost. Restarting Memory reader loop.")
|
||||
|
||||
|
||||
def find_root_directory(ctx: JakAndDaxterContext):
|
||||
@@ -454,21 +468,19 @@ def find_root_directory(ctx: JakAndDaxterContext):
|
||||
|
||||
|
||||
async def run_game(ctx: JakAndDaxterContext):
|
||||
|
||||
# These may already be running. If they are not running, try to start them.
|
||||
# TODO - Support other OS's. 1: Pymem is Windows-only. 2: on Linux, there's no ".exe."
|
||||
gk_running = False
|
||||
try:
|
||||
pymem.Pymem("gk.exe") # The GOAL Kernel
|
||||
OpenProcess(process_name=jak1_gk) # The GOAL Kernel
|
||||
gk_running = True
|
||||
except ProcessNotFound:
|
||||
except ProcessNotFoundError:
|
||||
ctx.on_log_warn(logger, "Game not running, attempting to start.")
|
||||
|
||||
goalc_running = False
|
||||
try:
|
||||
pymem.Pymem("goalc.exe") # The GOAL Compiler and REPL
|
||||
OpenProcess(process_name=jak1_goalc) # The GOAL Compiler and REPL
|
||||
goalc_running = True
|
||||
except ProcessNotFound:
|
||||
except ProcessNotFoundError:
|
||||
ctx.on_log_warn(logger, "Compiler not running, attempting to start.")
|
||||
|
||||
try:
|
||||
@@ -501,8 +513,8 @@ async def run_game(ctx: JakAndDaxterContext):
|
||||
return
|
||||
|
||||
# Now double-check the existence of the two executables we need.
|
||||
gk_path = os.path.join(root_path, "gk.exe")
|
||||
goalc_path = os.path.join(root_path, "goalc.exe")
|
||||
gk_path = os.path.join(root_path, jak1_gk)
|
||||
goalc_path = os.path.join(root_path, jak1_goalc)
|
||||
if not os.path.exists(gk_path) or not os.path.exists(goalc_path):
|
||||
msg = (f"The Game and Compiler could not be found in the ArchipelaGOAL root directory.\n"
|
||||
f"Please check your host.yaml file.\n"
|
||||
@@ -530,13 +542,26 @@ async def run_game(ctx: JakAndDaxterContext):
|
||||
log_path = os.path.join(Utils.user_path("logs"), f"JakAndDaxterGame_{timestamp}.txt")
|
||||
log_path = os.path.normpath(log_path)
|
||||
with open(log_path, "w") as log_file:
|
||||
gk_process = subprocess.Popen(
|
||||
[gk_path, "--game", "jak1",
|
||||
"--config-path", config_path,
|
||||
"--", "-v", "-boot", "-fakeiso", "-debug"],
|
||||
stdout=log_file,
|
||||
stderr=log_file,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
gk_args = [
|
||||
gk_path,
|
||||
"--game", "jak1",
|
||||
"--config-path", config_path,
|
||||
"--", "-v", "-boot", "-fakeiso", "-debug"
|
||||
]
|
||||
|
||||
if Utils.is_windows:
|
||||
gk_process = subprocess.Popen(
|
||||
gk_args,
|
||||
stdout=log_file,
|
||||
stderr=log_file,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW
|
||||
)
|
||||
else:
|
||||
gk_process = subprocess.Popen(
|
||||
gk_args,
|
||||
stdout=log_file,
|
||||
stderr=log_file
|
||||
)
|
||||
|
||||
if not goalc_running:
|
||||
# For the OpenGOAL Compiler, the existence of the "data" subfolder indicates you are running it from
|
||||
@@ -586,7 +611,36 @@ async def run_game(ctx: JakAndDaxterContext):
|
||||
goalc_args = [goalc_path, "--game", "jak1"]
|
||||
|
||||
# This needs to be a new console. The REPL console cannot share a window with any other process.
|
||||
goalc_process = subprocess.Popen(goalc_args, creationflags=subprocess.CREATE_NEW_CONSOLE)
|
||||
# We make similar decisions as Launcher.launch for terminals on Linux and Mac (despite not supporting Mac).
|
||||
if Utils.is_windows:
|
||||
goalc_process = subprocess.Popen(goalc_args, creationflags=subprocess.CREATE_NEW_CONSOLE)
|
||||
|
||||
elif Utils.is_linux:
|
||||
terminal = which('x-terminal-emulator') or which('gnome-terminal') or which('konsole') or which('xterm')
|
||||
|
||||
# Don't allow the terminal application to attempt loading system libraries, this can cause OpenSSL
|
||||
# errors when launching the REPL due to version mismatches between system vs shipped libraries.
|
||||
env = os.environ
|
||||
if "LD_LIBRARY_PATH" in env:
|
||||
env = env.copy()
|
||||
del env["LD_LIBRARY_PATH"]
|
||||
|
||||
if terminal:
|
||||
goalc_process = subprocess.Popen([terminal, '-e', shlex.join(goalc_args)], env=env)
|
||||
else:
|
||||
msg = (f"Your Linux installation does not have a supported terminal application.\n"
|
||||
f"We support the following options:\n"
|
||||
f" x-terminal-emulator\n"
|
||||
f" gnome-terminal\n"
|
||||
f" konsole\n"
|
||||
f" xterm\n"
|
||||
f"Please install one of these and try again.")
|
||||
ctx.on_log_error(logger, msg)
|
||||
return
|
||||
|
||||
elif Utils.is_macos:
|
||||
terminal = [which('open'), '-W', '-a', 'Terminal.app']
|
||||
goalc_process = subprocess.Popen([*terminal, *goalc_args])
|
||||
|
||||
except AttributeError as e:
|
||||
if " " in e.args[0]:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- A legally purchased copy of *Jak And Daxter: The Precursor Legacy.*
|
||||
- [The OpenGOAL Launcher](https://opengoal.dev/)
|
||||
|
||||
At this time, this method of setup works on Windows only, but Linux support is a strong likelihood in the near future as OpenGOAL itself supports Linux.
|
||||
This method of setup works on Windows and Linux.
|
||||
|
||||
## Installation via OpenGOAL Launcher
|
||||
|
||||
@@ -44,7 +44,7 @@ If you are in the middle of an async game, and you do not want to update the mod
|
||||
- The game window itself will launch, and Jak will be standing outside Samos's Hut.
|
||||
- Once compilation is complete, the title sequence will start.
|
||||
- Finally, the Archipelago text client will open.
|
||||
- If you see **BOTH** `The REPL is ready!` and `The Memory Reader is ready!` then that should indicate a successful startup. If you do not, see the Troubleshooting section.
|
||||
- If you see **BOTH** `The Compiler is ready!` and `The Memory Reader is ready!` then that should indicate a successful startup. If you do not, see the Troubleshooting section.
|
||||
- Once you see `CONNECT TO ARCHIPELAGO NOW` on the title screen, use the text client to connect to the Archipelago server. This will communicate your current settings and slot info to the game.
|
||||
- If you see `RECEIVING ITEMS, PLEASE WAIT...`, the game is busy receiving items from your starting inventory, assuming you have some.
|
||||
- Once you see `READY! PRESS START TO CONTINUE` on the title screen, you can press Start.
|
||||
@@ -84,7 +84,7 @@ If it cannot, you may have to tell it yourself. Follow these instructions.
|
||||
root_directory: "%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal"
|
||||
```
|
||||
|
||||
- Second, find the `root_directory` entry. Change this to `false`. You do not need to use double quotes.
|
||||
- Second, find the `auto_detect_root_directory` entry. Change this to `false`. You do not need to use double quotes.
|
||||
|
||||
```yaml
|
||||
auto_detect_root_directory: true
|
||||
@@ -100,7 +100,7 @@ You may start the game via the Text Client, but it never loads in the title scre
|
||||
-- Compilation Error! --
|
||||
```
|
||||
|
||||
If this happens, follow these instructions. If you are using a PAL version of the game, you should skip these instructions and follow the `Special PAL Instructions` above.
|
||||
If this happens, follow these instructions.
|
||||
|
||||
- Run the OpenGOAL Launcher (if you had it open before, close it and reopen it).
|
||||
- Click the Jak and Daxter logo on the left sidebar, then click `Advanced`, then click `Open Game Data Folder`. Copy the `iso_data` folder from this directory.
|
||||
@@ -112,9 +112,9 @@ If this happens, follow these instructions. If you are using a PAL version of th
|
||||
- Click `Features` in the bottom right corner, then click `Mods`, then under `Installed Mods`, click `ArchipelaGOAL`.
|
||||
- In the bottom right corner, click `Advanced`, then click `Compile`.
|
||||
|
||||
### The Text Client Says "Error reading game memory!" or "Error sending data to compiler"
|
||||
### The Text Client Says "Error reading game memory!" or "Error sending data to compiler!"
|
||||
|
||||
If at any point the text client says this, you will need to restart the **all** of these applications.
|
||||
If at any point the text client says this in big red letters, you will need to restart the **all** of these applications.
|
||||
|
||||
- Close all open windows: the client, the compiler, and the game.
|
||||
- Run the OpenGOAL Launcher, then click `Features`, then click `Mods`, then click `ArchipelaGOAL`.
|
||||
@@ -123,20 +123,24 @@ If at any point the text client says this, you will need to restart the **all**
|
||||
- Then close and reopen the Jak and Daxter Client from the Archipelago Launcher.
|
||||
- Once these are done, you can enter `/repl status` and `/memr status` in the text client to verify.
|
||||
|
||||
### The Client Cannot Open A REPL Connection
|
||||
### The Client Cannot Open A Compiler Connection
|
||||
|
||||
If the client cannot open a REPL connection to the game, you may need to check the following steps:
|
||||
If the client cannot open a REPL (compiler) connection to the game, you may need to check the following steps:
|
||||
|
||||
- Ensure you are not hosting anything on ports `8181` and `8112`. Those are for the REPL (goalc) and the game (gk) respectively.
|
||||
- Ensure that Windows Defender and Windows Firewall are not blocking those programs from hosting or listening on those ports.
|
||||
- You can use Windows Resource Monitor to verify those ports are open when the programs are running.
|
||||
- Ensure that you only opened those ports for your local network, not the wider internet.
|
||||
|
||||
### Your Linux Installation Does Not Have A Supported Terminal Application
|
||||
|
||||
If you are running Linux and your client shows this error, you will need to install a compatible terminal application like xterm.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- The game needs to boot in debug mode in order to allow the compiler to connect to it. **Clicking "Play" on the mod page in the OpenGOAL Launcher will not work.**
|
||||
- The Compiler console window is orphaned once you close the game - you will have to kill it manually when you stop playing.
|
||||
- The console windows cannot be run as background processes due to how the REPL works, so the best we can do is minimize them.
|
||||
- The console windows cannot be run as background processes due to how the compiler works, so the best we can do is minimize them.
|
||||
- Orbsanity checks may show up out of order in the text client.
|
||||
- Large item releases may take up to several minutes for the game to process them all. Item Messages will usually take longer to appear than Items themselves.
|
||||
- In Lost Precursor City, if you die in the Color Platforms room, the game may crash after you respawn. The cause is unknown.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Utils
|
||||
|
||||
# All Jak And Daxter Archipelago IDs must be offset by this number.
|
||||
jak1_id = 741000000
|
||||
|
||||
@@ -6,3 +8,9 @@ jak1_max = jak1_id + 999999
|
||||
|
||||
# The name of the game.
|
||||
jak1_name = "Jak and Daxter: The Precursor Legacy"
|
||||
|
||||
# The executable name of the GOAL Kernel.
|
||||
jak1_gk = "gk" + (".exe" if Utils.is_windows else "")
|
||||
|
||||
# The executable name of the GOAL Compiler.
|
||||
jak1_goalc = "goalc" + (".exe" if Utils.is_windows else "")
|
||||
@@ -1 +1 @@
|
||||
Pymem>=1.13.0
|
||||
PyMemoryEditor>=1.6.0
|
||||
|
||||
Reference in New Issue
Block a user