diff --git a/Makefile b/Makefile index 6601abd..bdbeef8 100644 --- a/Makefile +++ b/Makefile @@ -42,5 +42,20 @@ ifeq ($(FPSOVERLAY), 1) CPPFLAGS += -DPSXSPLASH_FPSOVERLAY endif +ifdef OT_SIZE +CPPFLAGS += -DOT_SIZE=$(OT_SIZE) +endif +ifdef BUMP_SIZE +CPPFLAGS += -DBUMP_SIZE=$(BUMP_SIZE) +endif + include third_party/nugget/psyqo-lua/psyqo-lua.mk include third_party/nugget/psyqo/psyqo.mk + +# Redirect Lua's allocator through our OOM-guarded wrapper +LDFLAGS := $(subst psyqo_realloc,lua_oom_realloc,$(LDFLAGS)) + +# NOPARSER=1 → Use precompiled bytecode, strip Lua parser from runtime (~25KB savings) +ifeq ($(NOPARSER),1) +LIBRARIES := $(subst liblua.a,liblua-noparser.a,$(LIBRARIES)) +endif diff --git a/src/cdromhelper.hh b/src/cdromhelper.hh new file mode 100644 index 0000000..0a55331 --- /dev/null +++ b/src/cdromhelper.hh @@ -0,0 +1,89 @@ +#pragma once + +// CDRomHelper - manages CD-ROM interrupt lifecycle around psyqo's CDRomDevice. +// +// psyqo's IRQ handler asserts if an interrupt fires with no action +// registered (m_action != nullptr check in cdrom-device.cpp:72). +// After readSectorsBlocking returns, IMask is re-enabled but no action +// is pending. We must mask before any unsolicited interrupt arrives. +// +// We mask at BOTH levels: +// - CPU level (IMask): prevents psyqo's handler from firing +// - Controller level (CauseMask=0): prevents the controller from +// generating or queueing responses, keeping its state clean +// +// The motor keeps spinning after Pause (psyqo's read action ends with +// CdlPause). This is normal PS1 behavior - PSBW and other homebrew +// leave the motor spinning too. We don't send CdlStop because handling +// its response correctly while bypassing psyqo's handler is unreliable. + +#if defined(LOADER_CDROM) + +#include +#include + +namespace psxsplash { + +class CDRomHelper { + public: + // Call immediately after the last readSectorsBlocking returns. + // Masks interrupts at both CPU and controller level. + static void SilenceDrive() { + // Mask CPU-level IRQ so psyqo's handler can't fire. + psyqo::Hardware::CPU::IMask.clear(psyqo::Hardware::CPU::IRQ::CDRom); + psyqo::Hardware::CPU::flushWriteQueue(); + + // Mask controller-level interrupts. With CauseMask=0 the + // controller won't assert the interrupt line or queue + // responses. This keeps the controller in a clean state + // for the next load, unlike masking only IMask (which + // caused unacknowledged responses to pile up and eventually + // lock the controller). + psyqo::Hardware::CDRom::CauseMask = 0; + + s_silenced = true; + } + + // Call before the next file load. Restores the controller to a + // clean state ready for psyqo's CDRomDevice to use. + // No-op on first scene load (nothing to restore). + static void WakeDrive() { + if (!s_silenced) return; + s_silenced = false; + + // Drain any residual Cause bits while CauseMask is still 0. + drainController(); + + // Re-enable controller-level interrupts (INT1-5). + psyqo::Hardware::CDRom::CauseMask = 0x1f; + + // Drain again: restoring CauseMask may have caused a + // pending state to be signaled. + drainController(); + + // Re-enable CPU-level IRQ for psyqo's handler. + psyqo::Hardware::CPU::IMask.set(psyqo::Hardware::CPU::IRQ::CDRom); + } + + private: + static inline bool s_silenced = false; + + // Acknowledge and drain any pending interrupt from the CD-ROM + // controller. Clears Cause bits, drains the response FIFO, and + // clears the CPU IReg flag. + static void drainController() { + uint8_t cause = psyqo::Hardware::CDRom::Cause; + if (cause & 7) + psyqo::Hardware::CDRom::Cause = 7; + if (cause & 0x18) + psyqo::Hardware::CDRom::Cause = 0x18; + while (psyqo::Hardware::CDRom::Ctrl.access() & 0x20) + psyqo::Hardware::CDRom::Response; // drain FIFO + psyqo::Hardware::CPU::IReg.clear( + psyqo::Hardware::CPU::IRQ::CDRom); + } +}; + +} // namespace psxsplash + +#endif // LOADER_CDROM diff --git a/src/gameobject_bytecode.h b/src/gameobject_bytecode.h new file mode 100644 index 0000000..5857ffe --- /dev/null +++ b/src/gameobject_bytecode.h @@ -0,0 +1,140 @@ +#pragma once + +// Pre-compiled PS1 Lua bytecode for GAMEOBJECT_SCRIPT +// Generated by luac_psx - do not edit manually +// 1581 bytes + +static const unsigned char GAMEOBJECT_BYTECODE[] = { + 0x1b, 0x4c, 0x75, 0x61, 0x52, 0x00, 0x01, 0x04, 0x04, 0x04, 0x04, 0x01, + 0x19, 0x93, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, + 0x00, 0x1f, 0x00, 0x00, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x08, 0x11, 0x00, 0x00, 0x00, 0x47, 0x00, 0x40, 0x00, + 0x87, 0x40, 0x40, 0x00, 0xc7, 0x80, 0x40, 0x00, 0x07, 0xc1, 0x40, 0x00, + 0x47, 0x01, 0x41, 0x00, 0x87, 0x41, 0x41, 0x00, 0x0a, 0x80, 0x41, 0x80, + 0x0a, 0x80, 0xc1, 0x80, 0x0a, 0x80, 0x41, 0x81, 0x0a, 0x80, 0xc1, 0x81, + 0x0a, 0x80, 0x41, 0x82, 0x0a, 0x80, 0xc1, 0x82, 0xe5, 0x01, 0x00, 0x00, + 0x0a, 0xc0, 0x81, 0x83, 0xe5, 0x41, 0x00, 0x00, 0x0a, 0xc0, 0x01, 0x84, + 0x1f, 0x00, 0x80, 0x00, 0x09, 0x00, 0x00, 0x00, 0x04, 0x0d, 0x00, 0x00, + 0x00, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x00, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x04, 0x0b, 0x00, 0x00, + 0x00, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, + 0x04, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x00, 0x04, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, + 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x04, + 0x0e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, + 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x04, 0x0b, 0x00, 0x00, + 0x00, 0x5f, 0x5f, 0x6e, 0x65, 0x77, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x05, 0x1e, 0x00, 0x00, 0x00, 0x86, 0x00, 0x40, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x9d, 0x80, 0x80, 0x01, 0x58, + 0x40, 0x40, 0x01, 0x17, 0x00, 0x00, 0x80, 0x9f, 0x00, 0x00, 0x01, 0x18, + 0x80, 0xc0, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5, 0x00, 0x80, 0x00, 0x07, + 0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x03, 0x80, 0x18, 0x00, 0xc1, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5, + 0x00, 0x00, 0x01, 0x07, 0xc1, 0x40, 0x00, 0xde, 0x00, 0x00, 0x01, 0xdf, + 0x00, 0x00, 0x00, 0x17, 0x40, 0x01, 0x80, 0x18, 0x40, 0xc1, 0x00, 0x17, + 0xc0, 0x00, 0x80, 0xc5, 0x00, 0x80, 0x01, 0x07, 0xc1, 0x40, 0x00, 0xde, + 0x00, 0x00, 0x01, 0xdf, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xdf, + 0x00, 0x00, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, + 0x07, 0x00, 0x00, 0x00, 0x72, 0x61, 0x77, 0x67, 0x65, 0x74, 0x00, 0x00, + 0x04, 0x09, 0x00, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x5f, 0x5f, 0x63, 0x70, 0x70, + 0x5f, 0x70, 0x74, 0x72, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x03, 0x01, 0x05, + 0x0f, 0x00, 0x00, 0x00, 0x67, 0x61, 0x6d, 0x65, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x2e, 0x6c, 0x75, 0x61, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12, + 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x73, 0x65, 0x6c, 0x66, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x6b, 0x65, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x72, 0x61, 0x77, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x5f, 0x45, 0x4e, 0x56, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, + 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x0b, 0x00, + 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x00, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x1d, 0x00, 0x00, 0x00, 0x18, 0x00, + 0xc0, 0x00, 0x17, 0x40, 0x01, 0x80, 0xc5, 0x00, 0x00, 0x00, 0x07, 0x41, + 0x40, 0x00, 0x40, 0x01, 0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f, 0x00, + 0x80, 0x00, 0x17, 0x80, 0x03, 0x80, 0x18, 0x80, 0xc0, 0x00, 0x17, 0x40, + 0x01, 0x80, 0xc5, 0x00, 0x80, 0x00, 0x07, 0x41, 0x40, 0x00, 0x40, 0x01, + 0x00, 0x01, 0xdd, 0x40, 0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0x17, 0x80, + 0x01, 0x80, 0x18, 0xc0, 0xc0, 0x00, 0x17, 0x00, 0x01, 0x80, 0xc5, 0x00, + 0x00, 0x01, 0x07, 0x41, 0x40, 0x00, 0x40, 0x01, 0x00, 0x01, 0xdd, 0x40, + 0x80, 0x01, 0x1f, 0x00, 0x80, 0x00, 0xc6, 0x00, 0xc1, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x40, 0x01, 0x80, 0x00, 0x80, 0x01, 0x00, 0x01, 0xdd, 0x40, + 0x00, 0x02, 0x1f, 0x00, 0x80, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x09, + 0x00, 0x00, 0x00, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, + 0x04, 0x0a, 0x00, 0x00, 0x00, 0x5f, 0x5f, 0x63, 0x70, 0x70, 0x5f, 0x70, + 0x74, 0x72, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x00, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00, 0x72, + 0x61, 0x77, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x01, 0x04, 0x01, 0x06, 0x00, 0x00, 0x0f, 0x00, + 0x00, 0x00, 0x67, 0x61, 0x6d, 0x65, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x2e, 0x6c, 0x75, 0x61, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, + 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, + 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, + 0x00, 0x22, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, + 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, + 0x00, 0x24, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, + 0x00, 0x25, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, + 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0x29, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x73, 0x65, 0x6c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6b, 0x65, 0x79, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x0b, 0x00, 0x00, + 0x00, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, 0x05, 0x00, 0x00, 0x00, 0x5f, 0x45, + 0x4e, 0x56, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0x00, 0x67, 0x61, 0x6d, 0x65, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x6c, 0x75, 0x61, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x6d, 0x65, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x67, 0x65, + 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x73, + 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x73, + 0x65, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x67, 0x65, + 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x59, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x74, 0x5f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x59, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x5f, 0x45, 0x4e, 0x56, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x67, 0x61, 0x6d, + 0x65, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x6c, 0x75, 0x61, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x5f, 0x45, 0x4e, 0x56, 0x00, +}; diff --git a/src/lua.cpp b/src/lua.cpp index 21714e0..dd556c8 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -2,56 +2,27 @@ #include +#include #include #include #include #include "gameobject.hh" -constexpr const char GAMEOBJECT_SCRIPT[] = R"( -return function(metatable) - local get_position = metatable.get_position - local set_position = metatable.set_position - local get_active = metatable.get_active - local set_active = metatable.set_active - local get_rotationY = metatable.get_rotationY - local set_rotationY = metatable.set_rotationY +// OOM-guarded allocator for Lua. The linker redirects luaI_realloc +// here instead of straight to psyqo_realloc, so we can log before +// returning NULL. +extern "C" void *lua_oom_realloc(void *ptr, size_t size) { + void *result = psyqo_realloc(ptr, size); + if (!result && size > 0) { + printf("Lua OOM: alloc %u bytes failed\n", (unsigned)size); + } + return result; +} - metatable.get_position = nil - metatable.set_position = nil - metatable.get_active = nil - metatable.set_active = nil - metatable.get_rotationY = nil - metatable.set_rotationY = nil - - function metatable.__index(self, key) - local raw = rawget(self, key) - if raw ~= nil then return raw end - if key == "position" then - return get_position(self.__cpp_ptr) - elseif key == "active" then - return get_active(self.__cpp_ptr) - elseif key == "rotationY" then - return get_rotationY(self.__cpp_ptr) - end - return nil - end - - function metatable.__newindex(self, key, value) - if key == "position" then - set_position(self.__cpp_ptr, value) - return - elseif key == "active" then - set_active(self.__cpp_ptr, value) - return - elseif key == "rotationY" then - set_rotationY(self.__cpp_ptr, value) - return - end - rawset(self, key, value) - end -end -)"; +// Pre-compiled PS1 Lua bytecode for the GameObject metatable script. +// Compiled with luac_psx to avoid needing the Lua parser at runtime. +#include "gameobject_bytecode.h" // Lua helpers @@ -149,7 +120,7 @@ static int gameobjectSetRotationY(psyqo::Lua L) { void psxsplash::Lua::Init() { auto L = m_state; // Load and run the game objects script - if (L.loadBuffer(GAMEOBJECT_SCRIPT, "buffer:gameObjects") == 0) { + if (L.loadBuffer(reinterpret_cast(GAMEOBJECT_BYTECODE), sizeof(GAMEOBJECT_BYTECODE), "bytecode:gameObjects") == 0) { if (L.pcall(0, 1) == 0) { // This will be our metatable L.newTable(); diff --git a/src/renderer.hh b/src/renderer.hh index 3e86d50..8663842 100644 --- a/src/renderer.hh +++ b/src/renderer.hh @@ -37,8 +37,14 @@ class Renderer final { Renderer(const Renderer&) = delete; Renderer& operator=(const Renderer&) = delete; - static constexpr size_t ORDERING_TABLE_SIZE = 2048 * 8; - static constexpr size_t BUMP_ALLOCATOR_SIZE = 8096 * 24; +#ifndef OT_SIZE +#define OT_SIZE (2048 * 8) +#endif +#ifndef BUMP_SIZE +#define BUMP_SIZE (8096 * 24) +#endif + static constexpr size_t ORDERING_TABLE_SIZE = OT_SIZE; + static constexpr size_t BUMP_ALLOCATOR_SIZE = BUMP_SIZE; static constexpr size_t MAX_VISIBLE_TRIANGLES = 4096; static constexpr int32_t PROJ_H = 120; diff --git a/src/scenemanager.cpp b/src/scenemanager.cpp index 0fbf70a..5000bc5 100644 --- a/src/scenemanager.cpp +++ b/src/scenemanager.cpp @@ -13,6 +13,10 @@ #include #include +#if defined(LOADER_CDROM) +#include "cdromhelper.hh" +#endif + #include "lua.h" using namespace psyqo::trig_literals; @@ -632,6 +636,11 @@ void psxsplash::SceneManager::processPendingSceneLoad() { } void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool isFirstScene) { + // Restore CD-ROM controller and CPU IRQ state for file loading. +#if defined(LOADER_CDROM) + CDRomHelper::WakeDrive(); +#endif + // Build filename using the active backend's naming convention char filename[32]; FileLoader::BuildSceneFilename(sceneIndex, filename, sizeof(filename)); @@ -698,6 +707,11 @@ void psxsplash::SceneManager::loadScene(psyqo::GPU& gpu, int sceneIndex, bool is if (loading.isActive()) loading.updateProgress(gpu, 30); + // Stop the CD-ROM motor and mask all interrupts for gameplay. +#if defined(LOADER_CDROM) + CDRomHelper::SilenceDrive(); +#endif + m_currentSceneData = newData; m_currentSceneIndex = sceneIndex; diff --git a/tools/luac_psx/Makefile b/tools/luac_psx/Makefile new file mode 100644 index 0000000..fe02031 --- /dev/null +++ b/tools/luac_psx/Makefile @@ -0,0 +1,34 @@ +# luac_psx - PS1 Lua bytecode compiler +# +# Minimal PS1 executable that compiles Lua source to bytecode. +# Links only psyqo (allocator, xprintf) and psxlua (full parser). +# No psxsplash code, no renderer, no GPU. + +TARGET = luac_psx +TYPE = ps-exe + +SRCS = main.c + +# Relative path to nugget root +NUGGETDIR = ../../third_party/nugget + +# Lua library (full parser variant - NOT noparser) +LUADIR = $(NUGGETDIR)/third_party/psxlua/src +LIBRARIES += $(LUADIR)/liblua.a + +# Include paths +CPPFLAGS += -I$(LUADIR) +CPPFLAGS += -I$(NUGGETDIR) +CPPFLAGS += -DLUA_TARGET_PSX + +# psyqo build system (provides libpsyqo.a, linker scripts, toolchain config) +include $(NUGGETDIR)/psyqo/psyqo.mk + +# Build liblua.a if not already built +$(LUADIR)/liblua.a: + $(MAKE) -C $(NUGGETDIR)/third_party/psxlua psx + +clean:: + $(MAKE) -C $(NUGGETDIR)/third_party/psxlua clean + +.PHONY: $(LUADIR)/liblua.a diff --git a/tools/luac_psx/main.c b/tools/luac_psx/main.c new file mode 100644 index 0000000..32e3531 --- /dev/null +++ b/tools/luac_psx/main.c @@ -0,0 +1,324 @@ +/* + * luac_psx - PS1 Lua bytecode compiler + * + * A minimal PS1 executable that reads Lua source files via PCdrv, + * compiles them using psxlua's compiler, and writes bytecode back + * via PCdrv. Designed to run inside PCSX-Redux headless mode as + * a build tool. + * + * Links only psyqo (allocator) and psxlua (full parser). No game + * code, no renderer, no GPU. + */ + +#include +#include +#include + +#include "pcdrv.h" + +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + +/* Internal headers for luaU_dump (supports strip parameter) */ +#include "lobject.h" +#include "lstate.h" +#include "lundump.h" + +/* psyqo's xprintf provides sprintf/printf via PS1 syscalls */ +#include + +/* psyqo allocator - provides psyqo_realloc and psyqo_free */ +#include + +/* + * Lua runtime support functions. + * + * psxlua declares these as extern in llibc.h when LUA_TARGET_PSX + * is defined. Normally they're provided via linker --defsym redirects + * to psyqo functions, but we define them directly here. + */ +int luaI_sprintf(char *str, const char *format, ...) { + va_list ap; + va_start(ap, format); + int ret = vsprintf(str, format, ap); + va_end(ap); + return ret; +} + +void luaI_free(void *ptr) { + psyqo_free(ptr); +} + +void *luaI_realloc(void *ptr, size_t size) { + return psyqo_realloc(ptr, size); +} + +/* Maximum source file size: 256KB should be plenty */ +#define MAX_SOURCE_SIZE (256 * 1024) + +/* Maximum bytecode output size */ +#define MAX_OUTPUT_SIZE (256 * 1024) + +/* Maximum manifest line length */ +#define MAX_LINE_LEN 256 + +/* Bytecode writer state */ +typedef struct { + uint8_t *buf; + size_t size; + size_t capacity; +} WriterState; + +/* lua_dump writer callback - accumulates bytecode into a buffer */ +static int bytecode_writer(lua_State *L, const void *p, size_t sz, void *ud) { + WriterState *ws = (WriterState *)ud; + if (ws->size + sz > ws->capacity) { + printf("ERROR: bytecode output exceeds buffer capacity\n"); + return 1; + } + /* memcpy via byte loop - no libc on PS1 */ + const uint8_t *src = (const uint8_t *)p; + uint8_t *dst = ws->buf + ws->size; + for (size_t i = 0; i < sz; i++) { + dst[i] = src[i]; + } + ws->size += sz; + return 0; +} + +/* Read an entire file via PCdrv into a buffer. Returns bytes read, or -1 on error. */ +static int read_file(const char *path, char *buf, int max_size) { + int fd = PCopen(path, 0, 0); /* O_RDONLY = 0 */ + if (fd < 0) { + printf("ERROR: cannot open '%s'\n", path); + return -1; + } + + int total = 0; + while (total < max_size) { + int chunk = max_size - total; + if (chunk > 2048) chunk = 2048; /* read in 2KB chunks */ + int n = PCread(fd, buf + total, chunk); + if (n <= 0) break; + total += n; + } + + PCclose(fd); + return total; +} + +/* Write a buffer to a file via PCdrv. Returns 0 on success, -1 on error. */ +static int write_file(const char *path, const void *buf, int size) { + int fd = PCcreat(path, 0); + if (fd < 0) { + printf("ERROR: cannot create '%s'\n", path); + return -1; + } + + const uint8_t *p = (const uint8_t *)buf; + int remaining = size; + while (remaining > 0) { + int chunk = remaining; + if (chunk > 2048) chunk = 2048; /* write in 2KB chunks */ + int n = PCwrite(fd, p, chunk); + if (n < 0) { + printf("ERROR: write failed for '%s'\n", path); + PCclose(fd); + return -1; + } + p += n; + remaining -= n; + } + + PCclose(fd); + return 0; +} + +/* Simple strlen - no libc on PS1 */ +static int str_len(const char *s) { + int n = 0; + while (s[n]) n++; + return n; +} + +/* Error log buffer - accumulates error messages for the sentinel file */ +static char error_log[4096]; +static int error_log_len = 0; + +static void error_log_append(const char *msg) { + int len = str_len(msg); + if (error_log_len + len + 1 < (int)sizeof(error_log)) { + const char *src = msg; + char *dst = error_log + error_log_len; + while (*src) *dst++ = *src++; + *dst++ = '\n'; + *dst = '\0'; + error_log_len += len + 1; + } +} + +/* Write the sentinel file to signal completion */ +static void write_sentinel(const char *status) { + /* For errors, write the error log as sentinel content */ + if (str_len(status) == 5 && status[0] == 'E') { + /* "ERROR" - write error details */ + if (error_log_len > 0) + write_file("__done__", error_log, error_log_len); + else + write_file("__done__", status, str_len(status)); + } else { + write_file("__done__", status, str_len(status)); + } +} + +/* Parse the next line from the manifest buffer. Returns line length, or -1 at end. */ +static int next_line(const char *buf, int buf_len, int *pos, char *line, int max_line) { + if (*pos >= buf_len) return -1; + + int i = 0; + while (*pos < buf_len && i < max_line - 1) { + char c = buf[*pos]; + (*pos)++; + if (c == '\n') break; + if (c == '\r') continue; /* skip CR */ + line[i++] = c; + } + line[i] = '\0'; + return i; +} + +/* Compile a single Lua source file to bytecode */ +static int compile_file(const char *input_path, const char *output_path, + char *source_buf, uint8_t *output_buf) { + printf(" %s -> %s\n", input_path, output_path); + + /* Read source */ + int source_len = read_file(input_path, source_buf, MAX_SOURCE_SIZE); + if (source_len < 0) { + error_log_append("ERROR: cannot open source file"); + error_log_append(input_path); + return -1; + } + + /* Create a fresh Lua state for each file to avoid accumulating memory */ + lua_State *L = luaL_newstate(); + if (!L) { + printf("ERROR: cannot create Lua state (out of memory)\n"); + error_log_append("ERROR: cannot create Lua state (out of memory)"); + return -1; + } + + /* Compile source to bytecode */ + int status = luaL_loadbuffer(L, source_buf, source_len, input_path); + if (status != LUA_OK) { + const char *err = lua_tostring(L, -1); + if (err) { + printf("ERROR: %s\n", err); + error_log_append(err); + } else { + printf("ERROR: compilation failed for '%s'\n", input_path); + error_log_append("ERROR: compilation failed"); + error_log_append(input_path); + } + lua_close(L); + return -1; + } + + /* Dump bytecode (strip debug info to save space) */ + WriterState ws; + ws.buf = output_buf; + ws.size = 0; + ws.capacity = MAX_OUTPUT_SIZE; + + /* Use luaU_dump directly with strip=1 to remove debug info (line numbers, + * local variable names, source filename). Saves significant space. */ + status = luaU_dump(L, getproto(L->top - 1), bytecode_writer, &ws, 1); + lua_close(L); + + if (status != 0) { + printf("ERROR: bytecode dump failed for '%s'\n", input_path); + return -1; + } + + /* Write bytecode to output file */ + if (write_file(output_path, ws.buf, ws.size) != 0) { + return -1; + } + + printf(" OK (%d bytes source -> %d bytes bytecode)\n", source_len, (int)ws.size); + return 0; +} + +int main(void) { + /* Initialize PCdrv */ + PCinit(); + + printf("luac_psx: PS1 Lua bytecode compiler\n"); + + /* Allocate work buffers */ + char *source_buf = (char *)psyqo_realloc(NULL, MAX_SOURCE_SIZE); + uint8_t *output_buf = (uint8_t *)psyqo_realloc(NULL, MAX_OUTPUT_SIZE); + char *manifest_buf = (char *)psyqo_realloc(NULL, MAX_SOURCE_SIZE); + + if (!source_buf || !output_buf || !manifest_buf) { + printf("ERROR: cannot allocate work buffers\n"); + write_sentinel("ERROR"); + while (1) {} + } + + /* Read manifest file */ + int manifest_len = read_file("manifest.txt", manifest_buf, MAX_SOURCE_SIZE); + if (manifest_len < 0) { + printf("ERROR: cannot read manifest.txt\n"); + write_sentinel("ERROR"); + while (1) {} + } + + /* Process manifest: pairs of lines (input, output) */ + int pos = 0; + int file_count = 0; + int error_count = 0; + char input_path[MAX_LINE_LEN]; + char output_path[MAX_LINE_LEN]; + + while (1) { + /* Read input path */ + int len = next_line(manifest_buf, manifest_len, &pos, input_path, MAX_LINE_LEN); + if (len < 0) break; + if (len == 0) continue; /* skip blank lines */ + + /* Read output path */ + len = next_line(manifest_buf, manifest_len, &pos, output_path, MAX_LINE_LEN); + if (len <= 0) { + printf("ERROR: manifest has unpaired entry for '%s'\n", input_path); + error_count++; + break; + } + + /* Compile */ + if (compile_file(input_path, output_path, source_buf, output_buf) != 0) { + error_count++; + break; /* stop on first error */ + } + file_count++; + } + + /* Clean up */ + psyqo_free(source_buf); + psyqo_free(output_buf); + psyqo_free(manifest_buf); + + /* Write sentinel */ + if (error_count > 0) { + printf("FAILED: %d file(s) compiled, %d error(s)\n", file_count, error_count); + write_sentinel("ERROR"); + } else { + printf("SUCCESS: %d file(s) compiled\n", file_count); + write_sentinel("OK"); + } + + /* Halt - PCSX-Redux will kill us */ + while (1) {} + return 0; +} diff --git a/tools/luac_psx/pcdrv.h b/tools/luac_psx/pcdrv.h new file mode 100644 index 0000000..72878b5 --- /dev/null +++ b/tools/luac_psx/pcdrv.h @@ -0,0 +1,99 @@ +/* + +MIT License + +Copyright (c) 2021 PCSX-Redux authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#pragma once + +static inline int PCinit() { + register int r asm("v0"); + __asm__ volatile("break 0, 0x101\n" : "=r"(r)); + return r; +} + +static inline int PCcreat(const char *name, int perms) { + register const char *a0 asm("a0") = name; + register const char *a1 asm("a1") = name; + register int a2 asm("a2") = 0; + register int v0 asm("v0"); + register int v1 asm("v1"); + __asm__ volatile("break 0, 0x102\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2)); + if (v0 == 0) return v1; + return -1; +} + +static inline int PCopen(const char *name, int flags, int perms) { + register int a2 asm("a2") = flags; + register const char *a0 asm("a0") = name; + register const char *a1 asm("a1") = name; + register int v0 asm("v0"); + register int v1 asm("v1"); + __asm__ volatile("break 0, 0x103\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2)); + if (v0 == 0) return v1; + return -1; +} + +static inline int PCclose(int fd) { + register int a0 asm("a0") = fd; + register int a1 asm("a1") = fd; + register int v0 asm("v0"); + __asm__ volatile("break 0, 0x104\n" : "=r"(v0) : "r"(a0), "r"(a1) : "v1"); + return v0; +} + +static inline int PCread(int fd, void *buf, int len) { + register int a0 asm("a0") = 0; + register int a1 asm("a1") = fd; + register int a2 asm("a2") = len; + register void *a3 asm("a3") = buf; + register int v0 asm("v0"); + register int v1 asm("v1"); + __asm__ volatile("break 0, 0x105\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2), "r"(a3) : "memory"); + if (v0 == 0) return v1; + return -1; +} + +static inline int PCwrite(int fd, const void *buf, int len) { + register int a0 asm("a0") = 0; + register int a1 asm("a1") = fd; + register int a2 asm("a2") = len; + register const void *a3 asm("a3") = buf; + register int v0 asm("v0"); + register int v1 asm("v1"); + __asm__ volatile("break 0, 0x106\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2), "r"(a3)); + if (v0 == 0) return v1; + return -1; +} + +static inline int PClseek(int fd, int offset, int wheel) { + register int a3 asm("a3") = wheel; + register int a2 asm("a2") = offset; + register int a0 asm("a0") = fd; + register int a1 asm("a1") = fd; + register int v0 asm("v0"); + register int v1 asm("v1"); + __asm__ volatile("break 0, 0x107\n" : "=r"(v0), "=r"(v1) : "r"(a0), "r"(a1), "r"(a2), "r"(a3)); + if (v0 == 0) return v1; + return -1; +}