Linux diagnostics updates
This commit is contained in:
+1
-1
@@ -6,4 +6,4 @@ games/*/zig-cache/
|
||||
games/*/build_log.txt
|
||||
__pycache__/
|
||||
*.pyc
|
||||
release/rac1/
|
||||
release/rac1/
|
||||
@@ -48,6 +48,8 @@ Resolution (Native, or Upscaled to a whole multiple of the PS2's 448 lines that
|
||||
### Reporting problems
|
||||
|
||||
- **Open log** shows `rac1_log.txt` (or the build's `build_log.txt` when a build failed).
|
||||
- On Linux, `project-solana report` writes one file with everything a report needs (system details, the 32-bit library check, the launcher, build and game logs, and the previous run's game log); attach that file.
|
||||
- A crash writes the function it happened in and the call chain into the game log, and the previous run's log is kept as `rac1_log.prev.txt`.
|
||||
- **F9** in the game records the next few seconds of graphics input to `gsrec_NN.bin` (about 260 MB per second of capture); attach it to a report together with what looked wrong.
|
||||
|
||||
## Repository layout
|
||||
@@ -86,4 +88,8 @@ gh release create rac1 dist/rac1/rac1.json dist/rac1/rac1-build.zip dist/rac1/to
|
||||
|
||||
To update an existing game release, upload the new files over the old ones (`gh release upload rac1 dist/rac1/* --clobber`). Players get them with **Rebuild the game**.
|
||||
|
||||
The launchers go in a release of their own (for example `launcher-v1`): `Project Solana.exe` and `project-solana` (Linux).
|
||||
The launchers go in a release of their own (for example `launcher-v1`): `Project Solana.exe` and `project-solana` (Linux).
|
||||
|
||||
## Licence
|
||||
|
||||
TODO (maintainers): choose a licence for this repository. Third-party code keeps its own (see `THIRD_PARTY_NOTICES.md`).
|
||||
|
||||
+1
-1
@@ -241,7 +241,7 @@ if a.platform == "linux":
|
||||
else:
|
||||
# the menu and runtime as a DLL at 0x40000000, plus the small launcher that owns PS2 RAM
|
||||
dll = os.path.join(out, "rac1_game.dll")
|
||||
link = zig + ["cc", "-shared", "-target", target, "-o", dll, objs_rsp("link")] + ["-Wl,--image-base=0x40000000", "-Wl,--no-dynamicbase", "-lshell32", "-lwinmm", "-lgdi32", "-luser32"]
|
||||
link = zig + ["cc", "-shared", "-target", target, "-o", dll, objs_rsp("link")] + ["-Wl,--image-base=0x40000000", "-Wl,--no-dynamicbase", "-lshell32", "-lwinmm", "-lgdi32", "-luser32", "-ladvapi32"]
|
||||
r = subprocess.run(link, capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
print(r.stderr[-6000:]); sys.exit("link failed (dll)")
|
||||
|
||||
@@ -23,6 +23,7 @@ extern const int uya_tramp_count;
|
||||
/* platform layer */
|
||||
int plat_map_fixed(uint32_t addr, uint32_t size, int exec); /* 0 on success */
|
||||
int plat_install_trap_handler(void); /* calls into unmapped PS2 code */
|
||||
void plat_log_system(void); /* the system, for bug reports (once, at start) */
|
||||
void plat_flush_icache(void *p, size_t n);
|
||||
const char *plat_exe_dir(void); /* folder of the running program */
|
||||
void plat_at_exit(void);
|
||||
|
||||
@@ -63,6 +63,7 @@ int uya_main(int argc, char **argv)
|
||||
uya_log_open();
|
||||
uya_log("Project Solana: starting");
|
||||
plat_install_trap_handler(); /* first: a crash from here on is logged */
|
||||
plat_log_system();
|
||||
{ /* renderer=gl|soft in the cfg (UYA_RENDERER overrides it) */
|
||||
extern const char *uya_renderer_cfg;
|
||||
const char *r = cfg_value("renderer");
|
||||
|
||||
@@ -180,8 +180,30 @@ int plat_install_trap_handler(void)
|
||||
sigaction(SIGBUS, &sa, 0);
|
||||
sigaction(SIGILL, &sa, 0);
|
||||
sigaction(SIGFPE, &sa, 0);
|
||||
sigaction(SIGABRT, &sa, 0); /* (a library giving up: assert, abort) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <sys/utsname.h>
|
||||
void plat_log_system(void)
|
||||
{
|
||||
struct utsname u;
|
||||
char os[160] = "unknown", line[256], cpu[160] = "?";
|
||||
FILE *f = fopen("/etc/os-release", "r");
|
||||
while (f && fgets(line, sizeof line, f))
|
||||
if (!strncmp(line, "PRETTY_NAME=", 12)) { char *v = line + 12; if (*v == '"') v++; v[strcspn(v, "\"\n")] = 0; snprintf(os, sizeof os, "%s", v); }
|
||||
if (f) fclose(f);
|
||||
f = fopen("/proc/cpuinfo", "r");
|
||||
while (f && fgets(line, sizeof line, f))
|
||||
if (!strncmp(line, "model name", 10)) { char *v = strchr(line, ':'); if (v) { v += 2; v[strcspn(v, "\n")] = 0; snprintf(cpu, sizeof cpu, "%s", v); break; } }
|
||||
if (f) fclose(f);
|
||||
if (uname(&u) == 0) uya_log("system: Linux %s (%s), %s", u.release, u.machine, os);
|
||||
uya_log("system: %s, %ld cores, %ld MB RAM", cpu, sysconf(_SC_NPROCESSORS_ONLN), (long)((long long)sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE) >> 20));
|
||||
const char *st = getenv("XDG_SESSION_TYPE"), *de = getenv("XDG_CURRENT_DESKTOP");
|
||||
uya_log("system: session %s%s%s, DISPLAY %s, WAYLAND_DISPLAY %s%s", st ? st : "?", de ? ", desktop " : "", de ? de : "",
|
||||
getenv("DISPLAY") ? getenv("DISPLAY") : "-", getenv("WAYLAND_DISPLAY") ? getenv("WAYLAND_DISPLAY") : "-",
|
||||
getenv("SteamDeck") && !strcmp(getenv("SteamDeck"), "1") ? ", Steam Deck" : getenv("SteamAppId") ? ", started by Steam" : "");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -83,6 +83,9 @@ static struct {
|
||||
int (*QueueAudio)(SDL_AudioDeviceID, const void *, uint32_t);
|
||||
uint32_t (*GetQueuedAudioSize)(SDL_AudioDeviceID);
|
||||
void (*ClearQueuedAudio)(SDL_AudioDeviceID);
|
||||
void (*GetVersion)(uint8_t v[3]);
|
||||
const char *(*GetCurrentVideoDriver)(void);
|
||||
const char *(*GetCurrentAudioDriver)(void);
|
||||
} sdl;
|
||||
|
||||
/* 1: SDL is there and a display is (the game draws in a window); 0: headless */
|
||||
@@ -105,7 +108,7 @@ static int sdl_on(void)
|
||||
"SDL_NumJoysticks", "SDL_IsGameController", "SDL_GameControllerOpen", "SDL_GameControllerClose",
|
||||
"SDL_GameControllerGetAttached", "SDL_GameControllerName", "SDL_GameControllerGetButton", "SDL_GameControllerGetAxis",
|
||||
"SDL_OpenAudioDevice", "SDL_CloseAudioDevice", "SDL_PauseAudioDevice", "SDL_QueueAudio", "SDL_GetQueuedAudioSize",
|
||||
"SDL_ClearQueuedAudio",
|
||||
"SDL_ClearQueuedAudio", "SDL_GetVersion", "SDL_GetCurrentVideoDriver", "SDL_GetCurrentAudioDriver",
|
||||
};
|
||||
void **fp = (void **)&sdl;
|
||||
for (unsigned i = 0; i < sizeof names / sizeof names[0]; i++)
|
||||
@@ -116,6 +119,12 @@ static int sdl_on(void)
|
||||
uya_log("SDL2: cannot start (%s)", sdl.GetError());
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
uint8_t v[3] = {0, 0, 0};
|
||||
const char *vd = sdl.GetCurrentVideoDriver(), *ad = sdl.GetCurrentAudioDriver();
|
||||
sdl.GetVersion(v);
|
||||
uya_log("SDL %d.%d.%d: video %s, audio %s", v[0], v[1], v[2], vd ? vd : "none", ad ? ad : "none");
|
||||
}
|
||||
sdl_state = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -180,11 +180,37 @@ static LONG CALLBACK on_exception(EXCEPTION_POINTERS *ep)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#include <signal.h>
|
||||
/* abort() (a library giving up) ends the process without an exception: raise one, so the
|
||||
* crash handler reports it with the call chain */
|
||||
static void on_abort(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
uya_log("crash: abort() was called");
|
||||
RaiseException(0xE0000ABE, EXCEPTION_NONCONTINUABLE, 0, 0);
|
||||
}
|
||||
int plat_install_trap_handler(void)
|
||||
{
|
||||
signal(SIGABRT, on_abort);
|
||||
return AddVectoredExceptionHandler(1, on_exception) ? 0 : -1;
|
||||
}
|
||||
|
||||
void plat_log_system(void)
|
||||
{
|
||||
typedef struct { ULONG size, major, minor, build, platform; WCHAR sp[128]; } osv_t;
|
||||
HMODULE nt = GetModuleHandleA("ntdll.dll");
|
||||
LONG (WINAPI *rtl_ver)(osv_t *) = nt ? (void *)GetProcAddress(nt, "RtlGetVersion") : 0;
|
||||
const char *(*wine_ver)(void) = nt ? (void *)GetProcAddress(nt, "wine_get_version") : 0;
|
||||
osv_t v; memset(&v, 0, sizeof v); v.size = sizeof v;
|
||||
if (rtl_ver) rtl_ver(&v);
|
||||
SYSTEM_INFO si; GetNativeSystemInfo(&si);
|
||||
MEMORYSTATUSEX ms; memset(&ms, 0, sizeof ms); ms.dwLength = sizeof ms; GlobalMemoryStatusEx(&ms);
|
||||
char cpu[128] = "?"; DWORD cl = sizeof cpu;
|
||||
RegGetValueA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString", RRF_RT_REG_SZ, 0, cpu, &cl);
|
||||
uya_log("system: Windows %lu.%lu build %lu%s%s, %s, %lu cores, %llu MB RAM", v.major, v.minor, v.build,
|
||||
wine_ver ? ", Wine " : "", wine_ver ? wine_ver() : "", cpu, si.dwNumberOfProcessors, (unsigned long long)(ms.ullTotalPhys >> 20));
|
||||
}
|
||||
|
||||
uint64_t plat_time_us(void)
|
||||
{
|
||||
static LARGE_INTEGER f;
|
||||
|
||||
@@ -109,6 +109,11 @@ void uya_log_open(void)
|
||||
{
|
||||
char p[1024];
|
||||
snprintf(p, sizeof p, "%s/rac1_log.txt", plat_exe_dir());
|
||||
{ /* the previous run's log is kept as rac1_log.prev.txt (a retry after a crash keeps the crash) */
|
||||
char q[1100];
|
||||
snprintf(q, sizeof q, "%s/rac1_log.prev.txt", plat_exe_dir());
|
||||
remove(q); rename(p, q);
|
||||
}
|
||||
logfile = fopen(p, "w");
|
||||
if (logfile) fprintf(stderr, "[uya] log file: %s\n", p);
|
||||
}
|
||||
|
||||
+137
-10
@@ -6,6 +6,7 @@
|
||||
project-solana status what is installed, and whether the 32-bit libraries it needs are there
|
||||
project-solana desktop add Project Solana to your applications menu (and so to Steam)
|
||||
project-solana log print the last game log (rac1_log.txt)
|
||||
project-solana report write one file with everything needed for a bug report
|
||||
|
||||
Like the Windows launcher: nothing from the game is downloaded. The build files (the
|
||||
recompiler, the runtime, the decomp's source) come from the project's GitHub release, the
|
||||
@@ -25,12 +26,43 @@ OTHER = {"SCUS_971.99": "Ratchet & Clank (NTSC-U)", "SCPS_150.37": "Ratchet & Cl
|
||||
"SCUS_973.53": "Ratchet & Clank: Up Your Arsenal (NTSC-U)", "SCES_524.56": "Ratchet & Clank 3 (PAL)",
|
||||
"SCUS_974.65": "Ratchet: Deadlocked (NTSC-U)", "SCES_532.85": "Ratchet: Gladiator (PAL)"}
|
||||
|
||||
VERSION = "1.1"
|
||||
HOME = os.path.join(os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share"), "project-solana")
|
||||
INI = os.path.join(HOME, "solana.ini")
|
||||
LAUNCHER_LOG = os.path.join(HOME, "launcher_log.txt")
|
||||
|
||||
|
||||
def say(*a): print(*a, flush=True)
|
||||
def die(msg): sys.exit("project-solana: " + msg)
|
||||
def log(text):
|
||||
"""everything the launcher says also goes to launcher_log.txt (started from Steam or a menu,
|
||||
there is no terminal to read it in)"""
|
||||
try:
|
||||
os.makedirs(HOME, exist_ok=True)
|
||||
if os.path.exists(LAUNCHER_LOG) and os.path.getsize(LAUNCHER_LOG) > (1 << 20):
|
||||
os.replace(LAUNCHER_LOG, LAUNCHER_LOG + ".old")
|
||||
with open(LAUNCHER_LOG, "a") as f: f.write(time.strftime("%Y-%m-%d %H:%M:%S ") + text + "\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def notify(text):
|
||||
"""a desktop notification, when there is no terminal to print to"""
|
||||
if sys.stdout.isatty(): return
|
||||
for cmd in (["notify-send", "-a", "Project Solana", "Project Solana", text], ["zenity", "--error", "--title=Project Solana", "--text=" + text],
|
||||
["kdialog", "--title", "Project Solana", "--error", text]):
|
||||
if shutil.which(cmd[0]):
|
||||
try: subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL); return
|
||||
except OSError: pass
|
||||
|
||||
|
||||
def say(*a):
|
||||
t = " ".join(str(x) for x in a)
|
||||
print(t, flush=True); log(t)
|
||||
|
||||
|
||||
def die(msg):
|
||||
log("error: " + msg)
|
||||
notify(msg + "\n\n(project-solana report writes a file to send with a bug report)")
|
||||
sys.exit("project-solana: " + msg)
|
||||
|
||||
|
||||
def settings():
|
||||
@@ -141,15 +173,20 @@ def have_lib(name):
|
||||
return False
|
||||
|
||||
|
||||
def lib_paths(name):
|
||||
return [os.path.join(d, name) for d in LIBDIRS + ["/lib", "/usr/lib"] if os.path.exists(os.path.join(d, name))]
|
||||
|
||||
|
||||
def check_libs(quiet=False):
|
||||
missing = [n for n in ("libSDL2-2.0.so.0", "libGL.so.1") if not have_lib(n)]
|
||||
if not lib_paths("ld-linux.so.2"): missing.insert(0, "the 32-bit C library (ld-linux.so.2)")
|
||||
if missing and not quiet:
|
||||
say("\nThe game is a 32-bit program and needs these 32-bit libraries: %s" % ", ".join(missing))
|
||||
say("Installing Steam brings them. Otherwise:")
|
||||
say(" Debian, Ubuntu, Mint, Pop!_OS: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt install libsdl2-2.0-0:i386 libgl1:i386 libgl1-mesa-dri:i386")
|
||||
say(" Fedora: sudo dnf install SDL2.i686 mesa-libGL.i686 mesa-dri-drivers.i686")
|
||||
say(" Arch, Manjaro, EndeavourOS: enable [multilib], then sudo pacman -S lib32-sdl2 lib32-mesa")
|
||||
say(" openSUSE: sudo zypper install libSDL2-2_0-0-32bit Mesa-libGL1-32bit")
|
||||
say(" Debian, Ubuntu, Mint, Pop!_OS: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt install libc6:i386 libsdl2-2.0-0:i386 libgl1:i386 libgl1-mesa-dri:i386")
|
||||
say(" Fedora: sudo dnf install glibc.i686 SDL2.i686 mesa-libGL.i686 mesa-dri-drivers.i686")
|
||||
say(" Arch, Manjaro, EndeavourOS: enable [multilib], then sudo pacman -S lib32-glibc lib32-sdl2 lib32-mesa")
|
||||
say(" openSUSE: sudo zypper install glibc-32bit libSDL2-2_0-0-32bit Mesa-libGL1-32bit")
|
||||
say(" (NVIDIA's own driver: also its 32-bit OpenGL library, e.g. lib32-nvidia-utils on Arch)")
|
||||
return not missing
|
||||
|
||||
@@ -208,6 +245,7 @@ def build(args):
|
||||
if e != "build": rmtree(os.path.join(gdir, e))
|
||||
rmtree(os.path.join(gdir, "build", "linux", "gen")); rmtree(os.path.join(gdir, "build", "linux", "obj"))
|
||||
unzip(zp, gdir, "build kit"); os.remove(zp)
|
||||
open(kv, "w").write(man["version"]) # (unpacked: an interrupted build resumes with what it compiled)
|
||||
|
||||
levels = c.get("build", "levels", fallback="") or man.get("levels") or ",".join(str(i) for i in range(19))
|
||||
cpus = os.cpu_count() or 2
|
||||
@@ -227,7 +265,6 @@ def build(args):
|
||||
p.wait(); log.close(); print()
|
||||
if p.returncode: die("the build stopped (see %s)" % os.path.join(gdir, "build_log.txt"))
|
||||
for j in ("build/linux/gen", "build/linux/gen.new", "build/linux/obj", "build/levels", "build/disc", "zig-cache"): rmtree(os.path.join(gdir, j))
|
||||
open(kv, "w").write(man["version"])
|
||||
c.set("disc", kit, iso); c.set("build", "built_" + kit, man["version"]); save(c)
|
||||
say("Built. Ready to play: project-solana play")
|
||||
|
||||
@@ -257,8 +294,21 @@ def play(args):
|
||||
if full: env["UYA_FULLSCREEN"] = "1"
|
||||
if pc.get("vsync", "0") in ("1", "True", "true"): env["UYA_VSYNC"] = "1"
|
||||
if not check_libs(quiet=True): check_libs(); die("install the 32-bit libraries above, then play again")
|
||||
os.chdir(os.path.dirname(exe))
|
||||
os.execve(exe, [exe, "-", iso], env)
|
||||
gdir = os.path.dirname(exe)
|
||||
out = os.path.join(gdir, "game_output.txt") # what SDL, the graphics driver and the game print (kept: one run back)
|
||||
if os.path.exists(out): os.replace(out, out + ".prev")
|
||||
log("play: %s (UYA_SCALE=%s, fullscreen %s, vsync %s)" % (iso, env.get("UYA_SCALE", "1"), full, pc.get("vsync", "0")))
|
||||
with open(out, "w") as o:
|
||||
try:
|
||||
p = subprocess.Popen([exe, "-", iso], cwd=gdir, env=env, stdout=o, stderr=subprocess.STDOUT)
|
||||
except OSError as e:
|
||||
die("the game could not start (%s): run project-solana status" % e)
|
||||
try: rc = p.wait()
|
||||
except KeyboardInterrupt: p.terminate(); rc = p.wait()
|
||||
import signal
|
||||
if rc == 0: log("play: the game closed normally"); return
|
||||
why = ("it crashed (%s)" % signal.Signals(-rc).name) if rc < 0 and -rc in [s.value for s in signal.Signals] else "it stopped with an error (code %d)" % rc
|
||||
die("the game closed: %s. project-solana report writes a file to send with a bug report" % why)
|
||||
|
||||
|
||||
def status(args):
|
||||
@@ -291,8 +341,74 @@ def show_log(args):
|
||||
sys.stdout.write(open(p, errors="replace").read())
|
||||
|
||||
|
||||
def tail(path, n):
|
||||
try:
|
||||
with open(path, errors="replace") as f: lines = f.readlines()
|
||||
except OSError:
|
||||
return "(none)\n"
|
||||
return ("(... %d earlier lines left out)\n" % (len(lines) - n) if len(lines) > n else "") + "".join(lines[-n:])
|
||||
|
||||
|
||||
def report(args):
|
||||
"""one text file with what a bug report needs; nothing from the game, and no file of the user's"""
|
||||
c = settings()
|
||||
gdir = os.path.join(HOME, "games", "rac1")
|
||||
bdir = os.path.join(gdir, "build", "linux")
|
||||
out = []
|
||||
w = out.append
|
||||
w("Project Solana bug report (Linux launcher %s), %s\n" % (VERSION, time.strftime("%Y-%m-%d %H:%M:%S %z")))
|
||||
u = os.uname()
|
||||
pretty = "?"
|
||||
try:
|
||||
for l in open("/etc/os-release"):
|
||||
if l.startswith("PRETTY_NAME="): pretty = l.split("=", 1)[1].strip().strip('"')
|
||||
except OSError: pass
|
||||
w("\n== system\nLinux %s %s, %s\n" % (u.release, u.machine, pretty))
|
||||
try:
|
||||
cpu = next((l.split(":", 1)[1].strip() for l in open("/proc/cpuinfo") if l.startswith("model name")), "?")
|
||||
mem = next((l.split(":", 1)[1].strip() for l in open("/proc/meminfo") if l.startswith("MemTotal")), "?")
|
||||
except OSError: cpu = mem = "?"
|
||||
w("CPU %s, %d cores, RAM %s\n" % (cpu, os.cpu_count() or 0, mem))
|
||||
for k in ("XDG_SESSION_TYPE", "XDG_CURRENT_DESKTOP", "DISPLAY", "WAYLAND_DISPLAY", "SteamDeck", "SteamAppId"):
|
||||
w("%s=%s\n" % (k, os.environ.get(k, "")))
|
||||
for cmd in (["lspci"], ["glxinfo", "-B"]):
|
||||
if shutil.which(cmd[0]):
|
||||
try:
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=10).stdout
|
||||
if cmd[0] == "lspci": r = "".join(l + "\n" for l in r.splitlines() if "VGA" in l or "3D" in l or "Display" in l)
|
||||
w("-- %s\n%s" % (" ".join(cmd), r))
|
||||
except Exception: pass
|
||||
w("\n== 32-bit libraries\n")
|
||||
for n in ("ld-linux.so.2", "libSDL2-2.0.so.0", "libGL.so.1"):
|
||||
w("%s: %s\n" % (n, ", ".join(lib_paths(n)) or ("found by ldconfig" if have_lib(n) else "MISSING")))
|
||||
w("\n== Project Solana\ndata folder: %s\n" % HOME)
|
||||
tv = os.path.join(HOME, "toolchain", "version.txt")
|
||||
w("build tools: %s\n" % (open(tv).read().strip() if os.path.exists(tv) else "not downloaded"))
|
||||
kv = os.path.join(gdir, "kit_version.txt")
|
||||
w("build kit: %s\n" % (open(kv).read().strip() if os.path.exists(kv) else "none"))
|
||||
w("game built: %s\n" % ("yes" if os.path.exists(os.path.join(bdir, "rac1_wrapper")) else "no"))
|
||||
iso = c.get("disc", "rac1", fallback="")
|
||||
w("disc image: %s\n" % ((("found, %.2f GB" % (os.path.getsize(iso) / 1e9)) if os.path.exists(iso) else "MISSING (moved?)") if iso else "none"))
|
||||
w("settings:\n")
|
||||
for s in c.sections():
|
||||
for k, v in c.items(s):
|
||||
if s == "disc" or k == "release_url": continue
|
||||
w(" [%s] %s = %s\n" % (s, k, v))
|
||||
for title, path, n in (("launcher log", LAUNCHER_LOG, 60), ("build log (end)", os.path.join(gdir, "build_log.txt"), 80),
|
||||
("game log", os.path.join(bdir, "rac1_log.txt"), 400), ("game log, the run before", os.path.join(bdir, "rac1_log.prev.txt"), 150),
|
||||
("game output (SDL, graphics driver)", os.path.join(bdir, "game_output.txt"), 120),
|
||||
("game output, the run before", os.path.join(bdir, "game_output.txt.prev"), 60)):
|
||||
w("\n== %s\n%s" % (title, tail(path, n)))
|
||||
text = "".join(out).replace(os.path.expanduser("~"), "~") # (no user name in the report)
|
||||
dest_dir = next((d for d in (os.path.expanduser("~/Desktop"), os.path.expanduser("~")) if os.path.isdir(d)), HOME)
|
||||
dest = os.path.join(dest_dir, "project-solana-report-%s.txt" % time.strftime("%Y%m%d-%H%M%S"))
|
||||
open(dest, "w").write(text)
|
||||
say("Wrote %s: attach it to your bug report (it has your system details and the logs, nothing else)." % dest)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(prog="project-solana", description="Build Ratchet & Clank from your own disc, then play it.")
|
||||
ap.add_argument("--version", action="version", version="project-solana %s (Linux launcher)" % VERSION)
|
||||
sp = ap.add_subparsers(dest="cmd")
|
||||
b = sp.add_parser("build", help="build the game from your disc image")
|
||||
b.add_argument("disc"); b.add_argument("-j", "--jobs", type=int, default=0)
|
||||
@@ -310,9 +426,20 @@ def main():
|
||||
v.add_argument("--no-vsync", dest="vsync", action="store_const", const=0)
|
||||
sp.add_parser("status", help="what is installed"); sp.add_parser("desktop", help="add it to the applications menu")
|
||||
sp.add_parser("log", help="print the last game log")
|
||||
sp.add_parser("report", help="write one file with everything a bug report needs")
|
||||
a = ap.parse_args()
|
||||
if sys.platform != "linux": die("this is the Linux launcher: on Windows, use Project Solana.exe")
|
||||
{"build": build, "play": play, "status": status, "desktop": desktop, "log": show_log}.get(a.cmd, lambda _: ap.print_help())(a)
|
||||
log("project-solana %s: %s" % (VERSION, " ".join(sys.argv[1:])))
|
||||
try:
|
||||
{"build": build, "play": play, "status": status, "desktop": desktop, "log": show_log, "report": report}.get(a.cmd, lambda _: ap.print_help())(a)
|
||||
except SystemExit:
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(130)
|
||||
except Exception as e: # (an unexpected error: in the launcher log too, with where)
|
||||
import traceback
|
||||
log("unexpected error:\n" + traceback.format_exc())
|
||||
die("unexpected error: %s (details in %s)" % (e, LAUNCHER_LOG))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user