From 889b833af1e3d1798ec36cd0a438c0f15cee6e94 Mon Sep 17 00:00:00 2001 From: dP Date: Mon, 6 Jul 2020 16:47:03 +0300 Subject: [PATCH] Add CityMania savegame data (a bit broken for now) --- source.list | 5 + src/citymania/cm_bitstream.cpp | 87 +++++++ src/citymania/cm_bitstream.hpp | 49 ++++ src/citymania/cm_game.hpp | 3 +- src/citymania/cm_saveload.cpp | 440 +++++++++++++++++++++++++++++++++ src/citymania/cm_saveload.hpp | 11 + src/saveload/storage_sl.cpp | 3 +- 7 files changed, 595 insertions(+), 3 deletions(-) create mode 100644 src/citymania/cm_bitstream.cpp create mode 100644 src/citymania/cm_bitstream.hpp create mode 100644 src/citymania/cm_saveload.cpp create mode 100644 src/citymania/cm_saveload.hpp diff --git a/source.list b/source.list index 7aa7e52f5f..5944c757b9 100644 --- a/source.list +++ b/source.list @@ -1193,6 +1193,8 @@ sound/null_s.cpp thread.h # CityMania +citymania/cm_bitstream.hpp +citymania/cm_bitstream.cpp citymania/cm_console_cmds.hpp citymania/cm_console_cmds.cpp citymania/cm_event.hpp @@ -1203,5 +1205,8 @@ citymania/cm_game.cpp citymania/cm_main.hpp citymania/cm_main.cpp citymania/cm_type.hpp +citymania/cm_saveload.hpp +citymania/cm_saveload.cpp +citymania/cm_settings.hpp citymania/extensions/cmext_town.hpp citymania/extensions/cmext_company.hpp diff --git a/src/citymania/cm_bitstream.cpp b/src/citymania/cm_bitstream.cpp new file mode 100644 index 0000000000..7c9782dcda --- /dev/null +++ b/src/citymania/cm_bitstream.cpp @@ -0,0 +1,87 @@ +#include "../stdafx.h" + +#include "cm_bitstream.hpp" + +#include "../safeguards.h" + +namespace citymania { + +// Returns 8 higher bits of s-bit integer x +#define FIRST8(x, s) (((x) >> ((s) - 8)) & 0xFF) + +void BitOStream::WriteBytes(uint32 value, int amount) +{ + do { + v = (v << 8) | FIRST8(value, amount << 3); + f.push_back(FIRST8(v, c + 8)); + } while (--amount); +} + +void BitOStream::WriteBytes64(uint64 value, int amount) +{ + do { + v = (v << 8) | FIRST8(value, amount << 3); + f.push_back(FIRST8(v, c + 8)); + } while (--amount); +} + +void BitOStream::WriteMoney(Money value) { + this->WriteBytes64(value, 8); +} + + +/** + * Reserves memory for encoded data. Use to avoid reallocation of vector. + */ +void BitOStream::Reserve(int bytes) +{ + f.reserve(bytes); +} + +/** + * Returns encoded data + */ +const u8vector &BitOStream::GetVector() +{ + return f; +} + +/** + * Size of encoded data in bytes + */ +uint BitOStream::GetByteSize() const +{ + return f.size() + (c ? 1 : 0); +} + +BitIStreamUnexpectedEnd _bit_i_stream_unexpected_end; + +uint32 BitIStream::ReadBytes(uint amount) +{ + uint32 res = 0; + while (amount--) { + if (i >= f.size()) { + throw _bit_i_stream_unexpected_end; + } + res = (res << 8) | f[i++]; + } + return res; +} + +uint64 BitIStream::ReadBytes64(uint amount) +{ + uint64 res = 0; + while (amount--) { + if (i >= f.size()) { + throw _bit_i_stream_unexpected_end; + } + res = (res << 8) | f[i++]; + } + return res; +} + +Money BitIStream::ReadMoney() { + return (Money)this->ReadBytes64(8); +} + +} // namespace citymania diff --git a/src/citymania/cm_bitstream.hpp b/src/citymania/cm_bitstream.hpp new file mode 100644 index 0000000000..9b0748d483 --- /dev/null +++ b/src/citymania/cm_bitstream.hpp @@ -0,0 +1,49 @@ +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include "../economy_type.h" + +#include + +namespace citymania { + +typedef std::vector u8vector; + +class BitOStream { +protected: + u8vector f; + uint32 c; + uint32 v; + +public: + BitOStream(): c(0), v(0) {} + virtual ~BitOStream(){} + void Reserve(int bytes); + void WriteBytes(uint32 value, int amount); + void WriteBytes64(uint64 value, int amount); + void WriteMoney(Money value); + const u8vector &GetVector(); + uint GetByteSize() const; +}; + +class BitIStreamUnexpectedEnd: public std::exception { + virtual const char* what() const throw() { + return "Unexpected end of bit input stream"; + } +}; + +class BitIStream { +protected: + u8vector &f; + uint32 i; +public: + BitIStream(u8vector &data): f(data), i(0) {} + virtual ~BitIStream(){} + uint32 ReadBytes(uint amount); + uint64 ReadBytes64(uint amount); + Money ReadMoney(); +}; + +} // namespace citymania + +#endif diff --git a/src/citymania/cm_game.hpp b/src/citymania/cm_game.hpp index b7942a627e..a2ddbef41d 100644 --- a/src/citymania/cm_game.hpp +++ b/src/citymania/cm_game.hpp @@ -19,12 +19,11 @@ enum class TownGrowthTileState : uint8 { }; class Game { -protected: +public: typedef std::map TownsGrowthTilesIndex; TownsGrowthTilesIndex towns_growth_tiles_last_month; TownsGrowthTilesIndex towns_growth_tiles; -public: event::Dispatcher events; Game(); diff --git a/src/citymania/cm_saveload.cpp b/src/citymania/cm_saveload.cpp new file mode 100644 index 0000000000..c9b272a3a9 --- /dev/null +++ b/src/citymania/cm_saveload.cpp @@ -0,0 +1,440 @@ +#include "../stdafx.h" + +#include "../company_base.h" +#include "../date_func.h" +#include "../debug.h" +#include "../saveload/saveload.h" +#include "../town.h" + +#include "cm_bitstream.hpp" +#include "cm_saveload.hpp" +#include "cm_game.hpp" +#include "cm_main.hpp" + +namespace citymania { + +static const uint SAVEGAME_DATA_FORMAT_VERSION = 2; +static const uint CITYMANIA_GRFID = 0x534B0501U; // Luukland_Citybuilder grf id actually + + +// copied form storage_sl.cpp +static const SaveLoad _storage_desc[] = { + SLE_CONDVAR(PersistentStorage, grfid, SLE_UINT32, SLV_6, SL_MAX_VERSION), + SLE_CONDARR(PersistentStorage, storage, SLE_UINT32, 16, SLV_161, SLV_EXTEND_PERSISTENT_STORAGE), + SLE_CONDARR(PersistentStorage, storage, SLE_UINT32, 256, SLV_EXTEND_PERSISTENT_STORAGE, SL_MAX_VERSION), + SLE_END() +}; + + +static void EncodeTowns(BitOStream &bs) +{ + for (const Town *t : Town::Iterate()) { + bs.WriteBytes(t->cm.growing_by_chance, 1); + bs.WriteBytes(t->cm.houses_reconstructed_this_month, 2); + bs.WriteBytes(t->cm.houses_reconstructed_last_month, 2); + bs.WriteBytes(t->cm.houses_demolished_this_month, 2); + bs.WriteBytes(t->cm.houses_demolished_last_month, 2); + bs.WriteBytes(t->cm.hs_total, 4); + bs.WriteBytes(t->cm.hs_total_prev, 2); + bs.WriteBytes(t->cm.hs_last_month, 2); + bs.WriteBytes(t->cm.cs_total, 4); + bs.WriteBytes(t->cm.cs_total_prev, 2); + bs.WriteBytes(t->cm.cs_last_month, 2); + bs.WriteBytes(t->cm.hr_total, 4); + bs.WriteBytes(t->cm.hr_total_prev, 2); + bs.WriteBytes(t->cm.hr_last_month, 2); + } +} + +static void DecodeTowns(BitIStream &bs) +{ + for (Town *t : Town::Iterate()) { + t->cm.growing_by_chance = bs.ReadBytes(1); + t->cm.houses_reconstructed_this_month = bs.ReadBytes(2); + t->cm.houses_reconstructed_last_month = bs.ReadBytes(2); + t->cm.houses_demolished_this_month = bs.ReadBytes(2); + t->cm.houses_demolished_last_month = bs.ReadBytes(2); + t->cm.hs_total = bs.ReadBytes(2); + t->cm.hs_total_prev = bs.ReadBytes(2); + t->cm.hs_last_month = bs.ReadBytes(2); + t->cm.cs_total = bs.ReadBytes(2); + t->cm.cs_total_prev = bs.ReadBytes(2); + t->cm.cs_last_month = bs.ReadBytes(2); + t->cm.hr_total = bs.ReadBytes(2); + t->cm.hr_total_prev = bs.ReadBytes(2); + t->cm.hr_last_month = bs.ReadBytes(2); + } +} + +static void EncodeTownsGrowthTiles(BitOStream &bs, Game::TownsGrowthTilesIndex &tiles) +{ + bs.WriteBytes(tiles.size(), 4); + for (Game::TownsGrowthTilesIndex::iterator p = tiles.begin(); + p != tiles.end(); p++) { + bs.WriteBytes(p->first, 4); + bs.WriteBytes((uint8)p->second, 1); + } +} + +static void DecodeTownsGrowthTiles(BitIStream &bs, Game::TownsGrowthTilesIndex &tiles) +{ + uint n = bs.ReadBytes(4); + for (uint i = 0; i < n; i++) { + TileIndex tile = bs.ReadBytes(4); + TownGrowthTileState state = (TownGrowthTileState)bs.ReadBytes(1); + tiles[tile] = state; + } +} + +static void EncodeCompanies(BitOStream &bs) +{ + for (const Company *c : Company::Iterate()) { + bs.WriteBytes(c->cm.is_server, 1); + bs.WriteBytes(c->cm.is_scored, 1); + for (uint i = 0; i < NUM_CARGO; i++) + bs.WriteMoney(c->cur_economy.cm.cargo_income[i]); + for (uint j = 0; j < MAX_HISTORY_QUARTERS; j++) + for (uint i = 0; i < NUM_CARGO; i++) + bs.WriteMoney(c->old_economy[j].cm.cargo_income[i]); + } +} + +static void DecodeCompanies(BitIStream &bs) +{ + for (Company *c : Company::Iterate()) { + c->cm.is_server = bs.ReadBytes(1); + c->cm.is_scored = bs.ReadBytes(1); + for (uint i = 0; i < NUM_CARGO; i++) + c->cur_economy.cm.cargo_income[i] = bs.ReadMoney(); + for (uint j = 0; j < MAX_HISTORY_QUARTERS; j++) + for (uint i = 0; i < NUM_CARGO; i++) + c->old_economy[j].cm.cargo_income[i] = bs.ReadMoney(); + } +} + +void CBController_saveload_encode(BitOStream &bs) { + bs.WriteBytes(0 /* version */, 2); + // Controller::saveload_encode(bs); + bs.WriteBytes(0, 1); + bs.WriteBytes(0, 1); + // bs.WriteBytes(_settings_client.gui.cb_distance_check, 1); + bs.WriteBytes(0, 1); + bs.WriteBytes(0, 1); + bs.WriteBytes(0, 1); + bs.WriteBytes(0, 1); + bs.WriteBytes(0, 1); + bs.WriteBytes(0, 1); + std::vector cb_cargos; + // for(CargoID cargo = 0; cargo < NUM_CARGO; cargo++) { + // if (CB_GetReq(cargo) > 0) + // cb_cargos.push_back(cargo); + // } + + // for (auto cargo_id : cb_cargos) { + // // bs.WriteBytes(req.cargo_id, 1); + // // bs.WriteBytes(req.amount, 4); + // // bs.WriteBytes(req.from, 4); + // // bs.WriteBytes(req.decay, 1); + // bs.WriteBytes(cargo_id, 1); + // bs.WriteBytes(CB_GetReq(cargo_id), 4); + // bs.WriteBytes(CB_GetFrom(cargo_id), 4); + // bs.WriteBytes(CB_GetDecay(cargo_id), 1); + // } + // // uint16 cb_towns = 0; + // // ForEachCBTown([this, &cb_towns](Town *, Company *) { cb_towns++; }); + // bs.WriteBytes(Town::GetNumItems(), 2); + // for (Town *t : Town::Iterate()) { + // auto &tcb = t->cb; + // bs.WriteBytes(t->index, 2); + // bs.WriteBytes(tcb.pax_delivered, 4); + // bs.WriteBytes(tcb.mail_delivered, 4); + // bs.WriteBytes(tcb.pax_delivered_last_month, 4); + // bs.WriteBytes(tcb.mail_delivered_last_month, 4); + // bs.WriteBytes((uint8)tcb.growth_state, 1); + // bs.WriteBytes(tcb.shrink_effeciency, 1); + // bs.WriteBytes(tcb.shrink_rate, 2); + // bs.WriteBytes(tcb.shrink_counter, 2); + // for (auto cargo_id : cb_cargos) { + // bs.WriteBytes(tcb.stored[cargo_id], 4); + // bs.WriteBytes(tcb.delivered[cargo_id], 4); + // bs.WriteBytes(tcb.required[cargo_id], 4); + // bs.WriteBytes(tcb.delivered_last_month[cargo_id], 4); + // bs.WriteBytes(tcb.required_last_month[cargo_id], 4); + // } + // } +} + +bool CBController_saveload_decode(BitIStream &bs) { + auto version = bs.ReadBytes(2); + // if (version != 0) + // ConsoleError("Save uses incompatible CB data version {}", version); + // ConsoleError("Server is not supposed to load CB games"); + // if (!Controller::saveload_decode(bs)) + // return false; + bs.ReadBytes(1); /* _settings_game.citymania.cb.requirements_type */ + bs.ReadBytes(1); /* _settings_game.citymania.cb.acceptance_range */ + // _settings_client.gui.cb_distance_check = bs.ReadBytes(1); /* _settings_game.citymania.cb.acceptance_range */ + bs.ReadBytes(1); /* _settings_game.citymania.cb.storage_size */ + bs.ReadBytes(1); /* _settings_game.citymania.cb.town_protection_range */ + bs.ReadBytes(2); /* _settings_game.citymania.cb.claim_max_houses */ + bs.ReadBytes(1); /* _settings_game.citymania.cb.smooth_growth_rate */ + bs.ReadBytes(1); /* _settings_game.citymania.cb.allow_negative_growth */ + // _settings_game.citymania.cb.requirements.clear(); + auto n_cargos = bs.ReadBytes(1); + std::vector cb_cargos; + for (uint i = 0; i < n_cargos; i++) { + auto cargo_id = (CargoID)bs.ReadBytes(1); + if (cargo_id >= NUM_CARGO) { + DEBUG(sl, 0, "Invalid CargoID in CB towns cargo data (%u)", cargo_id); + // ConsoleError("Invalid CargoID in CB towns cargo data ({})", cargo_id); + return false; + } + cb_cargos.push_back(cargo_id); + auto required = bs.ReadBytes(4); + auto from = bs.ReadBytes(4); + auto decay = bs.ReadBytes(1); + // CB_SetRequirements(cargo_id, required, from, decay); + // _settings_game.citymania.cb.requirements.push_back(CBRequirement(cargo_id, from, required, decay, i, "")); + } + + auto n_towns = bs.ReadBytes(2); + for (uint i = 0; i < n_towns; i++) { + auto town_id = bs.ReadBytes(2); + auto t = Town::GetIfValid(town_id); + if (!t) { + DEBUG(sl, 0, "Invalid TownID in CB towns cargo data (%u)", town_id); + // ConsoleError("Invalid TownID in CB towns cargo data ({})", town_id); + return false; + } + + // auto &tcb = t->cb; + // tcb.pax_delivered = bs.ReadBytes(4); + // tcb.mail_delivered = bs.ReadBytes(4); + // tcb.pax_delivered_last_month = bs.ReadBytes(4); + // tcb.mail_delivered_last_month = bs.ReadBytes(4); + // tcb.growth_state = (TownGrowthState)bs.ReadBytes(1); + // tcb.shrink_effeciency = bs.ReadBytes(1); + // tcb.shrink_rate = bs.ReadBytes(2); + // tcb.shrink_counter = bs.ReadBytes(2); + // for (auto cargo_id : cb_cargos) { + // tcb.stored[cargo_id] = bs.ReadBytes(4); + // tcb.delivered[cargo_id] = bs.ReadBytes(4); + // tcb.required[cargo_id] = bs.ReadBytes(4); + // tcb.delivered_last_month[cargo_id] = bs.ReadBytes(4); + // tcb.required_last_month[cargo_id] = bs.ReadBytes(4); + // } + }; + return true; +} + +uint8 _controller_type = 0; +uint8 _game_type = 0; +uint16 _last_client_version = 1512; + +static u8vector EncodeData() { + BitOStream bs; + bs.Reserve(1000); + bs.WriteBytes(SAVEGAME_DATA_FORMAT_VERSION, 2); + bs.WriteBytes(_last_client_version, 2); + bs.WriteBytes(_controller_type, 1); + bs.WriteBytes(_date, 4); // Just in case we'll need to detect that game + bs.WriteBytes(_date_fract, 1); // was saved by unmodified client + bs.WriteBytes(_game_type, 1); + bs.WriteBytes(0, 3); // Reserved + bs.WriteBytes(0, 4); // Reserved + + EncodeCompanies(bs); + EncodeTowns(bs); + EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles); + EncodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); + if (_controller_type == 4) + CBController_saveload_encode(bs); + + return bs.GetVector(); +} + +static void DecodeDataV2(BitIStream &bs) { + DecodeCompanies(bs); + DecodeTowns(bs); + DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles); + DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); + if (_controller_type == 4) CBController_saveload_decode(bs); +} + +static void DecodeTownsCargoV1(BitIStream &bs) +{ + bs.ReadBytes(1);bs.ReadBytes(1); + // CB_SetStorage(bs.ReadBytes(1)); + // _settings_client.gui.cb_distance_check = bs.ReadBytes(1); + uint n_cb_cargos = bs.ReadBytes(1); + // CB_ResetRequirements(); + std::vector cb_cargos; + for (uint i = 0; i < n_cb_cargos; i++) { + uint cargo = bs.ReadBytes(1); + if (cargo >= NUM_CARGO) { + DEBUG(sl, 0, "Invalid CargoID in CB towns cargo data (%u)", cargo); + return; + } + cb_cargos.push_back(cargo); + uint req = bs.ReadBytes(4); + uint from = bs.ReadBytes(4); + uint decay = bs.ReadBytes(1); + // CB_SetRequirements(cargo, req, from, decay); + } + Town *t; + uint n_affected_towns = bs.ReadBytes(2); + for (uint i = 0; i < n_affected_towns; i++) { + uint town_id = bs.ReadBytes(2); + t = Town::GetIfValid(town_id); + if (!t) { + DEBUG(sl, 0, "Invalid TownID in CB towns cargo data (%u)", town_id); + return; + } + // auto &tcb = t->cb; + // for (auto cargo_id : cb_cargos) { + // tcb.stored[cargo_id] = bs.ReadBytes(4); + // tcb.delivered_last_month[cargo_id] = bs.ReadBytes(4); + // tcb.delivered[cargo_id] = bs.ReadBytes(4); + // bs.ReadBytes(1); /* delivered enough */ + + // tcb.required[cargo_id] = 0; + // tcb.required_last_month[cargo_id] = 0; + // } + } +} + +static void DecodeDataV1(BitIStream &bs) { + if (_controller_type != 0) DecodeTownsCargoV1(bs); + for (Town *t : Town::Iterate()) { + t->cm.growing_by_chance = bs.ReadBytes(1); + t->cm.houses_reconstructed_this_month = bs.ReadBytes(2); + t->cm.houses_reconstructed_last_month = bs.ReadBytes(2); + t->cm.houses_demolished_this_month = bs.ReadBytes(2); + t->cm.houses_demolished_last_month = bs.ReadBytes(2); + } + + Town *t; + uint n_affected_towns = bs.ReadBytes(2); + for (uint i = 0; i < n_affected_towns; i++) { + uint town_id = bs.ReadBytes(2); + t = Town::Get(town_id); + if (!t) { + DEBUG(sl, 0, "Invalid TownID in CB towns layout errors (%u)", town_id); + continue; + } + t->cm.hs_total = bs.ReadBytes(2); + t->cm.hs_total_prev = bs.ReadBytes(2); + t->cm.hs_last_month = bs.ReadBytes(2); + t->cm.cs_total = bs.ReadBytes(2); + t->cm.cs_total_prev = bs.ReadBytes(2); + t->cm.cs_last_month = bs.ReadBytes(2); + t->cm.hr_total = bs.ReadBytes(2); + t->cm.hr_total_prev = bs.ReadBytes(2); + t->cm.hr_last_month = bs.ReadBytes(2); + } + DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles); + DecodeTownsGrowthTiles(bs, _game->towns_growth_tiles_last_month); +} + +static void DecodeData(u8vector &data) { + if (data.size() == 0) { + DEBUG(sl, 2, "No CityMania save data"); + return; + } + DEBUG(sl, 2, "CityMania save data takes %lu bytes", data.size()); + BitIStream bs(data); + try { + uint version = bs.ReadBytes(2); + if (version > SAVEGAME_DATA_FORMAT_VERSION) { + DEBUG(sl, 0, "Savegame was made with newer version of client, extra save data was not loaded"); + return; + } + DEBUG(sl, 2, "CityMania savegame data version %u", version); + _last_client_version = bs.ReadBytes(2); + _controller_type = bs.ReadBytes(1); + if (version <= 1) _controller_type = (_controller_type ? 4 : 0); + int32 date = bs.ReadBytes(4); + uint32 date_fract = bs.ReadBytes(1); + if (date != _date || date_fract != _date_fract) { + DEBUG(sl, 0, "Savegame was run in unmodified client, extra save data " + "preserved, but may not be accurate"); + } + _game_type = bs.ReadBytes(1); + bs.ReadBytes(3); // reserved + bs.ReadBytes(4); // reserved + if (version == 1) DecodeDataV1(bs); + else DecodeDataV2(bs); + } + catch (BitIStreamUnexpectedEnd &e) { + DEBUG(sl, 0, "Invalid CityMania save data"); + } +} + +void Save_PSAC() { + /* Write the industries */ + for (PersistentStorage *ps : PersistentStorage::Iterate()) { + if (ps->grfid == CITYMANIA_GRFID) { + continue; + } + ps->ClearChanges(); + SlSetArrayIndex(ps->index); + SlObject(ps, _storage_desc); + } + + uint32 grfid = CITYMANIA_GRFID; + u8vector data = EncodeData(); + int n_chunks = (data.size() + 1023) / 1024; + data.resize(n_chunks * 1024); + DEBUG(sl, 2, "Citybuilder data takes %u bytes", (uint)data.size()); + uint8 *ptr = &data[0]; + SaveLoadGlobVarList _desc[] = { + SLEG_CONDVAR(grfid, SLE_UINT32, SLV_6, SL_MAX_VERSION), + SLEG_CONDARR(*ptr, SLE_UINT32, 256, SLV_EXTEND_PERSISTENT_STORAGE, SL_MAX_VERSION), + SLEG_END() + }; + + uint index = 0; + for (PersistentStorage *ps : PersistentStorage::Iterate()) { + if (ps->grfid != CITYMANIA_GRFID) + index = max(index, ps->index + 1); + } + + for (int i = 0; i < n_chunks; i++, ptr += 1024) { + _desc[1].address = (void *)ptr; + SlSetArrayIndex(index + i); + SlGlobList(_desc); + } +} + +void Load_PSAC() +{ + int index; + + /* + CITYMANIA_GRFID is used to hide extra data in persitant storages. + To save a bit of memory we only keep at most one PS with this + grfid and it is later discarded on save. + */ + PersistentStorage *ps = NULL; + u8vector cmdata; + uint chunk_size = IsSavegameVersionBefore(SLV_EXTEND_PERSISTENT_STORAGE) ? 64 : 1024; + + while ((index = SlIterateArray()) != -1) { + if (ps == NULL) { + assert(PersistentStorage::CanAllocateItem()); + ps = new (index) PersistentStorage(0, 0, 0); + } + SlObject(ps, _storage_desc); + + if (ps->grfid == CITYMANIA_GRFID) { + uint8 *data = (uint8 *)(ps->storage); + cmdata.insert(cmdata.end(), data, data + chunk_size); + } else { + ps = NULL; + } + } + + DecodeData(cmdata); +} + +} // namespace citymania diff --git a/src/citymania/cm_saveload.hpp b/src/citymania/cm_saveload.hpp new file mode 100644 index 0000000000..569feefb6a --- /dev/null +++ b/src/citymania/cm_saveload.hpp @@ -0,0 +1,11 @@ +#ifndef CITYMANIA_SAVELOAD_HPP +#define CITYMANIA_SAVELOAD_HPP + +namespace citymania { + +void Save_PSAC(); +void Load_PSAC(); + +} // namespace citymania + +#endif diff --git a/src/saveload/storage_sl.cpp b/src/saveload/storage_sl.cpp index 7abe396f38..1be1047ee3 100644 --- a/src/saveload/storage_sl.cpp +++ b/src/saveload/storage_sl.cpp @@ -9,6 +9,7 @@ #include "../stdafx.h" #include "../newgrf_storage.h" +#include "../citymania/cm_saveload.hpp" #include "saveload.h" #include "../safeguards.h" @@ -46,5 +47,5 @@ static void Save_PSAC() /** Chunk handler for persistent storages. */ extern const ChunkHandler _persistent_storage_chunk_handlers[] = { - { 'PSAC', Save_PSAC, Load_PSAC, nullptr, nullptr, CH_ARRAY | CH_LAST}, + { 'PSAC', citymania::Save_PSAC, citymania::Load_PSAC, nullptr, nullptr, CH_ARRAY | CH_LAST}, };