mirror of
https://github.com/griches/PS5_SwitchWaker.git
synced 2026-10-17 09:00:52 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c0f213f2d | ||
|
|
83b5a62161 | ||
|
|
fc1563da67 | ||
|
|
2f91becaa4 |
No files matched your search
@@ -20,8 +20,17 @@ image when the title starts.
|
||||
**Runs on the console.** First run on 2026-10-12 on a PS5 at 3840x2160: the
|
||||
title screen, name entry, the prologue and Outset, at 60 images a second
|
||||
(frame interpolation; the game's logic stays at 30), with the DualSense's
|
||||
symbols on the HUD. Played for minutes, not hours: nothing past Outset has been
|
||||
seen on the console.
|
||||
symbols on the HUD. Played on the console as far as the forest on Outset, the
|
||||
scenes that follow it, and the boss room of Dragon Roost Cavern; nothing else
|
||||
has been seen there.
|
||||
|
||||
**[Version 1.0.3](https://github.com/griches/PS5_SwitchWaker/releases/tag/v1.0.3)** fixes a crash in Dragon Roost Cavern, in the rooms with
|
||||
the hanging platforms. [Version 1.0.2](https://github.com/griches/PS5_SwitchWaker/releases/tag/v1.0.2) fixed a crash when the boss of Dragon
|
||||
Roost Cavern appears, and stopped the whole class of fault behind it (an
|
||||
integer division by zero) from ending the game anywhere else. [Version 1.0.1](https://github.com/griches/PS5_SwitchWaker/releases/tag/v1.0.1)
|
||||
fixed a stop on a black screen at the end of the forest scene. See
|
||||
[Development](#development) for what
|
||||
they were. Copy the [new release](https://github.com/griches/PS5_SwitchWaker/releases/latest) over the old folder; saves carry on.
|
||||
|
||||
A release is the title's folder with no game in it: the player copies their own `GZLE01` disc image (`.iso`, `.gcm` or `.ciso`) into
|
||||
`/data/homebrew/PPSA99309/` beside `eboot.bin`, and the title finds it (the
|
||||
@@ -33,10 +42,10 @@ first one in its folder, then on a USB drive).
|
||||
| Picture | 16:9. The output is 3840x2160 unless the options menu's "Output resolution" says 2560x1440 or 1920x1080 (applied at the next start); the console scales it to the television. "Internal resolution" follows it ("Window size") or is fixed, which dynamic resolution needs |
|
||||
| 60 fps | Held at 3840x2160 in the scenes above, by the frame counter on screen. Not measured over a long session |
|
||||
| Shaders | A first start with no cache compiled 3,056 render pipelines in about 12 minutes. The title now ships the caches a console produced (`ps5/shader-cache`) and copies them into place when the console has none: the same start then compiles nothing. They must be taken from a console again after a change to Dawn, the driver or the shaders |
|
||||
| Button prompts | The HUD shows Cross (A), Square (B), Circle (X), Triangle (Y), R1 for Z, R2 for R and a right stick for the C stick (`ps5/art/make-glyphs.py`, Kenney's CC0 Input Prompts). Not done: the button pictures inside message text (they are in the game's font), and the options menu's own hints, which still name the GameCube's buttons |
|
||||
| Button prompts | The HUD shows Cross (A), Square (B), Circle (X), Triangle (Y), R1 for Z, R2 for R and a right stick for the C stick (`ps5/art/make-glyphs.py`, [Kenney's CC0 Input Prompts](https://kenney.nl/assets/input-prompts)). The options menu's hints name the DualSense's buttons. Not done: the button pictures inside message text (they are in the game's font). With an HD texture pack the pack's buttons are drawn instead: see [Button symbols with a pack](#hd-textures-optional) |
|
||||
| Options menu | The touch pad's click opens it |
|
||||
| Saves, settings | `/data/homebrew/PPSA99309/user/`; the first start asks to create a save file, as the game does on an empty memory card |
|
||||
| HD textures | A Dolphin texture pack the player supplies replaces the game's textures (off by default; see HD textures below). Seen working on the console with Hypatia's pack at Outset |
|
||||
| HD textures | A Dolphin texture pack the player supplies replaces the game's textures (on when a pack is in `user/hd_textures`, off otherwise; see [HD textures](#hd-textures-optional)). Seen working on the console with Hypatia's pack on Outset and in its forest. The pack has an optional PlayStation button set for the HUD |
|
||||
| Sound | Not checked by ear in this log |
|
||||
| DevLoop | `devloop-shot` and `devloop-quit` in the title's folder (registered as `windwaker-gc`); the shot answers once the game's loop runs |
|
||||
| Art | The icon and background are the port's own, cel-shaded, made with an image generator (`ps5/art/`) |
|
||||
@@ -52,7 +61,7 @@ The symbol of an empty item slot sits in the middle of its bubble.
|
||||
Download `WindWaker-PS5-vX.Y.Z.zip` from the
|
||||
[latest release](https://github.com/griches/PS5_SwitchWaker/releases/latest) and
|
||||
unpack it: it holds one folder, `PPSA99309`. (Or build that folder yourself, see
|
||||
Build.) It contains no game data.
|
||||
[Build](#build).) It contains no game data.
|
||||
|
||||
**What the console needs**
|
||||
|
||||
@@ -131,14 +140,17 @@ to tell apart, and the PNG one is not suitable.
|
||||
With no pack in that folder the option does nothing.
|
||||
|
||||
Seen on the console on 2026-10-12 with that pack: Outset at 3840x2160 and 60
|
||||
images a second with the textures replaced. Not played further than that.
|
||||
images a second with the textures replaced, and its forest. Not played further
|
||||
than that.
|
||||
|
||||
**Button symbols with a pack.** The pack replaces the HUD's buttons as well, with
|
||||
its own GameCube ones, so the DualSense's symbols this port draws are lost while
|
||||
it is on. To have PlayStation buttons with the pack, also copy the `HUD` folder
|
||||
from its `-[Optional Textures]-/[HUD] Controller Buttons - Playstation` over
|
||||
the `HUD` folder in `hd_textures`, replacing files: those label the buttons by
|
||||
position, as this port maps them.
|
||||
position, as this port maps them (Cross where A is, Square for B, Circle for X,
|
||||
Triangle for Y). The button pictures inside message text stay the GameCube's
|
||||
either way.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -155,6 +167,48 @@ COS_BUILD_DIR=$PWD/build/native-mac COS_DISC=/path/to/GZLE01.iso COS_FPS60=1 \
|
||||
--preset sailing --aspect 16:9 --frames 700 --shot 690
|
||||
```
|
||||
|
||||
**Finding a fault on the console.** The game's own `[cos]` lines go to the
|
||||
console's log (klog, port 3232) with the driver's. A `[dev]` section at the end
|
||||
of `user/settings.ini` sets developer variables at the next start, for example
|
||||
`COS_TRACE=scene,res` for every scene created and every file opened. A scene
|
||||
change that stays in one step logs `scene-req-watch` lines every 300 calls: the
|
||||
step, and the processes still waiting to be deleted.
|
||||
|
||||
The stop that 1.0.1 fixes was found that way. Some actors declare their methods
|
||||
as returning `bool` and the game's process framework calls them as returning
|
||||
`int` and compares with `TRUE`. On x86-64 the rest of the register is undefined,
|
||||
so a delete could be refused for ever and the next scene never came; on ARM (the
|
||||
Mac, the Switch) it does not show. The framework now reads only the low byte
|
||||
([`f_pc_method.cpp` in the patch](ps5/patches/switchwaker.patch)). On the Mac, `COS_EVENT_SWEEP_FOLLOW=1` lets
|
||||
`run.sh event-sweep` go on through a stage change an event asks for.
|
||||
|
||||
The crash that 1.0.3 fixes is a third one. A hanging platform of Dragon Roost
|
||||
Cavern whose ropes were cut on an earlier visit is created with them cut, and
|
||||
the rope points are simulated from values nothing has set. A NaN among them
|
||||
reaches the arctangent table as an index: the conversion to an integer gives
|
||||
0x80000000, which the GameCube's 32 bit address wraps back to the first entry
|
||||
and x86-64 turns into a read 4 GB below the table. The lookup now gives the
|
||||
first entry for a NaN or a ratio outside the table ([`c_math.cpp` in the patch](ps5/patches/switchwaker.patch)).
|
||||
It was found from a player's photograph of the console's error message: the
|
||||
two addresses in it, less the load address 0x400000, name `cM_atan2s` and
|
||||
`himo_move` in the release's ELF.
|
||||
|
||||
The crash that 1.0.2 fixes is the same kind of difference. The boss of Dragon
|
||||
Roost Cavern turns towards the player with a step count that is still zero the
|
||||
first time, an integer division by zero: the GameCube and ARM give a quotient
|
||||
and go on, x86-64 raises SIGFPE. The three helpers involved now give 0
|
||||
([`c_lib.cpp` in the patch](ps5/patches/switchwaker.patch)), and so that the next one elsewhere is not a crash,
|
||||
the title handles SIGFPE itself: it finishes the division as ARM does (quotient
|
||||
0, remainder the dividend), logs `integer division fault` with the address, and
|
||||
goes on. The console's code cannot be read at run time, so the build lists
|
||||
every division instruction of the linked title in `res/div-table.bin`
|
||||
([`make-div-table.py`](ps5/tools/make-div-table.py)) and the handler looks the address up; a fault
|
||||
that is not in the list is still a crash with the console's record.
|
||||
`COS_DIV_TEST=1` in `[dev]` divides by zero once at the start to check it. On
|
||||
the Mac, `COS_SAVE_LOAD_WALK=<frames>` makes `run.sh save-load` walk forward
|
||||
from a loaded save (`COS_CARD_DIR` names a card folder holding a player's
|
||||
`.gci`).
|
||||
|
||||
`COS_TEXTURE_DUMPS=1` writes every texture drawn into the cache folder's
|
||||
`texture_dumps`, named as a replacement for it must be named.
|
||||
|
||||
|
||||
@@ -1,3 +1,76 @@
|
||||
diff --git a/game/src/SSystem/SComponent/c_lib.cpp b/game/src/SSystem/SComponent/c_lib.cpp
|
||||
index 0f1f94f..02eaf89 100644
|
||||
--- a/game/src/SSystem/SComponent/c_lib.cpp
|
||||
+++ b/game/src/SSystem/SComponent/c_lib.cpp
|
||||
@@ -8,6 +8,18 @@
|
||||
#include "SSystem/SComponent/c_math.h"
|
||||
#include "dolphin/mtx/mtxvec.h"
|
||||
|
||||
+#if TARGET_PC
|
||||
+// A scale of 0 reaches these (the first cLib_addCalcAngleS2 of d_a_btd.cpp: m5E92 is still 0). The
|
||||
+// GameCube's divw and ARM's sdiv give a quotient without a fault, x86-64's idiv raises SIGFPE: the
|
||||
+// quotient is 0 here, as on ARM.
|
||||
+static inline s32 cLib_div(s32 a, s32 b) {
|
||||
+ return b != 0 ? a / b : 0;
|
||||
+}
|
||||
+#define CLIB_DIV(a, b) cLib_div((a), (b))
|
||||
+#else
|
||||
+#define CLIB_DIV(a, b) ((a) / (b))
|
||||
+#endif
|
||||
+
|
||||
/* 802528A4-802528C4 .text cLib_memCpy__FPvPCvUl */
|
||||
void cLib_memCpy(void* dst, const void* src, unsigned long size) {
|
||||
memcpy(dst, src, size);
|
||||
@@ -160,7 +172,7 @@ void cLib_addCalcPosXZ2(cXyz* pValue, cXyz const& target, f32 scale, f32 maxStep
|
||||
s16 cLib_addCalcAngleS(s16* pValue, s16 target, s16 scale, s16 maxStep, s16 minStep) {
|
||||
s16 diff = target - *pValue;
|
||||
if (*pValue != target) {
|
||||
- s16 step = (diff) / scale;
|
||||
+ s16 step = CLIB_DIV(diff, scale);
|
||||
if (step > minStep || step < -minStep) {
|
||||
if (step > maxStep) {
|
||||
step = maxStep;
|
||||
@@ -191,7 +203,7 @@ s16 cLib_addCalcAngleS(s16* pValue, s16 target, s16 scale, s16 maxStep, s16 minS
|
||||
/* 80253270-802532C4 .text cLib_addCalcAngleS2__FPssss */
|
||||
void cLib_addCalcAngleS2(s16* pValue, s16 target, s16 scale, s16 maxStep) {
|
||||
s16 diff = target - *pValue;
|
||||
- s16 step = diff / scale;
|
||||
+ s16 step = CLIB_DIV(diff, scale);
|
||||
if (step > maxStep) {
|
||||
*pValue += maxStep;
|
||||
} else if (step < -maxStep) {
|
||||
@@ -205,7 +217,7 @@ void cLib_addCalcAngleS2(s16* pValue, s16 target, s16 scale, s16 maxStep) {
|
||||
s32 cLib_addCalcAngleL(s32* pValue, s32 target, s32 scale, s32 maxStep, s32 minStep) {
|
||||
s32 diff = target - *pValue;
|
||||
if (*pValue != target) {
|
||||
- scale = (diff) / scale;
|
||||
+ scale = CLIB_DIV(diff, scale);
|
||||
if (scale > minStep || scale < -minStep) {
|
||||
if (scale > maxStep) {
|
||||
scale = maxStep;
|
||||
diff --git a/game/src/SSystem/SComponent/c_math.cpp b/game/src/SSystem/SComponent/c_math.cpp
|
||||
index 5129f61..8c7e218 100644
|
||||
--- a/game/src/SSystem/SComponent/c_math.cpp
|
||||
+++ b/game/src/SSystem/SComponent/c_math.cpp
|
||||
@@ -111,7 +111,18 @@ static u16 atntable[] = {
|
||||
|
||||
/* 8024609C-802460D0 .text U_GetAtanTable__Fff */
|
||||
u16 U_GetAtanTable(float f0, float f1) {
|
||||
+#if TARGET_PC
|
||||
+ // A NaN reaches this (the cut ropes of d_a_mflft.cpp). The conversion gives 0x80000000 on the
|
||||
+ // GameCube and on x86-64: the GameCube's 32 bit address wraps back to entry 0, x86-64 reads 4 GB
|
||||
+ // below the table and faults. Entry 0 here, as on the GameCube.
|
||||
+ f32 ratio = f0 / f1 * 1024;
|
||||
+ if (!(ratio >= 0.0f && ratio <= 1024.0f)) {
|
||||
+ return atntable[0];
|
||||
+ }
|
||||
+ return atntable[(int)ratio];
|
||||
+#else
|
||||
return atntable[(int)(f0 / f1 * 1024)];
|
||||
+#endif
|
||||
}
|
||||
|
||||
/* 802460D0-80246270 .text cM_atan2s__Fff */
|
||||
diff --git a/game/src/d/d_meter.cpp b/game/src/d/d_meter.cpp
|
||||
index 05ecca5..292ce89 100644
|
||||
--- a/game/src/d/d_meter.cpp
|
||||
@@ -343,11 +416,90 @@ index aeba943..b972772 100644
|
||||
if (!sChecked) {
|
||||
sChecked = true;
|
||||
if (gConfig.smoke == nullptr || strcmp(gConfig.smoke, "event-sweep") != 0) {
|
||||
diff --git a/native/src/pc/harness/sweeps/pc_save_sweep.cpp b/native/src/pc/harness/sweeps/pc_save_sweep.cpp
|
||||
index 9a9f11f..6720a15 100644
|
||||
--- a/native/src/pc/harness/sweeps/pc_save_sweep.cpp
|
||||
+++ b/native/src/pc/harness/sweeps/pc_save_sweep.cpp
|
||||
@@ -110,6 +110,7 @@ enum State {
|
||||
kTitle, // save-load: START until the name scene
|
||||
kName, // A until the PLAY scene
|
||||
kPlay, // the PLAY scene: wait for the player in the return room
|
||||
+ kWalk, // COS_SAVE_LOAD_WALK: forward from the loaded save
|
||||
kDone,
|
||||
};
|
||||
|
||||
@@ -403,6 +404,8 @@ void compareLoaded() {
|
||||
slot + 1);
|
||||
}
|
||||
|
||||
+unsigned int sWalkFrames = 0;
|
||||
+
|
||||
void loadFrame(unsigned int frames) {
|
||||
++sSince;
|
||||
if (sState == kTitle) {
|
||||
@@ -463,8 +466,34 @@ void loadFrame(unsigned int frames) {
|
||||
}
|
||||
writef(STDERR_FILENO, "[cos] save-load: PASS loaded %s room %d point %d\n", sReturnName, sReturnRoom,
|
||||
sReturnPoint);
|
||||
+ // COS_SAVE_LOAD_WALK=<frames>: go on from the loaded save, the stick held forward and A
|
||||
+ // tapped (through the door ahead), to reproduce a fault a player reports after a save.
|
||||
+ const char* walk = getenv("COS_SAVE_LOAD_WALK");
|
||||
+ if (walk != nullptr && atoi(walk) > 0) {
|
||||
+ sWalkFrames = (unsigned int)atoi(walk);
|
||||
+ sState = kWalk;
|
||||
+ sSince = 0;
|
||||
+ return;
|
||||
+ }
|
||||
pc_exit(PC_EXIT_REACHED);
|
||||
}
|
||||
+ if (sState == kWalk) {
|
||||
+ fopAc_ac_c* player = dComIfGp_getPlayer(0);
|
||||
+ setDrivenPad(true, sSince % kAEvery < 2 ? PAD_BUTTON_A : 0, 0, 127);
|
||||
+ if (sSince % 60 == 0) {
|
||||
+ if (player != nullptr) {
|
||||
+ writef(STDERR_FILENO, "[cos] save-load: walk %u: %s room %d at (%.0f, %.0f, %.0f)\n", sSince,
|
||||
+ dComIfGp_getStartStageName(), (int)fopAcM_GetRoomNo(player), player->current.pos.x,
|
||||
+ player->current.pos.y, player->current.pos.z);
|
||||
+ } else {
|
||||
+ writef(STDERR_FILENO, "[cos] save-load: walk %u: no player\n", sSince);
|
||||
+ }
|
||||
+ }
|
||||
+ if (sSince >= sWalkFrames) {
|
||||
+ writef(STDERR_FILENO, "[cos] save-load: walk done\n");
|
||||
+ pc_exit(PC_EXIT_REACHED);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
} // namespace
|
||||
diff --git a/native/src/pc/runtime/pc_crash.cpp b/native/src/pc/runtime/pc_crash.cpp
|
||||
index 094f649..7adb0a1 100644
|
||||
index 094f649..08447d8 100644
|
||||
--- a/native/src/pc/runtime/pc_crash.cpp
|
||||
+++ b/native/src/pc/runtime/pc_crash.cpp
|
||||
@@ -221,6 +221,9 @@ void writeFrames(int fd, void** frames, int n, int exact) {
|
||||
@@ -36,6 +36,10 @@
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
+#if defined(__PROSPERO__)
|
||||
+#include <cstdlib>
|
||||
+#include <ucontext.h>
|
||||
+#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach/mach.h>
|
||||
@@ -45,6 +49,11 @@
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
|
||||
+#if defined(__PROSPERO__)
|
||||
+// The function whose address gives the title's load offset (ps5/tools/make-div-table.py).
|
||||
+extern "C" __attribute__((noinline, used)) void pc_div_table_anchor() {}
|
||||
+#endif
|
||||
+
|
||||
namespace pc {
|
||||
|
||||
namespace {
|
||||
@@ -221,6 +230,9 @@ void writeFrames(int fd, void** frames, int n, int exact) {
|
||||
void writeThreadName(int fd) {
|
||||
#if defined(__SWITCH__)
|
||||
writef(fd, "[cos] thread %llu\n", (unsigned long long)cos_switch_thread_id());
|
||||
@@ -357,7 +509,7 @@ index 094f649..7adb0a1 100644
|
||||
#else
|
||||
char name[64] = "";
|
||||
pthread_getname_np(pthread_self(), name, sizeof(name));
|
||||
@@ -338,6 +341,14 @@ void writef(int fd, const char* fmt, ...) {
|
||||
@@ -338,6 +350,14 @@ void writef(int fd, const char* fmt, ...) {
|
||||
if (len >= (int)sizeof(buf)) {
|
||||
len = sizeof(buf) - 1;
|
||||
}
|
||||
@@ -372,14 +524,130 @@ index 094f649..7adb0a1 100644
|
||||
const char* p = buf;
|
||||
while (len > 0) {
|
||||
ssize_t w = write(fd, p, (size_t)len);
|
||||
@@ -399,6 +410,10 @@ void installCrashHandler() {
|
||||
@@ -394,11 +414,126 @@ void traceLastResource(char* out, size_t size) {
|
||||
out[n - 1] = '\0';
|
||||
}
|
||||
|
||||
+#if defined(__PROSPERO__)
|
||||
+// The game divides integers by zero in places (the first cLib_addCalcAngleS2 of d_a_btd.cpp, before
|
||||
+// c_lib.cpp guarded it): the GameCube's divw and ARM's sdiv give a quotient without a fault, x86-64's
|
||||
+// div/idiv raises SIGFPE. This finishes the instruction as ARM does (quotient 0, remainder the
|
||||
+// dividend) and goes on, with a log line for the first faults. Anything it cannot read as a
|
||||
+// div/idiv gets the default action back, so the console still writes its crash record.
|
||||
+
|
||||
+// The console's code is execute-only, so the handler cannot read the instruction to learn its
|
||||
+// length: the title ships a table of its div/idiv instructions made after the link
|
||||
+// (ps5/tools/make-div-table.py, res/div-table.bin: ELF address, length, operand size).
|
||||
+#pragma pack(push, 1)
|
||||
+struct DivEntry {
|
||||
+ uint32_t address;
|
||||
+ uint8_t length;
|
||||
+ uint8_t size;
|
||||
+};
|
||||
+#pragma pack(pop)
|
||||
+DivEntry* sDivTable = nullptr;
|
||||
+uint32_t sDivCount = 0;
|
||||
+uintptr_t sDivBase = 0; // the load address of ELF address 0
|
||||
+
|
||||
+void loadDivTable() {
|
||||
+ const int fd = open("/app0/res/div-table.bin", O_RDONLY);
|
||||
+ if (fd < 0) {
|
||||
+ writef(STDERR_FILENO, "[cos] no res/div-table.bin: an integer division by zero is a crash\n");
|
||||
+ return;
|
||||
+ }
|
||||
+ struct {
|
||||
+ char magic[8];
|
||||
+ uint64_t anchor;
|
||||
+ uint32_t count;
|
||||
+ } __attribute__((packed)) head;
|
||||
+ if (read(fd, &head, sizeof(head)) == (ssize_t)sizeof(head) && memcmp(head.magic, "COSDIV1", 8) == 0 &&
|
||||
+ head.count > 0 && head.count < 4000000) {
|
||||
+ const size_t bytes = (size_t)head.count * sizeof(DivEntry);
|
||||
+ DivEntry* table = static_cast<DivEntry*>(malloc(bytes));
|
||||
+ size_t got = 0;
|
||||
+ while (table != nullptr && got < bytes) {
|
||||
+ const ssize_t n = read(fd, reinterpret_cast<char*>(table) + got, bytes - got);
|
||||
+ if (n <= 0) {
|
||||
+ break;
|
||||
+ }
|
||||
+ got += (size_t)n;
|
||||
+ }
|
||||
+ if (table != nullptr && got == bytes) {
|
||||
+ sDivBase = reinterpret_cast<uintptr_t>(&pc_div_table_anchor) - (uintptr_t)head.anchor;
|
||||
+ sDivCount = head.count;
|
||||
+ sDivTable = table;
|
||||
+ }
|
||||
+ }
|
||||
+ close(fd);
|
||||
+ writef(STDERR_FILENO, "[cos] division table: %u instruction(s), load offset 0x%llx\n", sDivCount,
|
||||
+ (unsigned long long)sDivBase);
|
||||
+}
|
||||
+
|
||||
+void divFaultHandler(int sig, siginfo_t* info, void* context) {
|
||||
+ (void)info;
|
||||
+ ucontext_t* uc = static_cast<ucontext_t*>(context);
|
||||
+ const uintptr_t rip = (uintptr_t)uc->uc_mcontext.mc_rip;
|
||||
+ const uint64_t address = rip - sDivBase;
|
||||
+ int length = 0, size = 0;
|
||||
+ for (uint32_t lo = 0, hi = sDivCount; lo < hi;) {
|
||||
+ const uint32_t mid = lo + (hi - lo) / 2;
|
||||
+ if (sDivTable[mid].address == address) {
|
||||
+ length = sDivTable[mid].length;
|
||||
+ size = sDivTable[mid].size;
|
||||
+ break;
|
||||
+ }
|
||||
+ if (sDivTable[mid].address < address) {
|
||||
+ lo = mid + 1;
|
||||
+ } else {
|
||||
+ hi = mid;
|
||||
+ }
|
||||
+ }
|
||||
+ if (length == 0) {
|
||||
+ signal(sig, SIG_DFL); // not a listed division: it faults again, into the console's record
|
||||
+ return;
|
||||
+ }
|
||||
+ static unsigned int sCount = 0;
|
||||
+ if (sCount++ < 16) {
|
||||
+ writef(STDERR_FILENO, "[cos] integer division fault at ELF address 0x%llx (%d-byte operand): quotient 0, going on\n",
|
||||
+ (unsigned long long)address, size);
|
||||
+ }
|
||||
+ uint64_t& rax = reinterpret_cast<uint64_t&>(uc->uc_mcontext.mc_rax);
|
||||
+ uint64_t& rdx = reinterpret_cast<uint64_t&>(uc->uc_mcontext.mc_rdx);
|
||||
+ switch (size) {
|
||||
+ case 1: rax = (rax & ~0xFFFFull) | ((rax & 0xFF) << 8); break; // AL = 0, AH = the dividend's low byte
|
||||
+ case 2: rdx = (rdx & ~0xFFFFull) | (rax & 0xFFFF); rax &= ~0xFFFFull; break;
|
||||
+ case 4: rdx = rax & 0xFFFFFFFFull; rax = 0; break;
|
||||
+ default: rdx = rax; rax = 0; break;
|
||||
+ }
|
||||
+ uc->uc_mcontext.mc_rip += length;
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
void installCrashHandler() {
|
||||
#if defined(__SWITCH__)
|
||||
// libnx's exception handler (switch/native/source/cos_switch.cpp) reports crashes; it adds
|
||||
// this state line.
|
||||
cos_switch_set_crash_state_writer(writeState);
|
||||
+#elif defined(__PROSPERO__)
|
||||
+ // PlayStation 5: no handler. The console writes a crash record of its own to klog (registers
|
||||
+ // and a frame-pointer walk), which the port's ps5/tools/run.sh symbolises; a handler here
|
||||
+ // would take the signal first and exit without one.
|
||||
+ // PlayStation 5: no crash handler. The console writes a crash record of its own to klog
|
||||
+ // (registers and a frame-pointer walk), which the port's ps5/tools/run.sh symbolises; a handler
|
||||
+ // here would take the signal first and exit without one. SIGFPE alone is handled, to finish an
|
||||
+ // integer division by zero as the other machines do (divFaultHandler).
|
||||
+ {
|
||||
+ loadDivTable();
|
||||
+ struct sigaction sa = {};
|
||||
+ sa.sa_sigaction = divFaultHandler;
|
||||
+ sa.sa_flags = SA_SIGINFO;
|
||||
+ sigemptyset(&sa.sa_mask);
|
||||
+ sigaction(SIGFPE, &sa, nullptr);
|
||||
+ const char* test = getenv("COS_DIV_TEST");
|
||||
+ if (test != nullptr && atoi(test) > 0) {
|
||||
+ volatile int a = 1234, b = 0;
|
||||
+ volatile int q = a / b;
|
||||
+ volatile int r = a % b;
|
||||
+ writef(STDERR_FILENO, "[cos] div-test: 1234 / 0 = %d, 1234 %% 0 = %d\n", (int)q, (int)r);
|
||||
+ }
|
||||
+ }
|
||||
#else
|
||||
// Alternate signal stack for this (the main) thread, so a stack overflow still reports.
|
||||
static char sAltStack[256 * 1024];
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"conceptId": "99309",
|
||||
"contentBadgeType": 1,
|
||||
"contentId": "UP9000-PPSA99309_00-WINDWAKER0000000",
|
||||
"contentVersion": "01.000.001",
|
||||
"contentVersion": "01.000.003",
|
||||
"downloadDataSize": 256,
|
||||
"gameIntent": {
|
||||
"permittedIntents": [
|
||||
|
||||
@@ -212,3 +212,7 @@ cp "$upstream/native/dsp_hle/LICENSE" "$app/licenses/Dolphin-DSPHLE-LICENSE.txt"
|
||||
cp "$upstream/THIRD_PARTY.md" "$app/licenses/SwitchWaker-THIRD_PARTY.md"
|
||||
cp "$root/.deps/aurora/LICENSE" "$app/licenses/Aurora-LICENSE.txt"
|
||||
cp "$vulkan/LICENSE" "$app/licenses/PS5_Vulkan-LICENSE.txt"
|
||||
# The title's integer division instructions, for the SIGFPE handler that finishes a division by
|
||||
# zero as the GameCube and ARM do (the console's code cannot be read at run time)
|
||||
mkdir -p "$app/res"
|
||||
python3 "$ps5/tools/make-div-table.py" "$work/link/llvm-pie.elf" "$app/res/div-table.bin"
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
# Wind Waker for the PlayStation 5 - the table of the title's integer division instructions.
|
||||
#
|
||||
# ps5/tools/make-div-table.py <linked.elf> <out.bin> [--objdump llvm-objdump] [--nm llvm-nm]
|
||||
#
|
||||
# The game divides integers by zero in places; x86-64 raises SIGFPE where the GameCube and ARM
|
||||
# give a quotient. The port's SIGFPE handler (pc_crash.cpp, divFaultHandler) finishes such an
|
||||
# instruction itself, but the console's code is execute-only, so it cannot read the instruction
|
||||
# to learn its length: this lists every div/idiv of the linked title (address, length, operand
|
||||
# size), and the handler looks the faulting address up. Addresses are the ELF's; the handler
|
||||
# takes the load offset from the anchor function, whose ELF address heads the file.
|
||||
#
|
||||
# File: "COSDIV1\0", u64 anchor address, u32 count, then count sorted entries of
|
||||
# u32 address, u8 length, u8 operand size in bytes (little-endian).
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
import argparse
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ANCHOR = "pc_div_table_anchor"
|
||||
LINE = re.compile(r"^\s*([0-9a-f]+):\s+((?:[0-9a-f]{2} )+)\s*(?:(?:lock|rep\w*|data16|addr32|cs|ds|es|ss|fs|gs)\s+)*(i?div)([bwlq])\s")
|
||||
SIZES = {"b": 1, "w": 2, "l": 4, "q": 8}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("elf")
|
||||
ap.add_argument("out")
|
||||
ap.add_argument("--objdump", default="llvm-objdump")
|
||||
ap.add_argument("--nm", default="llvm-nm")
|
||||
args = ap.parse_args()
|
||||
|
||||
anchor = None
|
||||
for line in subprocess.run([args.nm, args.elf], capture_output=True, text=True, check=True).stdout.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) == 3 and parts[2] == ANCHOR:
|
||||
anchor = int(parts[0], 16)
|
||||
if anchor is None:
|
||||
sys.exit(f"make-div-table: no symbol {ANCHOR} in {args.elf}")
|
||||
|
||||
entries = []
|
||||
dump = subprocess.Popen([args.objdump, "-d", "--no-print-imm-hex", args.elf], stdout=subprocess.PIPE, text=True)
|
||||
for line in dump.stdout:
|
||||
if "div" not in line:
|
||||
continue
|
||||
m = LINE.match(line)
|
||||
if m:
|
||||
entries.append((int(m.group(1), 16), len(m.group(2).split()), SIZES[m.group(4)]))
|
||||
if dump.wait() != 0:
|
||||
sys.exit("make-div-table: objdump failed")
|
||||
if not entries:
|
||||
sys.exit("make-div-table: no division found (objdump's output format changed?)")
|
||||
entries.sort()
|
||||
with open(args.out, "wb") as f:
|
||||
f.write(b"COSDIV1\0" + struct.pack("<QI", anchor, len(entries)))
|
||||
for address, length, size in entries:
|
||||
f.write(struct.pack("<IBB", address, length, size))
|
||||
print(f"make-div-table: {len(entries)} division(s), anchor 0x{anchor:x}: {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user