335 lines
11 KiB
C++
335 lines
11 KiB
C++
#include "common/common.h"
|
|
#include "common/dateTime.h"
|
|
#include "common/debug.h"
|
|
#include "common/file.h"
|
|
#include "common/magicEnum.h"
|
|
#include "common/platform/sysDbg.h"
|
|
#include "common/stringUtils.h"
|
|
#include "common/threads.h"
|
|
#include "common/virtualMemory.h"
|
|
#include "emulator.h"
|
|
#include "kytyGitVersion.h"
|
|
|
|
#include <charconv>
|
|
#include <cstdio>
|
|
#include <fmt/format.h>
|
|
|
|
using namespace Common;
|
|
using namespace Emulator;
|
|
|
|
static std::string GetBuildString() {
|
|
Date date = Date::FromMacros(std::string(__DATE__));
|
|
|
|
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
|
std::string type = "Debug";
|
|
#elif KYTY_BUILD == KYTY_BUILD_RELEASE
|
|
std::string type = "Release";
|
|
#else
|
|
std::string type = "????";
|
|
#endif
|
|
|
|
std::string compiler =
|
|
Debug::GetCompiler() + "-" + Debug::GetLinker() + "-" + Debug::GetBitness();
|
|
|
|
std::string str =
|
|
fmt::format("{}, {}, ver = {}, git = {}, date = {}", type.c_str(), compiler.c_str(),
|
|
KYTY_VERSION, KYTY_GIT_VERSION, date.ToString().c_str());
|
|
|
|
return str;
|
|
}
|
|
|
|
static void PrintUsage() {
|
|
::printf("%s\n", GetBuildString().c_str());
|
|
::printf("kyty_emulator --game <dir|elf> [options]\n\n");
|
|
::printf("Options:\n");
|
|
::printf(" --game <dir|elf> Game directory or ELF to load.\n");
|
|
::printf(
|
|
" --game-patch <json> Validated patch plan to apply before entry.\n");
|
|
::printf(" --screen-width <num> Window width. Default: 1280.\n");
|
|
::printf(" --screen-height <num> Window height. Default: 720.\n");
|
|
::printf(
|
|
" --user-name <name> Local user name (1-16 bytes). Default: Kyty.\n");
|
|
::printf(" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Fifo.\n");
|
|
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
|
|
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
|
|
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
|
|
::printf(" --vulkan-validation <true|false> Enable Vulkan validation.\n");
|
|
::printf(" --gpu-assisted-validation <t|f> Bounds-check shader accesses on the GPU.\n"
|
|
" Implies --vulkan-validation; very slow.\n");
|
|
::printf(" --shader-validation <true|false> Enable shader validation.\n");
|
|
::printf(" --shader-optimization-type <value> None, Size, or Performance.\n");
|
|
::printf(" --shader-log-direction <value> Silent, Console, or File.\n");
|
|
::printf(" --shader-log-folder <path> Shader log output folder.\n");
|
|
::printf(" --command-buffer-dump <true|false> Enable command buffer dumps.\n");
|
|
::printf(" --command-buffer-dump-folder <path> Command buffer dump folder.\n");
|
|
::printf(" --graphics-debug-dump <true|false> Enable graphics debug dumps.\n");
|
|
::printf(" --printf-direction <value> Silent, Console, or File.\n");
|
|
::printf(" --printf-output-file <path> Guest printf output file.\n");
|
|
::printf(" --profiler-direction <value> None or Network.\n");
|
|
::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n");
|
|
::printf(
|
|
" --readback-linear-images <true|false> Read back writable linear images on submit.\n");
|
|
::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\n");
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
::printf(" --redzone Protect the guest SysV red zone.\n");
|
|
#endif
|
|
::printf(" --keymap <Control=Input> DualSense mapping; may be repeated.\n");
|
|
::printf(" --rd Enable RenderDoc capture.\n");
|
|
}
|
|
|
|
static bool NextArg(int argc, char* argv[], int& index, std::string& out) {
|
|
if (index + 1 >= argc) {
|
|
return false;
|
|
}
|
|
|
|
index++;
|
|
out = argv[index];
|
|
return true;
|
|
}
|
|
|
|
static bool ParseBool(const std::string& value, bool& out) {
|
|
if (Common::EqualNoCase(value, "true") || value == "1" || Common::EqualNoCase(value, "yes") ||
|
|
Common::EqualNoCase(value, "on")) {
|
|
out = true;
|
|
return true;
|
|
}
|
|
|
|
if (Common::EqualNoCase(value, "false") || value == "0" || Common::EqualNoCase(value, "no") ||
|
|
Common::EqualNoCase(value, "off")) {
|
|
out = false;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename E>
|
|
static bool ParseEnum(const std::string& value, E& out) {
|
|
auto enum_value = magic_enum::enum_cast<E>(value.c_str());
|
|
if (!enum_value.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
out = enum_value.value();
|
|
return true;
|
|
}
|
|
|
|
static bool ParseConsoleLanguage(const std::string& value, uint32_t& out) {
|
|
uint32_t language = 0;
|
|
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), language);
|
|
if (error != std::errc {} || end != value.data() + value.size() ||
|
|
language > Config::MAX_CONSOLE_LANGUAGE) {
|
|
return false;
|
|
}
|
|
out = language;
|
|
return true;
|
|
}
|
|
|
|
static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_help) {
|
|
show_help = false;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
std::string arg = std::string(argv[i]);
|
|
std::string value;
|
|
|
|
if (arg == "--help" || arg == "-h") {
|
|
show_help = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--rd") {
|
|
options.config.renderdoc_enabled = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--fullscreen") {
|
|
options.config.fullscreen_enabled = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--playgo-hack") {
|
|
options.config.playgo_hack_enabled = true;
|
|
continue;
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
if (arg == "--redzone") {
|
|
options.config.red_zone_protection_enabled = true;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
if (!Common::StartsWith(arg, "--")) {
|
|
::printf("game input must be provided with --game\n");
|
|
return false;
|
|
}
|
|
|
|
if (!NextArg(argc, argv, i, value)) {
|
|
::printf("missing value for %s\n", arg.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (arg == "--game") {
|
|
if (!options.app0_dir.empty()) {
|
|
::printf("--game can only be specified once\n");
|
|
return false;
|
|
}
|
|
|
|
value = Common::FixFilenameSlash(value);
|
|
if (Common::File::IsDirectoryExisting(value)) {
|
|
options.app0_dir = value;
|
|
options.elf = "/app0/eboot.bin";
|
|
} else if (Common::File::IsFileExisting(value)) {
|
|
options.app0_dir = Common::DirectoryWithoutFilename(value);
|
|
if (options.app0_dir.empty()) {
|
|
options.app0_dir = ".";
|
|
}
|
|
options.elf = "/app0/" + Common::FilenameWithoutDirectory(value);
|
|
} else {
|
|
::printf("--game must point to an existing directory or ELF: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--game-patch") {
|
|
if (!options.game_patch.empty()) {
|
|
::printf("--game-patch can only be specified once\n");
|
|
return false;
|
|
}
|
|
value = Common::FixFilenameSlash(value);
|
|
if (!Common::File::IsFileExisting(value)) {
|
|
::printf("--game-patch must point to an existing file: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
options.game_patch = value;
|
|
} else if (arg == "--screen-width") {
|
|
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
|
|
} else if (arg == "--screen-height") {
|
|
options.config.screen_height = static_cast<uint32_t>(Common::ToInt32(value));
|
|
} else if (arg == "--user-name") {
|
|
if (value.empty() || value.size() > Config::MAX_USER_NAME_LENGTH) {
|
|
::printf("invalid user name: must contain 1-%zu bytes\n",
|
|
Config::MAX_USER_NAME_LENGTH);
|
|
return false;
|
|
}
|
|
options.config.user_name = value;
|
|
} else if (arg == "--present-mode") {
|
|
if (!ParseEnum(value, options.config.present_mode)) {
|
|
::printf("invalid present mode: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--vblank-frequency") {
|
|
const int32_t vblank_frequency = Common::ToInt32(value);
|
|
options.config.vblank_frequency =
|
|
static_cast<uint32_t>(vblank_frequency < 0 ? 0 : vblank_frequency);
|
|
} else if (arg == "--console-language") {
|
|
if (!ParseConsoleLanguage(value, options.config.console_language)) {
|
|
::printf("invalid console language: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--vulkan-validation") {
|
|
if (!ParseBool(value, options.config.vulkan_validation_enabled)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--gpu-assisted-validation") {
|
|
if (!ParseBool(value, options.config.gpu_assisted_validation_enabled)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--shader-validation") {
|
|
if (!ParseBool(value, options.config.shader_validation_enabled)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--shader-optimization-type") {
|
|
if (!ParseEnum(value, options.config.shader_optimization_type)) {
|
|
::printf("invalid shader optimization type: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--shader-log-direction") {
|
|
if (!ParseEnum(value, options.config.shader_log_direction)) {
|
|
::printf("invalid shader log direction: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--shader-log-folder") {
|
|
options.config.shader_log_folder = value;
|
|
} else if (arg == "--command-buffer-dump") {
|
|
if (!ParseBool(value, options.config.command_buffer_dump_enabled)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--command-buffer-dump-folder") {
|
|
options.config.command_buffer_dump_folder = value;
|
|
} else if (arg == "--graphics-debug-dump") {
|
|
if (!ParseBool(value, options.config.graphics_debug_dump_enabled)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--printf-direction") {
|
|
if (!ParseEnum(value, options.config.printf_direction)) {
|
|
::printf("invalid printf direction: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--printf-output-file") {
|
|
options.config.printf_output_file = value;
|
|
} else if (arg == "--profiler-direction") {
|
|
if (!ParseEnum(value, options.config.profiler_direction)) {
|
|
::printf("invalid profiler direction: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--spirv-debug-printf") {
|
|
if (!ParseBool(value, options.config.spirv_debug_printf_enabled)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--readback-linear-images") {
|
|
if (!ParseBool(value, options.config.readback_linear_images)) {
|
|
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
|
return false;
|
|
}
|
|
} else if (arg == "--keymap") {
|
|
const auto split = value.find('=');
|
|
if (split == std::string::npos || split == 0 || split + 1 == value.size()) {
|
|
::printf("invalid keymap: %s\n", value.c_str());
|
|
return false;
|
|
}
|
|
options.config.keymap.push_back(value);
|
|
} else {
|
|
::printf("unknown option: %s\n", arg.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (options.config.gpu_assisted_validation_enabled) {
|
|
options.config.vulkan_validation_enabled = true;
|
|
}
|
|
|
|
return show_help || (!options.app0_dir.empty() && !options.elf.empty());
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
VirtualMemory::Init();
|
|
InitializeThreads();
|
|
|
|
RunOptions options;
|
|
bool show_help = false;
|
|
|
|
if (argc < 2) {
|
|
PrintUsage();
|
|
return 0;
|
|
}
|
|
|
|
if (!ParseArgs(argc, argv, options, show_help)) {
|
|
PrintUsage();
|
|
return 1;
|
|
}
|
|
|
|
if (show_help) {
|
|
PrintUsage();
|
|
return 0;
|
|
}
|
|
|
|
Run(options);
|
|
|
|
return 0;
|
|
}
|