diff --git a/src/openrct2/drawing/Drawing.Sprite.cpp b/src/openrct2/drawing/Drawing.Sprite.cpp index 050f3d8bcf..bc9fd6a29f 100644 --- a/src/openrct2/drawing/Drawing.Sprite.cpp +++ b/src/openrct2/drawing/Drawing.Sprite.cpp @@ -14,6 +14,7 @@ #include "../PlatformEnvironment.h" #include "../config/Config.h" #include "../core/FileStream.h" +#include "../core/MemoryStream.h" #include "../core/Path.hpp" #include "../platform/Platform.h" #include "../sprites.h" @@ -361,25 +362,22 @@ bool gfx_load_csg() } } -std::optional GfxLoadGx(OpenRCT2::IStream* stream) +std::optional GfxLoadGx(const std::vector& buffer) { try { + OpenRCT2::MemoryStream istream(buffer.data(), buffer.size()); rct_gx gx; - gx.header = stream->ReadValue(); + gx.header = istream.ReadValue(); // Read element headers gx.elements.resize(gx.header.num_entries); - read_and_convert_gxdat(stream, gx.header.num_entries, false, gx.elements.data()); + read_and_convert_gxdat(&istream, gx.header.num_entries, false, gx.elements.data()); // Read element data - gx.data = std::make_unique(gx.header.total_size); - const auto readBytes = stream->TryRead(gx.data.get(), gx.header.total_size); - if (readBytes != gx.header.total_size) - { - log_verbose("GXdata size shorter than expected."); - } + gx.data = istream.ReadArray(gx.header.total_size); + return std::make_optional(std::move(gx)); } catch (const std::exception&) diff --git a/src/openrct2/drawing/Drawing.h b/src/openrct2/drawing/Drawing.h index 9d0435da6f..cd85e9c197 100644 --- a/src/openrct2/drawing/Drawing.h +++ b/src/openrct2/drawing/Drawing.h @@ -526,7 +526,7 @@ void gfx_unload_csg(); const rct_g1_element* gfx_get_g1_element(ImageId imageId); const rct_g1_element* gfx_get_g1_element(ImageIndex image_id); void gfx_set_g1_element(ImageIndex imageId, const rct_g1_element* g1); -std::optional GfxLoadGx(OpenRCT2::IStream* stream); +std::optional GfxLoadGx(const std::vector& buffer); bool is_csg_loaded(); void FASTCALL gfx_sprite_to_buffer(rct_drawpixelinfo& dpi, const DrawSpriteArgs& args); void FASTCALL gfx_bmp_sprite_to_buffer(rct_drawpixelinfo& dpi, const DrawSpriteArgs& args); diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index 8937c9f90e..98602dd3dd 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -16,7 +16,6 @@ #include "../core/FileScanner.h" #include "../core/IStream.hpp" #include "../core/Json.hpp" -#include "../core/MemoryStream.h" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../drawing/ImageImporter.h" @@ -241,8 +240,7 @@ std::vector> ImageTable::LoadImageArc { std::vector> result; auto gxRaw = context->GetData(path); - OpenRCT2::MemoryStream stream(gxRaw.data(), gxRaw.size()); - std::optional gxData = GfxLoadGx(&stream); + std::optional gxData = GfxLoadGx(gxRaw); if (gxData.has_value()) { // Fix entry data offsets @@ -430,19 +428,63 @@ void ImageTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream) { return; } - auto gxData = GfxLoadGx(stream); - if (gxData.has_value()) + try { - _data = std::move(gxData->data); - // Fix entry data offsets - for (auto& entry : gxData->elements) + uint32_t numImages = stream->ReadValue(); + uint32_t imageDataSize = stream->ReadValue(); + + uint64_t headerTableSize = numImages * 16; + uint64_t remainingBytes = stream->GetLength() - stream->GetPosition() - headerTableSize; + if (remainingBytes > imageDataSize) { - entry.offset += reinterpret_cast(_data.get()); + context->LogVerbose(ObjectError::BadImageTable, "Image table size longer than expected."); + imageDataSize = static_cast(remainingBytes); } - _entries.insert(_entries.end(), gxData->elements.begin(), gxData->elements.end()); + + auto dataSize = static_cast(imageDataSize); + auto data = std::make_unique(dataSize); + if (data == nullptr) + { + context->LogError(ObjectError::BadImageTable, "Image table too large."); + throw std::runtime_error("Image table too large."); + } + + // Read g1 element headers + uintptr_t imageDataBase = reinterpret_cast(data.get()); + std::vector newEntries; + for (uint32_t i = 0; i < numImages; i++) + { + rct_g1_element g1Element{}; + + uintptr_t imageDataOffset = static_cast(stream->ReadValue()); + g1Element.offset = reinterpret_cast(imageDataBase + imageDataOffset); + + g1Element.width = stream->ReadValue(); + g1Element.height = stream->ReadValue(); + g1Element.x_offset = stream->ReadValue(); + g1Element.y_offset = stream->ReadValue(); + g1Element.flags = stream->ReadValue(); + g1Element.zoomed_offset = stream->ReadValue(); + + newEntries.push_back(std::move(g1Element)); + } + + // Read g1 element data + size_t readBytes = static_cast(stream->TryRead(data.get(), dataSize)); + + // If data is shorter than expected (some custom objects are unfortunately like that) + size_t unreadBytes = dataSize - readBytes; + if (unreadBytes > 0) + { + std::fill_n(data.get() + readBytes, unreadBytes, 0); + context->LogWarning(ObjectError::BadImageTable, "Image table size shorter than expected."); + } + + _data = std::move(data); + _entries.insert(_entries.end(), newEntries.begin(), newEntries.end()); } - else + catch (const std::exception&) { context->LogError(ObjectError::BadImageTable, "Bad image table."); throw;