diff --git a/Source/RMG-Core/CMakeLists.txt b/Source/RMG-Core/CMakeLists.txt index bc08ace8..b94b070c 100644 --- a/Source/RMG-Core/CMakeLists.txt +++ b/Source/RMG-Core/CMakeLists.txt @@ -17,6 +17,7 @@ set(RMG_CORE_SOURCES m64p/CoreApi.cpp m64p/ConfigApi.cpp m64p/PluginApi.cpp + CachedRomHeaderAndSettings.cpp Settings/Settings.cpp SpeedLimiter.cpp RomSettings.cpp @@ -39,10 +40,12 @@ set(RMG_CORE_SOURCES if (WIN32 OR MSYS) list(APPEND RMG_CORE_SOURCES osal/osal_dynlib_win32.cpp + osal/osal_files_win32.cpp ) else() list(APPEND RMG_CORE_SOURCES osal/osal_dynlib_unix.cpp + osal/osal_files_unix.cpp ) endif() diff --git a/Source/RMG-Core/CachedRomHeaderAndSettings.cpp b/Source/RMG-Core/CachedRomHeaderAndSettings.cpp new file mode 100644 index 00000000..1a3e1a16 --- /dev/null +++ b/Source/RMG-Core/CachedRomHeaderAndSettings.cpp @@ -0,0 +1,214 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "CachedRomHeaderAndSettings.hpp" +#include "Directories.hpp" +#include "Error.hpp" + +#include "osal/osal_files.hpp" + +#include +#include +#include + +// +// Local Defines +// + +#define MAX_FILENAME_LEN 4096 +#define ROMHEADER_NAME_LEN 20 +#define GOODNAME_LEN 256 +#define MD5_LEN 33 + +#define CACHE_FILE_MAGIC "RMGCoreHeaderAndSettingsCache_01" + +// +// Local Structures +// + +struct l_CacheEntry +{ + std::string fileName; + osal_files_file_time fileTime; + + CoreRomHeader header; + CoreRomSettings settings; +}; + +// +// Local Variables +// + +static bool l_HasReadCache = false; +static std::vector l_CacheEntries; + +// +// Internal Functions +// + +static std::string get_cache_file_name() +{ + std::string file; + + file = CoreGetUserCacheDirectory(); + file += OSAL_FILES_DIR_SEPERATOR_STR; + file += "RomHeaderAndSettingsCache.cache"; + + return file; +} + +static std::vector::iterator get_cache_entry_iter(std::string file, bool checkFileTime = true) +{ + osal_files_file_time fileTime = osal_files_get_file_time(file); + + auto predicate = [file, fileTime, checkFileTime](const auto& entry) + { + return entry.fileName == file && + (!checkFileTime || entry.fileTime == fileTime); + }; + + return std::find_if(l_CacheEntries.begin(), l_CacheEntries.end(), predicate); +} + +// +// Exported Functions +// + +void CoreReadRomHeaderAndSettingsCache(void) +{ + std::ifstream inputStream; + char magicBuf[sizeof(CACHE_FILE_MAGIC)]; + char fileNameBuf[MAX_FILENAME_LEN]; + char headerNameBuf[ROMHEADER_NAME_LEN]; + char goodNameBuf[GOODNAME_LEN]; + char md5Buf[MD5_LEN]; + l_CacheEntry cacheEntry; + + inputStream.open(get_cache_file_name()); + if (!inputStream.good()) + { + return; + } + + // when magic doesn't match, don't read cache file + inputStream.read((char*)magicBuf, sizeof(CACHE_FILE_MAGIC)); + if (std::string(magicBuf) != std::string(CACHE_FILE_MAGIC)) + { + inputStream.close(); + return; + } + + // read all file entries +#define FREAD(x) inputStream.read((char*)&x, sizeof(x)) + while (!inputStream.eof()) + { + // file info + inputStream.read((char*)fileNameBuf, sizeof(fileNameBuf)); + cacheEntry.fileName = std::string(fileNameBuf); + FREAD(cacheEntry.fileTime); + // header + inputStream.read((char*)headerNameBuf, sizeof(headerNameBuf)); + cacheEntry.header.Name = std::string(headerNameBuf); + FREAD(cacheEntry.header.CRC1); + FREAD(cacheEntry.header.CRC2); + // (partial) settings + inputStream.read((char*)goodNameBuf, sizeof(goodNameBuf)); + cacheEntry.settings.GoodName = std::string(goodNameBuf); + inputStream.read((char*)md5Buf, sizeof(md5Buf)); + cacheEntry.settings.MD5 = std::string(md5Buf); + + // add to cached entries + l_CacheEntries.push_back(cacheEntry); + } +#undef FREAD + + inputStream.close(); +} + +bool CoreSaveRomHeaderAndSettingsCache(void) +{ + std::ofstream outputStream; + char fileNameBuf[MAX_FILENAME_LEN]; + char headerNameBuf[ROMHEADER_NAME_LEN]; + char goodNameBuf[GOODNAME_LEN]; + char md5Buf[MD5_LEN]; + + outputStream.open(get_cache_file_name()); + if (!outputStream.good()) + { + return false; + } + + // write magic header + outputStream.write((char*)CACHE_FILE_MAGIC, sizeof(CACHE_FILE_MAGIC)); + + // write each entry in the file +#define FWRITE(x) outputStream.write((char*)&x, sizeof(x)) + for (const auto& entry : l_CacheEntries) + { + strncpy(fileNameBuf, entry.fileName.c_str(), MAX_FILENAME_LEN); + strncpy(headerNameBuf, entry.header.Name.c_str(), sizeof(headerNameBuf)); + strncpy(goodNameBuf, entry.settings.GoodName.c_str(), sizeof(goodNameBuf)); + strncpy(md5Buf, entry.settings.MD5.c_str(), sizeof(md5Buf)); + + // file info + outputStream.write((char*)fileNameBuf, sizeof(fileNameBuf)); + FWRITE(entry.fileTime); + // header + outputStream.write((char*)headerNameBuf, sizeof(headerNameBuf)); + FWRITE(entry.header.CRC1); + FWRITE(entry.header.CRC2); + // (partial) settings + outputStream.write((char*)goodNameBuf, sizeof(goodNameBuf)); + outputStream.write((char*)md5Buf, sizeof(md5Buf)); + } +#undef FWRITE + + outputStream.close(); + return true; +} + +bool CoreHasRomHeaderAndSettingsCached(std::string file) +{ + return get_cache_entry_iter(file) != l_CacheEntries.end(); +} + +bool CoreGetCachedRomHeaderAndSettings(std::string file, CoreRomHeader& header, CoreRomSettings& settings) +{ + auto iter = get_cache_entry_iter(file); + if (iter == l_CacheEntries.end()) + { + return false; + } + + header = (*iter).header; + settings = (*iter).settings; + return true; +} + +bool CoreAddCachedRomHeaderAndSettings(std::string file, CoreRomHeader header, CoreRomSettings settings) +{ + l_CacheEntry cacheEntry; + + // try to find existing entry with same filename, + // when found, remove it from the cache + auto iter = get_cache_entry_iter(file, false); + if (iter != l_CacheEntries.end()) + { + l_CacheEntries.erase(iter); + } + + cacheEntry.fileName = file; + cacheEntry.fileTime = osal_files_get_file_time(file); + cacheEntry.header = header; + cacheEntry.settings = settings; + + l_CacheEntries.push_back(cacheEntry); + return true; +} diff --git a/Source/RMG-Core/CachedRomHeaderAndSettings.hpp b/Source/RMG-Core/CachedRomHeaderAndSettings.hpp new file mode 100644 index 00000000..97662557 --- /dev/null +++ b/Source/RMG-Core/CachedRomHeaderAndSettings.hpp @@ -0,0 +1,39 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CORE_CACHEDROMHEADERANDSETTINGS_HPP +#define CORE_CACHEDROMHEADERANDSETTINGS_HPP + +#include + +#include "RomHeader.hpp" +#include "RomSettings.hpp" + +#ifdef CORE_INTERNAL +// attempts to read rom header & settings cache +void CoreReadRomHeaderAndSettingsCache(void); + +// returns whether saving the rom header & settings cache +// succeeds +bool CoreSaveRomHeaderAndSettingsCache(void); +#endif // CORE_INTERNAL + +// returns whether rom header & settings have been cached +// for given filename +bool CoreHasRomHeaderAndSettingsCached(std::string file); + +// returns whether retrieving the rom header & settings +// for given filename succeeds +bool CoreGetCachedRomHeaderAndSettings(std::string file, CoreRomHeader& header, CoreRomSettings& settings); + +// returns whether adding cached rom header & settings +// for given filename succeeds +bool CoreAddCachedRomHeaderAndSettings(std::string file, CoreRomHeader header, CoreRomSettings settings); + +#endif // CORE_CACHEDROMHEADERANDSETTINGS_HPP diff --git a/Source/RMG-Core/Core.cpp b/Source/RMG-Core/Core.cpp index 6c6c231b..10f9e970 100644 --- a/Source/RMG-Core/Core.cpp +++ b/Source/RMG-Core/Core.cpp @@ -144,6 +144,8 @@ bool CoreInit(void) return false; } + CoreReadRomHeaderAndSettingsCache(); + return true; } @@ -175,5 +177,7 @@ void CoreShutdown(void) { CorePluginsShutdown(); + CoreSaveRomHeaderAndSettingsCache(); + osal_dynlib_close(l_CoreLibHandle); } diff --git a/Source/RMG-Core/Core.hpp b/Source/RMG-Core/Core.hpp index 3d64e91d..b8a5e267 100644 --- a/Source/RMG-Core/Core.hpp +++ b/Source/RMG-Core/Core.hpp @@ -10,6 +10,7 @@ #ifndef CORE_HPP #define CORE_HPP +#include "CachedRomHeaderAndSettings.hpp" #include "Settings/Settings.hpp" #include "SpeedLimiter.hpp" #include "RomSettings.hpp" diff --git a/Source/RMG-Core/osal/osal_files.hpp b/Source/RMG-Core/osal/osal_files.hpp new file mode 100644 index 00000000..37ba50d2 --- /dev/null +++ b/Source/RMG-Core/osal/osal_files.hpp @@ -0,0 +1,27 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef OSAL_FILES_HPP +#define OSAL_FILES_HPP + +#include + +typedef uint64_t osal_files_file_time; + +#ifdef _WIN32 +#define OSAL_FILES_DIR_SEPERATOR_STR "\\" +#else // Unix +#define OSAL_FILES_DIR_SEPERATOR_STR "/" +#endif // _WIN32 + +// returns file time for given file, +// returns -1 on failure +osal_files_file_time osal_files_get_file_time(std::string file); + +#endif // OSAL_FILES_HPP diff --git a/Source/RMG-Core/osal/osal_files_unix.cpp b/Source/RMG-Core/osal/osal_files_unix.cpp new file mode 100644 index 00000000..e2569982 --- /dev/null +++ b/Source/RMG-Core/osal/osal_files_unix.cpp @@ -0,0 +1,26 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "osal_files.hpp" + +#include + +osal_files_file_time osal_files_get_file_time(std::string file) +{ + int ret; + struct stat file_stat; + + ret = stat(file.c_str(), &file_stat); + if (ret != 0) + { + return -1; + } + + return file_stat.st_mtime; +} diff --git a/Source/RMG-Core/osal/osal_files_win32.cpp b/Source/RMG-Core/osal/osal_files_win32.cpp new file mode 100644 index 00000000..5f44528a --- /dev/null +++ b/Source/RMG-Core/osal/osal_files_win32.cpp @@ -0,0 +1,42 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "osal_files.hpp" + +#include +#include + +osal_files_file_time osal_files_get_file_time(std::string file) +{ + BOOL ret; + HANDLE file_handle; + FILETIME file_time; + + file_handle = CreateFileA(file.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file_handle == INVALID_HANDLE_VALUE) + { + return -1; + } + + ret = GetFileTime(file_handle, nullptr, nullptr, &file_time); + if (ret != TRUE) + { + return -1; + } + + ret = CloseHandle(file_handle); + if (ret != TRUE) + { + return -1; + } + + // this is probably incorrect but it doesn't matter + // for our use-case + return file_time.dwLowDateTime + file_time.dwHighDateTime; +}