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;