Skip to content

Commit

Permalink
another massive overhaul to file IO, better protect against errors an…
Browse files Browse the repository at this point in the history
…d unexpected situations, improve general flexibility of methods, update relevant emulator code, resulting in considerably more robust file IO and cleaner code :D
  • Loading branch information
coornio committed Dec 6, 2024
1 parent 663248a commit 663b62e
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 150 deletions.
4 changes: 2 additions & 2 deletions src/Assistants/HomeDirManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ bool HomeDirManager::validateGameFile(const Path gamePath) noexcept {

if (checkGame(
std::data(mFileData), std::size(mFileData),
gamePath.extension().string(), tempSHA1)
) {
gamePath.extension().string(), tempSHA1
)) {
mFilePath = gamePath;
mFileSHA1 = tempSHA1;
return true;
Expand Down
121 changes: 64 additions & 57 deletions src/Assistants/SimpleFileIO.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,81 +100,88 @@ namespace fs {
/*==================================================================*/

[[maybe_unused]]
inline auto readFileData(const Path& filePath) noexcept
-> std::expected<std::vector<char>, std::error_code>
{
std::vector<char> fileData{};

// get file's last write time stamp before we begin
auto fileModStampBegin{ fs::last_write_time(filePath) };
if (!fileModStampBegin) {
return std::unexpected(std::move(fileModStampBegin.error()));
}

std::ifstream ifs(filePath, std::ios::binary);

// check if the stream was unable to access the file
if (!ifs) {
return std::unexpected(std::make_error_code(std::errc::permission_denied));
}

// ensure we could copy all the data into the vector
inline auto readFileData(
const Path& filePath, const usz dataReadSize = 0,
const std::streamoff dataReadOffset = 0
) noexcept -> std::expected<std::vector<char>, std::error_code> {
try {
fileData.assign(std::istreambuf_iterator(ifs), {});
} catch (const std::exception&) {
return std::unexpected(std::make_error_code(std::errc::not_enough_memory));
}

// check if we failed in reading the file properly
if (!ifs.good()) {
std::vector<char> fileData{};

auto fileModStampBegin{ fs::last_write_time(filePath) };
if (!fileModStampBegin) { return std::unexpected(std::move(fileModStampBegin.error())); }

std::ifstream inFile(filePath, std::ios::binary);
if (!inFile) { return std::unexpected(std::make_error_code(std::errc::permission_denied)); }

inFile.seekg(static_cast<std::streampos>(dataReadOffset));
if (!inFile) { return std::unexpected(std::make_error_code(std::errc::invalid_argument)); }

if (dataReadSize) {
fileData.resize(dataReadSize);
inFile.read(fileData.data(), dataReadSize);
} else {
try {
fileData.assign(std::istreambuf_iterator<char>(inFile), {});
} catch (const std::exception&) {
return std::unexpected(std::make_error_code(std::errc::not_enough_memory));
}
}

if (!inFile.good()) { throw std::exception{}; }

auto fileModStampEnd{ fs::last_write_time(filePath) };
if (!fileModStampEnd) { return std::unexpected(std::move(fileModStampEnd.error())); }

if (fileModStampBegin.value() != fileModStampEnd.value()) {
return std::unexpected(std::make_error_code(std::errc::interrupted));
} else { return fileData; }
}
catch (const std::exception&) {
return std::unexpected(std::make_error_code(std::errc::io_error));
}

// get file's last write time stamp after our read ended
const auto fileModStampEnd{ fs::last_write_time(filePath) };
if (!fileModStampEnd) {
return std::unexpected(std::move(fileModStampEnd.error()));
}

// check if both timestamps are still the same
if (fileModStampBegin.value() != fileModStampEnd.value()) {
return std::unexpected(std::make_error_code(std::errc::interrupted));
}

return fileData;
}

/*==================================================================*/

template <typename T>
[[maybe_unused]]
inline auto writeFileData(const Path& filePath, const T* fileData, const usz fileSize) noexcept
-> std::expected<void, std::error_code>
{
std::ofstream ofs(filePath, std::ios::binary);

// check if the stream was unable to access the file
if (!ofs) {
return std::unexpected(std::make_error_code(std::errc::permission_denied));
}

// check if we failed in writing the file properly
inline auto writeFileData(
const Path& filePath, const T* fileData, const usz dataWriteSize,
const std::streamoff dataWriteOffset = 0
) noexcept -> std::expected<void, std::error_code> {
try {
ofs.write(reinterpret_cast<const char*>(fileData), fileSize * sizeof(T));
if (!ofs.good()) { throw std::exception{}; } else { return {}; }
} catch (const std::exception&) {
std::ofstream outFile(filePath, std::ios::binary | std::ios::out);
if (!outFile) { return std::unexpected(std::make_error_code(std::errc::permission_denied)); }

outFile.seekp(static_cast<std::streampos>(dataWriteOffset));
if (!outFile) { return std::unexpected(std::make_error_code(std::errc::invalid_argument)); }

outFile.write(reinterpret_cast<const char*>(fileData), dataWriteSize * sizeof(T));
if (!outFile.good()) { throw std::exception{}; } else { return {}; }
}
catch (const std::exception&) {
return std::unexpected(std::make_error_code(std::errc::io_error));
}
}

template <IsContiguousContainer T>
[[maybe_unused]]
inline auto writeFileData(const Path& filePath, const T& fileData) noexcept {
return writeFileData(filePath, std::data(fileData), std::size(fileData));
inline auto writeFileData(
const Path& filePath, const T& fileData, const usz dataWriteSize = 0,
const std::streamoff dataWriteOffset = 0
) noexcept {
return writeFileData(
filePath, std::data(fileData), dataWriteSize
? dataWriteSize : std::size(fileData),
dataWriteOffset
);
}

template <typename T, usz N>
[[maybe_unused]]
inline auto writeFileData(const Path& filePath, const T(&fileData)[N]) noexcept {
return writeFileData(filePath, fileData, N);
inline auto writeFileData(
const Path& filePath, const T(&fileData)[N],
const std::streamoff dataWriteOffset = 0
) noexcept {
return writeFileData(filePath, fileData, N, dataWriteOffset);
}
134 changes: 63 additions & 71 deletions src/Systems/CHIP8/Chip8_CoreInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ Chip8_CoreInterface::Chip8_CoreInterface() noexcept
: ASB{ std::make_unique<AudioSpecBlock>(SDL_AUDIO_S8, 1, 48'000, STREAM::COUNT) }
{
sSavestatePath = HDM->addSystemDir("savestate", "CHIP8");
if (!sSavestatePath) { setCoreState(EmuState::FATAL); }
if (sSavestatePath) { *sSavestatePath /= HDM->getFileSHA1(); }
sPermaRegsPath = HDM->addSystemDir("permaRegs", "CHIP8");
if (!sPermaRegsPath) { setCoreState(EmuState::FATAL); }
if (sPermaRegsPath) { *sPermaRegsPath /= HDM->getFileSHA1(); }

if (!checkFileValidity(sPermaRegsPath)) { sPermaRegsPath = nullptr; }

ASB->resumeStreams();
loadPresetBinds();
Expand Down Expand Up @@ -224,103 +226,93 @@ void Chip8_CoreInterface::triggerInterrupt(const Interrupt type) noexcept {
mActiveCPF = -std::abs(mActiveCPF);
}

void Chip8_CoreInterface::triggerCritError(const Str& msg) noexcept {
blog.newEntry(BLOG::WARN, msg);
triggerInterrupt(Interrupt::ERROR);
}

/*==================================================================*/

bool Chip8_CoreInterface::setPermaRegs(const u32 X) noexcept {
const auto path{ *sPermaRegsPath / HDM->getFileSHA1() };
bool Chip8_CoreInterface::checkFileValidity(const Path* filePath) noexcept {
if (!filePath) { return false; }

const auto fileExists{ fs::is_regular_file(path) };
const auto fileExists{ fs::exists(*filePath) };
if (!fileExists) {
blog.newEntry(BLOG::ERROR, "Path is ineligible: \"{}\" [{}]",
path.string(), fileExists.error().message()
blog.newEntry(BLOG::ERROR, "\"{}\" [{}]",
filePath->string(), fileExists.error().message()
);
return false;
}

if (fileExists.value()) {
auto regsData{ readFileData(path) };

if (!regsData) {
blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]",
path.string(), regsData.error().message()
);
return false;
}
if (regsData.value().size() > mRegisterV.size()) {
blog.newEntry(BLOG::ERROR, "File is too large: \"{}\" [{} bytes]",
path.string(), regsData.value().size()
const auto fileNormal{ fs::is_regular_file(*filePath) };
if (!fileNormal) {
blog.newEntry(BLOG::ERROR, "\"{}\" [{}]",
filePath->string(), fileExists.error().message()
);
return false;
}

regsData.value().resize(mRegisterV.size());
std::copy_n(mRegisterV.begin(), X, regsData.value().begin());
if (fileNormal.value()) { return true; }
else {
const auto fileRemove{ fs::remove(*filePath) };
if (!fileRemove) {
blog.newEntry(BLOG::ERROR, "\"{}\" [{}]",
filePath->string(), fileExists.error().message()
);
return false;
}

auto fileWritten{ writeFileData(path, regsData.value()) };
if (!fileWritten) {
blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]",
path.string(), fileWritten.error().message()
);
return false;
} else {
return true;
if (fileRemove.value()) { return true; }
else {
blog.newEntry(BLOG::WARN, "{}: \"{}\"",
"Cannot remove irregular file", filePath->string()
);
return false;
}
}
} else {
std::error_code error_code;
char regsData[sizeof(mRegisterV)]{};
std::copy_n(mRegisterV.begin(), X, regsData);

auto fileWritten{ writeFileData(path, regsData) };
if (!fileWritten) {
blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]",
path.string(), fileWritten.error().message()
const char blankRegs[sPermRegsV.size()]{};
const auto fileWritten{ writeFileData(*filePath, blankRegs) };
if (fileWritten) { return true; }
else {
blog.newEntry(BLOG::WARN, "{}: \"{}\"",
"Cannot write new file", filePath->string()
);
return false;
} else {
return true;
}
}
}

bool Chip8_CoreInterface::getPermaRegs(const u32 X) noexcept {
const auto path{ *sPermaRegsPath / HDM->getFileSHA1() };

const auto fileExists{ fs::is_regular_file(path) };
if (!fileExists) {
blog.newEntry(BLOG::ERROR, "Path is ineligible: \"{}\" [{}]",
path.string(), fileExists.error().message()
void Chip8_CoreInterface::setFilePermaRegs(const u32 X) noexcept {
auto fileData{ writeFileData(*sPermaRegsPath, mRegisterV, X) };
if (!fileData) {
blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]",
sPermaRegsPath->string(), fileData.error().message()
);
return false;
}
}

if (fileExists) {
auto regsData{ readFileData(path) };
void Chip8_CoreInterface::getFilePermaRegs(const u32 X) noexcept {
auto fileData{ readFileData(*sPermaRegsPath, X) };
if (!fileData) {
blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]",
sPermaRegsPath->string(), fileData.error().message()
);
} else {
std::copy_n(fileData.value().begin(), X, sPermRegsV.begin());
}
}

if (!regsData) {
blog.newEntry(BLOG::ERROR, "File IO error: \"{}\" [{}]",
path.string(), regsData.error().message()
);
return false;
}
if (regsData.value().size() > mRegisterV.size()) {
blog.newEntry(BLOG::ERROR, "File is too large: \"{}\" [{} bytes]",
path.string(), regsData.value().size()
);
return false;
}
void Chip8_CoreInterface::setPermaRegs(const u32 X) noexcept {
if (sPermaRegsPath) {
if (checkFileValidity(sPermaRegsPath)) { setFilePermaRegs(X); }
else { sPermaRegsPath = nullptr; }
}
std::copy_n(mRegisterV.begin(), X, sPermRegsV.begin());
}

regsData.value().resize(mRegisterV.size());
std::copy_n(regsData.value().begin(), X, mRegisterV.begin());
return true;
} else {
std::fill_n(mRegisterV.begin(), X, u8{});
return true;
void Chip8_CoreInterface::getPermaRegs(const u32 X) noexcept {
if (sPermaRegsPath) {
if (checkFileValidity(sPermaRegsPath)) { getFilePermaRegs(X); }
else { sPermaRegsPath = nullptr; }
}
std::copy_n(sPermRegsV.begin(), X, mRegisterV.begin());
}

/*==================================================================*/
Expand Down
15 changes: 12 additions & 3 deletions src/Systems/CHIP8/Chip8_CoreInterface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ class Chip8_CoreInterface : public EmuInterface {
u32 mStackTop{};
u8* mInputReg{};

inline static
std::array<u8, 16>
sPermRegsV{};

std::array<u8, 16>
mRegisterV{};

Expand All @@ -165,10 +169,15 @@ class Chip8_CoreInterface : public EmuInterface {
void instructionError(const u32 HI, const u32 LO);

void triggerInterrupt(const Interrupt type) noexcept;
void triggerCritError(const Str& msg) noexcept;

bool setPermaRegs(const u32 X) noexcept;
bool getPermaRegs(const u32 X) noexcept;
private:
bool checkFileValidity(const Path* filePath) noexcept;
void setFilePermaRegs(const u32 X) noexcept;
void getFilePermaRegs(const u32 X) noexcept;

protected:
void setPermaRegs(const u32 X) noexcept;
void getPermaRegs(const u32 X) noexcept;

void copyGameToMemory(void* dest) noexcept;
void copyFontToMemory(void* dest, const usz size) noexcept;
Expand Down
6 changes: 2 additions & 4 deletions src/Systems/CHIP8/Cores/MEGACHIP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1036,12 +1036,10 @@ void MEGACHIP::scrollBuffersRT() {
{ mRegisterV[idx] = readMemoryI(idx); }
}
void MEGACHIP::instruction_FN75(const s32 N) noexcept {
if (!setPermaRegs(std::min(N, 7) + 1)) [[unlikely]]
{ triggerCritError("Error :: Failed writing persistent registers!"); }
setPermaRegs(std::min(N, 7) + 1);
}
void MEGACHIP::instruction_FN85(const s32 N) noexcept {
if (!getPermaRegs(std::min(N, 7) + 1)) [[unlikely]]
{ triggerCritError("Error :: Failed reading persistent registers!"); }
getPermaRegs(std::min(N, 7) + 1);
}

#pragma endregion
Expand Down
6 changes: 2 additions & 4 deletions src/Systems/CHIP8/Cores/SCHIP_LEGACY.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,12 +642,10 @@ void SCHIP_LEGACY::scrollDisplayRT() {
{ mRegisterI = mRegisterI + N & 0xFFF; }
}
void SCHIP_LEGACY::instruction_FN75(const s32 N) noexcept {
if (!setPermaRegs(std::min(N, 7) + 1)) [[unlikely]]
{ triggerCritError("Error :: Failed writing persistent registers!"); }
setPermaRegs(std::min(N, 7) + 1);
}
void SCHIP_LEGACY::instruction_FN85(const s32 N) noexcept {
if (!getPermaRegs(std::min(N, 7) + 1)) [[unlikely]]
{ triggerCritError("Error :: Failed reading persistent registers!"); }
getPermaRegs(std::min(N, 7) + 1);
}

#pragma endregion
Expand Down
Loading

0 comments on commit 663b62e

Please sign in to comment.