From 1a1a282d9a817c4f4f98fbea3dc8a8842574b53b Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 15 Feb 2023 08:59:09 +0000 Subject: [PATCH] Use C FILE instead of C++ streams throughout Reduces binary size by ~2 KiB and may improve compatibility with oddball systems (e.g. PS2). --- Source/capture.cpp | 34 +++++++------- Source/debug.cpp | 65 +++++++++++++------------- Source/dvlnet/tcp_client.cpp | 1 - Source/engine/assets.hpp | 4 +- Source/engine/demomode.cpp | 29 +++++++----- Source/mpq/mpq_writer.cpp | 32 ++++++------- Source/mpq/mpq_writer.hpp | 2 +- Source/options.cpp | 22 +++++---- Source/pfile.cpp | 83 ++++++++++++--------------------- Source/utils/endian_stream.hpp | 33 +++++++------ Source/utils/file_util.cpp | 73 +++++++++++++++++++++++------ Source/utils/file_util.h | 4 +- Source/utils/logged_fstream.cpp | 34 +++----------- Source/utils/logged_fstream.hpp | 57 +++++++++++----------- test/file_util_test.cpp | 16 +++---- test/writehero_test.cpp | 14 ++++-- 16 files changed, 255 insertions(+), 248 deletions(-) diff --git a/Source/capture.cpp b/Source/capture.cpp index c5e855f02..1dab3b240 100644 --- a/Source/capture.cpp +++ b/Source/capture.cpp @@ -4,8 +4,9 @@ * Implementation of the screenshot function. */ #include +#include + #include -#include #include "DiabloUI/diabloui.h" #include "engine/backbuffer_state.hpp" @@ -28,7 +29,7 @@ namespace { * @param out File stream to write to * @return True on success */ -bool CaptureHdr(int16_t width, int16_t height, std::ofstream *out) +bool CaptureHdr(int16_t width, int16_t height, FILE *out) { PCXHeader buffer; @@ -44,8 +45,7 @@ bool CaptureHdr(int16_t width, int16_t height, std::ofstream *out) buffer.NPlanes = 1; buffer.BytesPerLine = SDL_SwapLE16(width); - out->write(reinterpret_cast(&buffer), sizeof(buffer)); - return !out->fail(); + return std::fwrite(&buffer, sizeof(buffer), 1, out) == 1; } /** @@ -54,7 +54,7 @@ bool CaptureHdr(int16_t width, int16_t height, std::ofstream *out) * @param out File stream for the PCX file. * @return True if successful, else false */ -bool CapturePal(SDL_Color *palette, std::ofstream *out) +bool CapturePal(SDL_Color *palette, FILE *out) { uint8_t pcxPalette[1 + 256 * 3]; @@ -65,8 +65,7 @@ bool CapturePal(SDL_Color *palette, std::ofstream *out) pcxPalette[1 + 3 * i + 2] = palette[i].b; } - out->write(reinterpret_cast(pcxPalette), sizeof(pcxPalette)); - return !out->fail(); + return std::fwrite(pcxPalette, sizeof(pcxPalette), 1, out) == 1; } /** @@ -118,7 +117,7 @@ uint8_t *CaptureEnc(uint8_t *src, uint8_t *dst, int width) * @param out File stream for the PCX file. * @return True if successful, else false */ -bool CapturePix(const Surface &buf, std::ofstream *out) +bool CapturePix(const Surface &buf, FILE *out) { int width = buf.w(); std::unique_ptr pBuffer { new uint8_t[2 * width] }; @@ -126,14 +125,13 @@ bool CapturePix(const Surface &buf, std::ofstream *out) for (int height = buf.h(); height > 0; height--) { const uint8_t *pBufferEnd = CaptureEnc(pixels, pBuffer.get(), width); pixels += buf.pitch(); - out->write(reinterpret_cast(pBuffer.get()), pBufferEnd - pBuffer.get()); - if (out->fail()) + if (std::fwrite(pBuffer.get(), pBufferEnd - pBuffer.get(), 1, out) != 1) return false; } return true; } -std::ofstream CaptureFile(std::string *dstPath) +FILE *CaptureFile(std::string *dstPath) { std::time_t tt = std::time(nullptr); std::tm *tm = std::localtime(&tt); @@ -144,7 +142,7 @@ std::ofstream CaptureFile(std::string *dstPath) i++; *dstPath = StrCat(paths::PrefPath(), filename, "-", i, ".pcx"); } - return std::ofstream(*dstPath, std::ios::binary | std::ios::trunc); + return OpenFile(dstPath->c_str(), "wb"); } /** @@ -168,22 +166,22 @@ void CaptureScreen() std::string fileName; bool success; - std::ofstream outStream = CaptureFile(&fileName); - if (!outStream.is_open()) + FILE *outStream = CaptureFile(&fileName); + if (outStream == nullptr) return; DrawAndBlit(); PaletteGetEntries(256, palette); RedPalette(); const Surface &buf = GlobalBackBuffer(); - success = CaptureHdr(buf.w(), buf.h(), &outStream); + success = CaptureHdr(buf.w(), buf.h(), outStream); if (success) { - success = CapturePix(buf, &outStream); + success = CapturePix(buf, outStream); } if (success) { - success = CapturePal(palette, &outStream); + success = CapturePal(palette, outStream); } - outStream.close(); + std::fclose(outStream); if (!success) { Log("Failed to save screenshot at {}", fileName); diff --git a/Source/debug.cpp b/Source/debug.cpp index 25970917c..d8f700c3d 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -6,8 +6,7 @@ #ifdef _DEBUG -#include -#include +#include #include "debug.h" @@ -29,9 +28,11 @@ #include "spells.h" #include "towners.h" #include "utils/endian_stream.hpp" +#include "utils/file_util.h" #include "utils/language.h" #include "utils/log.hpp" #include "utils/str_cat.hpp" +#include "utils/str_split.hpp" namespace devilution { @@ -262,21 +263,20 @@ std::string DebugCmdLoadMap(const string_view parameter) int mapType = 0; Point spawn = {}; - std::stringstream paramsStream(parameter.data()); int count = 0; - for (std::string tmp; std::getline(paramsStream, tmp, ' ');) { + for (string_view arg : SplitByChar(parameter, ' ')) { switch (count) { case 0: - TestMapPath = StrCat(tmp, ".dun"); + TestMapPath = StrCat(arg, ".dun"); break; case 1: - mapType = atoi(tmp.c_str()); + mapType = atoi(std::string(arg).c_str()); break; case 2: - spawn.x = atoi(tmp.c_str()); + spawn.x = atoi(std::string(arg).c_str()); break; case 3: - spawn.y = atoi(tmp.c_str()); + spawn.y = atoi(std::string(arg).c_str()); break; } count++; @@ -299,11 +299,9 @@ std::string DebugCmdLoadMap(const string_view parameter) std::string ExportDun(const string_view parameter) { - std::ofstream dunFile; - std::string levelName = StrCat(currlevel, "-", glSeedTbl[currlevel], ".dun"); - dunFile.open(levelName, std::ios::out | std::ios::app | std::ios::binary); + FILE *dunFile = OpenFile(levelName.c_str(), "ab"); WriteLE16(dunFile, DMAXX); WriteLE16(dunFile, DMAXY); @@ -361,7 +359,7 @@ std::string ExportDun(const string_view parameter) WriteLE16(dunFile, dTransVal[x][y]); } } - dunFile.close(); + std::fclose(dunFile); return StrCat(levelName, " saved. Happy mapping!"); } @@ -424,18 +422,18 @@ std::string DebugCmdResetLevel(const string_view parameter) { Player &myPlayer = *MyPlayer; - std::stringstream paramsStream(parameter.data()); - std::string singleParameter; - if (!std::getline(paramsStream, singleParameter, ' ')) + auto args = SplitByChar(parameter, ' '); + auto it = args.begin(); + if (it == args.end()) return "What level do you want to visit?"; - auto level = atoi(singleParameter.c_str()); + auto level = atoi(std::string(*it).c_str()); if (level < 0 || level > (gbIsHellfire ? 24 : 16)) return StrCat("Level ", level, " is not known. Do you want to write an extension mod?"); myPlayer._pLvlVisited[level] = false; DeltaClearLevel(level); - if (std::getline(paramsStream, singleParameter, ' ')) { - uint32_t seed = static_cast(std::stoul(singleParameter)); + if (++it != args.end()) { + const auto seed = static_cast(std::stoul(std::string(*it))); glSeedTbl[level] = seed; } @@ -682,15 +680,16 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter) if (leveltype == DTYPE_TOWN) return "Do you want to kill the towners?!?"; - std::stringstream paramsStream(parameter.data()); std::string name; int count = 1; - for (std::string tmp; std::getline(paramsStream, tmp, ' '); name += tmp + " ") { - int num = atoi(tmp.c_str()); + for (string_view arg : SplitByChar(parameter, ' ')) { + const int num = atoi(std::string(arg).c_str()); if (num > 0) { count = num; break; } + AppendStrView(name, arg); + name += ' '; } if (name.empty()) return "Monster name cannot be empty. Duh."; @@ -769,15 +768,16 @@ std::string DebugCmdSpawnMonster(const string_view parameter) if (leveltype == DTYPE_TOWN) return "Do you want to kill the towners?!?"; - std::stringstream paramsStream(parameter.data()); std::string name; int count = 1; - for (std::string tmp; std::getline(paramsStream, tmp, ' '); name += tmp + " ") { - int num = atoi(tmp.c_str()); + for (string_view arg : SplitByChar(parameter, ' ')) { + const int num = atoi(std::string(arg).c_str()); if (num > 0) { count = num; break; } + AppendStrView(name, arg); + name += ' '; } if (name.empty()) return "Monster name cannot be empty. Duh."; @@ -979,21 +979,22 @@ std::string DebugCmdToggleFPS(const string_view parameter) std::string DebugCmdChangeTRN(const string_view parameter) { - std::stringstream paramsStream(parameter.data()); - std::string first; std::string out; - if (std::getline(paramsStream, first, ' ')) { - std::string second; - if (std::getline(paramsStream, second, ' ')) { - std::string prefix; + const auto parts = SplitByChar(parameter, ' '); + auto it = parts.begin(); + if (it != parts.end()) { + const string_view first = *it; + if (++it != parts.end()) { + const string_view second = *it; + string_view prefix; if (first == "mon") { prefix = "monsters\\monsters\\"; } else if (first == "plr") { prefix = "plrgfx\\"; } - debugTRN = prefix + second + ".trn"; + debugTRN = StrCat(prefix, second, ".trn"); } else { - debugTRN = first + ".trn"; + debugTRN = StrCat(first, ".trn"); } out = fmt::format("I am a pretty butterfly. \n(Loading TRN: {:s})", debugTRN); } else { diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp index 6f02b8d47..773f9bb91 100644 --- a/Source/dvlnet/tcp_client.cpp +++ b/Source/dvlnet/tcp_client.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include diff --git a/Source/engine/assets.hpp b/Source/engine/assets.hpp index 66092cd7e..12f78b35b 100644 --- a/Source/engine/assets.hpp +++ b/Source/engine/assets.hpp @@ -81,7 +81,7 @@ struct AssetHandle { return std::fread(buffer, len, 1, handle) == 1; } - bool seek(std::ios::pos_type pos) + bool seek(long pos) { return std::fseek(handle, pos, SEEK_SET) == 0; } @@ -193,7 +193,7 @@ struct AssetHandle { return handle->read(handle, buffer, len, 1) == 1; } - bool seek(std::ios::pos_type pos) + bool seek(long pos) { return handle->seek(handle, pos, RW_SEEK_SET) != -1; } diff --git a/Source/engine/demomode.cpp b/Source/engine/demomode.cpp index 82117b09c..34e5b378b 100644 --- a/Source/engine/demomode.cpp +++ b/Source/engine/demomode.cpp @@ -1,9 +1,7 @@ #include "engine/demomode.h" +#include #include -#include -#include -#include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -76,7 +74,7 @@ bool Timedemo = false; int RecordNumber = -1; bool CreateDemoReference = false; -std::ofstream DemoRecording; +FILE *DemoRecording; std::deque Demo_Message_Queue; uint32_t DemoModeLastTick = 0; @@ -277,9 +275,9 @@ void LogDemoMessage(const DemoMsg &msg) bool LoadDemoMessages(int i) { - std::ifstream demofile; - demofile.open(StrCat(paths::PrefPath(), "demo_", i, ".dmo"), std::fstream::binary); - if (!demofile.is_open()) { + const std::string path = StrCat(paths::PrefPath(), "demo_", i, ".dmo"); + FILE *demofile = OpenFile(path.c_str(), "rb"); + if (demofile == nullptr) { return false; } @@ -294,7 +292,7 @@ bool LoadDemoMessages(int i) while (true) { const uint32_t typeNum = ReadLE32(demofile); - if (demofile.eof()) + if (std::feof(demofile)) break; const auto type = static_cast(typeNum); @@ -343,7 +341,7 @@ bool LoadDemoMessages(int i) } } - demofile.close(); + std::fclose(demofile); DemoModeLastTick = SDL_GetTicks(); @@ -496,7 +494,7 @@ void RecordGameLoopResult(bool runGameLoop) void RecordMessage(const SDL_Event &event, uint16_t modState) { - if (!gbRunGame || !DemoRecording.is_open()) + if (!gbRunGame || DemoRecording == nullptr) return; if (CurrentEventHandler == DisableInputEventHandler) return; @@ -553,7 +551,13 @@ void RecordMessage(const SDL_Event &event, uint16_t modState) void NotifyGameLoopStart() { if (IsRecording()) { - DemoRecording.open(StrCat(paths::PrefPath(), "demo_", RecordNumber, ".dmo"), std::fstream::trunc | std::fstream::binary); + const std::string path = StrCat(paths::PrefPath(), "demo_", RecordNumber, ".dmo"); + DemoRecording = OpenFile(path.c_str(), "wb"); + if (DemoRecording == nullptr) { + RecordNumber = -1; + LogError("Failed to open {} for writing", path); + return; + } constexpr uint8_t Version = 0; WriteByte(DemoRecording, Version); WriteLE32(DemoRecording, gSaveNumber); @@ -570,7 +574,8 @@ void NotifyGameLoopStart() void NotifyGameLoopEnd() { if (IsRecording()) { - DemoRecording.close(); + std::fclose(DemoRecording); + DemoRecording = nullptr; if (CreateDemoReference) pfile_write_hero_demo(RecordNumber); diff --git a/Source/mpq/mpq_writer.cpp b/Source/mpq/mpq_writer.cpp index 96bb63d24..fb448aaf0 100644 --- a/Source/mpq/mpq_writer.cpp +++ b/Source/mpq/mpq_writer.cpp @@ -48,8 +48,8 @@ constexpr uint32_t HashEntrySize = BlockEntriesCount * sizeof(MpqHashEntry); // We store the block and the hash entry tables immediately after the header. // This is unlike most other MPQ archives, that store these at the end of the file. -constexpr std::ios::off_type MpqBlockEntryOffset = sizeof(MpqFileHeader); -constexpr std::ios::off_type MpqHashEntryOffset = MpqBlockEntryOffset + BlockEntrySize; +constexpr long MpqBlockEntryOffset = sizeof(MpqFileHeader); +constexpr long MpqHashEntryOffset = MpqBlockEntryOffset + BlockEntrySize; // Special return value for `GetHashIndex` and `GetHandle`. constexpr uint32_t HashEntryNotFound = -1; @@ -100,21 +100,19 @@ MpqWriter::MpqWriter(const char *path) LogVerbose("Opening {}", path); std::string error; bool exists = FileExists(path); - std::ios::openmode mode = std::ios::out | std::ios::binary; + const char *mode = "wb"; if (exists) { - mode |= std::ios::in; + mode = "r+b"; if (!GetFileSize(path, &size_)) { error = R"(GetFileSize failed: "{}")"; LogError(error, path, std::strerror(errno)); goto on_error; } LogVerbose("GetFileSize(\"{}\") = {}", path, size_); - } else { - mode |= std::ios::trunc; } if (!stream_.Open(path, mode)) { stream_.Close(); - error = "Failed to open fstream"; + error = "Failed to open file"; goto on_error; } @@ -131,7 +129,7 @@ MpqWriter::MpqWriter(const char *path) blockTable_ = std::make_unique(BlockEntriesCount); std::memset(blockTable_.get(), 0, BlockEntriesCount * sizeof(MpqBlockEntry)); if (fhdr.blockEntriesCount > 0) { - if (!stream_.Read(reinterpret_cast(blockTable_.get()), static_cast(fhdr.blockEntriesCount * sizeof(MpqBlockEntry)))) { + if (!stream_.Read(reinterpret_cast(blockTable_.get()), static_cast(fhdr.blockEntriesCount * sizeof(MpqBlockEntry)))) { error = "Failed to read block table"; goto on_error; } @@ -144,7 +142,7 @@ MpqWriter::MpqWriter(const char *path) std::memset(hashTable_.get(), 0xFF, HashEntriesCount * sizeof(MpqHashEntry)); if (fhdr.hashEntriesCount > 0) { - if (!stream_.Read(reinterpret_cast(hashTable_.get()), static_cast(fhdr.hashEntriesCount * sizeof(MpqHashEntry)))) { + if (!stream_.Read(reinterpret_cast(hashTable_.get()), static_cast(fhdr.hashEntriesCount * sizeof(MpqHashEntry)))) { error = "Failed to read hash entries"; goto on_error; } @@ -153,7 +151,7 @@ MpqWriter::MpqWriter(const char *path) } #ifndef CAN_SEEKP_BEYOND_EOF - if (!stream_.Seekp(0, std::ios::beg)) + if (!stream_.Seekp(0, SEEK_SET)) goto on_error; // Memorize stream begin, we'll need it for calculations later. @@ -178,7 +176,7 @@ MpqWriter::~MpqWriter() LogVerbose("Closing {}", name_); bool result = true; - if (!(stream_.Seekp(0, std::ios::beg) && WriteHeaderAndTables())) + if (!(stream_.Seekp(0, SEEK_SET) && WriteHeaderAndTables())) result = false; stream_.Close(); if (result && size_ != 0) { @@ -395,12 +393,12 @@ bool MpqWriter::WriteFileContents(const char *filename, const byte *fileData, si std::unique_ptr offsetTable { new uint32_t[numSectors + 1] }; #ifdef CAN_SEEKP_BEYOND_EOF - if (!stream_.Seekp(block->offset + offsetTableByteSize, std::ios::beg)) + if (!stream_.Seekp(block->offset + offsetTableByteSize, SEEK_SET)) return false; #else // Ensure we do not Seekp beyond EOF by filling the missing space. - std::streampos stream_end; - if (!stream_.Seekp(0, std::ios::end) || !stream_.Tellp(&stream_end)) + long stream_end; + if (!stream_.Seekp(0, SEEK_END) || !stream_.Tellp(&stream_end)) return false; const std::uintmax_t cur_size = stream_end - streamBegin_; if (cur_size < block->offset + offsetTableByteSize) { @@ -412,7 +410,7 @@ bool MpqWriter::WriteFileContents(const char *filename, const byte *fileData, si if (!stream_.Write(reinterpret_cast(offsetTable.get()), offsetTableByteSize)) return false; } else { - if (!stream_.Seekp(block->offset + offsetTableByteSize, std::ios::beg)) + if (!stream_.Seekp(block->offset + offsetTableByteSize, SEEK_SET)) return false; } #endif @@ -436,11 +434,11 @@ bool MpqWriter::WriteFileContents(const char *filename, const byte *fileData, si } offsetTable[numSectors] = SDL_SwapLE32(destSize); - if (!stream_.Seekp(block->offset, std::ios::beg)) + if (!stream_.Seekp(block->offset, SEEK_SET)) return false; if (!stream_.Write(reinterpret_cast(offsetTable.get()), offsetTableByteSize)) return false; - if (!stream_.Seekp(destSize - offsetTableByteSize, std::ios::cur)) + if (!stream_.Seekp(destSize - offsetTableByteSize, SEEK_CUR)) return false; if (destSize < block->packedSize) { diff --git a/Source/mpq/mpq_writer.hpp b/Source/mpq/mpq_writer.hpp index 7f3a41580..f707ad23e 100644 --- a/Source/mpq/mpq_writer.hpp +++ b/Source/mpq/mpq_writer.hpp @@ -67,7 +67,7 @@ private: #endif #ifndef CAN_SEEKP_BEYOND_EOF - std::streampos streamBegin_; + long streamBegin_; #endif }; diff --git a/Source/options.cpp b/Source/options.cpp index b1ec17bca..fe9090364 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -5,11 +5,10 @@ */ #include -#include +#include #include -#define SI_SUPPORT_IOSTREAMS #define SI_NO_CONVERSION #include @@ -92,11 +91,13 @@ CSimpleIni &GetIni() static bool isIniLoaded = false; if (!isIniLoaded) { auto path = GetIniPath(); - auto stream = CreateFileStream(path.c_str(), std::fstream::in | std::fstream::binary); + FILE *file = OpenFile(path.c_str(), "rb"); ini.SetSpaces(false); ini.SetMultiKey(); - if (stream) - ini.LoadData(*stream); + if (file != nullptr) { + ini.LoadFile(file); + std::fclose(file); + } isIniLoaded = true; } return ini; @@ -244,9 +245,14 @@ void SaveIni() } } #endif - auto iniPath = GetIniPath(); - auto stream = CreateFileStream(iniPath.c_str(), std::fstream::out | std::fstream::trunc | std::fstream::binary); - GetIni().Save(*stream, true); + const std::string iniPath = GetIniPath(); + FILE *file = OpenFile(iniPath.c_str(), "wb"); + if (file != nullptr) { + GetIni().SaveFile(file, true); + std::fclose(file); + } else { + LogError("Failed to write ini file to {}: {}", iniPath, std::strerror(errno)); + } IniChanged = false; } diff --git a/Source/pfile.cpp b/Source/pfile.cpp index c3cc09ae8..ee2a3a644 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -5,7 +5,6 @@ */ #include "pfile.h" -#include #include #include @@ -28,6 +27,7 @@ #include "utils/stdcompat/abs.hpp" #include "utils/stdcompat/string_view.hpp" #include "utils/str_cat.hpp" +#include "utils/str_split.hpp" #include "utils/utf8.hpp" #ifdef UNPACKED_SAVES @@ -162,14 +162,8 @@ SaveWriter GetStashWriter() #ifndef DISABLE_DEMOMODE void CopySaveFile(uint32_t saveNum, std::string targetPath) { - std::string savePath = GetSavePath(saveNum); - auto saveStream = CreateFileStream(savePath.c_str(), std::fstream::in | std::fstream::binary); - if (!saveStream) - return; - auto targetStream = CreateFileStream(targetPath.c_str(), std::fstream::out | std::fstream::binary | std::fstream::trunc); - if (!targetStream) - return; - *targetStream << saveStream->rdbuf(); + const std::string savePath = GetSavePath(saveNum); + CopyFileOverwrite(savePath.c_str(), targetPath.c_str()); } #endif @@ -236,15 +230,6 @@ std::optional CreateSaveReader(std::string &&path) } #ifndef DISABLE_DEMOMODE -class MemoryBuffer : public std::basic_streambuf { -public: - MemoryBuffer(char *data, size_t byteCount) - { - setg(data, data, data + byteCount); - setp(data, data + byteCount); - } -}; - struct CompareInfo { std::unique_ptr &data; size_t currentPosition; @@ -269,11 +254,11 @@ struct CompareCounter { } }; -inline bool string_ends_with(std::string const &value, std::string const &ending) +inline bool string_ends_with(string_view value, string_view suffix) { - if (ending.size() > value.size()) + if (suffix.size() > value.size()) return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin()); } void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInfo &compareInfoReference, CompareInfo &compareInfoActual, std::unordered_map &foundDiffs) @@ -290,9 +275,7 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf size_t readBytes = SDL_RWsize(handle); std::unique_ptr memoryMapFileData { new byte[readBytes] }; SDL_RWread(handle, memoryMapFileData.get(), readBytes, 1); - - MemoryBuffer buffer(reinterpret_cast(memoryMapFileData.get()), readBytes); - std::istream reader(&buffer); + const string_view buffer(reinterpret_cast(memoryMapFileData.get()), readBytes); std::unordered_map counter; @@ -341,15 +324,18 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf return value; }; - std::string line; - while (std::getline(reader, line)) { - if (line.size() > 0 && line[line.size() - 1] == '\r') - line.resize(line.size() - 1); - if (line.size() == 0) + for (string_view line : SplitByChar(buffer, '\n')) { + if (!line.empty() && line.back() == '\r') + line.remove_suffix(1); + if (line.empty()) + continue; + const auto tokens = SplitByChar(line, ' '); + auto it = tokens.begin(); + const auto end = tokens.end(); + if (it == end) continue; - std::stringstream lineStream(line); - std::string command; - std::getline(lineStream, command, ' '); + + string_view command = *it; bool dataExistsReference = compareInfoReference.dataExists; bool dataExistsActual = compareInfoActual.dataExists; @@ -357,12 +343,12 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf if (string_ends_with(command, "_HF")) { if (!gbIsHellfire) continue; - command.resize(command.size() - 3); + command.remove_suffix(3); } if (string_ends_with(command, "_DA")) { if (gbIsHellfire) continue; - command.resize(command.size() - 3); + command.remove_suffix(3); } if (string_ends_with(command, "_DL")) { if (compareInfoReference.isTownLevel && compareInfoActual.isTownLevel) @@ -371,14 +357,11 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf compareInfoReference.dataExists = false; if (compareInfoActual.isTownLevel) compareInfoActual.dataExists = false; - command.resize(command.size() - 3); + command.remove_suffix(3); } if (command == "R" || command == "LT" || command == "LC" || command == "LC_LE") { - std::string bitsAsString; - std::getline(lineStream, bitsAsString, ' '); - std::string comment; - std::getline(lineStream, comment); - + const auto bitsAsString = std::string(*++it); + const auto comment = std::string(*++it); size_t bytes = static_cast(std::stoi(bitsAsString) / 8); if (command == "LT") { @@ -392,7 +375,7 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf int32_t valueReference = read32BitInt(compareInfoReference, command == "LC_LE"); int32_t valueActual = read32BitInt(compareInfoActual, command == "LC_LE"); assert(sizeof(valueReference) == bytes); - counter.insert_or_assign(comment, CompareCounter { valueReference, valueActual }); + counter.insert_or_assign(std::string(comment), CompareCounter { valueReference, valueActual }); } if (!compareBytes(bytes)) { @@ -400,12 +383,9 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf addDiff(diffKey); } } else if (command == "M") { - std::string countAsString; - std::getline(lineStream, countAsString, ' '); - std::string bitsAsString; - std::getline(lineStream, bitsAsString, ' '); - std::string comment; - std::getline(lineStream, comment); + const auto countAsString = std::string(*++it); + const auto bitsAsString = std::string(*++it); + string_view comment = *++it; CompareCounter count = getCounter(countAsString); size_t bytes = static_cast(std::stoi(bitsAsString) / 8); @@ -417,12 +397,9 @@ void CreateDetailDiffs(string_view prefix, string_view memoryMapFile, CompareInf } } } else if (command == "C") { - std::string countAsString; - std::getline(lineStream, countAsString, ' '); - std::string subMemoryMapFile; - std::getline(lineStream, subMemoryMapFile, ' '); - std::string comment; - std::getline(lineStream, comment); + const auto countAsString = std::string(*++it); + auto subMemoryMapFile = std::string(*++it); + const auto comment = std::string(*++it); CompareCounter count = getCounter(countAsString); subMemoryMapFile.erase(std::remove(subMemoryMapFile.begin(), subMemoryMapFile.end(), '\r'), subMemoryMapFile.end()); diff --git a/Source/utils/endian_stream.hpp b/Source/utils/endian_stream.hpp index 48b774f62..6dc33c2bc 100644 --- a/Source/utils/endian_stream.hpp +++ b/Source/utils/endian_stream.hpp @@ -1,41 +1,40 @@ #pragma once +#include #include -#include - #include "utils/endian.hpp" namespace devilution { template -T ReadByte(std::ifstream &stream) +T ReadByte(FILE *stream) { static_assert(sizeof(T) == 1, "invalid argument"); char buf; - stream.read(&buf, 1); + std::fread(&buf, sizeof(buf), 1, stream); return static_cast(buf); } template -T ReadLE16(std::ifstream &stream) +T ReadLE16(FILE *stream) { static_assert(sizeof(T) == 2, "invalid argument"); char buf[2]; - stream.read(buf, 2); + std::fread(buf, sizeof(buf), 1, stream); return static_cast(LoadLE16(buf)); } template -T ReadLE32(std::ifstream &stream) +T ReadLE32(FILE *stream) { static_assert(sizeof(T) == 4, "invalid argument"); char buf[4]; - stream.read(buf, 4); + std::fread(buf, sizeof(buf), 1, stream); return static_cast(LoadLE32(buf)); } -inline float ReadLEFloat(std::ifstream &stream) +inline float ReadLEFloat(FILE *stream) { static_assert(sizeof(float) == sizeof(uint32_t), "invalid float size"); const uint32_t val = ReadLE32(stream); @@ -44,33 +43,33 @@ inline float ReadLEFloat(std::ifstream &stream) return result; } -inline void WriteByte(std::ofstream &out, uint8_t val) +inline void WriteByte(FILE *out, uint8_t val) { - out.write(reinterpret_cast(&val), 1); + std::fwrite(&val, sizeof(val), 1, out); } -inline void WriteLE16(std::ofstream &out, uint16_t val) +inline void WriteLE16(FILE *out, uint16_t val) { char data[2]; WriteLE16(data, val); - out.write(data, 2); + std::fwrite(data, sizeof(data), 1, out); } -inline void WriteLE32(std::ofstream &out, uint32_t val) +inline void WriteLE32(FILE *out, uint32_t val) { char data[4]; WriteLE32(data, val); - out.write(data, 4); + std::fwrite(data, sizeof(data), 1, out); } -inline void WriteLEFloat(std::ofstream &out, float val) +inline void WriteLEFloat(FILE *out, float val) { static_assert(sizeof(float) == sizeof(uint32_t), "invalid float size"); uint32_t intVal; memcpy(&intVal, &val, sizeof(uint32_t)); char data[4]; WriteLE32(data, intVal); - out.write(data, 4); + std::fwrite(data, sizeof(data), 1, out); } } // namespace devilution diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index 72a4c531f..e803bb3b2 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -1,5 +1,8 @@ #include "utils/file_util.h" +#include +#include + #include #include @@ -7,6 +10,7 @@ #include "utils/log.hpp" #include "utils/stdcompat/filesystem.hpp" +#include "utils/stdcompat/string_view.hpp" #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -30,6 +34,10 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + namespace devilution { #if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) @@ -203,12 +211,61 @@ void RenameFile(const char *from, const char *to) ::MoveFileW(&fromUtf16[0], &toUtf16[0]); #elif defined(DVL_HAS_FILESYSTEM) std::error_code ec; - return std::filesystem::rename(from, to); + std::filesystem::rename(from, to, ec); #else ::rename(from, to); #endif } +void CopyFileOverwrite(const char *from, const char *to) +{ +#if defined(NXDK) + if (!::CopyFile(from, to, /*bFailIfExists=*/false)) { + LogError("Failed to copy {} to {}", from, to); + } +#elif defined(_WIN64) || defined(_WIN32) + const auto fromUtf16 = ToWideChar(from); + const auto toUtf16 = ToWideChar(to); + if (fromUtf16 == nullptr || toUtf16 == nullptr) { + LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError()); + return; + } + if (!::CopyFileW(&fromUtf16[0], &toUtf16[0], /*bFailIfExists=*/false)) { + LogError("Failed to copy {} to {}", from, to); + } +#elif defined(__APPLE__) + ::copyfile(from, to, nullptr, COPYFILE_ALL); +#elif defined(DVL_HAS_FILESYSTEM) + std::error_code error; + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, error); + if (error) { + LogError("Failed to copy {} to {}: {}", from, to, error.message()); + } +#else + FILE *infile = OpenFile(from, "rb"); + if (infile == nullptr) { + LogError("Failed to open {} for reading: {}", from, std::strerror(errno)); + return; + } + FILE *outfile = OpenFile(to, "wb"); + if (outfile == nullptr) { + LogError("Failed to open {} for writing: {}", to, std::strerror(errno)); + std::fclose(infile); + return; + } + char buffer[4096]; + size_t numRead; + while ((numRead = std::fread(buffer, sizeof(char), sizeof(buffer), infile)) > 0) { + if (std::fwrite(buffer, sizeof(char), numRead, outfile) != numRead) { + LogError("Write failed {}: {}", to, std::strerror(errno)); + break; + } + } + std::fclose(infile); + std::fclose(outfile); +#endif +} + void RemoveFile(const char *path) { #if defined(NXDK) @@ -235,20 +292,6 @@ void RemoveFile(const char *path) #endif } -std::optional CreateFileStream(const char *path, std::ios::openmode mode) -{ -#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) - const auto pathUtf16 = ToWideChar(path); - if (pathUtf16 == nullptr) { - LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError()); - return {}; - } - return { std::fstream(pathUtf16.get(), mode) }; -#else - return { std::fstream(path, mode) }; -#endif -} - FILE *OpenFile(const char *path, const char *mode) { #if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) diff --git a/Source/utils/file_util.h b/Source/utils/file_util.h index d25e14e51..9de80cae4 100644 --- a/Source/utils/file_util.h +++ b/Source/utils/file_util.h @@ -2,11 +2,9 @@ #include #include -#include #include #include -#include "utils/stdcompat/optional.hpp" #include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -22,8 +20,8 @@ bool FileExistsAndIsWriteable(const char *path); bool GetFileSize(const char *path, std::uintmax_t *size); bool ResizeFile(const char *path, std::uintmax_t size); void RenameFile(const char *from, const char *to); +void CopyFileOverwrite(const char *from, const char *to); void RemoveFile(const char *path); -std::optional CreateFileStream(const char *path, std::ios::openmode mode); FILE *OpenFile(const char *path, const char *mode); #if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) diff --git a/Source/utils/logged_fstream.cpp b/Source/utils/logged_fstream.cpp index 93541fcc5..140c8d14f 100644 --- a/Source/utils/logged_fstream.cpp +++ b/Source/utils/logged_fstream.cpp @@ -2,38 +2,18 @@ namespace devilution { -const char *LoggedFStream::DirToString(std::ios::seekdir dir) +const char *LoggedFStream::DirToString(int dir) { switch (dir) { - case std::ios::beg: - return "std::ios::beg"; - case std::ios::end: - return "std::ios::end"; - case std::ios::cur: - return "std::ios::cur"; + case SEEK_SET: + return "SEEK_SET"; + case SEEK_END: + return "SEEK_END"; + case SEEK_CUR: + return "SEEK_CUR"; default: return "invalid"; } } -std::string LoggedFStream::OpenModeToString(std::ios::openmode mode) -{ - std::string result; - if ((mode & std::ios::app) != 0) - result.append("std::ios::app | "); - if ((mode & std::ios::ate) != 0) - result.append("std::ios::ate | "); - if ((mode & std::ios::binary) != 0) - result.append("std::ios::binary | "); - if ((mode & std::ios::in) != 0) - result.append("std::ios::in | "); - if ((mode & std::ios::out) != 0) - result.append("std::ios::out | "); - if ((mode & std::ios::trunc) != 0) - result.append("std::ios::trunc | "); - if (!result.empty()) - result.resize(result.size() - 3); - return result; -} - } // namespace devilution diff --git a/Source/utils/logged_fstream.hpp b/Source/utils/logged_fstream.hpp index 8a01729b4..d45f71038 100644 --- a/Source/utils/logged_fstream.hpp +++ b/Source/utils/logged_fstream.hpp @@ -1,8 +1,8 @@ #pragma once #include +#include #include -#include #include #include "utils/file_util.h" @@ -11,63 +11,60 @@ namespace devilution { -// A wrapper around `std::fstream` that logs errors. +// A wrapper around `FILE *` that logs errors. struct LoggedFStream { public: - bool Open(const char *path, std::ios::openmode mode) + bool Open(const char *path, const char *mode) { - s_ = CreateFileStream(path, mode); - return CheckError("new std::fstream(\"{}\", {})", path, OpenModeToString(mode).c_str()); + s_ = OpenFile(path, mode); + return CheckError("fopen(\"{}\", \"{}\")", path, mode); } void Close() { - s_ = std::nullopt; + if (s_ != nullptr) { + std::fclose(s_); + s_ = nullptr; + } } [[nodiscard]] bool IsOpen() const { - return s_ != std::nullopt; - } - - bool Seekp(std::streampos pos) - { - s_->seekp(pos); - return CheckError("seekp({})", pos); + return s_ != nullptr; } - bool Seekp(std::streamoff pos, std::ios::seekdir dir) + bool Seekp(long pos, int dir = SEEK_SET) { - s_->seekp(pos, dir); - return CheckError("seekp({}, {})", pos, DirToString(dir)); + std::fseek(s_, pos, dir); + return CheckError("fseek({}, {})", pos, DirToString(dir)); } - bool Tellp(std::streampos *result) + bool Tellp(long *result) { - *result = s_->tellp(); - return CheckError("tellp() = {}", *result); + *result = std::ftell(s_); + return CheckError("ftell() = {}", *result); } - bool Write(const char *data, std::streamsize size) + bool Write(const char *data, size_t size) { - s_->write(data, size); - return CheckError("write(data, {})", size); + std::fwrite(data, size, 1, s_); + return CheckError("fwrite(data, {})", size); } - bool Read(char *out, std::streamsize size) + bool Read(char *out, size_t size) { - s_->read(out, size); - return CheckError("read(out, {})", size); + std::fread(out, size, 1, s_); + return CheckError("fread(out, {})", size); } private: - static const char *DirToString(std::ios::seekdir dir); - static std::string OpenModeToString(std::ios::openmode mode); + static const char *DirToString(int dir); template bool CheckError(const char *fmt, PrintFArgs... args) { - if (s_->fail()) { + const bool ok = s_ != nullptr && std::ferror(s_) == 0; + if (!ok) { std::string fmtWithError = fmt; fmtWithError.append(": failed with \"{}\""); const char *errorMessage = std::strerror(errno); @@ -77,10 +74,10 @@ private: } else { LogVerbose(LogCategory::System, fmt, args...); } - return !s_->fail(); + return ok; } - std::optional s_; + FILE *s_ = nullptr; }; } // namespace devilution diff --git a/test/file_util_test.cpp b/test/file_util_test.cpp index 381657367..44143263f 100644 --- a/test/file_util_test.cpp +++ b/test/file_util_test.cpp @@ -1,7 +1,6 @@ #include -#include -#include +#include #include "utils/file_util.h" @@ -11,13 +10,15 @@ namespace { void WriteDummyFile(const char *name, std::uintmax_t size) { - std::ofstream test_file(name, std::ios::out | std::ios::trunc | std::ios::binary); - ASSERT_FALSE(test_file.fail()); + std::printf("Writing test file %s\n", name); + FILE *test_file = std::fopen(name, "wb"); + ASSERT_TRUE(test_file != nullptr); const char c = '\0'; for (std::uintmax_t i = 0; i < size; ++i) { - test_file.write(&c, 1); - ASSERT_FALSE(test_file.fail()); + std::fwrite(&c, sizeof(c), 1, test_file); + ASSERT_EQ(std::ferror(test_file), 0); } + std::fclose(test_file); } std::string GetTmpPathName(const char *suffix = ".tmp") @@ -34,7 +35,6 @@ std::string GetTmpPathName(const char *suffix = ".tmp") TEST(FileUtil, GetFileSize) { const std::string path = GetTmpPathName(); - std::cout << path << std::endl; WriteDummyFile(path.c_str(), 42); std::uintmax_t result; ASSERT_TRUE(GetFileSize(path.c_str(), &result)); @@ -45,7 +45,6 @@ TEST(FileUtil, FileExists) { EXPECT_FALSE(FileExists("this-file-should-not-exist")); const std::string path = GetTmpPathName(); - std::cout << path << std::endl; WriteDummyFile(path.c_str(), 42); EXPECT_TRUE(FileExists(path.c_str())); } @@ -53,7 +52,6 @@ TEST(FileUtil, FileExists) TEST(FileUtil, ResizeFile) { const std::string path = GetTmpPathName(); - std::cout << path << std::endl; WriteDummyFile(path.c_str(), 42); std::uintmax_t size; ASSERT_TRUE(GetFileSize(path.c_str(), &size)); diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 129a51465..f81f8d978 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -12,6 +11,7 @@ #include "loadsave.h" #include "pack.h" #include "pfile.h" +#include "utils/file_util.h" #include "utils/paths.h" namespace devilution { @@ -389,9 +389,17 @@ TEST(Writehero, pfile_write_hero) AssertPlayer(Players[0]); pfile_write_hero(); - std::ifstream f("multi_0.sv", std::ios::binary); + const char *path = "multi_0.sv"; + uintmax_t size; + ASSERT_TRUE(GetFileSize(path, &size)); + FILE *f = std::fopen(path, "rb"); + ASSERT_TRUE(f != nullptr); + std::unique_ptr data { new char[size] }; + ASSERT_EQ(std::fread(data.get(), size, 1, f), 1); + std::fclose(f); + std::vector s(picosha2::k_digest_size); - picosha2::hash256(f, s.begin(), s.end()); + picosha2::hash256(data.get(), data.get() + size, s.begin(), s.end()); EXPECT_EQ(picosha2::bytes_to_hex_string(s.begin(), s.end()), "a79367caae6192d54703168d82e0316aa289b2a33251255fad8abe34889c1d3a"); }