From eec7d650482134352b7cde4801b979ce1ed2044e Mon Sep 17 00:00:00 2001 From: MegaMech Date: Fri, 5 Dec 2025 17:54:04 -0700 Subject: [PATCH] Auto-detect Game (#579) * Update GameExtractor.cpp * Update GameExtractor.h * Update GameExtractor.cpp * Update GameExtractor.cpp * Update GameExtractor.cpp --- src/port/GameExtractor.cpp | 167 ++++++++++++++++++++++++++++++++----- src/port/GameExtractor.h | 1 + 2 files changed, 149 insertions(+), 19 deletions(-) diff --git a/src/port/GameExtractor.cpp b/src/port/GameExtractor.cpp index 9e942333d..f330c53cf 100644 --- a/src/port/GameExtractor.cpp +++ b/src/port/GameExtractor.cpp @@ -1,5 +1,12 @@ +#ifdef _WIN32 +#include +#include +#include +#pragma comment(lib, "Shlwapi.lib") +#endif #include "GameExtractor.h" #include +#include #include @@ -7,6 +14,13 @@ #include "spdlog/spdlog.h" #include +#ifdef unix +#include +#include +#include +#include +#endif + #if !defined(__IOS__) && !defined(__ANDROID__) && !defined(__SWITCH__) #include "portable-file-dialogs.h" #endif @@ -16,33 +30,148 @@ std::unordered_map mGameList = { }; bool GameExtractor::SelectGameFromUI() { + std::vector roms; + GetRoms(roms); + + bool foundGame = false; + + // Store both path and already-read data + std::string romPath; + std::vector romData; + + // Auto detect first baserom with valid hash + for (const auto& rom : roms) { + if (!std::filesystem::exists(rom)) continue; + + std::ifstream inFile(rom, std::ios::binary); + if (!inFile.is_open()) { + SPDLOG_INFO("Failed to open ROM at path: {}, continuing", rom); + continue; + } + + inFile.seekg(0, std::ios::end); + size_t fileSize = inFile.tellg(); + inFile.seekg(0, std::ios::beg); + + std::vector data(fileSize); + if (!inFile.read(reinterpret_cast(data.data()), fileSize)) { + SPDLOG_INFO("Failed to read ROM at path: {}, continuing", rom); + continue; + } + + inFile.close(); + std::string hash = Companion::CalculateHash(data); + + if (mGameList.find(hash) != mGameList.end()) { + romPath = rom; + romData = std::move(data); + foundGame = true; + break; + } + } + #if !defined(__IOS__) && !defined(__ANDROID__) && !defined(__SWITCH__) - if (!pfd::settings::available()) { - SPDLOG_ERROR( - "portable-file-dialogs is not available on this system. Check " - "https://github.com/samhocevar/portable-file-dialogs for runtime " - "requirements." - ); - return false; + // Desktop: fallback to file dialogue if no baserom found + if (!foundGame) { + if (!pfd::settings::available()) { + SPDLOG_ERROR( + "portable-file-dialogs is not available on this system." + ); + return false; + } + + auto selection = pfd::open_file("Select a file", ".", { "N64 Roms", "*.z64" }).result(); + if (selection.empty()) return false; + + romPath = selection[0]; } - - auto selection = pfd::open_file("Select a file", ".", { "N64 Roms", "*.z64" }).result(); - - if (selection.empty()) { - return false; - } - - this->mGamePath = selection[0]; #else - this->mGamePath = Ship::Context::GetPathRelativeToAppDirectory("baserom.us.z64"); + // Mobile: fallback to baserom.us.z64 + if (!foundGame && !std::filesystem::exists(Ship::Context::GetPathRelativeToAppDirectory("baserom.us.z64"))) { + SPDLOG_ERROR("baserom not found"); + return false; + } + + if (!foundGame) { + romPath = Ship::Context::GetPathRelativeToAppDirectory("baserom.us.z64"); + } #endif - std::ifstream file(this->mGamePath, std::ios::binary); - this->mGameData = std::vector( std::istreambuf_iterator( file ), {} ); - file.close(); + // Load file if it is not already open + if (romData.empty()) { + if (!std::filesystem::exists(romPath)) { + SPDLOG_ERROR("Failed to find ROM at path: {}", romPath); + return false; + } + + std::ifstream inFile(romPath, std::ios::binary); + if (!inFile.is_open()) return false; + + romData = std::vector(std::istreambuf_iterator(inFile), {}); + inFile.close(); + } + + this->mGamePath = romPath; + this->mGameData = std::move(romData); + return true; } +void GameExtractor::GetRoms(std::vector& roms) { +#ifdef _WIN32 + WIN32_FIND_DATAA ffd; + HANDLE h = FindFirstFileA(".\\*", &ffd); + + do { + if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + char* ext = PathFindExtensionA(ffd.cFileName); + + // Check for any standard N64 rom file extensions. + if ((strcmp(ext, ".z64") == 0)) + roms.push_back(ffd.cFileName); + } + } while (FindNextFileA(h, &ffd) != 0); + // if (h != nullptr) { + // CloseHandle(h); + //} +#elif unix + // Open the directory of the app. + DIR* d = opendir("."); + struct dirent* dir; + + if (d != NULL) { + // Go through each file in the directory + while ((dir = readdir(d)) != NULL) { + struct stat path; + + auto fullPath = std::filesystem::path(".") / dir->d_name; + auto fullPathString = fullPath.string(); + const char* fullPathCStr = fullPathString.c_str(); + + // Check if current entry is not folder + stat(fullPathCStr, &path); + if (S_ISREG(path.st_mode)) { + + // Get the position of the extension character. + char* ext = strrchr(dir->d_name, '.'); + if (ext != NULL && (strcmp(ext, ".z64") == 0)) { + roms.push_back(fullPathCStr); + } + } + } + } + closedir(d); +#else + for (const auto& file : std::filesystem::directory_iterator(".")) { + if (file.is_directory()) + continue; + if (file.path().extension() == ".z64") { + roms.push_back((file.path())); + } + } +#endif +} + std::optional GameExtractor::ValidateChecksum() const { const auto rom = new N64::Cartridge(this->mGameData); rom->Initialize(); diff --git a/src/port/GameExtractor.h b/src/port/GameExtractor.h index e51e83aae..131734b9c 100644 --- a/src/port/GameExtractor.h +++ b/src/port/GameExtractor.h @@ -10,6 +10,7 @@ public: static bool GenAssetFile(); std::optional ValidateChecksum() const; bool SelectGameFromUI(); + void GetRoms(std::vector& roms); bool GenerateOTR() const; private: fs::path mGamePath;