diff --git a/src/xenia/kernel/fs/devices/disc_image_file.cc b/src/xenia/kernel/fs/devices/disc_image_file.cc index a6285a7b3..9f0f6c328 100644 --- a/src/xenia/kernel/fs/devices/disc_image_file.cc +++ b/src/xenia/kernel/fs/devices/disc_image_file.cc @@ -32,6 +32,11 @@ X_STATUS DiscImageFile::QueryInfo(XFileInfo* out_info) { return entry_->QueryInfo(out_info); } +X_STATUS DiscImageFile::QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart) { + XEASSERTALWAYS(); + return X_STATUS_SUCCESS; +} + X_STATUS DiscImageFile::ReadSync( void* buffer, size_t buffer_length, size_t byte_offset, size_t* out_bytes_read) { diff --git a/src/xenia/kernel/fs/devices/disc_image_file.h b/src/xenia/kernel/fs/devices/disc_image_file.h index d6d5c6440..895ca4ea1 100644 --- a/src/xenia/kernel/fs/devices/disc_image_file.h +++ b/src/xenia/kernel/fs/devices/disc_image_file.h @@ -30,6 +30,7 @@ public: virtual ~DiscImageFile(); virtual X_STATUS QueryInfo(XFileInfo* out_info); + virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart); protected: virtual X_STATUS ReadSync( diff --git a/src/xenia/kernel/fs/devices/host_path_entry.cc b/src/xenia/kernel/fs/devices/host_path_entry.cc index 9e481529e..182dde80c 100644 --- a/src/xenia/kernel/fs/devices/host_path_entry.cc +++ b/src/xenia/kernel/fs/devices/host_path_entry.cc @@ -39,14 +39,20 @@ private: HostPathEntry::HostPathEntry(Type type, Device* device, const char* path, const xechar_t* local_path) : - Entry(type, device, path) { + Entry(type, device, path), + find_file_(INVALID_HANDLE_VALUE) { local_path_ = xestrdup(local_path); } HostPathEntry::~HostPathEntry() { + if (find_file_ != INVALID_HANDLE_VALUE) { + FindClose(find_file_); + } xe_free(local_path_); } +#define COMBINE_TIME(t) (((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime) + X_STATUS HostPathEntry::QueryInfo(XFileInfo* out_info) { XEASSERTNOTNULL(out_info); @@ -56,7 +62,6 @@ X_STATUS HostPathEntry::QueryInfo(XFileInfo* out_info) { return X_STATUS_ACCESS_DENIED; } -#define COMBINE_TIME(t) (((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime) out_info->creation_time = COMBINE_TIME(data.ftCreationTime); out_info->last_access_time = COMBINE_TIME(data.ftLastAccessTime); out_info->last_write_time = COMBINE_TIME(data.ftLastWriteTime); @@ -67,6 +72,86 @@ X_STATUS HostPathEntry::QueryInfo(XFileInfo* out_info) { return X_STATUS_SUCCESS; } +X_STATUS HostPathEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart) { + XEASSERTNOTNULL(out_info); + + if (length < sizeof(XDirectoryInfo)) { + return X_STATUS_INFO_LENGTH_MISMATCH; + } + + memset(out_info, 0, length); + + WIN32_FIND_DATA ffd; + + HANDLE handle = find_file_; + + if (restart == true && handle != INVALID_HANDLE_VALUE) { + FindClose(find_file_); + handle = find_file_ = INVALID_HANDLE_VALUE; + } + + if (handle == INVALID_HANDLE_VALUE) { + handle = find_file_ = FindFirstFile(local_path_, &ffd); + if (handle == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + return X_STATUS_NO_SUCH_FILE; + } + return X_STATUS_UNSUCCESSFUL; + } + } + else { + if (FindNextFile(handle, &ffd) == FALSE) { + FindClose(handle); + find_file_ = INVALID_HANDLE_VALUE; + return X_STATUS_UNSUCCESSFUL; + } + } + + XDirectoryInfo* current; + + auto current_buf = (uint8_t*)out_info; + auto end = current_buf + length; + + current = (XDirectoryInfo*)current_buf; + if (((uint8_t*)¤t->file_name[0]) + wcslen(ffd.cFileName) > end) { + FindClose(handle); + find_file_ = INVALID_HANDLE_VALUE; + return X_STATUS_UNSUCCESSFUL; + } + + do + { + size_t file_name_length = wcslen(ffd.cFileName); + if (((uint8_t*)&((XDirectoryInfo*)current_buf)->file_name[0]) + wcslen(ffd.cFileName) > end) { + break; + } + + current = (XDirectoryInfo*)current_buf; + current->file_index = 0xCDCDCDCD; + current->creation_time = COMBINE_TIME(ffd.ftCreationTime); + current->last_access_time = COMBINE_TIME(ffd.ftLastAccessTime); + current->last_write_time = COMBINE_TIME(ffd.ftLastWriteTime); + current->change_time = COMBINE_TIME(ffd.ftLastWriteTime); + current->end_of_file = ((uint64_t)ffd.nFileSizeHigh << 32) | ffd.nFileSizeLow; + current->allocation_size = 4096; + current->attributes = (X_FILE_ATTRIBUTES)ffd.dwFileAttributes; + + // TODO: I am pretty sure you need to prefix the file paths with full path, not just the name. + current->file_name_length = (uint32_t)file_name_length; + for (size_t i = 0; i < file_name_length; ++i) { + current->file_name[i] = ffd.cFileName[i] < 256 ? (char)ffd.cFileName[i] : '?'; + } + + auto next_buf = (((uint8_t*)¤t->file_name[0]) + file_name_length); + next_buf += 8 - ((uint8_t)next_buf % 8); + + current->next_entry_offset = (uint32_t)(next_buf - current_buf); + current_buf = next_buf; + } while (current_buf < end && FindNextFile(handle, &ffd) == TRUE); + current->next_entry_offset = 0; + return X_STATUS_SUCCESS; +} + MemoryMapping* HostPathEntry::CreateMemoryMapping( xe_file_mode file_mode, const size_t offset, const size_t length) { xe_mmap_ref mmap = xe_mmap_open(file_mode, local_path_, offset, length); diff --git a/src/xenia/kernel/fs/devices/host_path_entry.h b/src/xenia/kernel/fs/devices/host_path_entry.h index d46a54f04..8965b3f50 100644 --- a/src/xenia/kernel/fs/devices/host_path_entry.h +++ b/src/xenia/kernel/fs/devices/host_path_entry.h @@ -30,6 +30,7 @@ public: const xechar_t* local_path() { return local_path_; } virtual X_STATUS QueryInfo(XFileInfo* out_info); + virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart); virtual MemoryMapping* CreateMemoryMapping( xe_file_mode file_mode, const size_t offset, const size_t length); @@ -41,6 +42,7 @@ public: private: xechar_t* local_path_; + HANDLE find_file_; }; diff --git a/src/xenia/kernel/fs/devices/host_path_file.cc b/src/xenia/kernel/fs/devices/host_path_file.cc index c1daebade..3a870ecbb 100644 --- a/src/xenia/kernel/fs/devices/host_path_file.cc +++ b/src/xenia/kernel/fs/devices/host_path_file.cc @@ -32,6 +32,10 @@ X_STATUS HostPathFile::QueryInfo(XFileInfo* out_info) { return entry_->QueryInfo(out_info); } +X_STATUS HostPathFile::QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart) { + return entry_->QueryDirectory(out_info, length, restart); +} + X_STATUS HostPathFile::ReadSync( void* buffer, size_t buffer_length, size_t byte_offset, size_t* out_bytes_read) { diff --git a/src/xenia/kernel/fs/devices/host_path_file.h b/src/xenia/kernel/fs/devices/host_path_file.h index 690ad19d5..e0fba8468 100644 --- a/src/xenia/kernel/fs/devices/host_path_file.h +++ b/src/xenia/kernel/fs/devices/host_path_file.h @@ -30,6 +30,7 @@ public: virtual ~HostPathFile(); virtual X_STATUS QueryInfo(XFileInfo* out_info); + virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart); protected: virtual X_STATUS ReadSync( diff --git a/src/xenia/kernel/fs/entry.h b/src/xenia/kernel/fs/entry.h index 3dde308fe..3c7a3a8be 100644 --- a/src/xenia/kernel/fs/entry.h +++ b/src/xenia/kernel/fs/entry.h @@ -19,6 +19,7 @@ XEDECLARECLASS2(xe, kernel, KernelState); XEDECLARECLASS2(xe, kernel, XFile); XEDECLARECLASS2(xe, kernel, XFileInfo); +XEDECLARECLASS2(xe, kernel, XDirectoryInfo); namespace xe { diff --git a/src/xenia/kernel/objects/xfile.h b/src/xenia/kernel/objects/xfile.h index 49743a6ff..da3f0a5d1 100644 --- a/src/xenia/kernel/objects/xfile.h +++ b/src/xenia/kernel/objects/xfile.h @@ -44,6 +44,44 @@ public: } }; +class XDirectoryInfo { +public: + // FILE_DIRECTORY_INFORMATION + uint32_t next_entry_offset; + uint32_t file_index; + uint64_t creation_time; + uint64_t last_access_time; + uint64_t last_write_time; + uint64_t change_time; + uint64_t end_of_file; + uint64_t allocation_size; + X_FILE_ATTRIBUTES attributes; + uint32_t file_name_length; + char file_name[1]; + + void Write(uint8_t* base, uint32_t p) { + uint8_t* dst = base + p; + uint8_t* src = (uint8_t*)this; + XDirectoryInfo* info; + do { + info = (XDirectoryInfo*)src; + XESETUINT32BE(dst, info->next_entry_offset); + XESETUINT32BE(dst + 4, info->file_index); + XESETUINT64BE(dst + 8, info->creation_time); + XESETUINT64BE(dst + 16, info->last_access_time); + XESETUINT64BE(dst + 24, info->last_write_time); + XESETUINT64BE(dst + 32, info->change_time); + XESETUINT64BE(dst + 40, info->end_of_file); + XESETUINT64BE(dst + 48, info->allocation_size); + XESETUINT32BE(dst + 56, info->attributes); + XESETUINT32BE(dst + 60, info->file_name_length); + xe_copy_memory(dst + 64, info->file_name_length, info->file_name, info->file_name_length); + dst += info->next_entry_offset; + src += info->next_entry_offset; + } while (info->next_entry_offset != 0); + } +}; +XEASSERTSTRUCTSIZE(XDirectoryInfo, 72); class XFile : public XObject { public: @@ -53,6 +91,7 @@ public: void set_position(size_t value) { position_ = value; } virtual X_STATUS QueryInfo(XFileInfo* out_info) = 0; + virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart) = 0; X_STATUS Read(void* buffer, size_t buffer_length, size_t byte_offset, size_t* out_bytes_read); diff --git a/src/xenia/kernel/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl_io.cc index 0b73d31f7..dcdb8d4fb 100644 --- a/src/xenia/kernel/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl_io.cc @@ -458,6 +458,71 @@ SHIM_CALL NtQueryVolumeInformationFile_shim( SHIM_SET_RETURN(X_STATUS_NO_SUCH_FILE); } +SHIM_CALL NtQueryDirectoryFile_shim( + PPCContext* ppc_state, KernelState* state) { + uint32_t file_handle = SHIM_GET_ARG_32(0); + uint32_t event_handle = SHIM_GET_ARG_32(1); + uint32_t apc_routine = SHIM_GET_ARG_32(2); + uint32_t apc_context = SHIM_GET_ARG_32(3); + uint32_t io_status_block_ptr = SHIM_GET_ARG_32(4); + uint32_t file_info_ptr = SHIM_GET_ARG_32(5); + uint32_t length = SHIM_GET_ARG_32(6); + uint32_t file_name_ptr = SHIM_GET_ARG_32(7); + + XELOGD( + "NtQueryDirectoryFile(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X, %d, %.8X)", + file_handle, + event_handle, + apc_routine, + apc_context, + io_status_block_ptr, + file_info_ptr, + length, + file_name_ptr); + + if (length < 72) { + SHIM_SET_RETURN(X_STATUS_INFO_LENGTH_MISMATCH); + return; + } + + if (file_name_ptr != 0) { + if (SHIM_MEM_16(file_name_ptr + 0) != 0 || + SHIM_MEM_16(file_name_ptr + 2) != 0) { + const char* file_name = (const char *)SHIM_MEM_ADDR(SHIM_MEM_32(file_name_ptr + 4)); + XEASSERT(strcmp(file_name, "*.*") == 0); + } + } + + X_STATUS result = X_STATUS_UNSUCCESSFUL; + uint32_t info = 0; + + XFile* file = NULL; + result = state->object_table()->GetObject( + file_handle, (XObject**)&file); + if (XSUCCEEDED(result)) { + XDirectoryInfo* dirInfo = (XDirectoryInfo*)xe_malloc(length); + result = file->QueryDirectory(dirInfo, length, false); + if (XSUCCEEDED(result)) { + dirInfo->Write(SHIM_MEM_BASE, file_info_ptr); + info = length; + } + } + + if (XFAILED(result)) { + info = 0; + } + if (io_status_block_ptr) { + SHIM_SET_MEM_32(io_status_block_ptr, result); // Status + SHIM_SET_MEM_32(io_status_block_ptr + 4, info); // Information + } + + if (file) { + file->Release(); + } + + SHIM_SET_RETURN(result); +} + SHIM_CALL FscSetCacheElementCount_shim( PPCContext* ppc_state, KernelState* state) { uint32_t unk_0 = SHIM_GET_ARG_32(0); @@ -486,6 +551,7 @@ void xe::kernel::xboxkrnl::RegisterIoExports( SHIM_SET_MAPPING("xboxkrnl.exe", NtSetInformationFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryFullAttributesFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryVolumeInformationFile, state); + SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryDirectoryFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", FscSetCacheElementCount, state); }