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.
This commit is contained in:
Mark Langen 2023-07-07 11:00:48 -07:00 committed by GitHub
parent f8fe635248
commit 889fd886f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 284 additions and 1 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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

29
LEGO1/mxsemaphore.cpp Normal file
View file

@ -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);
}

27
LEGO1/mxsemaphore.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef MX_SEMAPHORE_H
#define MX_SEMAPHORE_H
#include "mxtypes.h"
#include <windows.h>
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

View file

@ -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

99
LEGO1/mxthread.cpp Normal file
View file

@ -0,0 +1,99 @@
#include "mxthread.h"
#include <process.h>
#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<MxThread*>(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();
}

57
LEGO1/mxthread.h Normal file
View file

@ -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

View file

@ -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;
}