8 Commits
Author SHA1 Message Date
Gary Riches 5d47aa54c2 Version 1.0.1
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 22:56:20 +01:00
Gary Riches 3c816ae645 Version 1.01
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 22:54:33 +01:00
Gary Riches aa745f8c4d Fix the hang after the forest cutscene on the console
At the end of A_mori's MEET_TETORA the next scene never came: the old scene's
attention tag could not be deleted. Its delete method returns bool and the
process framework calls it as returning int and compares with TRUE; on x86-64
the rest of the register is undefined, so the delete was retried for ever.
The framework now reads only the low byte of execute, is-delete and delete.

Also: the game's own [cos] lines reach the console's log, a scene request
stuck in one step reports the step and the delete queue, and the event sweep
can follow a stage change (COS_EVENT_SWEEP_FOLLOW=1).

Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 22:50:21 +01:00
Gary Riches 6bc6d7641d README: a direct link to the texture pack used
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 20:28:32 +01:00
Gary Riches 2953584da5 README: a .ciso runs on the console
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 17:22:37 +01:00
Gary Riches 14603c312e README: the badges the other ports have
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 17:04:42 +01:00
Gary Riches 242f0a1ef1 README: drop the note about the pack's camera-stick picture
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 16:36:43 +01:00
Gary Riches e8730bb86c README: the pack's camera-stick picture and how to get the port's own
Claude-Session: https://claude.ai/code/session_018jvMS8Y8okH8SDZUfd8MP1
2026-10-12 16:33:06 +01:00
3 changed files with 158 additions and 6 deletions

No files matched your search

