#include #include #include #include #include "Window.h" #include "ResourceMgr.h" #include "KeyboardController.h" #include "UltraController.h" #include "DisplayList.h" #include "Console.h" #include "Array.h" #include "Texture.h" #include "Blob.h" #include "Matrix.h" #include "Hooks.h" #include "Lib/Fast3D/gfx_pc.h" #include "Lib/Fast3D/gfx_sdl.h" #include "Lib/Fast3D/gfx_dxgi.h" #include "Lib/Fast3D/gfx_glx.h" #include "Lib/Fast3D/gfx_opengl.h" #include "Lib/Fast3D/gfx_direct3d11.h" #include "Lib/Fast3D/gfx_direct3d12.h" #include "Lib/Fast3D/gfx_wiiu.h" #include "Lib/Fast3D/gfx_gx2.h" #include "Lib/Fast3D/gfx_rendering_api.h" #include "Lib/Fast3D/gfx_window_manager_api.h" #include #include "ImGuiImpl.h" #include "spdlog/async.h" #include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/sohconsole_sink.h" #include "PR/ultra64/gbi.h" #ifdef __APPLE__ #include "OSXFolderManager.h" #elif defined(__SWITCH__) #include "SwitchImpl.h" #elif defined(__WIIU__) #include "WiiUImpl.h" #endif #define LOAD_TEX(texPath) static_cast(Ship::Window::GetInstance()->GetResourceManager()->LoadResource(texPath).get()); extern "C" { struct OSMesgQueue; uint8_t __osMaxControllers = MAXCONTROLLERS; int32_t osContInit(OSMesgQueue* mq, uint8_t* controllerBits, OSContStatus* status) { *controllerBits = 0; #ifndef __WIIU__ if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) { SPDLOG_ERROR("Failed to initialize SDL game controllers ({})", SDL_GetError()); exit(EXIT_FAILURE); } #ifndef __SWITCH__ const char* controllerDb = "gamecontrollerdb.txt"; int mappingsAdded = SDL_GameControllerAddMappingsFromFile(controllerDb); if (mappingsAdded >= 0) { SPDLOG_INFO("Added SDL game controllers from \"{}\" ({})", controllerDb, mappingsAdded); } else { SPDLOG_ERROR("Failed add SDL game controller mappings from \"{}\" ({})", controllerDb, SDL_GetError()); } #endif #endif Ship::Window::GetInstance()->GetControlDeck()->Init(controllerBits); return 0; } int32_t osContStartReadData(OSMesgQueue* mesg) { return 0; } void osContGetReadData(OSContPad* pad) { pad->button = 0; pad->stick_x = 0; pad->stick_y = 0; pad->right_stick_x = 0; pad->right_stick_y = 0; pad->err_no = 0; pad->gyro_x = 0; pad->gyro_y = 0; if (SohImGui::GetInputEditor()->IsOpened()) return; Ship::Window::GetInstance()->GetControlDeck()->WriteToPad(pad); Ship::ExecuteHooks(pad); } const char* ResourceMgr_GetNameByCRC(uint64_t crc) { const std::string* hashStr = Ship::Window::GetInstance()->GetResourceManager()->HashToString(crc); return hashStr != nullptr ? hashStr->c_str() : nullptr; } Vtx* ResourceMgr_LoadVtxByCRC(uint64_t crc) { const std::string* hashStr = Ship::Window::GetInstance()->GetResourceManager()->HashToString(crc); if (hashStr != nullptr) { auto res = std::static_pointer_cast(Ship::Window::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str())); return (Vtx*)res->vertices.data(); } return nullptr; } int32_t* ResourceMgr_LoadMtxByCRC(uint64_t crc) { const std::string* hashStr = Ship::Window::GetInstance()->GetResourceManager()->HashToString(crc); if (hashStr != nullptr) { auto res = std::static_pointer_cast(Ship::Window::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str())); return (int32_t*)res->mtx.data(); } return nullptr; } Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc) { const std::string* hashStr = Ship::Window::GetInstance()->GetResourceManager()->HashToString(crc); if (hashStr != nullptr) { auto res = std::static_pointer_cast(Ship::Window::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str())); return (Gfx*)&res->instructions[0]; } else { return nullptr; } } char* ResourceMgr_LoadTexByCRC(uint64_t crc) { const std::string* hashStr = Ship::Window::GetInstance()->GetResourceManager()->HashToString(crc); if (hashStr != nullptr) { const auto res = LOAD_TEX(hashStr->c_str()); Ship::ExecuteHooks(hashStr->c_str(), &res->imageData); return reinterpret_cast(res->imageData); } else { return nullptr; } } void ResourceMgr_RegisterResourcePatch(uint64_t hash, uint32_t instrIndex, uintptr_t origData) { const std::string* hashStr = Ship::Window::GetInstance()->GetResourceManager()->HashToString(hash); if (hashStr != nullptr) { auto res = (Ship::Texture*)Ship::Window::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str()).get(); Ship::Patch patch; patch.crc = hash; patch.index = instrIndex; patch.origData = origData; res->patches.push_back(patch); } } char* ResourceMgr_LoadTexByName(char* texPath) { const auto res = LOAD_TEX(texPath); Ship::ExecuteHooks(texPath, &res->imageData); return (char*)res->imageData; } uint16_t ResourceMgr_LoadTexWidthByName(char* texPath) { const auto res = LOAD_TEX(texPath); if (res != nullptr) return res->width; SPDLOG_ERROR("Given texture path is a non-existent resource"); return -1; } uint16_t ResourceMgr_LoadTexHeightByName(char* texPath) { const auto res = LOAD_TEX(texPath); if (res != nullptr) return res->height; SPDLOG_ERROR("Given texture path is a non-existent resource"); return -1; } uint32_t ResourceMgr_LoadTexSizeByName(char* texPath) { const auto res = LOAD_TEX(texPath); if (res != nullptr) return res->imageDataSize; SPDLOG_ERROR("Given texture path is a non-existent resource"); return -1; } void ResourceMgr_WriteTexS16ByName(char* texPath, size_t index, s16 value) { const auto res = LOAD_TEX(texPath); if (res != nullptr) { if (index < res->imageDataSize) res->imageData[index] = value; else { // Dangit Morita int bp = 0; } } } char* ResourceMgr_LoadBlobByName(char* blobPath) { auto res = (Ship::Blob*)Ship::Window::GetInstance()->GetResourceManager()->LoadResource(blobPath).get(); return (char*)res->data.data(); } /* Should these go in their own file?*/ uint64_t osGetTime(void) { return std::chrono::steady_clock::now().time_since_epoch().count(); } uint32_t osGetCount(void) { return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); } } namespace Ship { std::weak_ptr Window::Context; std::shared_ptr Window::GetInstance() { return Context.lock(); } std::shared_ptr Window::CreateInstance(const std::string Name) { if (Context.expired()) { auto Shared = std::make_shared(Name); Context = Shared; Shared->Initialize(); return Shared; } SPDLOG_DEBUG("Trying to create a context when it already exists. Returning existing."); return GetInstance(); } Window::Window(std::string Name) : Name(std::move(Name)), APlayer(nullptr), ControllerApi(nullptr), ResMan(nullptr), Logger(nullptr), Config(nullptr) { WmApi = nullptr; RenderingApi = nullptr; bIsFullscreen = false; dwWidth = 320; dwHeight = 240; } Window::~Window() { SPDLOG_DEBUG("destruct window"); } void Window::CreateDefaults() { if (GetConfig()->isNewInstance) { GetConfig()->setInt("Window.Width", 640); GetConfig()->setInt("Window.Height", 480); GetConfig()->setBool("Window.Options", false); GetConfig()->setString("Window.GfxBackend", ""); GetConfig()->setBool("Window.Fullscreen.Enabled", false); GetConfig()->setInt("Window.Fullscreen.Width", 1920); GetConfig()->setInt("Window.Fullscreen.Height", 1080); GetConfig()->setString("Game.SaveName", ""); GetConfig()->setString("Game.Main Archive", ""); GetConfig()->setString("Game.Patches Archive", ""); GetConfig()->setInt("Shortcuts.Fullscreen", 0x044); GetConfig()->setInt("Shortcuts.Console", 0x029); GetConfig()->save(); } } void Window::Initialize() { InitializeLogging(); InitializeConfiguration(); InitializeResourceManager(); CreateDefaults(); InitializeAudioPlayer(); InitializeControlDeck(); bIsFullscreen = GetConfig()->getBool("Window.Fullscreen.Enabled", false); if (bIsFullscreen) { dwWidth = GetConfig()->getInt("Window.Fullscreen.Width", 1920); dwHeight = GetConfig()->getInt("Window.Fullscreen.Height", 1080); } else { dwWidth = GetConfig()->getInt("Window.Width", 640); dwHeight = GetConfig()->getInt("Window.Height", 480); } dwMenubar = GetConfig()->getBool("Window.Options", false); const std::string& gfx_backend = GetConfig()->getString("Window.GfxBackend"); InitializeWindowManager(); gfx_init(WmApi, RenderingApi, GetName().c_str(), bIsFullscreen, dwWidth, dwHeight); WmApi->set_fullscreen_changed_callback(OnFullscreenChanged); WmApi->set_keyboard_callbacks(KeyDown, KeyUp, AllKeysUp); Ship::RegisterHook([this]() { ControllerApi->SaveControllerSettings(); }); } std::string Window::GetAppDirectoryPath() { #ifdef __APPLE__ FolderManager folderManager; std::string fpath = std::string(folderManager.pathForDirectory(NSApplicationSupportDirectory, NSUserDomainMask)); fpath.append("/com.shipofharkinian.soh"); return fpath; #endif #ifdef __linux__ char* fpath = std::getenv("SHIP_HOME"); if (fpath != NULL) return std::string(fpath); #endif return "."; } std::string Window::GetPathRelativeToAppDirectory(const char* path) { return GetAppDirectoryPath() + "/" + path; } void Window::StartFrame() { gfx_start_frame(); } void Window::SetTargetFps(int32_t fps) { gfx_set_target_fps(fps); } void Window::SetMaximumFrameLatency(int32_t latency) { gfx_set_maximum_frame_latency(latency); } void Window::GetPixelDepthPrepare(float x, float y) { gfx_get_pixel_depth_prepare(x, y); } uint16_t Window::GetPixelDepth(float x, float y) { return gfx_get_pixel_depth(x, y); } void Window::ToggleFullscreen() { SetFullscreen(!bIsFullscreen); } void Window::SetFullscreen(bool bIsFullscreen) { this->bIsFullscreen = bIsFullscreen; WmApi->set_fullscreen(bIsFullscreen); } void Window::ShowCursor(bool hide) { if (!this->bIsFullscreen || this->dwMenubar) { WmApi->show_cursor(true); } else { WmApi->show_cursor(hide); } } void Window::MainLoop(void (*MainFunction)(void)) { WmApi->main_loop(MainFunction); } bool Window::KeyUp(int32_t dwScancode) { if (dwScancode == GetInstance()->GetConfig()->getInt("Shortcuts.Fullscreen", 0x044)) { GetInstance()->ToggleFullscreen(); } GetInstance()->SetLastScancode(-1); bool bIsProcessed = false; auto controlDeck = GetInstance()->GetControlDeck(); const auto pad = dynamic_cast(controlDeck->GetPhysicalDevice(controlDeck->GetNumPhysicalDevices() - 2).get()); if (pad != nullptr) { if (pad->ReleaseButton(dwScancode)) { bIsProcessed = true; } } return bIsProcessed; } bool Window::KeyDown(int32_t dwScancode) { bool bIsProcessed = false; auto controlDeck = GetInstance()->GetControlDeck(); const auto pad = dynamic_cast(controlDeck->GetPhysicalDevice(controlDeck->GetNumPhysicalDevices() - 2).get()); if (pad != nullptr) { if (pad->PressButton(dwScancode)) { bIsProcessed = true; } } GetInstance()->SetLastScancode(dwScancode); return bIsProcessed; } void Window::AllKeysUp(void) { auto controlDeck = Window::GetInstance()->GetControlDeck(); const auto pad = dynamic_cast(controlDeck->GetPhysicalDevice(controlDeck->GetNumPhysicalDevices() - 2).get()); if (pad != nullptr) { pad->ReleaseAllButtons(); } } void Window::OnFullscreenChanged(bool bIsFullscreen) { std::shared_ptr pConf = Window::GetInstance()->GetConfig(); Window::GetInstance()->bIsFullscreen = bIsFullscreen; pConf->setBool("Window.Fullscreen.Enabled", bIsFullscreen); Window::GetInstance()->ShowCursor(!bIsFullscreen); } uint32_t Window::GetCurrentWidth() { WmApi->get_dimensions(&dwWidth, &dwHeight); return dwWidth; } uint32_t Window::GetCurrentHeight() { WmApi->get_dimensions(&dwWidth, &dwHeight); return dwHeight; } void Window::InitializeAudioPlayer() { #ifdef _WIN32 APlayer = std::make_shared(); #elif defined(__linux) APlayer = std::make_shared(); #else APlayer = std::make_shared(); #endif } void Window::InitializeWindowManager() { // First set default #ifdef ENABLE_OPENGL RenderingApi = &gfx_opengl_api; #if defined(__linux__) && defined(X11_SUPPORTED) // LINUX_TODO: // *WmApi = &gfx_glx; WmApi = &gfx_sdl; #else WmApi = &gfx_sdl; #endif #endif #ifdef ENABLE_DX12 RenderingApi = &gfx_direct3d12_api; WmApi = &gfx_dxgi_api; #endif #ifdef ENABLE_DX11 RenderingApi = &gfx_direct3d11_api; WmApi = &gfx_dxgi_api; #endif #ifdef __WIIU__ RenderingApi = &gfx_gx2_api; WmApi = &gfx_wiiu; #endif // Config can override #ifdef ENABLE_DX11 if (gfxBackend == "dx11") { RenderingApi = &gfx_direct3d11_api; WmApi = &gfx_dxgi_api; } #endif #ifdef ENABLE_OPENGL if (gfxBackend == "sdl") { RenderingApi = &gfx_opengl_api; WmApi = &gfx_sdl; } #if defined(__linux__) && defined(X11_SUPPORTED) if (gfxBackend == "glx") { RenderingApi = &gfx_opengl_api; WmApi = &gfx_glx; } #endif #endif } void Window::InitializeControlDeck() { ControllerApi = std::make_shared(); } void Window::InitializeLogging() { try { // Setup Logging spdlog::init_thread_pool(8192, 1); std::vector Sinks; auto SohConsoleSink = std::make_shared(); SohConsoleSink->set_level(spdlog::level::trace); Sinks.push_back(SohConsoleSink); #if (!defined(_WIN32) && !defined(__WIIU__)) || defined(_DEBUG) auto ConsoleSink = std::make_shared(); ConsoleSink->set_level(spdlog::level::trace); Sinks.push_back(ConsoleSink); #endif #ifndef __WIIU__ auto logPath = GetPathRelativeToAppDirectory(("logs/" + GetName() + ".log").c_str()); auto FileSink = std::make_shared(logPath, 1024 * 1024 * 10, 10); FileSink->set_level(spdlog::level::trace); Sinks.push_back(FileSink); #endif Logger = std::make_shared(GetName(), Sinks.begin(), Sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); GetLogger()->set_level(spdlog::level::trace); #ifndef __WIIU__ GetLogger()->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%@] [%l] %v"); #else GetLogger()->set_pattern("[%s:%#] [%l] %v"); #endif spdlog::register_logger(GetLogger()); spdlog::set_default_logger(GetLogger()); } catch (const spdlog::spdlog_ex& ex) { std::cout << "Log initialization failed: " << ex.what() << std::endl; } } void Window::InitializeResourceManager() { MainPath = Config->getString("Game.Main Archive", GetPathRelativeToAppDirectory("oot.otr")); PatchesPath = Config->getString("Game.Patches Archive", GetAppDirectoryPath() + "/mods"); ResMan = std::make_shared(GetInstance(), MainPath, PatchesPath); if (!ResMan->DidLoadSuccessfully()) { #if defined(__SWITCH__) printf("Main OTR file not found!\n"); #elif defined(__WIIU__) Ship::WiiU::ThrowMissingOTR(MainPath.c_str()); #else SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OTR file not found", "Main OTR file not found. Please generate one", nullptr); SPDLOG_ERROR("Main OTR file not found!"); #endif exit(1); } #ifdef __SWITCH__ Ship::Switch::Init(PostInitPhase); #endif } void Window::InitializeConfiguration() { Config = std::make_shared(GetPathRelativeToAppDirectory("shipofharkinian.json")); } void Window::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr, const size_t size) { std::ofstream saveFile = std::ofstream(savePath, std::fstream::in | std::fstream::out | std::fstream::binary); saveFile.seekp(addr); saveFile.write((char*)dramAddr, size); saveFile.close(); } void Window::ReadSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size) { std::ifstream saveFile = std::ifstream(savePath, std::fstream::in | std::fstream::out | std::fstream::binary); // If the file doesn't exist, initialize DRAM if (saveFile.good()) { saveFile.seekg(addr); saveFile.read((char*)dramAddr, size); } else { memset(dramAddr, 0, size); } saveFile.close(); } bool Window::IsFullscreen() { return bIsFullscreen; } uint32_t Window::GetMenuBar() { return dwMenubar; } void Window::SetMenuBar(uint32_t dwMenuBar) { this->dwMenubar = dwMenuBar; } std::string Window::GetName() { return Name; } std::shared_ptr Window::GetControlDeck() { return ControllerApi; } std::shared_ptr Window::GetAudioPlayer() { return APlayer; } std::shared_ptr Window::GetResourceManager() { return ResMan; } std::shared_ptr Window::GetConfig() { return Config; } std::shared_ptr Window::GetLogger() { return Logger; } const char* Window::GetKeyName(int32_t scancode) { return WmApi->get_key_name(scancode); } int32_t Window::GetLastScancode() { return lastScancode; } void Window::SetLastScancode(int32_t scanCode) { lastScancode = scanCode; } }