diff --git a/src/xenia/base/debugging.h b/src/xenia/base/debugging.h index 401890fa4..a65b67f26 100644 --- a/src/xenia/base/debugging.h +++ b/src/xenia/base/debugging.h @@ -25,6 +25,9 @@ bool IsDebuggerAttached(); // If no debugger is present, a signal will be raised. void Break(); +// Prints a message to the attached debugger. +// This bypasses the normal logging mechanism. If no debugger is attached it's +// likely to no-op. void DebugPrint(const char* fmt, ...); } // namespace debugging diff --git a/src/xenia/base/filesystem.h b/src/xenia/base/filesystem.h index 5b7032641..15efda721 100644 --- a/src/xenia/base/filesystem.h +++ b/src/xenia/base/filesystem.h @@ -20,16 +20,34 @@ namespace xe { namespace filesystem { +// Canonicalizes a path, removing ..'s. std::string CanonicalizePath(const std::string& original_path); +// Returns true of the specified path exists as either a directory or file. bool PathExists(const std::wstring& path); +// Creates the parent folder of the specified path if needed. +// This can be used to ensure the destination path for a new file exists before +// attempting to create it. bool CreateParentFolder(const std::wstring& path); + +// Creates a folder at the specified path. +// Returns true if the path was created. bool CreateFolder(const std::wstring& path); + +// Recursively deletes the files and folders at the specified path. +// Returns true if the path was found and removed. bool DeleteFolder(const std::wstring& path); + +// Returns true if the given path exists and is a folder. bool IsFolder(const std::wstring& path); +// Opens the file at the given path with the specified mode. +// This behaves like fopen and the returned handle can be used with stdio. FILE* OpenFile(const std::wstring& path, const char* mode); + +// Deletes the file at the given path. +// Returns true if the file was found and removed. bool DeleteFile(const std::wstring& path); struct FileAccess { diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 79a7f1a1b..d0f71b35c 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -95,9 +95,8 @@ X_STATUS Emulator::Setup( // Create memory system first, as it is required for other systems. memory_ = std::make_unique(); - result = memory_->Initialize(); - if (result) { - return result; + if (!memory_->Initialize()) { + return false; } // Shared export resolver used to attach and query for HLE exports. diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index f50729cb7..c2c4f433b 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -43,33 +43,57 @@ class Window; namespace xe { +// The main type that runs the whole emulator. +// This is responsible for initializing and managing all the various subsystems. class Emulator { public: explicit Emulator(const std::wstring& command_line); ~Emulator(); + // Full command line used when launching the process. const std::wstring& command_line() const { return command_line_; } + // Window used for displaying graphical output. ui::Window* display_window() const { return display_window_; } + // Guest memory system modelling the RAM (both virtual and physical) of the + // system. Memory* memory() const { return memory_.get(); } + // Active debugger, which may or may not be attached. debug::Debugger* debugger() const { return debugger_.get(); } + // Virtualized processor that can execute PPC code. cpu::Processor* processor() const { return processor_.get(); } + + // Audio hardware emulation for decoding and playback. apu::AudioSystem* audio_system() const { return audio_system_.get(); } + + // GPU emulation for command list processing. gpu::GraphicsSystem* graphics_system() const { return graphics_system_.get(); } + + // Human-interface Device (HID) adapters for controllers. hid::InputSystem* input_system() const { return input_system_.get(); } + // Kernel function export table used to resolve exports when JITing code. cpu::ExportResolver* export_resolver() const { return export_resolver_.get(); } + + // File systems mapped to disc images, folders, etc for games and save data. vfs::VirtualFileSystem* file_system() const { return file_system_.get(); } + // The 'kernel', tracking all kernel objects and other state. + // This is effectively the guest operating system. kernel::KernelState* kernel_state() const { return kernel_state_.get(); } + // Initializes the emulator and configures all components. + // The given window is used for display and the provided functions are used + // to create subsystems as required. + // Once this function returns a game can be launched using one of the Launch + // functions. X_STATUS Setup( ui::Window* display_window, std::function(cpu::Processor*)> @@ -79,9 +103,19 @@ class Emulator { std::function>(ui::Window*)> input_driver_factory); + // Launches a game from the given file path. + // This will attempt to infer the type of the given file (such as an iso, etc) + // using heuristics. X_STATUS LaunchPath(std::wstring path); + + // Launches a game from a .xex file by mounting the containing folder as if it + // was an extracted STFS container. X_STATUS LaunchXexFile(std::wstring path); + + // Launches a game from a disc image file (.iso, etc). X_STATUS LaunchDiscImage(std::wstring path); + + // Launches a game from an STFS container file. X_STATUS LaunchStfsContainer(std::wstring path); private: diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index 6b13ecee6..436ccdeea 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -115,7 +115,7 @@ Memory::~Memory() { physical_membase_ = nullptr; } -int Memory::Initialize() { +bool Memory::Initialize() { file_name_ = std::wstring(L"Local\\xenia_memory_") + std::to_wstring(Clock::QueryHostTickCount()); @@ -128,7 +128,7 @@ int Memory::Initialize() { if (!mapping_) { XELOGE("Unable to reserve the 4gb guest address space."); assert_not_null(mapping_); - return 1; + return false; } // Attempt to create our views. This may fail at the first address @@ -144,7 +144,7 @@ int Memory::Initialize() { if (!mapping_base_) { XELOGE("Unable to find a continuous block in the 64bit address space."); assert_always(); - return 1; + return false; } virtual_membase_ = mapping_base_; physical_membase_ = mapping_base_ + 0x100000000ull; @@ -189,7 +189,7 @@ int Memory::Initialize() { if (!mmio_handler_) { XELOGE("Unable to install MMIO handlers"); assert_always(); - return 1; + return false; } // ? @@ -197,7 +197,7 @@ int Memory::Initialize() { heaps_.vA0000000.Alloc(0x340000, 64 * 1024, kMemoryAllocationReserve, kMemoryProtectNoAccess, true, &unk_phys_alloc); - return 0; + return true; } static const struct { diff --git a/src/xenia/memory.h b/src/xenia/memory.h index 0e51f6ca6..cc674fcee 100644 --- a/src/xenia/memory.h +++ b/src/xenia/memory.h @@ -63,45 +63,85 @@ struct HeapAllocationInfo { uint32_t type; }; +// Describes a single page in the page table. union PageEntry { struct { - uint32_t base_address : 20; // in 4k pages - uint32_t region_page_count : 20; // in 4k pages + // Base address of the allocated region in 4k pages. + uint32_t base_address : 20; + // Total number of pages in the allocated region in 4k pages. + uint32_t region_page_count : 20; + // Protection bits specified during region allocation. + // Composed of bits from MemoryProtectFlag. uint32_t allocation_protect : 4; + // Current protection bits as of the last Protect. + // Composed of bits from MemoryProtectFlag. uint32_t current_protect : 4; + // Allocation state of the page as a MemoryAllocationFlag bit mask. uint32_t state : 2; uint32_t reserved : 14; }; uint64_t qword; }; +// Heap abstraction for page-based allocation. class BaseHeap { public: virtual ~BaseHeap(); + // Size of each page within the heap range in bytes. uint32_t page_size() const { return page_size_; } + // Disposes and decommits all memory and clears the page table. virtual void Dispose(); + // Dumps information about all allocations within the heap to the log. void DumpMap(); + // Allocates pages with the given properties and allocation strategy. + // This can reserve and commit the pages as well as set protection modes. + // This will fail if not enough contiguous pages can be found. virtual bool Alloc(uint32_t size, uint32_t alignment, uint32_t allocation_type, uint32_t protect, bool top_down, uint32_t* out_address); + + // Allocates pages at the given address. + // This can reserve and commit the pages as well as set protection modes. + // This will fail if the pages are already allocated. virtual bool AllocFixed(uint32_t base_address, uint32_t size, uint32_t alignment, uint32_t allocation_type, uint32_t protect); + + // Allocates pages at an address within the given address range. + // This can reserve and commit the pages as well as set protection modes. + // This will fail if not enough contiguous pages can be found. virtual bool AllocRange(uint32_t low_address, uint32_t high_address, uint32_t size, uint32_t alignment, uint32_t allocation_type, uint32_t protect, bool top_down, uint32_t* out_address); + + // Decommits pages in the given range. + // Partial overlapping pages will also be decommitted. virtual bool Decommit(uint32_t address, uint32_t size); + + // Decommits and releases pages in the given range. + // Partial overlapping pages will also be released. virtual bool Release(uint32_t address, uint32_t* out_region_size = nullptr); + + // Modifies the protection mode of pages within the given range. virtual bool Protect(uint32_t address, uint32_t size, uint32_t protect); + // Queries information about the given region of pages. bool QueryRegionInfo(uint32_t base_address, HeapAllocationInfo* out_info); + + // Queries the size of the region containing the given address. bool QuerySize(uint32_t address, uint32_t* out_size); + + // Queries the current protection mode of the region containing the given + // address. bool QueryProtect(uint32_t address, uint32_t* out_protect); + + // Gets the physical address of a virtual address. + // This is only valid if the page is backed by a physical allocation. uint32_t GetPhysicalAddress(uint32_t address); protected: @@ -118,20 +158,30 @@ class BaseHeap { std::vector page_table_; }; +// Normal heap allowing allocations from guest virtual address ranges. class VirtualHeap : public BaseHeap { public: VirtualHeap(); ~VirtualHeap() override; + // Initializes the heap properties and allocates the page table. void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size, uint32_t page_size); }; +// A heap for ranges of memory that are mapped to physical ranges. +// Physical ranges are used by the audio and graphics subsystems representing +// hardware wired directly to memory in the console. +// +// The physical heap and the behavior of sharing pages with virtual pages is +// implemented by having a 'parent' heap that is used to perform allocation in +// the guest virtual address space 1:1 with the physical address space. class PhysicalHeap : public BaseHeap { public: PhysicalHeap(); ~PhysicalHeap() override; - + + // Initializes the heap properties and allocates the page table. void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size, uint32_t page_size, VirtualHeap* parent_heap); @@ -152,16 +202,40 @@ class PhysicalHeap : public BaseHeap { VirtualHeap* parent_heap_; }; +// Models the entire guest memory system on the console. +// This exposes interfaces to both virtual and physical memory and a TLB and +// page table for allocation, mapping, and protection. +// +// The memory is backed by a memory mapped file and is placed at a stable +// fixed address in the host address space (like 0x100000000). This allows +// efficient guest<->host address translations as well as easy sharing of the +// memory across various subsystems. +// +// The guest memory address space is split into several ranges that have varying +// properties such as page sizes, caching strategies, protections, and +// overlap with other ranges. Each range is represented by a BaseHeap of either +// VirtualHeap or PhysicalHeap depending on type. Heaps model the page tables +// and can handle reservation and committing of requested pages. class Memory { public: Memory(); ~Memory(); - int Initialize(); + // Initializes the memory system. + // This may fail if the host address space could not be reserved or the + // mapping to the file system fails. + bool Initialize(); + // Full file name and path of the memory-mapped file backing all memory. const std::wstring& file_name() const { return file_name_; } + // Base address of virtual memory in the host address space. + // This is often something like 0x100000000. inline uint8_t* virtual_membase() const { return virtual_membase_; } + + // Translates a guest virtual address to a host address that can be accessed + // as a normal pointer. + // Note that the contents at the specified host address are big-endian. inline uint8_t* TranslateVirtual(uint32_t guest_address) const { return virtual_membase_ + guest_address; } @@ -170,7 +244,13 @@ class Memory { return reinterpret_cast(virtual_membase_ + guest_address); } + // Base address of physical memory in the host address space. + // This is often something like 0x200000000. inline uint8_t* physical_membase() const { return physical_membase_; } + + // Translates a guest physical address to a host address that can be accessed + // as a normal pointer. + // Note that the contents at the specified host address are big-endian. inline uint8_t* TranslatePhysical(uint32_t guest_address) const { return physical_membase_ + (guest_address & 0x1FFFFFFF); } @@ -179,32 +259,62 @@ class Memory { return reinterpret_cast(physical_membase_ + (guest_address & 0x1FFFFFFF)); } - - // TODO(benvanik): make poly memory utils for these. + + // Zeros out a range of memory at the given guest address. void Zero(uint32_t address, uint32_t size); + + // Fills a range of guest memory with the given byte value. void Fill(uint32_t address, uint32_t size, uint8_t value); + + // Copies a non-overlapping range of guest memory (like a memcpy). void Copy(uint32_t dest, uint32_t src, uint32_t size); + + // Searches the given range of guest memory for a run of dword values in + // big-endian order. uint32_t SearchAligned(uint32_t start, uint32_t end, const uint32_t* values, size_t value_count); + // Defines a memory-mapped IO (MMIO) virtual address range that when accessed + // will trigger the specified read and write callbacks for dword read/writes. bool AddVirtualMappedRange(uint32_t virtual_address, uint32_t mask, uint32_t size, void* context, cpu::MMIOReadCallback read_callback, cpu::MMIOWriteCallback write_callback); + + // Gets the defined MMIO range for the given virtual address, if any. cpu::MMIORange* LookupVirtualMappedRange(uint32_t virtual_address); + // Adds a write watch for the given physical address range that will trigger + // the specified callback whenever any bytes are written in that range. + // The returned handle can be used with CancelWriteWatch to remove the watch + // if it is no longer required. + // + // This has a significant performance penalty for writes in in the range or + // nearby (sharing 64KiB pages). uintptr_t AddPhysicalWriteWatch(uint32_t physical_address, uint32_t length, cpu::WriteWatchCallback callback, void* callback_context, void* callback_data); + + // Cancels a write watch requested with AddPhysicalWriteWatch. void CancelWriteWatch(uintptr_t watch_handle); + // Allocates virtual memory from the 'system' heap. + // System memory is kept separate from game memory but is still accessible + // using normal guest virtual addresses. Kernel structures and other internal + // 'system' allocations should come from this heap when possible. uint32_t SystemHeapAlloc(uint32_t size, uint32_t alignment = 0x20, uint32_t system_heap_flags = kSystemHeapDefault); + + // Frees memory allocated with SystemHeapAlloc. void SystemHeapFree(uint32_t address); + // Gets the heap for the address space containing the given address. BaseHeap* LookupHeap(uint32_t address); + + // Gets the heap with the given properties. BaseHeap* LookupHeapByType(bool physical, uint32_t page_size); + // Dumps a map of all allocated memory to the log. void DumpMap(); private: