From 889fd886f0fd43f47fc26c352d79917f1094949f Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Fri, 7 Jul 2023 11:00:48 -0700 Subject: [PATCH] MxSemphore + MxThread + MxThread implementions (#80) * Add MxSemphore + MxThread and the two implementations I could find of MxThread (consumers extend it and override the Run method). * Implement a function in MxDiskStreamProvider which uses thread and semaphore to confirm correct layout / size of those classes. * All 100% match except two functions with a pair of registers swapped. --- CMakeLists.txt | 2 + LEGO1/mxdiskstreamprovider.cpp | 31 +++++++++++ LEGO1/mxdiskstreamprovider.h | 33 ++++++++++++ LEGO1/mxsemaphore.cpp | 29 ++++++++++ LEGO1/mxsemaphore.h | 27 ++++++++++ LEGO1/mxstreamprovider.h | 5 ++ LEGO1/mxthread.cpp | 99 ++++++++++++++++++++++++++++++++++ LEGO1/mxthread.h | 57 ++++++++++++++++++++ LEGO1/mxtimer.h | 2 +- 9 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 LEGO1/mxsemaphore.cpp create mode 100644 LEGO1/mxsemaphore.h create mode 100644 LEGO1/mxthread.cpp create mode 100644 LEGO1/mxthread.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c4e11d1..ca7f5679 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ add_library(lego1 SHARED LEGO1/mxpalette.cpp LEGO1/mxpresenter.cpp LEGO1/mxscheduler.cpp + LEGO1/mxsemaphore.cpp LEGO1/mxsmkpresenter.cpp LEGO1/mxsoundmanager.cpp LEGO1/mxsoundpresenter.cpp @@ -143,6 +144,7 @@ add_library(lego1 SHARED LEGO1/mxstreamer.cpp LEGO1/mxstring.cpp LEGO1/mxstringvariable.cpp + LEGO1/mxthread.cpp LEGO1/mxtimer.cpp LEGO1/mxtransitionmanager.cpp LEGO1/mxunknown100dc6b0.cpp diff --git a/LEGO1/mxdiskstreamprovider.cpp b/LEGO1/mxdiskstreamprovider.cpp index 5e59efee..38d5cddc 100644 --- a/LEGO1/mxdiskstreamprovider.cpp +++ b/LEGO1/mxdiskstreamprovider.cpp @@ -1,5 +1,17 @@ #include "mxdiskstreamprovider.h" +#include "mxthread.h" + +// OFFSET: LEGO1 0x100d0f30 +MxResult MxDiskStreamProviderThread::Run() +{ + if (m_target != NULL) + m_target->WaitForWorkToComplete(); + MxThread::Run(); + // They should probably have writen "return MxThread::Run()" but they didn't. + return SUCCESS; +} + // OFFSET: LEGO1 0x100d0f70 MxDiskStreamProvider::MxDiskStreamProvider() { @@ -11,3 +23,22 @@ MxDiskStreamProvider::~MxDiskStreamProvider() { // TODO } + +// Matching but with esi / edi swapped +// OFFSET: LEGO1 0x100d1750 +MxResult MxDiskStreamProvider::WaitForWorkToComplete() +{ + while (m_remainingWork != 0) + { + m_busySemaphore.Wait(INFINITE); + if (m_unk1 != 0) + PerformWork(); + } + return SUCCESS; +} + +// OFFSET: LEGO1 0x100d1760 STUB +void MxDiskStreamProvider::PerformWork() +{ + // TODO +} \ No newline at end of file diff --git a/LEGO1/mxdiskstreamprovider.h b/LEGO1/mxdiskstreamprovider.h index 198b6062..58679055 100644 --- a/LEGO1/mxdiskstreamprovider.h +++ b/LEGO1/mxdiskstreamprovider.h @@ -2,6 +2,25 @@ #define MXDISKSTREAMPROVIDER_H #include "mxstreamprovider.h" +#include "mxthread.h" +#include "mxcriticalsection.h" + +class MxDiskStreamProvider; + +// VTABLE 0x100dd130 +class MxDiskStreamProviderThread : public MxThread +{ +public: + // Only inlined, no offset + inline MxDiskStreamProviderThread() + : MxThread() + , m_target(NULL) {} + + MxResult Run() override; + +private: + MxDiskStreamProvider *m_target; +}; // VTABLE 0x100dd138 class MxDiskStreamProvider : public MxStreamProvider @@ -23,6 +42,20 @@ class MxDiskStreamProvider : public MxStreamProvider { return !strcmp(name, MxDiskStreamProvider::ClassName()) || MxStreamProvider::IsA(name); } + + MxResult WaitForWorkToComplete(); + + void PerformWork(); + +private: + MxDiskStreamProviderThread m_thread; + MxSemaphore m_busySemaphore; + byte m_remainingWork; + byte m_unk1; + MxCriticalSection m_criticalSection; + byte unk2[4]; + void* unk3; + void *unk4; }; #endif // MXDISKSTREAMPROVIDER_H diff --git a/LEGO1/mxsemaphore.cpp b/LEGO1/mxsemaphore.cpp new file mode 100644 index 00000000..889a06e8 --- /dev/null +++ b/LEGO1/mxsemaphore.cpp @@ -0,0 +1,29 @@ + +#include "mxsemaphore.h" + +// OFFSET: LEGO1 0x100c87d0 +MxSemaphore::MxSemaphore() +{ + m_hSemaphore = NULL; +} + +// OFFSET: LEGO1 0x100c8800 +MxResult MxSemaphore::Init(MxU32 p_initialCount, MxU32 p_maxCount) +{ + MxResult result = FAILURE; + if (m_hSemaphore = CreateSemaphoreA(NULL, p_initialCount, p_maxCount, NULL)) + result = SUCCESS; + return result; +} + +// OFFSET: LEGO1 0x100c8830 +void MxSemaphore::Wait(MxU32 p_timeoutMS) +{ + WaitForSingleObject(m_hSemaphore, p_timeoutMS); +} + +// OFFSET: LEGO1 0x100c8850 +void MxSemaphore::Release(MxU32 p_releaseCount) +{ + ReleaseSemaphore(m_hSemaphore, p_releaseCount, NULL); +} \ No newline at end of file diff --git a/LEGO1/mxsemaphore.h b/LEGO1/mxsemaphore.h new file mode 100644 index 00000000..521678ac --- /dev/null +++ b/LEGO1/mxsemaphore.h @@ -0,0 +1,27 @@ +#ifndef MX_SEMAPHORE_H +#define MX_SEMAPHORE_H + +#include "mxtypes.h" +#include + +class MxSemaphore +{ +public: + MxSemaphore(); + + // Inlined only, no offset + ~MxSemaphore() + { + CloseHandle(m_hSemaphore); + } + + virtual MxResult Init(MxU32 p_initialCount, MxU32 p_maxCount); + + void Wait(MxU32 p_timeoutMS); + void Release(MxU32 p_releaseCount); + +private: + HANDLE m_hSemaphore; +}; + +#endif // MX_SEMAPHORE_H \ No newline at end of file diff --git a/LEGO1/mxstreamprovider.h b/LEGO1/mxstreamprovider.h index 796d0ed3..b70b6446 100644 --- a/LEGO1/mxstreamprovider.h +++ b/LEGO1/mxstreamprovider.h @@ -2,6 +2,7 @@ #define MXSTREAMPROVIDER_H #include "mxcore.h" +#include "mxdsfile.h" // VTABLE 0x100dd100 class MxStreamProvider : public MxCore @@ -18,6 +19,10 @@ class MxStreamProvider : public MxCore { return !strcmp(name, MxStreamProvider::ClassName()) || MxCore::IsA(name); } + +private: + void *m_pLookup; + MxDSFile* m_pFile; }; #endif // MXSTREAMPROVIDER_H diff --git a/LEGO1/mxthread.cpp b/LEGO1/mxthread.cpp new file mode 100644 index 00000000..b63019ed --- /dev/null +++ b/LEGO1/mxthread.cpp @@ -0,0 +1,99 @@ + +#include "mxthread.h" + +#include + +#include "mxomni.h" + +// OFFSET: LEGO1 0x100bf690 +MxResult MxThread::Run() +{ + m_semaphore.Release(1); + return SUCCESS; +} + +// OFFSET: LEGO1 0x100bf510 +MxThread::MxThread() +{ + m_hThread = NULL; + m_running = TRUE; + m_threadId = 0; +} + +// OFFSET: LEGO1 0x100bf5a0 +MxThread::~MxThread() +{ + if (m_hThread) + CloseHandle((HANDLE)m_hThread); +} + +typedef unsigned(__stdcall *ThreadFunc)(void *); + +// OFFSET: LEGO1 0x100bf610 +MxResult MxThread::Start(int p_stack, int p_flag) +{ + MxResult result = FAILURE; + if (m_semaphore.Init(0, 1) == SUCCESS) + { + if (m_hThread = _beginthreadex(NULL, p_stack << 2, (ThreadFunc)&MxThread::ThreadProc, this, p_flag, &m_threadId)) + result = SUCCESS; + } + return result; +} + +// OFFSET: LEGO1 0x100bf670 +void MxThread::Terminate() +{ + m_running = FALSE; + m_semaphore.Wait(INFINITE); +} + +// OFFSET: LEGO1 0x100bf680 +unsigned MxThread::ThreadProc(void *p_thread) +{ + return static_cast(p_thread)->Run(); +} + +// OFFSET: LEGO1 0x100bf660 +void MxThread::Sleep(MxS32 p_milliseconds) +{ + ::Sleep(p_milliseconds); +} + +// OFFSET: LEGO1 0x100b8bb0 +MxTickleThread::MxTickleThread(MxCore *p_target, int p_frequencyMS) +{ + m_target = p_target; + m_frequencyMS = p_frequencyMS; +} + +// OFFSET: LEGO1 0x100d0f50 +MxResult MxTickleThread::StartWithTarget(MxCore* p_target) +{ + m_target = p_target; + return Start(0x1000, 0); +} + +// Match except for register allocation +// OFFSET: LEGO1 0x100b8c90 +MxResult MxTickleThread::Run() +{ + MxTimer* timer = Timer(); + int lastTickled = -m_frequencyMS; + while (IsRunning()) + { + int currentTime = timer->GetTime(); + + if (currentTime < lastTickled) { + lastTickled = -m_frequencyMS; + } + int timeRemainingMS = (m_frequencyMS - currentTime) + lastTickled; + if (timeRemainingMS <= 0) { + m_target->Tickle(); + timeRemainingMS = 0; + lastTickled = currentTime; + } + Sleep(timeRemainingMS); + } + return MxThread::Run(); +} \ No newline at end of file diff --git a/LEGO1/mxthread.h b/LEGO1/mxthread.h new file mode 100644 index 00000000..6ac96b59 --- /dev/null +++ b/LEGO1/mxthread.h @@ -0,0 +1,57 @@ +#ifndef MXTHREAD_H +#define MXTHREAD_H + +#include "mxtypes.h" +#include "mxsemaphore.h" + +class MxCore; + +class MxThread +{ +public: + // Note: Comes before virtual destructor + virtual MxResult Run(); + + MxResult Start(int p_stack, int p_flag); + + void Terminate(); + + void Sleep(MxS32 p_milliseconds); + + // Inferred, not in DLL + inline MxBool IsRunning() { return m_running; } + +protected: + MxThread(); + virtual ~MxThread(); + +private: + static unsigned ThreadProc(void *p_thread); + + MxULong m_hThread; + MxU32 m_threadId; + MxBool m_running; + MxSemaphore m_semaphore; +}; + +class MxTickleThread : public MxThread +{ +public: + MxTickleThread(MxCore *p_target, int p_frequencyMS); + + // Unclear at this time whether this function and the m_target field are + // actually a general "userdata" pointer in the base MxThread, but it seems + // like the only usage is with an MxTickleThread. + MxResult StartWithTarget(MxCore* p_target); + + // Only inlined, no offset + virtual ~MxTickleThread() {} + + MxResult Run() override; + +private: + MxCore *m_target; + MxS32 m_frequencyMS; +}; + +#endif // MXTHREAD_H \ No newline at end of file diff --git a/LEGO1/mxtimer.h b/LEGO1/mxtimer.h index 1714e8e1..357cf410 100644 --- a/LEGO1/mxtimer.h +++ b/LEGO1/mxtimer.h @@ -18,7 +18,7 @@ class MxTimer : public MxCore inline MxLong GetTime() { if (this->m_isRunning) - return s_LastTimeCalculated; + return s_LastTimeTimerStarted; else return s_LastTimeCalculated - this->m_startTime; }