KytyPS5/src/main.cpp

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;
}