From 2ee4dc0f83c38df14115f5e800f1de91ef3ec926 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Mon, 8 Oct 2018 20:22:23 +0200 Subject: [PATCH] Implement all TitanEngine functionality (finally) --- .gitignore | 3 +- GleeBug/Debugger.Loop.Exception.cpp | 40 +- GleeBug/Debugger.Loop.Process.cpp | 6 - GleeBug/Debugger.Loop.Thread.cpp | 4 - GleeBug/Debugger.Loop.cpp | 14 +- GleeBug/Debugger.Process.cpp | 3 +- GleeBug/Debugger.Process.h | 19 - .../Debugger.Thread.HardwareBreakpoint.cpp | 5 + GleeBug/Debugger.Thread.Registers.GetSet.cpp | 22 +- GleeBug/Debugger.Thread.Registers.Register.h | 2 + GleeBug/Debugger.Thread.Registers.cpp | 69 +- GleeBug/Debugger.Thread.Registers.h | 96 +-- GleeBug/Debugger.Thread.cpp | 25 +- GleeBug/Debugger.Thread.h | 17 - GleeBug/Debugger.cpp | 14 +- GleeBug/Debugger.h | 8 +- GleeBug/GleeBug.vcxproj | 4 +- MyDebugger/MyDebugger.h | 56 +- MyDebugger/MyDebugger.vcxproj | 4 +- MyDebugger/main.cpp | 4 +- TitanEngineEmulator/Emulator.h | 629 +++++++----------- TitanEngineEmulator/Global.Engine.Context.cpp | 482 ++++++++++++++ TitanEngineEmulator/Global.Engine.Context.h | 33 + TitanEngineEmulator/Hider.h | 203 ++++++ .../LibraryLoader/x32/LibraryLoader.exe | Bin 0 -> 37624 bytes .../LibraryLoader/x64/LibraryLoader.exe | Bin 0 -> 42744 bytes TitanEngineEmulator/TitanEngine.rc | Bin 0 -> 3994 bytes TitanEngineEmulator/TitanEngineEmulator.cpp | 11 + .../TitanEngineEmulator.vcxproj | 11 +- .../TitanEngineEmulator.vcxproj.filters | 20 + TitanEngineEmulator/resource.h | 15 + 31 files changed, 1120 insertions(+), 699 deletions(-) create mode 100644 TitanEngineEmulator/Global.Engine.Context.cpp create mode 100644 TitanEngineEmulator/Global.Engine.Context.h create mode 100644 TitanEngineEmulator/Hider.h create mode 100644 TitanEngineEmulator/LibraryLoader/x32/LibraryLoader.exe create mode 100644 TitanEngineEmulator/LibraryLoader/x64/LibraryLoader.exe create mode 100644 TitanEngineEmulator/TitanEngine.rc create mode 100644 TitanEngineEmulator/resource.h diff --git a/.gitignore b/.gitignore index 342392a..a6d29ef 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ Release/ *.vcxproj.user docs/ .vs/ -*.VC.db \ No newline at end of file +*.VC.db +*.aps diff --git a/GleeBug/Debugger.Loop.Exception.cpp b/GleeBug/Debugger.Loop.Exception.cpp index a9ca4b2..16b8468 100644 --- a/GleeBug/Debugger.Loop.Exception.cpp +++ b/GleeBug/Debugger.Loop.Exception.cpp @@ -1,4 +1,5 @@ #include "Debugger.h" +#include "Debugger.Thread.Registers.h" namespace GleeBug { @@ -48,7 +49,7 @@ namespace GleeBug mContinueStatus = DBG_CONTINUE; //set back the instruction pointer - mRegisters->Gip = info.address; + Registers(mThread->hThread, CONTEXT_CONTROL).Gip = info.address; //restore the original breakpoint byte and do an internal step mProcess->MemWriteUnsafe(info.address, info.internal.software.oldbytes, info.internal.software.size); @@ -107,27 +108,28 @@ namespace GleeBug void Debugger::exceptionHardwareBreakpoint(ptr exceptionAddress) { //determine the hardware breakpoint triggered - ptr dr6 = mRegisters->Dr6(); + Registers registers(mThread->hThread, CONTEXT_DEBUG_REGISTERS); + ptr dr6 = registers.Dr6(); HardwareSlot breakpointSlot; ptr breakpointAddress; - if (exceptionAddress == mRegisters->Dr0() || dr6 & 0x1) + if (exceptionAddress == registers.Dr0() || dr6 & 0x1) { - breakpointAddress = mRegisters->Dr0(); + breakpointAddress = registers.Dr0(); breakpointSlot = HardwareSlot::Dr0; } - else if (exceptionAddress == mRegisters->Dr1() || dr6 & 0x2) + else if (exceptionAddress == registers.Dr1() || dr6 & 0x2) { - breakpointAddress = mRegisters->Dr1(); + breakpointAddress = registers.Dr1(); breakpointSlot = HardwareSlot::Dr1; } - else if (exceptionAddress == mRegisters->Dr2() || dr6 & 0x4) + else if (exceptionAddress == registers.Dr2() || dr6 & 0x4) { - breakpointAddress = mRegisters->Dr2(); + breakpointAddress = registers.Dr2(); breakpointSlot = HardwareSlot::Dr2; } - else if (exceptionAddress == mRegisters->Dr3() || dr6 & 0x8) + else if (exceptionAddress == registers.Dr3() || dr6 & 0x8) { - breakpointAddress = mRegisters->Dr3(); + breakpointAddress = registers.Dr3(); breakpointSlot = HardwareSlot::Dr3; } else @@ -192,7 +194,7 @@ namespace GleeBug //We restore the protection if (!mProcess->MemProtect(foundPage->first, PAGE_SIZE, foundPage->second.OldProtect)) { - sprintf_s(error, "MemProtect failed on 0x%p", foundPage->first); + sprintf_s(error, "MemProtect failed on 0x%p", (void*)foundPage->first); cbInternalError(error); } @@ -225,7 +227,7 @@ namespace GleeBug auto foundInfo = mProcess->breakpoints.find({ BreakpointType::Memory, foundRange->first }); if (foundInfo == mProcess->breakpoints.end()) { - sprintf_s(error, "inconsistent memory breakpoint at 0x%p", exceptionAddress); + sprintf_s(error, "inconsistent memory breakpoint at 0x%p", (void*)exceptionAddress); cbInternalError(error); return; } @@ -243,7 +245,7 @@ namespace GleeBug if (bpxPage == mProcess->memoryBreakpointPages.end()) { - sprintf_s(error, "Process::memoryBreakPointPages data structure is incosistent, should dump page at 0x%p", exceptionAddress & ~(PAGE_SIZE - 1)); + sprintf_s(error, "Process::memoryBreakPointPages data structure is incosistent, should dump page at 0x%p", (void*)(exceptionAddress & ~(PAGE_SIZE - 1))); cbInternalError(error); return; } @@ -260,7 +262,7 @@ namespace GleeBug //We restore the protection if (!mProcess->MemProtect(pageAddr, PAGE_SIZE, pageProperties.OldProtect)) { - sprintf_s(error, "MemProtect failed on 0x%p", pageAddr); + sprintf_s(error, "MemProtect failed on 0x%p", (void*)pageAddr); cbInternalError(error); } @@ -323,7 +325,7 @@ namespace GleeBug //FIXED: if (!mProcess->MemProtect(pageAddr, PAGE_SIZE, pageProperties.OldProtect)) { - sprintf_s(error, "MemProtect failed on 0x%p", pageAddr); + sprintf_s(error, "MemProtect failed on 0x%p", (void*)pageAddr); cbInternalError(error); } //Pass info as well @@ -384,7 +386,7 @@ namespace GleeBug //We restore the protection if (!mProcess->MemProtect(foundPage->first, PAGE_SIZE, foundPage->second.OldProtect)) { - sprintf_s(error, "MemProtect failed on 0x%p", foundPage->first); + sprintf_s(error, "MemProtect failed on 0x%p", (void*)foundPage->first); cbInternalError(error); } @@ -417,7 +419,7 @@ namespace GleeBug auto foundInfo = mProcess->breakpoints.find({ BreakpointType::Memory, foundRange->first }); if (foundInfo == mProcess->breakpoints.end()) { - sprintf_s(error, "inconsistent memory breakpoint at 0x%p", exceptionAddress); + sprintf_s(error, "inconsistent memory breakpoint at 0x%p", (void*)exceptionAddress); cbInternalError(error); return; } @@ -435,7 +437,7 @@ namespace GleeBug if (bpxPage == mProcess->memoryBreakpointPages.end()) { - sprintf_s(error, "Process::memoryBreakPointPages data structure is incosistent, should dump page at 0x%p", exceptionAddress & ~(PAGE_SIZE - 1)); + sprintf_s(error, "Process::memoryBreakPointPages data structure is incosistent, should dump page at 0x%p", (void*)(exceptionAddress & ~(PAGE_SIZE - 1))); cbInternalError(error); return; } @@ -480,7 +482,7 @@ namespace GleeBug //FIXED: if (!mProcess->MemProtect(pageAddr, PAGE_SIZE, pageProperties.OldProtect)) { - sprintf_s(error, "MemProtect failed on 0x%p", pageAddr); + sprintf_s(error, "MemProtect failed on 0x%p", (void*)pageAddr); cbInternalError(error); } //Pass info as well diff --git a/GleeBug/Debugger.Loop.Process.cpp b/GleeBug/Debugger.Loop.Process.cpp index cb37192..0183fcc 100644 --- a/GleeBug/Debugger.Loop.Process.cpp +++ b/GleeBug/Debugger.Loop.Process.cpp @@ -29,11 +29,6 @@ namespace GleeBug createProcess.lpThreadLocalBase, createProcess.lpStartAddress) }); mThread = mProcess->thread = mProcess->threads.find(mDebugEvent.dwThreadId)->second.get(); - mRegisters = &mThread->registers; - - //read thread context from main thread - if (!mThread->RegReadContext()) - cbInternalError("Thread::RegReadContext() failed!"); //call the debug event callback cbCreateProcessEvent(createProcess, *mProcess); @@ -58,6 +53,5 @@ namespace GleeBug //set the current process mProcess = nullptr; mThread = nullptr; - mRegisters = nullptr; } }; \ No newline at end of file diff --git a/GleeBug/Debugger.Loop.Thread.cpp b/GleeBug/Debugger.Loop.Thread.cpp index 7c6f9ec..dd2e216 100644 --- a/GleeBug/Debugger.Loop.Thread.cpp +++ b/GleeBug/Debugger.Loop.Thread.cpp @@ -13,9 +13,6 @@ namespace GleeBug //set the current thread mThread = mProcess->thread = mProcess->threads.find(mDebugEvent.dwThreadId)->second.get(); - mRegisters = &mThread->registers; - if (!mThread->RegReadContext()) - cbInternalError("Thread::RegReadContext() failed!"); //call the debug event callback cbCreateThreadEvent(createThread, *mThread); @@ -31,6 +28,5 @@ namespace GleeBug //set the current thread mThread = mProcess->thread = nullptr; - mRegisters = nullptr; } }; \ No newline at end of file diff --git a/GleeBug/Debugger.Loop.cpp b/GleeBug/Debugger.Loop.cpp index 863e11e..0a3ad19 100644 --- a/GleeBug/Debugger.Loop.cpp +++ b/GleeBug/Debugger.Loop.cpp @@ -1,4 +1,5 @@ #include "Debugger.h" +#include "Debugger.Thread.Registers.h" namespace GleeBug { @@ -43,17 +44,14 @@ namespace GleeBug if (threadFound != mProcess->threads.end()) { mThread = mProcess->thread = threadFound->second.get(); - mRegisters = &mThread->registers; } else { mThread = mProcess->thread = nullptr; - mRegisters = nullptr; } } else { - mRegisters = nullptr; mThread = nullptr; if (mProcess) { @@ -62,10 +60,6 @@ namespace GleeBug } } - //read register contexts - if(mProcess && !mProcess->RegReadContext()) - cbInternalError("Process::RegReadContext() failed!"); - //call the pre debug event callback cbPreDebugEvent(mDebugEvent); @@ -119,13 +113,9 @@ namespace GleeBug if (mDetach && mThread) { if (mThread->isInternalStepping || mThread->isSingleStepping) - mThread->registers.TrapFlag = false; + Registers(mThread->hThread, CONTEXT_CONTROL).TrapFlag = false; } - //write register contexts - if(mProcess && !mProcess->RegWriteContext()) - cbInternalError("Process::RegWriteContext() failed!"); - //continue the debug event if (!ContinueDebugEvent(mDebugEvent.dwProcessId, mDebugEvent.dwThreadId, mContinueStatus)) break; diff --git a/GleeBug/Debugger.Process.cpp b/GleeBug/Debugger.Process.cpp index d263dbd..743b129 100644 --- a/GleeBug/Debugger.Process.cpp +++ b/GleeBug/Debugger.Process.cpp @@ -1,4 +1,5 @@ #include "Debugger.Process.h" +#include "Debugger.Thread.Registers.h" #define ZYDIS_EXPORTS #define ZYDIS_ENABLE_FEATURE_IMPLICITLY_USED_REGISTERS @@ -22,7 +23,7 @@ namespace GleeBug void Process::StepOver(const StepCallback & cbStep) { - auto gip = thread->registers.Gip(); + auto gip = Registers(thread->hThread, CONTEXT_CONTROL).Gip(); unsigned char data[16]; if (MemReadSafe(gip, data, sizeof(data))) { diff --git a/GleeBug/Debugger.Process.h b/GleeBug/Debugger.Process.h index d62abc9..48d38b0 100644 --- a/GleeBug/Debugger.Process.h +++ b/GleeBug/Debugger.Process.h @@ -396,25 +396,6 @@ namespace GleeBug static_cast(static_cast(debugger)); StepOver(std::bind(callback, debugger)); } - - bool RegReadContext() - { - //TODO: lazily retrieve the context - auto result = true; - for(auto & thread : this->threads) - if(!thread.second->RegReadContext()) - result = false; - return result; - } - - bool RegWriteContext() - { - auto result = true; - for(auto & thread : this->threads) - if(!thread.second->RegWriteContext()) - result = false; - return result; - } }; }; diff --git a/GleeBug/Debugger.Thread.HardwareBreakpoint.cpp b/GleeBug/Debugger.Thread.HardwareBreakpoint.cpp index c3aaa08..f443618 100644 --- a/GleeBug/Debugger.Thread.HardwareBreakpoint.cpp +++ b/GleeBug/Debugger.Thread.HardwareBreakpoint.cpp @@ -1,4 +1,5 @@ #include "Debugger.Thread.h" +#include "Debugger.Thread.Registers.h" #define BITSET(a,x) (a|=1<mContext, 0, sizeof(CONTEXT)); - InitializeCriticalSection(&mCr); - } - - LockedPtr Registers::GetContext() - { - handleLazyContext(); - return LockedPtr(&mCr, &mContext); - } - - /*void Registers::SetContext(const CONTEXT & context) - { - handleLazyContext(); - this->mContext = context; - }*/ - - void Registers::setContextLazy(CONTEXT* oldContext, HANDLE hThread) - { - this->mLazyOldContext = oldContext; - this->mLazyThread = hThread; - this->mLazySet = true; - } - - bool Registers::handleLazyContext() - { - ScopedCriticalSection lock(&mCr); - - if(!this->mLazySet) - return true; - - if(!this->mLazyOldContext || !this->mLazyThread) //assert - __debugbreak(); - - auto oldContext = this->mLazyOldContext; - auto lazyThread = this->mLazyThread; - - this->mLazyOldContext = nullptr; - this->mLazyThread = nullptr; - this->mLazySet = false; - - //TODO: handle failure of GetThreadContext - auto result = false; - if(GetThreadContext(lazyThread, oldContext)) + memset(&mContext, 0, sizeof(CONTEXT)); + mContext.ContextFlags = ContextFlags; + if (!!GetThreadContext(hThread, &mContext)) { - this->mContext = *oldContext; - result = true; + this->hThread = hThread; + memcpy(&mOldContext, &mContext, sizeof(CONTEXT)); } - - return result; + else + { + this->hThread = nullptr; + } + } + + Registers::~Registers() + { + if (hThread && memcmp(&mContext, &mOldContext, sizeof(CONTEXT)) != 0) + SetThreadContext(hThread, &mContext); + } + + PCONTEXT Registers::GetContext() + { + return &mContext; } }; \ No newline at end of file diff --git a/GleeBug/Debugger.Thread.Registers.h b/GleeBug/Debugger.Thread.Registers.h index ca9a7c3..4fd81c5 100644 --- a/GleeBug/Debugger.Thread.Registers.h +++ b/GleeBug/Debugger.Thread.Registers.h @@ -5,63 +5,6 @@ namespace GleeBug { - class ScopedCriticalSection - { - PCRITICAL_SECTION cr; - - public: - ScopedCriticalSection(PCRITICAL_SECTION cr) - : cr(cr) - { - EnterCriticalSection(cr); - } - - ~ScopedCriticalSection() - { - LeaveCriticalSection(cr); - } - }; - - template - class LockedPtr - { - PCRITICAL_SECTION locker; - T* ptr; - - public: - explicit LockedPtr(PCRITICAL_SECTION locker, T* ptr) - : locker(locker), ptr(ptr) - { - EnterCriticalSection(locker); - } - - ~LockedPtr() - { - LeaveCriticalSection(locker); - } - - LockedPtr(const LockedPtr &) = delete; - - LockedPtr &operator=(const LockedPtr &) = delete; - - LockedPtr(LockedPtr && other) - : locker(other.locker), ptr(other.ptr) - { - other.locker = nullptr; - other.ptr = nullptr; - } - - /*operator T*() const - { - return ptr; - }*/ - - T* operator->() const - { - return ptr; - } - }; - /** \brief Thread register context. */ @@ -71,15 +14,10 @@ namespace GleeBug friend class Thread; public: - /** - \brief Default constructor. - */ - Registers(); - - /** - \brief Copy constructor. - */ + Registers(HANDLE hThread, DWORD ContextFlags = CONTEXT_ALL); + ~Registers(); Registers(const Registers &) = delete; + Registers(const Registers &&) = delete; #include "Debugger.Thread.Registers.Register.h" @@ -219,34 +157,12 @@ namespace GleeBug \brief Gets a pointer to the context object. \return This function will never return a nullptr. */ - LockedPtr GetContext(); - - /** - \brief Sets the CONTEXT. - \param context The context to set. - */ - //void SetContext(const CONTEXT & context); + PCONTEXT GetContext(); private: + HANDLE hThread; CONTEXT mContext; - CRITICAL_SECTION mCr; - - LPCONTEXT mLazyOldContext = nullptr; - HANDLE mLazyThread = nullptr; - bool mLazySet = false; - - /** - \brief Lazily set CONTEXT. This will only actually retrieve the context if a function in this thread is called. - \param oldContext Pointer to the old context, used to determine if updates are required. - \param hThread Handle of the thread to get the context from if required. - */ - void setContextLazy(CONTEXT* oldContext, HANDLE hThread); - - /** - \brief Retrieve the actual context if setContextLazy has been called. - \return Whether retrieving the actual context was successful. - */ - bool handleLazyContext(); + CONTEXT mOldContext; void* getPtr(R reg); }; diff --git a/GleeBug/Debugger.Thread.cpp b/GleeBug/Debugger.Thread.cpp index 1285328..072aefa 100644 --- a/GleeBug/Debugger.Thread.cpp +++ b/GleeBug/Debugger.Thread.cpp @@ -1,4 +1,5 @@ #include "Debugger.Thread.h" +#include "Debugger.Thread.Registers.h" namespace GleeBug { @@ -13,29 +14,9 @@ namespace GleeBug { } - bool Thread::RegReadContext() - { - memset(&this->mOldContext, 0, sizeof(CONTEXT)); - this->mOldContext.ContextFlags = CONTEXT_ALL; //TODO: granular control over what's required - this->registers.setContextLazy(&this->mOldContext, this->hThread); - return true; - } - - bool Thread::RegWriteContext() - { - //check if something actually changed - if (this->registers.mLazySet || memcmp(&this->mOldContext, &this->registers.mContext, sizeof(CONTEXT)) == 0) - return true; - //update the context - if(SetThreadContext(this->hThread, &this->registers.mContext)) - return true; - __debugbreak(); - return false; - } - void Thread::StepInto() { - registers.TrapFlag.Set(); + Registers(hThread).TrapFlag.Set(); isSingleStepping = true; } @@ -57,7 +38,7 @@ namespace GleeBug void Thread::StepInternal(const StepCallback & cbStep) { - registers.TrapFlag.Set(); + Registers(hThread).TrapFlag.Set(); isInternalStepping = true; cbInternalStep = cbStep; } diff --git a/GleeBug/Debugger.Thread.h b/GleeBug/Debugger.Thread.h index c45e0fb..54e405e 100644 --- a/GleeBug/Debugger.Thread.h +++ b/GleeBug/Debugger.Thread.h @@ -2,7 +2,6 @@ #define DEBUGGER_THREAD_H #include "Debugger.Global.h" -#include "Debugger.Thread.Registers.h" #include "Debugger.Breakpoint.h" namespace GleeBug @@ -18,7 +17,6 @@ namespace GleeBug ptr lpThreadLocalBase; ptr lpStartAddress; - Registers registers; StepCallbackVector stepCallbacks; bool isSingleStepping; bool isInternalStepping; @@ -38,18 +36,6 @@ namespace GleeBug */ Thread(const Thread & other) = delete; - /** - \brief Read the register context from the thread. This fills the RegistersInfo member. - \return true if it succeeds, false if it fails. - */ - bool RegReadContext(); - - /** - \brief Write the register context to the thread. This does nothing if the registers did not change. - \return true if it succeeds, false if it fails. - */ - bool RegWriteContext(); - /** \brief Step into. */ @@ -121,9 +107,6 @@ namespace GleeBug \return true if it succeeds, false if it fails. */ bool Resume(); - - private: - CONTEXT mOldContext; }; }; diff --git a/GleeBug/Debugger.cpp b/GleeBug/Debugger.cpp index 229da83..d98e87d 100644 --- a/GleeBug/Debugger.cpp +++ b/GleeBug/Debugger.cpp @@ -1,4 +1,5 @@ #include "Debugger.h" +#include "Debugger.Thread.Registers.h" namespace GleeBug { @@ -14,7 +15,8 @@ namespace GleeBug bool Debugger::Init(const wchar_t* szFilePath, const wchar_t* szCommandLine, const wchar_t* szCurrentDirectory, - bool newConsole) + bool newConsole, + bool startSuspended) { memset(&mMainStartupInfo, 0, sizeof(mMainStartupInfo)); memset(&mMainProcess, 0, sizeof(mMainProcess)); @@ -40,7 +42,7 @@ namespace GleeBug nullptr, nullptr, FALSE, - DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS | (newConsole ? CREATE_NEW_CONSOLE : 0), + DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS | (newConsole ? CREATE_NEW_CONSOLE : 0) | (startSuspended ? CREATE_SUSPENDED : 0), nullptr, szCurrentDirectory, &mMainStartupInfo, @@ -72,8 +74,7 @@ namespace GleeBug bool Debugger::UnsafeDetach() { - mRegisters->TrapFlag = false; - mThread->RegWriteContext(); + Registers(mThread->hThread, CONTEXT_CONTROL).TrapFlag = false; return !!DebugActiveProcessStop(mMainProcess.dwProcessId); } @@ -85,12 +86,11 @@ namespace GleeBug bool Debugger::UnsafeDetachAndBreak() { - if (!mProcess || !mThread || !mRegisters) //fail when there is no process or thread currently specified + if (!mProcess || !mThread) //fail when there is no process or thread currently specified return false; //trigger an EXCEPTION_SINGLE_STEP in the debuggee - mRegisters->TrapFlag = true; - mThread->RegWriteContext(); + Registers(mThread->hThread, CONTEXT_CONTROL).TrapFlag = true; //detach from the process return UnsafeDetach(); diff --git a/GleeBug/Debugger.h b/GleeBug/Debugger.h index 552b8fb..8eddc32 100644 --- a/GleeBug/Debugger.h +++ b/GleeBug/Debugger.h @@ -39,7 +39,8 @@ namespace GleeBug bool Init(const wchar_t* szFilePath, const wchar_t* szCommandLine, const wchar_t* szCurrentDirectory, - bool newConsole = true); + bool newConsole = true, + bool startSuspended = false); /** \brief Attach to a debuggee. @@ -301,11 +302,6 @@ namespace GleeBug \brief The current thread (can be null in some cases). Should be a copy of mProcess->thread. */ Thread* mThread = nullptr; - - /** - \brief The current thread registers (can be null in some cases). Should be a copy of mThread->registers. - */ - Registers* mRegisters = nullptr; }; }; diff --git a/GleeBug/GleeBug.vcxproj b/GleeBug/GleeBug.vcxproj index 57c6ec2..de60782 100644 --- a/GleeBug/GleeBug.vcxproj +++ b/GleeBug/GleeBug.vcxproj @@ -120,7 +120,7 @@ true true true - MultiThreadedDLL + MultiThreaded true @@ -138,7 +138,7 @@ true true true - MultiThreadedDLL + MultiThreaded true diff --git a/MyDebugger/MyDebugger.h b/MyDebugger/MyDebugger.h index 80b777d..23a9994 100644 --- a/MyDebugger/MyDebugger.h +++ b/MyDebugger/MyDebugger.h @@ -2,6 +2,7 @@ #define MYDEBUGGER_H #include +#include using namespace GleeBug; @@ -11,36 +12,36 @@ protected: void cbMemoryBreakpoint2(const BreakpointInfo & info) { printf("Reached memory breakpoint#2! GIP: 0x%p\n", - mRegisters->Gip()); + (void*)Registers(mThread->hThread).Gip()); } void cbMemoryBreakpoint(const BreakpointInfo & info) { unsigned char dataToExec[4]; const char tmp[] = "aaaa"; - + Registers registers(mThread->hThread); printf("Reached memory breakpoint! GIP: 0x%p\n", - mRegisters->Gip()); + (void*)registers.Gip()); - mProcess->MemReadUnsafe(mRegisters->Gip(), dataToExec, 4); + mProcess->MemReadUnsafe(registers.Gip(), dataToExec, 4); printf("\n What are my bytes? I am so lost.. Dump: "); for (int i = 0; i < 4; i++) { printf("%02X ", dataToExec[i]); } - mProcess->DeleteMemoryBreakpoint(mRegisters->Gip()); + mProcess->DeleteMemoryBreakpoint(registers.Gip()); memcpy(dataToExec, tmp, 4); - mProcess->MemReadUnsafe(mRegisters->Gip(), dataToExec, 4); + mProcess->MemReadUnsafe(registers.Gip(), dataToExec, 4); printf("\n What are my bytes? I am so lost.. Dump: "); for (int i = 0; i < 4; i++) { printf("%02X ", dataToExec[i]); } - mProcess->SetMemoryBreakpoint(mRegisters->Gip() + 1, 0x1, this, &MyDebugger::cbMemoryBreakpoint2, MemoryType::Access, false); + mProcess->SetMemoryBreakpoint(registers.Gip() + 1, 0x1, this, &MyDebugger::cbMemoryBreakpoint2, MemoryType::Access, false); memcpy(dataToExec, tmp, 4); - mProcess->MemReadUnsafe(mRegisters->Gip(), dataToExec, 4); + mProcess->MemReadUnsafe(registers.Gip(), dataToExec, 4); printf("\n What are my bytes? I am so lost.. Dump: "); for (int i = 0; i < 4; i++) { @@ -50,14 +51,15 @@ protected: void cbEntryBreakpoint(const BreakpointInfo & info) { + Registers registers(mThread->hThread); printf("Reached entry breakpoint! GIP: 0x%p\n", - mRegisters->Gip()); + (void*)registers.Gip()); #ifdef _WIN64 - auto addr = mRegisters->Rbx(); + auto addr = registers.Rbx(); #else - auto addr = mRegisters->Esi(); + auto addr = registers.Esi(); #endif //_WIN64 - printf("Addr: 0x%p\n", addr); + printf("Addr: 0x%p\n", (void*)addr); if (mProcess->SetMemoryBreakpoint(addr, 0x10000, this, &MyDebugger::cbMemoryBreakpoint, MemoryType::Execute, false)) puts("Memory breakpoint set!"); else @@ -72,29 +74,32 @@ protected: mThread->StepInto([this]() { printf("Step after entry breakpoint! GIP: 0x%p\n", - mRegisters->Gip()); + registers.Gip()); });*/ } void cbEntryHardwareBreakpoint(const BreakpointInfo & info) { + Registers registers(mThread->hThread); printf("Reached entry hardware breakpoint! GIP: 0x%p\n", - mRegisters->Gip()); + (void*)registers.Gip()); if (mProcess->DeleteHardwareBreakpoint(info.address)) printf("Entry hardware breakpoint deleted!\n"); else printf("Failed to delete entry hardware breakpoint...\n"); mThread->StepInto([this]() { + Registers registers(mThread->hThread); printf("Step after entry hardware breakpoint! GIP: 0x%p\n", - mRegisters->Gip()); + (void*)registers.Gip()); }); } void cbStepSystem() { + Registers registers(mThread->hThread); printf("Reached step after system breakpoint, GIP: 0x%p!\n", - mRegisters->Gip()); + (void*)registers.Gip()); } void cbCreateProcessEvent(const CREATE_PROCESS_DEBUG_INFO & createProcess, const Process & process) override @@ -102,7 +107,7 @@ protected: ptr entry = ptr(createProcess.lpStartAddress); printf("Process %d created with entry 0x%p\n", mDebugEvent.dwProcessId, - entry); + (void*)entry); /*HardwareSlot slot; if (mProcess->GetFreeHardwareBreakpointSlot(slot)) { @@ -121,9 +126,9 @@ protected: entry = ptr(createProcess.lpBaseOfImage) + 0x108F; //MembpTest, main.cpp:43 (x32) #endif //_WIN64 if(mProcess->SetBreakpoint(entry, this, &MyDebugger::cbEntryBreakpoint, true)) - printf("Breakpoint set at 0x%p!\n", entry); + printf("Breakpoint set at 0x%p!\n", (void*)entry); else - printf("Failed to set breakpoint at 0x%p...\b", entry); + printf("Failed to set breakpoint at 0x%p...\b", (void*)entry); uint8 test[5]; ptr start = entry - 2; printf("unsafe: "); @@ -179,7 +184,7 @@ protected: exceptionInfo.ExceptionRecord.ExceptionCode, exceptionInfo.ExceptionRecord.ExceptionAddress); for (DWORD i = 0; i < exceptionInfo.ExceptionRecord.NumberParameters; i++) - printf(" ExceptionInformation[%d] = 0x%p\n", i, exceptionInfo.ExceptionRecord.ExceptionInformation[i]); + printf(" ExceptionInformation[%d] = 0x%p\n", i, (void*)exceptionInfo.ExceptionRecord.ExceptionInformation[i]); } void cbDebugStringEvent(const OUTPUT_DEBUG_STRING_INFO & debugString) override @@ -198,14 +203,16 @@ protected: void cbAttachBreakpoint() override { + Registers registers(mThread->hThread); printf("Attach breakpoint reached, GIP: 0x%p\n", - mRegisters->Gip()); + (void*)registers.Gip()); } void cbSystemBreakpoint() override { + Registers registers(mThread->hThread); printf("System breakpoint reached, GIP: 0x%p\n", - mRegisters->Gip()); + (void*)registers.Gip()); mThread->StepInto(this, &MyDebugger::cbStepSystem); } @@ -218,16 +225,17 @@ protected: void cbBreakpoint(const BreakpointInfo & info) override { printf("Breakpoint on 0x%p!\n", - info.address); + (void*)info.address); } void cbUnhandledException(const EXCEPTION_RECORD & exceptionRecord, bool firstChance) override { + Registers registers(mThread->hThread); printf("Unhandled exception (%s) 0x%08X on 0x%p, GIP: 0x%p\n", firstChance ? "first chance" : "second chance", exceptionRecord.ExceptionCode, exceptionRecord.ExceptionAddress, - mRegisters->Gip()); + (void*)registers.Gip()); } }; diff --git a/MyDebugger/MyDebugger.vcxproj b/MyDebugger/MyDebugger.vcxproj index 877b10b..16469de 100644 --- a/MyDebugger/MyDebugger.vcxproj +++ b/MyDebugger/MyDebugger.vcxproj @@ -114,7 +114,7 @@ true true true - MultiThreadedDLL + MultiThreaded true @@ -131,7 +131,7 @@ true true true - MultiThreadedDLL + MultiThreaded true diff --git a/MyDebugger/main.cpp b/MyDebugger/main.cpp index b8457a7..5d93614 100644 --- a/MyDebugger/main.cpp +++ b/MyDebugger/main.cpp @@ -56,7 +56,7 @@ static void printNtHeaders(T inth) puts("\n Optional Header:"); printf(" Magic : %04X\n", ioh->Magic); printf(" EntryPoint: %08X\n", ioh->AddressOfEntryPoint); - printf(" ImageBase : %p\n", PVOID(ioh->ImageBase)); + printf(" ImageBase : %llX\n", (uint64_t)ioh->ImageBase); printf(" Subsystem : %04X\n", ioh->Subsystem); } @@ -156,7 +156,7 @@ static void testCorkami() printf("file: %ws\n\n", fileName.c_str()); } } - printf("\n%d/%d parsed OK!\n", okCount, _countof(peTestFiles)); + printf("\n%d/%zu parsed OK!\n", okCount, _countof(peTestFiles)); } int main() diff --git a/TitanEngineEmulator/Emulator.h b/TitanEngineEmulator/Emulator.h index 6980bbd..b8ecb03 100644 --- a/TitanEngineEmulator/Emulator.h +++ b/TitanEngineEmulator/Emulator.h @@ -1,10 +1,13 @@ #include #include #include +#include #include "TitanEngine.h" #include "ntdll.h" #include "FileMap.h" #include "PEB.h" +#include "Global.Engine.Context.h" +#include "Hider.h" // Related to floating x87 registers #define GetSTInTOPStackFromStatusWord(StatusWord) ((StatusWord & 0x3800) >> 11) @@ -13,137 +16,13 @@ #define GetRegisterAreaOf87register(register_area, x87r0_position, index) (((char *) register_area) + 10 * Calculatex87registerPositionInRegisterArea(x87r0_position, index) ) #define GetSTValueFromIndex(x87r0_position, index) ((x87r0_position + index) % 8) -#ifdef _WIN64 -//https://stackoverflow.com/a/869597/1806760 -template struct identity -{ - typedef T type; -}; - -template Dst implicit_cast(typename identity::type t) -{ - return t; -} - -//https://github.com/electron/crashpad/blob/4054e6cba3ba023d9c00260518ec2912607ae17c/snapshot/cpu_context.cc -enum -{ - kX87TagValid = 0, - kX87TagZero, - kX87TagSpecial, - kX87TagEmpty, -}; - -typedef uint8_t X87Register[10]; - -union X87OrMMXRegister -{ - struct - { - X87Register st; - uint8_t st_reserved[6]; - }; - struct - { - uint8_t mm_value[8]; - uint8_t mm_reserved[8]; - }; -}; - -static_assert(sizeof(X87OrMMXRegister) == sizeof(M128A), "sizeof(X87OrMMXRegister) != sizeof(M128A)"); - -static uint16_t FxsaveToFsaveTagWord( - uint16_t fsw, - uint8_t fxsave_tag, - const X87OrMMXRegister* st_mm) -{ - // The x87 tag word (in both abridged and full form) identifies physical - // registers, but |st_mm| is arranged in logical stack order. In order to map - // physical tag word bits to the logical stack registers they correspond to, - // the "stack top" value from the x87 status word is necessary. - int stack_top = (fsw >> 11) & 0x7; - - uint16_t fsave_tag = 0; - for(int physical_index = 0; physical_index < 8; ++physical_index) - { - bool fxsave_bit = (fxsave_tag & (1 << physical_index)) != 0; - uint8_t fsave_bits; - - if(fxsave_bit) - { - int st_index = (physical_index + 8 - stack_top) % 8; - const X87Register & st = st_mm[st_index].st; - - uint32_t exponent = ((st[9] & 0x7f) << 8) | st[8]; - if(exponent == 0x7fff) - { - // Infinity, NaN, pseudo-infinity, or pseudo-NaN. If it was important to - // distinguish between these, the J bit and the M bit (the most - // significant bit of |fraction|) could be consulted. - fsave_bits = kX87TagSpecial; - } - else - { - // The integer bit the "J bit". - bool integer_bit = (st[7] & 0x80) != 0; - if(exponent == 0) - { - uint64_t fraction = ((implicit_cast(st[7]) & 0x7f) << 56) | - (implicit_cast(st[6]) << 48) | - (implicit_cast(st[5]) << 40) | - (implicit_cast(st[4]) << 32) | - (implicit_cast(st[3]) << 24) | - (st[2] << 16) | (st[1] << 8) | st[0]; - if(!integer_bit && fraction == 0) - { - fsave_bits = kX87TagZero; - } - else - { - // Denormal (if the J bit is clear) or pseudo-denormal. - fsave_bits = kX87TagSpecial; - } - } - else if(integer_bit) - { - fsave_bits = kX87TagValid; - } - else - { - // Unnormal. - fsave_bits = kX87TagSpecial; - } - } - } - else - { - fsave_bits = kX87TagEmpty; - } - - fsave_tag |= (fsave_bits << (physical_index * 2)); - } - - return fsave_tag; -} - -static uint8_t FsaveToFxsaveTagWord(uint16_t fsave_tag) -{ - uint8_t fxsave_tag = 0; - for(int physical_index = 0; physical_index < 8; ++physical_index) - { - const uint8_t fsave_bits = (fsave_tag >> (physical_index * 2)) & 0x3; - const bool fxsave_bit = fsave_bits != kX87TagEmpty; - fxsave_tag |= fxsave_bit << physical_index; - } - return fxsave_tag; -} -#endif //_WIN64 - using namespace GleeBug; class Emulator : public Debugger { public: + HINSTANCE engineHandle; + //Debugger PROCESS_INFORMATION* InitDebugW(const wchar_t* szFileName, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder) { @@ -153,9 +32,31 @@ public: return &mMainProcess; } - PROCESS_INFORMATION* InitDLLDebugW(const wchar_t* szFileName, bool ReserveModuleBase, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder, LPVOID EntryCallBack) + PROCESS_INFORMATION* InitDLLDebugW(const wchar_t* szFileName, bool /* ReserveModuleBase = false */, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder, LPVOID /*EntryCallBack = 0 */) { - //TODO + wcscpy_s(szDebuggeeName, szFileName); + if (TryExtractDllLoader()) + { + mCbATTACHBREAKPOINT = nullptr; + if (!Init(szDebuggeeName, szCommandLine, szCurrentFolder, true, true)) + return nullptr; + wchar_t szName[256] = L""; + swprintf(szName, 256, L"Local\\szLibraryName%X", mMainProcess.dwProcessId); + //TODO: close this once we actually see the DLL is loaded in the process + HANDLE DebugDLLFileMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, 512 * sizeof(wchar_t), szName); + if (DebugDLLFileMapping) + { + const size_t filemapSize = 512; + wchar_t* szLibraryPathMapping = (wchar_t*)MapViewOfFile(DebugDLLFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, filemapSize * sizeof(wchar_t)); + if (szLibraryPathMapping) + { + wcscpy_s(szLibraryPathMapping, filemapSize, szFileName); + UnmapViewOfFile(szLibraryPathMapping); + } + } + ResumeThread(mMainProcess.hThread); + return &mMainProcess; + } return nullptr; } @@ -288,23 +189,7 @@ public: //Misc void* GetPEBLocation(HANDLE hProcess) { - ULONG RequiredLen = 0; - void* PebAddress = 0; - PROCESS_BASIC_INFORMATION myProcessBasicInformation[5] = { 0 }; - - if(NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, sizeof(PROCESS_BASIC_INFORMATION), &RequiredLen) == 0) - { - PebAddress = (void*)myProcessBasicInformation->PebBaseAddress; - } - else - { - if(NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, RequiredLen, &RequiredLen) == 0) - { - PebAddress = (void*)myProcessBasicInformation->PebBaseAddress; - } - } - - return PebAddress; + return GetPEBLocation_(hProcess); } void* GetPEBLocation64(HANDLE hProcess) @@ -375,67 +260,7 @@ public: bool HideDebugger(HANDLE hProcess, DWORD PatchAPILevel) { - PEB_CURRENT myPEB = { 0 }; - SIZE_T ueNumberOfBytesRead = 0; - void* heapFlagsAddress = 0; - DWORD heapFlags = 0; - void* heapForceFlagsAddress = 0; - DWORD heapForceFlags = 0; - -#ifndef _WIN64 - PEB64 myPEB64 = { 0 }; - void* AddressOfPEB64 = GetPEBLocation64(hProcess); -#endif - - void* AddressOfPEB = GetPEBLocation(hProcess); - - if(!AddressOfPEB) - return false; - - if(ReadProcessMemory(hProcess, AddressOfPEB, (void*)&myPEB, sizeof(PEB_CURRENT), &ueNumberOfBytesRead)) - { -#ifndef _WIN64 - if(AddressOfPEB64) - { - ReadProcessMemory(hProcess, AddressOfPEB64, (void*)&myPEB64, sizeof(PEB64), &ueNumberOfBytesRead); - } -#endif - myPEB.BeingDebugged = FALSE; - myPEB.NtGlobalFlag &= ~0x70; - -#ifndef _WIN64 - myPEB64.BeingDebugged = FALSE; - myPEB64.NtGlobalFlag &= ~0x70; -#endif - -#ifdef _WIN64 - heapFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapFlagsOffset(true)); - heapForceFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapForceFlagsOffset(true)); -#else - heapFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapFlagsOffset(false)); - heapForceFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapForceFlagsOffset(false)); -#endif //_WIN64 - ReadProcessMemory(hProcess, heapFlagsAddress, &heapFlags, sizeof(DWORD), 0); - ReadProcessMemory(hProcess, heapForceFlagsAddress, &heapForceFlags, sizeof(DWORD), 0); - - heapFlags &= HEAP_GROWABLE; - heapForceFlags = 0; - - WriteProcessMemory(hProcess, heapFlagsAddress, &heapFlags, sizeof(DWORD), 0); - WriteProcessMemory(hProcess, heapForceFlagsAddress, &heapForceFlags, sizeof(DWORD), 0); - - if(WriteProcessMemory(hProcess, AddressOfPEB, (void*)&myPEB, sizeof(PEB_CURRENT), &ueNumberOfBytesRead)) - { -#ifndef _WIN64 - if(AddressOfPEB64) - { - WriteProcessMemory(hProcess, AddressOfPEB64, (void*)&myPEB64, sizeof(PEB64), &ueNumberOfBytesRead); - } -#endif - return true; - } - } - return false; + return FixPebInProcess(hProcess); } HANDLE TitanOpenProcess(DWORD dwDesiredAccess, bool bInheritHandle, DWORD dwProcessId) @@ -479,220 +304,178 @@ public: struct ThreadSuspender { - ThreadSuspender(Thread* thread, bool running, bool writeRegs) - : thread(running ? thread : nullptr), writeRegs(writeRegs) + ThreadSuspender(HANDLE hThread, bool running) + : hThread(running ? hThread : nullptr) { - if(this->thread) - { - this->thread->Suspend(); - this->thread->RegReadContext(); - } + if (this->hThread) + SuspendThread(this->hThread); } + ThreadSuspender(const ThreadSuspender &) = delete; + ThreadSuspender(const ThreadSuspender &&) = delete; + ~ThreadSuspender() { - if(this->thread) - { - if(this->writeRegs) - this->thread->RegWriteContext(); - this->thread->Resume(); - } + if (this->hThread) + ResumeThread(this->hThread); } - Thread* thread; - bool writeRegs; + HANDLE hThread; }; //Registers ULONG_PTR GetContextDataEx(HANDLE hActiveThread, DWORD IndexOfRegister) { - if(!hActiveThread) + if (!hActiveThread) return 0; - auto thread = threadFromHandle(hActiveThread); - if(!thread) - return 0; - ThreadSuspender suspender(thread, mIsRunning, false); - return thread->registers.Get(registerFromDword(IndexOfRegister)); + + ThreadSuspender suspender(hActiveThread, mIsRunning); + auto r = registerFromDword(IndexOfRegister); + if (r == Registers::R::Invalid) + __debugbreak(); + return Registers(hActiveThread).Get(r); } bool SetContextDataEx(HANDLE hActiveThread, DWORD IndexOfRegister, ULONG_PTR NewRegisterValue) { - auto thread = threadFromHandle(hActiveThread); - if (!thread) - return false; - ThreadSuspender suspender(thread, mIsRunning, true); - thread->registers.Set(registerFromDword(IndexOfRegister), NewRegisterValue); - return true; - } + if (!hActiveThread) + return 0; - bool GetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext) - { - if(!hActiveThread) + ThreadSuspender suspender(hActiveThread, mIsRunning); + + auto r = registerFromDword(IndexOfRegister); + if (r != Registers::R::Invalid) + { + Registers(hActiveThread).Set(r, NewRegisterValue); + return true; + } + + TITAN_ENGINE_CONTEXT_t titcontext; + if (!_GetFullContextDataEx(hActiveThread, &titcontext, IndexOfRegister >= UE_MXCSR)) return false; - auto thread = threadFromHandle(hActiveThread); - if (!thread || !titcontext) - return false; - ThreadSuspender suspender(thread, mIsRunning, false); - auto context = thread->registers.GetContext(); - memset(titcontext, 0, sizeof(TITAN_ENGINE_CONTEXT_t)); - //General purpose registers - titcontext->cax = thread->registers.Gax(); - titcontext->ccx = thread->registers.Gcx(); - titcontext->cdx = thread->registers.Gdx(); - titcontext->cbx = thread->registers.Gbx(); - titcontext->csp = thread->registers.Gsp(); - titcontext->cbp = thread->registers.Gbp(); - titcontext->csi = thread->registers.Gsi(); - titcontext->cdi = thread->registers.Gdi(); + + bool avx_priority = false; + switch (IndexOfRegister) + { + case UE_X87_STATUSWORD: + { + titcontext.x87fpu.StatusWord = WORD(NewRegisterValue); + break; + } + + case UE_X87_CONTROLWORD: + { + titcontext.x87fpu.ControlWord = WORD(NewRegisterValue); + break; + } + + case UE_X87_TAGWORD: + { + titcontext.x87fpu.TagWord = WORD(NewRegisterValue); + break; + } + + case UE_MXCSR: + { + titcontext.MxCsr = DWORD(NewRegisterValue); + break; + } + + case UE_XMM0: + case UE_XMM1: + case UE_XMM2: + case UE_XMM3: + case UE_XMM4: + case UE_XMM5: + case UE_XMM6: + case UE_XMM7: #ifdef _WIN64 - titcontext->r8 = thread->registers.R8(); - titcontext->r9 = thread->registers.R9(); - titcontext->r10 = thread->registers.R10(); - titcontext->r11 = thread->registers.R11(); - titcontext->r12 = thread->registers.R12(); - titcontext->r13 = thread->registers.R13(); - titcontext->r14 = thread->registers.R14(); - titcontext->r15 = thread->registers.R15(); + case UE_XMM8: + case UE_XMM9: + case UE_XMM10: + case UE_XMM11: + case UE_XMM12: + case UE_XMM13: + case UE_XMM14: + case UE_XMM15: #endif //_WIN64 - titcontext->cip = thread->registers.Gip(); - // Flags - titcontext->eflags = thread->registers.Eflags(); - // Debug registers - titcontext->dr0 = thread->registers.Dr0(); - titcontext->dr1 = thread->registers.Dr1(); - titcontext->dr2 = thread->registers.Dr2(); - titcontext->dr3 = thread->registers.Dr3(); - titcontext->dr6 = thread->registers.Dr6(); - titcontext->dr7 = thread->registers.Dr7(); - // Segments - titcontext->gs = thread->registers.Gs(); - titcontext->fs = thread->registers.Fs(); - titcontext->es = thread->registers.Es(); - titcontext->ds = thread->registers.Ds(); - titcontext->cs = thread->registers.Cs(); - titcontext->ss = thread->registers.Ss(); - // x87 + { + memcpy(&(titcontext.XmmRegisters[IndexOfRegister - UE_XMM0]), (void*)NewRegisterValue, 16); + break; + } + + case UE_MMX0: + case UE_MMX1: + case UE_MMX2: + case UE_MMX3: + case UE_MMX4: + case UE_MMX5: + case UE_MMX6: + case UE_MMX7: + { + int STInTopStack = GetSTInTOPStackFromStatusWord(titcontext.x87fpu.StatusWord); + DWORD x87r0_position = Getx87r0PositionInRegisterArea(STInTopStack); + + memcpy(((uint64_t*)GetRegisterAreaOf87register(titcontext.RegisterArea, x87r0_position, IndexOfRegister - UE_MMX0)), (char*)NewRegisterValue, 8); + break; + } + + case UE_x87_r0: + case UE_x87_r1: + case UE_x87_r2: + case UE_x87_r3: + case UE_x87_r4: + case UE_x87_r5: + case UE_x87_r6: + case UE_x87_r7: + { + int STInTopStack = GetSTInTOPStackFromStatusWord(titcontext.x87fpu.StatusWord); + DWORD x87r0_position = Getx87r0PositionInRegisterArea(STInTopStack); + + memcpy(((uint64_t*)GetRegisterAreaOf87register(titcontext.RegisterArea, x87r0_position, IndexOfRegister - UE_x87_r0)), (void*)NewRegisterValue, 10); + break; + } + + case UE_YMM0: + case UE_YMM1: + case UE_YMM2: + case UE_YMM3: + case UE_YMM4: + case UE_YMM5: + case UE_YMM6: + case UE_YMM7: #ifdef _WIN64 - titcontext->x87fpu.ControlWord = context->FltSave.ControlWord; - titcontext->x87fpu.StatusWord = context->FltSave.StatusWord; - titcontext->x87fpu.TagWord = FxsaveToFsaveTagWord(context->FltSave.StatusWord, context->FltSave.TagWord, (const X87OrMMXRegister*)context->FltSave.FloatRegisters); - titcontext->x87fpu.ErrorSelector = context->FltSave.ErrorSelector; - titcontext->x87fpu.ErrorOffset = context->FltSave.ErrorOffset; - titcontext->x87fpu.DataSelector = context->FltSave.DataSelector; - titcontext->x87fpu.DataOffset = context->FltSave.DataOffset; - // Skip titcontext->x87fpu.Cr0NpxState (https://github.com/x64dbg/x64dbg/issues/255) - titcontext->MxCsr = context->MxCsr; - - for(int i = 0; i < 8; i++) - memcpy(&titcontext->RegisterArea[i * 10], &context->FltSave.FloatRegisters[i], 10); - - for(int i = 0; i < 16; i++) - memcpy(&titcontext->XmmRegisters[i], &context->FltSave.XmmRegisters[i], 16); -#else //x86 - titcontext->x87fpu.ControlWord = (WORD)context->FloatSave.ControlWord; - titcontext->x87fpu.StatusWord = (WORD)context->FloatSave.StatusWord; - titcontext->x87fpu.TagWord = (WORD)context->FloatSave.TagWord; - titcontext->x87fpu.ErrorSelector = context->FloatSave.ErrorSelector; - titcontext->x87fpu.ErrorOffset = context->FloatSave.ErrorOffset; - titcontext->x87fpu.DataSelector = context->FloatSave.DataSelector; - titcontext->x87fpu.DataOffset = context->FloatSave.DataOffset; - titcontext->x87fpu.Cr0NpxState = context->FloatSave.Cr0NpxState; - - memcpy(titcontext->RegisterArea, context->FloatSave.RegisterArea, 80); - - // MXCSR ExtendedRegisters[24] - memcpy(&(titcontext->MxCsr), &(context->ExtendedRegisters[24]), sizeof(titcontext->MxCsr)); - - // for x86 copy the 8 Xmm Registers from ExtendedRegisters[(10+n)*16]; (n is the index of the xmm register) to the XMM register - for(int i = 0; i < 8; i++) - memcpy(&(titcontext->XmmRegisters[i]), &context->ExtendedRegisters[(10 + i) * 16], 16); + case UE_YMM8: + case UE_YMM9: + case UE_YMM10: + case UE_YMM11: + case UE_YMM12: + case UE_YMM13: + case UE_YMM14: + case UE_YMM15: #endif //_WIN64 + { + avx_priority = true; + memcpy(&titcontext.YmmRegisters[IndexOfRegister - UE_YMM0], (void*)NewRegisterValue, 32); + break; + } - //TODO: AVX - return true; + default: __debugbreak(); + } + + return _SetFullContextDataEx(hActiveThread, &titcontext, avx_priority); } bool SetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext) { - auto thread = threadFromHandle(hActiveThread); - if (!thread || !titcontext) - return false; - ThreadSuspender suspender(thread, mIsRunning, true); - auto context = thread->registers.GetContext(); - // General purpose registers - thread->registers.Gax = titcontext->cax; - thread->registers.Gcx = titcontext->ccx; - thread->registers.Gdx = titcontext->cdx; - thread->registers.Gbx = titcontext->cbx; - thread->registers.Gsp = titcontext->csp; - thread->registers.Gbp = titcontext->cbp; - thread->registers.Gsi = titcontext->csi; - thread->registers.Gdi = titcontext->cdi; -#ifdef _WIN64 - thread->registers.R8 = titcontext->r8; - thread->registers.R9 = titcontext->r9; - thread->registers.R10 = titcontext->r10; - thread->registers.R11 = titcontext->r11; - thread->registers.R12 = titcontext->r12; - thread->registers.R13 = titcontext->r13; - thread->registers.R14 = titcontext->r14; - thread->registers.R15 = titcontext->r15; -#endif //_WIN64 - thread->registers.Gip = titcontext->cip; - // Flags - thread->registers.Eflags = uint32(titcontext->eflags); - // Debug registers - thread->registers.Dr0 = titcontext->dr0; - thread->registers.Dr1 = titcontext->dr1; - thread->registers.Dr2 = titcontext->dr2; - thread->registers.Dr3 = titcontext->dr3; - thread->registers.Dr6 = titcontext->dr6; - thread->registers.Dr7 = titcontext->dr7; - // Segments - thread->registers.Gs = titcontext->gs; - thread->registers.Fs = titcontext->fs; - thread->registers.Es = titcontext->es; - thread->registers.Ds = titcontext->ds; - thread->registers.Cs = titcontext->cs; - thread->registers.Ss = titcontext->ss; - // x87 -#ifdef _WIN64 - context->FltSave.ControlWord = titcontext->x87fpu.ControlWord; - context->FltSave.StatusWord = titcontext->x87fpu.StatusWord; - context->FltSave.TagWord = FsaveToFxsaveTagWord(titcontext->x87fpu.TagWord); - context->FltSave.ErrorSelector = (WORD)titcontext->x87fpu.ErrorSelector; - context->FltSave.ErrorOffset = titcontext->x87fpu.ErrorOffset; - context->FltSave.DataSelector = (WORD)titcontext->x87fpu.DataSelector; - context->FltSave.DataOffset = titcontext->x87fpu.DataOffset; - // Skip titcontext->x87fpu.Cr0NpxState - context->MxCsr = titcontext->MxCsr; + ThreadSuspender suspender(hActiveThread, mIsRunning); + return _SetFullContextDataEx(hActiveThread, titcontext, false); + } - for(int i = 0; i < 8; i++) - memcpy(&context->FltSave.FloatRegisters[i], &(titcontext->RegisterArea[i * 10]), 10); - - for(int i = 0; i < 16; i++) - memcpy(&(context->FltSave.XmmRegisters[i]), &(titcontext->XmmRegisters[i]), 16); -#else //x86 - context->FloatSave.ControlWord = titcontext->x87fpu.ControlWord; - context->FloatSave.StatusWord = titcontext->x87fpu.StatusWord; - context->FloatSave.TagWord = titcontext->x87fpu.TagWord; - context->FloatSave.ErrorSelector = titcontext->x87fpu.ErrorSelector; - context->FloatSave.ErrorOffset = titcontext->x87fpu.ErrorOffset; - context->FloatSave.DataSelector = titcontext->x87fpu.DataSelector; - context->FloatSave.DataOffset = titcontext->x87fpu.DataOffset; - context->FloatSave.Cr0NpxState = titcontext->x87fpu.Cr0NpxState; - - memcpy(context->FloatSave.RegisterArea, titcontext->RegisterArea, 80); - - // MXCSR ExtendedRegisters[24] - memcpy(&(context->ExtendedRegisters[24]), &titcontext->MxCsr, sizeof(titcontext->MxCsr)); - - // for x86 copy the 8 Xmm Registers from ExtendedRegisters[(10+n)*16]; (n is the index of the xmm register) to the XMM register - for(int i = 0; i < 8; i++) - memcpy(&context->ExtendedRegisters[(10 + i) * 16], &(titcontext->XmmRegisters[i]), 16); -#endif //_WIN64 - //TODO: AVX - return true; + bool GetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext) + { + ThreadSuspender suspender(hActiveThread, mIsRunning); + return _GetFullContextDataEx(hActiveThread, titcontext, true); } void GetMMXRegisters(uint64_t mmx[8], TITAN_ENGINE_CONTEXT_t* titcontext) @@ -701,7 +484,7 @@ public: DWORD x87r0_position = Getx87r0PositionInRegisterArea(STInTopStack); int i; - for(i = 0; i < 8; i++) + for (i = 0; i < 8; i++) mmx[i] = *((uint64_t*)GetRegisterAreaOf87register(titcontext->RegisterArea, x87r0_position, i)); } @@ -718,7 +501,7 @@ public: int STInTopStack = GetSTInTOPStackFromStatusWord(titcontext->x87fpu.StatusWord); DWORD x87r0_position = Getx87r0PositionInRegisterArea(STInTopStack); - for(int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { memcpy(x87FPURegisters[i].data, GetRegisterAreaOf87register(titcontext->RegisterArea, x87r0_position, i), 10); x87FPURegisters[i].st_value = GetSTValueFromIndex(x87r0_position, i); @@ -925,7 +708,6 @@ public: { for(auto & thread : mProcess->threads) thread.second->Suspend(); - mProcess->RegReadContext(); } if(!mProcess->SetHardwareBreakpoint(bpxAddress, (HardwareSlot)IndexOfRegister, [bpxCallBack](const BreakpointInfo & info) @@ -935,7 +717,6 @@ public: return false; if(running) { - mProcess->RegWriteContext(); for(auto & thread : mProcess->threads) thread.second->Resume(); } @@ -1045,7 +826,7 @@ protected: } private: //functions - static Registers::R registerFromDword(DWORD IndexOfRegister) + inline Registers::R registerFromDword(DWORD IndexOfRegister) { switch (IndexOfRegister) { @@ -1093,14 +874,15 @@ private: //functions case UE_SEG_DS: return Registers::R::DS; case UE_SEG_CS: return Registers::R::CS; case UE_SEG_SS: return Registers::R::SS; - default: - __debugbreak(); - return Registers::R::EAX; + default: return Registers::R::Invalid; } } std::unordered_map threadFromHandleCache; + // Disable warnings about pointer truncation for the THREAD_BASIC_INFORMATION +#pragma warning(push) +#pragma warning(disable: 4311 4302) Thread* threadFromHandle(HANDLE hThread) { auto found = threadFromHandleCache.find(hThread); @@ -1123,6 +905,7 @@ private: //functions threadFromHandleCache[hThread] = result; return result; } +#pragma warning(pop) std::unordered_map processFromHandleCache; @@ -1313,6 +1096,53 @@ private: //functions } #endif + bool EngineExtractResource(char* szResourceName, wchar_t* szExtractedFileName) + { + bool result = false; + HRSRC hResource = FindResourceA(engineHandle, (LPCSTR)szResourceName, "BINARY"); + if (hResource != NULL) + { + HGLOBAL hResourceGlobal = LoadResource(engineHandle, hResource); + if (hResourceGlobal != NULL) + { + DWORD ResourceSize = SizeofResource(engineHandle, hResource); + LPVOID ResourceData = LockResource(hResourceGlobal); + HANDLE hFile = CreateFileW(szExtractedFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD NumberOfBytesWritten; + if (WriteFile(hFile, ResourceData, ResourceSize, &NumberOfBytesWritten, NULL)) + result = true; + CloseHandle(hFile); + } + } + } + return result; + } + + bool TryExtractDllLoader(bool failedBefore = false) + { + wchar_t* szPath = wcsrchr(szDebuggeeName, L'\\'); + if (szPath) + szPath[1] = '\0'; + wchar_t DLLLoaderName[64] = L""; +#ifdef _WIN64 + swprintf_s(DLLLoaderName, L"DLLLoader64_%.4X.exe", GetTickCount() & 0xFFFF); +#else + swprintf_s(DLLLoaderName, L"DLLLoader32_%.4X.exe", GetTickCount() & 0xFFFF); +#endif //_WIN64 + wcscat_s(szDebuggeeName, DLLLoaderName); +#ifdef _WIN64 + if (EngineExtractResource("LOADERX64", szDebuggeeName)) +#else + if (EngineExtractResource("LOADERX86", szDebuggeeName)) +#endif //_WIN64 + return true; + return !failedBefore && + GetModuleFileNameW(engineHandle, szDebuggeeName, _countof(szDebuggeeName)) && + TryExtractDllLoader(true); + } + private: //variables bool mSetDebugPrivilege = false; typedef void(*CUSTOMHANDLER)(const void*); @@ -1332,4 +1162,5 @@ private: //variables CUSTOMHANDLER mCbDEBUGEVENT = nullptr; STEPCALLBACK mCbATTACHBREAKPOINT = nullptr; PROCESS_INFORMATION* mAttachProcessInfo = nullptr; + wchar_t szDebuggeeName[MAX_PATH] = L""; }; \ No newline at end of file diff --git a/TitanEngineEmulator/Global.Engine.Context.cpp b/TitanEngineEmulator/Global.Engine.Context.cpp new file mode 100644 index 0000000..51fb1f3 --- /dev/null +++ b/TitanEngineEmulator/Global.Engine.Context.cpp @@ -0,0 +1,482 @@ +#include "Global.Engine.Context.h" +#include + +typedef int(*p_printf)(const char*, ...); + +static p_printf hax() +{ + auto d = p_printf(GetProcAddress(GetModuleHandleW(L"x64dbg.dll"), "_plugin_logprintf")); + return d ? d : printf; +} + +static auto dprintf = hax(); + +#ifdef _WIN64 +//https://stackoverflow.com/a/869597/1806760 +template struct identity +{ + typedef T type; +}; + +template Dst implicit_cast(typename identity::type t) +{ + return t; +} + +//https://github.com/electron/crashpad/blob/4054e6cba3ba023d9c00260518ec2912607ae17c/snapshot/cpu_context.cc +enum +{ + kX87TagValid = 0, + kX87TagZero, + kX87TagSpecial, + kX87TagEmpty, +}; + +typedef uint8_t X87Register[10]; + +union X87OrMMXRegister +{ + struct + { + X87Register st; + uint8_t st_reserved[6]; + }; + struct + { + uint8_t mm_value[8]; + uint8_t mm_reserved[8]; + }; +}; + +static_assert(sizeof(X87OrMMXRegister) == sizeof(M128A), "sizeof(X87OrMMXRegister) != sizeof(M128A)"); + +static uint16_t FxsaveToFsaveTagWord( + uint16_t fsw, + uint8_t fxsave_tag, + const X87OrMMXRegister* st_mm) +{ + // The x87 tag word (in both abridged and full form) identifies physical + // registers, but |st_mm| is arranged in logical stack order. In order to map + // physical tag word bits to the logical stack registers they correspond to, + // the "stack top" value from the x87 status word is necessary. + int stack_top = (fsw >> 11) & 0x7; + + uint16_t fsave_tag = 0; + for(int physical_index = 0; physical_index < 8; ++physical_index) + { + bool fxsave_bit = (fxsave_tag & (1 << physical_index)) != 0; + uint8_t fsave_bits; + + if(fxsave_bit) + { + int st_index = (physical_index + 8 - stack_top) % 8; + const X87Register & st = st_mm[st_index].st; + + uint32_t exponent = ((st[9] & 0x7f) << 8) | st[8]; + if(exponent == 0x7fff) + { + // Infinity, NaN, pseudo-infinity, or pseudo-NaN. If it was important to + // distinguish between these, the J bit and the M bit (the most + // significant bit of |fraction|) could be consulted. + fsave_bits = kX87TagSpecial; + } + else + { + // The integer bit the "J bit". + bool integer_bit = (st[7] & 0x80) != 0; + if(exponent == 0) + { + uint64_t fraction = ((implicit_cast(st[7]) & 0x7f) << 56) | + (implicit_cast(st[6]) << 48) | + (implicit_cast(st[5]) << 40) | + (implicit_cast(st[4]) << 32) | + (implicit_cast(st[3]) << 24) | + (st[2] << 16) | (st[1] << 8) | st[0]; + if(!integer_bit && fraction == 0) + { + fsave_bits = kX87TagZero; + } + else + { + // Denormal (if the J bit is clear) or pseudo-denormal. + fsave_bits = kX87TagSpecial; + } + } + else if(integer_bit) + { + fsave_bits = kX87TagValid; + } + else + { + // Unnormal. + fsave_bits = kX87TagSpecial; + } + } + } + else + { + fsave_bits = kX87TagEmpty; + } + + fsave_tag |= (fsave_bits << (physical_index * 2)); + } + + return fsave_tag; +} + +static uint8_t FsaveToFxsaveTagWord(uint16_t fsave_tag) +{ + uint8_t fxsave_tag = 0; + for(int physical_index = 0; physical_index < 8; ++physical_index) + { + const uint8_t fsave_bits = (fsave_tag >> (physical_index * 2)) & 0x3; + const bool fxsave_bit = fsave_bits != kX87TagEmpty; + fxsave_tag |= fxsave_bit << physical_index; + } + return fxsave_tag; +} +#endif //_WIN64 + +PGETENABLEDXSTATEFEATURES _GetEnabledXStateFeatures = NULL; +PINITIALIZECONTEXT _InitializeContext = NULL; +PGETXSTATEFEATURESMASK _GetXStateFeaturesMask = NULL; +LOCATEXSTATEFEATURE _LocateXStateFeature = NULL; +SETXSTATEFEATURESMASK _SetXStateFeaturesMask = NULL; + +static bool SetAVXContext(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext) +{ + if (InitXState() == false) + return false; + + DWORD64 FeatureMask = _GetEnabledXStateFeatures(); + if ((FeatureMask & XSTATE_MASK_AVX) == 0) + return false; + + DWORD ContextSize = 0; + BOOL Success = _InitializeContext(NULL, + CONTEXT_ALL | CONTEXT_XSTATE, + NULL, + &ContextSize); + + if ((Success == TRUE) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) + return false; + + std::vector dataBuffer; + dataBuffer.resize(ContextSize); + PVOID Buffer = dataBuffer.data(); + if (Buffer == NULL) + return false; + + PCONTEXT Context; + Success = _InitializeContext(Buffer, + CONTEXT_ALL | CONTEXT_XSTATE, + &Context, + &ContextSize); + if (Success == FALSE) + return false; + + if (_SetXStateFeaturesMask(Context, XSTATE_MASK_AVX) == FALSE) + return false; + + if (GetThreadContext(hActiveThread, Context) == FALSE) + return false; + + if (_GetXStateFeaturesMask(Context, &FeatureMask) == FALSE) + return false; + + DWORD FeatureLength; + XmmRegister_t* Sse = (XmmRegister_t*)_LocateXStateFeature(Context, XSTATE_LEGACY_SSE, &FeatureLength); + XmmRegister_t* Avx = (XmmRegister_t*)_LocateXStateFeature(Context, XSTATE_AVX, NULL); + int NumberOfRegisters = FeatureLength / sizeof(Sse[0]); + + if (Sse != NULL) //If the feature is unsupported by the processor it will return NULL + { + for (int i = 0; i < NumberOfRegisters; i++) + Sse[i] = titcontext->YmmRegisters[i].Low; + } + + if (Avx != NULL) //If the feature is unsupported by the processor it will return NULL + { + for (int i = 0; i < NumberOfRegisters; i++) + Avx[i] = titcontext->YmmRegisters[i].High; + } + + return (SetThreadContext(hActiveThread, Context) == TRUE); +} + +static bool GetAVXContext(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext) +{ + if (InitXState() == false) + return false; + + DWORD64 FeatureMask = _GetEnabledXStateFeatures(); + if ((FeatureMask & XSTATE_MASK_AVX) == 0) + return false; + + DWORD ContextSize = 0; + BOOL Success = _InitializeContext(NULL, + CONTEXT_ALL | CONTEXT_XSTATE, + NULL, + &ContextSize); + + if ((Success == TRUE) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) + return false; + + std::vector dataBuffer; + dataBuffer.resize(ContextSize); + PVOID Buffer = dataBuffer.data(); + if (Buffer == NULL) + return false; + + PCONTEXT Context; + Success = _InitializeContext(Buffer, + CONTEXT_ALL | CONTEXT_XSTATE, + &Context, + &ContextSize); + if (Success == FALSE) + return false; + + if (_SetXStateFeaturesMask(Context, XSTATE_MASK_AVX) == FALSE) + return false; + + if (GetThreadContext(hActiveThread, Context) == FALSE) + return false; + + if (_GetXStateFeaturesMask(Context, &FeatureMask) == FALSE) + return false; + + DWORD FeatureLength; + XmmRegister_t* Sse = (XmmRegister_t*)_LocateXStateFeature(Context, XSTATE_LEGACY_SSE, &FeatureLength); + XmmRegister_t* Avx = (XmmRegister_t*)_LocateXStateFeature(Context, XSTATE_AVX, NULL); + int NumberOfRegisters = FeatureLength / sizeof(Sse[0]); + + if (Sse != NULL) //If the feature is unsupported by the processor it will return NULL + { + for (int i = 0; i < NumberOfRegisters; i++) + titcontext->YmmRegisters[i].Low = Sse[i]; + } + + if (Avx != NULL) //If the feature is unsupported by the processor it will return NULL + { + for (int i = 0; i < NumberOfRegisters; i++) + titcontext->YmmRegisters[i].High = Avx[i]; + } + + return true; +} + +bool _SetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool AVX_PRIORITY) +{ + CONTEXT DBGContext; + memset(&DBGContext, 0, sizeof(DBGContext)); + + DBGContext.ContextFlags = CONTEXT_ALL | CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS; + + if (!GetThreadContext(hActiveThread, &DBGContext)) + return false; + + DBGContext.EFlags = (DWORD)titcontext->eflags; + DBGContext.Dr0 = titcontext->dr0; + DBGContext.Dr1 = titcontext->dr1; + DBGContext.Dr2 = titcontext->dr2; + DBGContext.Dr3 = titcontext->dr3; + DBGContext.Dr6 = titcontext->dr6; + DBGContext.Dr7 = titcontext->dr7; + DBGContext.SegGs = titcontext->gs; + DBGContext.SegFs = titcontext->fs; + DBGContext.SegEs = titcontext->es; + DBGContext.SegDs = titcontext->ds; + DBGContext.SegCs = titcontext->cs; + DBGContext.SegSs = titcontext->ss; + +#ifdef _WIN64 //x64 + DBGContext.Rax = titcontext->cax; + DBGContext.Rbx = titcontext->cbx; + DBGContext.Rcx = titcontext->ccx; + DBGContext.Rdx = titcontext->cdx; + DBGContext.Rdi = titcontext->cdi; + DBGContext.Rsi = titcontext->csi; + DBGContext.Rbp = titcontext->cbp; + DBGContext.Rsp = titcontext->csp; + DBGContext.Rip = titcontext->cip; + DBGContext.R8 = titcontext->r8; + DBGContext.R9 = titcontext->r9; + DBGContext.R10 = titcontext->r10; + DBGContext.R11 = titcontext->r11; + DBGContext.R12 = titcontext->r12; + DBGContext.R13 = titcontext->r13; + DBGContext.R14 = titcontext->r14; + DBGContext.R15 = titcontext->r15; + + DBGContext.FltSave.ControlWord = titcontext->x87fpu.ControlWord; + DBGContext.FltSave.StatusWord = titcontext->x87fpu.StatusWord; + DBGContext.FltSave.TagWord = FsaveToFxsaveTagWord(titcontext->x87fpu.TagWord); + DBGContext.FltSave.ErrorSelector = (WORD)titcontext->x87fpu.ErrorSelector; + DBGContext.FltSave.ErrorOffset = titcontext->x87fpu.ErrorOffset; + DBGContext.FltSave.DataSelector = (WORD)titcontext->x87fpu.DataSelector; + DBGContext.FltSave.DataOffset = titcontext->x87fpu.DataOffset; + // Skip titcontext->x87fpu.Cr0NpxState + DBGContext.MxCsr = titcontext->MxCsr; + + for(int i = 0; i < 8; i++) + memcpy(& DBGContext.FltSave.FloatRegisters[i], &(titcontext->RegisterArea[i * 10]), 10); + + for(int i = 0; i < 16; i++) + memcpy(& (DBGContext.FltSave.XmmRegisters[i]), & (titcontext->XmmRegisters[i]), 16); + +#else //x86 + DBGContext.Eax = titcontext->cax; + DBGContext.Ebx = titcontext->cbx; + DBGContext.Ecx = titcontext->ccx; + DBGContext.Edx = titcontext->cdx; + DBGContext.Edi = titcontext->cdi; + DBGContext.Esi = titcontext->csi; + DBGContext.Ebp = titcontext->cbp; + DBGContext.Esp = titcontext->csp; + DBGContext.Eip = titcontext->cip; + + DBGContext.FloatSave.ControlWord = titcontext->x87fpu.ControlWord; + DBGContext.FloatSave.StatusWord = titcontext->x87fpu.StatusWord; + DBGContext.FloatSave.TagWord = titcontext->x87fpu.TagWord; + DBGContext.FloatSave.ErrorSelector = titcontext->x87fpu.ErrorSelector; + DBGContext.FloatSave.ErrorOffset = titcontext->x87fpu.ErrorOffset; + DBGContext.FloatSave.DataSelector = titcontext->x87fpu.DataSelector; + DBGContext.FloatSave.DataOffset = titcontext->x87fpu.DataOffset; + DBGContext.FloatSave.Cr0NpxState = titcontext->x87fpu.Cr0NpxState; + + memcpy(DBGContext.FloatSave.RegisterArea, titcontext->RegisterArea, 80); + + // MXCSR ExtendedRegisters[24] + memcpy(& (DBGContext.ExtendedRegisters[24]), & titcontext->MxCsr, sizeof(titcontext->MxCsr)); + + // for x86 copy the 8 Xmm Registers from ExtendedRegisters[(10+n)*16]; (n is the index of the xmm register) to the XMM register + for(int i = 0; i < 8; i++) + memcpy(& DBGContext.ExtendedRegisters[(10 + i) * 16], &(titcontext->XmmRegisters[i]), 16); +#endif + + bool returnf = !!SetThreadContext(hActiveThread, &DBGContext); + + if (AVX_PRIORITY) + SetAVXContext(hActiveThread, titcontext); + + return returnf; +} + +bool _GetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool avx) +{ + CONTEXT DBGContext; + memset(&DBGContext, 0, sizeof(CONTEXT)); + memset(titcontext, 0, sizeof(TITAN_ENGINE_CONTEXT_t)); + + DBGContext.ContextFlags = CONTEXT_ALL | CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS; + + if(!GetThreadContext(hActiveThread, &DBGContext)) + return false; + + titcontext->eflags = DBGContext.EFlags; + titcontext->dr0 = DBGContext.Dr0; + titcontext->dr1 = DBGContext.Dr1; + titcontext->dr2 = DBGContext.Dr2; + titcontext->dr3 = DBGContext.Dr3; + titcontext->dr6 = DBGContext.Dr6; + titcontext->dr7 = DBGContext.Dr7; + titcontext->gs = (unsigned short) DBGContext.SegGs; + titcontext->fs = (unsigned short) DBGContext.SegFs; + titcontext->es = (unsigned short) DBGContext.SegEs; + titcontext->ds = (unsigned short) DBGContext.SegDs; + titcontext->cs = (unsigned short) DBGContext.SegCs; + titcontext->ss = (unsigned short) DBGContext.SegSs; + +#ifdef _WIN64 //x64 + titcontext->cax = DBGContext.Rax; + titcontext->cbx = DBGContext.Rbx; + titcontext->ccx = DBGContext.Rcx; + titcontext->cdx = DBGContext.Rdx; + titcontext->cdi = DBGContext.Rdi; + titcontext->csi = DBGContext.Rsi; + titcontext->cbp = DBGContext.Rbp; + titcontext->csp = DBGContext.Rsp; + titcontext->cip = DBGContext.Rip; + titcontext->r8 = DBGContext.R8; + titcontext->r9 = DBGContext.R9; + titcontext->r10 = DBGContext.R10; + titcontext->r11 = DBGContext.R11; + titcontext->r12 = DBGContext.R12; + titcontext->r13 = DBGContext.R13; + titcontext->r14 = DBGContext.R14; + titcontext->r15 = DBGContext.R15; + + titcontext->x87fpu.ControlWord = DBGContext.FltSave.ControlWord; + titcontext->x87fpu.StatusWord = DBGContext.FltSave.StatusWord; + titcontext->x87fpu.TagWord = FxsaveToFsaveTagWord(DBGContext.FltSave.StatusWord, DBGContext.FltSave.TagWord, (const X87OrMMXRegister*)DBGContext.FltSave.FloatRegisters); + titcontext->x87fpu.ErrorSelector = DBGContext.FltSave.ErrorSelector; + titcontext->x87fpu.ErrorOffset = DBGContext.FltSave.ErrorOffset; + titcontext->x87fpu.DataSelector = DBGContext.FltSave.DataSelector; + titcontext->x87fpu.DataOffset = DBGContext.FltSave.DataOffset; + // Skip titcontext->x87fpu.Cr0NpxState (https://github.com/x64dbg/x64dbg/issues/255) + titcontext->MxCsr = DBGContext.MxCsr; + + for(int i = 0; i < 8; i++) + memcpy(&titcontext->RegisterArea[i * 10], &DBGContext.FltSave.FloatRegisters[i], 10); + + for(int i = 0; i < 16; i++) + memcpy(&titcontext->XmmRegisters[i], &DBGContext.FltSave.XmmRegisters[i], 16); + +#else //x86 + titcontext->cax = DBGContext.Eax; + titcontext->cbx = DBGContext.Ebx; + titcontext->ccx = DBGContext.Ecx; + titcontext->cdx = DBGContext.Edx; + titcontext->cdi = DBGContext.Edi; + titcontext->csi = DBGContext.Esi; + titcontext->cbp = DBGContext.Ebp; + titcontext->csp = DBGContext.Esp; + titcontext->cip = DBGContext.Eip; + + titcontext->x87fpu.ControlWord = (WORD) DBGContext.FloatSave.ControlWord; + titcontext->x87fpu.StatusWord = (WORD) DBGContext.FloatSave.StatusWord; + titcontext->x87fpu.TagWord = (WORD) DBGContext.FloatSave.TagWord; + titcontext->x87fpu.ErrorSelector = DBGContext.FloatSave.ErrorSelector; + titcontext->x87fpu.ErrorOffset = DBGContext.FloatSave.ErrorOffset; + titcontext->x87fpu.DataSelector = DBGContext.FloatSave.DataSelector; + titcontext->x87fpu.DataOffset = DBGContext.FloatSave.DataOffset; + titcontext->x87fpu.Cr0NpxState = DBGContext.FloatSave.Cr0NpxState; + + memcpy(titcontext->RegisterArea, DBGContext.FloatSave.RegisterArea, 80); + + // MXCSR ExtendedRegisters[24] + memcpy(& (titcontext->MxCsr), & (DBGContext.ExtendedRegisters[24]), sizeof(titcontext->MxCsr)); + + // for x86 copy the 8 Xmm Registers from ExtendedRegisters[(10+n)*16]; (n is the index of the xmm register) to the XMM register + for(int i = 0; i < 8; i++) + memcpy(&(titcontext->XmmRegisters[i]), & DBGContext.ExtendedRegisters[(10 + i) * 16], 16); +#endif + + if(avx) + GetAVXContext(hActiveThread, titcontext); + + return true; +} + +bool InitXState() +{ + static bool init = false; + if(!init) + { + init = true; + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + if(kernel32 != NULL) + { + _GetEnabledXStateFeatures = (PGETENABLEDXSTATEFEATURES)GetProcAddress(kernel32, "GetEnabledXStateFeatures"); + _InitializeContext = (PINITIALIZECONTEXT)GetProcAddress(kernel32, "InitializeContext"); + _GetXStateFeaturesMask = (PGETXSTATEFEATURESMASK)GetProcAddress(kernel32, "GetXStateFeaturesMask"); + _LocateXStateFeature = (LOCATEXSTATEFEATURE)GetProcAddress(kernel32, "LocateXStateFeature"); + _SetXStateFeaturesMask = (SETXSTATEFEATURESMASK)GetProcAddress(kernel32, "SetXStateFeaturesMask"); + } + } + return (_GetEnabledXStateFeatures != NULL && + _InitializeContext != NULL && + _GetXStateFeaturesMask != NULL && + _LocateXStateFeature != NULL && + _SetXStateFeaturesMask != NULL); +} \ No newline at end of file diff --git a/TitanEngineEmulator/Global.Engine.Context.h b/TitanEngineEmulator/Global.Engine.Context.h new file mode 100644 index 0000000..ac344b2 --- /dev/null +++ b/TitanEngineEmulator/Global.Engine.Context.h @@ -0,0 +1,33 @@ +#ifndef _GLOBAL_ENGINE_CONTEXT_H +#define _GLOBAL_ENGINE_CONTEXT_H + +#include "TitanEngine.h" + +#undef CONTEXT_XSTATE + +#if defined(_M_X64) +#define CONTEXT_XSTATE (0x00100040) +#else +#define CONTEXT_XSTATE (0x00010040) +#endif + +#define XSTATE_AVX (XSTATE_GSSE) +#define XSTATE_MASK_AVX (XSTATE_MASK_GSSE) + +typedef DWORD64(WINAPI* PGETENABLEDXSTATEFEATURES)(); +typedef BOOL (WINAPI* PINITIALIZECONTEXT)(PVOID Buffer, DWORD ContextFlags, PCONTEXT* Context, PDWORD ContextLength); +typedef BOOL (WINAPI* PGETXSTATEFEATURESMASK)(PCONTEXT Context, PDWORD64 FeatureMask); +typedef PVOID(WINAPI* LOCATEXSTATEFEATURE)(PCONTEXT Context, DWORD FeatureId, PDWORD Length); +typedef BOOL (WINAPI* SETXSTATEFEATURESMASK)(PCONTEXT Context, DWORD64 FeatureMask); + +extern PGETENABLEDXSTATEFEATURES _GetEnabledXStateFeatures; +extern PINITIALIZECONTEXT _InitializeContext; +extern PGETXSTATEFEATURESMASK _GetXStateFeaturesMask; +extern LOCATEXSTATEFEATURE _LocateXStateFeature; +extern SETXSTATEFEATURESMASK _SetXStateFeaturesMask; + +bool _SetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool AVX_PRIORITY); +bool _GetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool avx); +bool InitXState(void); + +#endif //_GLOBAL_ENGINE_CONTEXT_H \ No newline at end of file diff --git a/TitanEngineEmulator/Hider.h b/TitanEngineEmulator/Hider.h new file mode 100644 index 0000000..c6dc8d9 --- /dev/null +++ b/TitanEngineEmulator/Hider.h @@ -0,0 +1,203 @@ +#pragma once + +#include "ntdll.h" +#include "PEB.h" + +#ifdef _WIN64 +#pragma comment(lib, "ntdll_x64.lib") +#else +#pragma comment(lib, "ntdll_x86.lib") +#endif + +//Quote from The Ultimate Anti-Debugging Reference by Peter Ferrie +//Flags field exists at offset 0x0C in the heap on the 32-bit versions of Windows NT, Windows 2000, and Windows XP; and at offset 0x40 on the 32-bit versions of Windows Vista and later. +//Flags field exists at offset 0x14 in the heap on the 64-bit versions of Windows XP, and at offset 0x70 in the heap on the 64-bit versions of Windows Vista and later. +//ForceFlags field exists at offset 0x10 in the heap on the 32-bit versions of Windows NT, Windows 2000, and Windows XP; and at offset 0x44 on the 32-bit versions of Windows Vista and later. +//ForceFlags field exists at offset 0x18 in the heap on the 64-bit versions of Windows XP, and at offset 0x74 in the heap on the 64-bit versions of Windows Vista and later. + +static bool +IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; +} + +static bool +IsWindowsVistaOrGreater() +{ + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0); +} + +static int getHeapFlagsOffset(bool x64) +{ + if (x64) //x64 offsets + { + if (IsWindowsVistaOrGreater()) + { + return 0x70; + } + else + { + return 0x14; + } + } + else //x86 offsets + { + if (IsWindowsVistaOrGreater()) + { + return 0x40; + } + else + { + return 0x0C; + } + } +} + +static int getHeapForceFlagsOffset(bool x64) +{ + if (x64) //x64 offsets + { + if (IsWindowsVistaOrGreater()) + { + return 0x74; + } + else + { + return 0x18; + } + } + else //x86 offsets + { + if (IsWindowsVistaOrGreater()) + { + return 0x44; + } + else + { + return 0x10; + } + } +} + +static void* GetPEBLocation_(HANDLE hProcess) +{ + ULONG RequiredLen = 0; + void* PebAddress = 0; + PROCESS_BASIC_INFORMATION myProcessBasicInformation[5] = { 0 }; + + if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, sizeof(PROCESS_BASIC_INFORMATION), &RequiredLen) == 0) + { + PebAddress = (void*)myProcessBasicInformation->PebBaseAddress; + } + else + { + if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, RequiredLen, &RequiredLen) == 0) + { + PebAddress = (void*)myProcessBasicInformation->PebBaseAddress; + } + } + + return PebAddress; +} + +static bool PebPatchHeapFlags(PEB_CURRENT* peb, HANDLE hProcess) +{ +#ifdef _WIN64 + const auto is_x64 = true; +#else + const auto is_x64 = false; +#endif + + std::vector heaps; + heaps.resize(peb->NumberOfHeaps); + + if (ReadProcessMemory(hProcess, (PVOID)peb->ProcessHeaps, (PVOID)heaps.data(), heaps.size() * sizeof(PVOID), nullptr) == FALSE) + return false; + + std::basic_string heap; + heap.resize(0x100); // hacky + for (DWORD i = 0; i < peb->NumberOfHeaps; i++) + { + if (ReadProcessMemory(hProcess, heaps[i], (PVOID)heap.data(), heap.size(), nullptr) == FALSE) + return false; + + auto flags = (DWORD *)(heap.data() + getHeapFlagsOffset(is_x64)); + auto force_flags = (DWORD *)(heap.data() + getHeapForceFlagsOffset(is_x64)); + + if (i == 0) + { + // Default heap. + *flags &= HEAP_GROWABLE; + } + else + { + // Flags from RtlCreateHeap/HeapCreate. + *flags &= (HEAP_GROWABLE | HEAP_GENERATE_EXCEPTIONS | HEAP_NO_SERIALIZE | HEAP_CREATE_ENABLE_EXECUTE); + } + + *force_flags = 0; + + if (WriteProcessMemory(hProcess, heaps[i], (PVOID)heap.data(), heap.size(), nullptr) == FALSE) + return false; + } + + return true; +} + +static bool FixPebInProcess(HANDLE hProcess) +{ + PEB_CURRENT myPEB = { 0 }; + SIZE_T ueNumberOfBytesRead = 0; + void* heapFlagsAddress = 0; + DWORD heapFlags = 0; + void* heapForceFlagsAddress = 0; + DWORD heapForceFlags = 0; + + void* AddressOfPEB = GetPEBLocation(hProcess); + + if (!AddressOfPEB) + return false; + + if (ReadProcessMemory(hProcess, AddressOfPEB, (void*)&myPEB, sizeof(PEB_CURRENT), &ueNumberOfBytesRead)) + { + myPEB.BeingDebugged = FALSE; + myPEB.NtGlobalFlag &= ~0x70; + +#ifdef _WIN64 + heapFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapFlagsOffset(true)); + heapForceFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapForceFlagsOffset(true)); +#else + heapFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapFlagsOffset(false)); + heapForceFlagsAddress = (void*)((LONG_PTR)myPEB.ProcessHeap + getHeapForceFlagsOffset(false)); +#endif //_WIN64 + + ReadProcessMemory(hProcess, heapFlagsAddress, &heapFlags, sizeof(DWORD), 0); + ReadProcessMemory(hProcess, heapForceFlagsAddress, &heapForceFlags, sizeof(DWORD), 0); + + heapFlags &= HEAP_GROWABLE; + heapForceFlags = 0; + + WriteProcessMemory(hProcess, heapFlagsAddress, &heapFlags, sizeof(DWORD), 0); + WriteProcessMemory(hProcess, heapForceFlagsAddress, &heapForceFlags, sizeof(DWORD), 0); + + PebPatchHeapFlags(&myPEB, hProcess); + + if (WriteProcessMemory(hProcess, AddressOfPEB, (void*)&myPEB, sizeof(PEB_CURRENT), &ueNumberOfBytesRead)) + { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/TitanEngineEmulator/LibraryLoader/x32/LibraryLoader.exe b/TitanEngineEmulator/LibraryLoader/x32/LibraryLoader.exe new file mode 100644 index 0000000000000000000000000000000000000000..6e69fcf0ac2c7c58a9b8299f2946db1f38982f20 GIT binary patch literal 37624 zcmeFa3qVxW_CJ1R7+}=FK?TKpG%6P6YlZ=tVL%6Yhz2@}f~Y9O!+{V8W>CuqIADy& zDJ$)9d&$LX_PTk?S7ipGpk;+=W@%k2vV%j3X^Kgn-)EgO18DWV{l4G-_ul*af4^s1 zXYalC-fOSD_TFo+z4pNp7i?lY7>1Dnc%EVQA*9DBy#M1DhwQ$$ztxx7)$4;1`y>e; zjF@ZAEm0K}7cMQ%SfN?S>VK|svk-n<`4Yk{L!FgR3FTH+o)++*Rz53)-wNTJ+)Ojk_&ttI zu?&+Skuh7MZ=dHb)67UEeI&gZCJq<^Jj?py`Io&Q0(36|^?7Ua)~ z!Hf8!e$rs~cvR@^gyO6WO9sO{bUQNA0YEY{06iWf1TsM^#?%f)Mj1c>$OH6vjOfaQ z;_UpwOol1)M#ffv98e1A@faCqYLDdqM*js8n8&u2x5+WVt7ThQ?#Kc2rAEO{>rIGn>FT#|=PlujoVfKLaX#l9Tf! z;3fzy?n_(%0q9W?Ysi}A3&OsoC$vZP zJ2^y^Tk7nIE!;h5qO#U1&l}36p?|5|seUemFP9)7%K9>l-P*}%vA`;8SIYB-aOW{7 zsl0z+x{;~b2i~}{J`7W%Lch4Tp~Zw!a_$h7;zF?A1Vk_H4MOBexD8T-h6anbiQ7u3 z>a2%A$c&eX4_$=}=Mjb&;g zgiuyvMCgc>*CbH7vbmGz8$GKaGYNjxlvxvjRHLUF?_M&dw$U?0$aO6Vd*x|Vsg{?4 znaZ<>^8OJt0A(lyp~j&{sd)_VuLIJo&VUsCLlCt^QUL}dEa65{88gx5{ng0Bgdd0C zXuTi>Hdf9u<+{w0WzE*RY%6>{tbwf2FH+_C*kFr)oO4uB~+{ zG0lD1zLiHUQvSHDL1wTjirW^u7_dd|#5`s!gUu9jQ-+{HFx^|=hft~9(%Z011JDPv z3@@&l*hLxZ+~gE+ydPjtAeHSfG?XY=Z;+AlO}0jv;YRUATjVZkXUSsA z5HrQNPf&?kQsgmtQW`$OLZtP1Ds#~a6lhUmTa$BI5`itd6M06xX9bprvf?y!Mok%_ z8)AL`yb7UhwSrMrZUveT-2pmmXad;bPqC-%X-$=9EM`7*8_+;>*jJylr=7L0K5e@o zL)>t^_-^iNtX;jg<$EM1^I=qt`v@qzH-AE;M<_Q5ecD;8r#|+yr7zJ*YqH0l7O1jm zP0p7aV_Swo2IZ?;R0vA?v9T>IO#%``Y>Ui!t}(VbP$3SrA;O=)N1xj2iTBb_DU?5dg*RNp;920U$^UDp2!_B$S$GwfVj)BEL z&>*t1!!1C$+AVME1d-|<`x|CvN;3B*=&&8yGpY7>WN6cHU0IQjf_Wa?gkd!59TH`Q zw}==<2uwO3TO?Bk)uF8T0sNHTAj?ZxF$smtL+`~D_48nucdIZZQn&|UAffM-waQAe zsm(GJI`9^>Ig@(%xtvj4*s@R-T;yIMu!;%jr!WoK1P*nx6T&# z61oQSO4J#%*+kzYZEdpMoBffUr)ZQA5q+%O(u+-#+hXN1`vBO$Ws^r*eYLgv*v>ab zDmT~00p*O~y(>Sq$TxUZNvGQ9DK-pO?x{D$ait^C3G`q9?@b$lr8j2C1}{EVQ6-5( z18A<|j3wB%x|6ZKSaw}y@hrRE%lbSh$r`5e4DBuGrm~_HY;5p~P+4RVy{ukw&Py|$ z-v}8^$gr)JGhi@QWYCwFXwO7zk2(1+u#*oXU17aEr?nu-n`wO*vYU<57m1RmvJ;qHiDmTB0yB9#YfO(;By zdt`*zNuLc~=$_a)-n+Y}P9LtoRm3BX_eRz{MO|;3)PvkwAP68OL_PP=;CKA*`2$Jy?fIGlMbqwDyYO zOREfJmgnfT7GGPfG)J$tcDTa!6gDyo<&u+=$JQqw?=YH@l9TPRm-$cau`pdMIurLG zY^qfB2&uc0lX+{0iML)xDgINabOnz?!7*BT=xh@2pH6LJcJ^|`wY5tYce9m{?1u&{01~hqK;(9&cY|>R656WxiEJp9x78XQrt9SmM8I&W_=BXB9DU+BWkp}CD>jxh)F~@G zC>mg^m7z}_^#`aV_4UhDQua&zFluh~z3*;AKpb)(Qr~Dn&9-?`p2|@r)bKXcm1tZ% z+0s3jvh>@_Z0TA?)pDE^&Utx+R8psf%62cv zeFIY!Co^w_wdjabpjMGkOIfiWtCo+$dhL)+<1v)iS3RUX!(Jj1g4kFuYP%1$?Nv5Y zR_45~Ax>6adB*CQcV)h_wi8 zQ87Au**ZL|GDPLS;>j`&{5X44u|_Yf3qIO*-9x$2j2JJBIq#nj<}rU3EK6JmZ z#>s-liNjrBBPAOa*H@cRo+LLAXVybqC1KzCP!3;B^eDeCHNJ=gG~R zz#=Ed(bU`ydyV&}QxdGf#<)c$q-{Ca@cC)>HGcLrMGfnZK)BHj>+i<&jc$xHt8AE| zKAZ~ES2z;zrDooLGYGhjx!h!w&r@(bno6~=?qp*X_PBg3S$TEU1}v<&e7-cFTLQ~1 z)m;OI9m5@fbykmN!3W55bf~TenZFAW2npl86|L~0)I+v|J4A!mB#3LK*-wf#CEQau zdO?GwR$j4$Lui65LTh`-2QxV!4`;Lv!C>P3p8-YNOc`(Ya`sB*R*?m-y@HmW1bzug z8Vz%6(U8!NJkOWQzYJ50av;WiB*r>44W7x|>Oeo(xIi=l=fXrH#>R~kQzOJwh?p86 zrXs|YriM;D(8DKBebm>!{?dy|@tUD_CjuFtF%gZXTD9 zrNf^xt0=75M+0}t>?b6I^>E6p5E8&X~X9rr<9z}1q)YTlJ zPSn*OqsN>++Q?+_q39yR#svD>^8 z;7j{}4?9!l@sU=c;|xx$yO|0$PA;a$QaVBTRZd|iLthTCt&vGAJ}LZ(6waW)Xx{-%+#oN%QC4nbR-F|}E@8buw-=SP zdYJeV-TCJhFBFgMnAm*CVIs@>??V&t!70qlN=P6b>zlUY)h#%}45L+R4d6~g59JN# zM*iS-0R&@lq^3gSIzj}7^}{VPw$ZsPFMyj2uE-&H-Kyk1#-`uM06`rx<@M&~BbsVY z>(mdojum8qtwWT9bx;bI*9W=Wvt5vW%-hV&(?DmJEN{#9X=;c6q1<`99UM@!# za#^1?Y=FsnlMKu1;=NZC9x&o-6^4f5wtJ5%JQC%LVI_M~HryI%I298O-k)?Xxi(cK z)?TC@c}Nv*NILUg@3Z!9i$7bRCruq&XBi2Q0}!`HbE~gn{=sW`8oQPsofhUo`y3z^ ztLf#O$1h*Ro)GUa97S^0%Q>5^w{c1wcsVc2hi5R!XHfw8btDE6~IR&gfw%O_p=g(dNz;x>cz zX+gw8Na_>Mt{%)Lo`pc!ltuQKesVtctn&*l{3jmfl6&C&T{LdzmuDsM^O{qggCR^7 zo?YC6z%9yf!?7q+>xI0V*@9oz%@d9gP4;P7+FEYkHB2BuJ<%ixffTw&y+(I)GNL;v z-k}KUOuW1U6DhXqyu~|-dkF)bYWoXac`$hij>;B9@)#4RgUWCSHzDHXhNDsqR&huc zFF5R#%}ClEXX(A=G`*iaN$=fF^nT|U-Z>k$(8XNNMn{_vJbOe4Dh~<4UuuQm)%^&b zdAPD20UIlGB+BfL%ZN7$?a}>78~*3q=wYb_cPG{S6pI?3HbJi&rjrFn-0W z4u^0jgXs~1>4Ej+i`l7gb$T~6W-9p==|1qsZ^*3(J(wpsFbMZA$$3)lFeVH7isQN( zwLxSY0@s}dA4;MXrycZ`M;hPETLUold8;p)AV;*|eRjK*8o?Vpt=;2h>>e3DloDOE zl&H)%(Ye+W`ZcXy@Ke)ngyF_vT=oHJP0N5N+qLn<7aj56+phIn-Z#%@K6$f0s6bv? zy=M%X%j=tJI5MPmRGqC>o>tGDy#yCat<`HEw+>4jIFXmxNw6$w@uaw~5X1`vx{~O` zeFE-UmFvneh1nZpuREs6Y#p*^RX}80S%5*h{9|nZDzQFZ8Gj#j05eD7$Ca9 zfcK=uy-?j6It2ymxzQ*oiipbYg+Rq?9^kmB$m*!ZC0bJ!F` zdk$8Q>nd1cfujdCqar-&%5}mG@CEJ1t~=oP0OjU`?7=j*ElBpJdxf*{5gtdBd!zi4 zl9hY!^U{8t;)sz4;P^I#U9Di_6^3iYz1aD3kI!%`)O)I}HVD2IPLS12E(o@W zExQeuy)jto*r+(`YV$KVsEHpau%|1}C@Xv=SW}V0DQ^?9lE{mvtoRw+hzBVgvdm?a zQA0%dPbr-*q~p8NCLx{RPS?yu48!1S)`=8Rr^WV?4W zBPO`%sOPc=cjML%MkQ%pa41}IjE7prc|DY@ecrzoI~W)WNah9*kL{u-%*55BVyfM% z%WR$}$N2Rat8bAn_H7Pg7%MF+^h{PIb$*RuY2P5PQg|6Fj?(nFgvrqlR}|c9RLj+) z2ATmb#^@A9yTu;3dvLjh;&s=tMni;_;Dg#DPej?>2~EPs%*I+5D=ZD89n(#FXwgKV6SMGQPGzwc{Di|sBG5ZB(}|^;3fZTb zS)nwlp=eJxJ6QqFhgfS18h8skaF63Qd$A!-K^h~IivX+kI5``iZ|}>-o9#1YY+r+q z;MwiZ`UpLUFS2@b55sW9O-`K5;3+US5MH*X^^*i5->42ON#A}9*mjT_eCjh+<6Xh5+wVG8TgIxVsAbJ;SA zesMuKqht^NVzPax*7Uf9sL;;6C$^N>;QiNk_D+Db-S4>D^&hD~e%nR}0_ z?bc?;-_mKW;6u!@8{)`R9HZPbS-Gdy36V8#C`&-5JjO5#)Fjgk9i`{l)pA>dls+qH z!u;X!$zp>Xo&^~w6t}XC;H@!Q4$c|wYb>5TFMG5Cha87}E(k5iVKq5+HFk1rhcm1f z*-M-W)3tRMbVFH!$1s`EP*-||$gkn?IhYzkcSH|=^9HDu$>qlB)@y9EjN1UZplVN& zJ3Z`h2aBpx)R+&D#&o`*52bmctegwNw3WEOks1Aoupa%)TWLK;V}9Ebn|Utj9F4Ah z^;vEk3Y8R{qK!UPv`XWEe6tF?VezupFa51Iv#1%=EmFH3I ziYF~GiL&wwB(PA=LnDZqAX-+FkN&aI@|q$cRMc1ylKZB`*5SF@H-}A#_J!FIn+~+m ziBk`9B5Chf?Xw{vU}#m9v63=c#SB_etD!doNYPbQ#jmHlbG)EDy4W5dD3vb8H^Qxz zY9B@m9`?BVqzDl)PBoa#B9~&lb2RPYs`Be%caykes>Ru%(?t8lDYIoa;}qM>UH=aH z&i(03|GZzgWQ<*EqbGgH0ItwEmxr2)$*|G2%l+o86_=rMK8Lm$caB8MwFZ@k^!i=*W@^u+4Y1qRPB@mB8e`=+kTc~EbQr6kxqGo5E)uo}z)Ek- zXm`5amrs;iLHZ=hdlE@BgrmKS9byAkZG{aP#2QVfe;HdiZZ-U2%?U@vO zOHvrQ6EoLhX4jL%fLN>#ur6TNlgN-7gcMx4GvEy5_fReFah#FhvRF@j?k+Hwj>e+=<=%kUc8XyQH=P))AXA*A>{ zwMcPqB8t&x*OUAs<7u>p=$|B>h~55heDixKi`#^Psi}@NU1Pge9NT`ZH@NQuCg4%M zmkQ~ZbXxke(`4P~xrDYg3Pat9`%g;VG{!J!Ee0HuF)KonS+(J+B`C$N!l z5_WE&FRlXp;06eoH^(%4ws~t**BxQCtPHy9kUb_)o@E~mdD5++8lD=I9;Y457AdgT zLj-dJLu^0au*eKQmQHYllsXo@;mh{Hjh5{J8Dl*rY-!MdTrsA`o1`HqQGtYWXV$}> zL>3=XL$dYci?|vkqeZxn%2~8%4_1v_dxZ1*h{w6f9KaVT_#$7v$d8*Wr~pNpPzK$w zOZUORXX@vHjGP_A-#3)MFMz)|LZuur^Dhn&t7Ef z@M*zEf`yOVqsZs^b1=^#q&#MhIc}8O8`-zJ@llLkh$`K7o&Wdpu|@1%3hgj1C%v ztz(aSG#GA_$_*XLbz32j@@r-2VDV5}WR-CHI3Ok2_MOspPHDKYc!|}sRSWK~Hj)sm zes!%<+t(iWX0v$ArN8ijTYO`%4K=<+nYLD0vBK8u;c#@V;*X;$2{DEkVD8JY(v z5Qn?0!=mYQtkQS7#vrhJ419jM^p{Sw`${ZY;jexMNiRYawA^EIO&pW4G$uR23rbMo zO|^)(02D}Ui1QeAylR?7lcu$>QQo2k8I8&O94LGKXYB!DB@?y-sUfCPGsg^)&XdyE76hB6}Vrm=JTjj%%d z+cai9n3dN=%odj=7E878H9U_<<3(% zG_a%|Z&|mG2#!Hd=n={}ghf)7bBg4~XyvoJ5Pkx02c(2=eKBHVnq>}+0Mn4DsZ zB>xg!C)fD5iy8y&W5MNR z@p3om^bwqtKuBN=bX_@<*Uj1o-X!i*+J)2bNs?88)_fpOE{m*o+?OY&IMo6N_!_l` zd#=ST54SLk$s8j#58uFz=F<0*xpr*HsoY#(-Hg!g!bJno?a#YJh2V7jnj3o){)~vn zb^d$q!*#W=0n^_pvhxCj?}ZH~*4)m<`myg#*9G7&5Df2Iuh@?A?7^{h@rHw|B}RK} zfIY24pB8BCgEfM0@^NO54a9fp;2uZcw4fxMd;*kPYH`o!kR6slIJnXZgqe&}RXaW& zpg#(j1N53`ah$H=d&}Z5N6Zv?Bm=kD@&VkrUJSz?f}J$9kvRr?&DqbyRgujDRIf&c zmK3h$@RctPeQB5VX_qapq~M!g@otg+Dh935sks?#v!NUqOG*+8;Jl#VPtoVH_2QRV zZ&H($iN5$$IaWo;S5QYE+o628Mr3?w&x)-N5yjQ`1Y*t99?VZ7<_&er2XfcGnR&JY)*1dt#xe!pL7|Cufq#)b=#G&|N%}OERhE?G^unQu zw+^LZ5NsgtpN0OQ2Pxbpa_^uX_aGwhUZgs*TVOiigsc64i)-k*2(Wb>ljt)K-Wr6< zDx&0~+kGJm6Pe7(LxI0_GMYWRd#(r*MVJLcd(MKP-LpVNv%pW71-{+0U?|N3Oaa{T zIoGakP+_H^pDyycKhfjUXz_P`4OdxzvhSIaa5&=SPh z4^zSftb+F+DoQPhTY$o%u-dwWHTdvv2m#d5C(AZ6i(uzgrtho3t=Fq5yBvDj1Z3FMhKyr z8!Ut&Tt6Wk%JmXL9VZdO2<{qGy6upWFEw&MAj+4+qCATL87lddnVOxagA&Pz`M2}GPLm`UA%5e3F3M@BKYPXO<2UYGRA$ppk&kNDB z6x}RDn<;7+qAe6%BSblhmI%={ikgL}aAZsqqV0s8BSbG#bea(DplE~;?WCwih?3WX zyF-Y|C^|%ll536gMO0u}K`E(_L3gCwRj7V`j~_*8!{qk_Q1mMyI+UWPgs6(5?+ejD ziq;CzAd2o5qT?v~k`Pr>^m!o~Leb4aR7X*}5RIVd8X;<=Xo(Pwp{QAi##1y6QGw+I zO3e~7Ocad~qV!D^rx&90DLO%jE~4ltA(~Fn07U7pV_kj+aoJf?Suca9;BbJ+^%HG( zf-8F02ACGWd?RAy2G4yi%yAK;Hh4C>Fz*VO(hx&vi-1YyUM0-U4Jj+gE62tu44(U4 z^&S){`5HWHT^P7TseM0#=OGs+Q^W)qJde0Avqj8MgXb|9W{QYW89bX@nDHVe(BOH} zg&87ZbOuk(h3O??A`G5wF3eSYtb^8#2G4dE=35aHWAMD}!kiE>@dnQh7p6|YlqMKL zI|WQK_Znf)d%dK6iLh;uKo?fT@{JvrY3; z(^410BfvM+r?3sSX8h^4va*gEK5qMJZQXfqOSaR?@iY)jjg!nRJHvXx_LUMJE!WYZ!G|4aALPJ=+*;Xm>m~LG9X5K=c{Fa? zbnDgmi{a^e3!W8bY}45HX0r#~1qN0P5(?CDH<5_9UP^841ND>FoA+AQo?Jf;ht1gLSy9RXROz_C&74G2J&F_U_k8 z%TV^9_DtP*sYPxd?3gZ_fxkFw_ON(W4UUSp{=9I0J$w4<8C$*YSbW|)hdqG<7xE-g zGps+?IjIgP$?mpxENGpFzKeaOH}y<579X5lZ5nliJv5qu0c1E*+?%aS-HflxWr6kM z=@a`(#@Tv|AAK6~`rqgOB? z1%v};0@48%z&gOAfad^z1sno=4!8{Hy&CNRCIJ=$@&RiAj{~*?UI)|yJ_MWwv;&wm z3^NdL2VgQF0gwYI1#ANB0Mr7G0Zsy%0p|f%0CMo{2T%dV0m1>r0D7LJKHTzDO)xTd zM;e*ilJIT80OKvW-AVdaLaG4Kg~hQeaxJFf!p!WF68yVSObL_86f(t3HhvSBEG8d6 zw8>bR0>;ARqC_^M0uNz8O+CNu3fau_BCwGx>q zp~SCi;@^E@CNXM8iyzS%4;oS6#sWT;F+~VffI_^j;Gg(Q1P9Z>i%RHCmVnCUF6=fg~f+qWoMTCj<3znfqPk3W4v< z%*8i|B7G{Cg?OfrpY3W>h1RSBeacs%-9n*N;-?5dqA1qLMNR?6L51E>FGZ@$fGrWy zRPtW%Ch~4UyVQFcy%JEw6$4U*Ob*^ekH*s7vkakSx17ccaw1vgqD(e;Rxv@$L}2ql zZ46R5pjUu2NrQMI49x(NX9nQck}42*q?tpt#JM8AyKBY>JSBnx@ijphU7ASl@eb}9 z?*dSu-ejX3Noy(kszM7zsGBMDI>*&UK0>j#BnzS|<}5==H}~%O>TXBV)y{9vE247q zcoS{6yraSIT%>7SNcLimC}ENn@i*bWWnSFc4|P{R+&sDE_FFy3x<%f>jIOIbjdq5x zD#ZTK>iR7jDq+siymj-^J$}JVSQkAvAAfB8mZPoTZt>S+*t5M!|B3c!CD9uF&$JiX z)m{&_OQv3DFaj(U0iHY+tm) z(LIXYt-05ihS7Jm_gmv#h*72S616C;=R!d{ke1AbP9!arEl{8_phxV7=9YOx+z=(w z1EOvrJw`TWsovyIqJDtkbj1tyF?vu??0sfpP4T=x0EHwJHsUf zcmKQBAxWRKf(81N^r#u-Y3I2Iy>ZJ{)XXFq_ZW$E)IG-MkIx69D$YTg7qnl~&Mi`Q z)9qg8f1GXxda)Gpc2g|r;?_OxYNlIX=U`k41=*0+puMyRX^N8;r`?HmI#DvTdx+Xy ztV6S_TNX4Qq=2$qd1mKy29RHh2y0Ky{-KvNWQHKvlx<{&A~Yf#PI!d3BhY0T!71ov8krFYn=^1XhOidlN`%`G z-jA>fVJE^eggnA@go(o1p#727Opeg!WZ~(r(V&0G~+Y>hnrN};Wmr6oD z+0;EtMPZiG+NZseo*9Uf&FPMlZl)(nn44rn5|1>8GokV5pKHw)W;M-z_q?8gcme3R zF(SWi9vPenh%bC+CGbPEi3fMvVp!m6hd2@2cDL(plgd#$M2B`p+NDWni@e7nmD?jd z7u-^bk{-Aew4VhEWK+9q&PA!7=|sqnEPXfKB;?mB zWS(B$@?O3BD17=Vef#wv;O9SZP{82Zh727x{C3rdk)r}f-*IQqn7hV~8$V&9S`(}d z37r(C(}x=(A}3EVX55pRm7TNHoSS!V{<4C?qWg+VEY{NHD^}jWa8cUgCF#GP|G=u% zYu5g&`~RXwS7qx&hB)`c0C*D&LbUXz#exW z+YrAD+;2VqlkbI6JQ6dCl5?|{&&r9*&Cg~|$>tR-y9qPJBcU)OD(Zs!#iruy5_F7-mAd=_QCV3);q(fd&#SZd=-h?>D~zcSy+yO5 zJyVKvE!l$Pv#55GCCep5#;=NX(nxWT75y_uDAnbyX`qzg!?^$sAx(H|?#<4cUXWRwO(I~J66y(b!^yd~~ zGt5T<2SS&b*JVlh**H%Z3w<(?i1EjxXgMm{#4ush=B(I6iucCch5T}}qG{s6-^2vS z=2-IQ6)cA{i#aK-D2y$`yf4H>(SIz&=VXhi@g51$SncAh!Yo|s(KVOm7P==GlPmO8 zn0Yh@P)>%nT}!gCI1ZAt7GsHu>*u#L@qaIlj9&W3@BTLU-56>K+aO;iUP=F_8m9X6 z^sG#6(BlrpRCoFx;eRjx_vrjyc{ko2y6gWdL*j-0Z8Y-J6L`2n@*9)y-4oyg@B)w@ z*BhV!kpGxM!cj;=MtS6WcIOdYs!JjM3`(Hy>%x)0zrPEp-4QN)C_+j{xzf?DG!~6; zM=-~QPjZEr&;tEbgw!^+E}_k32vvY00NoKl)d;*&KZu_)0P$Sz3fCc|zSsbiUjZQA z;rqtz556gJrK=GVf7<}m-U|RKw--Qt`Vc_*p8_a9074=85RhEHh%^2nK+%7~a&a`nr|tM!3dh``^V#^VYYd}V zBXSn)E>VQL+(IMM0k{lk2ebh=Kr`Sppb2mYun({kunn*k@C@Kdz-GWEz(zn7paM_^ zSOr)CumJJ_>45nF6M)Lc1B?J2U>qP25CBjB;&upYz1rrlmUtWivaNe9Uu@e6yOI?0Azs9W#~7c4bTEO3pfcl1~>%R1=tGM45$K7 z`Bi`-Ksq1+5CI4R_yH6E8K6BM`~h|W=#dET|L{wV-@Xz%X%xcws&fF#%f8Y}Rpj{gYOx}H~1eih399uQB&OYah+(5wpo zqctJ&Kjnp8hW<*Xw_2s1p-xt(sf*O>)tl8%t6xz6RlQezN_}3f)C|>((S&NEG}APx znhZ_8<_*n0%|T74MiML!?i*|iwg*2Pye0VQ;OgKV!LJ4H34SN|Q1JV~9|wOP{B`j8 z;48s=uuR)q>!%&24bp~a_1fv$ncBJ91==F*a{R*s4cZgh&$M4_TeW>c%0j9_o(|a_ z@^Z*KA&nu&Lb#A0L*$_YLdS>dLXDxO(B#mip<6>=3T+DgH1uL(9ifM{v%C;$c zrnF4Kq)@@eg}l7gKI#$bJJi$E3F>8Pi@FR_dsO|Tx*BrZsot+XsD4k~q&}$DGQAJ%TsKBN7! z_C@V$+P&HX+IsE#+LPL5?YG+V+IH<_?ax|X>lxBJWN^sDkkF9Gkf|XvLS}`ege(fl z3@Hv-9>UF$RWt?a)=~!cxZ8G!lb#A@+Unw>B~tSlZJ!^g@uGggvErJ!d8Zr zhiweo5_T-?fJzem4L|ET^6 z{WJP%{onM>`g8gp^r7M5;g;}~;bq~K;Tyvr34cDEG4wJh4Nn-h8+IES4KGCeE#g2# zXM{(jZ{)zpS&@q(?}^+Hxjph|$Y3ll`XLHbph% z^pr2AT$q9nmzg6FIZ66Xbs%Qn9L;=sHikz>%OTJ+{qnlnaxLyy2yy;}W* zdWZTe^=OSo6A^3;elNH)*iSn|I|}P!oK~$()vnb(tKFvEp?yWWQ@cyMPrD!Mqj!jJ zh+jxR$k32=A&!t8A+Lq(3fUL3Kje!L?@&EfMNDXXXad&8+|c=AEuAle*V+*L9oqS>a!XOALQ9R8Dzf%8OIp zp7OzzOH=Sc48Er%eg~^q2LWdRe$4+%J4+cwqRr@R0C`@R;y~@VViO!n4BjF?Uvl zS77GQL2(?&Wa-S9b31gqbWOUmy34u_U4UMvpRc#*SLmPA@6(^wqfi;nb6A7z!Iy(O zAVEee)5^69tuHi8d|13LL1)73ov&M@OV<&-d|eS{@(PS}nXUrVH|jQF#GlkXquZ+6 zhPGbO?L>R~bo+I+x+|(R0_UsrW%>$zm3|{;)n@eL8U0rM zHvJCFt$^^La9wyjB(VaLD1$7j!gq%63f~vLA9Lu5q;Z5Nu!<)lf!nyFa@b>V_ z;T_?fSYZ1=ONJqyV{%hHUnX`XtIXZEybL%=~FZDXvS@k-PLt8y? zH81VQ+*sOjaY5iW4L);MZB-pCEh3^WRwaGF&>Br2buTZEc`~m7Qd5Fvq*_IZ-U9~D zD}WQ%QmC4nooOy8%r9J;n_Z$xnh-TXbEkSVmFY8Jcyu=W@yk>cpwqb^{vD2h7_LP_|vmfZ)?)*Nd98S=)SumMjw8q;r&J5ls$UB=C$#R(YWg4sG!@& z%DLH1Y4)O#`MIS{rTrr&-0eN)j@AyzS54mrT?wrB|9ShS3QOf8+qk}mK3_3?bKbDP z^-r{ptZh()Hd}&UihNsgdd;x`397~|m9q{mE&JhUZu=?@w2f8O-wxzelEQ)HJ^;NU~Z1K@J-rk?=C9no$p`N>N^c;A~3ymI)1@890E|Lcmd zIO|n)Un-y^YBCS0cbR%1rAJd*q3-P^#|rZF^!AXeClda4SrClLz^x;cA7a-{ zPAVA~;3Btt`SKogqD@IX=O5N8=AVRl&(flMC-~@X5kHTee>i1)Xi}d|Uyk>D{h~J5 zIOK7DWbA|bzbe`Xe^}QP9CMpiQRa-)j$O4fWM)HoOvwD?xAZ>kEpK?d5@xNv-?`U# zdQkx3>?s-kWTCy5I%p@n^oePxZ+s z+vmUh)wg5P$}h@xw+zXeTCKY;@_~}9hTM}Q)sOECtd}nAw<99to(GmL{VHyK$|`Qd z^PAq*E&Ob^{k%ue8OPp*-phjDesJyCJ3gGSm^=0InRDBlz8);?e{JiT$g|Yc=NU5<-FvR5zud6LccYL`px&= z;5?;A(24tpCH47b)I0HC-y*Vq3-uPNQ3vZa8ZF+UdYhznC)MR;|Bd=BP(2b;5c+M1 zOTW>Tohm7JX#p;Cp?v?CqT73B$LS;A?En08+goqGd+L*vq^P~dw+1OrdZCP!7jlt2K>z?Olj6c8miv|5Au^AWI2A-Vmar(Qo z`s{^2&g?U`zAou!3aCZ8|gUZ`3 zmzOs#J1JX!ctp*Fq-)DRI{V_&^>yEVvUH+mf9*l@Gcz9l_V$aH;zRphY5CjX3lEls ze_f^sdG=?&tovGqR=@Q6N6ZV~gcV;izWQ9y7OBsNo4#3i*sbW2py+0)m9(e@MdzvZ zfJW+}>uMj^b!2Zr@(aD<|I|@D>(fsI$4#32f2r#vUg`nV!04M4oyWhqqSK3tE?BKr zPwtdc~Sqkmr}F0&&BQRoa??1&zib$<2#yBlALF`G1ZCn zrk}5N#P%Jw?$gd2+k<5#zueb$tV&t)sQhsGeCaQJat$Bsn=(DJ$uV?*pLxR5K7V?3 z=islO`*@Xpc~#-4UmmFFj9Iq(t$T|eotMA)Z>Ph4c`b1m=^)$pBQ^}VIBHJC_gnVA8vd7>sgh^+tJir(L(7eIX}IaC7Y6;kvX?>?@@}f` zmq)^H)o}j?8iZ^(om!_3QR{H1)^gWX5@|SN`a>La_NiX$Q_|vC9s-!||ab~tE zCc88{zpyB~Skq5fWioGtJOzIvet2ERa?K#MKUI+{`eqg87Ze_TI{#2*$-TMRnhEN0 zl;NQm{hPGLsbZ|y{0mgGmgeS{qP$8yyvqRYB^lT+OUO3Dyrt|3OYi%KhF=5olJ@e8 z&zfRdN+yl;`>4;Wt73nA!Xin2>W-%GYj#*~SHDnr*Lp?p#0!H0O0yh4J~;7(RLL`s z)+N1jPkq6(ZD*w4J@t;qr^BAVzvJa2&Ak(L#w|O!?vT&QpR)$m{e9EqjSo6?x&hl? zn%DHVw;r3>wC2rAe^*_e{l;9Md!F;`bI(#P>&OAk`CS|Rm!7+SLRxI#n)Ord*R|VL zop{CC@W|-Q>ynB2hb>!`GIsxGr(SG(w7<-%2;I4K&W_-Zj}+&G`g+aZcyPy^U%d3= z?(DBrmcNzTLq@KAa82)%FWw!zP0`!FZuH%U76;A!`;|V&Z>;{T?O^T1qGuneOWo=F z(b!-9^wQFchu{B2wSKf)A8vr8*56|I1(PPYT{afJk2|-H-a2aih~GBIN-{G_%o)Lg zO?IE#6oO5btGbmhdp|!aQ`GtWWdqfI<^3~VgpFPH=C91xyGbf4S&VwBTJGhwv|ND$ zpn6hIlTZdH-Dr1LU=j|du9U!#{k2W_oBPAfhdRMfG%0gy@7VRmOF?0~zx?Q>t0AxN zu7B(K(r-`onj9MT=liEGxLo(@xiJ-)?tPe%3qC~MQ6lXp%z{Z{^v zX>4v_pLaf5zT|G%k^KSQ{F}KWzTG*ha>n;Lk6cSgSkT&WZdYQ(z-MOlvn5N zY`E*j;;5v8Qx8~Pc`o+4G&VipgG*B$eLrf|cP%3Zk22o5KQrrYuLS1DmS-v#l_@73 z)~xfK164XxDv_wmxBMFzT2kNNQsq6I16xQnLKps6-N@bc zSkHY0hh5n^3G<*X{czruZ}{Z>mU~Ns51iT9_jS(kd~o~hyT^M@FMCsU@t5XBpSLbv zwDM|wGjk*A-Hk7udMHu*NZ*GhY|9 z;Pg(oeEU*5j zKYHnoTyDz2>IHXgId@Jue9h8BH4C0J->rJQ@0IFx9!i`w`gBXuOS4Yesa_}T5N6e5 z|LWt#@A!9m&Z%`0z52H94tQvMpm#)9RWDx&O`@)8;;B*6My^rA850(&)r2ni)g%fX z7MObD%QtwfqF`e|*w;VZwd?4l32`-l9aQ62aB2Vh52(xE??GOw(3HQeF5jmv->oix zUFJ8-kr3}SeQMqX{7dVfbgm5k&To9By8J11xoul{*}thQ-M%u{lGru1P5Rn;<=Ec) zn=dzw3OMHNpZSh_^An#ko0retA1%Ie|5q=qo%xV+?5YnOk6#@1(S;X2A3MXkzM=P5k=AN<$blzA zV;2tjKLMElX8+DxQ~Dh@o)>@U)6)$pQJ@$F9&(r(9-}Aoj_x+zkXV%(l z-_~Ax?X}mw980~Uic@kNr^4&?a@=M>dW_QVKmKV%bXec5Vcf>R{rxs8to!>-&Mzp@ z+w8@&>{)a4*;z$J#SZ<JW7HS4&MkksUSO_^8Oj8X(=3+vsle7PwqR# zm!^g5&jtGhaola7Ol4GaH^6WSH!27a?#FRz`oZIaTq6Py)`EK!kejjGmr2fxNLGjs zfY1@;Hay62qloBxYB;Wyg#S1o@zB6=R*3;$g1-nek&dx?)eM6jxup)oh^hD?7OCx2 zZ-+E-AW|qN7lEH`L7w4PITgL_g@&O_GoUUY!X@USjkw9*%SLK++~4UflqS zuB_2Mv0d2S>_;U(EH#FY-zRQ@;H;kKVyL+m;m&Z2P!eeozP)?32$@Oik=`5$R3WpqBO@dLAnw_RPchssp#AnxA2OP3wZ@*RO7S0RF;R3JWmE$#M zO-ijJMEj0O>1u4@ms^$f*7!XZVK?vEp}(_fG-}?58e&rgQvxp_;*jX}dcB)T?U5uF zEL`Qu%hUF)kb0X>3biccIA;W3TN|EtuGZ^y`u)0{uiYKq-xdiO+V*u-$; zGXfsP+lc%CBHk9ft}LVB+M& zQz%GOwI#h*PWrtD3CKE%3)+%yqogmv+*gTp5mfg4pe?;9i5f|~y4S4`gj-$u5-4vW zEAVb8O?+J|r$v#R_?a3%&xOb&FvN+wK-xm8B$_d)qk95`dhrbqt%CZ#WdLsV{Q$h6 zn+_m^PQtHv{({#lw7ShP`pRQY%D_{>cjBkHhtTR;5M$(=8jweWvQJFMZ%hKO*Mr(6sA<07QC|iPc+cdROkS|Y zWQYTKMo!~VXBy`)@pa~y1ms5nW>kh82Ew33=9pOCqpl&FKzv(~Kg}wjL|#B~@w>$% z369^d18J|`-{l%^*b|2&`+Z3q`Pv{#MC|+GLikG99y%RnIhcBUbV0Wo% zW^{EAO8o_X@?@e)*z0XTk@`EyPWkeRn@}iXbQ6#a^er0j-SiDcf)D?Mn32ut)a^$;%V^I)Jzk!a-GcAp8TyviOLUBwCWQXLJRU zG>2WS0j|--RGp(QGUid)Ls?~7jGFk}CQq85S9cre)TT~tmF9CZR5Ws$X|idGX{w1@ zZ%h#yh0o2xd8vQIG3b*j*XU=U@Ct2(0?J*dUQ7X0YF;w%exQ}dw3Qzr$P1zSo+IU$ zOG6ZMHi|aT&Zqr?+8K&vbJ?gB(O?i?{1deV~ zG-V(Ze>i@(*)vAbG+v6@x0PxDN!ZquNR+m*^%|xMj0_{9kzbl=s1UI$%V+hdcf+xY zudxUXNkNVfa93-0`|0?a?fi0Hi5PQ@hQ_NjKl1Lvm`Lgxy`&p3(P^jwrtB9d z0CW1tFA~m+E1{ z;#^1%I^yWTg0m!HI-}t~l|VzB-L{Z&h=mw)%ypVc15%o)til%-_njJxdv>J7U81+R z)59;bVX4A=O{y?dFsUrU{DjoFbE(4DR^dlpXj+x0tvrQdE!p?-7Wer2RCjU%FYHPh zsdbg3?HCBWXL?C{!~i*snoc-ReDEK{NcnNb4T!=2^ztKw$<1J1O>p{5brj)-{DC;N+O%T6ShGZ zg0(KZa<|i8D69)cYFT(1lkr z0Og5Qi1d-miHy`-dE#S44q>TjG17crdE(zX&?rqD?AK#6;CbTTHZ@ z%M+o|ZM3CCyS_Z}ZNGLJxuVV8%M&+(Ce&>uCbZ^lZi!prZ^W}S@1ZN*QcIeHNKk@B z1q$z$4T$&hSbp>( zk#9ugsL^oBu*%pgJ_EN8BUh*s4JStiQz{U{1*by4BA=1Pq1{Ohh+9 znq^6xBaL!TNA9wu(H@(^F__Q($u~5`wUpHzKzT}FxOw4YxTNBnFo-ba<9Sz|hR@!` zE03Ze=Oz@o424p8Y2=x@23Amy@vdv(GrfS`Q6R(wVB{eHj3Y-3mNB$C;$1m0`VRJ9 z`~(X=_zR&wqK8BUEAC6`ttM>1dU*lcE*Wr@@-YBxn5awj%!MOw*NU<;Q-#wOq1EKd zB`y92-mj^SoHsQ8up_|g_OrNlXyQ+mCG~ek#Me}wGQQJayQ~HyS`eEPP2Low^Ev{S zDwhQC-a73&@f34Fj6g>~6kR(fK&-LitfN=uDd#$4*&lj`KYjz9FHs?ZyT*CBW+m_4 z8-EsJ(pG#6F3OVh4s}^lpi_;k&8H>$UeHtFTEVK1iXrw)UVqu&X{Kb}+OnkS4wTW~ zd1J*MXLRLRM;Px(R+J_5M=`?D=3sK7D$k-FLd&71<_K&apnm^d0n^b2Vi|_4nbgBE zq4KO#GfM9mH!9GP41tW)eo!wCA4t;iq7^lcu%%#lyVQ8fD9fQa7*P<&cA`l#@NRFq zow|e70}n!~r>Prg)D*SF6%&J${-pFB`&?LnMJSdX%mHL;582&94?^{qVt|wCUl;(U zJfo)zK7R+Ei}2|#^=%I$-m4ov8qq?u6RCOk=&xW2Fg!@IoKwjQ$E6i9P2EPx!GvZ? zbB6dnO{Cd0`nG^mdiwH7r%R^u*;ZPvpZAtb&O)hXsiE%C1Hg(2U0!2$M|TC@ti+HdDPXucq_)$R02KTxdQ6KDukPMaWXMmj zYPA)s(Oz_No;gwP=tjN@umR4qZP-bxI|n8u?kGV6f9L|76zoEJ#w*}1gA_}2y;s)- z#q!xAI&vbpTH~dqBimk-N|3TsHmguaej?_WSb3uTG)Wrv!8}GnaSxiF&%hb->NbH6 z7or&v2(3nl4RSQKkoFoJv~{rn8v*3=Pv73r)-8yMl*euPNmzPvGKtA2P{!5_ggy7v zgUM^aWD2?14KPG(zL^aN3kJ;$yNrF9VE7OF-Jydil+;j?f>|bzcino{gQk9<_F zt)Pj{nyr7AgLSof@>QwI?-C=m*2tv2S`Vx%DeER=mbfPY-vdFB`OFt|kAlv7mJqkF zsmhPtx(A4oR9j-=J!y>LvYA85E@Rpd)PdoEE1BIb=tfd0FvUqn7A$G@7ox94*d+W> zd2HbVi)&Pj*O5=)4@Z{iYzn7v+(AI&46_I!G1!l}Niu0EeD=@0C-e-)+13@buoQN> zzU#}^wyOErr!siu4Yd|Q6~ntR?w7(idQ!ueq-ealFA^!YwxR%2n>QuITW9g^B-532 zVL?TT@V!O&iWf$|h(dYSs3flgo1&wV{%{19S(7wnO*BWF;E8*8nUgf-=ZV8Qe zbta#l^bed{w{9rjLmPxK}%dY}dy0It4g0W%*PJWVrxB&f_T2GkLZXOLf=JF=|E(rly)z4sC)cjv4y%_mq#?$ zc#YSsYz`2g>w=y>MkZ{mlosAjTR*XV2Jv1}7HOgH)Oh%C@qwQy4CjTm`XsOUItWVI zKwzCpn{Ji_+B;K=qfkCCjOSTsFbid{ke-ELVkl)77P7IBiiJv9=q&sqUJ%M4Hb(f4 z5mpevqwb2u5NsF<*$?`M)JNEbc>qF1FA3C3yqi|CsHb;}H_(ZbAoYs{pki;>KpFkT zWyn~COuW+9i#N0EzFu_1!O)9;kEWVNZ?j`Iie)!Z`ye#kK9ddXdaE z{K5o@+3B>=CG9!Nat&0ywtsHGgv2y6a=O(`E&!BzomV#%lu`aAJ>ixf0aZGtGi`xzaNg(D84xQ~fs*NfiqycjGE={s zO>AIh=J*eXy?lJo({TtV)j2}=b66j!S0cK3uC$i+>a7G}&F&?{&!cj~7F|Tg( z@OJzJa7vxvi`)x>6?qb@@yGbhXgD5F1{~!#$II1Ola5=pX7~rugHfC1+!0_m5L?J* zl>5Uq@H?`~&}f`h1dx{`ENLL6fxf7`hnlgMi%w+?q%`QOLsXUSS1^ip>{#TBmB%eD zPvN3V^E@)>t)77f)ER#^{+LC$=z372a9)EY1hdn-cuzD`yR}~3e3T6F zUxUTAt1jGiA!fmDCS_@K!nwdY3WqP$0CFy}w`0`~w{r)qcJr~0zJ_m|{9z0=$P>M4 zCdS4&UWntdNblvkU!!p7Fk^dleW@*&x`i-m%mBRG!+Gxk=SRqNKQdtuw~|1Du?6Dv z_v(HgM(HTcLFc#3Xu$F5xV+oPXYWS4ZluggIIw7%cc4KMneSYg$p|}CC-r>xXCGiJ zpv=*;%K@D|#Cn{@d2WjqGci!uUeK#QQ0H9WmDq0@^ko(;&lg<6P*lMYo4+L%*~DVv zpIIxdcmOuJjw7QCxnyj?E&(UPHj-J`%d$EL$+Kku+5|OcPNKed37_9so5pB{<2P)A&&+5d>|W#D5Gxx!@Tks+=1gbq!oX|cqh-! z!$*kM{YVW9InIhAGXYtJkci!(e_telR~dMXfmHygf)$1y_mV4T-+ zSSNGhS4y&!;=>{~(y@Tk;6QLPI-<&T zLF0&UT?lrBxGt!$SjO;*ZN9-Oe6R}ntSAzjdan}BA*HjcS$Jmy@VdqHOHyc7o5|;= z$V4I3G#bO~5GEbHpiTs?tWP@h!1a9g9%&>jUV#iSjP)AMyUjfri9*AXsQHK(b{gTzV4ouR^$RF`nj9~!wYd6suNQ{jCfRET-2+($-o>TIX{VTuLp&+ilS{>} z7#$U3TQJS5(+#E$`#X7~(w?(Lpc4}D`s)#fEgVTRNdoGn?P$ybnQTr6mSGiOHLY~+ zn;M=vRBDJ{cO@&?7oxtD<6y3B=wSyu{W7o4If$l2a<#I7QB}NG zmj)F28xD643WDB&nb;xewTBu{+u#p;#kRpoI&2%fL~@s>-wk6x^9I}M-P!{uxh8mS z;&bQ~ukNQCu=*exoqJ*u#ZW#1l6)qk>9jwF&1|Upo`odu&#>3S3%_`E7m(g8?8V}h z7e0XzCvo+&LQt7j(k^VqNwXd7GnNwVyH8l&L5SuUy?7TGf$eaego~0BbJ>>EJVb%5 z1j(L--j0g5Gyg?8TMgww@apaZ15{}^tS4=VI|7*7L3e9l%y1%X^@Ps3i_9=?wU|9d z4bG$DyfqeOgR6Cr{TGkvl&dvjp8w`hbjtSfS~Du$OjgHrKFp*YONUh^!Cb47?-Lxy zAqQELKuj+W-i=OH@vfS15}wSPvHcWJ{X_0jn|H8;FS$n%iAL;&1&!tzXt8> ziAzeyc+XTlpY(~{LUA5y7vwyOul3?IqBryaOT=ud-K`!4RxAQ;Kc!1y#KYUyjb)KN zq{y$RMDhMcG7FN+ZzRQlcHnje^;r8Fk9?wLG9TxT_NqB|)GO&|$8&S|2TJtphcvn3 z$lR;@k_N0>3FkmV&OvkgoJJE?hX?0sTi9hBjYCN%lG*$y+%k%A0l+5IY9q2gBuKr#+(Ue!`KOEndT9e1w^aQGy*4`@8geUixjtn3*zFK1hQ|(qP&BGSG!&*>PTD5=5); zi&bb67qnmrag0xlzLRdY2(8-6kEzc+p+|0{v9SMIqS7?4t@xW{#C6q(kTla23xp!> zqX^Q~O+@kNN~IJBptb%e6zBXzrue5=ys<6*r}-4$Dpi8`c`Tl5i~mH5KkAF0hIlLd zK(~57BB%@dqq;59Dg#4fgD>3>D#xu}fke$PU+Sj_p%wchK&(Raqm&zKmYG;dIA%z! zJuKx7byAGb;?*rcn2Z4SrbkglBfXyxxL#V>n4}dxt(p>~dCBCN_!~+wc|SsufU`L3 zgFCI=dP>)9gcS*tYCGr{?$upGwII!PWWJ?UUtc6>DJ6CeI=XswYB_;MGP;yT2(3l5 zSJ!+E%NlML_NNNFaJVQXVOJe7ozZM{@KY$ZYe$-V8{w`QcgnUiCH^ojH7SS4j%eO*iL1vr zm~r$plQRBC^e+Pn%*`Z^3FHowFlOBEVYv3n0Tq@Tl1 zJrWEd&Uz4MT_{qW=Z<}sFYCB89LZ?_BJn3h;JdCPg9h@eaZ~oPleVl<&u{`XJM~pjXgyg8@41u zypJ+U`nnTR-2>+T>^ws+jK19oc5&hCw8^y(Y#tC}x_i*BT^k5`4 zd!84u%rYk(Hwn!q?YNVsq?68iI#G}p)|j6$NlP=fL7JzhLuh|mKh8~sU*IuP<#45$ zb12^4&cgMH>P-%go7Lebd zbEEyDqn!baX0HQW3}sxvFkdTi@q)}nl*Gla;Npxa>5TJ(f5HV@|90}3^}OeHtOA-o zprcS2LbI?7o1xyr*t;jM>Va^RXS_cw!gjNJMu^otLzVaI8m!hAPC@FpOG{OKm(Z~w z?S=fxdpyx7WGn3xFI6QjQQ&?vnX_M+kJBz9s2`4ZX(^J?c2+xu_pB;q3#c--fO<9! zRY(gcn~GRFjiYl~`d~2>3k`!`KuWHy7zK##!%D=qM1e~?-rA(?_TQG8W2Wvh$Jo%m z1u;{buhYIWO<~6AfH`T~yzZvB29r<&*GOfft7LEL#V|A&n@N6418`J+L>L<)EvIN= zvSBnDgt00tA!z*ao=Te^8i`7=4q^+0+hO*R-pC=DFf|0p60n4`VfAL91hI77Xfq4j zadb8v>xp6Au9Cdch7vE&VcN{0COOlku8@Ieip`4pk&=(Yjiu*_6CRsD*0^1!vr2^{ zuq-fqsX6G|<}d{P9N}ewdec&~=G!IZB&_CuT@u882(Gqo$9%NcS>JpMwnk9|dH6C>!)_C|b0QZoD z`*Ae#?F{LRPr75S7i+P-01ru6QV$_khQ2%<9w2r%HrF7?VXhRG)2?)LAv;evf{iY$ zDPANREk3X(9X(_k5{{vR-Cix;U}twMbxuw~v~SB=Eq;xO6Ml+667uecq`ZQ zpE#G0l#N@ z6z*iWF3I6%r4sy{-CR-{zg@|Zisk@qxZkhZ2Ni7x4ZgY3h|>iqn;t`$POMx5FBOA1 z*M&cv1MAd30VlP^{LlyY4HjU(z>zMA%=Hqnd8s+Yb>Wh;OPzX0TgqKX+5C~~!bN8{ zI`GujsaMHyf>G7{E-NI2q71GTQX$e*VZ_-7x++q3o#8ke(dgY)G@3ciu-Lzd7O;rp zl~&9;p=ADNVEi|AhfZVvas~{Hu$LE_Ey7OId&G8P0y}CoDfd~FC*d#{T<6vHrgE=% ziSA1(k*3_+B;n_fT)PQYy9d}O8}U5|WIOa^%72Vz#GMlHw%)V>L)K3WJb>A8p~`(% zh&d7SwRZ8tQVMg-H3`#Wu5k=X$WS<9weKWo-{BQW=jZ9-4x-GF(O{@7NM%ueWQp77 z{Ea-Bgh=NR-ZQq(mOoHHnMPw#?vUby+f?OED=~*Tu5~r3u*2LG>d<GdTv(1 z%r$r2+=W2MW)*a$S-m*|A@|r2vpY}ItaRUnGt7nIu*O^bQ8G+%({aGW=pIFAaOGy` z4cw8Yk0rJ{G|aG{2Ik~B5N!0tbPUegZ&dH2U~YoPd_DFQ>rDxfj-d&+D;zgy--*B{ zIEti(c~LN1!=g<~l_up!CXIrqsmgsx4Ray;TEB!S=Rr8FnpEW%mbjD7cOkd(pQ&lY zsbhfaXO*Le>*r8MFc!Hz-(^jEmo@EO*0gu2Y1X(;AZ{3-@bR7n*Pq!+&4P=m&2uZL z7E9bm&K6RQ1Mu`{j<&+I!qN=cM}zdam@*{COmCEKQr<<|%I)*L4RcFrq}83NH_;xt zK{#)6?e~i26O^?@ETqXsc!7p24yHc9UZPoOOiEU<>$zL>P(#rOZwCGd-LQfZ&4iBd z!jsIF)SEU-Nq&Q^b0_h_5fe5ZG}3hsk=)-PDe)te7@M#YZlkl%Kzw{8NF+N=R0@)k%dQBx~I28zm&f ztsY9qr?_96vjs{FO?UmSen5vkRqZD8_;7gT75s)bG4{7LeDkc2FL8~(HW~3D@uQ`#SY3kiaI}sfzX8DUrUO( zZ8Sj<(EqX01cBKZUcoU_#R6rtvoF@DIPZa(gDp2wlc9>Y57xdJ0O3}~?{2cwGG||l7v{P=9Hr8_RVmFt<@(Emk(UPz9fKZI zhG{bm4=mIT5HFvY*5bhZnsD;mQJf#|DsAO(dMKo6+DSEfTDxITlxLu@7H?T506UjP zS8^uTZf}cOfho6*GZn(t&k_0jKVCNZoGL7-K z$H$*pv4j|;ov8MGM)};lJXVVNjKnt|`zpd@BuuJx%)wm)pIEb837m5}j$0)yalQX~`QA7vFHuzQGIHGGzc>u-ZG@KO=k*Mh)01Wc}*z%JU5 z1Kq^#BFx)OMiy}@#ubWT3D$jsfX|0(#eCP%H=?~v@n;l4x)ZdeI|>n75In1nMnQg# zZt~%Sj^m6teDD+devd;{OlF)|j-ntD-_yRn$EFD%cSyVF1@w&cp=j3G+5pFmXKNLX z;H4_s7LgV}XKVeiyD5O+68+>^?QE^e5z0s^X`WNj=c2VuYUf0TC~=<;@w^l%eKOjD zPfG)6d9JYt+okVCQHN9kjqhKHh!05V@qOVHO8TsW5b|usw@&gOE}RYiKZu7EZ8yCdFzSV zLey4gwWVC78Z70l1hg(!r9hA0jz_t$ZD(NFZPc)sFftqf{Lkw;JV;DUUU!y*Ns(U z(pcRQ&X1+;DPi5y-rrLHU=w;C>mT$5bxT_}vHq#;&_7nWK9X-M$rqYn4T9(=2T>mUt?5&x)vwvsi#ID|EEMv0TY^G*BWE98**cG1{A z){;z2)%=i>O3cyswQ&-s)w>mG+{rCSrFUEVLp0J6YCfT7Qpe)5ab1a4 zOz2QT^9)}5`uWW?6<{lf*P2^Vs>|CrFA|Jr-;K+#X;6+jukIG&9M)lwYjix==J4e| zoZ5R~;y~O>>*P1-WZa$>8JIUA{|BmB+v$bOpDBt@L5%8yKSO%)?onsD3sJe8ElHD}DxB z4SB}#t_zKG-zQ2PDB{gv*LNSKv+TrvWW8x8bDtKDZoRl0IdDhsV^QOac#B1BkRqr{ z-rZ-M(&3s%8t*B@;kbJSUMJ7H9{?}v8*c`S@jH2Uibj}`8Slk60x9^UdkVhbTzI`% zIARfMP2N4mgakM<-kKD&zs2O*gZ)rb+%|80(soaVVu7D<)GXwd3g_sv#{gbXn1xK$ zx#!CuZfqLih@sbEEDq?BgzFnf7|ykKn^$)x6naOuuBH1IXK5DMjL}Pvwz6j7T}&-3 z=dhq`^|MbH@39UF=kbpNV9_)WhbJa2e4lyvGzSXN*diVXL_6XdX|u~nyM}A)0`~1g zcXt&Vu(H8si!}4qabJFkbhtD{X_heVq^&G4XlD1ii9OvkFh@%sE7&p%POcw5NagXP zHwgz)g#GAZlkllmmw;q^b_=EKkCfQ30(U8ut&WjGs*S!F3IR3gwDwW@ERh#d^64Hz zTYi*9n=z=a1*+N92zjC5vwY(L7unHpT*S8C7;Z_bpNEgLSfhjZHbF^kKa7>K)ONbG zhA(d`u#DmAl)m;$>pba*D9qRX?csF7OCzDEBR9C|5KBW89Nh(u=<`a)C?UlLj=(rB z)*YY^UVs|VYr)hO>CmbER-`e;3C{d@Z;H^ADzun|-|@)_J_At* zHr2TqHsJt`kX{fqG!O%Y#z+H>&8^vu@)+3z78lNH>--0;NsaR|FfMQnM;;eL|4c<* zhsP|dh=IEOjW+do0~-slD>OW`9n=GRbjK;-65N=$!T=wI*m%(i|vvMVmq z{Xgn(>QD6JascXgBlWw1I+r@TO6py@1A{9tQV*xVpreN~BUa z&^SH;UsyV4lJ2!wG~y!6-?;P`F3x92q-bM^UYx~{Sn)1~7{nAUxgcp+- zk|qvgNQQVFLo!7@L-NHQ3`r2f7-AFE3@H^aVy?vEs0^(WTNu8C@DmKDR*Od&PI3|V zF?A0@ny;f;i6GF&8lGQ(R4 z=NaBg_$Y>Ru#TdE;dBj4jA6L60F7jLB+)|{PK#TSW4N@WJtNJ5g0!L(8J<9CzF@eK z@CJs{!c^SBa9WUxn;C8+d@aLE317kRGQyWJdxZgKT5cY;f;j1V!nqCh=iYHcnjf;3~wd; zBZkv)idfHZ4dL%GJe+Xy8&FOp;VT&~ZSXB;cr4K$X1IayGKMD*?qIl)@O*~zgimL9 z8sTXS&mi2)@JzxJ7@kk~4Ggytu4i~D;oTTsM!1IIO9;P+>sH{oitrYOFDLv2!&eY~ zl;Nug-^cI`gx4^9GvONvXG`>bnfh-Y-g^W0n5y>QhcsC=CBBCsJw!_fAe#cq~7eKqk&?Qh! zYWy+m-^WB+;~G-6V~*rnh28_hQB-Quk&^Ctrt)vNmQ>HP*x&P-b}5PK#99t{u>!$* ze@FOs@4y{OtkDM|7QY&}X-0Ojoc;v>Y)l>!G~(7;+G7Dba5vgwUGJBn9?lWW{t=I{ zhpv)_oub8ch>{x9RtDRl3GNWz7KQc05nOu5_gG-*%E}+eqcZ$PhQG*AQ_1RylwphvZ;~M|!>KZ~ z$*@d@&&cpi8SatcCo=p>hCj;ij0`zh{#|6)M~2tS@GcoL?l1y;{oo_~3EViGGs2>A zrQ;YBbqxMV@;@b8xneGx%i;>T>0Al^fk-P?fX^!IpxC+j_(v$SxH()dH;~JaOK5zV z)l>EggUQAAl+pr6n!PwXw*>cs=Ae{plxat7ZMz^GL@5-m^Se2$bhA1x{YmW5x+F$dBsgyi%{>qNQ& zw0agoMMy!dpDUHnUKYg{NHNq01?T~mt{C(nNW)h?rOQD)^%Zf;>eQn)r&K=0>rrp9 zR4egg!ylE%augt@2>qdlWT=&l>p7s7NMTAj8|5=pJnQjKlnw#45T#v*P#(%FLYSmMJQ0O7f#jKm_ex0>Nj#DU zQ7)!4?6*Cq8RZwEx3VO@21~t5`sC~HVQu|ggc3*sxu}VxH4AOkqXrxDW=pNklj|r1 zWUVDxP#WdTYG&&+v+nR?UnjPrCr|dREJOQ$>4i3!qh+12P}q3nq)?N z4*r+uhp#<8xf$Bp;p53CzpJ$%r%mom8a{ap;}Y7+q2AAuMhR;djkc?mrI&P&^w`H! zyL^XnBiqXJ@$=XEa~|rtdW~1_QznODuo^yl@z?94kws(nKU3d`w)$+)QKkcPQI;KL zIw8M8NbSmz;CPmHLZWRT|&LXc6fHSy*Z(X?VwSFW0A`o%D%#q!P(KFq=d6i*)B|bAYd`c6}P! zL7#}N6F!qSuDP%94?c?z2tDX9PTZLYw}onI<2CoR1WuLL2g4Xi`UCxqa5xhhtslI ze5oA9x7CtAI$sWl%i;UvaLY^$fy$dLX}~=E z;>ZS1=UP&bkNK_rr1H})EA1wfclo16S7UFs?ZKtM8dY0}<8X*@S=fWHPp2r^Bb~yJ zb_!#qe_8s+I)xwa6#iSMF#WS)9qXfidaPqu2_8tNYw+T*fg6|W^Yy0{6y+4pOLt^B z3W|$}KBXkrK4IoPx!Dd&QC_iqjue6GWzvIqYgUQFl(o10c_bK2VIjK+%NQn8B2 znw2}IxRm9b0{=S&y%3KPJs94-6@T>b_`3=HHwVAudHbFmj92=*0X>l_|Iw_%DD&YV z(;v1p@Ha%F50NPxmj)U=CK(cLh6SLxi2Qn5v-qCdpq=E)ccxE}=Ah!X)agh=>x#}P zP0(JlW#l8!b1QK2y?qabX&&-DCP{C}r)Cd%x^~o+FNen>EoncE5?ZOf`f zq5r)A@sF#S62G*rB0j0@#FvjB28$4;`lwXK2Md!lSiO{v>Y;MTOCXPh=Bsu-#)4AN zfi@YOQ>u~@y5Z{y^dd1%~XUH zQQr3F`#(K=u;-CSs~&s&Z_A!|@~P!dKlAK!&%f~E zikDt~W#!*reQnk1>NRWEt$%&P8*jd~@$GjuZQio=-S^)A;KQ2QZQFP3+*MbCw+VKla6!Umb7!`kQZ0eE0niV$+Y!Cr|zKbIa*pem(Qs*>kPu zf4^`M|0D^g@KdVP{sEf6px}_uFl~55m#*D(-Frkv_3YI~+`2 z-7skI5W~=6@xw>lI5Hverle7$Z@$HtH8VRWH*Z#c!9BAJ=M)v&?zNXVoO9=u&cE-@ z=`-%SJM+)u?_co1!UzAS+y6gZ|9@HjrZLIpl(FM@%dO+Bskco?n>ZTw>`3@dteT6dUi5e8l^}l zj!3TeoGi(>oKWoOGKl# zA+#pAt^ZCzQ|=$iad*n;qXt91IXfWZ?Oe3t11_v4#1@>!`mPZRcS%+=AuCDFkZn#N zWUS$$hB0aS_~5MYm%<5&q~K~T+Fx*Wp~tRJ9JtK2egk#d5rsH zT{0eb3-Rvv@rJsi28E{d#Ru)ZFwS~_zplw)j|a7A8vPqoHGXniC0R;kN1*I*PJ1}m zhI}LXI>M1pi+o{R*Z44d=X_8Z*a#YTTI37kv~PmPAU~3qK@sN1g$)a$BurPj;M=DM zDa4+vEIHixlu5UxSmTEc&MCx(Ly~e{3AP@J9C_2YDd{Pb zeDsHD&01VM z+i4r?EW$2haZyT(@CmkBX68=tQ z65o^O+j6;kSlhB7ZX0dvPU8+k5{0>r&Sh{vaVbR|C3J^kY+(tCnwnLJ-Jm-u1eRFn zb_!8bxiaFD_J&jJ_G0M6-VXXRIp3a}l>-IkG{i-RR2h`tR4DyFNssyQxmh-p(qZ43 z_hH_0V$eu0BtW0YC z{1R-|O)i*|YbufC0t}b3#fg;UUm}NUR_ynN$xuMs)HCgY z?iDE8bY^m~PmwsA)H+GSsIQUk-_QRp2@I3}4dwka^oR34@_H4W*khma;F5j&QCFuF z|G6NQ+c9V7dOL?ZrX{>{eCP0gH2r@x|G&EY&ir%^cdq~cuVIo)cf5Y`OXrD%(-|n8 zWn&3~Q+)Qnoel&Hz#D>BBSWH5nADr%g7Ny|s9efRP>Yw!4aZ9~EK4}Jhap}Vv7N%OzfQ0ACS_Q;U%8S zWw;2C+Ty}X@fCQfoX7BDiOszrhhG9Deplk9`d-6J={Dh|HXXoA@t@+Q_(%wa zZ2~)I|33v$^uO>4|I>$6s{hmHU&z#dW6^)UIm(#$^Ub^eMkfEoRsE?7&``$nrz)VA zqW@p}`M+Ex%9b(vqwu4DehGFA20wGr{6+obetPvdLtgqjgXJ$aUL?%8uuOuhkiPbn z&tK8A=qrB9|6nAe4B!5X=}mI_caZ+z%O_s$$$j^-_%i+>v`$Z3`3ql?a!kYoHSzPi zKG$=1e}2#B!#bsgeLr&atH$#jH*_J(ncOKwjh)HkJsED6;YJy5kl|Vxu9D#j87`M$ zl?=;dh~s>t0(IrcFhhouWtb*Is|*oJSxKm8P>>fgA7;6aHR~F%dkv_HW}i7#eJENnJ?2bWtbtuG#TLp+Sa`G9(@Q?|3#m&D!y2 zb^TArlN|n$EW%&7I{ytNjGkG_VB>rSYvlHpJ;%c2)6s+Pr7v4|;fO=Jx7b#`Tu=R* zEZ=_>J}5sbx!iqnx&KkCJJ^ z0k;A^ikIr8a~@A0oYR6{1(>PlxEmHSSk;f?z92f_-2-rr0z4CNY7Evt$eRIZ9f2v*7XbAYvYsoouc^RW=i0KR}0WH{5L^aNw@63+yu#o>Q7gESd{ zpW!9?7l6ZVVC4)4`~hzb(zF0}9fW=W-VJalUaD(2;AXrhffKw1gPhu!0eCacGda|2 z1gyYIJZuE~ekjrqKX5_D$8+3t;8wr~@KTv&fIs6U`LqCr4M#hWCK7NwUgC2ZAif)v z(i1!<(^~=kZbW@ZuLA6am&)u7I0-ND&8$(f+RL_Q~jKLXDLeiG0#hLu?bsE0FG0Xo4a zQqb?fmjlinhcO2n-(GSn@RFB2RJ?G1n1x-`U1dV*qESlh65hJOMTG*_{u~^UkO+=8M=wQHbDO=C=R#=FmEdK z1b9B+_joISi-4cpfpG`?D4_XHCQpL5O=t8>!1Z`nAr0M^p!*K1zym=#=O>(Cw2aeP zKb^r7ogkgB6HbuM#0e+(sEpIuH=QjLogkg%5>8N*aY*Ig4@n>sdI}Kvvi{}Rm#bc0 z{_^rQtJZ8@Q@`fu8gWhQ8qM0swXtgx*79pJ*5sL3dKDxSbwYa)vb?a)bT2mcf9a*if zj;%ISCsZ4&`RcUljOxtl{AycuX?0oklIp7J<<%>yS5Z$eYCo-cr~>sGC+Sy#WVVcpSnjq5b) z!`DZy*RPLVZ&;tO-ngD$pSC_@eg1me`qK4f>zAyrTEBe#iuJ43Z&(jm{9pLAb_3IX zew<-ZH_<=v`o;X>bD_8svSLxU&p`UvPoWs9HH7*HW-jIxxk{CvLd_Yb`v=AO7Ug1JJhl;Dd+J6W8&BU{ba`c+1K{! z73_&UaO>aizEoN}X2l|Hpka~f3&SGijulE=Y4nTW{+M;*^c^wZ?h2i}V1<6$TpJZC zv#JONLkoun8v^~6Q&j$4{nD&M`y2EW2=cYZj$_{WETdwxvMhM}vb zez|I3*v3%w`F>alv6zY~MJIII&@d+t$CZU~a?QE~5sI54i4{ zrdGw*4d2C{joGgI{ne@pN9AFQ%(JAc7=>+bPTH={jTEZ!hKz> z`no3uS9wMu&q`J% zs3#5ZgQhB8S){lF5}9fUfgq#1DimIopMf((Qz%T;UDfT^5qlmPs|ii?95>a)7v-Gj zp0qs9(3heIs3HyB%erai?%x~WjXAq>|EYJY-utFvvF~qFsze2ybPrk!pyJ^UfY+X{zdJBqTi z27_jp?aQoCsO~n*FvR-82F2pua^4c>Or$Q(F0l=gi^wjXV;I_&(r@uVIi)?E-oyavwGtc_vq#Po~dW1 ztQ_3*^^t{dCp`AVj)U*M+Q&J2YTC0!uQeZf?(2K?AAkI6#+t9cyKYALY1P{&dgY9H zDdFDH_m|}CD)^|s;mHj#+x_m0ST$<+%=>4}`g-i*X$!=qFIK&qaOY=lyMI*19`kIv zGhoiJcOQE2i)#)HzDxXM&9U!aZTKe2ugm!r$8LUp(hlqU(<)Duo~nJ)ynpP51Gyhh zv)>f6EBxyXB|9Fy!?pJ8F8i|c_IX~#$Nga6zJh(*#q3-7>|yt4m9am}Pax~QN}FPK$?Yb!9m zf6dTsI=1R){kQLZ{<&-GmJdJqcv`w?lW}X05c3lsHRUBexbcIkSC$3-eO*6&kFv2F zldrk?k?}oqPKFtWC0|_hqW9K8KQ8<7j))tDte;PI|Hz^|`ooOvxp)3@TX5X=+VtOz zgSM@Sk5QM~7S=yxeeqD)w#fU#y1jp!GI?(-zhTh#Hy-abTG;;Z^CMsQ+W*smX~*Y} z{(RHj&0{~98+#-E&8o4=hT*+RH^$aYtgCcb{|Jzp;K( z(bU%h`Da?~6F&Vk=7t+@{twzZg}RVDq^DiiNBG z?v}Ze{j9dI>sBW|>8~BSDfslO!lVn~k4_kK=fm$09iYg2UcBz5)a_}%pKDDCi(d5U z#XnvhrYgB~@5y~jw68p_*;$_9cPY3aY5(S1ETbDdy}NeHAN*|SGwU`)ee=TM1&Q;P z6c4y`f5k=foVT~mwmm+jaM>G2M_yWed-TTaH^1n*zx0uTBWj;{bY#Wgk5cx1``bQG z$*ON}n)AaG%WTsgOM7R|&n5N=L;N1f{N~60FP*yi(jg(wGuS?7!mb%3e)`+0<%eh8 z)AV`Nem~cF$Hlwvi&Vw-`Lcg}(%iyv>KPwT`Xg-X++SVcbw!4|U(2lf-Er;EXJcKb z`Yr8sdcdTLQ%}6N?xw%LGDh+Idxk~oWLUX4*}_@$uk|=y85p7;{^9Kjmma&RvxWO# zU_r=-OE4rDh8q$_B#uacksF%WA!sOHbGec0W9UtGDJVoommu}&#ZG&6uHKwGH@C3Z zmTMmxA&oLsK!|1<{_Ucji?Zeo?P1VS7EMT4PH|yT@y=%pcT|?lF324^*l+_yC_@Hb zwX7TTW?VVUD$-AwRZut=>Gg&_Z4PjtqI*P+6m5iiOVNX8-TO}+zg=8){F&9yr^0&c&+&Q#UaCn{M;jQZjR@dhlad%yW+XWYt!GK zxxHxI%42>%JpI1%)94rPYh6>{7-ZcrcFsqOc7)FVJ*Ru^@v562eyBMiq3f&FQySjb z`nTH}7H;|Vxc|H057H$JSJ_5FQ=XQad|Ts+2oA%5k8BWsRG3E?!Ba6JkVzkmqJpDJ2`&C$P)Bb#Tl+MT)Sf6iUEuJUELuo$<8Xt&l)EAWcT`9 zA^2puhHOKeFASfouP?hVLua>acw1t>#j*d;E%TKCpYAfFd{wL6?!eZ)XT1>m`c31! z{cmYVKl)<#pce;ZGn?N<)!opoyi2xRu(8eGyuy7QPg2pV%!V-rjlch_@(?Tl4L5dl z2~`GKRLPDlT*4@7N(mgLf=oO!{2;+`_>od ze)mb>%_By>e4pixGqvj$?MQTrsh4(?PucYJt0!9)?@M}cNXz`WZ!|rXf8*opt=}CC ze|%p}V{yRm8={ZIjvVny(fN==uk<N+fO>}So8deqNK8e`kv$zZ#Ex>PfzBw#x(Go9W`uX z4FVRWeoTyv28~k(K|_m409#RxG&r!J#y|jRFo4Gp)RmG%lvd1!JV??kz(N!;mcVVu z2h55Hz9_KC0PE*-vKT6{DBKG@cb3yHS7gRA;e{O67wi7>bi96mDgMblF2&ir*U!yF zu2+DaU6v+BhWTlc$09f@4p=;L4Lv=DH^sn3bj{KK*}ofO4E`5huo8OK@V0`*^#9p~ zFOzeG?r%-W5%1qz|D)hT3eU`(JGolX>{U-67<_%S<=3WL-#rx$_kTEib*;?YKRZjy z|E_<2^57gX<<@Q$t023uYps(+#or!FnfSsn|L$k8dv_=IZ%*q?=bd?1Sue4{?nk7A ze&F><0R~>R4HKT9u{x-y&cv~1d)D@V)itlK)YdGxb~C$xXGYJ%O842n-(K6M8Wn8O zxA9DtmVwdE7yGw7HkrEHXZn!?9);o_2^XLFh(_nGKV=zrxA60m#JL;A;y&)^|GQmk z>M!ohX^OY1&)*U9|FqqlF>)2t!X{>ZU|)k95t3|%O-z3bnwWloQ`O{w?q1*+-$71O zO^lWXQV0()8}b6n9mr4>rvM`;5y6uQt06zAA~!Su4#t?98XH?gVI(4DrdR7dmdHEp zP^^i1`He$&%J#W$`}mCR=NHyr<}$BWGH5)FWIPj(VdHLt#vKNYTMZgFv55Hh`gpK; zI%U1@Zo6{zPo>dw5#3gU#+e3<%}W{^25DHr#>${sV)L|z<`2F6*o$6degAk*#HIX? z)QXM90&C6JI;d@!mfd)L`QA;Rs^&bOn#DCi(|7u(b1LuOCzbR_-$)Me;MknTeq?>P z*0Ph&W?Xk_z?>=`@Je2?|(0=iSl@H yYj@|~G&k2t*5Se8{~Aq89$frb9JBTI!_-Ni+t>e532r@*dUgA)0QMQ6VOjttOJaWj literal 0 HcmV?d00001 diff --git a/TitanEngineEmulator/TitanEngine.rc b/TitanEngineEmulator/TitanEngine.rc new file mode 100644 index 0000000000000000000000000000000000000000..6e8d106d30e549219c08467a6d1b40af66e2d78d GIT binary patch literal 3994 zcmdUyYi|-k6o%i=CjN&NzSw9$t!eyPE+S!zra;psB%~IpBnXB}tckx}ea>u07IwF_ zc*!=C*)x0Q%$YOi^3Lv$?`4%0=tyH-Xs($S^pTeEPT(!H(zzzQRrQ1x=u#gvrO)9n zp+k6csI6Nux?mjeX4=O3j7&)n^?+TIMx>&+MS z)&)d&O8rfU)Pfuc^q81WdGmGHVH8#6LVGD$u!z;v*!kZeC_ zRoQt!c7-(4`;8W4uI!@Ym>eFks?GX6w`&Nu4YvnvxIFcoNUnkICbjxTPtn~$t^y}o zvn?@THmd3<1MDo>8DRBs!bw`}TDx~xkY43JCyZKuj?dC112uQ*>92cIT&&i*tcSx*;BpJ!LN$;mAy&hx;AIh-wNDXW4#B-&goxyn=!K6$*`@j zEA4}%Hp-SOA$>((e*true true true - MultiThreadedDLL + MultiThreaded true @@ -139,7 +139,7 @@ true true true - MultiThreadedDLL + MultiThreaded true @@ -150,14 +150,21 @@ + + + + + + + diff --git a/TitanEngineEmulator/TitanEngineEmulator.vcxproj.filters b/TitanEngineEmulator/TitanEngineEmulator.vcxproj.filters index 56208f4..c954387 100644 --- a/TitanEngineEmulator/TitanEngineEmulator.vcxproj.filters +++ b/TitanEngineEmulator/TitanEngineEmulator.vcxproj.filters @@ -9,11 +9,17 @@ {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd + + {042de20e-5da8-4247-81ff-f0b401bb7732} + Source Files + + Source Files + @@ -28,5 +34,19 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + \ No newline at end of file diff --git a/TitanEngineEmulator/resource.h b/TitanEngineEmulator/resource.h new file mode 100644 index 0000000..215fc58 --- /dev/null +++ b/TitanEngineEmulator/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by TitanEngine.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif