diff options
author | smatz <smatz@openttd.org> | 2009-01-04 15:32:25 +0000 |
---|---|---|
committer | smatz <smatz@openttd.org> | 2009-01-04 15:32:25 +0000 |
commit | 7368c740a646c958797b5dff90d6c5b51236e2a4 (patch) | |
tree | 56e0ff1f4048e467cf123e92ca788c3c4bbc0f94 /src/saveload | |
parent | c9e8fd307e36b3d35f5bf7d01cffe64b1e75b846 (diff) | |
download | openttd-7368c740a646c958797b5dff90d6c5b51236e2a4.tar.xz |
(svn r14828) -Codechange: move most of save/load-specific code to separate files
Diffstat (limited to 'src/saveload')
28 files changed, 8823 insertions, 0 deletions
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp new file mode 100644 index 000000000..c4ecb664a --- /dev/null +++ b/src/saveload/afterload.cpp @@ -0,0 +1,1704 @@ +/* $Id$ */ + +/** @file afterload.cpp Code updating data after game load */ + +#include "../stdafx.h" +#include "../strings_type.h" +#include "../tile_type.h" +#include "../tile_map.h" +#include "../map_type.h" +#include "../road_map.h" +#include "../town.h" +#include "../void_map.h" +#include "../signs_base.h" +#include "../window_func.h" +#include "../station_func.h" +#include "../company_base.h" +#include "../fios.h" +#include "../date_func.h" +#include "../engine_func.h" +#include "../train.h" +#include "../string_func.h" +#include "../newgrf_config.h" +#include "../gamelog.h" +#include "../waypoint.h" +#include "../station_map.h" +#include "../station_base.h" +#include "../tunnelbridge_map.h" +#include "../debug.h" +#include "../network/network.h" +#include "../openttd.h" +#include "../gfxinit.h" +#include "../gfx_func.h" +#include "../functions.h" +#include "../industry_map.h" +#include "../town_map.h" +#include "../clear_map.h" +#include "../engine_base.h" +#include "../landscape.h" +#include "../vehicle_func.h" +#include "../newgrf_station.h" +#include "../yapf/yapf.hpp" +#include "../elrail_func.h" +#include "../signs_func.h" +#include "../newgrf_house.h" +#include "../aircraft.h" +#include "../unmovable_map.h" +#include "../tree_map.h" +#include "../company_func.h" +#include "../command_func.h" +#include "../road_cmd.h" + +#include "table/strings.h" + +#include "saveload.h" +#include "saveload_internal.h" + +#include <signal.h> + +extern StringID _switch_mode_errorstr; +extern Company *DoStartupNewCompany(bool is_ai); +extern void InitializeRailGUI(); + +/** + * Makes a tile canal or water depending on the surroundings. + * + * Must only be used for converting old savegames. Use WaterClass now. + * + * This as for example docks and shipdepots do not store + * whether the tile used to be canal or 'normal' water. + * @param t the tile to change. + * @param o the owner of the new tile. + * @param include_invalid_water_class Also consider WATER_CLASS_INVALID, i.e. industry tiles on land + */ +void SetWaterClassDependingOnSurroundings(TileIndex t, bool include_invalid_water_class) +{ + /* If the slope is not flat, we always assume 'land' (if allowed). Also for one-corner-raised-shores. + * Note: Wrt. autosloping under industry tiles this is the most fool-proof behaviour. */ + if (GetTileSlope(t, NULL) != SLOPE_FLAT) { + if (include_invalid_water_class) { + SetWaterClass(t, WATER_CLASS_INVALID); + return; + } else { + NOT_REACHED(); + } + } + + /* Mark tile dirty in all cases */ + MarkTileDirtyByTile(t); + + if (TileX(t) == 0 || TileY(t) == 0 || TileX(t) == MapMaxX() - 1 || TileY(t) == MapMaxY() - 1) { + /* tiles at map borders are always WATER_CLASS_SEA */ + SetWaterClass(t, WATER_CLASS_SEA); + return; + } + + bool has_water = false; + bool has_canal = false; + bool has_river = false; + + for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { + TileIndex neighbour = TileAddByDiagDir(t, dir); + switch (GetTileType(neighbour)) { + case MP_WATER: + /* clear water and shipdepots have already a WaterClass associated */ + if (IsCoast(neighbour)) { + has_water = true; + } else if (!IsLock(neighbour)) { + switch (GetWaterClass(neighbour)) { + case WATER_CLASS_SEA: has_water = true; break; + case WATER_CLASS_CANAL: has_canal = true; break; + case WATER_CLASS_RIVER: has_river = true; break; + default: NOT_REACHED(); + } + } + break; + + case MP_RAILWAY: + /* Shore or flooded halftile */ + has_water |= (GetRailGroundType(neighbour) == RAIL_GROUND_WATER); + break; + + case MP_TREES: + /* trees on shore */ + has_water |= (GetTreeGround(neighbour) == TREE_GROUND_SHORE); + break; + + default: break; + } + } + + if (!has_water && !has_canal && !has_river && include_invalid_water_class) { + SetWaterClass(t, WATER_CLASS_INVALID); + return; + } + + if (has_river && !has_canal) { + SetWaterClass(t, WATER_CLASS_RIVER); + } else if (has_canal || !has_water) { + SetWaterClass(t, WATER_CLASS_CANAL); + } else { + SetWaterClass(t, WATER_CLASS_SEA); + } +} + +static void ConvertTownOwner() +{ + for (TileIndex tile = 0; tile != MapSize(); tile++) { + switch (GetTileType(tile)) { + case MP_ROAD: + if (GB(_m[tile].m5, 4, 2) == ROAD_TILE_CROSSING && HasBit(_m[tile].m3, 7)) { + _m[tile].m3 = OWNER_TOWN; + } + /* FALLTHROUGH */ + + case MP_TUNNELBRIDGE: + if (GetTileOwner(tile) & 0x80) SetTileOwner(tile, OWNER_TOWN); + break; + + default: break; + } + } +} + +/* since savegame version 4.1, exclusive transport rights are stored at towns */ +static void UpdateExclusiveRights() +{ + Town *t; + + FOR_ALL_TOWNS(t) { + t->exclusivity = INVALID_COMPANY; + } + + /* FIXME old exclusive rights status is not being imported (stored in s->blocked_months_obsolete) + * could be implemented this way: + * 1.) Go through all stations + * Build an array town_blocked[ town_id ][ company_id ] + * that stores if at least one station in that town is blocked for a company + * 2.) Go through that array, if you find a town that is not blocked for + * one company, but for all others, then give him exclusivity. + */ +} + +static const byte convert_currency[] = { + 0, 1, 12, 8, 3, + 10, 14, 19, 4, 5, + 9, 11, 13, 6, 17, + 16, 22, 21, 7, 15, + 18, 2, 20, +}; + +/* since savegame version 4.2 the currencies are arranged differently */ +static void UpdateCurrencies() +{ + _settings_game.locale.currency = convert_currency[_settings_game.locale.currency]; +} + +/* Up to revision 1413 the invisible tiles at the southern border have not been + * MP_VOID, even though they should have. This is fixed by this function + */ +static void UpdateVoidTiles() +{ + uint i; + + for (i = 0; i < MapMaxY(); ++i) MakeVoid(i * MapSizeX() + MapMaxX()); + for (i = 0; i < MapSizeX(); ++i) MakeVoid(MapSizeX() * MapMaxY() + i); +} + +/* since savegame version 6.0 each sign has an "owner", signs without owner (from old games are set to 255) */ +static void UpdateSignOwner() +{ + Sign *si; + + FOR_ALL_SIGNS(si) si->owner = OWNER_NONE; +} + +static inline RailType UpdateRailType(RailType rt, RailType min) +{ + return rt >= min ? (RailType)(rt + 1): rt; +} + +/** + * Initialization of the windows and several kinds of caches. + * This is not done directly in AfterLoadGame because these + * functions require that all saveload conversions have been + * done. As people tend to add savegame conversion stuff after + * the intialization of the windows and caches quite some bugs + * had been made. + * Moving this out of there is both cleaner and less bug-prone. + * + * @return true if everything went according to plan, otherwise false. + */ +static bool InitializeWindowsAndCaches() +{ + /* Initialize windows */ + ResetWindowSystem(); + SetupColorsAndInitialWindow(); + + ResetViewportAfterLoadGame(); + + /* Update coordinates of the signs. */ + UpdateAllStationVirtCoord(); + UpdateAllSignVirtCoords(); + UpdateAllTownVirtCoords(); + UpdateAllWaypointSigns(); + + Company *c; + FOR_ALL_COMPANIES(c) { + /* For each company, verify (while loading a scenario) that the inauguration date is the current year and set it + * accordingly if it is not the case. No need to set it on companies that are not been used already, + * thus the MIN_YEAR (which is really nothing more than Zero, initialized value) test */ + if (_file_to_saveload.filetype == FT_SCENARIO && c->inaugurated_year != MIN_YEAR) { + c->inaugurated_year = _cur_year; + } + } + + SetCachedEngineCounts(); + + /* Towns have a noise controlled number of airports system + * So each airport's noise value must be added to the town->noise_reached value + * Reset each town's noise_reached value to '0' before. */ + UpdateAirportsNoise(); + + CheckTrainsLengths(); + + return true; +} + +/** + * Signal handler used to give a user a more useful report for crashes during + * the savegame loading process; especially when there's problems with the + * NewGRFs that are required by the savegame. + * @param unused well... unused + */ +void CDECL HandleSavegameLoadCrash(int unused) +{ + char buffer[8192]; + char *p = buffer; + p += seprintf(p, lastof(buffer), + "Loading your savegame caused OpenTTD to crash.\n" + "This is most likely caused by a missing NewGRF or a NewGRF that has been\n" + "loaded as replacement for a missing NewGRF. OpenTTD cannot easily\n" + "determine whether a replacement NewGRF is of a newer or older version.\n" + "It will load a NewGRF with the same GRF ID as the missing NewGRF. This\n" + "means that if the author makes incompatible NewGRFs with the same GRF ID\n" + "OpenTTD cannot magically do the right thing. In most cases OpenTTD will\n" + "load the savegame and not crash, but this is an exception.\n" + "Please load the savegame with the appropriate NewGRFs. When loading a\n" + "savegame still crashes when all NewGRFs are found you should file a\n" + "bug report. The missing NewGRFs are:\n"); + + for (GRFConfig *c = _grfconfig; c != NULL; c = c->next) { + if (HasBit(c->flags, GCF_COMPATIBLE)) { + char buf[40]; + md5sumToString(buf, lastof(buf), c->md5sum); + p += seprintf(p, lastof(buffer), "NewGRF %08X (%s) not found; checksum %s. Tried another NewGRF with same GRF ID\n", BSWAP32(c->grfid), c->filename, buf); + } + if (c->status == GCS_NOT_FOUND) { + char buf[40]; + md5sumToString(buf, lastof(buf), c->md5sum); + p += seprintf(p, lastof(buffer), "NewGRF %08X (%s) not found; checksum %s\n", BSWAP32(c->grfid), c->filename, buf); + } + } + + ShowInfo(buffer); +} + + +bool AfterLoadGame() +{ + typedef void (CDECL *SignalHandlerPointer)(int); + SignalHandlerPointer prev_segfault = signal(SIGSEGV, HandleSavegameLoadCrash); + SignalHandlerPointer prev_abort = signal(SIGABRT, HandleSavegameLoadCrash); + + TileIndex map_size = MapSize(); + Company *c; + + if (CheckSavegameVersion(98)) GamelogOldver(); + + GamelogTestRevision(); + GamelogTestMode(); + + if (CheckSavegameVersion(98)) GamelogGRFAddList(_grfconfig); + + /* in very old versions, size of train stations was stored differently */ + if (CheckSavegameVersion(2)) { + Station *st; + FOR_ALL_STATIONS(st) { + if (st->train_tile != 0 && st->trainst_h == 0) { + extern SavegameType _savegame_type; + uint n = _savegame_type == SGT_OTTD ? 4 : 3; // OTTD uses 4 bits per dimensions, TTD 3 bits + uint w = GB(st->trainst_w, n, n); + uint h = GB(st->trainst_w, 0, n); + + if (GetRailStationAxis(st->train_tile) != AXIS_X) Swap(w, h); + + st->trainst_w = w; + st->trainst_h = h; + + assert(GetStationIndex(st->train_tile + TileDiffXY(w - 1, h - 1)) == st->index); + } + } + } + + /* in version 2.1 of the savegame, town owner was unified. */ + if (CheckSavegameVersionOldStyle(2, 1)) ConvertTownOwner(); + + /* from version 4.1 of the savegame, exclusive rights are stored at towns */ + if (CheckSavegameVersionOldStyle(4, 1)) UpdateExclusiveRights(); + + /* from version 4.2 of the savegame, currencies are in a different order */ + if (CheckSavegameVersionOldStyle(4, 2)) UpdateCurrencies(); + + /* from version 6.1 of the savegame, signs have an "owner" */ + if (CheckSavegameVersionOldStyle(6, 1)) UpdateSignOwner(); + + /* In old version there seems to be a problem that water is owned by + * OWNER_NONE, not OWNER_WATER.. I can't replicate it for the current + * (4.3) version, so I just check when versions are older, and then + * walk through the whole map.. */ + if (CheckSavegameVersionOldStyle(4, 3)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_WATER) && GetTileOwner(t) >= MAX_COMPANIES) { + SetTileOwner(t, OWNER_WATER); + } + } + } + + if (CheckSavegameVersion(84)) { + FOR_ALL_COMPANIES(c) { + c->name = CopyFromOldName(c->name_1); + if (c->name != NULL) c->name_1 = STR_SV_UNNAMED; + c->president_name = CopyFromOldName(c->president_name_1); + if (c->president_name != NULL) c->president_name_1 = SPECSTR_PRESIDENT_NAME; + } + + Station *st; + FOR_ALL_STATIONS(st) { + st->name = CopyFromOldName(st->string_id); + /* generating new name would be too much work for little effect, use the station name fallback */ + if (st->name != NULL) st->string_id = STR_SV_STNAME_FALLBACK; + } + + Town *t; + FOR_ALL_TOWNS(t) { + t->name = CopyFromOldName(t->townnametype); + if (t->name != NULL) t->townnametype = SPECSTR_TOWNNAME_START + _settings_game.game_creation.town_name; + } + + Waypoint *wp; + FOR_ALL_WAYPOINTS(wp) { + wp->name = CopyFromOldName(wp->string); + wp->string = STR_EMPTY; + } + + for (uint i = 0; i < GetSignPoolSize(); i++) { + /* invalid signs are determined by si->ower == INVALID_COMPANY now */ + Sign *si = GetSign(i); + if (!si->IsValid() && si->name != NULL) { + si->owner = OWNER_NONE; + } + } + } + + /* From this point the old names array is cleared. */ + ResetOldNames(); + + if (CheckSavegameVersion(106)) { + /* no station is determined by 'tile == INVALID_TILE' now (instead of '0') */ + Station *st; + FOR_ALL_STATIONS(st) { + if (st->airport_tile == 0) st->airport_tile = INVALID_TILE; + if (st->dock_tile == 0) st->dock_tile = INVALID_TILE; + if (st->train_tile == 0) st->train_tile = INVALID_TILE; + } + + /* the same applies to Company::location_of_HQ */ + Company *c; + FOR_ALL_COMPANIES(c) { + if (c->location_of_HQ == 0 || (CheckSavegameVersion(4) && c->location_of_HQ == 0xFFFF)) { + c->location_of_HQ = INVALID_TILE; + } + } + } + + /* convert road side to my format. */ + if (_settings_game.vehicle.road_side) _settings_game.vehicle.road_side = 1; + + /* Check if all NewGRFs are present, we are very strict in MP mode */ + GRFListCompatibility gcf_res = IsGoodGRFConfigList(); + if (_networking && gcf_res != GLC_ALL_GOOD) { + SetSaveLoadError(STR_NETWORK_ERR_CLIENT_NEWGRF_MISMATCH); + /* Restore the signals */ + signal(SIGSEGV, prev_segfault); + signal(SIGABRT, prev_abort); + return false; + } + + switch (gcf_res) { + case GLC_COMPATIBLE: _switch_mode_errorstr = STR_NEWGRF_COMPATIBLE_LOAD_WARNING; break; + case GLC_NOT_FOUND: _switch_mode_errorstr = STR_NEWGRF_DISABLED_WARNING; _pause_game = -1; break; + default: break; + } + + /* Update current year + * must be done before loading sprites as some newgrfs check it */ + SetDate(_date); + + /* Force dynamic engines off when loading older savegames */ + if (CheckSavegameVersion(95)) _settings_game.vehicle.dynamic_engines = 0; + + /* Load the sprites */ + GfxLoadSprites(); + LoadStringWidthTable(); + + /* Copy temporary data to Engine pool */ + CopyTempEngineData(); + + /* Connect front and rear engines of multiheaded trains and converts + * subtype to the new format */ + if (CheckSavegameVersionOldStyle(17, 1)) ConvertOldMultiheadToNew(); + + /* Connect front and rear engines of multiheaded trains */ + ConnectMultiheadedTrains(); + + /* reinit the landscape variables (landscape might have changed) */ + InitializeLandscapeVariables(true); + + /* Update all vehicles */ + AfterLoadVehicles(true); + + /* Update all waypoints */ + if (CheckSavegameVersion(12)) FixOldWaypoints(); + + /* in version 2.2 of the savegame, we have new airports */ + if (CheckSavegameVersionOldStyle(2, 2)) UpdateOldAircraft(); + + AfterLoadTown(); + + /* make sure there is a town in the game */ + if (_game_mode == GM_NORMAL && !ClosestTownFromTile(0, UINT_MAX)) { + SetSaveLoadError(STR_NO_TOWN_IN_SCENARIO); + /* Restore the signals */ + signal(SIGSEGV, prev_segfault); + signal(SIGABRT, prev_abort); + return false; + } + + /* The void tiles on the southern border used to belong to a wrong class (pre 4.3). + * This problem appears in savegame version 21 too, see r3455. But after loading the + * savegame and saving again, the buggy map array could be converted to new savegame + * version. It didn't show up before r12070. */ + if (CheckSavegameVersion(87)) UpdateVoidTiles(); + + /* If Load Scenario / New (Scenario) Game is used, + * a company does not exist yet. So create one here. + * 1 exeption: network-games. Those can have 0 companies + * But this exeption is not true for non dedicated network_servers! */ + if (!IsValidCompanyID(COMPANY_FIRST) && (!_networking || (_networking && _network_server && !_network_dedicated))) + DoStartupNewCompany(false); + + if (CheckSavegameVersion(72)) { + /* Locks/shiplifts in very old savegames had OWNER_WATER as owner */ + for (TileIndex t = 0; t < MapSize(); t++) { + switch (GetTileType(t)) { + default: break; + + case MP_WATER: + if (GetWaterTileType(t) == WATER_TILE_LOCK && GetTileOwner(t) == OWNER_WATER) SetTileOwner(t, OWNER_NONE); + break; + + case MP_STATION: { + if (HasBit(_m[t].m6, 3)) SetBit(_m[t].m6, 2); + StationGfx gfx = GetStationGfx(t); + StationType st; + if ( IsInsideMM(gfx, 0, 8)) { // Railway station + st = STATION_RAIL; + SetStationGfx(t, gfx - 0); + } else if (IsInsideMM(gfx, 8, 67)) { // Airport + st = STATION_AIRPORT; + SetStationGfx(t, gfx - 8); + } else if (IsInsideMM(gfx, 67, 71)) { // Truck + st = STATION_TRUCK; + SetStationGfx(t, gfx - 67); + } else if (IsInsideMM(gfx, 71, 75)) { // Bus + st = STATION_BUS; + SetStationGfx(t, gfx - 71); + } else if (gfx == 75) { // Oil rig + st = STATION_OILRIG; + SetStationGfx(t, gfx - 75); + } else if (IsInsideMM(gfx, 76, 82)) { // Dock + st = STATION_DOCK; + SetStationGfx(t, gfx - 76); + } else if (gfx == 82) { // Buoy + st = STATION_BUOY; + SetStationGfx(t, gfx - 82); + } else if (IsInsideMM(gfx, 83, 168)) { // Extended airport + st = STATION_AIRPORT; + SetStationGfx(t, gfx - 83 + 67 - 8); + } else if (IsInsideMM(gfx, 168, 170)) { // Drive through truck + st = STATION_TRUCK; + SetStationGfx(t, gfx - 168 + GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET); + } else if (IsInsideMM(gfx, 170, 172)) { // Drive through bus + st = STATION_BUS; + SetStationGfx(t, gfx - 170 + GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET); + } else { + /* Restore the signals */ + signal(SIGSEGV, prev_segfault); + signal(SIGABRT, prev_abort); + return false; + } + SB(_m[t].m6, 3, 3, st); + } break; + } + } + } + + for (TileIndex t = 0; t < map_size; t++) { + switch (GetTileType(t)) { + case MP_STATION: { + Station *st = GetStationByTile(t); + + /* Set up station spread; buoys do not have one */ + if (!IsBuoy(t)) st->rect.BeforeAddTile(t, StationRect::ADD_FORCE); + + switch (GetStationType(t)) { + case STATION_TRUCK: + case STATION_BUS: + if (CheckSavegameVersion(6)) { + /* From this version on there can be multiple road stops of the + * same type per station. Convert the existing stops to the new + * internal data structure. */ + RoadStop *rs = new RoadStop(t); + if (rs == NULL) error("Too many road stops in savegame"); + + RoadStop **head = + IsTruckStop(t) ? &st->truck_stops : &st->bus_stops; + *head = rs; + } + break; + + case STATION_OILRIG: { + /* Very old savegames sometimes have phantom oil rigs, i.e. + * an oil rig which got shut down, but not completly removed from + * the map + */ + TileIndex t1 = TILE_ADDXY(t, 0, 1); + if (IsTileType(t1, MP_INDUSTRY) && + GetIndustryGfx(t1) == GFX_OILRIG_1) { + /* The internal encoding of oil rigs was changed twice. + * It was 3 (till 2.2) and later 5 (till 5.1). + * Setting it unconditionally does not hurt. + */ + GetStationByTile(t)->airport_type = AT_OILRIG; + } else { + DeleteOilRig(t); + } + break; + } + + default: break; + } + break; + } + + default: break; + } + } + + /* In version 6.1 we put the town index in the map-array. To do this, we need + * to use m2 (16bit big), so we need to clean m2, and that is where this is + * all about ;) */ + if (CheckSavegameVersionOldStyle(6, 1)) { + for (TileIndex t = 0; t < map_size; t++) { + switch (GetTileType(t)) { + case MP_HOUSE: + _m[t].m4 = _m[t].m2; + SetTownIndex(t, CalcClosestTownFromTile(t, UINT_MAX)->index); + break; + + case MP_ROAD: + _m[t].m4 |= (_m[t].m2 << 4); + if ((GB(_m[t].m5, 4, 2) == ROAD_TILE_CROSSING ? (Owner)_m[t].m3 : GetTileOwner(t)) == OWNER_TOWN) { + SetTownIndex(t, CalcClosestTownFromTile(t, UINT_MAX)->index); + } else { + SetTownIndex(t, 0); + } + break; + + default: break; + } + } + } + + /* From version 9.0, we update the max passengers of a town (was sometimes negative + * before that. */ + if (CheckSavegameVersion(9)) { + Town *t; + FOR_ALL_TOWNS(t) UpdateTownMaxPass(t); + } + + /* From version 16.0, we included autorenew on engines, which are now saved, but + * of course, we do need to initialize them for older savegames. */ + if (CheckSavegameVersion(16)) { + FOR_ALL_COMPANIES(c) { + c->engine_renew_list = NULL; + c->engine_renew = false; + c->engine_renew_months = -6; + c->engine_renew_money = 100000; + } + + /* When loading a game, _local_company is not yet set to the correct value. + * However, in a dedicated server we are a spectator, so nothing needs to + * happen. In case we are not a dedicated server, the local company always + * becomes company 0, unless we are in the scenario editor where all the + * companies are 'invalid'. + */ + if (!_network_dedicated && IsValidCompanyID(COMPANY_FIRST)) { + c = GetCompany(COMPANY_FIRST); + c->engine_renew = _settings_client.gui.autorenew; + c->engine_renew_months = _settings_client.gui.autorenew_months; + c->engine_renew_money = _settings_client.gui.autorenew_money; + } + } + + if (CheckSavegameVersion(48)) { + for (TileIndex t = 0; t < map_size; t++) { + switch (GetTileType(t)) { + case MP_RAILWAY: + if (IsPlainRailTile(t)) { + /* Swap ground type and signal type for plain rail tiles, so the + * ground type uses the same bits as for depots and waypoints. */ + uint tmp = GB(_m[t].m4, 0, 4); + SB(_m[t].m4, 0, 4, GB(_m[t].m2, 0, 4)); + SB(_m[t].m2, 0, 4, tmp); + } else if (HasBit(_m[t].m5, 2)) { + /* Split waypoint and depot rail type and remove the subtype. */ + ClrBit(_m[t].m5, 2); + ClrBit(_m[t].m5, 6); + } + break; + + case MP_ROAD: + /* Swap m3 and m4, so the track type for rail crossings is the + * same as for normal rail. */ + Swap(_m[t].m3, _m[t].m4); + break; + + default: break; + } + } + } + + if (CheckSavegameVersion(61)) { + /* Added the RoadType */ + bool old_bridge = CheckSavegameVersion(42); + for (TileIndex t = 0; t < map_size; t++) { + switch(GetTileType(t)) { + case MP_ROAD: + SB(_m[t].m5, 6, 2, GB(_m[t].m5, 4, 2)); + switch (GetRoadTileType(t)) { + default: NOT_REACHED(); + case ROAD_TILE_NORMAL: + SB(_m[t].m4, 0, 4, GB(_m[t].m5, 0, 4)); + SB(_m[t].m4, 4, 4, 0); + SB(_m[t].m6, 2, 4, 0); + break; + case ROAD_TILE_CROSSING: + SB(_m[t].m4, 5, 2, GB(_m[t].m5, 2, 2)); + break; + case ROAD_TILE_DEPOT: break; + } + SetRoadTypes(t, ROADTYPES_ROAD); + break; + + case MP_STATION: + if (IsRoadStop(t)) SetRoadTypes(t, ROADTYPES_ROAD); + break; + + case MP_TUNNELBRIDGE: + /* Middle part of "old" bridges */ + if (old_bridge && IsBridge(t) && HasBit(_m[t].m5, 6)) break; + if (((old_bridge && IsBridge(t)) ? (TransportType)GB(_m[t].m5, 1, 2) : GetTunnelBridgeTransportType(t)) == TRANSPORT_ROAD) { + SetRoadTypes(t, ROADTYPES_ROAD); + } + break; + + default: break; + } + } + } + + if (CheckSavegameVersion(42)) { + Vehicle* v; + + for (TileIndex t = 0; t < map_size; t++) { + if (MayHaveBridgeAbove(t)) ClearBridgeMiddle(t); + if (IsBridgeTile(t)) { + if (HasBit(_m[t].m5, 6)) { // middle part + Axis axis = (Axis)GB(_m[t].m5, 0, 1); + + if (HasBit(_m[t].m5, 5)) { // transport route under bridge? + if (GB(_m[t].m5, 3, 2) == TRANSPORT_RAIL) { + MakeRailNormal( + t, + GetTileOwner(t), + axis == AXIS_X ? TRACK_BIT_Y : TRACK_BIT_X, + GetRailType(t) + ); + } else { + TownID town = IsTileOwner(t, OWNER_TOWN) ? ClosestTownFromTile(t, UINT_MAX)->index : 0; + + MakeRoadNormal( + t, + axis == AXIS_X ? ROAD_Y : ROAD_X, + ROADTYPES_ROAD, + town, + GetTileOwner(t), OWNER_NONE, OWNER_NONE + ); + } + } else { + if (GB(_m[t].m5, 3, 2) == 0) { + MakeClear(t, CLEAR_GRASS, 3); + } else { + if (GetTileSlope(t, NULL) != SLOPE_FLAT) { + MakeShore(t); + } else { + if (GetTileOwner(t) == OWNER_WATER) { + MakeWater(t); + } else { + MakeCanal(t, GetTileOwner(t), Random()); + } + } + } + } + SetBridgeMiddle(t, axis); + } else { // ramp + Axis axis = (Axis)GB(_m[t].m5, 0, 1); + uint north_south = GB(_m[t].m5, 5, 1); + DiagDirection dir = ReverseDiagDir(XYNSToDiagDir(axis, north_south)); + TransportType type = (TransportType)GB(_m[t].m5, 1, 2); + + _m[t].m5 = 1 << 7 | type << 2 | dir; + } + } + } + + FOR_ALL_VEHICLES(v) { + if (v->type != VEH_TRAIN && v->type != VEH_ROAD) continue; + if (IsBridgeTile(v->tile)) { + DiagDirection dir = GetTunnelBridgeDirection(v->tile); + + if (dir != DirToDiagDir(v->direction)) continue; + switch (dir) { + default: NOT_REACHED(); + case DIAGDIR_NE: if ((v->x_pos & 0xF) != 0) continue; break; + case DIAGDIR_SE: if ((v->y_pos & 0xF) != TILE_SIZE - 1) continue; break; + case DIAGDIR_SW: if ((v->x_pos & 0xF) != TILE_SIZE - 1) continue; break; + case DIAGDIR_NW: if ((v->y_pos & 0xF) != 0) continue; break; + } + } else if (v->z_pos > GetSlopeZ(v->x_pos, v->y_pos)) { + v->tile = GetNorthernBridgeEnd(v->tile); + } else { + continue; + } + if (v->type == VEH_TRAIN) { + v->u.rail.track = TRACK_BIT_WORMHOLE; + } else { + v->u.road.state = RVSB_WORMHOLE; + } + } + } + + /* Elrails got added in rev 24 */ + if (CheckSavegameVersion(24)) { + Vehicle *v; + RailType min_rail = RAILTYPE_ELECTRIC; + + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN) { + RailType rt = RailVehInfo(v->engine_type)->railtype; + + v->u.rail.railtype = rt; + if (rt == RAILTYPE_ELECTRIC) min_rail = RAILTYPE_RAIL; + } + } + + /* .. so we convert the entire map from normal to elrail (so maintain "fairness") */ + for (TileIndex t = 0; t < map_size; t++) { + switch (GetTileType(t)) { + case MP_RAILWAY: + SetRailType(t, UpdateRailType(GetRailType(t), min_rail)); + break; + + case MP_ROAD: + if (IsLevelCrossing(t)) { + SetRailType(t, UpdateRailType(GetRailType(t), min_rail)); + } + break; + + case MP_STATION: + if (IsRailwayStation(t)) { + SetRailType(t, UpdateRailType(GetRailType(t), min_rail)); + } + break; + + case MP_TUNNELBRIDGE: + if (GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) { + SetRailType(t, UpdateRailType(GetRailType(t), min_rail)); + } + break; + + default: + break; + } + } + + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) TrainConsistChanged(v, true); + } + + } + + /* In version 16.1 of the savegame a company can decide if trains, which get + * replaced, shall keep their old length. In all prior versions, just default + * to false */ + if (CheckSavegameVersionOldStyle(16, 1)) { + FOR_ALL_COMPANIES(c) c->renew_keep_length = false; + } + + /* In version 17, ground type is moved from m2 to m4 for depots and + * waypoints to make way for storing the index in m2. The custom graphics + * id which was stored in m4 is now saved as a grf/id reference in the + * waypoint struct. */ + if (CheckSavegameVersion(17)) { + Waypoint *wp; + + FOR_ALL_WAYPOINTS(wp) { + if (wp->deleted == 0) { + const StationSpec *statspec = NULL; + + if (HasBit(_m[wp->xy].m3, 4)) + statspec = GetCustomStationSpec(STAT_CLASS_WAYP, _m[wp->xy].m4 + 1); + + if (statspec != NULL) { + wp->stat_id = _m[wp->xy].m4 + 1; + wp->grfid = statspec->grffile->grfid; + wp->localidx = statspec->localidx; + } else { + /* No custom graphics set, so set to default. */ + wp->stat_id = 0; + wp->grfid = 0; + wp->localidx = 0; + } + + /* Move ground type bits from m2 to m4. */ + _m[wp->xy].m4 = GB(_m[wp->xy].m2, 0, 4); + /* Store waypoint index in the tile. */ + _m[wp->xy].m2 = wp->index; + } + } + } else { + /* As of version 17, we recalculate the custom graphic ID of waypoints + * from the GRF ID / station index. */ + AfterLoadWaypoints(); + } + + /* From version 15, we moved a semaphore bit from bit 2 to bit 3 in m4, making + * room for PBS. Now in version 21 move it back :P. */ + if (CheckSavegameVersion(21) && !CheckSavegameVersion(15)) { + for (TileIndex t = 0; t < map_size; t++) { + switch (GetTileType(t)) { + case MP_RAILWAY: + if (HasSignals(t)) { + /* convert PBS signals to combo-signals */ + if (HasBit(_m[t].m2, 2)) SetSignalType(t, TRACK_X, SIGTYPE_COMBO); + + /* move the signal variant back */ + SetSignalVariant(t, TRACK_X, HasBit(_m[t].m2, 3) ? SIG_SEMAPHORE : SIG_ELECTRIC); + ClrBit(_m[t].m2, 3); + } + + /* Clear PBS reservation on track */ + if (!IsRailDepotTile(t)) { + SB(_m[t].m4, 4, 4, 0); + } else { + ClrBit(_m[t].m3, 6); + } + break; + + case MP_ROAD: /* Clear PBS reservation on crossing */ + if (IsLevelCrossing(t)) ClrBit(_m[t].m5, 0); + break; + + case MP_STATION: /* Clear PBS reservation on station */ + ClrBit(_m[t].m3, 6); + break; + + default: break; + } + } + } + + if (CheckSavegameVersion(25)) { + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_ROAD) { + v->vehstatus &= ~0x40; + v->u.road.slot = NULL; + v->u.road.slot_age = 0; + } + } + } else { + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_ROAD && v->u.road.slot != NULL) v->u.road.slot->num_vehicles++; + } + } + + if (CheckSavegameVersion(26)) { + Station *st; + FOR_ALL_STATIONS(st) { + st->last_vehicle_type = VEH_INVALID; + } + } + + YapfNotifyTrackLayoutChange(INVALID_TILE, INVALID_TRACK); + + if (CheckSavegameVersion(34)) FOR_ALL_COMPANIES(c) ResetCompanyLivery(c); + + FOR_ALL_COMPANIES(c) { + c->avail_railtypes = GetCompanyRailtypes(c->index); + c->avail_roadtypes = GetCompanyRoadtypes(c->index); + } + + if (!CheckSavegameVersion(27)) AfterLoadStations(); + + /* Time starts at 0 instead of 1920. + * Account for this in older games by adding an offset */ + if (CheckSavegameVersion(31)) { + Station *st; + Waypoint *wp; + Engine *e; + Industry *i; + Vehicle *v; + + _date += DAYS_TILL_ORIGINAL_BASE_YEAR; + _cur_year += ORIGINAL_BASE_YEAR; + + FOR_ALL_STATIONS(st) st->build_date += DAYS_TILL_ORIGINAL_BASE_YEAR; + FOR_ALL_WAYPOINTS(wp) wp->build_date += DAYS_TILL_ORIGINAL_BASE_YEAR; + FOR_ALL_ENGINES(e) e->intro_date += DAYS_TILL_ORIGINAL_BASE_YEAR; + FOR_ALL_COMPANIES(c) c->inaugurated_year += ORIGINAL_BASE_YEAR; + FOR_ALL_INDUSTRIES(i) i->last_prod_year += ORIGINAL_BASE_YEAR; + + FOR_ALL_VEHICLES(v) { + v->date_of_last_service += DAYS_TILL_ORIGINAL_BASE_YEAR; + v->build_year += ORIGINAL_BASE_YEAR; + } + } + + /* From 32 on we save the industry who made the farmland. + * To give this prettyness to old savegames, we remove all farmfields and + * plant new ones. */ + if (CheckSavegameVersion(32)) { + Industry *i; + + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_CLEAR) && IsClearGround(t, CLEAR_FIELDS)) { + /* remove fields */ + MakeClear(t, CLEAR_GRASS, 3); + } else if (IsTileType(t, MP_CLEAR) || IsTileType(t, MP_TREES)) { + /* remove fences around fields */ + SetFenceSE(t, 0); + SetFenceSW(t, 0); + } + } + + FOR_ALL_INDUSTRIES(i) { + uint j; + + if (GetIndustrySpec(i->type)->behaviour & INDUSTRYBEH_PLANT_ON_BUILT) { + for (j = 0; j != 50; j++) PlantRandomFarmField(i); + } + } + } + + /* Setting no refit flags to all orders in savegames from before refit in orders were added */ + if (CheckSavegameVersion(36)) { + Order *order; + Vehicle *v; + + FOR_ALL_ORDERS(order) { + order->SetRefit(CT_NO_REFIT); + } + + FOR_ALL_VEHICLES(v) { + v->current_order.SetRefit(CT_NO_REFIT); + } + } + + /* from version 38 we have optional elrails, since we cannot know the + * preference of a user, let elrails enabled; it can be disabled manually */ + if (CheckSavegameVersion(38)) _settings_game.vehicle.disable_elrails = false; + /* do the same as when elrails were enabled/disabled manually just now */ + SettingsDisableElrail(_settings_game.vehicle.disable_elrails); + InitializeRailGUI(); + + /* From version 53, the map array was changed for house tiles to allow + * space for newhouses grf features. A new byte, m7, was also added. */ + if (CheckSavegameVersion(53)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_HOUSE)) { + if (GB(_m[t].m3, 6, 2) != TOWN_HOUSE_COMPLETED) { + /* Move the construction stage from m3[7..6] to m5[5..4]. + * The construction counter does not have to move. */ + SB(_m[t].m5, 3, 2, GB(_m[t].m3, 6, 2)); + SB(_m[t].m3, 6, 2, 0); + + /* The "house is completed" bit is now in m6[2]. */ + SetHouseCompleted(t, false); + } else { + /* The "lift has destination" bit has been moved from + * m5[7] to m7[0]. */ + SB(_me[t].m7, 0, 1, HasBit(_m[t].m5, 7)); + ClrBit(_m[t].m5, 7); + + /* The "lift is moving" bit has been removed, as it does + * the same job as the "lift has destination" bit. */ + ClrBit(_m[t].m1, 7); + + /* The position of the lift goes from m1[7..0] to m6[7..2], + * making m1 totally free, now. The lift position does not + * have to be a full byte since the maximum value is 36. */ + SetLiftPosition(t, GB(_m[t].m1, 0, 6 )); + + _m[t].m1 = 0; + _m[t].m3 = 0; + SetHouseCompleted(t, true); + } + } + } + } + + /* Check and update house and town values */ + UpdateHousesAndTowns(); + + if (CheckSavegameVersion(43)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_INDUSTRY)) { + switch (GetIndustryGfx(t)) { + case GFX_POWERPLANT_SPARKS: + SetIndustryAnimationState(t, GB(_m[t].m1, 2, 5)); + break; + + case GFX_OILWELL_ANIMATED_1: + case GFX_OILWELL_ANIMATED_2: + case GFX_OILWELL_ANIMATED_3: + SetIndustryAnimationState(t, GB(_m[t].m1, 0, 2)); + break; + + case GFX_COAL_MINE_TOWER_ANIMATED: + case GFX_COPPER_MINE_TOWER_ANIMATED: + case GFX_GOLD_MINE_TOWER_ANIMATED: + SetIndustryAnimationState(t, _m[t].m1); + break; + + default: /* No animation states to change */ + break; + } + } + } + } + + if (CheckSavegameVersion(44)) { + Vehicle *v; + /* If we remove a station while cargo from it is still enroute, payment calculation will assume + * 0, 0 to be the source of the cargo, resulting in very high payments usually. v->source_xy + * stores the coordinates, preserving them even if the station is removed. However, if a game is loaded + * where this situation exists, the cargo-source information is lost. in this case, we set the source + * to the current tile of the vehicle to prevent excessive profits + */ + FOR_ALL_VEHICLES(v) { + const CargoList::List *packets = v->cargo.Packets(); + for (CargoList::List::const_iterator it = packets->begin(); it != packets->end(); it++) { + CargoPacket *cp = *it; + cp->source_xy = IsValidStationID(cp->source) ? GetStation(cp->source)->xy : v->tile; + cp->loaded_at_xy = cp->source_xy; + } + v->cargo.InvalidateCache(); + } + + /* Store position of the station where the goods come from, so there + * are no very high payments when stations get removed. However, if the + * station where the goods came from is already removed, the source + * information is lost. In that case we set it to the position of this + * station */ + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID c = 0; c < NUM_CARGO; c++) { + GoodsEntry *ge = &st->goods[c]; + + const CargoList::List *packets = ge->cargo.Packets(); + for (CargoList::List::const_iterator it = packets->begin(); it != packets->end(); it++) { + CargoPacket *cp = *it; + cp->source_xy = IsValidStationID(cp->source) ? GetStation(cp->source)->xy : st->xy; + cp->loaded_at_xy = cp->source_xy; + } + } + } + } + + if (CheckSavegameVersion(45)) { + Vehicle *v; + /* Originally just the fact that some cargo had been paid for was + * stored to stop people cheating and cashing in several times. This + * wasn't enough though as it was cleared when the vehicle started + * loading again, even if it didn't actually load anything, so now the + * amount of cargo that has been paid for is stored. */ + FOR_ALL_VEHICLES(v) { + const CargoList::List *packets = v->cargo.Packets(); + for (CargoList::List::const_iterator it = packets->begin(); it != packets->end(); it++) { + CargoPacket *cp = *it; + cp->paid_for = HasBit(v->vehicle_flags, 2); + } + ClrBit(v->vehicle_flags, 2); + v->cargo.InvalidateCache(); + } + } + + /* Buoys do now store the owner of the previous water tile, which can never + * be OWNER_NONE. So replace OWNER_NONE with OWNER_WATER. */ + if (CheckSavegameVersion(46)) { + Station *st; + FOR_ALL_STATIONS(st) { + if (st->IsBuoy() && IsTileOwner(st->xy, OWNER_NONE) && TileHeight(st->xy) == 0) SetTileOwner(st->xy, OWNER_WATER); + } + } + + if (CheckSavegameVersion(50)) { + Vehicle *v; + /* Aircraft units changed from 8 mph to 1 km/h */ + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_AIRCRAFT && v->subtype <= AIR_AIRCRAFT) { + const AircraftVehicleInfo *avi = AircraftVehInfo(v->engine_type); + v->cur_speed *= 129; + v->cur_speed /= 10; + v->max_speed = avi->max_speed; + v->acceleration = avi->acceleration; + } + } + } + + if (CheckSavegameVersion(49)) FOR_ALL_COMPANIES(c) c->face = ConvertFromOldCompanyManagerFace(c->face); + + if (CheckSavegameVersion(52)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsStatueTile(t)) { + _m[t].m2 = CalcClosestTownFromTile(t, UINT_MAX)->index; + } + } + } + + /* A patch option containing the proportion of towns that grow twice as + * fast was added in version 54. From version 56 this is now saved in the + * town as cities can be built specifically in the scenario editor. */ + if (CheckSavegameVersion(56)) { + Town *t; + + FOR_ALL_TOWNS(t) { + if (_settings_game.economy.larger_towns != 0 && (t->index % _settings_game.economy.larger_towns) == 0) { + t->larger_town = true; + } + } + } + + if (CheckSavegameVersion(57)) { + Vehicle *v; + /* Added a FIFO queue of vehicles loading at stations */ + FOR_ALL_VEHICLES(v) { + if ((v->type != VEH_TRAIN || IsFrontEngine(v)) && // for all locs + !(v->vehstatus & (VS_STOPPED | VS_CRASHED)) && // not stopped or crashed + v->current_order.IsType(OT_LOADING)) { // loading + GetStation(v->last_station_visited)->loading_vehicles.push_back(v); + + /* The loading finished flag is *only* set when actually completely + * finished. Because the vehicle is loading, it is not finished. */ + ClrBit(v->vehicle_flags, VF_LOADING_FINISHED); + } + } + } else if (CheckSavegameVersion(59)) { + /* For some reason non-loading vehicles could be in the station's loading vehicle list */ + + Station *st; + FOR_ALL_STATIONS(st) { + std::list<Vehicle *>::iterator iter; + for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end();) { + Vehicle *v = *iter; + iter++; + if (!v->current_order.IsType(OT_LOADING)) st->loading_vehicles.remove(v); + } + } + } + + if (CheckSavegameVersion(58)) { + /* patch difficulty number_industries other than zero get bumped to +1 + * since a new option (very low at position1) has been added */ + if (_settings_game.difficulty.number_industries > 0) { + _settings_game.difficulty.number_industries++; + } + + /* Same goes for number of towns, although no test is needed, just an increment */ + _settings_game.difficulty.number_towns++; + } + + if (CheckSavegameVersion(64)) { + /* copy the signal type/variant and move signal states bits */ + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_RAILWAY) && HasSignals(t)) { + SetSignalStates(t, GB(_m[t].m2, 4, 4)); + SetSignalVariant(t, INVALID_TRACK, GetSignalVariant(t, TRACK_X)); + SetSignalType(t, INVALID_TRACK, GetSignalType(t, TRACK_X)); + ClrBit(_m[t].m2, 7); + } + } + } + + if (CheckSavegameVersion(69)) { + /* In some old savegames a bit was cleared when it should not be cleared */ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_ROAD && (v->u.road.state == 250 || v->u.road.state == 251)) { + SetBit(v->u.road.state, RVS_IS_STOPPING); + } + } + } + + if (CheckSavegameVersion(70)) { + /* Added variables to support newindustries */ + Industry *i; + FOR_ALL_INDUSTRIES(i) i->founder = OWNER_NONE; + } + + /* From version 82, old style canals (above sealevel (0), WATER owner) are no longer supported. + Replace the owner for those by OWNER_NONE. */ + if (CheckSavegameVersion(82)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_WATER) && + GetWaterTileType(t) == WATER_TILE_CLEAR && + GetTileOwner(t) == OWNER_WATER && + TileHeight(t) != 0) { + SetTileOwner(t, OWNER_NONE); + } + } + } + + /* + * Add the 'previous' owner to the ship depots so we can reset it with + * the correct values when it gets destroyed. This prevents that + * someone can remove canals owned by somebody else and it prevents + * making floods using the removal of ship depots. + */ + if (CheckSavegameVersion(83)) { + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_WATER) && IsShipDepot(t)) { + _m[t].m4 = (TileHeight(t) == 0) ? OWNER_WATER : OWNER_NONE; + } + } + } + + if (CheckSavegameVersion(74)) { + Station *st; + FOR_ALL_STATIONS(st) { + for (CargoID c = 0; c < NUM_CARGO; c++) { + st->goods[c].last_speed = 0; + if (st->goods[c].cargo.Count() != 0) SetBit(st->goods[c].acceptance_pickup, GoodsEntry::PICKUP); + } + } + } + + if (CheckSavegameVersion(78)) { + Industry *i; + uint j; + FOR_ALL_INDUSTRIES(i) { + const IndustrySpec *indsp = GetIndustrySpec(i->type); + for (j = 0; j < lengthof(i->produced_cargo); j++) { + i->produced_cargo[j] = indsp->produced_cargo[j]; + } + for (j = 0; j < lengthof(i->accepts_cargo); j++) { + i->accepts_cargo[j] = indsp->accepts_cargo[j]; + } + } + } + + /* Before version 81, the density of grass was always stored as zero, and + * grassy trees were always drawn fully grassy. Furthermore, trees on rough + * land used to have zero density, now they have full density. Therefore, + * make all grassy/rough land trees have a density of 3. */ + if (CheckSavegameVersion(81)) { + for (TileIndex t = 0; t < map_size; t++) { + if (GetTileType(t) == MP_TREES) { + TreeGround groundType = GetTreeGround(t); + if (groundType != TREE_GROUND_SNOW_DESERT) SetTreeGroundDensity(t, groundType, 3); + } + } + } + + + if (CheckSavegameVersion(93)) { + /* Rework of orders. */ + Order *order; + FOR_ALL_ORDERS(order) order->ConvertFromOldSavegame(); + + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->orders.list != NULL && v->orders.list->GetFirstOrder() != NULL && !v->orders.list->GetFirstOrder()->IsValid()) { + v->orders.list->FreeChain(); + v->orders.list = NULL; + } + + v->current_order.ConvertFromOldSavegame(); + if (v->type == VEH_ROAD && v->IsPrimaryVehicle() && v->FirstShared() == v) { + FOR_VEHICLE_ORDERS(v, order) order->SetNonStopType(ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS); + } + } + } else if (CheckSavegameVersion(94)) { + /* Unload and transfer are now mutual exclusive. */ + Order *order; + FOR_ALL_ORDERS(order) { + if ((order->GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) == (OUFB_UNLOAD | OUFB_TRANSFER)) { + order->SetUnloadType(OUFB_TRANSFER); + order->SetLoadType(OLFB_NO_LOAD); + } + } + + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if ((v->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) == (OUFB_UNLOAD | OUFB_TRANSFER)) { + v->current_order.SetUnloadType(OUFB_TRANSFER); + v->current_order.SetLoadType(OLFB_NO_LOAD); + } + } + } + + if (CheckSavegameVersion(84)) { + /* Update go to buoy orders because they are just waypoints */ + Order *order; + FOR_ALL_ORDERS(order) { + if (order->IsType(OT_GOTO_STATION) && GetStation(order->GetDestination())->IsBuoy()) { + order->SetLoadType(OLF_LOAD_IF_POSSIBLE); + order->SetUnloadType(OUF_UNLOAD_IF_POSSIBLE); + } + } + + /* Set all share owners to INVALID_COMPANY for + * 1) all inactive companies + * (when inactive companies were stored in the savegame - TTD, TTDP and some + * *really* old revisions of OTTD; else it is already set in InitializeCompanies()) + * 2) shares that are owned by inactive companies or self + * (caused by cheating clients in earlier revisions) */ + FOR_ALL_COMPANIES(c) { + for (uint i = 0; i < 4; i++) { + CompanyID company = c->share_owners[i]; + if (company == INVALID_COMPANY) continue; + if (!IsValidCompanyID(company) || company == c->index) c->share_owners[i] = INVALID_COMPANY; + } + } + } + + if (CheckSavegameVersion(86)) { + for (TileIndex t = 0; t < map_size; t++) { + /* Move river flag and update canals to use water class */ + if (IsTileType(t, MP_WATER)) { + if (GetWaterClass(t) != WATER_CLASS_RIVER) { + if (IsWater(t)) { + Owner o = GetTileOwner(t); + if (o == OWNER_WATER) { + MakeWater(t); + } else { + MakeCanal(t, o, Random()); + } + } else if (IsShipDepot(t)) { + Owner o = (Owner)_m[t].m4; // Original water owner + SetWaterClass(t, o == OWNER_WATER ? WATER_CLASS_SEA : WATER_CLASS_CANAL); + } + } + } + } + + /* Update locks, depots, docks and buoys to have a water class based + * on its neighbouring tiles. Done after river and canal updates to + * ensure neighbours are correct. */ + for (TileIndex t = 0; t < map_size; t++) { + if (GetTileSlope(t, NULL) != SLOPE_FLAT) continue; + + if (IsTileType(t, MP_WATER) && IsLock(t)) SetWaterClassDependingOnSurroundings(t, false); + if (IsTileType(t, MP_STATION) && (IsDock(t) || IsBuoy(t))) SetWaterClassDependingOnSurroundings(t, false); + } + } + + if (CheckSavegameVersion(87)) { + for (TileIndex t = 0; t < map_size; t++) { + /* skip oil rigs at borders! */ + if ((IsTileType(t, MP_WATER) || IsBuoyTile(t)) && + (TileX(t) == 0 || TileY(t) == 0 || TileX(t) == MapMaxX() - 1 || TileY(t) == MapMaxY() - 1)) { + /* Some version 86 savegames have wrong water class at map borders (under buoy, or after removing buoy). + * This conversion has to be done before buoys with invalid owner are removed. */ + SetWaterClass(t, WATER_CLASS_SEA); + } + + if (IsBuoyTile(t) || IsDriveThroughStopTile(t) || IsTileType(t, MP_WATER)) { + Owner o = GetTileOwner(t); + if (o < MAX_COMPANIES && !IsValidCompanyID(o)) { + _current_company = o; + ChangeTileOwner(t, o, INVALID_OWNER); + } + if (IsBuoyTile(t)) { + /* reset buoy owner to OWNER_NONE in the station struct + * (even if it is owned by active company) */ + GetStationByTile(t)->owner = OWNER_NONE; + } + } else if (IsTileType(t, MP_ROAD)) { + /* works for all RoadTileType */ + for (RoadType rt = ROADTYPE_ROAD; rt < ROADTYPE_END; rt++) { + /* update even non-existing road types to update tile owner too */ + Owner o = GetRoadOwner(t, rt); + if (o < MAX_COMPANIES && !IsValidCompanyID(o)) SetRoadOwner(t, rt, OWNER_NONE); + } + if (IsLevelCrossing(t)) { + Owner o = GetTileOwner(t); + if (!IsValidCompanyID(o)) { + /* remove leftover rail piece from crossing (from very old savegames) */ + _current_company = o; + DoCommand(t, 0, GetCrossingRailTrack(t), DC_EXEC | DC_BANKRUPT, CMD_REMOVE_SINGLE_RAIL); + } + } + } + } + + /* Convert old PF settings to new */ + if (_settings_game.pf.yapf.rail_use_yapf || CheckSavegameVersion(28)) { + _settings_game.pf.pathfinder_for_trains = VPF_YAPF; + } else { + _settings_game.pf.pathfinder_for_trains = (_settings_game.pf.new_pathfinding_all ? VPF_NPF : VPF_NTP); + } + + if (_settings_game.pf.yapf.road_use_yapf || CheckSavegameVersion(28)) { + _settings_game.pf.pathfinder_for_roadvehs = VPF_YAPF; + } else { + _settings_game.pf.pathfinder_for_roadvehs = (_settings_game.pf.new_pathfinding_all ? VPF_NPF : VPF_OPF); + } + + if (_settings_game.pf.yapf.ship_use_yapf) { + _settings_game.pf.pathfinder_for_ships = VPF_YAPF; + } else { + _settings_game.pf.pathfinder_for_ships = (_settings_game.pf.new_pathfinding_all ? VPF_NPF : VPF_OPF); + } + } + + if (CheckSavegameVersion(88)) { + /* Profits are now with 8 bit fract */ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + v->profit_this_year <<= 8; + v->profit_last_year <<= 8; + v->running_ticks = 0; + } + } + + if (CheckSavegameVersion(91)) { + /* Increase HouseAnimationFrame from 5 to 7 bits */ + for (TileIndex t = 0; t < map_size; t++) { + if (IsTileType(t, MP_HOUSE) && GetHouseType(t) >= NEW_HOUSE_OFFSET) { + SetHouseAnimationFrame(t, GB(_m[t].m6, 3, 5)); + } + } + } + + if (CheckSavegameVersion(62)) { + /* Remove all trams from savegames without tram support. + * There would be trams without tram track under causing crashes sooner or later. */ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_ROAD && v->First() == v && + HasBit(EngInfo(v->engine_type)->misc_flags, EF_ROAD_TRAM)) { + if (_switch_mode_errorstr == INVALID_STRING_ID || _switch_mode_errorstr == STR_NEWGRF_COMPATIBLE_LOAD_WARNING) { + _switch_mode_errorstr = STR_LOADGAME_REMOVED_TRAMS; + } + delete v; + } + } + } + + if (CheckSavegameVersion(99)) { + for (TileIndex t = 0; t < map_size; t++) { + /* Set newly introduced WaterClass of industry tiles */ + if (IsTileType(t, MP_STATION) && IsOilRig(t)) { + SetWaterClassDependingOnSurroundings(t, true); + } + if (IsTileType(t, MP_INDUSTRY)) { + if ((GetIndustrySpec(GetIndustryType(t))->behaviour & INDUSTRYBEH_BUILT_ONWATER) != 0) { + SetWaterClassDependingOnSurroundings(t, true); + } else { + SetWaterClass(t, WATER_CLASS_INVALID); + } + } + + /* Replace "house construction year" with "house age" */ + if (IsTileType(t, MP_HOUSE) && IsHouseCompleted(t)) { + _m[t].m5 = Clamp(_cur_year - (_m[t].m5 + ORIGINAL_BASE_YEAR), 0, 0xFF); + } + } + } + + /* Move the signal variant back up one bit for PBS. We don't convert the old PBS + * format here, as an old layout wouldn't work properly anyway. To be safe, we + * clear any possible PBS reservations as well. */ + if (CheckSavegameVersion(100)) { + for (TileIndex t = 0; t < map_size; t++) { + switch (GetTileType(t)) { + case MP_RAILWAY: + if (HasSignals(t)) { + /* move the signal variant */ + SetSignalVariant(t, TRACK_UPPER, HasBit(_m[t].m2, 2) ? SIG_SEMAPHORE : SIG_ELECTRIC); + SetSignalVariant(t, TRACK_LOWER, HasBit(_m[t].m2, 6) ? SIG_SEMAPHORE : SIG_ELECTRIC); + ClrBit(_m[t].m2, 2); + ClrBit(_m[t].m2, 6); + } + + /* Clear PBS reservation on track */ + if (IsRailDepot(t) ||IsRailWaypoint(t)) { + SetDepotWaypointReservation(t, false); + } else { + SetTrackReservation(t, TRACK_BIT_NONE); + } + break; + + case MP_ROAD: /* Clear PBS reservation on crossing */ + if (IsLevelCrossing(t)) SetCrossingReservation(t, false); + break; + + case MP_STATION: /* Clear PBS reservation on station */ + if (IsRailwayStation(t)) SetRailwayStationReservation(t, false); + break; + + case MP_TUNNELBRIDGE: /* Clear PBS reservation on tunnels/birdges */ + if (GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL) SetTunnelBridgeReservation(t, false); + break; + + default: break; + } + } + } + + /* Reserve all tracks trains are currently on. */ + if (CheckSavegameVersion(101)) { + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN) { + if ((v->u.rail.track & TRACK_BIT_WORMHOLE) == TRACK_BIT_WORMHOLE) { + TryReserveRailTrack(v->tile, DiagDirToDiagTrack(GetTunnelBridgeDirection(v->tile))); + } else if ((v->u.rail.track & TRACK_BIT_MASK) != TRACK_BIT_NONE) { + TryReserveRailTrack(v->tile, TrackBitsToTrack(v->u.rail.track)); + } + } + } + + /* Give owners to waypoints, based on rail tracks it is sitting on. + * If none is available, specify OWNER_NONE */ + Waypoint *wp; + FOR_ALL_WAYPOINTS(wp) { + Owner owner = (IsRailWaypointTile(wp->xy) ? GetTileOwner(wp->xy) : OWNER_NONE); + wp->owner = IsValidCompanyID(owner) ? owner : OWNER_NONE; + } + } + + if (CheckSavegameVersion(102)) { + for (TileIndex t = 0; t < map_size; t++) { + /* Now all crossings should be in correct state */ + if (IsLevelCrossingTile(t)) UpdateLevelCrossing(t, false); + } + } + + if (CheckSavegameVersion(103)) { + /* Non-town-owned roads now store the closest town */ + UpdateNearestTownForRoadTiles(false); + + /* signs with invalid owner left from older savegames */ + Sign *si; + FOR_ALL_SIGNS(si) { + if (si->owner != OWNER_NONE && !IsValidCompanyID(si->owner)) si->owner = OWNER_NONE; + } + + /* Station can get named based on an industry type, but the current ones + * are not, so mark them as if they are not named by an industry. */ + Station *st; + FOR_ALL_STATIONS(st) { + st->indtype = IT_INVALID; + } + } + + if (CheckSavegameVersion(104)) { + Vehicle *v; + FOR_ALL_VEHICLES(v) { + /* Set engine_type of shadow and rotor */ + if (v->type == VEH_AIRCRAFT && !IsNormalAircraft(v)) { + v->engine_type = v->First()->engine_type; + } + } + + /* More companies ... */ + Company *c; + FOR_ALL_COMPANIES(c) { + if (c->bankrupt_asked == 0xFF) c->bankrupt_asked = 0xFFFF; + } + + Engine *e; + FOR_ALL_ENGINES(e) { + if (e->company_avail == 0xFF) e->company_avail = 0xFFFF; + } + + Town *t; + FOR_ALL_TOWNS(t) { + if (t->have_ratings == 0xFF) t->have_ratings = 0xFFFF; + for (uint i = 8; i != MAX_COMPANIES; i++) t->ratings[i] = RATING_INITIAL; + } + } + + GamelogPrintDebug(1); + + bool ret = InitializeWindowsAndCaches(); + /* Restore the signals */ + signal(SIGSEGV, prev_segfault); + signal(SIGABRT, prev_abort); + return ret; +} + +/** Reload all NewGRF files during a running game. This is a cut-down + * version of AfterLoadGame(). + * XXX - We need to reset the vehicle position hash because with a non-empty + * hash AfterLoadVehicles() will loop infinitely. We need AfterLoadVehicles() + * to recalculate vehicle data as some NewGRF vehicle sets could have been + * removed or added and changed statistics */ +void ReloadNewGRFData() +{ + /* reload grf data */ + GfxLoadSprites(); + LoadStringWidthTable(); + ResetEconomy(); + /* reload vehicles */ + ResetVehiclePosHash(); + AfterLoadVehicles(false); + StartupEngines(); + SetCachedEngineCounts(); + /* update station and waypoint graphics */ + AfterLoadWaypoints(); + AfterLoadStations(); + /* Check and update house and town values */ + UpdateHousesAndTowns(); + /* Update livery selection windows */ + for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) InvalidateWindowData(WC_COMPANY_COLOR, i, _loaded_newgrf_features.has_2CC); + /* redraw the whole screen */ + MarkWholeScreenDirty(); + CheckTrainsLengths(); +} diff --git a/src/saveload/ai_sl.cpp b/src/saveload/ai_sl.cpp new file mode 100644 index 000000000..b4d16a6b5 --- /dev/null +++ b/src/saveload/ai_sl.cpp @@ -0,0 +1,79 @@ +/* $Id$ */ + +/** @file ai_sl.cpp Code handling saving and loading of old AI + new AI initialisation after game load */ + +#include "../stdafx.h" +#include "../ai/ai.h" +#include "../ai/default/default.h" + +#include "saveload.h" + +static const SaveLoad _company_ai_desc[] = { + SLE_VAR(CompanyAI, state, SLE_UINT8), + SLE_VAR(CompanyAI, tick, SLE_UINT8), + SLE_CONDVAR(CompanyAI, state_counter, SLE_FILE_U16 | SLE_VAR_U32, 0, 12), + SLE_CONDVAR(CompanyAI, state_counter, SLE_UINT32, 13, SL_MAX_VERSION), + SLE_VAR(CompanyAI, timeout_counter, SLE_UINT16), + + SLE_VAR(CompanyAI, state_mode, SLE_UINT8), + SLE_VAR(CompanyAI, banned_tile_count, SLE_UINT8), + SLE_VAR(CompanyAI, railtype_to_use, SLE_UINT8), + + SLE_VAR(CompanyAI, cargo_type, SLE_UINT8), + SLE_VAR(CompanyAI, num_wagons, SLE_UINT8), + SLE_VAR(CompanyAI, build_kind, SLE_UINT8), + SLE_VAR(CompanyAI, num_build_rec, SLE_UINT8), + SLE_VAR(CompanyAI, num_loco_to_build, SLE_UINT8), + SLE_VAR(CompanyAI, num_want_fullload, SLE_UINT8), + + SLE_VAR(CompanyAI, route_type_mask, SLE_UINT8), + + SLE_CONDVAR(CompanyAI, start_tile_a, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(CompanyAI, start_tile_a, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(CompanyAI, cur_tile_a, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(CompanyAI, cur_tile_a, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_VAR(CompanyAI, start_dir_a, SLE_UINT8), + SLE_VAR(CompanyAI, cur_dir_a, SLE_UINT8), + + SLE_CONDVAR(CompanyAI, start_tile_b, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(CompanyAI, start_tile_b, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(CompanyAI, cur_tile_b, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(CompanyAI, cur_tile_b, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_VAR(CompanyAI, start_dir_b, SLE_UINT8), + SLE_VAR(CompanyAI, cur_dir_b, SLE_UINT8), + + SLE_REF(CompanyAI, cur_veh, REF_VEHICLE), + + SLE_ARR(CompanyAI, wagon_list, SLE_UINT16, 9), + SLE_ARR(CompanyAI, order_list_blocks, SLE_UINT8, 20), + SLE_ARR(CompanyAI, banned_tiles, SLE_UINT16, 16), + + SLE_CONDNULL(64, 2, SL_MAX_VERSION), + SLE_END() +}; + +static const SaveLoad _company_ai_build_rec_desc[] = { + SLE_CONDVAR(AiBuildRec, spec_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(AiBuildRec, spec_tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(AiBuildRec, use_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(AiBuildRec, use_tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_VAR(AiBuildRec, rand_rng, SLE_UINT8), + SLE_VAR(AiBuildRec, cur_building_rule, SLE_UINT8), + SLE_VAR(AiBuildRec, unk6, SLE_UINT8), + SLE_VAR(AiBuildRec, unk7, SLE_UINT8), + SLE_VAR(AiBuildRec, buildcmd_a, SLE_UINT8), + SLE_VAR(AiBuildRec, buildcmd_b, SLE_UINT8), + SLE_VAR(AiBuildRec, direction, SLE_UINT8), + SLE_VAR(AiBuildRec, cargo, SLE_UINT8), + SLE_END() +}; + + +void SaveLoad_AI(CompanyID company) +{ + CompanyAI *cai = &_companies_ai[company]; + SlObject(cai, _company_ai_desc); + for (int i = 0; i != cai->num_build_rec; i++) { + SlObject(&cai->src + i, _company_ai_build_rec_desc); + } +} diff --git a/src/saveload/animated_tile_sl.cpp b/src/saveload/animated_tile_sl.cpp new file mode 100644 index 000000000..ba3f519db --- /dev/null +++ b/src/saveload/animated_tile_sl.cpp @@ -0,0 +1,56 @@ +/* $Id$ */ + +/** @file animated_tile_sl.cpp Code handling saving and loading of animated tiles */ + +#include "../stdafx.h" +#include "../tile_type.h" +#include "../core/alloc_func.hpp" + +#include "saveload.h" + +extern TileIndex *_animated_tile_list; +extern uint _animated_tile_count; +extern uint _animated_tile_allocated; + +/** + * Save the ANIT chunk. + */ +static void Save_ANIT() +{ + SlSetLength(_animated_tile_count * sizeof(*_animated_tile_list)); + SlArray(_animated_tile_list, _animated_tile_count, SLE_UINT32); +} + +/** + * Load the ANIT chunk; the chunk containing the animated tiles. + */ +static void Load_ANIT() +{ + /* Before version 80 we did NOT have a variable length animated tile table */ + if (CheckSavegameVersion(80)) { + /* In pre version 6, we has 16bit per tile, now we have 32bit per tile, convert it ;) */ + SlArray(_animated_tile_list, 256, CheckSavegameVersion(6) ? (SLE_FILE_U16 | SLE_VAR_U32) : SLE_UINT32); + + for (_animated_tile_count = 0; _animated_tile_count < 256; _animated_tile_count++) { + if (_animated_tile_list[_animated_tile_count] == 0) break; + } + return; + } + + _animated_tile_count = (uint)SlGetFieldLength() / sizeof(*_animated_tile_list); + + /* Determine a nice rounded size for the amount of allocated tiles */ + _animated_tile_allocated = 256; + while (_animated_tile_allocated < _animated_tile_count) _animated_tile_allocated *= 2; + + _animated_tile_list = ReallocT<TileIndex>(_animated_tile_list, _animated_tile_allocated); + SlArray(_animated_tile_list, _animated_tile_count, SLE_UINT32); +} + +/** + * "Definition" imported by the saveload code to be able to load and save + * the animated tile table. + */ +extern const ChunkHandler _animated_tile_chunk_handlers[] = { + { 'ANIT', Save_ANIT, Load_ANIT, CH_RIFF | CH_LAST}, +}; diff --git a/src/saveload/autoreplace_sl.cpp b/src/saveload/autoreplace_sl.cpp new file mode 100644 index 000000000..d24c290a8 --- /dev/null +++ b/src/saveload/autoreplace_sl.cpp @@ -0,0 +1,51 @@ +/* $Id$ */ + +/** @file autoreplace_sl.cpp Code handling saving and loading of autoreplace rules */ + +#include "../stdafx.h" +#include "../autoreplace_type.h" +#include "../engine_type.h" +#include "../group_type.h" +#include "../autoreplace_base.h" + +#include "saveload.h" + +static const SaveLoad _engine_renew_desc[] = { + SLE_VAR(EngineRenew, from, SLE_UINT16), + SLE_VAR(EngineRenew, to, SLE_UINT16), + + SLE_REF(EngineRenew, next, REF_ENGINE_RENEWS), + SLE_CONDVAR(EngineRenew, group_id, SLE_UINT16, 60, SL_MAX_VERSION), + SLE_END() +}; + +static void Save_ERNW() +{ + EngineRenew *er; + + FOR_ALL_ENGINE_RENEWS(er) { + SlSetArrayIndex(er->index); + SlObject(er, _engine_renew_desc); + } +} + +static void Load_ERNW() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + EngineRenew *er = new (index) EngineRenew(); + SlObject(er, _engine_renew_desc); + + /* Advanced vehicle lists, ungrouped vehicles got added */ + if (CheckSavegameVersion(60)) { + er->group_id = ALL_GROUP; + } else if (CheckSavegameVersion(71)) { + if (er->group_id == DEFAULT_GROUP) er->group_id = ALL_GROUP; + } + } +} + +extern const ChunkHandler _autoreplace_chunk_handlers[] = { + { 'ERNW', Save_ERNW, Load_ERNW, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/cargopacket_sl.cpp b/src/saveload/cargopacket_sl.cpp new file mode 100644 index 000000000..d38e4e22e --- /dev/null +++ b/src/saveload/cargopacket_sl.cpp @@ -0,0 +1,45 @@ +/* $Id$ */ + +/** @file cargopacket_sl.cpp Code handling saving and loading of cargo packets */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../cargopacket.h" + +#include "saveload.h" + +static const SaveLoad _cargopacket_desc[] = { + SLE_VAR(CargoPacket, source, SLE_UINT16), + SLE_VAR(CargoPacket, source_xy, SLE_UINT32), + SLE_VAR(CargoPacket, loaded_at_xy, SLE_UINT32), + SLE_VAR(CargoPacket, count, SLE_UINT16), + SLE_VAR(CargoPacket, days_in_transit, SLE_UINT8), + SLE_VAR(CargoPacket, feeder_share, SLE_INT64), + SLE_VAR(CargoPacket, paid_for, SLE_BOOL), + + SLE_END() +}; + +static void Save_CAPA() +{ + CargoPacket *cp; + + FOR_ALL_CARGOPACKETS(cp) { + SlSetArrayIndex(cp->index); + SlObject(cp, _cargopacket_desc); + } +} + +static void Load_CAPA() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + CargoPacket *cp = new (index) CargoPacket(); + SlObject(cp, _cargopacket_desc); + } +} + +extern const ChunkHandler _cargopacket_chunk_handlers[] = { + { 'CAPA', Save_CAPA, Load_CAPA, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/cheat_sl.cpp b/src/saveload/cheat_sl.cpp new file mode 100644 index 000000000..a8c939fed --- /dev/null +++ b/src/saveload/cheat_sl.cpp @@ -0,0 +1,37 @@ +/* $Id$ */ + +/** @file cheat_sl.cpp Code handling saving and loading of cheats */ + +#include "../stdafx.h" +#include "../cheat_type.h" + +#include "saveload.h" + +static void Save_CHTS() +{ + /* Cannot use lengthof because _cheats is of type Cheats, not Cheat */ + byte count = sizeof(_cheats) / sizeof(Cheat); + Cheat *cht = (Cheat*) &_cheats; + Cheat *cht_last = &cht[count]; + + SlSetLength(count * 2); + for (; cht != cht_last; cht++) { + SlWriteByte(cht->been_used); + SlWriteByte(cht->value); + } +} + +static void Load_CHTS() +{ + Cheat *cht = (Cheat*)&_cheats; + size_t count = SlGetFieldLength() / 2; + + for (uint i = 0; i < count; i++) { + cht[i].been_used = (SlReadByte() != 0); + cht[i].value = (SlReadByte() != 0); + } +} + +extern const ChunkHandler _cheat_chunk_handlers[] = { + { 'CHTS', Save_CHTS, Load_CHTS, CH_RIFF | CH_LAST} +}; diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp new file mode 100644 index 000000000..933cc0bde --- /dev/null +++ b/src/saveload/company_sl.cpp @@ -0,0 +1,240 @@ +/* $Id$ */ + +/** @file company_sl.cpp Code handling saving and loading of company data */ + +#include "../stdafx.h" +#include "../company_base.h" +#include "../company_func.h" +#include "../network/network.h" +#include "../ai/ai.h" +#include "../ai/trolly/trolly.h" +#include "../company_manager_face.h" + +#include "saveload.h" + +/** + * Converts an old company manager's face format to the new company manager's face format + * + * Meaning of the bits in the old face (some bits are used in several times): + * - 4 and 5: chin + * - 6 to 9: eyebrows + * - 10 to 13: nose + * - 13 to 15: lips (also moustache for males) + * - 16 to 19: hair + * - 20 to 22: eye color + * - 20 to 27: tie, ear rings etc. + * - 28 to 30: glasses + * - 19, 26 and 27: race (bit 27 set and bit 19 equal to bit 26 = black, otherwise white) + * - 31: gender (0 = male, 1 = female) + * + * @param face the face in the old format + * @return the face in the new format + */ +CompanyManagerFace ConvertFromOldCompanyManagerFace(uint32 face) +{ + CompanyManagerFace cmf = 0; + GenderEthnicity ge = GE_WM; + + if (HasBit(face, 31)) SetBit(ge, GENDER_FEMALE); + if (HasBit(face, 27) && (HasBit(face, 26) == HasBit(face, 19))) SetBit(ge, ETHNICITY_BLACK); + + SetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, ge, ge); + SetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge, GB(face, 28, 3) <= 1); + SetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR, ge, HasBit(ge, ETHNICITY_BLACK) ? 0 : ClampU(GB(face, 20, 3), 5, 7) - 5); + SetCompanyManagerFaceBits(cmf, CMFV_CHIN, ge, ScaleCompanyManagerFaceValue(CMFV_CHIN, ge, GB(face, 4, 2))); + SetCompanyManagerFaceBits(cmf, CMFV_EYEBROWS, ge, ScaleCompanyManagerFaceValue(CMFV_EYEBROWS, ge, GB(face, 6, 4))); + SetCompanyManagerFaceBits(cmf, CMFV_HAIR, ge, ScaleCompanyManagerFaceValue(CMFV_HAIR, ge, GB(face, 16, 4))); + SetCompanyManagerFaceBits(cmf, CMFV_JACKET, ge, ScaleCompanyManagerFaceValue(CMFV_JACKET, ge, GB(face, 20, 2))); + SetCompanyManagerFaceBits(cmf, CMFV_COLLAR, ge, ScaleCompanyManagerFaceValue(CMFV_COLLAR, ge, GB(face, 22, 2))); + SetCompanyManagerFaceBits(cmf, CMFV_GLASSES, ge, GB(face, 28, 1)); + + uint lips = GB(face, 10, 4); + if (!HasBit(ge, GENDER_FEMALE) && lips < 4) { + SetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge, true); + SetCompanyManagerFaceBits(cmf, CMFV_MOUSTACHE, ge, max(lips, 1U) - 1); + } else { + if (!HasBit(ge, GENDER_FEMALE)) { + lips = lips * 15 / 16; + lips -= 3; + if (HasBit(ge, ETHNICITY_BLACK) && lips > 8) lips = 0; + } else { + lips = ScaleCompanyManagerFaceValue(CMFV_LIPS, ge, lips); + } + SetCompanyManagerFaceBits(cmf, CMFV_LIPS, ge, lips); + + uint nose = GB(face, 13, 3); + if (ge == GE_WF) { + nose = (nose * 3 >> 3) * 3 >> 2; // There is 'hole' in the nose sprites for females + } else { + nose = ScaleCompanyManagerFaceValue(CMFV_NOSE, ge, nose); + } + SetCompanyManagerFaceBits(cmf, CMFV_NOSE, ge, nose); + } + + uint tie_earring = GB(face, 24, 4); + if (!HasBit(ge, GENDER_FEMALE) || tie_earring < 3) { // Not all females have an earring + if (HasBit(ge, GENDER_FEMALE)) SetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge, true); + SetCompanyManagerFaceBits(cmf, CMFV_TIE_EARRING, ge, HasBit(ge, GENDER_FEMALE) ? tie_earring : ScaleCompanyManagerFaceValue(CMFV_TIE_EARRING, ge, tie_earring / 2)); + } + + return cmf; +} + + + +/* Save/load of companies */ +static const SaveLoad _company_desc[] = { + SLE_VAR(Company, name_2, SLE_UINT32), + SLE_VAR(Company, name_1, SLE_STRINGID), + SLE_CONDSTR(Company, name, SLE_STR, 0, 84, SL_MAX_VERSION), + + SLE_VAR(Company, president_name_1, SLE_UINT16), + SLE_VAR(Company, president_name_2, SLE_UINT32), + SLE_CONDSTR(Company, president_name, SLE_STR, 0, 84, SL_MAX_VERSION), + + SLE_VAR(Company, face, SLE_UINT32), + + /* money was changed to a 64 bit field in savegame version 1. */ + SLE_CONDVAR(Company, money, SLE_VAR_I64 | SLE_FILE_I32, 0, 0), + SLE_CONDVAR(Company, money, SLE_INT64, 1, SL_MAX_VERSION), + + SLE_CONDVAR(Company, current_loan, SLE_VAR_I64 | SLE_FILE_I32, 0, 64), + SLE_CONDVAR(Company, current_loan, SLE_INT64, 65, SL_MAX_VERSION), + + SLE_VAR(Company, colour, SLE_UINT8), + SLE_VAR(Company, money_fraction, SLE_UINT8), + SLE_CONDVAR(Company, avail_railtypes, SLE_UINT8, 0, 57), + SLE_VAR(Company, block_preview, SLE_UINT8), + + SLE_CONDVAR(Company, cargo_types, SLE_FILE_U16 | SLE_VAR_U32, 0, 93), + SLE_CONDVAR(Company, cargo_types, SLE_UINT32, 94, SL_MAX_VERSION), + SLE_CONDVAR(Company, location_of_HQ, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Company, location_of_HQ, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Company, last_build_coordinate, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Company, last_build_coordinate, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Company, inaugurated_year, SLE_FILE_U8 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Company, inaugurated_year, SLE_INT32, 31, SL_MAX_VERSION), + + SLE_ARR(Company, share_owners, SLE_UINT8, 4), + + SLE_VAR(Company, num_valid_stat_ent, SLE_UINT8), + + SLE_VAR(Company, quarters_of_bankrupcy, SLE_UINT8), + SLE_CONDVAR(Company, bankrupt_asked, SLE_FILE_U8 | SLE_VAR_U16, 0, 103), + SLE_CONDVAR(Company, bankrupt_asked, SLE_UINT16, 104, SL_MAX_VERSION), + SLE_VAR(Company, bankrupt_timeout, SLE_INT16), + SLE_CONDVAR(Company, bankrupt_value, SLE_VAR_I64 | SLE_FILE_I32, 0, 64), + SLE_CONDVAR(Company, bankrupt_value, SLE_INT64, 65, SL_MAX_VERSION), + + /* yearly expenses was changed to 64-bit in savegame version 2. */ + SLE_CONDARR(Company, yearly_expenses, SLE_FILE_I32 | SLE_VAR_I64, 3 * 13, 0, 1), + SLE_CONDARR(Company, yearly_expenses, SLE_INT64, 3 * 13, 2, SL_MAX_VERSION), + + SLE_CONDVAR(Company, is_ai, SLE_BOOL, 2, SL_MAX_VERSION), + SLE_CONDNULL(1, 4, 99), + + /* Engine renewal settings */ + SLE_CONDNULL(512, 16, 18), + SLE_CONDREF(Company, engine_renew_list, REF_ENGINE_RENEWS, 19, SL_MAX_VERSION), + SLE_CONDVAR(Company, engine_renew, SLE_BOOL, 16, SL_MAX_VERSION), + SLE_CONDVAR(Company, engine_renew_months, SLE_INT16, 16, SL_MAX_VERSION), + SLE_CONDVAR(Company, engine_renew_money, SLE_UINT32, 16, SL_MAX_VERSION), + SLE_CONDVAR(Company, renew_keep_length, SLE_BOOL, 2, SL_MAX_VERSION), // added with 16.1, but was blank since 2 + + /* reserve extra space in savegame here. (currently 63 bytes) */ + SLE_CONDNULL(63, 2, SL_MAX_VERSION), + + SLE_END() +}; + +static const SaveLoad _company_economy_desc[] = { + /* these were changed to 64-bit in savegame format 2 */ + SLE_CONDVAR(CompanyEconomyEntry, income, SLE_FILE_I32 | SLE_VAR_I64, 0, 1), + SLE_CONDVAR(CompanyEconomyEntry, income, SLE_INT64, 2, SL_MAX_VERSION), + SLE_CONDVAR(CompanyEconomyEntry, expenses, SLE_FILE_I32 | SLE_VAR_I64, 0, 1), + SLE_CONDVAR(CompanyEconomyEntry, expenses, SLE_INT64, 2, SL_MAX_VERSION), + SLE_CONDVAR(CompanyEconomyEntry, company_value, SLE_FILE_I32 | SLE_VAR_I64, 0, 1), + SLE_CONDVAR(CompanyEconomyEntry, company_value, SLE_INT64, 2, SL_MAX_VERSION), + + SLE_VAR(CompanyEconomyEntry, delivered_cargo, SLE_INT32), + SLE_VAR(CompanyEconomyEntry, performance_history, SLE_INT32), + + SLE_END() +}; + +static const SaveLoad _company_livery_desc[] = { + SLE_CONDVAR(Livery, in_use, SLE_BOOL, 34, SL_MAX_VERSION), + SLE_CONDVAR(Livery, colour1, SLE_UINT8, 34, SL_MAX_VERSION), + SLE_CONDVAR(Livery, colour2, SLE_UINT8, 34, SL_MAX_VERSION), + SLE_END() +}; + +static void SaveLoad_PLYR(Company *c) +{ + int i; + + SlObject(c, _company_desc); + + /* Write AI? */ + if (!IsHumanCompany(c->index)) { + extern void SaveLoad_AI(CompanyID company); + SaveLoad_AI(c->index); + } + + /* Write economy */ + SlObject(&c->cur_economy, _company_economy_desc); + + /* Write old economy entries. */ + for (i = 0; i < c->num_valid_stat_ent; i++) { + SlObject(&c->old_economy[i], _company_economy_desc); + } + + /* Write each livery entry. */ + int num_liveries = CheckSavegameVersion(63) ? LS_END - 4 : (CheckSavegameVersion(85) ? LS_END - 2: LS_END); + for (i = 0; i < num_liveries; i++) { + SlObject(&c->livery[i], _company_livery_desc); + } + + if (num_liveries < LS_END) { + /* We want to insert some liveries somewhere in between. This means some have to be moved. */ + memmove(&c->livery[LS_FREIGHT_WAGON], &c->livery[LS_PASSENGER_WAGON_MONORAIL], (LS_END - LS_FREIGHT_WAGON) * sizeof(c->livery[0])); + c->livery[LS_PASSENGER_WAGON_MONORAIL] = c->livery[LS_MONORAIL]; + c->livery[LS_PASSENGER_WAGON_MAGLEV] = c->livery[LS_MAGLEV]; + } + + if (num_liveries == LS_END - 4) { + /* Copy bus/truck liveries over to trams */ + c->livery[LS_PASSENGER_TRAM] = c->livery[LS_BUS]; + c->livery[LS_FREIGHT_TRAM] = c->livery[LS_TRUCK]; + } +} + +static void Save_PLYR() +{ + Company *c; + FOR_ALL_COMPANIES(c) { + SlSetArrayIndex(c->index); + SlAutolength((AutolengthProc*)SaveLoad_PLYR, c); + } +} + +static void Load_PLYR() +{ + int index; + while ((index = SlIterateArray()) != -1) { + Company *c = new (index) Company(); + SaveLoad_PLYR(c); + _company_colours[index] = c->colour; + + /* This is needed so an AI is attached to a loaded AI */ + if (c->is_ai && (!_networking || _network_server) && _ai.enabled) { + /* Clear the memory of the new AI, otherwise we might be doing wrong things. */ + memset(&_companies_ainew[index], 0, sizeof(CompanyAiNew)); + AI_StartNewAI(c->index); + } + } +} + +extern const ChunkHandler _company_chunk_handlers[] = { + { 'PLYR', Save_PLYR, Load_PLYR, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/depot_sl.cpp b/src/saveload/depot_sl.cpp new file mode 100644 index 000000000..fb546ef73 --- /dev/null +++ b/src/saveload/depot_sl.cpp @@ -0,0 +1,39 @@ +/* $Id$ */ + +/** @file depot_sl.cpp Code handling saving and loading of depots */ + +#include "../stdafx.h" +#include "../depot_base.h" + +#include "saveload.h" + +static const SaveLoad _depot_desc[] = { + SLE_CONDVAR(Depot, xy, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Depot, xy, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_VAR(Depot, town_index, SLE_UINT16), + SLE_END() +}; + +static void Save_DEPT() +{ + Depot *depot; + + FOR_ALL_DEPOTS(depot) { + SlSetArrayIndex(depot->index); + SlObject(depot, _depot_desc); + } +} + +static void Load_DEPT() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + Depot *depot = new (index) Depot(); + SlObject(depot, _depot_desc); + } +} + +extern const ChunkHandler _depot_chunk_handlers[] = { + { 'DEPT', Save_DEPT, Load_DEPT, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/economy_sl.cpp b/src/saveload/economy_sl.cpp new file mode 100644 index 000000000..99f306f52 --- /dev/null +++ b/src/saveload/economy_sl.cpp @@ -0,0 +1,58 @@ +/* $Id$ */ + +/** @file economy_sl.cpp Code handling saving and loading of economy data */ + +#include "../stdafx.h" +#include "../economy_func.h" + +#include "saveload.h" + +/** Prices */ +static void SaveLoad_PRIC() +{ + int vt = CheckSavegameVersion(65) ? (SLE_FILE_I32 | SLE_VAR_I64) : SLE_INT64; + SlArray(&_price, NUM_PRICES, vt); + SlArray(&_price_frac, NUM_PRICES, SLE_UINT16); +} + +/** Cargo payment rates */ +static void SaveLoad_CAPR() +{ + uint num_cargo = CheckSavegameVersion(55) ? 12 : NUM_CARGO; + int vt = CheckSavegameVersion(65) ? (SLE_FILE_I32 | SLE_VAR_I64) : SLE_INT64; + SlArray(&_cargo_payment_rates, num_cargo, vt); + SlArray(&_cargo_payment_rates_frac, num_cargo, SLE_UINT16); +} + +static const SaveLoad _economy_desc[] = { + SLE_CONDVAR(Economy, max_loan, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), + SLE_CONDVAR(Economy, max_loan, SLE_INT64, 65, SL_MAX_VERSION), + SLE_CONDVAR(Economy, max_loan_unround, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), + SLE_CONDVAR(Economy, max_loan_unround, SLE_INT64, 65, SL_MAX_VERSION), + SLE_CONDVAR(Economy, max_loan_unround_fract, SLE_UINT16, 70, SL_MAX_VERSION), + SLE_VAR(Economy, fluct, SLE_INT16), + SLE_VAR(Economy, interest_rate, SLE_UINT8), + SLE_VAR(Economy, infl_amount, SLE_UINT8), + SLE_VAR(Economy, infl_amount_pr, SLE_UINT8), + SLE_CONDVAR(Economy, industry_daily_change_counter, SLE_UINT32, 102, SL_MAX_VERSION), + SLE_END() +}; + +/** Economy variables */ +static void Save_ECMY() +{ + SlObject(&_economy, _economy_desc); +} + +/** Economy variables */ +static void Load_ECMY() +{ + SlObject(&_economy, _economy_desc); + StartupIndustryDailyChanges(CheckSavegameVersion(102)); // old savegames will need to be initialized +} + +extern const ChunkHandler _economy_chunk_handlers[] = { + { 'PRIC', SaveLoad_PRIC, SaveLoad_PRIC, CH_RIFF | CH_AUTO_LENGTH}, + { 'CAPR', SaveLoad_CAPR, SaveLoad_CAPR, CH_RIFF | CH_AUTO_LENGTH}, + { 'ECMY', Save_ECMY, Load_ECMY, CH_RIFF | CH_LAST}, +}; diff --git a/src/saveload/engine_sl.cpp b/src/saveload/engine_sl.cpp new file mode 100644 index 000000000..6dc47c56a --- /dev/null +++ b/src/saveload/engine_sl.cpp @@ -0,0 +1,118 @@ +/* $Id$ */ + +/** @file engine_sl.cpp Code handling saving and loading of engines */ + +#include "../stdafx.h" +#include "saveload.h" +#include "saveload_internal.h" +#include "../engine_base.h" +#include <map> + +static const SaveLoad _engine_desc[] = { + SLE_CONDVAR(Engine, intro_date, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Engine, intro_date, SLE_INT32, 31, SL_MAX_VERSION), + SLE_CONDVAR(Engine, age, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Engine, age, SLE_INT32, 31, SL_MAX_VERSION), + SLE_VAR(Engine, reliability, SLE_UINT16), + SLE_VAR(Engine, reliability_spd_dec, SLE_UINT16), + SLE_VAR(Engine, reliability_start, SLE_UINT16), + SLE_VAR(Engine, reliability_max, SLE_UINT16), + SLE_VAR(Engine, reliability_final, SLE_UINT16), + SLE_VAR(Engine, duration_phase_1, SLE_UINT16), + SLE_VAR(Engine, duration_phase_2, SLE_UINT16), + SLE_VAR(Engine, duration_phase_3, SLE_UINT16), + + SLE_VAR(Engine, lifelength, SLE_UINT8), + SLE_VAR(Engine, flags, SLE_UINT8), + SLE_VAR(Engine, preview_company_rank,SLE_UINT8), + SLE_VAR(Engine, preview_wait, SLE_UINT8), + SLE_CONDNULL(1, 0, 44), + SLE_CONDVAR(Engine, company_avail, SLE_FILE_U8 | SLE_VAR_U16, 0, 103), + SLE_CONDVAR(Engine, company_avail, SLE_UINT16, 104, SL_MAX_VERSION), + SLE_CONDSTR(Engine, name, SLE_STR, 0, 84, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 16 bytes) */ + SLE_CONDNULL(16, 2, SL_MAX_VERSION), + + SLE_END() +}; + +static std::map<EngineID, Engine> _temp_engine; + +Engine *GetTempDataEngine(EngineID index) +{ + return &_temp_engine[index]; +} + +static void Save_ENGN() +{ + Engine *e; + FOR_ALL_ENGINES(e) { + SlSetArrayIndex(e->index); + SlObject(e, _engine_desc); + } +} + +static void Load_ENGN() +{ + /* As engine data is loaded before engines are initialized we need to load + * this information into a temporary array. This is then copied into the + * engine pool after processing NewGRFs by CopyTempEngineData(). */ + int index; + while ((index = SlIterateArray()) != -1) { + Engine *e = GetTempDataEngine(index); + SlObject(e, _engine_desc); + } +} + +/** + * Copy data from temporary engine array into the real engine pool. + */ +void CopyTempEngineData() +{ + Engine *e; + FOR_ALL_ENGINES(e) { + if (e->index >= _temp_engine.size()) break; + + const Engine *se = GetTempDataEngine(e->index); + e->intro_date = se->intro_date; + e->age = se->age; + e->reliability = se->reliability; + e->reliability_spd_dec = se->reliability_spd_dec; + e->reliability_start = se->reliability_start; + e->reliability_max = se->reliability_max; + e->reliability_final = se->reliability_final; + e->duration_phase_1 = se->duration_phase_1; + e->duration_phase_2 = se->duration_phase_2; + e->duration_phase_3 = se->duration_phase_3; + e->lifelength = se->lifelength; + e->flags = se->flags; + e->preview_company_rank= se->preview_company_rank; + e->preview_wait = se->preview_wait; + e->company_avail = se->company_avail; + if (se->name != NULL) e->name = strdup(se->name); + } + + /* Get rid of temporary data */ + _temp_engine.clear(); +} + +static void Load_ENGS() +{ + /* Load old separate String ID list into a temporary array. This + * was always 256 entries. */ + StringID names[256]; + + SlArray(names, lengthof(names), SLE_STRINGID); + + /* Copy each string into the temporary engine array. */ + for (EngineID engine = 0; engine < lengthof(names); engine++) { + Engine *e = GetTempDataEngine(engine); + e->name = CopyFromOldName(names[engine]); + } +} + +extern const ChunkHandler _engine_chunk_handlers[] = { + { 'ENGN', Save_ENGN, Load_ENGN, CH_ARRAY }, + { 'ENGS', NULL, Load_ENGS, CH_RIFF | CH_LAST }, +}; diff --git a/src/saveload/gamelog_sl.cpp b/src/saveload/gamelog_sl.cpp new file mode 100644 index 000000000..7364602bb --- /dev/null +++ b/src/saveload/gamelog_sl.cpp @@ -0,0 +1,161 @@ +/* $Id$ */ + +/** @file gamelog_sl.cpp Code handling saving and loading of gamelog data */ + +#include "../stdafx.h" +#include "../gamelog.h" +#include "../gamelog_internal.h" +#include "../core/alloc_func.hpp" + +#include "saveload.h" + +static const SaveLoad _glog_action_desc[] = { + SLE_VAR(LoggedAction, tick, SLE_UINT16), + SLE_END() +}; + +static const SaveLoad _glog_mode_desc[] = { + SLE_VAR(LoggedChange, mode.mode, SLE_UINT8), + SLE_VAR(LoggedChange, mode.landscape, SLE_UINT8), + SLE_END() +}; + +static const SaveLoad _glog_revision_desc[] = { + SLE_ARR(LoggedChange, revision.text, SLE_UINT8, NETWORK_REVISION_LENGTH), + SLE_VAR(LoggedChange, revision.newgrf, SLE_UINT32), + SLE_VAR(LoggedChange, revision.slver, SLE_UINT16), + SLE_VAR(LoggedChange, revision.modified, SLE_UINT8), + SLE_END() +}; + +static const SaveLoad _glog_oldver_desc[] = { + SLE_VAR(LoggedChange, oldver.type, SLE_UINT32), + SLE_VAR(LoggedChange, oldver.version, SLE_UINT32), + SLE_END() +}; + +static const SaveLoad _glog_patch_desc[] = { + SLE_STR(LoggedChange, patch.name, SLE_STR, 128), + SLE_VAR(LoggedChange, patch.oldval, SLE_INT32), + SLE_VAR(LoggedChange, patch.newval, SLE_INT32), + SLE_END() +}; + +static const SaveLoad _glog_grfadd_desc[] = { + SLE_VAR(LoggedChange, grfadd.grfid, SLE_UINT32 ), + SLE_ARR(LoggedChange, grfadd.md5sum, SLE_UINT8, 16), + SLE_END() +}; + +static const SaveLoad _glog_grfrem_desc[] = { + SLE_VAR(LoggedChange, grfrem.grfid, SLE_UINT32), + SLE_END() +}; + +static const SaveLoad _glog_grfcompat_desc[] = { + SLE_VAR(LoggedChange, grfcompat.grfid, SLE_UINT32 ), + SLE_ARR(LoggedChange, grfcompat.md5sum, SLE_UINT8, 16), + SLE_END() +}; + +static const SaveLoad _glog_grfparam_desc[] = { + SLE_VAR(LoggedChange, grfparam.grfid, SLE_UINT32), + SLE_END() +}; + +static const SaveLoad _glog_grfmove_desc[] = { + SLE_VAR(LoggedChange, grfmove.grfid, SLE_UINT32), + SLE_VAR(LoggedChange, grfmove.offset, SLE_INT32), + SLE_END() +}; + +static const SaveLoad _glog_grfbug_desc[] = { + SLE_VAR(LoggedChange, grfbug.data, SLE_UINT64), + SLE_VAR(LoggedChange, grfbug.grfid, SLE_UINT32), + SLE_VAR(LoggedChange, grfbug.bug, SLE_UINT8), + SLE_END() +}; + +static const SaveLoad *_glog_desc[] = { + _glog_mode_desc, + _glog_revision_desc, + _glog_oldver_desc, + _glog_patch_desc, + _glog_grfadd_desc, + _glog_grfrem_desc, + _glog_grfcompat_desc, + _glog_grfparam_desc, + _glog_grfmove_desc, + _glog_grfbug_desc, +}; + +assert_compile(lengthof(_glog_desc) == GLCT_END); + +static void Load_GLOG() +{ + assert(_gamelog_action == NULL); + assert(_gamelog_actions == 0); + + GamelogActionType at; + while ((at = (GamelogActionType)SlReadByte()) != GLAT_NONE) { + _gamelog_action = ReallocT(_gamelog_action, _gamelog_actions + 1); + LoggedAction *la = &_gamelog_action[_gamelog_actions++]; + + la->at = at; + + SlObject(la, _glog_action_desc); // has to be saved after 'DATE'! + la->change = NULL; + la->changes = 0; + + GamelogChangeType ct; + while ((ct = (GamelogChangeType)SlReadByte()) != GLCT_NONE) { + la->change = ReallocT(la->change, la->changes + 1); + + LoggedChange *lc = &la->change[la->changes++]; + /* for SLE_STR, pointer has to be valid! so make it NULL */ + memset(lc, 0, sizeof(*lc)); + lc->ct = ct; + + assert((uint)ct < GLCT_END); + + SlObject(lc, _glog_desc[ct]); + } + } +} + +static void Save_GLOG() +{ + const LoggedAction *laend = &_gamelog_action[_gamelog_actions]; + size_t length = 0; + + for (const LoggedAction *la = _gamelog_action; la != laend; la++) { + const LoggedChange *lcend = &la->change[la->changes]; + for (LoggedChange *lc = la->change; lc != lcend; lc++) { + assert((uint)lc->ct < lengthof(_glog_desc)); + length += SlCalcObjLength(lc, _glog_desc[lc->ct]) + 1; + } + length += 4; + } + length++; + + SlSetLength(length); + + for (LoggedAction *la = _gamelog_action; la != laend; la++) { + SlWriteByte(la->at); + SlObject(la, _glog_action_desc); + + const LoggedChange *lcend = &la->change[la->changes]; + for (LoggedChange *lc = la->change; lc != lcend; lc++) { + SlWriteByte(lc->ct); + assert((uint)lc->ct < GLCT_END); + SlObject(lc, _glog_desc[lc->ct]); + } + SlWriteByte(GLCT_NONE); + } + SlWriteByte(GLAT_NONE); +} + + +extern const ChunkHandler _gamelog_chunk_handlers[] = { + { 'GLOG', Save_GLOG, Load_GLOG, CH_RIFF | CH_LAST } +}; diff --git a/src/saveload/group_sl.cpp b/src/saveload/group_sl.cpp new file mode 100644 index 000000000..66e3585b4 --- /dev/null +++ b/src/saveload/group_sl.cpp @@ -0,0 +1,43 @@ +/* $Id$ */ + +/** @file group_sl.cpp Code handling saving and loading of economy data */ + +#include "../stdafx.h" +#include "../group.h" + +#include "saveload.h" + +static const SaveLoad _group_desc[] = { + SLE_CONDVAR(Group, name, SLE_NAME, 0, 83), + SLE_CONDSTR(Group, name, SLE_STR, 0, 84, SL_MAX_VERSION), + SLE_VAR(Group, num_vehicle, SLE_UINT16), + SLE_VAR(Group, owner, SLE_UINT8), + SLE_VAR(Group, vehicle_type, SLE_UINT8), + SLE_VAR(Group, replace_protection, SLE_BOOL), + SLE_END() +}; + +static void Save_GRPS(void) +{ + Group *g; + + FOR_ALL_GROUPS(g) { + SlSetArrayIndex(g->index); + SlObject(g, _group_desc); + } +} + + +static void Load_GRPS(void) +{ + int index; + + while ((index = SlIterateArray()) != -1) { + Group *g = new (index) Group(); + SlObject(g, _group_desc); + } +} + +extern const ChunkHandler _group_chunk_handlers[] = { + { 'GRPS', Save_GRPS, Load_GRPS, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp new file mode 100644 index 000000000..2b8aea788 --- /dev/null +++ b/src/saveload/industry_sl.cpp @@ -0,0 +1,155 @@ +/* $Id$ */ + +/** @file industry_sl.cpp Code handling saving and loading of industries */ + +#include "../stdafx.h" +#include "../tile_type.h" +#include "../strings_type.h" +#include "../company_type.h" +#include "../industry.h" +#include "../newgrf_commons.h" + +#include "saveload.h" + +static const SaveLoad _industry_desc[] = { + SLE_CONDVAR(Industry, xy, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Industry, xy, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_VAR(Industry, width, SLE_UINT8), + SLE_VAR(Industry, height, SLE_UINT8), + SLE_REF(Industry, town, REF_TOWN), + SLE_CONDNULL( 2, 0, 60), ///< used to be industry's produced_cargo + SLE_CONDARR(Industry, produced_cargo, SLE_UINT8, 2, 78, SL_MAX_VERSION), + SLE_CONDARR(Industry, incoming_cargo_waiting, SLE_UINT16, 3, 70, SL_MAX_VERSION), + SLE_ARR(Industry, produced_cargo_waiting, SLE_UINT16, 2), + SLE_ARR(Industry, production_rate, SLE_UINT8, 2), + SLE_CONDNULL( 3, 0, 60), ///< used to be industry's accepts_cargo + SLE_CONDARR(Industry, accepts_cargo, SLE_UINT8, 3, 78, SL_MAX_VERSION), + SLE_VAR(Industry, prod_level, SLE_UINT8), + SLE_ARR(Industry, this_month_production, SLE_UINT16, 2), + SLE_ARR(Industry, this_month_transported, SLE_UINT16, 2), + SLE_ARR(Industry, last_month_pct_transported, SLE_UINT8, 2), + SLE_ARR(Industry, last_month_production, SLE_UINT16, 2), + SLE_ARR(Industry, last_month_transported, SLE_UINT16, 2), + + SLE_VAR(Industry, counter, SLE_UINT16), + + SLE_VAR(Industry, type, SLE_UINT8), + SLE_VAR(Industry, owner, SLE_UINT8), + SLE_VAR(Industry, random_color, SLE_UINT8), + SLE_CONDVAR(Industry, last_prod_year, SLE_FILE_U8 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Industry, last_prod_year, SLE_INT32, 31, SL_MAX_VERSION), + SLE_VAR(Industry, was_cargo_delivered, SLE_UINT8), + + SLE_CONDVAR(Industry, founder, SLE_UINT8, 70, SL_MAX_VERSION), + SLE_CONDVAR(Industry, construction_date, SLE_INT32, 70, SL_MAX_VERSION), + SLE_CONDVAR(Industry, construction_type, SLE_UINT8, 70, SL_MAX_VERSION), + SLE_CONDVAR(Industry, last_cargo_accepted_at, SLE_INT32, 70, SL_MAX_VERSION), + SLE_CONDVAR(Industry, selected_layout, SLE_UINT8, 73, SL_MAX_VERSION), + + SLE_CONDARRX(cpp_offsetof(Industry, psa) + cpp_offsetof(Industry::PersistentStorage, storage), SLE_UINT32, 16, 76, SL_MAX_VERSION), + + SLE_CONDVAR(Industry, random_triggers, SLE_UINT8, 82, SL_MAX_VERSION), + SLE_CONDVAR(Industry, random, SLE_UINT16, 82, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 32 bytes) */ + SLE_CONDNULL(32, 2, SL_MAX_VERSION), + + SLE_END() +}; + +static void Save_INDY() +{ + Industry *ind; + + /* Write the industries */ + FOR_ALL_INDUSTRIES(ind) { + SlSetArrayIndex(ind->index); + SlObject(ind, _industry_desc); + } +} + +/* Save and load the mapping between the industry/tile id on the map, and the grf file + * it came from. */ +static const SaveLoad _industries_id_mapping_desc[] = { + SLE_VAR(EntityIDMapping, grfid, SLE_UINT32), + SLE_VAR(EntityIDMapping, entity_id, SLE_UINT8), + SLE_VAR(EntityIDMapping, substitute_id, SLE_UINT8), + SLE_END() +}; + +static void Save_IIDS() +{ + uint i; + uint j = _industry_mngr.GetMaxMapping(); + + for (i = 0; i < j; i++) { + SlSetArrayIndex(i); + SlObject(&_industry_mngr.mapping_ID[i], _industries_id_mapping_desc); + } +} + +static void Save_TIDS() +{ + uint i; + uint j = _industile_mngr.GetMaxMapping(); + + for (i = 0; i < j; i++) { + SlSetArrayIndex(i); + SlObject(&_industile_mngr.mapping_ID[i], _industries_id_mapping_desc); + } +} + +static void Load_INDY() +{ + int index; + + ResetIndustryCounts(); + + while ((index = SlIterateArray()) != -1) { + Industry *i = new (index) Industry(); + SlObject(i, _industry_desc); + IncIndustryTypeCount(i->type); + } +} + +static void Load_IIDS() +{ + int index; + uint max_id; + + /* clear the current mapping stored. + * This will create the manager if ever it is not yet done */ + _industry_mngr.ResetMapping(); + + /* get boundary for the temporary map loader NUM_INDUSTRYTYPES? */ + max_id = _industry_mngr.GetMaxMapping(); + + while ((index = SlIterateArray()) != -1) { + if ((uint)index >= max_id) break; + SlObject(&_industry_mngr.mapping_ID[index], _industries_id_mapping_desc); + } +} + +static void Load_TIDS() +{ + int index; + uint max_id; + + /* clear the current mapping stored. + * This will create the manager if ever it is not yet done */ + _industile_mngr.ResetMapping(); + + /* get boundary for the temporary map loader NUM_INDUSTILES? */ + max_id = _industile_mngr.GetMaxMapping(); + + while ((index = SlIterateArray()) != -1) { + if ((uint)index >= max_id) break; + SlObject(&_industile_mngr.mapping_ID[index], _industries_id_mapping_desc); + } +} + +extern const ChunkHandler _industry_chunk_handlers[] = { + { 'INDY', Save_INDY, Load_INDY, CH_ARRAY}, + { 'IIDS', Save_IIDS, Load_IIDS, CH_ARRAY}, + { 'TIDS', Save_TIDS, Load_TIDS, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/map_sl.cpp b/src/saveload/map_sl.cpp new file mode 100644 index 000000000..6025f0fca --- /dev/null +++ b/src/saveload/map_sl.cpp @@ -0,0 +1,249 @@ +/* $Id$ */ + +/** @file map_sl.cpp Code handling saving and loading of map */ + +#include "../stdafx.h" +#include "../tile_type.h" +#include "../map_func.h" +#include "../core/alloc_type.hpp" +#include "../core/bitmath_func.hpp" + +#include "saveload.h" + +static uint32 _map_dim_x; +static uint32 _map_dim_y; + +static const SaveLoadGlobVarList _map_dimensions[] = { + SLEG_CONDVAR(_map_dim_x, SLE_UINT32, 6, SL_MAX_VERSION), + SLEG_CONDVAR(_map_dim_y, SLE_UINT32, 6, SL_MAX_VERSION), + SLEG_END() +}; + +static void Save_MAPS() +{ + _map_dim_x = MapSizeX(); + _map_dim_y = MapSizeY(); + SlGlobList(_map_dimensions); +} + +static void Load_MAPS() +{ + SlGlobList(_map_dimensions); + AllocateMap(_map_dim_x, _map_dim_y); +} + +enum { + MAP_SL_BUF_SIZE = 4096 +}; + +static void Load_MAPT() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].type_height = buf[j]; + } +} + +static void Save_MAPT() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].type_height; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +static void Load_MAP1() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].m1 = buf[j]; + } +} + +static void Save_MAP1() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].m1; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +static void Load_MAP2() +{ + SmallStackSafeStackAlloc<uint16, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, + /* In those versions the m2 was 8 bits */ + CheckSavegameVersion(5) ? SLE_FILE_U8 | SLE_VAR_U16 : SLE_UINT16 + ); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].m2 = buf[j]; + } +} + +static void Save_MAP2() +{ + SmallStackSafeStackAlloc<uint16, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size * sizeof(uint16)); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].m2; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT16); + } +} + +static void Load_MAP3() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].m3 = buf[j]; + } +} + +static void Save_MAP3() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].m3; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +static void Load_MAP4() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].m4 = buf[j]; + } +} + +static void Save_MAP4() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].m4; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +static void Load_MAP5() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].m5 = buf[j]; + } +} + +static void Save_MAP5() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].m5; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +static void Load_MAP6() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + if (CheckSavegameVersion(42)) { + for (TileIndex i = 0; i != size;) { + /* 1024, otherwise we overflow on 64x64 maps! */ + SlArray(buf, 1024, SLE_UINT8); + for (uint j = 0; j != 1024; j++) { + _m[i++].m6 = GB(buf[j], 0, 2); + _m[i++].m6 = GB(buf[j], 2, 2); + _m[i++].m6 = GB(buf[j], 4, 2); + _m[i++].m6 = GB(buf[j], 6, 2); + } + } + } else { + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _m[i++].m6 = buf[j]; + } + } +} + +static void Save_MAP6() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _m[i++].m6; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +static void Load_MAP7() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + for (TileIndex i = 0; i != size;) { + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) _me[i++].m7 = buf[j]; + } +} + +static void Save_MAP7() +{ + SmallStackSafeStackAlloc<byte, MAP_SL_BUF_SIZE> buf; + TileIndex size = MapSize(); + + SlSetLength(size); + for (TileIndex i = 0; i != size;) { + for (uint j = 0; j != MAP_SL_BUF_SIZE; j++) buf[j] = _me[i++].m7; + SlArray(buf, MAP_SL_BUF_SIZE, SLE_UINT8); + } +} + +extern const ChunkHandler _map_chunk_handlers[] = { + { 'MAPS', Save_MAPS, Load_MAPS, CH_RIFF }, + { 'MAPT', Save_MAPT, Load_MAPT, CH_RIFF }, + { 'MAPO', Save_MAP1, Load_MAP1, CH_RIFF }, + { 'MAP2', Save_MAP2, Load_MAP2, CH_RIFF }, + { 'M3LO', Save_MAP3, Load_MAP3, CH_RIFF }, + { 'M3HI', Save_MAP4, Load_MAP4, CH_RIFF }, + { 'MAP5', Save_MAP5, Load_MAP5, CH_RIFF }, + { 'MAPE', Save_MAP6, Load_MAP6, CH_RIFF }, + { 'MAP7', Save_MAP7, Load_MAP7, CH_RIFF }, +}; diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp new file mode 100644 index 000000000..4b8e8b2c7 --- /dev/null +++ b/src/saveload/misc_sl.cpp @@ -0,0 +1,105 @@ +/* $Id$ */ + +/** @file misc_sl.cpp Saving and loading of things that didn't fit anywhere else */ + +#include "../stdafx.h" +#include "../date_func.h" +#include "../variables.h" +#include "../core/random_func.hpp" +#include "../openttd.h" +#include "../tile_type.h" +#include "../zoom_func.h" +#include "../vehicle_func.h" +#include "../window_gui.h" +#include "../window_func.h" +#include "../viewport_func.h" +#include "../gfx_func.h" + +#include "saveload.h" + +extern TileIndex _cur_tileloop_tile; + +/* Keep track of current game position */ +int _saved_scrollpos_x; +int _saved_scrollpos_y; + +void SaveViewportBeforeSaveGame() +{ + const Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + + if (w != NULL) { + _saved_scrollpos_x = w->viewport->scrollpos_x; + _saved_scrollpos_y = w->viewport->scrollpos_y; + _saved_scrollpos_zoom = w->viewport->zoom; + } +} + +void ResetViewportAfterLoadGame() +{ + Window *w = FindWindowById(WC_MAIN_WINDOW, 0); + + w->viewport->scrollpos_x = _saved_scrollpos_x; + w->viewport->scrollpos_y = _saved_scrollpos_y; + w->viewport->dest_scrollpos_x = _saved_scrollpos_x; + w->viewport->dest_scrollpos_y = _saved_scrollpos_y; + + ViewPort *vp = w->viewport; + vp->zoom = min(_saved_scrollpos_zoom, ZOOM_LVL_MAX); + vp->virtual_width = ScaleByZoom(vp->width, vp->zoom); + vp->virtual_height = ScaleByZoom(vp->height, vp->zoom); + + DoZoomInOutWindow(ZOOM_NONE, w); // update button status + MarkWholeScreenDirty(); +} + + +static const SaveLoadGlobVarList _date_desc[] = { + SLEG_CONDVAR(_date, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLEG_CONDVAR(_date, SLE_INT32, 31, SL_MAX_VERSION), + SLEG_VAR(_date_fract, SLE_UINT16), + SLEG_VAR(_tick_counter, SLE_UINT16), + SLEG_VAR(_vehicle_id_ctr_day, SLE_UINT16), + SLEG_VAR(_age_cargo_skip_counter, SLE_UINT8), + SLE_CONDNULL(1, 0, 45), + SLEG_CONDVAR(_cur_tileloop_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLEG_CONDVAR(_cur_tileloop_tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLEG_VAR(_disaster_delay, SLE_UINT16), + SLEG_VAR(_station_tick_ctr, SLE_UINT16), + SLEG_VAR(_random.state[0], SLE_UINT32), + SLEG_VAR(_random.state[1], SLE_UINT32), + SLEG_CONDVAR(_cur_town_ctr, SLE_FILE_U8 | SLE_VAR_U32, 0, 9), + SLEG_CONDVAR(_cur_town_ctr, SLE_UINT32, 10, SL_MAX_VERSION), + SLEG_VAR(_cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32), + SLEG_VAR(_next_competitor_start, SLE_FILE_U16 | SLE_VAR_U32), + SLEG_VAR(_trees_tick_ctr, SLE_UINT8), + SLEG_CONDVAR(_pause_game, SLE_UINT8, 4, SL_MAX_VERSION), + SLEG_CONDVAR(_cur_town_iter, SLE_UINT32, 11, SL_MAX_VERSION), + SLEG_END() +}; + +/* Save load date related variables as well as persistent tick counters + * XXX: currently some unrelated stuff is just put here */ +static void SaveLoad_DATE() +{ + SlGlobList(_date_desc); +} + + +static const SaveLoadGlobVarList _view_desc[] = { + SLEG_CONDVAR(_saved_scrollpos_x, SLE_FILE_I16 | SLE_VAR_I32, 0, 5), + SLEG_CONDVAR(_saved_scrollpos_x, SLE_INT32, 6, SL_MAX_VERSION), + SLEG_CONDVAR(_saved_scrollpos_y, SLE_FILE_I16 | SLE_VAR_I32, 0, 5), + SLEG_CONDVAR(_saved_scrollpos_y, SLE_INT32, 6, SL_MAX_VERSION), + SLEG_VAR(_saved_scrollpos_zoom, SLE_UINT8), + SLEG_END() +}; + +static void SaveLoad_VIEW() +{ + SlGlobList(_view_desc); +} + +extern const ChunkHandler _misc_chunk_handlers[] = { + { 'DATE', SaveLoad_DATE, SaveLoad_DATE, CH_RIFF}, + { 'VIEW', SaveLoad_VIEW, SaveLoad_VIEW, CH_RIFF | CH_LAST}, +}; diff --git a/src/saveload/newgrf_sl.cpp b/src/saveload/newgrf_sl.cpp new file mode 100644 index 000000000..5269f235d --- /dev/null +++ b/src/saveload/newgrf_sl.cpp @@ -0,0 +1,52 @@ +/* $Id$ */ + +/** @file newgrf_sl.cpp Code handling saving and loading of newgrf config */ + +#include "../stdafx.h" +#include "../newgrf_config.h" +#include "../core/bitmath_func.hpp" +#include "../core/alloc_func.hpp" +#include "../gfx_func.h" + +#include "saveload.h" + +static const SaveLoad _grfconfig_desc[] = { + SLE_STR(GRFConfig, filename, SLE_STR, 0x40), + SLE_VAR(GRFConfig, grfid, SLE_UINT32), + SLE_ARR(GRFConfig, md5sum, SLE_UINT8, 16), + SLE_ARR(GRFConfig, param, SLE_UINT32, 0x80), + SLE_VAR(GRFConfig, num_params, SLE_UINT8), + SLE_CONDVAR(GRFConfig, windows_paletted, SLE_BOOL, 101, SL_MAX_VERSION), + SLE_END() +}; + + +static void Save_NGRF() +{ + int index = 0; + + for (GRFConfig *c = _grfconfig; c != NULL; c = c->next) { + if (HasBit(c->flags, GCF_STATIC)) continue; + SlSetArrayIndex(index++); + SlObject(c, _grfconfig_desc); + } +} + + +static void Load_NGRF() +{ + ClearGRFConfigList(&_grfconfig); + while (SlIterateArray() != -1) { + GRFConfig *c = CallocT<GRFConfig>(1); + SlObject(c, _grfconfig_desc); + if (CheckSavegameVersion(101)) c->windows_paletted = (_use_palette == PAL_WINDOWS); + AppendToGRFConfigList(&_grfconfig, c); + } + + /* Append static NewGRF configuration */ + AppendStaticGRFConfigs(&_grfconfig); +} + +extern const ChunkHandler _newgrf_chunk_handlers[] = { + { 'NGRF', Save_NGRF, Load_NGRF, CH_ARRAY | CH_LAST } +}; diff --git a/src/saveload/oldloader.cpp b/src/saveload/oldloader.cpp new file mode 100644 index 000000000..532d58f8c --- /dev/null +++ b/src/saveload/oldloader.cpp @@ -0,0 +1,1736 @@ +/* $Id$ */ + +/** @file oldloader.cpp Loading of old TTD(patch) savegames. */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../station_map.h" +#include "../town.h" +#include "../industry.h" +#include "../company_func.h" +#include "../company_base.h" +#include "../aircraft.h" +#include "../roadveh.h" +#include "../ship.h" +#include "../train.h" +#include "../signs_base.h" +#include "../debug.h" +#include "../depot_base.h" +#include "../newgrf_config.h" +#include "../ai/ai.h" +#include "../ai/default/default.h" +#include "../zoom_func.h" +#include "../functions.h" +#include "../date_func.h" +#include "../vehicle_func.h" +#include "../variables.h" +#include "../strings_func.h" +#include "../effectvehicle_base.h" + +#include "table/strings.h" + +#include "saveload.h" +#include "saveload_internal.h" + +enum { + HEADER_SIZE = 49, + BUFFER_SIZE = 4096, + + OLD_MAP_SIZE = 256 * 256 +}; + +struct LoadgameState { + FILE *file; + + uint chunk_size; + + bool decoding; + byte decode_char; + + uint buffer_count; + uint buffer_cur; + byte buffer[BUFFER_SIZE]; + + uint total_read; + bool failed; +}; + +/* OldChunk-Type */ +enum OldChunkType { + OC_SIMPLE = 0, + OC_NULL = 1, + OC_CHUNK = 2, + OC_ASSERT = 3, + /* 8 bits allocated (256 max) */ + + OC_VAR_I8 = 1 << 8, + OC_VAR_U8 = 2 << 8, + OC_VAR_I16 = 3 << 8, + OC_VAR_U16 = 4 << 8, + OC_VAR_I32 = 5 << 8, + OC_VAR_U32 = 6 << 8, + OC_VAR_I64 = 7 << 8, + /* 8 bits allocated (256 max) */ + + OC_FILE_I8 = 1 << 16, + OC_FILE_U8 = 2 << 16, + OC_FILE_I16 = 3 << 16, + OC_FILE_U16 = 4 << 16, + OC_FILE_I32 = 5 << 16, + OC_FILE_U32 = 6 << 16, + /* 8 bits allocated (256 max) */ + + OC_INT8 = OC_VAR_I8 | OC_FILE_I8, + OC_UINT8 = OC_VAR_U8 | OC_FILE_U8, + OC_INT16 = OC_VAR_I16 | OC_FILE_I16, + OC_UINT16 = OC_VAR_U16 | OC_FILE_U16, + OC_INT32 = OC_VAR_I32 | OC_FILE_I32, + OC_UINT32 = OC_VAR_U32 | OC_FILE_U32, + + OC_TILE = OC_VAR_U32 | OC_FILE_U16, + + /** + * Dereference the pointer once before writing to it, + * so we do not have to use big static arrays. + */ + OC_DEREFERENCE_POINTER = 1 << 31, + + OC_END = 0 ///< End of the whole chunk, all 32 bits set to zero +}; + +DECLARE_ENUM_AS_BIT_SET(OldChunkType); + +typedef bool OldChunkProc(LoadgameState *ls, int num); + +struct OldChunks { + OldChunkType type; ///< Type of field + uint32 amount; ///< Amount of fields + + void *ptr; ///< Pointer where to save the data (may only be set if offset is 0) + uint offset; ///< Offset from basepointer (may only be set if ptr is NULL) + OldChunkProc *proc; ///< Pointer to function that is called with OC_CHUNK +}; + +/* If it fails, check lines above.. */ +assert_compile(sizeof(TileIndex) == 4); + +extern SavegameType _savegame_type; +extern uint32 _ttdp_version; + +static uint32 _bump_assert_value; +static bool _read_ttdpatch_flags; + +static OldChunkType GetOldChunkType(OldChunkType type) {return (OldChunkType)GB(type, 0, 8);} +static OldChunkType GetOldChunkVarType(OldChunkType type) {return (OldChunkType)(GB(type, 8, 8) << 8);} +static OldChunkType GetOldChunkFileType(OldChunkType type) {return (OldChunkType)(GB(type, 16, 8) << 16);} + +static inline byte CalcOldVarLen(OldChunkType type) +{ + static const byte type_mem_size[] = {0, 1, 1, 2, 2, 4, 4, 8}; + byte length = GB(type, 8, 8); + assert(length != 0 && length < lengthof(type_mem_size)); + return type_mem_size[length]; +} + +/** + * + * Reads a byte from a file (do not call yourself, use ReadByte()) + * + */ +static byte ReadByteFromFile(LoadgameState *ls) +{ + /* To avoid slow reads, we read BUFFER_SIZE of bytes per time + and just return a byte per time */ + if (ls->buffer_cur >= ls->buffer_count) { + /* Read some new bytes from the file */ + int count = (int)fread(ls->buffer, 1, BUFFER_SIZE, ls->file); + + /* We tried to read, but there is nothing in the file anymore.. */ + if (count == 0) { + DEBUG(oldloader, 0, "Read past end of file, loading failed"); + ls->failed = true; + } + + ls->buffer_count = count; + ls->buffer_cur = 0; + } + + return ls->buffer[ls->buffer_cur++]; +} + +/** + * + * Reads a byte from the buffer and decompress if needed + * + */ +static byte ReadByte(LoadgameState *ls) +{ + /* Old savegames have a nice compression algorithm (RLE) + which means that we have a chunk, which starts with a length + byte. If that byte is negative, we have to repeat the next byte + that many times ( + 1). Else, we need to read that amount of bytes. + Works pretty good if you have many zero's behind eachother */ + + if (ls->chunk_size == 0) { + /* Read new chunk */ + int8 new_byte = ReadByteFromFile(ls); + + if (new_byte < 0) { + /* Repeat next char for new_byte times */ + ls->decoding = true; + ls->decode_char = ReadByteFromFile(ls); + ls->chunk_size = -new_byte + 1; + } else { + ls->decoding = false; + ls->chunk_size = new_byte + 1; + } + } + + ls->total_read++; + ls->chunk_size--; + + return ls->decoding ? ls->decode_char : ReadByteFromFile(ls); +} + +static inline uint16 ReadUint16(LoadgameState *ls) +{ + byte x = ReadByte(ls); + return x | ReadByte(ls) << 8; +} + +static inline uint32 ReadUint32(LoadgameState *ls) +{ + uint16 x = ReadUint16(ls); + return x | ReadUint16(ls) << 16; +} + +/** + * + * Loads a chunk from the old savegame + * + */ +static bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks) +{ + const OldChunks *chunk = chunks; + byte *base_ptr = (byte*)base; + + while (chunk->type != OC_END) { + byte *ptr = (byte*)chunk->ptr; + if ((chunk->type & OC_DEREFERENCE_POINTER) != 0) ptr = *(byte**)ptr; + + for (uint i = 0; i < chunk->amount; i++) { + if (ls->failed) return false; + + /* Handle simple types */ + if (GetOldChunkType(chunk->type) != 0) { + switch (GetOldChunkType(chunk->type)) { + /* Just read the byte and forget about it */ + case OC_NULL: ReadByte(ls); break; + + case OC_CHUNK: + /* Call function, with 'i' as parameter to tell which item we + * are going to read */ + if (!chunk->proc(ls, i)) return false; + break; + + case OC_ASSERT: + DEBUG(oldloader, 4, "Assert point: 0x%X / 0x%X", ls->total_read, chunk->offset + _bump_assert_value); + if (ls->total_read != chunk->offset + _bump_assert_value) ls->failed = true; + default: break; + } + } else { + uint64 res = 0; + + /* Reading from the file: bits 16 to 23 have the FILE type */ + switch (GetOldChunkFileType(chunk->type)) { + case OC_FILE_I8: res = (int8)ReadByte(ls); break; + case OC_FILE_U8: res = ReadByte(ls); break; + case OC_FILE_I16: res = (int16)ReadUint16(ls); break; + case OC_FILE_U16: res = ReadUint16(ls); break; + case OC_FILE_I32: res = (int32)ReadUint32(ls); break; + case OC_FILE_U32: res = ReadUint32(ls); break; + default: NOT_REACHED(); + } + + /* Sanity check */ + assert(base_ptr != NULL || chunk->ptr != NULL); + + /* Writing to the var: bits 8 to 15 have the VAR type */ + if (chunk->ptr == NULL) ptr = base_ptr + chunk->offset; + + /* Write the data */ + switch (GetOldChunkVarType(chunk->type)) { + case OC_VAR_I8: *(int8 *)ptr = GB(res, 0, 8); break; + case OC_VAR_U8: *(uint8 *)ptr = GB(res, 0, 8); break; + case OC_VAR_I16:*(int16 *)ptr = GB(res, 0, 16); break; + case OC_VAR_U16:*(uint16*)ptr = GB(res, 0, 16); break; + case OC_VAR_I32:*(int32 *)ptr = res; break; + case OC_VAR_U32:*(uint32*)ptr = res; break; + case OC_VAR_I64:*(int64 *)ptr = res; break; + default: NOT_REACHED(); + } + + /* Increase pointer base for arrays when looping */ + if (chunk->amount > 1 && chunk->ptr != NULL) ptr += CalcOldVarLen(chunk->type); + } + } + + chunk++; + } + + return true; +} + +/** + * + * Initialize some data before reading + * + */ +static void InitLoading(LoadgameState *ls) +{ + ls->chunk_size = 0; + ls->total_read = 0; + ls->failed = false; + + ls->decoding = false; + ls->decode_char = 0; + + ls->buffer_cur = 0; + ls->buffer_count = 0; + memset(ls->buffer, 0, BUFFER_SIZE); + + _bump_assert_value = 0; + + _savegame_type = SGT_TTD; + _ttdp_version = 0; + + _read_ttdpatch_flags = false; +} + + +/* + * Begin -- Stuff to fix the savegames to be OpenTTD compatible + */ + +extern uint32 GetOldTownName(uint32 townnameparts, byte old_town_name_type); + +static void FixOldTowns() +{ + Town *town; + + /* Convert town-names if needed */ + FOR_ALL_TOWNS(town) { + if (IsInsideMM(town->townnametype, 0x20C1, 0x20C3)) { + town->townnametype = SPECSTR_TOWNNAME_ENGLISH + _settings_game.game_creation.town_name; + town->townnameparts = GetOldTownName(town->townnameparts, _settings_game.game_creation.town_name); + } + } +} + +static StringID *_old_vehicle_names = NULL; + +static void FixOldVehicles() +{ + Vehicle* v; + + FOR_ALL_VEHICLES(v) { + v->name = CopyFromOldName(_old_vehicle_names[v->index]); + + /* We haven't used this bit for stations for ages */ + if (v->type == VEH_ROAD && + v->u.road.state != RVSB_IN_DEPOT && + v->u.road.state != RVSB_WORMHOLE) { + ClrBit(v->u.road.state, RVS_IS_STOPPING); + } + + /* The subtype should be 0, but it sometimes isn't :( */ + if (v->type == VEH_ROAD) v->subtype = 0; + + /* Sometimes primary vehicles would have a nothing (invalid) order + * or vehicles that could not have an order would still have a + * (loading) order which causes assertions and the like later on. + */ + if (!IsCompanyBuildableVehicleType(v) || + (v->IsPrimaryVehicle() && v->current_order.IsType(OT_NOTHING))) { + v->current_order.MakeDummy(); + } + + /* Shared orders are fixed in AfterLoadVehicles now */ + } +} + +/* + * End -- Stuff to fix the savegames to be OpenTTD compatible + */ + + +/* Help: + * - OCL_SVAR: load 'type' to offset 'offset' in a struct of type 'base', which must also + * be given via base in LoadChunk() as real pointer + * - OCL_VAR: load 'type' to a global var + * - OCL_END: every struct must end with this + * - OCL_NULL: read 'amount' of bytes and send them to /dev/null or something + * - OCL_CHUNK: load an other proc to load a part of the savegame, 'amount' times + * - OCL_ASSERT: to check if we are really at the place we expect to be.. because old savegames are too binary to be sure ;) + */ +#define OCL_SVAR(type, base, offset) { type, 1, NULL, (uint)cpp_offsetof(base, offset), NULL } +#define OCL_VAR(type, amount, pointer) { type, amount, pointer, 0, NULL } +#define OCL_END() { OC_END, 0, NULL, 0, NULL } +#define OCL_NULL(amount) { OC_NULL, amount, NULL, 0, NULL } +#define OCL_CHUNK(amount, proc) { OC_CHUNK, amount, NULL, 0, proc } +#define OCL_ASSERT(size) { OC_ASSERT, 1, NULL, size, NULL } + +/* The savegames has some hard-coded pointers, because it always enters the same + piece of memory.. we don't.. so we need to remap ;) + Old Towns are 94 bytes big + Old Orders are 2 bytes big */ +#define REMAP_TOWN_IDX(x) ((x) - (0x0459154 - 0x0458EF0)) / 94 +#define REMAP_ORDER_IDX(x) ((x) - (0x045AB08 - 0x0458EF0)) / 2 + +extern TileIndex *_animated_tile_list; +extern uint _animated_tile_count; +extern char *_old_name_array; + +static byte _old_vehicle_multiplier; +static uint8 *_old_map3; +static uint32 _old_town_index; +static uint16 _old_string_id; +static uint16 _old_string_id_2; +static uint16 _old_extra_chunk_nums; + +static void ReadTTDPatchFlags() +{ + if (_read_ttdpatch_flags) return; + + _read_ttdpatch_flags = true; + + /* TTDPatch misuses _old_map3 for flags.. read them! */ + _old_vehicle_multiplier = _old_map3[0]; + /* Somehow.... there was an error in some savegames, so 0 becomes 1 + and 1 becomes 2. The rest of the values are okay */ + if (_old_vehicle_multiplier < 2) _old_vehicle_multiplier++; + + _old_vehicle_names = MallocT<StringID>(_old_vehicle_multiplier * 850); + + /* TTDPatch increases the Vehicle-part in the middle of the game, + so if the multipler is anything else but 1, the assert fails.. + bump the assert value so it doesn't! + (1 multipler == 850 vehicles + 1 vehicle == 128 bytes */ + _bump_assert_value = (_old_vehicle_multiplier - 1) * 850 * 128; + + for (uint i = 0; i < 17; i++) { // check tile 0, too + if (_old_map3[i] != 0) _savegame_type = SGT_TTDP1; + } + + /* Check if we have a modern TTDPatch savegame (has extra data all around) */ + if (memcmp(&_old_map3[0x1FFFA], "TTDp", 4) == 0) _savegame_type = SGT_TTDP2; + + _old_extra_chunk_nums = _old_map3[_savegame_type == SGT_TTDP2 ? 0x1FFFE : 0x2]; + + /* Clean the misused places */ + for (uint i = 0; i < 17; i++) _old_map3[i] = 0; + for (uint i = 0x1FE00; i < 0x20000; i++) _old_map3[i] = 0; + + if (_savegame_type == SGT_TTDP2) DEBUG(oldloader, 2, "Found TTDPatch game"); + + DEBUG(oldloader, 3, "Vehicle-multiplier is set to %d (%d vehicles)", _old_vehicle_multiplier, _old_vehicle_multiplier * 850); +} + +static const OldChunks town_chunk[] = { + OCL_SVAR( OC_TILE, Town, xy ), + OCL_NULL( 2 ), ///< population, no longer in use + OCL_SVAR( OC_UINT16, Town, townnametype ), + OCL_SVAR( OC_UINT32, Town, townnameparts ), + OCL_SVAR( OC_UINT8, Town, grow_counter ), + OCL_NULL( 1 ), ///< sort_index, no longer in use + OCL_NULL( 4 ), ///< sign-coordinates, no longer in use + OCL_NULL( 2 ), ///< namewidth, no longer in use + OCL_SVAR( OC_UINT16, Town, flags12 ), + OCL_NULL( 10 ), ///< radius, no longer in use + + OCL_SVAR( OC_UINT16, Town, ratings[0] ), + OCL_SVAR( OC_UINT16, Town, ratings[1] ), + OCL_SVAR( OC_UINT16, Town, ratings[2] ), + OCL_SVAR( OC_UINT16, Town, ratings[3] ), + OCL_SVAR( OC_UINT16, Town, ratings[4] ), + OCL_SVAR( OC_UINT16, Town, ratings[5] ), + OCL_SVAR( OC_UINT16, Town, ratings[6] ), + OCL_SVAR( OC_UINT16, Town, ratings[7] ), + + /* XXX - This is pretty odd.. we read 32bit, but only write 16bit.. sure there is + nothing changed ? ? */ + OCL_SVAR( OC_FILE_U32 | OC_VAR_U16, Town, have_ratings ), + OCL_SVAR( OC_FILE_U32 | OC_VAR_U16, Town, statues ), + OCL_NULL( 2 ), ///< num_houses, no longer in use + OCL_SVAR( OC_UINT8, Town, time_until_rebuild ), + OCL_SVAR( OC_UINT8, Town, growth_rate ), + + OCL_SVAR( OC_UINT16, Town, new_max_pass ), + OCL_SVAR( OC_UINT16, Town, new_max_mail ), + OCL_SVAR( OC_UINT16, Town, new_act_pass ), + OCL_SVAR( OC_UINT16, Town, new_act_mail ), + OCL_SVAR( OC_UINT16, Town, max_pass ), + OCL_SVAR( OC_UINT16, Town, max_mail ), + OCL_SVAR( OC_UINT16, Town, act_pass ), + OCL_SVAR( OC_UINT16, Town, act_mail ), + + OCL_SVAR( OC_UINT8, Town, pct_pass_transported ), + OCL_SVAR( OC_UINT8, Town, pct_mail_transported ), + + OCL_SVAR( OC_UINT16, Town, new_act_food ), + OCL_SVAR( OC_UINT16, Town, new_act_water ), + OCL_SVAR( OC_UINT16, Town, act_food ), + OCL_SVAR( OC_UINT16, Town, act_water ), + + OCL_SVAR( OC_UINT8, Town, road_build_months ), + OCL_SVAR( OC_UINT8, Town, fund_buildings_months ), + + OCL_NULL( 8 ), ///< some junk at the end of the record + + OCL_END() +}; +static bool LoadOldTown(LoadgameState *ls, int num) +{ + Town *t = new (num) Town(); + if (!LoadChunk(ls, t, town_chunk)) return false; + + if (t->xy == 0) t->xy = INVALID_TILE; + + return true; +} + +static uint16 _old_order; +static const OldChunks order_chunk[] = { + OCL_VAR ( OC_UINT16, 1, &_old_order ), + OCL_END() +}; + +static bool LoadOldOrder(LoadgameState *ls, int num) +{ + if (!LoadChunk(ls, NULL, order_chunk)) return false; + + new (num) Order(UnpackOldOrder(_old_order)); + + /* Relink the orders to eachother (in TTD(Patch) the orders for one + vehicle are behind eachother, with an invalid order (OT_NOTHING) as indication that + it is the last order */ + if (num > 0 && GetOrder(num)->IsValid()) + GetOrder(num - 1)->next = GetOrder(num); + + return true; +} + +static bool LoadOldAnimTileList(LoadgameState *ls, int num) +{ + /* This is sligthly hackish - we must load a chunk into an array whose + * address isn't static, but instead pointed to by _animated_tile_list. + * To achieve that, create an OldChunks list on the stack on the fly. + * The list cannot be static because the value of _animated_tile_list + * can change between calls. */ + + const OldChunks anim_chunk[] = { + OCL_VAR ( OC_TILE, 256, _animated_tile_list ), + OCL_END () + }; + + if (!LoadChunk(ls, NULL, anim_chunk)) return false; + + /* Update the animated tile counter by counting till the first zero in the array */ + for (_animated_tile_count = 0; _animated_tile_count < 256; _animated_tile_count++) { + if (_animated_tile_list[_animated_tile_count] == 0) break; + } + + return true; +} + +static const OldChunks depot_chunk[] = { + OCL_SVAR( OC_TILE, Depot, xy ), + OCL_VAR ( OC_UINT32, 1, &_old_town_index ), + OCL_END() +}; + +static bool LoadOldDepot(LoadgameState *ls, int num) +{ + Depot *d = new (num) Depot(); + if (!LoadChunk(ls, d, depot_chunk)) return false; + + if (d->xy != 0) { + GetDepot(num)->town_index = REMAP_TOWN_IDX(_old_town_index); + } else { + d->xy = INVALID_TILE; + } + + return true; +} + +static int32 _old_price; +static uint16 _old_price_frac; +static const OldChunks price_chunk[] = { + OCL_VAR ( OC_INT32, 1, &_old_price ), + OCL_VAR ( OC_UINT16, 1, &_old_price_frac ), + OCL_END() +}; + +static bool LoadOldPrice(LoadgameState *ls, int num) +{ + if (!LoadChunk(ls, NULL, price_chunk)) return false; + + /* We use a struct to store the prices, but they are ints in a row.. + so just access the struct as an array of int32's */ + ((Money*)&_price)[num] = _old_price; + _price_frac[num] = _old_price_frac; + + return true; +} + +static const OldChunks cargo_payment_rate_chunk[] = { + OCL_VAR ( OC_INT32, 1, &_old_price ), + OCL_VAR ( OC_UINT16, 1, &_old_price_frac ), + + OCL_NULL( 2 ), ///< Junk + OCL_END() +}; + +static bool LoadOldCargoPaymentRate(LoadgameState *ls, int num) +{ + if (!LoadChunk(ls, NULL, cargo_payment_rate_chunk)) return false; + + _cargo_payment_rates[num] = -_old_price; + _cargo_payment_rates_frac[num] = _old_price_frac; + + return true; +} + +static uint _current_station_id; +static uint16 _waiting_acceptance; +static uint8 _cargo_source; +static uint8 _cargo_days; + +static const OldChunks goods_chunk[] = { + OCL_VAR ( OC_UINT16, 1, &_waiting_acceptance ), + OCL_SVAR( OC_UINT8, GoodsEntry, days_since_pickup ), + OCL_SVAR( OC_UINT8, GoodsEntry, rating ), + OCL_VAR ( OC_UINT8, 1, &_cargo_source ), + OCL_VAR ( OC_UINT8, 1, &_cargo_days ), + OCL_SVAR( OC_UINT8, GoodsEntry, last_speed ), + OCL_SVAR( OC_UINT8, GoodsEntry, last_age ), + + OCL_END() +}; + +static bool LoadOldGood(LoadgameState *ls, int num) +{ + Station *st = GetStation(_current_station_id); + GoodsEntry *ge = &st->goods[num]; + bool ret = LoadChunk(ls, ge, goods_chunk); + if (!ret) return false; + + SB(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE, 1, HasBit(_waiting_acceptance, 15)); + SB(ge->acceptance_pickup, GoodsEntry::PICKUP, 1, _cargo_source != 0xFF); + if (GB(_waiting_acceptance, 0, 12) != 0) { + CargoPacket *cp = new CargoPacket(); + cp->source = (_cargo_source == 0xFF) ? INVALID_STATION : _cargo_source; + cp->count = GB(_waiting_acceptance, 0, 12); + cp->days_in_transit = _cargo_days; + ge->cargo.Append(cp); + } + return ret; +} + +static const OldChunks station_chunk[] = { + OCL_SVAR( OC_TILE, Station, xy ), + OCL_VAR ( OC_UINT32, 1, &_old_town_index ), + + OCL_NULL( 4 ), ///< bus/lorry tile + OCL_SVAR( OC_TILE, Station, train_tile ), + OCL_SVAR( OC_TILE, Station, airport_tile ), + OCL_SVAR( OC_TILE, Station, dock_tile ), + OCL_SVAR( OC_UINT8, Station, trainst_w ), + + OCL_NULL( 1 ), ///< sort-index, no longer in use + OCL_NULL( 2 ), ///< sign-width, no longer in use + + OCL_VAR ( OC_UINT16, 1, &_old_string_id ), + + OCL_NULL( 4 ), ///< sign left/top, no longer in use + + OCL_SVAR( OC_UINT16, Station, had_vehicle_of_type ), + + OCL_CHUNK( 12, LoadOldGood ), + + OCL_SVAR( OC_UINT8, Station, time_since_load ), + OCL_SVAR( OC_UINT8, Station, time_since_unload ), + OCL_SVAR( OC_UINT8, Station, delete_ctr ), + OCL_SVAR( OC_UINT8, Station, owner ), + OCL_SVAR( OC_UINT8, Station, facilities ), + OCL_SVAR( OC_UINT8, Station, airport_type ), + /* Bus/truck status, no longer in use + * Blocked months + * Unknown + */ + OCL_NULL( 4 ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Station, airport_flags ), + OCL_NULL( 2 ), ///< last_vehicle. now last_vehicle_type + + OCL_NULL( 4 ), ///< Junk at end of chunk + + OCL_END() +}; +static bool LoadOldStation(LoadgameState *ls, int num) +{ + Station *st = new (num) Station(); + _current_station_id = num; + + if (!LoadChunk(ls, st, station_chunk)) + return false; + + if (st->xy != 0) { + st->town = GetTown(REMAP_TOWN_IDX(_old_town_index)); + st->string_id = RemapOldStringID(_old_string_id); + } else { + st->xy = INVALID_TILE; + } + + return true; +} + +static const OldChunks industry_chunk[] = { + OCL_SVAR( OC_TILE, Industry, xy ), + OCL_VAR ( OC_UINT32, 1, &_old_town_index ), + OCL_SVAR( OC_UINT8, Industry, width ), + OCL_SVAR( OC_UINT8, Industry, height ), + OCL_NULL( 2 ), ///< used to be industry's produced_cargo + + OCL_SVAR( OC_UINT16, Industry, produced_cargo_waiting[0] ), + OCL_SVAR( OC_UINT16, Industry, produced_cargo_waiting[1] ), + + OCL_SVAR( OC_UINT8, Industry, production_rate[0] ), + OCL_SVAR( OC_UINT8, Industry, production_rate[1] ), + + OCL_NULL( 3 ), ///< used to be industry's accepts_cargo + + OCL_SVAR( OC_UINT8, Industry, prod_level ), + + OCL_SVAR( OC_UINT16, Industry, this_month_production[0] ), + OCL_SVAR( OC_UINT16, Industry, this_month_production[1] ), + OCL_SVAR( OC_UINT16, Industry, this_month_transported[0] ), + OCL_SVAR( OC_UINT16, Industry, this_month_transported[1] ), + + OCL_SVAR( OC_UINT8, Industry, last_month_pct_transported[0] ), + OCL_SVAR( OC_UINT8, Industry, last_month_pct_transported[1] ), + + OCL_SVAR( OC_UINT16, Industry, last_month_production[0] ), + OCL_SVAR( OC_UINT16, Industry, last_month_production[1] ), + OCL_SVAR( OC_UINT16, Industry, last_month_transported[0] ), + OCL_SVAR( OC_UINT16, Industry, last_month_transported[1] ), + + OCL_SVAR( OC_UINT8, Industry, type ), + OCL_SVAR( OC_UINT8, Industry, owner ), + OCL_SVAR( OC_UINT8, Industry, random_color ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_I32, Industry, last_prod_year ), + OCL_SVAR( OC_UINT16, Industry, counter ), + OCL_SVAR( OC_UINT8, Industry, was_cargo_delivered ), + + OCL_NULL( 9 ), ///< Random junk at the end of this chunk + + OCL_END() +}; + +static bool LoadOldIndustry(LoadgameState *ls, int num) +{ + Industry *i = new (num) Industry(); + if (!LoadChunk(ls, i, industry_chunk)) return false; + + if (i->xy != 0) { + i->town = GetTown(REMAP_TOWN_IDX(_old_town_index)); + IncIndustryTypeCount(i->type); + } else { + i->xy = INVALID_TILE; + } + + return true; +} + +static CompanyID _current_company_id; +static int32 _old_yearly; + +static const OldChunks _company_yearly_chunk[] = { + OCL_VAR( OC_INT32, 1, &_old_yearly ), + OCL_END() +}; + +static bool OldCompanyYearly(LoadgameState *ls, int num) +{ + int i; + Company *c = GetCompany(_current_company_id); + + for (i = 0; i < 13; i++) { + if (!LoadChunk(ls, NULL, _company_yearly_chunk)) return false; + + c->yearly_expenses[num][i] = _old_yearly; + } + + return true; +} + +static const OldChunks _company_economy_chunk[] = { + OCL_SVAR( OC_FILE_I32 | OC_VAR_I64, CompanyEconomyEntry, income ), + OCL_SVAR( OC_FILE_I32 | OC_VAR_I64, CompanyEconomyEntry, expenses ), + OCL_SVAR( OC_INT32, CompanyEconomyEntry, delivered_cargo ), + OCL_SVAR( OC_INT32, CompanyEconomyEntry, performance_history ), + OCL_SVAR( OC_FILE_I32 | OC_VAR_I64, CompanyEconomyEntry, company_value ), + + OCL_END() +}; + +static bool OldCompanyEconomy(LoadgameState *ls, int num) +{ + int i; + Company *c = GetCompany(_current_company_id); + + if (!LoadChunk(ls, &c->cur_economy, _company_economy_chunk)) return false; + + /* Don't ask, but the number in TTD(Patch) are inversed to OpenTTD */ + c->cur_economy.income = -c->cur_economy.income; + c->cur_economy.expenses = -c->cur_economy.expenses; + + for (i = 0; i < 24; i++) { + if (!LoadChunk(ls, &c->old_economy[i], _company_economy_chunk)) return false; + + c->old_economy[i].income = -c->old_economy[i].income; + c->old_economy[i].expenses = -c->old_economy[i].expenses; + } + + return true; +} + +static const OldChunks _company_ai_build_rec_chunk[] = { + OCL_SVAR( OC_TILE, AiBuildRec, spec_tile ), + OCL_SVAR( OC_TILE, AiBuildRec, use_tile ), + OCL_SVAR( OC_UINT8, AiBuildRec, rand_rng ), + OCL_SVAR( OC_UINT8, AiBuildRec, cur_building_rule ), + OCL_SVAR( OC_UINT8, AiBuildRec, unk6 ), + OCL_SVAR( OC_UINT8, AiBuildRec, unk7 ), + OCL_SVAR( OC_UINT8, AiBuildRec, buildcmd_a ), + OCL_SVAR( OC_UINT8, AiBuildRec, buildcmd_b ), + OCL_SVAR( OC_UINT8, AiBuildRec, direction ), + OCL_SVAR( OC_UINT8, AiBuildRec, cargo ), + + OCL_NULL( 8 ), ///< Junk... + + OCL_END() +}; + +static bool OldLoadAIBuildRec(LoadgameState *ls, int num) +{ + Company *c = GetCompany(_current_company_id); + + switch (num) { + case 0: return LoadChunk(ls, &_companies_ai[c->index].src, _company_ai_build_rec_chunk); + case 1: return LoadChunk(ls, &_companies_ai[c->index].dst, _company_ai_build_rec_chunk); + case 2: return LoadChunk(ls, &_companies_ai[c->index].mid1, _company_ai_build_rec_chunk); + case 3: return LoadChunk(ls, &_companies_ai[c->index].mid2, _company_ai_build_rec_chunk); + } + + return false; +} +static const OldChunks _company_ai_chunk[] = { + OCL_SVAR( OC_UINT8, CompanyAI, state ), + OCL_NULL( 1 ), ///< Junk + OCL_SVAR( OC_UINT8, CompanyAI, state_mode ), + OCL_SVAR( OC_UINT16, CompanyAI, state_counter ), + OCL_SVAR( OC_UINT16, CompanyAI, timeout_counter ), + + OCL_CHUNK( 4, OldLoadAIBuildRec ), + + OCL_NULL( 20 ), ///< More junk + + OCL_SVAR( OC_UINT8, CompanyAI, cargo_type ), + OCL_SVAR( OC_UINT8, CompanyAI, num_wagons ), + OCL_SVAR( OC_UINT8, CompanyAI, build_kind ), + OCL_SVAR( OC_UINT8, CompanyAI, num_build_rec ), + OCL_SVAR( OC_UINT8, CompanyAI, num_loco_to_build ), + OCL_SVAR( OC_UINT8, CompanyAI, num_want_fullload ), + + OCL_NULL( 14 ), ///< Oh no more junk :| + + OCL_NULL( 2 ), ///< Loco-id, not used + + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[0] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[1] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[2] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[3] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[4] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[5] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[6] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[7] ), + OCL_SVAR( OC_UINT16, CompanyAI, wagon_list[8] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[0] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[1] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[2] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[3] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[4] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[5] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[6] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[7] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[8] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[9] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[10] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[11] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[12] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[13] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[14] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[15] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[16] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[17] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[18] ), + OCL_SVAR( OC_UINT8, CompanyAI, order_list_blocks[19] ), + + OCL_SVAR( OC_UINT16, CompanyAI, start_tile_a ), + OCL_SVAR( OC_UINT16, CompanyAI, start_tile_b ), + OCL_SVAR( OC_UINT16, CompanyAI, cur_tile_a ), + OCL_SVAR( OC_UINT16, CompanyAI, cur_tile_b ), + + OCL_SVAR( OC_UINT8, CompanyAI, start_dir_a ), + OCL_SVAR( OC_UINT8, CompanyAI, start_dir_b ), + OCL_SVAR( OC_UINT8, CompanyAI, cur_dir_a ), + OCL_SVAR( OC_UINT8, CompanyAI, cur_dir_b ), + + OCL_SVAR( OC_UINT8, CompanyAI, banned_tile_count ), + + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[0] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[0] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[1] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[1] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[2] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[2] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[3] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[3] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[4] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[4] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[5] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[5] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[6] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[6] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[7] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[7] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[8] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[8] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[9] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[9] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[10] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[10] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[11] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[11] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[12] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[12] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[13] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[13] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[14] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[14] ), + OCL_SVAR( OC_TILE, CompanyAI, banned_tiles[15] ), + OCL_SVAR( OC_UINT8, CompanyAI, banned_val[15] ), + + OCL_SVAR( OC_UINT8, CompanyAI, railtype_to_use ), + OCL_SVAR( OC_UINT8, CompanyAI, route_type_mask ), + + OCL_END() +}; + +static bool OldCompanyAI(LoadgameState *ls, int num) +{ + return LoadChunk(ls, &_companies_ai[_current_company_id], _company_ai_chunk); +} + +uint8 ai_tick; +static const OldChunks _company_chunk[] = { + OCL_VAR ( OC_UINT16, 1, &_old_string_id ), + OCL_SVAR( OC_UINT32, Company, name_2 ), + OCL_SVAR( OC_UINT32, Company, face ), + OCL_VAR ( OC_UINT16, 1, &_old_string_id_2 ), + OCL_SVAR( OC_UINT32, Company, president_name_2 ), + + OCL_SVAR( OC_INT32, Company, money ), + OCL_SVAR( OC_INT32, Company, current_loan ), + + OCL_SVAR( OC_UINT8, Company, colour ), + OCL_SVAR( OC_UINT8, Company, money_fraction ), + OCL_SVAR( OC_UINT8, Company, quarters_of_bankrupcy ), + OCL_SVAR( OC_UINT8, Company, bankrupt_asked ), + OCL_SVAR( OC_FILE_U32 | OC_VAR_I64, Company, bankrupt_value ), + OCL_SVAR( OC_UINT16, Company, bankrupt_timeout ), + + OCL_SVAR( OC_UINT32, Company, cargo_types ), + + OCL_CHUNK( 3, OldCompanyYearly ), + OCL_CHUNK( 1, OldCompanyEconomy ), + + OCL_SVAR( OC_FILE_U16 | OC_VAR_I32, Company, inaugurated_year), + OCL_SVAR( OC_TILE, Company, last_build_coordinate ), + OCL_SVAR( OC_UINT8, Company, num_valid_stat_ent ), + + OCL_CHUNK( 1, OldCompanyAI ), + + OCL_SVAR( OC_UINT8, Company, block_preview ), + OCL_VAR( OC_UINT8, 1, &ai_tick ), + OCL_SVAR( OC_UINT8, Company, avail_railtypes ), + OCL_SVAR( OC_TILE, Company, location_of_HQ ), + OCL_SVAR( OC_UINT8, Company, share_owners[0] ), + OCL_SVAR( OC_UINT8, Company, share_owners[1] ), + OCL_SVAR( OC_UINT8, Company, share_owners[2] ), + OCL_SVAR( OC_UINT8, Company, share_owners[3] ), + + OCL_NULL( 8 ), ///< junk at end of chunk + + OCL_END() +}; + +static bool LoadOldCompany(LoadgameState *ls, int num) +{ + Company *c = new (num) Company(); + + _current_company_id = (CompanyID)num; + + if (!LoadChunk(ls, c, _company_chunk)) return false; + + if (_old_string_id == 0) { + delete c; + return true; + } + + c->name_1 = RemapOldStringID(_old_string_id); + c->president_name_1 = RemapOldStringID(_old_string_id_2); + _companies_ai[_current_company_id].tick = ai_tick; + + if (num == 0) { + /* If the first company has no name, make sure we call it UNNAMED */ + if (c->name_1 == 0) + c->name_1 = STR_SV_UNNAMED; + } else { + /* Beside some multiplayer maps (1 on 1), which we don't official support, + * all other companys are an AI.. mark them as such */ + c->is_ai = true; + } + + /* Sometimes it is better to not ask.. in old scenarios, the money + * was always 893288 pounds. In the newer versions this is correct, + * but correct for those oldies + * Ps: this also means that if you had exact 893288 pounds, you will go back + * to 100000.. this is a very VERY small chance ;) */ + if (c->money == 893288) c->money = c->current_loan = 100000; + + _company_colours[num] = c->colour; + c->inaugurated_year -= ORIGINAL_BASE_YEAR; + + /* State 20 for AI companies is sell vehicle. Since the AI struct is not + * really figured out as of now, _companies_ai[c->index].cur_veh; needed for 'sell vehicle' + * is NULL and the function will crash. To fix this, just change the state + * to some harmless state, like 'loop vehicle'; 1 */ + if (!IsHumanCompany((CompanyID)num) && _companies_ai[c->index].state == 20) _companies_ai[c->index].state = 1; + + if (c->is_ai && (!_networking || _network_server) && _ai.enabled) + AI_StartNewAI(c->index); + + return true; +} + +static uint32 _old_order_ptr; +static uint16 _old_next_ptr; +static uint32 _current_vehicle_id; + +static const OldChunks vehicle_train_chunk[] = { + OCL_SVAR( OC_UINT8, VehicleRail, track ), + OCL_SVAR( OC_UINT8, VehicleRail, force_proceed ), + OCL_SVAR( OC_UINT16, VehicleRail, crash_anim_pos ), + OCL_SVAR( OC_UINT8, VehicleRail, railtype ), + + OCL_NULL( 5 ), ///< Junk + + OCL_END() +}; + +static const OldChunks vehicle_road_chunk[] = { + OCL_SVAR( OC_UINT8, VehicleRoad, state ), + OCL_SVAR( OC_UINT8, VehicleRoad, frame ), + OCL_SVAR( OC_UINT16, VehicleRoad, blocked_ctr ), + OCL_SVAR( OC_UINT8, VehicleRoad, overtaking ), + OCL_SVAR( OC_UINT8, VehicleRoad, overtaking_ctr ), + OCL_SVAR( OC_UINT16, VehicleRoad, crashed_ctr ), + OCL_SVAR( OC_UINT8, VehicleRoad, reverse_ctr ), + + OCL_NULL( 1 ), ///< Junk + + OCL_END() +}; + +static const OldChunks vehicle_ship_chunk[] = { + OCL_SVAR( OC_UINT8, VehicleShip, state ), + + OCL_NULL( 9 ), ///< Junk + + OCL_END() +}; + +static const OldChunks vehicle_air_chunk[] = { + OCL_SVAR( OC_UINT8, VehicleAir, pos ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, VehicleAir, targetairport ), + OCL_SVAR( OC_UINT16, VehicleAir, crashed_counter ), + OCL_SVAR( OC_UINT8, VehicleAir, state ), + + OCL_NULL( 5 ), ///< Junk + + OCL_END() +}; + +static const OldChunks vehicle_effect_chunk[] = { + OCL_SVAR( OC_UINT16, VehicleEffect, animation_state ), + OCL_SVAR( OC_UINT8, VehicleEffect, animation_substate ), + + OCL_NULL( 7 ), // Junk + + OCL_END() +}; + +static const OldChunks vehicle_disaster_chunk[] = { + OCL_SVAR( OC_UINT16, VehicleDisaster, image_override ), + OCL_SVAR( OC_UINT16, VehicleDisaster, big_ufo_destroyer_target ), + + OCL_NULL( 6 ), ///< Junk + + OCL_END() +}; + +static const OldChunks vehicle_empty_chunk[] = { + OCL_NULL( 10 ), ///< Junk + + OCL_END() +}; + +static bool LoadOldVehicleUnion(LoadgameState *ls, int num) +{ + Vehicle *v = GetVehicle(_current_vehicle_id); + uint temp = ls->total_read; + bool res; + + switch (v->type) { + default: NOT_REACHED(); + case VEH_INVALID : res = LoadChunk(ls, NULL, vehicle_empty_chunk); break; + case VEH_TRAIN : res = LoadChunk(ls, &v->u.rail, vehicle_train_chunk); break; + case VEH_ROAD : res = LoadChunk(ls, &v->u.road, vehicle_road_chunk); break; + case VEH_SHIP : res = LoadChunk(ls, &v->u.ship, vehicle_ship_chunk); break; + case VEH_AIRCRAFT: res = LoadChunk(ls, &v->u.air, vehicle_air_chunk); break; + case VEH_EFFECT : res = LoadChunk(ls, &v->u.effect, vehicle_effect_chunk); break; + case VEH_DISASTER: res = LoadChunk(ls, &v->u.disaster, vehicle_disaster_chunk); break; + } + + /* This chunk size should always be 10 bytes */ + if (ls->total_read - temp != 10) { + DEBUG(oldloader, 0, "Assert failed in VehicleUnion: invalid chunk size"); + return false; + } + + return res; +} + +static uint16 _cargo_count; + +static const OldChunks vehicle_chunk[] = { + OCL_SVAR( OC_UINT8, Vehicle, subtype ), + + OCL_NULL( 2 ), ///< Hash, calculated automatically + OCL_NULL( 2 ), ///< Index, calculated automatically + + OCL_VAR ( OC_UINT32, 1, &_old_order_ptr ), + OCL_VAR ( OC_UINT16, 1, &_old_order ), + + OCL_NULL ( 1 ), ///< num_orders, now calculated + OCL_SVAR( OC_UINT8, Vehicle, cur_order_index ), + OCL_SVAR( OC_TILE, Vehicle, dest_tile ), + OCL_SVAR( OC_UINT16, Vehicle, load_unload_time_rem ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Vehicle, date_of_last_service ), + OCL_SVAR( OC_UINT16, Vehicle, service_interval ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Vehicle, last_station_visited ), + OCL_SVAR( OC_UINT8, Vehicle, tick_counter ), + OCL_SVAR( OC_UINT16, Vehicle, max_speed ), + + OCL_SVAR( OC_FILE_U16 | OC_VAR_I32, Vehicle, x_pos ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_I32, Vehicle, y_pos ), + OCL_SVAR( OC_UINT8, Vehicle, z_pos ), + OCL_SVAR( OC_UINT8, Vehicle, direction ), + OCL_NULL( 2 ), ///< x_offs and y_offs, calculated automatically + OCL_NULL( 2 ), ///< x_extent and y_extent, calculated automatically + OCL_NULL( 1 ), ///< z_extent, calculated automatically + + OCL_SVAR( OC_UINT8, Vehicle, owner ), + OCL_SVAR( OC_TILE, Vehicle, tile ), + OCL_SVAR( OC_UINT16, Vehicle, cur_image ), + + OCL_NULL( 8 ), ///< Vehicle sprite box, calculated automatically + + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, Vehicle, vehstatus ), + OCL_SVAR( OC_UINT16, Vehicle, cur_speed ), + OCL_SVAR( OC_UINT8, Vehicle, subspeed ), + OCL_SVAR( OC_UINT8, Vehicle, acceleration ), + OCL_SVAR( OC_UINT8, Vehicle, progress ), + + OCL_SVAR( OC_UINT8, Vehicle, cargo_type ), + OCL_SVAR( OC_UINT16, Vehicle, cargo_cap ), + OCL_VAR ( OC_UINT16, 1, &_cargo_count ), + OCL_VAR ( OC_UINT8, 1, &_cargo_source ), + OCL_VAR ( OC_UINT8, 1, &_cargo_days ), + + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Vehicle, age ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Vehicle, max_age ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_I32, Vehicle, build_year ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Vehicle, unitnumber ), + + OCL_SVAR( OC_UINT16, Vehicle, engine_type ), + + OCL_SVAR( OC_UINT8, Vehicle, spritenum ), + OCL_SVAR( OC_UINT8, Vehicle, day_counter ), + + OCL_SVAR( OC_UINT8, Vehicle, breakdowns_since_last_service ), + OCL_SVAR( OC_UINT8, Vehicle, breakdown_ctr ), + OCL_SVAR( OC_UINT8, Vehicle, breakdown_delay ), + OCL_SVAR( OC_UINT8, Vehicle, breakdown_chance ), + + OCL_SVAR( OC_UINT16, Vehicle, reliability ), + OCL_SVAR( OC_UINT16, Vehicle, reliability_spd_dec ), + + OCL_SVAR( OC_FILE_I32 | OC_VAR_I64, Vehicle, profit_this_year ), + OCL_SVAR( OC_FILE_I32 | OC_VAR_I64, Vehicle, profit_last_year ), + + OCL_VAR ( OC_UINT16, 1, &_old_next_ptr ), + + OCL_SVAR( OC_UINT32, Vehicle, value ), + + OCL_VAR ( OC_UINT16, 1, &_old_string_id ), + + OCL_CHUNK( 1, LoadOldVehicleUnion ), + + OCL_NULL( 20 ), ///< Junk at end of struct (TTDPatch has some data in it) + + OCL_END() +}; + +bool LoadOldVehicle(LoadgameState *ls, int num) +{ + uint i; + + /* Read the TTDPatch flags, because we need some info from it */ + ReadTTDPatchFlags(); + + for (i = 0; i < _old_vehicle_multiplier; i++) { + _current_vehicle_id = num * _old_vehicle_multiplier + i; + + /* Read the vehicle type and allocate the right vehicle */ + Vehicle *v; + switch (ReadByte(ls)) { + default: NOT_REACHED(); + case 0x00 /*VEH_INVALID */: v = new (_current_vehicle_id) InvalidVehicle(); break; + case 0x10 /*VEH_TRAIN */: v = new (_current_vehicle_id) Train(); break; + case 0x11 /*VEH_ROAD */: v = new (_current_vehicle_id) RoadVehicle(); break; + case 0x12 /*VEH_SHIP */: v = new (_current_vehicle_id) Ship(); break; + case 0x13 /*VEH_AIRCRAFT*/: v = new (_current_vehicle_id) Aircraft(); break; + case 0x14 /*VEH_EFFECT */: v = new (_current_vehicle_id) EffectVehicle(); break; + case 0x15 /*VEH_DISASTER*/: v = new (_current_vehicle_id) DisasterVehicle(); break; + } + if (!LoadChunk(ls, v, vehicle_chunk)) return false; + + /* This should be consistent, else we have a big problem... */ + if (v->index != _current_vehicle_id) { + DEBUG(oldloader, 0, "Loading failed - vehicle-array is invalid"); + return false; + } + + if (_old_order_ptr != 0 && _old_order_ptr != 0xFFFFFFFF) { + uint old_id = REMAP_ORDER_IDX(_old_order_ptr); + /* There is a maximum of 5000 orders in old savegames, so *if* + * we go over that limit something is very wrong. In that case + * we just assume there are no orders for the vehicle. + */ + if (old_id < 5000) v->orders.old = GetOrder(old_id); + } + v->current_order.AssignOrder(UnpackOldOrder(_old_order)); + + /* For some reason we need to correct for this */ + switch (v->spritenum) { + case 0xfd: break; + case 0xff: v->spritenum = 0xfe; break; + default: v->spritenum >>= 1; break; + } + + if (_old_next_ptr != 0xFFFF) v->next = GetVehiclePoolSize() <= _old_next_ptr ? new (_old_next_ptr) InvalidVehicle() : GetVehicle(_old_next_ptr); + + _old_vehicle_names[_current_vehicle_id] = RemapOldStringID(_old_string_id); + v->name = NULL; + + /* Vehicle-subtype is different in TTD(Patch) */ + if (v->type == VEH_EFFECT) v->subtype = v->subtype >> 1; + + if (_cargo_count != 0) { + CargoPacket *cp = new CargoPacket((_cargo_source == 0xFF) ? INVALID_STATION : _cargo_source, _cargo_count); + cp->days_in_transit = _cargo_days; + v->cargo.Append(cp); + } + } + + return true; +} + +static const OldChunks sign_chunk[] = { + OCL_VAR ( OC_UINT16, 1, &_old_string_id ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_I32, Sign, x ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_I32, Sign, y ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_I8, Sign, z ), + + OCL_NULL( 6 ), ///< Width of sign, no longer in use + + OCL_END() +}; + +static bool LoadOldSign(LoadgameState *ls, int num) +{ + Sign *si = new (num) Sign(); + if (!LoadChunk(ls, si, sign_chunk)) return false; + + _old_string_id = RemapOldStringID(_old_string_id); + si->name = CopyFromOldName(_old_string_id); + + return true; +} + +static const OldChunks engine_chunk[] = { + OCL_SVAR( OC_UINT16, Engine, company_avail ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Engine, intro_date ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, Engine, age ), + OCL_SVAR( OC_UINT16, Engine, reliability ), + OCL_SVAR( OC_UINT16, Engine, reliability_spd_dec ), + OCL_SVAR( OC_UINT16, Engine, reliability_start ), + OCL_SVAR( OC_UINT16, Engine, reliability_max ), + OCL_SVAR( OC_UINT16, Engine, reliability_final ), + OCL_SVAR( OC_UINT16, Engine, duration_phase_1 ), + OCL_SVAR( OC_UINT16, Engine, duration_phase_2 ), + OCL_SVAR( OC_UINT16, Engine, duration_phase_3 ), + + OCL_SVAR( OC_UINT8, Engine, lifelength ), + OCL_SVAR( OC_UINT8, Engine, flags ), + OCL_SVAR( OC_UINT8, Engine, preview_company_rank ), + OCL_SVAR( OC_UINT8, Engine, preview_wait ), + + OCL_NULL( 2 ), ///< Junk + + OCL_END() +}; + +static bool LoadOldEngine(LoadgameState *ls, int num) +{ + Engine *e = GetTempDataEngine(num); + return LoadChunk(ls, e, engine_chunk); +} + +static bool LoadOldEngineName(LoadgameState *ls, int num) +{ + Engine *e = GetTempDataEngine(num); + e->name = CopyFromOldName(RemapOldStringID(ReadUint16(ls))); + return true; +} + +static const OldChunks subsidy_chunk[] = { + OCL_SVAR( OC_UINT8, Subsidy, cargo_type ), + OCL_SVAR( OC_UINT8, Subsidy, age ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Subsidy, from ), + OCL_SVAR( OC_FILE_U8 | OC_VAR_U16, Subsidy, to ), + + OCL_END() +}; + +static inline bool LoadOldSubsidy(LoadgameState *ls, int num) +{ + return LoadChunk(ls, &_subsidies[num], subsidy_chunk); +} + +static const OldChunks game_difficulty_chunk[] = { + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, max_no_competitors ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, competitor_start_time ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, number_towns ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, number_industries ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U32, DifficultySettings, max_loan ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, initial_interest ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, vehicle_costs ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, competitor_speed ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, competitor_intelligence ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, vehicle_breakdowns ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, subsidy_multiplier ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, construction_cost ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, terrain_type ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, quantity_sea_lakes ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, economy ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, line_reverse_mode ), + OCL_SVAR( OC_FILE_U16 | OC_VAR_U8, DifficultySettings, disasters ), + OCL_END() +}; + +static inline bool LoadOldGameDifficulty(LoadgameState *ls, int num) +{ + bool ret = LoadChunk(ls, &_settings_game.difficulty, game_difficulty_chunk); + _settings_game.difficulty.max_loan *= 1000; + return ret; +} + + +static bool LoadOldMapPart1(LoadgameState *ls, int num) +{ + uint i; + + for (i = 0; i < OLD_MAP_SIZE; i++) { + _m[i].m1 = ReadByte(ls); + } + for (i = 0; i < OLD_MAP_SIZE; i++) { + _m[i].m2 = ReadByte(ls); + } + for (i = 0; i < OLD_MAP_SIZE; i++) { + _old_map3[i * 2] = ReadByte(ls); + _old_map3[i * 2 + 1] = ReadByte(ls); + } + for (i = 0; i < OLD_MAP_SIZE / 4; i++) { + byte b = ReadByte(ls); + _m[i * 4 + 0].m6 = GB(b, 0, 2); + _m[i * 4 + 1].m6 = GB(b, 2, 2); + _m[i * 4 + 2].m6 = GB(b, 4, 2); + _m[i * 4 + 3].m6 = GB(b, 6, 2); + } + + return !ls->failed; +} + +static bool LoadOldMapPart2(LoadgameState *ls, int num) +{ + uint i; + + for (i = 0; i < OLD_MAP_SIZE; i++) { + _m[i].type_height = ReadByte(ls); + } + for (i = 0; i < OLD_MAP_SIZE; i++) { + _m[i].m5 = ReadByte(ls); + } + + return !ls->failed; +} + +static bool LoadTTDPatchExtraChunks(LoadgameState *ls, int num) +{ + ReadTTDPatchFlags(); + + DEBUG(oldloader, 2, "Found %d extra chunk(s)", _old_extra_chunk_nums); + + for (int i = 0; i != _old_extra_chunk_nums; i++) { + uint16 id = ReadUint16(ls); + uint32 len = ReadUint32(ls); + + switch (id) { + /* List of GRFIDs, used in the savegame. 0x8004 is the new ID + * They are saved in a 'GRFID:4 active:1' format, 5 bytes for each entry */ + case 0x2: + case 0x8004: { + /* Skip the first element: TTDP hack for the Action D special variables (FFFF0000 01) */ + ReadUint32(ls); ReadByte(ls); len -= 5; + + ClearGRFConfigList(&_grfconfig); + while (len != 0) { + uint32 grfid = ReadUint32(ls); + + if (ReadByte(ls) == 1) { + GRFConfig *c = CallocT<GRFConfig>(1); + c->grfid = grfid; + c->filename = strdup("TTDP game, no information"); + + AppendToGRFConfigList(&_grfconfig, c); + DEBUG(oldloader, 3, "TTDPatch game using GRF file with GRFID %0X", BSWAP32(c->grfid)); + } + len -= 5; + }; + + /* Append static NewGRF configuration */ + AppendStaticGRFConfigs(&_grfconfig); + } break; + + /* TTDPatch version and configuration */ + case 0x3: + _ttdp_version = ReadUint32(ls); + DEBUG(oldloader, 3, "Game saved with TTDPatch version %d.%d.%d r%d", + GB(_ttdp_version, 24, 8), GB(_ttdp_version, 20, 4), GB(_ttdp_version, 16, 4), GB(_ttdp_version, 0, 16)); + len -= 4; + while (len-- != 0) ReadByte(ls); // skip the configuration + break; + + default: + DEBUG(oldloader, 4, "Skipping unknown extra chunk %X", id); + while (len-- != 0) ReadByte(ls); + break; + } + } + + return !ls->failed; +} + +extern TileIndex _cur_tileloop_tile; +static uint32 _old_cur_town_ctr; +static const OldChunks main_chunk[] = { + OCL_ASSERT( 0 ), + OCL_VAR ( OC_FILE_U16 | OC_VAR_U32, 1, &_date ), + OCL_VAR ( OC_UINT16, 1, &_date_fract ), + OCL_NULL( 600 ), ///< TextEffects + OCL_VAR ( OC_UINT32, 2, &_random.state ), + + OCL_ASSERT( 0x264 ), + OCL_CHUNK( 70, LoadOldTown ), + OCL_ASSERT( 0x1C18 ), + OCL_CHUNK(5000, LoadOldOrder ), + OCL_ASSERT( 0x4328 ), + + OCL_CHUNK( 1, LoadOldAnimTileList ), + OCL_NULL( 4 ), ///< old end-of-order-list-pointer, no longer in use + + OCL_CHUNK( 255, LoadOldDepot ), + OCL_ASSERT( 0x4B26 ), + + OCL_VAR ( OC_UINT32, 1, &_old_cur_town_ctr ), + OCL_NULL( 2 ), ///< timer_counter, no longer in use + OCL_NULL( 2 ), ///< land_code, no longer in use + + OCL_VAR ( OC_FILE_U16 | OC_VAR_U8, 1, &_age_cargo_skip_counter ), + OCL_VAR ( OC_UINT16, 1, &_tick_counter ), + OCL_VAR ( OC_TILE, 1, &_cur_tileloop_tile ), + + OCL_CHUNK( 49, LoadOldPrice ), + OCL_CHUNK( 12, LoadOldCargoPaymentRate ), + + OCL_ASSERT( 0x4CBA ), + + OCL_CHUNK( 1, LoadOldMapPart1 ), + + OCL_ASSERT( 0x48CBA ), + + OCL_CHUNK(250, LoadOldStation ), + OCL_CHUNK( 90, LoadOldIndustry ), + OCL_CHUNK( 8, LoadOldCompany ), + + OCL_ASSERT( 0x547F2 ), + + OCL_CHUNK( 850, LoadOldVehicle ), + + OCL_ASSERT( 0x6F0F2 ), + + OCL_VAR ( OC_UINT8 | OC_DEREFERENCE_POINTER, 32 * 500, &_old_name_array ), + + OCL_NULL( 0x2000 ), ///< Old hash-table, no longer in use + + OCL_CHUNK( 40, LoadOldSign ), + OCL_CHUNK(256, LoadOldEngine ), + + OCL_VAR ( OC_UINT16, 1, &_vehicle_id_ctr_day ), + + OCL_CHUNK( 8, LoadOldSubsidy ), + + OCL_VAR ( OC_FILE_U16 | OC_VAR_U32, 1, &_next_competitor_start ), + OCL_VAR ( OC_FILE_I16 | OC_VAR_I32, 1, &_saved_scrollpos_x ), + OCL_VAR ( OC_FILE_I16 | OC_VAR_I32, 1, &_saved_scrollpos_y ), + OCL_VAR ( OC_FILE_U16 | OC_VAR_U8, 1, &_saved_scrollpos_zoom ), + + OCL_VAR ( OC_FILE_U32 | OC_VAR_I64, 1, &_economy.max_loan ), + OCL_VAR ( OC_FILE_U32 | OC_VAR_I64, 1, &_economy.max_loan_unround ), + OCL_VAR ( OC_INT16, 1, &_economy.fluct ), + + OCL_VAR ( OC_UINT16, 1, &_disaster_delay ), + + OCL_NULL( 144 ), ///< cargo-stuff, calculated in InitializeLandscapeVariables + + OCL_CHUNK(256, LoadOldEngineName ), + + OCL_NULL( 144 ), ///< AI cargo-stuff, calculated in InitializeLandscapeVariables + OCL_NULL( 2 ), ///< Company indexes of companies, no longer in use + + OCL_VAR ( OC_FILE_U8 | OC_VAR_U16, 1, &_station_tick_ctr ), + + OCL_VAR ( OC_UINT8, 1, &_settings_game.locale.currency ), + OCL_VAR ( OC_UINT8, 1, &_settings_game.locale.units ), + OCL_VAR ( OC_FILE_U8 | OC_VAR_U32, 1, &_cur_company_tick_index ), + + OCL_NULL( 2 ), ///< Date stuff, calculated automatically + OCL_NULL( 8 ), ///< Company colors, calculated automatically + + OCL_VAR ( OC_UINT8, 1, &_economy.infl_amount ), + OCL_VAR ( OC_UINT8, 1, &_economy.infl_amount_pr ), + OCL_VAR ( OC_UINT8, 1, &_economy.interest_rate ), + OCL_NULL( 1 ), // available airports + OCL_VAR ( OC_UINT8, 1, &_settings_game.vehicle.road_side ), + OCL_VAR ( OC_UINT8, 1, &_settings_game.game_creation.town_name ), + + OCL_CHUNK( 1, LoadOldGameDifficulty ), + + OCL_ASSERT( 0x77130 ), + + OCL_VAR ( OC_UINT8, 1, &_settings_game.difficulty.diff_level ), + OCL_VAR ( OC_UINT8, 1, &_settings_game.game_creation.landscape ), + OCL_VAR ( OC_UINT8, 1, &_trees_tick_ctr ), + + OCL_NULL( 1 ), ///< Custom vehicle types yes/no, no longer used + OCL_VAR ( OC_UINT8, 1, &_settings_game.game_creation.snow_line ), + + OCL_NULL( 32 ), ///< new_industry_randtable, no longer used (because of new design) + OCL_NULL( 36 ), ///< cargo-stuff, calculated in InitializeLandscapeVariables + + OCL_ASSERT( 0x77179 ), + + OCL_CHUNK( 1, LoadOldMapPart2 ), + + OCL_ASSERT( 0x97179 ), + + /* Below any (if available) extra chunks from TTDPatch can follow */ + OCL_CHUNK(1, LoadTTDPatchExtraChunks), + + OCL_END() +}; + +static bool LoadOldMain(LoadgameState *ls) +{ + int i; + + /* The first 49 is the name of the game + checksum, skip it */ + fseek(ls->file, HEADER_SIZE, SEEK_SET); + + DEBUG(oldloader, 3, "Reading main chunk..."); + /* Load the biggest chunk */ + _old_map3 = MallocT<byte>(OLD_MAP_SIZE * 2); + _old_vehicle_names = NULL; + if (!LoadChunk(ls, NULL, main_chunk)) { + DEBUG(oldloader, 0, "Loading failed"); + free(_old_map3); + free(_old_vehicle_names); + return false; + } + DEBUG(oldloader, 3, "Done, converting game data..."); + + /* Fix some general stuff */ + _settings_game.game_creation.landscape = _settings_game.game_creation.landscape & 0xF; + + /* Remap some pointers */ + _cur_town_ctr = REMAP_TOWN_IDX(_old_cur_town_ctr); + + /* _old_map3 is changed in _map3_lo and _map3_hi */ + for (i = 0; i < OLD_MAP_SIZE; i++) { + _m[i].m3 = _old_map3[i * 2]; + _m[i].m4 = _old_map3[i * 2 + 1]; + } + + for (i = 0; i < OLD_MAP_SIZE; i ++) { + switch (GetTileType(i)) { + case MP_STATION: + _m[i].m4 = 0; // We do not understand this TTDP station mapping (yet) + switch (_m[i].m5) { + /* We have drive through stops at a totally different place */ + case 0x53: case 0x54: _m[i].m5 += 170 - 0x53; break; // Bus drive through + case 0x57: case 0x58: _m[i].m5 += 168 - 0x57; break; // Truck drive through + case 0x55: case 0x56: _m[i].m5 += 170 - 0x55; break; // Bus tram stop + case 0x59: case 0x5A: _m[i].m5 += 168 - 0x59; break; // Truck tram stop + default: break; + } + break; + + case MP_RAILWAY: + /* We save presignals different from TTDPatch, convert them */ + if (GetRailTileType(i) == RAIL_TILE_SIGNALS) { + /* This byte is always zero in TTD for this type of tile */ + if (_m[i].m4) /* Convert the presignals to our own format */ + _m[i].m4 = (_m[i].m4 >> 1) & 7; + } + /* TTDPatch stores PBS things in L6 and all elsewhere; so we'll just + * clear it for ourselves and let OTTD's rebuild PBS itself */ + _m[i].m4 &= 0xF; /* Only keep the lower four bits; upper four is PBS */ + break; + + case MP_WATER: + if (GetWaterClass(i) == 3) MakeRiver(i, Random()); + break; + + default: + break; + } + } + + /* Make sure the available engines are really available, otherwise + * we will get a "new vehicle"-spree. */ + Engine *e; + FOR_ALL_ENGINES(e) { + if (_date >= (e->intro_date + 365)) { + e->flags = (e->flags & ~ENGINE_EXCLUSIVE_PREVIEW) | ENGINE_AVAILABLE; + e->company_avail = (CompanyMask)-1; + } + } + + /* Fix the game to be compatible with OpenTTD */ + FixOldTowns(); + FixOldVehicles(); + + /* We have a new difficulty setting */ + _settings_game.difficulty.town_council_tolerance = Clamp(_settings_game.difficulty.diff_level, 0, 2); + + DEBUG(oldloader, 3, "Finished converting game data"); + DEBUG(oldloader, 1, "TTD(Patch) savegame successfully converted"); + + free(_old_map3); + free(_old_vehicle_names); + + return true; +} + +bool LoadOldSaveGame(const char *file) +{ + LoadgameState ls; + + DEBUG(oldloader, 3, "Trying to load a TTD(Patch) savegame"); + + InitLoading(&ls); + + /* Open file */ + ls.file = fopen(file, "rb"); + + if (ls.file == NULL) { + DEBUG(oldloader, 0, "Cannot open file '%s'", file); + return false; + } + + /* Load the main chunk */ + if (!LoadOldMain(&ls)) return false; + + fclose(ls.file); + + /* Some old TTD(Patch) savegames could have buoys at tile 0 + * (without assigned station struct) + * MakeWater() can be used as long as sea has the same + * format as old savegames (eg. everything is zeroed) */ + MakeWater(0); + + _pause_game = 2; + + return true; +} + +void GetOldSaveGameName(char *title, const char *path, const char *file) +{ + char filename[MAX_PATH]; + FILE *f; + + snprintf(filename, lengthof(filename), "%s" PATHSEP "%s", path, file); + f = fopen(filename, "rb"); + title[0] = '\0'; + title[48] = '\0'; + + if (f == NULL) return; + + if (fread(title, 1, 48, f) != 48) snprintf(title, 48, "Corrupt file"); + + fclose(f); +} diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp new file mode 100644 index 000000000..ef11056ed --- /dev/null +++ b/src/saveload/order_sl.cpp @@ -0,0 +1,203 @@ +/* $Id$ */ + +/** @file order_sl.cpp Code handling saving and loading of orders */ + +#include "../stdafx.h" +#include "../order_base.h" +#include "../core/alloc_func.hpp" +#include "../settings_type.h" + +#include "saveload.h" + +void Order::ConvertFromOldSavegame() +{ + uint8 old_flags = this->flags; + this->flags = 0; + + /* First handle non-stop */ + if (_settings_client.gui.sg_new_nonstop) { + /* OFB_NON_STOP */ + this->SetNonStopType((old_flags & 8) ? ONSF_NO_STOP_AT_ANY_STATION : ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS); + } else { + this->SetNonStopType((old_flags & 8) ? ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS : ONSF_STOP_EVERYWHERE); + } + + switch (this->GetType()) { + /* Only a few types need the other savegame conversions. */ + case OT_GOTO_DEPOT: case OT_GOTO_STATION: case OT_LOADING: break; + default: return; + } + + if (this->GetType() != OT_GOTO_DEPOT) { + /* Then the load flags */ + if ((old_flags & 2) != 0) { // OFB_UNLOAD + this->SetLoadType(OLFB_NO_LOAD); + } else if ((old_flags & 4) == 0) { // !OFB_FULL_LOAD + this->SetLoadType(OLF_LOAD_IF_POSSIBLE); + } else { + this->SetLoadType(_settings_client.gui.sg_full_load_any ? OLF_FULL_LOAD_ANY : OLFB_FULL_LOAD); + } + + /* Finally fix the unload flags */ + if ((old_flags & 1) != 0) { // OFB_TRANSFER + this->SetUnloadType(OUFB_TRANSFER); + } else if ((old_flags & 2) != 0) { // OFB_UNLOAD + this->SetUnloadType(OUFB_UNLOAD); + } else { + this->SetUnloadType(OUF_UNLOAD_IF_POSSIBLE); + } + } else { + /* Then the depot action flags */ + this->SetDepotActionType(((old_flags & 6) == 4) ? ODATFB_HALT : ODATF_SERVICE_ONLY); + + /* Finally fix the depot type flags */ + uint t = ((old_flags & 6) == 6) ? ODTFB_SERVICE : ODTF_MANUAL; + if ((old_flags & 2) != 0) t |= ODTFB_PART_OF_ORDERS; + this->SetDepotOrderType((OrderDepotTypeFlags)t); + } +} + +/** Unpacks a order from savegames with version 4 and lower + * @param packed packed order + * @return unpacked order + */ +static Order UnpackVersion4Order(uint16 packed) +{ + return Order(GB(packed, 8, 8) << 16 | GB(packed, 4, 4) << 8 | GB(packed, 0, 4)); +} + +/** Unpacks a order from savegames made with TTD(Patch) + * @param packed packed order + * @return unpacked order + */ +Order UnpackOldOrder(uint16 packed) +{ + Order order = UnpackVersion4Order(packed); + + /* + * Sanity check + * TTD stores invalid orders as OT_NOTHING with non-zero flags/station + */ + if (!order.IsValid() && packed != 0) order.MakeDummy(); + + return order; +} + +const SaveLoad *GetOrderDescription() +{ + static const SaveLoad _order_desc[] = { + SLE_VAR(Order, type, SLE_UINT8), + SLE_VAR(Order, flags, SLE_UINT8), + SLE_VAR(Order, dest, SLE_UINT16), + SLE_REF(Order, next, REF_ORDER), + SLE_CONDVAR(Order, refit_cargo, SLE_UINT8, 36, SL_MAX_VERSION), + SLE_CONDVAR(Order, refit_subtype, SLE_UINT8, 36, SL_MAX_VERSION), + SLE_CONDVAR(Order, wait_time, SLE_UINT16, 67, SL_MAX_VERSION), + SLE_CONDVAR(Order, travel_time, SLE_UINT16, 67, SL_MAX_VERSION), + + /* Leftover from the minor savegame version stuff + * We will never use those free bytes, but we have to keep this line to allow loading of old savegames */ + SLE_CONDNULL(10, 5, 35), + SLE_END() + }; + + return _order_desc; +} + +static void Save_ORDR() +{ + Order *order; + + FOR_ALL_ORDERS(order) { + SlSetArrayIndex(order->index); + SlObject(order, GetOrderDescription()); + } +} + +static void Load_ORDR() +{ + if (CheckSavegameVersionOldStyle(5, 2)) { + /* Version older than 5.2 did not have a ->next pointer. Convert them + (in the old days, the orderlist was 5000 items big) */ + size_t len = SlGetFieldLength(); + uint i; + + if (CheckSavegameVersion(5)) { + /* Pre-version 5 had an other layout for orders + (uint16 instead of uint32) */ + len /= sizeof(uint16); + uint16 *orders = MallocT<uint16>(len + 1); + + SlArray(orders, len, SLE_UINT16); + + for (i = 0; i < len; ++i) { + Order *order = new (i) Order(); + order->AssignOrder(UnpackVersion4Order(orders[i])); + } + + free(orders); + } else if (CheckSavegameVersionOldStyle(5, 2)) { + len /= sizeof(uint16); + uint16 *orders = MallocT<uint16>(len + 1); + + SlArray(orders, len, SLE_UINT32); + + for (i = 0; i < len; ++i) { + new (i) Order(orders[i]); + } + + free(orders); + } + + /* Update all the next pointer */ + for (i = 1; i < len; ++i) { + /* The orders were built like this: + * While the order is valid, set the previous will get it's next pointer set + * We start with index 1 because no order will have the first in it's next pointer */ + if (GetOrder(i)->IsValid()) + GetOrder(i - 1)->next = GetOrder(i); + } + } else { + int index; + + while ((index = SlIterateArray()) != -1) { + Order *order = new (index) Order(); + SlObject(order, GetOrderDescription()); + } + } +} + +const SaveLoad *GetOrderListDescription() +{ + static const SaveLoad _orderlist_desc[] = { + SLE_REF(OrderList, first, REF_ORDER), + SLE_END() + }; + + return _orderlist_desc; +} + +static void Save_ORDL() +{ + OrderList *list; + + FOR_ALL_ORDER_LISTS(list) { + SlSetArrayIndex(list->index); + SlObject(list, GetOrderListDescription()); + } +} + +static void Load_ORDL() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + OrderList *list = new (index) OrderList(); + SlObject(list, GetOrderListDescription()); + } +} + +extern const ChunkHandler _order_chunk_handlers[] = { + { 'ORDR', Save_ORDR, Load_ORDR, CH_ARRAY}, + { 'ORDL', Save_ORDL, Load_ORDL, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp new file mode 100644 index 000000000..0a6585ce8 --- /dev/null +++ b/src/saveload/saveload.cpp @@ -0,0 +1,1899 @@ +/* $Id$ */ + +/** @file saveload.cpp + * All actions handling saving and loading goes on in this file. The general actions + * are as follows for saving a game (loading is analogous): + * <ol> + * <li>initialize the writer by creating a temporary memory-buffer for it + * <li>go through all to-be saved elements, each 'chunk' (ChunkHandler) prefixed by a label + * <li>use their description array (SaveLoad) to know what elements to save and in what version + * of the game it was active (used when loading) + * <li>write all data byte-by-byte to the temporary buffer so it is endian-safe + * <li>when the buffer is full; flush it to the output (eg save to file) (_sl.buf, _sl.bufp, _sl.bufe) + * <li>repeat this until everything is done, and flush any remaining output to file + * </ol> + */ +#include "../stdafx.h" +#include "../openttd.h" +#include "../debug.h" +#include "../station_base.h" +#include "../thread.h" +#include "../town.h" +#include "../network/network.h" +#include "../variables.h" +#include "../window_func.h" +#include "../strings_func.h" +#include "../gfx_func.h" +#include "../core/alloc_func.hpp" +#include "../functions.h" +#include "../core/endian_func.hpp" +#include "../vehicle_base.h" +#include "../company_func.h" +#include "../date_func.h" +#include "../autoreplace_base.h" +#include "../statusbar_gui.h" +#include "../fileio_func.h" +#include "../gamelog.h" + +#include "table/strings.h" + +#include "saveload.h" +#include "saveload_internal.h" + +#include <list> + +extern const uint16 SAVEGAME_VERSION = 105; + +SavegameType _savegame_type; ///< type of savegame we are loading + +uint32 _ttdp_version; ///< version of TTDP savegame (if applicable) +uint16 _sl_version; ///< the major savegame version identifier +byte _sl_minor_version; ///< the minor savegame version, DO NOT USE! + +typedef void WriterProc(size_t len); +typedef size_t ReaderProc(); + +/** The saveload struct, containing reader-writer functions, bufffer, version, etc. */ +static struct { + bool save; ///< are we doing a save or a load atm. True when saving + byte need_length; ///< ??? + byte block_mode; ///< ??? + bool error; ///< did an error occur or not + + size_t obj_len; ///< the length of the current object we are busy with + int array_index, last_array_index; ///< in the case of an array, the current and last positions + + size_t offs_base; ///< the offset in number of bytes since we started writing data (eg uncompressed savegame size) + + WriterProc *write_bytes; ///< savegame writer function + ReaderProc *read_bytes; ///< savegame loader function + + const ChunkHandler* const *chs; ///< the chunk of data that is being processed atm (vehicles, signs, etc.) + + /* When saving/loading savegames, they are always saved to a temporary memory-place + * to be flushed to file (save) or to final place (load) when full. */ + byte *bufp, *bufe; ///< bufp(ointer) gives the current position in the buffer bufe(nd) gives the end of the buffer + + /* these 3 may be used by compressor/decompressors. */ + byte *buf; ///< pointer to temporary memory to read/write, initialized by SaveLoadFormat->initread/write + byte *buf_ori; ///< pointer to the original memory location of buf, used to free it afterwards + uint bufsize; ///< the size of the temporary memory *buf + FILE *fh; ///< the file from which is read or written to + + void (*excpt_uninit)(); ///< the function to execute on any encountered error + StringID error_str; ///< the translateable error message to show + char *extra_msg; ///< the error message +} _sl; + + +enum NeedLengthValues {NL_NONE = 0, NL_WANTLENGTH = 1, NL_CALCLENGTH = 2}; + +/** Error handler, calls longjmp to simulate an exception. + * @todo this was used to have a central place to handle errors, but it is + * pretty ugly, and seriously interferes with any multithreaded approaches */ +static void NORETURN SlError(StringID string, const char *extra_msg = NULL) +{ + _sl.error_str = string; + free(_sl.extra_msg); + _sl.extra_msg = (extra_msg == NULL) ? NULL : strdup(extra_msg); + throw std::exception(); +} + +typedef void (*AsyncSaveFinishProc)(); +static AsyncSaveFinishProc _async_save_finish = NULL; +static ThreadObject *_save_thread; + +/** + * Called by save thread to tell we finished saving. + */ +static void SetAsyncSaveFinish(AsyncSaveFinishProc proc) +{ + if (_exit_game) return; + while (_async_save_finish != NULL) CSleep(10); + + _async_save_finish = proc; +} + +/** + * Handle async save finishes. + */ +void ProcessAsyncSaveFinish() +{ + if (_async_save_finish == NULL) return; + + _async_save_finish(); + + _async_save_finish = NULL; + + if (_save_thread != NULL) { + _save_thread->Join(); + delete _save_thread; + _save_thread = NULL; + } +} + +/** + * Fill the input buffer by reading from the file with the given reader + */ +static void SlReadFill() +{ + size_t len = _sl.read_bytes(); + if (len == 0) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unexpected end of chunk"); + + _sl.bufp = _sl.buf; + _sl.bufe = _sl.buf + len; + _sl.offs_base += len; +} + +static inline size_t SlGetOffs() {return _sl.offs_base - (_sl.bufe - _sl.bufp);} + +/** Return the size in bytes of a certain type of normal/atomic variable + * as it appears in memory. See VarTypes + * @param conv VarType type of variable that is used for calculating the size + * @return Return the size of this type in bytes */ +static inline byte SlCalcConvMemLen(VarType conv) +{ + static const byte conv_mem_size[] = {1, 1, 1, 2, 2, 4, 4, 8, 8, 0}; + byte length = GB(conv, 4, 4); + assert(length < lengthof(conv_mem_size)); + return conv_mem_size[length]; +} + +/** Return the size in bytes of a certain type of normal/atomic variable + * as it appears in a saved game. See VarTypes + * @param conv VarType type of variable that is used for calculating the size + * @return Return the size of this type in bytes */ +static inline byte SlCalcConvFileLen(VarType conv) +{ + static const byte conv_file_size[] = {1, 1, 2, 2, 4, 4, 8, 8, 2}; + byte length = GB(conv, 0, 4); + assert(length < lengthof(conv_file_size)); + return conv_file_size[length]; +} + +/** Return the size in bytes of a reference (pointer) */ +static inline size_t SlCalcRefLen() {return CheckSavegameVersion(69) ? 2 : 4;} + +/** Flush the output buffer by writing to disk with the given reader. + * If the buffer pointer has not yet been set up, set it up now. Usually + * only called when the buffer is full, or there is no more data to be processed + */ +static void SlWriteFill() +{ + /* flush the buffer to disk (the writer) */ + if (_sl.bufp != NULL) { + uint len = _sl.bufp - _sl.buf; + _sl.offs_base += len; + if (len) _sl.write_bytes(len); + } + + /* All the data from the buffer has been written away, rewind to the beginning + * to start reading in more data */ + _sl.bufp = _sl.buf; + _sl.bufe = _sl.buf + _sl.bufsize; +} + +/** Read in a single byte from file. If the temporary buffer is full, + * flush it to its final destination + * @return return the read byte from file + */ +static inline byte SlReadByteInternal() +{ + if (_sl.bufp == _sl.bufe) SlReadFill(); + return *_sl.bufp++; +} + +/** Wrapper for SlReadByteInternal */ +byte SlReadByte() {return SlReadByteInternal();} + +/** Write away a single byte from memory. If the temporary buffer is full, + * flush it to its destination (file) + * @param b the byte that is currently written + */ +static inline void SlWriteByteInternal(byte b) +{ + if (_sl.bufp == _sl.bufe) SlWriteFill(); + *_sl.bufp++ = b; +} + +/** Wrapper for SlWriteByteInternal */ +void SlWriteByte(byte b) {SlWriteByteInternal(b);} + +static inline int SlReadUint16() +{ + int x = SlReadByte() << 8; + return x | SlReadByte(); +} + +static inline uint32 SlReadUint32() +{ + uint32 x = SlReadUint16() << 16; + return x | SlReadUint16(); +} + +static inline uint64 SlReadUint64() +{ + uint32 x = SlReadUint32(); + uint32 y = SlReadUint32(); + return (uint64)x << 32 | y; +} + +static inline void SlWriteUint16(uint16 v) +{ + SlWriteByte(GB(v, 8, 8)); + SlWriteByte(GB(v, 0, 8)); +} + +static inline void SlWriteUint32(uint32 v) +{ + SlWriteUint16(GB(v, 16, 16)); + SlWriteUint16(GB(v, 0, 16)); +} + +static inline void SlWriteUint64(uint64 x) +{ + SlWriteUint32((uint32)(x >> 32)); + SlWriteUint32((uint32)x); +} + +/** + * Read in the header descriptor of an object or an array. + * If the highest bit is set (7), then the index is bigger than 127 + * elements, so use the next byte to read in the real value. + * The actual value is then both bytes added with the first shifted + * 8 bits to the left, and dropping the highest bit (which only indicated a big index). + * x = ((x & 0x7F) << 8) + SlReadByte(); + * @return Return the value of the index + */ +static uint SlReadSimpleGamma() +{ + uint i = SlReadByte(); + if (HasBit(i, 7)) { + i &= ~0x80; + if (HasBit(i, 6)) { + i &= ~0x40; + if (HasBit(i, 5)) { + i &= ~0x20; + if (HasBit(i, 4)) + SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unsupported gamma"); + i = (i << 8) | SlReadByte(); + } + i = (i << 8) | SlReadByte(); + } + i = (i << 8) | SlReadByte(); + } + return i; +} + +/** + * Write the header descriptor of an object or an array. + * If the element is bigger than 127, use 2 bytes for saving + * and use the highest byte of the first written one as a notice + * that the length consists of 2 bytes, etc.. like this: + * 0xxxxxxx + * 10xxxxxx xxxxxxxx + * 110xxxxx xxxxxxxx xxxxxxxx + * 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx + * @param i Index being written + */ + +static void SlWriteSimpleGamma(size_t i) +{ + if (i >= (1 << 7)) { + if (i >= (1 << 14)) { + if (i >= (1 << 21)) { + assert(i < (1 << 28)); + SlWriteByte((byte)(0xE0 | (i >> 24))); + SlWriteByte((byte)(i >> 16)); + } else { + SlWriteByte((byte)(0xC0 | (i >> 16))); + } + SlWriteByte((byte)(i >> 8)); + } else { + SlWriteByte((byte)(0x80 | (i >> 8))); + } + } + SlWriteByte((byte)i); +} + +/** Return how many bytes used to encode a gamma value */ +static inline uint SlGetGammaLength(size_t i) +{ + return 1 + (i >= (1 << 7)) + (i >= (1 << 14)) + (i >= (1 << 21)); +} + +static inline uint SlReadSparseIndex() {return SlReadSimpleGamma();} +static inline void SlWriteSparseIndex(uint index) {SlWriteSimpleGamma(index);} + +static inline uint SlReadArrayLength() {return SlReadSimpleGamma();} +static inline void SlWriteArrayLength(size_t length) {SlWriteSimpleGamma(length);} +static inline uint SlGetArrayLength(size_t length) {return SlGetGammaLength(length);} + +void SlSetArrayIndex(uint index) +{ + _sl.need_length = NL_WANTLENGTH; + _sl.array_index = index; +} + +static size_t _next_offs; + +/** + * Iterate through the elements of an array and read the whole thing + * @return The index of the object, or -1 if we have reached the end of current block + */ +int SlIterateArray() +{ + int index; + + /* After reading in the whole array inside the loop + * we must have read in all the data, so we must be at end of current block. */ + if (_next_offs != 0 && SlGetOffs() != _next_offs) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Invalid chunk size"); + + while (true) { + uint length = SlReadArrayLength(); + if (length == 0) { + _next_offs = 0; + return -1; + } + + _sl.obj_len = --length; + _next_offs = SlGetOffs() + length; + + switch (_sl.block_mode) { + case CH_SPARSE_ARRAY: index = (int)SlReadSparseIndex(); break; + case CH_ARRAY: index = _sl.array_index++; break; + default: + DEBUG(sl, 0, "SlIterateArray error"); + return -1; // error + } + + if (length != 0) return index; + } +} + +/** + * Sets the length of either a RIFF object or the number of items in an array. + * This lets us load an object or an array of arbitrary size + * @param length The length of the sought object/array + */ +void SlSetLength(size_t length) +{ + assert(_sl.save); + + switch (_sl.need_length) { + case NL_WANTLENGTH: + _sl.need_length = NL_NONE; + switch (_sl.block_mode) { + case CH_RIFF: + /* Ugly encoding of >16M RIFF chunks + * The lower 24 bits are normal + * The uppermost 4 bits are bits 24:27 */ + assert(length < (1 << 28)); + SlWriteUint32((uint32)((length & 0xFFFFFF) | ((length >> 24) << 28))); + break; + case CH_ARRAY: + assert(_sl.last_array_index <= _sl.array_index); + while (++_sl.last_array_index <= _sl.array_index) + SlWriteArrayLength(1); + SlWriteArrayLength(length + 1); + break; + case CH_SPARSE_ARRAY: + SlWriteArrayLength(length + 1 + SlGetArrayLength(_sl.array_index)); // Also include length of sparse index. + SlWriteSparseIndex(_sl.array_index); + break; + default: NOT_REACHED(); + } break; + case NL_CALCLENGTH: + _sl.obj_len += (int)length; + break; + } +} + +/** + * Save/Load bytes. These do not need to be converted to Little/Big Endian + * so directly write them or read them to/from file + * @param ptr The source or destination of the object being manipulated + * @param length number of bytes this fast CopyBytes lasts + */ +static void SlCopyBytes(void *ptr, size_t length) +{ + byte *p = (byte*)ptr; + + if (_sl.save) { + for (; length != 0; length--) {SlWriteByteInternal(*p++);} + } else { + for (; length != 0; length--) {*p++ = SlReadByteInternal();} + } +} + +/** Read in bytes from the file/data structure but don't do + * anything with them, discarding them in effect + * @param length The amount of bytes that is being treated this way + */ +static inline void SlSkipBytes(size_t length) +{ + for (; length != 0; length--) SlReadByte(); +} + +/* Get the length of the current object */ +size_t SlGetFieldLength() {return _sl.obj_len;} + +/** Return a signed-long version of the value of a setting + * @param ptr pointer to the variable + * @param conv type of variable, can be a non-clean + * type, eg one with other flags because it is parsed + * @return returns the value of the pointer-setting */ +int64 ReadValue(const void *ptr, VarType conv) +{ + switch (GetVarMemType(conv)) { + case SLE_VAR_BL: return (*(bool*)ptr != 0); + case SLE_VAR_I8: return *(int8* )ptr; + case SLE_VAR_U8: return *(byte* )ptr; + case SLE_VAR_I16: return *(int16* )ptr; + case SLE_VAR_U16: return *(uint16*)ptr; + case SLE_VAR_I32: return *(int32* )ptr; + case SLE_VAR_U32: return *(uint32*)ptr; + case SLE_VAR_I64: return *(int64* )ptr; + case SLE_VAR_U64: return *(uint64*)ptr; + case SLE_VAR_NULL:return 0; + default: NOT_REACHED(); + } + + /* useless, but avoids compiler warning this way */ + return 0; +} + +/** Write the value of a setting + * @param ptr pointer to the variable + * @param conv type of variable, can be a non-clean type, eg + * with other flags. It is parsed upon read + * @param val the new value being given to the variable */ +void WriteValue(void *ptr, VarType conv, int64 val) +{ + switch (GetVarMemType(conv)) { + case SLE_VAR_BL: *(bool *)ptr = (val != 0); break; + case SLE_VAR_I8: *(int8 *)ptr = val; break; + case SLE_VAR_U8: *(byte *)ptr = val; break; + case SLE_VAR_I16: *(int16 *)ptr = val; break; + case SLE_VAR_U16: *(uint16*)ptr = val; break; + case SLE_VAR_I32: *(int32 *)ptr = val; break; + case SLE_VAR_U32: *(uint32*)ptr = val; break; + case SLE_VAR_I64: *(int64 *)ptr = val; break; + case SLE_VAR_U64: *(uint64*)ptr = val; break; + case SLE_VAR_NAME: *(char**)ptr = CopyFromOldName(val); break; + case SLE_VAR_NULL: break; + default: NOT_REACHED(); + } +} + +/** + * Handle all conversion and typechecking of variables here. + * In the case of saving, read in the actual value from the struct + * and then write them to file, endian safely. Loading a value + * goes exactly the opposite way + * @param ptr The object being filled/read + * @param conv VarType type of the current element of the struct + */ +static void SlSaveLoadConv(void *ptr, VarType conv) +{ + int64 x = 0; + + if (_sl.save) { // SAVE values + /* Read a value from the struct. These ARE endian safe. */ + x = ReadValue(ptr, conv); + + /* Write the value to the file and check if its value is in the desired range */ + switch (GetVarFileType(conv)) { + case SLE_FILE_I8: assert(x >= -128 && x <= 127); SlWriteByte(x);break; + case SLE_FILE_U8: assert(x >= 0 && x <= 255); SlWriteByte(x);break; + case SLE_FILE_I16:assert(x >= -32768 && x <= 32767); SlWriteUint16(x);break; + case SLE_FILE_STRINGID: + case SLE_FILE_U16:assert(x >= 0 && x <= 65535); SlWriteUint16(x);break; + case SLE_FILE_I32: + case SLE_FILE_U32: SlWriteUint32((uint32)x);break; + case SLE_FILE_I64: + case SLE_FILE_U64: SlWriteUint64(x);break; + default: NOT_REACHED(); + } + } else { // LOAD values + + /* Read a value from the file */ + switch (GetVarFileType(conv)) { + case SLE_FILE_I8: x = (int8 )SlReadByte(); break; + case SLE_FILE_U8: x = (byte )SlReadByte(); break; + case SLE_FILE_I16: x = (int16 )SlReadUint16(); break; + case SLE_FILE_U16: x = (uint16)SlReadUint16(); break; + case SLE_FILE_I32: x = (int32 )SlReadUint32(); break; + case SLE_FILE_U32: x = (uint32)SlReadUint32(); break; + case SLE_FILE_I64: x = (int64 )SlReadUint64(); break; + case SLE_FILE_U64: x = (uint64)SlReadUint64(); break; + case SLE_FILE_STRINGID: x = RemapOldStringID((uint16)SlReadUint16()); break; + default: NOT_REACHED(); + } + + /* Write The value to the struct. These ARE endian safe. */ + WriteValue(ptr, conv, x); + } +} + +/** Calculate the net length of a string. This is in almost all cases + * just strlen(), but if the string is not properly terminated, we'll + * resort to the maximum length of the buffer. + * @param ptr pointer to the stringbuffer + * @param length maximum length of the string (buffer). If -1 we don't care + * about a maximum length, but take string length as it is. + * @return return the net length of the string */ +static inline size_t SlCalcNetStringLen(const char *ptr, size_t length) +{ + if (ptr == NULL) return 0; + return min(strlen(ptr), length - 1); +} + +/** Calculate the gross length of the string that it + * will occupy in the savegame. This includes the real length, returned + * by SlCalcNetStringLen and the length that the index will occupy. + * @param ptr pointer to the stringbuffer + * @param length maximum length of the string (buffer size, etc.) + * @param conv type of data been used + * @return return the gross length of the string */ +static inline size_t SlCalcStringLen(const void *ptr, size_t length, VarType conv) +{ + size_t len; + const char *str; + + switch (GetVarMemType(conv)) { + default: NOT_REACHED(); + case SLE_VAR_STR: + case SLE_VAR_STRQ: + str = *(const char**)ptr; + len = SIZE_MAX; + break; + case SLE_VAR_STRB: + case SLE_VAR_STRBQ: + str = (const char*)ptr; + len = length; + break; + } + + len = SlCalcNetStringLen(str, len); + return len + SlGetArrayLength(len); // also include the length of the index +} + +/** + * Save/Load a string. + * @param ptr the string being manipulated + * @param length of the string (full length) + * @param conv must be SLE_FILE_STRING */ +static void SlString(void *ptr, size_t length, VarType conv) +{ + size_t len; + + if (_sl.save) { // SAVE string + switch (GetVarMemType(conv)) { + default: NOT_REACHED(); + case SLE_VAR_STRB: + case SLE_VAR_STRBQ: + len = SlCalcNetStringLen((char*)ptr, length); + break; + case SLE_VAR_STR: + case SLE_VAR_STRQ: + ptr = *(char**)ptr; + len = SlCalcNetStringLen((char*)ptr, SIZE_MAX); + break; + } + + SlWriteArrayLength(len); + SlCopyBytes(ptr, len); + } else { // LOAD string + len = SlReadArrayLength(); + + switch (GetVarMemType(conv)) { + default: NOT_REACHED(); + case SLE_VAR_STRB: + case SLE_VAR_STRBQ: + if (len >= length) { + DEBUG(sl, 1, "String length in savegame is bigger than buffer, truncating"); + SlCopyBytes(ptr, length); + SlSkipBytes(len - length); + len = length - 1; + } else { + SlCopyBytes(ptr, len); + } + break; + case SLE_VAR_STR: + case SLE_VAR_STRQ: // Malloc'd string, free previous incarnation, and allocate + free(*(char**)ptr); + if (len == 0) { + *(char**)ptr = NULL; + } else { + *(char**)ptr = MallocT<char>(len + 1); // terminating '\0' + ptr = *(char**)ptr; + SlCopyBytes(ptr, len); + } + break; + } + + ((char*)ptr)[len] = '\0'; // properly terminate the string + } +} + +/** + * Return the size in bytes of a certain type of atomic array + * @param length The length of the array counted in elements + * @param conv VarType type of the variable that is used in calculating the size + */ +static inline size_t SlCalcArrayLen(size_t length, VarType conv) +{ + return SlCalcConvFileLen(conv) * length; +} + +/** + * Save/Load an array. + * @param array The array being manipulated + * @param length The length of the array in elements + * @param conv VarType type of the atomic array (int, byte, uint64, etc.) + */ +void SlArray(void *array, size_t length, VarType conv) +{ + /* Automatically calculate the length? */ + if (_sl.need_length != NL_NONE) { + SlSetLength(SlCalcArrayLen(length, conv)); + /* Determine length only? */ + if (_sl.need_length == NL_CALCLENGTH) return; + } + + /* NOTICE - handle some buggy stuff, in really old versions everything was saved + * as a byte-type. So detect this, and adjust array size accordingly */ + if (!_sl.save && _sl_version == 0) { + /* all arrays except difficulty settings */ + if (conv == SLE_INT16 || conv == SLE_UINT16 || conv == SLE_STRINGID || + conv == SLE_INT32 || conv == SLE_UINT32) { + SlCopyBytes(array, length * SlCalcConvFileLen(conv)); + return; + } + /* used for conversion of Money 32bit->64bit */ + if (conv == (SLE_FILE_I32 | SLE_VAR_I64)) { + for (uint i = 0; i < length; i++) { + ((int64*)array)[i] = (int32)BSWAP32(SlReadUint32()); + } + return; + } + } + + /* If the size of elements is 1 byte both in file and memory, no special + * conversion is needed, use specialized copy-copy function to speed up things */ + if (conv == SLE_INT8 || conv == SLE_UINT8) { + SlCopyBytes(array, length); + } else { + byte *a = (byte*)array; + byte mem_size = SlCalcConvMemLen(conv); + + for (; length != 0; length --) { + SlSaveLoadConv(a, conv); + a += mem_size; // get size + } + } +} + + +static uint ReferenceToInt(const void* obj, SLRefType rt); +static void* IntToReference(uint index, SLRefType rt); + + +/** + * Return the size in bytes of a list + * @param list The std::list to find the size of + */ +static inline size_t SlCalcListLen(const void *list) +{ + std::list<void *> *l = (std::list<void *> *) list; + + int type_size = CheckSavegameVersion(69) ? 2 : 4; + /* Each entry is saved as type_size bytes, plus type_size bytes are used for the length + * of the list */ + return l->size() * type_size + type_size; +} + + +/** + * Save/Load a list. + * @param list The list being manipulated + * @param conv SLRefType type of the list (Vehicle *, Station *, etc) + */ +void SlList(void *list, SLRefType conv) +{ + /* Automatically calculate the length? */ + if (_sl.need_length != NL_NONE) { + SlSetLength(SlCalcListLen(list)); + /* Determine length only? */ + if (_sl.need_length == NL_CALCLENGTH) return; + } + + std::list<void *> *l = (std::list<void *> *) list; + + if (_sl.save) { + SlWriteUint32((uint32)l->size()); + + std::list<void *>::iterator iter; + for (iter = l->begin(); iter != l->end(); ++iter) { + void *ptr = *iter; + SlWriteUint32(ReferenceToInt(ptr, conv)); + } + } else { + uint length = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32(); + + /* Load each reference and push to the end of the list */ + for (uint i = 0; i < length; i++) { + void *ptr = IntToReference(CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32(), conv); + l->push_back(ptr); + } + } +} + + +/** Are we going to save this object or not? */ +static inline bool SlIsObjectValidInSavegame(const SaveLoad *sld) +{ + if (_sl_version < sld->version_from || _sl_version > sld->version_to) return false; + if (sld->conv & SLF_SAVE_NO) return false; + + return true; +} + +/** Are we going to load this variable when loading a savegame or not? + * @note If the variable is skipped it is skipped in the savegame + * bytestream itself as well, so there is no need to skip it somewhere else */ +static inline bool SlSkipVariableOnLoad(const SaveLoad *sld) +{ + if ((sld->conv & SLF_NETWORK_NO) && !_sl.save && _networking && !_network_server) { + SlSkipBytes(SlCalcConvMemLen(sld->conv) * sld->length); + return true; + } + + return false; +} + +/** + * Calculate the size of an object. + * @param object to be measured + * @param sld The SaveLoad description of the object so we know how to manipulate it + * @return size of given objetc + */ +size_t SlCalcObjLength(const void *object, const SaveLoad *sld) +{ + size_t length = 0; + + /* Need to determine the length and write a length tag. */ + for (; sld->cmd != SL_END; sld++) { + length += SlCalcObjMemberLength(object, sld); + } + return length; +} + +size_t SlCalcObjMemberLength(const void *object, const SaveLoad *sld) +{ + assert(_sl.save); + + switch (sld->cmd) { + case SL_VAR: + case SL_REF: + case SL_ARR: + case SL_STR: + case SL_LST: + /* CONDITIONAL saveload types depend on the savegame version */ + if (!SlIsObjectValidInSavegame(sld)) break; + + switch (sld->cmd) { + case SL_VAR: return SlCalcConvFileLen(sld->conv); + case SL_REF: return SlCalcRefLen(); + case SL_ARR: return SlCalcArrayLen(sld->length, sld->conv); + case SL_STR: return SlCalcStringLen(GetVariableAddress(object, sld), sld->length, sld->conv); + case SL_LST: return SlCalcListLen(GetVariableAddress(object, sld)); + default: NOT_REACHED(); + } + break; + case SL_WRITEBYTE: return 1; // a byte is logically of size 1 + case SL_VEH_INCLUDE: return SlCalcObjLength(object, GetVehicleDescription(VEH_END)); + default: NOT_REACHED(); + } + return 0; +} + + +bool SlObjectMember(void *ptr, const SaveLoad *sld) +{ + VarType conv = GB(sld->conv, 0, 8); + switch (sld->cmd) { + case SL_VAR: + case SL_REF: + case SL_ARR: + case SL_STR: + case SL_LST: + /* CONDITIONAL saveload types depend on the savegame version */ + if (!SlIsObjectValidInSavegame(sld)) return false; + if (SlSkipVariableOnLoad(sld)) return false; + + switch (sld->cmd) { + case SL_VAR: SlSaveLoadConv(ptr, conv); break; + case SL_REF: // Reference variable, translate + if (_sl.save) { + SlWriteUint32(ReferenceToInt(*(void**)ptr, (SLRefType)conv)); + } else { + *(void**)ptr = IntToReference(CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32(), (SLRefType)conv); + } + break; + case SL_ARR: SlArray(ptr, sld->length, conv); break; + case SL_STR: SlString(ptr, sld->length, conv); break; + case SL_LST: SlList(ptr, (SLRefType)conv); break; + default: NOT_REACHED(); + } + break; + + /* SL_WRITEBYTE translates a value of a variable to another one upon + * saving or loading. + * XXX - variable renaming abuse + * game_value: the value of the variable ingame is abused by sld->version_from + * file_value: the value of the variable in the savegame is abused by sld->version_to */ + case SL_WRITEBYTE: + if (_sl.save) { + SlWriteByte(sld->version_to); + } else { + *(byte*)ptr = sld->version_from; + } + break; + + /* SL_VEH_INCLUDE loads common code for vehicles */ + case SL_VEH_INCLUDE: + SlObject(ptr, GetVehicleDescription(VEH_END)); + break; + default: NOT_REACHED(); + } + return true; +} + +/** + * Main SaveLoad function. + * @param object The object that is being saved or loaded + * @param sld The SaveLoad description of the object so we know how to manipulate it + */ +void SlObject(void *object, const SaveLoad *sld) +{ + /* Automatically calculate the length? */ + if (_sl.need_length != NL_NONE) { + SlSetLength(SlCalcObjLength(object, sld)); + if (_sl.need_length == NL_CALCLENGTH) return; + } + + for (; sld->cmd != SL_END; sld++) { + void *ptr = sld->global ? sld->address : GetVariableAddress(object, sld); + SlObjectMember(ptr, sld); + } +} + +/** + * Save or Load (a list of) global variables + * @param sldg The global variable that is being loaded or saved + */ +void SlGlobList(const SaveLoadGlobVarList *sldg) +{ + SlObject(NULL, (const SaveLoad*)sldg); +} + +/** + * Do something of which I have no idea what it is :P + * @param proc The callback procedure that is called + * @param arg The variable that will be used for the callback procedure + */ +void SlAutolength(AutolengthProc *proc, void *arg) +{ + size_t offs; + + assert(_sl.save); + + /* Tell it to calculate the length */ + _sl.need_length = NL_CALCLENGTH; + _sl.obj_len = 0; + proc(arg); + + /* Setup length */ + _sl.need_length = NL_WANTLENGTH; + SlSetLength(_sl.obj_len); + + offs = SlGetOffs() + _sl.obj_len; + + /* And write the stuff */ + proc(arg); + + if (offs != SlGetOffs()) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Invalid chunk size"); +} + +/** + * Load a chunk of data (eg vehicles, stations, etc.) + * @param ch The chunkhandler that will be used for the operation + */ +static void SlLoadChunk(const ChunkHandler *ch) +{ + byte m = SlReadByte(); + size_t len; + size_t endoffs; + + _sl.block_mode = m; + _sl.obj_len = 0; + + switch (m) { + case CH_ARRAY: + _sl.array_index = 0; + ch->load_proc(); + break; + case CH_SPARSE_ARRAY: + ch->load_proc(); + break; + default: + if ((m & 0xF) == CH_RIFF) { + /* Read length */ + len = (SlReadByte() << 16) | ((m >> 4) << 24); + len += SlReadUint16(); + _sl.obj_len = len; + endoffs = SlGetOffs() + len; + ch->load_proc(); + if (SlGetOffs() != endoffs) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Invalid chunk size"); + } else { + SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Invalid chunk type"); + } + break; + } +} + +/* Stub Chunk handlers to only calculate length and do nothing else */ +static ChunkSaveLoadProc *_tmp_proc_1; +static inline void SlStubSaveProc2(void *arg) {_tmp_proc_1();} +static void SlStubSaveProc() {SlAutolength(SlStubSaveProc2, NULL);} + +/** Save a chunk of data (eg. vehicles, stations, etc.). Each chunk is + * prefixed by an ID identifying it, followed by data, and terminator where appropiate + * @param ch The chunkhandler that will be used for the operation + */ +static void SlSaveChunk(const ChunkHandler *ch) +{ + ChunkSaveLoadProc *proc = ch->save_proc; + + /* Don't save any chunk information if there is no save handler. */ + if (proc == NULL) return; + + SlWriteUint32(ch->id); + DEBUG(sl, 2, "Saving chunk %c%c%c%c", ch->id >> 24, ch->id >> 16, ch->id >> 8, ch->id); + + if (ch->flags & CH_AUTO_LENGTH) { + /* Need to calculate the length. Solve that by calling SlAutoLength in the save_proc. */ + _tmp_proc_1 = proc; + proc = SlStubSaveProc; + } + + _sl.block_mode = ch->flags & CH_TYPE_MASK; + switch (ch->flags & CH_TYPE_MASK) { + case CH_RIFF: + _sl.need_length = NL_WANTLENGTH; + proc(); + break; + case CH_ARRAY: + _sl.last_array_index = 0; + SlWriteByte(CH_ARRAY); + proc(); + SlWriteArrayLength(0); // Terminate arrays + break; + case CH_SPARSE_ARRAY: + SlWriteByte(CH_SPARSE_ARRAY); + proc(); + SlWriteArrayLength(0); // Terminate arrays + break; + default: NOT_REACHED(); + } +} + +/** Save all chunks */ +static void SlSaveChunks() +{ + const ChunkHandler *ch; + const ChunkHandler* const *chsc; + uint p; + + for (p = 0; p != CH_NUM_PRI_LEVELS; p++) { + for (chsc = _sl.chs; (ch = *chsc++) != NULL;) { + while (true) { + if (((ch->flags >> CH_PRI_SHL) & (CH_NUM_PRI_LEVELS - 1)) == p) + SlSaveChunk(ch); + if (ch->flags & CH_LAST) + break; + ch++; + } + } + } + + /* Terminator */ + SlWriteUint32(0); +} + +/** Find the ChunkHandler that will be used for processing the found + * chunk in the savegame or in memory + * @param id the chunk in question + * @return returns the appropiate chunkhandler + */ +static const ChunkHandler *SlFindChunkHandler(uint32 id) +{ + const ChunkHandler *ch; + const ChunkHandler *const *chsc; + for (chsc = _sl.chs; (ch = *chsc++) != NULL;) { + for (;;) { + if (ch->id == id) return ch; + if (ch->flags & CH_LAST) break; + ch++; + } + } + return NULL; +} + +/** Load all chunks */ +static void SlLoadChunks() +{ + uint32 id; + const ChunkHandler *ch; + + for (id = SlReadUint32(); id != 0; id = SlReadUint32()) { + DEBUG(sl, 2, "Loading chunk %c%c%c%c", id >> 24, id >> 16, id >> 8, id); + + ch = SlFindChunkHandler(id); + if (ch == NULL) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unknown chunk type"); + SlLoadChunk(ch); + } +} + +/******************************************* + ********** START OF LZO CODE ************** + *******************************************/ +#define LZO_SIZE 8192 + +#include "../minilzo.h" + +static size_t ReadLZO() +{ + byte out[LZO_SIZE + LZO_SIZE / 64 + 16 + 3 + 8]; + uint32 tmp[2]; + uint32 size; + uint len; + + /* Read header*/ + if (fread(tmp, sizeof(tmp), 1, _sl.fh) != 1) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE, "File read failed"); + + /* Check if size is bad */ + ((uint32*)out)[0] = size = tmp[1]; + + if (_sl_version != 0) { + tmp[0] = TO_BE32(tmp[0]); + size = TO_BE32(size); + } + + if (size >= sizeof(out)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Inconsistent size"); + + /* Read block */ + if (fread(out + sizeof(uint32), size, 1, _sl.fh) != 1) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); + + /* Verify checksum */ + if (tmp[0] != lzo_adler32(0, out, size + sizeof(uint32))) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Bad checksum"); + + /* Decompress */ + lzo1x_decompress(out + sizeof(uint32)*1, size, _sl.buf, &len, NULL); + return len; +} + +/* p contains the pointer to the buffer, len contains the pointer to the length. + * len bytes will be written, p and l will be updated to reflect the next buffer. */ +static void WriteLZO(size_t size) +{ + byte out[LZO_SIZE + LZO_SIZE / 64 + 16 + 3 + 8]; + byte wrkmem[sizeof(byte*)*4096]; + uint outlen; + + lzo1x_1_compress(_sl.buf, (lzo_uint)size, out + sizeof(uint32)*2, &outlen, wrkmem); + ((uint32*)out)[1] = TO_BE32(outlen); + ((uint32*)out)[0] = TO_BE32(lzo_adler32(0, out + sizeof(uint32), outlen + sizeof(uint32))); + if (fwrite(out, outlen + sizeof(uint32)*2, 1, _sl.fh) != 1) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE); +} + +static bool InitLZO() +{ + _sl.bufsize = LZO_SIZE; + _sl.buf = _sl.buf_ori = MallocT<byte>(LZO_SIZE); + return true; +} + +static void UninitLZO() +{ + free(_sl.buf_ori); +} + +/********************************************* + ******** START OF NOCOMP CODE (uncompressed)* + *********************************************/ +static size_t ReadNoComp() +{ + return fread(_sl.buf, 1, LZO_SIZE, _sl.fh); +} + +static void WriteNoComp(size_t size) +{ + if (fwrite(_sl.buf, 1, size, _sl.fh) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE); +} + +static bool InitNoComp() +{ + _sl.bufsize = LZO_SIZE; + _sl.buf = _sl.buf_ori = MallocT<byte>(LZO_SIZE); + return true; +} + +static void UninitNoComp() +{ + free(_sl.buf_ori); +} + +/******************************************** + ********** START OF MEMORY CODE (in ram)**** + ********************************************/ + +#include "../table/sprites.h" +#include "../gui.h" + +struct ThreadedSave { + uint count; + byte ff_state; + bool saveinprogress; + CursorID cursor; +}; + +/* A maximum size of of 128K * 500 = 64.000KB savegames */ +STATIC_OLD_POOL(Savegame, byte, 17, 500, NULL, NULL) +static ThreadedSave _ts; + +static bool InitMem() +{ + _ts.count = 0; + + _Savegame_pool.CleanPool(); + _Savegame_pool.AddBlockToPool(); + + /* A block from the pool is a contigious area of memory, so it is safe to write to it sequentially */ + _sl.bufsize = GetSavegamePoolSize(); + _sl.buf = GetSavegame(_ts.count); + return true; +} + +static void UnInitMem() +{ + _Savegame_pool.CleanPool(); +} + +static void WriteMem(size_t size) +{ + _ts.count += (uint)size; + /* Allocate new block and new buffer-pointer */ + _Savegame_pool.AddBlockIfNeeded(_ts.count); + _sl.buf = GetSavegame(_ts.count); +} + +/******************************************** + ********** START OF ZLIB CODE ************** + ********************************************/ + +#if defined(WITH_ZLIB) +#include <zlib.h> + +static z_stream _z; + +static bool InitReadZlib() +{ + memset(&_z, 0, sizeof(_z)); + if (inflateInit(&_z) != Z_OK) return false; + + _sl.bufsize = 4096; + _sl.buf = _sl.buf_ori = MallocT<byte>(4096 + 4096); // also contains fread buffer + return true; +} + +static size_t ReadZlib() +{ + int r; + + _z.next_out = _sl.buf; + _z.avail_out = 4096; + + do { + /* read more bytes from the file? */ + if (_z.avail_in == 0) { + _z.avail_in = (uint)fread(_z.next_in = _sl.buf + 4096, 1, 4096, _sl.fh); + } + + /* inflate the data */ + r = inflate(&_z, 0); + if (r == Z_STREAM_END) + break; + + if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "inflate() failed"); + } while (_z.avail_out); + + return 4096 - _z.avail_out; +} + +static void UninitReadZlib() +{ + inflateEnd(&_z); + free(_sl.buf_ori); +} + +static bool InitWriteZlib() +{ + memset(&_z, 0, sizeof(_z)); + if (deflateInit(&_z, 6) != Z_OK) return false; + + _sl.bufsize = 4096; + _sl.buf = _sl.buf_ori = MallocT<byte>(4096); // also contains fread buffer + return true; +} + +static void WriteZlibLoop(z_streamp z, byte *p, size_t len, int mode) +{ + byte buf[1024]; // output buffer + int r; + uint n; + z->next_in = p; + z->avail_in = (uInt)len; + do { + z->next_out = buf; + z->avail_out = sizeof(buf); + r = deflate(z, mode); + /* bytes were emitted? */ + if ((n = sizeof(buf) - z->avail_out) != 0) { + if (fwrite(buf, n, 1, _sl.fh) != 1) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE); + } + if (r == Z_STREAM_END) + break; + if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "zlib returned error code"); + } while (z->avail_in || !z->avail_out); +} + +static void WriteZlib(size_t len) +{ + WriteZlibLoop(&_z, _sl.buf, len, 0); +} + +static void UninitWriteZlib() +{ + /* flush any pending output. */ + if (_sl.fh) WriteZlibLoop(&_z, NULL, 0, Z_FINISH); + deflateEnd(&_z); + free(_sl.buf_ori); +} + +#endif /* WITH_ZLIB */ + +/******************************************* + ************* END OF CODE ***************** + *******************************************/ + +/* these define the chunks */ +extern const ChunkHandler _gamelog_chunk_handlers[]; +extern const ChunkHandler _map_chunk_handlers[]; +extern const ChunkHandler _misc_chunk_handlers[]; +extern const ChunkHandler _name_chunk_handlers[]; +extern const ChunkHandler _cheat_chunk_handlers[] ; +extern const ChunkHandler _setting_chunk_handlers[]; +extern const ChunkHandler _company_chunk_handlers[]; +extern const ChunkHandler _engine_chunk_handlers[]; +extern const ChunkHandler _veh_chunk_handlers[]; +extern const ChunkHandler _waypoint_chunk_handlers[]; +extern const ChunkHandler _depot_chunk_handlers[]; +extern const ChunkHandler _order_chunk_handlers[]; +extern const ChunkHandler _town_chunk_handlers[]; +extern const ChunkHandler _sign_chunk_handlers[]; +extern const ChunkHandler _station_chunk_handlers[]; +extern const ChunkHandler _industry_chunk_handlers[]; +extern const ChunkHandler _economy_chunk_handlers[]; +extern const ChunkHandler _subsidy_chunk_handlers[]; +extern const ChunkHandler _animated_tile_chunk_handlers[]; +extern const ChunkHandler _newgrf_chunk_handlers[]; +extern const ChunkHandler _group_chunk_handlers[]; +extern const ChunkHandler _cargopacket_chunk_handlers[]; +extern const ChunkHandler _autoreplace_chunk_handlers[]; + +static const ChunkHandler * const _chunk_handlers[] = { + _gamelog_chunk_handlers, + _map_chunk_handlers, + _misc_chunk_handlers, + _name_chunk_handlers, + _cheat_chunk_handlers, + _setting_chunk_handlers, + _veh_chunk_handlers, + _waypoint_chunk_handlers, + _depot_chunk_handlers, + _order_chunk_handlers, + _industry_chunk_handlers, + _economy_chunk_handlers, + _subsidy_chunk_handlers, + _engine_chunk_handlers, + _town_chunk_handlers, + _sign_chunk_handlers, + _station_chunk_handlers, + _company_chunk_handlers, + _animated_tile_chunk_handlers, + _newgrf_chunk_handlers, + _group_chunk_handlers, + _cargopacket_chunk_handlers, + _autoreplace_chunk_handlers, + NULL, +}; + +/** + * Pointers cannot be saved to a savegame, so this functions gets + * the index of the item, and if not available, it hussles with + * pointers (looks really bad :() + * Remember that a NULL item has value 0, and all + * indeces have +1, so vehicle 0 is saved as index 1. + * @param obj The object that we want to get the index of + * @param rt SLRefType type of the object the index is being sought of + * @return Return the pointer converted to an index of the type pointed to + */ +static uint ReferenceToInt(const void *obj, SLRefType rt) +{ + if (obj == NULL) return 0; + + switch (rt) { + case REF_VEHICLE_OLD: // Old vehicles we save as new onces + case REF_VEHICLE: return ((const Vehicle*)obj)->index + 1; + case REF_STATION: return ((const Station*)obj)->index + 1; + case REF_TOWN: return ((const Town*)obj)->index + 1; + case REF_ORDER: return ((const Order*)obj)->index + 1; + case REF_ROADSTOPS: return ((const RoadStop*)obj)->index + 1; + case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1; + case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1; + case REF_ORDERLIST: return ((const OrderList*)obj)->index + 1; + default: NOT_REACHED(); + } + + return 0; // avoid compiler warning +} + +/** + * Pointers cannot be loaded from a savegame, so this function + * gets the index from the savegame and returns the appropiate + * pointer from the already loaded base. + * Remember that an index of 0 is a NULL pointer so all indeces + * are +1 so vehicle 0 is saved as 1. + * @param index The index that is being converted to a pointer + * @param rt SLRefType type of the object the pointer is sought of + * @return Return the index converted to a pointer of any type + */ +static void *IntToReference(uint index, SLRefType rt) +{ + /* After version 4.3 REF_VEHICLE_OLD is saved as REF_VEHICLE, + * and should be loaded like that */ + if (rt == REF_VEHICLE_OLD && !CheckSavegameVersionOldStyle(4, 4)) { + rt = REF_VEHICLE; + } + + /* No need to look up NULL pointers, just return immediately */ + if (rt != REF_VEHICLE_OLD && index == 0) { + return NULL; + } + + index--; // correct for the NULL index + + switch (rt) { + case REF_ORDERLIST: + if (_OrderList_pool.AddBlockIfNeeded(index)) return GetOrderList(index); + error("Orders: failed loading savegame: too many order lists"); + + case REF_ORDER: + if (_Order_pool.AddBlockIfNeeded(index)) return GetOrder(index); + error("Orders: failed loading savegame: too many orders"); + + case REF_VEHICLE: + if (_Vehicle_pool.AddBlockIfNeeded(index)) return GetVehicle(index); + error("Vehicles: failed loading savegame: too many vehicles"); + + case REF_STATION: + if (_Station_pool.AddBlockIfNeeded(index)) return GetStation(index); + error("Stations: failed loading savegame: too many stations"); + + case REF_TOWN: + if (_Town_pool.AddBlockIfNeeded(index)) return GetTown(index); + error("Towns: failed loading savegame: too many towns"); + + case REF_ROADSTOPS: + if (_RoadStop_pool.AddBlockIfNeeded(index)) return GetRoadStop(index); + error("RoadStops: failed loading savegame: too many RoadStops"); + + case REF_ENGINE_RENEWS: + if (_EngineRenew_pool.AddBlockIfNeeded(index)) return GetEngineRenew(index); + error("EngineRenews: failed loading savegame: too many EngineRenews"); + + case REF_CARGO_PACKET: + if (_CargoPacket_pool.AddBlockIfNeeded(index)) return GetCargoPacket(index); + error("CargoPackets: failed loading savegame: too many Cargo packets"); + + case REF_VEHICLE_OLD: + /* Old vehicles were saved differently: + * invalid vehicle was 0xFFFF, + * and the index was not - 1.. correct for this */ + index++; + if (index == INVALID_VEHICLE) return NULL; + + if (_Vehicle_pool.AddBlockIfNeeded(index)) return GetVehicle(index); + error("Vehicles: failed loading savegame: too many vehicles"); + + default: NOT_REACHED(); + } + + return NULL; +} + +/** The format for a reader/writer type of a savegame */ +struct SaveLoadFormat { + const char *name; ///< name of the compressor/decompressor (debug-only) + uint32 tag; ///< the 4-letter tag by which it is identified in the savegame + + bool (*init_read)(); ///< function executed upon initalization of the loader + ReaderProc *reader; ///< function that loads the data from the file + void (*uninit_read)(); ///< function executed when reading is finished + + bool (*init_write)(); ///< function executed upon intialization of the saver + WriterProc *writer; ///< function that saves the data to the file + void (*uninit_write)(); ///< function executed when writing is done +}; + +static const SaveLoadFormat _saveload_formats[] = { + {"memory", 0, NULL, NULL, NULL, InitMem, WriteMem, UnInitMem}, + {"lzo", TO_BE32X('OTTD'), InitLZO, ReadLZO, UninitLZO, InitLZO, WriteLZO, UninitLZO}, + {"none", TO_BE32X('OTTN'), InitNoComp, ReadNoComp, UninitNoComp, InitNoComp, WriteNoComp, UninitNoComp}, +#if defined(WITH_ZLIB) + {"zlib", TO_BE32X('OTTZ'), InitReadZlib, ReadZlib, UninitReadZlib, InitWriteZlib, WriteZlib, UninitWriteZlib}, +#else + {"zlib", TO_BE32X('OTTZ'), NULL, NULL, NULL, NULL, NULL, NULL}, +#endif +}; + +/** + * Return the savegameformat of the game. Whether it was create with ZLIB compression + * uncompressed, or another type + * @param s Name of the savegame format. If NULL it picks the first available one + * @return Pointer to SaveLoadFormat struct giving all characteristics of this type of savegame + */ +static const SaveLoadFormat *GetSavegameFormat(const char *s) +{ + const SaveLoadFormat *def = endof(_saveload_formats) - 1; + + /* find default savegame format, the highest one with which files can be written */ + while (!def->init_write) def--; + + if (s != NULL && s[0] != '\0') { + const SaveLoadFormat *slf; + for (slf = &_saveload_formats[0]; slf != endof(_saveload_formats); slf++) { + if (slf->init_write != NULL && strcmp(s, slf->name) == 0) + return slf; + } + + ShowInfoF("Savegame format '%s' is not available. Reverting to '%s'.", s, def->name); + } + return def; +} + +/* actual loader/saver function */ +void InitializeGame(uint size_x, uint size_y, bool reset_date); +extern bool AfterLoadGame(); +extern bool LoadOldSaveGame(const char *file); + +/** Small helper function to close the to be loaded savegame an signal error */ +static inline SaveOrLoadResult AbortSaveLoad() +{ + if (_sl.fh != NULL) fclose(_sl.fh); + + _sl.fh = NULL; + return SL_ERROR; +} + +/** Update the gui accordingly when starting saving + * and set locks on saveload. Also turn off fast-forward cause with that + * saving takes Aaaaages */ +static void SaveFileStart() +{ + _ts.ff_state = _fast_forward; + _fast_forward = 0; + if (_cursor.sprite == SPR_CURSOR_MOUSE) SetMouseCursor(SPR_CURSOR_ZZZ, PAL_NONE); + + InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SAVELOAD_START); + _ts.saveinprogress = true; +} + +/** Update the gui accordingly when saving is done and release locks + * on saveload */ +static void SaveFileDone() +{ + if (_game_mode != GM_MENU) _fast_forward = _ts.ff_state; + if (_cursor.sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE, PAL_NONE); + + InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SAVELOAD_FINISH); + _ts.saveinprogress = false; +} + +/** Set the error message from outside of the actual loading/saving of the game (AfterLoadGame and friends) */ +void SetSaveLoadError(StringID str) +{ + _sl.error_str = str; +} + +/** Get the string representation of the error message */ +const char *GetSaveLoadErrorString() +{ + SetDParam(0, _sl.error_str); + SetDParamStr(1, _sl.extra_msg); + + static char err_str[512]; + GetString(err_str, _sl.save ? STR_4007_GAME_SAVE_FAILED : STR_4009_GAME_LOAD_FAILED, lastof(err_str)); + return err_str; +} + +/** Show a gui message when saving has failed */ +static void SaveFileError() +{ + SetDParamStr(0, GetSaveLoadErrorString()); + ShowErrorMessage(STR_JUST_RAW_STRING, STR_NULL, 0, 0); + SaveFileDone(); +} + +/** We have written the whole game into memory, _Savegame_pool, now find + * and appropiate compressor and start writing to file. + */ +static SaveOrLoadResult SaveFileToDisk(bool threaded) +{ + const SaveLoadFormat *fmt; + uint32 hdr[2]; + + _sl.excpt_uninit = NULL; + try { + fmt = GetSavegameFormat(_savegame_format); + + /* We have written our stuff to memory, now write it to file! */ + hdr[0] = fmt->tag; + hdr[1] = TO_BE32(SAVEGAME_VERSION << 16); + if (fwrite(hdr, sizeof(hdr), 1, _sl.fh) != 1) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE); + + if (!fmt->init_write()) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor"); + + { + uint i; + uint count = 1 << Savegame_POOL_BLOCK_SIZE_BITS; + + if (_ts.count != _sl.offs_base) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unexpected size of chunk"); + for (i = 0; i != _Savegame_pool.GetBlockCount() - 1; i++) { + _sl.buf = _Savegame_pool.blocks[i]; + fmt->writer(count); + } + + /* The last block is (almost) always not fully filled, so only write away + * as much data as it is in there */ + _sl.buf = _Savegame_pool.blocks[i]; + fmt->writer(_ts.count - (i * count)); + } + + fmt->uninit_write(); + if (_ts.count != _sl.offs_base) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unexpected size of chunk"); + GetSavegameFormat("memory")->uninit_write(); // clean the memorypool + fclose(_sl.fh); + + if (threaded) SetAsyncSaveFinish(SaveFileDone); + + return SL_OK; + } + catch (...) { + AbortSaveLoad(); + if (_sl.excpt_uninit != NULL) _sl.excpt_uninit(); + + /* Skip the "color" character */ + DEBUG(sl, 0, GetSaveLoadErrorString() + 3); + + if (threaded) { + SetAsyncSaveFinish(SaveFileError); + } else { + SaveFileError(); + } + return SL_ERROR; + } +} + +static void SaveFileToDiskThread(void *arg) +{ + SaveFileToDisk(true); +} + +void WaitTillSaved() +{ + if (_save_thread == NULL) return; + + _save_thread->Join(); + delete _save_thread; + _save_thread = NULL; +} + +/** + * Main Save or Load function where the high-level saveload functions are + * handled. It opens the savegame, selects format and checks versions + * @param filename The name of the savegame being created/loaded + * @param mode Save or load. Load can also be a TTD(Patch) game. Use SL_LOAD, SL_OLD_LOAD or SL_SAVE + * @return Return the results of the action. SL_OK, SL_ERROR or SL_REINIT ("unload" the game) + */ +SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb) +{ + uint32 hdr[2]; + const SaveLoadFormat *fmt; + + /* An instance of saving is already active, so don't go saving again */ + if (_ts.saveinprogress && mode == SL_SAVE) { + /* if not an autosave, but a user action, show error message */ + if (!_do_autosave) ShowErrorMessage(INVALID_STRING_ID, STR_SAVE_STILL_IN_PROGRESS, 0, 0); + return SL_OK; + } + WaitTillSaved(); + + _next_offs = 0; + + /* Load a TTDLX or TTDPatch game */ + if (mode == SL_OLD_LOAD) { + InitializeGame(256, 256, true); // set a mapsize of 256x256 for TTDPatch games or it might get confused + GamelogReset(); + if (!LoadOldSaveGame(filename)) return SL_REINIT; + _sl_version = 0; + _sl_minor_version = 0; + GamelogStartAction(GLAT_LOAD); + if (!AfterLoadGame()) { + GamelogStopAction(); + return SL_REINIT; + } + GamelogStopAction(); + return SL_OK; + } + + _sl.excpt_uninit = NULL; + try { + _sl.fh = (mode == SL_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb); + + /* Make it a little easier to load savegames from the console */ + if (_sl.fh == NULL && mode == SL_LOAD) _sl.fh = FioFOpenFile(filename, "rb", SAVE_DIR); + if (_sl.fh == NULL && mode == SL_LOAD) _sl.fh = FioFOpenFile(filename, "rb", BASE_DIR); + + if (_sl.fh == NULL) { + SlError(mode == SL_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); + } + + _sl.bufe = _sl.bufp = NULL; + _sl.offs_base = 0; + _sl.save = (mode != 0); + _sl.chs = _chunk_handlers; + + /* General tactic is to first save the game to memory, then use an available writer + * to write it to file, either in threaded mode if possible, or single-threaded */ + if (mode == SL_SAVE) { /* SAVE game */ + DEBUG(desync, 1, "save: %s\n", filename); + fmt = GetSavegameFormat("memory"); // write to memory + + _sl.write_bytes = fmt->writer; + _sl.excpt_uninit = fmt->uninit_write; + if (!fmt->init_write()) { + DEBUG(sl, 0, "Initializing writer '%s' failed.", fmt->name); + return AbortSaveLoad(); + } + + _sl_version = SAVEGAME_VERSION; + + SaveViewportBeforeSaveGame(); + SlSaveChunks(); + SlWriteFill(); // flush the save buffer + + SaveFileStart(); + if (_network_server || + (_save_thread = ThreadObject::New(&SaveFileToDiskThread, NULL)) == NULL) { + if (!_network_server) DEBUG(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode..."); + + SaveOrLoadResult result = SaveFileToDisk(false); + SaveFileDone(); + + return result; + } + } else { /* LOAD game */ + assert(mode == SL_LOAD); + DEBUG(desync, 1, "load: %s\n", filename); + + if (fread(hdr, sizeof(hdr), 1, _sl.fh) != 1) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); + + /* see if we have any loader for this type. */ + for (fmt = _saveload_formats; ; fmt++) { + /* No loader found, treat as version 0 and use LZO format */ + if (fmt == endof(_saveload_formats)) { + DEBUG(sl, 0, "Unknown savegame type, trying to load it as the buggy format"); + #if defined(WINCE) + /* Of course some system had not to support rewind ;) */ + fseek(_sl.fh, 0L, SEEK_SET); + clearerr(_sl.fh); + #else + rewind(_sl.fh); + #endif + _sl_version = 0; + _sl_minor_version = 0; + fmt = _saveload_formats + 1; // LZO + break; + } + + if (fmt->tag == hdr[0]) { + /* check version number */ + _sl_version = TO_BE32(hdr[1]) >> 16; + /* Minor is not used anymore from version 18.0, but it is still needed + * in versions before that (4 cases) which can't be removed easy. + * Therefor it is loaded, but never saved (or, it saves a 0 in any scenario). + * So never EVER use this minor version again. -- TrueLight -- 22-11-2005 */ + _sl_minor_version = (TO_BE32(hdr[1]) >> 8) & 0xFF; + + DEBUG(sl, 1, "Loading savegame version %d", _sl_version); + + /* Is the version higher than the current? */ + if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME); + break; + } + } + + _sl.read_bytes = fmt->reader; + _sl.excpt_uninit = fmt->uninit_read; + + /* loader for this savegame type is not implemented? */ + if (fmt->init_read == NULL) { + char err_str[64]; + snprintf(err_str, lengthof(err_str), "Loader for '%s' is not available.", fmt->name); + SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, err_str); + } + + if (!fmt->init_read()) { + char err_str[64]; + snprintf(err_str, lengthof(err_str), "Initializing loader '%s' failed", fmt->name); + SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, err_str); + } + + /* Old maps were hardcoded to 256x256 and thus did not contain + * any mapsize information. Pre-initialize to 256x256 to not to + * confuse old games */ + InitializeGame(256, 256, true); + + GamelogReset(); + + SlLoadChunks(); + fmt->uninit_read(); + fclose(_sl.fh); + + GamelogStartAction(GLAT_LOAD); + + _savegame_type = SGT_OTTD; + + /* After loading fix up savegame for any internal changes that + * might've occured since then. If it fails, load back the old game */ + if (!AfterLoadGame()) { + GamelogStopAction(); + return SL_REINIT; + } + + GamelogStopAction(); + } + + return SL_OK; + } + catch (...) { + AbortSaveLoad(); + + /* deinitialize compressor. */ + if (_sl.excpt_uninit != NULL) _sl.excpt_uninit(); + + /* Skip the "color" character */ + DEBUG(sl, 0, GetSaveLoadErrorString() + 3); + + /* A saver/loader exception!! reinitialize all variables to prevent crash! */ + return (mode == SL_LOAD) ? SL_REINIT : SL_ERROR; + } +} + +/** Do a save when exiting the game (patch option) _settings_client.gui.autosave_on_exit */ +void DoExitSave() +{ + SaveOrLoad("exit.sav", SL_SAVE, AUTOSAVE_DIR); +} + +/** + * Fill the buffer with the default name for a savegame *or* screenshot. + * @param buf the buffer to write to. + * @param last the last element in the buffer. + */ +void GenerateDefaultSaveName(char *buf, const char *last) +{ + /* Check if we have a name for this map, which is the name of the first + * available company. When there's no company available we'll use + * 'Spectator' as "company" name. */ + CompanyID cid = _local_company; + if (!IsValidCompanyID(cid)) { + const Company *c; + FOR_ALL_COMPANIES(c) { + cid = c->index; + break; + } + } + + SetDParam(0, cid); + + /* Insert current date */ + switch (_settings_client.gui.date_format_in_default_names) { + case 0: SetDParam(1, STR_JUST_DATE_LONG); break; + case 1: SetDParam(1, STR_JUST_DATE_TINY); break; + case 2: SetDParam(1, STR_JUST_DATE_ISO); break; + default: NOT_REACHED(); + } + SetDParam(2, _date); + + /* Get the correct string (special string for when there's not company) */ + GetString(buf, !IsValidCompanyID(cid) ? STR_GAME_SAVELOAD_SPECTATOR_SAVEGAME : STR_4004, last); + SanitizeFilename(buf); +} + +#if 0 +/** + * Function to get the type of the savegame by looking at the file header. + * NOTICE: Not used right now, but could be used if extensions of savegames are garbled + * @param file Savegame to be checked + * @return SL_OLD_LOAD or SL_LOAD of the file + */ +int GetSavegameType(char *file) +{ + const SaveLoadFormat *fmt; + uint32 hdr; + FILE *f; + int mode = SL_OLD_LOAD; + + f = fopen(file, "rb"); + if (fread(&hdr, sizeof(hdr), 1, f) != 1) { + DEBUG(sl, 0, "Savegame is obsolete or invalid format"); + mode = SL_LOAD; // don't try to get filename, just show name as it is written + } else { + /* see if we have any loader for this type. */ + for (fmt = _saveload_formats; fmt != endof(_saveload_formats); fmt++) { + if (fmt->tag == hdr) { + mode = SL_LOAD; // new type of savegame + break; + } + } + } + + fclose(f); + return mode; +} +#endif diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h new file mode 100644 index 000000000..f71b0dec4 --- /dev/null +++ b/src/saveload/saveload.h @@ -0,0 +1,332 @@ +/* $Id$ */ + +/** @file saveload.h Functions/types related to saving and loading games. */ + +#ifndef SAVELOAD_H +#define SAVELOAD_H + +#include "../fileio_type.h" + +#ifdef SIZE_MAX +#undef SIZE_MAX +#endif + +#define SIZE_MAX ((size_t)-1) + +enum SaveOrLoadResult { + SL_OK = 0, ///< completed successfully + SL_ERROR = 1, ///< error that was caught before internal structures were modified + SL_REINIT = 2, ///< error that was caught in the middle of updating game state, need to clear it. (can only happen during load) +}; + +enum SaveOrLoadMode { + SL_INVALID = -1, + SL_LOAD = 0, + SL_SAVE = 1, + SL_OLD_LOAD = 2, + SL_PNG = 3, + SL_BMP = 4, +}; + +enum SavegameType { + SGT_TTD, ///< TTD savegame (can be detected incorrectly) + SGT_TTDP1, ///< TTDP savegame ( -//- ) (data at NW border) + SGT_TTDP2, ///< TTDP savegame in new format (data at SE border) + SGT_OTTD ///< OTTD savegame +}; + +void GenerateDefaultSaveName(char *buf, const char *last); +void SetSaveLoadError(uint16 str); +const char *GetSaveLoadErrorString(); +SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb); +void WaitTillSaved(); +void DoExitSave(); + + +typedef void ChunkSaveLoadProc(); +typedef void AutolengthProc(void *arg); + +struct ChunkHandler { + uint32 id; + ChunkSaveLoadProc *save_proc; + ChunkSaveLoadProc *load_proc; + uint32 flags; +}; + +struct NullStruct { + byte null; +}; + +enum SLRefType { + REF_ORDER = 0, + REF_VEHICLE = 1, + REF_STATION = 2, + REF_TOWN = 3, + REF_VEHICLE_OLD = 4, + REF_ROADSTOPS = 5, + REF_ENGINE_RENEWS = 6, + REF_CARGO_PACKET = 7, + REF_ORDERLIST = 8, +}; + +#define SL_MAX_VERSION 255 + +enum { + INC_VEHICLE_COMMON = 0, +}; + +enum { + CH_RIFF = 0, + CH_ARRAY = 1, + CH_SPARSE_ARRAY = 2, + CH_TYPE_MASK = 3, + CH_LAST = 8, + CH_AUTO_LENGTH = 16, + + CH_PRI_0 = 0 << 4, + CH_PRI_1 = 1 << 4, + CH_PRI_2 = 2 << 4, + CH_PRI_3 = 3 << 4, + CH_PRI_SHL = 4, + CH_NUM_PRI_LEVELS = 4, +}; + +/** VarTypes is the general bitmasked magic type that tells us + * certain characteristics about the variable it refers to. For example + * SLE_FILE_* gives the size(type) as it would be in the savegame and + * SLE_VAR_* the size(type) as it is in memory during runtime. These are + * the first 8 bits (0-3 SLE_FILE, 4-7 SLE_VAR). + * Bits 8-15 are reserved for various flags as explained below */ +enum VarTypes { + /* 4 bits allocated a maximum of 16 types for NumberType */ + SLE_FILE_I8 = 0, + SLE_FILE_U8 = 1, + SLE_FILE_I16 = 2, + SLE_FILE_U16 = 3, + SLE_FILE_I32 = 4, + SLE_FILE_U32 = 5, + SLE_FILE_I64 = 6, + SLE_FILE_U64 = 7, + SLE_FILE_STRINGID = 8, ///< StringID offset into strings-array + SLE_FILE_STRING = 9, + /* 6 more possible file-primitives */ + + /* 4 bits allocated a maximum of 16 types for NumberType */ + SLE_VAR_BL = 0 << 4, + SLE_VAR_I8 = 1 << 4, + SLE_VAR_U8 = 2 << 4, + SLE_VAR_I16 = 3 << 4, + SLE_VAR_U16 = 4 << 4, + SLE_VAR_I32 = 5 << 4, + SLE_VAR_U32 = 6 << 4, + SLE_VAR_I64 = 7 << 4, + SLE_VAR_U64 = 8 << 4, + SLE_VAR_NULL = 9 << 4, ///< useful to write zeros in savegame. + SLE_VAR_STRB = 10 << 4, ///< string (with pre-allocated buffer) + SLE_VAR_STRBQ = 11 << 4, ///< string enclosed in quotes (with pre-allocated buffer) + SLE_VAR_STR = 12 << 4, ///< string pointer + SLE_VAR_STRQ = 13 << 4, ///< string pointer enclosed in quotes + SLE_VAR_NAME = 14 << 4, ///< old custom name to be converted to a char pointer + /* 1 more possible memory-primitives */ + + /* Shortcut values */ + SLE_VAR_CHAR = SLE_VAR_I8, + + /* Default combinations of variables. As savegames change, so can variables + * and thus it is possible that the saved value and internal size do not + * match and you need to specify custom combo. The defaults are listed here */ + SLE_BOOL = SLE_FILE_I8 | SLE_VAR_BL, + SLE_INT8 = SLE_FILE_I8 | SLE_VAR_I8, + SLE_UINT8 = SLE_FILE_U8 | SLE_VAR_U8, + SLE_INT16 = SLE_FILE_I16 | SLE_VAR_I16, + SLE_UINT16 = SLE_FILE_U16 | SLE_VAR_U16, + SLE_INT32 = SLE_FILE_I32 | SLE_VAR_I32, + SLE_UINT32 = SLE_FILE_U32 | SLE_VAR_U32, + SLE_INT64 = SLE_FILE_I64 | SLE_VAR_I64, + SLE_UINT64 = SLE_FILE_U64 | SLE_VAR_U64, + SLE_CHAR = SLE_FILE_I8 | SLE_VAR_CHAR, + SLE_STRINGID = SLE_FILE_STRINGID | SLE_VAR_U16, + SLE_STRINGBUF = SLE_FILE_STRING | SLE_VAR_STRB, + SLE_STRINGBQUOTE = SLE_FILE_STRING | SLE_VAR_STRBQ, + SLE_STRING = SLE_FILE_STRING | SLE_VAR_STR, + SLE_STRINGQUOTE = SLE_FILE_STRING | SLE_VAR_STRQ, + SLE_NAME = SLE_FILE_STRINGID | SLE_VAR_NAME, + + /* Shortcut values */ + SLE_UINT = SLE_UINT32, + SLE_INT = SLE_INT32, + SLE_STRB = SLE_STRINGBUF, + SLE_STRBQ = SLE_STRINGBQUOTE, + SLE_STR = SLE_STRING, + SLE_STRQ = SLE_STRINGQUOTE, + + /* 8 bits allocated for a maximum of 8 flags + * Flags directing saving/loading of a variable */ + SLF_SAVE_NO = 1 << 8, ///< do not save with savegame, basically client-based + SLF_CONFIG_NO = 1 << 9, ///< do not save to config file + SLF_NETWORK_NO = 1 << 10, ///< do not synchronize over network (but it is saved if SSF_SAVE_NO is not set) + /* 5 more possible flags */ +}; + +typedef uint32 VarType; + +enum SaveLoadTypes { + SL_VAR = 0, + SL_REF = 1, + SL_ARR = 2, + SL_STR = 3, + SL_LST = 4, + // non-normal save-load types + SL_WRITEBYTE = 8, + SL_VEH_INCLUDE = 9, + SL_END = 15 +}; + +typedef byte SaveLoadType; + +/** SaveLoad type struct. Do NOT use this directly but use the SLE_ macros defined just below! */ +struct SaveLoad { + bool global; ///< should we load a global variable or a non-global one + SaveLoadType cmd; ///< the action to take with the saved/loaded type, All types need different action + VarType conv; ///< type of the variable to be saved, int + uint16 length; ///< (conditional) length of the variable (eg. arrays) (max array size is 65536 elements) + uint16 version_from; ///< save/load the variable starting from this savegame version + uint16 version_to; ///< save/load the variable until this savegame version + /* NOTE: This element either denotes the address of the variable for a global + * variable, or the offset within a struct which is then bound to a variable + * during runtime. Decision on which one to use is controlled by the function + * that is called to save it. address: global=true, offset: global=false */ + void *address; ///< address of variable OR offset of variable in the struct (max offset is 65536) +}; + +/* Same as SaveLoad but global variables are used (for better readability); */ +typedef SaveLoad SaveLoadGlobVarList; + +/* Simple variables, references (pointers) and arrays */ +#define SLE_GENERAL(cmd, base, variable, type, length, from, to) {false, cmd, type, length, from, to, (void*)cpp_offsetof(base, variable)} +#define SLE_CONDVAR(base, variable, type, from, to) SLE_GENERAL(SL_VAR, base, variable, type, 0, from, to) +#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 0, from, to) +#define SLE_CONDARR(base, variable, type, length, from, to) SLE_GENERAL(SL_ARR, base, variable, type, length, from, to) +#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_GENERAL(SL_STR, base, variable, type, length, from, to) +#define SLE_CONDLST(base, variable, type, from, to) SLE_GENERAL(SL_LST, base, variable, type, 0, from, to) + +#define SLE_VAR(base, variable, type) SLE_CONDVAR(base, variable, type, 0, SL_MAX_VERSION) +#define SLE_REF(base, variable, type) SLE_CONDREF(base, variable, type, 0, SL_MAX_VERSION) +#define SLE_ARR(base, variable, type, length) SLE_CONDARR(base, variable, type, length, 0, SL_MAX_VERSION) +#define SLE_STR(base, variable, type, length) SLE_CONDSTR(base, variable, type, length, 0, SL_MAX_VERSION) +#define SLE_LST(base, variable, type) SLE_CONDLST(base, variable, type, 0, SL_MAX_VERSION) + +#define SLE_CONDNULL(length, from, to) SLE_CONDARR(NullStruct, null, SLE_FILE_U8 | SLE_VAR_NULL | SLF_CONFIG_NO, length, from, to) + +/* Translate values ingame to different values in the savegame and vv */ +#define SLE_WRITEBYTE(base, variable, value) SLE_GENERAL(SL_WRITEBYTE, base, variable, 0, 0, value, value) + +/* The same as the ones at the top, only the offset is given directly; used for unions */ +#define SLE_GENERALX(cmd, offset, type, length, param1, param2) {false, cmd, type, length, param1, param2, (void*)(offset)} +#define SLE_CONDVARX(offset, type, from, to) SLE_GENERALX(SL_VAR, offset, type, 0, from, to) +#define SLE_CONDARRX(offset, type, length, from, to) SLE_GENERALX(SL_ARR, offset, type, length, from, to) +#define SLE_CONDREFX(offset, type, from, to) SLE_GENERALX(SL_REF, offset, type, 0, from, to) + +#define SLE_VARX(offset, type) SLE_CONDVARX(offset, type, 0, SL_MAX_VERSION) +#define SLE_REFX(offset, type) SLE_CONDREFX(offset, type, 0, SL_MAX_VERSION) + +#define SLE_WRITEBYTEX(offset, something) SLE_GENERALX(SL_WRITEBYTE, offset, 0, 0, something, 0) +#define SLE_VEH_INCLUDEX() SLE_GENERALX(SL_VEH_INCLUDE, 0, 0, 0, 0, SL_MAX_VERSION) + +/* End marker */ +#define SLE_END() {false, SL_END, 0, 0, 0, 0, NULL} + +/* Simple variables, references (pointers) and arrays, but for global variables */ +#define SLEG_GENERAL(cmd, variable, type, length, from, to) {true, cmd, type, length, from, to, (void*)&variable} + +#define SLEG_CONDVAR(variable, type, from, to) SLEG_GENERAL(SL_VAR, variable, type, 0, from, to) +#define SLEG_CONDREF(variable, type, from, to) SLEG_GENERAL(SL_REF, variable, type, 0, from, to) +#define SLEG_CONDARR(variable, type, length, from, to) SLEG_GENERAL(SL_ARR, variable, type, length, from, to) +#define SLEG_CONDSTR(variable, type, length, from, to) SLEG_GENERAL(SL_STR, variable, type, length, from, to) +#define SLEG_CONDLST(variable, type, from, to) SLEG_GENERAL(SL_LST, variable, type, 0, from, to) + +#define SLEG_VAR(variable, type) SLEG_CONDVAR(variable, type, 0, SL_MAX_VERSION) +#define SLEG_REF(variable, type) SLEG_CONDREF(variable, type, 0, SL_MAX_VERSION) +#define SLEG_ARR(variable, type) SLEG_CONDARR(variable, type, lengthof(variable), 0, SL_MAX_VERSION) +#define SLEG_STR(variable, type) SLEG_CONDSTR(variable, type, lengthof(variable), 0, SL_MAX_VERSION) +#define SLEG_LST(variable, type) SLEG_CONDLST(variable, type, 0, SL_MAX_VERSION) + +#define SLEG_CONDNULL(length, from, to) {true, SL_ARR, SLE_FILE_U8 | SLE_VAR_NULL | SLF_CONFIG_NO, length, from, to, (void*)NULL} + +#define SLEG_END() {true, SL_END, 0, 0, 0, 0, NULL} + +/** Checks if the savegame is below major.minor. + */ +static inline bool CheckSavegameVersionOldStyle(uint16 major, byte minor) +{ + extern uint16 _sl_version; + extern byte _sl_minor_version; + return (_sl_version < major) || (_sl_version == major && _sl_minor_version < minor); +} + +/** Checks if the savegame is below version. + */ +static inline bool CheckSavegameVersion(uint16 version) +{ + extern uint16 _sl_version; + return _sl_version < version; +} + +/** Checks if some version from/to combination falls within the range of the + * active savegame version */ +static inline bool SlIsObjectCurrentlyValid(uint16 version_from, uint16 version_to) +{ + extern const uint16 SAVEGAME_VERSION; + if (SAVEGAME_VERSION < version_from || SAVEGAME_VERSION > version_to) return false; + + return true; +} + +/* Get the NumberType of a setting. This describes the integer type + * as it is represented in memory + * @param type VarType holding information about the variable-type + * @return return the SLE_VAR_* part of a variable-type description */ +static inline VarType GetVarMemType(VarType type) +{ + return type & 0xF0; // GB(type, 4, 4) << 4; +} + +/* Get the FileType of a setting. This describes the integer type + * as it is represented in a savegame/file + * @param type VarType holding information about the variable-type + * @param return the SLE_FILE_* part of a variable-type description */ +static inline VarType GetVarFileType(VarType type) +{ + return type & 0xF; // GB(type, 0, 4); +} + +/** Get the address of the variable. Which one to pick depends on the object + * pointer. If it is NULL we are dealing with global variables so the address + * is taken. If non-null only the offset is stored in the union and we need + * to add this to the address of the object */ +static inline void *GetVariableAddress(const void *object, const SaveLoad *sld) +{ + return (byte*)(sld->global ? NULL : object) + (ptrdiff_t)sld->address; +} + +int64 ReadValue(const void *ptr, VarType conv); +void WriteValue(void *ptr, VarType conv, int64 val); + +void SlSetArrayIndex(uint index); +int SlIterateArray(); + +void SlAutolength(AutolengthProc *proc, void *arg); +size_t SlGetFieldLength(); +void SlSetLength(size_t length); +size_t SlCalcObjMemberLength(const void *object, const SaveLoad *sld); +size_t SlCalcObjLength(const void *object, const SaveLoad *sld); + +byte SlReadByte(); +void SlWriteByte(byte b); + +void SlGlobList(const SaveLoadGlobVarList *sldg); +void SlArray(void *array, size_t length, VarType conv); +void SlObject(void *object, const SaveLoad *sld); +bool SlObjectMember(void *object, const SaveLoad *sld); + +#endif /* SAVELOAD_H */ diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h new file mode 100644 index 000000000..7bb865d81 --- /dev/null +++ b/src/saveload/saveload_internal.h @@ -0,0 +1,40 @@ +/* $Id$ */ + +/** @file saveload_internal.h Declaration of functions used in more save/load files */ + +#ifndef SAVELOAD_INTERNAL_H +#define SAVELOAD_INTERNAL_H + +#include "../strings_type.h" +#include "../company_manager_face.h" +#include "../order_base.h" + +void InitializeOldNames(); +StringID RemapOldStringID(StringID s); +char *CopyFromOldName(StringID id); +void ResetOldNames(); + +void FixOldWaypoints(); + +void AfterLoadWaypoints(); +void AfterLoadVehicles(bool part_of_load); +void AfterLoadStations(); +void AfterLoadTown(); +void UpdateHousesAndTowns(); + +void UpdateOldAircraft(); + +void SaveViewportBeforeSaveGame(); +void ResetViewportAfterLoadGame(); + +void ConvertOldMultiheadToNew(); +void ConnectMultiheadedTrains(); + +extern int32 _saved_scrollpos_x; +extern int32 _saved_scrollpos_y; + +CompanyManagerFace ConvertFromOldCompanyManagerFace(uint32 face); + +Order UnpackOldOrder(uint16 packed); + +#endif /* SAVELOAD_INTERNAL_H */ diff --git a/src/saveload/signs_sl.cpp b/src/saveload/signs_sl.cpp new file mode 100644 index 000000000..ef4530dcd --- /dev/null +++ b/src/saveload/signs_sl.cpp @@ -0,0 +1,49 @@ +/* $Id$ */ + +/** @file signs_sl.cpp Code handling saving and loading of economy data */ + +#include "../stdafx.h" +#include "../strings_func.h" +#include "../company_func.h" +#include "../signs_base.h" +#include "../signs_func.h" + +#include "saveload_internal.h" +#include "saveload.h" + +static const SaveLoad _sign_desc[] = { + SLE_CONDVAR(Sign, name, SLE_NAME, 0, 83), + SLE_CONDSTR(Sign, name, SLE_STR, 0, 84, SL_MAX_VERSION), + SLE_CONDVAR(Sign, x, SLE_FILE_I16 | SLE_VAR_I32, 0, 4), + SLE_CONDVAR(Sign, y, SLE_FILE_I16 | SLE_VAR_I32, 0, 4), + SLE_CONDVAR(Sign, x, SLE_INT32, 5, SL_MAX_VERSION), + SLE_CONDVAR(Sign, y, SLE_INT32, 5, SL_MAX_VERSION), + SLE_CONDVAR(Sign, owner, SLE_UINT8, 6, SL_MAX_VERSION), + SLE_VAR(Sign, z, SLE_UINT8), + SLE_END() +}; + +/** Save all signs */ +static void Save_SIGN() +{ + Sign *si; + + FOR_ALL_SIGNS(si) { + SlSetArrayIndex(si->index); + SlObject(si, _sign_desc); + } +} + +/** Load all signs */ +static void Load_SIGN() +{ + int index; + while ((index = SlIterateArray()) != -1) { + Sign *si = new (index) Sign(); + SlObject(si, _sign_desc); + } +} + +extern const ChunkHandler _sign_chunk_handlers[] = { + { 'SIGN', Save_SIGN, Load_SIGN, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp new file mode 100644 index 000000000..b03e59c7d --- /dev/null +++ b/src/saveload/station_sl.cpp @@ -0,0 +1,227 @@ +/* $Id$ */ + +/** @file station_sl.cpp Code handling saving and loading of economy data */ + +#include "../stdafx.h" +#include "../station_base.h" +#include "../core/bitmath_func.hpp" +#include "../core/alloc_func.hpp" +#include "../variables.h" +#include "../newgrf_station.h" + +#include "saveload.h" + + +void AfterLoadStations() +{ + /* Update the speclists of all stations to point to the currently loaded custom stations. */ + Station *st; + FOR_ALL_STATIONS(st) { + for (uint i = 0; i < st->num_specs; i++) { + if (st->speclist[i].grfid == 0) continue; + + st->speclist[i].spec = GetCustomStationSpecByGrf(st->speclist[i].grfid, st->speclist[i].localidx); + } + + for (CargoID c = 0; c < NUM_CARGO; c++) st->goods[c].cargo.InvalidateCache(); + + StationUpdateAnimTriggers(st); + } +} + +static const SaveLoad _roadstop_desc[] = { + SLE_VAR(RoadStop, xy, SLE_UINT32), + SLE_CONDNULL(1, 0, 44), + SLE_VAR(RoadStop, status, SLE_UINT8), + /* Index was saved in some versions, but this is not needed */ + SLE_CONDNULL(4, 0, 8), + SLE_CONDNULL(2, 0, 44), + SLE_CONDNULL(1, 0, 25), + + SLE_REF(RoadStop, next, REF_ROADSTOPS), + SLE_CONDNULL(2, 0, 44), + + SLE_CONDNULL(4, 0, 24), + SLE_CONDNULL(1, 25, 25), + + SLE_END() +}; + +static const SaveLoad _station_desc[] = { + SLE_CONDVAR(Station, xy, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Station, xy, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDNULL(4, 0, 5), ///< bus/lorry tile + SLE_CONDVAR(Station, train_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Station, train_tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Station, airport_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Station, airport_tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Station, dock_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Station, dock_tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_REF(Station, town, REF_TOWN), + SLE_VAR(Station, trainst_w, SLE_UINT8), + SLE_CONDVAR(Station, trainst_h, SLE_UINT8, 2, SL_MAX_VERSION), + + SLE_CONDNULL(1, 0, 3), ///< alpha_order + + SLE_VAR(Station, string_id, SLE_STRINGID), + SLE_CONDSTR(Station, name, SLE_STR, 0, 84, SL_MAX_VERSION), + SLE_CONDVAR(Station, indtype, SLE_UINT8, 103, SL_MAX_VERSION), + SLE_VAR(Station, had_vehicle_of_type, SLE_UINT16), + + SLE_VAR(Station, time_since_load, SLE_UINT8), + SLE_VAR(Station, time_since_unload, SLE_UINT8), + SLE_VAR(Station, delete_ctr, SLE_UINT8), + SLE_VAR(Station, owner, SLE_UINT8), + SLE_VAR(Station, facilities, SLE_UINT8), + SLE_VAR(Station, airport_type, SLE_UINT8), + + SLE_CONDNULL(2, 0, 5), ///< Truck/bus stop status + SLE_CONDNULL(1, 0, 4), ///< Blocked months + + SLE_CONDVAR(Station, airport_flags, SLE_VAR_U64 | SLE_FILE_U16, 0, 2), + SLE_CONDVAR(Station, airport_flags, SLE_VAR_U64 | SLE_FILE_U32, 3, 45), + SLE_CONDVAR(Station, airport_flags, SLE_UINT64, 46, SL_MAX_VERSION), + + SLE_CONDNULL(2, 0, 25), ///< last-vehicle + SLE_CONDVAR(Station, last_vehicle_type, SLE_UINT8, 26, SL_MAX_VERSION), + + SLE_CONDNULL(2, 3, 25), ///< custom station class and id + SLE_CONDVAR(Station, build_date, SLE_FILE_U16 | SLE_VAR_I32, 3, 30), + SLE_CONDVAR(Station, build_date, SLE_INT32, 31, SL_MAX_VERSION), + + SLE_CONDREF(Station, bus_stops, REF_ROADSTOPS, 6, SL_MAX_VERSION), + SLE_CONDREF(Station, truck_stops, REF_ROADSTOPS, 6, SL_MAX_VERSION), + + /* Used by newstations for graphic variations */ + SLE_CONDVAR(Station, random_bits, SLE_UINT16, 27, SL_MAX_VERSION), + SLE_CONDVAR(Station, waiting_triggers, SLE_UINT8, 27, SL_MAX_VERSION), + SLE_CONDVAR(Station, num_specs, SLE_UINT8, 27, SL_MAX_VERSION), + + SLE_CONDLST(Station, loading_vehicles, REF_VEHICLE, 57, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 32 bytes) */ + SLE_CONDNULL(32, 2, SL_MAX_VERSION), + + SLE_END() +}; + +static uint16 _waiting_acceptance; +static uint16 _cargo_source; +static uint32 _cargo_source_xy; +static uint16 _cargo_days; +static Money _cargo_feeder_share; + +static const SaveLoad _station_speclist_desc[] = { + SLE_CONDVAR(StationSpecList, grfid, SLE_UINT32, 27, SL_MAX_VERSION), + SLE_CONDVAR(StationSpecList, localidx, SLE_UINT8, 27, SL_MAX_VERSION), + + SLE_END() +}; + + +void SaveLoad_STNS(Station *st) +{ + static const SaveLoad _goods_desc[] = { + SLEG_CONDVAR( _waiting_acceptance, SLE_UINT16, 0, 67), + SLE_CONDVAR(GoodsEntry, acceptance_pickup, SLE_UINT8, 68, SL_MAX_VERSION), + SLE_CONDNULL(2, 51, 67), + SLE_VAR(GoodsEntry, days_since_pickup, SLE_UINT8), + SLE_VAR(GoodsEntry, rating, SLE_UINT8), + SLEG_CONDVAR( _cargo_source, SLE_FILE_U8 | SLE_VAR_U16, 0, 6), + SLEG_CONDVAR( _cargo_source, SLE_UINT16, 7, 67), + SLEG_CONDVAR( _cargo_source_xy, SLE_UINT32, 44, 67), + SLEG_CONDVAR( _cargo_days, SLE_UINT8, 0, 67), + SLE_VAR(GoodsEntry, last_speed, SLE_UINT8), + SLE_VAR(GoodsEntry, last_age, SLE_UINT8), + SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_U32 | SLE_VAR_I64, 14, 64), + SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, 65, 67), + SLE_CONDLST(GoodsEntry, cargo.packets, REF_CARGO_PACKET, 68, SL_MAX_VERSION), + + SLE_END() + }; + + + SlObject(st, _station_desc); + + _waiting_acceptance = 0; + + uint num_cargo = CheckSavegameVersion(55) ? 12 : NUM_CARGO; + for (CargoID i = 0; i < num_cargo; i++) { + GoodsEntry *ge = &st->goods[i]; + SlObject(ge, _goods_desc); + if (CheckSavegameVersion(68)) { + SB(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE, 1, HasBit(_waiting_acceptance, 15)); + if (GB(_waiting_acceptance, 0, 12) != 0) { + /* Don't construct the packet with station here, because that'll fail with old savegames */ + CargoPacket *cp = new CargoPacket(); + /* In old versions, enroute_from used 0xFF as INVALID_STATION */ + cp->source = (CheckSavegameVersion(7) && _cargo_source == 0xFF) ? INVALID_STATION : _cargo_source; + cp->count = GB(_waiting_acceptance, 0, 12); + cp->days_in_transit = _cargo_days; + cp->feeder_share = _cargo_feeder_share; + cp->source_xy = _cargo_source_xy; + cp->days_in_transit = _cargo_days; + cp->feeder_share = _cargo_feeder_share; + SB(ge->acceptance_pickup, GoodsEntry::PICKUP, 1, 1); + ge->cargo.Append(cp); + } + } + } + + if (st->num_specs != 0) { + /* Allocate speclist memory when loading a game */ + if (st->speclist == NULL) st->speclist = CallocT<StationSpecList>(st->num_specs); + for (uint i = 0; i < st->num_specs; i++) { + SlObject(&st->speclist[i], _station_speclist_desc); + } + } +} + +static void Save_STNS() +{ + Station *st; + /* Write the stations */ + FOR_ALL_STATIONS(st) { + SlSetArrayIndex(st->index); + SlAutolength((AutolengthProc*)SaveLoad_STNS, st); + } +} + +static void Load_STNS() +{ + int index; + while ((index = SlIterateArray()) != -1) { + Station *st = new (index) Station(); + + SaveLoad_STNS(st); + } + + /* This is to ensure all pointers are within the limits of _stations_size */ + if (_station_tick_ctr > GetMaxStationIndex()) _station_tick_ctr = 0; +} + +static void Save_ROADSTOP() +{ + RoadStop *rs; + + FOR_ALL_ROADSTOPS(rs) { + SlSetArrayIndex(rs->index); + SlObject(rs, _roadstop_desc); + } +} + +static void Load_ROADSTOP() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + RoadStop *rs = new (index) RoadStop(INVALID_TILE); + + SlObject(rs, _roadstop_desc); + } +} + +extern const ChunkHandler _station_chunk_handlers[] = { + { 'STNS', Save_STNS, Load_STNS, CH_ARRAY }, + { 'ROAD', Save_ROADSTOP, Load_ROADSTOP, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/strings_sl.cpp b/src/saveload/strings_sl.cpp new file mode 100644 index 000000000..f436bdedf --- /dev/null +++ b/src/saveload/strings_sl.cpp @@ -0,0 +1,126 @@ +/* $Id$ */ + +/** @file strings_sl.cpp Code handling saving and loading of strings */ + +#include "../stdafx.h" +#include "../strings_type.h" +#include "../core/math_func.hpp" +#include "../core/bitmath_func.hpp" +#include "../core/alloc_func.hpp" +#include "../string_func.h" + +#include "table/strings.h" + +#include "saveload.h" + +/** + * Remap a string ID from the old format to the new format + * @param s StringID that requires remapping + * @return translated ID + */ +StringID RemapOldStringID(StringID s) +{ + switch (s) { + case 0x0006: return STR_SV_EMPTY; + case 0x7000: return STR_SV_UNNAMED; + case 0x70E4: return SPECSTR_PLAYERNAME_ENGLISH; + case 0x70E9: return SPECSTR_PLAYERNAME_ENGLISH; + case 0x8864: return STR_SV_TRAIN_NAME; + case 0x902B: return STR_SV_ROADVEH_NAME; + case 0x9830: return STR_SV_SHIP_NAME; + case 0xA02F: return STR_SV_AIRCRAFT_NAME; + + default: + if (IsInsideMM(s, 0x300F, 0x3030)) { + return s - 0x300F + STR_SV_STNAME; + } else { + return s; + } + } +} + +/** Location to load the old names to. */ +char *_old_name_array = NULL; + +/** + * Copy and convert old custom names to UTF-8. + * They were all stored in a 512 by 32 long string array and are + * now stored with stations, waypoints and other places with names. + * @param id the StringID of the custom name to clone. + * @return the clones custom name. + */ +char *CopyFromOldName(StringID id) +{ + /* Is this name an (old) custom name? */ + if (GB(id, 11, 5) != 15) return NULL; + + if (CheckSavegameVersion(37)) { + /* Old names were 32 characters long, so 128 characters should be + * plenty to allow for expansion when converted to UTF-8. */ + char tmp[128]; + const char *strfrom = &_old_name_array[32 * GB(id, 0, 9)]; + char *strto = tmp; + + for (; *strfrom != '\0'; strfrom++) { + WChar c = (byte)*strfrom; + + /* Map from non-ISO8859-15 characters to UTF-8. */ + switch (c) { + case 0xA4: c = 0x20AC; break; // Euro + case 0xA6: c = 0x0160; break; // S with caron + case 0xA8: c = 0x0161; break; // s with caron + case 0xB4: c = 0x017D; break; // Z with caron + case 0xB8: c = 0x017E; break; // z with caron + case 0xBC: c = 0x0152; break; // OE ligature + case 0xBD: c = 0x0153; break; // oe ligature + case 0xBE: c = 0x0178; break; // Y with diaresis + default: break; + } + + /* Check character will fit into our buffer. */ + if (strto + Utf8CharLen(c) > lastof(tmp)) break; + + strto += Utf8Encode(strto, c); + } + + /* Terminate the new string and copy it back to the name array */ + *strto = '\0'; + + return strdup(tmp); + } else { + /* Name will already be in UTF-8. */ + return strdup(&_old_name_array[32 * GB(id, 0, 9)]); + } +} + +/** + * Free the memory of the old names array. + * Should be called once the old names have all been converted. + */ +void ResetOldNames() +{ + free(_old_name_array); + _old_name_array = NULL; +} + +/** + * Initialize the old names table memory. + */ +void InitializeOldNames() +{ + free(_old_name_array); + _old_name_array = CallocT<char>(512 * 32); +} + +static void Load_NAME() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + SlArray(&_old_name_array[32 * index], SlGetFieldLength(), SLE_UINT8); + } +} + +extern const ChunkHandler _name_chunk_handlers[] = { + { 'NAME', NULL, Load_NAME, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/subsidy_sl.cpp b/src/saveload/subsidy_sl.cpp new file mode 100644 index 000000000..95fac25c7 --- /dev/null +++ b/src/saveload/subsidy_sl.cpp @@ -0,0 +1,43 @@ +/* $Id$ */ + +/** @file subsidy_sl.cpp Code handling saving and loading of subsidies */ + +#include "../stdafx.h" +#include "../economy_func.h" + +#include "saveload.h" + +static const SaveLoad _subsidies_desc[] = { + SLE_VAR(Subsidy, cargo_type, SLE_UINT8), + SLE_VAR(Subsidy, age, SLE_UINT8), + SLE_CONDVAR(Subsidy, from, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVAR(Subsidy, from, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_CONDVAR(Subsidy, to, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVAR(Subsidy, to, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_END() +}; + +void Save_SUBS() +{ + int i; + Subsidy *s; + + for (i = 0; i != lengthof(_subsidies); i++) { + s = &_subsidies[i]; + if (s->cargo_type != CT_INVALID) { + SlSetArrayIndex(i); + SlObject(s, _subsidies_desc); + } + } +} + +void Load_SUBS() +{ + int index; + while ((index = SlIterateArray()) != -1) + SlObject(&_subsidies[index], _subsidies_desc); +} + +extern const ChunkHandler _subsidy_chunk_handlers[] = { + { 'SUBS', Save_SUBS, Load_SUBS, CH_ARRAY}, +}; diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp new file mode 100644 index 000000000..eaa3edaa3 --- /dev/null +++ b/src/saveload/town_sl.cpp @@ -0,0 +1,210 @@ +/* $Id$ */ + +/** @file town_sl.cpp Code handling saving and loading of towns and houses */ + +#include "../stdafx.h" +#include "../town.h" +#include "../newgrf_house.h" +#include "../newgrf_commons.h" +#include "../variables.h" +#include "../tile_map.h" +#include "../town_map.h" + +#include "saveload.h" + +extern uint _total_towns; + +/** + * Check and update town and house values. + * + * Checked are the HouseIDs. Updated are the + * town population the number of houses per + * town, the town radius and the max passengers + * of the town. + */ +void UpdateHousesAndTowns() +{ + Town *town; + InitializeBuildingCounts(); + + /* Reset town population and num_houses */ + FOR_ALL_TOWNS(town) { + town->population = 0; + town->num_houses = 0; + } + + for (TileIndex t = 0; t < MapSize(); t++) { + HouseID house_id; + + if (!IsTileType(t, MP_HOUSE)) continue; + + house_id = GetHouseType(t); + if (!GetHouseSpecs(house_id)->enabled && house_id >= NEW_HOUSE_OFFSET) { + /* The specs for this type of house are not available any more, so + * replace it with the substitute original house type. */ + house_id = _house_mngr.GetSubstituteID(house_id); + SetHouseType(t, house_id); + } + + town = GetTownByTile(t); + IncreaseBuildingCount(town, house_id); + if (IsHouseCompleted(t)) town->population += GetHouseSpecs(house_id)->population; + + /* Increase the number of houses for every house, but only once. */ + if (GetHouseNorthPart(house_id) == 0) town->num_houses++; + } + + /* Update the population and num_house dependant values */ + FOR_ALL_TOWNS(town) { + UpdateTownRadius(town); + UpdateTownMaxPass(town); + } +} + +/** Save and load of towns. */ +static const SaveLoad _town_desc[] = { + SLE_CONDVAR(Town, xy, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Town, xy, SLE_UINT32, 6, SL_MAX_VERSION), + + SLE_CONDNULL(2, 0, 2), ///< population, no longer in use + SLE_CONDNULL(4, 3, 84), ///< population, no longer in use + SLE_CONDNULL(2, 0, 91), ///< num_houses, no longer in use + + SLE_CONDVAR(Town, townnamegrfid, SLE_UINT32, 66, SL_MAX_VERSION), + SLE_VAR(Town, townnametype, SLE_UINT16), + SLE_VAR(Town, townnameparts, SLE_UINT32), + SLE_CONDSTR(Town, name, SLE_STR, 0, 84, SL_MAX_VERSION), + + SLE_VAR(Town, flags12, SLE_UINT8), + SLE_CONDVAR(Town, statues, SLE_FILE_U8 | SLE_VAR_U16, 0, 103), + SLE_CONDVAR(Town, statues, SLE_UINT16, 104, SL_MAX_VERSION), + + SLE_CONDNULL(1, 0, 1), ///< sort_index, no longer in use + + SLE_CONDVAR(Town, have_ratings, SLE_FILE_U8 | SLE_VAR_U16, 0, 103), + SLE_CONDVAR(Town, have_ratings, SLE_UINT16, 104, SL_MAX_VERSION), + SLE_CONDARR(Town, ratings, SLE_INT16, 8, 0, 103), + SLE_CONDARR(Town, ratings, SLE_INT16, MAX_COMPANIES, 104, SL_MAX_VERSION), + /* failed bribe attempts are stored since savegame format 4 */ + SLE_CONDARR(Town, unwanted, SLE_INT8, 8, 4, 103), + SLE_CONDARR(Town, unwanted, SLE_INT8, MAX_COMPANIES, 104, SL_MAX_VERSION), + + SLE_CONDVAR(Town, max_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, max_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, new_max_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, new_max_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, act_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, act_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, new_act_pass, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + SLE_CONDVAR(Town, new_act_mail, SLE_FILE_U16 | SLE_VAR_U32, 0, 8), + + SLE_CONDVAR(Town, max_pass, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, max_mail, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, new_max_pass, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, new_max_mail, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, act_pass, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, act_mail, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, new_act_pass, SLE_UINT32, 9, SL_MAX_VERSION), + SLE_CONDVAR(Town, new_act_mail, SLE_UINT32, 9, SL_MAX_VERSION), + + SLE_VAR(Town, pct_pass_transported, SLE_UINT8), + SLE_VAR(Town, pct_mail_transported, SLE_UINT8), + + SLE_VAR(Town, act_food, SLE_UINT16), + SLE_VAR(Town, act_water, SLE_UINT16), + SLE_VAR(Town, new_act_food, SLE_UINT16), + SLE_VAR(Town, new_act_water, SLE_UINT16), + + SLE_CONDVAR(Town, time_until_rebuild, SLE_UINT8, 0, 53), + SLE_CONDVAR(Town, grow_counter, SLE_UINT8, 0, 53), + SLE_CONDVAR(Town, growth_rate, SLE_UINT8, 0, 53), + + SLE_CONDVAR(Town, time_until_rebuild, SLE_UINT16, 54, SL_MAX_VERSION), + SLE_CONDVAR(Town, grow_counter, SLE_UINT16, 54, SL_MAX_VERSION), + SLE_CONDVAR(Town, growth_rate, SLE_INT16, 54, SL_MAX_VERSION), + + SLE_VAR(Town, fund_buildings_months, SLE_UINT8), + SLE_VAR(Town, road_build_months, SLE_UINT8), + + SLE_CONDVAR(Town, exclusivity, SLE_UINT8, 2, SL_MAX_VERSION), + SLE_CONDVAR(Town, exclusive_counter, SLE_UINT8, 2, SL_MAX_VERSION), + + SLE_CONDVAR(Town, larger_town, SLE_BOOL, 56, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 30 bytes) */ + SLE_CONDNULL(30, 2, SL_MAX_VERSION), + + SLE_END() +}; + +/* Save and load the mapping between the house id on the map, and the grf file + * it came from. */ +static const SaveLoad _house_id_mapping_desc[] = { + SLE_VAR(EntityIDMapping, grfid, SLE_UINT32), + SLE_VAR(EntityIDMapping, entity_id, SLE_UINT8), + SLE_VAR(EntityIDMapping, substitute_id, SLE_UINT8), + SLE_END() +}; + +static void Save_HOUSEIDS() +{ + uint j = _house_mngr.GetMaxMapping(); + + for (uint i = 0; i < j; i++) { + SlSetArrayIndex(i); + SlObject(&_house_mngr.mapping_ID[i], _house_id_mapping_desc); + } +} + +static void Load_HOUSEIDS() +{ + int index; + + _house_mngr.ResetMapping(); + uint max_id = _house_mngr.GetMaxMapping(); + + while ((index = SlIterateArray()) != -1) { + if ((uint)index >= max_id) break; + SlObject(&_house_mngr.mapping_ID[index], _house_id_mapping_desc); + } +} + +static void Save_TOWN() +{ + Town *t; + + FOR_ALL_TOWNS(t) { + SlSetArrayIndex(t->index); + SlObject(t, _town_desc); + } +} + +static void Load_TOWN() +{ + int index; + + _total_towns = 0; + + while ((index = SlIterateArray()) != -1) { + Town *t = new (index) Town(); + SlObject(t, _town_desc); + + _total_towns++; + } + + /* This is to ensure all pointers are within the limits of + * the size of the TownPool */ + if (_cur_town_ctr > GetMaxTownIndex()) + _cur_town_ctr = 0; +} + +void AfterLoadTown() +{ + Town *t; + FOR_ALL_TOWNS(t) t->InitializeLayout(); +} + +extern const ChunkHandler _town_chunk_handlers[] = { + { 'HIDS', Save_HOUSEIDS, Load_HOUSEIDS, CH_ARRAY }, + { 'CITY', Save_TOWN, Load_TOWN, CH_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp new file mode 100644 index 000000000..33f043b45 --- /dev/null +++ b/src/saveload/vehicle_sl.cpp @@ -0,0 +1,670 @@ +/* $Id$ */ + +/** @file vehicle_sl.cpp Code handling saving and loading of vehicles */ + +#include "../stdafx.h" +#include "../vehicle_base.h" +#include "../vehicle_func.h" +#include "../train.h" +#include "../roadveh.h" +#include "../ship.h" +#include "../aircraft.h" +#include "../effectvehicle_base.h" + +#include "saveload.h" + +#include <map> + +/* + * Link front and rear multiheaded engines to each other + * This is done when loading a savegame + */ +void ConnectMultiheadedTrains() +{ + Vehicle *v; + + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN) { + v->u.rail.other_multiheaded_part = NULL; + } + } + + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) { + /* Two ways to associate multiheaded parts to each other: + * sequential-matching: Trains shall be arranged to look like <..>..<..>..<..>.. + * bracket-matching: Free vehicle chains shall be arranged to look like ..<..<..>..<..>..>.. + * + * Note: Old savegames might contain chains which do not comply with these rules, e.g. + * - the front and read parts have invalid orders + * - different engine types might be combined + * - there might be different amounts of front and rear parts. + * + * Note: The multiheaded parts need to be matched exactly like they are matched on the server, else desyncs will occur. + * This is why two matching strategies are needed. + */ + + bool sequential_matching = IsFrontEngine(v); + + for (Vehicle *u = v; u != NULL; u = GetNextVehicle(u)) { + if (u->u.rail.other_multiheaded_part != NULL) continue; // we already linked this one + + if (IsMultiheaded(u)) { + if (!IsTrainEngine(u)) { + /* we got a rear car without a front car. We will convert it to a front one */ + SetTrainEngine(u); + u->spritenum--; + } + + /* Find a matching back part */ + EngineID eid = u->engine_type; + Vehicle *w; + if (sequential_matching) { + for (w = GetNextVehicle(u); w != NULL; w = GetNextVehicle(w)) { + if (w->engine_type != eid || w->u.rail.other_multiheaded_part != NULL || !IsMultiheaded(w)) continue; + + /* we found a car to partner with this engine. Now we will make sure it face the right way */ + if (IsTrainEngine(w)) { + ClearTrainEngine(w); + w->spritenum++; + } + break; + } + } else { + uint stack_pos = 0; + for (w = GetNextVehicle(u); w != NULL; w = GetNextVehicle(w)) { + if (w->engine_type != eid || w->u.rail.other_multiheaded_part != NULL || !IsMultiheaded(w)) continue; + + if (IsTrainEngine(w)) { + stack_pos++; + } else { + if (stack_pos == 0) break; + stack_pos--; + } + } + } + + if (w != NULL) { + w->u.rail.other_multiheaded_part = u; + u->u.rail.other_multiheaded_part = w; + } else { + /* we got a front car and no rear cars. We will fake this one for forget that it should have been multiheaded */ + ClearMultiheaded(u); + } + } + } + } + } +} + +/** + * Converts all trains to the new subtype format introduced in savegame 16.2 + * It also links multiheaded engines or make them forget they are multiheaded if no suitable partner is found + */ +void ConvertOldMultiheadToNew() +{ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN) { + SetBit(v->subtype, 7); // indicates that it's the old format and needs to be converted in the next loop + } + } + + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN) { + if (HasBit(v->subtype, 7) && ((v->subtype & ~0x80) == 0 || (v->subtype & ~0x80) == 4)) { + for (Vehicle *u = v; u != NULL; u = u->Next()) { + const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); + + ClrBit(u->subtype, 7); + switch (u->subtype) { + case 0: /* TS_Front_Engine */ + if (rvi->railveh_type == RAILVEH_MULTIHEAD) SetMultiheaded(u); + SetFrontEngine(u); + SetTrainEngine(u); + break; + + case 1: /* TS_Artic_Part */ + u->subtype = 0; + SetArticulatedPart(u); + break; + + case 2: /* TS_Not_First */ + u->subtype = 0; + if (rvi->railveh_type == RAILVEH_WAGON) { + // normal wagon + SetTrainWagon(u); + break; + } + if (rvi->railveh_type == RAILVEH_MULTIHEAD && rvi->image_index == u->spritenum - 1) { + // rear end of a multiheaded engine + SetMultiheaded(u); + break; + } + if (rvi->railveh_type == RAILVEH_MULTIHEAD) SetMultiheaded(u); + SetTrainEngine(u); + break; + + case 4: /* TS_Free_Car */ + u->subtype = 0; + SetTrainWagon(u); + SetFreeWagon(u); + break; + default: NOT_REACHED(); break; + } + } + } + } + } +} + + +/** need to be called to load aircraft from old version */ +void UpdateOldAircraft() +{ + /* set airport_flags to 0 for all airports just to be sure */ + Station *st; + FOR_ALL_STATIONS(st) { + st->airport_flags = 0; // reset airport + } + + Vehicle *v_oldstyle; + FOR_ALL_VEHICLES(v_oldstyle) { + /* airplane has another vehicle with subtype 4 (shadow), helicopter also has 3 (rotor) + * skip those */ + if (v_oldstyle->type == VEH_AIRCRAFT && IsNormalAircraft(v_oldstyle)) { + /* airplane in terminal stopped doesn't hurt anyone, so goto next */ + if (v_oldstyle->vehstatus & VS_STOPPED && v_oldstyle->u.air.state == 0) { + v_oldstyle->u.air.state = HANGAR; + continue; + } + + AircraftLeaveHangar(v_oldstyle); // make airplane visible if it was in a depot for example + v_oldstyle->vehstatus &= ~VS_STOPPED; // make airplane moving + v_oldstyle->u.air.state = FLYING; + AircraftNextAirportPos_and_Order(v_oldstyle); // move it to the entry point of the airport + GetNewVehiclePosResult gp = GetNewVehiclePos(v_oldstyle); + v_oldstyle->tile = 0; // aircraft in air is tile=0 + + /* correct speed of helicopter-rotors */ + if (v_oldstyle->subtype == AIR_HELICOPTER) v_oldstyle->Next()->Next()->cur_speed = 32; + + /* set new position x,y,z */ + SetAircraftPosition(v_oldstyle, gp.x, gp.y, GetAircraftFlyingAltitude(v_oldstyle)); + } + } +} + +/** Called after load to update coordinates */ +void AfterLoadVehicles(bool part_of_load) +{ + Vehicle *v; + + FOR_ALL_VEHICLES(v) { + /* Reinstate the previous pointer */ + if (v->Next() != NULL) v->Next()->previous = v; + if (v->NextShared() != NULL) v->NextShared()->previous_shared = v; + + v->UpdateDeltaXY(v->direction); + + if (part_of_load) v->fill_percent_te_id = INVALID_TE_ID; + v->first = NULL; + if (v->type == VEH_TRAIN) v->u.rail.first_engine = INVALID_ENGINE; + if (v->type == VEH_ROAD) v->u.road.first_engine = INVALID_ENGINE; + + v->cargo.InvalidateCache(); + } + + /* AfterLoadVehicles may also be called in case of NewGRF reload, in this + * case we may not convert orders again. */ + if (part_of_load) { + /* Create shared vehicle chain for very old games (pre 5,2) and create + * OrderList from shared vehicle chains. For this to work correctly, the + * following conditions must be fulfilled: + * a) both next_shared and previous_shared are not set for pre 5,2 games + * b) both next_shared and previous_shared are set for later games + */ + std::map<Order*, OrderList*> mapping; + + FOR_ALL_VEHICLES(v) { + if (v->orders.old != NULL) { + if (CheckSavegameVersion(105)) { // Pre-105 didn't save an OrderList + if (mapping[v->orders.old] == NULL) { + /* This adds the whole shared vehicle chain for case b */ + v->orders.list = mapping[v->orders.old] = new OrderList(v->orders.old, v); + } else { + v->orders.list = mapping[v->orders.old]; + /* For old games (case a) we must create the shared vehicle chain */ + if (CheckSavegameVersionOldStyle(5, 2)) { + v->AddToShared(v->orders.list->GetFirstSharedVehicle()); + } + } + } else { // OrderList was saved as such, only recalculate not saved values + if (v->PreviousShared() == NULL) { + new (v->orders.list) OrderList(v->orders.list->GetFirstOrder(), v); + } + } + } + } + } + + FOR_ALL_VEHICLES(v) { + /* Fill the first pointers */ + if (v->Previous() == NULL) { + for (Vehicle *u = v; u != NULL; u = u->Next()) { + u->first = v; + } + } + } + + FOR_ALL_VEHICLES(v) { + assert(v->first != NULL); + + if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) { + if (IsFrontEngine(v)) v->u.rail.last_speed = v->cur_speed; // update displayed train speed + TrainConsistChanged(v, false); + } else if (v->type == VEH_ROAD && IsRoadVehFront(v)) { + RoadVehUpdateCache(v); + } + } + + /* Stop non-front engines */ + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_TRAIN && IsTrainEngine(v) && !IsFrontEngine(v)) v->vehstatus |= VS_STOPPED; + } + + FOR_ALL_VEHICLES(v) { + switch (v->type) { + case VEH_ROAD: + v->u.road.roadtype = HasBit(EngInfo(v->engine_type)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD; + v->u.road.compatible_roadtypes = RoadTypeToRoadTypes(v->u.road.roadtype); + /* FALL THROUGH */ + case VEH_TRAIN: + case VEH_SHIP: + v->cur_image = v->GetImage(v->direction); + break; + + case VEH_AIRCRAFT: + if (IsNormalAircraft(v)) { + v->cur_image = v->GetImage(v->direction); + + /* The plane's shadow will have the same image as the plane */ + Vehicle *shadow = v->Next(); + shadow->cur_image = v->cur_image; + + /* In the case of a helicopter we will update the rotor sprites */ + if (v->subtype == AIR_HELICOPTER) { + Vehicle *rotor = shadow->Next(); + rotor->cur_image = GetRotorImage(v); + } + + UpdateAircraftCache(v); + } + break; + default: break; + } + + v->left_coord = INVALID_COORD; + VehiclePositionChanged(v); + } +} + +static uint8 _cargo_days; +static uint16 _cargo_source; +static uint32 _cargo_source_xy; +static uint16 _cargo_count; +static uint16 _cargo_paid_for; +static Money _cargo_feeder_share; +static uint32 _cargo_loaded_at_xy; + +/** + * Make it possible to make the saveload tables "friends" of other classes. + * @param vt the vehicle type. Can be VEH_END for the common vehicle description data + * @return the saveload description + */ +const SaveLoad *GetVehicleDescription(VehicleType vt) +{ + /** Save and load of vehicles */ + static const SaveLoad _common_veh_desc[] = { + SLE_VAR(Vehicle, subtype, SLE_UINT8), + + SLE_REF(Vehicle, next, REF_VEHICLE_OLD), + SLE_CONDVAR(Vehicle, name, SLE_NAME, 0, 83), + SLE_CONDSTR(Vehicle, name, SLE_STR, 0, 84, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, unitnumber, SLE_FILE_U8 | SLE_VAR_U16, 0, 7), + SLE_CONDVAR(Vehicle, unitnumber, SLE_UINT16, 8, SL_MAX_VERSION), + SLE_VAR(Vehicle, owner, SLE_UINT8), + SLE_CONDVAR(Vehicle, tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, dest_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, dest_tile, SLE_UINT32, 6, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, x_pos, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, x_pos, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, y_pos, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, y_pos, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_VAR(Vehicle, z_pos, SLE_UINT8), + SLE_VAR(Vehicle, direction, SLE_UINT8), + + SLE_CONDNULL(2, 0, 57), + SLE_VAR(Vehicle, spritenum, SLE_UINT8), + SLE_CONDNULL(5, 0, 57), + SLE_VAR(Vehicle, engine_type, SLE_UINT16), + + SLE_VAR(Vehicle, max_speed, SLE_UINT16), + SLE_VAR(Vehicle, cur_speed, SLE_UINT16), + SLE_VAR(Vehicle, subspeed, SLE_UINT8), + SLE_VAR(Vehicle, acceleration, SLE_UINT8), + SLE_VAR(Vehicle, progress, SLE_UINT8), + + SLE_VAR(Vehicle, vehstatus, SLE_UINT8), + SLE_CONDVAR(Vehicle, last_station_visited, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVAR(Vehicle, last_station_visited, SLE_UINT16, 5, SL_MAX_VERSION), + + SLE_VAR(Vehicle, cargo_type, SLE_UINT8), + SLE_CONDVAR(Vehicle, cargo_subtype, SLE_UINT8, 35, SL_MAX_VERSION), + SLEG_CONDVAR( _cargo_days, SLE_UINT8, 0, 67), + SLEG_CONDVAR( _cargo_source, SLE_FILE_U8 | SLE_VAR_U16, 0, 6), + SLEG_CONDVAR( _cargo_source, SLE_UINT16, 7, 67), + SLEG_CONDVAR( _cargo_source_xy, SLE_UINT32, 44, 67), + SLE_VAR(Vehicle, cargo_cap, SLE_UINT16), + SLEG_CONDVAR( _cargo_count, SLE_UINT16, 0, 67), + SLE_CONDLST(Vehicle, cargo, REF_CARGO_PACKET, 68, SL_MAX_VERSION), + + SLE_VAR(Vehicle, day_counter, SLE_UINT8), + SLE_VAR(Vehicle, tick_counter, SLE_UINT8), + SLE_CONDVAR(Vehicle, running_ticks, SLE_UINT8, 88, SL_MAX_VERSION), + + SLE_VAR(Vehicle, cur_order_index, SLE_UINT8), + /* num_orders is now part of OrderList and is not saved but counted */ + SLE_CONDNULL(1, 0, 104), + + /* This next line is for version 4 and prior compatibility.. it temporarily reads + type and flags (which were both 4 bits) into type. Later on this is + converted correctly */ + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, type), SLE_UINT8, 0, 4), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, dest), SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + + /* Orders for version 5 and on */ + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, type), SLE_UINT8, 5, SL_MAX_VERSION), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, flags), SLE_UINT8, 5, SL_MAX_VERSION), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, dest), SLE_UINT16, 5, SL_MAX_VERSION), + + /* Refit in current order */ + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, refit_cargo), SLE_UINT8, 36, SL_MAX_VERSION), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, refit_subtype), SLE_UINT8, 36, SL_MAX_VERSION), + + /* Timetable in current order */ + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, wait_time), SLE_UINT16, 67, SL_MAX_VERSION), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, travel_time), SLE_UINT16, 67, SL_MAX_VERSION), + + SLE_CONDREF(Vehicle, orders, REF_ORDER, 0, 104), + SLE_CONDREF(Vehicle, orders, REF_ORDERLIST, 105, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, age, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Vehicle, age, SLE_INT32, 31, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, max_age, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Vehicle, max_age, SLE_INT32, 31, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, date_of_last_service, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Vehicle, date_of_last_service, SLE_INT32, 31, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, service_interval, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Vehicle, service_interval, SLE_INT32, 31, SL_MAX_VERSION), + SLE_VAR(Vehicle, reliability, SLE_UINT16), + SLE_VAR(Vehicle, reliability_spd_dec, SLE_UINT16), + SLE_VAR(Vehicle, breakdown_ctr, SLE_UINT8), + SLE_VAR(Vehicle, breakdown_delay, SLE_UINT8), + SLE_VAR(Vehicle, breakdowns_since_last_service, SLE_UINT8), + SLE_VAR(Vehicle, breakdown_chance, SLE_UINT8), + SLE_CONDVAR(Vehicle, build_year, SLE_FILE_U8 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Vehicle, build_year, SLE_INT32, 31, SL_MAX_VERSION), + + SLE_VAR(Vehicle, load_unload_time_rem, SLE_UINT16), + SLEG_CONDVAR( _cargo_paid_for, SLE_UINT16, 45, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, vehicle_flags, SLE_UINT8, 40, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, profit_this_year, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), + SLE_CONDVAR(Vehicle, profit_this_year, SLE_INT64, 65, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, profit_last_year, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), + SLE_CONDVAR(Vehicle, profit_last_year, SLE_INT64, 65, SL_MAX_VERSION), + SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_I32 | SLE_VAR_I64, 51, 64), + SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, 65, 67), + SLEG_CONDVAR( _cargo_loaded_at_xy, SLE_UINT32, 51, 67), + SLE_CONDVAR(Vehicle, value, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), + SLE_CONDVAR(Vehicle, value, SLE_INT64, 65, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, random_bits, SLE_UINT8, 2, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, waiting_triggers, SLE_UINT8, 2, SL_MAX_VERSION), + + SLE_CONDREF(Vehicle, next_shared, REF_VEHICLE, 2, SL_MAX_VERSION), + SLE_CONDNULL(2, 2, 68), + SLE_CONDNULL(4, 69, 100), + + SLE_CONDVAR(Vehicle, group_id, SLE_UINT16, 60, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, current_order_time, SLE_UINT32, 67, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, lateness_counter, SLE_INT32, 67, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 10 bytes) */ + SLE_CONDNULL(10, 2, SL_MAX_VERSION), + + SLE_END() + }; + + + static const SaveLoad _train_desc[] = { + SLE_WRITEBYTE(Vehicle, type, VEH_TRAIN), + SLE_VEH_INCLUDEX(), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, crash_anim_pos), SLE_UINT16), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, force_proceed), SLE_UINT8), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, railtype), SLE_UINT8), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, track), SLE_UINT8), + + SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, flags), SLE_FILE_U8 | SLE_VAR_U16, 2, 99), + SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRail, flags), SLE_UINT16, 100, SL_MAX_VERSION), + SLE_CONDNULL(2, 2, 59), + + SLE_CONDNULL(2, 2, 19), + /* reserve extra space in savegame here. (currently 11 bytes) */ + SLE_CONDNULL(11, 2, SL_MAX_VERSION), + + SLE_END() + }; + + static const SaveLoad _roadveh_desc[] = { + SLE_WRITEBYTE(Vehicle, type, VEH_ROAD), + SLE_VEH_INCLUDEX(), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, state), SLE_UINT8), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, frame), SLE_UINT8), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, blocked_ctr), SLE_UINT16), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, overtaking), SLE_UINT8), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, overtaking_ctr), SLE_UINT8), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, crashed_ctr), SLE_UINT16), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, reverse_ctr), SLE_UINT8), + + SLE_CONDREFX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, slot), REF_ROADSTOPS, 6, SL_MAX_VERSION), + SLE_CONDNULL(1, 6, SL_MAX_VERSION), + SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleRoad, slot_age), SLE_UINT8, 6, SL_MAX_VERSION), + /* reserve extra space in savegame here. (currently 16 bytes) */ + SLE_CONDNULL(16, 2, SL_MAX_VERSION), + + SLE_END() + }; + + static const SaveLoad _ship_desc[] = { + SLE_WRITEBYTE(Vehicle, type, VEH_SHIP), + SLE_VEH_INCLUDEX(), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleShip, state), SLE_UINT8), + + /* reserve extra space in savegame here. (currently 16 bytes) */ + SLE_CONDNULL(16, 2, SL_MAX_VERSION), + + SLE_END() + }; + + static const SaveLoad _aircraft_desc[] = { + SLE_WRITEBYTE(Vehicle, type, VEH_AIRCRAFT), + SLE_VEH_INCLUDEX(), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleAir, crashed_counter), SLE_UINT16), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleAir, pos), SLE_UINT8), + + SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleAir, targetairport), SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleAir, targetairport), SLE_UINT16, 5, SL_MAX_VERSION), + + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleAir, state), SLE_UINT8), + + SLE_CONDVARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleAir, previous_pos), SLE_UINT8, 2, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 15 bytes) */ + SLE_CONDNULL(15, 2, SL_MAX_VERSION), + + SLE_END() + }; + + static const SaveLoad _special_desc[] = { + SLE_WRITEBYTE(Vehicle, type, VEH_EFFECT), + + SLE_VAR(Vehicle, subtype, SLE_UINT8), + + SLE_CONDVAR(Vehicle, tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, tile, SLE_UINT32, 6, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, x_pos, SLE_FILE_I16 | SLE_VAR_I32, 0, 5), + SLE_CONDVAR(Vehicle, x_pos, SLE_INT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, y_pos, SLE_FILE_I16 | SLE_VAR_I32, 0, 5), + SLE_CONDVAR(Vehicle, y_pos, SLE_INT32, 6, SL_MAX_VERSION), + SLE_VAR(Vehicle, z_pos, SLE_UINT8), + + SLE_VAR(Vehicle, cur_image, SLE_UINT16), + SLE_CONDNULL(5, 0, 57), + SLE_VAR(Vehicle, progress, SLE_UINT8), + SLE_VAR(Vehicle, vehstatus, SLE_UINT8), + + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleEffect, animation_state), SLE_UINT16), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleEffect, animation_substate), SLE_UINT8), + + SLE_CONDVAR(Vehicle, spritenum, SLE_UINT8, 2, SL_MAX_VERSION), + + /* reserve extra space in savegame here. (currently 15 bytes) */ + SLE_CONDNULL(15, 2, SL_MAX_VERSION), + + SLE_END() + }; + + static const SaveLoad _disaster_desc[] = { + SLE_WRITEBYTE(Vehicle, type, VEH_DISASTER), + + SLE_REF(Vehicle, next, REF_VEHICLE_OLD), + + SLE_VAR(Vehicle, subtype, SLE_UINT8), + SLE_CONDVAR(Vehicle, tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, tile, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, dest_tile, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Vehicle, dest_tile, SLE_UINT32, 6, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, x_pos, SLE_FILE_I16 | SLE_VAR_I32, 0, 5), + SLE_CONDVAR(Vehicle, x_pos, SLE_INT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, y_pos, SLE_FILE_I16 | SLE_VAR_I32, 0, 5), + SLE_CONDVAR(Vehicle, y_pos, SLE_INT32, 6, SL_MAX_VERSION), + SLE_VAR(Vehicle, z_pos, SLE_UINT8), + SLE_VAR(Vehicle, direction, SLE_UINT8), + + SLE_CONDNULL(5, 0, 57), + SLE_VAR(Vehicle, owner, SLE_UINT8), + SLE_VAR(Vehicle, vehstatus, SLE_UINT8), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, dest), SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVARX(cpp_offsetof(Vehicle, current_order) + cpp_offsetof(Order, dest), SLE_UINT16, 5, SL_MAX_VERSION), + + SLE_VAR(Vehicle, cur_image, SLE_UINT16), + SLE_CONDVAR(Vehicle, age, SLE_FILE_U16 | SLE_VAR_I32, 0, 30), + SLE_CONDVAR(Vehicle, age, SLE_INT32, 31, SL_MAX_VERSION), + SLE_VAR(Vehicle, tick_counter, SLE_UINT8), + + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleDisaster, image_override), SLE_UINT16), + SLE_VARX(cpp_offsetof(Vehicle, u) + cpp_offsetof(VehicleDisaster, big_ufo_destroyer_target), SLE_UINT16), + + /* reserve extra space in savegame here. (currently 16 bytes) */ + SLE_CONDNULL(16, 2, SL_MAX_VERSION), + + SLE_END() + }; + + + static const SaveLoad *_veh_descs[] = { + _train_desc, + _roadveh_desc, + _ship_desc, + _aircraft_desc, + _special_desc, + _disaster_desc, + _common_veh_desc, + }; + + return _veh_descs[vt]; +} + +/** Will be called when the vehicles need to be saved. */ +static void Save_VEHS() +{ + Vehicle *v; + /* Write the vehicles */ + FOR_ALL_VEHICLES(v) { + SlSetArrayIndex(v->index); + SlObject(v, GetVehicleDescription(v->type)); + } +} + +/** Will be called when vehicles need to be loaded. */ +void Load_VEHS() +{ + int index; + + _cargo_count = 0; + + while ((index = SlIterateArray()) != -1) { + Vehicle *v; + VehicleType vtype = (VehicleType)SlReadByte(); + + switch (vtype) { + case VEH_TRAIN: v = new (index) Train(); break; + case VEH_ROAD: v = new (index) RoadVehicle(); break; + case VEH_SHIP: v = new (index) Ship(); break; + case VEH_AIRCRAFT: v = new (index) Aircraft(); break; + case VEH_EFFECT: v = new (index) EffectVehicle(); break; + case VEH_DISASTER: v = new (index) DisasterVehicle(); break; + case VEH_INVALID: v = new (index) InvalidVehicle(); break; + default: NOT_REACHED(); + } + + SlObject(v, GetVehicleDescription(vtype)); + + if (_cargo_count != 0 && IsCompanyBuildableVehicleType(v)) { + /* Don't construct the packet with station here, because that'll fail with old savegames */ + CargoPacket *cp = new CargoPacket(); + cp->source = _cargo_source; + cp->source_xy = _cargo_source_xy; + cp->count = _cargo_count; + cp->days_in_transit = _cargo_days; + cp->feeder_share = _cargo_feeder_share; + cp->loaded_at_xy = _cargo_loaded_at_xy; + v->cargo.Append(cp); + } + + /* Old savegames used 'last_station_visited = 0xFF' */ + if (CheckSavegameVersion(5) && v->last_station_visited == 0xFF) + v->last_station_visited = INVALID_STATION; + + if (CheckSavegameVersion(5)) { + /* Convert the current_order.type (which is a mix of type and flags, because + * in those versions, they both were 4 bits big) to type and flags */ + v->current_order.flags = GB(v->current_order.type, 4, 4); + v->current_order.type &= 0x0F; + } + + /* Advanced vehicle lists got added */ + if (CheckSavegameVersion(60)) v->group_id = DEFAULT_GROUP; + } +} + +extern const ChunkHandler _veh_chunk_handlers[] = { + { 'VEHS', Save_VEHS, Load_VEHS, CH_SPARSE_ARRAY | CH_LAST}, +}; diff --git a/src/saveload/waypoint_sl.cpp b/src/saveload/waypoint_sl.cpp new file mode 100644 index 000000000..e8a8bf949 --- /dev/null +++ b/src/saveload/waypoint_sl.cpp @@ -0,0 +1,96 @@ +/* $Id$ */ + +/** @file waypoint_sl.cpp Code handling saving and loading of waypoints */ + +#include "../stdafx.h" +#include "../waypoint.h" +#include "../newgrf_station.h" +#include "../town.h" + +#include "table/strings.h" + +#include "saveload.h" + +/** + * Update waypoint graphics id against saved GRFID/localidx. + * This is to ensure the chosen graphics are correct if GRF files are changed. + */ +void AfterLoadWaypoints() +{ + Waypoint *wp; + + FOR_ALL_WAYPOINTS(wp) { + uint i; + + if (wp->grfid == 0) continue; + + for (i = 0; i < GetNumCustomStations(STAT_CLASS_WAYP); i++) { + const StationSpec *statspec = GetCustomStationSpec(STAT_CLASS_WAYP, i); + if (statspec != NULL && statspec->grffile->grfid == wp->grfid && statspec->localidx == wp->localidx) { + wp->stat_id = i; + break; + } + } + } +} + +/** + * Fix savegames which stored waypoints in their old format + */ +void FixOldWaypoints() +{ + Waypoint *wp; + + /* Convert the old 'town_or_string', to 'string' / 'town' / 'town_cn' */ + FOR_ALL_WAYPOINTS(wp) { + wp->town_index = ClosestTownFromTile(wp->xy, UINT_MAX)->index; + wp->town_cn = 0; + if (wp->string & 0xC000) { + wp->town_cn = wp->string & 0x3F; + wp->string = STR_NULL; + } + } +} + +static const SaveLoad _waypoint_desc[] = { + SLE_CONDVAR(Waypoint, xy, SLE_FILE_U16 | SLE_VAR_U32, 0, 5), + SLE_CONDVAR(Waypoint, xy, SLE_UINT32, 6, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, town_index, SLE_UINT16, 12, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, town_cn, SLE_FILE_U8 | SLE_VAR_U16, 12, 88), + SLE_CONDVAR(Waypoint, town_cn, SLE_UINT16, 89, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, string, SLE_STRINGID, 0, 83), + SLE_CONDSTR(Waypoint, name, SLE_STR, 0, 84, SL_MAX_VERSION), + SLE_VAR(Waypoint, deleted, SLE_UINT8), + + SLE_CONDVAR(Waypoint, build_date, SLE_FILE_U16 | SLE_VAR_I32, 3, 30), + SLE_CONDVAR(Waypoint, build_date, SLE_INT32, 31, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, localidx, SLE_UINT8, 3, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, grfid, SLE_UINT32, 17, SL_MAX_VERSION), + SLE_CONDVAR(Waypoint, owner, SLE_UINT8, 101, SL_MAX_VERSION), + + SLE_END() +}; + +static void Save_WAYP() +{ + Waypoint *wp; + + FOR_ALL_WAYPOINTS(wp) { + SlSetArrayIndex(wp->index); + SlObject(wp, _waypoint_desc); + } +} + +static void Load_WAYP() +{ + int index; + + while ((index = SlIterateArray()) != -1) { + Waypoint *wp = new (index) Waypoint(); + SlObject(wp, _waypoint_desc); + } +} + +extern const ChunkHandler _waypoint_chunk_handlers[] = { + { 'CHKP', Save_WAYP, Load_WAYP, CH_ARRAY | CH_LAST}, +}; |