From 9fd803210078bea3644961f684688cbfcff8d50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A3=CF=84=CE=AD=CF=86=CE=B1=CE=BD=CE=BF=CF=82=20=22Coor?= =?UTF-8?q?nio/8924th=22=20=CE=92=CE=BB=CE=B1=CF=83=CF=84=CF=8C=CF=82?= <8924th@gmail.com> Date: Fri, 27 Sep 2024 02:25:46 +0300 Subject: [PATCH] some work to "ease up" on complexity of file io and hardening, buuuut it's not super pretty yet. --- CubeChip (SDL).vcxproj | 1 + CubeChip (SDL).vcxproj.filters | 3 + src/Assistants/HomeDirManager.cpp | 57 +++----- src/Assistants/HomeDirManager.hpp | 8 +- src/Assistants/SimpleFileIO.hpp | 150 ++++++++++++++++++++++ src/EmuHost.cpp | 4 + src/Systems/CHIP8/Chip8_CoreInterface.cpp | 141 +++++++++++--------- src/Systems/CHIP8/Chip8_CoreInterface.hpp | 13 +- 8 files changed, 269 insertions(+), 108 deletions(-) create mode 100644 src/Assistants/SimpleFileIO.hpp diff --git a/CubeChip (SDL).vcxproj b/CubeChip (SDL).vcxproj index 11c16d7..d4f332c 100644 --- a/CubeChip (SDL).vcxproj +++ b/CubeChip (SDL).vcxproj @@ -54,6 +54,7 @@ + diff --git a/CubeChip (SDL).vcxproj.filters b/CubeChip (SDL).vcxproj.filters index e1e54a1..f297e19 100644 --- a/CubeChip (SDL).vcxproj.filters +++ b/CubeChip (SDL).vcxproj.filters @@ -196,5 +196,8 @@ Source Files\Systems\GAMEBOY\Cores + + Source Files\Assistants + \ No newline at end of file diff --git a/src/Assistants/HomeDirManager.cpp b/src/Assistants/HomeDirManager.cpp index 42e6513..b07b330 100644 --- a/src/Assistants/HomeDirManager.cpp +++ b/src/Assistants/HomeDirManager.cpp @@ -10,25 +10,12 @@ #include "HomeDirManager.hpp" #include "../Assistants/SHA1.hpp" +#include "../Assistants/SimpleFileIO.hpp" #include "../Assistants/PathGetters.hpp" #include "../Assistants/BasicLogger.hpp" #include -/*==================================================================*/ - -[[maybe_unused]] -static auto getFileModTime(const Path& filePath) noexcept { - std::error_code error; - return std::filesystem::last_write_time(filePath, error); -} - -[[maybe_unused]] -static auto getFileSize(const Path& filePath) noexcept { - std::error_code error; - return std::filesystem::file_size(filePath, error); -} - /*==================================================================*/ #pragma region HomeDirManager Class @@ -82,52 +69,44 @@ void HomeDirManager::clearCachedFileData() noexcept { } bool HomeDirManager::validateGameFile(const Path gamePath) noexcept { - if (gamePath.empty()) { return false; } - namespace fs = std::filesystem; std::error_code error; - blog.newEntry(BLOG::INFO, "Attempting to access file: {}", gamePath.string()); - - if (!fs::exists(gamePath, error) || error) { - blog.newEntry(BLOG::WARN, "Unable to locate path! {}", error.message()); + if (!::doesFileExist(gamePath, &error) || error) { + blog.newEntry(BLOG::WARN, "Path is ineligible: {}", error.message()); return false; } - if (!fs::is_regular_file(gamePath, error) || error) { - blog.newEntry(BLOG::WARN, "Provided path is not to a file!"); + const auto fileSize{ ::getFileSize(gamePath, &error) }; + if (error) { + blog.newEntry(BLOG::WARN, "Path is ineligible: {}", error.message()); return false; } - const auto tempTime{ getFileModTime(gamePath) }; - - std::ifstream ifs(gamePath, std::ios::binary); - mFileData.assign(std::istreambuf_iterator(ifs), {}); + if (fileSize == 0) { + blog.newEntry(BLOG::WARN, "Game file must not be empty!"); + return false; + } - if (tempTime != getFileModTime(gamePath)) { - blog.newEntry(BLOG::WARN, "File was modified while reading!"); + if (fileSize >= 33'554'432) { // 32 MB upper limit + blog.newEntry(BLOG::WARN, "Game file is too large!"); return false; } - if (!getFileSize()) { - blog.newEntry(BLOG::WARN, "File must not be empty!"); + mFileData = std::move(::readFileData(gamePath, &error)); + if (error) { + blog.newEntry(BLOG::WARN, "Path is ineligible: {}", error.message()); return false; } const auto tempSHA1{ SHA1::from_span(mFileData) }; - const bool gameApproved{ checkGame(mFileData, gamePath.extension().string(), tempSHA1)}; - if (gameApproved) { + if (checkGame(mFileData, gamePath.extension().string(), tempSHA1)) { mFilePath = gamePath; mFileSHA1 = tempSHA1; - } - - if (gameApproved) { - blog.newEntry(BLOG::INFO, "File is a valid game!"); + return true; } else { - blog.newEntry(BLOG::INFO, "File is not a valid game!"); + return false; } - - return gameApproved; } #pragma endregion diff --git a/src/Assistants/HomeDirManager.hpp b/src/Assistants/HomeDirManager.hpp index 2b6767e..37d2004 100644 --- a/src/Assistants/HomeDirManager.hpp +++ b/src/Assistants/HomeDirManager.hpp @@ -23,12 +23,12 @@ class HomeDirManager final { using GameValidator = bool (*)( std::span, - const std::string&, - const std::string& + const Str&, + const Str& ) noexcept; - Path mFilePath{}; - std::string mFileSHA1{}; + Path mFilePath{}; + Str mFileSHA1{}; std::vector mFileData{}; diff --git a/src/Assistants/SimpleFileIO.hpp b/src/Assistants/SimpleFileIO.hpp new file mode 100644 index 0000000..8466a40 --- /dev/null +++ b/src/Assistants/SimpleFileIO.hpp @@ -0,0 +1,150 @@ +/* + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include "Typedefs.hpp" + +/*==================================================================*/ + +[[maybe_unused]] +inline auto getFileModTime( + const Path& filePath, + std::error_code* const ioError = nullptr +) noexcept { + std::error_code error; + const auto modifiedTime{ std::filesystem::last_write_time(filePath, error) }; + if (error && ioError) { *ioError = error; } + return modifiedTime; +} + +[[maybe_unused]] +inline auto getFileSize( + const Path& filePath, + std::error_code* const ioError = nullptr +) noexcept { + std::error_code error; + const auto fileSize{ std::filesystem::file_size(filePath, error) }; + if (error && ioError) { *ioError = error; } + return fileSize; +} + +[[maybe_unused]] +inline auto doesPathExist( + const Path& filePath, + std::error_code* const ioError = nullptr +) noexcept { + std::error_code error; + + // Check if the path doesn't lead anywhere + if (!std::filesystem::exists(filePath, error) || error) { + if (ioError) { *ioError = error; } + return false; + } else { + return true; + } +} + +[[maybe_unused]] +inline auto doesFileExist( + const Path& filePath, + std::error_code* const ioError = nullptr +) noexcept { + std::error_code error; + + // Check if the path doesn't lead to a regular file + if (!std::filesystem::is_regular_file(filePath, error) || error) { + if (ioError) { *ioError = error; } + return false; + } else { + return true; + } +} + +[[maybe_unused]] +inline auto readFileData( + const Path& filePath, + std::error_code* const ioError = nullptr +) noexcept { + std::vector fileData{}; + std::error_code error; + + // Attempt first file mod time fetch + const auto fileModStampBegin{ ::getFileModTime(filePath, &error) }; + if (error) { + if (ioError) { *ioError = error; } + fileData.clear(); return fileData; + } + + std::ifstream ifs(filePath, std::ios::binary); + + // Attempt to init file stream + if (ifs) { + try { + // Attempt to read data into vector + fileData.assign(std::istreambuf_iterator(ifs), {}); + } catch (const std::exception&) { + if (ioError) { *ioError = std::make_error_code(std::errc::not_enough_memory); } + fileData.clear(); return fileData; + } + // Check if the stream failed to reach EOF safely + if (!ifs.good()) { + if (ioError) { *ioError = std::make_error_code(std::errc::io_error); } + fileData.clear(); return fileData; + } + } else { + if (ioError) { + *ioError = std::make_error_code(std::errc::permission_denied); + fileData.clear(); return fileData; + } + } + + // Attempt second file mod time fetch + const auto fileModStampEnd{ ::getFileModTime(filePath, &error) }; + if (error) { + if (ioError) { *ioError = error; } + fileData.clear(); return fileData; + } + + // Compare times to ensure no modification occurred during read + if (fileModStampBegin != fileModStampEnd) { + if (ioError) { *ioError = std::make_error_code(std::errc::interrupted); } + fileData.clear(); return fileData; + } else { + return fileData; + } +} + +template [[maybe_unused]] +inline auto writeFileData( + const Path& filePath, + std::span fileData, + std::error_code* const ioError = nullptr +) noexcept requires std::is_trivially_constructible_v { + std::ofstream ofs(filePath, std::ios::binary); + + // Attempt to init file stream + if (ofs) { + ofs.write(reinterpret_cast(fileData.data()), fileData.size() * sizeof(T)); + + // Check if the stream failed to write fully + if (!ofs.good()) { + if (ioError) { *ioError = std::make_error_code(std::errc::io_error); } + return true; + } else { + return false; + } + } else { + if (ioError) { *ioError = std::make_error_code(std::errc::permission_denied); } + return true; + } +} diff --git a/src/EmuHost.cpp b/src/EmuHost.cpp index 47b4770..6a5ae41 100644 --- a/src/EmuHost.cpp +++ b/src/EmuHost.cpp @@ -94,8 +94,12 @@ void EmuHost::replaceCore() { void EmuHost::loadGameFile(const Path& gameFile) { BVS->raiseWindow(); + blog.newEntry(BLOG::INFO, "Attempting to access file: \"{}\"", gameFile.string()); if (HDM->validateGameFile(gameFile)) { replaceCore(); + blog.newEntry(BLOG::INFO, "File has been accepted!"); + } else { + blog.newEntry(BLOG::INFO, "File has been rejected!"); } } diff --git a/src/Systems/CHIP8/Chip8_CoreInterface.cpp b/src/Systems/CHIP8/Chip8_CoreInterface.cpp index 013a990..100bac3 100644 --- a/src/Systems/CHIP8/Chip8_CoreInterface.cpp +++ b/src/Systems/CHIP8/Chip8_CoreInterface.cpp @@ -9,14 +9,15 @@ #include "../../Assistants/HomeDirManager.hpp" #include "../../Assistants/BasicVideoSpec.hpp" #include "../../Assistants/BasicAudioSpec.hpp" +#include "../../Assistants/SimpleFileIO.hpp" #include "../../Assistants/Well512.hpp" #include "Chip8_CoreInterface.hpp" /*==================================================================*/ -Path* Chip8_CoreInterface::sPermaRegsPath{}; -Path* Chip8_CoreInterface::sSavestatePath{}; +Path* Chip8_CoreInterface::sPermaRegsPath{}; +Path* Chip8_CoreInterface::sSavestatePath{}; std::array Chip8_CoreInterface::sFontsData{ Chip8_CoreInterface::cFontsData }; std::array Chip8_CoreInterface::sBitColors{ Chip8_CoreInterface::cBitColors }; @@ -194,96 +195,116 @@ void Chip8_CoreInterface::triggerInterrupt(const Interrupt type) noexcept { mActiveCPF = -std::abs(mActiveCPF); } -void Chip8_CoreInterface::triggerCritError(const std::string& msg) noexcept { +void Chip8_CoreInterface::triggerCritError(const Str& msg) noexcept { blog.newEntry(BLOG::INFO, msg); triggerInterrupt(Interrupt::ERROR); } /*==================================================================*/ -bool Chip8_CoreInterface::setPermaRegs(const s32 X) noexcept { +bool Chip8_CoreInterface::setPermaRegs(const u32 X) noexcept { const auto path{ *sPermaRegsPath / HDM->getFileSHA1() }; + std::error_code error_code; - if (std::filesystem::exists(path)) { - if (!std::filesystem::is_regular_file(path)) { - blog.newEntry(BLOG::ERROR, "SHA1 file is malformed: {}", path.string()); - return true; - } - - char tempV[16]{}; - std::ifstream in(path, std::ios::binary); + const bool fileExists{ doesFileExist(path, &error_code) }; + if (error_code) { + blog.newEntry(BLOG::ERROR, "Path is ineligible: \"{}\" [{}]", + path.string(), error_code.message() + ); + return true; + } - if (in.is_open()) { - in.seekg(0, std::ios::end); - const auto totalBytes{ in.tellg() }; - in.seekg(0, std::ios::beg); + if (fileExists) { + std::vector regsData{ readFileData(path, &error_code) }; - in.read(tempV, std::min(totalBytes, X)); - in.close(); - } else { - blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to read: {}", path.string()); + if (error_code) { + blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]", + path.string(), error_code.message() + ); + return true; + } + if (regsData.size() > mRegisterV.size()) { + blog.newEntry(BLOG::ERROR, "File is too large: \"{}\" [{} bytes]", + path.string(), regsData.size() + ); return true; } - std::copy_n(mRegisterV, X, tempV); - - std::ofstream out(path, std::ios::binary); - if (out.is_open()) { - out.write(tempV, 16); - out.close(); - } else { - blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to write: {}", path.string()); + regsData.resize(mRegisterV.size()); + std::copy_n( + std::execution::unseq, + mRegisterV.begin(), X, regsData.begin() + ); + writeFileData(path, regsData, &error_code); + if (error_code) { + blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]", + path.string(), error_code.message() + ); return true; + } else { + return false; } } else { - std::ofstream out(path, std::ios::binary); - if (out.is_open()) { - out.write(reinterpret_cast(mRegisterV), X); - if (X < 16) { - const char padding[16]{}; - out.write(padding, 16 - X); - } - out.close(); - } else { - blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to write: {}", path.string()); + std::array regsData{ 16 }; + + std::copy_n( + std::execution::unseq, + mRegisterV.begin(), X, regsData.begin() + ); + writeFileData(path, regsData, &error_code); + if (error_code) { + blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]", + path.string(), error_code.message() + ); return true; + } else { + return false; } + return false; } - return false; } -bool Chip8_CoreInterface::getPermaRegs(const s32 X) noexcept { +bool Chip8_CoreInterface::getPermaRegs(const u32 X) noexcept { const auto path{ *sPermaRegsPath / HDM->getFileSHA1() }; + std::error_code error_code; - if (std::filesystem::exists(path)) { - if (!std::filesystem::is_regular_file(path)) { - blog.newEntry(BLOG::ERROR, "SHA1 file is malformed: {}", path.string()); - return true; - } - - std::ifstream in(path, std::ios::binary); - if (in.is_open()) { - in.seekg(0, std::ios::end); - const auto totalBytes{ static_cast(in.tellg()) }; - in.seekg(0, std::ios::beg); + const bool fileExists{ doesFileExist(path, &error_code) }; + if (error_code) { + blog.newEntry(BLOG::ERROR, "Path is ineligible: \"{}\" [{}]", + path.string(), error_code.message() + ); + return true; + } - in.read(reinterpret_cast(mRegisterV), std::min(totalBytes, X)); - in.close(); + if (fileExists) { + std::vector regsData{ readFileData(path, &error_code) }; - if (totalBytes < X) { - std::fill_n(mRegisterV + totalBytes, X - totalBytes, u8()); - } - } else { - blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to read: {}", path.string()); + if (error_code) { + blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]", + path.string(), error_code.message() + ); + return true; + } + if (regsData.size() > mRegisterV.size()) { + blog.newEntry(BLOG::ERROR, "File is too large: \"{}\" [{} bytes]", + path.string(), regsData.size() + ); return true; } + + regsData.resize(mRegisterV.size()); + std::copy_n( + std::execution::unseq, + regsData.begin(), X, mRegisterV.begin() + ); + return false; } else { std::fill_n( std::execution::unseq, - mRegisterV, X, u8{} + mRegisterV.begin(), X, u8{} ); + return false; } - return false; } /*==================================================================*/ diff --git a/src/Systems/CHIP8/Chip8_CoreInterface.hpp b/src/Systems/CHIP8/Chip8_CoreInterface.hpp index 146d613..e35f3cf 100644 --- a/src/Systems/CHIP8/Chip8_CoreInterface.hpp +++ b/src/Systems/CHIP8/Chip8_CoreInterface.hpp @@ -130,18 +130,21 @@ class Chip8_CoreInterface : public EmuInterface { u32 mStackTop{}; u8* mInputReg{}; - u8 mRegisterV[16]{}; - u32 mStackBank[16]{}; + std::array + mRegisterV{}; + + std::array + mStackBank{}; /*==================================================================*/ void instructionError(const u32 HI, const u32 LO); void triggerInterrupt(const Interrupt type) noexcept; - void triggerCritError(const std::string& msg) noexcept; + void triggerCritError(const Str& msg) noexcept; - bool setPermaRegs(const s32 X) noexcept; - bool getPermaRegs(const s32 X) noexcept; + bool setPermaRegs(const u32 X) noexcept; + bool getPermaRegs(const u32 X) noexcept; void copyGameToMemory(u8* dest, const u32 offset) noexcept; void copyFontToMemory(u8* dest, const u32 offset, const usz size) noexcept;