(svn r15216) -Feature: native support for Transport Tycoon (Original) savegames. Based on SVXConverter's source code and documentation by Roman Vetter

This commit is contained in:
smatz 2009-01-23 02:35:17 +00:00
parent 83e1288c6a
commit 6dffd8ae82
14 changed files with 898 additions and 266 deletions

View File

@ -2075,6 +2075,10 @@
RelativePath=".\..\src\saveload\oldloader.h"
>
</File>
<File
RelativePath=".\..\src\saveload\oldloader_sl.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\order_sl.cpp"
>
@ -2111,10 +2115,6 @@
RelativePath=".\..\src\saveload\town_sl.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\ttdloader.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\vehicle_sl.cpp"
>

View File

@ -2072,6 +2072,10 @@
RelativePath=".\..\src\saveload\oldloader.h"
>
</File>
<File
RelativePath=".\..\src\saveload\oldloader_sl.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\order_sl.cpp"
>
@ -2108,10 +2112,6 @@
RelativePath=".\..\src\saveload\town_sl.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\ttdloader.cpp"
>
</File>
<File
RelativePath=".\..\src\saveload\vehicle_sl.cpp"
>

View File

@ -468,6 +468,7 @@ saveload/misc_sl.cpp
saveload/newgrf_sl.cpp
saveload/oldloader.cpp
saveload/oldloader.h
saveload/oldloader_sl.cpp
saveload/order_sl.cpp
saveload/saveload.cpp
saveload/saveload.h
@ -477,7 +478,6 @@ saveload/station_sl.cpp
saveload/strings_sl.cpp
saveload/subsidy_sl.cpp
saveload/town_sl.cpp
saveload/ttdloader.cpp
saveload/vehicle_sl.cpp
saveload/waypoint_sl.cpp

View File

@ -252,6 +252,49 @@ void SetYearEngineAgingStops()
}
}
void StartupOneEngine(Engine *e, Date aging_date)
{
const EngineInfo *ei = &e->info;
uint32 r;
e->age = 0;
e->flags = 0;
e->company_avail = 0;
/* The magic value of 729 days below comes from the NewGRF spec. If the
* base intro date is before 1922 then the random number of days is not
* added. */
r = Random();
e->intro_date = ei->base_intro <= ConvertYMDToDate(1922, 0, 1) ? ei->base_intro : (Date)GB(r, 0, 9) + ei->base_intro;
if (e->intro_date <= _date) {
e->age = (aging_date - e->intro_date) >> 5;
e->company_avail = (CompanyMask)-1;
e->flags |= ENGINE_AVAILABLE;
}
e->reliability_start = GB(r, 16, 14) + 0x7AE0;
r = Random();
e->reliability_max = GB(r, 0, 14) + 0xBFFF;
e->reliability_final = GB(r, 16, 14) + 0x3FFF;
r = Random();
e->duration_phase_1 = GB(r, 0, 5) + 7;
e->duration_phase_2 = GB(r, 5, 4) + ei->base_life * 12 - 96;
e->duration_phase_3 = GB(r, 9, 7) + 120;
e->reliability_spd_dec = ei->decay_speed << 2;
CalcEngineReliability(e);
e->lifelength = ei->lifelength + _settings_game.vehicle.extend_vehicle_life;
/* prevent certain engines from ever appearing. */
if (!HasBit(ei->climates, _settings_game.game_creation.landscape)) {
e->flags |= ENGINE_AVAILABLE;
e->company_avail = 0;
}
}
void StartupEngines()
{
Engine *e;
@ -259,45 +302,7 @@ void StartupEngines()
const Date aging_date = min(_date, ConvertYMDToDate(_year_engine_aging_stops, 0, 1));
FOR_ALL_ENGINES(e) {
const EngineInfo *ei = &e->info;
uint32 r;
e->age = 0;
e->flags = 0;
e->company_avail = 0;
/* The magic value of 729 days below comes from the NewGRF spec. If the
* base intro date is before 1922 then the random number of days is not
* added. */
r = Random();
e->intro_date = ei->base_intro <= ConvertYMDToDate(1922, 0, 1) ? ei->base_intro : (Date)GB(r, 0, 9) + ei->base_intro;
if (e->intro_date <= _date) {
e->age = (aging_date - e->intro_date) >> 5;
e->company_avail = (CompanyMask)-1;
e->flags |= ENGINE_AVAILABLE;
}
e->reliability_start = GB(r, 16, 14) + 0x7AE0;
r = Random();
e->reliability_max = GB(r, 0, 14) + 0xBFFF;
e->reliability_final = GB(r, 16, 14) + 0x3FFF;
r = Random();
e->duration_phase_1 = GB(r, 0, 5) + 7;
e->duration_phase_2 = GB(r, 5, 4) + ei->base_life * 12 - 96;
e->duration_phase_3 = GB(r, 9, 7) + 120;
e->reliability_spd_dec = ei->decay_speed << 2;
CalcEngineReliability(e);
e->lifelength = ei->lifelength + _settings_game.vehicle.extend_vehicle_life;
/* prevent certain engines from ever appearing. */
if (!HasBit(ei->climates, _settings_game.game_creation.landscape)) {
e->flags |= ENGINE_AVAILABLE;
e->company_avail = 0;
}
StartupOneEngine(e, aging_date);
}
/* Update the bitmasks for the vehicle lists */

View File

@ -27,5 +27,6 @@ bool IsEngineRefittable(EngineID engine);
CargoID GetEngineCargoType(EngineID engine);
void SetCachedEngineCounts();
void SetYearEngineAgingStops();
void StartupOneEngine(Engine *e, Date aging_date);
#endif /* ENGINE_H */

View File

@ -195,6 +195,10 @@ void GamelogPrint(GamelogPrintProc *proc)
GB(lc->oldver.version, 8, 16), GB(lc->oldver.version, 0, 8));
break;
case SGT_TTO:
AddDebugText(buf, "TTO savegame");
break;
case SGT_TTD:
AddDebugText(buf, "TTD savegame");
break;

