/** ****************************************************************************** * 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. * ****************************************************************************** */ #include "xenia/kernel/xfile.h" #include "xenia/vfs/virtual_file_system.h" #include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/mutex.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/xevent.h" #include "xenia/memory.h" namespace xe { namespace kernel { XFile::XFile(KernelState* kernel_state, vfs::File* file, bool synchronous) : XObject(kernel_state, kType), file_(file), is_synchronous_(synchronous) { async_event_ = threading::Event::CreateAutoResetEvent(false); } XFile::XFile() : XObject(kType) { async_event_ = threading::Event::CreateAutoResetEvent(false); } XFile::~XFile() { // TODO(benvanik): signal that the file is closing? async_event_->Set(); file_->Destroy(); } X_STATUS XFile::QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length, const char* file_name, bool restart) { assert_not_null(out_info); vfs::Entry* entry = nullptr; if (file_name != nullptr) { // Only queries in the current directory are supported for now. assert_true(std::strchr(file_name, '\\') == nullptr); find_engine_.SetRule(file_name); // Always restart the search? find_index_ = 0; entry = file_->entry()->IterateChildren(find_engine_, &find_index_); if (!entry) { return X_STATUS_NO_SUCH_FILE; } } else { if (restart) { find_index_ = 0; } entry = file_->entry()->IterateChildren(find_engine_, &find_index_); if (!entry) { return X_STATUS_NO_MORE_FILES; } } auto end = reinterpret_cast(out_info) + length; const auto& entry_name = entry->name(); if (reinterpret_cast(&out_info->file_name[0]) + entry_name.size() > end) { assert_always("Buffer overflow?"); return X_STATUS_NO_SUCH_FILE; } out_info->next_entry_offset = 0; out_info->file_index = static_cast(find_index_); out_info->creation_time = entry->create_timestamp(); out_info->last_access_time = entry->access_timestamp(); out_info->last_write_time = entry->write_timestamp(); out_info->change_time = entry->write_timestamp(); out_info->end_of_file = entry->size(); out_info->allocation_size = entry->allocation_size(); out_info->attributes = entry->attributes(); out_info->file_name_length = static_cast(entry_name.size()); std::memcpy(out_info->file_name, entry_name.data(), entry_name.size()); return X_STATUS_SUCCESS; } X_STATUS XFile::Read(uint32_t buffer_guest_address, uint32_t buffer_length, uint64_t byte_offset, uint32_t* out_bytes_read, uint32_t apc_context) { if (byte_offset == uint64_t(-1)) { // Read from current position. byte_offset = position_; } size_t bytes_read = 0; X_STATUS result = X_STATUS_SUCCESS; // Zero length means success for a valid file object according to Windows // tests. if (buffer_length) { if (UINT32_MAX - buffer_guest_address < buffer_length) { result = X_STATUS_ACCESS_VIOLATION; } else { // Games often read directly to texture/vertex buffer memory - in this // case, invalidation notifications must be sent. However, having any // memory callbacks in the range will result in STATUS_ACCESS_VIOLATION at // least on Windows, without anything being read or any callbacks being // triggered. So for physical memory, host protection must be bypassed, // and invalidation callbacks must be triggered manually (it's also wrong // to trigger invalidation callbacks before reading in this case, because // during the read, the guest may still access the data around the buffer // that is located in the same host pages as the buffer's start and end, // on the GPU - and that must not trigger a race condition). uint32_t buffer_guest_high_address = buffer_guest_address + buffer_length - 1; xe::BaseHeap* buffer_start_heap = memory()->LookupHeap(buffer_guest_address); 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 != buffer_end_heap)) { result = X_STATUS_ACCESS_VIOLATION; } else { xe::PhysicalHeap* buffer_physical_heap = buffer_start_heap->IsGuestPhysicalHeap() ? static_cast(buffer_start_heap) : nullptr; if (buffer_physical_heap && buffer_physical_heap->QueryRangeAccess(buffer_guest_address, buffer_guest_high_address) != memory::PageAccess::kReadWrite) { result = X_STATUS_ACCESS_VIOLATION; } else { result = file_->ReadSync( buffer_physical_heap ? memory()->TranslatePhysical( buffer_physical_heap->GetPhysicalAddress( buffer_guest_address)) : memory()->TranslateVirtual(buffer_guest_address), buffer_length, size_t(byte_offset), &bytes_read); if (XSUCCEEDED(result)) { if (buffer_physical_heap) { buffer_physical_heap->TriggerCallbacks( xe::global_critical_region::AcquireDirect(), buffer_guest_address, buffer_length, true, true); } position_ += bytes_read; } } } } } XIOCompletion::IONotification notify; notify.apc_context = apc_context; notify.num_bytes = uint32_t(bytes_read); notify.status = result; NotifyIOCompletionPorts(notify); if (out_bytes_read) { *out_bytes_read = uint32_t(bytes_read); } async_event_->Set(); return result; } X_STATUS XFile::Write(uint32_t buffer_guest_address, uint32_t buffer_length, uint64_t byte_offset, uint32_t* out_bytes_written, uint32_t apc_context) { if (byte_offset == uint64_t(-1)) { // Write from current position. byte_offset = position_; } size_t bytes_written = 0; X_STATUS result = file_->WriteSync(memory()->TranslateVirtual(buffer_guest_address), buffer_length, size_t(byte_offset), &bytes_written); if (XSUCCEEDED(result)) { position_ += bytes_written; } XIOCompletion::IONotification notify; notify.apc_context = apc_context; notify.num_bytes = uint32_t(bytes_written); notify.status = result; NotifyIOCompletionPorts(notify); if (out_bytes_written) { *out_bytes_written = uint32_t(bytes_written); } async_event_->Set(); return result; } X_STATUS XFile::SetLength(size_t length) { return file_->SetLength(length); } void XFile::RegisterIOCompletionPort(uint32_t key, object_ref port) { std::lock_guard lock(completion_port_lock_); completion_ports_.push_back({key, port}); } void XFile::RemoveIOCompletionPort(uint32_t key) { std::lock_guard lock(completion_port_lock_); for (auto it = completion_ports_.begin(); it != completion_ports_.end(); it++) { if (it->first == key) { completion_ports_.erase(it); break; } } } bool XFile::Save(ByteStream* stream) { XELOGD("XFile %.8X (%s)", handle(), file_->entry()->absolute_path().c_str()); if (!SaveObject(stream)) { return false; } stream->Write(file_->entry()->absolute_path()); stream->Write(position_); stream->Write(file_access()); stream->Write( (file_->entry()->attributes() & vfs::kFileAttributeDirectory) != 0); stream->Write(is_synchronous_); return true; } object_ref XFile::Restore(KernelState* kernel_state, ByteStream* stream) { auto file = new XFile(); file->kernel_state_ = kernel_state; if (!file->RestoreObject(stream)) { delete file; return nullptr; } auto abs_path = stream->Read(); uint64_t position = stream->Read(); auto access = stream->Read(); auto is_directory = stream->Read(); auto is_synchronous = stream->Read(); XELOGD("XFile %.8X (%s)", file->handle(), abs_path.c_str()); vfs::File* vfs_file = nullptr; vfs::FileAction action; auto res = kernel_state->file_system()->OpenFile( abs_path, vfs::FileDisposition::kOpen, access, is_directory, &vfs_file, &action); if (XFAILED(res)) { XELOGE("Failed to open XFile: error %.8X", res); return object_ref(file); } file->file_ = vfs_file; file->position_ = position; file->is_synchronous_ = is_synchronous; return object_ref(file); } void XFile::NotifyIOCompletionPorts( XIOCompletion::IONotification& notification) { std::lock_guard lock(completion_port_lock_); for (auto port : completion_ports_) { notification.key_context = port.first; port.second->QueueNotification(notification); } } } // namespace kernel } // namespace xe