Merge cleanup and fixes
This commit is contained in:
parent
127b465ccc
commit
ba46b6a6f8
|
@ -14,7 +14,9 @@
|
||||||
#include "xenia/cpu/ppc/ppc_context.h"
|
#include "xenia/cpu/ppc/ppc_context.h"
|
||||||
#include "xenia/cpu/ppc/ppc_hir_builder.h"
|
#include "xenia/cpu/ppc/ppc_hir_builder.h"
|
||||||
|
|
||||||
DEFINE_bool(UE_HACK, true, "Hack for Unreal Engine 3 titles to run", "CPU");
|
DEFINE_bool(UE_Workaround, true,
|
||||||
|
"Workaround for Unreal Engine 3 titles to run, try disabling if other games have problems",
|
||||||
|
"CPU");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
@ -1083,7 +1085,7 @@ int InstrEmit_stfsx(PPCHIRBuilder& f, const InstrData& i) {
|
||||||
|
|
||||||
int InstrEmit_dcbf(PPCHIRBuilder& f, const InstrData& i) {
|
int InstrEmit_dcbf(PPCHIRBuilder& f, const InstrData& i) {
|
||||||
//UE Hack
|
//UE Hack
|
||||||
if ((i.X.RB == 11) && (cvars::UE_HACK) && (f.CompareEQ(f.LoadGPR(i.X.RB), f.LoadConstantUint16(0xFEED)))) {
|
if ((i.X.RB == 11) && (cvars::UE_Workaround) && (f.CompareEQ(f.LoadGPR(i.X.RB), f.LoadConstantUint16(0xFEED)))) {
|
||||||
Value* val = f.Sub(f.LoadGPR(i.X.RB), f.LoadConstantUint64(0x0004));
|
Value* val = f.Sub(f.LoadGPR(i.X.RB), f.LoadConstantUint64(0x0004));
|
||||||
f.StoreGPR(i.X.RB, val);
|
f.StoreGPR(i.X.RB, val);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -659,6 +659,7 @@ X_STATUS Emulator::CompleteLaunch(const std::wstring& path,
|
||||||
module->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info);
|
module->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info);
|
||||||
if (info) {
|
if (info) {
|
||||||
title_id_ = info->title_id;
|
title_id_ = info->title_id;
|
||||||
|
xe::LogLineFormat(xe::LogLevel::Error, 'i', "Title ID : %.8X\n", title_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try and load the resource database (xex only).
|
// Try and load the resource database (xex only).
|
||||||
|
|
|
@ -1,856 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* 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/user_module.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/base/byte_stream.h"
|
|
||||||
#include "xenia/base/logging.h"
|
|
||||||
#include "xenia/cpu/elf_module.h"
|
|
||||||
#include "xenia/cpu/processor.h"
|
|
||||||
#include "xenia/cpu/xex_module.h"
|
|
||||||
#include "xenia/emulator.h"
|
|
||||||
#include "xenia/kernel/xfile.h"
|
|
||||||
#include "xenia/kernel/xthread.h"
|
|
||||||
#include "xenia/vfs/devices/stfs_container_device.h"
|
|
||||||
|
|
||||||
DEFINE_bool(xex_apply_patches, true, "Apply XEX patches.", "Kernel");
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
|
|
||||||
UserModule::UserModule(KernelState* kernel_state)
|
|
||||||
: XModule(kernel_state, ModuleType::kUserModule) {}
|
|
||||||
|
|
||||||
UserModule::~UserModule() { Unload(); }
|
|
||||||
|
|
||||||
uint32_t UserModule::title_id() const {
|
|
||||||
if (module_format_ != kModuleFormatXex) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
auto header = xex_header();
|
|
||||||
for (uint32_t i = 0; i < header->header_count; i++) {
|
|
||||||
auto& opt_header = header->headers[i];
|
|
||||||
if (opt_header.key == XEX_HEADER_EXECUTION_INFO) {
|
|
||||||
auto opt_header_ptr =
|
|
||||||
reinterpret_cast<const uint8_t*>(header) + opt_header.offset;
|
|
||||||
auto opt_exec_info =
|
|
||||||
reinterpret_cast<const xex2_opt_execution_info*>(opt_header_ptr);
|
|
||||||
return static_cast<uint32_t>(opt_exec_info->title_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::LoadFromFile(std::string path) {
|
|
||||||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
|
||||||
|
|
||||||
auto file_system = kernel_state()->file_system();
|
|
||||||
|
|
||||||
// Resolve the file to open.
|
|
||||||
// TODO(benvanik): make this code shared?
|
|
||||||
auto fs_entry = file_system->ResolvePath(path);
|
|
||||||
if (!fs_entry) {
|
|
||||||
XELOGE("File not found: %s", path.c_str());
|
|
||||||
return X_STATUS_NO_SUCH_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
path_ = fs_entry->absolute_path();
|
|
||||||
name_ = NameFromPath(path_);
|
|
||||||
|
|
||||||
// If the FS supports mapping, map the file in and load from that.
|
|
||||||
if (fs_entry->can_map()) {
|
|
||||||
// Map.
|
|
||||||
auto mmap = fs_entry->OpenMapped(MappedMemory::Mode::kRead);
|
|
||||||
if (!mmap) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the module.
|
|
||||||
result = LoadFromMemory(mmap->data(), mmap->size());
|
|
||||||
} else {
|
|
||||||
std::vector<uint8_t> buffer(fs_entry->size());
|
|
||||||
|
|
||||||
// Open file for reading.
|
|
||||||
vfs::File* file = nullptr;
|
|
||||||
result = fs_entry->Open(vfs::FileAccess::kGenericRead, &file);
|
|
||||||
if (XFAILED(result)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read entire file into memory.
|
|
||||||
// Ugh.
|
|
||||||
size_t bytes_read = 0;
|
|
||||||
result = file->ReadSync(buffer.data(), buffer.size(), 0, &bytes_read);
|
|
||||||
if (XFAILED(result)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the module.
|
|
||||||
result = LoadFromMemory(buffer.data(), bytes_read);
|
|
||||||
|
|
||||||
// Close the file.
|
|
||||||
file->Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only XEX returns X_STATUS_PENDING
|
|
||||||
if (result != X_STATUS_PENDING) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
if (cvars::xex_apply_patches) {
|
|
||||||
// Search for xexp patch file
|
|
||||||
auto patch_entry = kernel_state()->file_system()->ResolvePath(path_ + "p");
|
|
||||||
=======
|
|
||||||
if (!FLAGS_xex_apply_patches) {
|
|
||||||
return LoadXexContinue();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto module_path = fs_entry->path();
|
|
||||||
|
|
||||||
auto content_manager = kernel_state()->content_manager();
|
|
||||||
|
|
||||||
if (!file_system->IsSymbolicLink("update:")) {
|
|
||||||
// update:\\ path isn't symlinked, try searching for an update package
|
|
||||||
>>>>>>> emoose/title-updates
|
|
||||||
|
|
||||||
xex2_opt_execution_info* exec_info = 0;
|
|
||||||
xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &exec_info);
|
|
||||||
|
|
||||||
if (exec_info) {
|
|
||||||
content_manager->SetTitleIdOverride(exec_info->title_id);
|
|
||||||
|
|
||||||
auto update_packages = content_manager->ListContent(
|
|
||||||
0, (uint32_t)vfs::StfsContentType::kInstaller);
|
|
||||||
|
|
||||||
for (auto& update : update_packages) {
|
|
||||||
auto result = content_manager->OpenContent("update", update);
|
|
||||||
|
|
||||||
if (!file_system->ResolvePath("update:\\" + module_path + "p")) {
|
|
||||||
// XEXP/DLLP doesn't exist in this package, lets just close it
|
|
||||||
content_manager->CloseContent("update");
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// XEXP/DLLP found, break out of package loop
|
|
||||||
// TODO: verify XEXP/DLLP works first?
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unset content_manager title ID override
|
|
||||||
content_manager->SetTitleIdOverride(0);
|
|
||||||
|
|
||||||
// First try checking update:\ root for patch, otherwise try same path as XEX
|
|
||||||
auto patch_entry = file_system->ResolvePath("update:\\" + module_path + "p");
|
|
||||||
if (!patch_entry) {
|
|
||||||
patch_entry = file_system->ResolvePath(path_ + "p");
|
|
||||||
}
|
|
||||||
if (patch_entry) {
|
|
||||||
auto patch_path = patch_entry->absolute_path();
|
|
||||||
|
|
||||||
XELOGI("Loading XEX patch from %s", patch_path.c_str());
|
|
||||||
|
|
||||||
auto patch_module = object_ref<UserModule>(new UserModule(kernel_state_));
|
|
||||||
result = patch_module->LoadFromFile(patch_path);
|
|
||||||
if (!result) {
|
|
||||||
result = patch_module->xex_module()->ApplyPatch(xex_module());
|
|
||||||
if (result) {
|
|
||||||
XELOGE("Failed to apply XEX patch, code: %d", result);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
XELOGE("Failed to load XEX patch, code: %d", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadXexContinue();
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::LoadFromMemory(const void* addr, const size_t length) {
|
|
||||||
auto processor = kernel_state()->processor();
|
|
||||||
|
|
||||||
auto magic = xe::load_and_swap<uint32_t>(addr);
|
|
||||||
if (magic == 'XEX2') {
|
|
||||||
module_format_ = kModuleFormatXex;
|
|
||||||
} else if (magic == 0x7F454C46 /* 0x7F 'ELF' */) {
|
|
||||||
module_format_ = kModuleFormatElf;
|
|
||||||
} else {
|
|
||||||
auto magic16 = xe::load_and_swap<uint16_t>(addr);
|
|
||||||
if (magic16 == 0x4D5A) {
|
|
||||||
XELOGE("XNA executables are not yet implemented");
|
|
||||||
return X_STATUS_NOT_IMPLEMENTED;
|
|
||||||
} else {
|
|
||||||
XELOGE("Unknown module magic: %.8X", magic);
|
|
||||||
return X_STATUS_NOT_IMPLEMENTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module_format_ == kModuleFormatXex) {
|
|
||||||
// Prepare the module for execution.
|
|
||||||
// Runtime takes ownership.
|
|
||||||
auto xex_module =
|
|
||||||
std::make_unique<cpu::XexModule>(processor, kernel_state());
|
|
||||||
if (!xex_module->Load(name_, path_, addr, length)) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
processor_module_ = xex_module.get();
|
|
||||||
if (!processor->AddModule(std::move(xex_module))) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only XEX headers + image are loaded right now
|
|
||||||
// Caller will have to call LoadXexContinue after they've loaded in a patch
|
|
||||||
// (or after patch isn't found anywhere)
|
|
||||||
// or if this is an XEXP being loaded return success since there's nothing
|
|
||||||
// else to load
|
|
||||||
return this->xex_module()->is_patch() ? X_STATUS_SUCCESS : X_STATUS_PENDING;
|
|
||||||
|
|
||||||
} else if (module_format_ == kModuleFormatElf) {
|
|
||||||
auto elf_module =
|
|
||||||
std::make_unique<cpu::ElfModule>(processor, kernel_state());
|
|
||||||
if (!elf_module->Load(name_, path_, addr, length)) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry_point_ = elf_module->entry_point();
|
|
||||||
stack_size_ = 1024 * 1024; // 1 MB
|
|
||||||
is_dll_module_ = false; // Hardcoded not a DLL (for now)
|
|
||||||
|
|
||||||
processor_module_ = elf_module.get();
|
|
||||||
if (!processor->AddModule(std::move(elf_module))) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnLoad();
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::LoadXexContinue() {
|
|
||||||
// LoadXexContinue: finishes loading XEX after a patch has been applied (or
|
|
||||||
// patch wasn't found)
|
|
||||||
|
|
||||||
if (!this->xex_module()) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If guest_xex_header is set we must have already loaded the XEX
|
|
||||||
if (guest_xex_header_) {
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish XexModule load (PE sections/imports/symbols...)
|
|
||||||
if (!xex_module()->LoadContinue()) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the xex2 header into guest memory.
|
|
||||||
auto header = this->xex_module()->xex_header();
|
|
||||||
auto security_header = this->xex_module()->xex_security_info();
|
|
||||||
guest_xex_header_ = memory()->SystemHeapAlloc(header->header_size);
|
|
||||||
|
|
||||||
uint8_t* xex_header_ptr = memory()->TranslateVirtual(guest_xex_header_);
|
|
||||||
std::memcpy(xex_header_ptr, header, header->header_size);
|
|
||||||
|
|
||||||
// Cache some commonly used headers...
|
|
||||||
this->xex_module()->GetOptHeader(XEX_HEADER_ENTRY_POINT, &entry_point_);
|
|
||||||
this->xex_module()->GetOptHeader(XEX_HEADER_DEFAULT_STACK_SIZE, &stack_size_);
|
|
||||||
is_dll_module_ = !!(header->module_flags & XEX_MODULE_DLL_MODULE);
|
|
||||||
|
|
||||||
// Setup the loader data entry
|
|
||||||
auto ldr_data =
|
|
||||||
memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule_ptr_);
|
|
||||||
|
|
||||||
ldr_data->dll_base = 0; // GetProcAddress will read this.
|
|
||||||
ldr_data->xex_header_base = guest_xex_header_;
|
|
||||||
ldr_data->full_image_size = security_header->image_size;
|
|
||||||
ldr_data->image_base = this->xex_module()->base_address();
|
|
||||||
ldr_data->entry_point = entry_point_;
|
|
||||||
|
|
||||||
OnLoad();
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::Unload() {
|
|
||||||
if (module_format_ == kModuleFormatXex &&
|
|
||||||
(!processor_module_ || !xex_module()->loaded())) {
|
|
||||||
// Quick abort.
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module_format_ == kModuleFormatXex && processor_module_ &&
|
|
||||||
xex_module()->Unload()) {
|
|
||||||
OnUnload();
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t UserModule::GetProcAddressByOrdinal(uint16_t ordinal) {
|
|
||||||
return xex_module()->GetProcAddress(ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t UserModule::GetProcAddressByName(const char* name) {
|
|
||||||
return xex_module()->GetProcAddress(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::GetSection(const char* name, uint32_t* out_section_data,
|
|
||||||
uint32_t* out_section_size) {
|
|
||||||
xex2_opt_resource_info* resource_header = nullptr;
|
|
||||||
if (!cpu::XexModule::GetOptHeader(xex_header(), XEX_HEADER_RESOURCE_INFO,
|
|
||||||
&resource_header)) {
|
|
||||||
// No resources.
|
|
||||||
return X_STATUS_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t count = (resource_header->size - 4) / sizeof(xex2_resource);
|
|
||||||
for (uint32_t i = 0; i < count; i++) {
|
|
||||||
auto& res = resource_header->resources[i];
|
|
||||||
if (std::strncmp(name, res.name, 8) == 0) {
|
|
||||||
// Found!
|
|
||||||
*out_section_data = res.address;
|
|
||||||
*out_section_size = res.size;
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_STATUS_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::GetOptHeader(xex2_header_keys key, void** out_ptr) {
|
|
||||||
assert_not_null(out_ptr);
|
|
||||||
|
|
||||||
if (module_format_ == kModuleFormatElf) {
|
|
||||||
// Quick die.
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ret = xex_module()->GetOptHeader(key, out_ptr);
|
|
||||||
if (!ret) {
|
|
||||||
return X_STATUS_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::GetOptHeader(xex2_header_keys key,
|
|
||||||
uint32_t* out_header_guest_ptr) {
|
|
||||||
if (module_format_ == kModuleFormatElf) {
|
|
||||||
// Quick die.
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto header =
|
|
||||||
memory()->TranslateVirtual<const xex2_header*>(guest_xex_header_);
|
|
||||||
if (!header) {
|
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
|
||||||
}
|
|
||||||
return GetOptHeader(memory(), header, key, out_header_guest_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS UserModule::GetOptHeader(const Memory* memory,
|
|
||||||
const xex2_header* header,
|
|
||||||
xex2_header_keys key,
|
|
||||||
uint32_t* out_header_guest_ptr) {
|
|
||||||
assert_not_null(out_header_guest_ptr);
|
|
||||||
uint32_t field_value = 0;
|
|
||||||
bool field_found = false;
|
|
||||||
for (uint32_t i = 0; i < header->header_count; i++) {
|
|
||||||
auto& opt_header = header->headers[i];
|
|
||||||
if (opt_header.key != key) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
field_found = true;
|
|
||||||
switch (opt_header.key & 0xFF) {
|
|
||||||
case 0x00:
|
|
||||||
// Return data stored in header value.
|
|
||||||
field_value = opt_header.value;
|
|
||||||
break;
|
|
||||||
case 0x01:
|
|
||||||
// Return pointer to data stored in header value.
|
|
||||||
field_value = memory->HostToGuestVirtual(&opt_header.value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Data stored at offset to header.
|
|
||||||
field_value = memory->HostToGuestVirtual(header) + opt_header.offset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*out_header_guest_ptr = field_value;
|
|
||||||
if (!field_found) {
|
|
||||||
return X_STATUS_NOT_FOUND;
|
|
||||||
}
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UserModule::Save(ByteStream* stream) {
|
|
||||||
if (!XModule::Save(stream)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A lot of the information stored on this class can be reconstructed at
|
|
||||||
// runtime.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
object_ref<UserModule> UserModule::Restore(KernelState* kernel_state,
|
|
||||||
ByteStream* stream,
|
|
||||||
std::string path) {
|
|
||||||
auto module = new UserModule(kernel_state);
|
|
||||||
|
|
||||||
// XModule::Save took care of this earlier...
|
|
||||||
// TODO: Find a nicer way to represent that here.
|
|
||||||
if (!module->RestoreObject(stream)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = module->LoadFromFile(path);
|
|
||||||
if (XFAILED(result)) {
|
|
||||||
XELOGD("UserModule::Restore LoadFromFile(%s) FAILED - code %.8X",
|
|
||||||
path.c_str(), result);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kernel_state->RegisterUserModule(retain_object(module))) {
|
|
||||||
// Already loaded?
|
|
||||||
assert_always();
|
|
||||||
}
|
|
||||||
|
|
||||||
return object_ref<UserModule>(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserModule::Dump() {
|
|
||||||
if (module_format_ == kModuleFormatElf) {
|
|
||||||
// Quick die.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuffer sb;
|
|
||||||
|
|
||||||
xe::cpu::ExportResolver* export_resolver =
|
|
||||||
kernel_state_->emulator()->export_resolver();
|
|
||||||
auto header = xex_header();
|
|
||||||
|
|
||||||
// XEX header.
|
|
||||||
sb.AppendFormat("Module %s:\n", path_.c_str());
|
|
||||||
sb.AppendFormat(" Module Flags: %.8X\n", (uint32_t)header->module_flags);
|
|
||||||
|
|
||||||
// Security header
|
|
||||||
auto security_info = xex_module()->xex_security_info();
|
|
||||||
sb.AppendFormat("Security Header:\n");
|
|
||||||
sb.AppendFormat(" Image Flags: %.8X\n",
|
|
||||||
(uint32_t)security_info->image_flags);
|
|
||||||
sb.AppendFormat(" Load Address: %.8X\n",
|
|
||||||
(uint32_t)security_info->load_address);
|
|
||||||
sb.AppendFormat(" Image Size: %.8X\n",
|
|
||||||
(uint32_t)security_info->image_size);
|
|
||||||
sb.AppendFormat(" Export Table: %.8X\n",
|
|
||||||
(uint32_t)security_info->export_table);
|
|
||||||
|
|
||||||
// Optional headers
|
|
||||||
sb.AppendFormat("Optional Header Count: %d\n",
|
|
||||||
(uint32_t)header->header_count);
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < header->header_count; i++) {
|
|
||||||
auto& opt_header = header->headers[i];
|
|
||||||
|
|
||||||
// Stash a pointer (although this isn't used in every case)
|
|
||||||
auto opt_header_ptr =
|
|
||||||
reinterpret_cast<const uint8_t*>(header) + opt_header.offset;
|
|
||||||
switch (opt_header.key) {
|
|
||||||
case XEX_HEADER_RESOURCE_INFO: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_RESOURCE_INFO:\n");
|
|
||||||
auto opt_resource_info =
|
|
||||||
reinterpret_cast<const xex2_opt_resource_info*>(opt_header_ptr);
|
|
||||||
|
|
||||||
uint32_t count = (opt_resource_info->size - 4) / 16;
|
|
||||||
for (uint32_t j = 0; j < count; j++) {
|
|
||||||
auto& res = opt_resource_info->resources[j];
|
|
||||||
|
|
||||||
// Manually NULL-terminate the name.
|
|
||||||
char name[9];
|
|
||||||
std::memcpy(name, res.name, sizeof(res.name));
|
|
||||||
name[8] = 0;
|
|
||||||
|
|
||||||
sb.AppendFormat(
|
|
||||||
" %-8s %.8X-%.8X, %db\n", name, (uint32_t)res.address,
|
|
||||||
(uint32_t)res.address + (uint32_t)res.size, (uint32_t)res.size);
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_FILE_FORMAT_INFO: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_FILE_FORMAT_INFO (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_DELTA_PATCH_DESCRIPTOR: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_DELTA_PATCH_DESCRIPTOR (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_BOUNDING_PATH: {
|
|
||||||
auto opt_bound_path =
|
|
||||||
reinterpret_cast<const xex2_opt_bound_path*>(opt_header_ptr);
|
|
||||||
sb.AppendFormat(" XEX_HEADER_BOUNDING_PATH: %s\n",
|
|
||||||
opt_bound_path->path);
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_ORIGINAL_BASE_ADDRESS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_ORIGINAL_BASE_ADDRESS: %.8X\n",
|
|
||||||
(uint32_t)opt_header.value);
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_ENTRY_POINT: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_ENTRY_POINT: %.8X\n",
|
|
||||||
(uint32_t)opt_header.value);
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_IMAGE_BASE_ADDRESS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_IMAGE_BASE_ADDRESS: %.8X\n",
|
|
||||||
(uint32_t)opt_header.value);
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_IMPORT_LIBRARIES: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_IMPORT_LIBRARIES:\n");
|
|
||||||
auto opt_import_libraries =
|
|
||||||
reinterpret_cast<const xex2_opt_import_libraries*>(opt_header_ptr);
|
|
||||||
|
|
||||||
// FIXME: Don't know if 32 is the actual limit, but haven't seen more
|
|
||||||
// than 2.
|
|
||||||
const char* string_table[32];
|
|
||||||
std::memset(string_table, 0, sizeof(string_table));
|
|
||||||
|
|
||||||
// Parse the string table
|
|
||||||
for (size_t j = 0, o = 0; j < opt_import_libraries->string_table.size &&
|
|
||||||
o < opt_import_libraries->string_table.count;
|
|
||||||
o++) {
|
|
||||||
assert_true(o < xe::countof(string_table));
|
|
||||||
const char* str = &opt_import_libraries->string_table.data[j];
|
|
||||||
|
|
||||||
string_table[o] = str;
|
|
||||||
j += std::strlen(str) + 1;
|
|
||||||
|
|
||||||
// Padding
|
|
||||||
if ((j % 4) != 0) {
|
|
||||||
j += 4 - (j % 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto library_data =
|
|
||||||
reinterpret_cast<const uint8_t*>(opt_import_libraries);
|
|
||||||
uint32_t library_offset = opt_import_libraries->string_table.size + 12;
|
|
||||||
while (library_offset < opt_import_libraries->size) {
|
|
||||||
auto library = reinterpret_cast<const xex2_import_library*>(
|
|
||||||
library_data + library_offset);
|
|
||||||
if (!library->size) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto name = string_table[library->name_index & 0xFF];
|
|
||||||
assert_not_null(name);
|
|
||||||
sb.AppendFormat(" %s - %d imports\n", name,
|
|
||||||
(uint16_t)library->count);
|
|
||||||
|
|
||||||
// Manually byteswap these because of the bitfields.
|
|
||||||
xex2_version version, version_min;
|
|
||||||
version.value = xe::byte_swap<uint32_t>(library->version.value);
|
|
||||||
version_min.value =
|
|
||||||
xe::byte_swap<uint32_t>(library->version_min.value);
|
|
||||||
sb.AppendFormat(" Version: %d.%d.%d.%d\n", version.major,
|
|
||||||
version.minor, version.build, version.qfe);
|
|
||||||
sb.AppendFormat(" Min Version: %d.%d.%d.%d\n", version_min.major,
|
|
||||||
version_min.minor, version_min.build,
|
|
||||||
version_min.qfe);
|
|
||||||
|
|
||||||
library_offset += library->size;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_CHECKSUM_TIMESTAMP: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_CHECKSUM_TIMESTAMP (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_ORIGINAL_PE_NAME: {
|
|
||||||
auto opt_pe_name =
|
|
||||||
reinterpret_cast<const xex2_opt_original_pe_name*>(opt_header_ptr);
|
|
||||||
sb.AppendFormat(" XEX_HEADER_ORIGINAL_PE_NAME: %s\n",
|
|
||||||
opt_pe_name->name);
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_STATIC_LIBRARIES: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_STATIC_LIBRARIES:\n");
|
|
||||||
auto opt_static_libraries =
|
|
||||||
reinterpret_cast<const xex2_opt_static_libraries*>(opt_header_ptr);
|
|
||||||
|
|
||||||
uint32_t count = (opt_static_libraries->size - 4) / 0x10;
|
|
||||||
for (uint32_t l = 0; l < count; l++) {
|
|
||||||
auto& library = opt_static_libraries->libraries[l];
|
|
||||||
sb.AppendFormat(" %-8s : %d.%d.%d.%d\n", library.name,
|
|
||||||
static_cast<uint16_t>(library.version_major),
|
|
||||||
static_cast<uint16_t>(library.version_minor),
|
|
||||||
static_cast<uint16_t>(library.version_build),
|
|
||||||
static_cast<uint16_t>(library.version_qfe));
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_TLS_INFO: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_TLS_INFO:\n");
|
|
||||||
auto opt_tls_info =
|
|
||||||
reinterpret_cast<const xex2_opt_tls_info*>(opt_header_ptr);
|
|
||||||
|
|
||||||
sb.AppendFormat(" Slot Count: %d\n",
|
|
||||||
static_cast<uint32_t>(opt_tls_info->slot_count));
|
|
||||||
sb.AppendFormat(" Raw Data Address: %.8X\n",
|
|
||||||
static_cast<uint32_t>(opt_tls_info->raw_data_address));
|
|
||||||
sb.AppendFormat(" Data Size: %d\n",
|
|
||||||
static_cast<uint32_t>(opt_tls_info->data_size));
|
|
||||||
sb.AppendFormat(" Raw Data Size: %d\n",
|
|
||||||
static_cast<uint32_t>(opt_tls_info->raw_data_size));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_DEFAULT_STACK_SIZE: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_DEFAULT_STACK_SIZE: %d\n",
|
|
||||||
static_cast<uint32_t>(opt_header.value));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE: %d\n",
|
|
||||||
static_cast<uint32_t>(opt_header.value));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_DEFAULT_HEAP_SIZE: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_DEFAULT_HEAP_SIZE: %d\n",
|
|
||||||
static_cast<uint32_t>(opt_header.value));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_SYSTEM_FLAGS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_SYSTEM_FLAGS: %.8X\n",
|
|
||||||
static_cast<uint32_t>(opt_header.value));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_EXECUTION_INFO: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_EXECUTION_INFO:\n");
|
|
||||||
auto opt_exec_info =
|
|
||||||
reinterpret_cast<const xex2_opt_execution_info*>(opt_header_ptr);
|
|
||||||
|
|
||||||
sb.AppendFormat(" Media ID: %.8X\n",
|
|
||||||
static_cast<uint32_t>(opt_exec_info->media_id));
|
|
||||||
sb.AppendFormat(" Title ID: %.8X\n",
|
|
||||||
static_cast<uint32_t>(opt_exec_info->title_id));
|
|
||||||
sb.AppendFormat(" Savegame ID: %.8X\n",
|
|
||||||
static_cast<uint32_t>(opt_exec_info->title_id));
|
|
||||||
sb.AppendFormat(" Disc Number / Total: %d / %d\n",
|
|
||||||
opt_exec_info->disc_number, opt_exec_info->disc_count);
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_TITLE_WORKSPACE_SIZE: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_TITLE_WORKSPACE_SIZE: %d\n",
|
|
||||||
uint32_t(opt_header.value));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_GAME_RATINGS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_GAME_RATINGS (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_LAN_KEY: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_LAN_KEY:");
|
|
||||||
auto opt_lan_key =
|
|
||||||
reinterpret_cast<const xex2_opt_lan_key*>(opt_header_ptr);
|
|
||||||
|
|
||||||
for (int l = 0; l < 16; l++) {
|
|
||||||
sb.AppendFormat(" %.2X", opt_lan_key->key[l]);
|
|
||||||
}
|
|
||||||
sb.Append("\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_XBOX360_LOGO: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_XBOX360_LOGO (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_MULTIDISC_MEDIA_IDS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_MULTIDISC_MEDIA_IDS (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_ALTERNATE_TITLE_IDS: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_ALTERNATE_TITLE_IDS (TODO):\n");
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_ADDITIONAL_TITLE_MEMORY: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_ADDITIONAL_TITLE_MEMORY: %d\n",
|
|
||||||
uint32_t(opt_header.value));
|
|
||||||
} break;
|
|
||||||
case XEX_HEADER_EXPORTS_BY_NAME: {
|
|
||||||
sb.AppendFormat(" XEX_HEADER_EXPORTS_BY_NAME:\n");
|
|
||||||
auto dir =
|
|
||||||
reinterpret_cast<const xex2_opt_data_directory*>(opt_header_ptr);
|
|
||||||
|
|
||||||
auto exe_address = xex_module()->base_address();
|
|
||||||
auto e = memory()->TranslateVirtual<const X_IMAGE_EXPORT_DIRECTORY*>(
|
|
||||||
exe_address + dir->offset);
|
|
||||||
auto e_base = reinterpret_cast<uintptr_t>(e);
|
|
||||||
|
|
||||||
// e->AddressOfX RVAs are relative to the IMAGE_EXPORT_DIRECTORY!
|
|
||||||
auto function_table =
|
|
||||||
reinterpret_cast<const uint32_t*>(e_base + e->AddressOfFunctions);
|
|
||||||
// Names relative to directory.
|
|
||||||
auto name_table =
|
|
||||||
reinterpret_cast<const uint32_t*>(e_base + e->AddressOfNames);
|
|
||||||
// Table of ordinals (by name).
|
|
||||||
auto ordinal_table = reinterpret_cast<const uint16_t*>(
|
|
||||||
e_base + e->AddressOfNameOrdinals);
|
|
||||||
for (uint32_t n = 0; n < e->NumberOfNames; n++) {
|
|
||||||
auto name = reinterpret_cast<const char*>(e_base + name_table[n]);
|
|
||||||
uint16_t ordinal = ordinal_table[n];
|
|
||||||
uint32_t addr = exe_address + function_table[ordinal];
|
|
||||||
sb.AppendFormat(" %-28s - %.3X - %.8X\n", name, ordinal, addr);
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
default: {
|
|
||||||
sb.AppendFormat(" Unknown Header %.8X\n", (uint32_t)opt_header.key);
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendFormat("Sections:\n");
|
|
||||||
for (uint32_t i = 0, page = 0; i < security_info->page_descriptor_count;
|
|
||||||
i++) {
|
|
||||||
// Manually byteswap the bitfield data.
|
|
||||||
xex2_page_descriptor page_descriptor;
|
|
||||||
page_descriptor.value =
|
|
||||||
xe::byte_swap(security_info->page_descriptors[i].value);
|
|
||||||
|
|
||||||
const char* type = "UNKNOWN";
|
|
||||||
switch (page_descriptor.info) {
|
|
||||||
case XEX_SECTION_CODE:
|
|
||||||
type = "CODE ";
|
|
||||||
break;
|
|
||||||
case XEX_SECTION_DATA:
|
|
||||||
type = "RWDATA ";
|
|
||||||
break;
|
|
||||||
case XEX_SECTION_READONLY_DATA:
|
|
||||||
type = "RODATA ";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32_t page_size =
|
|
||||||
xex_module()->base_address() < 0x90000000 ? 64 * 1024 : 4 * 1024;
|
|
||||||
uint32_t start_address = xex_module()->base_address() + (page * page_size);
|
|
||||||
uint32_t end_address =
|
|
||||||
start_address + (page_descriptor.page_count * page_size);
|
|
||||||
|
|
||||||
sb.AppendFormat(" %3u %s %3u pages %.8X - %.8X (%d bytes)\n", page,
|
|
||||||
type, page_descriptor.page_count, start_address,
|
|
||||||
end_address, page_descriptor.page_count * page_size);
|
|
||||||
page += page_descriptor.page_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print out imports.
|
|
||||||
|
|
||||||
auto import_libs = xex_module()->import_libraries();
|
|
||||||
|
|
||||||
sb.AppendFormat("Imports:\n");
|
|
||||||
for (std::vector<cpu::XexModule::ImportLibrary>::const_iterator library =
|
|
||||||
import_libs->begin();
|
|
||||||
library != import_libs->end(); ++library) {
|
|
||||||
if (library->imports.size() > 0) {
|
|
||||||
sb.AppendFormat(" %s - %lld imports\n", library->name.c_str(),
|
|
||||||
library->imports.size());
|
|
||||||
sb.AppendFormat(" Version: %d.%d.%d.%d\n", library->version.major,
|
|
||||||
library->version.minor, library->version.build,
|
|
||||||
library->version.qfe);
|
|
||||||
sb.AppendFormat(" Min Version: %d.%d.%d.%d\n",
|
|
||||||
library->min_version.major, library->min_version.minor,
|
|
||||||
library->min_version.build, library->min_version.qfe);
|
|
||||||
sb.AppendFormat("\n");
|
|
||||||
|
|
||||||
// Counts.
|
|
||||||
int known_count = 0;
|
|
||||||
int unknown_count = 0;
|
|
||||||
int impl_count = 0;
|
|
||||||
int unimpl_count = 0;
|
|
||||||
|
|
||||||
for (std::vector<cpu::XexModule::ImportLibraryFn>::const_iterator info =
|
|
||||||
library->imports.begin();
|
|
||||||
info != library->imports.end(); ++info) {
|
|
||||||
if (kernel_state_->IsKernelModule(library->name.c_str())) {
|
|
||||||
auto kernel_export = export_resolver->GetExportByOrdinal(
|
|
||||||
library->name.c_str(), info->ordinal);
|
|
||||||
if (kernel_export) {
|
|
||||||
known_count++;
|
|
||||||
if (kernel_export->is_implemented()) {
|
|
||||||
impl_count++;
|
|
||||||
} else {
|
|
||||||
unimpl_count++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unknown_count++;
|
|
||||||
unimpl_count++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto module = kernel_state_->GetModule(library->name.c_str());
|
|
||||||
if (module) {
|
|
||||||
uint32_t export_addr =
|
|
||||||
module->GetProcAddressByOrdinal(info->ordinal);
|
|
||||||
if (export_addr) {
|
|
||||||
impl_count++;
|
|
||||||
known_count++;
|
|
||||||
} else {
|
|
||||||
unimpl_count++;
|
|
||||||
unknown_count++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unimpl_count++;
|
|
||||||
unknown_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
float total_count = static_cast<float>(library->imports.size()) / 100.0f;
|
|
||||||
sb.AppendFormat(" Total: %4llu\n", library->imports.size());
|
|
||||||
sb.AppendFormat(" Known: %3d%% (%d known, %d unknown)\n",
|
|
||||||
static_cast<int>(known_count / total_count), known_count,
|
|
||||||
unknown_count);
|
|
||||||
sb.AppendFormat(
|
|
||||||
" Implemented: %3d%% (%d implemented, %d unimplemented)\n",
|
|
||||||
static_cast<int>(impl_count / total_count), impl_count, unimpl_count);
|
|
||||||
sb.AppendFormat("\n");
|
|
||||||
|
|
||||||
// Listing.
|
|
||||||
for (std::vector<cpu::XexModule::ImportLibraryFn>::const_iterator info =
|
|
||||||
library->imports.begin();
|
|
||||||
info != library->imports.end(); ++info) {
|
|
||||||
const char* name = "UNKNOWN";
|
|
||||||
bool implemented = false;
|
|
||||||
|
|
||||||
cpu::Export* kernel_export = nullptr;
|
|
||||||
if (kernel_state_->IsKernelModule(library->name.c_str())) {
|
|
||||||
kernel_export = export_resolver->GetExportByOrdinal(
|
|
||||||
library->name.c_str(), info->ordinal);
|
|
||||||
if (kernel_export) {
|
|
||||||
name = kernel_export->name;
|
|
||||||
implemented = kernel_export->is_implemented();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto module = kernel_state_->GetModule(library->name.c_str());
|
|
||||||
if (module && module->GetProcAddressByOrdinal(info->ordinal)) {
|
|
||||||
// TODO(benvanik): name lookup.
|
|
||||||
implemented = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (kernel_export &&
|
|
||||||
kernel_export->type == cpu::Export::Type::kVariable) {
|
|
||||||
sb.AppendFormat(" V %.8X %.3X (%4d) %s %s\n",
|
|
||||||
info->value_address, info->ordinal, info->ordinal,
|
|
||||||
implemented ? " " : "!!", name);
|
|
||||||
} else if (info->thunk_address) {
|
|
||||||
sb.AppendFormat(" F %.8X %.8X %.3X (%4d) %s %s\n",
|
|
||||||
info->value_address, info->thunk_address,
|
|
||||||
info->ordinal, info->ordinal,
|
|
||||||
implemented ? " " : "!!", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendFormat("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
xe::LogLine(xe::LogLevel::Info, 'i', sb.GetString());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
|
@ -110,7 +110,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||||
}
|
}
|
||||||
case 0x000B0011: {
|
case 0x000B0011: {
|
||||||
// TODO(PermaNull): reverse buffer contents.
|
// TODO(PermaNull): reverse buffer contents.
|
||||||
//TEST
|
// TEST
|
||||||
XELOGD("XGISessionDelete");
|
XELOGD("XGISessionDelete");
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -128,12 +128,12 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
case 0x000B0014: {
|
case 0x000B0014: {
|
||||||
//TEST Gets Jetpac XBLA in game
|
// TEST Gets Jetpac XBLA in game
|
||||||
XELOGD("XGI_unknown");
|
XELOGD("XGI_unknown");
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
case 0x000B0015: {
|
case 0x000B0015: {
|
||||||
//TEST Gets Jetpac XBLA in game
|
// TEST Gets Jetpac XBLA in game
|
||||||
XELOGD("XGI_unknown");
|
XELOGD("XGI_unknown");
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,6 @@ constexpr const wchar_t* const ContentManager::kStfsHeadersExtension;
|
||||||
|
|
||||||
static const wchar_t* kGameUserContentDirName = L"profile";
|
static const wchar_t* kGameUserContentDirName = L"profile";
|
||||||
|
|
||||||
static int content_device_id_ = 0;
|
|
||||||
|
|
||||||
|
|
||||||
ContentManager::ContentManager(KernelState* kernel_state,
|
ContentManager::ContentManager(KernelState* kernel_state,
|
||||||
std::wstring root_path)
|
std::wstring root_path)
|
||||||
: kernel_state_(kernel_state), root_path_(std::move(root_path)) {}
|
: kernel_state_(kernel_state), root_path_(std::move(root_path)) {}
|
||||||
|
@ -54,6 +51,30 @@ std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type) {
|
||||||
wchar_t content_type_str[9] = L"00000000";
|
wchar_t content_type_str[9] = L"00000000";
|
||||||
std::swprintf(content_type_str, 9, L"%.8X", content_type);
|
std::swprintf(content_type_str, 9, L"%.8X", content_type);
|
||||||
|
|
||||||
|
std::wstring type_name;
|
||||||
|
switch (content_type) {
|
||||||
|
case 1:
|
||||||
|
// Save games.
|
||||||
|
type_name = L"00000001";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// DLC from the marketplace.
|
||||||
|
type_name = L"00000002";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Publisher content?
|
||||||
|
type_name = L"00000003";
|
||||||
|
break;
|
||||||
|
case 0x000D0000:
|
||||||
|
// ???
|
||||||
|
type_name = L"000D0000";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type_name = L"00000000";
|
||||||
|
//assert_unhandled_case(data.content_type);
|
||||||
|
//return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Package root path:
|
// Package root path:
|
||||||
// content_root/title_id/type_name/
|
// content_root/title_id/type_name/
|
||||||
auto package_root = xe::join_paths(
|
auto package_root = xe::join_paths(
|
||||||
|
|
|
@ -1,354 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/kernel/xam/content_manager.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "xenia/base/filesystem.h"
|
|
||||||
#include "xenia/base/string.h"
|
|
||||||
#include "xenia/kernel/kernel_state.h"
|
|
||||||
<<<<<<< HEAD
|
|
||||||
#include "xenia/kernel/user_module.h"
|
|
||||||
=======
|
|
||||||
#include "xenia/kernel/xam/content_package.h"
|
|
||||||
>>>>>>> emoose/stfs-packages
|
|
||||||
#include "xenia/kernel/xobject.h"
|
|
||||||
#include "xenia/vfs/devices/host_path_device.h"
|
|
||||||
#include "xenia/vfs/devices/stfs_container_device.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
namespace xam {
|
|
||||||
|
|
||||||
constexpr const wchar_t* const ContentManager::kStfsHeadersExtension;
|
|
||||||
|
|
||||||
static const wchar_t* kGameUserContentDirName = L"profile";
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
static int content_device_id_ = 0;
|
|
||||||
|
|
||||||
ContentPackage::ContentPackage(KernelState* kernel_state, std::string root_name,
|
|
||||||
const XCONTENT_DATA& data,
|
|
||||||
std::wstring package_path)
|
|
||||||
: kernel_state_(kernel_state), root_name_(std::move(root_name)) {
|
|
||||||
device_path_ = std::string("\\Device\\Content\\") +
|
|
||||||
std::to_string(++content_device_id_) + "\\";
|
|
||||||
|
|
||||||
auto fs = kernel_state_->file_system();
|
|
||||||
|
|
||||||
std::unique_ptr<vfs::Device> device;
|
|
||||||
|
|
||||||
// If this isn't a folder try mounting as STFS package
|
|
||||||
// Otherwise mount as a local host path
|
|
||||||
if (filesystem::PathExists(package_path) &&
|
|
||||||
!filesystem::IsFolder(package_path)) {
|
|
||||||
device =
|
|
||||||
std::make_unique<vfs::StfsContainerDevice>(device_path_, package_path);
|
|
||||||
} else {
|
|
||||||
device = std::make_unique<vfs::HostPathDevice>(device_path_, package_path,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
device->Initialize();
|
|
||||||
fs->RegisterDevice(std::move(device));
|
|
||||||
fs->RegisterSymbolicLink(root_name_ + ":", device_path_);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentPackage::~ContentPackage() {
|
|
||||||
auto fs = kernel_state_->file_system();
|
|
||||||
fs->UnregisterSymbolicLink(root_name_ + ":");
|
|
||||||
fs->UnregisterDevice(device_path_);
|
|
||||||
}
|
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> emoose/stfs-packages
|
|
||||||
ContentManager::ContentManager(KernelState* kernel_state,
|
|
||||||
std::wstring root_path)
|
|
||||||
: kernel_state_(kernel_state), root_path_(std::move(root_path)) {}
|
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
|
||||||
|
|
||||||
uint32_t ContentManager::title_id() {
|
|
||||||
if (title_id_override_) {
|
|
||||||
return title_id_override_;
|
|
||||||
}
|
|
||||||
if (!kernel_state_->GetExecutableModule()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return kernel_state_->title_id();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type) {
|
|
||||||
wchar_t title_id_str[9] = L"00000000";
|
|
||||||
std::swprintf(title_id_str, 9, L"%.8X", title_id());
|
|
||||||
|
|
||||||
wchar_t content_type_str[9] = L"00000000";
|
|
||||||
std::swprintf(content_type_str, 9, L"%.8X", content_type);
|
|
||||||
|
|
||||||
// Package root path:
|
|
||||||
// content_root/title_id/type_name/
|
|
||||||
auto package_root = xe::join_paths(
|
|
||||||
root_path_, xe::join_paths(title_id_str, content_type_str));
|
|
||||||
return package_root + xe::kWPathSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring ContentManager::ResolvePackagePath(const XCONTENT_DATA& data) {
|
|
||||||
// Content path:
|
|
||||||
// content_root/title_id/type_name/data_file_name/
|
|
||||||
auto package_root = ResolvePackageRoot(data.content_type);
|
|
||||||
auto package_path =
|
|
||||||
xe::join_paths(package_root, xe::to_wstring(data.file_name));
|
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
// Add slash to end of path if this is a folder
|
|
||||||
// (or package doesn't exist, meaning we're creating a new folder)
|
|
||||||
if (!xe::filesystem::PathExists(package_path) ||
|
|
||||||
xe::filesystem::IsFolder(package_path)) {
|
|
||||||
package_path += xe::kPathSeparator;
|
|
||||||
}
|
|
||||||
=======
|
|
||||||
>>>>>>> emoose/stfs-packages
|
|
||||||
return package_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
|
|
||||||
uint32_t content_type) {
|
|
||||||
std::vector<XCONTENT_DATA> result;
|
|
||||||
|
|
||||||
// StfsHeader is a huge class - alloc on heap instead of stack
|
|
||||||
vfs::StfsHeader* header = new vfs::StfsHeader();
|
|
||||||
|
|
||||||
// Search path:
|
|
||||||
// content_root/title_id/type_name/*
|
|
||||||
auto package_root = ResolvePackageRoot(content_type);
|
|
||||||
auto file_infos = xe::filesystem::ListFiles(package_root);
|
|
||||||
for (const auto& file_info : file_infos) {
|
|
||||||
XCONTENT_DATA content_data;
|
|
||||||
content_data.device_id = device_id;
|
|
||||||
content_data.content_type = content_type;
|
|
||||||
content_data.display_name = file_info.name;
|
|
||||||
content_data.file_name = xe::to_string(file_info.name);
|
|
||||||
|
|
||||||
auto headers_path = file_info.path + file_info.name;
|
|
||||||
if (file_info.type == xe::filesystem::FileInfo::Type::kDirectory) {
|
|
||||||
headers_path = headers_path + ContentManager::kStfsHeadersExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xe::filesystem::PathExists(headers_path)) {
|
|
||||||
// File is either package or directory that has .headers file
|
|
||||||
|
|
||||||
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
|
|
||||||
// Not a directory so must be a package, verify size to make sure
|
|
||||||
if (file_info.total_size <= vfs::StfsHeader::kHeaderLength) {
|
|
||||||
continue; // Invalid package (maybe .headers.bin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto map = MappedMemory::Open(headers_path, MappedMemory::Mode::kRead, 0,
|
|
||||||
vfs::StfsHeader::kHeaderLength);
|
|
||||||
if (map) {
|
|
||||||
if (header->Read(map->data())) {
|
|
||||||
content_data.content_type =
|
|
||||||
static_cast<uint32_t>(header->content_type);
|
|
||||||
content_data.display_name = header->display_names;
|
|
||||||
// TODO: select localized display name
|
|
||||||
// some games may expect different ones depending on language setting.
|
|
||||||
}
|
|
||||||
map->Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.emplace_back(std::move(content_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
delete header;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentPackage* ContentManager::ResolvePackage(const XCONTENT_DATA& data) {
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
|
||||||
if (!xe::filesystem::PathExists(package_path)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
for (auto package : open_packages_) {
|
|
||||||
if (package->package_path() == package_path) {
|
|
||||||
return package;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> package;
|
|
||||||
|
|
||||||
// Open as FolderContentPackage if the package is a folder or doesn't exist
|
|
||||||
if (xe::filesystem::IsFolder(package_path) ||
|
|
||||||
!xe::filesystem::PathExists(package_path)) {
|
|
||||||
package = std::make_unique<FolderContentPackage>(kernel_state_, data,
|
|
||||||
package_path);
|
|
||||||
} else {
|
|
||||||
package =
|
|
||||||
std::make_unique<StfsContentPackage>(kernel_state_, data, package_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return package.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContentManager::ContentExists(const XCONTENT_DATA& data) {
|
|
||||||
auto path = ResolvePackagePath(data);
|
|
||||||
return xe::filesystem::PathExists(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT ContentManager::CreateContent(std::string root_name,
|
|
||||||
const XCONTENT_DATA& data) {
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
|
||||||
if (xe::filesystem::PathExists(package_path)) {
|
|
||||||
// Exists, must not!
|
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto package : open_packages_) {
|
|
||||||
if (package->package_path() == package_path) {
|
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!xe::filesystem::CreateFolder(package_path)) {
|
|
||||||
return X_ERROR_ACCESS_DENIED;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto package = ResolvePackage(data);
|
|
||||||
if (!package) {
|
|
||||||
return X_ERROR_FUNCTION_FAILED; // Failed to create directory?
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!package->Mount(root_name)) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
open_packages_.push_back(package);
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT ContentManager::OpenContent(std::string root_name,
|
|
||||||
const XCONTENT_DATA& data) {
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
auto package = ResolvePackage(data);
|
|
||||||
if (!package) {
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!package->Mount(root_name)) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
open_packages_.push_back(package);
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT ContentManager::CloseContent(std::string root_name) {
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
for (auto it = open_packages_.begin(); it != open_packages_.end(); ++it) {
|
|
||||||
auto& root_names = (*it)->root_names();
|
|
||||||
auto root = std::find(root_names.begin(), root_names.end(), root_name);
|
|
||||||
if (root != root_names.end()) {
|
|
||||||
if ((*it)->Unmount(root_name)) {
|
|
||||||
delete *it;
|
|
||||||
open_packages_.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data,
|
|
||||||
std::vector<uint8_t>* buffer) {
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
auto package = ResolvePackage(data);
|
|
||||||
if (!package) {
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
return package->GetThumbnail(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data,
|
|
||||||
std::vector<uint8_t> buffer) {
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
auto package = ResolvePackage(data);
|
|
||||||
if (!package) {
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
return package->SetThumbnail(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) {
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
|
||||||
if (xe::filesystem::PathExists(package_path)) {
|
|
||||||
if (xe::filesystem::IsFolder(package_path)) {
|
|
||||||
xe::filesystem::DeleteFolder(package_path);
|
|
||||||
} else {
|
|
||||||
// TODO: delete STFS package?
|
|
||||||
}
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
} else {
|
|
||||||
=======
|
|
||||||
auto package = ResolvePackage(data);
|
|
||||||
if (!package) {
|
|
||||||
>>>>>>> emoose/stfs-packages
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = package->Delete();
|
|
||||||
if (XSUCCEEDED(result)) {
|
|
||||||
auto it = std::find(open_packages_.begin(), open_packages_.end(), package);
|
|
||||||
if (it != open_packages_.end()) {
|
|
||||||
open_packages_.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete package;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring ContentManager::ResolveGameUserContentPath() {
|
|
||||||
wchar_t title_id[9] = L"00000000";
|
|
||||||
std::swprintf(title_id, 9, L"%.8X", kernel_state_->title_id());
|
|
||||||
auto user_name = xe::to_wstring(kernel_state_->user_profile()->name());
|
|
||||||
|
|
||||||
// Per-game per-profile data location:
|
|
||||||
// content_root/title_id/profile/user_name
|
|
||||||
auto package_root = xe::join_paths(
|
|
||||||
root_path_,
|
|
||||||
xe::join_paths(title_id,
|
|
||||||
xe::join_paths(kGameUserContentDirName, user_name)));
|
|
||||||
return package_root + xe::kWPathSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace xam
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
|
@ -30,8 +30,6 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
class ContentPackage;
|
|
||||||
|
|
||||||
struct XCONTENT_DATA {
|
struct XCONTENT_DATA {
|
||||||
static const size_t kSize = 4 + 4 + 128 * 2 + 42 + 2; // = 306 + 2b padding
|
static const size_t kSize = 4 + 4 + 128 * 2 + 42 + 2; // = 306 + 2b padding
|
||||||
uint32_t device_id;
|
uint32_t device_id;
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_KERNEL_XAM_CONTENT_MANAGER_H_
|
|
||||||
#define XENIA_KERNEL_XAM_CONTENT_MANAGER_H_
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/base/memory.h"
|
|
||||||
#include "xenia/base/mutex.h"
|
|
||||||
#include "xenia/xbox.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
class KernelState;
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
namespace xam {
|
|
||||||
|
|
||||||
class ContentPackage;
|
|
||||||
|
|
||||||
struct XCONTENT_DATA {
|
|
||||||
static const size_t kSize = 4 + 4 + 128 * 2 + 42 + 2; // = 306 + 2b padding
|
|
||||||
uint32_t device_id;
|
|
||||||
uint32_t content_type;
|
|
||||||
std::wstring display_name; // 128 chars
|
|
||||||
std::string file_name;
|
|
||||||
|
|
||||||
XCONTENT_DATA() = default;
|
|
||||||
explicit XCONTENT_DATA(const uint8_t* ptr) {
|
|
||||||
device_id = xe::load_and_swap<uint32_t>(ptr + 0);
|
|
||||||
content_type = xe::load_and_swap<uint32_t>(ptr + 4);
|
|
||||||
display_name = xe::load_and_swap<std::wstring>(ptr + 8);
|
|
||||||
file_name = xe::load_and_swap<std::string>(ptr + 8 + 128 * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Write(uint8_t* ptr) {
|
|
||||||
xe::store_and_swap<uint32_t>(ptr + 0, device_id);
|
|
||||||
xe::store_and_swap<uint32_t>(ptr + 4, content_type);
|
|
||||||
xe::store_and_swap<std::wstring>(ptr + 8, display_name);
|
|
||||||
xe::store_and_swap<std::string>(ptr + 8 + 128 * 2, file_name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ContentManager {
|
|
||||||
public:
|
|
||||||
// Extension to append to folder path when searching for STFS headers
|
|
||||||
static constexpr const wchar_t* const kStfsHeadersExtension = L".headers.bin";
|
|
||||||
|
|
||||||
ContentManager(KernelState* kernel_state, std::wstring root_path);
|
|
||||||
~ContentManager();
|
|
||||||
|
|
||||||
std::vector<XCONTENT_DATA> ListContent(uint32_t device_id,
|
|
||||||
uint32_t content_type);
|
|
||||||
|
|
||||||
ContentPackage* ResolvePackage(const XCONTENT_DATA& data);
|
|
||||||
|
|
||||||
bool ContentExists(const XCONTENT_DATA& data);
|
|
||||||
X_RESULT CreateContent(std::string root_name, const XCONTENT_DATA& data);
|
|
||||||
X_RESULT OpenContent(std::string root_name, const XCONTENT_DATA& data);
|
|
||||||
X_RESULT CloseContent(std::string root_name);
|
|
||||||
X_RESULT GetContentThumbnail(const XCONTENT_DATA& data,
|
|
||||||
std::vector<uint8_t>* buffer);
|
|
||||||
X_RESULT SetContentThumbnail(const XCONTENT_DATA& data,
|
|
||||||
std::vector<uint8_t> buffer);
|
|
||||||
X_RESULT DeleteContent(const XCONTENT_DATA& data);
|
|
||||||
std::wstring ResolveGameUserContentPath();
|
|
||||||
|
|
||||||
void SetTitleIdOverride(uint32_t title_id) { title_id_override_ = title_id; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint32_t title_id();
|
|
||||||
|
|
||||||
std::wstring ResolvePackageRoot(uint32_t content_type);
|
|
||||||
std::wstring ResolvePackagePath(const XCONTENT_DATA& data);
|
|
||||||
|
|
||||||
KernelState* kernel_state_;
|
|
||||||
std::wstring root_path_;
|
|
||||||
|
|
||||||
// TODO(benvanik): remove use of global lock, it's bad here!
|
|
||||||
xe::global_critical_region global_critical_region_;
|
|
||||||
<<<<<<< HEAD
|
|
||||||
std::unordered_map<std::string, ContentPackage*> open_packages_;
|
|
||||||
|
|
||||||
uint32_t title_id_override_ =
|
|
||||||
0; // can be used for games/apps that request content for other IDs
|
|
||||||
=======
|
|
||||||
std::vector<ContentPackage*> open_packages_;
|
|
||||||
>>>>>>> emoose/stfs-packages
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace xam
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif // XENIA_KERNEL_XAM_CONTENT_MANAGER_H_
|
|
|
@ -11,9 +11,7 @@
|
||||||
|
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
#include <gflags/gflags.h>
|
|
||||||
|
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
@ -21,14 +19,12 @@
|
||||||
#include "xenia/kernel/util/crypto_utils.h"
|
#include "xenia/kernel/util/crypto_utils.h"
|
||||||
#include "xenia/kernel/xam/user_profile.h"
|
#include "xenia/kernel/xam/user_profile.h"
|
||||||
|
|
||||||
DEFINE_string(profile_name, "XeniaUser", "Gamertag", "General");
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
DEFINE_string(profile_directory, "Content\\Profile\\",
|
DEFINE_string(profile_directory, "Content\\Profile\\",
|
||||||
"The directory to store profile data inside");
|
"The directory to store profile data inside", "Kernel");
|
||||||
|
|
||||||
constexpr uint32_t kDashboardID = 0xFFFE07D1;
|
constexpr uint32_t kDashboardID = 0xFFFE07D1;
|
||||||
|
|
||||||
|
@ -108,7 +104,7 @@ void UserProfile::EncryptAccountFile(const X_XAMACCOUNTINFO* input,
|
||||||
|
|
||||||
UserProfile::UserProfile() : dash_gpd_(kDashboardID) {
|
UserProfile::UserProfile() : dash_gpd_(kDashboardID) {
|
||||||
account_.xuid_online = 0xE000BABEBABEBABE;
|
account_.xuid_online = 0xE000BABEBABEBABE;
|
||||||
wcscpy_s(account_.gamertag, cvars::profile_name);
|
wcscpy_s(account_.gamertag, L"XeniaUser");
|
||||||
|
|
||||||
// https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195
|
// https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195
|
||||||
// https://github.com/arkem/py360/blob/master/py360/constants.py
|
// https://github.com/arkem/py360/blob/master/py360/constants.py
|
||||||
|
@ -181,11 +177,11 @@ UserProfile::UserProfile() : dash_gpd_(kDashboardID) {
|
||||||
|
|
||||||
void UserProfile::LoadProfile() {
|
void UserProfile::LoadProfile() {
|
||||||
auto mmap_ =
|
auto mmap_ =
|
||||||
MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + L"Account",
|
MappedMemory::Open(xe::to_wstring(cvars::profile_directory) + L"Account",
|
||||||
MappedMemory::Mode::kRead);
|
MappedMemory::Mode::kRead);
|
||||||
if (mmap_) {
|
if (mmap_) {
|
||||||
XELOGI("Loading Account file from path %sAccount",
|
XELOGI("Loading Account file from path %SAccount",
|
||||||
FLAGS_profile_directory.c_str());
|
xe::to_wstring(cvars::profile_directory).c_str());
|
||||||
|
|
||||||
X_XAMACCOUNTINFO tmp_acct;
|
X_XAMACCOUNTINFO tmp_acct;
|
||||||
bool success = DecryptAccountFile(mmap_->data(), &tmp_acct);
|
bool success = DecryptAccountFile(mmap_->data(), &tmp_acct);
|
||||||
|
@ -203,10 +199,10 @@ void UserProfile::LoadProfile() {
|
||||||
mmap_->Close();
|
mmap_->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
XELOGI("Loading profile GPDs from path %s", FLAGS_profile_directory.c_str());
|
XELOGI("Loading profile GPDs from path %S", xe::to_wstring(cvars::profile_directory).c_str());
|
||||||
|
|
||||||
mmap_ = MappedMemory::Open(
|
mmap_ = MappedMemory::Open(
|
||||||
xe::to_wstring(FLAGS_profile_directory) + L"FFFE07D1.gpd",
|
xe::to_wstring(cvars::profile_directory) + L"FFFE07D1.gpd",
|
||||||
MappedMemory::Mode::kRead);
|
MappedMemory::Mode::kRead);
|
||||||
if (!mmap_) {
|
if (!mmap_) {
|
||||||
XELOGW(
|
XELOGW(
|
||||||
|
@ -223,7 +219,7 @@ void UserProfile::LoadProfile() {
|
||||||
for (auto title : titles) {
|
for (auto title : titles) {
|
||||||
wchar_t fname[256];
|
wchar_t fname[256];
|
||||||
_swprintf(fname, L"%X.gpd", title.title_id);
|
_swprintf(fname, L"%X.gpd", title.title_id);
|
||||||
mmap_ = MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + fname,
|
mmap_ = MappedMemory::Open(xe::to_wstring(cvars::profile_directory) + fname,
|
||||||
MappedMemory::Mode::kRead);
|
MappedMemory::Mode::kRead);
|
||||||
if (!mmap_) {
|
if (!mmap_) {
|
||||||
XELOGE("Failed to open GPD for title %X (%s)!", title.title_id,
|
XELOGE("Failed to open GPD for title %X (%s)!", title.title_id,
|
||||||
|
@ -451,16 +447,16 @@ bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filesystem::PathExists(xe::to_wstring(FLAGS_profile_directory))) {
|
if (!filesystem::PathExists(xe::to_wstring(cvars::profile_directory))) {
|
||||||
filesystem::CreateFolder(xe::to_wstring(FLAGS_profile_directory));
|
filesystem::CreateFolder(xe::to_wstring(cvars::profile_directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t fname[256];
|
wchar_t fname[256];
|
||||||
_swprintf(fname, L"%X.gpd", title_id);
|
_swprintf(fname, L"%X.gpd", title_id);
|
||||||
|
|
||||||
filesystem::CreateFile(xe::to_wstring(FLAGS_profile_directory) + fname);
|
filesystem::CreateFile(xe::to_wstring(cvars::profile_directory) + fname);
|
||||||
auto mmap_ =
|
auto mmap_ =
|
||||||
MappedMemory::Open(xe::to_wstring(FLAGS_profile_directory) + fname,
|
MappedMemory::Open(xe::to_wstring(cvars::profile_directory) + fname,
|
||||||
MappedMemory::Mode::kReadWrite, 0, gpd_length);
|
MappedMemory::Mode::kReadWrite, 0, gpd_length);
|
||||||
if (!mmap_) {
|
if (!mmap_) {
|
||||||
XELOGE("Failed to open %X.gpd for writing!", title_id);
|
XELOGE("Failed to open %X.gpd for writing!", title_id);
|
||||||
|
|
|
@ -313,7 +313,7 @@ class UserProfile {
|
||||||
|
|
||||||
uint64_t xuid() const { return account_.xuid_online; }
|
uint64_t xuid() const { return account_.xuid_online; }
|
||||||
std::string name() const { return account_.GetGamertagString(); }
|
std::string name() const { return account_.GetGamertagString(); }
|
||||||
uint32_t signin_state() const { return 1; }
|
// uint32_t signin_state() const { return 1; }
|
||||||
|
|
||||||
void AddSetting(std::unique_ptr<Setting> setting);
|
void AddSetting(std::unique_ptr<Setting> setting);
|
||||||
Setting* GetSetting(uint32_t setting_id);
|
Setting* GetSetting(uint32_t setting_id);
|
||||||
|
|
|
@ -290,7 +290,9 @@ void XamLoaderLaunchTitle(lpstring_t raw_name, dword_t flags) {
|
||||||
|
|
||||||
auto& loader_data = xam->loader_data();
|
auto& loader_data = xam->loader_data();
|
||||||
loader_data.launch_flags = flags;
|
loader_data.launch_flags = flags;
|
||||||
|
XELOGI(
|
||||||
|
"XamLoaderLaunchTitle launching: (%S) with flags (%d)",
|
||||||
|
std::string(raw_name), flags);
|
||||||
// Translate the launch path to a full path.
|
// Translate the launch path to a full path.
|
||||||
if (raw_name && raw_name.value() == "") {
|
if (raw_name && raw_name.value() == "") {
|
||||||
loader_data.launch_path = "game:\\default.xex";
|
loader_data.launch_path = "game:\\default.xex";
|
||||||
|
|
|
@ -182,7 +182,7 @@ X_HRESULT_result_t XamUserGetDeviceContext(dword_t user_index, dword_t unk,
|
||||||
// Games check the result - usually with some masking.
|
// Games check the result - usually with some masking.
|
||||||
// If this function fails they assume zero, so let's fail AND
|
// If this function fails they assume zero, so let's fail AND
|
||||||
// set zero just to be safe.
|
// set zero just to be safe.
|
||||||
*out_ptr = 0;
|
//*out_ptr = 0;
|
||||||
if (!user_index || (user_index & 0xFF) == 0xFF) {
|
if (!user_index || (user_index & 0xFF) == 0xFF) {
|
||||||
return X_E_SUCCESS;
|
return X_E_SUCCESS;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -461,7 +461,7 @@ DECLARE_XAM_EXPORT1(NetDll_XNetXnAddrToMachineId, kNetworking, kStub);
|
||||||
|
|
||||||
void NetDll_XNetInAddrToString(dword_t caller, dword_t in_addr,
|
void NetDll_XNetInAddrToString(dword_t caller, dword_t in_addr,
|
||||||
lpstring_t string_out, dword_t string_size) {
|
lpstring_t string_out, dword_t string_size) {
|
||||||
strncpy(string_out, "666.666.666.666", string_size);
|
strncpy(string_out, "127.0.0.1", string_size);
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(NetDll_XNetInAddrToString, kNetworking, kStub);
|
DECLARE_XAM_EXPORT1(NetDll_XNetInAddrToString, kNetworking, kStub);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,10 @@ namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
dword_result_t XamNotifyCreateListenerInternal(qword_t mask) {
|
dword_result_t XamNotifyCreateListenerInternal(qword_t mask, dword_t unk,
|
||||||
|
dword_t one) {
|
||||||
|
// r4=1 may indicate user process?
|
||||||
|
|
||||||
auto listener =
|
auto listener =
|
||||||
object_ref<XNotifyListener>(new XNotifyListener(kernel_state()));
|
object_ref<XNotifyListener>(new XNotifyListener(kernel_state()));
|
||||||
listener->Initialize(mask);
|
listener->Initialize(mask);
|
||||||
|
@ -28,10 +31,11 @@ dword_result_t XamNotifyCreateListenerInternal(qword_t mask) {
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamNotifyCreateListenerInternal, kNone, kImplemented);
|
DECLARE_XAM_EXPORT2(XamNotifyCreateListenerInternal, kNone, kImplemented,
|
||||||
|
kSketchy);
|
||||||
|
|
||||||
dword_result_t XamNotifyCreateListener(qword_t mask) {
|
dword_result_t XamNotifyCreateListener(qword_t mask, dword_t one) {
|
||||||
return XamNotifyCreateListenerInternal(mask);
|
return XamNotifyCreateListenerInternal(mask, 0, one);
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamNotifyCreateListener, kNone, kImplemented);
|
DECLARE_XAM_EXPORT1(XamNotifyCreateListener, kNone, kImplemented);
|
||||||
|
|
||||||
|
@ -56,6 +60,9 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id,
|
||||||
// Asking for a specific notification
|
// Asking for a specific notification
|
||||||
id = match_id;
|
id = match_id;
|
||||||
dequeued = listener->DequeueNotification(match_id, ¶m);
|
dequeued = listener->DequeueNotification(match_id, ¶m);
|
||||||
|
// TODO(Gliniak): Requires research. There is no such match_id!
|
||||||
|
if (!dequeued && !param)
|
||||||
|
dequeued = listener->DequeueNotification(&id, ¶m);
|
||||||
} else {
|
} else {
|
||||||
// Just get next.
|
// Just get next.
|
||||||
dequeued = listener->DequeueNotification(&id, ¶m);
|
dequeued = listener->DequeueNotification(&id, ¶m);
|
||||||
|
@ -71,7 +78,7 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id,
|
||||||
|
|
||||||
return dequeued ? 1 : 0;
|
return dequeued ? 1 : 0;
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XNotifyGetNext, kNone, kImplemented);
|
DECLARE_XAM_EXPORT2(XNotifyGetNext, kNone, kImplemented, kHighFrequency);
|
||||||
|
|
||||||
dword_result_t XNotifyDelayUI(dword_t delay_ms) {
|
dword_result_t XNotifyDelayUI(dword_t delay_ms) {
|
||||||
// Ignored.
|
// Ignored.
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
#include "xenia/kernel/xenumerator.h"
|
#include "xenia/kernel/xenumerator.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
|
|
||||||
|
|
||||||
|
DEFINE_bool(signin_state, true,
|
||||||
|
"User signed in", "Kernel");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
@ -121,7 +126,7 @@ dword_result_t XamUserGetSigninState(dword_t user_index) {
|
||||||
|
|
||||||
if (user_index == 0 || (user_index & 0xFF) == 0xFF) {
|
if (user_index == 0 || (user_index & 0xFF) == 0xFF) {
|
||||||
const auto& user_profile = kernel_state()->user_profile();
|
const auto& user_profile = kernel_state()->user_profile();
|
||||||
return user_profile->signin_state();
|
return ((cvars::signin_state) ? 1 : 0);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -152,7 +157,7 @@ X_HRESULT_result_t XamUserGetSigninInfo(dword_t user_index, dword_t flags,
|
||||||
|
|
||||||
const auto& user_profile = kernel_state()->user_profile();
|
const auto& user_profile = kernel_state()->user_profile();
|
||||||
info->xuid = user_profile->xuid();
|
info->xuid = user_profile->xuid();
|
||||||
info->signin_state = user_profile->signin_state();
|
info->signin_state = ((cvars::signin_state) ? 1 : 0);
|
||||||
std::strncpy(info->name, user_profile->name().data(), 15);
|
std::strncpy(info->name, user_profile->name().data(), 15);
|
||||||
return X_E_SUCCESS;
|
return X_E_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -502,7 +507,7 @@ dword_result_t XamUserAreUsersFriends(dword_t user_index, dword_t unk1,
|
||||||
X_RESULT result = X_ERROR_SUCCESS;
|
X_RESULT result = X_ERROR_SUCCESS;
|
||||||
|
|
||||||
const auto& user_profile = kernel_state()->user_profile();
|
const auto& user_profile = kernel_state()->user_profile();
|
||||||
if (user_profile->signin_state() == 0) {
|
if (((cvars::signin_state) ? 1 : 0) == 0) {
|
||||||
result = X_ERROR_NOT_LOGGED_ON;
|
result = X_ERROR_NOT_LOGGED_ON;
|
||||||
} else {
|
} else {
|
||||||
// No friends!
|
// No friends!
|
||||||
|
|
|
@ -434,17 +434,75 @@ void XeCryptHmacSha(lpvoid_t key, dword_t key_size_in, lpvoid_t inp_1,
|
||||||
dword_t inp_1_size, lpvoid_t inp_2, dword_t inp_2_size,
|
dword_t inp_1_size, lpvoid_t inp_2, dword_t inp_2_size,
|
||||||
lpvoid_t inp_3, dword_t inp_3_size, lpvoid_t out,
|
lpvoid_t inp_3, dword_t inp_3_size, lpvoid_t out,
|
||||||
dword_t out_size) {
|
dword_t out_size) {
|
||||||
util::HmacSha(key, key_size_in, inp_1, inp_1_size, inp_2, inp_2_size, inp_3,
|
uint32_t key_size = key_size_in;
|
||||||
inp_3_size, out, out_size);
|
sha1::SHA1 sha;
|
||||||
|
uint8_t kpad_i[0x40];
|
||||||
|
uint8_t kpad_o[0x40];
|
||||||
|
uint8_t tmp_key[0x40];
|
||||||
|
std::memset(kpad_i, 0x36, 0x40);
|
||||||
|
std::memset(kpad_o, 0x5C, 0x40);
|
||||||
|
|
||||||
|
// Setup HMAC key
|
||||||
|
// If > block size, use its hash
|
||||||
|
if (key_size > 0x40) {
|
||||||
|
sha1::SHA1 sha_key;
|
||||||
|
sha_key.processBytes(key, key_size);
|
||||||
|
sha_key.finalize((uint8_t*)tmp_key);
|
||||||
|
|
||||||
|
key_size = 0x14u;
|
||||||
|
} else {
|
||||||
|
std::memcpy(tmp_key, key, key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < key_size; i++) {
|
||||||
|
kpad_i[i] = tmp_key[i] ^ 0x36;
|
||||||
|
kpad_o[i] = tmp_key[i] ^ 0x5C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inner
|
||||||
|
sha.processBytes(kpad_i, 0x40);
|
||||||
|
|
||||||
|
if (inp_1_size) {
|
||||||
|
sha.processBytes(inp_1, inp_1_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inp_2_size) {
|
||||||
|
sha.processBytes(inp_2, inp_2_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inp_3_size) {
|
||||||
|
sha.processBytes(inp_3, inp_3_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t digest[0x14];
|
||||||
|
sha.finalize(digest);
|
||||||
|
sha.reset();
|
||||||
|
|
||||||
|
// Outer
|
||||||
|
sha.processBytes(kpad_o, 0x40);
|
||||||
|
sha.processBytes(digest, 0x14);
|
||||||
|
sha.finalize(digest);
|
||||||
|
|
||||||
|
std::memcpy(out, digest, std::min((uint32_t)out_size, 0x14u));
|
||||||
}
|
}
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XeCryptHmacSha, kNone, kImplemented);
|
DECLARE_XBOXKRNL_EXPORT1(XeCryptHmacSha, kNone, kImplemented);
|
||||||
|
|
||||||
|
// Keys
|
||||||
|
// TODO: Array of keys we need
|
||||||
|
|
||||||
|
// Retail key 0x19
|
||||||
|
static const uint8_t key19[] = {0xE1, 0xBC, 0x15, 0x9C, 0x73, 0xB1, 0xEA, 0xE9,
|
||||||
|
0xAB, 0x31, 0x70, 0xF3, 0xAD, 0x47, 0xEB, 0xF3};
|
||||||
|
|
||||||
dword_result_t XeKeysHmacSha(dword_t key_num, lpvoid_t inp_1,
|
dword_result_t XeKeysHmacSha(dword_t key_num, lpvoid_t inp_1,
|
||||||
dword_t inp_1_size, lpvoid_t inp_2,
|
dword_t inp_1_size, lpvoid_t inp_2,
|
||||||
dword_t inp_2_size, lpvoid_t inp_3,
|
dword_t inp_2_size, lpvoid_t inp_3,
|
||||||
dword_t inp_3_size, lpvoid_t out,
|
dword_t inp_3_size, lpvoid_t out,
|
||||||
dword_t out_size) {
|
dword_t out_size) {
|
||||||
const uint8_t* key = util::GetXeKey(key_num);
|
const uint8_t* key = nullptr;
|
||||||
|
if (key_num == 0x19) {
|
||||||
|
key = key19;
|
||||||
|
}
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
XeCryptHmacSha((void*)key, 0x10, inp_1, inp_1_size, inp_2, inp_2_size,
|
XeCryptHmacSha((void*)key, 0x10, inp_1, inp_1_size, inp_2, inp_2_size,
|
||||||
|
|
|
@ -136,8 +136,9 @@ DECLARE_XBOXKRNL_EXPORT2(RtlRaiseException, kDebug, kStub, kImportant);
|
||||||
|
|
||||||
void KeBugCheckEx(dword_t code, dword_t param1, dword_t param2, dword_t param3,
|
void KeBugCheckEx(dword_t code, dword_t param1, dword_t param2, dword_t param3,
|
||||||
dword_t param4) {
|
dword_t param4) {
|
||||||
XELOGD("*** STOP: 0x%.8X (0x%.8X, 0x%.8X, 0x%.8X, 0x%.8X)", code, param1,
|
XELOGE("*** STOP: 0x%.8X (0x%.8X, 0x%.8X, 0x%.8X, 0x%.8X)", code, param1,
|
||||||
param2, param3, param4);
|
param2, param3, param4);
|
||||||
|
XELOGE(" ### GUEST RAISE EXCEPTION - should have crashed here ###");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
//xe::debugging::Break();
|
//xe::debugging::Break();
|
||||||
assert_always();
|
assert_always();
|
||||||
|
|
|
@ -14,14 +14,15 @@
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
|
|
||||||
|
DEFINE_bool(xconfig_initial_setup, false,
|
||||||
|
"Enable the dashboard initial setup/OOBE", "Kernel");
|
||||||
|
|
||||||
DEFINE_int32(game_language, 1,
|
DEFINE_int32(game_language, 1,
|
||||||
"The language for the game to run in. 1=EN / 2=JP / 3=DE / 4=FR / "
|
"The language for the game to run in. 1=EN / 2=JP / 3=DE / 4=FR / "
|
||||||
"5=ES / 6=IT / 7=KR / 8=CN",
|
"5=ES / 6=IT / 7=KR / 8=CN",
|
||||||
"General");
|
"General");
|
||||||
|
|
||||||
DEFINE_bool(xconfig_initial_setup, false,
|
|
||||||
"Enable the dashboard initial setup/OOBE", "Kernel");
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
|
@ -90,7 +91,7 @@ X_STATUS xeExGetXConfigSetting(uint16_t category, uint16_t setting,
|
||||||
break;
|
break;
|
||||||
case 0x000F: // XCONFIG_USER_PC_FLAGS (parental control?)
|
case 0x000F: // XCONFIG_USER_PC_FLAGS (parental control?)
|
||||||
setting_size = 1;
|
setting_size = 1;
|
||||||
value[0] = 0;
|
xe::store_and_swap<uint32_t>(value, 0);
|
||||||
break;
|
break;
|
||||||
case 0x0010: // XCONFIG_USER_SMB_CONFIG (0x100 byte string)
|
case 0x0010: // XCONFIG_USER_SMB_CONFIG (0x100 byte string)
|
||||||
// Just set the start of the buffer to 0 so that callers
|
// Just set the start of the buffer to 0 so that callers
|
||||||
|
|
|
@ -1,318 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* 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/base/logging.h"
|
|
||||||
#include "xenia/cpu/processor.h"
|
|
||||||
#include "xenia/kernel/kernel_state.h"
|
|
||||||
#include "xenia/kernel/user_module.h"
|
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
|
|
||||||
#include "xenia/xbox.h"
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
DEFINE_int32(game_language, 1,
|
|
||||||
"The language for the game to run in. 1=EN / 2=JP / 3=DE / 4=FR / "
|
|
||||||
"5=ES / 6=IT / 7=KR / 8=CN",
|
|
||||||
"General");
|
|
||||||
=======
|
|
||||||
DEFINE_bool(xconfig_initial_setup, false,
|
|
||||||
"Enable the dashboard initial setup/OOBE", "Kernel");
|
|
||||||
>>>>>>> emoose/dashboard
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace kernel {
|
|
||||||
namespace xboxkrnl {
|
|
||||||
|
|
||||||
X_STATUS xeExGetXConfigSetting(uint16_t category, uint16_t setting,
|
|
||||||
void* buffer, uint16_t buffer_size,
|
|
||||||
uint16_t* required_size) {
|
|
||||||
uint16_t setting_size = 0;
|
|
||||||
alignas(uint32_t) uint8_t value[4];
|
|
||||||
|
|
||||||
// TODO(benvanik): have real structs here that just get copied from.
|
|
||||||
// https://free60project.github.io/wiki/XConfig.html
|
|
||||||
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/ExConfig.h
|
|
||||||
switch (category) {
|
|
||||||
case 0x0002:
|
|
||||||
// XCONFIG_SECURED_CATEGORY
|
|
||||||
switch (setting) {
|
|
||||||
case 0x0001: // XCONFIG_SECURED_MAC_ADDRESS (6 bytes)
|
|
||||||
return X_STATUS_SUCCESS; // Just return, easier than setting up code
|
|
||||||
// for different size configs
|
|
||||||
case 0x0002: // XCONFIG_SECURED_AV_REGION
|
|
||||||
setting_size = 4;
|
|
||||||
xe::store_and_swap<uint32_t>(value, 0x00001000); // USA/Canada
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert_unhandled_case(setting);
|
|
||||||
return X_STATUS_INVALID_PARAMETER_2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0x0003:
|
|
||||||
// XCONFIG_USER_CATEGORY
|
|
||||||
switch (setting) {
|
|
||||||
case 0x0001: // XCONFIG_USER_TIME_ZONE_BIAS
|
|
||||||
case 0x0002: // XCONFIG_USER_TIME_ZONE_STD_NAME
|
|
||||||
case 0x0003: // XCONFIG_USER_TIME_ZONE_DLT_NAME
|
|
||||||
case 0x0004: // XCONFIG_USER_TIME_ZONE_STD_DATE
|
|
||||||
case 0x0005: // XCONFIG_USER_TIME_ZONE_DLT_DATE
|
|
||||||
case 0x0006: // XCONFIG_USER_TIME_ZONE_STD_BIAS
|
|
||||||
case 0x0007: // XCONFIG_USER_TIME_ZONE_DLT_BIAS
|
|
||||||
setting_size = 4;
|
|
||||||
// TODO(benvanik): get this value.
|
|
||||||
xe::store_and_swap<uint32_t>(value, 0);
|
|
||||||
break;
|
|
||||||
case 0x0009: // XCONFIG_USER_LANGUAGE
|
|
||||||
setting_size = 4;
|
|
||||||
xe::store_and_swap<uint32_t>(value, cvars::game_language); // English
|
|
||||||
break;
|
|
||||||
case 0x000A: // XCONFIG_USER_VIDEO_FLAGS
|
|
||||||
setting_size = 4;
|
|
||||||
xe::store_and_swap<uint32_t>(value, 0x00040000);
|
|
||||||
break;
|
|
||||||
case 0x000C: // XCONFIG_USER_RETAIL_FLAGS
|
|
||||||
setting_size = 4;
|
|
||||||
// TODO(benvanik): get this value.
|
|
||||||
|
|
||||||
// 0x40 = dashboard initial setup complete
|
|
||||||
xe::store_and_swap<uint32_t>(value,
|
|
||||||
cvars::xconfig_initial_setup ? 0 : 0x40);
|
|
||||||
break;
|
|
||||||
case 0x000E: // XCONFIG_USER_COUNTRY
|
|
||||||
// Halo: Reach sub_82804888 - min 0x5, max 0x6E.
|
|
||||||
setting_size = 1;
|
|
||||||
// TODO(benvanik): get this value.
|
|
||||||
value[0] = 5;
|
|
||||||
break;
|
|
||||||
case 0x000F: // XCONFIG_USER_PC_FLAGS (parental control?)
|
|
||||||
setting_size = 1;
|
|
||||||
value[0] = 0;
|
|
||||||
break;
|
|
||||||
case 0x0010: // XCONFIG_USER_SMB_CONFIG (0x100 byte string)
|
|
||||||
// Just set the start of the buffer to 0 so that callers
|
|
||||||
// don't error from an un-inited buffer
|
|
||||||
setting_size = 4;
|
|
||||||
xe::store_and_swap<uint32_t>(value, 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert_unhandled_case(setting);
|
|
||||||
return X_STATUS_INVALID_PARAMETER_2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert_unhandled_case(category);
|
|
||||||
return X_STATUS_INVALID_PARAMETER_1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer) {
|
|
||||||
if (buffer_size < setting_size) {
|
|
||||||
return X_STATUS_BUFFER_TOO_SMALL;
|
|
||||||
}
|
|
||||||
std::memcpy(buffer, value, setting_size);
|
|
||||||
} else {
|
|
||||||
if (buffer_size) {
|
|
||||||
return X_STATUS_INVALID_PARAMETER_3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (required_size) {
|
|
||||||
*required_size = setting_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
dword_result_t ExGetXConfigSetting(word_t category, word_t setting,
|
|
||||||
lpdword_t buffer_ptr, word_t buffer_size,
|
|
||||||
lpword_t required_size_ptr) {
|
|
||||||
uint16_t required_size = 0;
|
|
||||||
X_STATUS result = xeExGetXConfigSetting(category, setting, buffer_ptr,
|
|
||||||
buffer_size, &required_size);
|
|
||||||
|
|
||||||
if (required_size_ptr) {
|
|
||||||
*required_size_ptr = required_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(ExGetXConfigSetting, kModules, kImplemented);
|
|
||||||
|
|
||||||
dword_result_t XexCheckExecutablePrivilege(dword_t privilege) {
|
|
||||||
// BOOL
|
|
||||||
// DWORD Privilege
|
|
||||||
|
|
||||||
// Privilege is bit position in xe_xex2_system_flags enum - so:
|
|
||||||
// Privilege=6 -> 0x00000040 -> XEX_SYSTEM_INSECURE_SOCKETS
|
|
||||||
uint32_t mask = 1 << privilege;
|
|
||||||
|
|
||||||
auto module = kernel_state()->GetExecutableModule();
|
|
||||||
if (!module) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t flags = 0;
|
|
||||||
module->GetOptHeader<uint32_t>(XEX_HEADER_SYSTEM_FLAGS, &flags);
|
|
||||||
|
|
||||||
return (flags & mask) > 0;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XexCheckExecutablePrivilege, kModules, kImplemented);
|
|
||||||
|
|
||||||
dword_result_t XexGetModuleHandle(lpstring_t module_name,
|
|
||||||
lpdword_t hmodule_ptr) {
|
|
||||||
object_ref<XModule> module;
|
|
||||||
|
|
||||||
if (!module_name) {
|
|
||||||
module = kernel_state()->GetExecutableModule();
|
|
||||||
} else {
|
|
||||||
module = kernel_state()->GetModule(module_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!module) {
|
|
||||||
*hmodule_ptr = 0;
|
|
||||||
return X_ERROR_NOT_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: we don't retain the handle for return.
|
|
||||||
*hmodule_ptr = module->hmodule_ptr();
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XexGetModuleHandle, kModules, kImplemented);
|
|
||||||
|
|
||||||
dword_result_t XexGetModuleSection(lpvoid_t hmodule, lpstring_t name,
|
|
||||||
lpdword_t data_ptr, lpdword_t size_ptr) {
|
|
||||||
X_STATUS result = X_STATUS_SUCCESS;
|
|
||||||
|
|
||||||
auto module = XModule::GetFromHModule(kernel_state(), hmodule);
|
|
||||||
if (module) {
|
|
||||||
uint32_t section_data = 0;
|
|
||||||
uint32_t section_size = 0;
|
|
||||||
result = module->GetSection(name, §ion_data, §ion_size);
|
|
||||||
if (XSUCCEEDED(result)) {
|
|
||||||
*data_ptr = section_data;
|
|
||||||
*size_ptr = section_size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = X_STATUS_INVALID_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XexGetModuleSection, kModules, kImplemented);
|
|
||||||
|
|
||||||
dword_result_t XexLoadImage(lpstring_t module_name, dword_t module_flags,
|
|
||||||
dword_t min_version, lpdword_t hmodule_ptr) {
|
|
||||||
X_STATUS result = X_STATUS_NO_SUCH_FILE;
|
|
||||||
|
|
||||||
uint32_t hmodule = 0;
|
|
||||||
auto module = kernel_state()->GetModule(module_name);
|
|
||||||
if (module) {
|
|
||||||
// Existing module found.
|
|
||||||
hmodule = module->hmodule_ptr();
|
|
||||||
result = X_STATUS_SUCCESS;
|
|
||||||
} else {
|
|
||||||
// Not found; attempt to load as a user module.
|
|
||||||
auto user_module = kernel_state()->LoadUserModule(module_name);
|
|
||||||
if (user_module) {
|
|
||||||
user_module->Retain();
|
|
||||||
hmodule = user_module->hmodule_ptr();
|
|
||||||
result = X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the module's load count.
|
|
||||||
if (hmodule) {
|
|
||||||
auto ldr_data =
|
|
||||||
kernel_memory()->TranslateVirtual<X_LDR_DATA_TABLE_ENTRY*>(hmodule);
|
|
||||||
ldr_data->load_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*hmodule_ptr = hmodule;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XexLoadImage, kModules, kImplemented);
|
|
||||||
|
|
||||||
dword_result_t XexUnloadImage(lpvoid_t hmodule) {
|
|
||||||
auto module = XModule::GetFromHModule(kernel_state(), hmodule);
|
|
||||||
if (!module) {
|
|
||||||
return X_STATUS_INVALID_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't unload kernel modules from user code.
|
|
||||||
if (module->module_type() != XModule::ModuleType::kKernelModule) {
|
|
||||||
auto ldr_data = hmodule.as<X_LDR_DATA_TABLE_ENTRY*>();
|
|
||||||
if (--ldr_data->load_count == 0) {
|
|
||||||
// No more references, free it.
|
|
||||||
module->Release();
|
|
||||||
kernel_state()->object_table()->RemoveHandle(module->handle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XexUnloadImage, kModules, kImplemented);
|
|
||||||
|
|
||||||
dword_result_t XexGetProcedureAddress(lpvoid_t hmodule, dword_t ordinal,
|
|
||||||
lpdword_t out_function_ptr) {
|
|
||||||
// May be entry point?
|
|
||||||
assert_not_zero(ordinal);
|
|
||||||
|
|
||||||
bool is_string_name = (ordinal & 0xFFFF0000) != 0;
|
|
||||||
auto string_name =
|
|
||||||
reinterpret_cast<const char*>(kernel_memory()->TranslateVirtual(ordinal));
|
|
||||||
|
|
||||||
X_STATUS result = X_STATUS_INVALID_HANDLE;
|
|
||||||
|
|
||||||
object_ref<XModule> module;
|
|
||||||
if (!hmodule) {
|
|
||||||
module = kernel_state()->GetExecutableModule();
|
|
||||||
} else {
|
|
||||||
module = XModule::GetFromHModule(kernel_state(), hmodule);
|
|
||||||
}
|
|
||||||
if (module) {
|
|
||||||
uint32_t ptr;
|
|
||||||
if (is_string_name) {
|
|
||||||
ptr = module->GetProcAddressByName(string_name);
|
|
||||||
} else {
|
|
||||||
ptr = module->GetProcAddressByOrdinal(ordinal);
|
|
||||||
}
|
|
||||||
if (ptr) {
|
|
||||||
*out_function_ptr = ptr;
|
|
||||||
result = X_STATUS_SUCCESS;
|
|
||||||
} else {
|
|
||||||
XELOGW("ERROR: XexGetProcedureAddress ordinal not found!");
|
|
||||||
*out_function_ptr = 0;
|
|
||||||
result = X_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(XexGetProcedureAddress, kModules, kImplemented);
|
|
||||||
|
|
||||||
void ExRegisterTitleTerminateNotification(
|
|
||||||
pointer_t<X_EX_TITLE_TERMINATE_REGISTRATION> reg, dword_t create) {
|
|
||||||
if (create) {
|
|
||||||
// Adding.
|
|
||||||
kernel_state()->RegisterTitleTerminateNotification(
|
|
||||||
reg->notification_routine, reg->priority);
|
|
||||||
} else {
|
|
||||||
// Removing.
|
|
||||||
kernel_state()->RemoveTitleTerminateNotification(reg->notification_routine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DECLARE_XBOXKRNL_EXPORT1(ExRegisterTitleTerminateNotification, kModules,
|
|
||||||
kImplemented);
|
|
||||||
|
|
||||||
void RegisterModuleExports(xe::cpu::ExportResolver* export_resolver,
|
|
||||||
KernelState* kernel_state) {}
|
|
||||||
|
|
||||||
} // namespace xboxkrnl
|
|
||||||
} // namespace kernel
|
|
||||||
} // namespace xe
|
|
|
@ -137,6 +137,9 @@ dword_result_t ExCreateThread(lpdword_t handle_ptr, dword_t stack_size,
|
||||||
if (handle_ptr) {
|
if (handle_ptr) {
|
||||||
if (creation_flags & 0x80) {
|
if (creation_flags & 0x80) {
|
||||||
*handle_ptr = thread->guest_object();
|
*handle_ptr = thread->guest_object();
|
||||||
|
} else if (!*handle_ptr && (creation_flags == X_CREATE_SUSPENDED)) {
|
||||||
|
// TODO(Gliniak): Temporary solution, requires more research // && !stack_size
|
||||||
|
*handle_ptr = thread->handle();
|
||||||
} else {
|
} else {
|
||||||
thread->RetainHandle();
|
thread->RetainHandle();
|
||||||
*handle_ptr = thread->handle();
|
*handle_ptr = thread->handle();
|
||||||
|
|
|
@ -68,7 +68,7 @@ Emulator* XObject::emulator() const { return kernel_state_->emulator_; }
|
||||||
KernelState* XObject::kernel_state() const { return kernel_state_; }
|
KernelState* XObject::kernel_state() const { return kernel_state_; }
|
||||||
Memory* XObject::memory() const { return kernel_state_->memory(); }
|
Memory* XObject::memory() const { return kernel_state_->memory(); }
|
||||||
|
|
||||||
XObject::Type XObject::type() const { return type_; }
|
XObject::Type XObject::type() { return type_; }
|
||||||
|
|
||||||
void XObject::RetainHandle() {
|
void XObject::RetainHandle() {
|
||||||
kernel_state_->object_table()->RetainHandle(handles_[0]);
|
kernel_state_->object_table()->RetainHandle(handles_[0]);
|
||||||
|
|
|
@ -134,7 +134,7 @@ class XObject {
|
||||||
KernelState* kernel_state() const;
|
KernelState* kernel_state() const;
|
||||||
Memory* memory() const;
|
Memory* memory() const;
|
||||||
|
|
||||||
Type type() const;
|
Type type();
|
||||||
|
|
||||||
// Returns the primary handle of this object.
|
// Returns the primary handle of this object.
|
||||||
X_HANDLE handle() const { return handles_[0]; }
|
X_HANDLE handle() const { return handles_[0]; }
|
||||||
|
|
|
@ -85,7 +85,6 @@ bool VirtualFileSystem::UnregisterSymbolicLink(const std::string& path) {
|
||||||
symlinks_.erase(it);
|
symlinks_.erase(it);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VirtualFileSystem::IsSymbolicLink(const std::string& path) {
|
bool VirtualFileSystem::IsSymbolicLink(const std::string& path) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
auto it = symlinks_.find(path);
|
auto it = symlinks_.find(path);
|
||||||
|
|
|
@ -34,7 +34,7 @@ class VirtualFileSystem {
|
||||||
|
|
||||||
bool RegisterSymbolicLink(const std::string& path, const std::string& target);
|
bool RegisterSymbolicLink(const std::string& path, const std::string& target);
|
||||||
bool UnregisterSymbolicLink(const std::string& path);
|
bool UnregisterSymbolicLink(const std::string& path);
|
||||||
bool VirtualFileSystem::IsSymbolicLink(const std::string& path);
|
bool IsSymbolicLink(const std::string& path);
|
||||||
bool FindSymbolicLink(const std::string& path, std::string& target);
|
bool FindSymbolicLink(const std::string& path, std::string& target);
|
||||||
|
|
||||||
Entry* ResolvePath(const std::string& path);
|
Entry* ResolvePath(const std::string& path);
|
||||||
|
|
Loading…
Reference in New Issue