1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-18 04:23:20 +01:00

Merge pull request #5179 from IntelOrca/refactor/sc-chunk-writer

Use streams for SV6 export
This commit is contained in:
Ted John
2017-02-08 12:01:38 +00:00
committed by GitHub
22 changed files with 634 additions and 448 deletions

View File

@@ -290,7 +290,7 @@ endif ()
# set necessary flags to compile code as is
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TARGET_M} -std=gnu99 ${COMMON_COMPILE_OPTIONS} -Wimplicit")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TARGET_M} -std=gnu++11 ${COMMON_COMPILE_OPTIONS} -Wnon-virtual-dtor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TARGET_M} -std=gnu++14 ${COMMON_COMPILE_OPTIONS} -Wnon-virtual-dtor")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TARGET_M}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS} ${PIE_FLAG}")

View File

@@ -242,6 +242,9 @@
D41B73EF1C2101890080A7B9 /* libcurl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73EE1C2101890080A7B9 /* libcurl.tbd */; };
D41B741D1C210A7A0080A7B9 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B741C1C210A7A0080A7B9 /* libiconv.tbd */; };
D41B74731C2125E50080A7B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D41B74721C2125E50080A7B9 /* Assets.xcassets */; };
D433A5001E4A861F00D9A6DF /* SawyerChunk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D433A4FA1E4A861F00D9A6DF /* SawyerChunk.cpp */; };
D433A5011E4A861F00D9A6DF /* SawyerChunkReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D433A4FC1E4A861F00D9A6DF /* SawyerChunkReader.cpp */; };
D433A5021E4A861F00D9A6DF /* SawyerChunkWriter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D433A4FE1E4A861F00D9A6DF /* SawyerChunkWriter.cpp */; };
D43407D61D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D43407C01D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp */; };
D43407D81D0E14BE00C2B3D4 /* DrawImageShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D43407C41D0E14BE00C2B3D4 /* DrawImageShader.cpp */; };
D43407D91D0E14BE00C2B3D4 /* DrawLineShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D43407C61D0E14BE00C2B3D4 /* DrawLineShader.cpp */; };
@@ -709,6 +712,13 @@
D41B73EE1C2101890080A7B9 /* libcurl.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurl.tbd; path = usr/lib/libcurl.tbd; sourceTree = SDKROOT; };
D41B741C1C210A7A0080A7B9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
D41B74721C2125E50080A7B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = distribution/macos/Assets.xcassets; sourceTree = SOURCE_ROOT; };
D433A4FA1E4A861F00D9A6DF /* SawyerChunk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerChunk.cpp; path = rct12/SawyerChunk.cpp; sourceTree = "<group>"; };
D433A4FB1E4A861F00D9A6DF /* SawyerChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerChunk.h; path = rct12/SawyerChunk.h; sourceTree = "<group>"; };
D433A4FC1E4A861F00D9A6DF /* SawyerChunkReader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerChunkReader.cpp; path = rct12/SawyerChunkReader.cpp; sourceTree = "<group>"; };
D433A4FD1E4A861F00D9A6DF /* SawyerChunkReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerChunkReader.h; path = rct12/SawyerChunkReader.h; sourceTree = "<group>"; };
D433A4FE1E4A861F00D9A6DF /* SawyerChunkWriter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SawyerChunkWriter.cpp; path = rct12/SawyerChunkWriter.cpp; sourceTree = "<group>"; };
D433A4FF1E4A861F00D9A6DF /* SawyerChunkWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SawyerChunkWriter.h; path = rct12/SawyerChunkWriter.h; sourceTree = "<group>"; };
D433A5031E4A862F00D9A6DF /* rct12.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = rct12.h; sourceTree = "<group>"; };
D43407C01D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CopyFramebufferShader.cpp; sourceTree = "<group>"; };
D43407C11D0E14BE00C2B3D4 /* CopyFramebufferShader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CopyFramebufferShader.h; sourceTree = "<group>"; };
D43407C41D0E14BE00C2B3D4 /* DrawImageShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DrawImageShader.cpp; sourceTree = "<group>"; };
@@ -1242,6 +1252,12 @@
652747E91E41CDFE000F36FD /* rct12 */ = {
isa = PBXGroup;
children = (
D433A4FA1E4A861F00D9A6DF /* SawyerChunk.cpp */,
D433A4FB1E4A861F00D9A6DF /* SawyerChunk.h */,
D433A4FC1E4A861F00D9A6DF /* SawyerChunkReader.cpp */,
D433A4FD1E4A861F00D9A6DF /* SawyerChunkReader.h */,
D433A4FE1E4A861F00D9A6DF /* SawyerChunkWriter.cpp */,
D433A4FF1E4A861F00D9A6DF /* SawyerChunkWriter.h */,
652747EA1E41CE1B000F36FD /* SawyerEncoding.cpp */,
652747EB1E41CE1B000F36FD /* SawyerEncoding.h */,
);
@@ -1524,8 +1540,8 @@
D442715B1CC81B3200D84D28 /* peep */,
D44271601CC81B3200D84D28 /* platform */,
C650B2141CCABBDD00B4D91C /* rct1 */,
652747E91E41CDFE000F36FD /* rct12 */,
C6B5A7CF1CDFE4CB00C9C006 /* rct2 */,
652747E91E41CDFE000F36FD /* rct12 */,
D442716E1CC81B3200D84D28 /* ride */,
C6E96E1C1E04070E0076A04F /* scenario */,
C6E96E261E04072F0076A04F /* title */,
@@ -1564,6 +1580,7 @@
D442716A1CC81B3200D84D28 /* rct1.h */,
D442716B1CC81B3200D84D28 /* rct2.c */,
D442716C1CC81B3200D84D28 /* rct2.h */,
D433A5031E4A862F00D9A6DF /* rct12.h */,
D44271851CC81B3200D84D28 /* sprites.h */,
D4CA88651D4E64C800060C11 /* version.c */,
D442718D1CC81B3200D84D28 /* version.h */,
@@ -2896,6 +2913,7 @@
C6E96E2F1E04072F0076A04F /* TitleScreen.cpp in Sources */,
D442728E1CC81B3200D84D28 /* title_logo.c in Sources */,
C686F8B21CDBC37E009F9BFC /* scenery_multiple.c in Sources */,
D433A5011E4A861F00D9A6DF /* SawyerChunkReader.cpp in Sources */,
D442725E1CC81B3200D84D28 /* editor_objective_options.c in Sources */,
D44272531CC81B3200D84D28 /* about.c in Sources */,
C686F9311CDBC3B7009F9BFC /* ghost_train.c in Sources */,
@@ -2923,6 +2941,7 @@
D44272761CC81B3200D84D28 /* options.c in Sources */,
C686F93D1CDBC3B7009F9BFC /* shop.c in Sources */,
C686F8AD1CDBC37E009F9BFC /* entrance.c in Sources */,
D433A5001E4A861F00D9A6DF /* SawyerChunk.cpp in Sources */,
D44272111CC81B3200D84D28 /* string.c in Sources */,
C686F93E1CDBC3B7009F9BFC /* 3d_cinema.c in Sources */,
D44272771CC81B3200D84D28 /* park.c in Sources */,
@@ -2968,6 +2987,7 @@
D442726C1CC81B3200D84D28 /* map_tooltip.c in Sources */,
C686F9281CDBC3B7009F9BFC /* wild_mouse.c in Sources */,
D44272701CC81B3200D84D28 /* music_credits.c in Sources */,
D433A5021E4A861F00D9A6DF /* SawyerChunkWriter.cpp in Sources */,
C686F9511CDBC3B7009F9BFC /* river_rafts.c in Sources */,
C686F90E1CDBC3B7009F9BFC /* corkscrew_roller_coaster.c in Sources */,
D44272A61CC81B3200D84D28 /* scenery.c in Sources */,
@@ -3084,7 +3104,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = NO;
@@ -3132,7 +3152,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = NO;

View File

@@ -16,7 +16,7 @@
#include "core/FileStream.hpp"
#include "FileClassifier.h"
#include "rct12/SawyerEncoding.h"
#include "rct12/SawyerChunkReader.h"
extern "C"
{
@@ -71,9 +71,10 @@ bool TryClassifyFile(IStream * stream, ClassifiedFile * result)
static bool TryClassifyAsS6(IStream * stream, ClassifiedFile * result)
{
rct_s6_header s6Header;
if (SawyerEncoding::TryReadChunk(&s6Header, stream))
try
{
auto chunkReader = SawyerChunkReader(stream);
auto s6Header = chunkReader.ReadChunkAs<rct_s6_header>();
if (s6Header.type == S6_TYPE_SAVEDGAME)
{
result->Type = FILE_TYPE::SAVED_GAME;
@@ -85,7 +86,9 @@ static bool TryClassifyAsS6(IStream * stream, ClassifiedFile * result)
result->Version = s6Header.version;
return true;
}
catch (Exception)
{
}
return false;
}

View File

@@ -20,6 +20,7 @@
#include <SDL.h>
#include "IStream.hpp"
#include "Math.hpp"
enum
{
@@ -56,8 +57,8 @@ public:
_canWrite = false;
break;
case FILE_MODE_WRITE:
mode = "wb";
_canRead = false;
mode = "w+b";
_canRead = true;
_canWrite = true;
break;
default:
@@ -82,7 +83,7 @@ public:
_canWrite = false;
break;
case FILE_MODE_WRITE:
_canRead = false;
_canRead = true;
_canWrite = true;
break;
default:
@@ -148,6 +149,9 @@ public:
{
throw IOException("Unable to write to file.");
}
uint64 position = GetPosition();
_fileSize = Math::Max(_fileSize, position);
}
uint64 TryRead(void * buffer, uint64 length) override

View File

@@ -74,6 +74,9 @@
<ClCompile Include="audio\NullAudioSource.cpp" />
<ClCompile Include="FileClassifier.cpp" />
<ClCompile Include="ParkImporter.cpp" />
<ClCompile Include="rct12\SawyerChunk.cpp" />
<ClCompile Include="rct12\SawyerChunkReader.cpp" />
<ClCompile Include="rct12\SawyerChunkWriter.cpp" />
<ClCompile Include="rct12\SawyerEncoding.cpp" />
<ClCompile Include="rct2\addresses.c" />
<ClCompile Include="audio\audio.c" />
@@ -417,6 +420,9 @@
<ClInclude Include="FileClassifier.h" />
<ClInclude Include="rct12.h" />
<ClInclude Include="ParkImporter.h" />
<ClInclude Include="rct12\SawyerChunk.h" />
<ClInclude Include="rct12\SawyerChunkReader.h" />
<ClInclude Include="rct12\SawyerChunkWriter.h" />
<ClInclude Include="rct12\SawyerEncoding.h" />
<ClInclude Include="rct2\addresses.h" />
<ClInclude Include="audio\audio.h" />

View File

@@ -50,6 +50,7 @@ sint32 _pickup_peep_old_x = SPRITE_LOCATION_NULL;
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../core/Util.hpp"
#include "../object/ObjectManager.h"
#include "../object/ObjectRepository.h"
#include "../rct2/S6Exporter.h"
@@ -929,7 +930,8 @@ void Network::Server_Send_MAP(NetworkConnection* connection)
} else {
// This will send all custom objects to connected clients
// TODO: fix it so custom objects negotiation is performed even in this case.
objects = scenario_get_packable_objects();
IObjectManager * objManager = GetObjectManager();
objects = objManager->GetPackableObjects();
}
header = save_for_network(rw, out_size, objects);
SDL_RWclose(rw);
@@ -1465,7 +1467,9 @@ void Network::Server_Client_Joined(const char* name, const std::string &keyhash,
const char * player_name = (const char *) player->Name.c_str();
format_string(text, 256, STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, &player_name);
chat_history_add(text);
std::vector<const ObjectRepositoryItem *> objects = scenario_get_packable_objects();
IObjectManager * objManager = GetObjectManager();
auto objects = objManager->GetPackableObjects();
Server_Send_OBJECTS(connection, objects);
}
}

View File

@@ -215,6 +215,23 @@ public:
}
}
std::vector<const ObjectRepositoryItem *> GetPackableObjects() override
{
std::vector<const ObjectRepositoryItem *> objects;
size_t numObjects = _objectRepository->GetNumObjects();
for (size_t i = 0; i < numObjects; i++)
{
const ObjectRepositoryItem * item = &_objectRepository->GetObjects()[i];
if (item->LoadedObject != nullptr &&
item->LoadedObject->GetLegacyData() != nullptr &&
IsObjectCustom(item))
{
objects.push_back(item);
}
}
return objects;
}
private:
sint32 FindSpareSlot(uint8 objectType)
{

View File

@@ -16,6 +16,10 @@
#pragma once
#ifdef __cplusplus
#include <vector>
#endif
#include "../common.h"
#ifdef __cplusplus
@@ -29,7 +33,8 @@ extern "C"
#ifdef __cplusplus
class Object;
class Object;
struct ObjectRepositoryItem;
interface IObjectManager
{
@@ -45,6 +50,8 @@ interface IObjectManager
virtual void UnloadAll() abstract;
virtual void ResetObjects() abstract;
virtual std::vector<const ObjectRepositoryItem *> GetPackableObjects() abstract;
};
IObjectManager * GetObjectManager();

View File

@@ -31,7 +31,8 @@
#include "../core/Stopwatch.hpp"
#include "../core/String.hpp"
#include "../PlatformEnvironment.h"
#include "../rct12/SawyerEncoding.h"
#include "../rct12/SawyerChunkReader.h"
#include "../rct12/SawyerChunkWriter.h"
#include "../scenario/ScenarioRepository.h"
#include "Object.h"
#include "ObjectFactory.h"
@@ -224,29 +225,41 @@ public:
}
}
bool TryExportPackedObject(IStream * stream) override
void ExportPackedObject(IStream * stream) override
{
auto chunkReader = SawyerChunkReader(stream);
// Check if we already have this object
rct_object_entry entry = stream->ReadValue<rct_object_entry>();
if (FindObject(&entry) != nullptr)
{
SawyerEncoding::SkipChunk(stream);
chunkReader.SkipChunk();
}
else
{
// Read object and save to new file
size_t chunkSize;
void * chunk = SawyerEncoding::ReadChunk(stream, &chunkSize);
if (chunk == nullptr)
{
log_error("Failed to reallocate buffer for packed object.");
return false;
}
AddObject(&entry, chunk, chunkSize);
Memory::Free(chunk);
std::shared_ptr<SawyerChunk> chunk = chunkReader.ReadChunk();
AddObject(&entry, chunk->GetData(), chunk->GetLength());
}
}
void WritePackedObjects(IStream * stream, std::vector<const ObjectRepositoryItem *> &objects) override
{
log_verbose("packing %u objects", objects.size());
for (const auto &object : objects)
{
Guard::ArgumentNotNull(object);
log_verbose("exporting object %.8s", object->ObjectEntry.name);
if (IsObjectCustom(object))
{
WritePackedObject(stream, &object->ObjectEntry);
}
else
{
log_warning("Refusing to pack vanilla/expansion object \"%s\"", object->ObjectEntry.name);
}
}
return true;
}
private:
@@ -654,6 +667,30 @@ private:
String::Append(buffer, bufferSize, ".DAT");
}
}
void WritePackedObject(IStream * stream, const rct_object_entry * entry)
{
const ObjectRepositoryItem * item = FindObject(entry);
if (item == nullptr)
{
throw Exception(String::StdFormat("Unable to find object '%.8s'", entry->name));
}
// Read object data from file
auto fs = FileStream(item->Path, FILE_MODE_OPEN);
auto fileEntry = fs.ReadValue<rct_object_entry>();
if (!object_entry_compare(entry, &fileEntry))
{
throw Exception("Header found in object file does not match object to pack.");
}
auto chunkReader = SawyerChunkReader(&fs);
auto chunk = chunkReader.ReadChunk();
// Write object data to stream
auto chunkWriter = SawyerChunkWriter(stream);
stream->WriteValue(*entry);
chunkWriter.WriteChunk(chunk.get());
}
};
static std::unique_ptr<ObjectRepository> _objectRepository;
@@ -669,6 +706,14 @@ IObjectRepository * GetObjectRepository()
return _objectRepository.get();
}
bool IsObjectCustom(const ObjectRepositoryItem * object)
{
Guard::ArgumentNotNull(object);
// Validate the object is not one from base game or expansion pack
return !(object->ObjectEntry.flags & 0xF0);
}
extern "C"
{
rct_object_entry * object_list_find(rct_object_entry * entry)
@@ -761,45 +806,6 @@ extern "C"
}
}
bool object_saved_packed(SDL_RWops * rw, const rct_object_entry * entry)
{
IObjectRepository * objectRepository = GetObjectRepository();
const ObjectRepositoryItem * item = objectRepository->FindObject(entry);
if (item == nullptr)
{
return false;
}
auto fs = FileStream(item->Path, FILE_MODE_OPEN);
rct_object_entry fileEntry = fs.ReadValue<rct_object_entry>();
if (!object_entry_compare(entry, &fileEntry))
{
return false;
}
sawyercoding_chunk_header chunkHeader = fs.ReadValue<sawyercoding_chunk_header>();
uint8 * chunkData = fs.ReadArray<uint8>(chunkHeader.length);
if (SDL_RWwrite(rw, entry, sizeof(rct_object_entry), 1) != 1)
{
Memory::Free(chunkData);
return false;
}
if (SDL_RWwrite(rw, &chunkHeader, sizeof(sawyercoding_chunk_header), 1) != 1)
{
Memory::Free(chunkData);
return false;
}
if (SDL_RWwrite(rw, chunkData, chunkHeader.length, 1) != 1)
{
Memory::Free(chunkData);
return false;
}
Memory::Free(chunkData);
return true;
}
size_t object_repository_get_items_count()
{
IObjectRepository * objectRepository = GetObjectRepository();

View File

@@ -16,6 +16,10 @@
#pragma once
#ifdef __cplusplus
#include <vector>
#endif
#include "../common.h"
#ifdef __cplusplus
@@ -79,12 +83,15 @@ interface IObjectRepository
const void * data,
size_t dataSize) abstract;
virtual bool TryExportPackedObject(IStream * stream) abstract;
virtual void ExportPackedObject(IStream * stream) abstract;
virtual void WritePackedObjects(IStream * stream, std::vector<const ObjectRepositoryItem *> &objects) abstract;
};
IObjectRepository * CreateObjectRepository(IPlatformEnvironment * env);
IObjectRepository * GetObjectRepository();
bool IsObjectCustom(const ObjectRepositoryItem * object);
#endif
#ifdef __cplusplus

