commit db51236165e36023c8b4c70b1a7354305ff7069c Author: Sajid Date: Mon Sep 30 12:06:17 2024 +0600 Initial Commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..31f2d519 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +end_of_line = lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..47d4afcc --- /dev/null +++ b/.gitignore @@ -0,0 +1,399 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ +[Oo]ut/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d24c43d0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/PowerRecomp"] + path = thirdparty/PowerRecomp + url = https://github.com/hedge-dev/PowerRecomp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ec9d2137 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required (VERSION 3.20) + +set(SWA_THIRDPARTY_ROOT ${CMAKE_SOURCE_DIR}/thirdparty) +set(CMAKE_CXX_STANDARD 23) +set(BUILD_SHARED_LIBS OFF) + +# Enable Hot Reload for MSVC compilers if supported. +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +add_subdirectory(${SWA_THIRDPARTY_ROOT}) +project("UnleashedRecomp-ALL") + +# Include sub-projects. +add_subdirectory("UnleashedRecomp") diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 00000000..35574a29 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,28 @@ +{ + "configurations": [ + { + "name": "x64-Clang-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [] + }, + { + "name": "x64-Clang-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "variables": [] + } + ] +} diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt new file mode 100644 index 00000000..87e872e1 --- /dev/null +++ b/UnleashedRecomp/CMakeLists.txt @@ -0,0 +1,61 @@ +project("UnleashedRecomp") +set(TARGET_NAME "SWA") +file(GLOB "*.cpp") + +add_compile_definitions(SWA_IMPL) +add_compile_options( + "/D_HAS_EXCEPTIONS=0" + "/fp:strict" + "/GS-" + "/EHa-" + "-march=haswell" + "-fno-strict-aliasing") + + +file(GLOB SWA_RECOMPILED_SOURCES "ppc/*.cpp") + +set(SWA_KERNEL_CXX_SOURCES + "kernel/imports.cpp" + "kernel/xdm.cpp" + "kernel/heap.cpp" + "kernel/memory.cpp" + "kernel/xam.cpp" + "kernel/io/file_system.cpp" +) + +set(SWA_CPU_CXX_SOURCES + "cpu/guest_thread.cpp" + "cpu/code_cache.cpp" +) + +set(SWA_GPU_CXX_SOURCES + "gpu/window.cpp" +) + +set(SWA_HID_CXX_SOURCES + "hid/hid.cpp" +) + +set(SWA_CXX_SOURCES + "main.cpp" + + ${SWA_KERNEL_CXX_SOURCES} + ${SWA_CPU_CXX_SOURCES} + ${SWA_GPU_CXX_SOURCES} + ${SWA_HID_CXX_SOURCES} +) + +add_executable(UnleashedRecomp ${SWA_RECOMPILED_SOURCES} ${SWA_CXX_SOURCES}) +set_target_properties(UnleashedRecomp PROPERTIES OUTPUT_NAME ${TARGET_NAME}) + +target_link_libraries(UnleashedRecomp PUBLIC + PowerUtils + o1heap + xxHash::xxhash + unordered_dense::unordered_dense + winmm + ntdll + comctl32 +) +target_include_directories(UnleashedRecomp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_precompile_headers(UnleashedRecomp PUBLIC "ppc/ppc_recomp_shared.h") diff --git a/UnleashedRecomp/Config.h b/UnleashedRecomp/Config.h new file mode 100644 index 00000000..e4ced4ba --- /dev/null +++ b/UnleashedRecomp/Config.h @@ -0,0 +1,75 @@ +#pragma once + +#define INI_FILE "SWA.ini" + +#define INI_BEGIN_SECTION(section) { std::string CurrentSection = section; +#define INI_END_SECTION() } + +#define INI_READ_STRING(var) var = BasicIni::Get(ini, CurrentSection, #var, var) +#define INI_READ_BOOLEAN(var) var = BasicIni::GetBoolean(ini, CurrentSection, #var, var) +#define INI_READ_FLOAT(var) var = BasicIni::GetFloat(ini, CurrentSection, #var, var) +#define INI_READ_INTEGER(var) var = BasicIni::GetInteger(ini, CurrentSection, #var, var) +#define INI_READ_DOUBLE(var) var = BasicIni::GetDouble(ini, CurrentSection, #var, var) +#define INI_READ_ENUM(type, var) var = (type)BasicIni::GetInteger(ini, CurrentSection, #var, var) + +enum ELanguage : uint32_t +{ + ELanguage_English = 1, + ELanguage_Japanese, + ELanguage_German, + ELanguage_French, + ELanguage_Spanish, + ELanguage_Italian +}; + +enum EScoreBehaviour : uint32_t +{ + EScoreBehaviour_CheckpointReset, + EScoreBehaviour_CheckpointRetain +}; + +enum EMovieScaleMode : uint32_t +{ + EMovieScaleMode_Stretch, + EMovieScaleMode_Fit, + EMovieScaleMode_Fill +}; + +enum EUIScaleMode : uint32_t +{ + EUIScaleMode_Stretch, + EUIScaleMode_Edge, + EUIScaleMode_Centre +}; + +class Config +{ +public: + // System + inline static ELanguage Language = ELanguage_English; + inline static EScoreBehaviour ScoreBehaviour = EScoreBehaviour_CheckpointReset; + inline static bool Hints = true; + inline static bool WerehogHubTransformVideo = true; + inline static bool BootToTitle = false; + + // Controls + inline static bool XButtonHoming = true; + inline static bool UnleashCancel = false; + + // Audio + inline static bool WerehogBattleMusic = true; + + // Video + inline static uint32_t Width = 1280; + inline static uint32_t Height = 720; + inline static int32_t ShadowResolution = 4096; + inline static size_t MSAA = 4; + inline static EMovieScaleMode MovieScaleMode = EMovieScaleMode_Fit; + inline static EUIScaleMode UIScaleMode = EUIScaleMode_Centre; + inline static bool AlphaToCoverage = false; + inline static bool Fullscreen = false; + inline static bool VSync = false; + inline static uint32_t BufferCount = 3; + + static void Read(); +}; diff --git a/UnleashedRecomp/Mutex.h b/UnleashedRecomp/Mutex.h new file mode 100644 index 00000000..9cc12b36 --- /dev/null +++ b/UnleashedRecomp/Mutex.h @@ -0,0 +1,23 @@ +#pragma once + +struct Mutex : CRITICAL_SECTION +{ + Mutex() + { + InitializeCriticalSection(this); + } + ~Mutex() + { + DeleteCriticalSection(this); + } + + void lock() + { + EnterCriticalSection(this); + } + + void unlock() + { + LeaveCriticalSection(this); + } +}; \ No newline at end of file diff --git a/UnleashedRecomp/cpu/code_cache.cpp b/UnleashedRecomp/cpu/code_cache.cpp new file mode 100644 index 00000000..630872b7 --- /dev/null +++ b/UnleashedRecomp/cpu/code_cache.cpp @@ -0,0 +1,37 @@ +#include +#include "code_cache.h" +#include "ppc_context.h" + +CodeCache::CodeCache() +{ + bucket = (char*)VirtualAlloc(nullptr, 0x200000000, MEM_RESERVE, PAGE_READWRITE); + assert(bucket); +} + +CodeCache::~CodeCache() +{ + VirtualFree(bucket, 0, MEM_RELEASE); +} + +void CodeCache::Init() +{ + for (size_t i = 0; PPCFuncMappings[i].guest != 0; i++) + { + if (PPCFuncMappings[i].host != nullptr) + { + VirtualAlloc(bucket + PPCFuncMappings[i].guest * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE); + *(void**)(bucket + PPCFuncMappings[i].guest * 2) = PPCFuncMappings[i].host; + } + } +} + +void CodeCache::Insert(uint32_t guest, const void* host) +{ + VirtualAlloc(bucket + static_cast(guest) * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE); + *reinterpret_cast(bucket + static_cast(guest) * 2) = host; +} + +void* CodeCache::Find(uint32_t guest) const +{ + return *reinterpret_cast(bucket + static_cast(guest) * 2); +} diff --git a/UnleashedRecomp/cpu/code_cache.h b/UnleashedRecomp/cpu/code_cache.h new file mode 100644 index 00000000..443cb6d0 --- /dev/null +++ b/UnleashedRecomp/cpu/code_cache.h @@ -0,0 +1,16 @@ +#pragma once + +struct CodeCache +{ + char* bucket{}; + + CodeCache(); + ~CodeCache(); + + void Init(); + void Insert(uint32_t guest, const void* host); + + void* Find(uint32_t guest) const; +}; + +extern CodeCache gCodeCache; \ No newline at end of file diff --git a/UnleashedRecomp/cpu/guest_code.h b/UnleashedRecomp/cpu/guest_code.h new file mode 100644 index 00000000..bf0453c8 --- /dev/null +++ b/UnleashedRecomp/cpu/guest_code.h @@ -0,0 +1,23 @@ +#pragma once +#include "ppc_context.h" +#include "memory.h" + +struct GuestCode +{ + inline static void Run(void* hostAddress, PPCContext* ctx, void* baseAddress, void* callStack) + { + ctx->fpscr.loadFromHost(); + reinterpret_cast(hostAddress)(*ctx, reinterpret_cast(baseAddress)); + } + + inline static void Run(void* hostAddress, PPCContext* ctx) + { + ctx->fpscr.loadFromHost(); + reinterpret_cast(hostAddress)(*ctx, reinterpret_cast(gMemory.base)); + } + + inline static void Run(void* hostAddress) + { + Run(hostAddress, GetPPCContext()); + } +}; diff --git a/UnleashedRecomp/cpu/guest_thread.cpp b/UnleashedRecomp/cpu/guest_thread.cpp new file mode 100644 index 00000000..0edb3464 --- /dev/null +++ b/UnleashedRecomp/cpu/guest_thread.cpp @@ -0,0 +1,143 @@ +#include +#include "guest_thread.h" +#include +#include +#include +#include "code_cache.h" +#include "guest_code.h" +#include "ppc_context.h" + +constexpr size_t PCR_SIZE = 0xAB0; +constexpr size_t TLS_SIZE = 0x100; +constexpr size_t TEB_SIZE = 0x2E0; +constexpr size_t STACK_SIZE = 0x40000; +constexpr size_t CALL_STACK_SIZE = 0x8000; +constexpr size_t TOTAL_SIZE = PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE + CALL_STACK_SIZE; + +constexpr size_t TEB_OFFSET = PCR_SIZE + TLS_SIZE; + +DWORD GuestThread::Start(uint32_t function) +{ + const GuestThreadParameter parameter{ function }; + return Start(parameter); +} + +DWORD GuestThread::Start(const GuestThreadParameter& parameter) +{ + auto* thread = (uint8_t*)gUserHeap.Alloc(TOTAL_SIZE); + + const auto procMask = (uint8_t)(parameter.flags >> 24); + const auto cpuNumber = procMask == 0 ? 0 : 7 - std::countl_zero(procMask); + + memset(thread, 0, TOTAL_SIZE); + + *(uint32_t*)thread = std::byteswap(gMemory.MapVirtual(thread + PCR_SIZE)); // tls pointer + *(uint32_t*)(thread + 0x100) = std::byteswap(gMemory.MapVirtual(thread + PCR_SIZE + TLS_SIZE)); // teb pointer + *(thread + 0x10C) = cpuNumber; + + *(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky + *(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = std::byteswap(GetCurrentThreadId()); // thread id + + PPCContext ppcContext{}; + ppcContext.fn = (uint8_t*)gCodeCache.bucket; + ppcContext.r1.u64 = gMemory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer + ppcContext.r3.u64 = parameter.value; + ppcContext.r13.u64 = gMemory.MapVirtual(thread); + + SetPPCContext(ppcContext); + + GuestCode::Run(gCodeCache.Find(parameter.function), &ppcContext, gMemory.Translate(0), gMemory.Translate(ppcContext.r1.u32)); + gUserHeap.Free(thread); + + return (DWORD)ppcContext.r3.u64; +} + +DWORD HostThreadStart(void* pParameter) +{ + auto* parameter = static_cast(pParameter); + const auto result = GuestThread::Start(*parameter); + + delete parameter; + return result; +} + +HANDLE GuestThread::Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId) +{ + const auto hostCreationFlags = (flags & 1) != 0 ? CREATE_SUSPENDED : 0; + //return CreateThread(nullptr, 0, Start, (void*)((uint64_t(parameter) << 32) | function), suspended ? CREATE_SUSPENDED : 0, threadId); + return CreateThread(nullptr, 0, HostThreadStart, new GuestThreadParameter{ function, parameter, flags }, hostCreationFlags, threadId); +} + +void GuestThread::SetThreadName(uint32_t id, const char* name) +{ +#pragma pack(push,8) + const DWORD MS_VC_EXCEPTION = 0x406D1388; + + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; +#pragma pack(pop) + + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = id; + info.dwFlags = 0; + + __try + { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } +} + +void GuestThread::SetLastError(DWORD error) +{ + auto* thread = (char*)gMemory.Translate(GetPPCContext()->r13.u32); + if (*(DWORD*)(thread + 0x150)) + { + // Program doesn't want errors + return; + } + + // TEB + 0x160 : Win32LastError + *(DWORD*)(thread + TEB_OFFSET + 0x160) = std::byteswap(error); +} + +PPCContext* GuestThread::Invoke(uint32_t address) +{ + auto* ctx = GetPPCContext(); + GuestCode::Run(gCodeCache.Find(address), ctx); + + return ctx; +} + +void SetThreadNameImpl(uint32_t a1, uint32_t threadId, uint32_t* name) +{ + GuestThread::SetThreadName(threadId, (const char*)gMemory.Translate(std::byteswap(*name))); +} + +int GetThreadPriorityImpl(uint32_t hThread) +{ + return GetThreadPriority((HANDLE)hThread); +} + +DWORD SetThreadIdealProcessorImpl(uint32_t hThread, DWORD dwIdealProcessor) +{ + return SetThreadIdealProcessor((HANDLE)hThread, dwIdealProcessor); +} + +GUEST_FUNCTION_HOOK(sub_82DFA2E8, SetThreadNameImpl); +GUEST_FUNCTION_HOOK(sub_82BD57A8, GetThreadPriorityImpl); +GUEST_FUNCTION_HOOK(sub_82BD5910, SetThreadIdealProcessorImpl); + +void GuestThread::InitHooks() +{ + +} diff --git a/UnleashedRecomp/cpu/guest_thread.h b/UnleashedRecomp/cpu/guest_thread.h new file mode 100644 index 00000000..e37fad85 --- /dev/null +++ b/UnleashedRecomp/cpu/guest_thread.h @@ -0,0 +1,21 @@ +#pragma once + +struct PPCContext; +struct GuestThreadParameter +{ + uint32_t function; + uint32_t value; + uint32_t flags; +}; + +struct GuestThread +{ + static DWORD Start(uint32_t function); + static DWORD Start(const GuestThreadParameter& parameter); + static HANDLE Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId); + + static void SetThreadName(uint32_t id, const char* name); + static void SetLastError(DWORD error); + static PPCContext* Invoke(uint32_t address); + static void InitHooks(); +}; \ No newline at end of file diff --git a/UnleashedRecomp/cpu/ppc_context.h b/UnleashedRecomp/cpu/ppc_context.h new file mode 100644 index 00000000..888c7983 --- /dev/null +++ b/UnleashedRecomp/cpu/ppc_context.h @@ -0,0 +1,15 @@ +#pragma once +#include "ppc/ppc_context.h" +#include "ppc/ppc_recomp_shared.h" + +inline thread_local PPCContext* gPPCContext; + +inline PPCContext* GetPPCContext() +{ + return gPPCContext; +} + +inline void SetPPCContext(PPCContext& ctx) +{ + gPPCContext = &ctx; +} diff --git a/UnleashedRecomp/framework.h b/UnleashedRecomp/framework.h new file mode 100644 index 00000000..a8a87599 --- /dev/null +++ b/UnleashedRecomp/framework.h @@ -0,0 +1,69 @@ +#pragma once + +#ifdef _MSVC + #define SWA_DLLEXPORT __declspec(dllexport) + #define SWA_DLLIMPORT __declspec(dllimport) +#else + #define SWA_DLLEXPORT __attribute__((dllexport)) + #define SWA_DLLIMPORT __attribute__((dllimport)) +#endif + +#ifdef SWA_IMPL +#define SWA_API extern "C" SWA_DLLEXPORT +#else +#define SWA_API extern "C" SWA_DLLIMPORT +#endif + +template +void ByteSwap(T& value) +{ + value = std::byteswap(value); +} + +template +T RoundUp(const T& in_rValue, uint32_t in_round) +{ + return (in_rValue + in_round - 1) & ~(in_round - 1); +} + +template +T RoundDown(const T& in_rValue, uint32_t in_round) +{ + return in_rValue & ~(in_round - 1); +} + +inline bool FileExists(const char* path) +{ + const auto attributes = GetFileAttributesA(path); + return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); + +} + +inline bool DirectoryExists(const char* path) +{ + const auto attributes = GetFileAttributesA(path); + return attributes != INVALID_FILE_ATTRIBUTES && !!(attributes & FILE_ATTRIBUTE_DIRECTORY); +} + +inline size_t StringHash(const std::string_view& str) +{ + return XXH3_64bits(str.data(), str.size()); +} + +template +constexpr size_t FirstBitLow(TValue value) +{ + constexpr size_t nbits = sizeof(TValue) * 8; + constexpr auto zero = TValue{}; + constexpr auto one = static_cast(1); + + for (size_t i = 0; i < nbits; i++) + { + if ((value & (one << i)) != zero) + { + return i; + } + } + + return 0; +} \ No newline at end of file diff --git a/UnleashedRecomp/gpu/Window.cpp b/UnleashedRecomp/gpu/Window.cpp new file mode 100644 index 00000000..a4b8a232 --- /dev/null +++ b/UnleashedRecomp/gpu/Window.cpp @@ -0,0 +1 @@ +#include "Window.h" \ No newline at end of file diff --git a/UnleashedRecomp/gpu/Window.h b/UnleashedRecomp/gpu/Window.h new file mode 100644 index 00000000..7b9637ef --- /dev/null +++ b/UnleashedRecomp/gpu/Window.h @@ -0,0 +1 @@ +#pragma once \ No newline at end of file diff --git a/UnleashedRecomp/hid/hid.cpp b/UnleashedRecomp/hid/hid.cpp new file mode 100644 index 00000000..712f2333 --- /dev/null +++ b/UnleashedRecomp/hid/hid.cpp @@ -0,0 +1,22 @@ +#include +#include "hid.h" + +DWORD hid::GetState(DWORD dwUserIndex, XAMINPUT_STATE* pState) +{ + return 1; +} + +DWORD hid::SetState(DWORD dwUserIndex, XAMINPUT_VIBRATION* pVibration) +{ + return 1; +} + +DWORD hid::GetCapabilities(DWORD dwUserIndex, XAMINPUT_CAPABILITIES* pCaps) +{ + return 1; +} + +int hid::OnSDLEvent(void*, SDL_Event* event) +{ + return 0; +} diff --git a/UnleashedRecomp/hid/hid.h b/UnleashedRecomp/hid/hid.h new file mode 100644 index 00000000..a0a68f93 --- /dev/null +++ b/UnleashedRecomp/hid/hid.h @@ -0,0 +1,13 @@ +#pragma once + +union SDL_Event; +namespace hid +{ + void Init(); + DWORD GetState(DWORD dwUserIndex, XAMINPUT_STATE* pState); + DWORD SetState(DWORD dwUserIndex, XAMINPUT_VIBRATION* pVibration); + + DWORD GetCapabilities(DWORD dwUserIndex, XAMINPUT_CAPABILITIES* pCaps); + + int OnSDLEvent(void*, SDL_Event* event); +} \ No newline at end of file diff --git a/UnleashedRecomp/kernel/FreeList.h b/UnleashedRecomp/kernel/FreeList.h new file mode 100644 index 00000000..0adb5d39 --- /dev/null +++ b/UnleashedRecomp/kernel/FreeList.h @@ -0,0 +1,40 @@ +#pragma once + +template +struct FreeList +{ + std::vector items; + std::vector freed{}; + + void Free(T& item) + { + std::destroy_at(&item); + freed.push_back(&item - items.data()); + } + + void Free(size_t index) + { + std::destroy_at(&items[index]); + freed.push_back(index); + } + + size_t Alloc() + { + if (freed.size()) + { + auto idx = freed[freed.size() - 1]; + freed.pop_back(); + + std::construct_at(&items[idx]); + return idx; + } + + items.emplace_back(); + return items.size() - 1; + } + + T& operator[](size_t idx) + { + return items[idx]; + } +}; \ No newline at end of file diff --git a/UnleashedRecomp/kernel/function.h b/UnleashedRecomp/kernel/function.h new file mode 100644 index 00000000..5cdafc5f --- /dev/null +++ b/UnleashedRecomp/kernel/function.h @@ -0,0 +1,216 @@ +#pragma once +#include +#include +#include "xbox.h" + +template +constexpr std::tuple function_args(R(*)(T...)) noexcept +{ + return std::tuple(); +} + +template +static constexpr decltype(V) constant_v = V; + +template +static constexpr bool is_precise_v = std::is_same_v || std::is_same_v; + +template +struct arg_count_t +{ + static constexpr size_t value = std::tuple_size_v; +}; + +template +std::enable_if_t<(I >= sizeof...(TArgs)), void> _tuple_for(std::tuple&, const TCallable& callable) noexcept +{ + +} + +template +std::enable_if_t<(I < sizeof...(TArgs)), void> _tuple_for(std::tuple& tpl, const TCallable& callable) noexcept +{ + callable(std::get(tpl), I); + + _tuple_for(tpl, callable); +} + +struct ArgTranslator +{ + FORCEINLINE constexpr static uint64_t GetIntegerArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept + { + if (arg <= 7) + { + switch (arg) + { + case 0: return ctx.r3.u32; + case 1: return ctx.r4.u32; + case 2: return ctx.r5.u32; + case 3: return ctx.r6.u32; + case 4: return ctx.r7.u32; + case 5: return ctx.r8.u32; + case 6: return ctx.r9.u32; + case 7: return ctx.r10.u32; + default: break; + } + } + + return *reinterpret_cast(base + ctx.r1.u32 + 0x54 + ((arg - 8) * 8)); + } + + FORCEINLINE static double GetPrecisionArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept + { + switch (arg) + { + case 0: return ctx.f1.f64; + case 1: return ctx.f2.f64; + case 2: return ctx.f3.f64; + case 3: return ctx.f4.f64; + case 4: return ctx.f5.f64; + case 5: return ctx.f6.f64; + case 6: return ctx.f7.f64; + case 7: return ctx.f8.f64; + case 8: return ctx.f9.f64; + case 9: return ctx.f10.f64; + case 10: return ctx.f11.f64; + case 11: return ctx.f12.f64; + case 12: return ctx.f13.f64; + [[unlikely]] default: break; + } + + // how did you end up here + return 0; + } + + template + FORCEINLINE constexpr static std::enable_if_t, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept + { + if constexpr (is_precise_v) + { + return static_cast(GetPrecisionArgumentValue(ctx, base, idx)); + } + else + { + return static_cast(GetIntegerArgumentValue(ctx, base, idx)); + } + } + + template + FORCEINLINE constexpr static std::enable_if_t, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept + { + const auto v = GetIntegerArgumentValue(ctx, base, idx); + if (!v) + { + return nullptr; + } + + return reinterpret_cast(base + static_cast(v)); + } +}; + +struct Argument +{ + int type{}; + int ordinal{}; +}; + +template +constexpr std::array::value> GatherFunctionArguments() +{ + std::array::value> args{}; + + int intOrdinal{}; + int floatOrdinal{}; + size_t i{}; + + if constexpr (!args.empty()) + { + std::apply([&](const auto& first, const auto&... rest) + { + auto append = [&](const T & v) + { + if constexpr (is_precise_v) + { + args[i] = { 1, floatOrdinal++ }; + } + else + { + intOrdinal++; + args[i] = { 0, static_cast(i) }; // what the fuck + } + + i++; + }; + + append(first); + (append(rest), ...); + }, function_args(Func)); + } + + return args; +} + +template +struct arg_ordinal_t +{ + static constexpr size_t value = GatherFunctionArguments()[I].ordinal; +}; + +template +void _translate_args(PPCContext& ctx, uint8_t* base, std::tuple&) noexcept +requires (I >= sizeof...(TArgs)) +{ +} + +template +std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args(PPCContext& ctx, uint8_t* base, std::tuple& tpl) noexcept +{ + using T = std::tuple_element_t>; + std::get(tpl) = ArgTranslator::GetValue(ctx, base, arg_ordinal_t::value); + + _translate_args(ctx, base, tpl); +} + +template +PPC_FUNC(GuestFunction) +{ + using ret_t = decltype(std::apply(Func, function_args(Func))); + + auto args = function_args(Func); + _translate_args(ctx, base, args); + + if constexpr (std::is_same_v) + { + std::apply(Func, args); + } + else + { + auto v = std::apply(Func, args); + + if constexpr (std::is_pointer()) + { + if (v != nullptr) + { + ctx.r3.u64 = static_cast(reinterpret_cast(v) - reinterpret_cast(base)); + } + else + { + ctx.r3.u64 = NULL; + } + } + else if constexpr (is_precise_v) + { + ctx.f1.f64 = v; + } + else + { + ctx.r3.u64 = (uint64_t)v; + } + } +} + +#define GUEST_FUNCTION_HOOK(subroutine, function) \ + PPC_FUNC(subroutine) { GuestFunction(ctx, base); } + +#define GUEST_FUNCTION_STUB(subroutine) \ + PPC_FUNC(subroutine) { } diff --git a/UnleashedRecomp/kernel/heap.cpp b/UnleashedRecomp/kernel/heap.cpp new file mode 100644 index 00000000..4cf184ff --- /dev/null +++ b/UnleashedRecomp/kernel/heap.cpp @@ -0,0 +1,135 @@ +#include +#include "heap.h" +#include "memory.h" +#include "function.h" + +constexpr size_t RESERVED_BEGIN = 0x7FEA0000; +constexpr size_t RESERVED_END = 0xA0000000; + +void Heap::Init() +{ + gMemory.Alloc(0x20000, RESERVED_BEGIN - 0x20000, MEM_COMMIT); + heap = o1heapInit(gMemory.Translate(0x20000), RESERVED_BEGIN - 0x20000); + + gMemory.Alloc(RESERVED_END, 0x100000000 - RESERVED_END, MEM_COMMIT); + physicalHeap = o1heapInit(gMemory.Translate(RESERVED_END), 0x100000000 - RESERVED_END); +} + +void* Heap::Alloc(size_t size) +{ + std::lock_guard lock(mutex); + + return o1heapAllocate(heap, std::max(1, size)); +} + +void* Heap::AllocPhysical(size_t size, size_t alignment) +{ + size = std::max(1, size); + alignment = alignment == 0 ? 0x1000 : std::max(16, alignment); + + std::lock_guard lock(physicalMutex); + + void* ptr = o1heapAllocate(physicalHeap, size + alignment); + size_t aligned = ((size_t)ptr + alignment) & ~(alignment - 1); + + *((void**)aligned - 1) = ptr; + *((size_t*)aligned - 2) = size + O1HEAP_ALIGNMENT; + + return (void*)aligned; +} + +void Heap::Free(void* ptr) +{ + if (ptr >= physicalHeap) + { + std::lock_guard lock(physicalMutex); + o1heapFree(physicalHeap, *((void**)ptr - 1)); + } + else + { + std::lock_guard lock(mutex); + o1heapFree(heap, ptr); + } +} + +size_t Heap::Size(void* ptr) +{ + if (ptr) + return *((size_t*)ptr - 2) - O1HEAP_ALIGNMENT; // relies on fragment header in o1heap.c + + return 0; +} + +uint32_t RtlAllocateHeap(uint32_t heapHandle, uint32_t flags, uint32_t size) +{ + void* ptr = gUserHeap.Alloc(size); + if ((flags & 0x8) != 0) + memset(ptr, 0, size); + + assert(ptr); + return gMemory.MapVirtual(ptr); +} + +uint32_t RtlReAllocateHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer, uint32_t size) +{ + void* ptr = gUserHeap.Alloc(size); + if ((flags & 0x8) != 0) + memset(ptr, 0, size); + + if (memoryPointer != 0) + { + void* oldPtr = gMemory.Translate(memoryPointer); + memcpy(ptr, oldPtr, std::min(size, gUserHeap.Size(oldPtr))); + gUserHeap.Free(oldPtr); + } + + assert(ptr); + return gMemory.MapVirtual(ptr); +} + +uint32_t RtlFreeHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer) +{ + if (memoryPointer != NULL) + gUserHeap.Free(gMemory.Translate(memoryPointer)); + + return true; +} + +uint32_t RtlSizeHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer) +{ + if (memoryPointer != NULL) + return (uint32_t)gUserHeap.Size(gMemory.Translate(memoryPointer)); + + return 0; +} + +uint32_t XAlloc(uint32_t size, uint32_t flags) +{ + void* ptr = (flags & 0x80000000) != 0 ? + gUserHeap.AllocPhysical(size, (1ull << ((flags >> 24) & 0xF))) : + gUserHeap.Alloc(size); + + if ((flags & 0x40000000) != 0) + memset(ptr, 0, size); + + assert(ptr); + return gMemory.MapVirtual(ptr); +} + +void XFree(uint32_t baseAddress, uint32_t flags) +{ + if (baseAddress != NULL) + gUserHeap.Free(gMemory.Translate(baseAddress)); +} + +GUEST_FUNCTION_STUB(sub_82BD7788); // HeapCreate +GUEST_FUNCTION_STUB(sub_82BD9250); // HeapDestroy + +GUEST_FUNCTION_HOOK(sub_82BD7D30, RtlAllocateHeap); +GUEST_FUNCTION_HOOK(sub_82BD8600, RtlFreeHeap); +GUEST_FUNCTION_HOOK(sub_82BD88F0, RtlReAllocateHeap); +GUEST_FUNCTION_HOOK(sub_82BD6FD0, RtlSizeHeap); + +// Seems like these handle allocation of virtual and physical pages +GUEST_FUNCTION_HOOK(sub_831CC9C8, XAlloc); +GUEST_FUNCTION_HOOK(sub_831CCA60, XFree); \ No newline at end of file diff --git a/UnleashedRecomp/kernel/heap.h b/UnleashedRecomp/kernel/heap.h new file mode 100644 index 00000000..e3e2ef75 --- /dev/null +++ b/UnleashedRecomp/kernel/heap.h @@ -0,0 +1,38 @@ +#pragma once +#include "Mutex.h" +#include + +struct Heap +{ + Mutex mutex; + O1HeapInstance* heap; + + Mutex physicalMutex; + O1HeapInstance* physicalHeap; + + void Init(); + + void* Alloc(size_t size); + void* AllocPhysical(size_t size, size_t alignment); + void Free(void* ptr); + + size_t Size(void* ptr); + + template + T* Alloc(Args... args) + { + T* obj = (T*)Alloc(sizeof(T)); + new (obj) T(std::forward(args)...); + return obj; + } + + template + T* AllocPhysical(Args... args) + { + T* obj = (T*)AllocPhysical(sizeof(T), alignof(T)); + new (obj) T(std::forward(args)...); + return obj; + } +}; + +extern Heap gUserHeap; \ No newline at end of file diff --git a/UnleashedRecomp/kernel/imports.cpp b/UnleashedRecomp/kernel/imports.cpp new file mode 100644 index 00000000..dc826e2b --- /dev/null +++ b/UnleashedRecomp/kernel/imports.cpp @@ -0,0 +1,1726 @@ +#include +#include +#include +#include "function.h" +#include "xex.h" +#include "xbox.h" +#include "heap.h" +#include "memory.h" +// #include "audio.h" +#include +#include "xam.h" +#include "xdm.h" +#include + +#include + +inline void CloseKernelObject(XDISPATCHER_HEADER& header) +{ + if (header.WaitListHead.Flink != OBJECT_SIGNATURE) + { + return; + } + + ObCloseHandle(header.WaitListHead.Blink); +} + +DWORD GuestTimeoutToMilliseconds(XLPQWORD timeout) +{ + return timeout ? (*timeout * -1) / 10000 : INFINITE; +} + +void XboxKrnlVersion() +{ + printf("!!! STUB !!! XboxKrnlVersion\n"); +} + +void VdGpuClockInMHz() +{ + printf("!!! STUB !!! VdGpuClockInMHz\n"); +} + +void VdHSIOCalibrationLock() +{ + printf("!!! STUB !!! VdHSIOCalibrationLock\n"); +} + +void VdGlobalXamDevice() +{ + printf("!!! STUB !!! VdGlobalXamDevice\n"); +} + +void VdGlobalDevice() +{ + printf("!!! STUB !!! VdGlobalDevice\n"); +} + +void KeCertMonitorData() +{ + printf("!!! STUB !!! KeCertMonitorData\n"); +} + +void XexExecutableModuleHandle() +{ + printf("!!! STUB !!! XexExecutableModuleHandle\n"); +} + +void ExLoadedCommandLine() +{ + printf("!!! STUB !!! ExLoadedCommandLine\n"); +} + +void KeDebugMonitorData() +{ + printf("!!! STUB !!! KeDebugMonitorData\n"); +} + +void ExThreadObjectType() +{ + printf("!!! STUB !!! ExThreadObjectType\n"); +} + +void KeTimeStampBundle() +{ + printf("!!! STUB !!! KeTimeStampBundle\n"); +} + +void XboxHardwareInfo() +{ + printf("!!! STUB !!! XboxHardwareInfo\n"); +} + +void XGetVideoMode() +{ + printf("!!! STUB !!! XGetVideoMode\n"); +} + +uint32_t XGetGameRegion() +{ + // printf("!!! STUB !!! XGetGameRegion\n"); + if (Config::Language == ELanguage_Japanese) + return 0x0101; + + return 0x03FF; +} + +uint32_t XMsgStartIORequest(DWORD App, DWORD Message, XXOVERLAPPED* lpOverlapped, void* Buffer, DWORD szBuffer) +{ + // printf("!!! STUB !!! XMsgStartIORequest\n"); + return STATUS_SUCCESS; +} + +uint32_t XamUserGetSigninState(uint32_t userIndex) +{ + // printf("!!! STUB !!! XamUserGetSigninState\n"); + return userIndex == 0; +} + +uint32_t XamGetSystemVersion() +{ + // printf("!!! STUB !!! XamGetSystemVersion\n"); + return 0; +} + + + +void XamContentDelete() +{ + printf("!!! STUB !!! XamContentDelete\n"); +} + + +uint32_t XamContentGetCreator(DWORD userIndex, const XCONTENT_DATA* contentData, PBOOL isCreator, XLPQWORD xuid, XXOVERLAPPED* overlapped) +{ + // printf("!!! STUB !!! XamContentGetCreator\n"); + + if (isCreator) + { + *isCreator = true; + } + + if (xuid) + { + *xuid = 0xB13EBABEBABEBABE; + } + + return 0; +} + +uint32_t XamContentGetDeviceState() +{ + // printf("!!! STUB !!! XamContentGetDeviceState\n"); + return 0; +} + +uint32_t XamUserGetSigninInfo(uint32_t userIndex, uint32_t flags, XUSER_SIGNIN_INFO* info) +{ + //printf("!!! STUB !!! XamUserGetSigninInfo\n"); + if (userIndex == 0) + { + memset(info, 0, sizeof(*info)); + info->xuid = 0xB13EBABEBABEBABE; + info->SigninState = 1; + strcpy(info->Name, "SWA"); + return 0; + } + + return 0x00000525; +} + +void XamShowSigninUI() +{ + printf("!!! STUB !!! XamShowSigninUI\n"); +} + +uint32_t XamShowDeviceSelectorUI( + uint32_t userIndex, + uint32_t contentType, + uint32_t contentFlags, + uint64_t totalRequested, + XDWORD* deviceId, + XXOVERLAPPED* overlapped) +{ + // printf("!!! STUB !!! XamShowDeviceSelectorUI\n"); + XamNotifyEnqueueEvent(9, true); + *deviceId = 1; + XamNotifyEnqueueEvent(9, false); + return 0; +} + +void XamShowDirtyDiscErrorUI() +{ + printf("!!! STUB !!! XamShowDirtyDiscErrorUI\n"); +} + +void XamEnableInactivityProcessing() +{ + printf("!!! STUB !!! XamEnableInactivityProcessing\n"); +} + +void XamResetInactivity() +{ + printf("!!! STUB !!! XamResetInactivity\n"); +} + +void XamShowMessageBoxUIEx() +{ + printf("!!! STUB !!! XamShowMessageBoxUIEx\n"); +} + +uint32_t XGetLanguage() +{ + // printf("!!! STUB !!! XGetLanguage\n"); + return Config::Language; +} + +uint32_t XGetAVPack() +{ + // printf("!!! STUB !!! XGetAVPack\n"); + return 0; +} + +void XamLoaderTerminateTitle() +{ + printf("!!! STUB !!! XamLoaderTerminateTitle(void)\n"); +} + +void XamGetExecutionId() +{ + printf("!!! STUB !!! XamGetExecutionId\n"); +} + +void XamLoaderLaunchTitle() +{ + printf("!!! STUB !!! XamLoaderLaunchTitle(char\n"); +} + +void NtOpenFile() +{ + printf("!!! STUB !!! NtOpenFile\n"); +} + +void RtlInitAnsiString(XANSI_STRING* destination, char* source) +{ + printf("!!! STUB !!! RtlInitAnsiString %s\n", source); + const uint16_t length = source ? (uint16_t)strlen(source) : 0; + destination->Length = length; + destination->MaximumLength = length + 1; + destination->Buffer = source; +} + +DWORD NtCreateFile( + XLPDWORD FileHandle, + DWORD DesiredAccess, + XOBJECT_ATTRIBUTES* Attributes, + XIO_STATUS_BLOCK* IoStatusBlock, + uint64_t* AllocationSize, + uint32_t FileAttributes, + uint32_t ShareAccess, + uint32_t CreateDisposition, + uint32_t CreateOptions) +{ + return 0; +} + +uint32_t NtClose(uint32_t handle) +{ + if (handle == (uint32_t)INVALID_HANDLE_VALUE) + { + return 0xFFFFFFFF; + } + + if (CHECK_GUEST_HANDLE(handle)) + { + ObCloseHandle(HOST_HANDLE(handle)); + return 0; + } + + // printf("!!! STUB !!! NtClose\n"); + return CloseHandle((HANDLE)handle) ? 0 : 0xFFFFFFFF; +} + +void NtSetInformationFile() +{ + printf("!!! STUB !!! NtSetInformationFile\n"); +} + +uint32_t FscSetCacheElementCount() +{ + // printf("!!! STUB !!! FscSetCacheElementCount\n"); + return 0; +} + +DWORD NtWaitForSingleObjectEx(DWORD Handle, DWORD WaitMode, DWORD Alertable, XLPQWORD Timeout) +{ + // printf("!!! STUB !!! NtWaitForSingleObjectEx\n"); + const auto status = WaitForSingleObjectEx((HANDLE)Handle, GuestTimeoutToMilliseconds(Timeout), Alertable); + + if (status == WAIT_IO_COMPLETION) + { + return STATUS_USER_APC; + } + else if (status) + { + return STATUS_ALERTED; + } + + return STATUS_SUCCESS; +} + +void NtWriteFile() +{ + printf("!!! STUB !!! NtWriteFile\n"); +} + +void vsprintf_x() +{ + printf("!!! STUB !!! vsprintf\n"); +} + +uint32_t ExGetXConfigSetting(uint16_t Category, uint16_t Setting, void* Buffer, uint16_t SizeOfBuffer, XLPDWORD RequiredSize) +{ + // printf("Invoking method ExGetXConfigSetting\n"); + uint32_t data[4]{}; + + switch (Category) + { + // XCONFIG_SECURED_CATEGORY + case 0x0002: + { + switch (Setting) + { + // XCONFIG_SECURED_AV_REGION + case 0x0002: + data[0] = std::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] = std::byteswap((uint32_t)Config::Language); + break; + + // XCONFIG_USER_VIDEO_FLAGS + case 0x000A: + data[0] = std::byteswap(0x00040000); + break; + + // XCONFIG_USER_RETAIL_FLAGS + case 0x000C: + data[0] = std::byteswap(1); + break; + + // XCONFIG_USER_COUNTRY + case 0x000E: + data[0] = std::byteswap(103); + break; + + default: + return 1; + } + } + } + + *RequiredSize = 4; + memcpy(Buffer, data, std::min((size_t)SizeOfBuffer, sizeof(data))); + + return 0; +} + +void NtQueryVirtualMemory() +{ + printf("!!! STUB !!! NtQueryVirtualMemory\n"); +} + +void MmQueryStatistics() +{ + printf("!!! STUB !!! MmQueryStatistics\n"); +} + +uint32_t NtCreateEvent(uint32_t* handle, void* objAttributes, uint32_t eventType, uint32_t initialState) +{ + //printf("!!! STUB !!! NtCreateEvent\n"); + *handle = std::byteswap((uint32_t)CreateEventA(nullptr, !eventType, !!initialState, nullptr)); + return 0; +} + +uint32_t XexCheckExecutablePrivilege() +{ + //printf("!!! STUB !!! XexCheckExecutablePrivilege\n"); + return 0; +} + +void DbgPrint() +{ + printf("!!! STUB !!! DbgPrint\n"); +} + +void __C_specific_handler_x() +{ + printf("!!! STUB !!! __C_specific_handler\n"); +} + +void RtlNtStatusToDosError() +{ + printf("!!! STUB !!! RtlNtStatusToDosError\n"); +} + +void XexGetProcedureAddress() +{ + printf("!!! STUB !!! XexGetProcedureAddress\n"); +} + +void XexGetModuleSection() +{ + printf("!!! STUB !!! XexGetModuleSection\n"); +} + +NTSTATUS RtlUnicodeToMultiByteN(PCHAR MultiByteString, DWORD MaxBytesInMultiByteString, XLPDWORD BytesInMultiByteString, PCWCH UnicodeString, ULONG BytesInUnicodeString) +{ + const auto reqSize = BytesInUnicodeString / sizeof(wchar_t); + if (BytesInMultiByteString) + { + *BytesInMultiByteString = reqSize; + } + + if (reqSize > MaxBytesInMultiByteString) + { + return STATUS_FAIL_CHECK; + } + + for (size_t i = 0; i < reqSize; i++) + { + const auto c = std::byteswap(UnicodeString[i]); + MultiByteString[i] = c < 256 ? c : '?'; + } + + return STATUS_SUCCESS; +} + +DWORD KeDelayExecutionThread(DWORD WaitMode, bool Alertable, XLPQWORD Timeout) +{ + if (Alertable) // We don't do async file reads + return STATUS_USER_APC; + + // printf("!!! STUB !!! KeDelayExecutionThread\n"); + + timeBeginPeriod(1); + const auto status = SleepEx(GuestTimeoutToMilliseconds(Timeout), Alertable); + timeEndPeriod(1); + + if (status == WAIT_IO_COMPLETION) + { + return STATUS_USER_APC; + } + else if (status) + { + return STATUS_ALERTED; + } + + return STATUS_SUCCESS; +} + +void ExFreePool() +{ + printf("!!! STUB !!! ExFreePool\n"); +} + +void NtQueryInformationFile() +{ + printf("!!! STUB !!! NtQueryInformationFile\n"); +} + +void NtQueryVolumeInformationFile() +{ + printf("!!! STUB !!! NtQueryVolumeInformationFile\n"); +} + +void NtQueryDirectoryFile() +{ + printf("!!! STUB !!! NtQueryDirectoryFile\n"); +} + +void NtReadFileScatter() +{ + printf("!!! STUB !!! NtReadFileScatter\n"); +} + +void NtReadFile() +{ + printf("!!! STUB !!! NtReadFile\n"); +} + +void NtDuplicateObject() +{ + printf("!!! STUB !!! NtDuplicateObject\n"); +} + +void NtAllocateVirtualMemory() +{ + printf("!!! STUB !!! NtAllocateVirtualMemory\n"); +} + +void NtFreeVirtualMemory() +{ + printf("!!! STUB !!! NtFreeVirtualMemory\n"); +} + +void ObDereferenceObject() +{ + //printf("!!! STUB !!! ObDereferenceObject\n"); +} + +void KeSetBasePriorityThread(uint32_t thread, int priority) +{ + //printf("!!! STUB !!! KeSetBasePriorityThread\n"); + if (priority == 16) + priority = 15; + else if (priority == -16) + priority = -15; + + SetThreadPriority((HANDLE)thread, priority); +} + +uint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, XLPDWORD object) +{ + //printf("Invoking method ObReferenceObjectByHandle\n"); + *object = handle; + return GetPPCContext()->lr == 0x82BD588C ? 0xFFFFFFFF : 0; +} + +void KeQueryBasePriorityThread() +{ + printf("!!! STUB !!! KeQueryBasePriorityThread\n"); +} + +uint32_t NtSuspendThread(uint32_t hThread, uint32_t* suspendCount) +{ + //printf("NtSuspendThread(): %x %x\n", hThread, suspendCount); + DWORD count = SuspendThread((HANDLE)hThread); + if (count == (DWORD)-1) + return E_FAIL; + + if (suspendCount != nullptr) + *suspendCount = std::byteswap(count); + + return S_OK; +} + +uint32_t KeSetAffinityThread(DWORD Thread, DWORD Affinity, XLPDWORD lpPreviousAffinity) +{ + // printf("Invoking method KeSetAffinityThread\n"); + if (lpPreviousAffinity) + { + *lpPreviousAffinity = 2; + } + return 0; +} + +struct Event : HostObject +{ + HANDLE handle; + + Event(XKEVENT* header) + { + handle = CreateEventA(nullptr, !header->Type, !!header->SignalState, nullptr); + } + + bool Set() + { + return SetEvent(handle); + } + + bool Reset() + { + return ResetEvent(handle); + } +}; + +struct Semaphore : HostObject +{ + HANDLE handle; + + Semaphore(XKSEMAPHORE* semaphore) + { + handle = CreateSemaphoreA(nullptr, semaphore->Header.SignalState, semaphore->Limit, nullptr); + } +}; + +extern "C" NTSYSAPI NTSTATUS NTAPI NtReleaseKeyedEvent( + IN HANDLE KeyedEventHandle, + IN PVOID Key, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL); + +void RtlLeaveCriticalSection(XRTL_CRITICAL_SECTION* cs) +{ + //printf("!!! STUB !!! RtlLeaveCriticalSection\n"); + + if (--cs->RecursionCount != 0) + { + InterlockedDecrement(&cs->LockCount); + return; + } + + cs->OwningThread = NULL; + + if (InterlockedDecrement(&cs->LockCount) != -1) + NtReleaseKeyedEvent(nullptr, cs, FALSE, nullptr); +} + +extern "C" NTSYSAPI NTSTATUS NTAPI NtWaitForKeyedEvent( + IN HANDLE KeyedEventHandle, + IN PVOID Key, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL); + +void RtlEnterCriticalSection(XRTL_CRITICAL_SECTION* cs) +{ + //printf("!!! STUB !!! RtlEnterCriticalSection\n"); + + const uint32_t thread = static_cast(GetPPCContext()->r13.u64); + if (cs->OwningThread == thread) + { + InterlockedIncrement(&cs->LockCount); + ++cs->RecursionCount; + return; + } + + uint32_t spinCount = cs->Header.Absolute * 256; + while (spinCount--) + { + if (InterlockedCompareExchange(&cs->LockCount, 0, -1) == -1) + { + cs->OwningThread = thread; + cs->RecursionCount = 1; + return; + } + } + + if (InterlockedIncrement(&cs->LockCount) != 0) + NtWaitForKeyedEvent(nullptr, cs, FALSE, nullptr); + + cs->OwningThread = thread; + cs->RecursionCount = 1; +} + +void RtlImageXexHeaderField() +{ + printf("!!! STUB !!! RtlImageXexHeaderField\n"); +} + +void HalReturnToFirmware() +{ + printf("!!! STUB !!! HalReturnToFirmware\n"); +} + +void RtlFillMemoryUlong() +{ + printf("!!! STUB !!! RtlFillMemoryUlong\n"); +} + +void KeBugCheckEx() +{ + __debugbreak(); +} + +uint32_t KeGetCurrentProcessType() +{ + //printf("!!! STUB !!! KeGetCurrentProcessType\n"); + return 1; +} + +void RtlCompareMemoryUlong() +{ + printf("!!! STUB !!! RtlCompareMemoryUlong\n"); +} + +uint32_t RtlInitializeCriticalSection(XRTL_CRITICAL_SECTION* cs) +{ + //printf("!!! STUB !!! RtlInitializeCriticalSection\n"); + cs->Header.Absolute = 0; + cs->LockCount = -1; + cs->RecursionCount = 0; + cs->OwningThread = 0; + + return 0; +} + +void RtlRaiseException_x() +{ + printf("!!! STUB !!! RtlRaiseException\n"); +} + +void KfReleaseSpinLock(uint32_t* spinLock) +{ + //printf("!!! STUB !!! KfReleaseSpinLock\n"); + *spinLock = 0; +} + +void KfAcquireSpinLock(uint32_t* spinLock) +{ + const auto ctx = GetPPCContext(); + //printf("!!! STUB !!! KfAcquireSpinLock\n"); + + while (InterlockedCompareExchange((volatile long*)spinLock, std::byteswap(*(uint32_t*)(gMemory.Translate(ctx->r13.u32 + 0x110))), 0) != 0) + { + Sleep(0); + } +} + +uint64_t KeQueryPerformanceFrequency() +{ + //printf("!!! STUB !!! KeQueryPerformanceFrequency\n"); + return 49875000; +} + +void MmFreePhysicalMemory(uint32_t type, uint32_t guestAddress) +{ + //printf("!!! STUB !!! MmFreePhysicalMemory\n"); + if (guestAddress != NULL) + gUserHeap.Free(gMemory.Translate(guestAddress)); +} + +bool VdPersistDisplay(uint32_t a1, uint32_t* a2) +{ + //printf("!!! STUB !!! VdPersistDisplay\n"); + *a2 = NULL; + return false; +} + +void VdSwap() +{ + printf("!!! STUB !!! VdSwap\n"); +} + +void VdGetSystemCommandBuffer() +{ + printf("!!! STUB !!! VdGetSystemCommandBuffer\n"); +} + +void KeReleaseSpinLockFromRaisedIrql(uint32_t* spinLock) +{ + //printf("!!! STUB !!! KeReleaseSpinLockFromRaisedIrql\n"); + *spinLock = 0; +} + +void KeAcquireSpinLockAtRaisedIrql(uint32_t* spinLock) +{ + const auto ctx = GetPPCContext(); + //printf("!!! STUB !!! KeAcquireSpinLockAtRaisedIrql\n"); + + while (InterlockedCompareExchange((volatile long*)spinLock, std::byteswap(*(uint32_t*)(gMemory.Translate(ctx->r13.u32 + 0x110))), 0) != 0) + { + Sleep(0); + } +} + +uint32_t KiApcNormalRoutineNop() +{ + //printf("Invoking method KiApcNormalRoutineNop\n"); + return 0; +} + +void VdEnableRingBufferRPtrWriteBack() +{ + printf("!!! STUB !!! VdEnableRingBufferRPtrWriteBack\n"); +} + +void VdInitializeRingBuffer() +{ + printf("!!! STUB !!! VdInitializeRingBuffer\n"); +} + +uint32_t MmGetPhysicalAddress(uint32_t address) +{ + printf("MmGetPhysicalAddress(): %x\n", address); + return address; +} + +void VdSetSystemCommandBufferGpuIdentifierAddress() +{ + printf("!!! STUB !!! VdSetSystemCommandBufferGpuIdentifierAddress\n"); +} + +void _vsnprintf_x() +{ + printf("!!! STUB !!! _vsnprintf\n"); +} + +void sprintf_x() +{ + printf("!!! STUB !!! sprintf\n"); +} + +void ExRegisterTitleTerminateNotification() +{ + printf("!!! STUB !!! ExRegisterTitleTerminateNotification\n"); +} + +void VdShutdownEngines() +{ + printf("!!! STUB !!! VdShutdownEngines\n"); +} + +void VdQueryVideoMode(XVIDEO_MODE* vm) +{ + //printf("!!! STUB !!! VdQueryVideoMode\n"); + memset(vm, 0, sizeof(XVIDEO_MODE)); + vm->DisplayWidth = 1280; + vm->DisplayHeight = 720; + vm->IsInterlaced = false; + vm->IsWidescreen = true; + vm->IsHighDefinition = true; + vm->RefreshRate = 0x42700000; + vm->VideoStandard = 1; + vm->Unknown4A = 0x4A; + vm->Unknown01 = 0x01; +} + +void VdGetCurrentDisplayInformation() +{ + printf("!!! STUB !!! VdGetCurrentDisplayInformation\n"); +} + +void VdSetDisplayMode() +{ + printf("!!! STUB !!! VdSetDisplayMode\n"); +} + +void VdSetGraphicsInterruptCallback() +{ + printf("!!! STUB !!! VdSetGraphicsInterruptCallback\n"); +} + +void VdInitializeEngines() +{ + printf("!!! STUB !!! VdInitializeEngines\n"); +} + +void VdIsHSIOTrainingSucceeded() +{ + printf("!!! STUB !!! VdIsHSIOTrainingSucceeded\n"); +} + +void VdGetCurrentDisplayGamma() +{ + printf("!!! STUB !!! VdGetCurrentDisplayGamma\n"); +} + +void VdQueryVideoFlags() +{ + printf("!!! STUB !!! VdQueryVideoFlags\n"); +} + +void VdCallGraphicsNotificationRoutines() +{ + printf("!!! STUB !!! VdCallGraphicsNotificationRoutines\n"); +} + +void VdInitializeScalerCommandBuffer() +{ + printf("!!! STUB !!! VdInitializeScalerCommandBuffer\n"); +} + +void KeLeaveCriticalRegion() +{ + printf("!!! STUB !!! KeLeaveCriticalRegion\n"); +} + +uint32_t VdRetrainEDRAM() +{ + //printf("!!! STUB !!! VdRetrainEDRAM\n"); + return 0; +} + +void VdRetrainEDRAMWorker() +{ + printf("!!! STUB !!! VdRetrainEDRAMWorker\n"); +} + +void KeEnterCriticalRegion() +{ + printf("!!! STUB !!! KeEnterCriticalRegion\n"); +} + +uint32_t MmAllocatePhysicalMemoryEx( + uint32_t flags, + uint32_t size, + uint32_t protect, + uint32_t minAddress, + uint32_t maxAddress, + uint32_t alignment) +{ + printf("MmAllocatePhysicalMemoryEx(): %x %x %x %x %x %x\n", flags, size, protect, minAddress, maxAddress, alignment); + return gMemory.MapVirtual(gUserHeap.AllocPhysical(size, alignment)); +} + +void ObDeleteSymbolicLink() +{ + printf("!!! STUB !!! ObDeleteSymbolicLink\n"); +} + +void ObCreateSymbolicLink() +{ + printf("!!! STUB !!! ObCreateSymbolicLink\n"); +} + +uint32_t MmQueryAddressProtect(uint32_t guestAddress) +{ + return PAGE_READWRITE; +} + +void VdEnableDisableClockGating() +{ + printf("!!! STUB !!! VdEnableDisableClockGating\n"); +} + +void KeBugCheck() +{ + __debugbreak(); +} + +void KeLockL2() +{ + printf("!!! STUB !!! KeLockL2\n"); +} + +void KeUnlockL2() +{ + printf("!!! STUB !!! KeUnlockL2\n"); +} + +bool KeSetEvent(XKEVENT* pEvent, DWORD Increment, bool Wait) +{ + // printf("!!! STUB !!! KeSetEvent\n"); + return ObQueryObject(*pEvent)->Set(); +} + +bool KeResetEvent(XKEVENT* pEvent) +{ + // printf("!!! STUB !!! KeResetEvent %X\n", GetCurrentThreadId()); + return ObQueryObject(*pEvent)->Reset(); +} + +DWORD KeWaitForSingleObject(XDISPATCHER_HEADER* Object, DWORD WaitReason, DWORD WaitMode, bool Alertable, XLPQWORD Timeout) +{ + // printf("!!! STUB !!! KeWaitForSingleObject %X\n", GetCurrentThreadId()); + const uint64_t timeout = GuestTimeoutToMilliseconds(Timeout); + + HANDLE handle = nullptr; + + switch (Object->Type) + { + case 0: + case 1: + handle = ObQueryObject(*Object)->handle; + break; + + case 5: + handle = ObQueryObject(*Object)->handle; + break; + + default: + assert(false); + break; + } + + return WaitForSingleObjectEx(handle, timeout, Alertable); +} + +uint32_t KeTlsGetValue(DWORD dwTlsIndex) +{ + //printf("!!! STUB !!! KeTlsGetValue\n"); + return (uint32_t)TlsGetValue(dwTlsIndex); +} + +BOOL KeTlsSetValue(DWORD dwTlsIndex, DWORD lpTlsValue) +{ + //printf("!!! STUB !!! KeTlsSetValue\n"); + return TlsSetValue(dwTlsIndex, (LPVOID)lpTlsValue); +} + +DWORD KeTlsAlloc() +{ + //printf("!!! STUB !!! KeTlsAlloc\n"); + return TlsAlloc(); +} + +BOOL KeTlsFree(DWORD dwTlsIndex) +{ + //printf("!!! STUB !!! KeTlsFree\n"); + return TlsFree(dwTlsIndex); +} + +DWORD XMsgInProcessCall(uint32_t app, uint32_t message, XDWORD* param1, XDWORD* param2) +{ + //printf("!!! STUB !!! XMsgInProcessCall\n"); + + if (message == 0x7001B) + { + uint32_t* ptr = (uint32_t*)gMemory.Translate(param1[1]); + ptr[0] = 0; + ptr[1] = 0; + } + + return 0; +} + +void XamUserReadProfileSettings( + uint32_t titleId, + uint32_t userIndex, + uint32_t xuidCount, + uint64_t* xuids, + uint32_t settingCount, + uint32_t* settingIds, + XDWORD* bufferSize, + void* buffer, + void* overlapped) +{ + //printf("!!! STUB !!! XamUserReadProfileSettings\n"); + if (buffer != nullptr) + { + memset(buffer, 0, *bufferSize); + } + else + { + *bufferSize = 4; + } +} + +void NetDll_WSAStartup() +{ + printf("!!! STUB !!! NetDll_WSAStartup\n"); +} + +void NetDll_WSACleanup() +{ + printf("!!! STUB !!! NetDll_WSACleanup\n"); +} + +void NetDll_socket() +{ + printf("!!! STUB !!! NetDll_socket\n"); +} + +void NetDll_closesocket() +{ + printf("!!! STUB !!! NetDll_closesocket\n"); +} + +void NetDll_setsockopt() +{ + printf("!!! STUB !!! NetDll_setsockopt\n"); +} + +void NetDll_bind() +{ + printf("!!! STUB !!! NetDll_bind\n"); +} + +void NetDll_connect() +{ + printf("!!! STUB !!! NetDll_connect\n"); +} + +void NetDll_listen() +{ + printf("!!! STUB !!! NetDll_listen\n"); +} + +void NetDll_accept() +{ + printf("!!! STUB !!! NetDll_accept\n"); +} + +void NetDll_select() +{ + printf("!!! STUB !!! NetDll_select\n"); +} + +void NetDll_recv() +{ + printf("!!! STUB !!! NetDll_recv\n"); +} + +void NetDll_send() +{ + printf("!!! STUB !!! NetDll_send\n"); +} + +void NetDll_inet_addr() +{ + printf("!!! STUB !!! NetDll_inet_addr\n"); +} + +void NetDll___WSAFDIsSet() +{ + printf("!!! STUB !!! NetDll___WSAFDIsSet\n"); +} + +void XMsgStartIORequestEx() +{ + printf("!!! STUB !!! XMsgStartIORequestEx\n"); +} + +void XexGetModuleHandle() +{ + printf("!!! STUB !!! XexGetModuleHandle\n"); +} + +bool RtlTryEnterCriticalSection(XRTL_CRITICAL_SECTION* cs) +{ + const uint32_t thread = static_cast(GetPPCContext()->r13.u64); + + if (InterlockedCompareExchange(&cs->LockCount, 0, -1) == -1) + { + cs->OwningThread = thread; + cs->RecursionCount = 1; + return true; + } + + if (cs->OwningThread == thread) + { + InterlockedIncrement(&cs->LockCount); + ++cs->RecursionCount; + return true; + } + + return false; +} + +void RtlInitializeCriticalSectionAndSpinCount(XRTL_CRITICAL_SECTION* cs, uint32_t spinCount) +{ + //printf("!!! STUB !!! RtlInitializeCriticalSectionAndSpinCount\n"); + cs->Header.Absolute = (spinCount + 255) >> 8; + cs->LockCount = -1; + cs->RecursionCount = 0; + cs->OwningThread = 0; +} + +void _vswprintf_x() +{ + printf("!!! STUB !!! _vswprintf\n"); +} + +void _vscwprintf_x() +{ + printf("!!! STUB !!! _vscwprintf\n"); +} + +void _swprintf_x() +{ + printf("!!! STUB !!! _swprintf\n"); +} + +void _snwprintf_x() +{ + printf("!!! STUB !!! _snwprintf\n"); +} + +void XeCryptBnQwBeSigVerify() +{ + printf("!!! STUB !!! XeCryptBnQwBeSigVerify\n"); +} + +void XeKeysGetKey() +{ + printf("!!! STUB !!! XeKeysGetKey\n"); +} + +void XeCryptRotSumSha() +{ + printf("!!! STUB !!! XeCryptRotSumSha\n"); +} + +void XeCryptSha() +{ + printf("!!! STUB !!! XeCryptSha\n"); +} + +void KeEnableFpuExceptions() +{ + printf("!!! STUB !!! KeEnableFpuExceptions\n"); +} + +void RtlUnwind_x() +{ + printf("!!! STUB !!! RtlUnwind\n"); +} + +void RtlCaptureContext_x() +{ + printf("!!! STUB !!! RtlCaptureContext\n"); +} + +void NtQueryFullAttributesFile() +{ + printf("!!! STUB !!! NtQueryFullAttributesFile\n"); +} + +NTSTATUS RtlMultiByteToUnicodeN(PWCH UnicodeString, ULONG MaxBytesInUnicodeString, XLPDWORD BytesInUnicodeString, const CHAR* MultiByteString, ULONG BytesInMultiByteString) +{ + // i am lazy + const auto n = MultiByteToWideChar(CP_UTF8, 0, MultiByteString, BytesInMultiByteString, UnicodeString, MaxBytesInUnicodeString); + if (BytesInUnicodeString) + { + *BytesInUnicodeString = n * sizeof(wchar_t); + } + + if (n) + { + for (size_t i = 0; i < n; i++) + { + UnicodeString[i] = std::byteswap(UnicodeString[i]); + } + } + + return STATUS_SUCCESS; +} + +void DbgBreakPoint() +{ + printf("!!! STUB !!! DbgBreakPoint\n"); +} + +void MmQueryAllocationSize() +{ + printf("!!! STUB !!! MmQueryAllocationSize\n"); +} + +uint32_t NtClearEvent(uint32_t handle, uint32_t* previousState) +{ + //printf("!!! STUB !!! NtClearEvent\n"); + return ResetEvent((HANDLE)handle) ? 0 : 0xFFFFFFFF; +} + +uint32_t NtResumeThread(uint32_t hThread, uint32_t* suspendCount) +{ + //printf("NtResumeThread(): %x %x\n", hThread, suspendCount); + DWORD count = ResumeThread((HANDLE)hThread); + if (count == (DWORD)-1) + return E_FAIL; + + if (suspendCount != nullptr) + *suspendCount = std::byteswap(count); + + return S_OK; +} + +uint32_t NtSetEvent(uint32_t handle, uint32_t* previousState) +{ + //printf("!!! STUB !!! NtSetEvent\n"); + return SetEvent((HANDLE)handle) ? 0 : 0xFFFFFFFF; +} + +NTSTATUS NtCreateSemaphore(XLPDWORD Handle, XOBJECT_ATTRIBUTES* ObjectAttributes, DWORD InitialCount, DWORD MaximumCount) +{ + *Handle = (uint32_t)CreateSemaphoreA(nullptr, InitialCount, MaximumCount, nullptr); + return STATUS_SUCCESS; +} + +NTSTATUS NtReleaseSemaphore(uint32_t Handle, DWORD ReleaseCount, LONG* PreviousCount) +{ + //printf("!!! STUB !!! NtReleaseSemaphore\n"); + ReleaseSemaphore((HANDLE)Handle, ReleaseCount, PreviousCount); + if (PreviousCount) + { + *PreviousCount = std::byteswap(*PreviousCount); + } + + return STATUS_SUCCESS; +} + +void NtWaitForMultipleObjectsEx() +{ + printf("!!! STUB !!! NtWaitForMultipleObjectsEx\n"); +} + +void RtlCompareStringN() +{ + printf("!!! STUB !!! RtlCompareStringN\n"); +} + +void _snprintf_x() +{ + printf("!!! STUB !!! _snprintf\n"); +} + +void StfsControlDevice() +{ + printf("!!! STUB !!! StfsControlDevice\n"); +} + +void StfsCreateDevice() +{ + printf("!!! STUB !!! StfsCreateDevice\n"); +} + +void NtFlushBuffersFile() +{ + printf("!!! STUB !!! NtFlushBuffersFile\n"); +} + +void KeQuerySystemTime(uint64_t* time) +{ + //printf("!!! STUB !!! KeQuerySystemTime\n"); + FILETIME t; + GetSystemTimeAsFileTime(&t); + *time = std::byteswap((uint64_t(t.dwHighDateTime) << 32) | t.dwLowDateTime); +} + +void RtlTimeToTimeFields() +{ + printf("!!! STUB !!! RtlTimeToTimeFields\n"); +} + +void RtlFreeAnsiString() +{ + printf("!!! STUB !!! RtlFreeAnsiString\n"); +} + +void RtlUnicodeStringToAnsiString() +{ + printf("!!! STUB !!! RtlUnicodeStringToAnsiString\n"); +} + +void RtlInitUnicodeString() +{ + printf("!!! STUB !!! RtlInitUnicodeString\n"); +} + +void ExTerminateThread() +{ + printf("!!! STUB !!! ExTerminateThread\n"); +} + +uint32_t ExCreateThread(XLPDWORD handle, uint32_t stackSize, XLPDWORD threadId, uint32_t xApiThreadStartup, uint32_t startAddress, uint32_t startContext, uint32_t creationFlags) +{ + printf("ExCreateThread(): %x %x %x %x %x %x %x\n", handle, stackSize, threadId, xApiThreadStartup, startAddress, startContext, creationFlags); + DWORD hostThreadId; + + *handle = (uint32_t)GuestThread::Start(startAddress, startContext, creationFlags, &hostThreadId); + if (threadId != nullptr) + *threadId = hostThreadId; + + return 0; +} + +void IoInvalidDeviceRequest() +{ + printf("!!! STUB !!! IoInvalidDeviceRequest\n"); +} + +void ObReferenceObject() +{ + printf("!!! STUB !!! ObReferenceObject\n"); +} + +void IoCreateDevice() +{ + printf("!!! STUB !!! IoCreateDevice\n"); +} + +void IoDeleteDevice() +{ + printf("!!! STUB !!! IoDeleteDevice\n"); +} + +void ExAllocatePoolTypeWithTag() +{ + printf("!!! STUB !!! ExAllocatePoolTypeWithTag\n"); +} + +void RtlTimeFieldsToTime() +{ + printf("!!! STUB !!! RtlTimeFieldsToTime\n"); +} + +void IoCompleteRequest() +{ + printf("!!! STUB !!! IoCompleteRequest\n"); +} + +void RtlUpcaseUnicodeChar() +{ + printf("!!! STUB !!! RtlUpcaseUnicodeChar\n"); +} + +void ObIsTitleObject() +{ + printf("!!! STUB !!! ObIsTitleObject\n"); +} + +void IoCheckShareAccess() +{ + printf("!!! STUB !!! IoCheckShareAccess\n"); +} + +void IoSetShareAccess() +{ + printf("!!! STUB !!! IoSetShareAccess\n"); +} + +void IoRemoveShareAccess() +{ + printf("!!! STUB !!! IoRemoveShareAccess\n"); +} + +void NetDll_XNetStartup() +{ + printf("!!! STUB !!! NetDll_XNetStartup\n"); +} + +void NetDll_XNetGetTitleXnAddr() +{ + printf("!!! STUB !!! NetDll_XNetGetTitleXnAddr\n"); +} + +DWORD KeWaitForMultipleObjects(DWORD Count, xpointer* Objects, DWORD WaitType, DWORD WaitReason, DWORD WaitMode, DWORD Alertable, XLPQWORD Timeout) +{ + // TODO: Create actual objects by type + const uint64_t timeout = GuestTimeoutToMilliseconds(Timeout); + + thread_local std::vector events; + events.resize(Count); + + for (size_t i = 0; i < Count; i++) + { + assert(Objects[i]->Type <= 1); + events[i] = ObQueryObject(*Objects[i].get())->handle; + } + + return WaitForMultipleObjectsEx(Count, events.data(), WaitType == 0, timeout, Alertable); +} + +uint32_t KeRaiseIrqlToDpcLevel() +{ + //printf("!!! STUB !!! KeRaiseIrqlToDpcLevel\n"); + return 0; +} + +void KfLowerIrql() +{ + //printf("!!! STUB !!! KfLowerIrql\n"); +} + +uint32_t KeReleaseSemaphore(XKSEMAPHORE* semaphore, uint32_t increment, uint32_t adjustment, uint32_t wait) +{ + //printf("!!! STUB !!! KeReleaseSemaphore\n"); + auto* object = ObQueryObject(semaphore->Header); + return ReleaseSemaphore(object->handle, adjustment, nullptr) ? 0 : 0xFFFFFFFF; +} + +void XAudioGetVoiceCategoryVolume() +{ + printf("!!! STUB !!! XAudioGetVoiceCategoryVolume\n"); +} + +DWORD XAudioGetVoiceCategoryVolumeChangeMask(DWORD Driver, XLPDWORD Mask) +{ + // printf("Invoking method XAudioGetVoiceCategoryVolumeChangeMask\n"); + *Mask = 0; + + return 0; +} + +uint32_t KeResumeThread(uint32_t object) +{ + printf("KeResumeThread(): %x\n", object); + return ResumeThread((HANDLE)object); +} + +void KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limit) +{ + //printf("!!! STUB !!! KeInitializeSemaphore\n"); + semaphore->Header.Type = 5; + semaphore->Header.SignalState = count; + semaphore->Limit = limit; + auto* object = ObQueryObject(semaphore->Header); +} + +void XMAReleaseContext() +{ + printf("!!! STUB !!! XMAReleaseContext\n"); +} + +void XMACreateContext() +{ + printf("!!! STUB !!! XMACreateContext\n"); +} + +uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD 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__XGetVideoMode, VdQueryVideoMode); // XGetVideoMode +GUEST_FUNCTION_HOOK(__imp__XNotifyGetNext, XNotifyGetNext); +GUEST_FUNCTION_HOOK(__imp__XGetGameRegion, XGetGameRegion); +GUEST_FUNCTION_HOOK(__imp__XMsgStartIORequest, XMsgStartIORequest); +GUEST_FUNCTION_HOOK(__imp__XamUserGetSigninState, XamUserGetSigninState); +GUEST_FUNCTION_HOOK(__imp__XamGetSystemVersion, XamGetSystemVersion); +GUEST_FUNCTION_HOOK(__imp__XamContentCreateEx, XamContentCreateEx); +GUEST_FUNCTION_HOOK(__imp__XamContentDelete, XamContentDelete); +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__XamShowSigninUI, XamShowSigninUI); +GUEST_FUNCTION_HOOK(__imp__XamShowDeviceSelectorUI, XamShowDeviceSelectorUI); +GUEST_FUNCTION_HOOK(__imp__XamShowMessageBoxUI, XamShowMessageBoxUI); +GUEST_FUNCTION_HOOK(__imp__XamShowDirtyDiscErrorUI, XamShowDirtyDiscErrorUI); +GUEST_FUNCTION_HOOK(__imp__XamEnableInactivityProcessing, XamEnableInactivityProcessing); +GUEST_FUNCTION_HOOK(__imp__XamResetInactivity, XamResetInactivity); +GUEST_FUNCTION_HOOK(__imp__XamShowMessageBoxUIEx, XamShowMessageBoxUIEx); +GUEST_FUNCTION_HOOK(__imp__XGetLanguage, XGetLanguage); +GUEST_FUNCTION_HOOK(__imp__XGetAVPack, XGetAVPack); +GUEST_FUNCTION_HOOK(__imp__XamLoaderTerminateTitle, XamLoaderTerminateTitle); +GUEST_FUNCTION_HOOK(__imp__XamGetExecutionId, XamGetExecutionId); +GUEST_FUNCTION_HOOK(__imp__XamLoaderLaunchTitle, XamLoaderLaunchTitle); +GUEST_FUNCTION_HOOK(__imp__NtOpenFile, NtOpenFile); +GUEST_FUNCTION_HOOK(__imp__RtlInitAnsiString, RtlInitAnsiString); +GUEST_FUNCTION_HOOK(__imp__NtCreateFile, NtCreateFile); +GUEST_FUNCTION_HOOK(__imp__NtClose, NtClose); +GUEST_FUNCTION_HOOK(__imp__NtSetInformationFile, NtSetInformationFile); +GUEST_FUNCTION_HOOK(__imp__FscSetCacheElementCount, FscSetCacheElementCount); +GUEST_FUNCTION_HOOK(__imp__NtWaitForSingleObjectEx, NtWaitForSingleObjectEx); +GUEST_FUNCTION_HOOK(__imp__NtWriteFile, NtWriteFile); +GUEST_FUNCTION_HOOK(__imp__ExGetXConfigSetting, ExGetXConfigSetting); +GUEST_FUNCTION_HOOK(__imp__NtQueryVirtualMemory, NtQueryVirtualMemory); +GUEST_FUNCTION_HOOK(__imp__MmQueryStatistics, MmQueryStatistics); +GUEST_FUNCTION_HOOK(__imp__NtCreateEvent, NtCreateEvent); +GUEST_FUNCTION_HOOK(__imp__XexCheckExecutablePrivilege, XexCheckExecutablePrivilege); +GUEST_FUNCTION_HOOK(__imp__DbgPrint, DbgPrint); +GUEST_FUNCTION_HOOK(__imp____C_specific_handler, __C_specific_handler_x); +GUEST_FUNCTION_HOOK(__imp__RtlNtStatusToDosError, RtlNtStatusToDosError); +GUEST_FUNCTION_HOOK(__imp__XexGetProcedureAddress, XexGetProcedureAddress); +GUEST_FUNCTION_HOOK(__imp__XexGetModuleSection, XexGetModuleSection); +GUEST_FUNCTION_HOOK(__imp__RtlUnicodeToMultiByteN, RtlUnicodeToMultiByteN); +GUEST_FUNCTION_HOOK(__imp__KeDelayExecutionThread, KeDelayExecutionThread); +GUEST_FUNCTION_HOOK(__imp__ExFreePool, ExFreePool); +GUEST_FUNCTION_HOOK(__imp__NtQueryInformationFile, NtQueryInformationFile); +GUEST_FUNCTION_HOOK(__imp__NtQueryVolumeInformationFile, NtQueryVolumeInformationFile); +GUEST_FUNCTION_HOOK(__imp__NtQueryDirectoryFile, NtQueryDirectoryFile); +GUEST_FUNCTION_HOOK(__imp__NtReadFileScatter, NtReadFileScatter); +GUEST_FUNCTION_HOOK(__imp__NtReadFile, NtReadFile); +GUEST_FUNCTION_HOOK(__imp__NtDuplicateObject, NtDuplicateObject); +GUEST_FUNCTION_HOOK(__imp__NtAllocateVirtualMemory, NtAllocateVirtualMemory); +GUEST_FUNCTION_HOOK(__imp__NtFreeVirtualMemory, NtFreeVirtualMemory); +GUEST_FUNCTION_HOOK(__imp__ObDereferenceObject, ObDereferenceObject); +GUEST_FUNCTION_HOOK(__imp__KeSetBasePriorityThread, KeSetBasePriorityThread); +GUEST_FUNCTION_HOOK(__imp__ObReferenceObjectByHandle, ObReferenceObjectByHandle); +GUEST_FUNCTION_HOOK(__imp__KeQueryBasePriorityThread, KeQueryBasePriorityThread); +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__RtlImageXexHeaderField, RtlImageXexHeaderField); +GUEST_FUNCTION_HOOK(__imp__HalReturnToFirmware, HalReturnToFirmware); +GUEST_FUNCTION_HOOK(__imp__RtlFillMemoryUlong, RtlFillMemoryUlong); +GUEST_FUNCTION_HOOK(__imp__KeBugCheckEx, KeBugCheckEx); +GUEST_FUNCTION_HOOK(__imp__KeGetCurrentProcessType, KeGetCurrentProcessType); +GUEST_FUNCTION_HOOK(__imp__RtlCompareMemoryUlong, RtlCompareMemoryUlong); +GUEST_FUNCTION_HOOK(__imp__RtlInitializeCriticalSection, RtlInitializeCriticalSection); +GUEST_FUNCTION_HOOK(__imp__RtlRaiseException, RtlRaiseException_x); +GUEST_FUNCTION_HOOK(__imp__KfReleaseSpinLock, KfReleaseSpinLock); +GUEST_FUNCTION_HOOK(__imp__KfAcquireSpinLock, KfAcquireSpinLock); +GUEST_FUNCTION_HOOK(__imp__KeQueryPerformanceFrequency, KeQueryPerformanceFrequency); +GUEST_FUNCTION_HOOK(__imp__MmFreePhysicalMemory, MmFreePhysicalMemory); +GUEST_FUNCTION_HOOK(__imp__VdPersistDisplay, VdPersistDisplay); +GUEST_FUNCTION_HOOK(__imp__VdSwap, VdSwap); +GUEST_FUNCTION_HOOK(__imp__VdGetSystemCommandBuffer, VdGetSystemCommandBuffer); +GUEST_FUNCTION_HOOK(__imp__KeReleaseSpinLockFromRaisedIrql, KeReleaseSpinLockFromRaisedIrql); +GUEST_FUNCTION_HOOK(__imp__KeAcquireSpinLockAtRaisedIrql, KeAcquireSpinLockAtRaisedIrql); +GUEST_FUNCTION_HOOK(__imp__KiApcNormalRoutineNop, KiApcNormalRoutineNop); +GUEST_FUNCTION_HOOK(__imp__VdEnableRingBufferRPtrWriteBack, VdEnableRingBufferRPtrWriteBack); +GUEST_FUNCTION_HOOK(__imp__VdInitializeRingBuffer, VdInitializeRingBuffer); +GUEST_FUNCTION_HOOK(__imp__MmGetPhysicalAddress, MmGetPhysicalAddress); +GUEST_FUNCTION_HOOK(__imp__VdSetSystemCommandBufferGpuIdentifierAddress, VdSetSystemCommandBufferGpuIdentifierAddress); +GUEST_FUNCTION_HOOK(__imp__ExRegisterTitleTerminateNotification, ExRegisterTitleTerminateNotification); +GUEST_FUNCTION_HOOK(__imp__VdShutdownEngines, VdShutdownEngines); +GUEST_FUNCTION_HOOK(__imp__VdQueryVideoMode, VdQueryVideoMode); +GUEST_FUNCTION_HOOK(__imp__VdGetCurrentDisplayInformation, VdGetCurrentDisplayInformation); +GUEST_FUNCTION_HOOK(__imp__VdSetDisplayMode, VdSetDisplayMode); +GUEST_FUNCTION_HOOK(__imp__VdSetGraphicsInterruptCallback, VdSetGraphicsInterruptCallback); +GUEST_FUNCTION_HOOK(__imp__VdInitializeEngines, VdInitializeEngines); +GUEST_FUNCTION_HOOK(__imp__VdIsHSIOTrainingSucceeded, VdIsHSIOTrainingSucceeded); +GUEST_FUNCTION_HOOK(__imp__VdGetCurrentDisplayGamma, VdGetCurrentDisplayGamma); +GUEST_FUNCTION_HOOK(__imp__VdQueryVideoFlags, VdQueryVideoFlags); +GUEST_FUNCTION_HOOK(__imp__VdCallGraphicsNotificationRoutines, VdCallGraphicsNotificationRoutines); +GUEST_FUNCTION_HOOK(__imp__VdInitializeScalerCommandBuffer, VdInitializeScalerCommandBuffer); +GUEST_FUNCTION_HOOK(__imp__KeLeaveCriticalRegion, KeLeaveCriticalRegion); +GUEST_FUNCTION_HOOK(__imp__VdRetrainEDRAM, VdRetrainEDRAM); +GUEST_FUNCTION_HOOK(__imp__VdRetrainEDRAMWorker, VdRetrainEDRAMWorker); +GUEST_FUNCTION_HOOK(__imp__KeEnterCriticalRegion, KeEnterCriticalRegion); +GUEST_FUNCTION_HOOK(__imp__MmAllocatePhysicalMemoryEx, MmAllocatePhysicalMemoryEx); +GUEST_FUNCTION_HOOK(__imp__ObDeleteSymbolicLink, ObDeleteSymbolicLink); +GUEST_FUNCTION_HOOK(__imp__ObCreateSymbolicLink, ObCreateSymbolicLink); +GUEST_FUNCTION_HOOK(__imp__MmQueryAddressProtect, MmQueryAddressProtect); +GUEST_FUNCTION_HOOK(__imp__VdEnableDisableClockGating, VdEnableDisableClockGating); +GUEST_FUNCTION_HOOK(__imp__KeBugCheck, KeBugCheck); +GUEST_FUNCTION_HOOK(__imp__KeLockL2, KeLockL2); +GUEST_FUNCTION_HOOK(__imp__KeUnlockL2, KeUnlockL2); +GUEST_FUNCTION_HOOK(__imp__KeSetEvent, KeSetEvent); +GUEST_FUNCTION_HOOK(__imp__KeResetEvent, KeResetEvent); +GUEST_FUNCTION_HOOK(__imp__KeWaitForSingleObject, KeWaitForSingleObject); +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__XMsgInProcessCall, XMsgInProcessCall); +GUEST_FUNCTION_HOOK(__imp__XamUserReadProfileSettings, XamUserReadProfileSettings); +GUEST_FUNCTION_HOOK(__imp__NetDll_WSAStartup, NetDll_WSAStartup); +GUEST_FUNCTION_HOOK(__imp__NetDll_WSACleanup, NetDll_WSACleanup); +GUEST_FUNCTION_HOOK(__imp__NetDll_socket, NetDll_socket); +GUEST_FUNCTION_HOOK(__imp__NetDll_closesocket, NetDll_closesocket); +GUEST_FUNCTION_HOOK(__imp__NetDll_setsockopt, NetDll_setsockopt); +GUEST_FUNCTION_HOOK(__imp__NetDll_bind, NetDll_bind); +GUEST_FUNCTION_HOOK(__imp__NetDll_connect, NetDll_connect); +GUEST_FUNCTION_HOOK(__imp__NetDll_listen, NetDll_listen); +GUEST_FUNCTION_HOOK(__imp__NetDll_accept, NetDll_accept); +GUEST_FUNCTION_HOOK(__imp__NetDll_select, NetDll_select); +GUEST_FUNCTION_HOOK(__imp__NetDll_recv, NetDll_recv); +GUEST_FUNCTION_HOOK(__imp__NetDll_send, NetDll_send); +GUEST_FUNCTION_HOOK(__imp__NetDll_inet_addr, NetDll_inet_addr); +GUEST_FUNCTION_HOOK(__imp__NetDll___WSAFDIsSet, NetDll___WSAFDIsSet); +GUEST_FUNCTION_HOOK(__imp__XMsgStartIORequestEx, XMsgStartIORequestEx); +GUEST_FUNCTION_HOOK(__imp__XamInputGetCapabilities, XamInputGetCapabilities); +GUEST_FUNCTION_HOOK(__imp__XamInputGetState, XamInputGetState); +GUEST_FUNCTION_HOOK(__imp__XamInputSetState, XamInputSetState); +GUEST_FUNCTION_HOOK(__imp__XexGetModuleHandle, XexGetModuleHandle); +GUEST_FUNCTION_HOOK(__imp__RtlTryEnterCriticalSection, RtlTryEnterCriticalSection); +GUEST_FUNCTION_HOOK(__imp__RtlInitializeCriticalSectionAndSpinCount, RtlInitializeCriticalSectionAndSpinCount); +GUEST_FUNCTION_HOOK(__imp__XeCryptBnQwBeSigVerify, XeCryptBnQwBeSigVerify); +GUEST_FUNCTION_HOOK(__imp__XeKeysGetKey, XeKeysGetKey); +GUEST_FUNCTION_HOOK(__imp__XeCryptRotSumSha, XeCryptRotSumSha); +GUEST_FUNCTION_HOOK(__imp__XeCryptSha, XeCryptSha); +GUEST_FUNCTION_HOOK(__imp__KeEnableFpuExceptions, KeEnableFpuExceptions); +GUEST_FUNCTION_HOOK(__imp__RtlUnwind, RtlUnwind_x); +GUEST_FUNCTION_HOOK(__imp__RtlCaptureContext, RtlCaptureContext_x); +GUEST_FUNCTION_HOOK(__imp__NtQueryFullAttributesFile, NtQueryFullAttributesFile); +GUEST_FUNCTION_HOOK(__imp__RtlMultiByteToUnicodeN, RtlMultiByteToUnicodeN); +GUEST_FUNCTION_HOOK(__imp__DbgBreakPoint, DbgBreakPoint); +GUEST_FUNCTION_HOOK(__imp__MmQueryAllocationSize, MmQueryAllocationSize); +GUEST_FUNCTION_HOOK(__imp__NtClearEvent, NtClearEvent); +GUEST_FUNCTION_HOOK(__imp__NtResumeThread, NtResumeThread); +GUEST_FUNCTION_HOOK(__imp__NtSetEvent, NtSetEvent); +GUEST_FUNCTION_HOOK(__imp__NtCreateSemaphore, NtCreateSemaphore); +GUEST_FUNCTION_HOOK(__imp__NtReleaseSemaphore, NtReleaseSemaphore); +GUEST_FUNCTION_HOOK(__imp__NtWaitForMultipleObjectsEx, NtWaitForMultipleObjectsEx); +GUEST_FUNCTION_HOOK(__imp__RtlCompareStringN, RtlCompareStringN); +GUEST_FUNCTION_HOOK(__imp__StfsControlDevice, StfsControlDevice); +GUEST_FUNCTION_HOOK(__imp__StfsCreateDevice, StfsCreateDevice); +GUEST_FUNCTION_HOOK(__imp__NtFlushBuffersFile, NtFlushBuffersFile); +GUEST_FUNCTION_HOOK(__imp__KeQuerySystemTime, KeQuerySystemTime); +GUEST_FUNCTION_HOOK(__imp__RtlTimeToTimeFields, RtlTimeToTimeFields); +GUEST_FUNCTION_HOOK(__imp__RtlFreeAnsiString, RtlFreeAnsiString); +GUEST_FUNCTION_HOOK(__imp__RtlUnicodeStringToAnsiString, RtlUnicodeStringToAnsiString); +GUEST_FUNCTION_HOOK(__imp__RtlInitUnicodeString, RtlInitUnicodeString); +GUEST_FUNCTION_HOOK(__imp__ExTerminateThread, ExTerminateThread); +GUEST_FUNCTION_HOOK(__imp__ExCreateThread, ExCreateThread); +GUEST_FUNCTION_HOOK(__imp__IoInvalidDeviceRequest, IoInvalidDeviceRequest); +GUEST_FUNCTION_HOOK(__imp__ObReferenceObject, ObReferenceObject); +GUEST_FUNCTION_HOOK(__imp__IoCreateDevice, IoCreateDevice); +GUEST_FUNCTION_HOOK(__imp__IoDeleteDevice, IoDeleteDevice); +GUEST_FUNCTION_HOOK(__imp__ExAllocatePoolTypeWithTag, ExAllocatePoolTypeWithTag); +GUEST_FUNCTION_HOOK(__imp__RtlTimeFieldsToTime, RtlTimeFieldsToTime); +GUEST_FUNCTION_HOOK(__imp__IoCompleteRequest, IoCompleteRequest); +GUEST_FUNCTION_HOOK(__imp__RtlUpcaseUnicodeChar, RtlUpcaseUnicodeChar); +GUEST_FUNCTION_HOOK(__imp__ObIsTitleObject, ObIsTitleObject); +GUEST_FUNCTION_HOOK(__imp__IoCheckShareAccess, IoCheckShareAccess); +GUEST_FUNCTION_HOOK(__imp__IoSetShareAccess, IoSetShareAccess); +GUEST_FUNCTION_HOOK(__imp__IoRemoveShareAccess, IoRemoveShareAccess); +GUEST_FUNCTION_HOOK(__imp__NetDll_XNetStartup, NetDll_XNetStartup); +GUEST_FUNCTION_HOOK(__imp__NetDll_XNetGetTitleXnAddr, NetDll_XNetGetTitleXnAddr); +GUEST_FUNCTION_HOOK(__imp__KeWaitForMultipleObjects, KeWaitForMultipleObjects); +GUEST_FUNCTION_HOOK(__imp__KeRaiseIrqlToDpcLevel, KeRaiseIrqlToDpcLevel); +GUEST_FUNCTION_HOOK(__imp__KfLowerIrql, KfLowerIrql); +GUEST_FUNCTION_HOOK(__imp__KeReleaseSemaphore, KeReleaseSemaphore); +GUEST_FUNCTION_HOOK(__imp__XAudioGetVoiceCategoryVolume, XAudioGetVoiceCategoryVolume); +GUEST_FUNCTION_HOOK(__imp__XAudioGetVoiceCategoryVolumeChangeMask, XAudioGetVoiceCategoryVolumeChangeMask); +GUEST_FUNCTION_HOOK(__imp__KeResumeThread, KeResumeThread); +GUEST_FUNCTION_HOOK(__imp__KeInitializeSemaphore, KeInitializeSemaphore); +GUEST_FUNCTION_HOOK(__imp__XMAReleaseContext, XMAReleaseContext); +GUEST_FUNCTION_HOOK(__imp__XMACreateContext, XMACreateContext); +GUEST_FUNCTION_HOOK(__imp__XAudioRegisterRenderDriverClient, XAudioRegisterRenderDriverClient); +GUEST_FUNCTION_HOOK(__imp__XAudioUnregisterRenderDriverClient, XAudioUnregisterRenderDriverClient); +GUEST_FUNCTION_HOOK(__imp__XAudioSubmitRenderDriverFrame, XAudioSubmitRenderDriverFrame); diff --git a/UnleashedRecomp/kernel/io/file_system.cpp b/UnleashedRecomp/kernel/io/file_system.cpp new file mode 100644 index 00000000..d5c92ba9 --- /dev/null +++ b/UnleashedRecomp/kernel/io/file_system.cpp @@ -0,0 +1,244 @@ +#include +#include "file_system.h" +#include +#include +#include +#include + +bool FindHandleCloser(void* handle) +{ + FindClose(handle); + return false; +} + +static uint32_t CreateFileImpl( + LPCSTR lpFileName, + DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes) +{ + const auto handle = (uint32_t)CreateFileA( + FileSystem::TransformPath(lpFileName), + dwDesiredAccess, + dwShareMode, + nullptr, + dwCreationDisposition, + dwFlagsAndAttributes & ~(FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED), + nullptr); + + GuestThread::SetLastError(GetLastError()); + printf("CreateFileA(%s, %x, %x, %x, %x, %x): %x\n", lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, handle); + + return handle; +} + +static DWORD GetFileSizeImpl( + uint32_t hFile, + LPDWORD lpFileSizeHigh) +{ + DWORD fileSize = GetFileSize((HANDLE)hFile, lpFileSizeHigh); + if (lpFileSizeHigh != nullptr) + *lpFileSizeHigh = std::byteswap(*lpFileSizeHigh); + + return fileSize; +} + +BOOL GetFileSizeExImpl( + uint32_t hFile, + PLARGE_INTEGER lpFileSize) +{ + BOOL result = GetFileSizeEx((HANDLE)hFile, lpFileSize); + if (result) + lpFileSize->QuadPart = std::byteswap(lpFileSize->QuadPart); + + return result; +} + +BOOL ReadFileImpl( + uint32_t hFile, + LPVOID lpBuffer, + DWORD nNumberOfBytesToRead, + XLPDWORD lpNumberOfBytesRead, + XOVERLAPPED* lpOverlapped) +{ + if (lpOverlapped != nullptr) + { + LONG distanceToMoveHigh = lpOverlapped->OffsetHigh; + if (SetFilePointer((HANDLE)hFile, lpOverlapped->Offset, &distanceToMoveHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + return FALSE; + } + + DWORD numberOfBytesRead; + BOOL result = ReadFile((HANDLE)hFile, lpBuffer, nNumberOfBytesToRead, &numberOfBytesRead, nullptr); + if (result) + { + if (lpOverlapped != nullptr) + { + lpOverlapped->Internal = 0; + lpOverlapped->InternalHigh = numberOfBytesRead; + + if (lpOverlapped->hEvent != NULL) + SetEvent((HANDLE)lpOverlapped->hEvent.get()); + } + else if (lpNumberOfBytesRead != nullptr) + { + *lpNumberOfBytesRead = numberOfBytesRead; + } + } + + //printf("ReadFile(): %x %x %x %x %x %x\n", hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped, result); + + return result; +} + +DWORD SetFilePointerImpl( + uint32_t hFile, + LONG lDistanceToMove, + PLONG lpDistanceToMoveHigh, + DWORD dwMoveMethod) +{ + LONG distanceToMoveHigh = lpDistanceToMoveHigh ? std::byteswap(*lpDistanceToMoveHigh) : 0; + DWORD result = SetFilePointer((HANDLE)hFile, lDistanceToMove, lpDistanceToMoveHigh ? &distanceToMoveHigh : nullptr, dwMoveMethod); + if (lpDistanceToMoveHigh != nullptr) + *lpDistanceToMoveHigh = std::byteswap(distanceToMoveHigh); + + return result; +} + +BOOL SetFilePointerExImpl( + uint32_t hFile, + LONG lDistanceToMove, + PLARGE_INTEGER lpNewFilePointer, + DWORD dwMoveMethod) +{ + LARGE_INTEGER distanceToMove; + distanceToMove.QuadPart = lDistanceToMove; + + DWORD result = SetFilePointerEx((HANDLE)hFile, distanceToMove, lpNewFilePointer, dwMoveMethod); + if (lpNewFilePointer != nullptr) + lpNewFilePointer->QuadPart = std::byteswap(lpNewFilePointer->QuadPart); + + return result; +} + +uint32_t FindFirstFileImpl( + LPCSTR lpFileName, + LPWIN32_FIND_DATAA lpFindFileData) +{ + auto& data = *lpFindFileData; + const auto handle = FindFirstFileA(FileSystem::TransformPath(lpFileName), &data); + GuestThread::SetLastError(GetLastError()); + if (handle == INVALID_HANDLE_VALUE) + { + return 0xFFFFFFFF; + } + + ByteSwap(data.dwFileAttributes); + ByteSwap(*(uint64_t*)&data.ftCreationTime); + ByteSwap(*(uint64_t*)&data.ftLastAccessTime); + ByteSwap(*(uint64_t*)&data.ftLastWriteTime); + ByteSwap(*(uint64_t*)&data.nFileSizeHigh); + + return GUEST_HANDLE(ObInsertObject(handle, FindHandleCloser)); +} + +uint32_t FindNextFileImpl(uint32_t Handle, LPWIN32_FIND_DATAA lpFindFileData) +{ + auto* handle = ObQueryObject(HOST_HANDLE(Handle)); + auto& data = *lpFindFileData; + const auto result = FindNextFileA(handle, &data); + + ByteSwap(data.dwFileAttributes); + ByteSwap(*(uint64_t*)&data.ftCreationTime); + ByteSwap(*(uint64_t*)&data.ftLastAccessTime); + ByteSwap(*(uint64_t*)&data.ftLastWriteTime); + ByteSwap(*(uint64_t*)&data.nFileSizeHigh); + + return result; +} + +BOOL ReadFileExImpl( + uint32_t hFile, + LPVOID lpBuffer, + DWORD nNumberOfBytesToRead, + XOVERLAPPED* lpOverlapped, + uint32_t lpCompletionRoutine) +{ + LONG distanceToMoveHigh = lpOverlapped->OffsetHigh; + if (SetFilePointer((HANDLE)hFile, lpOverlapped->Offset, &distanceToMoveHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + return FALSE; + + DWORD numberOfBytesRead; + BOOL result = ReadFile((HANDLE)hFile, lpBuffer, nNumberOfBytesToRead, &numberOfBytesRead, nullptr); + if (result) + { + lpOverlapped->Internal = 0; + lpOverlapped->InternalHigh = numberOfBytesRead; + + if (lpOverlapped->hEvent != NULL) + SetEvent((HANDLE)lpOverlapped->hEvent.get()); + } + + //printf("ReadFileEx(): %x %x %x %x %x %x\n", hFile, lpBuffer, nNumberOfBytesToRead, lpOverlapped, lpCompletionRoutine, result); + + return result; +} + +DWORD GetFileAttributesAImpl(LPCSTR lpFileName) +{ + return GetFileAttributesA(FileSystem::TransformPath(lpFileName)); +} + +BOOL WriteFileImpl( + uint32_t hFile, + LPCVOID lpBuffer, + DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten, + LPOVERLAPPED lpOverlapped) +{ + assert(lpOverlapped == nullptr); + + BOOL result = WriteFile((HANDLE)hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, nullptr); + if (result && lpNumberOfBytesWritten != nullptr) + ByteSwap(*lpNumberOfBytesWritten); + + return result; +} + +const char* FileSystem::TransformPath(const char* path) +{ + thread_local char builtPath[2048]{}; + const char* relativePath = strstr(path, ":\\"); + if (relativePath != nullptr) + { + // rooted folder, handle direction + const std::string_view root = std::string_view{ path, path + (relativePath - path) }; + const auto newRoot = XamGetRootPath(root); + + if (!newRoot.empty()) + { + strncpy(builtPath, newRoot.data(), newRoot.size()); + builtPath[newRoot.size()] = '\\'; + strcpy(builtPath + newRoot.size() + 1, relativePath + 2); + + return builtPath; + } + } + + return relativePath != nullptr ? relativePath + 2 : path; +} + +GUEST_FUNCTION_HOOK(sub_82BD4668, CreateFileImpl); +GUEST_FUNCTION_HOOK(sub_82BD4600, GetFileSizeImpl); +GUEST_FUNCTION_HOOK(sub_82BD5608, GetFileSizeExImpl); +GUEST_FUNCTION_HOOK(sub_82BD4478, ReadFileImpl); +GUEST_FUNCTION_HOOK(sub_831CD3E8, SetFilePointerImpl); +GUEST_FUNCTION_HOOK(sub_831CE888, SetFilePointerExImpl); +GUEST_FUNCTION_HOOK(sub_831CDC58, FindFirstFileImpl); +GUEST_FUNCTION_HOOK(sub_831CDC00, FindNextFileImpl); +GUEST_FUNCTION_HOOK(sub_831CDF40, ReadFileExImpl); +GUEST_FUNCTION_HOOK(sub_831CD6E8, GetFileAttributesAImpl); +GUEST_FUNCTION_HOOK(sub_831CE3F8, CreateFileImpl); +GUEST_FUNCTION_HOOK(sub_82BD4860, WriteFileImpl); diff --git a/UnleashedRecomp/kernel/io/file_system.h b/UnleashedRecomp/kernel/io/file_system.h new file mode 100644 index 00000000..bbe5aefd --- /dev/null +++ b/UnleashedRecomp/kernel/io/file_system.h @@ -0,0 +1,6 @@ +#pragma once + +struct FileSystem +{ + static const char* TransformPath(const char* path); +}; \ No newline at end of file diff --git a/UnleashedRecomp/kernel/memory.cpp b/UnleashedRecomp/kernel/memory.cpp new file mode 100644 index 00000000..d6581682 --- /dev/null +++ b/UnleashedRecomp/kernel/memory.cpp @@ -0,0 +1,37 @@ +#include +#include "memory.h" + +Memory::Memory(void* address, size_t size) : size(size) +{ + base = (char*)VirtualAlloc(address, size, MEM_RESERVE, PAGE_READWRITE); +} + +void* Memory::Alloc(size_t offset, size_t size, uint32_t type) +{ + return VirtualAlloc(base + offset, size, type, PAGE_READWRITE); +} + +void* Memory::Commit(size_t offset, size_t size) +{ + return Alloc(offset, size, MEM_COMMIT); +} + +void* Memory::Reserve(size_t offset, size_t size) +{ + return Alloc(offset, size, MEM_RESERVE); +} + +void* Memory::Translate(size_t offset) const noexcept +{ + return base + offset; +} + +uint32_t Memory::MapVirtual(void* host) const noexcept +{ + return static_cast(static_cast(host) - base); +} + +extern "C" void* MmGetHostAddress(uint32_t ptr) +{ + return gMemory.Translate(ptr); +} \ No newline at end of file diff --git a/UnleashedRecomp/kernel/memory.h b/UnleashedRecomp/kernel/memory.h new file mode 100644 index 00000000..4e6d9ace --- /dev/null +++ b/UnleashedRecomp/kernel/memory.h @@ -0,0 +1,21 @@ +#pragma once + +class Memory +{ +public: + char* base{}; + size_t size{}; + size_t guestBase{}; + + Memory(void* address, size_t size); + + void* Alloc(size_t offset, size_t size, uint32_t type); + + void* Commit(size_t offset, size_t size); + void* Reserve(size_t offset, size_t size); + + void* Translate(size_t offset) const noexcept; + uint32_t MapVirtual(void* host) const noexcept; +}; + +extern Memory gMemory; \ No newline at end of file diff --git a/UnleashedRecomp/kernel/xam.cpp b/UnleashedRecomp/kernel/xam.cpp new file mode 100644 index 00000000..37f24672 --- /dev/null +++ b/UnleashedRecomp/kernel/xam.cpp @@ -0,0 +1,403 @@ +#include +#include "xam.h" +#include "xdm.h" +#include +#include +#include +#include +#include +#include +#include "xxHashMap.h" + +// Needed for commctrl +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") + +std::array, 3> gContentRegistry{}; +std::unordered_set gListeners{}; +xxHashMap gRootMap; + +std::string_view XamGetRootPath(const std::string_view& root) +{ + const auto result = gRootMap.find(StringHash(root)); + if (result == gRootMap.end()) + { + return ""; + } + + return result->second; +} + +void XamRootCreate(const std::string_view& root, const std::string_view& path) +{ + gRootMap.emplace(StringHash(root), path); +} + +XamListener::XamListener() +{ + gListeners.insert(this); +} + +XamListener::~XamListener() +{ + gListeners.erase(this); +} + +XCONTENT_DATA XamMakeContent(DWORD type, const std::string_view& name) +{ + XCONTENT_DATA data{ 1, type }; + strncpy(data.szFileName, name.data(), sizeof(data.szFileName)); + + return data; +} + +void XamRegisterContent(const XCONTENT_DATA& data, const std::string_view& root) +{ + const auto idx = data.dwContentType - 1; + gContentRegistry[idx].emplace(StringHash(data.szFileName), XHOSTCONTENT_DATA{ data }).first->second.szRoot = root; +} + +void XamRegisterContent(DWORD type, const std::string_view name, const std::string_view& root) +{ + XCONTENT_DATA data{ 1, type, {}, "" }; + strncpy(data.szFileName, name.data(), sizeof(data.szFileName)); + + XamRegisterContent(data, root); +} + +SWA_API DWORD XamNotifyCreateListener(uint64_t qwAreas) +{ + int handle; + auto* listener = ObCreateObject(handle); + listener->areas = qwAreas; + + return GUEST_HANDLE(handle); +} + +SWA_API void XamNotifyEnqueueEvent(DWORD dwId, DWORD dwParam) +{ + for (const auto& listener : gListeners) + { + if (((1 << MSG_AREA(dwId)) & listener->areas) == 0) + { + continue; + } + + listener->notifications.emplace_back(dwId, dwParam); + } +} + +SWA_API bool XNotifyGetNext(DWORD hNotification, DWORD dwMsgFilter, XDWORD* pdwId, XDWORD* pParam) +{ + auto& listener = *ObTryQueryObject(HOST_HANDLE(hNotification)); + if (dwMsgFilter) + { + for (size_t i = 0; i < listener.notifications.size(); i++) + { + if (std::get<0>(listener.notifications[i]) == dwMsgFilter) + { + if (pdwId) + { + *pdwId = std::get<0>(listener.notifications[i]); + } + + if (pParam) + { + *pParam = std::get<1>(listener.notifications[i]); + } + + listener.notifications.erase(listener.notifications.begin() + i); + return true; + } + } + } + else + { + if (listener.notifications.empty()) + { + return false; + } + + if (pdwId) + { + *pdwId = std::get<0>(listener.notifications[0]); + } + + if (pParam) + { + *pParam = std::get<1>(listener.notifications[0]); + } + + listener.notifications.erase(listener.notifications.begin()); + return true; + } + return false; +} + +SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* wszText, DWORD cButtons, + xpointer* pwszButtons, DWORD dwFocusButton, DWORD dwFlags, XLPDWORD pResult, XXOVERLAPPED* pOverlapped) +{ + // printf("!!! STUB !!! XamShowMessageBoxUI\n"); + + std::vector texts{}; + std::vector buttons{}; + texts.emplace_back(reinterpret_cast(wszTitle)); + texts.emplace_back(reinterpret_cast(wszText)); + + for (size_t i = 0; i < cButtons; i++) + { + texts.emplace_back(reinterpret_cast(pwszButtons[i].get())); + } + + for (auto& text : texts) + { + for (size_t i = 0; i < text.size(); i++) + { + ByteSwap(text[i]); + } + } + + for (size_t i = 0; i < cButtons; i++) + { + buttons.emplace_back(i, texts[2 + i].c_str()); + } + + XamNotifyEnqueueEvent(9, 1); + + TASKDIALOGCONFIG config{}; + config.cbSize = sizeof(config); + // config.hwndParent = Window::s_hWnd; + config.pszWindowTitle = texts[0].c_str(); + config.pszContent = texts[1].c_str(); + config.cButtons = cButtons; + config.pButtons = buttons.data(); + + int button{}; + TaskDialogIndirect(&config, &button, nullptr, nullptr); + + *pResult = button; + if (pOverlapped) + { + pOverlapped->dwCompletionContext = GetCurrentThreadId(); + pOverlapped->Error = 0; + pOverlapped->Length = -1; + } + + XamNotifyEnqueueEvent(9, 0); + + return 0; +} + +SWA_API uint32_t XamContentCreateEnumerator(DWORD dwUserIndex, DWORD DeviceID, DWORD dwContentType, + DWORD dwContentFlags, DWORD cItem, XLPDWORD pcbBuffer, XLPDWORD phEnum) +{ + if (dwUserIndex != 0) + { + GuestThread::SetLastError(ERROR_NO_SUCH_USER); + return 0xFFFFFFFF; + } + + const auto& registry = gContentRegistry[dwContentType - 1]; + const auto& values = registry | std::views::values; + const int handle = ObInsertObject(new XamEnumerator(cItem, sizeof(_XCONTENT_DATA), values.begin(), values.end())); + + if (pcbBuffer) + { + *pcbBuffer = sizeof(_XCONTENT_DATA) * cItem; + } + + *phEnum = GUEST_HANDLE(handle); + return 0; +} + +SWA_API uint32_t XamEnumerate(uint32_t hEnum, DWORD dwFlags, PVOID pvBuffer, DWORD cbBuffer, XLPDWORD pcItemsReturned, XXOVERLAPPED* pOverlapped) +{ + auto* enumerator = ObTryQueryObject(HOST_HANDLE(hEnum)); + const auto count = enumerator->Next(pvBuffer); + if (count == -1) + { + return ERROR_NO_MORE_FILES; + } + + if (pcItemsReturned) + { + *pcItemsReturned = count; + } + return ERROR_SUCCESS; +} + +SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const XCONTENT_DATA* pContentData, + DWORD dwContentFlags, XLPDWORD pdwDisposition, XLPDWORD pdwLicenseMask, + DWORD dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped) +{ + // printf("!!! STUB !!! XamContentCreateEx\n"); + + const auto& registry = gContentRegistry[pContentData->dwContentType - 1]; + const auto exists = registry.contains(StringHash(pContentData->szFileName)); + const auto mode = dwContentFlags & 0xF; + + if (mode == CREATE_ALWAYS) + { + if (pdwDisposition) *pdwDisposition = XCONTENT_NEW; + + if (!exists) + { + const char* root = ""; + if (pContentData->dwContentType == XCONTENTTYPE_SAVEDATA) + { + root = ".\\save"; + } + else if (pContentData->dwContentType == XCONTENTTYPE_DLC) + { + root = ".\\dlc"; + } + else + { + root = "."; + } + + XamRegisterContent(*pContentData, root); + CreateDirectoryA(root, nullptr); + XamRootCreate(szRootName, root); + } + else + { + XamRootCreate(szRootName, registry.find(StringHash(pContentData->szFileName))->second.szRoot); + } + + return ERROR_SUCCESS; + } + if (mode == OPEN_EXISTING) + { + if (exists) + { + if (pdwDisposition) *pdwDisposition = XCONTENT_EXISTING; + + XamRootCreate(szRootName, registry.find(StringHash(pContentData->szFileName))->second.szRoot); + return ERROR_SUCCESS; + } + else + { + if (pdwDisposition) *pdwDisposition = XCONTENT_NEW; + return ERROR_PATH_NOT_FOUND; + } + } + + return ERROR_PATH_NOT_FOUND; +} + +SWA_API uint32_t XamContentClose(LPCSTR szRootName, XXOVERLAPPED* pOverlapped) +{ + // printf("!!! STUB !!! XamContentClose %s\n", szRootName); + gRootMap.erase(StringHash(szRootName)); + return 0; +} + +SWA_API uint32_t XamContentGetDeviceData(DWORD DeviceID, XDEVICE_DATA* pDeviceData) +{ + // printf("!!! STUB !!! XamContentGetDeviceData\n"); + + pDeviceData->DeviceID = DeviceID; + pDeviceData->DeviceType = XCONTENTDEVICETYPE_HDD; + pDeviceData->ulDeviceBytes = 0x10000000; + pDeviceData->ulDeviceFreeBytes = 0x10000000; + pDeviceData->wszName[0] = 'S'; + pDeviceData->wszName[1] = 'o'; + pDeviceData->wszName[2] = 'n'; + pDeviceData->wszName[3] = 'i'; + pDeviceData->wszName[4] = 'c'; + pDeviceData->wszName[5] = '\0'; + + return 0; +} + +SWA_API uint32_t XamInputGetCapabilities(uint32_t unk, uint32_t userIndex, uint32_t flags, XAMINPUT_CAPABILITIES* caps) +{ + //printf("!!! STUB !!! XamInputGetCapabilities\n"); + uint32_t result = hid::GetCapabilities(userIndex, caps); + if (result == ERROR_SUCCESS) + { + ByteSwap(caps->Flags); + ByteSwap(caps->Gamepad.wButtons); + ByteSwap(caps->Gamepad.sThumbLX); + ByteSwap(caps->Gamepad.sThumbLY); + ByteSwap(caps->Gamepad.sThumbRX); + ByteSwap(caps->Gamepad.sThumbRY); + ByteSwap(caps->Vibration.wLeftMotorSpeed); + ByteSwap(caps->Vibration.wRightMotorSpeed); + } + return result; +} + +SWA_API uint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_STATE* state) +{ + //printf("!!! STUB !!! XamInputGetState\n"); + uint32_t result = hid::GetState(userIndex, state); + + if (result == ERROR_SUCCESS) + { + ByteSwap(state->dwPacketNumber); + ByteSwap(state->Gamepad.wButtons); + ByteSwap(state->Gamepad.sThumbLX); + ByteSwap(state->Gamepad.sThumbLY); + ByteSwap(state->Gamepad.sThumbRX); + ByteSwap(state->Gamepad.sThumbRY); + } + else if (userIndex == 0) + { + memset(state, 0, sizeof(*state)); + if (GetAsyncKeyState('W') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_Y; + if (GetAsyncKeyState('A') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_X; + if (GetAsyncKeyState('S') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_A; + if (GetAsyncKeyState('D') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_B; + if (GetAsyncKeyState('Q') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_LEFT_SHOULDER; + if (GetAsyncKeyState('E') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_RIGHT_SHOULDER; + if (GetAsyncKeyState('1') & 0x8000) + state->Gamepad.bLeftTrigger = 0xFF; + if (GetAsyncKeyState('3') & 0x8000) + state->Gamepad.bRightTrigger = 0xFF; + + if (GetAsyncKeyState('I') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_UP; + if (GetAsyncKeyState('J') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_LEFT; + if (GetAsyncKeyState('K') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_DOWN; + if (GetAsyncKeyState('L') & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_RIGHT; + + if (GetAsyncKeyState(VK_UP) & 0x8000) + state->Gamepad.sThumbLY = 32767; + if (GetAsyncKeyState(VK_LEFT) & 0x8000) + state->Gamepad.sThumbLX = -32768; + if (GetAsyncKeyState(VK_DOWN) & 0x8000) + state->Gamepad.sThumbLY = -32768; + if (GetAsyncKeyState(VK_RIGHT) & 0x8000) + state->Gamepad.sThumbLX = 32767; + + if (GetAsyncKeyState(VK_RETURN) & 0x8000) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_START; + + ByteSwap(state->Gamepad.wButtons); + ByteSwap(state->Gamepad.sThumbLX); + ByteSwap(state->Gamepad.sThumbLY); + ByteSwap(state->Gamepad.sThumbRX); + ByteSwap(state->Gamepad.sThumbRY); + + result = ERROR_SUCCESS; + } + return result; +} + +SWA_API uint32_t XamInputSetState(uint32_t userIndex, uint32_t flags, XAMINPUT_VIBRATION* vibration) +{ + //printf("!!! STUB !!! XamInputSetState\n"); + ByteSwap(vibration->wLeftMotorSpeed); + ByteSwap(vibration->wRightMotorSpeed); + return hid::SetState(userIndex, vibration); +} diff --git a/UnleashedRecomp/kernel/xam.h b/UnleashedRecomp/kernel/xam.h new file mode 100644 index 00000000..6fd7e262 --- /dev/null +++ b/UnleashedRecomp/kernel/xam.h @@ -0,0 +1,109 @@ +#pragma once +#include + +#define MSGID(Area, Number) (DWORD)((WORD)(Area) << 16 | (WORD)(Number)) +#define MSG_AREA(msgid) (((msgid) >> 16) & 0xFFFF) +#define MSG_NUMBER(msgid) ((msgid) & 0xFFFF) + +struct XamListener +{ + uint32_t id{}; + uint64_t areas{}; + std::vector> notifications; + + XamListener(const XamListener&) = delete; + XamListener& operator=(const XamListener&) = delete; + + XamListener(); + ~XamListener(); +}; + +class XamEnumeratorBase +{ +public: + virtual ~XamEnumeratorBase() = default; + virtual uint32_t Next(void* buffer) + { + return -1; + } +}; + +template::iterator> +class XamEnumerator : public XamEnumeratorBase +{ +public: + uint32_t fetch; + size_t size; + TIterator position; + TIterator begin; + TIterator end; + + XamEnumerator() = default; + XamEnumerator(uint32_t fetch, size_t size, TIterator begin, TIterator end) : fetch(fetch), size(size), position(begin), begin(begin), end(end) + { + + } + + uint32_t Next(void* buffer) override + { + if (position == end) + { + return -1; + } + + if (buffer == nullptr) + { + for (size_t i = 0; i < fetch; i++) + { + if (position == end) + { + return i == 0 ? -1 : i; + } + + ++position; + } + } + + for (size_t i = 0; i < fetch; i++) + { + if (position == end) + { + return i == 0 ? -1 : i; + } + + memcpy(buffer, &*position, size); + + ++position; + buffer = (void*)((size_t)buffer + size); + } + + return fetch; + } +}; + +XCONTENT_DATA XamMakeContent(DWORD type, const std::string_view& name); +void XamRegisterContent(const XCONTENT_DATA& data, const std::string_view& root); + +std::string_view XamGetRootPath(const std::string_view& root); +void XamRootCreate(const std::string_view& root, const std::string_view& path); + +SWA_API DWORD XamNotifyCreateListener(uint64_t qwAreas); +SWA_API void XamNotifyEnqueueEvent(DWORD dwId, DWORD dwParam); // i made it the fuck up +SWA_API bool XNotifyGetNext(DWORD hNotification, DWORD dwMsgFilter, XDWORD* pdwId, XDWORD* pParam); + +SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* wszText, DWORD cButtons, + xpointer* pwszButtons, DWORD dwFocusButton, DWORD dwFlags, XLPDWORD pResult, XXOVERLAPPED* pOverlapped); + +SWA_API uint32_t XamContentCreateEnumerator(DWORD dwUserIndex, DWORD DeviceID, DWORD dwContentType, + DWORD dwContentFlags, DWORD cItem, XLPDWORD pcbBuffer, XLPDWORD phEnum); +SWA_API uint32_t XamEnumerate(uint32_t hEnum, DWORD dwFlags, PVOID pvBuffer, DWORD cbBuffer, XLPDWORD pcItemsReturned, XXOVERLAPPED* pOverlapped); + +SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const XCONTENT_DATA* pContentData, + DWORD dwContentFlags, XLPDWORD pdwDisposition, XLPDWORD pdwLicenseMask, + DWORD dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped); +SWA_API uint32_t XamContentGetDeviceData(DWORD DeviceID, XDEVICE_DATA* pDeviceData); +SWA_API uint32_t XamContentClose(LPCSTR szRootName, XXOVERLAPPED* pOverlapped); + +SWA_API uint32_t XamInputGetCapabilities(uint32_t unk, uint32_t userIndex, uint32_t flags, XAMINPUT_CAPABILITIES* caps); +SWA_API uint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_STATE* state); +SWA_API uint32_t XamInputSetState(uint32_t userIndex, uint32_t flags, XAMINPUT_VIBRATION* vibration); diff --git a/UnleashedRecomp/kernel/xdm.cpp b/UnleashedRecomp/kernel/xdm.cpp new file mode 100644 index 00000000..15cf4424 --- /dev/null +++ b/UnleashedRecomp/kernel/xdm.cpp @@ -0,0 +1,49 @@ +#include +#include "xdm.h" +#include "FreeList.h" + +FreeList, TypeDestructor_t>> gKernelObjects; +Mutex gKernelLock; + +void* ObQueryObject(size_t handle) +{ + std::lock_guard guard{ gKernelLock }; + + if (handle >= gKernelObjects.items.size()) + { + return nullptr; + } + + return std::get<0>(gKernelObjects[handle]).get(); +} + +uint32_t ObInsertObject(void* object, TypeDestructor_t destructor) +{ + std::lock_guard guard{ gKernelLock }; + + const auto handle = gKernelObjects.Alloc(); + + auto& holder = gKernelObjects[handle]; + + std::get<0>(holder).reset(static_cast(object)); + std::get<1>(holder) = destructor; + + return handle; +} + +void ObCloseHandle(uint32_t handle) +{ + std::lock_guard guard{ gKernelLock }; + + auto& obj = gKernelObjects[handle]; + if (std::get<1>(obj)(std::get<0>(obj).get())) + { + std::get<0>(obj).reset(); + } + else + { + std::get<0>(obj).release(); + } + + gKernelObjects.Free(handle); +} \ No newline at end of file diff --git a/UnleashedRecomp/kernel/xdm.h b/UnleashedRecomp/kernel/xdm.h new file mode 100644 index 00000000..32ffaa4e --- /dev/null +++ b/UnleashedRecomp/kernel/xdm.h @@ -0,0 +1,56 @@ +#pragma once +#define DUMMY_HANDLE (DWORD)('HAND') +#define OBJECT_SIGNATURE (DWORD)'XBOX' + +extern Mutex gKernelLock; + +void* ObQueryObject(size_t handle); +uint32_t ObInsertObject(void* object, TypeDestructor_t destructor); +void ObCloseHandle(uint32_t handle); + +template +T* ObQueryObject(XDISPATCHER_HEADER& header) +{ + std::lock_guard guard{ gKernelLock }; + if (header.WaitListHead.Flink != OBJECT_SIGNATURE) + { + header.WaitListHead.Flink = OBJECT_SIGNATURE; + auto* obj = new T(reinterpret_cast(&header)); + header.WaitListHead.Blink = ObInsertObject(obj, DestroyObject); + + return obj; + } + + return static_cast(ObQueryObject(header.WaitListHead.Blink.get())); +} + +template +size_t ObInsertObject(T* object) +{ + return ObInsertObject(object, DestroyObject); +} + +template +T* ObCreateObject(int& handle) +{ + auto* obj = new T(); + handle = ::ObInsertObject(obj, DestroyObject); + + return obj; +} + +// Get object without initialisation +template +T* ObTryQueryObject(XDISPATCHER_HEADER& header) +{ + if (header.WaitListHead.Flink != OBJECT_SIGNATURE) + return nullptr; + + return static_cast(ObQueryObject(header.WaitListHead.Blink)); +} + +template +T* ObTryQueryObject(int handle) +{ + return static_cast(ObQueryObject(handle)); +} \ No newline at end of file diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp new file mode 100644 index 00000000..63064a2b --- /dev/null +++ b/UnleashedRecomp/main.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GAME_XEX_PATH "game:\\default.xex" + +const size_t XMAIOBegin = 0x7FEA0000; +const size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF; + +Memory gMemory{ reinterpret_cast(0x100000000), 0x100000000 }; +Heap gUserHeap; +CodeCache gCodeCache; + +int main() +{ +#ifdef _WIN32 + CoInitializeEx(nullptr, COINIT_MULTITHREADED); +#endif + + gMemory.Alloc(0x10000, 0x1000, MEM_COMMIT); + gUserHeap.Init(); + gCodeCache.Init(); + + gMemory.Alloc(XMAIOBegin, 0xFFFF, MEM_COMMIT); + + const auto gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Game"); + const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update"); + XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : "."); + XamRegisterContent(updateContent, ".\\update"); + + if (FileExists(".\\save\\SYS-DATA")) + { + XamRegisterContent(XamMakeContent(XCONTENTTYPE_SAVEDATA, "SYS-DATA"), ".\\save"); + } + else if (FileExists(".\\SYS-DATA")) + { + XamRegisterContent(XamMakeContent(XCONTENTTYPE_SAVEDATA, "SYS-DATA"), "."); + } + + // Mount game + XamContentCreateEx(0, "game", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr); + XamContentCreateEx(0, "update", &updateContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr); + + // OS mounts game data to D: + XamContentCreateEx(0, "D", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr); + + auto loadResult = LoadFile(FileSystem::TransformPath(GAME_XEX_PATH)); + if (!loadResult.has_value()) + { + assert("Failed to load default.xex" && false); + return 1; + } + + auto* xex = reinterpret_cast(loadResult->data()); + auto headers = reinterpret_cast(&xex[1]); + auto security = reinterpret_cast((char*)xex + xex->AddressOfSecurityInfo); + + gMemory.Alloc(security->ImageBase, security->SizeOfImage, MEM_COMMIT); + + auto format = Xex2FindOptionalHeader(xex, XEX_HEADER_FILE_FORMAT_INFO); + auto entry = *Xex2FindOptionalHeader(xex, XEX_HEADER_ENTRY_POINT); + ByteSwap(entry); + assert(format->CompressionType >= 1); + + if (format->CompressionType == 1) + { + auto srcData = (char*)xex + xex->SizeOfHeader; + auto destData = (char*)gMemory.Translate(security->ImageBase); + + auto numBlocks = (format->SizeOfHeader / sizeof(XEX_BASIC_FILE_COMPRESSION_INFO)) - 1; + auto blocks = reinterpret_cast(format + 1); + + for (size_t i = 0; i < numBlocks; i++) + { + memcpy(destData, srcData, blocks[i].SizeOfData); + + srcData += blocks[i].SizeOfData; + destData += blocks[i].SizeOfData; + memset(destData, 0, blocks[i].SizeOfPadding); + + destData += blocks[i].SizeOfPadding; + } + } + + GuestThread::Start(entry); + + return 0; +} + +GUEST_FUNCTION_STUB(__imp__vsprintf); +GUEST_FUNCTION_STUB(__imp___vsnprintf); +GUEST_FUNCTION_STUB(__imp__sprintf); +GUEST_FUNCTION_STUB(__imp___snprintf); +GUEST_FUNCTION_STUB(__imp___snwprintf); +GUEST_FUNCTION_STUB(__imp__vswprintf); +GUEST_FUNCTION_STUB(__imp___vscwprintf); +GUEST_FUNCTION_STUB(__imp__swprintf); diff --git a/UnleashedRecomp/ppc/.gitignore b/UnleashedRecomp/ppc/.gitignore new file mode 100644 index 00000000..1d6f231f --- /dev/null +++ b/UnleashedRecomp/ppc/.gitignore @@ -0,0 +1 @@ +ppc_* \ No newline at end of file diff --git a/UnleashedRecomp/stdafx.h b/UnleashedRecomp/stdafx.h new file mode 100644 index 00000000..6ef9cc44 --- /dev/null +++ b/UnleashedRecomp/stdafx.h @@ -0,0 +1,15 @@ +#pragma once + +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include + +#include "framework.h" +#include "Mutex.h" +#include "Config.h" \ No newline at end of file diff --git a/UnleashedRecomp/xxHashMap.h b/UnleashedRecomp/xxHashMap.h new file mode 100644 index 00000000..687d9271 --- /dev/null +++ b/UnleashedRecomp/xxHashMap.h @@ -0,0 +1,14 @@ +#pragma once + +struct xxHash +{ + using is_avalanching = void; + + uint64_t operator()(XXH64_hash_t const& x) const noexcept + { + return x; + } +}; + +template +using xxHashMap = ankerl::unordered_dense::map; \ No newline at end of file diff --git a/thirdparty/.gitignore b/thirdparty/.gitignore new file mode 100644 index 00000000..030c8d17 --- /dev/null +++ b/thirdparty/.gitignore @@ -0,0 +1,8 @@ +!* + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt new file mode 100644 index 00000000..9a680635 --- /dev/null +++ b/thirdparty/CMakeLists.txt @@ -0,0 +1,18 @@ +include(FetchContent) + +FetchContent_Declare( + unordered_dense + GIT_REPOSITORY https://github.com/martinus/unordered_dense.git + GIT_TAG main +) +FetchContent_Declare( + xxHash + GIT_REPOSITORY https://github.com/Cyan4973/xxHash.git + GIT_TAG v0.8.2 + SOURCE_SUBDIR "cmake_unofficial" +) +FetchContent_MakeAvailable(unordered_dense) +FetchContent_MakeAvailable(xxHash) + +add_subdirectory(${SWA_THIRDPARTY_ROOT}/PowerRecomp) +add_subdirectory(${SWA_THIRDPARTY_ROOT}/o1heap) \ No newline at end of file diff --git a/thirdparty/PowerRecomp b/thirdparty/PowerRecomp new file mode 160000 index 00000000..c4de7026 --- /dev/null +++ b/thirdparty/PowerRecomp @@ -0,0 +1 @@ +Subproject commit c4de70262f0bc6e44c95df99772c136d1bdd71cc diff --git a/thirdparty/o1heap/CMakeLists.txt b/thirdparty/o1heap/CMakeLists.txt new file mode 100644 index 00000000..08e9670f --- /dev/null +++ b/thirdparty/o1heap/CMakeLists.txt @@ -0,0 +1,4 @@ +project("o1heap") + +add_library(o1heap "o1heap.h" "o1heap.c") +target_include_directories(o1heap PUBLIC ".") \ No newline at end of file diff --git a/thirdparty/o1heap/o1heap.c b/thirdparty/o1heap/o1heap.c new file mode 100644 index 00000000..d6a54661 --- /dev/null +++ b/thirdparty/o1heap/o1heap.c @@ -0,0 +1,497 @@ +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2020 Pavel Kirienko +// Authors: Pavel Kirienko + +#include "o1heap.h" +#include +#include + +// ---------------------------------------- BUILD CONFIGURATION OPTIONS ---------------------------------------- + +/// Define this macro to include build configuration header. This is an alternative to the -D compiler flag. +/// Usage example with CMake: "-DO1HEAP_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/my_o1heap_config.h\"" +#ifdef O1HEAP_CONFIG_HEADER +# include O1HEAP_CONFIG_HEADER +#endif + +/// The assertion macro defaults to the standard assert(). +/// It can be overridden to manually suppress assertion checks or use a different error handling policy. +#ifndef O1HEAP_ASSERT +// Intentional violation of MISRA: the assertion check macro cannot be replaced with a function definition. +# define O1HEAP_ASSERT(x) assert(x) // NOSONAR +#endif + +/// Allow usage of compiler intrinsics for branch annotation and CLZ. +#ifndef O1HEAP_USE_INTRINSICS +# define O1HEAP_USE_INTRINSICS 1 +#endif + +/// Branch probability annotations are used to improve the worst case execution time (WCET). They are entirely optional. +#if O1HEAP_USE_INTRINSICS && !defined(O1HEAP_LIKELY) +# if defined(__GNUC__) || defined(__clang__) || defined(__CC_ARM) +// Intentional violation of MISRA: branch hinting macro cannot be replaced with a function definition. +# define O1HEAP_LIKELY(x) __builtin_expect((x), 1) // NOSONAR +# endif +#endif +#ifndef O1HEAP_LIKELY +# define O1HEAP_LIKELY(x) x +#endif + +/// This option is used for testing only. Do not use in production. +#ifndef O1HEAP_PRIVATE +# define O1HEAP_PRIVATE static inline +#endif + +/// Count leading zeros (CLZ) is used for fast computation of binary logarithm (which needs to be done very often). +/// Most of the modern processors (including the embedded ones) implement dedicated hardware support for fast CLZ +/// computation, which is available via compiler intrinsics. The default implementation will automatically use +/// the intrinsics for some of the compilers; for others it will default to the slow software emulation, +/// which can be overridden by the user via O1HEAP_CONFIG_HEADER. The library guarantees that the argument is positive. +#if O1HEAP_USE_INTRINSICS && !defined(O1HEAP_CLZ) +# if defined(__GNUC__) || defined(__clang__) || defined(__CC_ARM) +# if SIZE_MAX == 0xFFFFFFFFFFFFFFFF +# define O1HEAP_CLZ __builtin_clzll +# else +# define O1HEAP_CLZ __builtin_clzl +# endif +# endif +#endif +#ifndef O1HEAP_CLZ +O1HEAP_PRIVATE uint_fast8_t O1HEAP_CLZ(const size_t x) +{ + O1HEAP_ASSERT(x > 0); + size_t t = ((size_t)1U) << ((sizeof(size_t) * CHAR_BIT) - 1U); + uint_fast8_t r = 0; + while ((x & t) == 0) + { + t >>= 1U; + r++; + } + return r; +} +#endif + +// ---------------------------------------- INTERNAL DEFINITIONS ---------------------------------------- + +#if __STDC_VERSION__ < 201112L +// Intentional violation of MISRA: static assertion macro cannot be replaced with a function definition. +# define static_assert(x, ...) typedef char _static_assert_gl(_static_assertion_, __LINE__)[(x) ? 1 : -1] // NOSONAR +# define _static_assert_gl(a, b) _static_assert_gl_impl(a, b) // NOSONAR +// Intentional violation of MISRA: the paste operator ## cannot be avoided in this context. +# define _static_assert_gl_impl(a, b) a##b // NOSONAR +#endif + +/// The overhead is at most O1HEAP_ALIGNMENT bytes large, +/// then follows the user data which shall keep the next fragment aligned. +#define FRAGMENT_SIZE_MIN (O1HEAP_ALIGNMENT * 2U) + +/// This is risky, handle with care: if the allocation amount plus per-fragment overhead exceeds 2**(b-1), +/// where b is the pointer bit width, then ceil(log2(amount)) yields b; then 2**b causes an integer overflow. +/// To avoid this, we put a hard limit on fragment size (which is amount + per-fragment overhead): 2**(b-1) +#define FRAGMENT_SIZE_MAX ((SIZE_MAX >> 1U) + 1U) + +/// Normally we should subtract log2(FRAGMENT_SIZE_MIN) but log2 is bulky to compute using the preprocessor only. +/// We will certainly end up with unused bins this way, but it is cheap to ignore. +#define NUM_BINS_MAX (sizeof(size_t) * CHAR_BIT) + +static_assert((O1HEAP_ALIGNMENT& (O1HEAP_ALIGNMENT - 1U)) == 0U, "Not a power of 2"); +static_assert((FRAGMENT_SIZE_MIN& (FRAGMENT_SIZE_MIN - 1U)) == 0U, "Not a power of 2"); +static_assert((FRAGMENT_SIZE_MAX& (FRAGMENT_SIZE_MAX - 1U)) == 0U, "Not a power of 2"); + +typedef struct Fragment Fragment; + +typedef struct FragmentHeader +{ + Fragment* next; + Fragment* prev; + size_t size; + bool used; +} FragmentHeader; +static_assert(sizeof(FragmentHeader) <= O1HEAP_ALIGNMENT, "Memory layout error"); + +struct Fragment +{ + FragmentHeader header; + // Everything past the header may spill over into the allocatable space. The header survives across alloc/free. + Fragment* next_free; // Next free fragment in the bin; NULL in the last one. + Fragment* prev_free; // Same but points back; NULL in the first one. +}; +static_assert(sizeof(Fragment) <= FRAGMENT_SIZE_MIN, "Memory layout error"); + +struct O1HeapInstance +{ + Fragment* bins[NUM_BINS_MAX]; ///< Smallest fragments are in the bin at index 0. + size_t nonempty_bin_mask; ///< Bit 1 represents a non-empty bin; bin at index 0 is for the smallest fragments. + + O1HeapDiagnostics diagnostics; +}; + +/// The amount of space allocated for the heap instance. +/// Its size is padded up to O1HEAP_ALIGNMENT to ensure correct alignment of the allocation arena that follows. +#define INSTANCE_SIZE_PADDED ((sizeof(O1HeapInstance) + O1HEAP_ALIGNMENT - 1U) & ~(O1HEAP_ALIGNMENT - 1U)) + +static_assert(INSTANCE_SIZE_PADDED >= sizeof(O1HeapInstance), "Invalid instance footprint computation"); +static_assert((INSTANCE_SIZE_PADDED% O1HEAP_ALIGNMENT) == 0U, "Invalid instance footprint computation"); + +/// Undefined for zero argument. +O1HEAP_PRIVATE uint_fast8_t log2Floor(const size_t x) +{ + O1HEAP_ASSERT(x > 0); + // NOLINTNEXTLINE redundant cast to the same type. + return (uint_fast8_t)(((sizeof(x) * CHAR_BIT) - 1U) - ((uint_fast8_t)O1HEAP_CLZ(x))); +} + +/// Special case: if the argument is zero, returns zero. +O1HEAP_PRIVATE uint_fast8_t log2Ceil(const size_t x) +{ + // NOLINTNEXTLINE redundant cast to the same type. + return (x <= 1U) ? 0U : (uint_fast8_t)((sizeof(x) * CHAR_BIT) - ((uint_fast8_t)O1HEAP_CLZ(x - 1U))); +} + +/// Raise 2 into the specified power. +/// You might be tempted to do something like (1U << power). WRONG! We humans are prone to forgetting things. +/// If you forget to cast your 1U to size_t or ULL, you may end up with undefined behavior. +O1HEAP_PRIVATE size_t pow2(const uint_fast8_t power) +{ + return ((size_t)1U) << power; +} + +/// This is equivalent to pow2(log2Ceil(x)). Undefined for x<2. +O1HEAP_PRIVATE size_t roundUpToPowerOf2(const size_t x) +{ + O1HEAP_ASSERT(x >= 2U); + // NOLINTNEXTLINE redundant cast to the same type. + return ((size_t)1U) << ((sizeof(x) * CHAR_BIT) - ((uint_fast8_t)O1HEAP_CLZ(x - 1U))); +} + +/// Links two fragments so that their next/prev pointers point to each other; left goes before right. +O1HEAP_PRIVATE void interlink(Fragment* const left, Fragment* const right) +{ + if (O1HEAP_LIKELY(left != NULL)) + { + left->header.next = right; + } + if (O1HEAP_LIKELY(right != NULL)) + { + right->header.prev = left; + } +} + +/// Adds a new fragment into the appropriate bin and updates the lookup mask. +O1HEAP_PRIVATE void rebin(O1HeapInstance* const handle, Fragment* const fragment) +{ + O1HEAP_ASSERT(handle != NULL); + O1HEAP_ASSERT(fragment != NULL); + O1HEAP_ASSERT(fragment->header.size >= FRAGMENT_SIZE_MIN); + O1HEAP_ASSERT((fragment->header.size % FRAGMENT_SIZE_MIN) == 0U); + const uint_fast8_t idx = log2Floor(fragment->header.size / FRAGMENT_SIZE_MIN); // Round DOWN when inserting. + O1HEAP_ASSERT(idx < NUM_BINS_MAX); + // Add the new fragment to the beginning of the bin list. + // I.e., each allocation will be returning the most-recently-used fragment -- good for caching. + fragment->next_free = handle->bins[idx]; + fragment->prev_free = NULL; + if (O1HEAP_LIKELY(handle->bins[idx] != NULL)) + { + handle->bins[idx]->prev_free = fragment; + } + handle->bins[idx] = fragment; + handle->nonempty_bin_mask |= pow2(idx); +} + +/// Removes the specified fragment from its bin. +O1HEAP_PRIVATE void unbin(O1HeapInstance* const handle, const Fragment* const fragment) +{ + O1HEAP_ASSERT(handle != NULL); + O1HEAP_ASSERT(fragment != NULL); + O1HEAP_ASSERT(fragment->header.size >= FRAGMENT_SIZE_MIN); + O1HEAP_ASSERT((fragment->header.size % FRAGMENT_SIZE_MIN) == 0U); + const uint_fast8_t idx = log2Floor(fragment->header.size / FRAGMENT_SIZE_MIN); // Round DOWN when removing. + O1HEAP_ASSERT(idx < NUM_BINS_MAX); + // Remove the bin from the free fragment list. + if (O1HEAP_LIKELY(fragment->next_free != NULL)) + { + fragment->next_free->prev_free = fragment->prev_free; + } + if (O1HEAP_LIKELY(fragment->prev_free != NULL)) + { + fragment->prev_free->next_free = fragment->next_free; + } + // Update the bin header. + if (O1HEAP_LIKELY(handle->bins[idx] == fragment)) + { + O1HEAP_ASSERT(fragment->prev_free == NULL); + handle->bins[idx] = fragment->next_free; + if (O1HEAP_LIKELY(handle->bins[idx] == NULL)) + { + handle->nonempty_bin_mask &= ~pow2(idx); + } + } +} + +// ---------------------------------------- PUBLIC API IMPLEMENTATION ---------------------------------------- + +O1HeapInstance* o1heapInit(void* const base, const size_t size) +{ + O1HeapInstance* out = NULL; + if ((base != NULL) && ((((size_t)base) % O1HEAP_ALIGNMENT) == 0U) && + (size >= (INSTANCE_SIZE_PADDED + FRAGMENT_SIZE_MIN))) + { + // Allocate the core heap metadata structure in the beginning of the arena. + O1HEAP_ASSERT(((size_t)base) % sizeof(O1HeapInstance*) == 0U); + out = (O1HeapInstance*)base; + out->nonempty_bin_mask = 0U; + for (size_t i = 0; i < NUM_BINS_MAX; i++) + { + out->bins[i] = NULL; + } + + // Limit and align the capacity. + size_t capacity = size - INSTANCE_SIZE_PADDED; + if (capacity > FRAGMENT_SIZE_MAX) + { + capacity = FRAGMENT_SIZE_MAX; + } + while ((capacity % FRAGMENT_SIZE_MIN) != 0) + { + O1HEAP_ASSERT(capacity > 0U); + capacity--; + } + O1HEAP_ASSERT((capacity % FRAGMENT_SIZE_MIN) == 0); + O1HEAP_ASSERT((capacity >= FRAGMENT_SIZE_MIN) && (capacity <= FRAGMENT_SIZE_MAX)); + + // Initialize the root fragment. + Fragment* const frag = (Fragment*)(void*)(((char*)base) + INSTANCE_SIZE_PADDED); + O1HEAP_ASSERT((((size_t)frag) % O1HEAP_ALIGNMENT) == 0U); + frag->header.next = NULL; + frag->header.prev = NULL; + frag->header.size = capacity; + frag->header.used = false; + frag->next_free = NULL; + frag->prev_free = NULL; + rebin(out, frag); + O1HEAP_ASSERT(out->nonempty_bin_mask != 0U); + + // Initialize the diagnostics. + out->diagnostics.capacity = capacity; + out->diagnostics.allocated = 0U; + out->diagnostics.peak_allocated = 0U; + out->diagnostics.peak_request_size = 0U; + out->diagnostics.oom_count = 0U; + } + + return out; +} + +void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount) +{ + O1HEAP_ASSERT(handle != NULL); + O1HEAP_ASSERT(handle->diagnostics.capacity <= FRAGMENT_SIZE_MAX); + void* out = NULL; + + // If the amount approaches approx. SIZE_MAX/2, an undetected integer overflow may occur. + // To avoid that, we do not attempt allocation if the amount exceeds the hard limit. + // We perform multiple redundant checks to account for a possible unaccounted overflow. + if (O1HEAP_LIKELY((amount > 0U) && (amount <= (handle->diagnostics.capacity - O1HEAP_ALIGNMENT)))) + { + // Add the header size and align the allocation size to the power of 2. + // See "Timing-Predictable Memory Allocation In Hard Real-Time Systems", Herter, page 27. + const size_t fragment_size = roundUpToPowerOf2(amount + O1HEAP_ALIGNMENT); + O1HEAP_ASSERT(fragment_size <= FRAGMENT_SIZE_MAX); + O1HEAP_ASSERT(fragment_size >= FRAGMENT_SIZE_MIN); + O1HEAP_ASSERT(fragment_size >= amount + O1HEAP_ALIGNMENT); + O1HEAP_ASSERT((fragment_size & (fragment_size - 1U)) == 0U); // Is power of 2. + + const uint_fast8_t optimal_bin_index = log2Ceil(fragment_size / FRAGMENT_SIZE_MIN); // Use CEIL when fetching. + O1HEAP_ASSERT(optimal_bin_index < NUM_BINS_MAX); + const size_t candidate_bin_mask = ~(pow2(optimal_bin_index) - 1U); + + // Find the smallest non-empty bin we can use. + const size_t suitable_bins = handle->nonempty_bin_mask & candidate_bin_mask; + const size_t smallest_bin_mask = suitable_bins & ~(suitable_bins - 1U); // Clear all bits but the lowest. + if (O1HEAP_LIKELY(smallest_bin_mask != 0)) + { + O1HEAP_ASSERT((smallest_bin_mask & (smallest_bin_mask - 1U)) == 0U); // Is power of 2. + const uint_fast8_t bin_index = log2Floor(smallest_bin_mask); + O1HEAP_ASSERT(bin_index >= optimal_bin_index); + O1HEAP_ASSERT(bin_index < NUM_BINS_MAX); + + // The bin we found shall not be empty, otherwise it's a state divergence (memory corruption?). + Fragment* const frag = handle->bins[bin_index]; + O1HEAP_ASSERT(frag != NULL); + O1HEAP_ASSERT(frag->header.size >= fragment_size); + O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U); + O1HEAP_ASSERT(!frag->header.used); + unbin(handle, frag); + + // Split the fragment if it is too large. + const size_t leftover = frag->header.size - fragment_size; + frag->header.size = fragment_size; + O1HEAP_ASSERT(leftover < handle->diagnostics.capacity); // Overflow check. + O1HEAP_ASSERT(leftover % FRAGMENT_SIZE_MIN == 0U); // Alignment check. + if (O1HEAP_LIKELY(leftover >= FRAGMENT_SIZE_MIN)) + { + Fragment* const new_frag = (Fragment*)(void*)(((char*)frag) + fragment_size); + O1HEAP_ASSERT(((size_t)new_frag) % O1HEAP_ALIGNMENT == 0U); + new_frag->header.size = leftover; + new_frag->header.used = false; + interlink(new_frag, frag->header.next); + interlink(frag, new_frag); + rebin(handle, new_frag); + } + + // Update the diagnostics. + O1HEAP_ASSERT((handle->diagnostics.allocated % FRAGMENT_SIZE_MIN) == 0U); + handle->diagnostics.allocated += fragment_size; + O1HEAP_ASSERT(handle->diagnostics.allocated <= handle->diagnostics.capacity); + if (O1HEAP_LIKELY(handle->diagnostics.peak_allocated < handle->diagnostics.allocated)) + { + handle->diagnostics.peak_allocated = handle->diagnostics.allocated; + } + + // Finalize the fragment we just allocated. + O1HEAP_ASSERT(frag->header.size >= amount + O1HEAP_ALIGNMENT); + frag->header.used = true; + + out = ((char*)frag) + O1HEAP_ALIGNMENT; + } + } + + // Update the diagnostics. + if (O1HEAP_LIKELY(handle->diagnostics.peak_request_size < amount)) + { + handle->diagnostics.peak_request_size = amount; + } + if (O1HEAP_LIKELY((out == NULL) && (amount > 0U))) + { + handle->diagnostics.oom_count++; + } + + return out; +} + +void o1heapFree(O1HeapInstance* const handle, void* const pointer) +{ + O1HEAP_ASSERT(handle != NULL); + O1HEAP_ASSERT(handle->diagnostics.capacity <= FRAGMENT_SIZE_MAX); + if (O1HEAP_LIKELY(pointer != NULL)) // NULL pointer is a no-op. + { + Fragment* const frag = (Fragment*)(void*)(((char*)pointer) - O1HEAP_ALIGNMENT); + + // Check for heap corruption in debug builds. + O1HEAP_ASSERT(((size_t)frag) % sizeof(Fragment*) == 0U); + O1HEAP_ASSERT(((size_t)frag) >= (((size_t)handle) + INSTANCE_SIZE_PADDED)); + O1HEAP_ASSERT(((size_t)frag) <= + (((size_t)handle) + INSTANCE_SIZE_PADDED + handle->diagnostics.capacity - FRAGMENT_SIZE_MIN)); + O1HEAP_ASSERT(frag->header.used); // Catch double-free + O1HEAP_ASSERT(((size_t)frag->header.next) % sizeof(Fragment*) == 0U); + O1HEAP_ASSERT(((size_t)frag->header.prev) % sizeof(Fragment*) == 0U); + O1HEAP_ASSERT(frag->header.size >= FRAGMENT_SIZE_MIN); + O1HEAP_ASSERT(frag->header.size <= handle->diagnostics.capacity); + O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U); + + // Even if we're going to drop the fragment later, mark it free anyway to prevent double-free. + frag->header.used = false; + + // Update the diagnostics. It must be done before merging because it invalidates the fragment size information. + O1HEAP_ASSERT(handle->diagnostics.allocated >= frag->header.size); // Heap corruption check. + handle->diagnostics.allocated -= frag->header.size; + + // Merge with siblings and insert the returned fragment into the appropriate bin and update metadata. + Fragment* const prev = frag->header.prev; + Fragment* const next = frag->header.next; + const bool join_left = (prev != NULL) && (!prev->header.used); + const bool join_right = (next != NULL) && (!next->header.used); + if (join_left && join_right) // [ prev ][ this ][ next ] => [ ------- prev ------- ] + { + unbin(handle, prev); + unbin(handle, next); + prev->header.size += frag->header.size + next->header.size; + frag->header.size = 0; // Invalidate the dropped fragment headers to prevent double-free. + next->header.size = 0; + O1HEAP_ASSERT((prev->header.size % FRAGMENT_SIZE_MIN) == 0U); + interlink(prev, next->header.next); + rebin(handle, prev); + } + else if (join_left) // [ prev ][ this ][ next ] => [ --- prev --- ][ next ] + { + unbin(handle, prev); + prev->header.size += frag->header.size; + frag->header.size = 0; + O1HEAP_ASSERT((prev->header.size % FRAGMENT_SIZE_MIN) == 0U); + interlink(prev, next); + rebin(handle, prev); + } + else if (join_right) // [ prev ][ this ][ next ] => [ prev ][ --- this --- ] + { + unbin(handle, next); + frag->header.size += next->header.size; + next->header.size = 0; + O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U); + interlink(frag, next->header.next); + rebin(handle, frag); + } + else + { + rebin(handle, frag); + } + } +} + +bool o1heapDoInvariantsHold(const O1HeapInstance* const handle) +{ + O1HEAP_ASSERT(handle != NULL); + bool valid = true; + + // Check the bin mask consistency. + for (size_t i = 0; i < NUM_BINS_MAX; i++) // Dear compiler, feel free to unroll this loop. + { + const bool mask_bit_set = (handle->nonempty_bin_mask & pow2((uint_fast8_t)i)) != 0U; + const bool bin_nonempty = handle->bins[i] != NULL; + valid = valid && (mask_bit_set == bin_nonempty); + } + + // Create a local copy of the diagnostics struct. + const O1HeapDiagnostics diag = handle->diagnostics; + + // Capacity check. + valid = valid && (diag.capacity <= FRAGMENT_SIZE_MAX) && (diag.capacity >= FRAGMENT_SIZE_MIN) && + ((diag.capacity % FRAGMENT_SIZE_MIN) == 0U); + + // Allocation info check. + valid = valid && (diag.allocated <= diag.capacity) && ((diag.allocated % FRAGMENT_SIZE_MIN) == 0U) && + (diag.peak_allocated <= diag.capacity) && (diag.peak_allocated >= diag.allocated) && + ((diag.peak_allocated % FRAGMENT_SIZE_MIN) == 0U); + + // Peak request check + valid = valid && ((diag.peak_request_size < diag.capacity) || (diag.oom_count > 0U)); + if (diag.peak_request_size == 0U) + { + valid = valid && (diag.peak_allocated == 0U) && (diag.allocated == 0U) && (diag.oom_count == 0U); + } + else + { + valid = valid && // Overflow on summation is possible but safe to ignore. + (((diag.peak_request_size + O1HEAP_ALIGNMENT) <= diag.peak_allocated) || (diag.oom_count > 0U)); + } + + return valid; +} + +O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance* const handle) +{ + O1HEAP_ASSERT(handle != NULL); + const O1HeapDiagnostics out = handle->diagnostics; + return out; +} diff --git a/thirdparty/o1heap/o1heap.h b/thirdparty/o1heap/o1heap.h new file mode 100644 index 00000000..9afe6c74 --- /dev/null +++ b/thirdparty/o1heap/o1heap.h @@ -0,0 +1,122 @@ +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2020 Pavel Kirienko +// Authors: Pavel Kirienko +// +// READ THE DOCUMENTATION IN README.md. + +#ifndef O1HEAP_H_INCLUDED +#define O1HEAP_H_INCLUDED + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + /// The semantic version number of this distribution. +#define O1HEAP_VERSION_MAJOR 2 + +/// The guaranteed alignment depends on the platform pointer width. +#define O1HEAP_ALIGNMENT (sizeof(void*) * 4U) + +/// The definition is private, so the user code can only operate on pointers. This is done to enforce encapsulation. + typedef struct O1HeapInstance O1HeapInstance; + + /// Runtime diagnostic information. This information can be used to facilitate runtime self-testing, + /// as required by certain safety-critical development guidelines. + /// If assertion checks are not disabled, the library will perform automatic runtime self-diagnostics that trigger + /// an assertion failure if a heap corruption is detected. + /// Health checks and validation can be done with o1heapDoInvariantsHold(). + typedef struct + { + /// The total amount of memory available for serving allocation requests (heap size). + /// The maximum allocation size is (capacity - O1HEAP_ALIGNMENT). + /// This parameter does not include the overhead used up by O1HeapInstance and arena alignment. + /// This parameter is constant. + size_t capacity; + + /// The amount of memory that is currently allocated, including the per-fragment overhead and size alignment. + /// For example, if the application requested a fragment of size 1 byte, the value reported here may be 32 bytes. + size_t allocated; + + /// The maximum value of 'allocated' seen since initialization. This parameter is never decreased. + size_t peak_allocated; + + /// The largest amount of memory that the allocator has attempted to allocate (perhaps unsuccessfully) + /// since initialization (not including the rounding and the allocator's own per-fragment overhead, + /// so the total is larger). This parameter is never decreased. The initial value is zero. + size_t peak_request_size; + + /// The number of times an allocation request could not be completed due to the lack of memory or + /// excessive fragmentation. OOM stands for "out of memory". This parameter is never decreased. + uint64_t oom_count; + } O1HeapDiagnostics; + + /// The arena base pointer shall be aligned at O1HEAP_ALIGNMENT, otherwise NULL is returned. + /// + /// The total heap capacity cannot exceed approx. (SIZE_MAX/2). If the arena size allows for a larger heap, + /// the excess will be silently truncated away (no error). This is not a realistic use case because a typical + /// application is unlikely to be able to dedicate that much of the address space for the heap. + /// + /// The function initializes a new heap instance allocated in the provided arena, taking some of its space for its + /// own needs (normally about 40..600 bytes depending on the architecture, but this parameter is not characterized). + /// A pointer to the newly initialized instance is returned. + /// + /// If the provided space is insufficient, NULL is returned. + /// + /// An initialized instance does not hold any resources. Therefore, if the instance is no longer needed, + /// it can be discarded without any de-initialization procedures. + /// + /// The heap is not thread-safe; external synchronization may be required. + O1HeapInstance* o1heapInit(void* const base, const size_t size); + + /// The semantics follows malloc() with additional guarantees the full list of which is provided below. + /// + /// If the allocation request is served successfully, a pointer to the newly allocated memory fragment is returned. + /// The returned pointer is guaranteed to be aligned at O1HEAP_ALIGNMENT. + /// + /// If the allocation request cannot be served due to the lack of memory or its excessive fragmentation, + /// a NULL pointer is returned. + /// + /// The function is executed in constant time. + /// The allocated memory is NOT zero-filled (because zero-filling is a variable-complexity operation). + void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount); + + /// The semantics follows free() with additional guarantees the full list of which is provided below. + /// + /// If the pointer does not point to a previously allocated block and is not NULL, the behavior is undefined. + /// Builds where assertion checks are enabled may trigger an assertion failure for some invalid inputs. + /// + /// The function is executed in constant time. + void o1heapFree(O1HeapInstance* const handle, void* const pointer); + + /// Performs a basic sanity check on the heap. + /// This function can be used as a weak but fast method of heap corruption detection. + /// If the handle pointer is NULL, the behavior is undefined. + /// The time complexity is constant. + /// The return value is truth if the heap looks valid, falsity otherwise. + bool o1heapDoInvariantsHold(const O1HeapInstance* const handle); + + /// Samples and returns a copy of the diagnostic information, see O1HeapDiagnostics. + /// This function merely copies the structure from an internal storage, so it is fast to return. + /// If the handle pointer is NULL, the behavior is undefined. + O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance* const handle); + +#ifdef __cplusplus +} +#endif +#endif // O1HEAP_H_INCLUDED