diff --git a/projects/openttd_vs140.vcxproj b/projects/openttd_vs140.vcxproj
index 88f7b44ddd..b82aba741c 100644
--- a/projects/openttd_vs140.vcxproj
+++ b/projects/openttd_vs140.vcxproj
@@ -886,6 +886,8 @@
+
+
diff --git a/projects/openttd_vs140.vcxproj.filters b/projects/openttd_vs140.vcxproj.filters
index 06800ffdaf..0b6dc573b4 100644
--- a/projects/openttd_vs140.vcxproj.filters
+++ b/projects/openttd_vs140.vcxproj.filters
@@ -1836,6 +1836,12 @@
Save/Load handlers
+
+ Save/Load handlers
+
+
+ Save/Load handlers
+
Tables
diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp
index ad64ae802f..0bc0d708da 100644
--- a/src/genworld_gui.cpp
+++ b/src/genworld_gui.cpp
@@ -20,6 +20,7 @@
#include "sound_func.h"
#include "fios.h"
#include "string_func.h"
+#include "gui.h"
#include "widgets/dropdown_type.h"
#include "widgets/dropdown_func.h"
#include "querystring_gui.h"
@@ -281,12 +282,37 @@ static void LandscapeGenerationCallback(Window *w, bool confirmed)
if (confirmed) StartGeneratingLandscape((GenerateLandscapeWindowMode)w->window_number);
}
-static DropDownList *BuildMapsizeDropDown()
+/**
+ * Check if map size set lies in allowed boundaries.
+ * @param print_warning If set to true, messagebox with warning is printed out if size is outside limits.
+ * @return true if size is ok, false otherwise.
+ */
+static bool CheckMapSize(bool print_warning = true)
+{
+ uint64 tiles = 1ULL << (_settings_newgame.game_creation.map_x + _settings_newgame.game_creation.map_y);
+
+ if (_settings_newgame.game_creation.map_x + _settings_newgame.game_creation.map_y > MAX_MAP_TILES_BITS) {
+ if (print_warning) {
+ SetDParam(0, MAX_MAP_TILES);
+ SetDParam(1, tiles);
+ ShowErrorMessage(STR_MAPGEN_TOO_MANY_TILES_MESSAGE, INVALID_STRING_ID, WL_ERROR, 0, 0);
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Build dropdown list with map sizes
+ * Dimension selected in the other dropdown is used to suggest which choices are 'valid'
+ * @param other_dimension Dimension specified by the second dropdown.
+ */
+static DropDownList *BuildMapsizeDropDown(int other_dimension)
{
DropDownList *list = new DropDownList();
for (uint i = MIN_MAP_SIZE_BITS; i <= MAX_MAP_SIZE_BITS; i++) {
- DropDownListParamStringItem *item = new DropDownListParamStringItem(STR_JUST_INT, i, false);
+ DropDownListParamStringItem *item = new DropDownListParamStringItem((i + other_dimension > MAX_MAP_TILES_BITS) ? STR_RED_INT : STR_JUST_INT, i, false);
item->SetParam(0, 1 << i);
*list->Append() = item;
}
@@ -314,6 +340,14 @@ struct GenerateLandscapeWindow : public Window {
char name[64];
GenerateLandscapeWindowMode mode;
+ void SetDropDownColor()
+ {
+ /* Draw sizes in mapsize selection dropdowns in red if too large size is selected */
+ bool mapsize_valid = CheckMapSize(false);
+ this->GetWidget(WID_GL_MAPSIZE_X_PULLDOWN)->widget_data = mapsize_valid ? STR_JUST_INT : STR_RED_INT;
+ this->GetWidget(WID_GL_MAPSIZE_Y_PULLDOWN)->widget_data = mapsize_valid ? STR_JUST_INT : STR_RED_INT;
+ }
+
GenerateLandscapeWindow(WindowDesc *desc, WindowNumber number = 0) : Window(desc)
{
this->InitNested(number);
@@ -322,6 +356,8 @@ struct GenerateLandscapeWindow : public Window {
this->mode = (GenerateLandscapeWindowMode)this->window_number;
+ SetDropDownColor();
+
/* Disable town, industry and trees in SE */
this->SetWidgetDisabledState(WID_GL_TOWN_PULLDOWN, _game_mode == GM_EDITOR);
this->SetWidgetDisabledState(WID_GL_INDUSTRY_PULLDOWN, _game_mode == GM_EDITOR);
@@ -538,11 +574,11 @@ struct GenerateLandscapeWindow : public Window {
break;
case WID_GL_MAPSIZE_X_PULLDOWN: // Mapsize X
- ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_x, WID_GL_MAPSIZE_X_PULLDOWN);
+ ShowDropDownList(this, BuildMapsizeDropDown(_settings_newgame.game_creation.map_y), _settings_newgame.game_creation.map_x, WID_GL_MAPSIZE_X_PULLDOWN);
break;
case WID_GL_MAPSIZE_Y_PULLDOWN: // Mapsize Y
- ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, WID_GL_MAPSIZE_Y_PULLDOWN);
+ ShowDropDownList(this, BuildMapsizeDropDown(_settings_newgame.game_creation.map_x), _settings_newgame.game_creation.map_y, WID_GL_MAPSIZE_Y_PULLDOWN);
break;
case WID_GL_TOWN_PULLDOWN: // Number of towns
@@ -554,6 +590,7 @@ struct GenerateLandscapeWindow : public Window {
break;
case WID_GL_GENERATE_BUTTON: { // Generate
+ if (!CheckMapSize()) break;
/* Get rotated map size. */
uint map_x;
uint map_y;
@@ -716,8 +753,14 @@ struct GenerateLandscapeWindow : public Window {
virtual void OnDropdownSelect(int widget, int index)
{
switch (widget) {
- case WID_GL_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break;
- case WID_GL_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break;
+ case WID_GL_MAPSIZE_X_PULLDOWN:
+ _settings_newgame.game_creation.map_x = index;
+ SetDropDownColor();
+ break;
+ case WID_GL_MAPSIZE_Y_PULLDOWN:
+ _settings_newgame.game_creation.map_y = index;
+ SetDropDownColor();
+ break;
case WID_GL_TREE_PULLDOWN: _settings_newgame.game_creation.tree_placer = index; break;
case WID_GL_RIVER_PULLDOWN: _settings_newgame.game_creation.amount_of_rivers = index; break;
case WID_GL_SMOOTHNESS_PULLDOWN: _settings_newgame.game_creation.tgen_smoothness = index; break;
@@ -881,10 +924,19 @@ struct CreateScenarioWindow : public Window
{
uint widget_id;
+ void SetDropDownColor()
+ {
+ /* Draw sizes in mapsize selection dropdowns in red if too large size is selected */
+ bool mapsize_valid = CheckMapSize(false);
+ this->GetWidget(WID_CS_MAPSIZE_X_PULLDOWN)->widget_data = mapsize_valid ? STR_JUST_INT : STR_RED_INT;
+ this->GetWidget(WID_CS_MAPSIZE_Y_PULLDOWN)->widget_data = mapsize_valid ? STR_JUST_INT : STR_RED_INT;
+ }
+
CreateScenarioWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
{
this->InitNested(window_number);
this->LowerWidget(_settings_newgame.game_creation.landscape + WID_CS_TEMPERATE);
+ SetDropDownColor();
}
virtual void SetStringParameters(int widget) const
@@ -908,6 +960,8 @@ struct CreateScenarioWindow : public Window
}
}
+
+
virtual void OnPaint()
{
this->SetWidgetDisabledState(WID_CS_START_DATE_DOWN, _settings_newgame.game_creation.starting_year <= MIN_YEAR);
@@ -961,18 +1015,20 @@ struct CreateScenarioWindow : public Window
break;
case WID_CS_MAPSIZE_X_PULLDOWN: // Mapsize X
- ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_x, WID_CS_MAPSIZE_X_PULLDOWN);
+ ShowDropDownList(this, BuildMapsizeDropDown(_settings_newgame.game_creation.map_y), _settings_newgame.game_creation.map_x, WID_CS_MAPSIZE_X_PULLDOWN);
break;
case WID_CS_MAPSIZE_Y_PULLDOWN: // Mapsize Y
- ShowDropDownList(this, BuildMapsizeDropDown(), _settings_newgame.game_creation.map_y, WID_CS_MAPSIZE_Y_PULLDOWN);
+ ShowDropDownList(this, BuildMapsizeDropDown(_settings_newgame.game_creation.map_x), _settings_newgame.game_creation.map_y, WID_CS_MAPSIZE_Y_PULLDOWN);
break;
case WID_CS_EMPTY_WORLD: // Empty world / flat world
+ if (!CheckMapSize()) break;
StartGeneratingLandscape(GLWM_SCENARIO);
break;
case WID_CS_RANDOM_WORLD: // Generate
+ if (!CheckMapSize()) break;
ShowGenerateLandscape();
break;
@@ -1031,6 +1087,8 @@ struct CreateScenarioWindow : public Window
case WID_CS_MAPSIZE_X_PULLDOWN: _settings_newgame.game_creation.map_x = index; break;
case WID_CS_MAPSIZE_Y_PULLDOWN: _settings_newgame.game_creation.map_y = index; break;
}
+ SetDropDownColor();
+
this->SetDirty();
}
diff --git a/src/landscape.cpp b/src/landscape.cpp
index d1c73fd42b..00640f9486 100644
--- a/src/landscape.cpp
+++ b/src/landscape.cpp
@@ -723,12 +723,13 @@ void RunTileLoop()
* shift register (LFSR). This allows a deterministic pseudorandom ordering, but
* still with minimal state and fast iteration. */
- /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) to 24-bit (for 4096x4096 maps).
+ /* Maximal length LFSR feedback terms, from 12-bit (for 64x64 maps) to 28-bit (for 16kx16k maps).
* Extracted from http://www.ece.cmu.edu/~koopman/lfsr/ */
static const uint32 feedbacks[] = {
- 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8, 0x4004B2, 0x800B87
+ 0xD8F, 0x1296, 0x2496, 0x4357, 0x8679, 0x1030E, 0x206CD, 0x403FE, 0x807B8, 0x1004B2, 0x2006A8,
+ 0x4004B2, 0x800B87, 0x10004F3, 0x200072D, 0x40006AE, 0x80009E3,
};
- assert_compile(lengthof(feedbacks) == 2 * MAX_MAP_SIZE_BITS - 2 * MIN_MAP_SIZE_BITS + 1);
+ assert_compile(lengthof(feedbacks) == MAX_MAP_TILES_BITS - 2 * MIN_MAP_SIZE_BITS + 1);
const uint32 feedback = feedbacks[MapLogX() + MapLogY() - 2 * MIN_MAP_SIZE_BITS];
/* We update every tile every 256 ticks, so divide the map size by 2^8 = 256 */
diff --git a/src/lang/english.txt b/src/lang/english.txt
index ad5ab957fa..545628aca3 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -3070,6 +3070,7 @@ STR_MAPGEN_HEIGHTMAP_ROTATION :{BLACK}Heightma
STR_MAPGEN_HEIGHTMAP_NAME :{BLACK}Heightmap name:
STR_MAPGEN_HEIGHTMAP_SIZE_LABEL :{BLACK}Size:
STR_MAPGEN_HEIGHTMAP_SIZE :{ORANGE}{NUM} x {NUM}
+STR_MAPGEN_TOO_MANY_TILES_MESSAGE :{YELLOW}Too many tiles in map. Maximum number of tiles is {NUM}, you have selected {NUM}
STR_MAPGEN_MAX_HEIGHTLEVEL_QUERY_CAPT :{WHITE}Change maximum map height
STR_MAPGEN_SNOW_LINE_QUERY_CAPT :{WHITE}Change snow line height
@@ -5482,6 +5483,7 @@ STR_DATE_LONG_SMALL :{TINY_FONT}{BLA
STR_TINY_GROUP :{TINY_FONT}{GROUP}
STR_BLACK_INT :{BLACK}{NUM}
STR_ORANGE_INT :{ORANGE}{NUM}
+STR_RED_INT :{RED}{NUM}
STR_WHITE_SIGN :{WHITE}{SIGN}
STR_TINY_BLACK_STATION :{TINY_FONT}{BLACK}{STATION}
STR_BLACK_STRING :{BLACK}{STRING}
diff --git a/src/map.cpp b/src/map.cpp
index 252f20b607..392ab8ab6c 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -40,17 +40,19 @@ TileExtended *_me = NULL; ///< Extended Tiles of the map
*/
void AllocateMap(uint size_x, uint size_y)
{
+ DEBUG(map, 2, "Min/max map size %d/%d, max map tiles %d", MIN_MAP_SIZE, MAX_MAP_SIZE, MAX_MAP_TILES);
+ DEBUG(map, 1, "Allocating map of size %dx%d", size_x, size_y);
+
/* Make sure that the map size is within the limits and that
* size of both axes is a power of 2. */
- if (!IsInsideMM(size_x, MIN_MAP_SIZE, MAX_MAP_SIZE + 1) ||
- !IsInsideMM(size_y, MIN_MAP_SIZE, MAX_MAP_SIZE + 1) ||
+ if (size_x * size_y > MAX_MAP_TILES ||
+ size_x < MIN_MAP_SIZE ||
+ size_y < MIN_MAP_SIZE ||
(size_x & (size_x - 1)) != 0 ||
(size_y & (size_y - 1)) != 0) {
error("Invalid map size");
}
- DEBUG(map, 1, "Allocating map of size %dx%d", size_x, size_y);
-
_map_log_x = FindFirstBit(size_x);
_map_log_y = FindFirstBit(size_y);
_map_size_x = size_x;
diff --git a/src/map_type.h b/src/map_type.h
index 620885e5da..d2403fdb3d 100644
--- a/src/map_type.h
+++ b/src/map_type.h
@@ -62,9 +62,11 @@ struct TileIndexDiffC {
/** Minimal and maximal map width and height */
static const uint MIN_MAP_SIZE_BITS = 6; ///< Minimal size of map is equal to 2 ^ MIN_MAP_SIZE_BITS
-static const uint MAX_MAP_SIZE_BITS = 12; ///< Maximal size of map is equal to 2 ^ MAX_MAP_SIZE_BITS
+static const uint MAX_MAP_SIZE_BITS = 20; ///< Maximal size of map is equal to 2 ^ MAX_MAP_SIZE_BITS
+static const uint MAX_MAP_TILES_BITS = 28; ///< Maximal number of tiles in a map is equal to 2 ^ MAX_MAP_TILES_BITS.
static const uint MIN_MAP_SIZE = 1 << MIN_MAP_SIZE_BITS; ///< Minimal map size = 64
-static const uint MAX_MAP_SIZE = 1 << MAX_MAP_SIZE_BITS; ///< Maximal map size = 4096
+static const uint MAX_MAP_SIZE = 1 << MAX_MAP_SIZE_BITS; ///< Maximal map size = 1M
+static const uint MAX_MAP_TILES = 1 << MAX_MAP_TILES_BITS;///< Maximal number of tiles in a map = 256M (16k x 16k)
/**
* Approximation of the length of a straight track, relative to a diagonal
diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp
index 0074af2181..af9094c693 100644
--- a/src/newgrf_debug_gui.cpp
+++ b/src/newgrf_debug_gui.cpp
@@ -56,7 +56,7 @@ NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, Small
*/
static inline uint GetFeatureIndex(uint window_number)
{
- return GB(window_number, 0, 24);
+ return GB(window_number, 0, 27);
}
/**
@@ -68,8 +68,8 @@ static inline uint GetFeatureIndex(uint window_number)
*/
static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
{
- assert((index >> 24) == 0);
- return (feature << 24) | index;
+ assert((index >> 27) == 0);
+ return (feature << 27) | index;
}
/**
@@ -246,7 +246,7 @@ struct NIFeature {
*/
static inline GrfSpecFeature GetFeatureNum(uint window_number)
{
- return (GrfSpecFeature)GB(window_number, 24, 8);
+ return (GrfSpecFeature)GB(window_number, 27, 5);
}
/**
@@ -699,6 +699,7 @@ static WindowDesc _newgrf_inspect_desc(
*/
void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
{
+ if (index >= (1 << 27)) return;
if (!IsNewGRFInspectable(feature, index)) return;
WindowNumber wno = GetInspectWindowNumber(feature, index);
@@ -718,6 +719,7 @@ void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 gr
void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
{
if (feature == GSF_INVALID) return;
+ if (index >= (1 << 27)) return;
WindowNumber wno = GetInspectWindowNumber(feature, index);
InvalidateWindowData(WC_NEWGRF_INSPECT, wno);
@@ -734,6 +736,7 @@ void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
{
if (feature == GSF_INVALID) return;
+ if (index >= (1 << 27)) return;
WindowNumber wno = GetInspectWindowNumber(feature, index);
DeleteWindowById(WC_NEWGRF_INSPECT, wno);
@@ -755,6 +758,7 @@ void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
*/
bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
{
+ if (index >= (1 << 27)) return false;
const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
if (nif == NULL) return false;
return nif->helper->IsInspectable(index);
diff --git a/src/openttd.cpp b/src/openttd.cpp
index ba3b344b87..91f1ff7558 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -977,6 +977,24 @@ static void MakeNewGameDone()
MarkWholeScreenDirty();
}
+/*
+ * Too large size may be stored in settings (especially if switching between between OpenTTD
+ * versions with different map size limits), we have to check if it is valid before generating world.
+ * Simple separate checking of X and Y map sizes is not enough, as their sum is what counts for the limit.
+ * Check the size and decrease the larger of the sizes till the size is in limit.
+ */
+static void FixConfigMapSize()
+{
+ while (_settings_game.game_creation.map_x + _settings_game.game_creation.map_y > MAX_MAP_TILES_BITS) {
+ /* Repeat reducing larger of X/Y dimensions until the map size is within allowable limits */
+ if (_settings_game.game_creation.map_x > _settings_game.game_creation.map_y) {
+ _settings_game.game_creation.map_x--;
+ } else {
+ _settings_game.game_creation.map_y--;
+ }
+ }
+}
+
static void MakeNewGame(bool from_heightmap, bool reset_settings)
{
_game_mode = GM_NORMAL;
@@ -984,6 +1002,7 @@ static void MakeNewGame(bool from_heightmap, bool reset_settings)
ResetGRFConfig(true);
GenerateWorldSetCallback(&MakeNewGameDone);
+ FixConfigMapSize();
GenerateWorld(from_heightmap ? GWM_HEIGHTMAP : GWM_NEWGAME, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y, reset_settings);
}
@@ -999,6 +1018,7 @@ static void MakeNewEditorWorld()
ResetGRFConfig(true);
GenerateWorldSetCallback(&MakeNewEditorWorldDone);
+ FixConfigMapSize();
GenerateWorld(GWM_EMPTY, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y);
}
@@ -1144,6 +1164,7 @@ void SwitchToMode(SwitchMode new_mode)
case SM_LOAD_HEIGHTMAP: // Load heightmap from scenario editor
SetLocalCompany(OWNER_NONE);
+ FixConfigMapSize();
GenerateWorld(GWM_HEIGHTMAP, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y);
MarkWholeScreenDirty();
break;
@@ -1186,6 +1207,7 @@ void SwitchToMode(SwitchMode new_mode)
case SM_GENRANDLAND: // Generate random land within scenario editor
SetLocalCompany(OWNER_NONE);
+ FixConfigMapSize();
GenerateWorld(GWM_RANDOM, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y);
/* XXX: set date */
MarkWholeScreenDirty();
diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp
index 9df256bb2c..965efbdd33 100644
--- a/src/saveload/saveload.cpp
+++ b/src/saveload/saveload.cpp
@@ -894,9 +894,20 @@ void SlSetLength(size_t length)
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));
+ * The uppermost 4 bits are bits 24:27
+ *
+ * If we have more than 28 bits, use an extra uint32 and
+ * signal this using the extended chunk header */
+ assert(length < (1LL << 32));
+ if (length >= (1 << 28)) {
+ /* write out extended chunk header */
+ SlWriteByte(CH_EXT_HDR);
+ SlWriteUint32(static_cast(SLCEHF_BIG_RIFF));
+ }
SlWriteUint32((uint32)((length & 0xFFFFFF) | ((length >> 24) << 28)));
+ if (length >= (1 << 28)) {
+ SlWriteUint32(length >> 28);
+ }
break;
case CH_ARRAY:
assert(_sl.last_array_index <= _sl.array_index);
@@ -1667,6 +1678,16 @@ void SlAutolength(AutolengthProc *proc, void *arg)
if (offs != _sl.dumper->GetSize()) SlErrorCorrupt("Invalid chunk size");
}
+/*
+ * Notes on extended chunk header:
+ *
+ * If the chunk type is CH_EXT_HDR (15), then a u32 flags field follows.
+ * This flag field may define additional fields which follow the flags field in future.
+ * The standard chunk header follows, though it my be modified by the flags field.
+ * At present SLCEHF_BIG_RIFF increases the RIFF size limit to a theoretical 60 bits,
+ * by adding a further u32 field for the high bits after the existing RIFF size field.
+ */
+
/**
* Load a chunk of data (eg vehicles, stations, etc.)
* @param ch The chunkhandler that will be used for the operation
@@ -1680,6 +1701,15 @@ static void SlLoadChunk(const ChunkHandler *ch)
_sl.block_mode = m;
_sl.obj_len = 0;
+ SaveLoadChunkExtHeaderFlags ext_flags = static_cast(0);
+ if ((m & 0xF) == CH_EXT_HDR) {
+ ext_flags = static_cast(SlReadUint32());
+
+ /* read in real header */
+ m = SlReadByte();
+ _sl.block_mode = m;
+ }
+
switch (m) {
case CH_ARRAY:
_sl.array_index = 0;
@@ -1701,6 +1731,10 @@ static void SlLoadChunk(const ChunkHandler *ch)
}
len = SlReadUint32();
}
+ if (ext_flags & SLCEHF_BIG_RIFF) {
+ len |= SlReadUint32() << 28;
+ }
+
_sl.obj_len = len;
endoffs = _sl.reader->GetSize() + len;
ch->load_proc();
@@ -1726,9 +1760,21 @@ static void SlLoadCheckChunk(const ChunkHandler *ch)
_sl.block_mode = m;
_sl.obj_len = 0;
+ SaveLoadChunkExtHeaderFlags ext_flags = static_cast(0);
+ if ((m & 0xF) == CH_EXT_HDR) {
+ ext_flags = static_cast(SlReadUint32());
+
+ /* read in real header */
+ m = SlReadByte();
+ _sl.block_mode = m;
+ }
+
switch (m) {
case CH_ARRAY:
_sl.array_index = 0;
+ if (ext_flags) {
+ SlErrorCorruptFmt("CH_ARRAY does not take chunk header extension flags: 0x%X", ext_flags);
+ }
if (ch && ch->load_check_proc) {
ch->load_check_proc();
} else {
@@ -1736,6 +1782,9 @@ static void SlLoadCheckChunk(const ChunkHandler *ch)
}
break;
case CH_SPARSE_ARRAY:
+ if (ext_flags) {
+ SlErrorCorruptFmt("CH_SPARSE_ARRAY does not take chunk header extension flags: 0x%X", ext_flags);
+ }
if (ch && ch->load_check_proc) {
ch->load_check_proc();
} else {
@@ -1744,6 +1793,9 @@ static void SlLoadCheckChunk(const ChunkHandler *ch)
break;
default:
if ((m & 0xF) == CH_RIFF) {
+ if (ext_flags != (ext_flags & SLCEHF_BIG_RIFF)) {
+ SlErrorCorruptFmt("Unknown chunk header extension flags for CH_RIFF: 0x%X", ext_flags);
+ }
/* Read length */
len = (SlReadByte() << 16) | ((m >> 4) << 24);
len += SlReadUint16();
@@ -1752,6 +1804,14 @@ static void SlLoadCheckChunk(const ChunkHandler *ch)
SlErrorCorrupt("RIFF chunk too large");
}
len = SlReadUint32();
+ if (ext_flags & SLCEHF_BIG_RIFF) SlErrorCorrupt("XSLFI_RIFF_HEADER_60_BIT and SLCEHF_BIG_RIFF both present");
+ }
+ if (ext_flags & SLCEHF_BIG_RIFF) {
+ uint64 full_len = len | (static_cast(SlReadUint32()) << 28);
+ if (full_len >= (1LL << 32)) {
+ SlErrorCorrupt("Chunk size too large: " OTTD_PRINTFHEX64, full_len);
+ }
+ len = static_cast(full_len);
}
_sl.obj_len = len;
endoffs = _sl.reader->GetSize() + len;
diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h
index e34d4f3e9e..330cdde072 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -99,10 +99,17 @@ enum ChunkType {
CH_ARRAY = 1,
CH_SPARSE_ARRAY = 2,
CH_TYPE_MASK = 3,
+ CH_EXT_HDR = 15, ///< Extended chunk header
CH_LAST = 8, ///< Last chunk in this array.
CH_AUTO_LENGTH = 16,
};
+/** Flags for chunk extended headers */
+enum SaveLoadChunkExtHeaderFlags {
+ SLCEHF_BIG_RIFF = 1 << 0, ///< This block uses a 60-bit RIFF chunk size
+};
+DECLARE_ENUM_AS_BIT_SET(SaveLoadChunkExtHeaderFlags)
+
/**
* VarTypes is the general bitmasked magic type that tells us
* certain characteristics about the variable it refers to. For example