View File

@@ -0,0 +1,30 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../core/Memory.hpp"
#include "SawyerChunk.h"
SawyerChunk::SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length)
{
_encoding = encoding;
_data = data;
_length = length;
}
SawyerChunk::~SawyerChunk()
{
Memory::Free(_data);
}

View File

@@ -0,0 +1,49 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include "../common.h"
/**
* The type of encoding / compression for a sawyer encoded chunk.
*/
enum class SAWYER_ENCODING : uint8
{
NONE,
RLE,
RLECOMPRESSED,
ROTATE,
};
/**
* Represents a sawyer encoded chunk.
*/
class SawyerChunk final
{
private:
void * _data = nullptr;
size_t _length = 0;
SAWYER_ENCODING _encoding = SAWYER_ENCODING::NONE;
public:
const void * GetData() const { return _data; }
size_t GetLength() const { return _length; }
SAWYER_ENCODING GetEncoding() const { return _encoding; }
SawyerChunk(SAWYER_ENCODING encoding, void * data, size_t length);
~SawyerChunk();
};

View File

@@ -0,0 +1,124 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../core/Exception.hpp"
#include "../core/IStream.hpp"
#include "../core/Math.hpp"
#include "SawyerChunkReader.h"
extern "C"
{
#include "../util/sawyercoding.h"
}
// Allow chunks to be uncompressed to a maximum of 16 MiB
constexpr size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024;
class SawyerChunkException : public IOException
{
public:
explicit SawyerChunkException(const char * message) : IOException(message) { }
explicit SawyerChunkException(const std::string &message) : IOException(message) { }
};
SawyerChunkReader::SawyerChunkReader(IStream * stream)
: _stream(stream)
{
}
void SawyerChunkReader::SkipChunk()
{
uint64 originalPosition = _stream->GetPosition();
try
{
auto header = _stream->ReadValue<sawyercoding_chunk_header>();
_stream->Seek(header.length, STREAM_SEEK_CURRENT);
}
catch (Exception)
{
// Rewind stream back to original position
_stream->SetPosition(originalPosition);
throw;
}
}
std::shared_ptr<SawyerChunk> SawyerChunkReader::ReadChunk()
{
uint64 originalPosition = _stream->GetPosition();
try
{
auto header = _stream->ReadValue<sawyercoding_chunk_header>();
switch (header.encoding) {
case CHUNK_ENCODING_NONE:
case CHUNK_ENCODING_RLE:
case CHUNK_ENCODING_RLECOMPRESSED:
case CHUNK_ENCODING_ROTATE:
{
std::unique_ptr<uint8> compressedData = std::unique_ptr<uint8>(Memory::Allocate<uint8>(header.length));
if (_stream->TryRead(compressedData.get(), header.length) != header.length)
{
throw SawyerChunkException("Corrupt chunk size.");
}
// Allow 16MiB for chunk data
size_t bufferSize = MAX_UNCOMPRESSED_CHUNK_SIZE;
uint8 * buffer = Memory::Allocate<uint8>(bufferSize);
if (buffer == nullptr)
{
throw Exception("Unable to allocate buffer.");
}
size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer, compressedData.get(), header, bufferSize);
buffer = Memory::Reallocate(buffer, uncompressedLength);
if (buffer == nullptr)
{
throw Exception("Unable to reallocate buffer.");
}
return std::make_shared<SawyerChunk>((SAWYER_ENCODING)header.encoding, buffer, uncompressedLength);
}
default:
throw SawyerChunkException("Invalid chunk encoding.");
}
}
catch (Exception)
{
// Rewind stream back to original position
_stream->SetPosition(originalPosition);
throw;
}
}
void SawyerChunkReader::ReadChunk(void * dst, size_t length)
{
auto chunk = ReadChunk();
const void * chunkData = chunk->GetData();
size_t chunkLength = chunk->GetLength();
if (chunkLength > length)
{
Memory::Copy(dst, chunkData, length);
}
else
{
Memory::Copy(dst, chunkData, chunkLength);
size_t remainingLength = length - chunkLength;
if (remainingLength > 0)
{
void * offset = (void *)((uintptr_t)dst + chunkLength);
Memory::Set(offset, 0, remainingLength);
}
}
}