View File

@ -327,7 +327,7 @@ static void FixOwnerOfRailTrack(TileIndex t)
if (IsLevelCrossingTile(t)) {
/* else change the crossing to normal road (road vehicles won't care) */
MakeRoadNormal(t, GetCrossingRoadBits(t), GetRoadTypes(t), GetTownIndex(t),
GetRoadOwner(t, ROADTYPE_ROAD), GetRoadOwner(t, ROADTYPE_TRAM), GetRoadOwner(t, ROADTYPE_HWAY));
GetRoadOwner(t, ROADTYPE_ROAD), GetRoadOwner(t, ROADTYPE_TRAM), GetRoadOwner(t, ROADTYPE_HWAY));
return;
}
@ -356,7 +356,6 @@ bool AfterLoadGame()
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);

View File

@ -17,12 +17,13 @@
#include "oldloader.h"
enum {
HEADER_SIZE = 49,
TTO_HEADER_SIZE = 41,
TTD_HEADER_SIZE = 49,
};
uint32 _bump_assert_value;
static inline OldChunkType GetOldChunkType(OldChunkType type) {return (OldChunkType)GB(type, 0, 8);}
static inline OldChunkType GetOldChunkType(OldChunkType type) {return (OldChunkType)GB(type, 0, 4);}
static inline OldChunkType GetOldChunkVarType(OldChunkType type) {return (OldChunkType)(GB(type, 8, 8) << 8);}
static inline OldChunkType GetOldChunkFileType(OldChunkType type) {return (OldChunkType)(GB(type, 16, 8) << 16);}
@ -101,12 +102,17 @@ byte ReadByte(LoadgameState *ls)
*/
bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks)
{
const OldChunks *chunk = chunks;
byte *base_ptr = (byte*)base;
while (chunk->type != OC_END) {
for (const OldChunks *chunk = chunks; chunk->type != OC_END; chunk++) {
if (((chunk->type & OC_TTD) && (_savegame_type == SGT_TTO)) ||
((chunk->type & OC_TTO) && (_savegame_type != SGT_TTO))) {
/* TTD(P)-only chunk, but TTO savegame || TTO-only chunk, but TTD/TTDP savegame */
continue;
}
byte *ptr = (byte*)chunk->ptr;
if ((chunk->type & OC_DEREFERENCE_POINTER) != 0) ptr = *(byte**)ptr;
if (chunk->type & OC_DEREFERENCE_POINTER) ptr = *(byte**)ptr;
for (uint i = 0; i < chunk->amount; i++) {
if (ls->failed) return false;
@ -164,8 +170,6 @@ bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks)
if (chunk->amount > 1 && chunk->ptr != NULL) ptr += CalcOldVarLen(chunk->type);
}
}
chunk++;
}
return true;
@ -194,30 +198,72 @@ static void InitLoading(LoadgameState *ls)
_settings_game.construction.freeform_edges = false; // disable so we can convert map array (SetTileType is still used)
}
/**
* Verifies the title has a valid checksum
* @param title title and checksum
* @return true iff the title is valid
* @note the title (incl. checksum) has to be at least 49 (HEADER_SIZE) bytes long!
* @note the title (incl. checksum) has to be at least 41/49 (HEADER_SIZE) bytes long!
*/
static bool VerifyOldNameChecksum(char *title)
static bool VerifyOldNameChecksum(char *title, uint len)
{
uint16 sum = 0;
for (uint i = 0; i < HEADER_SIZE - 2; i++) {
for (uint i = 0; i < len - 2; i++) {
sum += title[i];
sum = ROL(sum, 1);
}
sum ^= 0xAAAA; // computed checksum
uint16 sum2 = title[HEADER_SIZE - 2]; // checksum in file
SB(sum2, 8, 8, title[HEADER_SIZE - 1]);
uint16 sum2 = title[len - 2]; // checksum in file
SB(sum2, 8, 8, title[len - 1]);
return sum == sum2;
}
static inline bool CheckOldSavegameType(FILE *f, char *temp, const char *last, uint len)
{
assert(last - temp + 1 >= len);
fseek(f, 0, SEEK_SET);
if (fread(temp, 1, len, f) != len) {
temp[0] = '\0'; // if reading failed, make the name empty
return false;
}
bool ret = VerifyOldNameChecksum(temp, len);
temp[len - 2] = '\0'; // name is nul-terminated in savegame, but it's better to be sure
return ret;
}
assert_compile(TTD_HEADER_SIZE >= TTO_HEADER_SIZE);
static SavegameType DetermineOldSavegameType(FILE *f, char *title, const char *last)
{
char temp[TTD_HEADER_SIZE];
SavegameType type = SGT_TTO;
if (!CheckOldSavegameType(f, temp, lastof(temp), TTO_HEADER_SIZE)) {
type = SGT_TTD;
if (!CheckOldSavegameType(f, temp, lastof(temp), TTD_HEADER_SIZE)) {
type = SGT_INVALID;
}
}
if (title != NULL) {
switch (type) {
case SGT_TTO: title = strecpy(title, "(TTO) ", last); break;
case SGT_TTD: title = strecpy(title, "(TTD) ", last); break;
default: title = strecpy(title, "(broken) ", last); break;
}
title = strecpy(title, temp, last);
}
return type;
}
typedef bool LoadOldMainProc(LoadgameState *ls);
bool LoadOldSaveGame(const char *file)
{
LoadgameState ls;
@ -234,8 +280,19 @@ bool LoadOldSaveGame(const char *file)
return false;
}
char temp[HEADER_SIZE];
if (fread(temp, 1, HEADER_SIZE, ls.file) != HEADER_SIZE || !VerifyOldNameChecksum(temp) || !LoadOldMain(&ls)) {
SavegameType type = DetermineOldSavegameType(ls.file, NULL, NULL);
LoadOldMainProc *proc = NULL;
switch (type) {
case SGT_TTO: proc = &LoadTTOMain; break;
case SGT_TTD: proc = &LoadTTDMain; break;
default: break;
}
_savegame_type = type;
if (proc == NULL || !proc(&ls)) {
SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED);
fclose(ls.file);
return false;
@ -249,23 +306,16 @@ bool LoadOldSaveGame(const char *file)
void GetOldSaveGameName(const char *path, const char *file, char *title, const char *last)
{
char filename[MAX_PATH];
char temp[HEADER_SIZE];
snprintf(filename, lengthof(filename), "%s" PATHSEP "%s", path, file);
FILE *f = fopen(filename, "rb");
temp[0] = '\0'; // name is nul-terminated in savegame ...
if (f == NULL) {
*title = '\0';
return;
}
bool broken = (fread(temp, 1, HEADER_SIZE, f) != HEADER_SIZE || !VerifyOldNameChecksum(temp));
temp[HEADER_SIZE - 2] = '\0'; // ... but it's better to be sure
if (broken) title = strecpy(title, "(broken) ", last);
title = strecpy(title, temp, last);
DetermineOldSavegameType(f, title, last);
fclose(f);
}

View File

@ -34,7 +34,11 @@ enum OldChunkType {
OC_NULL = 1,
OC_CHUNK = 2,
OC_ASSERT = 3,
/* 8 bits allocated (256 max) */
/* 4 bits allocated (16 max) */
OC_TTD = 1 << 4, ///< chunk is valid ONLY for TTD savegames
OC_TTO = 1 << 5, ///< -//- TTO (default in neither of these)
/* 4 bits allocated */
OC_VAR_I8 = 1 << 8,
OC_VAR_U8 = 2 << 8,
@ -91,7 +95,8 @@ extern uint _bump_assert_value;
byte ReadByte(LoadgameState *ls);
bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks);
bool LoadOldMain(LoadgameState *ls);
bool LoadTTDMain(LoadgameState *ls);
bool LoadTTOMain(LoadgameState *ls);
static inline uint16 ReadUint16(LoadgameState *ls)
{
@ -114,11 +119,13 @@ static inline uint32 ReadUint32(LoadgameState *ls)
* - 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 }
#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_CNULL(type, amount) { OC_NULL | type, amount, NULL, 0, NULL }
#define OCL_CCHUNK(type, amount, proc) { OC_CHUNK | type, amount, NULL, 0, proc }
#define OCL_ASSERT(type, size) { OC_ASSERT | type, 1, NULL, size, NULL }
#define OCL_NULL(amount) OCL_CNULL((OldChunkType)0, amount)
#define OCL_CHUNK(amount, proc) OCL_CCHUNK((OldChunkType)0, amount, proc)
#endif /* OLDLOADER_H */

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,9 @@ 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
SGT_OTTD, ///< OTTD savegame
SGT_TTO, ///< TTO savegame
SGT_INVALID = 0xFF ///< broken savegame (used internally)
};
void GenerateDefaultSaveName(char *buf, const char *last);

View File

@ -9,6 +9,7 @@
#include "../company_manager_face.h"
#include "../order_base.h"
#include "../engine_type.h"
#include "saveload.h"
void InitializeOldNames();
StringID RemapOldStringID(StringID s);
@ -37,6 +38,9 @@ void CopyTempEngineData();
extern int32 _saved_scrollpos_x;
extern int32 _saved_scrollpos_y;
extern SavegameType _savegame_type;
extern uint32 _ttdp_version;
CompanyManagerFace ConvertFromOldCompanyManagerFace(uint32 face);
Order UnpackOldOrder(uint16 packed);

View File

@ -8,6 +8,7 @@
#include "../core/bitmath_func.hpp"
#include "../core/alloc_func.hpp"
#include "../string_func.h"
#include "saveload_internal.h"
#include "table/strings.h"
@ -44,8 +45,8 @@ 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.
* They were all stored in a 512 by 32 (200 by 24 for TTO) 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.
*/
@ -55,10 +56,11 @@ char *CopyFromOldName(StringID id)
if (GB(id, 11, 5) != 15) return NULL;
if (CheckSavegameVersion(37)) {
/* Old names were 32 characters long, so 128 characters should be
/* Old names were 24/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)];
uint offs = _savegame_type == SGT_TTO ? 24 * GB(id, 0, 8) : 32 * GB(id, 0, 9);
const char *strfrom = &_old_name_array[offs];
char *strto = tmp;
for (; *strfrom != '\0'; strfrom++) {
@ -109,7 +111,7 @@ void ResetOldNames()
void InitializeOldNames()
{
free(_old_name_array);
_old_name_array = CallocT<char>(512 * 32);
_old_name_array = CallocT<char>(512 * 32); // 200 * 24 would be enough for TTO savegames
}
static void Load_NAME()

View File

@ -207,7 +207,7 @@ private:
Vehicle *previous_shared; ///< NOSAVE: pointer to the previous vehicle in the shared order chain
public:
friend const SaveLoad *GetVehicleDescription(VehicleType vt); ///< So we can use private/protected variables in the saveload code
friend void AfterLoadVehicles(bool part_of_load); ///< So we can set the previous and first pointers while loading
friend void AfterLoadVehicles(bool part_of_load); ///< So we can set the previous and first pointers while loading
friend bool LoadOldVehicle(LoadgameState *ls, int num); ///< So we can set the proper next pointer while loading
char *name; ///< Name of vehicle