Wiring up xex loading via the new virtual filesystem.

File resolution is hacked, but works well enough for testing.
This commit is contained in:
Ben Vanik 2013-01-31 16:52:50 -08:00
parent 59189f12ab
commit f78fdba9c3
26 changed files with 392 additions and 60 deletions

View File

@ -26,6 +26,7 @@
#define XE_OPTION_LOG_SDB 1
#define XE_OPTION_LOG_GPU 1
#define XE_OPTION_LOG_KERNEL 1
#define XE_OPTION_LOG_FS 1
// TODO(benvanik): make this a runtime option

View File

@ -23,13 +23,17 @@ namespace fs {
class Device {
public:
Device(xe_pal_ref pal);
Device(xe_pal_ref pal, const char* path);
virtual ~Device();
xe_pal_ref pal();
const char* path();
virtual Entry* ResolvePath(const char* path) = 0;
protected:
xe_pal_ref pal_;
char* path_;
};

View File

@ -45,12 +45,29 @@ private:
};
class MemoryMapping {
public:
MemoryMapping(uint8_t* address, size_t length);
virtual ~MemoryMapping();
uint8_t* address();
size_t length();
private:
uint8_t* address_;
size_t length_;
};
class FileEntry : public Entry {
public:
FileEntry(Device* device, const char* path);
virtual ~FileEntry();
//virtual void Query();
//virtual void Query() = 0;
virtual MemoryMapping* CreateMemoryMapping(
xe_file_mode file_mode, const size_t offset, const size_t length) = 0;
};
@ -59,7 +76,7 @@ public:
DirectoryEntry(Device* device, const char* path);
virtual ~DirectoryEntry();
//virtual void Query();
//virtual void Query() = 0;
};

View File

@ -13,6 +13,8 @@
#include <xenia/common.h>
#include <xenia/core.h>
#include <vector>
#include <xenia/kernel/fs/entry.h>
@ -41,6 +43,8 @@ public:
private:
xe_pal_ref pal_;
std::vector<Device*> devices_;
std::tr1::unordered_map<std::string, std::string> symlinks_;
};

View File

@ -54,10 +54,11 @@ public:
shared_ptr<ExportResolver> export_resolver();
shared_ptr<fs::FileSystem> filesystem();
int LaunchModule(const xechar_t* path);
int LaunchXexFile(const xechar_t* path);
int LaunchDiscImage(const xechar_t* path);
private:
xechar_t command_line_[2048];
xechar_t command_line_[XE_MAX_PATH];
xe_pal_ref pal_;
xe_memory_ref memory_;

View File

@ -34,6 +34,7 @@ typedef uint32_t X_STATUS;
#define X_STATUS_ACCESS_VIOLATION ((uint32_t)0xC0000005L)
#define X_STATUS_INVALID_HANDLE ((uint32_t)0xC0000008L)
#define X_STATUS_INVALID_PARAMETER ((uint32_t)0xC000000DL)
#define X_STATUS_NO_SUCH_FILE ((uint32_t)0xC000000FL)
#define X_STATUS_NO_MEMORY ((uint32_t)0xC0000017L)
#define X_STATUS_ALREADY_COMMITTED ((uint32_t)0xC0000021L)
#define X_STATUS_ACCESS_DENIED ((uint32_t)0xC0000022L)

View File

