diff --git a/.gitmodules b/.gitmodules index 38ee6fb0b..0ab83261a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,6 +61,9 @@ [submodule "third_party/premake-androidmk"] path = third_party/premake-androidmk url = https://github.com/Triang3l/premake-androidmk.git +[submodule "third_party/date"] + path = third_party/date + url = https://github.com/HowardHinnant/date.git [submodule "third_party/glslang"] path = third_party/glslang url = https://github.com/KhronosGroup/glslang.git diff --git a/src/xenia/base/mapped_memory_win.cc b/src/xenia/base/mapped_memory_win.cc index 25ec5cb92..3c9da7554 100644 --- a/src/xenia/base/mapped_memory_win.cc +++ b/src/xenia/base/mapped_memory_win.cc @@ -22,6 +22,11 @@ namespace xe { class Win32MappedMemory : public MappedMemory { public: + // CreateFile returns INVALID_HANDLE_VALUE in case of failure. + static constexpr HANDLE kFileHandleInvalid = INVALID_HANDLE_VALUE; + // CreateFileMapping returns nullptr in case of failure. + static constexpr HANDLE kMappingHandleInvalid = nullptr; + Win32MappedMemory(const std::filesystem::path& path, Mode mode) : MappedMemory(path, mode) {} @@ -29,10 +34,10 @@ class Win32MappedMemory : public MappedMemory { if (data_) { UnmapViewOfFile(data_); } - if (mapping_handle != INVALID_HANDLE_VALUE) { + if (mapping_handle != kMappingHandleInvalid) { CloseHandle(mapping_handle); } - if (file_handle != INVALID_HANDLE_VALUE) { + if (file_handle != kFileHandleInvalid) { CloseHandle(file_handle); } } @@ -42,11 +47,11 @@ class Win32MappedMemory : public MappedMemory { UnmapViewOfFile(data_); data_ = nullptr; } - if (mapping_handle != INVALID_HANDLE_VALUE) { + if (mapping_handle != kMappingHandleInvalid) { CloseHandle(mapping_handle); - mapping_handle = INVALID_HANDLE_VALUE; + mapping_handle = kMappingHandleInvalid; } - if (file_handle != INVALID_HANDLE_VALUE) { + if (file_handle != kFileHandleInvalid) { if (truncate_size) { LONG distance_high = truncate_size >> 32; SetFilePointer(file_handle, truncate_size & 0xFFFFFFFF, &distance_high, @@ -55,7 +60,7 @@ class Win32MappedMemory : public MappedMemory { } CloseHandle(file_handle); - file_handle = INVALID_HANDLE_VALUE; + file_handle = kFileHandleInvalid; } } @@ -65,8 +70,13 @@ class Win32MappedMemory : public MappedMemory { size_t aligned_length = length + (offset - aligned_offset); UnmapViewOfFile(data_); +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) data_ = MapViewOfFile(mapping_handle, view_access_, aligned_offset >> 32, aligned_offset & 0xFFFFFFFF, aligned_length); +#else + data_ = MapViewOfFileFromApp(mapping_handle, ULONG(view_access_), + ULONG64(aligned_offset), aligned_length); +#endif if (!data_) { return false; } @@ -83,8 +93,8 @@ class Win32MappedMemory : public MappedMemory { return true; } - HANDLE file_handle = INVALID_HANDLE_VALUE; - HANDLE mapping_handle = INVALID_HANDLE_VALUE; + HANDLE file_handle = kFileHandleInvalid; + HANDLE mapping_handle = kMappingHandleInvalid; DWORD view_access_ = 0; }; @@ -125,20 +135,32 @@ std::unique_ptr MappedMemory::Open( mm->file_handle = CreateFile(path.c_str(), file_access, file_share, nullptr, create_mode, FILE_ATTRIBUTE_NORMAL, nullptr); - if (mm->file_handle == INVALID_HANDLE_VALUE) { + if (mm->file_handle == Win32MappedMemory::kFileHandleInvalid) { return nullptr; } - mm->mapping_handle = CreateFileMapping(mm->file_handle, nullptr, - mapping_protect, aligned_length >> 32, - aligned_length & 0xFFFFFFFF, nullptr); - if (mm->mapping_handle == INVALID_HANDLE_VALUE) { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + mm->mapping_handle = CreateFileMapping( + mm->file_handle, nullptr, mapping_protect, DWORD(aligned_length >> 32), + DWORD(aligned_length), nullptr); +#else + mm->mapping_handle = + CreateFileMappingFromApp(mm->file_handle, nullptr, ULONG(mapping_protect), + ULONG64(aligned_length), nullptr); +#endif + if (mm->mapping_handle == Win32MappedMemory::kMappingHandleInvalid) { return nullptr; } +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) mm->data_ = reinterpret_cast(MapViewOfFile( - mm->mapping_handle, view_access, static_cast(aligned_offset >> 32), - static_cast(aligned_offset & 0xFFFFFFFF), aligned_length)); + mm->mapping_handle, view_access, DWORD(aligned_offset >> 32), + DWORD(aligned_offset), aligned_length)); +#else + mm->data_ = reinterpret_cast( + MapViewOfFileFromApp(mm->mapping_handle, ULONG(view_access), + ULONG64(aligned_offset), aligned_length)); +#endif if (!mm->data_) { return nullptr; } @@ -203,8 +225,8 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { class Chunk { public: explicit Chunk(size_t capacity) - : file_handle_(0), - mapping_handle_(0), + : file_handle_(Win32MappedMemory::kFileHandleInvalid), + mapping_handle_(Win32MappedMemory::kMappingHandleInvalid), data_(nullptr), offset_(0), capacity_(capacity), @@ -214,10 +236,10 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { if (data_) { UnmapViewOfFile(data_); } - if (mapping_handle_) { + if (mapping_handle_ != Win32MappedMemory::kMappingHandleInvalid) { CloseHandle(mapping_handle_); } - if (file_handle_) { + if (file_handle_ != Win32MappedMemory::kFileHandleInvalid) { CloseHandle(file_handle_); } } @@ -231,14 +253,20 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { file_handle_ = CreateFile(path.c_str(), file_access, file_share, nullptr, create_mode, FILE_ATTRIBUTE_NORMAL, nullptr); - if (!file_handle_) { + if (file_handle_ == Win32MappedMemory::kFileHandleInvalid) { return false; } +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) mapping_handle_ = - CreateFileMapping(file_handle_, nullptr, mapping_protect, 0, - static_cast(capacity_), nullptr); - if (!mapping_handle_) { + CreateFileMapping(file_handle_, nullptr, mapping_protect, + DWORD(capacity_ >> 32), DWORD(capacity_), nullptr); +#else + mapping_handle_ = CreateFileMappingFromApp(file_handle_, nullptr, + ULONG(mapping_protect), + ULONG64(capacity_), nullptr); +#endif + if (mapping_handle_ == Win32MappedMemory::kMappingHandleInvalid) { return false; } @@ -247,10 +275,32 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { if (low_address_space) { bool successful = false; data_ = reinterpret_cast(0x10000000); +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + HANDLE process = GetCurrentProcess(); +#endif for (int i = 0; i < 1000; ++i) { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_, data_)) { successful = true; + } +#else + // VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in + // 10.0.17134.0. + // https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis + if (VirtualAlloc2FromApp(process, data_, capacity_, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, nullptr, 0)) { + if (MapViewOfFile3FromApp(mapping_handle_, process, data_, 0, + capacity_, MEM_REPLACE_PLACEHOLDER, + ULONG(mapping_protect), nullptr, 0)) { + successful = true; + } else { + VirtualFree(data_, capacity_, MEM_RELEASE); + } + } +#endif + if (successful) { break; } data_ += capacity_; @@ -261,8 +311,13 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { } } } else { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) data_ = reinterpret_cast( MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_)); +#else + data_ = reinterpret_cast(MapViewOfFileFromApp( + mapping_handle_, ULONG(view_access), 0, capacity_)); +#endif } if (!data_) { return false; diff --git a/src/xenia/base/memory.cc b/src/xenia/base/memory.cc index f7e70d2ff..155946779 100644 --- a/src/xenia/base/memory.cc +++ b/src/xenia/base/memory.cc @@ -8,11 +8,26 @@ */ #include "xenia/base/memory.h" +#include "xenia/base/cvar.h" #include "xenia/base/platform.h" #include +DEFINE_bool( + writable_executable_memory, true, + "Allow mapping memory with both write and execute access, for simulating " + "behavior on platforms where that's not supported", + "Memory"); + namespace xe { +namespace memory { + +bool IsWritableExecutableMemoryPreferred() { + return IsWritableExecutableMemorySupported() && + cvars::writable_executable_memory; +} + +} // namespace memory // TODO(benvanik): fancy AVX versions. // https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_16u_byteswap.h diff --git a/src/xenia/base/memory.h b/src/xenia/base/memory.h index 45471e50f..96eb9b68a 100644 --- a/src/xenia/base/memory.h +++ b/src/xenia/base/memory.h @@ -35,6 +35,7 @@ enum class PageAccess { kNoAccess = 0, kReadOnly = 1 << 0, kReadWrite = kReadOnly | 1 << 1, + kExecuteReadOnly = kReadOnly | 1 << 2, kExecuteReadWrite = kReadWrite | 1 << 2, }; @@ -49,6 +50,16 @@ enum class DeallocationType { kDecommit = 1 << 1, }; +// Whether the host allows the pages to be allocated or mapped with +// PageAccess::kExecuteReadWrite - if not, separate mappings backed by the same +// memory-mapped file must be used to write to executable pages. +bool IsWritableExecutableMemorySupported(); + +// Whether PageAccess::kExecuteReadWrite is a supported and preferred way of +// writing executable memory, useful for simulating how Xenia would work without +// writable executable memory on a system with it. +bool IsWritableExecutableMemoryPreferred(); + // Allocates a block of memory at the given page-aligned base address. // Fails if the memory is not available. // Specify nullptr for base_address to leave it up to the system. diff --git a/src/xenia/base/memory_posix.cc b/src/xenia/base/memory_posix.cc index bd078d90f..271249a87 100644 --- a/src/xenia/base/memory_posix.cc +++ b/src/xenia/base/memory_posix.cc @@ -39,6 +39,8 @@ uint32_t ToPosixProtectFlags(PageAccess access) { return PROT_READ; case PageAccess::kReadWrite: return PROT_READ | PROT_WRITE; + case PageAccess::kExecuteReadOnly: + return PROT_READ | PROT_EXEC; case PageAccess::kExecuteReadWrite: return PROT_READ | PROT_WRITE | PROT_EXEC; default: @@ -47,6 +49,8 @@ uint32_t ToPosixProtectFlags(PageAccess access) { } } +bool IsWritableExecutableMemorySupported() { return true; } + void* AllocFixed(void* base_address, size_t length, AllocationType allocation_type, PageAccess access) { // mmap does not support reserve / commit, so ignore allocation_type. @@ -112,6 +116,7 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, oflag = 0; break; case PageAccess::kReadOnly: + case PageAccess::kExecuteReadOnly: oflag = O_RDONLY; break; case PageAccess::kReadWrite: diff --git a/src/xenia/base/memory_win.cc b/src/xenia/base/memory_win.cc index 343285d94..231a65633 100644 --- a/src/xenia/base/memory_win.cc +++ b/src/xenia/base/memory_win.cc @@ -42,6 +42,8 @@ DWORD ToWin32ProtectFlags(PageAccess access) { return PAGE_READONLY; case PageAccess::kReadWrite: return PAGE_READWRITE; + case PageAccess::kExecuteReadOnly: + return PAGE_EXECUTE_READ; case PageAccess::kExecuteReadWrite: return PAGE_EXECUTE_READWRITE; default: @@ -63,6 +65,8 @@ PageAccess ToXeniaProtectFlags(DWORD access) { return PageAccess::kReadOnly; case PAGE_READWRITE: return PageAccess::kReadWrite; + case PAGE_EXECUTE_READ: + return PageAccess::kExecuteReadOnly; case PAGE_EXECUTE_READWRITE: return PageAccess::kExecuteReadWrite; default: @@ -70,6 +74,17 @@ PageAccess ToXeniaProtectFlags(DWORD access) { } } +bool IsWritableExecutableMemorySupported() { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + return true; +#else + // To test FromApp functions on desktop, replace + // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) with 0 in the #ifs and + // link to WindowsApp.lib. + return false; +#endif +} + void* AllocFixed(void* base_address, size_t length, AllocationType allocation_type, PageAccess access) { DWORD alloc_type = 0; @@ -88,7 +103,12 @@ void* AllocFixed(void* base_address, size_t length, break; } DWORD protect = ToWin32ProtectFlags(access); +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) return VirtualAlloc(base_address, length, alloc_type, protect); +#else + return VirtualAllocFromApp(base_address, length, ULONG(alloc_type), + ULONG(protect)); +#endif } bool DeallocFixed(void* base_address, size_t length, @@ -115,13 +135,19 @@ bool Protect(void* base_address, size_t length, PageAccess access, *out_old_access = PageAccess::kNoAccess; } DWORD new_protect = ToWin32ProtectFlags(access); +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) DWORD old_protect = 0; BOOL result = VirtualProtect(base_address, length, new_protect, &old_protect); +#else + ULONG old_protect = 0; + BOOL result = VirtualProtectFromApp(base_address, length, ULONG(new_protect), + &old_protect); +#endif if (!result) { return false; } if (out_old_access) { - *out_old_access = ToXeniaProtectFlags(old_protect); + *out_old_access = ToXeniaProtectFlags(DWORD(old_protect)); } return true; } @@ -148,9 +174,14 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, DWORD protect = ToWin32ProtectFlags(access) | (commit ? SEC_COMMIT : SEC_RESERVE); auto full_path = "Local" / path; - return CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, protect, +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + return CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, protect, static_cast(length >> 32), static_cast(length), full_path.c_str()); +#else + return CreateFileMappingFromApp(INVALID_HANDLE_VALUE, nullptr, ULONG(protect), + ULONG64(length), full_path.c_str()); +#endif } void CloseFileMappingHandle(FileMappingHandle handle, @@ -160,6 +191,7 @@ void CloseFileMappingHandle(FileMappingHandle handle, void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, PageAccess access, size_t file_offset) { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) DWORD target_address_low = static_cast(file_offset); DWORD target_address_high = static_cast(file_offset >> 32); DWORD file_access = 0; @@ -170,6 +202,9 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, case PageAccess::kReadWrite: file_access = FILE_MAP_ALL_ACCESS; break; + case PageAccess::kExecuteReadOnly: + file_access = FILE_MAP_READ | FILE_MAP_EXECUTE; + break; case PageAccess::kExecuteReadWrite: file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE; break; @@ -180,6 +215,25 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, } return MapViewOfFileEx(handle, file_access, target_address_high, target_address_low, length, base_address); +#else + // VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in 10.0.17134.0. + // https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis + HANDLE process = GetCurrentProcess(); + void* placeholder = VirtualAlloc2FromApp( + process, base_address, length, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, nullptr, 0); + if (!placeholder) { + return nullptr; + } + void* mapping = MapViewOfFile3FromApp( + handle, process, placeholder, ULONG64(file_offset), length, + MEM_REPLACE_PLACEHOLDER, ULONG(ToWin32ProtectFlags(access)), nullptr, 0); + if (!mapping) { + VirtualFree(placeholder, length, MEM_RELEASE); + return nullptr; + } + return mapping; +#endif } bool UnmapFileView(FileMappingHandle handle, void* base_address, diff --git a/src/xenia/base/platform_android.cc b/src/xenia/base/platform_android.cc index f2c807856..3db9a7f9f 100644 --- a/src/xenia/base/platform_android.cc +++ b/src/xenia/base/platform_android.cc @@ -48,6 +48,7 @@ void Initialize(const ANativeActivity* activity) { dlsym(lib, #name)); \ assert_not_null(api_functions_.api_##api.name); XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(libandroid, ASharedMemory_create, 26); + // pthreads are a part of Bionic libc on Android. XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(libc, pthread_getname_np, 26); #undef XE_PLATFORM_ANDROID_LOAD_API_FUNCTION } diff --git a/src/xenia/cpu/backend/code_cache.h b/src/xenia/cpu/backend/code_cache.h index 03d70114f..318608822 100644 --- a/src/xenia/cpu/backend/code_cache.h +++ b/src/xenia/cpu/backend/code_cache.h @@ -10,6 +10,8 @@ #ifndef XENIA_CPU_BACKEND_CODE_CACHE_H_ #define XENIA_CPU_BACKEND_CODE_CACHE_H_ +#include +#include #include #include "xenia/cpu/function.h" @@ -24,8 +26,8 @@ class CodeCache { virtual ~CodeCache() = default; virtual const std::filesystem::path& file_name() const = 0; - virtual uint32_t base_address() const = 0; - virtual uint32_t total_size() const = 0; + virtual uintptr_t execute_base_address() const = 0; + virtual size_t total_size() const = 0; // Finds a function based on the given host PC (that may be within a // function). diff --git a/src/xenia/cpu/backend/x64/x64_code_cache.cc b/src/xenia/cpu/backend/x64/x64_code_cache.cc index d3b22d14c..beb715661 100644 --- a/src/xenia/cpu/backend/x64/x64_code_cache.cc +++ b/src/xenia/cpu/backend/x64/x64_code_cache.cc @@ -41,8 +41,15 @@ X64CodeCache::~X64CodeCache() { // Unmap all views and close mapping. if (mapping_ != xe::memory::kFileMappingHandleInvalid) { - xe::memory::UnmapFileView(mapping_, generated_code_base_, - kGeneratedCodeSize); + if (generated_code_write_base_ && + generated_code_write_base_ != generated_code_execute_base_) { + xe::memory::UnmapFileView(mapping_, generated_code_write_base_, + kGeneratedCodeSize); + } + if (generated_code_execute_base_) { + xe::memory::UnmapFileView(mapping_, generated_code_execute_base_, + kGeneratedCodeSize); + } xe::memory::CloseFileMappingHandle(mapping_, file_name_); mapping_ = xe::memory::kFileMappingHandleInvalid; } @@ -73,17 +80,41 @@ bool X64CodeCache::Initialize() { } // Map generated code region into the file. Pages are committed as required. - generated_code_base_ = reinterpret_cast(xe::memory::MapFileView( - mapping_, reinterpret_cast(kGeneratedCodeBase), kGeneratedCodeSize, - xe::memory::PageAccess::kExecuteReadWrite, 0)); - if (!generated_code_base_) { - XELOGE("Unable to allocate code cache generated code storage"); - XELOGE( - "This is likely because the {:X}-{:X} range is in use by some other " - "system DLL", - static_cast(kGeneratedCodeBase), - kGeneratedCodeBase + kGeneratedCodeSize); - return false; + if (xe::memory::IsWritableExecutableMemoryPreferred()) { + generated_code_execute_base_ = + reinterpret_cast(xe::memory::MapFileView( + mapping_, reinterpret_cast(kGeneratedCodeExecuteBase), + kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, 0)); + generated_code_write_base_ = generated_code_execute_base_; + if (!generated_code_execute_base_ || !generated_code_write_base_) { + XELOGE("Unable to allocate code cache generated code storage"); + XELOGE( + "This is likely because the {:X}-{:X} range is in use by some other " + "system DLL", + uint64_t(kGeneratedCodeExecuteBase), + uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize)); + return false; + } + } else { + generated_code_execute_base_ = + reinterpret_cast(xe::memory::MapFileView( + mapping_, reinterpret_cast(kGeneratedCodeExecuteBase), + kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0)); + generated_code_write_base_ = + reinterpret_cast(xe::memory::MapFileView( + mapping_, reinterpret_cast(kGeneratedCodeWriteBase), + kGeneratedCodeSize, xe::memory::PageAccess::kReadWrite, 0)); + if (!generated_code_execute_base_ || !generated_code_write_base_) { + XELOGE("Unable to allocate code cache generated code storage"); + XELOGE( + "This is likely because the {:X}-{:X} and {:X}-{:X} ranges are in " + "use by some other system DLL", + uint64_t(kGeneratedCodeExecuteBase), + uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize), + uint64_t(kGeneratedCodeWriteBase), + uint64_t(kGeneratedCodeWriteBase + kGeneratedCodeSize)); + return false; + } } // Preallocate the function map to a large, reasonable size. @@ -117,7 +148,7 @@ void X64CodeCache::CommitExecutableRange(uint32_t guest_low, xe::memory::AllocFixed( indirection_table_base_ + (guest_low - kIndirectionTableBase), guest_high - guest_low, xe::memory::AllocationType::kCommit, - xe::memory::PageAccess::kExecuteReadWrite); + xe::memory::PageAccess::kReadWrite); // Fill memory with the default value. uint32_t* p = reinterpret_cast(indirection_table_base_); @@ -126,21 +157,26 @@ void X64CodeCache::CommitExecutableRange(uint32_t guest_low, } } -void* X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code, - const EmitFunctionInfo& func_info) { +void X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code, + const EmitFunctionInfo& func_info, + void*& code_execute_address_out, + void*& code_write_address_out) { // Same for now. We may use different pools or whatnot later on, like when // we only want to place guest code in a serialized cache on disk. - return PlaceGuestCode(guest_address, machine_code, func_info, nullptr); + PlaceGuestCode(guest_address, machine_code, func_info, nullptr, + code_execute_address_out, code_write_address_out); } -void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, - const EmitFunctionInfo& func_info, - GuestFunction* function_info) { +void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, + const EmitFunctionInfo& func_info, + GuestFunction* function_info, + void*& code_execute_address_out, + void*& code_write_address_out) { // Hold a lock while we bump the pointers up. This is important as the // unwind table requires entries AND code to be sorted in order. size_t low_mark; size_t high_mark; - uint8_t* code_address; + uint8_t* code_execute_address; UnwindReservation unwind_reservation; { auto global_lock = global_critical_region_.Acquire(); @@ -149,26 +185,33 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, // Reserve code. // Always move the code to land on 16b alignment. - code_address = generated_code_base_ + generated_code_offset_; + code_execute_address = + generated_code_execute_base_ + generated_code_offset_; + code_execute_address_out = code_execute_address; + uint8_t* code_write_address = + generated_code_write_base_ + generated_code_offset_; + code_write_address_out = code_write_address; generated_code_offset_ += xe::round_up(func_info.code_size.total, 16); - auto tail_address = generated_code_base_ + generated_code_offset_; + auto tail_write_address = + generated_code_write_base_ + generated_code_offset_; // Reserve unwind info. // We go on the high size of the unwind info as we don't know how big we // need it, and a few extra bytes of padding isn't the worst thing. - unwind_reservation = - RequestUnwindReservation(generated_code_base_ + generated_code_offset_); + unwind_reservation = RequestUnwindReservation(generated_code_write_base_ + + generated_code_offset_); generated_code_offset_ += xe::round_up(unwind_reservation.data_size, 16); - auto end_address = generated_code_base_ + generated_code_offset_; + auto end_write_address = + generated_code_write_base_ + generated_code_offset_; high_mark = generated_code_offset_; // Store in map. It is maintained in sorted order of host PC dependent on // us also being append-only. generated_code_map_.emplace_back( - (uint64_t(code_address - generated_code_base_) << 32) | + (uint64_t(code_execute_address - generated_code_execute_base_) << 32) | generated_code_offset_, function_info); @@ -185,21 +228,30 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, if (high_mark <= old_commit_mark) break; new_commit_mark = old_commit_mark + 16 * 1024 * 1024; - xe::memory::AllocFixed(generated_code_base_, new_commit_mark, - xe::memory::AllocationType::kCommit, - xe::memory::PageAccess::kExecuteReadWrite); + if (generated_code_execute_base_ == generated_code_write_base_) { + xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kExecuteReadWrite); + } else { + xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kExecuteReadOnly); + xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kReadWrite); + } } while (generated_code_commit_mark_.compare_exchange_weak( old_commit_mark, new_commit_mark)); // Copy code. - std::memcpy(code_address, machine_code, func_info.code_size.total); + std::memcpy(code_write_address, machine_code, func_info.code_size.total); // Fill unused slots with 0xCC - std::memset(tail_address, 0xCC, - static_cast(end_address - tail_address)); + std::memset(tail_write_address, 0xCC, + static_cast(end_write_address - tail_write_address)); // Notify subclasses of placed code. - PlaceCode(guest_address, machine_code, func_info, code_address, + PlaceCode(guest_address, machine_code, func_info, code_execute_address, unwind_reservation); } @@ -214,7 +266,7 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, iJIT_Method_Load_V2 method = {0}; method.method_id = iJIT_GetNewMethodID(); - method.method_load_address = code_address; + method.method_load_address = code_execute_address; method.method_size = uint32_t(code_size); method.method_name = const_cast(method_name.data()); method.module_name = function_info @@ -230,10 +282,9 @@ void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code, if (guest_address && indirection_table_base_) { uint32_t* indirection_slot = reinterpret_cast( indirection_table_base_ + (guest_address - kIndirectionTableBase)); - *indirection_slot = uint32_t(reinterpret_cast(code_address)); + *indirection_slot = + uint32_t(reinterpret_cast(code_execute_address)); } - - return code_address; } uint32_t X64CodeCache::PlaceData(const void* data, size_t length) { @@ -245,7 +296,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) { // Reserve code. // Always move the code to land on 16b alignment. - data_address = generated_code_base_ + generated_code_offset_; + data_address = generated_code_write_base_ + generated_code_offset_; generated_code_offset_ += xe::round_up(length, 16); high_mark = generated_code_offset_; @@ -260,9 +311,18 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) { if (high_mark <= old_commit_mark) break; new_commit_mark = old_commit_mark + 16 * 1024 * 1024; - xe::memory::AllocFixed(generated_code_base_, new_commit_mark, - xe::memory::AllocationType::kCommit, - xe::memory::PageAccess::kExecuteReadWrite); + if (generated_code_execute_base_ == generated_code_write_base_) { + xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kExecuteReadWrite); + } else { + xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kExecuteReadOnly); + xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kReadWrite); + } } while (generated_code_commit_mark_.compare_exchange_weak(old_commit_mark, new_commit_mark)); @@ -273,7 +333,7 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) { } GuestFunction* X64CodeCache::LookupFunction(uint64_t host_pc) { - uint32_t key = uint32_t(host_pc - kGeneratedCodeBase); + uint32_t key = uint32_t(host_pc - kGeneratedCodeExecuteBase); void* fn_entry = std::bsearch( &key, generated_code_map_.data(), generated_code_map_.size() + 1, sizeof(std::pair), diff --git a/src/xenia/cpu/backend/x64/x64_code_cache.h b/src/xenia/cpu/backend/x64/x64_code_cache.h index 9f7424ec8..021e5e684 100644 --- a/src/xenia/cpu/backend/x64/x64_code_cache.h +++ b/src/xenia/cpu/backend/x64/x64_code_cache.h @@ -11,6 +11,8 @@ #define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_ #include +#include +#include #include #include #include @@ -46,8 +48,10 @@ class X64CodeCache : public CodeCache { virtual bool Initialize(); const std::filesystem::path& file_name() const override { return file_name_; } - uint32_t base_address() const override { return kGeneratedCodeBase; } - uint32_t total_size() const override { return kGeneratedCodeSize; } + uintptr_t execute_base_address() const override { + return kGeneratedCodeExecuteBase; + } + size_t total_size() const override { return kGeneratedCodeSize; } // TODO(benvanik): ELF serialization/etc // TODO(benvanik): keep track of code blocks @@ -59,11 +63,15 @@ class X64CodeCache : public CodeCache { void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high); - void* PlaceHostCode(uint32_t guest_address, void* machine_code, - const EmitFunctionInfo& func_info); - void* PlaceGuestCode(uint32_t guest_address, void* machine_code, - const EmitFunctionInfo& func_info, - GuestFunction* function_info); + void PlaceHostCode(uint32_t guest_address, void* machine_code, + const EmitFunctionInfo& func_info, + void*& code_execute_address_out, + void*& code_write_address_out); + void PlaceGuestCode(uint32_t guest_address, void* machine_code, + const EmitFunctionInfo& func_info, + GuestFunction* function_info, + void*& code_execute_address_out, + void*& code_write_address_out); uint32_t PlaceData(const void* data, size_t length); GuestFunction* LookupFunction(uint64_t host_pc) override; @@ -71,13 +79,16 @@ class X64CodeCache : public CodeCache { protected: // All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can // only map enough for lookups within that range. - static const uint64_t kIndirectionTableBase = 0x80000000; - static const uint64_t kIndirectionTableSize = 0x1FFFFFFF; + static const size_t kIndirectionTableSize = 0x1FFFFFFF; + static const uintptr_t kIndirectionTableBase = 0x80000000; // The code range is 512MB, but we know the total code games will have is // pretty small (dozens of mb at most) and our expansion is reasonablish // so 256MB should be more than enough. - static const uint64_t kGeneratedCodeBase = 0xA0000000; - static const uint64_t kGeneratedCodeSize = 0x0FFFFFFF; + static const size_t kGeneratedCodeSize = 0x0FFFFFFF; + static const uintptr_t kGeneratedCodeExecuteBase = 0xA0000000; + // Used for writing when PageAccess::kExecuteReadWrite is not supported. + static const uintptr_t kGeneratedCodeWriteBase = + kGeneratedCodeExecuteBase + kGeneratedCodeSize + 1; // This is picked to be high enough to cover whatever we can reasonably // expect. If we hit issues with this it probably means some corner case @@ -96,7 +107,8 @@ class X64CodeCache : public CodeCache { return UnwindReservation(); } virtual void PlaceCode(uint32_t guest_address, void* machine_code, - const EmitFunctionInfo& func_info, void* code_address, + const EmitFunctionInfo& func_info, + void* code_execute_address, UnwindReservation unwind_reservation) {} std::filesystem::path file_name_; @@ -114,9 +126,13 @@ class X64CodeCache : public CodeCache { // the generated code table that correspond to the PPC functions in guest // space. uint8_t* indirection_table_base_ = nullptr; - // Fixed at kGeneratedCodeBase and holding all generated code, growing as - // needed. - uint8_t* generated_code_base_ = nullptr; + // Fixed at kGeneratedCodeExecuteBase and holding all generated code, growing + // as needed. + uint8_t* generated_code_execute_base_ = nullptr; + // View of the memory that backs generated_code_execute_base_ when + // PageAccess::kExecuteReadWrite is not supported, for writing the generated + // code. Equals to generated_code_execute_base_ when it's supported. + uint8_t* generated_code_write_base_ = nullptr; // Current offset to empty space in generated code. size_t generated_code_offset_ = 0; // Current high water mark of COMMITTED code. diff --git a/src/xenia/cpu/backend/x64/x64_code_cache_posix.cc b/src/xenia/cpu/backend/x64/x64_code_cache_posix.cc index 490ab2ce9..e889eba0c 100644 --- a/src/xenia/cpu/backend/x64/x64_code_cache_posix.cc +++ b/src/xenia/cpu/backend/x64/x64_code_cache_posix.cc @@ -27,7 +27,7 @@ class PosixX64CodeCache : public X64CodeCache { /* UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override; void PlaceCode(uint32_t guest_address, void* machine_code, size_t code_size, - size_t stack_size, void* code_address, + size_t stack_size, void* code_execute_address, UnwindReservation unwind_reservation) override; void InitializeUnwindEntry(uint8_t* unwind_entry_address, diff --git a/src/xenia/cpu/backend/x64/x64_code_cache_win.cc b/src/xenia/cpu/backend/x64/x64_code_cache_win.cc index aed5d3f60..0aff67034 100644 --- a/src/xenia/cpu/backend/x64/x64_code_cache_win.cc +++ b/src/xenia/cpu/backend/x64/x64_code_cache_win.cc @@ -107,11 +107,12 @@ class Win32X64CodeCache : public X64CodeCache { private: UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override; void PlaceCode(uint32_t guest_address, void* machine_code, - const EmitFunctionInfo& func_info, void* code_address, + const EmitFunctionInfo& func_info, void* code_execute_address, UnwindReservation unwind_reservation) override; void InitializeUnwindEntry(uint8_t* unwind_entry_address, - size_t unwind_table_slot, void* code_address, + size_t unwind_table_slot, + void* code_execute_address, const EmitFunctionInfo& func_info); // Growable function table system handle. @@ -140,9 +141,9 @@ Win32X64CodeCache::~Win32X64CodeCache() { delete_growable_table_(unwind_table_handle_); } } else { - if (generated_code_base_) { + if (generated_code_execute_base_) { RtlDeleteFunctionTable(reinterpret_cast( - reinterpret_cast(generated_code_base_) | 0x3)); + reinterpret_cast(generated_code_execute_base_) | 0x3)); } } } @@ -176,11 +177,12 @@ bool Win32X64CodeCache::Initialize() { // Create table and register with the system. It's empty now, but we'll grow // it as functions are added. if (supports_growable_table_) { - if (add_growable_table_(&unwind_table_handle_, unwind_table_.data(), - unwind_table_count_, DWORD(unwind_table_.size()), - reinterpret_cast(generated_code_base_), - reinterpret_cast(generated_code_base_ + - kGeneratedCodeSize))) { + if (add_growable_table_( + &unwind_table_handle_, unwind_table_.data(), unwind_table_count_, + DWORD(unwind_table_.size()), + reinterpret_cast(generated_code_execute_base_), + reinterpret_cast(generated_code_execute_base_ + + kGeneratedCodeSize))) { XELOGE("Unable to create unwind function table"); return false; } @@ -188,8 +190,9 @@ bool Win32X64CodeCache::Initialize() { // Install a callback that the debugger will use to lookup unwind info on // demand. if (!RtlInstallFunctionTableCallback( - reinterpret_cast(generated_code_base_) | 0x3, - reinterpret_cast(generated_code_base_), kGeneratedCodeSize, + reinterpret_cast(generated_code_execute_base_) | 0x3, + reinterpret_cast(generated_code_execute_base_), + kGeneratedCodeSize, [](DWORD64 control_pc, PVOID context) { auto code_cache = reinterpret_cast(context); return reinterpret_cast( @@ -216,11 +219,12 @@ Win32X64CodeCache::RequestUnwindReservation(uint8_t* entry_address) { void Win32X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code, const EmitFunctionInfo& func_info, - void* code_address, + void* code_execute_address, UnwindReservation unwind_reservation) { // Add unwind info. InitializeUnwindEntry(unwind_reservation.entry_address, - unwind_reservation.table_slot, code_address, func_info); + unwind_reservation.table_slot, code_execute_address, + func_info); if (supports_growable_table_) { // Notify that the unwind table has grown. @@ -229,13 +233,15 @@ void Win32X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code, } // This isn't needed on x64 (probably), but is convention. - FlushInstructionCache(GetCurrentProcess(), code_address, + // On UWP, FlushInstructionCache available starting from 10.0.16299.0. + // https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis + FlushInstructionCache(GetCurrentProcess(), code_execute_address, func_info.code_size.total); } void Win32X64CodeCache::InitializeUnwindEntry( - uint8_t* unwind_entry_address, size_t unwind_table_slot, void* code_address, - const EmitFunctionInfo& func_info) { + uint8_t* unwind_entry_address, size_t unwind_table_slot, + void* code_execute_address, const EmitFunctionInfo& func_info) { auto unwind_info = reinterpret_cast(unwind_entry_address); UNWIND_CODE* unwind_code = nullptr; @@ -299,10 +305,12 @@ void Win32X64CodeCache::InitializeUnwindEntry( // Add entry. auto& fn_entry = unwind_table_[unwind_table_slot]; fn_entry.BeginAddress = - (DWORD)(reinterpret_cast(code_address) - generated_code_base_); + DWORD(reinterpret_cast(code_execute_address) - + generated_code_execute_base_); fn_entry.EndAddress = - (DWORD)(fn_entry.BeginAddress + func_info.code_size.total); - fn_entry.UnwindData = (DWORD)(unwind_entry_address - generated_code_base_); + DWORD(fn_entry.BeginAddress + func_info.code_size.total); + fn_entry.UnwindData = + DWORD(unwind_entry_address - generated_code_execute_base_); } void* Win32X64CodeCache::LookupUnwindInfo(uint64_t host_pc) { @@ -310,8 +318,8 @@ void* Win32X64CodeCache::LookupUnwindInfo(uint64_t host_pc) { &host_pc, unwind_table_.data(), unwind_table_count_, sizeof(RUNTIME_FUNCTION), [](const void* key_ptr, const void* element_ptr) { - auto key = - *reinterpret_cast(key_ptr) - kGeneratedCodeBase; + auto key = *reinterpret_cast(key_ptr) - + kGeneratedCodeExecuteBase; auto element = reinterpret_cast(element_ptr); if (key < element->BeginAddress) { return -1; diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index e772276e7..37d1cdc77 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -125,20 +125,26 @@ void* X64Emitter::Emplace(const EmitFunctionInfo& func_info, // top_ points to the Xbyak buffer, and since we are in AutoGrow mode // it has pending relocations. We copy the top_ to our buffer, swap the // pointer, relocate, then return the original scratch pointer for use. + // top_ is used by Xbyak's ready() as both write base pointer and the absolute + // address base, which would not work on platforms not supporting writable + // executable memory, but Xenia doesn't use absolute label addresses in the + // generated code. uint8_t* old_address = top_; - void* new_address; + void* new_execute_address; + void* new_write_address; assert_true(func_info.code_size.total == size_); if (function) { - new_address = code_cache_->PlaceGuestCode(function->address(), top_, - func_info, function); + code_cache_->PlaceGuestCode(function->address(), top_, func_info, function, + new_execute_address, new_write_address); } else { - new_address = code_cache_->PlaceHostCode(0, top_, func_info); + code_cache_->PlaceHostCode(0, top_, func_info, new_execute_address, + new_write_address); } - top_ = reinterpret_cast(new_address); + top_ = reinterpret_cast(new_write_address); ready(); top_ = old_address; reset(); - return new_address; + return new_execute_address; } bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) { diff --git a/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc b/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc index c5f835133..074552d36 100644 --- a/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc +++ b/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc @@ -177,6 +177,9 @@ class TestRunner { public: TestRunner() { memory_size_ = 64 * 1024 * 1024; + // FIXME(Triang3l): If this is ever compiled for a platform without + // xe::memory::IsWritableExecutableMemorySupported, two memory mappings must + // be used. memory_ = memory::AllocFixed(nullptr, memory_size_, memory::AllocationType::kReserveCommit, memory::PageAccess::kExecuteReadWrite); diff --git a/src/xenia/cpu/stack_walker_win.cc b/src/xenia/cpu/stack_walker_win.cc index 4339e3a8b..cbfa96023 100644 --- a/src/xenia/cpu/stack_walker_win.cc +++ b/src/xenia/cpu/stack_walker_win.cc @@ -9,6 +9,7 @@ #include "xenia/cpu/stack_walker.h" +#include #include #include "xenia/base/logging.h" @@ -120,8 +121,8 @@ class Win32StackWalker : public StackWalker { // They never change, so it's fine even if they are touched from multiple // threads. code_cache_ = code_cache; - code_cache_min_ = code_cache_->base_address(); - code_cache_max_ = code_cache_->base_address() + code_cache_->total_size(); + code_cache_min_ = code_cache_->execute_base_address(); + code_cache_max_ = code_cache_min_ + code_cache_->total_size(); } bool Initialize() { @@ -297,13 +298,13 @@ class Win32StackWalker : public StackWalker { std::mutex dbghelp_mutex_; static xe::cpu::backend::CodeCache* code_cache_; - static uint32_t code_cache_min_; - static uint32_t code_cache_max_; + static uintptr_t code_cache_min_; + static uintptr_t code_cache_max_; }; xe::cpu::backend::CodeCache* Win32StackWalker::code_cache_ = nullptr; -uint32_t Win32StackWalker::code_cache_min_ = 0; -uint32_t Win32StackWalker::code_cache_max_ = 0; +uintptr_t Win32StackWalker::code_cache_min_ = 0; +uintptr_t Win32StackWalker::code_cache_max_ = 0; std::unique_ptr StackWalker::Create( backend::CodeCache* code_cache) { diff --git a/src/xenia/debug/ui/debug_window.cc b/src/xenia/debug/ui/debug_window.cc index 410e42104..3020c170c 100644 --- a/src/xenia/debug/ui/debug_window.cc +++ b/src/xenia/debug/ui/debug_window.cc @@ -1454,7 +1454,7 @@ void DebugWindow::UpdateCache() { // Fetch module listing. // We hold refs so that none are unloaded. cache_.modules = - object_table->GetObjectsByType(XObject::Type::kTypeModule); + object_table->GetObjectsByType(XObject::Type::Module); cache_.thread_debug_infos = processor_->QueryThreadDebugInfos(); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 2d3d1033e..aca6bd52a 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -358,7 +358,7 @@ void Emulator::Pause() { auto lock = global_critical_region::AcquireDirect(); auto threads = kernel_state()->object_table()->GetObjectsByType( - kernel::XObject::kTypeThread); + kernel::XObject::Type::Thread); auto current_thread = kernel::XThread::IsInThread() ? kernel::XThread::GetCurrentThread() : nullptr; @@ -388,7 +388,7 @@ void Emulator::Resume() { auto threads = kernel_state()->object_table()->GetObjectsByType( - kernel::XObject::kTypeThread); + kernel::XObject::Type::Thread); for (auto thread : threads) { if (!thread->can_debugger_suspend()) { // Don't pause host threads. @@ -513,7 +513,7 @@ bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) { bool Emulator::ExceptionCallback(Exception* ex) { // Check to see if the exception occurred in guest code. auto code_cache = processor()->backend()->code_cache(); - auto code_base = code_cache->base_address(); + auto code_base = code_cache->execute_base_address(); auto code_end = code_base + code_cache->total_size(); if (!processor()->is_debugger_attached() && debugging::IsDebuggerAttached()) { diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 8884d8efa..3d7df1824 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -763,13 +763,13 @@ bool KernelState::Save(ByteStream* stream) { for (auto object : objects) { auto prev_offset = stream->offset(); - if (object->is_host_object() || object->type() == XObject::kTypeThread) { + if (object->is_host_object() || object->type() == XObject::Type::Thread) { // Don't save host objects or save XThreads again num_objects--; continue; } - stream->Write(object->type()); + stream->Write(static_cast(object->type())); if (!object->Save(stream)) { XELOGD("Did not save object of type {}", object->type()); assert_always(); @@ -804,7 +804,7 @@ bool KernelState::Restore(ByteStream* stream) { uint32_t num_threads = stream->Read(); XELOGD("Loading {} threads...", num_threads); for (uint32_t i = 0; i < num_threads; i++) { - auto thread = XObject::Restore(this, XObject::kTypeThread, stream); + auto thread = XObject::Restore(this, XObject::Type::Thread, stream); if (!thread) { // Can't continue the restore or we risk misalignment. assert_always(); diff --git a/src/xenia/kernel/util/object_table.h b/src/xenia/kernel/util/object_table.h index 93c98111c..93306d942 100644 --- a/src/xenia/kernel/util/object_table.h +++ b/src/xenia/kernel/util/object_table.h @@ -51,7 +51,7 @@ class ObjectTable { object_ref LookupObject(X_HANDLE handle) { auto object = LookupObject(handle, false); if (object) { - assert_true(object->type() == T::kType); + assert_true(object->type() == T::kObjectType); } auto result = object_ref(reinterpret_cast(object)); return result; @@ -72,7 +72,7 @@ class ObjectTable { std::vector> GetObjectsByType() { std::vector> results; GetObjectsByType( - T::kType, + T::kObjectType, reinterpret_cast>*>(&results)); return results; } diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc index 345c82628..a3e92c55f 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc @@ -47,8 +47,15 @@ void HandleSetThreadName(pointer_t record) { return; } - auto name = - kernel_memory()->TranslateVirtual(thread_info->name_ptr); + // Shadowrun (and its demo) has a bug where it ends up passing freed memory + // for the name, so at the point of SetThreadName it's filled with junk. + + // TODO(gibbed): cvar for thread name encoding for conversion, some games use + // SJIS and there's no way to automatically know this. + auto name = std::string( + kernel_memory()->TranslateVirtual(thread_info->name_ptr)); + std::replace_if( + name.begin(), name.end(), [](auto c) { return c < 32 || c > 127; }, '?'); object_ref thread; if (thread_info->thread_id == -1) { diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc index 814120140..6b0b6783b 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc @@ -131,7 +131,7 @@ dword_result_t NtCreateFile(lpdword_t handle_out, dword_t desired_access, auto root_file = kernel_state()->object_table()->LookupObject( object_attrs->root_directory); assert_not_null(root_file); - assert_true(root_file->type() == XObject::Type::kTypeFile); + assert_true(root_file->type() == XObject::Type::File); root_entry = root_file->entry(); } @@ -399,7 +399,7 @@ dword_result_t NtQueryFullAttributesFile( root_file = kernel_state()->object_table()->LookupObject( obj_attribs->root_directory); assert_not_null(root_file); - assert_true(root_file->type() == XObject::Type::kTypeFile); + assert_true(root_file->type() == XObject::Type::File); assert_always(); } diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_memory.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_memory.cc index 0c6fa0a4d..8ecdabd5d 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_memory.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_memory.cc @@ -101,6 +101,9 @@ dword_result_t NtAllocateVirtualMemory(lpdword_t base_addr_ptr, if (*base_addr_ptr != 0) { // ignore specified page size when base address is specified. auto heap = kernel_memory()->LookupHeap(*base_addr_ptr); + if (heap->heap_type() != HeapType::kGuestVirtual) { + return X_STATUS_INVALID_PARAMETER; + } page_size = heap->page_size(); } else { // Adjust size. @@ -192,7 +195,9 @@ dword_result_t NtProtectVirtualMemory(lpdword_t base_addr_ptr, } auto heap = kernel_memory()->LookupHeap(*base_addr_ptr); - + if (heap->heap_type() != HeapType::kGuestVirtual) { + return X_STATUS_INVALID_PARAMETER; + } // Adjust the base downwards to the nearest page boundary. uint32_t adjusted_base = *base_addr_ptr - (*base_addr_ptr % heap->page_size()); @@ -240,6 +245,9 @@ dword_result_t NtFreeVirtualMemory(lpdword_t base_addr_ptr, } auto heap = kernel_state()->memory()->LookupHeap(base_addr_value); + if (heap->heap_type() != HeapType::kGuestVirtual) { + return X_STATUS_INVALID_PARAMETER; + } bool result = false; if (free_type == X_MEM_DECOMMIT) { // If zero, we may need to query size (free whole region). @@ -401,6 +409,11 @@ DECLARE_XBOXKRNL_EXPORT2(MmQueryAddressProtect, kMemory, kImplemented, void MmSetAddressProtect(lpvoid_t base_address, dword_t region_size, dword_t protect_bits) { + if (!protect_bits) { + XELOGE("MmSetAddressProtect: Failed due to incorrect protect_bits"); + return; + } + uint32_t protect = FromXdkProtectFlags(protect_bits); auto heap = kernel_memory()->LookupHeap(base_address); heap->Protect(base_address.guest_address(), region_size, protect); diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_ob.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_ob.cc index dc4c67dc2..68564a055 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_ob.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_ob.cc @@ -78,22 +78,21 @@ DECLARE_XBOXKRNL_EXPORT1(ObLookupThreadByThreadId, kNone, kImplemented); dword_result_t ObReferenceObjectByHandle(dword_t handle, dword_t object_type_ptr, lpdword_t out_object_ptr) { - const static std::unordered_map obj_type_match = { - {XObject::kTypeEvent, 0xD00EBEEF}, - {XObject::kTypeSemaphore, 0xD017BEEF}, - {XObject::kTypeThread, 0xD01BBEEF}}; - + // These values come from how Xenia handles uninitialized kernel data exports. + // D###BEEF where ### is the ordinal. + const static std::unordered_map object_types = { + {XObject::Type::Event, 0xD00EBEEF}, + {XObject::Type::Semaphore, 0xD017BEEF}, + {XObject::Type::Thread, 0xD01BBEEF}}; auto object = kernel_state()->object_table()->LookupObject(handle); - if (!object) { return X_STATUS_INVALID_HANDLE; } uint32_t native_ptr = object->guest_object(); - auto obj_type = obj_type_match.find(object->type()); - - if (obj_type != obj_type_match.end()) { - if (object_type_ptr && object_type_ptr != obj_type->second) { + auto object_type = object_types.find(object->type()); + if (object_type != object_types.end()) { + if (object_type_ptr && object_type_ptr != object_type->second) { return X_STATUS_OBJECT_TYPE_MISMATCH; } } else { diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc index e06daba48..db3cef5db 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc @@ -21,14 +21,10 @@ #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h" +#include "xenia/kernel/xclock.h" #include "xenia/kernel/xevent.h" #include "xenia/kernel/xthread.h" -#if XE_PLATFORM_WIN32 -#include "xenia/base/platform_win.h" -#define timegm _mkgmtime -#endif - namespace xe { namespace kernel { namespace xboxkrnl { @@ -507,44 +503,51 @@ struct X_TIME_FIELDS { xe::be milliseconds; xe::be weekday; }; -static_assert(sizeof(X_TIME_FIELDS) == 16, "Must be LARGEINTEGER"); +static_assert_size(X_TIME_FIELDS, 16); -// https://support.microsoft.com/en-us/kb/167296 +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtltimetotimefields void RtlTimeToTimeFields(lpqword_t time_ptr, pointer_t time_fields_ptr) { - int64_t time_ms = time_ptr.value() / 10000 - 11644473600000LL; - time_t timet = time_ms / 1000; - struct tm* tm = gmtime(&timet); - - time_fields_ptr->year = tm->tm_year + 1900; - time_fields_ptr->month = tm->tm_mon + 1; - time_fields_ptr->day = tm->tm_mday; - time_fields_ptr->hour = tm->tm_hour; - time_fields_ptr->minute = tm->tm_min; - time_fields_ptr->second = tm->tm_sec; - time_fields_ptr->milliseconds = time_ms % 1000; - time_fields_ptr->weekday = tm->tm_wday; + auto tp = XClock::to_sys(XClock::from_file_time(time_ptr.value())); + auto dp = date::floor(tp); + auto year_month_day = date::year_month_day{dp}; + auto weekday = date::weekday{dp}; + auto time = date::hh_mm_ss{date::floor(tp - dp)}; + time_fields_ptr->year = static_cast(year_month_day.year()); + time_fields_ptr->month = static_cast(year_month_day.month()); + time_fields_ptr->day = static_cast(year_month_day.day()); + time_fields_ptr->weekday = weekday.c_encoding(); + time_fields_ptr->hour = time.hours().count(); + time_fields_ptr->minute = time.minutes().count(); + time_fields_ptr->second = static_cast(time.seconds().count()); + time_fields_ptr->milliseconds = + static_cast(time.subseconds().count()); } DECLARE_XBOXKRNL_EXPORT1(RtlTimeToTimeFields, kNone, kImplemented); +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtltimefieldstotime dword_result_t RtlTimeFieldsToTime(pointer_t time_fields_ptr, lpqword_t time_ptr) { - struct tm tm; - tm.tm_year = time_fields_ptr->year - 1900; - tm.tm_mon = time_fields_ptr->month - 1; - tm.tm_mday = time_fields_ptr->day; - tm.tm_hour = time_fields_ptr->hour; - tm.tm_min = time_fields_ptr->minute; - tm.tm_sec = time_fields_ptr->second; - tm.tm_isdst = 0; - time_t timet = timegm(&tm); - if (timet == -1) { - // set last error = ERROR_INVALID_PARAMETER + if (time_fields_ptr->year < 1601 || time_fields_ptr->month < 1 || + time_fields_ptr->month > 11 || time_fields_ptr->day < 1 || + time_fields_ptr->hour > 23 || time_fields_ptr->minute > 59 || + time_fields_ptr->second > 59 || time_fields_ptr->milliseconds > 999) { return 0; } - uint64_t time = - ((timet + 11644473600LL) * 1000 + time_fields_ptr->milliseconds) * 10000; - *time_ptr = time; + auto year = date::year{time_fields_ptr->year}; + auto month = date::month{time_fields_ptr->month}; + auto day = date::day{time_fields_ptr->day}; + auto year_month_day = date::year_month_day{year, month, day}; + if (!year_month_day.ok()) { + return 0; + } + auto dp = static_cast(year_month_day); + std::chrono::system_clock::time_point time = dp; + time += std::chrono::hours{time_fields_ptr->hour}; + time += std::chrono::minutes{time_fields_ptr->minute}; + time += std::chrono::seconds{time_fields_ptr->second}; + time += std::chrono::milliseconds{time_fields_ptr->milliseconds}; + *time_ptr = XClock::to_file_time(XClock::from_sys(time)); return 1; } DECLARE_XBOXKRNL_EXPORT1(RtlTimeFieldsToTime, kNone, kImplemented); diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index 1f0cd2cc2..bf399937e 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -432,7 +432,7 @@ dword_result_t NtCreateEvent(lpdword_t handle_ptr, auto existing_object = LookupNamedObject(kernel_state(), obj_attributes_ptr); if (existing_object) { - if (existing_object->type() == XObject::kTypeEvent) { + if (existing_object->type() == XObject::Type::Event) { if (handle_ptr) { existing_object->RetainHandle(); *handle_ptr = existing_object->handle(); @@ -559,7 +559,7 @@ dword_result_t NtCreateSemaphore(lpdword_t handle_ptr, auto existing_object = LookupNamedObject(kernel_state(), obj_attributes_ptr); if (existing_object) { - if (existing_object->type() == XObject::kTypeSemaphore) { + if (existing_object->type() == XObject::Type::Semaphore) { if (handle_ptr) { existing_object->RetainHandle(); *handle_ptr = existing_object->handle(); @@ -613,7 +613,7 @@ dword_result_t NtCreateMutant(lpdword_t handle_out, auto existing_object = LookupNamedObject( kernel_state(), obj_attributes.guest_address()); if (existing_object) { - if (existing_object->type() == XObject::kTypeMutant) { + if (existing_object->type() == XObject::Type::Mutant) { if (handle_out) { existing_object->RetainHandle(); *handle_out = existing_object->handle(); @@ -674,7 +674,7 @@ dword_result_t NtCreateTimer(lpdword_t handle_ptr, lpvoid_t obj_attributes_ptr, auto existing_object = LookupNamedObject(kernel_state(), obj_attributes_ptr); if (existing_object) { - if (existing_object->type() == XObject::kTypeTimer) { + if (existing_object->type() == XObject::Type::Timer) { if (handle_ptr) { existing_object->RetainHandle(); *handle_ptr = existing_object->handle(); diff --git a/src/xenia/kernel/xclock.h b/src/xenia/kernel/xclock.h new file mode 100644 index 000000000..809dfd775 --- /dev/null +++ b/src/xenia/kernel/xclock.h @@ -0,0 +1,78 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2020 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_XCLOCK_H_ +#define XENIA_KERNEL_XCLOCK_H_ + +#include + +#include "xenia/base/clock.h" + +#include "third_party/date/include/date/date.h" + +namespace xe { +namespace kernel { + +struct XClock { + using rep = int64_t; + using period = std::ratio_multiply, std::nano>; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; + static constexpr bool is_steady = false; + + static time_point now() noexcept { + return from_file_time(Clock::QueryGuestSystemTime()); + } + + static uint64_t to_file_time(time_point const& tp) noexcept { + return static_cast(tp.time_since_epoch().count()); + } + + static time_point from_file_time(uint64_t const& tp) noexcept { + return time_point{duration{tp}}; + } + + static std::chrono::system_clock::time_point to_sys(time_point const& tp) { + // TODO(gibbed): verify behavior under Linux + using sys_duration = std::chrono::system_clock::duration; + using sys_time = std::chrono::system_clock::time_point; + auto dp = tp; + dp += system_clock_delta(); + auto cdp = std::chrono::time_point_cast(dp); + return sys_time{cdp.time_since_epoch()}; + } + + static time_point from_sys(std::chrono::system_clock::time_point const& tp) { + // TODO(gibbed): verify behavior under Linux + auto ctp = std::chrono::time_point_cast(tp); + auto dp = time_point{ctp.time_since_epoch()}; + dp -= system_clock_delta(); + return dp; + } + + private: + // The delta between std::chrono::system_clock (Jan 1 1970) and Xenon file + // time (Jan 1 1601), in seconds. In the spec std::chrono::system_clock's + // epoch is undefined, but C++20 cements it as Jan 1 1970. + static constexpr std::chrono::seconds system_clock_delta() { + auto filetime_epoch = date::year{1601} / date::month{1} / date::day{1}; + auto system_clock_epoch = date::year{1970} / date::month{1} / date::day{1}; + std::chrono::system_clock::time_point fp{ + static_cast(filetime_epoch)}; + std::chrono::system_clock::time_point sp{ + static_cast(system_clock_epoch)}; + return std::chrono::floor(fp.time_since_epoch() - + sp.time_since_epoch()); + } +}; + +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_XCLOCK_H_ diff --git a/src/xenia/kernel/xenumerator.cc b/src/xenia/kernel/xenumerator.cc index 03f324dc1..5de0a3d12 100644 --- a/src/xenia/kernel/xenumerator.cc +++ b/src/xenia/kernel/xenumerator.cc @@ -14,7 +14,7 @@ namespace kernel { XEnumerator::XEnumerator(KernelState* kernel_state, size_t items_per_enumerate, size_t item_size) - : XObject(kernel_state, kType), + : XObject(kernel_state, kObjectType), items_per_enumerate_(items_per_enumerate), item_size_(item_size) {} diff --git a/src/xenia/kernel/xenumerator.h b/src/xenia/kernel/xenumerator.h index e52048dd8..189453813 100644 --- a/src/xenia/kernel/xenumerator.h +++ b/src/xenia/kernel/xenumerator.h @@ -21,7 +21,7 @@ namespace kernel { class XEnumerator : public XObject { public: - static const Type kType = kTypeEnumerator; + static const XObject::Type kObjectType = XObject::Type::Enumerator; XEnumerator(KernelState* kernel_state, size_t items_per_enumerate, size_t item_size); diff --git a/src/xenia/kernel/xevent.cc b/src/xenia/kernel/xevent.cc index 04efadfee..f46fdb5fb 100644 --- a/src/xenia/kernel/xevent.cc +++ b/src/xenia/kernel/xevent.cc @@ -15,7 +15,8 @@ namespace xe { namespace kernel { -XEvent::XEvent(KernelState* kernel_state) : XObject(kernel_state, kType) {} +XEvent::XEvent(KernelState* kernel_state) + : XObject(kernel_state, kObjectType) {} XEvent::~XEvent() = default; diff --git a/src/xenia/kernel/xevent.h b/src/xenia/kernel/xevent.h index 95ea8613f..b5f4accf5 100644 --- a/src/xenia/kernel/xevent.h +++ b/src/xenia/kernel/xevent.h @@ -25,7 +25,7 @@ static_assert_size(X_KEVENT, 0x10); class XEvent : public XObject { public: - static const Type kType = kTypeEvent; + static const XObject::Type kObjectType = XObject::Type::Event; explicit XEvent(KernelState* kernel_state); ~XEvent() override; diff --git a/src/xenia/kernel/xfile.cc b/src/xenia/kernel/xfile.cc index 15529fc6d..ee749b9e7 100644 --- a/src/xenia/kernel/xfile.cc +++ b/src/xenia/kernel/xfile.cc @@ -22,11 +22,13 @@ namespace xe { namespace kernel { XFile::XFile(KernelState* kernel_state, vfs::File* file, bool synchronous) - : XObject(kernel_state, kType), file_(file), is_synchronous_(synchronous) { + : XObject(kernel_state, kObjectType), + file_(file), + is_synchronous_(synchronous) { async_event_ = threading::Event::CreateAutoResetEvent(false); } -XFile::XFile() : XObject(kType) { +XFile::XFile() : XObject(kObjectType) { async_event_ = threading::Event::CreateAutoResetEvent(false); } @@ -122,14 +124,14 @@ X_STATUS XFile::Read(uint32_t buffer_guest_address, uint32_t buffer_length, const xe::BaseHeap* buffer_end_heap = memory()->LookupHeap(buffer_guest_high_address); if (!buffer_start_heap || !buffer_end_heap || - buffer_start_heap->IsGuestPhysicalHeap() != - buffer_end_heap->IsGuestPhysicalHeap() || - (buffer_start_heap->IsGuestPhysicalHeap() && + (buffer_start_heap->heap_type() == HeapType::kGuestPhysical) != + (buffer_end_heap->heap_type() == HeapType::kGuestPhysical) || + (buffer_start_heap->heap_type() == HeapType::kGuestPhysical && buffer_start_heap != buffer_end_heap)) { result = X_STATUS_ACCESS_VIOLATION; } else { xe::PhysicalHeap* buffer_physical_heap = - buffer_start_heap->IsGuestPhysicalHeap() + buffer_start_heap->heap_type() == HeapType::kGuestPhysical ? static_cast(buffer_start_heap) : nullptr; if (buffer_physical_heap && diff --git a/src/xenia/kernel/xfile.h b/src/xenia/kernel/xfile.h index d7e668ce7..b432b69d5 100644 --- a/src/xenia/kernel/xfile.h +++ b/src/xenia/kernel/xfile.h @@ -75,7 +75,7 @@ class X_FILE_DIRECTORY_INFORMATION { class XFile : public XObject { public: - static const Type kType = kTypeFile; + static const XObject::Type kObjectType = XObject::Type::File; XFile(KernelState* kernel_state, vfs::File* file, bool synchronous); ~XFile() override; diff --git a/src/xenia/kernel/xiocompletion.cc b/src/xenia/kernel/xiocompletion.cc index 829163b43..638a0a3ef 100644 --- a/src/xenia/kernel/xiocompletion.cc +++ b/src/xenia/kernel/xiocompletion.cc @@ -13,7 +13,7 @@ namespace xe { namespace kernel { XIOCompletion::XIOCompletion(KernelState* kernel_state) - : XObject(kernel_state, kType) { + : XObject(kernel_state, kObjectType) { notification_semaphore_ = threading::Semaphore::Create(0, kMaxNotifications); } diff --git a/src/xenia/kernel/xiocompletion.h b/src/xenia/kernel/xiocompletion.h index f974ee4ee..a7d34889d 100644 --- a/src/xenia/kernel/xiocompletion.h +++ b/src/xenia/kernel/xiocompletion.h @@ -21,7 +21,7 @@ namespace kernel { class XIOCompletion : public XObject { public: - static const Type kType = kTypeIOCompletion; + static const XObject::Type kObjectType = XObject::Type::IOCompletion; explicit XIOCompletion(KernelState* kernel_state); ~XIOCompletion() override; diff --git a/src/xenia/kernel/xmodule.cc b/src/xenia/kernel/xmodule.cc index 26191c193..06510cfee 100644 --- a/src/xenia/kernel/xmodule.cc +++ b/src/xenia/kernel/xmodule.cc @@ -19,7 +19,7 @@ namespace xe { namespace kernel { XModule::XModule(KernelState* kernel_state, ModuleType module_type) - : XObject(kernel_state, kType), + : XObject(kernel_state, kObjectType), module_type_(module_type), processor_module_(nullptr), hmodule_ptr_(0) { diff --git a/src/xenia/kernel/xmodule.h b/src/xenia/kernel/xmodule.h index 2fbd6b9d5..8b558681a 100644 --- a/src/xenia/kernel/xmodule.h +++ b/src/xenia/kernel/xmodule.h @@ -57,7 +57,7 @@ class XModule : public XObject { kUserModule = 1, }; - static const Type kType = kTypeModule; + static const XObject::Type kObjectType = XObject::Type::Module; XModule(KernelState* kernel_state, ModuleType module_type); virtual ~XModule(); diff --git a/src/xenia/kernel/xmutant.cc b/src/xenia/kernel/xmutant.cc index a64c8d76f..4328fc342 100644 --- a/src/xenia/kernel/xmutant.cc +++ b/src/xenia/kernel/xmutant.cc @@ -17,9 +17,10 @@ namespace xe { namespace kernel { -XMutant::XMutant() : XObject(kType) {} +XMutant::XMutant(KernelState* kernel_state) + : XObject(kernel_state, kObjectType) {} -XMutant::XMutant(KernelState* kernel_state) : XObject(kernel_state, kType) {} +XMutant::XMutant() : XObject(kObjectType) {} XMutant::~XMutant() = default; diff --git a/src/xenia/kernel/xmutant.h b/src/xenia/kernel/xmutant.h index e3b9cad22..5dddf905b 100644 --- a/src/xenia/kernel/xmutant.h +++ b/src/xenia/kernel/xmutant.h @@ -20,7 +20,7 @@ class XThread; class XMutant : public XObject { public: - static const Type kType = kTypeMutant; + static const XObject::Type kObjectType = XObject::Type::Mutant; explicit XMutant(KernelState* kernel_state); ~XMutant() override; diff --git a/src/xenia/kernel/xnotifylistener.cc b/src/xenia/kernel/xnotifylistener.cc index 0770bd2d8..be920f157 100644 --- a/src/xenia/kernel/xnotifylistener.cc +++ b/src/xenia/kernel/xnotifylistener.cc @@ -16,7 +16,7 @@ namespace xe { namespace kernel { XNotifyListener::XNotifyListener(KernelState* kernel_state) - : XObject(kernel_state, kType) {} + : XObject(kernel_state, kObjectType) {} XNotifyListener::~XNotifyListener() {} diff --git a/src/xenia/kernel/xnotifylistener.h b/src/xenia/kernel/xnotifylistener.h index 800fa047d..38c19b63b 100644 --- a/src/xenia/kernel/xnotifylistener.h +++ b/src/xenia/kernel/xnotifylistener.h @@ -23,7 +23,7 @@ namespace kernel { class XNotifyListener : public XObject { public: - static const Type kType = kTypeNotifyListener; + static const XObject::Type kObjectType = XObject::Type::NotifyListener; explicit XNotifyListener(KernelState* kernel_state); ~XNotifyListener() override; diff --git a/src/xenia/kernel/xobject.cc b/src/xenia/kernel/xobject.cc index 2b90b88f1..3675a1bce 100644 --- a/src/xenia/kernel/xobject.cc +++ b/src/xenia/kernel/xobject.cc @@ -129,33 +129,33 @@ bool XObject::RestoreObject(ByteStream* stream) { object_ref XObject::Restore(KernelState* kernel_state, Type type, ByteStream* stream) { switch (type) { - case kTypeEnumerator: + case Type::Enumerator: break; - case kTypeEvent: + case Type::Event: return XEvent::Restore(kernel_state, stream); - case kTypeFile: + case Type::File: return XFile::Restore(kernel_state, stream); - case kTypeIOCompletion: + case Type::IOCompletion: break; - case kTypeModule: + case Type::Module: return XModule::Restore(kernel_state, stream); - case kTypeMutant: + case Type::Mutant: return XMutant::Restore(kernel_state, stream); - case kTypeNotifyListener: + case Type::NotifyListener: return XNotifyListener::Restore(kernel_state, stream); - case kTypeSemaphore: + case Type::Semaphore: return XSemaphore::Restore(kernel_state, stream); - case kTypeSession: + case Type::Session: break; - case kTypeSocket: + case Type::Socket: break; - case kTypeSymbolicLink: + case Type::SymbolicLink: return XSymbolicLink::Restore(kernel_state, stream); - case kTypeThread: + case Type::Thread: return XThread::Restore(kernel_state, stream); - case kTypeTimer: + case Type::Timer: break; - case kTypeUndefined: + case Type::Undefined: break; } diff --git a/src/xenia/kernel/xobject.h b/src/xenia/kernel/xobject.h index af07a790b..163b6afe4 100644 --- a/src/xenia/kernel/xobject.h +++ b/src/xenia/kernel/xobject.h @@ -116,21 +116,21 @@ class XObject { // one with 0x8A... which causes crash static constexpr uint32_t kHandleBase = 0xF8000000; - enum Type { - kTypeUndefined, - kTypeEnumerator, - kTypeEvent, - kTypeFile, - kTypeIOCompletion, - kTypeModule, - kTypeMutant, - kTypeNotifyListener, - kTypeSemaphore, - kTypeSession, - kTypeSocket, - kTypeSymbolicLink, - kTypeThread, - kTypeTimer, + enum class Type : uint32_t { + Undefined, + Enumerator, + Event, + File, + IOCompletion, + Module, + Mutant, + NotifyListener, + Semaphore, + Session, + Socket, + SymbolicLink, + Thread, + Timer, }; XObject(Type type); diff --git a/src/xenia/kernel/xsemaphore.cc b/src/xenia/kernel/xsemaphore.cc index 74bf3e7e6..a582fe6d6 100644 --- a/src/xenia/kernel/xsemaphore.cc +++ b/src/xenia/kernel/xsemaphore.cc @@ -16,7 +16,7 @@ namespace xe { namespace kernel { XSemaphore::XSemaphore(KernelState* kernel_state) - : XObject(kernel_state, kTypeSemaphore) {} + : XObject(kernel_state, kObjectType) {} XSemaphore::~XSemaphore() = default; diff --git a/src/xenia/kernel/xsemaphore.h b/src/xenia/kernel/xsemaphore.h index 42ae7ee26..b9e78a0b1 100644 --- a/src/xenia/kernel/xsemaphore.h +++ b/src/xenia/kernel/xsemaphore.h @@ -25,7 +25,7 @@ static_assert_size(X_KSEMAPHORE, 0x14); class XSemaphore : public XObject { public: - static const Type kType = kTypeSemaphore; + static const XObject::Type kObjectType = XObject::Type::Semaphore; explicit XSemaphore(KernelState* kernel_state); ~XSemaphore() override; diff --git a/src/xenia/kernel/xsocket.cc b/src/xenia/kernel/xsocket.cc index ad35ff69a..ea15734e7 100644 --- a/src/xenia/kernel/xsocket.cc +++ b/src/xenia/kernel/xsocket.cc @@ -31,10 +31,11 @@ namespace xe { namespace kernel { -XSocket::XSocket(KernelState* kernel_state) : XObject(kernel_state, kType) {} +XSocket::XSocket(KernelState* kernel_state) + : XObject(kernel_state, kObjectType) {} XSocket::XSocket(KernelState* kernel_state, uint64_t native_handle) - : XObject(kernel_state, kType), native_handle_(native_handle) {} + : XObject(kernel_state, kObjectType), native_handle_(native_handle) {} XSocket::~XSocket() { Close(); } diff --git a/src/xenia/kernel/xsocket.h b/src/xenia/kernel/xsocket.h index 3c85a6c89..8c5103ec9 100644 --- a/src/xenia/kernel/xsocket.h +++ b/src/xenia/kernel/xsocket.h @@ -66,7 +66,7 @@ struct N_XSOCKADDR_IN { class XSocket : public XObject { public: - static const Type kType = kTypeSocket; + static const XObject::Type kObjectType = XObject::Type::Socket; enum AddressFamily { AF_INET = 2, diff --git a/src/xenia/kernel/xsymboliclink.cc b/src/xenia/kernel/xsymboliclink.cc index 557a4a3c1..0d0a2ad43 100644 --- a/src/xenia/kernel/xsymboliclink.cc +++ b/src/xenia/kernel/xsymboliclink.cc @@ -16,9 +16,9 @@ namespace xe { namespace kernel { XSymbolicLink::XSymbolicLink(KernelState* kernel_state) - : XObject(kernel_state, kType), path_(), target_() {} + : XObject(kernel_state, kObjectType), path_(), target_() {} -XSymbolicLink::XSymbolicLink() : XObject(kType), path_(), target_() {} +XSymbolicLink::XSymbolicLink() : XObject(kObjectType), path_(), target_() {} XSymbolicLink::~XSymbolicLink() {} diff --git a/src/xenia/kernel/xsymboliclink.h b/src/xenia/kernel/xsymboliclink.h index 2d37dfd72..12e339250 100644 --- a/src/xenia/kernel/xsymboliclink.h +++ b/src/xenia/kernel/xsymboliclink.h @@ -23,7 +23,7 @@ namespace kernel { class XSymbolicLink : public XObject { public: - static const Type kType = kTypeSymbolicLink; + static const XObject::Type kObjectType = XObject::Type::SymbolicLink; explicit XSymbolicLink(KernelState* kernel_state); ~XSymbolicLink() override; diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index 34a95dd7c..baa014b01 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -48,13 +48,13 @@ using xe::cpu::ppc::PPCOpcode; uint32_t next_xthread_id_ = 0; XThread::XThread(KernelState* kernel_state) - : XObject(kernel_state, kType), guest_thread_(true) {} + : XObject(kernel_state, kObjectType), guest_thread_(true) {} XThread::XThread(KernelState* kernel_state, uint32_t stack_size, uint32_t xapi_thread_startup, uint32_t start_address, uint32_t start_context, uint32_t creation_flags, bool guest_thread, bool main_thread) - : XObject(kernel_state, kType), + : XObject(kernel_state, kObjectType), thread_id_(++next_xthread_id_), guest_thread_(guest_thread), main_thread_(main_thread), diff --git a/src/xenia/kernel/xthread.h b/src/xenia/kernel/xthread.h index 7ab55c686..ec0fe41ef 100644 --- a/src/xenia/kernel/xthread.h +++ b/src/xenia/kernel/xthread.h @@ -106,7 +106,7 @@ static_assert_size(X_KTHREAD, 0xAB0); class XThread : public XObject, public cpu::Thread { public: - static const Type kType = kTypeThread; + static const XObject::Type kObjectType = XObject::Type::Thread; struct CreationParams { uint32_t stack_size; diff --git a/src/xenia/kernel/xtimer.cc b/src/xenia/kernel/xtimer.cc index df8bda2e2..fe66a73b7 100644 --- a/src/xenia/kernel/xtimer.cc +++ b/src/xenia/kernel/xtimer.cc @@ -17,7 +17,8 @@ namespace xe { namespace kernel { -XTimer::XTimer(KernelState* kernel_state) : XObject(kernel_state, kType) {} +XTimer::XTimer(KernelState* kernel_state) + : XObject(kernel_state, kObjectType) {} XTimer::~XTimer() = default; diff --git a/src/xenia/kernel/xtimer.h b/src/xenia/kernel/xtimer.h index 15ec47cca..0a3dee618 100644 --- a/src/xenia/kernel/xtimer.h +++ b/src/xenia/kernel/xtimer.h @@ -21,7 +21,7 @@ class XThread; class XTimer : public XObject { public: - static const Type kType = kTypeTimer; + static const XObject::Type kObjectType = XObject::Type::Timer; explicit XTimer(KernelState* kernel_state); ~XTimer() override; diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index f70e00f66..5b631e416 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -158,24 +158,26 @@ bool Memory::Initialize() { physical_membase_ = mapping_base_ + 0x100000000ull; // Prepare virtual heaps. - heaps_.v00000000.Initialize(this, virtual_membase_, 0x00000000, 0x40000000, - 4096); - heaps_.v40000000.Initialize(this, virtual_membase_, 0x40000000, - 0x40000000 - 0x01000000, 64 * 1024); - heaps_.v80000000.Initialize(this, virtual_membase_, 0x80000000, 0x10000000, - 64 * 1024); - heaps_.v90000000.Initialize(this, virtual_membase_, 0x90000000, 0x10000000, - 4096); + heaps_.v00000000.Initialize(this, virtual_membase_, HeapType::kGuestVirtual, + 0x00000000, 0x40000000, 4096); + heaps_.v40000000.Initialize(this, virtual_membase_, HeapType::kGuestVirtual, + 0x40000000, 0x40000000 - 0x01000000, 64 * 1024); + heaps_.v80000000.Initialize(this, virtual_membase_, HeapType::kGuestXex, + 0x80000000, 0x10000000, 64 * 1024); + heaps_.v90000000.Initialize(this, virtual_membase_, HeapType::kGuestXex, + 0x90000000, 0x10000000, 4096); // Prepare physical heaps. - heaps_.physical.Initialize(this, physical_membase_, 0x00000000, 0x20000000, - 4096); - heaps_.vA0000000.Initialize(this, virtual_membase_, 0xA0000000, 0x20000000, - 64 * 1024, &heaps_.physical); - heaps_.vC0000000.Initialize(this, virtual_membase_, 0xC0000000, 0x20000000, - 16 * 1024 * 1024, &heaps_.physical); - heaps_.vE0000000.Initialize(this, virtual_membase_, 0xE0000000, 0x1FD00000, - 4096, &heaps_.physical); + heaps_.physical.Initialize(this, physical_membase_, HeapType::kGuestPhysical, + 0x00000000, 0x20000000, 4096); + heaps_.vA0000000.Initialize(this, virtual_membase_, HeapType::kGuestPhysical, + 0xA0000000, 0x20000000, 64 * 1024, + &heaps_.physical); + heaps_.vC0000000.Initialize(this, virtual_membase_, HeapType::kGuestPhysical, + 0xC0000000, 0x20000000, 16 * 1024 * 1024, + &heaps_.physical); + heaps_.vE0000000.Initialize(this, virtual_membase_, HeapType::kGuestPhysical, + 0xE0000000, 0x1FD00000, 4096, &heaps_.physical); // Protect the first and last 64kb of memory. heaps_.v00000000.AllocFixed( @@ -373,7 +375,7 @@ uint32_t Memory::HostToGuestVirtualThunk(const void* context, uint32_t Memory::GetPhysicalAddress(uint32_t address) const { const BaseHeap* heap = LookupHeap(address); - if (!heap || !heap->IsGuestPhysicalHeap()) { + if (!heap || heap->heap_type() != HeapType::kGuestPhysical) { return UINT32_MAX; } return static_cast(heap)->GetPhysicalAddress(address); @@ -449,7 +451,7 @@ bool Memory::AccessViolationCallback( } uint32_t virtual_address = HostToGuestVirtual(host_address); BaseHeap* heap = LookupHeap(virtual_address); - if (!heap->IsGuestPhysicalHeap()) { + if (heap->heap_type() != HeapType::kGuestPhysical) { return false; } @@ -475,7 +477,7 @@ bool Memory::TriggerPhysicalMemoryCallbacks( uint32_t virtual_address, uint32_t length, bool is_write, bool unwatch_exact_range, bool unprotect) { BaseHeap* heap = LookupHeap(virtual_address); - if (heap->IsGuestPhysicalHeap()) { + if (heap->heap_type() == HeapType::kGuestPhysical) { auto physical_heap = static_cast(heap); return physical_heap->TriggerCallbacks(std::move(global_lock_locked_once), virtual_address, length, is_write, @@ -619,6 +621,10 @@ uint32_t FromPageAccess(xe::memory::PageAccess protect) { return kMemoryProtectRead; case memory::PageAccess::kReadWrite: return kMemoryProtectRead | kMemoryProtectWrite; + case memory::PageAccess::kExecuteReadOnly: + // Guest memory cannot be executable - this should never happen :) + assert_always(); + return kMemoryProtectRead; case memory::PageAccess::kExecuteReadWrite: // Guest memory cannot be executable - this should never happen :) assert_always(); @@ -633,11 +639,12 @@ BaseHeap::BaseHeap() BaseHeap::~BaseHeap() = default; -void BaseHeap::Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base, - uint32_t heap_size, uint32_t page_size, - uint32_t host_address_offset) { +void BaseHeap::Initialize(Memory* memory, uint8_t* membase, HeapType heap_type, + uint32_t heap_base, uint32_t heap_size, + uint32_t page_size, uint32_t host_address_offset) { memory_ = memory; membase_ = membase; + heap_type_ = heap_type; heap_base_ = heap_base; heap_size_ = heap_size; page_size_ = page_size; @@ -1346,9 +1353,10 @@ VirtualHeap::VirtualHeap() = default; VirtualHeap::~VirtualHeap() = default; void VirtualHeap::Initialize(Memory* memory, uint8_t* membase, - uint32_t heap_base, uint32_t heap_size, - uint32_t page_size) { - BaseHeap::Initialize(memory, membase, heap_base, heap_size, page_size); + HeapType heap_type, uint32_t heap_base, + uint32_t heap_size, uint32_t page_size) { + BaseHeap::Initialize(memory, membase, heap_type, heap_base, heap_size, + page_size); } PhysicalHeap::PhysicalHeap() : parent_heap_(nullptr) {} @@ -1356,8 +1364,9 @@ PhysicalHeap::PhysicalHeap() : parent_heap_(nullptr) {} PhysicalHeap::~PhysicalHeap() = default; void PhysicalHeap::Initialize(Memory* memory, uint8_t* membase, - uint32_t heap_base, uint32_t heap_size, - uint32_t page_size, VirtualHeap* parent_heap) { + HeapType heap_type, uint32_t heap_base, + uint32_t heap_size, uint32_t page_size, + VirtualHeap* parent_heap) { uint32_t host_address_offset; if (heap_base >= 0xE0000000 && xe::memory::allocation_granularity() > 0x1000) { @@ -1366,8 +1375,8 @@ void PhysicalHeap::Initialize(Memory* memory, uint8_t* membase, host_address_offset = 0; } - BaseHeap::Initialize(memory, membase, heap_base, heap_size, page_size, - host_address_offset); + BaseHeap::Initialize(memory, membase, heap_type, heap_base, heap_size, + page_size, host_address_offset); parent_heap_ = parent_heap; system_page_size_ = uint32_t(xe::memory::page_size()); diff --git a/src/xenia/memory.h b/src/xenia/memory.h index df76c39bc..5a0f6084d 100644 --- a/src/xenia/memory.h +++ b/src/xenia/memory.h @@ -36,6 +36,13 @@ enum SystemHeapFlag : uint32_t { kSystemHeapDefault = kSystemHeapVirtual, }; +enum class HeapType : uint8_t { + kGuestVirtual, + kGuestXex, + kGuestPhysical, + kHostPhysical, +}; + enum MemoryAllocationFlag : uint32_t { kMemoryAllocationReserve = 1 << 0, kMemoryAllocationCommit = 1 << 1, @@ -106,6 +113,9 @@ class BaseHeap { // Size of each page within the heap range in bytes. uint32_t page_size() const { return page_size_; } + // Type of specified heap + HeapType heap_type() const { return heap_type_; } + // Offset added to the virtual addresses to convert them to host addresses // (not including membase). uint32_t host_address_offset() const { return host_address_offset_; } @@ -177,9 +187,6 @@ class BaseHeap { xe::memory::PageAccess QueryRangeAccess(uint32_t low_address, uint32_t high_address); - // Whether the heap is a guest virtual memory mapping of the physical memory. - virtual bool IsGuestPhysicalHeap() const { return false; } - bool Save(ByteStream* stream); bool Restore(ByteStream* stream); @@ -188,12 +195,13 @@ class BaseHeap { protected: BaseHeap(); - void Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base, - uint32_t heap_size, uint32_t page_size, + void Initialize(Memory* memory, uint8_t* membase, HeapType heap_type, + uint32_t heap_base, uint32_t heap_size, uint32_t page_size, uint32_t host_address_offset = 0); Memory* memory_; uint8_t* membase_; + HeapType heap_type_; uint32_t heap_base_; uint32_t heap_size_; uint32_t page_size_; @@ -209,8 +217,8 @@ class VirtualHeap : public BaseHeap { ~VirtualHeap() override; // Initializes the heap properties and allocates the page table. - void Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base, - uint32_t heap_size, uint32_t page_size); + void Initialize(Memory* memory, uint8_t* membase, HeapType heap_type, + uint32_t heap_base, uint32_t heap_size, uint32_t page_size); }; // A heap for ranges of memory that are mapped to physical ranges. @@ -226,8 +234,8 @@ class PhysicalHeap : public BaseHeap { ~PhysicalHeap() override; // Initializes the heap properties and allocates the page table. - void Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base, - uint32_t heap_size, uint32_t page_size, + void Initialize(Memory* memory, uint8_t* membase, HeapType heap_type, + uint32_t heap_base, uint32_t heap_size, uint32_t page_size, VirtualHeap* parent_heap); bool Alloc(uint32_t size, uint32_t alignment, uint32_t allocation_type, @@ -253,7 +261,6 @@ class PhysicalHeap : public BaseHeap { uint32_t virtual_address, uint32_t length, bool is_write, bool unwatch_exact_range, bool unprotect = true); - bool IsGuestPhysicalHeap() const override { return true; } uint32_t GetPhysicalAddress(uint32_t address) const; protected: diff --git a/src/xenia/vfs/devices/host_path_entry.cc b/src/xenia/vfs/devices/host_path_entry.cc index 04c9e2dc8..8d1025d4d 100644 --- a/src/xenia/vfs/devices/host_path_entry.cc +++ b/src/xenia/vfs/devices/host_path_entry.cc @@ -97,13 +97,15 @@ std::unique_ptr HostPathEntry::CreateEntryInternal( bool HostPathEntry::DeleteEntryInternal(Entry* entry) { auto full_path = host_path_ / xe::to_path(entry->name()); + std::error_code ec; // avoid exception on remove/remove_all failure if (entry->attributes() & kFileAttributeDirectory) { // Delete entire directory and contents. - return std::filesystem::remove_all(full_path); + auto removed = std::filesystem::remove_all(full_path, ec); + return removed >= 1 && removed != static_cast(-1); } else { // Delete file. return !std::filesystem::is_directory(full_path) && - std::filesystem::remove(full_path); + std::filesystem::remove(full_path, ec); } } diff --git a/third_party/date b/third_party/date new file mode 160000 index 000000000..97246a638 --- /dev/null +++ b/third_party/date @@ -0,0 +1 @@ +Subproject commit 97246a638a6d8f0269f4555c5e31106a86e3fd94 diff --git a/third_party/premake-core b/third_party/premake-core index 11aff7aea..df6096721 160000 --- a/third_party/premake-core +++ b/third_party/premake-core @@ -1 +1 @@ -Subproject commit 11aff7aeacc8315e85a659bc1e803c1064adc6b3 +Subproject commit df609672110ac07ff7ea6597911575c4365c2928 diff --git a/tools/build/bin/premake5.exe b/tools/build/bin/premake5.exe index 7db2d08b7..2a01f91d7 100644 Binary files a/tools/build/bin/premake5.exe and b/tools/build/bin/premake5.exe differ diff --git a/tools/build/scripts/test_suite.lua b/tools/build/scripts/test_suite.lua index 096968f7c..afb8a9b31 100644 --- a/tools/build/scripts/test_suite.lua +++ b/tools/build/scripts/test_suite.lua @@ -26,8 +26,12 @@ local function combined_test_suite(test_suite_name, project_root, base_path, con })) links(merge_arrays(config["links"], { })) + defines({ + "XE_TEST_SUITE_NAME=\""..test_suite_name.."\"", + }) files({ project_root.."/"..build_tools_src.."/test_suite_main.cc", + project_root.."/src/xenia/base/main_"..platform_suffix..".cc", base_path.."/**_test.cc", }) end diff --git a/tools/build/src/test_suite_main.cc b/tools/build/src/test_suite_main.cc index b5daf3972..d84c7047c 100644 --- a/tools/build/src/test_suite_main.cc +++ b/tools/build/src/test_suite_main.cc @@ -13,44 +13,34 @@ #include #include +#include "xenia/base/cvar.h" +#include "xenia/base/main.h" + #define CATCH_CONFIG_RUNNER #include "third_party/catch/include/catch.hpp" -#include "xenia/base/cvar.h" namespace xe { +namespace test_suite { -bool has_console_attached() { return true; } - -// Used in console mode apps; automatically picked based on subsystem. -int Main(int argc, char* argv[]) { - cvar::ParseLaunchArguments(argc, argv, "", std::vector()); +int test_suite_main(const std::vector& args) { + // Catch doesn't expose a way to pass a vector of strings, despite building a + // vector internally. + int argc = 0; + std::vector argv; + for (const auto& arg : args) { + argv.push_back(arg.c_str()); + argc++; + } // Run Catch. - int result = Catch::Session().run(argc, argv); - - return result; + return Catch::Session().run(argc, argv.data()); } +} // namespace test_suite } // namespace xe -#if _WIN32 -#include "xenia/base/platform_win.h" +#ifndef XE_TEST_SUITE_NAME +#error XE_TEST_SUITE_NAME is undefined! +#endif -extern "C" int main(int argc, wchar_t* argv[]) { - // Setup COM on the main thread. - // NOTE: this may fail if COM has already been initialized - that's OK. - CoInitializeEx(nullptr, COINIT_MULTITHREADED); - - // Convert all args to narrow, as gflags doesn't support wchar. - int argca = argc; - char** argva = (char**)alloca(sizeof(char*) * argca); - for (int n = 0; n < argca; n++) { - size_t len = wcslen(argv[n]); - argva[n] = (char*)alloca(len + 1); - std::wcstombs(argva[n], argv[n], len + 1); - } - return xe::Main(argc, argva); -} -#else -extern "C" int main(int argc, char* argv[]) { return xe::Main(argc, argv); } -#endif // _WIN32 +DEFINE_ENTRY_POINT(XE_TEST_SUITE_NAME, xe::test_suite::test_suite_main, ""); diff --git a/xenia-build b/xenia-build index f60dcb1b0..0a755fa9d 100755 --- a/xenia-build +++ b/xenia-build @@ -7,7 +7,7 @@ Run with --help or no arguments for possible commands. """ from __future__ import print_function - +from datetime import datetime import argparse import json import os @@ -271,6 +271,48 @@ def generate_version_h(): with open('build/version.h', 'w') as f: f.write(contents) +def generate_source_class(path): + header_path = '{}.h'.format(path) + source_path = '{}.cc'.format(path) + + if os.path.isfile(header_path) or os.path.isfile(source_path): + print('ERROR: Target file already exists') + return 1 + + if generate_source_file(header_path) > 0: + return 1 + if generate_source_file(source_path) > 0: + # remove header if source file generation failed + os.remove(os.path.join(source_root, header_path)) + return 1 + + return 0 + +def generate_source_file(path): + """Generates a source file at the specified path containing copyright notice + """ + copyright = '''/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright {} Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */'''.format(datetime.now().year) + + if os.path.isfile(path): + print('ERROR: Target file already exists') + return 1 + try: + with open(path, 'w') as f: + f.write(copyright) + except Exception as e: + print('ERROR: Could not write to file [path {}]'.format(path)) + return 1 + + return 0 + + def git_get_head_info(): """Queries the current branch and commit checksum from git. @@ -513,6 +555,7 @@ def discover_commands(subparsers): 'format': FormatCommand(subparsers), 'style': StyleCommand(subparsers), 'tidy': TidyCommand(subparsers), + 'stub': StubCommand(subparsers), } if sys.platform == 'win32': commands['gendxbc'] = GenDxbcCommand(subparsers) @@ -1538,6 +1581,57 @@ class TidyCommand(Command): print('Tidy completed successfully.') return 0 +class StubCommand(Command): + """'stub' command.""" + + def __init__(self, subparsers, *args, **kwargs): + super(StubCommand, self).__init__( + subparsers, + name='stub', + help_short='Create new file(s) in the xenia source tree and run premake', + *args, **kwargs) + self.parser.add_argument( + '--file', default=None, + help='Generate a source file at the provided location in the source tree') + self.parser.add_argument( + '--class', default=None, + help='Generate a class pair (.cc/.h) at the provided location in the source tree') + self.parser.add_argument( + '--target_os', default=None, + help='Target OS passed to premake, for cross-compilation') + + def execute(self, args, pass_args, cwd): + root = os.path.dirname(os.path.realpath(__file__)) + source_root = os.path.join(root, os.path.normpath('src/xenia')) + + if args['class']: + path = os.path.normpath(os.path.join(source_root, args['class'])) + target_dir = os.path.dirname(path) + class_name = os.path.basename(path) + + status = generate_source_class(path) + if status > 0: + return status + + print('Created class \'{0}\' at {1}'.format(class_name, target_dir)) + + elif args['file']: + path = os.path.normpath(os.path.join(source_root, args['file'])) + target_dir = os.path.dirname(path) + file_name = os.path.basename(path) + + status = generate_source_file(path) + if status > 0: + return status + + print('Created file \'{0}\' at {1}'.format(file_name, target_dir)) + + else: + print('ERROR: Please specify a file/class to generate') + return 1 + + run_platform_premake(target_os_override=args['target_os']) + return 0 class DevenvCommand(Command): """'devenv' command."""