+9 -3
View File
@@ -1,5 +1,9 @@
# Wind Waker (GameCube) for PlayStation 5
[![stars](https://img.shields.io/github/stars/griches/PS5_SwitchWaker?style=flat)](https://github.com/griches/PS5_SwitchWaker/stargazers)
[![release](https://img.shields.io/github/v/release/griches/PS5_SwitchWaker)](https://github.com/griches/PS5_SwitchWaker/releases/latest)
[![licence](https://img.shields.io/badge/licence-GPL--3.0%20as%20built-blue)](LICENSING.md)
A PS5 homebrew port of [SwitchWaker](https://github.com/centollOS/SwitchWaker), the
native port of the 2002 GameCube game (USA disc `GZLE01`, revision 0) compiled
from its decompilation and drawn with [Aurora](https://github.com/encounter/aurora).
@@ -25,7 +29,7 @@ first one in its folder, then on a USB drive).
| Area | State |
| --- | --- |
| Disc | Found in the title's folder; a `.iso` was used on the console, `.ciso` only on the Mac build. A missing or wrong disc closes the title with a console notification (the close is seen working; the notification's text has not been read on a television yet) |
| Disc | Found in the title's folder; a `.iso` and a `.ciso` have both been run on the console; `.gcm` has not. A missing or wrong disc closes the title with a console notification (the close is seen working; the notification's text has not been read on a television yet) |
| 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 |
@@ -109,8 +113,10 @@ own work, and you download it yourself.
The pack tried here is **Hypatia's "WWHD Mod" v2.0001a**, about 5,700 textures
covering most of the game. Its downloads are in the first post of
[its thread on the Dolphin forum](https://forums.dolphin-emu.org/thread-55004.html).
Take **"PC DDS Half Size"** (a `.7z` of 850 MB, 2.5 GB unpacked): the port reads
it as it is. The full-size one is four times larger for a picture that is hard
Take **"PC DDS Half Size"** (a `.7z` of 850 MB, 2.5 GB unpacked), which is the
one used here:
[direct download from the author's Google Drive mirror](https://drive.google.com/file/d/1qBK8YOeSAwLpNP3NUkwcXgvUnQPKlgj7/view?usp=sharing)
(`Hypatia WWHD Mod v2.0001a (DDS-Half).7z`). The port reads it as it is. The full-size one is four times larger for a picture that is hard
to tell apart, and the PNG one is not suitable.
1. Unpack the `.7z` on a computer. Inside is a folder `GZL`.
+148 -2
View File
@@ -55,6 +55,99 @@ index 05ecca5..292ce89 100644
fopMsgM_setPaneData(&i_Meter->field_0x1da8[0], sMainParts1, 'baa');
fopMsgM_setPaneData(&i_Meter->field_0x1e18[0], sMainParts1, 'bawe');
fopMsgM_setPaneData(&i_Meter->field_0x26a0, sMainParts1, 'bar1');
diff --git a/game/src/f_op/f_op_scene_req.cpp b/game/src/f_op/f_op_scene_req.cpp
index 08b0cee..0febd75 100644
--- a/game/src/f_op/f_op_scene_req.cpp
+++ b/game/src/f_op/f_op_scene_req.cpp
@@ -35,7 +35,46 @@ static cPhs_State fopScnRq_phase_Done(scene_request_class* i_sceneReq) {
return cPhs_NEXT_e;
}
+#if TARGET_PC
+#include "f_pc/f_pc_base.h"
+#include "f_pc/f_pc_delete_tag.h"
+#include <cstdio>
+// A scene request that stays in one step: every 300 calls, which step and what the delete queue holds.
+static void pc_fopScnRq_watch(scene_request_class* i_sceneReq) {
+ static scene_request_class* sReq;
+ static int sScene = -2, sNode = -2;
+ static unsigned sCalls;
+ const int scene = i_sceneReq->mReqPhsProcCls.id, node = i_sceneReq->mCrtReq.mReqPhsProc.id;
+ if (i_sceneReq != sReq || scene != sScene || node != sNode) {
+ sReq = i_sceneReq;
+ sScene = scene;
+ sNode = node;
+ sCalls = 0;
+ return;
+ }
+ if (++sCalls % 300 != 0) {
+ return;
+ }
+ fprintf(stderr, "[cos] scene-req-watch: %u calls in one step: fade %d, scene step %d, node step %d, proc %d, "
+ "delete queue %d, overlap doing %d done %d\n", sCalls, i_sceneReq->mFadeRequest != NULL, scene,
+ node, (int)i_sceneReq->mCrtReq.mProcName, (int)g_fpcDtTg_Queue.mSize, (int)fopOvlpM_IsDoingReq(),
+ (int)fopOvlpM_IsDone());
+ int shown = 0;
+ for (node_class* n = g_fpcDtTg_Queue.mpHead; n != NULL && shown < 12; n = n->mpNextNode, shown++) {
+ delete_tag_class* tag = (delete_tag_class*)n;
+ base_process_class* proc = (base_process_class*)tag->base.mpTagData;
+ fprintf(stderr, "[cos] scene-req-watch: deleting proc name %d id %u timer %d\n",
+ proc != NULL ? (int)proc->mProfName : -1, proc != NULL ? (unsigned)proc->mBsPcId : 0u,
+ (int)tag->mTimer);
+ }
+ fflush(stderr);
+}
+#endif
+
static cPhs_State fopScnRq_Execute(scene_request_class* i_sceneReq) {
+#if TARGET_PC
+ pc_fopScnRq_watch(i_sceneReq);
+#endif
cPhs_State phase_state = cPhs_Do(&i_sceneReq->mReqPhsProcCls, i_sceneReq);
switch (phase_state) {
case cPhs_NEXT_e:
diff --git a/game/src/f_pc/f_pc_method.cpp b/game/src/f_pc/f_pc_method.cpp
index 355f0e7..57070e1 100644
--- a/game/src/f_pc/f_pc_method.cpp
+++ b/game/src/f_pc/f_pc_method.cpp
@@ -5,6 +5,16 @@
#include "f_pc/f_pc_method.h"
+#if TARGET_PC
+// Some actors declare these methods as returning bool (one byte) and are called here as
+// returning int: on x86-64 the rest of the register is whatever was there, so TRUE can read as
+// something other than 1 and e.g. a delete is retried for ever (d_a_tag_attention's Mthd_Delete
+// at the end of A_mori's MEET_TETORA: the next scene never came). Only the low byte counts.
+#define PC_MTD_BOOL(x) ((x) & 0xFF)
+#else
+#define PC_MTD_BOOL(x) (x)
+#endif
+
/* 8003EF00-8003EF38 .text fpcMtd_Method__FPFPv_iPv */
int fpcMtd_Method(process_method_func i_methodFunc, void* i_data) {
if (i_methodFunc != NULL)
@@ -15,17 +25,17 @@ int fpcMtd_Method(process_method_func i_methodFunc, void* i_data) {
/* 8003EF38-8003EF5C .text fpcMtd_Execute__FP20process_method_classPv */
BOOL fpcMtd_Execute(process_method_class* i_methods, void* i_data) {
- return fpcMtd_Method(i_methods->mpExecuteFunc, i_data);
+ return PC_MTD_BOOL(fpcMtd_Method(i_methods->mpExecuteFunc, i_data));
}
/* 8003EF5C-8003EF80 .text fpcMtd_IsDelete__FP20process_method_classPv */
BOOL fpcMtd_IsDelete(process_method_class* i_methods, void* i_data) {
- return fpcMtd_Method(i_methods->mpIsDeleteFunc, i_data);
+ return PC_MTD_BOOL(fpcMtd_Method(i_methods->mpIsDeleteFunc, i_data));
}
/* 8003EF80-8003EFA4 .text fpcMtd_Delete__FP20process_method_classPv */
BOOL fpcMtd_Delete(process_method_class* i_methods, void* i_data) {
- return fpcMtd_Method(i_methods->mpDeleteFunc, i_data);
+ return PC_MTD_BOOL(fpcMtd_Method(i_methods->mpDeleteFunc, i_data));
}
/* 8003EFA4-8003EFC8 .text fpcMtd_Create__FP20process_method_classPv */
diff --git a/native/src/pc/features/pc_menu.cpp b/native/src/pc/features/pc_menu.cpp
index 3a26b60..fb9c5f0 100644
--- a/native/src/pc/features/pc_menu.cpp
@@ -212,8 +305,46 @@ index aef4e19..c2c3de0 100644
debugShotFrameEnd(frame);
if (!sShotOn || !wanted(frame)) {
return;
diff --git a/native/src/pc/harness/sweeps/pc_event_sweep.cpp b/native/src/pc/harness/sweeps/pc_event_sweep.cpp
index aeba943..b972772 100644
--- a/native/src/pc/harness/sweeps/pc_event_sweep.cpp
+++ b/native/src/pc/harness/sweeps/pc_event_sweep.cpp
@@ -504,10 +504,23 @@ int demoBank(const char* file) {
}
// A stage change asked for by the running event (or anything else): the run ends here.
+bool sFollowing = false;
+
bool checkStageChange() {
if (!dComIfGp_isEnableNextStage()) {
return false;
}
+ // COS_EVENT_SWEEP_FOLLOW=1: go on through the change instead (the run ends with --frames).
+ if (getenv("COS_EVENT_SWEEP_FOLLOW") != nullptr) {
+ if (!sFollowing) {
+ sFollowing = true;
+ setDrivenPad(true, 0, 0, 0);
+ writef(STDERR_FILENO, "[cos] event-sweep: following the stage change to %s room %d point %d layer %d\n",
+ dComIfGp_getNextStageName(), (int)dComIfGp_getNextStageRoomNo(),
+ (int)dComIfGp_getNextStagePoint(), (int)dComIfGp_getNextStageLayer());
+ }
+ return true;
+ }
detail(" next=%s", dComIfGp_getNextStageName());
result("stage-change");
stopRun("a stage change");
@@ -516,6 +529,9 @@ bool checkStageChange() {
} // namespace
void eventSweepFrame(unsigned int frames) {
+ if (sFollowing) {
+ return;
+ }
if (!sChecked) {
sChecked = true;
if (gConfig.smoke == nullptr || strcmp(gConfig.smoke, "event-sweep") != 0) {
diff --git a/native/src/pc/runtime/pc_crash.cpp b/native/src/pc/runtime/pc_crash.cpp
index 094f649..48d32d3 100644
index 094f649..7adb0a1 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) {
@@ -226,7 +357,22 @@ index 094f649..48d32d3 100644
#else
char name[64] = "";
pthread_getname_np(pthread_self(), name, sizeof(name));
@@ -399,6 +402,10 @@ void installCrashHandler() {
@@ -338,6 +341,14 @@ void writef(int fd, const char* fmt, ...) {
if (len >= (int)sizeof(buf)) {
len = sizeof(buf) - 1;
}
+#ifdef __PROSPERO__
+ // The console's log takes standard error through stdio only (ps5/src/platform.c).
+ if (fd == STDERR_FILENO) {
+ fwrite(buf, 1, (size_t)len, stderr);
+ fflush(stderr);
+ return;
+ }
+#endif
const char* p = buf;
while (len > 0) {
ssize_t w = write(fd, p, (size_t)len);
@@ -399,6 +410,10 @@ void installCrashHandler() {
// libnx's exception handler (switch/native/source/cos_switch.cpp) reports crashes; it adds
// this state line.
cos_switch_set_crash_state_writer(writeState);
+1 -1
View File
@@ -10,7 +10,7 @@
"conceptId": "99309",
"contentBadgeType": 1,
"contentId": "UP9000-PPSA99309_00-WINDWAKER0000000",
"contentVersion": "01.000.000",
"contentVersion": "01.000.001",
"downloadDataSize": 256,
"gameIntent": {
"permittedIntents": [