diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bbc6d50..af1dac3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,8 +45,8 @@ jobs: - name: Setup cmake uses: jwlawson/actions-setup-cmake@v1.13 with: - # Use 2.8 for maximum backwards compatibility - cmake-version: '2.8.x' + # Use minimum supported version + cmake-version: '3.0.x' - name: Build shell: cmd @@ -112,8 +112,8 @@ jobs: curl -fLSs -o ISLEPROGRESS-OLD.TXT https://github.com/isledecomp/isle/releases/download/continuous/ISLEPROGRESS.TXT curl -fLSs -o LEGO1PROGRESS-OLD.TXT https://github.com/isledecomp/isle/releases/download/continuous/LEGO1PROGRESS.TXT - diff -u ISLEPROGRESS-OLD.TXT ISLEPROGRESS.TXT || true - diff -u LEGO1PROGRESS-OLD.TXT LEGO1PROGRESS.TXT || true + diff -u0 ISLEPROGRESS-OLD.TXT ISLEPROGRESS.TXT || true + diff -u0 LEGO1PROGRESS-OLD.TXT LEGO1PROGRESS.TXT || true - name: Test Exports shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index cdfebd05..6067d1f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8...3.5 FATAL_ERROR) +cmake_minimum_required(VERSION 3.0...3.5 FATAL_ERROR) project(isle CXX) @@ -80,6 +80,7 @@ add_library(lego1 SHARED LEGO1/legoroi.cpp LEGO1/legosoundmanager.cpp LEGO1/legostate.cpp + LEGO1/legostream.cpp LEGO1/legotexturepresenter.cpp LEGO1/legoutil.cpp LEGO1/legovideomanager.cpp @@ -135,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 @@ -142,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/legostream.cpp b/LEGO1/legostream.cpp new file mode 100644 index 00000000..1fd2bf11 --- /dev/null +++ b/LEGO1/legostream.cpp @@ -0,0 +1,149 @@ + +#include "legostream.h" + +#include +#include + +// Very likely but not certain sizes. +// The classes are only used on the stack in functions we have not 100% matched +// yet, we can confirm the size once we have. +DECOMP_SIZE_ASSERT(LegoStream, 0x8); +DECOMP_SIZE_ASSERT(LegoFileStream, 0xC); +DECOMP_SIZE_ASSERT(LegoMemoryStream, 0x10); + +// OFFSET: LEGO1 0x10045ae0 +MxBool LegoStream::IsWriteMode() +{ + return m_mode == LEGOSTREAM_MODE_WRITE; +} + +// OFFSET: LEGO1 0x10045af0 +MxBool LegoStream::IsReadMode() +{ + return m_mode == LEGOSTREAM_MODE_READ; +} + +// OFFSET: LEGO1 0x100991c0 +LegoFileStream::LegoFileStream() + : LegoStream() +{ + m_hFile = NULL; +} + +// OFFSET: LEGO1 0x10099250 +LegoFileStream::~LegoFileStream() +{ + if (m_hFile != NULL) + fclose(m_hFile); +} + +// OFFSET: LEGO1 0x100992c0 +MxResult LegoFileStream::Read(char* p_buffer, MxU32 p_size) +{ + if (m_hFile == NULL) + return FAILURE; + + return (fread(p_buffer, 1, p_size, m_hFile) == p_size) ? SUCCESS : FAILURE; +} + +// OFFSET: LEGO1 0x10099300 +MxResult LegoFileStream::Write(char* p_buffer, MxU32 p_size) +{ + if (m_hFile == NULL) + return FAILURE; + + return (fwrite(p_buffer, 1, p_size, m_hFile) == p_size) ? SUCCESS : FAILURE; +} + +// OFFSET: LEGO1 0x10099340 +MxResult LegoFileStream::Tell(MxU32* p_offset) +{ + if (m_hFile == NULL) + return FAILURE; + + int got = ftell(m_hFile); + if (got == -1) + return FAILURE; + + *p_offset = got; + return SUCCESS; +} + +// OFFSET: LEGO1 0x10099370 +MxResult LegoFileStream::Seek(MxU32 p_offset) +{ + if (m_hFile == NULL) + return FAILURE; + + return (fseek(m_hFile, p_offset, 0) == 0) ? SUCCESS : FAILURE; +} + +// OFFSET: LEGO1 0x100993a0 +MxResult LegoFileStream::Open(const char* p_filename, OpenFlags p_mode) +{ + char modeString[4]; + + if (m_hFile != NULL) + fclose(m_hFile); + + modeString[0] = '\0'; + if (p_mode & ReadBit) + { + m_mode = LEGOSTREAM_MODE_READ; + strcat(modeString, "r"); + } + + if (p_mode & WriteBit) + { + if (m_mode != LEGOSTREAM_MODE_READ) + m_mode = LEGOSTREAM_MODE_WRITE; + strcat(modeString, "w"); + } + + if ((p_mode & 4) != 0) + strcat(modeString, "b"); + else + strcat(modeString, "t"); + + return (m_hFile = fopen(p_filename, modeString)) ? SUCCESS : FAILURE; +} + +// OFFSET: LEGO1 0x10099080 +LegoMemoryStream::LegoMemoryStream(char* p_buffer) + : LegoStream() +{ + m_buffer = p_buffer; + m_offset = 0; +} + +// OFFSET: LEGO1 0x10099160 +MxResult LegoMemoryStream::Read(char* p_buffer, MxU32 p_size) +{ + memcpy(p_buffer, m_buffer + m_offset, p_size); + m_offset += p_size; + return SUCCESS; +} + +// OFFSET: LEGO1 0x10099190 +MxResult LegoMemoryStream::Write(char* p_buffer, MxU32 p_size) +{ + memcpy(m_buffer + m_offset, p_buffer, p_size); + m_offset += p_size; + return SUCCESS; +} + +// OFFSET: LEGO1 0x100994a0 +MxResult LegoMemoryStream::Tell(MxU32* p_offset) +{ + *p_offset = m_offset; + return SUCCESS; +} + +// OFFSET: LEGO1 0x100994b0 +MxResult LegoMemoryStream::Seek(MxU32 p_offset) +{ + m_offset = p_offset; + return SUCCESS; +} + + diff --git a/LEGO1/legostream.h b/LEGO1/legostream.h new file mode 100644 index 00000000..a505cd54 --- /dev/null +++ b/LEGO1/legostream.h @@ -0,0 +1,73 @@ +#ifndef LEGOSTREAM_H +#define LEGOSTREAM_H + +#include "decomp.h" +#include "mxtypes.h" + +#include + +#define LEGOSTREAM_MODE_READ 1 +#define LEGOSTREAM_MODE_WRITE 2 + +// VTABLE 0x100d7d80 +class LegoStream +{ +public: + LegoStream() : m_mode(0) {} + inline virtual ~LegoStream() {}; + + virtual MxResult Read(char* p_buffer, MxU32 p_size) = 0; + virtual MxResult Write(char* p_buffer, MxU32 p_size) = 0; + virtual MxResult Tell(MxU32* p_offset) = 0; + virtual MxResult Seek(MxU32 p_offset) = 0; + + virtual MxBool IsWriteMode(); + virtual MxBool IsReadMode(); + + enum OpenFlags + { + ReadBit = 1, + WriteBit = 2, + BinaryBit = 4, + }; + +protected: + MxU8 m_mode; +}; + +// VTABLE 0x100db730 +class LegoFileStream : public LegoStream +{ +public: + LegoFileStream(); + virtual ~LegoFileStream(); + + MxResult Read(char* p_buffer, MxU32 p_size) override; + MxResult Write(char* p_buffer, MxU32 p_size) override; + MxResult Tell(MxU32* p_offset) override; + MxResult Seek(MxU32 p_offset) override; + + MxResult Open(const char* p_filename, OpenFlags p_mode); + +private: + FILE *m_hFile; +}; + +// VTABLE 0x100db710 +class LegoMemoryStream : public LegoStream +{ +public: + LegoMemoryStream(char* p_buffer); + ~LegoMemoryStream() {} + + MxResult Read(char* p_buffer, MxU32 p_size) override; + MxResult Write(char* p_buffer, MxU32 p_size) override; + MxResult Tell(MxU32* p_offset) override; + MxResult Seek(MxU32 p_offset) override; + +private: + char *m_buffer; + MxU32 m_offset; +}; + +#endif // LEGOSTREAM_H \ No newline at end of file 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; }