View File

@@ -0,0 +1,70 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <memory>
#include "../common.h"
#include "SawyerChunk.h"
interface IStream;
/**
* Reads sawyer encoding chunks from a data stream. This can be used to read
* SC6, SV6 and RCT2 objects.
*/
class SawyerChunkReader final
{
private:
IStream * const _stream = nullptr;
public:
SawyerChunkReader(IStream * stream);
/**
* Skips the next chunk in the stream without decoding or reading its data
* into RAM.
*/
void SkipChunk();
/**
* Reads the next chunk from the stream.
*/
std::shared_ptr<SawyerChunk> ReadChunk();
/**
* Reads the next chunk from the stream and copies it directly to the
* destination buffer. If the chunk is larger than length, only length
* is copied. If the chunk is smaller than length, the remaining space
* is padded with zero.
* @param dst The destination buffer.
* @param length The size of the destination buffer.
*/
void ReadChunk(void * dst, size_t length);
/**
* Reads the next chunk from the stream into a buffer returned as the
* specified type. If the chunk is smaller than the size of the type
* then the remaining space is padded with zero.
*/
template<typename T>
T ReadChunkAs()
{
T result;
ReadChunk(&result, sizeof(result));
return result;
}
};

View File

@@ -0,0 +1,50 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../core/Exception.hpp"
#include "../core/IStream.hpp"
#include "../core/Math.hpp"
#include "SawyerChunkWriter.h"
extern "C"
{
#include "../util/sawyercoding.h"
}
// Maximum buffer size to store compressed data, maximum of 16 MiB
constexpr size_t MAX_COMPRESSED_CHUNK_SIZE = 16 * 1024 * 1024;
SawyerChunkWriter::SawyerChunkWriter(IStream * stream)
: _stream(stream)
{
}
void SawyerChunkWriter::WriteChunk(const SawyerChunk * chunk)
{
WriteChunk(chunk->GetData(), chunk->GetLength(), chunk->GetEncoding());
}
void SawyerChunkWriter::WriteChunk(const void * src, size_t length, SAWYER_ENCODING encoding)
{
sawyercoding_chunk_header header;
header.encoding = (uint8)encoding;
header.length = (uint32)length;
auto data = std::make_unique<uint8[]>(MAX_COMPRESSED_CHUNK_SIZE);
size_t dataLength = sawyercoding_write_chunk_buffer(data.get(), (const uint8 *)src, header);
_stream->Write(data.get(), dataLength);
}

