diff --git a/include/xenia/core/mmap.h b/include/xenia/core/mmap.h index 098a81755..df73b345d 100644 --- a/include/xenia/core/mmap.h +++ b/include/xenia/core/mmap.h @@ -25,7 +25,7 @@ xe_mmap_ref xe_mmap_open(xe_pal_ref pal, const xe_file_mode mode, const size_t offset, const size_t length); xe_mmap_ref xe_mmap_retain(xe_mmap_ref mmap); void xe_mmap_release(xe_mmap_ref mmap); -void *xe_mmap_get_addr(xe_mmap_ref mmap); +uint8_t* xe_mmap_get_addr(xe_mmap_ref mmap); size_t xe_mmap_get_length(xe_mmap_ref mmap); diff --git a/src/core/mmap_posix.cc b/src/core/mmap_posix.cc index 15e74390e..7e8ce3c71 100644 --- a/src/core/mmap_posix.cc +++ b/src/core/mmap_posix.cc @@ -92,8 +92,8 @@ void xe_mmap_release(xe_mmap_ref mmap) { xe_ref_release((xe_ref)mmap, (xe_ref_dealloc_t)xe_mmap_dealloc); } -void* xe_mmap_get_addr(xe_mmap_ref mmap) { - return mmap->addr; +uint8_t* xe_mmap_get_addr(xe_mmap_ref mmap) { + return (uint8_t*)mmap->addr; } size_t xe_mmap_get_length(xe_mmap_ref mmap) { diff --git a/src/core/mmap_win.cc b/src/core/mmap_win.cc index 3dd245252..244bb3faa 100644 --- a/src/core/mmap_win.cc +++ b/src/core/mmap_win.cc @@ -89,7 +89,7 @@ xe_mmap_ref xe_mmap_open(xe_pal_ref pal, const xe_file_mode mode, size_t map_length = GetFileSize(file_handle, NULL); mmap->length = map_length; } - + return mmap; XECLEANUP: @@ -123,7 +123,7 @@ void xe_mmap_release(xe_mmap_ref mmap) { xe_ref_release((xe_ref)mmap, (xe_ref_dealloc_t)xe_mmap_dealloc); } -void* xe_mmap_get_addr(xe_mmap_ref mmap) { +uint8_t* xe_mmap_get_addr(xe_mmap_ref mmap) { return mmap->addr; } diff --git a/src/kernel/fs/devices/disc_image_device.cc b/src/kernel/fs/devices/disc_image_device.cc index 9be525419..dcd10bb09 100644 --- a/src/kernel/fs/devices/disc_image_device.cc +++ b/src/kernel/fs/devices/disc_image_device.cc @@ -9,22 +9,105 @@ #include "kernel/fs/devices/disc_image_device.h" +#include "kernel/fs/gdfx.h" + using namespace xe; using namespace xe::kernel; using namespace xe::kernel::fs; +namespace { + + +class DiscImageMemoryMapping : public MemoryMapping { +public: + DiscImageMemoryMapping(uint8_t* address, size_t length, xe_mmap_ref mmap) : + MemoryMapping(address, length) { + mmap_ = xe_mmap_retain(mmap); + } + + virtual ~DiscImageMemoryMapping() { + xe_mmap_release(mmap_); + } + +private: + xe_mmap_ref mmap_; +}; + + +class DiscImageFileEntry : public FileEntry { +public: + DiscImageFileEntry(Device* device, const char* path, + xe_mmap_ref mmap, GDFXEntry* gdfx_entry) : + FileEntry(device, path), + gdfx_entry_(gdfx_entry) { + mmap_ = xe_mmap_retain(mmap); + } + + virtual ~DiscImageFileEntry() { + delete gdfx_entry_; + xe_mmap_release(mmap_); + } + + virtual MemoryMapping* CreateMemoryMapping( + xe_file_mode file_mode, const size_t offset, const size_t length) { + if (file_mode & kXEFileModeWrite) { + // Only allow reads. + return NULL; + } + + size_t real_offset = gdfx_entry_->offset + offset; + size_t real_length = length ? + MIN(length, gdfx_entry_->size) : gdfx_entry_->size; + return new DiscImageMemoryMapping( + xe_mmap_get_addr(mmap_) + real_offset, + real_length, + mmap_); + } + +private: + xe_mmap_ref mmap_; + GDFXEntry* gdfx_entry_; +}; + + +} + + DiscImageDevice::DiscImageDevice(xe_pal_ref pal, const char* path, const xechar_t* local_path) : Device(pal, path) { local_path_ = xestrdup(local_path); + mmap_ = NULL; + gdfx_ = NULL; } DiscImageDevice::~DiscImageDevice() { + delete gdfx_; + xe_mmap_release(mmap_); xe_free(local_path_); } +int DiscImageDevice::Init() { + mmap_ = xe_mmap_open(pal_, kXEFileModeRead, local_path_, 0, 0); + if (!mmap_) { + XELOGE(XT("Disc image could not be mapped")); + return 1; + } + + gdfx_ = new GDFX(mmap_); + GDFX::Error error = gdfx_->Load(); + if (error != GDFX::kSuccess) { + XELOGE(XT("GDFX init failed: %d"), error); + return 1; + } + + //gdfx_->Dump(); + + return 0; +} + Entry* DiscImageDevice::ResolvePath(const char* path) { // The filesystem will have stripped our prefix off already, so the path will // be in the form: @@ -32,5 +115,44 @@ Entry* DiscImageDevice::ResolvePath(const char* path) { XELOGFS(XT("DiscImageDevice::ResolvePath(%s)"), path); - return NULL; + GDFXEntry* gdfx_entry = gdfx_->root_entry(); + + // Walk the path, one separator at a time. + // We copy it into the buffer and shift it left over and over. + char remaining[XE_MAX_PATH]; + XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), path)); + while (remaining[0]) { + char* next_slash = xestrchra(remaining, '\\'); + if (next_slash == remaining) { + // Leading slash - shift + XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), remaining + 1)); + continue; + } + + // Make the buffer just the name. + if (next_slash) { + *next_slash = 0; + } + + // Look up in the entry. + gdfx_entry = gdfx_entry->GetChild(remaining); + if (!gdfx_entry) { + // Not found. + return NULL; + } + + // Shift the buffer down, unless we are at the end. + if (!next_slash) { + break; + } + XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), next_slash + 1)); + } + + if (gdfx_entry->attributes & GDFXEntry::kAttrFolder) { + //return new DiscImageDirectoryEntry(mmap_, gdfx_entry); + XEASSERTALWAYS(); + return NULL; + } else { + return new DiscImageFileEntry(this, path, mmap_, gdfx_entry); + } } diff --git a/src/kernel/fs/devices/disc_image_device.h b/src/kernel/fs/devices/disc_image_device.h index bc443c3d1..a01898dc2 100644 --- a/src/kernel/fs/devices/disc_image_device.h +++ b/src/kernel/fs/devices/disc_image_device.h @@ -21,15 +21,22 @@ namespace kernel { namespace fs { +class GDFX; + + class DiscImageDevice : public Device { public: DiscImageDevice(xe_pal_ref pal, const char* path, const xechar_t* local_path); virtual ~DiscImageDevice(); + int Init(); + virtual Entry* ResolvePath(const char* path); private: xechar_t* local_path_; + xe_mmap_ref mmap_; + GDFX* gdfx_; }; diff --git a/src/kernel/fs/filesystem.cc b/src/kernel/fs/filesystem.cc index 586ede1d8..d1b8e514b 100644 --- a/src/kernel/fs/filesystem.cc +++ b/src/kernel/fs/filesystem.cc @@ -48,7 +48,10 @@ int FileSystem::RegisterLocalDirectoryDevice( int FileSystem::RegisterDiscImageDevice( const char* path, const xechar_t* local_path) { - Device* device = new DiscImageDevice(pal_, path, local_path); + DiscImageDevice* device = new DiscImageDevice(pal_, path, local_path); + if (device->Init()) { + return 1; + } return RegisterDevice(path, device); } diff --git a/src/kernel/fs/gdfx.cc b/src/kernel/fs/gdfx.cc new file mode 100644 index 000000000..099586976 --- /dev/null +++ b/src/kernel/fs/gdfx.cc @@ -0,0 +1,207 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2013 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + * Major contributions to this file from: + * - abgx360 + */ + +#include "kernel/fs/gdfx.h" + + +using namespace xe; +using namespace xe::kernel; +using namespace xe::kernel::fs; + + +namespace { + +#define kXESectorSize 2048 + +} + + +GDFXEntry::~GDFXEntry() { + for (std::vector::iterator it = children.begin(); + it != children.end(); ++it) { + delete *it; + } +} + +GDFXEntry* GDFXEntry::GetChild(const char* name) { + // TODO(benvanik): a faster search + for (std::vector::iterator it = children.begin(); + it != children.end(); ++it) { + GDFXEntry* entry = *it; + if (xestrcasecmpa(entry->name.c_str(), name) == 0) { + return entry; + } + } + return NULL; +} + +void GDFXEntry::Dump(int indent) { + printf("%s%s\n", std::string(indent, ' ').c_str(), name.c_str()); + for (std::vector::iterator it = children.begin(); + it != children.end(); ++it) { + GDFXEntry* entry = *it; + entry->Dump(indent + 2); + } +} + + +GDFX::GDFX(xe_mmap_ref mmap) { + mmap_ = xe_mmap_retain(mmap); + + root_entry_ = NULL; +} + +GDFX::~GDFX() { + delete root_entry_; + + xe_mmap_release(mmap_); +} + +GDFXEntry* GDFX::root_entry() { + return root_entry_; +} + +GDFX::Error GDFX::Load() { + Error result = kErrorOutOfMemory; + + ParseState state; + xe_zero_struct(&state, sizeof(state)); + + state.ptr = (uint8_t*)xe_mmap_get_addr(mmap_); + state.size = xe_mmap_get_length(mmap_); + + result = Verify(state); + XEEXPECTZERO(result); + + result = ReadAllEntries(state, state.ptr + state.root_offset); + XEEXPECTZERO(result); + + result = kSuccess; +XECLEANUP: + return result; +} + +void GDFX::Dump() { + if (root_entry_) { + root_entry_->Dump(0); + } +} + +GDFX::Error GDFX::Verify(ParseState& state) { + // Find sector 32 of the game partition - try at a few points. + const static size_t likely_offsets[] = { + 0x00000000, 0x0000FB20, 0x00020600, 0x0FD90000, + }; + bool magic_found = false; + for (size_t n = 0; n < XECOUNT(likely_offsets); n++) { + state.game_offset = likely_offsets[n]; + if (VerifyMagic(state, state.game_offset + (32 * kXESectorSize))) { + magic_found = true; + break; + } + } + if (!magic_found) { + // File doesn't have the magic values - likely not a real GDFX source. + return kErrorFileMismatch; + } + + // Read sector 32 to get FS state. + if (state.size < state.game_offset + (32 * kXESectorSize)) { + return kErrorReadError; + } + uint8_t* fs_ptr = state.ptr + state.game_offset + (32 * kXESectorSize); + state.root_sector = XEGETUINT32LE(fs_ptr + 20); + state.root_size = XEGETUINT32LE(fs_ptr + 24); + state.root_offset = state.game_offset + (state.root_sector * kXESectorSize); + if (state.root_size < 13 || + state.root_size > 32 * 1024 * 1024) { + return kErrorDamagedFile; + } + + return kSuccess; +} + +bool GDFX::VerifyMagic(ParseState& state, size_t offset) { + // Simple check to see if the given offset contains the magic value. + return memcmp(state.ptr + offset, "MICROSOFT*XBOX*MEDIA", 20) == 0; +} + +GDFX::Error GDFX::ReadAllEntries(ParseState& state, + const uint8_t* root_buffer) { + root_entry_ = new GDFXEntry(); + root_entry_->offset = 0; + root_entry_->size = 0; + root_entry_->name = ""; + root_entry_->attributes = GDFXEntry::kAttrFolder; + + if (!ReadEntry(state, root_buffer, 0, root_entry_)) { + return kErrorOutOfMemory; + } + + return kSuccess; +} + +bool GDFX::ReadEntry(ParseState& state, const uint8_t* buffer, + uint16_t entry_ordinal, GDFXEntry* parent) { + const uint8_t* p = buffer + (entry_ordinal * 4); + + uint16_t node_l = XEGETUINT16LE(p + 0); + uint16_t node_r = XEGETUINT16LE(p + 2); + size_t sector = XEGETUINT32LE(p + 4); + size_t length = XEGETUINT32LE(p + 8); + uint8_t attributes = XEGETUINT8LE(p + 12); + uint8_t name_length = XEGETUINT8LE(p + 13); + char* name = (char*)(p + 14); + + if (node_l && !ReadEntry(state, buffer, node_l, parent)) { + return false; + } + + GDFXEntry* entry = new GDFXEntry(); + entry->name = std::string(name, name_length); + entry->name.append(1, '\0'); + entry->attributes = attributes; + + // Add to parent (if we have one). + if (parent) { + parent->children.push_back(entry); + } + + if (attributes & GDFXEntry::kAttrFolder) { + // Folder. + entry->offset = 0; + entry->size = 0; + if (length) { + // Not a leaf - read in children. + if (state.size < state.game_offset + (sector * kXESectorSize)) { + // Out of bounds read. + return false; + } + // Read child list. + uint8_t* folder_ptr = + state.ptr + state.game_offset + (sector * kXESectorSize); + if (!ReadEntry(state, folder_ptr, 0, entry)) { + return false; + } + } + } else { + // File. + entry->offset = state.game_offset + (sector * kXESectorSize); + entry->size = length; + } + + // Read next file in the list. + if (node_r && !ReadEntry(state, buffer, node_r, parent)) { + return false; + } + + return true; +} diff --git a/src/kernel/fs/gdfx.h b/src/kernel/fs/gdfx.h new file mode 100644 index 000000000..a2f671de5 --- /dev/null +++ b/src/kernel/fs/gdfx.h @@ -0,0 +1,105 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2013 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_KERNEL_FS_GDFX_H_ +#define XENIA_KERNEL_FS_GDFX_H_ + +#include +#include + +#include + +#include + + +namespace xe { +namespace kernel { +namespace fs { + + +class GDFX; + + +class GDFXEntry { +public: + enum Attributes { + kAttrNone = 0x00000000, + kAttrReadOnly = 0x00000001, + kAttrHidden = 0x00000002, + kAttrSystem = 0x00000004, + kAttrFolder = 0x00000010, + kAttrArchived = 0x00000020, + kAttrNormal = 0x00000080, + }; + + GDFXEntry() {} + ~GDFXEntry(); + + GDFXEntry* GetChild(const char* name); + + void Dump(int indent); + + std::string name; + uint32_t attributes; + size_t offset; + size_t size; + + std::vector children; +}; + + +class GDFX { +public: + enum Error { + kSuccess = 0, + kErrorOutOfMemory = -1, + kErrorReadError = -10, + kErrorFileMismatch = -30, + kErrorDamagedFile = -31, + }; + + GDFX(xe_mmap_ref mmap); + virtual ~GDFX(); + + GDFXEntry* root_entry(); + + Error Load(); + void Dump(); + +private: + typedef struct { + uint8_t* ptr; + + size_t size; // Size (bytes) of total image + + size_t game_offset; // Offset (bytes) of game partition + + size_t root_sector; // Offset (sector) of root + size_t root_offset; // Offset (bytes) of root + size_t root_size; // Size (bytes) of root + } ParseState; + + Error Verify(ParseState& state); + bool VerifyMagic(ParseState& state, size_t offset); + Error ReadAllEntries(ParseState& state, const uint8_t* root_buffer); + bool ReadEntry(ParseState& state, const uint8_t* buffer, + uint16_t entry_ordinal, GDFXEntry* parent); + + xe_mmap_ref mmap_; + + GDFXEntry* root_entry_; +}; + + +} // namespace fs +} // namespace kernel +} // namespace xe + + +#endif // XENIA_KERNEL_FS_GDFX_H_ diff --git a/src/kernel/fs/sources.gypi b/src/kernel/fs/sources.gypi index 62acd6181..51108e2b0 100644 --- a/src/kernel/fs/sources.gypi +++ b/src/kernel/fs/sources.gypi @@ -4,6 +4,7 @@ 'device.cc', 'entry.cc', 'filesystem.cc', + 'gdfx.cc', ], 'includes': [