// Adapted from Unleashed Recompiled (https://github.com/hedge-dev/UnleashedRecomp), // GPL-3.0-or-later. Modified for SpeedBreaker: only functions with real // implementations are kept. Unleashed's silent "!!! STUB !!!" functions, // its Sonic-specific hooks, and the audio/video exports (later phases) are // removed, so those imports fall back to our loud stubs // (scripts/gen_import_stubs.py). #include #include #include #include #include #include "dispatcher.h" #include "function.h" #include "xex.h" #include "xbox.h" #include "heap.h" #include "memory.h" #include #include "xam.h" #include "xdm.h" #ifdef _WIN32 #include #endif uint32_t XGetGameRegion() { return 0x03FF; // all regions } uint32_t XamUserGetSigninState(uint32_t userIndex) { return true; } uint32_t XamGetSystemVersion() { return 0; } uint32_t XamContentGetCreator(uint32_t userIndex, const XCONTENT_DATA* contentData, be* isCreator, be* xuid, XXOVERLAPPED* overlapped) { if (isCreator) *isCreator = true; if (xuid) *xuid = 0xB13EBABEBABEBABE; return 0; } uint32_t XamContentGetDeviceState() { return 0; } uint32_t XamUserGetSigninInfo(uint32_t userIndex, uint32_t flags, XUSER_SIGNIN_INFO* info) { if (userIndex == 0) { memset(info, 0, sizeof(*info)); info->xuid = 0xB13EBABEBABEBABE; info->SigninState = 1; strcpy(info->Name, "Player"); return 0; } return 0x00000525; // ERROR_NO_SUCH_USER } uint32_t XamShowDeviceSelectorUI ( uint32_t userIndex, uint32_t contentType, uint32_t contentFlags, uint64_t totalRequested, be* deviceId, XXOVERLAPPED* overlapped ) { // One storage device (the hard drive, id 1) is always "selected". XamNotifyEnqueueEvent(9, true); *deviceId = 1; XamNotifyEnqueueEvent(9, false); if (overlapped) { CompleteOverlapped(overlapped, 0); return 0x3E5; // ERROR_IO_PENDING } return 0; } uint32_t XGetLanguage() { return 1; // XC_LANGUAGE_ENGLISH (TODO: setting) } uint32_t XGetAVPack() { return 0; } void RtlInitAnsiString(XANSI_STRING* destination, char* source) { const uint16_t length = source ? (uint16_t)strlen(source) : 0; destination->Length = length; destination->MaximumLength = length + 1; destination->Buffer = source; } uint32_t NtClose(uint32_t handle) { if (handle == GUEST_INVALID_HANDLE_VALUE) return 0xFFFFFFFF; if (IsKernelObject(handle)) { ReleaseKernelObject(GetKernelObject(handle)); return 0; } else { assert(false && "Unrecognized kernel object."); return 0xFFFFFFFF; } } uint32_t FscSetCacheElementCount() { return 0; } uint32_t ExGetXConfigSetting(uint16_t Category, uint16_t Setting, void* Buffer, uint16_t SizeOfBuffer, be* RequiredSize) { uint32_t data[4]{}; switch (Category) { // XCONFIG_SECURED_CATEGORY case 0x0002: { switch (Setting) { // XCONFIG_SECURED_AV_REGION case 0x0002: data[0] = ByteSwap(0x00001000); // USA/Canada break; default: return 1; } } case 0x0003: { switch (Setting) { case 0x0001: // XCONFIG_USER_TIME_ZONE_BIAS case 0x0002: // XCONFIG_USER_TIME_ZONE_STD_NAME case 0x0003: // XCONFIG_USER_TIME_ZONE_DLT_NAME case 0x0004: // XCONFIG_USER_TIME_ZONE_STD_DATE case 0x0005: // XCONFIG_USER_TIME_ZONE_DLT_DATE case 0x0006: // XCONFIG_USER_TIME_ZONE_STD_BIAS case 0x0007: // XCONFIG_USER_TIME_ZONE_DLT_BIAS data[0] = 0; break; // XCONFIG_USER_LANGUAGE case 0x0009: data[0] = ByteSwap(1u); // English (TODO: setting) break; // XCONFIG_USER_VIDEO_FLAGS case 0x000A: data[0] = ByteSwap(0x00040000); break; // XCONFIG_USER_RETAIL_FLAGS case 0x000C: data[0] = ByteSwap(1); break; // XCONFIG_USER_COUNTRY case 0x000E: data[0] = ByteSwap(103); break; default: return 1; } } } *RequiredSize = 4; memcpy(Buffer, data, std::min((size_t)SizeOfBuffer, sizeof(data))); return 0; } uint32_t XexCheckExecutablePrivilege() { return 0; } uint32_t RtlUnicodeToMultiByteN(char* MultiByteString, uint32_t MaxBytesInMultiByteString, be* BytesInMultiByteString, const be* UnicodeString, uint32_t BytesInUnicodeString) { const auto reqSize = BytesInUnicodeString / sizeof(uint16_t); if (BytesInMultiByteString) *BytesInMultiByteString = reqSize; if (reqSize > MaxBytesInMultiByteString) return STATUS_FAIL_CHECK; for (size_t i = 0; i < reqSize; i++) { const auto c = UnicodeString[i].get(); MultiByteString[i] = c < 256 ? c : '?'; } return STATUS_SUCCESS; } void KeSetBasePriorityThread(GuestThreadHandle* hThread, int priority) { #ifdef _WIN32 if (priority == 16) { priority = 15; } else if (priority == -16) { priority = -15; } SetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle(), priority); #endif } uint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, be* object) { *object = handle; return 0; } uint32_t NtSuspendThread(GuestThreadHandle* hThread, uint32_t* suspendCount) { assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE) && hThread->GetThreadId() == GuestThread::GetCurrentThreadId()); hThread->suspended = true; hThread->suspended.wait(true); return S_OK; } uint32_t KeSetAffinityThread(uint32_t Thread, uint32_t Affinity, be* lpPreviousAffinity) { if (lpPreviousAffinity) *lpPreviousAffinity = 2; return 0; } void RtlLeaveCriticalSection(XRTL_CRITICAL_SECTION* cs) { cs->RecursionCount--; if (cs->RecursionCount != 0) return; std::atomic_ref owningThread(cs->OwningThread); owningThread.store(0); owningThread.notify_one(); } void RtlEnterCriticalSection(XRTL_CRITICAL_SECTION* cs) { uint32_t thisThread = g_ppcContext->r13.u32; assert(thisThread != NULL); std::atomic_ref owningThread(cs->OwningThread); while (true) { uint32_t previousOwner = 0; if (owningThread.compare_exchange_weak(previousOwner, thisThread) || previousOwner == thisThread) { cs->RecursionCount++; return; } owningThread.wait(previousOwner); } } void KeBugCheckEx() { __builtin_debugtrap(); } uint32_t KeGetCurrentProcessType() { return 1; } uint32_t RtlInitializeCriticalSection(XRTL_CRITICAL_SECTION* cs) { cs->Header.Absolute = 0; cs->LockCount = -1; cs->RecursionCount = 0; cs->OwningThread = 0; return 0; } void KfReleaseSpinLock(uint32_t* spinLock) { std::atomic_ref spinLockRef(*spinLock); spinLockRef = 0; } void KfAcquireSpinLock(uint32_t* spinLock) { std::atomic_ref spinLockRef(*spinLock); while (true) { uint32_t expected = 0; if (spinLockRef.compare_exchange_weak(expected, g_ppcContext->r13.u32)) break; // SpeedBreaker: while the game is suspended the holder is likely // parked until it's back; park too rather than spin a core on yield. guesttime::WaitWhileSuspended(); std::this_thread::yield(); } } uint64_t KeQueryPerformanceFrequency() { return 49875000; } void KeReleaseSpinLockFromRaisedIrql(uint32_t* spinLock) { std::atomic_ref spinLockRef(*spinLock); spinLockRef = 0; } void KeAcquireSpinLockAtRaisedIrql(uint32_t* spinLock) { std::atomic_ref spinLockRef(*spinLock); while (true) { uint32_t expected = 0; if (spinLockRef.compare_exchange_weak(expected, g_ppcContext->r13.u32)) break; guesttime::WaitWhileSuspended(); // SpeedBreaker: as KfAcquireSpinLock std::this_thread::yield(); } } uint32_t KiApcNormalRoutineNop() { return 0; } void KeBugCheck() { __builtin_debugtrap(); } static std::vector g_tlsFreeIndices; static size_t g_tlsNextIndex = 0; static Mutex g_tlsAllocationMutex; static uint32_t& KeTlsGetValueRef(size_t index) { // Having this a global thread_local variable // for some reason crashes on boot in debug builds. thread_local std::vector s_tlsValues; if (s_tlsValues.size() <= index) { s_tlsValues.resize(index + 1, 0); } return s_tlsValues[index]; } uint32_t KeTlsGetValue(uint32_t dwTlsIndex) { return KeTlsGetValueRef(dwTlsIndex); } uint32_t KeTlsSetValue(uint32_t dwTlsIndex, uint32_t lpTlsValue) { KeTlsGetValueRef(dwTlsIndex) = lpTlsValue; return TRUE; } uint32_t KeTlsAlloc() { std::lock_guard lock(g_tlsAllocationMutex); if (!g_tlsFreeIndices.empty()) { size_t index = g_tlsFreeIndices.back(); g_tlsFreeIndices.pop_back(); return index; } return g_tlsNextIndex++; } uint32_t KeTlsFree(uint32_t dwTlsIndex) { std::lock_guard lock(g_tlsAllocationMutex); g_tlsFreeIndices.push_back(dwTlsIndex); return TRUE; } bool RtlTryEnterCriticalSection(XRTL_CRITICAL_SECTION* cs) { uint32_t thisThread = g_ppcContext->r13.u32; assert(thisThread != NULL); std::atomic_ref owningThread(cs->OwningThread); uint32_t previousOwner = 0; if (owningThread.compare_exchange_weak(previousOwner, thisThread) || previousOwner == thisThread) { cs->RecursionCount++; return true; } return false; } void RtlInitializeCriticalSectionAndSpinCount(XRTL_CRITICAL_SECTION* cs, uint32_t spinCount) { cs->Header.Absolute = (spinCount + 255) >> 8; cs->LockCount = -1; cs->RecursionCount = 0; cs->OwningThread = 0; } uint32_t RtlMultiByteToUnicodeN(be* UnicodeString, uint32_t MaxBytesInUnicodeString, be* BytesInUnicodeString, const char* MultiByteString, uint32_t BytesInMultiByteString) { uint32_t length = std::min(MaxBytesInUnicodeString / 2, BytesInMultiByteString); for (size_t i = 0; i < length; i++) UnicodeString[i] = MultiByteString[i]; if (BytesInUnicodeString != nullptr) *BytesInUnicodeString = length * 2; return STATUS_SUCCESS; } uint32_t NtResumeThread(GuestThreadHandle* hThread, uint32_t* suspendCount) { assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE)); hThread->suspended = false; hThread->suspended.notify_all(); return S_OK; } void KeQuerySystemTime(be* time) { // SpeedBreaker: the guest's system time, which stands still while the // game is suspended (the same clock as the kernel's absolute timeouts). *time = uint64_t(dispatcher::GuestSystemTime()); } uint32_t ExCreateThread(be* handle, uint32_t stackSize, be* threadId, uint32_t xApiThreadStartup, uint32_t startAddress, uint32_t startContext, uint32_t creationFlags) { LOGF_UTILITY("0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}", (intptr_t)handle, stackSize, (intptr_t)threadId, xApiThreadStartup, startAddress, startContext, creationFlags); uint32_t hostThreadId; // SpeedBreaker: honour the XAPI startup routine like the 360 kernel (and // Xenia): it runs startup(startAddress, startContext) and sets up the // thread's CRT state before calling the start address. GuestThreadParams params = xApiThreadStartup != 0 ? GuestThreadParams{ xApiThreadStartup, startAddress, creationFlags, startContext } : GuestThreadParams{ startAddress, startContext, creationFlags }; *handle = GetKernelHandle(GuestThread::Start(params, &hostThreadId)); if (threadId != nullptr) *threadId = hostThreadId; return 0; } uint32_t KeRaiseIrqlToDpcLevel() { return 0; } void KfLowerIrql() { } uint32_t KeResumeThread(GuestThreadHandle* object) { assert(object != GetKernelObject(CURRENT_THREAD_HANDLE)); object->suspended = false; object->suspended.notify_all(); return 0; } // uint32_t XAudioRegisterRenderDriverClient(be* callback, be* driver) // { // //printf("XAudioRegisterRenderDriverClient(): %x %x\n"); // // *driver = apu::RegisterClient(callback[0], callback[1]); // return 0; // } // void XAudioUnregisterRenderDriverClient() // { // printf("!!! STUB !!! XAudioUnregisterRenderDriverClient\n"); // } // uint32_t XAudioSubmitRenderDriverFrame(uint32_t driver, void* samples) // { // // printf("!!! STUB !!! XAudioSubmitRenderDriverFrame\n"); // apu::SubmitFrames(samples); // // return 0; // } GUEST_FUNCTION_HOOK(__imp__XNotifyGetNext, XNotifyGetNext); GUEST_FUNCTION_HOOK(__imp__XGetGameRegion, XGetGameRegion); GUEST_FUNCTION_HOOK(__imp__XamUserGetSigninState, XamUserGetSigninState); GUEST_FUNCTION_HOOK(__imp__XamGetSystemVersion, XamGetSystemVersion); GUEST_FUNCTION_HOOK(__imp__XamContentCreateEx, XamContentCreateEx); GUEST_FUNCTION_HOOK(__imp__XamContentClose, XamContentClose); GUEST_FUNCTION_HOOK(__imp__XamContentGetCreator, XamContentGetCreator); GUEST_FUNCTION_HOOK(__imp__XamContentCreateEnumerator, XamContentCreateEnumerator); GUEST_FUNCTION_HOOK(__imp__XamContentGetDeviceState, XamContentGetDeviceState); GUEST_FUNCTION_HOOK(__imp__XamContentGetDeviceData, XamContentGetDeviceData); GUEST_FUNCTION_HOOK(__imp__XamEnumerate, XamEnumerate); GUEST_FUNCTION_HOOK(__imp__XamNotifyCreateListener, XamNotifyCreateListener); GUEST_FUNCTION_HOOK(__imp__XamUserGetSigninInfo, XamUserGetSigninInfo); GUEST_FUNCTION_HOOK(__imp__XamShowDeviceSelectorUI, XamShowDeviceSelectorUI); GUEST_FUNCTION_HOOK(__imp__XamShowMessageBoxUI, XamShowMessageBoxUI); GUEST_FUNCTION_HOOK(__imp__XGetLanguage, XGetLanguage); GUEST_FUNCTION_HOOK(__imp__XGetAVPack, XGetAVPack); GUEST_FUNCTION_HOOK(__imp__RtlInitAnsiString, RtlInitAnsiString); GUEST_FUNCTION_HOOK(__imp__NtClose, NtClose); GUEST_FUNCTION_HOOK(__imp__FscSetCacheElementCount, FscSetCacheElementCount); GUEST_FUNCTION_HOOK(__imp__ExGetXConfigSetting, ExGetXConfigSetting); GUEST_FUNCTION_HOOK(__imp__XexCheckExecutablePrivilege, XexCheckExecutablePrivilege); GUEST_FUNCTION_HOOK(__imp__RtlUnicodeToMultiByteN, RtlUnicodeToMultiByteN); GUEST_FUNCTION_HOOK(__imp__KeSetBasePriorityThread, KeSetBasePriorityThread); GUEST_FUNCTION_HOOK(__imp__ObReferenceObjectByHandle, ObReferenceObjectByHandle); GUEST_FUNCTION_HOOK(__imp__NtSuspendThread, NtSuspendThread); GUEST_FUNCTION_HOOK(__imp__KeSetAffinityThread, KeSetAffinityThread); GUEST_FUNCTION_HOOK(__imp__RtlLeaveCriticalSection, RtlLeaveCriticalSection); GUEST_FUNCTION_HOOK(__imp__RtlEnterCriticalSection, RtlEnterCriticalSection); GUEST_FUNCTION_HOOK(__imp__KeBugCheckEx, KeBugCheckEx); GUEST_FUNCTION_HOOK(__imp__KeGetCurrentProcessType, KeGetCurrentProcessType); GUEST_FUNCTION_HOOK(__imp__RtlInitializeCriticalSection, RtlInitializeCriticalSection); GUEST_FUNCTION_HOOK(__imp__KfReleaseSpinLock, KfReleaseSpinLock); GUEST_FUNCTION_HOOK(__imp__KfAcquireSpinLock, KfAcquireSpinLock); GUEST_FUNCTION_HOOK(__imp__KeQueryPerformanceFrequency, KeQueryPerformanceFrequency); GUEST_FUNCTION_HOOK(__imp__KeReleaseSpinLockFromRaisedIrql, KeReleaseSpinLockFromRaisedIrql); GUEST_FUNCTION_HOOK(__imp__KeAcquireSpinLockAtRaisedIrql, KeAcquireSpinLockAtRaisedIrql); GUEST_FUNCTION_HOOK(__imp__KiApcNormalRoutineNop, KiApcNormalRoutineNop); GUEST_FUNCTION_HOOK(__imp__KeBugCheck, KeBugCheck); GUEST_FUNCTION_HOOK(__imp__KeTlsGetValue, KeTlsGetValue); GUEST_FUNCTION_HOOK(__imp__KeTlsSetValue, KeTlsSetValue); GUEST_FUNCTION_HOOK(__imp__KeTlsAlloc, KeTlsAlloc); GUEST_FUNCTION_HOOK(__imp__KeTlsFree, KeTlsFree); GUEST_FUNCTION_HOOK(__imp__RtlTryEnterCriticalSection, RtlTryEnterCriticalSection); GUEST_FUNCTION_HOOK(__imp__RtlInitializeCriticalSectionAndSpinCount, RtlInitializeCriticalSectionAndSpinCount); GUEST_FUNCTION_HOOK(__imp__RtlMultiByteToUnicodeN, RtlMultiByteToUnicodeN); GUEST_FUNCTION_HOOK(__imp__NtResumeThread, NtResumeThread); GUEST_FUNCTION_HOOK(__imp__KeQuerySystemTime, KeQuerySystemTime); GUEST_FUNCTION_HOOK(__imp__ExCreateThread, ExCreateThread); GUEST_FUNCTION_HOOK(__imp__KeRaiseIrqlToDpcLevel, KeRaiseIrqlToDpcLevel); GUEST_FUNCTION_HOOK(__imp__KfLowerIrql, KfLowerIrql); GUEST_FUNCTION_HOOK(__imp__KeResumeThread, KeResumeThread);