View File

@@ -0,0 +1,57 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include <memory>
#include "../common.h"
#include "SawyerChunk.h"
interface IStream;
/**
* Writes sawyer encoding chunks to a data stream. This can be used to write
* SC6 and SV6 files.
*/
class SawyerChunkWriter final
{
private:
IStream * const _stream = nullptr;
public:
SawyerChunkWriter(IStream * stream);
/**
* Writes a chunk to the stream.
*/
void WriteChunk(const SawyerChunk * chunk);
/**
* Writes a chunk to the stream containing the given buffer.
* @param dst The source buffer.
* @param length The size of the source buffer.
*/
void WriteChunk(const void * src, size_t length, SAWYER_ENCODING encoding);
/**
* Writes a chunk to the stream containing the given type.
*/
template<typename T>
void WriteChunk(const T * src, SAWYER_ENCODING encoding)
{
WriteChunk(src, sizeof(T), encoding);
}
};

View File

@@ -14,140 +14,14 @@
*****************************************************************************/
#pragma endregion
#include <memory>
#include "../core/IStream.hpp"
#include "../core/Math.hpp"
#include "SawyerEncoding.h"
extern "C"
{
#include "../util/sawyercoding.h"
}
namespace SawyerEncoding
{
void SkipChunk(IStream * stream)
{
auto header = stream->ReadValue<sawyercoding_chunk_header>();
stream->Seek(header.length, STREAM_SEEK_CURRENT);
}
void * ReadChunk(IStream * stream, size_t * outSize)
{
uint64 originalPosition = stream->GetPosition();
auto header = stream->ReadValue<sawyercoding_chunk_header>();
switch (header.encoding) {
case CHUNK_ENCODING_NONE:
case CHUNK_ENCODING_RLE:
case CHUNK_ENCODING_RLECOMPRESSED:
case CHUNK_ENCODING_ROTATE:
{
std::unique_ptr<uint8> compressedData = std::unique_ptr<uint8>(Memory::Allocate<uint8>(header.length));
if (stream->TryRead(compressedData.get(), header.length) != header.length)
{
throw IOException("Corrupt chunk size.");
}
// Allow 16MiB for chunk data
size_t bufferSize = 16 * 1024 * 1024;
uint8 * buffer = Memory::Allocate<uint8>(bufferSize);
if (buffer == nullptr)
{
throw Exception("Unable to allocate buffer.");
}
size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer, compressedData.get(), header, bufferSize);
buffer = Memory::Reallocate(buffer, uncompressedLength);
if (buffer == nullptr)
{
throw Exception("Unable to reallocate buffer.");
}
if (outSize != nullptr) *outSize = uncompressedLength;
return buffer;
}
default:
stream->SetPosition(originalPosition);
throw IOException("Invalid chunk encoding.");
}
}
void ReadChunk(void * dst, size_t expectedSize, IStream * stream)
{
if (!TryReadChunk(dst, expectedSize, stream))
{
throw IOException("Invalid or incorrect chunk size.");
}
}
void ReadChunkTolerant(void * dst, size_t expectedSize, IStream * stream)
{
uint64 originalPosition = stream->GetPosition();
auto header = stream->ReadValue<sawyercoding_chunk_header>();
switch (header.encoding) {
case CHUNK_ENCODING_NONE:
case CHUNK_ENCODING_RLE:
case CHUNK_ENCODING_RLECOMPRESSED:
case CHUNK_ENCODING_ROTATE:
{
std::unique_ptr<uint8> compressedData = std::unique_ptr<uint8>(Memory::Allocate<uint8>(header.length));
if (stream->TryRead(compressedData.get(), header.length) != header.length)
{
throw IOException("Corrupt chunk size.");
}
// Allow 16MiB for chunk data
size_t bufferSize = 16 * 1024 * 1024;
std::unique_ptr<uint8> buffer = std::unique_ptr<uint8>(Memory::Allocate<uint8>(bufferSize));
size_t uncompressedLength = sawyercoding_read_chunk_buffer(buffer.get(), compressedData.get(), header, bufferSize);
size_t copyLength = Math::Min(uncompressedLength, expectedSize);
Memory::Set(dst, 0, expectedSize);
Memory::Copy<void>(dst, buffer.get(), copyLength);
break;
}
default:
stream->SetPosition(originalPosition);
throw IOException("Invalid chunk encoding.");
}
}
bool TryReadChunk(void * dst, size_t expectedSize, IStream * stream)
{
uint64 originalPosition = stream->GetPosition();
bool success = false;
auto header = stream->ReadValue<sawyercoding_chunk_header>();
switch (header.encoding) {
case CHUNK_ENCODING_NONE:
case CHUNK_ENCODING_RLE:
case CHUNK_ENCODING_RLECOMPRESSED:
case CHUNK_ENCODING_ROTATE:
uint8 * compressedData = Memory::Allocate<uint8>(header.length);
if (stream->TryRead(compressedData, header.length) == header.length)
{
size_t uncompressedLength = sawyercoding_read_chunk_buffer((uint8 *)dst, compressedData, header, expectedSize);
if (uncompressedLength == expectedSize)
{
success = true;
}
}
Memory::Free(compressedData);
break;
}
if (!success)
{
stream->SetPosition(originalPosition);
}
return success;
}
bool ValidateChecksum(IStream * stream)
{
// Get data size
uint64 initialPosition = stream->GetPosition();
uint64 dataSize = stream->GetLength() - initialPosition;
if (dataSize < 8)
@@ -156,29 +30,37 @@ namespace SawyerEncoding
}
dataSize -= 4;
// Calculate checksum
uint32 checksum = 0;
do
try
{
uint8 buffer[4096];
uint64 bufferSize = Math::Min<uint64>(dataSize, sizeof(buffer));
stream->Read(buffer, bufferSize);
for (uint64 i = 0; i < bufferSize; i++)
// Calculate checksum
uint32 checksum = 0;
do
{
checksum += buffer[i];
uint8 buffer[4096];
uint64 bufferSize = Math::Min<uint64>(dataSize, sizeof(buffer));
stream->Read(buffer, bufferSize);
for (uint64 i = 0; i < bufferSize; i++)
{
checksum += buffer[i];
}
dataSize -= bufferSize;
}
while (dataSize != 0);
dataSize -= bufferSize;
// Read file checksum
uint32 fileChecksum = stream->ReadValue<uint32>();
// Rewind back to original position
stream->SetPosition(initialPosition);
return checksum == fileChecksum;
}
catch (Exception)
{
// Rewind back to original position
stream->SetPosition(initialPosition);
return false;
}
while (dataSize != 0);
// Read file checksum
uint32 fileChecksum = stream->ReadValue<uint32>();
// Rewind
stream->SetPosition(initialPosition);
return checksum == fileChecksum;
}
}

