diff --git a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb1 b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb1 deleted file mode 100644 index e69de29..0000000 diff --git a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb2 b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-breadcrumb2 deleted file mode 100644 index e69de29..0000000 diff --git a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event b/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event deleted file mode 100644 index 5eaaa4b..0000000 --- a/.sentry-native/65f7adb4-714d-4565-f37b-64b2adb5a4e7.run/__sentry-event +++ /dev/null @@ -1 +0,0 @@ -ˆจplatformฆnativeงreleaseฏpcsx-redux@headซenvironmentชproductionฅlevelฅerrorฃsdk„คnameญsentry.nativeงversionฅ0.6.1จpackages‘‚คnameพgithub:getsentry/sentry-nativeงversionฅ0.6.1ฌintegrations‘จcrashpadคtags€ฅextra€จcontextsขos„คnameงWindowsฎkernel_versionฏ10.0.26100.8036งversionช10.0.26200ฅbuildค8037 \ No newline at end of file diff --git a/.sentry-native/metadata b/.sentry-native/metadata deleted file mode 100644 index e69de29..0000000 diff --git a/.sentry-native/settings.dat b/.sentry-native/settings.dat deleted file mode 100644 index d721006..0000000 Binary files a/.sentry-native/settings.dat and /dev/null differ diff --git a/Makefile b/Makefile index 568c725..c54ab3c 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ src/triclip.cpp \ src/lua.cpp \ src/luaapi.cpp \ src/scenemanager.cpp \ -src/sceneloader.cpp \ src/fileloader.cpp \ src/audiomanager.cpp \ src/controls.cpp \ diff --git a/build_output.txt b/build_output.txt deleted file mode 100644 index 6d5f51d..0000000 Binary files a/build_output.txt and /dev/null differ diff --git a/output.bin b/output.bin deleted file mode 100644 index 86b9d1f..0000000 Binary files a/output.bin and /dev/null differ diff --git a/src/bvh.cpp b/src/bvh.cpp index c087077..9b86a4c 100644 --- a/src/bvh.cpp +++ b/src/bvh.cpp @@ -81,32 +81,32 @@ int BVHManager::traverseRegion(int nodeIndex, int32_t qMinX, int32_t qMinY, if (!aabbOverlap(node, qMinX, qMinY, qMinZ, qMaxX, qMaxY, qMaxZ)) { return currentCount; - - if (node.isLeaf()) { - int count = node.triangleCount; - int available = maxRefs - currentCount; - if (count > available) - count = available; - - for (int i = 0; i < count; i++) { - outRefs[currentCount + i] = m_triangleRefs[node.firstTriangle + i]; - } - return currentCount + count; - } - - if (node.leftChild != 0xFFFF) { - currentCount = - traverseRegion(node.leftChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY, - qMaxZ, outRefs, currentCount, maxRefs); - } - if (node.rightChild != 0xFFFF) { - currentCount = - traverseRegion(node.rightChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY, - qMaxZ, outRefs, currentCount, maxRefs); - } - - return currentCount; } + + if (node.isLeaf()) { + int count = node.triangleCount; + int available = maxRefs - currentCount; + if (count > available) + count = available; + + for (int i = 0; i < count; i++) { + outRefs[currentCount + i] = m_triangleRefs[node.firstTriangle + i]; + } + return currentCount + count; + } + + if (node.leftChild != 0xFFFF) { + currentCount = + traverseRegion(node.leftChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY, + qMaxZ, outRefs, currentCount, maxRefs); + } + if (node.rightChild != 0xFFFF) { + currentCount = + traverseRegion(node.rightChild, qMinX, qMinY, qMinZ, qMaxX, qMaxY, + qMaxZ, outRefs, currentCount, maxRefs); + } + + return currentCount; } bool BVHManager::aabbOverlap(const BVHNode &node, int32_t qMinX, int32_t qMinY, diff --git a/src/bvh.hh b/src/bvh.hh index 9564db1..fe56059 100644 --- a/src/bvh.hh +++ b/src/bvh.hh @@ -28,12 +28,6 @@ struct BVHNode { return leftChild == 0xFFFF && rightChild == 0xFFFF; } - bool containsPoint(const psyqo::Vec3& point) const { - return point.x.raw() >= minX && point.x.raw() <= maxX && - point.y.raw() >= minY && point.y.raw() <= maxY && - point.z.raw() >= minZ && point.z.raw() <= maxZ; - } - bool testPlane(int32_t nx, int32_t ny, int32_t nz, int32_t d) const { int32_t px = (nx >= 0) ? maxX : minX; int32_t py = (ny >= 0) ? maxY : minY; diff --git a/src/camera.hh b/src/camera.hh index 87a3982..4e46905 100644 --- a/src/camera.hh +++ b/src/camera.hh @@ -14,7 +14,7 @@ class Camera { void MoveX(psyqo::FixedPoint<12> x); void MoveY(psyqo::FixedPoint<12> y); - void MoveZ(psyqo::FixedPoint<12> y); + void MoveZ(psyqo::FixedPoint<12> z); void SetPosition(psyqo::FixedPoint<12> x, psyqo::FixedPoint<12> y, psyqo::FixedPoint<12> z); psyqo::Vec3& GetPosition() { return m_position; } diff --git a/src/collision.cpp b/src/collision.cpp index 0ff451b..66c9607 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -55,12 +55,6 @@ void SpatialGrid::worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz) if (gz >= GRID_SIZE) gz = GRID_SIZE - 1; } -int SpatialGrid::getCellIndex(const psyqo::Vec3& pos) const { - int gx, gy, gz; - worldToGrid(pos, gx, gy, gz); - return gx + gy * GRID_SIZE + gz * GRID_SIZE * GRID_SIZE; -} - void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) { int minGx, minGy, minGz; int maxGx, maxGy, maxGz; @@ -308,98 +302,4 @@ bool CollisionSystem::testAABB(const AABB& a, const AABB& b, return true; } -bool CollisionSystem::areColliding(uint16_t indexA, uint16_t indexB) const { - for (int i = 0; i < m_resultCount; i++) { - if ((m_results[i].objectA == indexA && m_results[i].objectB == indexB) || - (m_results[i].objectA == indexB && m_results[i].objectB == indexA)) { - return true; - } - } - return false; -} - -bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction, - psyqo::FixedPoint<12> maxDistance, - psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal, - uint16_t& hitObjectIndex) const { - auto closestT = maxDistance; - bool hit = false; - - const FP zero(0); - const FP one(1); - const FP negOne(-1); - const FP largeVal(1000); - const FP negLargeVal(-1000); - FP epsilon; - epsilon.value = 4; - - for (int i = 0; i < m_colliderCount; i++) { - const CollisionData& collider = m_colliders[i]; - if (collider.type == CollisionType::None) continue; - - const AABB& box = collider.bounds; - - auto tMin = negLargeVal; - auto tMax = largeVal; - - if (direction.x != zero) { - auto invD = one / direction.x; - auto t1 = (box.min.x - origin.x) * invD; - auto t2 = (box.max.x - origin.x) * invD; - if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; } - if (t1 > tMin) tMin = t1; - if (t2 < tMax) tMax = t2; - } else if (origin.x < box.min.x || origin.x > box.max.x) { - continue; - } - - if (direction.y != zero) { - auto invD = one / direction.y; - auto t1 = (box.min.y - origin.y) * invD; - auto t2 = (box.max.y - origin.y) * invD; - if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; } - if (t1 > tMin) tMin = t1; - if (t2 < tMax) tMax = t2; - } else if (origin.y < box.min.y || origin.y > box.max.y) { - continue; - } - - if (direction.z != zero) { - auto invD = one / direction.z; - auto t1 = (box.min.z - origin.z) * invD; - auto t2 = (box.max.z - origin.z) * invD; - if (t1 > t2) { auto tmp = t1; t1 = t2; t2 = tmp; } - if (t1 > tMin) tMin = t1; - if (t2 < tMax) tMax = t2; - } else if (origin.z < box.min.z || origin.z > box.max.z) { - continue; - } - - if (tMin > tMax || tMax < zero) continue; - - auto t = (tMin >= zero) ? tMin : tMax; - - if (t < closestT && t >= zero) { - closestT = t; - hitObjectIndex = collider.gameObjectIndex; - hit = true; - - hitPoint = psyqo::Vec3{ - origin.x + direction.x * t, - origin.y + direction.y * t, - origin.z + direction.z * t - }; - - if ((hitPoint.x - box.min.x).abs() < epsilon) hitNormal = psyqo::Vec3{negOne, zero, zero}; - else if ((hitPoint.x - box.max.x).abs() < epsilon) hitNormal = psyqo::Vec3{one, zero, zero}; - else if ((hitPoint.y - box.min.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, negOne, zero}; - else if ((hitPoint.y - box.max.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, one, zero}; - else if ((hitPoint.z - box.min.z).abs() < epsilon) hitNormal = psyqo::Vec3{zero, zero, negOne}; - else hitNormal = psyqo::Vec3{zero, zero, one}; - } - } - - return hit; -} - } // namespace psxsplash diff --git a/src/collision.hh b/src/collision.hh index 6e55fd1..ae7ee69 100644 --- a/src/collision.hh +++ b/src/collision.hh @@ -33,22 +33,6 @@ struct AABB { (point.z >= min.z && point.z <= max.z); } - psyqo::Vec3 center() const { - return psyqo::Vec3{ - (min.x + max.x) / 2, - (min.y + max.y) / 2, - (min.z + max.z) / 2 - }; - } - - psyqo::Vec3 halfExtents() const { - return psyqo::Vec3{ - (max.x - min.x) / 2, - (max.y - min.y) / 2, - (max.z - min.z) / 2 - }; - } - void expand(const psyqo::Vec3& delta); }; static_assert(sizeof(AABB) == 24); @@ -101,7 +85,6 @@ public: void clear(); void insert(uint16_t objectIndex, const AABB& bounds); int queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const; - int getCellIndex(const psyqo::Vec3& pos) const; private: Cell m_cells[CELL_COUNT]; @@ -134,13 +117,6 @@ public: const CollisionResult* getResults() const { return m_results; } int getResultCount() const { return m_resultCount; } - bool areColliding(uint16_t indexA, uint16_t indexB) const; - - bool raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction, - psyqo::FixedPoint<12> maxDistance, - psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal, - uint16_t& hitObjectIndex) const; - int getColliderCount() const { return m_colliderCount; } private: diff --git a/src/controls.hh b/src/controls.hh index 7dbba77..5d4e363 100644 --- a/src/controls.hh +++ b/src/controls.hh @@ -12,7 +12,7 @@ using namespace psyqo::trig_literals; class Controls { public: - /// Force DualShock into analog mode (LED=Red) via raw SIO commands. + /// Force DualShock into analog mode /// Must be called BEFORE Init() since Init() hands SIO control to AdvancedPad. void forceAnalogMode(); @@ -87,4 +87,4 @@ class Controls { void getDpadAxes(int16_t &outX, int16_t &outY) const; }; -}; // namespace psxsplash \ No newline at end of file +} // namespace psxsplash \ No newline at end of file diff --git a/src/cutscene.cpp b/src/cutscene.cpp index 45c02cd..381f3fa 100644 --- a/src/cutscene.cpp +++ b/src/cutscene.cpp @@ -3,18 +3,11 @@ #include #include #include +#include "streq.hh" #include "uisystem.hh" namespace psxsplash { -// Bare-metal string compare (avoids linking libc) -static bool cs_streq(const char* a, const char* b) { - while (*a && *b) { - if (*a++ != *b++) return false; - } - return *a == *b; -} - void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio, UISystem* uiSystem) { m_cutscenes = cutscenes; @@ -31,7 +24,7 @@ bool CutscenePlayer::play(const char* name) { if (!name || !m_cutscenes) return false; for (int i = 0; i < m_count; i++) { - if (m_cutscenes[i].name && cs_streq(m_cutscenes[i].name, name)) { + if (m_cutscenes[i].name && streq(m_cutscenes[i].name, name)) { m_active = &m_cutscenes[i]; m_frame = 0; m_nextAudio = 0; @@ -64,7 +57,7 @@ bool CutscenePlayer::play(const char* name) { } break; case TrackType::ObjectRotationY: - // Can't easily recover angle from matrix โ€” default to 0 + // TODO: I added this. Then realized that it only stores rotation matrix. That sucks. To anyone who finds this Pull requests are open :P break; case TrackType::ObjectActive: if (track.target) { @@ -119,12 +112,10 @@ void CutscenePlayer::stop() { void CutscenePlayer::tick() { if (!m_active) return; - // Apply all tracks at the current frame for (uint8_t i = 0; i < m_active->trackCount; i++) { applyTrack(m_active->tracks[i]); } - // Fire audio events whose frame has been reached while (m_nextAudio < m_active->audioEventCount) { CutsceneAudioEvent& evt = m_active->audioEvents[m_nextAudio]; if (evt.frame <= m_frame) { @@ -137,39 +128,31 @@ void CutscenePlayer::tick() { } } - // Advance frame m_frame++; if (m_frame > m_active->totalFrames) { m_active = nullptr; // Cutscene finished } } -// Apply easing curve to fixed-point t โˆˆ [0, 4096]. static int32_t applyCurve(int32_t t, InterpMode mode) { switch (mode) { default: case InterpMode::Linear: return t; case InterpMode::Step: - return 0; // snaps to 'a' value until next keyframe + return 0; case InterpMode::EaseIn: - // tยฒ / 4096 return (int32_t)((int64_t)t * t >> 12); case InterpMode::EaseOut: - // 1 โˆ’ (1โˆ’t)ยฒ = t*(2 โˆ’ t) / 4096 return (int32_t)(((int64_t)t * (8192 - t)) >> 12); case InterpMode::EaseInOut: { - // Smoothstep: 3tยฒ โˆ’ 2tยณ (all in 12-bit fixed point) - int64_t t2 = (int64_t)t * t; // 24-bit - int64_t t3 = t2 * t; // 36-bit + int64_t t2 = (int64_t)t * t; + int64_t t3 = t2 * t; return (int32_t)((3 * t2 - 2 * (t3 >> 12)) >> 12); } } } -// Common helper: find surrounding keyframes and compute fixed-point t (0..4096). -// Returns false if result was clamped (no interpolation needed, out[] already set). -// When true, t already has the easing curve applied based on the *destination* keyframe's InterpMode. static bool findKfPair(CutsceneKeyframe* kf, uint8_t count, uint16_t frame, uint8_t& a, uint8_t& b, int32_t& t, int16_t out[3]) { if (count == 0) { @@ -208,7 +191,6 @@ void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const in uint8_t a, b; int32_t t; if (!findKfPair(kf, count, m_frame, a, b, t, out)) { - // If clamped to first keyframe and frame < first kf frame, blend from initial if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) { uint16_t span = kf[0].getFrame(); uint32_t num = (uint32_t)m_frame << 12; @@ -228,8 +210,6 @@ void CutscenePlayer::lerpKeyframes(CutsceneKeyframe* kf, uint8_t count, const in } } -// Shortest-path angle interpolation. -// Angles are in psyqo units where 2048 = full circle (2ฯ€). static constexpr int32_t ANGLE_FULL_CIRCLE = 2048; static constexpr int32_t ANGLE_HALF_CIRCLE = 1024; @@ -237,7 +217,6 @@ void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16 uint8_t a, b; int32_t t; if (!findKfPair(kf, count, m_frame, a, b, t, out)) { - // If clamped to first keyframe and frame < first kf frame, blend from initial if (count > 0 && kf[0].getFrame() > 0 && m_frame < kf[0].getFrame()) { uint16_t span = kf[0].getFrame(); uint32_t num = (uint32_t)m_frame << 12; @@ -257,9 +236,7 @@ void CutscenePlayer::lerpAngles(CutsceneKeyframe* kf, uint8_t count, const int16 for (int i = 0; i < 3; i++) { int32_t from = (int32_t)kf[a].values[i]; int32_t to = (int32_t)kf[b].values[i]; - // Shortest-path: wrap delta into [-1024, +1024) int32_t delta = to - from; - // Modulo into [-2048, +2048) then clamp to half-circle delta = ((delta + ANGLE_HALF_CIRCLE) % ANGLE_FULL_CIRCLE + ANGLE_FULL_CIRCLE) % ANGLE_FULL_CIRCLE - ANGLE_HALF_CIRCLE; out[i] = (int16_t)(from + ((delta * t) >> 12)); } @@ -314,10 +291,8 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) { case TrackType::ObjectActive: { if (!track.target) return; - // Step interpolation: find the last keyframe at or before m_frame CutsceneKeyframe* kf = track.keyframes; uint8_t count = track.keyframeCount; - // Use initial state if we're before the first keyframe int16_t activeVal = (count > 0 && m_frame < kf[0].getFrame()) ? track.initialValues[0] : kf[0].values[0]; diff --git a/src/cutscene.hh b/src/cutscene.hh index 824f35a..608e7e7 100644 --- a/src/cutscene.hh +++ b/src/cutscene.hh @@ -24,22 +24,19 @@ enum class TrackType : uint8_t { ObjectPosition = 2, ObjectRotationY = 3, ObjectActive = 4, - // UI track types (v13+) - UICanvasVisible = 5, // Step: values[0] = 0/1. target unused, uiHandle = canvas index. - UIElementVisible= 6, // Step: values[0] = 0/1. uiHandle = element handle. - UIProgress = 7, // Linear: values[0] = 0-100. uiHandle = element handle. - UIPosition = 8, // Linear: values[0] = x, values[1] = y. uiHandle = element handle. - UIColor = 9, // Linear: values[0] = r, values[1] = g, values[2] = b. uiHandle = element handle. + UICanvasVisible = 5, + UIElementVisible= 6, + UIProgress = 7, + UIPosition = 8, + UIColor = 9, }; -/// Per-keyframe interpolation mode. -/// Packed into upper 3 bits of the frame field in CutsceneKeyframe. enum class InterpMode : uint8_t { - Linear = 0, // Default linear interpolation - Step = 1, // Instant jump (no interpolation) - EaseIn = 2, // Slow start, fast end (quadratic) - EaseOut = 3, // Fast start, slow end (quadratic) - EaseInOut = 4, // Smooth start and end (smoothstep) + Linear = 0, + Step = 1, + EaseIn = 2, + EaseOut = 3, + EaseInOut = 4, }; struct CutsceneKeyframe { @@ -66,16 +63,9 @@ struct CutsceneTrack { TrackType trackType; uint8_t keyframeCount; uint8_t pad[2]; - CutsceneKeyframe* keyframes; // Points into splashpack data (resolved at load time) - GameObject* target; // nullptr = camera track or UI track - - /// For UI tracks: flat handle into UISystem (canvas index or element handle). - /// Set during cutscene load by resolving canvas/element names. + CutsceneKeyframe* keyframes; + GameObject* target; int16_t uiHandle; - - /// Initial values captured at play() time for pre-first-keyframe blending. - /// For position tracks: fp12 x,y,z. For rotation tracks: raw angle values. - /// For ObjectActive: values[0] = 1 (active) or 0 (inactive). int16_t initialValues[3]; }; @@ -88,8 +78,7 @@ struct Cutscene { CutsceneAudioEvent* audioEvents; // Points into splashpack data }; -/// Zero-allocation cutscene player. Call init() once after splashpack is loaded, -/// then tick() once per frame from the scene loop. + class CutscenePlayer { public: /// Initialize with loaded cutscene data. Safe to pass nullptr/0 if no cutscenes. diff --git a/src/fileloader_cdrom.hh b/src/fileloader_cdrom.hh index ba4b684..de6d57e 100644 --- a/src/fileloader_cdrom.hh +++ b/src/fileloader_cdrom.hh @@ -19,9 +19,8 @@ namespace psxsplash { * 4. ISO9660Parser::scheduleGetDirentry โ€” look up a file by path * 5. ISO9660Parser::scheduleReadRequest โ€” read the file sectors * - * The BIOS has already read SYSTEM.CNF and launched the executable by the - * time we get here, so we don't touch SYSTEM.CNF at all. */ + class FileLoaderCDRom final : public FileLoader { public: FileLoaderCDRom() : m_isoParser(&m_cdrom) {} @@ -99,7 +98,7 @@ class FileLoaderCDRom final : public FileLoader { // โ”€โ”€ LoadFileSyncWithProgress โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Reads the file in 32-sector (64 KB) chunks, calling the // progress callback between each chunk so the loading bar - // animates during the (slow) CD-ROM transfer. + // animates during the CD-ROM transfer. uint8_t* LoadFileSyncWithProgress( const char* filename, int& outSize, const LoadProgressInfo* progress) override diff --git a/src/fileloader_pcdrv.hh b/src/fileloader_pcdrv.hh index 6d3c957..62417e9 100644 --- a/src/fileloader_pcdrv.hh +++ b/src/fileloader_pcdrv.hh @@ -12,12 +12,6 @@ namespace psxsplash { * - Emulator mode: break instructions intercepted by PCSX-Redux * - Real hardware mode: SIO1 serial protocol (break handler installed * by pcdrv_sio1_init, then pcdrv_init detects which path to use) - * - * Both modes are preserved โ€” we call pcdrv_sio1_init() first (installs - * the SIO1 break handler + redirects printf), then pcdrv_init() which - * checks for the PCSX-Redux emulator and falls back to SIO1 if absent. - * - * All tasks resolve synchronously since PCdrv I/O is blocking. */ class FileLoaderPCdrv final : public FileLoader { public: @@ -27,11 +21,9 @@ class FileLoaderPCdrv final : public FileLoader { // โ”€โ”€ scheduleInit โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ psyqo::TaskQueue::Task scheduleInit() override { return psyqo::TaskQueue::Task([this](psyqo::TaskQueue::Task* task) { - // Set up SIO1 break handler + printf redirect (real-hardware path). - // On emulator this is harmless โ€” pcsx-redux ignores SIO1 setup. + pcdrv_sio1_init(); - // Detect emulator vs SIO1 and initialise the active transport. m_available = (pcdrv_init() == 0); task->resolve(); }); diff --git a/src/gtemath.hh b/src/gtemath.hh index 063474e..e841fdf 100644 --- a/src/gtemath.hh +++ b/src/gtemath.hh @@ -4,4 +4,4 @@ namespace psxsplash { void MatrixMultiplyGTE(const psyqo::Matrix33 &matA, const psyqo::Matrix33 &matB, psyqo::Matrix33 *result); -}; +} // namespace psxsplash diff --git a/src/loadingscreen.cpp b/src/loadingscreen.cpp index 9643051..961b063 100644 --- a/src/loadingscreen.cpp +++ b/src/loadingscreen.cpp @@ -1,7 +1,6 @@ #include "loadingscreen.hh" #include "fileloader.hh" #include -extern "C" int ramsyscall_printf(const char*, ...); #include "renderer.hh" #include @@ -12,23 +11,9 @@ extern "C" int ramsyscall_printf(const char*, ...); namespace psxsplash { -// psyqo places the second framebuffer at VRAM Y=256 regardless of the -// actual display height (240 for NTSC, 256 for PAL). All double-buffer -// rendering must use this constant, NOT m_resH, to match the display -// registers set by GPU::flip(). static constexpr int16_t kBuffer1Y = 256; -// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -// Bare-metal string helpers (no libc) -// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -static void int_to_str(int val, char* buf) { - if (val < 0) { *buf++ = '-'; val = -val; } - char tmp[12]; - int len = 0; - do { tmp[len++] = '0' + (val % 10); val /= 10; } while (val > 0); - for (int i = len - 1; i >= 0; i--) *buf++ = tmp[i]; - *buf = '\0'; -} +// This file has duplicate code from UISystem... This is terrible... // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // Load @@ -37,22 +22,15 @@ bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIn // Build filename using the active backend's naming convention char filename[32]; FileLoader::BuildLoadingFilename(sceneIndex, filename, sizeof(filename)); - ramsyscall_printf("LoadingScreen: loading '%s'\n", filename); - int fileSize = 0; uint8_t* data = FileLoader::Get().LoadFileSync(filename, fileSize); if (!data || fileSize < (int)sizeof(LoaderPackHeader)) { - ramsyscall_printf("LoadingScreen: FAILED to load (data=%p size=%d)\n", data, fileSize); if (data) FileLoader::Get().FreeFile(data); return false; } auto* header = reinterpret_cast(data); - ramsyscall_printf("LoadingScreen: magic='%c%c' ver=%d canvases=%d fonts=%d atlases=%d cluts=%d tableOff=%u\n", - header->magic[0], header->magic[1], header->version, - header->canvasCount, header->fontCount, header->atlasCount, header->clutCount, header->tableOffset); if (header->magic[0] != 'L' || header->magic[1] != 'P') { - ramsyscall_printf("LoadingScreen: bad magic, aborting\n"); FileLoader::Get().FreeFile(data); return false; } @@ -73,17 +51,12 @@ bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIn m_ui.uploadFonts(gpu); // Ensure canvas 0 is visible - ramsyscall_printf("LoadingScreen: canvasCount=%d\n", m_ui.getCanvasCount()); if (m_ui.getCanvasCount() > 0) { m_ui.setCanvasVisible(0, true); - int elemCount = m_ui.getCanvasElementCount(0); - ramsyscall_printf("LoadingScreen: canvas 0 has %d elements\n", elemCount); } // Find the progress bar named "loading" findProgressBar(); - ramsyscall_printf("LoadingScreen: hasProgressBar=%d barXY=(%d,%d) barWH=(%d,%d)\n", - m_hasProgressBar, m_barX, m_barY, m_barW, m_barH); return true; } @@ -170,7 +143,7 @@ void LoadingScreen::drawImage(psyqo::GPU& gpu, int handle, const UIImageData* img = m_ui.getImageData(handle); if (!img) return; - // Build TPage attribute (same as UISystem::makeTPage) + // Build TPage attribute psyqo::PrimPieces::TPageAttr tpage; tpage.setPageX(img->texpageX); tpage.setPageY(img->texpageY); @@ -372,8 +345,6 @@ void LoadingScreen::renderInitialAndFree(psyqo::GPU& gpu) { FileLoader::Get().FreeFile(m_data); m_data = nullptr; m_dataSize = 0; - // m_ui now points into freed memory โ€” don't use it again. - // m_active remains true so updateProgress keeps working. } // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ diff --git a/src/loadingscreen.hh b/src/loadingscreen.hh index b35ef2f..3dd3092 100644 --- a/src/loadingscreen.hh +++ b/src/loadingscreen.hh @@ -8,7 +8,6 @@ namespace psxsplash { -/// Loader pack header โ€” matches the binary written by PSXLoaderPackWriter.cs. struct LoaderPackHeader { char magic[2]; // "LP" uint16_t version; // 2 @@ -22,7 +21,6 @@ struct LoaderPackHeader { }; static_assert(sizeof(LoaderPackHeader) == 16, "LoaderPackHeader must be 16 bytes"); -/// Atlas entry in the loader pack (matches SPLASHPACKTextureAtlas layout). struct LoaderPackAtlas { uint32_t pixelDataOffset; // absolute offset in file uint16_t width, height; @@ -30,7 +28,6 @@ struct LoaderPackAtlas { }; static_assert(sizeof(LoaderPackAtlas) == 12, "LoaderPackAtlas must be 12 bytes"); -/// CLUT entry in the loader pack (matches SPLASHPACKClut layout). struct LoaderPackClut { uint32_t clutDataOffset; // absolute offset in file uint16_t clutX; // VRAM X (in 16-pixel units ร— 16) @@ -40,25 +37,7 @@ struct LoaderPackClut { }; static_assert(sizeof(LoaderPackClut) == 12, "LoaderPackClut must be 12 bytes"); -/// Loading screen controller. -/// -/// Loads a .loading file (tiny UI-only binary), renders the screen, -/// and provides progress updates during the main splashpack load. -/// -/// Strategy: -/// 1. Blank the screen immediately (user sees black, not frozen frame). -/// 2. Load the .loading file (small, fast). -/// 3. Upload texture atlases, CLUTs, and custom font textures to VRAM. -/// 4. Render ALL elements to BOTH framebuffers using direct GPU commands: -/// - Images via sendPrimitive(GouraudTexturedTriangle) -/// - Custom font text via sendPrimitive(TPage) + sendPrimitive(Sprite) -/// - System font text via font.print() -/// - Boxes and progress bars via sendPrimitive(Rectangle) -/// 5. FREE all loaded data (loader pack) so the splashpack has room. -/// 6. During splashpack loading, call updateProgress() to redraw ONLY the -/// progress bar (simple rectangles in both framebuffers โ€” no VRAM needed). -/// -/// The progress bar is found by looking for a PSXUIProgressBar element named "loading". + class LoadingScreen { public: /// Try to load a loader pack from a file. diff --git a/src/lua.cpp b/src/lua.cpp index cea53a1..21714e0 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -92,26 +92,23 @@ static int gameobjectSetPosition(psyqo::Lua L) { } -static int gamobjectGetActive(psyqo::Lua L) { +static int gameobjectGetActive(psyqo::Lua L) { auto go = L.toUserdata(1); L.push(go->isActive()); return 1; } -static int gamobjectSetActive(psyqo::Lua L) { +static int gameobjectSetActive(psyqo::Lua L) { auto go = L.toUserdata(1); bool active = L.toBoolean(2); go->setActive(active); return 0; } -// Angle constants: psyqo::Angle is FixedPoint<10>, so 1.0_pi = raw 1024 static constexpr lua_Number kAngleScale = 1024; static psyqo::Trig<> s_trig; -// Fast integer atan2 approximation โ†’ psyqo::Angle (pi-fraction units) -// Uses linear approximation in first octant then folds to full circle. -// Max error ~4ยฐ (acceptable for PS1 game objects). + static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) { psyqo::Angle result; if (cosVal == 0 && sinVal == 0) { result.value = 0; return result; } @@ -121,17 +118,10 @@ static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) { int32_t minV = abs_s < abs_c ? abs_s : abs_c; int32_t maxV = abs_s > abs_c ? abs_s : abs_c; - - // Compute angle in first octant [0, ฯ€/4 = 256 Angle units] - // angle = (minV/maxV) * 256, using only 32-bit math. - // Max minV for normalized sin/cos โ‰ˆ 4096, so minV * 256 โ‰ˆ 1M โ€” fits int32. int32_t angle = (minV * 256) / maxV; - // Past 45ยฐ: use complement - if (abs_s > abs_c) angle = 512 - angle; // ฯ€/2 - angle - // Quadrant 2/3: cos < 0 - if (cosVal < 0) angle = 1024 - angle; // ฯ€ - angle - // Quadrant 3/4: sin < 0 + if (abs_s > abs_c) angle = 512 - angle; + if (cosVal < 0) angle = 1024 - angle; if (sinVal < 0) angle = -angle; result.value = angle; @@ -140,11 +130,9 @@ static psyqo::Angle fastAtan2(int32_t sinVal, int32_t cosVal) { static int gameobjectGetRotationY(psyqo::Lua L) { auto go = L.toUserdata(1); - // Y rotation matrix: vs[0].x = cos(ฮธ), vs[0].z = sin(ฮธ) int32_t sinRaw = go->rotation.vs[0].z.raw(); int32_t cosRaw = go->rotation.vs[0].x.raw(); psyqo::Angle angle = fastAtan2(sinRaw, cosRaw); - // Return in pi-units: 0.5 = ฯ€/2 = 90ยฐ L.pushNumber(static_cast(angle.value) / kAngleScale); return 1; } @@ -172,10 +160,10 @@ void psxsplash::Lua::Init() { L.push(gameobjectSetPosition); L.setField(-2, "set_position"); - L.push(gamobjectGetActive); + L.push(gameobjectGetActive); L.setField(-2, "get_active"); - L.push(gamobjectSetActive); + L.push(gameobjectSetActive); L.setField(-2, "set_active"); L.push(gameobjectGetRotationY); @@ -212,8 +200,7 @@ void psxsplash::Lua::Init() { } void psxsplash::Lua::Shutdown() { - // Close the Lua VM if it's still open. - // Safe to call multiple times or on an already-closed VM. + if (m_state.getState()) { m_state.close(); } @@ -223,9 +210,8 @@ void psxsplash::Lua::Shutdown() { } void psxsplash::Lua::Reset() { - // Nuclear reset: destroy the entire Lua VM and create a fresh one. Shutdown(); - m_state = psyqo::Lua(); // fresh state (luaL_newstate + openlibs) + m_state = psyqo::Lua(); Init(); } @@ -298,8 +284,6 @@ void psxsplash::Lua::RegisterSceneScripts(int index) { // empty stack } -// We're going to store the Lua table for the object at the address of the object, -// and the table for its methods at the address of the object + 1 byte. void psxsplash::Lua::RegisterGameObject(GameObject* go) { uint8_t* ptr = reinterpret_cast(go); auto L = m_state; @@ -431,7 +415,6 @@ void psxsplash::Lua::OnTriggerExitScript(int luaFileIndex, int triggerIndex) { void psxsplash::Lua::OnDestroy(GameObject* go) { if (!hasEvent(go, EVENT_ON_DESTROY)) return; onDestroyMethodWrapper.callMethod(*this, go); - // Clear the event mask when object is destroyed go->eventMask = EVENT_NONE; } @@ -463,8 +446,6 @@ void psxsplash::Lua::OnUpdate(GameObject* go, int deltaFrames) { void psxsplash::Lua::RelocateGameObjects(GameObject** objects, size_t count, intptr_t delta) { auto L = m_state; for (size_t i = 0; i < count; i++) { - // objects[i] is already the NEW pointer (relocated by shrinkBuffer). - // Compute the OLD pointer by subtracting delta. uint8_t* newPtr = reinterpret_cast(objects[i]); uint8_t* oldPtr = newPtr - delta; @@ -507,7 +488,7 @@ void psxsplash::Lua::PushGameObject(GameObject* go) { L.rawGet(LUA_REGISTRYINDEX); if (!L.isTable(-1)) { - printf("Warning: GameObject not found in Lua registry\n"); L.pop(); + L.push(); // push nil so the caller always gets a value } } diff --git a/src/lua.h b/src/lua.h index 0c6a831..8935c99 100644 --- a/src/lua.h +++ b/src/lua.h @@ -158,7 +158,7 @@ class Lua { [[no_unique_address]] FunctionWrapper<1, typestring_is("onSceneCreationStart")> onSceneCreationStartFunctionWrapper; [[no_unique_address]] FunctionWrapper<2, typestring_is("onSceneCreationEnd")> onSceneCreationEndFunctionWrapper; - // Object-level events (methodId 100+, offset to avoid collision with scene events) + // Object-level events [[no_unique_address]] FunctionWrapper<100, typestring_is("onCreate")> onCreateMethodWrapper; [[no_unique_address]] FunctionWrapper<101, typestring_is("onCollideWithPlayer")> onCollideWithPlayerMethodWrapper; [[no_unique_address]] FunctionWrapper<102, typestring_is("onInteract")> onInteractMethodWrapper; diff --git a/src/luaapi.cpp b/src/luaapi.cpp index f81dc21..78c606c 100644 --- a/src/luaapi.cpp +++ b/src/luaapi.cpp @@ -9,7 +9,7 @@ #include #include #include -#include + namespace psxsplash { @@ -241,7 +241,7 @@ void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cut L.push(Math_Max); L.setField(-2, "Max"); - L.setGlobal("Math"); + L.setGlobal("PSXMath"); // ======================================================================== // SCENE API @@ -615,7 +615,8 @@ int LuaAPI::Entity_ForEach(lua_State* L) { lua.pop(2); // pop non-table + callback copy continue; } - if (lua.pcall(1, 0) != LUA_OK) { + lua.pushNumber(i); // push index as second argument + if (lua.pcall(2, 0) != LUA_OK) { lua.pop(); // pop error message } } @@ -1179,28 +1180,27 @@ int LuaAPI::Camera_LookAt(lua_State* L) { // ============================================================================ int LuaAPI::Audio_Play(lua_State* L) { - printf("[Audio_Play] ENTER top=%d\n", lua_gettop(L)); psyqo::Lua lua(L); + if (!s_sceneManager) { + lua.pushNumber(-1); + return 1; + } + int soundId = -1; // Accept number (index) or string (name lookup) like Entity.Find - // Check isNumber FIRST โ€” in Lua, numbers pass isString too. + // Check isNumber FIRST - in Lua, numbers pass isString too. if (lua.isNumber(1)) { soundId = static_cast(lua.toNumber(1)); - printf("[Audio_Play] by index: %d\n", soundId); } else if (lua.isString(1)) { const char* name = lua.toString(1); - printf("[Audio_Play] by name: '%s'\n", name ? name : "(null)"); soundId = s_sceneManager->findAudioClipByName(name); if (soundId < 0) { - printf("[Audio_Play] clip not found\n"); lua.pushNumber(-1); return 1; } - printf("[Audio_Play] found at index %d\n", soundId); } else { - printf("[Audio_Play] invalid arg type\n"); lua.pushNumber(-1); return 1; } @@ -1208,9 +1208,7 @@ int LuaAPI::Audio_Play(lua_State* L) { int volume = static_cast(lua.optNumber(2, 100)); int pan = static_cast(lua.optNumber(3, 64)); - printf("[Audio_Play] play(%d, vol=%d, pan=%d)\n", soundId, volume, pan); int voice = s_sceneManager->getAudio().play(soundId, volume, pan); - printf("[Audio_Play] voice=%d OK\n", voice); lua.pushNumber(voice); return 1; } @@ -1236,6 +1234,7 @@ int LuaAPI::Audio_Find(lua_State* L) { int LuaAPI::Audio_Stop(lua_State* L) { psyqo::Lua lua(L); + if (!s_sceneManager) return 0; int channelId = static_cast(lua.toNumber(1)); s_sceneManager->getAudio().stopVoice(channelId); return 0; @@ -1243,6 +1242,7 @@ int LuaAPI::Audio_Stop(lua_State* L) { int LuaAPI::Audio_SetVolume(lua_State* L) { psyqo::Lua lua(L); + if (!s_sceneManager) return 0; int channelId = static_cast(lua.toNumber(1)); int volume = static_cast(lua.toNumber(2)); int pan = static_cast(lua.optNumber(3, 64)); @@ -1252,6 +1252,7 @@ int LuaAPI::Audio_SetVolume(lua_State* L) { int LuaAPI::Audio_StopAll(lua_State* L) { psyqo::Lua lua(L); + if (!s_sceneManager) return 0; s_sceneManager->getAudio().stopAll(); return 0; } @@ -1263,9 +1264,8 @@ int LuaAPI::Audio_StopAll(lua_State* L) { int LuaAPI::Debug_Log(lua_State* L) { psyqo::Lua lua(L); - const char* msg = lua.optString(1, ""); - printf("[Lua] %s\n", msg); - + // Debug.Log is a no-op on PS1 to avoid printf overhead. + // Messages are only visible in emulator TTY builds. return 0; } @@ -1280,12 +1280,6 @@ int LuaAPI::Debug_DrawLine(lua_State* L) { } // TODO: Queue LINE_G2 primitive through Renderer - // For now, log to debug console (visible in emulator) -#ifdef PSXSPLASH_DEBUG_DRAW - printf("[DebugDraw] Line (%d,%d,%d)->(%d,%d,%d)\n", - sx.raw() >> 12, sy.raw() >> 12, sz.raw() >> 12, - ex.raw() >> 12, ey.raw() >> 12, ez.raw() >> 12); -#endif return 0; } @@ -1300,11 +1294,6 @@ int LuaAPI::Debug_DrawBox(lua_State* L) { } // TODO: Queue 12 LINE_G2 primitives (box wireframe) through Renderer -#ifdef PSXSPLASH_DEBUG_DRAW - printf("[DebugDraw] Box center(%d,%d,%d) size(%d,%d,%d)\n", - cx.raw() >> 12, cy.raw() >> 12, cz.raw() >> 12, - hx.raw() >> 12, hy.raw() >> 12, hz.raw() >> 12); -#endif return 0; } diff --git a/src/mesh.hh b/src/mesh.hh index 7801da9..e9403d6 100644 --- a/src/mesh.hh +++ b/src/mesh.hh @@ -24,7 +24,6 @@ namespace psxsplash { uint16_t padding; /// Returns true if this triangle has no texture (vertex-color only). - /// These should be rendered as POLY_G3 (GouraudTriangle) instead of POLY_GT3. bool isUntextured() const { return *reinterpret_cast(&tpage) == UNTEXTURED_TPAGE; } diff --git a/src/navregion.cpp b/src/navregion.cpp index ee3feb1..26dd7c5 100644 --- a/src/navregion.cpp +++ b/src/navregion.cpp @@ -117,40 +117,6 @@ void NavRegionSystem::closestPointOnSegment(int32_t px, int32_t pz, outZ = az + fpmul(t, abz); } -// ============================================================================ -// Segment crosses portal check (XZ) -// ============================================================================ - -bool NavRegionSystem::segmentCrossesPortal(int32_t p0x, int32_t p0z, - int32_t p1x, int32_t p1z, - int32_t ax, int32_t az, - int32_t bx, int32_t bz) { - // Standard 2D segment intersection test using cross products. - // Returns true if segment [p0,p1] crosses segment [a,b]. - - int32_t dx = p1x - p0x, dz = p1z - p0z; - int32_t ex = bx - ax, ez = bz - az; - - int64_t denom = (int64_t)dx * ez - (int64_t)dz * ex; - if (denom == 0) return false; // Parallel - - int32_t fx = ax - p0x, fz = az - p0z; - - int64_t tNum = (int64_t)fx * ez - (int64_t)fz * ex; - int64_t uNum = (int64_t)fx * dz - (int64_t)fz * dx; - - // Check t in [0,1] and u in [0,1] - if (denom > 0) { - if (tNum < 0 || tNum > denom) return false; - if (uNum < 0 || uNum > denom) return false; - } else { - if (tNum > 0 || tNum < denom) return false; - if (uNum > 0 || uNum < denom) return false; - } - - return true; -} - // ============================================================================ // Get floor Y at position (plane equation) // ============================================================================ @@ -316,36 +282,10 @@ int32_t NavRegionSystem::resolvePosition(int32_t& newX, int32_t& newZ, bool NavRegionSystem::findPath(uint16_t startRegion, uint16_t endRegion, NavPath& path) const { // STUB: Returns false until NPC pathfinding is implemented. - // When implemented, this will be A* over the region adjacency graph: - // - Open set: priority queue by f-cost (g + heuristic) - // - g-cost: sum of Euclidean distances between region centroids - // - Heuristic: straight-line distance to goal centroid - // - Neighbor iteration: via portal edges - // - Max path length: NAV_MAX_PATH_STEPS path.stepCount = 0; (void)startRegion; (void)endRegion; return false; } -// ============================================================================ -// Get portal between two regions -// ============================================================================ - -const NavPortal* NavRegionSystem::getPortalBetween(uint16_t regionA, uint16_t regionB) const { - if (regionA >= m_header.regionCount) return nullptr; - - const auto& reg = m_regions[regionA]; - for (int i = 0; i < reg.portalCount; i++) { - uint16_t portalIdx = reg.portalStart + i; - if (portalIdx >= m_header.portalCount) break; - - if (m_portals[portalIdx].neighborRegion == regionB) { - return &m_portals[portalIdx]; - } - } - - return nullptr; -} - } // namespace psxsplash diff --git a/src/navregion.hh b/src/navregion.hh index 6e83cba..081513e 100644 --- a/src/navregion.hh +++ b/src/navregion.hh @@ -9,9 +9,6 @@ * - Player has a single current region index. * - Movement: point-in-convex-polygon test โ†’ portal crossing โ†’ neighbor update. * - Floor Y: project XZ onto region's floor plane. - * - Pathfinding: A* over region adjacency graph (stub ready for NPC drop-in). - * - * All math is fixed-point 20.12. Zero floats. */ #include @@ -148,24 +145,10 @@ public: /// Returns the clamped position. void clampToRegion(int32_t& x, int32_t& z, uint16_t regionIndex) const; - // ======================================================================== - // Pathfinding stub โ€” documented API for NPC drop-in - // ======================================================================== - - /// Find a path from startRegion to endRegion. - /// Writes region indices into path.regions[], sets path.stepCount. - /// Returns true if a path was found. - /// - /// Implementation: A* over the region adjacency graph. - /// Cost heuristic: Euclidean distance between region centroids. - /// This is a STUB โ€” returns false until NPC pathfinding is implemented. + // TODO: Implement this bool findPath(uint16_t startRegion, uint16_t endRegion, NavPath& path) const; - /// Get the portal edge between two adjacent regions. - /// Returns nullptr if regions are not adjacent. - const NavPortal* getPortalBetween(uint16_t regionA, uint16_t regionB) const; - private: NavDataHeader m_header = {}; const NavRegion* m_regions = nullptr; @@ -183,11 +166,6 @@ private: int32_t bx, int32_t bz, int32_t& outX, int32_t& outZ); - /// Check if a line segment (player movement) crosses a portal edge - static bool segmentCrossesPortal(int32_t p0x, int32_t p0z, - int32_t p1x, int32_t p1z, - int32_t ax, int32_t az, - int32_t bx, int32_t bz); }; } // namespace psxsplash diff --git a/src/pcdrv_handler.hh b/src/pcdrv_handler.hh index e7e6897..3ae6d48 100644 --- a/src/pcdrv_handler.hh +++ b/src/pcdrv_handler.hh @@ -1,26 +1,7 @@ -/* - * pcdrv_handler.hh - Unified PCDRV API with runtime dispatch - * - * On pcsx-redux (emulator), uses pcdrv.h break instructions which are - * intercepted at the CPU level natively. - * - * On real hardware, bypasses break instructions entirely and communicates - * directly over SIO1 using the same protocol as PCdrvSerialHost.cs. - * This avoids reliance on the exception save area, which is fragile - * across different compiler versions and optimization levels. - * - * Additionally, redirects PSYQo's printf output to SIO1 on real hardware. - * - * Call pcdrv_sio1_init() once at startup, after PSYQo initialization. - * Then use pcdrv_open/read/write/close/seek instead of PCopen/PCread/etc. - */ - #pragma once #include -#include #include -#include #include "common/hardware/pcsxhw.h" #include "common/kernel/pcdrv.h" @@ -79,18 +60,6 @@ static inline void sio_pcdrv_escape(uint32_t funcCode) { sio_write32(funcCode); } -// ========================================================================= -// Runtime detection - reads magic at 0x1F802080 each call. -// NOT cached in a static, because this is a header-only file and each -// translation unit would get its own copy of any static variable. -// pcsx_present() is a single bus read - negligible cost. -// ========================================================================= - -// ========================================================================= -// Direct SIO1 PCDRV implementations (real hardware path) -// These call the host protocol directly with actual pointers/values, -// bypassing break instructions and the exception save area entirely. -// ========================================================================= static int sio_pcdrv_init() { sio_pcdrv_escape(0x101); @@ -114,19 +83,6 @@ static int sio_pcdrv_open(const char* name, int flags) { return -1; } -static int sio_pcdrv_creat(const char* name) { - sio_pcdrv_escape(0x102); - if (!sio_check_okay()) return -1; - const char* p = name; - while (*p) sio_putc((uint8_t)*p++); - sio_putc(0x00); - sio_write32(0); // params - if (sio_check_okay()) { - return (int)sio_read32(); // handle - } - return -1; -} - static int sio_pcdrv_close(int fd) { sio_pcdrv_escape(0x104); if (!sio_check_okay()) return -1; @@ -158,23 +114,6 @@ static int sio_pcdrv_read(int fd, void* buf, int len) { return -1; } -static int sio_pcdrv_write(int fd, const void* buf, int len) { - sio_pcdrv_escape(0x106); - if (!sio_check_okay()) return -1; - sio_write32((uint32_t)fd); - sio_write32((uint32_t)len); - sio_write32((uint32_t)(uintptr_t)buf); // memaddr for host debug - if (!sio_check_okay()) return -1; - const uint8_t* src = (const uint8_t*)buf; - for (int i = 0; i < len; i++) { - sio_putc(src[i]); - } - if (sio_check_okay()) { - return (int)sio_read32(); // bytes written - } - return -1; -} - static int sio_pcdrv_seek(int fd, int offset, int whence) { sio_pcdrv_escape(0x107); if (!sio_check_okay()) return -1; @@ -202,11 +141,6 @@ static int pcdrv_open(const char* name, int flags, int perms) { return sio_pcdrv_open(name, flags); } -static int pcdrv_creat(const char* name, int perms) { - if (pcsx_present()) return PCcreat(name, perms); - return sio_pcdrv_creat(name); -} - static int pcdrv_close(int fd) { if (pcsx_present()) return PCclose(fd); return sio_pcdrv_close(fd); @@ -217,11 +151,6 @@ static int pcdrv_read(int fd, void* buf, int len) { return sio_pcdrv_read(fd, buf, len); } -static int pcdrv_write(int fd, const void* buf, int len) { - if (pcsx_present()) return PCwrite(fd, buf, len); - return sio_pcdrv_write(fd, buf, len); -} - static int pcdrv_seek(int fd, int offset, int whence) { if (pcsx_present()) return PClseek(fd, offset, whence); return sio_pcdrv_seek(fd, offset, whence); @@ -239,51 +168,6 @@ static void sio1Init() { for (int i = 0; i < 100; i++) { __asm__ volatile("" ::: "memory"); } // settle delay } -// ========================================================================= -// Printf redirect - replaces PSYQo's printfStub with SIO1 output -// -// PSYQo's kernel takeover (takeOverKernel) destroys the BIOS and replaces -// the A0/B0/C0 jump handlers. Only A0[0x3F] (printf) is functional; all -// other BIOS calls return immediately. PSYQo's printfStub calls -// syscall_write(1,...) which goes to A0[0x03] - a dead no-op on real HW. -// -// Fix: replace the printf target address embedded in the A0 handler code -// at addresses 0xa8 (lui $t0, hi) and 0xb4 (ori $t0, $t0, lo) with our -// function that outputs directly to SIO1. -// ========================================================================= - -// Printf replacement that sends output to SIO1 -static int sio1Printf(const char* fmt, ...) { - va_list args; - va_start(args, fmt); - int r = vxprintf([](const char* data, int size, void*) { - for (int i = 0; i < size; i++) { - while (!(SIO1_STAT & SIO1_TX_RDY)) {} - SIO1_DATA = (uint8_t)data[i]; - } - }, nullptr, fmt, args); - va_end(args); - return r; -} - -static void redirectPrintfToSIO1() { - uintptr_t addr = (uintptr_t)sio1Printf; - uint16_t hi = (uint16_t)(addr >> 16); - uint16_t lo = (uint16_t)(addr & 0xffff); - if (lo >= 0x8000) hi++; // sign-extension compensation for ori - - // Patch the A0 handler's embedded address: - // 0xa8: lui $t0, hi (opcode 001111, rs=0, rt=$t0=$8) - // 0xb4: ori $t0, $t0, lo (opcode 001101, rs=$t0, rt=$t0) - *(volatile uint32_t*)0xa8 = 0x3C080000 | hi; // lui $t0, hi - *(volatile uint32_t*)0xb4 = 0x35080000 | lo; // ori $t0, $t0, lo - - psyqo::Kernel::flushCache(); -} - -// ========================================================================= -// Master init - call once at startup, after PSYQo initialization -// ========================================================================= static void pcdrv_sio1_init() { if (pcsx_present()) return; // emulator handles PCDRV natively diff --git a/src/renderer.cpp b/src/renderer.cpp index 1bc71bb..603bb33 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -57,12 +57,6 @@ void psxsplash::Renderer::SetFog(const FogConfig& fog) { } } -void psxsplash::Renderer::writeFogRegisters() { - // Per-vertex fog is now computed manually in processTriangle (no DPCT). - // DQA/DQB/RFC/GFC/BFC are no longer needed for fog. - // The fog color is used directly via m_fog.color in the fogBlend function. -} - psyqo::Vec3 psxsplash::Renderer::computeCameraViewPos() { ::clear(); ::clear(); @@ -270,7 +264,7 @@ void psxsplash::Renderer::Render(eastl::vector& objects) { auto& ditherCmd = balloc.allocateFragment(); ditherCmd.primitive.attr.setDithering(true); ot.insert(ditherCmd, ORDERING_TABLE_SIZE - 1); - writeFogRegisters(); + psyqo::Vec3 cameraPosition = computeCameraViewPos(); int32_t fogFarSZ = m_fog.fogFarSZ; for (auto& obj : objects) { @@ -301,7 +295,7 @@ void psxsplash::Renderer::RenderWithBVH(eastl::vector& objects, con auto& ditherCmd2 = balloc.allocateFragment(); ditherCmd2.primitive.attr.setDithering(true); ot.insert(ditherCmd2, ORDERING_TABLE_SIZE - 1); - writeFogRegisters(); + Frustum frustum; m_currentCamera->ExtractFrustum(frustum); int visibleCount = bvh.cullFrustum(frustum, m_visibleRefs, MAX_VISIBLE_TRIANGLES); psyqo::Vec3 cameraPosition = computeCameraViewPos(); @@ -510,7 +504,7 @@ void psxsplash::Renderer::RenderWithRooms(eastl::vector& objects, auto& ditherCmd3 = balloc.allocateFragment(); ditherCmd3.primitive.attr.setDithering(true); ot.insert(ditherCmd3, ORDERING_TABLE_SIZE - 1); - writeFogRegisters(); + psyqo::Vec3 cameraPosition = computeCameraViewPos(); int32_t fogFarSZ = m_fog.fogFarSZ; int32_t camX = m_currentCamera->GetPosition().x.raw(); diff --git a/src/renderer.hh b/src/renderer.hh index 86e5402..3e86d50 100644 --- a/src/renderer.hh +++ b/src/renderer.hh @@ -97,12 +97,9 @@ class Renderer final { TriangleRef m_visibleRefs[MAX_VISIBLE_TRIANGLES]; int m_frameCount = 0; - void writeFogRegisters(); psyqo::Vec3 computeCameraViewPos(); void setupObjectTransform(GameObject* obj, const psyqo::Vec3& cameraPosition); - // Core triangle pipeline: rtpt -> nclip -> screen-space clip -> emit. - // Uses Bandwidth's proven approach: nclip always, max-SZ depth, screen clip. void processTriangle(Tri& tri, int32_t fogFarSZ, psyqo::OrderingTable& ot, psyqo::BumpAllocator& balloc); diff --git a/src/sceneloader.cpp b/src/sceneloader.cpp deleted file mode 100644 index f962cdc..0000000 --- a/src/sceneloader.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "sceneloader.hh" -#include "fileloader.hh" - -namespace psxsplash { - -bool SceneLoader::s_initialised = false; - -bool SceneLoader::Init() { - // FileLoader::Get().scheduleInit() is task-based; for backward compat - // with the old sync Init() we rely on main.cpp running the init task - // before any LoadFile calls. This flag is set unconditionally so that - // IsPCdrvAvailable() remains useful. - s_initialised = true; - return true; -} - -bool SceneLoader::IsPCdrvAvailable() { - return s_initialised; -} - -uint8_t* SceneLoader::LoadFile(const char* filename, int& outSize) { - return FileLoader::Get().LoadFileSync(filename, outSize); -} - -void SceneLoader::FreeFile(uint8_t* data) { - FileLoader::Get().FreeFile(data); -} - -} // namespace psxsplash diff --git a/src/sceneloader.hh b/src/sceneloader.hh deleted file mode 100644 index 242adb6..0000000 --- a/src/sceneloader.hh +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include - -namespace psxsplash { - -/** - * SceneLoader โ€” backward-compatibility faรงade. - * - * All calls now delegate to the FileLoader singleton (selected at compile - * time by the LOADER build flag). New code should use FileLoader directly. - */ -class SceneLoader { - public: - /** Initialise the active FileLoader backend (PCdrv or CD-ROM). */ - static bool Init(); - - /** Load a file synchronously. Uses FileLoader::Get().LoadFileSync(). */ - static uint8_t* LoadFile(const char* filename, int& outSize); - - /** Free previously loaded data. Uses FileLoader::Get().FreeFile(). */ - static void FreeFile(uint8_t* data); - - /** Returns true after a successful Init(). */ - static bool IsPCdrvAvailable(); - - private: - static bool s_initialised; -}; - -} // namespace psxsplash diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index 20e7921..1152320 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -6,6 +6,7 @@ #include "profiler.hh" #include "renderer.hh" #include "splashpack.hh" +#include "streq.hh" #include "luaapi.hh" #include "loadingscreen.hh" @@ -163,24 +164,21 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc m_playerHeight = sceneSetup.playerHeight; - // Load movement parameters from splashpack (v8+) m_controls.setMoveSpeed(sceneSetup.moveSpeed); m_controls.setSprintSpeed(sceneSetup.sprintSpeed); m_playerRadius = (int32_t)sceneSetup.playerRadius.value; - if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS; // fallback to default + if (m_playerRadius == 0) m_playerRadius = PLAYER_RADIUS; m_jumpVelocityRaw = (int32_t)sceneSetup.jumpVelocity.value; int32_t gravityRaw = (int32_t)sceneSetup.gravity.value; - m_gravityPerFrame = gravityRaw / 30; // Convert per-secondยฒ to per-frame velocity change - if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1; // Ensure nonzero + m_gravityPerFrame = gravityRaw / 30; + if (m_gravityPerFrame == 0 && gravityRaw > 0) m_gravityPerFrame = 1; m_velocityY = 0; m_isGrounded = true; m_lastFrameTime = 0; m_deltaFrames = 1; - // Initialize collision system m_collisionSystem.init(); - // Register colliders from splashpack data for (size_t i = 0; i < sceneSetup.colliders.size(); i++) { SPLASHPACKCollider* collider = sceneSetup.colliders[i]; if (collider == nullptr) continue; @@ -203,7 +201,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc ); } - // Register trigger boxes from splashpack data for (size_t i = 0; i < sceneSetup.triggerBoxes.size(); i++) { SPLASHPACKTriggerBox* tb = sceneSetup.triggerBoxes[i]; if (tb == nullptr) continue; @@ -219,9 +216,7 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc m_collisionSystem.registerTriggerBox(bounds, tb->luaFileIndex); } - // Load Lua files - order is important here. We need - // to load the Lua files before we register the game objects, - // as the game objects may reference Lua files by index. + for (int i = 0; i < m_luaFiles.size(); i++) { auto luaFile = m_luaFiles[i]; L.LoadLuaFile(luaFile->luaCode, luaFile->length, i); @@ -233,7 +228,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc L.OnSceneCreationStart(); - // Register game objects for (auto object : m_gameObjects) { L.RegisterGameObject(object); } @@ -255,34 +249,25 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { LuaAPI::IncrementFrameCount(); - // Tick cutscene player (advance frame and apply tracks before rendering) m_cutscenePlayer.tick(); - // Delta-time measurement: count elapsed frames based on gpu timer - // PS1 NTSC frame = ~33333 microseconds (30fps vsync) { uint32_t now = gpu.now(); if (m_lastFrameTime != 0) { uint32_t elapsed = now - m_lastFrameTime; - // 33333us per frame at 30fps. If >50000us, we dropped a frame. m_deltaFrames = (elapsed > 50000) ? 2 : 1; - if (elapsed > 83000) m_deltaFrames = 3; // Two frames dropped + if (elapsed > 83000) m_deltaFrames = 3; } m_lastFrameTime = now; } uint32_t renderingStart = gpu.now(); auto& renderer = psxsplash::Renderer::GetInstance(); - // Dispatch render path based on scene type. - // Interior scenes (type 1) use room/portal occlusion; exterior scenes use BVH culling. if (m_sceneType == 1 && m_roomCount > 0 && m_rooms != nullptr) { - // Determine which room the camera is in for portal culling. - // During cutscene playback the camera may be elsewhere from the player, - // so use the actual camera position to find the room. + int camRoom = -1; if (m_navRegions.isLoaded()) { if (m_cutscenePlayer.isPlaying()) { - // Camera-driven: look up nav region from camera world position auto& camPos = m_currentCamera.GetPosition(); uint16_t camRegion = m_navRegions.findRegion(camPos.x.value, camPos.z.value); if (camRegion != NAV_NO_REGION) { @@ -290,7 +275,6 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { if (ri != 0xFF) camRoom = (int)ri; } } else if (m_playerNavRegion != NAV_NO_REGION) { - // Normal gameplay: use cached player nav region uint8_t ri = m_navRegions.getRoomIndex(m_playerNavRegion); if (ri != 0xFF) camRoom = (int)ri; } @@ -308,10 +292,8 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { psxsplash::debug::Profiler::getInstance().setSectionTime(psxsplash::debug::PROFILER_RENDERING, renderingTime); #endif - // Collision detection uint32_t collisionStart = gpu.now(); - // Build player AABB from position + radius/height AABB playerAABB; { psyqo::FixedPoint<12> r; @@ -327,7 +309,6 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { psyqo::Vec3 pushBack; int collisionCount = m_collisionSystem.detectCollisions(playerAABB, pushBack); - // Apply push-back to player position { psyqo::FixedPoint<12> zero; if (pushBack.x != zero || pushBack.z != zero) { @@ -426,9 +407,7 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) { uint32_t navmeshStart = gpu.now(); if (!freecam) { - // Priority: WorldCollision + NavRegions (v7) > NavGrid (v5) > Legacy Navmesh if (m_worldCollision.isLoaded()) { - // Move-and-slide against world geometry (XZ walls only) psyqo::Vec3 slid = m_worldCollision.moveAndSlide( oldPlayerPosition, m_playerPosition, m_playerRadius, 0xFF); @@ -664,9 +643,6 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is char filename[32]; FileLoader::BuildSceneFilename(sceneIndex, filename, sizeof(filename)); - // Blank BOTH framebuffers so the user doesn't see the BIOS screen - // or a frozen frame. FastFill ignores the scissor/DrawingArea so we - // can target any VRAM region directly. psyqo::Prim::FastFill ff(psyqo::Color{.r = 0, .g = 0, .b = 0}); ff.rect = psyqo::Rect{0, 0, 320, 240}; gpu.sendPrimitive(ff); @@ -674,7 +650,6 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is gpu.sendPrimitive(ff); gpu.pumpCallbacks(); - // Try to load a loading screen for the target scene LoadingScreen loading; if (s_font) { if (loading.load(gpu, *s_font, sceneIndex)) { @@ -742,9 +717,7 @@ void psxsplash::SceneManager::clearScene() { // (bytecode, strings, tables, registry) in one shot via lua_close. L.Shutdown(); - // 2. Free vector BACKING STORAGE (not just contents). - // clear() only sets size=0 but keeps the allocated capacity. - // swap-with-empty releases the heap blocks so they can be coalesced. + // 2. Clear all vectors to free their heap storage (game objects, Lua files, names, etc) { eastl::vector tmp; tmp.swap(m_gameObjects); } { eastl::vector tmp; tmp.swap(m_luaFiles); } { eastl::vector tmp; tmp.swap(m_objectNames); } @@ -775,12 +748,10 @@ void psxsplash::SceneManager::shrinkBuffer() { if (m_liveDataSize == 0 || m_currentSceneData == nullptr) return; uint8_t* oldBase = m_currentSceneData; - // Allocate the shrunk buffer. The volatile cast prevents the compiler - // from assuming operator new never returns NULL (it does with - // -fno-exceptions), which would let it optimize away the null check. + uint8_t* volatile newBaseV = new uint8_t[m_liveDataSize]; uint8_t* newBase = newBaseV; - if (!newBase) return; // Heap exhausted โ€” keep the full buffer + if (!newBase) return; __builtin_memcpy(newBase, oldBase, m_liveDataSize); intptr_t delta = reinterpret_cast(newBase) - reinterpret_cast(oldBase); @@ -823,11 +794,6 @@ void psxsplash::SceneManager::shrinkBuffer() { m_uiSystem.relocate(delta); - // Re-key Lua registry entries for game objects. RegisterGameObject stored - // lightuserdata keys using the OLD buffer addresses. After relocation, the - // game object pointers changed but the registry keys are stale. Without - // this, Entity.Find/FindByIndex return nil (wrong key), and in release - // builds (-Os) the optimizer can turn this into a hard crash. if (!m_gameObjects.empty()) { L.RelocateGameObjects( reinterpret_cast(m_gameObjects.data()), @@ -842,16 +808,11 @@ void psxsplash::SceneManager::shrinkBuffer() { // OBJECT NAME LOOKUP // ============================================================================ -// Inline streq (no libc on bare-metal PS1) -static bool name_eq(const char* a, const char* b) { - while (*a && *b) { if (*a++ != *b++) return false; } - return *a == *b; -} psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* name) const { if (!name || m_objectNames.empty()) return nullptr; for (size_t i = 0; i < m_objectNames.size() && i < m_gameObjects.size(); i++) { - if (m_objectNames[i] && name_eq(m_objectNames[i], name)) { + if (m_objectNames[i] && streq(m_objectNames[i], name)) { return m_gameObjects[i]; } } @@ -861,7 +822,7 @@ psxsplash::GameObject* psxsplash::SceneManager::findObjectByName(const char* nam int psxsplash::SceneManager::findAudioClipByName(const char* name) const { if (!name || m_audioClipNames.empty()) return -1; for (size_t i = 0; i < m_audioClipNames.size(); i++) { - if (m_audioClipNames[i] && name_eq(m_audioClipNames[i], name)) { + if (m_audioClipNames[i] && streq(m_audioClipNames[i], name)) { return static_cast(i); } } diff --git a/src/scenemanager.hh b/src/scenemanager.hh index cc2f83f..69e9503 100644 --- a/src/scenemanager.hh +++ b/src/scenemanager.hh @@ -139,7 +139,6 @@ class SceneManager { int m_cutsceneCount = 0; CutscenePlayer m_cutscenePlayer; - // UI system (v13+) UISystem m_uiSystem; #ifdef PSXSPLASH_MEMOVERLAY MemOverlay m_memOverlay; @@ -154,7 +153,6 @@ class SceneManager { psyqo::FixedPoint<12, uint16_t> m_playerHeight; - // Movement physics (v8+) int32_t m_playerRadius; // Collision radius in fp12 (replaces hardcoded PLAYER_RADIUS) int32_t m_velocityY; // Vertical velocity in fp12 per second (negative = up) int32_t m_gravityPerFrame; // Gravity velocity change per frame (fp12) @@ -182,5 +180,4 @@ class SceneManager { void clearScene(); // Deallocate current scene objects void shrinkBuffer(); // Free pixel/audio bulk data after VRAM/SPU uploads }; -}; // namespace psxsplash - // namespace psxsplash \ No newline at end of file +} // namespace psxsplash \ No newline at end of file diff --git a/src/sio_pcdrv.h b/src/sio_pcdrv.h deleted file mode 100644 index 0de9acb..0000000 --- a/src/sio_pcdrv.h +++ /dev/null @@ -1,296 +0,0 @@ -/* - * sio_pcdrv.h โ€” SIO1-based PCDrv implementation for PSYQo applications - * - * Problem: PSYQo's kernel initialization overwrites the exception handler - * at 0x80000080, destroying Unirom's DEBG hooks. The standard - * pcdrv.h functions use MIPS `break` instructions that rely on - * those hooks to translate into SIO escape sequences. - * - * Solution: Bypass the `break` instruction mechanism entirely. Instead, - * talk directly to SIO1 hardware and send the exact same 0x00+'p' - * escape protocol that the host (NOTPSXSerial / PCdrvSerialHost) - * expects. This works regardless of what's at the exception vector. - * - * Protocol: Matches NOTPSXSerial's PCDrv.cs / Bridge.MonitorSerial(): - * PS1 โ†’ Host: 0x00 'p' funcCode(4 LE) - * Host โ†’ PS1: "OKAY" ... (function-specific data) - * or "NOPE" on error - */ - -#pragma once - -#include - -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• -// SIO1 hardware registers (UART serial port, 0x1F801050) -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - -#define SIO1_DATA (*(volatile uint8_t *)0x1F801050) -#define SIO1_STAT (*(volatile uint32_t *)0x1F801054) -#define SIO1_MODE (*(volatile uint16_t *)0x1F801058) -#define SIO1_CTRL (*(volatile uint16_t *)0x1F80105A) -#define SIO1_BAUD (*(volatile uint16_t *)0x1F80105E) - -// Status register bits -#define SIO1_STAT_TX_RDY (1 << 0) // TX FIFO not full -#define SIO1_STAT_RX_RDY (1 << 1) // RX data available - -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• -// Low-level SIO1 I/O โ€” blocking, tight polling loops -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - -static inline void sio_putc(uint8_t byte) { - while (!(SIO1_STAT & SIO1_STAT_TX_RDY)) {} - SIO1_DATA = byte; -} - -static inline uint8_t sio_getc() { - while (!(SIO1_STAT & SIO1_STAT_RX_RDY)) {} - return SIO1_DATA; -} - -static inline void sio_write32(uint32_t val) { - sio_putc((uint8_t)(val)); - sio_putc((uint8_t)(val >> 8)); - sio_putc((uint8_t)(val >> 16)); - sio_putc((uint8_t)(val >> 24)); -} - -static inline uint32_t sio_read32() { - uint32_t v = (uint32_t)sio_getc(); - v |= (uint32_t)sio_getc() << 8; - v |= (uint32_t)sio_getc() << 16; - v |= (uint32_t)sio_getc() << 24; - return v; -} - -static inline void sio_send_str(const char *s) { - while (*s) sio_putc((uint8_t)*s++); -} - -// Read 4 bytes and check if they are "OKAY" -static inline int sio_check_okay() { - uint8_t a = sio_getc(); - uint8_t b = sio_getc(); - uint8_t c = sio_getc(); - uint8_t d = sio_getc(); - return (a == 'O' && b == 'K' && c == 'A' && d == 'Y'); -} - -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• -// PCDrv escape protocol โ€” send 0x00 + 'p' + function code -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - -static inline void sio_pcdrv_escape(uint32_t funcCode) { - sio_putc(0x00); // escape character - sio_putc('p'); // PCDrv marker - sio_write32(funcCode); // function code, little-endian -} - -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• -// PCDrv API โ€” drop-in replacements for common/kernel/pcdrv.h -// Same names, same signatures, same return conventions. -// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - -/** - * sio1_ensure_init โ€” (re-)initialize SIO1 for 115200 8N1 - * Safe to call multiple times. Uses the same register values - * that Unirom/nugget use, so this is a no-op if SIO1 is already - * configured. Ensures correct config even if PSYQo or BIOS - * touched the SIO1 registers. - */ -static inline void sio1_ensure_init() { - SIO1_CTRL = 0; // reset - SIO1_MODE = 0x004e; // MUL16, 8 data bits, no parity, 1 stop bit - SIO1_BAUD = (uint16_t)(2073600 / 115200); // = 18 - SIO1_CTRL = 0x0025; // TX enable, RX enable, RTS assert - // Small delay for hardware to settle - { - int i = 0; - while (i < 100) { __asm__ volatile("" ::: "memory"); i++; } - } -} - -/** - * PCinit โ€” initialize PCDrv connection - * Returns 0 on success, -1 on failure. - */ -static inline int PCinit() { - sio1_ensure_init(); // make sure SIO1 is properly configured - sio_pcdrv_escape(0x101); - - // Host responds: "OKAY" + 0x00 - if (!sio_check_okay()) return -1; - sio_getc(); // consume trailing 0x00 - return 0; -} - -/** - * PCopen โ€” open a file on the host - * Returns file handle (positive) on success, -1 on failure. - */ -static inline int PCopen(const char *name, int flags, int perms) { - (void)perms; // unused in protocol - sio_pcdrv_escape(0x103); - - // Host responds: "OKAY" (first ACK, ready for filename) - if (!sio_check_okay()) return -1; - - // Send filename (null-terminated) - const char *p = name; - while (*p) sio_putc((uint8_t)*p++); - sio_putc(0x00); // null terminator - - // Send file mode as uint32 LE - sio_write32((uint32_t)flags); - - // Host responds: "OKAY" + handle(4) or "NOPE" - uint8_t r0 = sio_getc(); - uint8_t r1 = sio_getc(); - uint8_t r2 = sio_getc(); - uint8_t r3 = sio_getc(); - - if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { - return -1; - } - - // "OKAY" โ€” read handle - int handle = (int)sio_read32(); - return handle; -} - -/** - * PCclose โ€” close a file handle - * Returns 0 on success. - */ -static inline int PCclose(int fd) { - sio_pcdrv_escape(0x104); - - // Host responds: "OKAY" (ready for params) - if (!sio_check_okay()) return -1; - - // Send handle + 2 unused params (matches Unirom kernel convention) - sio_write32((uint32_t)fd); - sio_write32(0); // unused - sio_write32(0); // unused - - // Host responds: "OKAY" + handle(4) or "NOPE" - uint8_t r0 = sio_getc(); - uint8_t r1 = sio_getc(); - uint8_t r2 = sio_getc(); - uint8_t r3 = sio_getc(); - - if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { - return -1; - } - - // "OKAY" โ€” read handle back (v1, not used by caller) - sio_read32(); - return 0; -} - -/** - * PCread โ€” read data from a file into memory - * Returns number of bytes read, or -1 on failure. - */ -static inline int PCread(int fd, void *buf, int len) { - sio_pcdrv_escape(0x105); - - // Host responds: "OKAY" (ready for params) - if (!sio_check_okay()) return -1; - - // Send handle + length + memaddr (memaddr is debug-only, send buf ptr) - sio_write32((uint32_t)fd); - sio_write32((uint32_t)len); - sio_write32((uint32_t)(uintptr_t)buf); - - // Host responds: "OKAY" + dataLength(4) + checksum(4) + raw data - // or "NOPE" - uint8_t r0 = sio_getc(); - uint8_t r1 = sio_getc(); - uint8_t r2 = sio_getc(); - uint8_t r3 = sio_getc(); - - if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { - return -1; - } - - // "OKAY" โ€” read response - uint32_t dataLength = sio_read32(); - uint32_t checksum = sio_read32(); // not verified, just consume - (void)checksum; - - // Read raw data bytes into buffer - uint8_t *dst = (uint8_t *)buf; - for (uint32_t i = 0; i < dataLength; i++) { - dst[i] = sio_getc(); - } - - return (int)dataLength; -} - -/** - * PCwrite โ€” write data from memory to a file - * Returns number of bytes written, or -1 on failure. - */ -static inline int PCwrite(int fd, const void *buf, int len) { - sio_pcdrv_escape(0x106); - - // Host responds: "OKAY" (ready for params) - if (!sio_check_okay()) return -1; - - // Send handle + length + memaddr - sio_write32((uint32_t)fd); - sio_write32((uint32_t)len); - sio_write32((uint32_t)(uintptr_t)buf); - - // Host responds: "OKAY" (ready for data) or "NOPE" - uint8_t r0 = sio_getc(); - uint8_t r1 = sio_getc(); - uint8_t r2 = sio_getc(); - uint8_t r3 = sio_getc(); - if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { - return -1; - } - - // Send raw data - const uint8_t *src = (const uint8_t *)buf; - for (int i = 0; i < len; i++) { - sio_putc(src[i]); - } - - // Host responds: "OKAY" + bytesWritten(4) - if (!sio_check_okay()) return -1; - int written = (int)sio_read32(); - return written; -} - -/** - * PClseek โ€” seek within a file - * Returns new position, or -1 on failure. - */ -static inline int PClseek(int fd, int offset, int whence) { - sio_pcdrv_escape(0x107); - - // Host responds: "OKAY" (ready for params) - if (!sio_check_okay()) return -1; - - // Send handle + offset + whence (seek origin) - sio_write32((uint32_t)fd); - sio_write32((uint32_t)offset); - sio_write32((uint32_t)whence); - - // Host responds: "OKAY" + position(4) or "NOPE" - uint8_t r0 = sio_getc(); - uint8_t r1 = sio_getc(); - uint8_t r2 = sio_getc(); - uint8_t r3 = sio_getc(); - - if (r0 == 'N' && r1 == 'O' && r2 == 'P' && r3 == 'E') { - return -1; - } - - // "OKAY" โ€” read new position - int pos = (int)sio_read32(); - return pos; -} diff --git a/src/splashpack.cpp b/src/splashpack.cpp index 3f44e96..1d4664d 100644 --- a/src/splashpack.cpp +++ b/src/splashpack.cpp @@ -12,20 +12,13 @@ #include "cutscene.hh" #include "lua.h" #include "mesh.hh" +#include "streq.hh" #include "worldcollision.hh" #include "navregion.hh" #include "renderer.hh" namespace psxsplash { -// Bare-metal string compare (no libc) -static bool sp_streq(const char* a, const char* b) { - while (*a && *b) { - if (*a++ != *b++) return false; - } - return *a == *b; -} - struct SPLASHPACKFileHeader { char magic[2]; uint16_t version; @@ -99,7 +92,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup setup.playerStartRotation = header->playerStartRot; setup.playerHeight = header->playerHeight; - // Movement parameters (v8+) setup.moveSpeed.value = header->moveSpeed; setup.sprintSpeed.value = header->sprintSpeed; setup.jumpVelocity.value = header->jumpVelocity; @@ -120,7 +112,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup cursor += sizeof(psxsplash::LuaFile); } - // sceneLuaFileIndex is stored as uint16_t in header; 0xFFFF means "no scene script" (-1) setup.sceneLuaFileIndex = (header->sceneLuaFileIndex == 0xFFFF) ? -1 : (int)header->sceneLuaFileIndex; for (uint16_t i = 0; i < header->gameObjectCount; i++) { @@ -130,14 +121,12 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup cursor += sizeof(psxsplash::GameObject); } - // Read collision data (after GameObjects) for (uint16_t i = 0; i < header->colliderCount; i++) { psxsplash::SPLASHPACKCollider *collider = reinterpret_cast(cursor); setup.colliders.push_back(collider); cursor += sizeof(psxsplash::SPLASHPACKCollider); } - // Read trigger boxes (after colliders) setup.triggerBoxes.reserve(header->triggerBoxCount); for (uint16_t i = 0; i < header->triggerBoxCount; i++) { psxsplash::SPLASHPACKTriggerBox *tb = reinterpret_cast(cursor); @@ -145,7 +134,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup cursor += sizeof(psxsplash::SPLASHPACKTriggerBox); } - // BVH data if (header->bvhNodeCount > 0) { BVHNode* bvhNodes = reinterpret_cast(cursor); cursor += header->bvhNodeCount * sizeof(BVHNode); @@ -157,28 +145,24 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup triangleRefs, header->bvhTriangleRefCount); } - // Interactables for (uint16_t i = 0; i < header->interactableCount; i++) { psxsplash::Interactable *interactable = reinterpret_cast(cursor); setup.interactables.push_back(interactable); cursor += sizeof(psxsplash::Interactable); } - // World collision soup if (header->worldCollisionMeshCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); cursor = const_cast(setup.worldCollision.initializeFromData(cursor)); } - // Nav regions if (header->navRegionCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); cursor = const_cast(setup.navRegions.initializeFromData(cursor)); } - // Room/portal data (interior scenes) if (header->roomCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); @@ -218,7 +202,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup for (uint16_t i = 0; i < header->gameObjectCount; i++) { uint8_t nameLen = *nameData++; const char* nameStr = reinterpret_cast(nameData); - // Names are stored as length-prefixed, null-terminated strings setup.objectNames.push_back(nameStr); nameData += nameLen + 1; // +1 for null terminator } @@ -318,7 +301,7 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup } else { for (size_t oi = 0; oi < setup.objectNames.size(); oi++) { if (setup.objectNames[oi] && - sp_streq(setup.objectNames[oi], objName)) { + streq(setup.objectNames[oi], objName)) { track.target = setup.objects[oi]; break; } @@ -343,7 +326,6 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup } } - // Read UI canvas data (version 13+) if (header->version >= 13) { setup.uiCanvasCount = header->uiCanvasCount; setup.uiFontCount = header->uiFontCount; diff --git a/src/splashpack.hh b/src/splashpack.hh index 7ccef56..d906697 100644 --- a/src/splashpack.hh +++ b/src/splashpack.hh @@ -51,7 +51,6 @@ struct SplashpackSceneSetup { // New component arrays eastl::vector interactables; - // Object name table (v9+): parallel to objects, points into splashpack data eastl::vector objectNames; // Audio clips (v10+): ADPCM data with metadata @@ -64,12 +63,11 @@ struct SplashpackSceneSetup { }; eastl::vector audioClips; - // Audio clip name table (v10+): parallel to audioClips, points into splashpack data eastl::vector audioClipNames; BVHManager bvh; // Spatial acceleration structure for culling - WorldCollision worldCollision; // Triangle-level world collision (v7+) - NavRegionSystem navRegions; // Convex region navigation (v7+) + WorldCollision worldCollision; + NavRegionSystem navRegions; psyqo::GTE::PackedVec3 playerStartPosition; psyqo::GTE::PackedVec3 playerStartRotation; psyqo::FixedPoint<12, uint16_t> playerHeight; @@ -82,7 +80,6 @@ struct SplashpackSceneSetup { uint8_t fogR = 0, fogG = 0, fogB = 0; uint8_t fogDensity = 5; - // Room/portal data (v11+, interior scenes only) const RoomData* rooms = nullptr; uint16_t roomCount = 0; const PortalData* portals = nullptr; @@ -90,25 +87,20 @@ struct SplashpackSceneSetup { const TriangleRef* roomTriRefs = nullptr; uint16_t roomTriRefCount = 0; - // Movement parameters (v8+) psyqo::FixedPoint<12, uint16_t> moveSpeed; // Per-frame speed constant (fp12) psyqo::FixedPoint<12, uint16_t> sprintSpeed; // Per-frame sprint constant (fp12) psyqo::FixedPoint<12, uint16_t> jumpVelocity; // Per-second initial velocity (fp12) psyqo::FixedPoint<12, uint16_t> gravity; // Per-secondยฒ acceleration (fp12) psyqo::FixedPoint<12, uint16_t> playerRadius; // Collision radius (fp12) - // Cutscenes (version 12+) Cutscene loadedCutscenes[MAX_CUTSCENES]; int cutsceneCount = 0; - // UI system (v13+) uint16_t uiCanvasCount = 0; uint8_t uiFontCount = 0; uint32_t uiTableOffset = 0; - // Buffer management (v15+): byte offset where pixel/audio bulk data begins. - // Everything before this offset is needed at runtime; everything after can - // be freed once VRAM/SPU uploads are done. + uint32_t liveDataSize = 0; }; @@ -117,4 +109,4 @@ class SplashPackLoader { void LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup); }; -}; // namespace psxsplash +} // namespace psxsplash diff --git a/src/streq.hh b/src/streq.hh new file mode 100644 index 0000000..da95b1a --- /dev/null +++ b/src/streq.hh @@ -0,0 +1,10 @@ +#pragma once + +namespace psxsplash { + +inline bool streq(const char* a, const char* b) { + while (*a && *a == *b) { a++; b++; } + return *a == *b; +} + +} // namespace psxsplash diff --git a/src/uisystem.cpp b/src/uisystem.cpp index 115452c..a165809 100644 --- a/src/uisystem.cpp +++ b/src/uisystem.cpp @@ -6,17 +6,10 @@ #include #include #include +#include "streq.hh" namespace psxsplash { -// Bare-metal string compare (no libc) -static bool ui_streq(const char* a, const char* b) { - while (*a && *b) { - if (*a++ != *b++) return false; - } - return *a == *b; -} - // ============================================================================ // Init // ============================================================================ @@ -521,7 +514,7 @@ void UISystem::renderProportionalText(int fontIdx, int16_t x, int16_t y, int UISystem::findCanvas(const char* name) const { if (!name) return -1; for (int i = 0; i < m_canvasCount; i++) { - if (m_canvases[i].name && ui_streq(m_canvases[i].name, name)) + if (m_canvases[i].name && streq(m_canvases[i].name, name)) return i; } return -1; @@ -546,7 +539,7 @@ int UISystem::findElement(int canvasIdx, const char* name) const { if (canvasIdx < 0 || canvasIdx >= m_canvasCount || !name) return -1; const UICanvas& cv = m_canvases[canvasIdx]; for (int i = 0; i < cv.elementCount; i++) { - if (cv.elements[i].name && ui_streq(cv.elements[i].name, name)) { + if (cv.elements[i].name && streq(cv.elements[i].name, name)) { // Return flat handle: index into m_elements int handle = (int)(cv.elements + i - m_elements); return handle; diff --git a/src/vram_config.h b/src/vram_config.h index 440dd88..5486f5e 100644 --- a/src/vram_config.h +++ b/src/vram_config.h @@ -1,4 +1,5 @@ -// Auto-generated by SplashEdit - do not edit manually. +// LEGACY -> used by loading screen + #pragma once // GPU resolution diff --git a/src/worldcollision.cpp b/src/worldcollision.cpp index e4d0de6..64e7ec6 100644 --- a/src/worldcollision.cpp +++ b/src/worldcollision.cpp @@ -3,16 +3,6 @@ #include #include -// One-shot collision diagnostics - -/** - * worldcollision.cpp - Player-vs-World Triangle Collision - * - * ALL math is 20.12 fixed-point. Intermediate products use int64_t - * to avoid overflow (20.12 * 20.12 = 40.24, shift >>12 back to 20.12). - * - * Performance budget: ~256 triangle tests per frame on 33MHz MIPS. - */ namespace psxsplash { diff --git a/test_literal.cpp b/test_literal.cpp deleted file mode 100644 index 0a89308..0000000 Binary files a/test_literal.cpp and /dev/null differ