diff --git a/src/engine/h2d_file.cpp b/src/engine/h2d_file.cpp index 6cb58a766e2..e2c4d44d7af 100644 --- a/src/engine/h2d_file.cpp +++ b/src/engine/h2d_file.cpp @@ -142,7 +142,7 @@ namespace fheroes2 } for ( const auto & data : _fileData ) { - fileStream.putRaw( reinterpret_cast( data.second.data() ), data.second.size() ); + fileStream.putRaw( data.second.data(), data.second.size() ); } return true; @@ -213,8 +213,8 @@ namespace fheroes2 stream.putLE32( static_cast( image.y() ) ); const size_t imageSize = static_cast( image.width() ) * static_cast( image.height() ); - stream.putRaw( reinterpret_cast( image.image() ), imageSize ); - stream.putRaw( reinterpret_cast( image.transform() ), imageSize ); + stream.putRaw( image.image(), imageSize ); + stream.putRaw( image.transform(), imageSize ); return writer.add( name, stream.getRaw() ); } diff --git a/src/engine/serialize.cpp b/src/engine/serialize.cpp index 54e703cab3a..05087967e81 100644 --- a/src/engine/serialize.cpp +++ b/src/engine/serialize.cpp @@ -416,7 +416,7 @@ std::vector StreamBuf::getRaw( size_t sz ) return v; } -void StreamBuf::putRaw( const char * ptr, size_t sz ) +void StreamBuf::putRaw( const void * ptr, size_t sz ) { if ( sz == 0 ) { return; @@ -671,7 +671,7 @@ std::vector StreamFile::getRaw( const size_t size ) return v; } -void StreamFile::putRaw( const char * ptr, size_t sz ) +void StreamFile::putRaw( const void * ptr, size_t sz ) { if ( !_file ) { return; diff --git a/src/engine/serialize.h b/src/engine/serialize.h index 4ca9fb93c84..ec9bc5ce866 100644 --- a/src/engine/serialize.h +++ b/src/engine/serialize.h @@ -100,7 +100,7 @@ class StreamBase virtual void putLE16( uint16_t ) = 0; virtual std::vector getRaw( size_t = 0 /* all data */ ) = 0; - virtual void putRaw( const char *, size_t ) = 0; + virtual void putRaw( const void *, size_t ) = 0; uint16_t get16(); uint32_t get32(); @@ -322,7 +322,7 @@ class StreamBuf : public StreamBase void putLE16( uint16_t v ) override; std::vector getRaw( size_t sz = 0 /* all data */ ) override; - void putRaw( const char * ptr, size_t sz ) override; + void putRaw( const void * ptr, size_t sz ) override; std::string toString( const size_t size = 0 ); @@ -383,7 +383,7 @@ class StreamFile : public StreamBase // 0 stands for full data. std::vector getRaw( const size_t size = 0 ) override; - void putRaw( const char *, size_t ) override; + void putRaw( const void * ptr, size_t sz ) override; std::string toString( const size_t size = 0 ); diff --git a/src/engine/zzlib.cpp b/src/engine/zzlib.cpp index d103b5cc484..35f7679e90c 100644 --- a/src/engine/zzlib.cpp +++ b/src/engine/zzlib.cpp @@ -26,18 +26,21 @@ #include #include #include -#include #include #include #include "logging.h" +#include "serialize.h" namespace { constexpr uint16_t FORMAT_VERSION_0 = 0; +} - std::vector zlibDecompress( const uint8_t * src, const size_t srcSize, size_t realSize = 0 ) +namespace Compression +{ + std::vector decompressData( const uint8_t * src, const size_t srcSize, size_t realSize /* = 0 */ ) { if ( src == nullptr || srcSize == 0 ) { return {}; @@ -100,7 +103,7 @@ namespace return res; } - std::vector zlibCompress( const uint8_t * src, const size_t srcSize ) + std::vector compressData( const uint8_t * src, const size_t srcSize ) { if ( src == nullptr || srcSize == 0 ) { return {}; @@ -131,94 +134,94 @@ namespace return res; } -} - -bool ZStreamBuf::read( const std::string & fn, const size_t offset /* = 0 */ ) -{ - StreamFile sf; - sf.setbigendian( true ); - - if ( !sf.open( fn, "rb" ) ) { - return false; - } - if ( offset ) { - sf.seek( offset ); - } + bool readFile( StreamBuf & output, const std::string & fn, const size_t offset /* = 0 */ ) + { + StreamFile sf; + sf.setbigendian( true ); - const uint32_t rawSize = sf.get32(); - const uint32_t zipSize = sf.get32(); - if ( zipSize == 0 ) { - return false; - } + if ( !sf.open( fn, "rb" ) ) { + return false; + } - const uint16_t version = sf.get16(); - if ( version != FORMAT_VERSION_0 ) { - return false; - } + if ( offset ) { + sf.seek( offset ); + } - sf.skip( 2 ); // Unused bytes + const uint32_t rawSize = sf.get32(); + const uint32_t zipSize = sf.get32(); + if ( zipSize == 0 ) { + return false; + } - const std::vector zip = sf.getRaw( zipSize ); - const std::vector raw = zlibDecompress( zip.data(), zip.size(), rawSize ); - if ( raw.size() != rawSize ) { - return false; - } + const uint16_t version = sf.get16(); + if ( version != FORMAT_VERSION_0 ) { + return false; + } - putRaw( reinterpret_cast( raw.data() ), raw.size() ); + sf.skip( 2 ); // Unused bytes - return !fail(); -} + const std::vector zip = sf.getRaw( zipSize ); + const std::vector raw = decompressData( zip.data(), zip.size(), rawSize ); + if ( raw.size() != rawSize ) { + return false; + } -bool ZStreamBuf::write( const std::string & fn, const bool append /* = false */ ) -{ - StreamFile sf; - sf.setbigendian( true ); + output.putRaw( raw.data(), raw.size() ); - if ( !sf.open( fn, append ? "ab" : "wb" ) ) { - return false; + return !output.fail(); } - const std::vector zip = zlibCompress( data(), size() ); - if ( zip.empty() ) { - return false; - } + bool writeFile( StreamBuf & input, const std::string & fn, const bool append /* = false */ ) + { + StreamFile sf; + sf.setbigendian( true ); - sf.put32( static_cast( size() ) ); - sf.put32( static_cast( zip.size() ) ); - sf.put16( FORMAT_VERSION_0 ); - sf.put16( 0 ); // Unused bytes - sf.putRaw( reinterpret_cast( zip.data() ), zip.size() ); + if ( !sf.open( fn, append ? "ab" : "wb" ) ) { + return false; + } - return !sf.fail(); -} + const std::vector zip = compressData( input.data(), input.size() ); + if ( zip.empty() ) { + return false; + } -fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ) -{ - if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) { - return {}; - } + sf.put32( static_cast( input.size() ) ); + sf.put32( static_cast( zip.size() ) ); + sf.put16( FORMAT_VERSION_0 ); + sf.put16( 0 ); // Unused bytes + sf.putRaw( zip.data(), zip.size() ); - const std::vector & uncompressedData = zlibDecompress( imageData, imageSize ); - if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) { - return {}; + return !sf.fail(); } - const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size(); + fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ) + { + if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) { + return {}; + } - if ( static_cast( width * height ) != uncompressedSize ) { - return {}; - } + const std::vector & uncompressedData = decompressData( imageData, imageSize ); + if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) { + return {}; + } - fheroes2::Image out; - if ( !doubleLayer ) { - out._disableTransformLayer(); - } - out.resize( width, height ); + const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size(); - std::memcpy( out.image(), uncompressedData.data(), uncompressedSize ); - if ( doubleLayer ) { - std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize ); + if ( static_cast( width ) * height != uncompressedSize ) { + return {}; + } + + fheroes2::Image out; + if ( !doubleLayer ) { + out._disableTransformLayer(); + } + out.resize( width, height ); + + std::memcpy( out.image(), uncompressedData.data(), uncompressedSize ); + if ( doubleLayer ) { + std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize ); + } + return out; } - return out; } diff --git a/src/engine/zzlib.h b/src/engine/zzlib.h index 4b8a3ebb92e..93af925341b 100644 --- a/src/engine/zzlib.h +++ b/src/engine/zzlib.h @@ -27,32 +27,34 @@ #include #include #include +#include #include "image.h" -#include "serialize.h" -class ZStreamBuf : public StreamBuf -{ -public: - ZStreamBuf() = default; - - ZStreamBuf( const ZStreamBuf & ) = delete; +class StreamBuf; - ~ZStreamBuf() override = default; +namespace Compression +{ + // Zips the input data and returns the compressed data or an empty vector in case of an error. + std::vector compressData( const uint8_t * src, const size_t srcSize ); - ZStreamBuf & operator=( const ZStreamBuf & ) = delete; + // Unzips the input data and returns the uncompressed data or an empty vector in case of an error. + // The 'realSize' parameter represents the planned size of the decompressed data and is optional + // (it is only used to speed up the decompression process). If this parameter is omitted or set to + // zero, the size of the decompressed data will be determined automatically. + std::vector decompressData( const uint8_t * src, const size_t srcSize, size_t realSize = 0 ); // Reads & unzips the zipped chunk from the specified file at the specified offset and appends // it to the end of the buffer. The current read position of the buffer does not change. Returns // true on success or false on error. - bool read( const std::string & fn, const size_t offset = 0 ); + bool readFile( StreamBuf & output, const std::string & fn, const size_t offset = 0 ); // Zips the contents of the buffer from the current read position to the end of the buffer and // writes (or appends) it to the specified file. The current read position of the buffer does // not change. Returns true on success and false on error. - bool write( const std::string & fn, const bool append = false ); -}; + bool writeFile( StreamBuf & input, const std::string & fn, const bool append = false ); -fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ); + fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ); +} #endif diff --git a/src/fheroes2/dialog/dialog_resolution.cpp b/src/fheroes2/dialog/dialog_resolution.cpp index 7fa585db7fb..da3b7b602df 100644 --- a/src/fheroes2/dialog/dialog_resolution.cpp +++ b/src/fheroes2/dialog/dialog_resolution.cpp @@ -325,7 +325,7 @@ namespace Dialog display.setResolution( selectedResolution ); #if !defined( MACOS_APP_BUNDLE ) - const fheroes2::Image & appIcon = CreateImageFromZlib( 32, 32, iconImage, sizeof( iconImage ), true ); + const fheroes2::Image & appIcon = Compression::CreateImageFromZlib( 32, 32, iconImage, sizeof( iconImage ), true ); fheroes2::engine().setIcon( appIcon ); #endif diff --git a/src/fheroes2/game/fheroes2.cpp b/src/fheroes2/game/fheroes2.cpp index bafab3f7e4b..ecae10bf90a 100644 --- a/src/fheroes2/game/fheroes2.cpp +++ b/src/fheroes2/game/fheroes2.cpp @@ -122,7 +122,7 @@ namespace void displayMissingResourceWindow() { fheroes2::Display & display = fheroes2::Display::instance(); - const fheroes2::Image & image = CreateImageFromZlib( 290, 190, errorMessage, sizeof( errorMessage ), false ); + const fheroes2::Image & image = Compression::CreateImageFromZlib( 290, 190, errorMessage, sizeof( errorMessage ), false ); display.fill( 0 ); fheroes2::Resize( image, display ); @@ -190,7 +190,7 @@ namespace fheroes2::cursor().registerUpdater( Cursor::Refresh ); #if !defined( MACOS_APP_BUNDLE ) - const fheroes2::Image & appIcon = CreateImageFromZlib( 32, 32, iconImage, sizeof( iconImage ), true ); + const fheroes2::Image & appIcon = Compression::CreateImageFromZlib( 32, 32, iconImage, sizeof( iconImage ), true ); fheroes2::engine().setIcon( appIcon ); #endif } diff --git a/src/fheroes2/game/game_io.cpp b/src/fheroes2/game/game_io.cpp index bf53a164101..b693a92c7c5 100644 --- a/src/fheroes2/game/game_io.cpp +++ b/src/fheroes2/game/game_io.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2023 * + * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * @@ -131,20 +131,20 @@ bool Game::Save( const std::string & filePath, const bool autoSave /* = false */ << HeaderSAV( conf.getCurrentMapInfo(), conf.GameType(), world.GetDay(), world.GetWeek(), world.GetMonth() ); fs.close(); - ZStreamBuf zb; - zb.setbigendian( true ); + StreamBuf sb; + sb.setbigendian( true ); // Game data in ZIP format - zb << World::Get() << Settings::Get() << GameOver::Result::Get(); + sb << World::Get() << Settings::Get() << GameOver::Result::Get(); if ( conf.isCampaignGameType() ) { - zb << Campaign::CampaignSaveData::Get(); + sb << Campaign::CampaignSaveData::Get(); } // End-of-data marker - zb << SAV2ID3; + sb << SAV2ID3; - if ( zb.fail() || !zb.write( filePath, true ) ) { + if ( sb.fail() || !Compression::writeFile( sb, filePath, true ) ) { return false; } @@ -221,10 +221,10 @@ fheroes2::GameMode Game::Load( const std::string & filePath ) return fheroes2::GameMode::CANCEL; } - ZStreamBuf zb; - zb.setbigendian( true ); + StreamBuf sb; + sb.setbigendian( true ); - if ( !zb.read( filePath, offset ) ) { + if ( !Compression::readFile( sb, filePath, offset ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Error uncompressing the file " << filePath ) showGenericErrorMessage(); @@ -240,13 +240,13 @@ fheroes2::GameMode Game::Load( const std::string & filePath ) return fheroes2::GameMode::CANCEL; } - zb >> World::Get() >> conf >> GameOver::Result::Get(); + sb >> World::Get() >> conf >> GameOver::Result::Get(); fheroes2::GameMode returnValue = fheroes2::GameMode::START_GAME; if ( conf.isCampaignGameType() ) { Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); - zb >> saveData; + sb >> saveData; if ( !saveData.isStarting() && saveData.getCurrentScenarioInfoId() == saveData.getLastCompletedScenarioInfoID() ) { // This is the end of the current scenario. We should show next scenario selection. @@ -255,9 +255,9 @@ fheroes2::GameMode Game::Load( const std::string & filePath ) } uint16_t endOfDataMarker = 0; - zb >> endOfDataMarker; + sb >> endOfDataMarker; - if ( zb.fail() || endOfDataMarker != SAV2ID3 ) { + if ( sb.fail() || endOfDataMarker != SAV2ID3 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "File " << filePath << " is corrupted" ) showGenericErrorMessage(); diff --git a/src/fheroes2/game/highscores.cpp b/src/fheroes2/game/highscores.cpp index 06d80addfc6..6ddaf513ae2 100644 --- a/src/fheroes2/game/highscores.cpp +++ b/src/fheroes2/game/highscores.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2022 - 2023 * + * Copyright (C) 2022 - 2024 * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -128,8 +128,8 @@ namespace fheroes2 bool HighScoreDataContainer::load( const std::string & fileName ) { - ZStreamBuf hdata; - if ( !hdata.read( fileName ) ) { + StreamBuf hdata; + if ( !Compression::readFile( hdata, fileName ) ) { return false; } @@ -193,11 +193,11 @@ namespace fheroes2 bool HighScoreDataContainer::save( const std::string & fileName ) const { - ZStreamBuf hdata; + StreamBuf hdata; hdata.setbigendian( true ); hdata << highscoreFileMagicValueV2 << _highScoresStandard << _highScoresCampaign; - return !hdata.fail() && hdata.write( fileName ); + return !hdata.fail() && Compression::writeFile( hdata, fileName ); } int32_t HighScoreDataContainer::registerScoreStandard( HighscoreData && data ) diff --git a/src/fheroes2/maps/map_format_info.cpp b/src/fheroes2/maps/map_format_info.cpp index abc6295532a..5a1e37ee49f 100644 --- a/src/fheroes2/maps/map_format_info.cpp +++ b/src/fheroes2/maps/map_format_info.cpp @@ -25,6 +25,7 @@ #include #include "serialize.h" +#include "zzlib.h" namespace { @@ -152,15 +153,56 @@ namespace Maps::Map_Format StreamBase & operator<<( StreamBase & msg, const MapFormat & map ) { - return msg << static_cast( map ) << map.additionalInfo << map.tiles << map.standardMetadata << map.castleMetadata << map.heroMetadata - << map.sphinxMetadata << map.signMetadata << map.adventureMapEventMetadata; + // Only the base map information is not encoded. + // The rest of data must be compressed to prevent manual corruption of the file. + msg << static_cast( map ); + + StreamBuf compressed; + compressed.setbigendian( true ); + + compressed << map.additionalInfo << map.tiles << map.standardMetadata << map.castleMetadata << map.heroMetadata << map.sphinxMetadata << map.signMetadata + << map.adventureMapEventMetadata; + + const std::vector temp = Compression::compressData( compressed.data(), compressed.size() ); + + msg.putRaw( temp.data(), temp.size() ); + + return msg; } StreamBase & operator>>( StreamBase & msg, MapFormat & map ) { // TODO: verify the correctness of metadata. - return msg >> static_cast( map ) >> map.additionalInfo >> map.tiles >> map.standardMetadata >> map.castleMetadata >> map.heroMetadata - >> map.sphinxMetadata >> map.signMetadata >> map.adventureMapEventMetadata; + msg >> static_cast( map ); + + StreamBuf decompressed; + decompressed.setbigendian( true ); + + { + std::vector temp = msg.getRaw(); + if ( temp.empty() ) { + // This is a corrupted file. + map = {}; + return msg; + } + + const std::vector decompressedData = Compression::decompressData( temp.data(), temp.size() ); + if ( decompressedData.empty() ) { + // This is a corrupted file. + map = {}; + return msg; + } + + // Let's try to free up some memory + temp = std::vector{}; + + decompressed.putRaw( decompressedData.data(), decompressedData.size() ); + } + + decompressed >> map.additionalInfo >> map.tiles >> map.standardMetadata >> map.castleMetadata >> map.heroMetadata >> map.sphinxMetadata >> map.signMetadata + >> map.adventureMapEventMetadata; + + return msg; } bool loadBaseMap( const std::string & path, BaseMapFormat & map ) diff --git a/src/fheroes2/maps/map_format_info.h b/src/fheroes2/maps/map_format_info.h index 81d08732beb..63cd889808a 100644 --- a/src/fheroes2/maps/map_format_info.h +++ b/src/fheroes2/maps/map_format_info.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include #include