View File

@@ -22,17 +22,5 @@ interface IStream;
namespace SawyerEncoding
{
void SkipChunk(IStream * stream);
void * ReadChunk(IStream * stream, size_t * outSize);
void ReadChunk(void * dst, size_t expectedSize, IStream * stream);
void ReadChunkTolerant(void * dst, size_t expectedSize, IStream * stream);
bool TryReadChunk(void * dst, size_t expectedSize, IStream * stream);
template<typename T>
bool TryReadChunk(T * dst, IStream * stream)
{
return TryReadChunk(dst, sizeof(T), stream);
}
bool ValidateChecksum(IStream * stream);
}

View File

@@ -15,11 +15,14 @@
#pragma endregion
#include "../core/Exception.hpp"
#include "../core/FileStream.hpp"
#include "../core/IStream.hpp"
#include "../core/String.hpp"
#include "../management/award.h"
#include "../object/Object.h"
#include "../object/ObjectManager.h"
#include "../object/ObjectRepository.h"
#include "../rct12/SawyerChunkWriter.h"
#include "S6Exporter.h"
extern "C"
@@ -56,178 +59,91 @@ S6Exporter::S6Exporter()
void S6Exporter::SaveGame(const utf8 * path)
{
SDL_RWops * rw = SDL_RWFromFile(path, "rb");
if (rw == nullptr)
{
throw IOException("Unable to write to destination file.");
}
SaveGame(rw);
SDL_RWclose(rw);
auto fs = FileStream(path, FILE_MODE_WRITE);
SaveGame(&fs);
}
void S6Exporter::SaveGame(SDL_RWops *rw)
void S6Exporter::SaveGame(IStream * stream)
{
Save(rw, false);
Save(stream, false);
}
void S6Exporter::SaveScenario(const utf8 * path)
{
SDL_RWops * rw = SDL_RWFromFile(path, "rb");
if (rw == nullptr)
{
throw IOException("Unable to write to destination file.");
}
SaveGame(rw);
SDL_RWclose(rw);
auto fs = FileStream(path, FILE_MODE_WRITE);
SaveScenario(&fs);
}
void S6Exporter::SaveScenario(SDL_RWops *rw)
void S6Exporter::SaveScenario(IStream * stream)
{
Save(rw, true);
Save(stream, true);
}
void S6Exporter::Save(SDL_RWops * rw, bool isScenario)
void S6Exporter::Save(IStream * stream, bool isScenario)
{
_s6.header.type = isScenario ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME;
_s6.header.classic_flag = 0;
_s6.header.num_packed_objects = uint16(ExportObjectsList.size());
_s6.header.version = S6_RCT2_VERSION;
_s6.header.magic_number = S6_MAGIC_NUMBER;
_s6.game_version_number = 201028;
uint8 * buffer = (uint8 *)malloc(0x600000);
if (buffer == NULL)
{
log_error("Unable to allocate enough space for a write buffer.");
throw Exception("Unable to allocate memory.");
}
sawyercoding_chunk_header chunkHeader;
size_t encodedLength;
auto chunkWriter = SawyerChunkWriter(stream);
// 0: Write header chunk
chunkHeader.encoding = CHUNK_ENCODING_ROTATE;
chunkHeader.length = sizeof(rct_s6_header);
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.header, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
chunkWriter.WriteChunk(&_s6.header, SAWYER_ENCODING::ROTATE);
// 1: Write scenario info chunk
if (_s6.header.type == S6_TYPE_SCENARIO)
{
chunkHeader.encoding = CHUNK_ENCODING_ROTATE;
chunkHeader.length = sizeof(rct_s6_info);
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.info, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
chunkWriter.WriteChunk(&_s6.info, SAWYER_ENCODING::ROTATE);
}
log_verbose("exporting %u objects", _s6.header.num_packed_objects);
// 2: Write packed objects
if (_s6.header.num_packed_objects > 0)
{
if (!scenario_write_packed_objects(rw, ExportObjectsList))
{
free(buffer);
throw Exception("Unable to pack objects.");
}
IObjectRepository * objRepo = GetObjectRepository();
objRepo->WritePackedObjects(stream, ExportObjectsList);
}
// 3: Write available objects chunk
chunkHeader.encoding = CHUNK_ENCODING_ROTATE;
chunkHeader.length = OBJECT_ENTRY_COUNT * sizeof(rct_object_entry);
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.objects, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
chunkWriter.WriteChunk(_s6.objects, sizeof(_s6.objects), SAWYER_ENCODING::ROTATE);
// 4: Misc fields (data, rand...) chunk
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 16;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.elapsed_months, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
chunkWriter.WriteChunk(&_s6.elapsed_months, 16, SAWYER_ENCODING::RLECOMPRESSED);
// 5: Map elements + sprites and other fields chunk
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 0x180000;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.map_elements, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
chunkWriter.WriteChunk(&_s6.map_elements, 0x180000, SAWYER_ENCODING::RLECOMPRESSED);
if (_s6.header.type == S6_TYPE_SCENARIO)
{
// 6:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 0x27104C;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.next_free_map_element_pointer_index, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 7:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 4;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.guests_in_park, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 8:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 8;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.last_guests_in_park, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 9:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 2;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.park_rating, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 10:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 1082;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.active_research_types, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 11:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 16;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.current_expenditure, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 12:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 4;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.park_value, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 13:
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 0x761E8;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.completed_company_value, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
// 6 to 13:
chunkWriter.WriteChunk(&_s6.next_free_map_element_pointer_index, 0x27104C, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.guests_in_park, 4, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.last_guests_in_park, 8, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.park_rating, 2, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.active_research_types, 1082, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.current_expenditure, 16, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.park_value, 4, SAWYER_ENCODING::RLECOMPRESSED);
chunkWriter.WriteChunk(&_s6.completed_company_value, 0x761E8, SAWYER_ENCODING::RLECOMPRESSED);
}
else
{
// 6: Everything else...
chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED;
chunkHeader.length = 0x2E8570;
encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)&_s6.next_free_map_element_pointer_index, chunkHeader);
SDL_RWwrite(rw, buffer, encodedLength, 1);
chunkWriter.WriteChunk(&_s6.next_free_map_element_pointer_index, 0x2E8570, SAWYER_ENCODING::RLECOMPRESSED);
}
free(buffer);
// Determine number of bytes written
size_t fileSize = (size_t)SDL_RWtell(rw);
SDL_RWseek(rw, 0, RW_SEEK_SET);
size_t fileSize = stream->GetLength();
// Read all written bytes back into a single buffer
buffer = (uint8 *)malloc(fileSize);
SDL_RWread(rw, buffer, fileSize, 1);
uint32 checksum = sawyercoding_calculate_checksum(buffer, fileSize);
free(buffer);
stream->SetPosition(0);
auto data = std::unique_ptr<uint8>(stream->ReadArray<uint8>(fileSize));
uint32 checksum = sawyercoding_calculate_checksum(data.get(), fileSize);
// Append the checksum
SDL_RWseek(rw, fileSize, RW_SEEK_SET);
SDL_RWwrite(rw, &checksum, sizeof(uint32), 1);
// Write the checksum on the end
stream->SetPosition(fileSize);
stream->WriteValue(checksum);
}
void S6Exporter::Export()
@@ -480,7 +396,6 @@ uint32 S6Exporter::GetLoanHash(money32 initialCash, money32 bankLoan, uint32 max
return value;
}
// Save game state without modifying any of the state for multiplayer
sint32 scenario_save_network(SDL_RWops * rw, const std::vector<const ObjectRepositoryItem *> &objects)
{
@@ -490,9 +405,11 @@ sint32 scenario_save_network(SDL_RWops * rw, const std::vector<const ObjectRepos
auto s6exporter = new S6Exporter();
try
{
auto rwStream = FileStream(rw, FILE_MODE_WRITE);
s6exporter->ExportObjectsList = objects;
s6exporter->Export();
s6exporter->SaveGame(rw);
s6exporter->SaveGame(&rwStream);
result = true;
}
catch (const Exception &)
@@ -534,58 +451,6 @@ sint32 scenario_save_network(SDL_RWops * rw, const std::vector<const ObjectRepos
return 1;
}
static bool object_is_custom(const ObjectRepositoryItem * object)
{
Guard::ArgumentNotNull(object);
// Validate the object is not one from base game or expansion pack
return (object->LoadedObject != nullptr &&
object->LoadedObject->GetLegacyData() != nullptr
&& !(object->ObjectEntry.flags & 0xF0));
}
sint32 scenario_write_packed_objects(SDL_RWops* rw, std::vector<const ObjectRepositoryItem *> &objects)
{
log_verbose("exporting packed objects");
for (const auto &object : objects)
{
Guard::ArgumentNotNull(object);
log_verbose("exporting object %.8s", object->ObjectEntry.name);
if (object_is_custom(object))
{
if (!object_saved_packed(rw, &object->ObjectEntry))
{
return 0;
}
}
else
{
log_warning("Refusing to pack vanilla/expansion object \"%s\"", object->ObjectEntry.name);
}
}
return 1;
}
/**
*
* rct2: 0x006AA244
*/
std::vector<const ObjectRepositoryItem *> scenario_get_packable_objects()
{
std::vector<const ObjectRepositoryItem *> objects;
IObjectRepository * repo = GetObjectRepository();
for (size_t i = 0; i < repo->GetNumObjects(); i++)
{
const ObjectRepositoryItem *item = &repo->GetObjects()[i];
// Validate the object is not one from base game or expansion pack
if (object_is_custom(item))
{
objects.push_back(item);
}
}
return objects;
}
extern "C"
{
enum {
@@ -624,18 +489,22 @@ extern "C"
auto s6exporter = new S6Exporter();
try
{
if (flags & S6_SAVE_FLAG_EXPORT) {
s6exporter->ExportObjectsList = scenario_get_packable_objects();
auto rwStream = FileStream(rw, FILE_MODE_WRITE);
if (flags & S6_SAVE_FLAG_EXPORT)
{
IObjectManager * objManager = GetObjectManager();
s6exporter->ExportObjectsList = objManager->GetPackableObjects();
}
s6exporter->RemoveTracklessRides = true;
s6exporter->Export();
if (flags & S6_SAVE_FLAG_SCENARIO)
{
s6exporter->SaveScenario(rw);
s6exporter->SaveScenario(&rwStream);
}
else
{
s6exporter->SaveGame(rw);
s6exporter->SaveGame(&rwStream);
}
result = true;
}

View File

@@ -27,11 +27,10 @@ extern "C"
#include "../object_list.h"
}
struct ObjectRepositoryItem;
interface IStream;
struct ObjectRepositoryItem;
sint32 scenario_save_network(SDL_RWops* rw, const std::vector<const ObjectRepositoryItem *> &objects);
sint32 scenario_write_packed_objects(SDL_RWops* rw, std::vector<const ObjectRepositoryItem *> &objects);
std::vector<const ObjectRepositoryItem *> scenario_get_packable_objects();
/**
* Class to export RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6).
@@ -45,14 +44,14 @@ public:
S6Exporter();
void SaveGame(const utf8 * path);
void SaveGame(SDL_RWops *rw);
void SaveGame(IStream * stream);
void SaveScenario(const utf8 * path);
void SaveScenario(SDL_RWops *rw);
void SaveScenario(IStream * stream);
void Export();
private:
rct_s6_data _s6;
void Save(SDL_RWops *rw, bool isScenario);
void Save(IStream * stream, bool isScenario);
static uint32 GetLoanHash(money32 initialCash, money32 bankLoan, uint32 maxBankLoan);
};

View File

@@ -25,6 +25,7 @@
#include "../object/ObjectRepository.h"
#include "../ParkImporter.h"
#include "../rct12/SawyerEncoding.h"
#include "../rct12/SawyerChunkReader.h"
extern "C"
{
@@ -110,7 +111,8 @@ public:
throw IOException("Invalid checksum.");
}
SawyerEncoding::ReadChunkTolerant(&_s6.header, sizeof(_s6.header), stream);
auto chunkReader = SawyerChunkReader(stream);
chunkReader.ReadChunk(&_s6.header, sizeof(_s6.header));
log_verbose("saved game classic_flag = 0x%02x\n", _s6.header.classic_flag);
if (isScenario)
@@ -119,7 +121,7 @@ public:
{
throw Exception("Park is not a scenario.");
}
SawyerEncoding::ReadChunkTolerant(&_s6.info, sizeof(_s6.info), stream);
chunkReader.ReadChunk(&_s6.info, sizeof(_s6.info));
}
else
{
@@ -134,29 +136,29 @@ public:
IObjectRepository * objectRepo = GetObjectRepository();
for (uint16 i = 0; i < _s6.header.num_packed_objects; i++)
{
objectRepo->TryExportPackedObject(stream);
objectRepo->ExportPackedObject(stream);
}
if (isScenario)
{
SawyerEncoding::ReadChunkTolerant(&_s6.objects, sizeof(_s6.objects), stream);
SawyerEncoding::ReadChunkTolerant(&_s6.elapsed_months, 16, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.map_elements, sizeof(_s6.map_elements), stream);
SawyerEncoding::ReadChunkTolerant(&_s6.next_free_map_element_pointer_index, 2560076, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.guests_in_park, 4, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.last_guests_in_park, 8, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.park_rating, 2, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.active_research_types, 1082, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.current_expenditure, 16, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.park_value, 4, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.completed_company_value, 483816, stream);
chunkReader.ReadChunk(&_s6.objects, sizeof(_s6.objects));
chunkReader.ReadChunk(&_s6.elapsed_months, 16);
chunkReader.ReadChunk(&_s6.map_elements, sizeof(_s6.map_elements));
chunkReader.ReadChunk(&_s6.next_free_map_element_pointer_index, 2560076);
chunkReader.ReadChunk(&_s6.guests_in_park, 4);
chunkReader.ReadChunk(&_s6.last_guests_in_park, 8);
chunkReader.ReadChunk(&_s6.park_rating, 2);
chunkReader.ReadChunk(&_s6.active_research_types, 1082);
chunkReader.ReadChunk(&_s6.current_expenditure, 16);
chunkReader.ReadChunk(&_s6.park_value, 4);
chunkReader.ReadChunk(&_s6.completed_company_value, 483816);
}
else
{
SawyerEncoding::ReadChunkTolerant(&_s6.objects, sizeof(_s6.objects), stream);
SawyerEncoding::ReadChunkTolerant(&_s6.elapsed_months, 16, stream);
SawyerEncoding::ReadChunkTolerant(&_s6.map_elements, sizeof(_s6.map_elements), stream);
SawyerEncoding::ReadChunkTolerant(&_s6.next_free_map_element_pointer_index, 3048816, stream);
chunkReader.ReadChunk(&_s6.objects, sizeof(_s6.objects));
chunkReader.ReadChunk(&_s6.elapsed_months, 16);
chunkReader.ReadChunk(&_s6.map_elements, sizeof(_s6.map_elements));
chunkReader.ReadChunk(&_s6.next_free_map_element_pointer_index, 3048816);
}
}

View File

@@ -26,7 +26,7 @@
#include "../core/Util.hpp"
#include "../ParkImporter.h"
#include "../PlatformEnvironment.h"
#include "../rct12/SawyerEncoding.h"
#include "../rct12/SawyerChunkReader.h"
#include "ScenarioRepository.h"
#include "ScenarioSources.h"
@@ -336,26 +336,18 @@ private:
{
// RCT2 scenario
auto fs = FileStream(path, FILE_MODE_OPEN);
rct_s6_header header;
if (SawyerEncoding::TryReadChunk(&header, &fs))
auto chunkReader = SawyerChunkReader(&fs);
rct_s6_header header = chunkReader.ReadChunkAs<rct_s6_header>();
if (header.type == S6_TYPE_SCENARIO)
{
if (header.type == S6_TYPE_SCENARIO)
{
rct_s6_info info;
if (SawyerEncoding::TryReadChunk(&info, &fs))
{
*entry = CreateNewScenarioEntry(path, timestamp, &info);
return true;
}
else
{
throw new IOException("Unable to read S6_INFO chunk.");
}
}
else
{
log_verbose("%s is not a scenario", path.c_str());
}
rct_s6_info info = chunkReader.ReadChunkAs<rct_s6_info>();
*entry = CreateNewScenarioEntry(path, timestamp, &info);
return true;
}
else
{
log_verbose("%s is not a scenario", path.c_str());
}
}
}