OpenTTD Source  20240919-master-gdf0233f4c2
oldloader.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "../stdafx.h"
11 #include "../debug.h"
12 #include "../strings_type.h"
13 #include "../string_func.h"
14 #include "../settings_type.h"
15 #include "../fileio_func.h"
16 
17 #include "table/strings.h"
18 
19 #include "saveload_internal.h"
20 #include "oldloader.h"
21 
22 
23 #include "../safeguards.h"
24 
25 static const int TTO_HEADER_SIZE = 41;
26 static const int TTD_HEADER_SIZE = 49;
28 static const int HEADER_CHECKSUM_SIZE = 2;
29 
30 uint32_t _bump_assert_value;
31 
32 static inline OldChunkType GetOldChunkType(OldChunkType type) {return (OldChunkType)GB(type, 0, 4);}
33 static inline OldChunkType GetOldChunkVarType(OldChunkType type) {return (OldChunkType)(GB(type, 8, 8) << 8);}
34 static inline OldChunkType GetOldChunkFileType(OldChunkType type) {return (OldChunkType)(GB(type, 16, 8) << 16);}
35 
41 static inline uint8_t CalcOldVarLen(OldChunkType type)
42 {
43  switch (GetOldChunkVarType(type)) {
44  case OC_VAR_I8: return sizeof(int8_t);
45  case OC_VAR_U8: return sizeof(uint8_t);
46  case OC_VAR_I16: return sizeof(int16_t);
47  case OC_VAR_U16: return sizeof(uint16_t);
48  case OC_VAR_I32: return sizeof(int32_t);
49  case OC_VAR_U32: return sizeof(uint32_t);
50  case OC_VAR_I64: return sizeof(int64_t);
51  case OC_VAR_U64: return sizeof(uint64_t);
52  default: NOT_REACHED();
53  }
54 }
55 
61 static uint8_t ReadByteFromFile(LoadgameState *ls)
62 {
63  /* To avoid slow reads, we read BUFFER_SIZE of bytes per time
64  and just return a byte per time */
65  if (ls->buffer_cur >= ls->buffer_count) {
66 
67  /* Read some new bytes from the file */
68  int count = static_cast<int>(fread(ls->buffer, 1, BUFFER_SIZE, *ls->file));
69 
70  /* We tried to read, but there is nothing in the file anymore.. */
71  if (count == 0) {
72  Debug(oldloader, 0, "Read past end of file, loading failed");
73  throw std::exception();
74  }
75 
76  ls->buffer_count = count;
77  ls->buffer_cur = 0;
78  }
79 
80  return ls->buffer[ls->buffer_cur++];
81 }
82 
88 uint8_t ReadByte(LoadgameState *ls)
89 {
90  /* Old savegames have a nice compression algorithm (RLE)
91  which means that we have a chunk, which starts with a length
92  byte. If that byte is negative, we have to repeat the next byte
93  that many times ( + 1). Else, we need to read that amount of bytes.
94  Works pretty well if you have many zeros behind each other */
95 
96  if (ls->chunk_size == 0) {
97  /* Read new chunk */
98  int8_t new_byte = ReadByteFromFile(ls);
99 
100  if (new_byte < 0) {
101  /* Repeat next char for new_byte times */
102  ls->decoding = true;
103  ls->decode_char = ReadByteFromFile(ls);
104  ls->chunk_size = -new_byte + 1;
105  } else {
106  ls->decoding = false;
107  ls->chunk_size = new_byte + 1;
108  }
109  }
110 
111  ls->total_read++;
112  ls->chunk_size--;
113 
114  return ls->decoding ? ls->decode_char : ReadByteFromFile(ls);
115 }
116 
122 bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks)
123 {
124  for (const OldChunks *chunk = chunks; chunk->type != OC_END; chunk++) {
125  if (((chunk->type & OC_TTD) && _savegame_type == SGT_TTO) ||
126  ((chunk->type & OC_TTO) && _savegame_type != SGT_TTO)) {
127  /* TTD(P)-only chunk, but TTO savegame || TTO-only chunk, but TTD/TTDP savegame */
128  continue;
129  }
130 
131  uint8_t *ptr = (uint8_t*)chunk->ptr;
132  if (chunk->type & OC_DEREFERENCE_POINTER) ptr = *(uint8_t**)ptr;
133 
134  for (uint i = 0; i < chunk->amount; i++) {
135  /* Handle simple types */
136  if (GetOldChunkType(chunk->type) != 0) {
137  switch (GetOldChunkType(chunk->type)) {
138  /* Just read the byte and forget about it */
139  case OC_NULL: ReadByte(ls); break;
140 
141  case OC_CHUNK:
142  /* Call function, with 'i' as parameter to tell which item we
143  * are going to read */
144  if (!chunk->proc(ls, i)) return false;
145  break;
146 
147  case OC_ASSERT:
148  Debug(oldloader, 4, "Assert point: 0x{:X} / 0x{:X}", ls->total_read, (uint)(size_t)chunk->ptr + _bump_assert_value);
149  if (ls->total_read != (size_t)chunk->ptr + _bump_assert_value) throw std::exception();
150  default: break;
151  }
152  } else {
153  uint64_t res = 0;
154 
155  /* Reading from the file: bits 16 to 23 have the FILE type */
156  switch (GetOldChunkFileType(chunk->type)) {
157  case OC_FILE_I8: res = (int8_t)ReadByte(ls); break;
158  case OC_FILE_U8: res = ReadByte(ls); break;
159  case OC_FILE_I16: res = (int16_t)ReadUint16(ls); break;
160  case OC_FILE_U16: res = ReadUint16(ls); break;
161  case OC_FILE_I32: res = (int32_t)ReadUint32(ls); break;
162  case OC_FILE_U32: res = ReadUint32(ls); break;
163  default: NOT_REACHED();
164  }
165 
166  /* When both pointers are nullptr, we are just skipping data */
167  if (base == nullptr && chunk->ptr == nullptr) continue;
168 
169  /* Chunk refers to a struct member, get address in base. */
170  if (chunk->ptr == nullptr) ptr = (uint8_t *)chunk->offset(base);
171 
172  /* Write the data */
173  switch (GetOldChunkVarType(chunk->type)) {
174  case OC_VAR_I8: *(int8_t *)ptr = GB(res, 0, 8); break;
175  case OC_VAR_U8: *(uint8_t *)ptr = GB(res, 0, 8); break;
176  case OC_VAR_I16:*(int16_t *)ptr = GB(res, 0, 16); break;
177  case OC_VAR_U16:*(uint16_t*)ptr = GB(res, 0, 16); break;
178  case OC_VAR_I32:*(int32_t *)ptr = res; break;
179  case OC_VAR_U32:*(uint32_t*)ptr = res; break;
180  case OC_VAR_I64:*(int64_t *)ptr = res; break;
181  case OC_VAR_U64:*(uint64_t*)ptr = res; break;
182  default: NOT_REACHED();
183  }
184 
185  /* Increase pointer base for arrays when looping */
186  if (chunk->amount > 1 && chunk->ptr != nullptr) ptr += CalcOldVarLen(chunk->type);
187  }
188  }
189  }
190 
191  return true;
192 }
193 
199 static void InitLoading(LoadgameState *ls)
200 {
201  ls->chunk_size = 0;
202  ls->total_read = 0;
203 
204  ls->decoding = false;
205  ls->decode_char = 0;
206 
207  ls->buffer_cur = 0;
208  ls->buffer_count = 0;
209  memset(ls->buffer, 0, BUFFER_SIZE);
210 
211  _bump_assert_value = 0;
212 
213  _settings_game.construction.freeform_edges = false; // disable so we can convert map array (SetTileType is still used)
214 }
215 
222 static bool VerifyOldNameChecksum(char *title, uint len)
223 {
224  uint16_t sum = 0;
225  for (uint i = 0; i < len - HEADER_CHECKSUM_SIZE; i++) {
226  sum += title[i];
227  sum = std::rotl(sum, 1);
228  }
229 
230  sum ^= 0xAAAA; // computed checksum
231 
232  uint16_t sum2 = title[len - HEADER_CHECKSUM_SIZE]; // checksum in file
233  SB(sum2, 8, 8, title[len - HEADER_CHECKSUM_SIZE + 1]);
234 
235  return sum == sum2;
236 }
237 
238 static std::tuple<SavegameType, std::string> DetermineOldSavegameTypeAndName(FileHandle &f)
239 {
240  long pos = ftell(f);
241  char buffer[std::max(TTO_HEADER_SIZE, TTD_HEADER_SIZE)];
242  if (pos < 0 || fread(buffer, 1, lengthof(buffer), f) != lengthof(buffer)) {
243  return { SGT_INVALID, "(broken) Unable to read file" };
244  }
245 
246  if (VerifyOldNameChecksum(buffer, TTO_HEADER_SIZE) && fseek(f, pos + TTO_HEADER_SIZE, SEEK_SET) == 0) {
247  return { SGT_TTO, "(TTO)" + StrMakeValid({buffer, TTO_HEADER_SIZE - HEADER_CHECKSUM_SIZE}) };
248  }
249 
250  if (VerifyOldNameChecksum(buffer, TTD_HEADER_SIZE) && fseek(f, pos + TTD_HEADER_SIZE, SEEK_SET) == 0) {
251  return { SGT_TTD, "(TTD)" + StrMakeValid({buffer, TTD_HEADER_SIZE - HEADER_CHECKSUM_SIZE}) };
252  }
253 
254  return { SGT_INVALID, "(broken) Unknown" };
255 }
256 
257 typedef bool LoadOldMainProc(LoadgameState *ls);
258 
259 bool LoadOldSaveGame(const std::string &file)
260 {
261  LoadgameState ls;
262 
263  Debug(oldloader, 3, "Trying to load a TTD(Patch) savegame");
264 
265  InitLoading(&ls);
266 
267  /* Open file */
268  ls.file = FioFOpenFile(file, "rb", NO_DIRECTORY);
269 
270  if (!ls.file.has_value()) {
271  Debug(oldloader, 0, "Cannot open file '{}'", file);
272  return false;
273  }
274 
275  SavegameType type;
276  std::tie(type, std::ignore) = DetermineOldSavegameTypeAndName(*ls.file);
277 
278  LoadOldMainProc *proc = nullptr;
279 
280  switch (type) {
281  case SGT_TTO: proc = &LoadTTOMain; break;
282  case SGT_TTD: proc = &LoadTTDMain; break;
283  default:
284  Debug(oldloader, 0, "Unknown savegame type; cannot be loaded");
285  break;
286  }
287 
288  _savegame_type = type;
289 
290  bool game_loaded;
291  try {
292  game_loaded = proc != nullptr && proc(&ls);
293  } catch (...) {
294  game_loaded = false;
295  }
296 
297  if (!game_loaded) {
298  SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED);
299  ls.file.reset();
300  return false;
301  }
302 
304 
305  return true;
306 }
307 
308 std::string GetOldSaveGameName(const std::string &file)
309 {
310  auto f = FioFOpenFile(file, "rb", NO_DIRECTORY);
311  if (!f.has_value()) return {};
312 
313  std::string name;
314  std::tie(std::ignore, name) = DetermineOldSavegameTypeAndName(*f);
315  return name;
316 }
SGT_INVALID
@ SGT_INVALID
broken savegame (used internally)
Definition: saveload.h:421
OC_DEREFERENCE_POINTER
@ OC_DEREFERENCE_POINTER
Dereference the pointer once before writing to it, so we do not have to use big static arrays.
Definition: oldloader.h:77
LoadgameState
Definition: oldloader.h:19
oldloader.h
SetSaveLoadError
void SetSaveLoadError(StringID str)
Set the error message from outside of the actual loading/saving of the game (AfterLoadGame and friend...
Definition: saveload.cpp:2794
GB
constexpr static debug_inline uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
Definition: bitmath_func.hpp:32
StrMakeValid
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition: string.cpp:107
PM_PAUSED_SAVELOAD
@ PM_PAUSED_SAVELOAD
A game paused for saving/loading.
Definition: openttd.h:71
CalcOldVarLen
static uint8_t CalcOldVarLen(OldChunkType type)
Return expected size in bytes of a OldChunkType.
Definition: oldloader.cpp:41
_savegame_type
SavegameType _savegame_type
type of savegame we are loading
Definition: saveload.cpp:59
Debug
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
SavegameType
SavegameType
Types of save games.
Definition: saveload.h:415
SGT_TTO
@ SGT_TTO
TTO savegame.
Definition: saveload.h:420
LoadChunk
bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks)
Loads a chunk from the old savegame.
Definition: oldloader.cpp:122
VerifyOldNameChecksum
static bool VerifyOldNameChecksum(char *title, uint len)
Verifies the title has a valid checksum.
Definition: oldloader.cpp:222
_pause_mode
PauseMode _pause_mode
The current pause mode.
Definition: gfx.cpp:50
_settings_game
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition: settings.cpp:57
OC_TTO
@ OC_TTO
-//- TTO (default is neither of these)
Definition: oldloader.h:43
lengthof
#define lengthof(array)
Return the length of an fixed size array.
Definition: stdafx.h:280
ConstructionSettings::freeform_edges
bool freeform_edges
allow terraforming the tiles at the map edges
Definition: settings_type.h:395
FileHandle
Definition: fileio_type.h:158
HEADER_CHECKSUM_SIZE
static const int HEADER_CHECKSUM_SIZE
The size of the checksum in the name/header of the TTD/TTO savegames.
Definition: oldloader.cpp:28
OldChunkType
OldChunkType
Definition: oldloader.h:35
SGT_TTD
@ SGT_TTD
TTD savegame (can be detected incorrectly)
Definition: saveload.h:416
OldChunks
Definition: oldloader.h:87
ReadByte
uint8_t ReadByte(LoadgameState *ls)
Reads a byte from the buffer and decompress if needed.
Definition: oldloader.cpp:88
NO_DIRECTORY
@ NO_DIRECTORY
A path without any base directory.
Definition: fileio_type.h:133
InitLoading
static void InitLoading(LoadgameState *ls)
Initialize some data before reading.
Definition: oldloader.cpp:199
FioFOpenFile
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:242
OC_END
@ OC_END
End of the whole chunk, all 32 bits set to zero.
Definition: oldloader.h:79
saveload_internal.h
OC_TTD
@ OC_TTD
chunk is valid ONLY for TTD savegames
Definition: oldloader.h:42
ReadByteFromFile
static uint8_t ReadByteFromFile(LoadgameState *ls)
Reads a byte from a file (do not call yourself, use ReadByte())
Definition: oldloader.cpp:61
SB
constexpr T SB(T &x, const uint8_t s, const uint8_t n, const U d)
Set n bits in x starting at bit s to d.
Definition: bitmath_func.hpp:58
GameSettings::construction
ConstructionSettings construction
construction of things in-game
Definition: settings_type.h:595
OldChunks::type
OldChunkType type
Type of field.
Definition: oldloader.h:88