diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dda2e81..cf176594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,6 +270,8 @@ set(COMMON src/common/logging/backend.cpp src/common/types.h src/common/uint128.h src/common/version.h + src/common/ntapi.h + src/common/ntapi.cpp ) set(CORE src/core/aerolib/stubs.cpp diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index 1148b29c..e1c22d2a 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -11,6 +11,8 @@ #include "common/path_util.h" #ifdef _WIN32 +#include "common/ntapi.h" + #include #include #include @@ -221,15 +223,46 @@ void IOFile::Close() { #endif } +void IOFile::Unlink() { + if (!IsOpen()) { + return; + } + + // Mark the file for deletion + // TODO: Also remove the file path? +#if _WIN64 + FILE_DISPOSITION_INFORMATION disposition; + IO_STATUS_BLOCK iosb; + + const int fd = fileno(file); + HANDLE hfile = reinterpret_cast(_get_osfhandle(fd)); + + disposition.DeleteFile = TRUE; + NtSetInformationFile(hfile, &iosb, &disposition, sizeof(disposition), + FileDispositionInformation); +#else + UNREACHABLE_MSG("Missing Linux implementation"); +#endif +} + uintptr_t IOFile::GetFileMapping() { if (file_mapping) { return file_mapping; } #ifdef _WIN64 const int fd = fileno(file); + HANDLE hfile = reinterpret_cast(_get_osfhandle(fd)); - HANDLE mapping = - CreateFileMapping2(hfile, NULL, FILE_MAP_READ, PAGE_READONLY, SEC_COMMIT, 0, NULL, NULL, 0); + HANDLE mapping = nullptr; + + if (file_access_mode == FileAccessMode::ReadWrite) { + mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0, + NULL, NULL, 0); + } else { + mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_READ, PAGE_READONLY, SEC_COMMIT, 0, NULL, + NULL, 0); + } + file_mapping = std::bit_cast(mapping); ASSERT_MSG(file_mapping, "{}", Common::GetLastErrorMsg()); return file_mapping; diff --git a/src/common/io_file.h b/src/common/io_file.h index 778a42b4..7cb3246c 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -107,6 +107,8 @@ public: FileShareFlag flag = FileShareFlag::ShareReadOnly); void Close(); + void Unlink(); + bool Flush() const; bool Commit() const; diff --git a/src/common/ntapi.cpp b/src/common/ntapi.cpp new file mode 100644 index 00000000..f596aa4f --- /dev/null +++ b/src/common/ntapi.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ntapi.h" + +NtDelayExecution_t NtDelayExecution = nullptr; +NtSetInformationFile_t NtSetInformationFile = nullptr; + +namespace Common::NtApi { + +void Initialize() { + HMODULE nt_handle = GetModuleHandleA("ntdll.dll"); + + // http://stackoverflow.com/a/31411628/4725495 + NtDelayExecution = (NtDelayExecution_t)GetProcAddress(nt_handle, "NtDelayExecution"); + NtSetInformationFile = + (NtSetInformationFile_t)GetProcAddress(nt_handle, "NtSetInformationFile"); +} + +} // namespace Common::NtApi diff --git a/src/common/ntapi.h b/src/common/ntapi.h new file mode 100644 index 00000000..17d35340 --- /dev/null +++ b/src/common/ntapi.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _WIN32 + +#include +#include "common/types.h" + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileBasicInformation = 4, + FileStandardInformation = 5, + FileInternalInformation = 6, + FileEaInformation = 7, + FileAccessInformation = 8, + FileNameInformation = 9, + FileRenameInformation = 10, + FileLinkInformation = 11, + FileNamesInformation = 12, + FileDispositionInformation = 13, + FilePositionInformation = 14, + FileFullEaInformation = 15, + FileModeInformation = 16, + FileAlignmentInformation = 17, + FileAllInformation = 18, + FileAllocationInformation = 19, + FileEndOfFileInformation = 20, + FileAlternateNameInformation = 21, + FileStreamInformation = 22, + FilePipeInformation = 23, + FilePipeLocalInformation = 24, + FilePipeRemoteInformation = 25, + FileMailslotQueryInformation = 26, + FileMailslotSetInformation = 27, + FileCompressionInformation = 28, + FileObjectIdInformation = 29, + FileCompletionInformation = 30, + FileMoveClusterInformation = 31, + FileQuotaInformation = 32, + FileReparsePointInformation = 33, + FileNetworkOpenInformation = 34, + FileAttributeTagInformation = 35, + FileTrackingInformation = 36, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileValidDataLengthInformation = 39, + FileShortNameInformation = 40, + FileIoCompletionNotificationInformation = 41, + FileIoStatusBlockRangeInformation = 42, + FileIoPriorityHintInformation = 43, + FileSfioReserveInformation = 44, + FileSfioVolumeInformation = 45, + FileHardLinkInformation = 46, + FileProcessIdsUsingFileInformation = 47, + FileNormalizedNameInformation = 48, + FileNetworkPhysicalNameInformation = 49, + FileIdGlobalTxDirectoryInformation = 50, + FileIsRemoteDeviceInformation = 51, + FileUnusedInformation = 52, + FileNumaNodeInformation = 53, + FileStandardLinkInformation = 54, + FileRemoteProtocolInformation = 55, + FileRenameInformationBypassAccessCheck = 56, + FileLinkInformationBypassAccessCheck = 57, + FileVolumeNameInformation = 58, + FileIdInformation = 59, + FileIdExtdDirectoryInformation = 60, + FileReplaceCompletionInformation = 61, + FileHardLinkFullIdInformation = 62, + FileIdExtdBothDirectoryInformation = 63, + FileDispositionInformationEx = 64, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck = 66, + FileDesiredStorageClassInformation = 67, + FileStatInformation = 68, + FileMemoryPartitionInformation = 69, + FileStatLxInformation = 70, + FileCaseSensitiveInformation = 71, + FileLinkInformationEx = 72, + FileLinkInformationExBypassAccessCheck = 73, + FileStorageReserveIdInformation = 74, + FileCaseSensitiveInformationForceAccessCheck = 75, + FileKnownFolderInformation = 76, + FileStatBasicInformation = 77, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81, + FileStreamReservationInformation, + FileMupProviderInfo, + FileMaximumInformation +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef struct _IO_STATUS_BLOCK { + union { + u32 Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef struct _FILE_DISPOSITION_INFORMATION { + BOOLEAN DeleteFile; +} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; + +typedef u32(__stdcall* NtDelayExecution_t)(BOOL Alertable, PLARGE_INTEGER DelayInterval); + +typedef u32(__stdcall* NtSetInformationFile_t)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass); + +extern NtDelayExecution_t NtDelayExecution; +extern NtSetInformationFile_t NtSetInformationFile; + +namespace Common::NtApi { +void Initialize(); +} + +#endif diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 7749ec2d..8cd8cec4 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -6,6 +6,7 @@ #include "common/error.h" #include "core/address_space.h" #include "core/libraries/kernel/memory_management.h" +#include "core/memory.h" #ifdef _WIN32 #include @@ -18,6 +19,18 @@ namespace Core { static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE; +[[nodiscard]] constexpr u64 ToWindowsProt(Core::MemoryProt prot) { + switch (prot) { + case Core::MemoryProt::NoAccess: + default: + return PAGE_NOACCESS; + case Core::MemoryProt::CpuRead: + return PAGE_READONLY; + case Core::MemoryProt::CpuReadWrite: + return PAGE_READWRITE; + } +} + #ifdef _WIN32 struct AddressSpace::Impl { Impl() : process{GetCurrentProcess()} { @@ -325,8 +338,10 @@ void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr ph is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); } -void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, uintptr_t fd) { - return impl->Map(virtual_addr, offset, size, fd ? PAGE_READONLY : PAGE_READWRITE, fd); +void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, + uintptr_t fd) { + return impl->Map(virtual_addr, offset, size, + ToWindowsProt(std::bit_cast(prot)), fd); } void AddressSpace::Unmap(VAddr virtual_addr, size_t size, bool has_backing) { diff --git a/src/core/address_space.h b/src/core/address_space.h index b979481f..5b68839c 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -63,7 +63,7 @@ public: bool exec = false); /// Memory maps a specified file descriptor. - void* MapFile(VAddr virtual_addr, size_t size, size_t offset, uintptr_t fd); + void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd); /// Unmaps specified virtual memory area. void Unmap(VAddr virtual_addr, size_t size, bool has_backing); diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 448c5fa6..eb6248d0 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -157,6 +157,33 @@ size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) { return file->f.WriteRaw(buf, nbytes); } +int PS4_SYSV_ABI sceKernelUnlink(const char* path) { + if (path == nullptr) { + return SCE_KERNEL_ERROR_EINVAL; + } + + auto* h = Common::Singleton::Instance(); + auto* mnt = Common::Singleton::Instance(); + + std::string host_path = mnt->GetHostFile(path); + + if (host_path.empty()) { + return SCE_KERNEL_ERROR_EACCES; + } + + if (std::filesystem::is_directory(host_path)) { + return SCE_KERNEL_ERROR_EPERM; + } + + auto* file = h->getFile(host_path); + if (file != nullptr) { + file->f.Unlink(); + } + + LOG_INFO(Kernel_Fs, "Unlinked {}", path); + return SCE_OK; +} + size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); @@ -356,6 +383,22 @@ s32 PS4_SYSV_ABI sceKernelFsync(int fd) { return ORBIS_OK; } +int PS4_SYSV_ABI sceKernelFtruncate(int fd, s64 length) { + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(fd); + + if (file == nullptr) { + return SCE_KERNEL_ERROR_EBADF; + } + + if (file->m_host_name.empty()) { + return SCE_KERNEL_ERROR_EACCES; + } + + file->f.SetSize(length); + return SCE_OK; +} + static int GetDents(int fd, char* buf, int nbytes, s64* basep) { // TODO error codes ASSERT(buf != nullptr); @@ -372,7 +415,7 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) { static int fileno = 1000; // random OrbisKernelDirent* sce_ent = (OrbisKernelDirent*)buf; sce_ent->d_fileno = fileno++; // TODO this should be unique but atm it changes maybe switch to a - // hash or something? + // hash or something? sce_ent->d_reclen = sizeof(OrbisKernelDirent); sce_ent->d_type = (entry.isFile ? 8 : 4); sce_ent->d_namlen = str_size; @@ -435,6 +478,7 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("eV9wAD2riIA", "libkernel", 1, "libkernel", 1, 1, sceKernelStat); LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat); LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat); + LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate); LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat); LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread); @@ -443,6 +487,7 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("j2AIqSqJP0w", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdents); LIB_FUNCTION("taRWhTJFTgE", "libkernel", 1, "libkernel", 1, 1, sceKernelGetdirentries); LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPwrite); + LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", 1, 1, sceKernelUnlink); // openOrbis (to check if it is valid out of OpenOrbis LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", 1, 1, diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index 57c55a03..0c5b3917 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -370,6 +370,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("hwVSPCmp5tM", "libkernel", 1, "libkernel", 1, 1, sceKernelCheckedReleaseDirectMemory); LIB_FUNCTION("rVjRvHJ0X6c", "libkernel", 1, "libkernel", 1, 1, sceKernelVirtualQuery); + LIB_FUNCTION("7oxv3PPCumo", "libkernel", 1, "libkernel", 1, 1, sceKernelReserveVirtualRange); LIB_FUNCTION("pO96TwzOm5E", "libkernel", 1, "libkernel", 1, 1, sceKernelGetDirectMemorySize); LIB_FUNCTION("NcaWUxfMNIQ", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedDirectMemory); LIB_FUNCTION("L-Q3LEjIbgA", "libkernel", 1, "libkernel", 1, 1, sceKernelMapDirectMemory); diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index b6e3054c..92fde960 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -87,6 +87,33 @@ s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtual return memory->VirtualQuery(std::bit_cast(addr), flags, info); } +s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment) { + LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}, alignment = {:#x}", + fmt::ptr(*addr), len, flags, alignment); + + if (addr == nullptr) { + LOG_ERROR(Kernel_Vmm, "Address is invalid!"); + return SCE_KERNEL_ERROR_EINVAL; + } + if (len == 0 || !Common::Is16KBAligned(len)) { + LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!"); + return SCE_KERNEL_ERROR_EINVAL; + } + if (alignment != 0) { + if ((!std::has_single_bit(alignment) && !Common::Is16KBAligned(alignment))) { + LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); + return SCE_KERNEL_ERROR_EINVAL; + } + } + + auto* memory = Core::Memory::Instance(); + const VAddr in_addr = reinterpret_cast(*addr); + const auto map_flags = static_cast(flags); + memory->Reserve(addr, in_addr, len, map_flags, alignment); + + return SCE_OK; +} + int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, s64 directMemoryStart, u64 alignment, const char* name) { diff --git a/src/core/libraries/kernel/memory_management.h b/src/core/libraries/kernel/memory_management.h index 6e3a0660..81f8bd7a 100644 --- a/src/core/libraries/kernel/memory_management.h +++ b/src/core/libraries/kernel/memory_management.h @@ -70,6 +70,7 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE size_t* sizeOut); s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, size_t infoSize); +s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment); s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot, int flags, const char* name); s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 8101ab6d..fbbcfdcf 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -12,10 +12,8 @@ #include #include -// http://stackoverflow.com/a/31411628/4725495 -static u32(__stdcall* NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = - (u32(__stdcall*)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandleA("ntdll.dll"), - "NtDelayExecution"); +#include "common/ntapi.h" + #else #include #include diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 5166908a..98628ee4 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -83,6 +83,25 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) { MergeAdjacent(dmem_map, dmem_area); } +int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags, + u64 alignment) { + std::scoped_lock lk{mutex}; + + ASSERT_MSG(virtual_addr != 0, "TODO: Reserve address is zero - search for free space"); + + VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr; + + // Add virtual memory area + auto& new_vma = AddMapping(mapped_addr, size); + new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce); + new_vma.prot = MemoryProt::NoAccess; + new_vma.name = ""; + new_vma.type = VMAType::Reserved; + + *out_addr = std::bit_cast(mapped_addr); + return ORBIS_OK; +} + int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name, bool is_exec, PAddr phys_addr, u64 alignment) { @@ -146,20 +165,37 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, MemoryMapFlags flags, uintptr_t fd, size_t offset) { - ASSERT(virtual_addr == 0); - virtual_addr = impl.VirtualBase(); + if (virtual_addr == 0) { + virtual_addr = impl.VirtualBase(); + } else { + LOG_INFO(Kernel_Vmm, "Virtual addr {:#x} with size {:#x}", virtual_addr, size); + } + + VAddr mapped_addr = 0; const size_t size_aligned = Common::AlignUp(size, 16_KB); // Find first free area to map the file. - auto it = FindVMA(virtual_addr); - while (it->second.type != VMAType::Free || it->second.size < size_aligned) { - it++; + if (False(flags & MemoryMapFlags::Fixed)) { + auto it = FindVMA(virtual_addr); + while (it->second.type != VMAType::Free || it->second.size < size_aligned) { + it++; + } + ASSERT(it != vma_map.end()); + + mapped_addr = it->second.base; + } + + if (True(flags & MemoryMapFlags::Fixed)) { + const auto& vma = FindVMA(virtual_addr)->second; + const size_t remaining_size = vma.base + vma.size - virtual_addr; + ASSERT_MSG((vma.type == VMAType::Free || vma.type == VMAType::Reserved) && + remaining_size >= size); + + mapped_addr = virtual_addr; } - ASSERT(it != vma_map.end()); // Map the file. - const VAddr mapped_addr = it->second.base; - impl.MapFile(mapped_addr, size, offset, fd); + impl.MapFile(mapped_addr, size, offset, std::bit_cast(prot), fd); // Add virtual memory area auto& new_vma = AddMapping(mapped_addr, size_aligned); @@ -303,7 +339,8 @@ VirtualMemoryArea& MemoryManager::AddMapping(VAddr virtual_addr, size_t size) { ASSERT_MSG(vma_handle != vma_map.end(), "Virtual address not in vm_map"); const VirtualMemoryArea& vma = vma_handle->second; - ASSERT_MSG(vma.type == VMAType::Free && vma.base <= virtual_addr, + ASSERT_MSG((vma.type == VMAType::Free || vma.type == VMAType::Reserved) && + vma.base <= virtual_addr, "Adding a mapping to already mapped region"); const VAddr start_in_vma = virtual_addr - vma.base; diff --git a/src/core/memory.h b/src/core/memory.h index 220fa43f..e584afab 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -137,6 +137,9 @@ public: void Free(PAddr phys_addr, size_t size); + int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags, + u64 alignment = 0); + int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name = "", bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0); diff --git a/src/emulator.cpp b/src/emulator.cpp index 16f20297..8d5378fd 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -13,6 +13,7 @@ #include "common/config.h" #include "common/debug.h" #include "common/logging/backend.h" +#include "common/ntapi.h" #include "common/path_util.h" #include "common/singleton.h" #include "common/version.h" @@ -38,6 +39,9 @@ Emulator::Emulator() const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(config_dir / "config.toml"); + // Initialize NT API functions + Common::NtApi::Initialize(); + // Start logger. Common::Log::Initialize(); Common::Log::Start();