@ -74,6 +74,11 @@ void xe_log_line(const xechar_t* file_path, const uint32_t line_number,
#else
#define XELOGKERNEL(fmt, ...) XE_EMPTY_MACRO
#endif
#if XE_OPTION(LOG_FS)
#define XELOGFS(fmt, ...) XELOGCORE('F', fmt, ##__VA_ARGS__)
#else
#define XELOGFS(fmt, ...) XE_EMPTY_MACRO
#endif
#endif // XENIA_LOGGING_H_

View File

@ -31,7 +31,7 @@ int strncpy_s(char* dest, size_t destLength, const char* source, size_t count);
#define xestrdupa _strdup
#define xestrtoullw _wcstoui64
#define xestrtoulla _strtoui64
#endif
#endif // !WIN32
#define xestrlenw wcslen
#define xestrcmpw wcscmp
@ -40,6 +40,7 @@ int strncpy_s(char* dest, size_t destLength, const char* source, size_t count);
#define xestrchrw wcschr
#define xestrrchrw wcsrchr
#define xestrstrw wcsstr
#define xestrcasestrw ??
#define xestrcpyw(dest, destLength, source) (wcscpy_s(dest, destLength, source) == 0)
#define xestrncpyw(dest, destLength, source, count) (wcsncpy_s(dest, destLength, source, count) == 0)
#define xestrcatw(dest, destLength, source) (wcscat_s(dest, destLength, source) == 0)
@ -53,6 +54,7 @@ int strncpy_s(char* dest, size_t destLength, const char* source, size_t count);
#define xestrchra strchr
#define xestrrchra strrchr
#define xestrstra strstr
#define xestrcasestra strcasestr
#define xestrcpya(dest, destLength, source) (strcpy_s(dest, destLength, source) == 0)
#define xestrncpya(dest, destLength, source, count) (strncpy_s(dest, destLength, source, count) == 0)
#define xestrcata(dest, destLength, source) (strcat_s(dest, destLength, source) == 0)
@ -72,6 +74,7 @@ typedef wchar_t xechar_t;
#define xestrchr xestrchrw
#define xestrrchr xestrrchrw
#define xestrstr xestrstrw
#define xestrcasestr xestrcasestrw
#define xestrtoull xestrtoullw
#define xestrcpy xestrcpyw
#define xestrncpy xestrncpyw
@ -94,6 +97,7 @@ typedef char xechar_t;
#define xestrchr xestrchra
#define xestrrchr xestrrchra
#define xestrstr xestrstra
#define xestrcasestr xestrcasestra
#define xestrtoull xestrtoulla
#define xestrcpy xestrcpya
#define xestrncpy xestrncpya
@ -109,8 +113,10 @@ typedef char xechar_t;
#if XE_LIKE(WIN32)
#define XE_PATH_SEPARATOR ((xechar_t)'\\')
#define XE_MAX_PATH _MAX_PATH
#else
#define XE_PATH_SEPARATOR ((xechar_t)'/')
#define XE_MAX_PATH PATH_MAX
#endif // WIN32
#endif // XENIA_STRING_H_

View File

@ -71,7 +71,7 @@ int ModuleGenerator::Generate() {
// Setup a debug info builder.
// This is used when creating any debug info. We may want to go more
// fine grained than this, but for now it's something.
char dir[2048];
char dir[XE_MAX_PATH];
XEIGNORE(xestrcpya(dir, XECOUNT(dir), module_path_));
char* slash = xestrrchra(dir, '/');
if (slash) {

View File

@ -97,7 +97,7 @@ int ExecModule::Prepare() {
int result_code = 1;
std::string error_message;
char file_name[2048];
char file_name[XE_MAX_PATH];
OwningPtr<MemoryBuffer> shared_module_buffer;
auto_ptr<Module> shared_module;

View File

@ -133,9 +133,9 @@ int Processor::LoadBinary(const xechar_t* path, uint32_t start_address,
addr, length));
// Prepare the module.
char name_a[2048];
char name_a[XE_MAX_PATH];
XEEXPECTTRUE(xestrnarrow(name_a, XECOUNT(name_a), name));
char path_a[2048];
char path_a[XE_MAX_PATH];
XEEXPECTTRUE(xestrnarrow(path_a, XECOUNT(path_a), path));
exec_module = new ExecModule(

View File

@ -15,10 +15,20 @@ using namespace xe::kernel;
using namespace xe::kernel::fs;
Device::Device(xe_pal_ref pal) {
Device::Device(xe_pal_ref pal, const char* path) {
pal_ = xe_pal_retain(pal);
path_ = xestrdupa(path);
}
Device::~Device() {
xe_free(path_);
xe_pal_release(pal_);
}
xe_pal_ref Device::pal() {
return xe_pal_retain(pal_);
}
const char* Device::path() {
return path_;
}

View File

@ -15,8 +15,9 @@ using namespace xe::kernel;
using namespace xe::kernel::fs;
DiscImageDevice::DiscImageDevice(xe_pal_ref pal, const xechar_t* local_path) :
Device(pal) {
DiscImageDevice::DiscImageDevice(xe_pal_ref pal, const char* path,
const xechar_t* local_path) :
Device(pal, path) {
local_path_ = xestrdup(local_path);
}
@ -25,5 +26,11 @@ DiscImageDevice::~DiscImageDevice() {
}
Entry* DiscImageDevice::ResolvePath(const char* path) {
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
XELOGFS(XT("DiscImageDevice::ResolvePath(%s)"), path);
return NULL;
}

View File

@ -23,7 +23,7 @@ namespace fs {
class DiscImageDevice : public Device {
public:
DiscImageDevice(xe_pal_ref pal, const xechar_t* local_path);
DiscImageDevice(xe_pal_ref pal, const char* path, const xechar_t* local_path);
virtual ~DiscImageDevice();
virtual Entry* ResolvePath(const char* path);

View File

@ -15,9 +15,64 @@ using namespace xe::kernel;
using namespace xe::kernel::fs;
LocalDirectoryDevice::LocalDirectoryDevice(xe_pal_ref pal,
namespace {
class LocalFileMemoryMapping : public MemoryMapping {
public:
LocalFileMemoryMapping(uint8_t* address, size_t length, xe_mmap_ref mmap) :
MemoryMapping(address, length) {
mmap_ = xe_mmap_retain(mmap);
}
virtual ~LocalFileMemoryMapping() {
xe_mmap_release(mmap_);
}
private:
xe_mmap_ref mmap_;
};
class LocalFileEntry : public FileEntry {
public:
LocalFileEntry(Device* device, const char* path, const xechar_t* local_path) :
FileEntry(device, path) {
local_path_ = xestrdup(local_path);
}
virtual ~LocalFileEntry() {
xe_free(local_path_);
}
const xechar_t* local_path() { return local_path_; }
virtual MemoryMapping* CreateMemoryMapping(
xe_file_mode file_mode, const size_t offset, const size_t length) {
xe_pal_ref pal = device()->pal();
xe_mmap_ref mmap = xe_mmap_open(pal, file_mode, local_path_,
offset, length);
xe_pal_release(pal);
if (!mmap) {
return NULL;
}
LocalFileMemoryMapping* lfmm = new LocalFileMemoryMapping(
(uint8_t*)xe_mmap_get_addr(mmap), xe_mmap_get_length(mmap),
mmap);
xe_mmap_release(mmap);
return lfmm;
}
private:
xechar_t* local_path_;
};
}
LocalDirectoryDevice::LocalDirectoryDevice(xe_pal_ref pal, const char* path,
const xechar_t* local_path) :
Device(pal) {
Device(pal, path) {
local_path_ = xestrdup(local_path);
}
@ -26,5 +81,32 @@ LocalDirectoryDevice::~LocalDirectoryDevice() {
}
Entry* LocalDirectoryDevice::ResolvePath(const char* path) {
return NULL;
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
XELOGFS(XT("LocalDirectoryDevice::ResolvePath(%s)"), path);
xechar_t full_path[XE_MAX_PATH];
xesnprintf(full_path, XECOUNT(full_path), XT("%s%c%s"),
local_path_, XE_PATH_SEPARATOR, path);
// Swap around path separators.
if (XE_PATH_SEPARATOR != '\\') {
for (size_t n = 0; n < XECOUNT(full_path); n++) {
if (full_path[n] == 0) {
break;
}
if (full_path[n] == '\\') {
full_path[n] = XE_PATH_SEPARATOR;
}
}
}
// TODO(benvanik): get file info
// TODO(benvanik): fail if does not exit
// TODO(benvanik): switch based on type
LocalFileEntry* file_entry = new LocalFileEntry(this, path, full_path);
return file_entry;
}

View File

@ -23,7 +23,8 @@ namespace fs {
class LocalDirectoryDevice : public Device {
public:
LocalDirectoryDevice(xe_pal_ref pal, const xechar_t* local_path);
LocalDirectoryDevice(xe_pal_ref pal, const char* path,
const xechar_t* local_path);
virtual ~LocalDirectoryDevice();
virtual Entry* ResolvePath(const char* path);

View File

@ -45,6 +45,22 @@ const char* Entry::name() {
}
MemoryMapping::MemoryMapping(uint8_t* address, size_t length) :
address_(address), length_(length) {
}
MemoryMapping::~MemoryMapping() {
}
uint8_t* MemoryMapping::address() {
return address_;
}
size_t MemoryMapping::length() {
return length_;
}
FileEntry::FileEntry(Device* device, const char* path) :
Entry(kTypeFile, device, path) {
}

View File

@ -23,33 +23,91 @@ FileSystem::FileSystem(xe_pal_ref pal) {
}
FileSystem::~FileSystem() {
// Delete all devices.
// This will explode if anyone is still using data from them.
for (std::vector<Device*>::iterator it = devices_.begin();
it != devices_.end(); ++it) {
delete *it;
}
devices_.clear();
symlinks_.clear();
xe_pal_release(pal_);
}
int FileSystem::RegisterDevice(const char* path, Device* device) {
return 1;
devices_.push_back(device);
return 0;
}
int FileSystem::RegisterLocalDirectoryDevice(
const char* path, const xechar_t* local_path) {
Device* device = new LocalDirectoryDevice(pal_, local_path);
Device* device = new LocalDirectoryDevice(pal_, path, local_path);
return RegisterDevice(path, device);
}
int FileSystem::RegisterDiscImageDevice(
const char* path, const xechar_t* local_path) {
Device* device = new DiscImageDevice(pal_, local_path);
Device* device = new DiscImageDevice(pal_, path, local_path);
return RegisterDevice(path, device);
}
int FileSystem::CreateSymbolicLink(const char* path, const char* target) {
return 1;
symlinks_.insert(std::pair<const char*, const char*>(
xestrdupa(path),
xestrdupa(target)));
return 0;
}
int FileSystem::DeleteSymbolicLink(const char* path) {
std::tr1::unordered_map<std::string, std::string>::iterator it =
symlinks_.find(std::string(path));
if (it != symlinks_.end()) {
symlinks_.erase(it);
return 0;
}
return 1;
}
Entry* FileSystem::ResolvePath(const char* path) {
// Strip off prefix and pass to device.
// e.g., d:\some\PATH.foo -> some\PATH.foo
// Support both symlinks and device specifiers, like:
// \\Device\Foo\some\PATH.foo, d:\some\PATH.foo, etc.
// TODO(benvanik): normalize path/etc
// e.g., remove ..'s and such
// Resolve symlinks.
// TODO(benvanik): more robust symlink handling - right now we assume simple
// drive path -> device mappings with nothing nested.
char full_path[XE_MAX_PATH];
XEIGNORE(xestrcpya(full_path, XECOUNT(full_path), path));
for (std::tr1::unordered_map<std::string, std::string>::iterator it =
symlinks_.begin(); it != symlinks_.end(); ++it) {
if (xestrcasestra(path, it->first.c_str()) == path) {
// Found symlink, fixup.
const char* after_path = path + it->first.size();
XEIGNORE(xesnprintf(full_path, XECOUNT(full_path), "%s%s",
it->second.c_str(), after_path));
break;
}
}
// Scan all devices.
for (std::vector<Device*>::iterator it = devices_.begin();
it != devices_.end(); ++it) {
Device* device = *it;
if (xestrcasestra(full_path, device->path()) == full_path) {
// Found!
// Trim the device prefix off and pass down.
char device_path[XE_MAX_PATH];
XEIGNORE(xestrcpya(device_path, XECOUNT(device_path),
full_path + xestrlena(device->path())));
return device->ResolvePath(device_path);
}
}
XELOGE(XT("ResolvePath(%s) failed - no root found"), path);
return NULL;
}

View File

@ -31,6 +31,7 @@ KernelState::KernelState(Runtime* runtime) :
pal_ = runtime->pal();
memory_ = runtime->memory();
processor_ = runtime->processor();
filesystem_ = runtime->filesystem();
objects_mutex_ = xe_mutex_alloc(0);
XEASSERTNOTNULL(objects_mutex_);
@ -65,6 +66,7 @@ KernelState::~KernelState() {
xe_mutex_free(objects_mutex_);
objects_mutex_ = NULL;
filesystem_.reset();
processor_.reset();
xe_memory_release(memory_);
xe_pal_release(pal_);
@ -86,6 +88,10 @@ cpu::Processor* KernelState::processor() {
return processor_.get();
}
fs::FileSystem* KernelState::filesystem() {
return filesystem_.get();
}
// TODO(benvanik): invesitgate better handle storage/structure.
// A much better way of doing handles, if performance becomes an issue, would
// be to try to make the pointers 32bit. Then we could round-trip them through

View File

@ -16,6 +16,7 @@
#include <xenia/kernel/export.h>
#include <xenia/kernel/kernel_module.h>
#include <xenia/kernel/xbox.h>
#include <xenia/kernel/fs/filesystem.h>
namespace xe {
@ -37,6 +38,7 @@ public:
xe_pal_ref pal();
xe_memory_ref memory();
cpu::Processor* processor();
fs::FileSystem* filesystem();
XObject* GetObject(X_HANDLE handle);
@ -52,6 +54,7 @@ private:
xe_pal_ref pal_;
xe_memory_ref memory_;
shared_ptr<cpu::Processor> processor_;
shared_ptr<fs::FileSystem> filesystem_;
XModule* executable_module_;

View File

@ -104,36 +104,24 @@ XboxkrnlModule::XboxkrnlModule(Runtime* runtime) :
XboxkrnlModule::~XboxkrnlModule() {
}
int XboxkrnlModule::LaunchModule(const xechar_t* path) {
// TODO(benvanik): setup the virtual filesystem map and use LoadFromFile.
int XboxkrnlModule::LaunchModule(const char* path) {
// Create and register the module. We keep it local to this function and
// dispose it on exit.
XModule* module = new XModule(kernel_state_.get(), path);
xe_mmap_ref mmap = xe_mmap_open(pal_, kXEFileModeRead, path, 0, 0);
if (!mmap) {
return NULL;
}
void* addr = xe_mmap_get_addr(mmap);
size_t length = xe_mmap_get_length(mmap);
char path_a[2048];
XEIGNORE(xestrnarrow(path_a, XECOUNT(path_a), path));
// Create and load the module.
XModule* module = new XModule(kernel_state_.get(), path_a);
X_STATUS status = module->LoadFromMemory(addr, length);
// TODO(benvanik): retain memory somehow? is it needed?
xe_mmap_release(mmap);
if (XFAILED(status)) {
XELOGE(XT("Failed to load module"));
// Load the module into memory from the filesystem.
X_STATUS result_code = module->LoadFromFile(path);
if (XFAILED(result_code)) {
XELOGE(XT("Failed to load module %s: %.8X"), path, result_code);
module->Release();
return 1;
}
// Launch the module.
status = module->Launch(0);
if (XFAILED(status)) {
XELOGE(XT("Failed to launch module"));
// NOTE: this won't return until the module exits.
result_code = module->Launch(0);
if (XFAILED(result_code)) {
XELOGE(XT("Failed to launch module %s: %.8X"), path, result_code);
module->Release();
return 2;
}

View File

@ -29,7 +29,7 @@ public:
XboxkrnlModule(Runtime* runtime);
virtual ~XboxkrnlModule();
int LaunchModule(const xechar_t* path);
int LaunchModule(const char* path);
private:
auto_ptr<KernelState> kernel_state_;

View File

@ -24,10 +24,13 @@ namespace {
XModule::XModule(KernelState* kernel_state, const char* path) :
XObject(kernel_state, kTypeModule),
xex_(NULL) {
XEIGNORE(xestrcpy(path_, XECOUNT(path_), path));
XEIGNORE(xestrcpya(path_, XECOUNT(path_), path));
const xechar_t *slash = xestrrchr(path, '/');
if (!slash) {
slash = xestrrchr(path, '\\');
}
if (slash) {
XEIGNORE(xestrcpy(name_, XECOUNT(name_), slash + 1));
XEIGNORE(xestrcpya(name_, XECOUNT(name_), slash + 1));
}
}
@ -52,9 +55,33 @@ const xe_xex2_header_t* XModule::xex_header() {
}
X_STATUS XModule::LoadFromFile(const char* path) {
// TODO(benvanik): load from virtual filesystem.
XEASSERTALWAYS();
// Resolve the file to open.
// TODO(benvanik): make this code shared?
fs::Entry* fs_entry = kernel_state()->filesystem()->ResolvePath(path);
if (!fs_entry) {
XELOGE(XT("File not found: %s"), path);
return X_STATUS_NO_SUCH_FILE;
}
if (fs_entry->type() != fs::Entry::kTypeFile) {
XELOGE(XT("Invalid file type: %s"), path);
return X_STATUS_NO_SUCH_FILE;
}
fs::FileEntry* fs_file = static_cast<fs::FileEntry*>(fs_entry);
// Map into memory.
fs::MemoryMapping* mmap = fs_file->CreateMemoryMapping(kXEFileModeRead, 0, 0);
if (!mmap) {
return X_STATUS_UNSUCCESSFUL;
}
// Load the module.
X_STATUS return_code = LoadFromMemory(mmap->address(), mmap->length());
// Unmap memory and cleanup.
delete mmap;
delete fs_entry;
return return_code;
}
X_STATUS XModule::LoadFromMemory(const void* addr, const size_t length) {

View File

@ -48,7 +48,7 @@ private:
int LoadPE();
char name_[256];
char path_[2048];
char path_[XE_MAX_PATH];
xe_xex2_ref xex_;
};

View File

@ -63,6 +63,79 @@ shared_ptr<FileSystem> Runtime::filesystem() {
return filesystem_;
}
int Runtime::LaunchModule(const xechar_t* path) {
return xboxkrnl_->LaunchModule(path);
int Runtime::LaunchXexFile(const xechar_t* path) {
// We create a virtual filesystem pointing to its directory and symlink
// that to the game filesystem.
// e.g., /my/files/foo.xex will get a local fs at:
// \\Device\\Harddisk0\\Partition1
// and then get that symlinked to game:\, so
// -> game:\foo.xex
int result_code = 0;
// Get just the filename (foo.xex).
const xechar_t* file_name = xestrrchr(path, XE_PATH_SEPARATOR);
if (file_name) {
// Skip slash.
file_name++;
} else {
// No slash found, whole thing is a file.
file_name = path;
}
// Get the parent path of the file.
xechar_t parent_path[XE_MAX_PATH];
XEIGNORE(xestrcpy(parent_path, XECOUNT(parent_path), path));
parent_path[file_name - path] = 0;
// Register the local directory in the virtual filesystem.
result_code = filesystem_->RegisterLocalDirectoryDevice(
"\\Device\\Harddisk1\\Partition0", parent_path);
if (result_code) {
XELOGE(XT("Unable to mount local directory %s"), parent_path);
return result_code;
}
// Create symlinks to the device.
filesystem_->CreateSymbolicLink(
"game:", "\\Device\\Harddisk1\\Partition0");
filesystem_->CreateSymbolicLink(
"d:", "\\Device\\Harddisk1\\Partition0");
// Get the file name of the module to load from the filesystem.
char fs_path[XE_MAX_PATH];
XEIGNORE(xestrcpya(fs_path, XECOUNT(fs_path), "game:\\"));
char* fs_path_ptr = fs_path + xestrlena(fs_path);
*fs_path_ptr = 0;
#if XE_WCHAR
XEIGNORE(xestrnarrow(fs_path_ptr, XECOUNT(fs_path), file_name));
#else
XEIGNORE(xestrcpya(fs_path_ptr, XECOUNT(fs_path), file_name));
#endif
// Launch the game.
return xboxkrnl_->LaunchModule(fs_path);
}
int Runtime::LaunchDiscImage(const xechar_t* path) {
int result_code = 0;
// Register the disc image in the virtual filesystem.
result_code = filesystem_->RegisterDiscImageDevice(
"\\Device\\Cdrom0", path);
if (result_code) {
XELOGE(XT("Unable to mount disc image %s"), path);
return result_code;
}
// Create symlinks to the device.
filesystem_->CreateSymbolicLink(
"game:",
"\\Device\\Cdrom0");
filesystem_->CreateSymbolicLink(
"d:",
"\\Device\\Cdrom0");
// Launch the game.
return xboxkrnl_->LaunchModule("game:\\default.xex");
}

View File

@ -70,10 +70,32 @@ int Run::Launch(const xechar_t* path) {
return 0;
}
// TODO(benvanik): wait until the module thread exits
runtime_->LaunchModule(path);
// Normalize the path and make absolute.
// TODO(benvanik): move this someplace common.
xechar_t abs_path[XE_MAX_PATH];
#if XE_PLATFORM(WIN32)
_wfullpath(abs_path, path, XECOUNT(abs_path));
#else
realpath(path, abs_path);
#endif // WIN32
return 0;
// Grab file extension.
const xechar_t* dot = xestrrchr(abs_path, '.');
if (!dot) {
XELOGE(XT("Invalid input path; no extension found"));
return 1;
}
// Launch based on file type.
// This is a silly guess based on file extension.
// NOTE: the runtime launch routine will wait until the module exits.
if (xestrcmp(dot, XT(".xex")) == 0) {
// Treat as a naked xex file.
return runtime_->LaunchXexFile(abs_path);
} else {
// Assume a disc image.
return runtime_->LaunchDiscImage(abs_path);
}
}
int xenia_run(int argc, xechar_t **argv) {