diff --git a/src/Server/CommandList.hpp b/src/Server/CommandList.hpp index b18b39e..c6ce284 100644 --- a/src/Server/CommandList.hpp +++ b/src/Server/CommandList.hpp @@ -39,6 +39,8 @@ enum ClientCommands : MessageCommandType { CLIENT_REMOVE_BOOKMARK }; -// NOTE: Change these values whenever savefile is incompatible with previous version +// NOTE: Do not change savefile header length #define SAVEFILE_HEADER_LEN 12 -#define SAVEFILE_HEADER "INFPNT000001" +// NOTE: Change these values whenever savefile is incompatible with previous version +#define SAVEFILE_HEADER "INFPNT000002" +#define SAVEFILE_HEADER_V1 "INFPNT000001" diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index b0e848a..7dee24a 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -342,13 +342,8 @@ void Toolbar::update() { void Toolbar::save_func() { if(main.world->filePath == std::filesystem::path()) save_as_func(); - else { - #ifdef __EMSCRIPTEN__ - main.world->download_file(main.world->filePath.string()); - #else - main.world->save_to_file(main.world->filePath.string()); - #endif - } + else + main.world->save_to_file(main.world->filePath.string()); } void Toolbar::save_as_func() { @@ -1390,7 +1385,7 @@ void Toolbar::options_menu() { gui.input_text_field("file name", "File Name", &downloadNameSet); gui.left_to_right_line_layout([&]() { if(gui.text_button_wide("download save button", "Save")) { - main.world->download_file(downloadNameSet); + main.world->save_to_file(downloadNameSet); optionsMenuOpen = false; } if(gui.text_button_wide("cancel", "Cancel")) diff --git a/src/World.cpp b/src/World.cpp index e1640e2..2a986f9 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #ifdef __EMSCRIPTEN__ #include @@ -223,16 +224,27 @@ void World::start_hosting(const std::string& initNetSource, const std::string& s clientStillConnecting = true; } -void World::download_file(const std::filesystem::path& fileName) { +void World::save_to_file(const std::filesystem::path& fileName) { try { filePath = force_extension_on_path(fileName, FILE_EXTENSION); std::stringstream f; f.write(SAVEFILE_HEADER, SAVEFILE_HEADER_LEN); - cereal::PortableBinaryOutputArchive a(f); - SaveLoadFileOp op{.world = this}; - a(op); - Logger::get().log("USERINFO", "File saved"); + + { + std::stringstream fWorldDataToCompress; + { + cereal::PortableBinaryOutputArchive a(fWorldDataToCompress); + SaveLoadFileOp op{.world = this}; + a(op); + } + + std::vector compressedData(ZSTD_compressBound(fWorldDataToCompress.view().size())); + size_t trueCompressedSize = ZSTD_compress(compressedData.data(), compressedData.size(), fWorldDataToCompress.view().data(), fWorldDataToCompress.view().size(), ZSTD_CLEVEL_DEFAULT); + std::string_view compressedF(compressedData.data(), trueCompressedSize); + + f << compressedF; + } set_name(filePath.stem().string()); @@ -242,26 +254,13 @@ void World::download_file(const std::filesystem::path& fileName) { "application/octet-stream", f.view() ); + #else + std::ofstream fi(filePath, std::ios::out | std::ios::binary); + fi << f.view(); + fi.close(); #endif - } - catch(const std::exception& e) { - Logger::get().log("WORLDFATAL", std::string("Save error: ") + e.what()); - } -} -void World::save_to_file(const std::filesystem::path& fileName) { - try { - filePath = force_extension_on_path(fileName, FILE_EXTENSION); - - std::ofstream f(filePath, std::ios::out | std::ios::binary); - f.write(SAVEFILE_HEADER, SAVEFILE_HEADER_LEN); - cereal::PortableBinaryOutputArchive a(f); - SaveLoadFileOp op{.world = this}; - a(op); - f.close(); Logger::get().log("USERINFO", "File saved"); - - set_name(filePath.stem().string()); } catch(const std::exception& e) { Logger::get().log("WORLDFATAL", std::string("Save error: ") + e.what()); @@ -271,34 +270,38 @@ void World::save_to_file(const std::filesystem::path& fileName) { void World::load_from_file(const std::filesystem::path& fileName, std::string_view buffer) { filePath = force_extension_on_path(fileName, FILE_EXTENSION); + std::string byteDataFromFile; if(buffer.empty()) { - std::ifstream f(filePath, std::ios::in | std::ios::binary); - std::string fileHeader; - fileHeader.resize(SAVEFILE_HEADER_LEN); - f.read(fileHeader.data(), SAVEFILE_HEADER_LEN); - if(fileHeader != std::string(SAVEFILE_HEADER)) - throw std::runtime_error("[World::load_from_file] File does not have correct header"); - - cereal::PortableBinaryInputArchive a(f); - SaveLoadFileOp op{.world = this}; - a(op); - f.close(); - nextClientID = std::max(rMan.get_max_id(ownID), drawProg.get_max_id(ownID)); + byteDataFromFile = read_file_to_string(fileName); + buffer = byteDataFromFile; } - else { - ByteMemStream f((char*)buffer.data(), buffer.size()); - std::string fileHeader; - fileHeader.resize(SAVEFILE_HEADER_LEN); - f.read(fileHeader.data(), SAVEFILE_HEADER_LEN); - if(fileHeader != std::string(SAVEFILE_HEADER)) - throw std::runtime_error("[World::load_from_file] File does not have correct header"); - cereal::PortableBinaryInputArchive a(f); - SaveLoadFileOp op{.world = this}; - a(op); - nextClientID = std::max(rMan.get_max_id(ownID), drawProg.get_max_id(ownID)); + if(buffer.size() < SAVEFILE_HEADER_LEN) + throw std::runtime_error("[World::load_from_file] File too small"); + + std::string_view fileHeader = buffer.substr(0, SAVEFILE_HEADER_LEN); + std::string_view uncompressedDataView; + std::vector uncompressedDataVector; + + if(fileHeader == SAVEFILE_HEADER) { + uncompressedDataVector.resize(ZSTD_getFrameContentSize(buffer.data() + SAVEFILE_HEADER_LEN, buffer.size() - SAVEFILE_HEADER_LEN)); + size_t trueUncompressedSize = ZSTD_decompress(uncompressedDataVector.data(), uncompressedDataVector.size(), buffer.data() + SAVEFILE_HEADER_LEN, buffer.size() - SAVEFILE_HEADER_LEN); + uncompressedDataView = std::string_view(uncompressedDataVector.data(), trueUncompressedSize); } + else if(fileHeader == SAVEFILE_HEADER_V1) + uncompressedDataView = std::string_view(buffer.data() + SAVEFILE_HEADER_LEN, buffer.size() - SAVEFILE_HEADER_LEN); + else + throw std::runtime_error("[World::load_from_file] File does not have correct header"); + + + + ByteMemStream f((char*)uncompressedDataView.data(), uncompressedDataView.size()); + + cereal::PortableBinaryInputArchive a(f); + SaveLoadFileOp op{.world = this}; + a(op); + nextClientID = std::max(rMan.get_max_id(ownID), drawProg.get_max_id(ownID)); Logger::get().log("USERINFO", "File loaded"); set_name(filePath.stem().string()); diff --git a/src/World.hpp b/src/World.hpp index 69d8e2c..66f361f 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -74,7 +74,6 @@ class World { ConnectionType conType; bool clientStillConnecting = false; - void download_file(const std::filesystem::path& fileName); void save_to_file(const std::filesystem::path& fileName); void load_from_file(const std::filesystem::path& fileName, std::string_view buffer);