// Copyright 2021 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/NetPlayCommon.h" #include #include #include #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/MsgHandler.h" #include "Common/SFMLHelper.h" namespace NetPlay { constexpr u32 LZO_IN_LEN = 1024 * 64; constexpr u32 LZO_OUT_LEN = LZO_IN_LEN + (LZO_IN_LEN / 16) + 64 + 3; bool CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet) { File::IOFile file(file_path, "rb"); if (!file) { PanicAlertFmtT("Failed to open file \"{0}\".", file_path); return false; } const sf::Uint64 size = file.GetSize(); packet << size; if (size == 0) return true; std::vector in_buffer(LZO_IN_LEN); std::vector out_buffer(LZO_OUT_LEN); std::vector wrkmem(LZO1X_1_MEM_COMPRESS); lzo_uint i = 0; while (true) { lzo_uint32 cur_len = 0; // number of bytes to read lzo_uint out_len = 0; // number of bytes to write if ((i + LZO_IN_LEN) >= size) { cur_len = static_cast(size - i); } else { cur_len = LZO_IN_LEN; } if (cur_len <= 0) break; // EOF if (!file.ReadBytes(in_buffer.data(), cur_len)) { PanicAlertFmtT("Error reading file: {0}", file_path.c_str()); return false; } if (lzo1x_1_compress(in_buffer.data(), cur_len, out_buffer.data(), &out_len, wrkmem.data()) != LZO_E_OK) { PanicAlertFmtT("Internal LZO Error - compression failed"); return false; } // The size of the data to write is 'out_len' packet << static_cast(out_len); for (size_t j = 0; j < out_len; j++) { packet << out_buffer[j]; } if (cur_len != LZO_IN_LEN) break; i += cur_len; } // Mark end of data packet << static_cast(0); return true; } static bool CompressFolderIntoPacketInternal(const File::FSTEntry& folder, sf::Packet& packet) { const sf::Uint64 size = folder.children.size(); packet << size; for (const auto& child : folder.children) { const bool is_folder = child.isDirectory; packet << child.virtualName; packet << is_folder; const bool success = is_folder ? CompressFolderIntoPacketInternal(child, packet) : CompressFileIntoPacket(child.physicalName, packet); if (!success) return false; } return true; } bool CompressFolderIntoPacket(const std::string& folder_path, sf::Packet& packet) { if (!File::IsDirectory(folder_path)) { packet << false; return true; } packet << true; return CompressFolderIntoPacketInternal(File::ScanDirectoryTree(folder_path, true), packet); } bool CompressBufferIntoPacket(const std::vector& in_buffer, sf::Packet& packet) { const sf::Uint64 size = in_buffer.size(); packet << size; if (size == 0) return true; std::vector out_buffer(LZO_OUT_LEN); std::vector wrkmem(LZO1X_1_MEM_COMPRESS); lzo_uint i = 0; while (true) { lzo_uint32 cur_len = 0; // number of bytes to read lzo_uint out_len = 0; // number of bytes to write if ((i + LZO_IN_LEN) >= size) { cur_len = static_cast(size - i); } else { cur_len = LZO_IN_LEN; } if (cur_len <= 0) break; // end of buffer if (lzo1x_1_compress(&in_buffer[i], cur_len, out_buffer.data(), &out_len, wrkmem.data()) != LZO_E_OK) { PanicAlertFmtT("Internal LZO Error - compression failed"); return false; } // The size of the data to write is 'out_len' packet << static_cast(out_len); for (size_t j = 0; j < out_len; j++) { packet << out_buffer[j]; } if (cur_len != LZO_IN_LEN) break; i += cur_len; } // Mark end of data packet << static_cast(0); return true; } bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path) { u64 file_size = Common::PacketReadU64(packet); if (file_size == 0) return true; File::IOFile file(file_path, "wb"); if (!file) { PanicAlertFmtT("Failed to open file \"{0}\". Verify your write permissions.", file_path); return false; } std::vector in_buffer(LZO_OUT_LEN); std::vector out_buffer(LZO_IN_LEN); while (true) { u32 cur_len = 0; // number of bytes to read lzo_uint new_len = 0; // number of bytes to write packet >> cur_len; if (!cur_len) break; // We reached the end of the data stream for (size_t j = 0; j < cur_len; j++) { packet >> in_buffer[j]; } if (lzo1x_decompress(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) != LZO_E_OK) { PanicAlertFmtT("Internal LZO Error - decompression failed"); return false; } if (!file.WriteBytes(out_buffer.data(), new_len)) { PanicAlertFmtT("Error writing file: {0}", file_path); return false; } } return true; } static bool DecompressPacketIntoFolderInternal(sf::Packet& packet, const std::string& folder_path) { if (!File::CreateFullPath(folder_path + "/")) return false; sf::Uint64 size; packet >> size; for (size_t i = 0; i < size; ++i) { std::string name; packet >> name; if (name.find('/') != std::string::npos) return false; #ifdef _WIN32 if (name.find('\\') != std::string::npos) return false; #endif if (std::all_of(name.begin(), name.end(), [](char c) { return c == '.'; })) return false; bool is_folder; packet >> is_folder; std::string path = fmt::format("{}/{}", folder_path, name); const bool success = is_folder ? DecompressPacketIntoFolderInternal(packet, path) : DecompressPacketIntoFile(packet, path); if (!success) return false; } return true; } bool DecompressPacketIntoFolder(sf::Packet& packet, const std::string& folder_path) { bool folder_existed; packet >> folder_existed; if (!folder_existed) return true; return DecompressPacketIntoFolderInternal(packet, folder_path); } std::optional> DecompressPacketIntoBuffer(sf::Packet& packet) { u64 size = Common::PacketReadU64(packet); std::vector out_buffer(size); if (size == 0) return out_buffer; std::vector in_buffer(LZO_OUT_LEN); lzo_uint i = 0; while (true) { u32 cur_len = 0; // number of bytes to read lzo_uint new_len = 0; // number of bytes to write packet >> cur_len; if (!cur_len) break; // We reached the end of the data stream for (size_t j = 0; j < cur_len; j++) { packet >> in_buffer[j]; } if (lzo1x_decompress(in_buffer.data(), cur_len, &out_buffer[i], &new_len, nullptr) != LZO_E_OK) { PanicAlertFmtT("Internal LZO Error - decompression failed"); return {}; } i += new_len; } return out_buffer; } } // namespace NetPlay