More string swapping and cleaning up main().
This commit is contained in:
parent
18ee972b47
commit
7c5fa88661
|
@ -14,7 +14,6 @@
|
|||
#include <xenia/config.h>
|
||||
#include <xenia/logging.h>
|
||||
#include <xenia/malloc.h>
|
||||
#include <xenia/platform.h>
|
||||
#include <xenia/profiling.h>
|
||||
#include <xenia/string.h>
|
||||
#include <xenia/types.h>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef POLY_MAIN_H_
|
||||
#define POLY_MAIN_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <poly/platform.h>
|
||||
|
||||
namespace poly {
|
||||
|
||||
// Returns true if there is a user-visible console attached to receive stdout.
|
||||
bool has_console_attached();
|
||||
|
||||
// Extern defined by user code. This must be present for the application to
|
||||
// launch.
|
||||
struct EntryInfo {
|
||||
std::wstring name;
|
||||
std::wstring usage;
|
||||
int (*entry_point)(std::vector<std::wstring>& args);
|
||||
};
|
||||
EntryInfo GetEntryInfo();
|
||||
|
||||
#define DEFINE_ENTRY_POINT(name, usage, entry_point) \
|
||||
poly::EntryInfo poly::GetEntryInfo() { return {name, usage, entry_point}; }
|
||||
|
||||
} // namespace poly
|
||||
|
||||
#endif // POLY_MAIN_H_
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <poly/main.h>
|
||||
|
||||
#include <poly/string.h>
|
||||
|
||||
namespace poly {
|
||||
|
||||
bool has_console_attached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace poly
|
||||
|
||||
|
||||
extern "C" int main(int argc, char** argv) {
|
||||
auto entry_info = poly::GetEntryInfo();
|
||||
|
||||
google::SetUsageMessage(std::string("usage: ") +
|
||||
poly::to_string(entry_info.usage));
|
||||
google::SetVersionString("1.0");
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
std::vector<std::wstring> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
args.push_back(poly::to_wstring(argv[n]));
|
||||
}
|
||||
|
||||
// Call app-provided entry point.
|
||||
int result = entry_info.entry_point(args);
|
||||
|
||||
google::ShutDownCommandLineFlags();
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <poly/main.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <poly/string.h>
|
||||
|
||||
namespace poly {
|
||||
|
||||
bool has_console_attached_ = true;
|
||||
|
||||
bool has_console_attached() { return has_console_attached_; }
|
||||
|
||||
void AttachConsole() {
|
||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||
if (!has_console) {
|
||||
// We weren't launched from a console, so just return.
|
||||
// We could alloc our own console, but meh:
|
||||
// has_console = AllocConsole() == TRUE;
|
||||
has_console_attached_ = false;
|
||||
return;
|
||||
}
|
||||
has_console_attached_ = true;
|
||||
|
||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
auto fp = _fdopen(con_handle, "w");
|
||||
*stdout = *fp;
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
fp = _fdopen(con_handle, "w");
|
||||
*stderr = *fp;
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
}
|
||||
|
||||
} // namespace poly
|
||||
|
||||
// Used in console mode apps; automatically picked based on subsystem.
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
auto entry_info = poly::GetEntryInfo();
|
||||
|
||||
google::SetUsageMessage(std::string("usage: ") +
|
||||
poly::to_string(entry_info.usage));
|
||||
google::SetVersionString("1.0");
|
||||
|
||||
// Convert all args to narrow, as gflags doesn't support wchar.
|
||||
int argca = argc;
|
||||
char** argva = (char**)alloca(sizeof(char*) * argca);
|
||||
for (int n = 0; n < argca; n++) {
|
||||
size_t len = wcslen(argv[n]);
|
||||
argva[n] = (char*)alloca(len + 1);
|
||||
wcstombs_s(NULL, argva[n], len + 1, argv[n], _TRUNCATE);
|
||||
}
|
||||
|
||||
// Parse flags; this may delete some of them.
|
||||
google::ParseCommandLineFlags(&argc, &argva, true);
|
||||
|
||||
// Widen all remaining flags and convert to usable strings.
|
||||
std::vector<std::wstring> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
args.push_back(poly::to_wstring(argva[n]));
|
||||
}
|
||||
|
||||
// Call app-provided entry point.
|
||||
int result = entry_info.entry_point(args);
|
||||
|
||||
google::ShutDownCommandLineFlags();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Used in windowed apps; automatically picked based on subsystem.
|
||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
|
||||
// Attach a console so we can write output to stdout. If the user hasn't
|
||||
// redirected output themselves it'll pop up a window.
|
||||
poly::AttachConsole();
|
||||
|
||||
auto entry_info = poly::GetEntryInfo();
|
||||
|
||||
// Convert to an argv-like format so we can share code/use gflags.
|
||||
std::wstring buffer = entry_info.name + L" " + command_line;
|
||||
int argc;
|
||||
wchar_t** argv = CommandLineToArgvW(buffer.c_str(), &argc);
|
||||
if (!argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Run normal entry point.
|
||||
int result = wmain(argc, argv);
|
||||
|
||||
LocalFree(argv);
|
||||
return result;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef POLY_POLY_PRIVATE_H_
|
||||
#define POLY_POLY_PRIVATE_H_
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#endif // POLY_POLY_PRIVATE_H_
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <poly/poly.h>
|
||||
#include <poly/poly-private.h>
|
||||
|
||||
namespace poly {} // namespace poly
|
|
@ -7,13 +7,12 @@
|
|||
'debugging.h',
|
||||
'config.h',
|
||||
'cxx_compat.h',
|
||||
'main.h',
|
||||
'math.cc',
|
||||
'math.h',
|
||||
'memory.cc',
|
||||
'memory.h',
|
||||
'platform.h',
|
||||
'poly-private.h',
|
||||
'poly.cc',
|
||||
'poly.h',
|
||||
'string.cc',
|
||||
'string.h',
|
||||
|
@ -23,6 +22,7 @@
|
|||
'conditions': [
|
||||
['OS == "mac" or OS == "linux"', {
|
||||
'sources': [
|
||||
'main_posix.cc',
|
||||
],
|
||||
}],
|
||||
['OS == "linux"', {
|
||||
|
@ -39,6 +39,7 @@
|
|||
['OS == "win"', {
|
||||
'sources': [
|
||||
'debugging_win.cc',
|
||||
'main_win.cc',
|
||||
'threading_win.cc',
|
||||
],
|
||||
}],
|
||||
|
|
|
@ -39,4 +39,52 @@ std::string::size_type find_first_of_case(const std::string& target,
|
|||
}
|
||||
}
|
||||
|
||||
std::wstring to_absolute_path(const std::wstring& path) {
|
||||
#if XE_LIKE_WIN32
|
||||
wchar_t buffer[poly::max_path];
|
||||
_wfullpath(buffer, path.c_str(), sizeof(buffer) / sizeof(wchar_t));
|
||||
return buffer;
|
||||
#else
|
||||
char buffer[poly::max_path];
|
||||
realpath(poly::to_string(path).c_str(), buffer);
|
||||
return poly::to_wstring(buffer);
|
||||
#endif // XE_LIKE_WIN32
|
||||
}
|
||||
|
||||
std::wstring join_paths(const std::wstring& left, const std::wstring& right,
|
||||
wchar_t sep) {
|
||||
if (!left.size()) {
|
||||
return right;
|
||||
} else if (!right.size()) {
|
||||
return left;
|
||||
}
|
||||
if (left[left.size() - 1] == sep) {
|
||||
return left + right;
|
||||
} else {
|
||||
return left + sep + right;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring fix_path_separators(const std::wstring& source, wchar_t new_sep) {
|
||||
// Swap all separators to new_sep.
|
||||
wchar_t old_sep = new_sep == '\\' ? '/' : '\\';
|
||||
std::wstring::size_type pos = 0;
|
||||
std::wstring dest = source;
|
||||
while ((pos = source.find_first_of(old_sep, pos)) != std::wstring::npos) {
|
||||
dest[pos] = new_sep;
|
||||
++pos;
|
||||
}
|
||||
// Replace redundant separators.
|
||||
pos = 0;
|
||||
while ((pos = dest.find_first_of(new_sep, pos)) != std::wstring::npos) {
|
||||
if (pos < dest.size() - 1) {
|
||||
if (dest[pos + 1] == new_sep) {
|
||||
dest.erase(pos + 1, 1);
|
||||
}
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
} // namespace poly
|
||||
|
|
|
@ -24,9 +24,22 @@ namespace poly {
|
|||
std::string to_string(const std::wstring& source);
|
||||
std::wstring to_wstring(const std::string& source);
|
||||
|
||||
// find_first_of string, case insensitive.
|
||||
std::string::size_type find_first_of_case(const std::string& target,
|
||||
const std::string& search);
|
||||
|
||||
// Converts the given path to an absolute path based on cwd.
|
||||
std::wstring to_absolute_path(const std::wstring& path);
|
||||
|
||||
// Joins two path segments with the given separator.
|
||||
std::wstring join_paths(const std::wstring& left, const std::wstring& right,
|
||||
wchar_t sep = poly::path_separator);
|
||||
|
||||
// Replaces all path separators with the given value and removes redundant
|
||||
// separators.
|
||||
std::wstring fix_path_separators(const std::wstring& source,
|
||||
wchar_t new_sep = poly::path_separator);
|
||||
|
||||
} // namespace poly
|
||||
|
||||
#endif // POLY_STRING_H_
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include <xenia/config.h>
|
||||
#include <xenia/logging.h>
|
||||
#include <xenia/malloc.h>
|
||||
#include <xenia/platform.h>
|
||||
#include <xenia/profiling.h>
|
||||
#include <xenia/string.h>
|
||||
#include <xenia/types.h>
|
||||
|
|
|
@ -20,7 +20,6 @@ namespace xe {
|
|||
#include <xenia/core/hash.h>
|
||||
#include <xenia/core/mmap.h>
|
||||
#include <xenia/core/pal.h>
|
||||
#include <xenia/core/path.h>
|
||||
#include <xenia/core/ref.h>
|
||||
#include <xenia/core/run_loop.h>
|
||||
#include <xenia/core/socket.h>
|
||||
|
|
|
@ -1,35 +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/core/path.h>
|
||||
|
||||
|
||||
void xe_path_join(const xechar_t* left, const xechar_t* right,
|
||||
xechar_t* out_path, size_t out_path_size) {
|
||||
#if XE_WCHAR
|
||||
xesnprintf(out_path, out_path_size, L"%ls%c%ls",
|
||||
left, poly::path_separator, right);
|
||||
#else
|
||||
xesnprintf(out_path, out_path_size, L"%s%c%s",
|
||||
left, poly::path_separator, right);
|
||||
#endif // XE_WCHAR
|
||||
}
|
||||
|
||||
void xe_path_get_absolute(const xechar_t* path, xechar_t* out_abs_path,
|
||||
size_t abs_path_size) {
|
||||
#if XE_PLATFORM_WIN32
|
||||
#if XE_WCHAR
|
||||
_wfullpath(out_abs_path, path, abs_path_size);
|
||||
#else
|
||||
_fullpath(out_abs_path, path, abs_path_size);
|
||||
#endif // XE_WCHAR
|
||||
#else
|
||||
realpath(path, out_abs_path);
|
||||
#endif // WIN32
|
||||
}
|
|
@ -1,25 +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. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CORE_PATH_H_
|
||||
#define XENIA_CORE_PATH_H_
|
||||
|
||||
#include <xenia/common.h>
|
||||
#include <xenia/core/pal.h>
|
||||
#include <xenia/core/ref.h>
|
||||
|
||||
|
||||
void xe_path_join(const xechar_t* left, const xechar_t* right,
|
||||
xechar_t* out_path, size_t out_path_size);
|
||||
void xe_path_get_absolute(const xechar_t* path, xechar_t* out_abs_path,
|
||||
size_t abs_path_size);
|
||||
|
||||
const xechar_t* xe_path_get_tmp(const xechar_t* prefix);
|
||||
|
||||
#endif // XENIA_CORE_PATH_H_
|
|
@ -1,16 +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/core/path.h>
|
||||
|
||||
|
||||
const xechar_t* xe_path_get_tmp(const xechar_t* prefix) {
|
||||
//
|
||||
assert_always();
|
||||
}
|
|
@ -1,17 +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/core/path.h>
|
||||
|
||||
|
||||
const xechar_t* xe_path_get_tmp(const xechar_t* prefix) {
|
||||
//
|
||||
assert_always();
|
||||
return NULL;
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
'hash.h',
|
||||
'mmap.h',
|
||||
'pal.h',
|
||||
'path.cc',
|
||||
'path.h',
|
||||
'ref.cc',
|
||||
'ref.h',
|
||||
'run_loop.h',
|
||||
|
@ -17,7 +15,6 @@
|
|||
['OS == "mac" or OS == "linux"', {
|
||||
'sources': [
|
||||
'mmap_posix.cc',
|
||||
'path_posix.cc',
|
||||
'socket_posix.cc',
|
||||
],
|
||||
}],
|
||||
|
@ -35,7 +32,6 @@
|
|||
'sources': [
|
||||
'mmap_win.cc',
|
||||
'pal_win.cc',
|
||||
'path_win.cc',
|
||||
'run_loop_win.cc',
|
||||
'socket_win.cc',
|
||||
],
|
||||
|
|
|
@ -59,7 +59,7 @@ Entry* DiscImageDevice::ResolvePath(const char* path) {
|
|||
char remaining[poly::max_path];
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), path));
|
||||
while (remaining[0]) {
|
||||
char* next_slash = xestrchra(remaining, '\\');
|
||||
char* next_slash = strchr(remaining, '\\');
|
||||
if (next_slash == remaining) {
|
||||
// Leading slash - shift
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), remaining + 1));
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include <xenia/kernel/fs/devices/host_path_device.h>
|
||||
|
||||
#include <xenia/core/path.h>
|
||||
#include <xenia/kernel/fs/devices/host_path_entry.h>
|
||||
#include <xenia/kernel/objects/xfile.h>
|
||||
|
||||
|
@ -30,27 +29,9 @@ Entry* HostPathDevice::ResolvePath(const char* path) {
|
|||
|
||||
XELOGFS("HostPathDevice::ResolvePath(%s)", path);
|
||||
|
||||
#if XE_WCHAR
|
||||
xechar_t rel_path[poly::max_path];
|
||||
XEIGNORE(xestrwiden(rel_path, XECOUNT(rel_path), path));
|
||||
#else
|
||||
const xechar_t* rel_path = path;
|
||||
#endif
|
||||
|
||||
xechar_t full_path[poly::max_path];
|
||||
xe_path_join(local_path_.c_str(), rel_path, full_path, XECOUNT(full_path));
|
||||
|
||||
// Swap around path separators.
|
||||
if (poly::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] = poly::path_separator;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto rel_path = poly::to_wstring(path);
|
||||
auto full_path = poly::join_paths(local_path_, rel_path);
|
||||
full_path = poly::fix_path_separators(full_path);
|
||||
|
||||
// TODO(benvanik): get file info
|
||||
// TODO(benvanik): fail if does not exit
|
||||
|
|
|
@ -59,7 +59,7 @@ Entry* STFSContainerDevice::ResolvePath(const char* path) {
|
|||
char remaining[poly::max_path];
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), path));
|
||||
while (remaining[0]) {
|
||||
char* next_slash = xestrchra(remaining, '\\');
|
||||
char* next_slash = strchr(remaining, '\\');
|
||||
if (next_slash == remaining) {
|
||||
// Leading slash - shift
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), remaining + 1));
|
||||
|
|
|
@ -17,19 +17,20 @@ using namespace xe::cpu;
|
|||
using namespace xe::kernel;
|
||||
|
||||
|
||||
XModule::XModule(KernelState* kernel_state, const char* path) :
|
||||
XObject(kernel_state, kTypeModule) {
|
||||
XEIGNORE(xestrcpya(path_, XECOUNT(path_), path));
|
||||
const char* slash = xestrrchra(path, '/');
|
||||
if (!slash) {
|
||||
slash = xestrrchra(path, '\\');
|
||||
XModule::XModule(KernelState* kernel_state, const std::string& path) :
|
||||
XObject(kernel_state, kTypeModule), path_(path) {
|
||||
auto last_slash = path.find_last_of('/');
|
||||
if (last_slash == path.npos) {
|
||||
last_slash = path.find_last_of('\\');
|
||||
}
|
||||
if (slash) {
|
||||
XEIGNORE(xestrcpya(name_, XECOUNT(name_), slash + 1));
|
||||
if (last_slash == path.npos) {
|
||||
name_ = path_;
|
||||
} else {
|
||||
name_ = path_.substr(last_slash + 1);
|
||||
}
|
||||
char* dot = xestrrchra(name_, '.');
|
||||
if (dot) {
|
||||
*dot = 0;
|
||||
auto dot = name_.find_last_of('.');
|
||||
if (dot != name_.npos) {
|
||||
name_ = name_.substr(0, dot);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#ifndef XENIA_KERNEL_XBOXKRNL_XMODULE_H_
|
||||
#define XENIA_KERNEL_XBOXKRNL_XMODULE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <xenia/kernel/xobject.h>
|
||||
|
||||
#include <xenia/xbox.h>
|
||||
|
@ -21,11 +23,11 @@ namespace kernel {
|
|||
|
||||
class XModule : public XObject {
|
||||
public:
|
||||
XModule(KernelState* kernel_state, const char* path);
|
||||
XModule(KernelState* kernel_state, const std::string& path);
|
||||
virtual ~XModule();
|
||||
|
||||
const char* path() const { return path_; }
|
||||
const char* name() const { return name_; }
|
||||
const std::string& path() const { return path_; }
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
virtual void* GetProcAddressByOrdinal(uint16_t ordinal) = 0;
|
||||
virtual X_STATUS GetSection(
|
||||
|
@ -35,8 +37,8 @@ public:
|
|||
protected:
|
||||
void OnLoad();
|
||||
|
||||
char name_[256];
|
||||
char path_[poly::max_path];
|
||||
std::string name_;
|
||||
std::string path_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -200,19 +200,19 @@ SHIM_CALL RtlInitUnicodeString_shim(
|
|||
uint32_t destination_ptr = SHIM_GET_ARG_32(0);
|
||||
uint32_t source_ptr = SHIM_GET_ARG_32(1);
|
||||
|
||||
const wchar_t* source =
|
||||
source_ptr ? (const wchar_t*)SHIM_MEM_ADDR(source_ptr) : NULL;
|
||||
XELOGD("RtlInitUnicodeString(%.8X, %.8X = %ls)",
|
||||
destination_ptr, source_ptr, source ? source : L"<null>");
|
||||
auto source =
|
||||
source_ptr ? poly::load_and_swap<std::wstring>(SHIM_MEM_ADDR(source_ptr))
|
||||
: L"";
|
||||
XELOGD("RtlInitUnicodeString(%.8X, %.8X = %ls)", destination_ptr, source_ptr,
|
||||
source.empty() ? L"<null>" : source.c_str());
|
||||
|
||||
// VOID
|
||||
// _Out_ PUNICODE_STRING DestinationString,
|
||||
// _In_opt_ PCWSTR SourceString
|
||||
|
||||
if (source) {
|
||||
uint16_t length = (uint16_t)xestrlenw(source);
|
||||
SHIM_SET_MEM_16(destination_ptr + 0, length * 2);
|
||||
SHIM_SET_MEM_16(destination_ptr + 2, (length + 1) * 2);
|
||||
if (source.size()) {
|
||||
SHIM_SET_MEM_16(destination_ptr + 0, source.size() * 2);
|
||||
SHIM_SET_MEM_16(destination_ptr + 2, (source.size() + 1) * 2);
|
||||
SHIM_SET_MEM_32(destination_ptr + 4, source_ptr);
|
||||
} else {
|
||||
SHIM_SET_MEM_16(destination_ptr + 0, 0);
|
||||
|
|
|
@ -11,10 +11,9 @@
|
|||
|
||||
#include <mutex>
|
||||
|
||||
#include <xenia/common.h>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <poly/main.h>
|
||||
#include <xenia/common.h>
|
||||
|
||||
DEFINE_bool(fast_stdout, false,
|
||||
"Don't lock around stdout/stderr. May introduce weirdness.");
|
||||
|
@ -31,7 +30,7 @@ void xe_format_log_line(
|
|||
const char* function_name, const char level_char,
|
||||
const char* fmt, va_list args) {
|
||||
// Strip out just the filename from the path.
|
||||
const char* filename = xestrrchra(file_path, poly::path_separator);
|
||||
const char* filename = strrchr(file_path, poly::path_separator);
|
||||
if (filename) {
|
||||
// Slash - skip over it.
|
||||
filename++;
|
||||
|
@ -108,7 +107,7 @@ void xe_handle_fatal(
|
|||
}
|
||||
|
||||
#if XE_LIKE_WIN32
|
||||
if (!xe_has_console()) {
|
||||
if (!poly::has_console_attached()) {
|
||||
MessageBoxA(NULL, buffer, "Xenia Error",
|
||||
MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
#include <xenia/platform.h>
|
||||
#include <xenia/config.h>
|
||||
#include <xenia/string.h>
|
||||
|
||||
|
|
|
@ -1,126 +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/platform.h>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <xenia/common.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
typedef int (*user_main_t)(int argc, xechar_t** argv);
|
||||
|
||||
bool _has_console = true;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
#if XE_LIKE_WIN32 && defined(UNICODE) && UNICODE
|
||||
|
||||
#include <windows.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
int xe_main_thunk(
|
||||
int argc, wchar_t* argv[],
|
||||
void* user_main, const char* usage) {
|
||||
google::SetUsageMessage(std::string("usage: ") + usage);
|
||||
google::SetVersionString("1.0");
|
||||
|
||||
int argca = argc;
|
||||
char** argva = (char**)alloca(sizeof(char*) * argca);
|
||||
for (int n = 0; n < argca; n++) {
|
||||
size_t len = xestrlenw(argv[n]);
|
||||
argva[n] = (char*)alloca(len + 1);
|
||||
xestrnarrow(argva[n], len + 1, argv[n]);
|
||||
}
|
||||
|
||||
google::ParseCommandLineFlags(&argc, &argva, true);
|
||||
|
||||
// Parse may have deleted flags - so widen again.
|
||||
int argcw = argc;
|
||||
wchar_t** argvw = (wchar_t**)alloca(sizeof(wchar_t*) * argca);
|
||||
for (int n = 0; n < argc; n++) {
|
||||
size_t len = xestrlena(argva[n]);
|
||||
argvw[n] = (wchar_t*)alloca(sizeof(wchar_t) * (len + 1));
|
||||
xestrwiden(argvw[n], len + 1, argva[n]);
|
||||
}
|
||||
|
||||
int result = ((user_main_t)user_main)(argcw, (xechar_t**)argvw);
|
||||
google::ShutDownCommandLineFlags();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool xe_has_console() {
|
||||
return _has_console;
|
||||
}
|
||||
|
||||
void xe_attach_console() {
|
||||
bool has_console = AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||
if (!has_console) {
|
||||
// We weren't launched from a console, so just return.
|
||||
// We could alloc our own console, but meh:
|
||||
// has_console = AllocConsole() == TRUE;
|
||||
_has_console = false;
|
||||
return;
|
||||
}
|
||||
_has_console = true;
|
||||
|
||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
auto fp = _fdopen(con_handle, "w");
|
||||
*stdout = *fp;
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
fp = _fdopen(con_handle, "w");
|
||||
*stderr = *fp;
|
||||
setvbuf(stderr, NULL, _IONBF, 0);
|
||||
}
|
||||
|
||||
int xe_main_window_thunk(
|
||||
wchar_t* command_line,
|
||||
void* user_main, const wchar_t* name, const char* usage) {
|
||||
xe_attach_console();
|
||||
wchar_t buffer[2048];
|
||||
xestrcpy(buffer, XECOUNT(buffer), name);
|
||||
xestrcat(buffer, XECOUNT(buffer), L" ");
|
||||
xestrcat(buffer, XECOUNT(buffer), command_line);
|
||||
int argc;
|
||||
wchar_t** argv = CommandLineToArgvW(buffer, &argc);
|
||||
if (!argv) {
|
||||
return 1;
|
||||
}
|
||||
int result = xe_main_thunk(argc, argv, user_main, usage);
|
||||
LocalFree(argv);
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool xe_has_console() {
|
||||
return _has_console;
|
||||
}
|
||||
|
||||
int xe_main_thunk(
|
||||
int argc, char** argv,
|
||||
void* user_main, const char* usage) {
|
||||
google::SetUsageMessage(std::string("usage: ") + usage);
|
||||
google::SetVersionString("1.0");
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
int result = ((user_main_t)user_main)(argc, argv);
|
||||
google::ShutDownCommandLineFlags();
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // WIN32
|
|
@ -1,44 +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. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_PLATFORM_H_
|
||||
#define XENIA_PLATFORM_H_
|
||||
|
||||
#include <poly/platform.h>
|
||||
|
||||
bool xe_has_console();
|
||||
#if XE_LIKE_WIN32 && defined(UNICODE) && UNICODE
|
||||
int xe_main_thunk(
|
||||
int argc, wchar_t* argv[],
|
||||
void* user_main, const char* usage);
|
||||
#define XE_MAIN_THUNK(NAME, USAGE) \
|
||||
int wmain(int argc, wchar_t *argv[]) { \
|
||||
return xe_main_thunk(argc, argv, NAME, USAGE); \
|
||||
}
|
||||
int xe_main_window_thunk(
|
||||
wchar_t* command_line,
|
||||
void* user_main, const wchar_t* name, const char* usage);
|
||||
#define XE_MAIN_WINDOW_THUNK(NAME, APP_NAME, USAGE) \
|
||||
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPTSTR command_line, int) { \
|
||||
return xe_main_window_thunk(command_line, NAME, APP_NAME, USAGE); \
|
||||
}
|
||||
#else
|
||||
int xe_main_thunk(
|
||||
int argc, char** argv,
|
||||
void* user_main, const char* usage);
|
||||
#define XE_MAIN_THUNK(NAME, USAGE) \
|
||||
int main(int argc, char **argv) { \
|
||||
return xe_main_thunk(argc, argv, (void*)NAME, USAGE); \
|
||||
}
|
||||
#define XE_MAIN_WINDOW_THUNK(NAME, APP_NAME, USAGE) \
|
||||
XE_MAIN_THUNK(NAME, USAGE)
|
||||
#endif // WIN32
|
||||
|
||||
|
||||
#endif // XENIA_PLATFORM_H_
|
|
@ -13,7 +13,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include <xenia/config.h>
|
||||
#include <xenia/platform.h>
|
||||
#include <xenia/string.h>
|
||||
#include <xenia/types.h>
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
'logging.h',
|
||||
'malloc.cc',
|
||||
'malloc.h',
|
||||
'platform.cc',
|
||||
'platform.h',
|
||||
'profiling.cc',
|
||||
'profiling.h',
|
||||
'string.h',
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#ifndef XENIA_STRING_H_
|
||||
#define XENIA_STRING_H_
|
||||
|
||||
#include <xenia/platform.h>
|
||||
#include <poly/string.h>
|
||||
|
||||
// NOTE: these differing implementations should behave pretty much the same.
|
||||
|
@ -23,39 +22,25 @@ int strncpy_s(char* dest, size_t destLength, const char* source, size_t count);
|
|||
#define _snprintf_s(dest, destLength, x, format, ...) snprintf(dest, destLength, format, ##__VA_ARGS__)
|
||||
#endif // !WIN32
|
||||
|
||||
#define xestrlenw wcslen
|
||||
#define xestrchrw wcschr
|
||||
#define xestrcpyw(dest, destLength, source) (wcscpy_s(dest, destLength, source) == 0)
|
||||
#define xestrcatw(dest, destLength, source) (wcscat_s(dest, destLength, source) == 0)
|
||||
#define xesnprintfw(buffer, bufferCount, format, ...) _snwprintf_s(buffer, bufferCount, (bufferCount) ? (bufferCount - 1) : 0, format, ##__VA_ARGS__)
|
||||
|
||||
#define xestrlena strlen
|
||||
#define xestrchra strchr
|
||||
#define xestrrchra strrchr
|
||||
#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)
|
||||
#define xesnprintfa(buffer, bufferCount, format, ...) _snprintf_s(buffer, bufferCount, bufferCount, format, ##__VA_ARGS__)
|
||||
#define xevsnprintfa(buffer, bufferCount, format, args) vsnprintf(buffer, bufferCount, format, args)
|
||||
|
||||
#if XE_PLATFORM_WIN32 && defined(UNICODE) && UNICODE
|
||||
|
||||
typedef wchar_t xechar_t;
|
||||
#define XE_WCHAR 1
|
||||
|
||||
// xestrchr 2 uses in fs
|
||||
// xestrrchra xmodule/logging
|
||||
// xestrcpy fs + module
|
||||
// xestrncpya one use in xbox.h
|
||||
// xestrcat 2 uses in platform
|
||||
// xesnprintf many uses - only remove some?
|
||||
// xevsnprintf logging, disasm
|
||||
|
||||
#define xestrcpy xestrcpyw
|
||||
#define xestrcat xestrcatw
|
||||
#define xesnprintf xesnprintfw
|
||||
#define xestrnarrow(dest, destLength, source) (wcstombs_s(NULL, dest, destLength, source, _TRUNCATE) == 0)
|
||||
#define xestrwiden(dest, destLength, source) (mbstowcs_s(NULL, dest, destLength, source, _TRUNCATE) == 0)
|
||||
|
||||
#else
|
||||
|
||||
|
@ -63,10 +48,7 @@ typedef char xechar_t;
|
|||
#define XE_CHAR 1
|
||||
|
||||
#define xestrcpy xestrcpya
|
||||
#define xestrcat xestrcata
|
||||
#define xesnprintf xesnprintfa
|
||||
#define xestrnarrow(dest, destLength, source) xestrcpy(dest, destLength, source)
|
||||
#define xestrwiden(dest, destLength, source) xestrcpy(dest, destLength, source)
|
||||
|
||||
#endif // WIN32
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include <xenia/platform.h>
|
||||
#include <poly/platform.h>
|
||||
|
||||
|
||||
#define XE_EMPTY_MACRO do { } while(0)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <alloy/frontend/ppc/ppc_context.h>
|
||||
#include <alloy/frontend/ppc/ppc_frontend.h>
|
||||
#include <alloy/runtime/raw_module.h>
|
||||
#include <poly/main.h>
|
||||
#include <poly/poly.h>
|
||||
#include <xenia/cpu/xenon_memory.h>
|
||||
|
||||
|
@ -75,7 +76,7 @@ class ThreadState : public alloy::runtime::ThreadState {
|
|||
|
||||
// TODO(benvanik): simple memory? move more into core?
|
||||
|
||||
int main(int argc, xechar_t** argv) {
|
||||
int main(std::vector<std::wstring>& args) {
|
||||
xe::Profiler::Initialize();
|
||||
xe::Profiler::ThreadEnter("main");
|
||||
|
||||
|
@ -122,7 +123,4 @@ int main(int argc, xechar_t** argv) {
|
|||
} // namespace sandbox
|
||||
} // namespace alloy
|
||||
|
||||
// TODO(benvanik): move main thunk into poly
|
||||
// ehhh
|
||||
#include <xenia/platform.cc>
|
||||
XE_MAIN_THUNK(alloy::sandbox::main, "alloy-sandbox");
|
||||
DEFINE_ENTRY_POINT(L"alloy-sandbox", L"?", alloy::sandbox::main);
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
'target_name': 'alloy-sandbox',
|
||||
'type': 'executable',
|
||||
|
||||
'msvs_settings': {
|
||||
'VCLinkerTool': {
|
||||
'SubSystem': '1'
|
||||
},
|
||||
},
|
||||
|
||||
'dependencies': [
|
||||
'alloy',
|
||||
'xenia',
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <memory>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <poly/main.h>
|
||||
#include <poly/poly.h>
|
||||
#include <xdb/postmortem_debug_target.h>
|
||||
#include <xdb/xdb.h>
|
||||
|
@ -21,7 +22,7 @@ namespace xc {
|
|||
|
||||
using xdb::PostmortemDebugTarget;
|
||||
|
||||
int main(int argc, xechar_t** argv) {
|
||||
int main(std::vector<std::wstring>& args) {
|
||||
// Create platform abstraction layer.
|
||||
xe_pal_options_t pal_options;
|
||||
xe_zero_struct(&pal_options, sizeof(pal_options));
|
||||
|
@ -46,6 +47,4 @@ int main(int argc, xechar_t** argv) {
|
|||
|
||||
} // namespace xc
|
||||
|
||||
// TODO(benvanik): move main thunk into poly
|
||||
// ehhh
|
||||
XE_MAIN_WINDOW_THUNK(xc::main, L"xenia-compare", "xenia-compare");
|
||||
DEFINE_ENTRY_POINT(L"xenia-compare", L"xenia-compare", xc::main);
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
'msvs_settings': {
|
||||
'VCLinkerTool': {
|
||||
'SubSystem': '2'
|
||||
}
|
||||
'SubSystem': '1'
|
||||
},
|
||||
},
|
||||
|
||||
'dependencies': [
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <memory>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <poly/main.h>
|
||||
#include <poly/poly.h>
|
||||
#include <third_party/wxWidgets/include/wx/wx.h>
|
||||
#include <xdb/ui/xdb_app.h>
|
||||
|
@ -22,7 +23,7 @@ DEFINE_string(content_file, "",
|
|||
|
||||
namespace xdb {
|
||||
|
||||
int main(int argc, xechar_t** argv) {
|
||||
int main(std::vector<std::wstring>& args) {
|
||||
// Create platform abstraction layer.
|
||||
xe_pal_options_t pal_options;
|
||||
xe_zero_struct(&pal_options, sizeof(pal_options));
|
||||
|
@ -67,6 +68,4 @@ int main(int argc, xechar_t** argv) {
|
|||
|
||||
} // namespace xdb
|
||||
|
||||
// TODO(benvanik): move main thunk into poly
|
||||
// ehhh
|
||||
XE_MAIN_WINDOW_THUNK(xdb::main, L"xenia-debug", "xenia-debug");
|
||||
DEFINE_ENTRY_POINT(L"xenia-debug", L"xenia-debug", xdb::main);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
'msvs_settings': {
|
||||
'VCLinkerTool': {
|
||||
'SubSystem': '2'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
'dependencies': [
|
||||
|
|
|
@ -10,16 +10,13 @@
|
|||
#include <xenia/xenia.h>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <poly/main.h>
|
||||
|
||||
using namespace xe;
|
||||
|
||||
DEFINE_string(target, "", "Specifies the target .xex or .iso to execute.");
|
||||
|
||||
DEFINE_string(target, "",
|
||||
"Specifies the target .xex or .iso to execute.");
|
||||
|
||||
|
||||
int xenia_run(int argc, xechar_t** argv) {
|
||||
int xenia_run(std::vector<std::wstring>& args) {
|
||||
int result_code = 1;
|
||||
|
||||
Profiler::Initialize();
|
||||
|
@ -28,33 +25,28 @@ int xenia_run(int argc, xechar_t** argv) {
|
|||
Emulator* emulator = NULL;
|
||||
|
||||
// Grab path from the flag or unnamed argument.
|
||||
if (!FLAGS_target.size() && argc < 2) {
|
||||
if (!FLAGS_target.size() && args.size() < 2) {
|
||||
google::ShowUsageWithFlags("xenia-run");
|
||||
XEFATAL("Pass a file to launch.");
|
||||
return 1;
|
||||
}
|
||||
const xechar_t* path = NULL;
|
||||
std::wstring path;
|
||||
if (FLAGS_target.size()) {
|
||||
// Passed as a named argument.
|
||||
// TODO(benvanik): find something better than gflags that supports unicode.
|
||||
xechar_t buffer[poly::max_path];
|
||||
XEIGNORE(xestrwiden(buffer, sizeof(buffer), FLAGS_target.c_str()));
|
||||
path = buffer;
|
||||
path = poly::to_wstring(FLAGS_target);
|
||||
} else {
|
||||
// Passed as an unnamed argument.
|
||||
path = argv[1];
|
||||
path = args[1];
|
||||
}
|
||||
// Normalize the path and make absolute.
|
||||
std::wstring abs_path = poly::to_absolute_path(path);
|
||||
|
||||
// Create platform abstraction layer.
|
||||
xe_pal_options_t pal_options;
|
||||
xe_zero_struct(&pal_options, sizeof(pal_options));
|
||||
XEEXPECTZERO(xe_pal_init(pal_options));
|
||||
|
||||
// Normalize the path and make absolute.
|
||||
// TODO(benvanik): move this someplace common.
|
||||
xechar_t abs_path[poly::max_path];
|
||||
xe_path_get_absolute(path, abs_path, XECOUNT(abs_path));
|
||||
|
||||
// Create the emulator.
|
||||
emulator = new Emulator(L"");
|
||||
XEEXPECTNOTNULL(emulator);
|
||||
|
@ -93,4 +85,5 @@ XECLEANUP:
|
|||
Profiler::Shutdown();
|
||||
return result_code;
|
||||
}
|
||||
XE_MAIN_WINDOW_THUNK(xenia_run, L"xenia-run", "xenia-run some.xex");
|
||||
|
||||
DEFINE_ENTRY_POINT(L"xenia-run", L"xenia-run some.xex", xenia_run);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
'msvs_settings': {
|
||||
'VCLinkerTool': {
|
||||
'SubSystem': '2'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
'dependencies': [
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#endif // !WIN32
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <poly/main.h>
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace xe;
|
||||
|
@ -273,7 +275,7 @@ int xenia_test(int argc, xechar_t **argv) {
|
|||
if (argc >= 2) {
|
||||
test_name = argv[1];
|
||||
}
|
||||
|
||||
|
||||
string test_name_str;
|
||||
if (test_name) {
|
||||
#if XE_WCHAR
|
||||
|
@ -290,4 +292,5 @@ int xenia_test(int argc, xechar_t **argv) {
|
|||
|
||||
return result_code;
|
||||
}
|
||||
XE_MAIN_THUNK(xenia_test, "xenia-test some.xex");
|
||||
|
||||
DEFINE_ENTRY_POINT(L"xenia-test", L"xenia-test some.xex", xenia_test);
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
'target_name': 'xenia-test',
|
||||
'type': 'executable',
|
||||
|
||||
'msvs_settings': {
|
||||
'VCLinkerTool': {
|
||||
'SubSystem': '1',
|
||||
},
|
||||
},
|
||||
|
||||
'dependencies': [
|
||||
'xenia',
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue