OpenTTD Source 20250524-master-gc366e6a48e
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#include "saveload_internal.h"
17#include "oldloader.h"
18
19#include "table/strings.h"
20
21#include "../safeguards.h"
22
23static const int TTO_HEADER_SIZE = 41;
24static const int TTD_HEADER_SIZE = 49;
26static const int HEADER_CHECKSUM_SIZE = 2;
27
28uint32_t _bump_assert_value;
29
30static inline OldChunkType GetOldChunkType(OldChunkType type) {return (OldChunkType)GB(type, 0, 4);}
31static inline OldChunkType GetOldChunkVarType(OldChunkType type) {return (OldChunkType)(GB(type, 8, 8) << 8);}
32static inline OldChunkType GetOldChunkFileType(OldChunkType type) {return (OldChunkType)(GB(type, 16, 8) << 16);}
33
39static inline uint8_t CalcOldVarLen(OldChunkType type)
40{
41 switch (GetOldChunkVarType(type)) {
42 case OC_VAR_I8: return sizeof(int8_t);
43 case OC_VAR_U8: return sizeof(uint8_t);
44 case OC_VAR_I16: return sizeof(int16_t);
45 case OC_VAR_U16: return sizeof(uint16_t);
46 case OC_VAR_I32: return sizeof(int32_t);
47 case OC_VAR_U32: return sizeof(uint32_t);
48 case OC_VAR_I64: return sizeof(int64_t);
49 case OC_VAR_U64: return sizeof(uint64_t);
50 default: NOT_REACHED();
51 }
52}
53
60{
61 /* To avoid slow reads, we read BUFFER_SIZE of bytes per time
62 and just return a byte per time */
63 if (ls.buffer_cur >= ls.buffer_count) {
64
65 /* Read some new bytes from the file */
66 int count = static_cast<int>(fread(ls.buffer.data(), 1, ls.buffer.size(), *ls.file));
67
68 /* We tried to read, but there is nothing in the file anymore.. */
69 if (count == 0) {
70 Debug(oldloader, 0, "Read past end of file, loading failed");
71 throw std::exception();
72 }
73
74 ls.buffer_count = count;
75 ls.buffer_cur = 0;
76 }
77
78 return ls.buffer[ls.buffer_cur++];
79}
80
87{
88 /* Old savegames have a nice compression algorithm (RLE)
89 which means that we have a chunk, which starts with a length
90 byte. If that byte is negative, we have to repeat the next byte
91 that many times ( + 1). Else, we need to read that amount of bytes.
92 Works pretty well if you have many zeros behind each other */
93
94 if (ls.chunk_size == 0) {
95 /* Read new chunk */
96 int8_t new_byte = ReadByteFromFile(ls);
97
98 if (new_byte < 0) {
99 /* Repeat next char for new_byte times */
100 ls.decoding = true;
101 ls.decode_char = ReadByteFromFile(ls);
102 ls.chunk_size = -new_byte + 1;
103 } else {
104 ls.decoding = false;
105 ls.chunk_size = new_byte + 1;
106 }
107 }
108
109 ls.total_read++;
110 ls.chunk_size--;
111
112 return ls.decoding ? ls.decode_char : ReadByteFromFile(ls);
113}
114
120bool LoadChunk(LoadgameState &ls, void *base, const OldChunks *chunks)
121{
122 for (const OldChunks *chunk = chunks; chunk->type != OC_END; chunk++) {
123 if (((chunk->type & OC_TTD) && _savegame_type == SGT_TTO) ||
124 ((chunk->type & OC_TTO) && _savegame_type != SGT_TTO)) {
125 /* TTD(P)-only chunk, but TTO savegame || TTO-only chunk, but TTD/TTDP savegame */
126 continue;
127 }
128
129 uint8_t *ptr = (uint8_t*)chunk->ptr;
130
131 for (uint i = 0; i < chunk->amount; i++) {
132 /* Handle simple types */
133 if (GetOldChunkType(chunk->type) != 0) {
134 switch (GetOldChunkType(chunk->type)) {
135 /* Just read the byte and forget about it */
136 case OC_NULL: ReadByte(ls); break;
137
138 case OC_CHUNK:
139 /* Call function, with 'i' as parameter to tell which item we
140 * are going to read */
141 if (!chunk->proc(ls, i)) return false;
142 break;
143
144 case OC_ASSERT:
145 Debug(oldloader, 4, "Assert point: 0x{:X} / 0x{:X}", ls.total_read, reinterpret_cast<size_t>(chunk->ptr) + _bump_assert_value);
146 if (ls.total_read != reinterpret_cast<size_t>(chunk->ptr) + _bump_assert_value) throw std::exception();
147 default: break;
148 }
149 } else {
150 uint64_t res = 0;
151
152 /* Reading from the file: bits 16 to 23 have the FILE type */
153 switch (GetOldChunkFileType(chunk->type)) {
154 case OC_FILE_I8: res = (int8_t)ReadByte(ls); break;
155 case OC_FILE_U8: res = ReadByte(ls); break;
156 case OC_FILE_I16: res = (int16_t)ReadUint16(ls); break;
157 case OC_FILE_U16: res = ReadUint16(ls); break;
158 case OC_FILE_I32: res = (int32_t)ReadUint32(ls); break;
159 case OC_FILE_U32: res = ReadUint32(ls); break;
160 default: NOT_REACHED();
161 }
162
163 /* When both pointers are nullptr, we are just skipping data */
164 if (base == nullptr && chunk->ptr == nullptr) continue;
165
166 /* Chunk refers to a struct member, get address in base. */
167 if (chunk->ptr == nullptr) ptr = (uint8_t *)chunk->offset(base);
168
169 /* Write the data */
170 switch (GetOldChunkVarType(chunk->type)) {
171 case OC_VAR_I8: *(int8_t *)ptr = GB(res, 0, 8); break;
172 case OC_VAR_U8: *(uint8_t *)ptr = GB(res, 0, 8); break;
173 case OC_VAR_I16:*(int16_t *)ptr = GB(res, 0, 16); break;
174 case OC_VAR_U16:*(uint16_t*)ptr = GB(res, 0, 16); break;
175 case OC_VAR_I32:*(int32_t *)ptr = res; break;
176 case OC_VAR_U32:*(uint32_t*)ptr = res; break;
177 case OC_VAR_I64:*(int64_t *)ptr = res; break;
178 case OC_VAR_U64:*(uint64_t*)ptr = res; break;
179 default: NOT_REACHED();
180 }
181
182 /* Increase pointer base for arrays when looping */
183 if (chunk->amount > 1 && chunk->ptr != nullptr) ptr += CalcOldVarLen(chunk->type);
184 }
185 }
186 }
187
188 return true;
189}
190
197static bool VerifyOldNameChecksum(char *title, uint len)
198{
199 uint16_t sum = 0;
200 for (uint i = 0; i < len - HEADER_CHECKSUM_SIZE; i++) {
201 sum += title[i];
202 sum = std::rotl(sum, 1);
203 }
204
205 sum ^= 0xAAAA; // computed checksum
206
207 uint16_t sum2 = title[len - HEADER_CHECKSUM_SIZE]; // checksum in file
208 SB(sum2, 8, 8, title[len - HEADER_CHECKSUM_SIZE + 1]);
209
210 return sum == sum2;
211}
212
213static std::tuple<SavegameType, std::string> DetermineOldSavegameTypeAndName(FileHandle &f)
214{
215 long pos = ftell(f);
216 char buffer[std::max(TTO_HEADER_SIZE, TTD_HEADER_SIZE)];
217 if (pos < 0 || fread(buffer, 1, lengthof(buffer), f) != lengthof(buffer)) {
218 return { SGT_INVALID, "(broken) Unable to read file" };
219 }
220
221 if (VerifyOldNameChecksum(buffer, TTO_HEADER_SIZE) && fseek(f, pos + TTO_HEADER_SIZE, SEEK_SET) == 0) {
222 return { SGT_TTO, "(TTO)" + StrMakeValid(std::string_view{buffer, TTO_HEADER_SIZE - HEADER_CHECKSUM_SIZE}) };
223 }
224
225 if (VerifyOldNameChecksum(buffer, TTD_HEADER_SIZE) && fseek(f, pos + TTD_HEADER_SIZE, SEEK_SET) == 0) {
226 return { SGT_TTD, "(TTD)" + StrMakeValid(std::string_view{buffer, TTD_HEADER_SIZE - HEADER_CHECKSUM_SIZE}) };
227 }
228
229 return { SGT_INVALID, "(broken) Unknown" };
230}
231
232typedef bool LoadOldMainProc(LoadgameState &ls);
233
234bool LoadOldSaveGame(std::string_view file)
235{
236 LoadgameState ls{};
237
238 Debug(oldloader, 3, "Trying to load a TTD(Patch) savegame");
239
240 _bump_assert_value = 0;
241 _settings_game.construction.freeform_edges = false; // disable so we can convert map array (SetTileType is still used)
242
243 /* Open file */
244 ls.file = FioFOpenFile(file, "rb", NO_DIRECTORY);
245
246 if (!ls.file.has_value()) {
247 Debug(oldloader, 0, "Cannot open file '{}'", file);
248 SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
249 return false;
250 }
251
252 SavegameType type;
253 std::tie(type, std::ignore) = DetermineOldSavegameTypeAndName(*ls.file);
254
255 LoadOldMainProc *proc = nullptr;
256
257 switch (type) {
258 case SGT_TTO: proc = &LoadTTOMain; break;
259 case SGT_TTD: proc = &LoadTTDMain; break;
260 default:
261 Debug(oldloader, 0, "Unknown savegame type; cannot be loaded");
262 break;
263 }
264
265 _savegame_type = type;
266
267 bool game_loaded;
268 try {
269 game_loaded = proc != nullptr && proc(ls);
270 } catch (...) {
271 game_loaded = false;
272 }
273
274 if (!game_loaded) {
275 SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED);
276 ls.file.reset();
277 return false;
278 }
279
281
282 return true;
283}
284
285std::string GetOldSaveGameName(std::string_view file)
286{
287 auto f = FioFOpenFile(file, "rb", NO_DIRECTORY);
288 if (!f.has_value()) return {};
289
290 std::string name;
291 std::tie(std::ignore, name) = DetermineOldSavegameTypeAndName(*f);
292 return name;
293}
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.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:242
@ NO_DIRECTORY
A path without any base directory.
SavegameType _savegame_type
type of savegame we are loading
Definition saveload.cpp:65
PauseModes _pause_mode
The current pause mode.
Definition gfx.cpp:50
static bool VerifyOldNameChecksum(char *title, uint len)
Verifies the title has a valid checksum.
static const int HEADER_CHECKSUM_SIZE
The size of the checksum in the name/header of the TTD/TTO savegames.
Definition oldloader.cpp:26
static uint8_t CalcOldVarLen(OldChunkType type)
Return expected size in bytes of a OldChunkType.
Definition oldloader.cpp:39
static uint8_t ReadByteFromFile(LoadgameState &ls)
Reads a byte from a file (do not call yourself, use ReadByte())
Definition oldloader.cpp:59
bool LoadChunk(LoadgameState &ls, void *base, const OldChunks *chunks)
Loads a chunk from the old savegame.
uint8_t ReadByte(LoadgameState &ls)
Reads a byte from the buffer and decompress if needed.
Definition oldloader.cpp:86
Declarations of structures and functions used in loader of old savegames.
OldChunkType
Definition oldloader.h:38
@ OC_END
End of the whole chunk, all 32 bits set to zero.
Definition oldloader.h:76
@ OC_TTO
-//- TTO (default is neither of these)
Definition oldloader.h:46
@ OC_TTD
chunk is valid ONLY for TTD savegames
Definition oldloader.h:45
@ SaveLoad
A game paused for saving/loading.
void SetSaveLoadError(StringID str)
Set the error message from outside of the actual loading/saving of the game (AfterLoadGame and friend...
SavegameType
Types of save games.
Definition saveload.h:428
@ SGT_TTD
TTD savegame (can be detected incorrectly)
Definition saveload.h:429
@ SGT_INVALID
broken savegame (used internally)
Definition saveload.h:434
@ SGT_TTO
TTO savegame.
Definition saveload.h:433
Declaration of functions used in more save/load files.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:60
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
bool freeform_edges
allow terraforming the tiles at the map edges
ConstructionSettings construction
construction of things in-game
OldChunkType type
Type of field.
Definition oldloader.h:85