diff --git a/src/controls.cpp b/src/controls.cpp index f16cfa4..73a5e47 100644 --- a/src/controls.cpp +++ b/src/controls.cpp @@ -1,7 +1,92 @@ #include "controls.hh" +#include +#include #include +namespace { + +using namespace psyqo::Hardware; + +void busyLoop(unsigned delay) { + unsigned cycles = 0; + while (++cycles < delay) asm(""); +} + +void flushRxBuffer() { + while (SIO::Stat & SIO::Status::STAT_RXRDY) { + SIO::Data.throwAway(); + } +} + +uint8_t transceive(uint8_t dataOut) { + SIO::Ctrl |= SIO::Control::CTRL_ERRRES; + CPU::IReg.clear(CPU::IRQ::Controller); + SIO::Data = dataOut; + while (!(SIO::Stat & SIO::Status::STAT_RXRDY)); + return SIO::Data; +} + +bool waitForAck() { + int cyclesWaited = 0; + static constexpr int ackTimeout = 0x137; + while (!(CPU::IReg.isSet(CPU::IRQ::Controller)) && ++cyclesWaited < ackTimeout); + if (cyclesWaited >= ackTimeout) return false; + while (SIO::Stat & SIO::Status::STAT_ACK); // Wait for ACK to go high + return true; +} + +void configurePort(uint8_t port) { + SIO::Ctrl = (port * SIO::Control::CTRL_PORTSEL) | SIO::Control::CTRL_DTR; + SIO::Baud = 0x88; + flushRxBuffer(); + SIO::Ctrl |= (SIO::Control::CTRL_TXEN | SIO::Control::CTRL_ACKIRQEN); + busyLoop(100); +} + +// Send a command sequence to the pad and wait for ACK between each byte. +// Returns false if ACK was lost at any point. +bool sendCommand(const uint8_t *cmd, unsigned len) { + for (unsigned i = 0; i < len; i++) { + transceive(cmd[i]); + if (i < len - 1) { + if (!waitForAck()) return false; + } + } + return true; +} + +} // namespace + +void psxsplash::Controls::forceAnalogMode() { + // Initialize SIO for pad communication + using namespace psyqo::Hardware; + SIO::Ctrl = SIO::Control::CTRL_IR; + SIO::Baud = 0x88; + SIO::Mode = 0xd; + SIO::Ctrl = 0; + + // Sequence for port 0 (Pad 1): + // 1) Enter config mode + static const uint8_t enterConfig[] = {0x01, 0x43, 0x00, 0x01, 0x00}; + // 2) Set analog mode (0x01) + lock (0x03) + static const uint8_t setAnalog[] = {0x01, 0x44, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}; + // 3) Exit config mode + static const uint8_t exitConfig[] = {0x01, 0x43, 0x00, 0x00, 0x00}; + + configurePort(0); + sendCommand(enterConfig, sizeof(enterConfig)); + SIO::Ctrl = 0; + + configurePort(0); + sendCommand(setAnalog, sizeof(setAnalog)); + SIO::Ctrl = 0; + + configurePort(0); + sendCommand(exitConfig, sizeof(exitConfig)); + SIO::Ctrl = 0; +} + void psxsplash::Controls::Init() { m_input.initialize(); } bool psxsplash::Controls::isDigitalPad() const { diff --git a/src/controls.hh b/src/controls.hh index f8dff40..7dbba77 100644 --- a/src/controls.hh +++ b/src/controls.hh @@ -12,6 +12,10 @@ using namespace psyqo::trig_literals; class Controls { public: + /// Force DualShock into analog mode (LED=Red) via raw SIO commands. + /// Must be called BEFORE Init() since Init() hands SIO control to AdvancedPad. + void forceAnalogMode(); + void Init(); void HandleControls(psyqo::Vec3 &playerPosition, psyqo::Angle &playerRotationX, psyqo::Angle &playerRotationY, psyqo::Angle &playerRotationZ, bool freecam, int deltaFrames); diff --git a/src/lua.cpp b/src/lua.cpp index 2d17ecc..cea53a1 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -460,6 +460,47 @@ void psxsplash::Lua::OnUpdate(GameObject* go, int deltaFrames) { onUpdateMethodWrapper.callMethod(*this, go, 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; + + // Re-key the main game object table: registry[oldPtr] -> registry[newPtr] + L.push(oldPtr); + L.rawGet(LUA_REGISTRYINDEX); + if (L.isTable(-1)) { + // Update __cpp_ptr inside the table + L.push(newPtr); + L.setField(-2, "__cpp_ptr"); + // Store at new key + L.push(newPtr); + L.copy(-2); + L.rawSet(LUA_REGISTRYINDEX); + // Remove old key + L.push(oldPtr); + L.push(); // nil + L.rawSet(LUA_REGISTRYINDEX); + } + L.pop(); + + // Re-key the methods table: registry[oldPtr+1] -> registry[newPtr+1] + L.push(oldPtr + 1); + L.rawGet(LUA_REGISTRYINDEX); + if (L.isTable(-1)) { + L.push(newPtr + 1); + L.copy(-2); + L.rawSet(LUA_REGISTRYINDEX); + L.push(oldPtr + 1); + L.push(); + L.rawSet(LUA_REGISTRYINDEX); + } + L.pop(); + } +} + void psxsplash::Lua::PushGameObject(GameObject* go) { auto L = m_state; L.push(go); diff --git a/src/lua.h b/src/lua.h index 90a8aab..0c6a831 100644 --- a/src/lua.h +++ b/src/lua.h @@ -49,6 +49,7 @@ class Lua { void LoadLuaFile(const char* code, size_t len, int index); void RegisterSceneScripts(int index); void RegisterGameObject(GameObject* go); + void RelocateGameObjects(GameObject** objects, size_t count, intptr_t delta); // Get the underlying psyqo::Lua state for API registration psyqo::Lua& getState() { return m_state; } diff --git a/src/luaapi.cpp b/src/luaapi.cpp index fc8ec85..f81dc21 100644 --- a/src/luaapi.cpp +++ b/src/luaapi.cpp @@ -1179,31 +1179,38 @@ 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); - + int soundId = -1; - + // Accept number (index) or string (name lookup) like Entity.Find // 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("[Lua] Audio.Play: clip '%s' not found\n", name); + 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; } - + 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; } diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index 90b90d9..c765dff 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -231,6 +231,7 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc L.RegisterGameObject(object); } + m_controls.forceAnalogMode(); m_controls.Init(); Renderer::GetInstance().SetCamera(m_currentCamera); @@ -815,6 +816,17 @@ 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()), + m_gameObjects.size(), delta); + } + FileLoader::Get().FreeFile(oldBase); m_currentSceneData = newBase; }