summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/base_media_base.h2
-rw-r--r--src/gfxinit.cpp44
-rw-r--r--src/newgrf.cpp126
-rw-r--r--src/newgrf.h2
-rw-r--r--src/newgrf_config.cpp26
-rw-r--r--src/newgrf_config.h2
-rw-r--r--src/newgrf_sound.cpp10
-rw-r--r--src/sound_type.h1
-rw-r--r--src/spritecache.cpp70
-rw-r--r--src/spritecache.h4
-rw-r--r--src/spriteloader/grf.cpp67
-rw-r--r--src/spriteloader/grf.hpp2
12 files changed, 323 insertions, 33 deletions
diff --git a/src/base_media_base.h b/src/base_media_base.h
index f8e9db95e..a5556f12b 100644
--- a/src/base_media_base.h
+++ b/src/base_media_base.h
@@ -223,6 +223,8 @@ struct GraphicsSet : BaseSet<GraphicsSet, MAX_GFT, true> {
BlitterType blitter; ///< Blitter of this graphics set
bool FillSetDetails(struct IniFile *ini, const char *path, const char *full_filename);
+
+ static MD5File::ChecksumResult CheckMD5(const MD5File *file, Subdirectory subdir);
};
/** All data/functions related with replacing the base graphics. */
diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp
index f98e7c670..b2feb63a8 100644
--- a/src/gfxinit.cpp
+++ b/src/gfxinit.cpp
@@ -52,7 +52,16 @@ static uint LoadGrfFile(const char *filename, uint load_index, int file_index)
DEBUG(sprite, 2, "Reading grf-file '%s'", filename);
- while (LoadNextSprite(load_index, file_index, sprite_id)) {
+ byte container_ver = GetGRFContainerVersion();
+ if (container_ver == 0) usererror("Base grf '%s' is corrupt", filename);
+ ReadGRFSpriteOffsets(container_ver);
+ if (container_ver >= 2) {
+ /* Read compression. */
+ byte compression = FioReadByte();
+ if (compression != 0) usererror("Unsupported compression format");
+ }
+
+ while (LoadNextSprite(load_index, file_index, sprite_id, container_ver)) {
load_index++;
sprite_id++;
if (load_index >= MAX_SPRITES) {
@@ -80,11 +89,20 @@ static void LoadGrfFileIndexed(const char *filename, const SpriteID *index_tbl,
DEBUG(sprite, 2, "Reading indexed grf-file '%s'", filename);
+ byte container_ver = GetGRFContainerVersion();
+ if (container_ver == 0) usererror("Base grf '%s' is corrupt", filename);
+ ReadGRFSpriteOffsets(container_ver);
+ if (container_ver >= 2) {
+ /* Read compression. */
+ byte compression = FioReadByte();
+ if (compression != 0) usererror("Unsupported compression format");
+ }
+
while ((start = *index_tbl++) != END) {
uint end = *index_tbl++;
do {
- bool b = LoadNextSprite(start, file_index, sprite_id);
+ bool b = LoadNextSprite(start, file_index, sprite_id, container_ver);
assert(b);
sprite_id++;
} while (++start <= end);
@@ -262,6 +280,28 @@ bool GraphicsSet::FillSetDetails(IniFile *ini, const char *path, const char *ful
return ret;
}
+/**
+ * Calculate and check the MD5 hash of the supplied GRF.
+ * @param file The file get the hash of.
+ * @param subdir The sub directory to get the files from.
+ * @return
+ * - #CR_MATCH if the MD5 hash matches
+ * - #CR_MISMATCH if the MD5 does not match
+ * - #CR_NO_FILE if the file misses
+ */
+/* static */ MD5File::ChecksumResult GraphicsSet::CheckMD5(const MD5File *file, Subdirectory subdir)
+{
+ size_t size = 0;
+ FILE *f = FioFOpenFile(file->filename, "rb", subdir, &size);
+ if (f == NULL) return MD5File::CR_NO_FILE;
+
+ size_t max = GRFGetSizeOfDataSection(f);
+
+ FioFCloseFile(f);
+
+ return file->CheckMD5(subdir, max);
+}
+
/**
* Calculate and check the MD5 hash of the supplied filename.
diff --git a/src/newgrf.cpp b/src/newgrf.cpp
index 18ea542a7..1a4894044 100644
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -97,6 +97,7 @@ public:
GRFFile *grffile; ///< Currently processed GRF file.
GRFConfig *grfconfig; ///< Config of the currently processed GRF file.
uint32 nfo_line; ///< Currently processed pseudo sprite number in the GRF.
+ byte grf_container_ver; ///< Container format of the current GRF file.
/* Kind of return values when processing certain actions */
int skip_sprites; ///< Number of psuedo sprites to skip before processing the next one. (-1 to skip to end of file)
@@ -4332,7 +4333,7 @@ static void NewSpriteSet(ByteReader *buf)
for (int i = 0; i < num_sets * num_ents; i++) {
_cur.nfo_line++;
- LoadNextSprite(_cur.spriteid++, _cur.file_index, _cur.nfo_line);
+ LoadNextSprite(_cur.spriteid++, _cur.file_index, _cur.nfo_line, _cur.grf_container_ver);
}
}
@@ -5425,16 +5426,16 @@ static void GraphicsNew(ByteReader *buf)
/* Special not-TTDP-compatible case used in openttd.grf
* Missing shore sprites and initialisation of SPR_SHORE_BASE */
grfmsg(2, "GraphicsNew: Loading 10 missing shore sprites from extra grf.");
- LoadNextSprite(SPR_SHORE_BASE + 0, _cur.file_index, _cur.nfo_line++); // SLOPE_STEEP_S
- LoadNextSprite(SPR_SHORE_BASE + 5, _cur.file_index, _cur.nfo_line++); // SLOPE_STEEP_W
- LoadNextSprite(SPR_SHORE_BASE + 7, _cur.file_index, _cur.nfo_line++); // SLOPE_WSE
- LoadNextSprite(SPR_SHORE_BASE + 10, _cur.file_index, _cur.nfo_line++); // SLOPE_STEEP_N
- LoadNextSprite(SPR_SHORE_BASE + 11, _cur.file_index, _cur.nfo_line++); // SLOPE_NWS
- LoadNextSprite(SPR_SHORE_BASE + 13, _cur.file_index, _cur.nfo_line++); // SLOPE_ENW
- LoadNextSprite(SPR_SHORE_BASE + 14, _cur.file_index, _cur.nfo_line++); // SLOPE_SEN
- LoadNextSprite(SPR_SHORE_BASE + 15, _cur.file_index, _cur.nfo_line++); // SLOPE_STEEP_E
- LoadNextSprite(SPR_SHORE_BASE + 16, _cur.file_index, _cur.nfo_line++); // SLOPE_EW
- LoadNextSprite(SPR_SHORE_BASE + 17, _cur.file_index, _cur.nfo_line++); // SLOPE_NS
+ LoadNextSprite(SPR_SHORE_BASE + 0, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_S
+ LoadNextSprite(SPR_SHORE_BASE + 5, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_W
+ LoadNextSprite(SPR_SHORE_BASE + 7, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_WSE
+ LoadNextSprite(SPR_SHORE_BASE + 10, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_N
+ LoadNextSprite(SPR_SHORE_BASE + 11, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_NWS
+ LoadNextSprite(SPR_SHORE_BASE + 13, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_ENW
+ LoadNextSprite(SPR_SHORE_BASE + 14, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_SEN
+ LoadNextSprite(SPR_SHORE_BASE + 15, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_E
+ LoadNextSprite(SPR_SHORE_BASE + 16, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_EW
+ LoadNextSprite(SPR_SHORE_BASE + 17, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_NS
if (_loaded_newgrf_features.shore == SHORE_REPLACE_NONE) _loaded_newgrf_features.shore = SHORE_REPLACE_ONLY_NEW;
return;
}
@@ -5473,7 +5474,7 @@ static void GraphicsNew(ByteReader *buf)
for (; num > 0; num--) {
_cur.nfo_line++;
- LoadNextSprite(replace == 0 ? _cur.spriteid++ : replace++, _cur.file_index, _cur.nfo_line);
+ LoadNextSprite(replace == 0 ? _cur.spriteid++ : replace++, _cur.file_index, _cur.nfo_line, _cur.grf_container_ver);
}
if (type == 0x0D) _loaded_newgrf_features.shore = SHORE_REPLACE_ACTION_5;
@@ -5723,7 +5724,7 @@ static void CfgApply(ByteReader *buf)
/* Preload the next sprite */
size_t pos = FioGetPos();
- uint16 num = FioReadWord();
+ uint32 num = _cur.grf_container_ver >= 2 ? FioReadDword() : FioReadWord();
uint8 type = FioReadByte();
byte *preload_sprite = NULL;
@@ -6067,7 +6068,7 @@ static void SpriteReplace(ByteReader *buf)
for (uint j = 0; j < num_sprites; j++) {
int load_index = first_sprite + j;
_cur.nfo_line++;
- LoadNextSprite(load_index, _cur.file_index, _cur.nfo_line); // XXX
+ LoadNextSprite(load_index, _cur.file_index, _cur.nfo_line, _cur.grf_container_ver); // XXX
/* Shore sprites now located at different addresses.
* So detect when the old ones get replaced. */
@@ -6846,8 +6847,12 @@ static void LoadGRFSound(size_t offs)
sound->volume = 0x80;
sound->priority = 0;
- sound->file_slot = _cur.file_index;
- sound->file_offset = offs;
+ if (offs != SIZE_MAX) {
+ /* Sound is present in the NewGRF. */
+ sound->file_slot = _cur.file_index;
+ sound->file_offset = offs;
+ sound->grf_container_ver = _cur.grf_container_ver;
+ }
}
/* Action 0x11 */
@@ -6869,9 +6874,21 @@ static void GRFSound(ByteReader *buf)
size_t offs = FioGetPos();
- uint16 len = FioReadWord();
+ uint32 len = _cur.grf_container_ver >= 2 ? FioReadDword() : FioReadWord();
byte type = FioReadByte();
+ if (_cur.grf_container_ver >= 2 && type == 0xFD) {
+ /* Reference to sprite section. */
+ if (len != 4) {
+ grfmsg(1, "GRFSound: Invalid sprite section import");
+ FioSkipBytes(len);
+ } else {
+ uint32 id = FioReadDword();
+ if (_cur.stage == GLS_INIT) LoadGRFSound(GetGRFSpriteOffset(id));
+ }
+ continue;
+ }
+
if (type != 0xFF) {
grfmsg(1, "GRFSound: Unexpected RealSprite found, skipping");
FioSkipBytes(7);
@@ -6883,7 +6900,13 @@ static void GRFSound(ByteReader *buf)
switch (action) {
case 0xFF:
/* Allocate sound only in init stage. */
- if (_cur.stage == GLS_INIT) LoadGRFSound(offs);
+ if (_cur.stage == GLS_INIT) {
+ if (_cur.grf_container_ver >= 2) {
+ grfmsg(1, "GRFSound: Inline sounds are not supported for container version >= 2");
+ } else {
+ LoadGRFSound(offs);
+ }
+ }
FioSkipBytes(len - 1); // <type> is not included in the length for pseudo-sprites.
break;
@@ -6944,7 +6967,7 @@ static void LoadFontGlyph(ByteReader *buf)
for (uint c = 0; c < num_char; c++) {
if (size < FS_END) SetUnicodeGlyph(size, base_char + c, _cur.spriteid);
_cur.nfo_line++;
- LoadNextSprite(_cur.spriteid++, _cur.file_index, _cur.nfo_line);
+ LoadNextSprite(_cur.spriteid++, _cur.file_index, _cur.nfo_line, _cur.grf_container_ver);
}
}
}
@@ -8560,6 +8583,32 @@ static void DecodeSpecialSprite(byte *buf, uint num, GrfLoadingStage stage)
}
+/** Signature of a container version 2 GRF. */
+extern const byte _grf_cont_v2_sig[8] = {'G', 'R', 'F', 0x82, 0x0D, 0x0A, 0x1A, 0x0A};
+
+/**
+ * Get the container version of the currently opened GRF file.
+ * @return Container version of the GRF file or 0 if the file is corrupt/no GRF file.
+ */
+byte GetGRFContainerVersion()
+{
+ size_t pos = FioGetPos();
+
+ if (FioReadWord() == 0) {
+ /* Check for GRF container version 2, which is identified by the bytes
+ * '47 52 46 82 0D 0A 1A 0A' at the start of the file. */
+ for (uint i = 0; i < lengthof(_grf_cont_v2_sig); i++) {
+ if (FioReadByte() != _grf_cont_v2_sig[i]) return 0; // Invalid format
+ }
+
+ return 2;
+ }
+
+ /* Container version 1 has no header, rewind to start. */
+ FioSeekTo(pos, SEEK_SET);
+ return 1;
+}
+
/**
* Load a particular NewGRF.
* @param config The configuration of the to be loaded NewGRF.
@@ -8570,7 +8619,6 @@ static void DecodeSpecialSprite(byte *buf, uint num, GrfLoadingStage stage)
void LoadNewGRFFile(GRFConfig *config, uint file_index, GrfLoadingStage stage, Subdirectory subdir)
{
const char *filename = config->filename;
- uint16 num;
/* A .grf file is activated only if it was active when the game was
* started. If a game is loaded, only its active .grfs will be
@@ -8604,10 +8652,35 @@ void LoadNewGRFFile(GRFConfig *config, uint file_index, GrfLoadingStage stage, S
DEBUG(grf, 2, "LoadNewGRFFile: Reading NewGRF-file '%s'", filename);
+ _cur.grf_container_ver = GetGRFContainerVersion();
+ if (_cur.grf_container_ver == 0) {
+ DEBUG(grf, 7, "LoadNewGRFFile: Custom .grf has invalid format");
+ return;
+ }
+
+ if (stage == GLS_INIT || stage == GLS_ACTIVATION) {
+ /* We need the sprite offsets in the init stage for NewGRF sounds
+ * and in the activation stage for real sprites. */
+ ReadGRFSpriteOffsets(_cur.grf_container_ver);
+ } else {
+ /* Skip sprite section offset if present. */
+ if (_cur.grf_container_ver >= 2) FioReadDword();
+ }
+
+ if (_cur.grf_container_ver >= 2) {
+ /* Read compression value. */
+ byte compression = FioReadByte();
+ if (compression != 0) {
+ DEBUG(grf, 7, "LoadNewGRFFile: Unsupported compression format");
+ return;
+ }
+ }
+
/* Skip the first sprite; we don't care about how many sprites this
* does contain; newest TTDPatches and George's longvehicles don't
* neither, apparently. */
- if (FioReadWord() == 4 && FioReadByte() == 0xFF) {
+ uint32 num = _cur.grf_container_ver >= 2 ? FioReadDword() : FioReadWord();
+ if (num == 4 && FioReadByte() == 0xFF) {
FioReadDword();
} else {
DEBUG(grf, 7, "LoadNewGRFFile: Custom .grf has invalid format");
@@ -8618,7 +8691,7 @@ void LoadNewGRFFile(GRFConfig *config, uint file_index, GrfLoadingStage stage, S
ReusableBuffer<byte> buf;
- while ((num = FioReadWord()) != 0) {
+ while ((num = (_cur.grf_container_ver >= 2 ? FioReadDword() : FioReadWord())) != 0) {
byte type = FioReadByte();
_cur.nfo_line++;
@@ -8640,8 +8713,13 @@ void LoadNewGRFFile(GRFConfig *config, uint file_index, GrfLoadingStage stage, S
break;
}
- FioSkipBytes(7);
- SkipSpriteData(type, num - 8);
+ if (_cur.grf_container_ver >= 2 && type == 0xFD) {
+ /* Reference to data section. Container version >= 2 only. */
+ FioSkipBytes(num);
+ } else {
+ FioSkipBytes(7);
+ SkipSpriteData(type, num - 8);
+ }
}
if (_cur.skip_sprites > 0) _cur.skip_sprites--;
diff --git a/src/newgrf.h b/src/newgrf.h
index 74a95f8b8..57ce45d1c 100644
--- a/src/newgrf.h
+++ b/src/newgrf.h
@@ -177,6 +177,8 @@ static inline bool HasGrfMiscBit(GrfMiscBit bit)
/* Indicates which are the newgrf features currently loaded ingame */
extern GRFLoadedFeatures _loaded_newgrf_features;
+byte GetGRFContainerVersion();
+
void LoadNewGRFFile(struct GRFConfig *config, uint file_index, GrfLoadingStage stage, Subdirectory subdir);
void LoadNewGRF(uint load_index, uint file_index);
void ReloadNewGRFData(); // in saveload/afterload.cpp
diff --git a/src/newgrf_config.cpp b/src/newgrf_config.cpp
index f644315f1..4c296d20f 100644
--- a/src/newgrf_config.cpp
+++ b/src/newgrf_config.cpp
@@ -294,6 +294,28 @@ bool UpdateNewGRFConfigPalette(int32 p1)
}
/**
+ * Get the data section size of a GRF.
+ * @param f GRF.
+ * @return Size of the data section or SIZE_MAX if the file has no separate data section.
+ */
+size_t GRFGetSizeOfDataSection(FILE *f)
+{
+ extern const byte _grf_cont_v2_sig[];
+ static const uint header_len = 14;
+
+ byte data[header_len];
+ if (fread(data, 1, header_len, f) == header_len) {
+ if (data[0] == 0 && data[1] == 0 && MemCmpT(data + 2, _grf_cont_v2_sig, 8) == 0) {
+ /* Valid container version 2, get data section size. */
+ size_t offset = (data[13] << 24) | (data[12] << 16) | (data[11] << 8) | data[10];
+ return header_len + offset;
+ }
+ }
+
+ return SIZE_MAX;
+}
+
+/**
* Calculate the MD5 sum for a GRF, and store it in the config.
* @param config GRF to compute.
* @param subdir The subdirectory to look in.
@@ -310,6 +332,10 @@ static bool CalcGRFMD5Sum(GRFConfig *config, Subdirectory subdir)
f = FioFOpenFile(config->filename, "rb", subdir, &size);
if (f == NULL) return false;
+ size_t start = ftell(f);
+ size = min(size, GRFGetSizeOfDataSection(f));
+ fseek(f, start, SEEK_SET);
+
/* calculate md5sum */
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
size -= len;
diff --git a/src/newgrf_config.h b/src/newgrf_config.h
index a7c7da2ad..038b18f86 100644
--- a/src/newgrf_config.h
+++ b/src/newgrf_config.h
@@ -218,6 +218,8 @@ struct NewGRFScanCallback {
virtual void OnNewGRFsScanned() = 0;
};
+size_t GRFGetSizeOfDataSection(FILE *f);
+
void ScanNewGRFFiles(NewGRFScanCallback *callback);
void CheckForMissingSprites();
const GRFConfig *FindGRFConfig(uint32 grfid, FindGRFConfigMode mode, const uint8 *md5sum = NULL, uint32 desired_version = 0);
diff --git a/src/newgrf_sound.cpp b/src/newgrf_sound.cpp
index 401c77386..5abd443c9 100644
--- a/src/newgrf_sound.cpp
+++ b/src/newgrf_sound.cpp
@@ -64,9 +64,13 @@ bool LoadNewGRFSound(SoundEntry *sound)
FioSeekToFile(sound->file_slot, sound->file_offset);
+ /* Skip ID for container version >= 2 as we only look at the first
+ * entry and ignore any further entries with the same ID. */
+ if (sound->grf_container_ver >= 2) FioReadDword();
+
/* Format: <num> <FF> <FF> <name_len> <name> '\0' <data> */
- uint16 num = FioReadWord();
+ uint32 num = sound->grf_container_ver >= 2 ? FioReadDword() : FioReadWord();
if (FioReadByte() != 0xFF) return false;
if (FioReadByte() != 0xFF) return false;
@@ -88,7 +92,9 @@ bool LoadNewGRFSound(SoundEntry *sound)
}
uint32 total_size = FioReadDword();
- if (total_size + name_len + 11 > num) { // The first FF in the sprite is not counted for <num>.
+ uint header_size = 11;
+ if (sound->grf_container_ver >= 2) header_size++; // The first FF in the sprite is only counted for container version >= 2.
+ if (total_size + name_len + header_size > num) {
DEBUG(grf, 1, "LoadNewGRFSound [%s]: RIFF was truncated", FioGetFilename(sound->file_slot));
return false;
}
diff --git a/src/sound_type.h b/src/sound_type.h
index 4eac434f3..b014c2694 100644
--- a/src/sound_type.h
+++ b/src/sound_type.h
@@ -21,6 +21,7 @@ struct SoundEntry {
uint8 channels;
uint8 volume;
uint8 priority;
+ byte grf_container_ver; ///< NewGRF container version if the sound is from a NewGRF.
};
enum SoundFx {
diff --git a/src/spritecache.cpp b/src/spritecache.cpp
index 609d92acb..050612cbb 100644
--- a/src/spritecache.cpp
+++ b/src/spritecache.cpp
@@ -35,6 +35,7 @@ struct SpriteCache {
int16 lru;
SpriteTypeByte type; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble.
bool warned; ///< True iff the user has been warned about incorrect use of this sprite
+ byte container_ver; ///< Container version of the GRF the sprite is from.
};
@@ -227,7 +228,7 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
#endif /* WITH_PNG */
}
- SpriteLoaderGrf sprite_loader;
+ SpriteLoaderGrf sprite_loader(sc->container_ver);
SpriteLoader::Sprite sprite;
sprite.type = sprite_type;
@@ -269,12 +270,62 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
}
-bool LoadNextSprite(int load_index, byte file_slot, uint file_sprite_id)
+/** */
+static std::map<uint32, size_t> _grf_sprite_offsets;
+
+/**
+ * Get the file offset for a specific sprite in the sprite section of a GRF.
+ * @param id ID of the sprite to look up.
+ * @return Position of the sprite in the sprite section or SIZE_MAX if no such sprite is present.
+ */
+size_t GetGRFSpriteOffset(uint32 id)
+{
+ return _grf_sprite_offsets.find(id) != _grf_sprite_offsets.end() ? _grf_sprite_offsets[id] : SIZE_MAX;
+}
+
+/**
+ * Parse the sprite section of GRFs.
+ * @param container_version Container version of the GRF we're currently processing.
+ */
+void ReadGRFSpriteOffsets(byte container_version)
+{
+ _grf_sprite_offsets.clear();
+
+ if (container_version >= 2) {
+ /* Seek to sprite section of the GRF. */
+ size_t data_offset = FioReadDword();
+ size_t old_pos = FioGetPos();
+ FioSeekTo(data_offset, SEEK_CUR);
+
+ /* Loop over all sprite section entries and store the file
+ * offset for each newly encountered ID. */
+ uint32 id, prev_id = 0;
+ while ((id = FioReadDword()) != 0) {
+ if (id != prev_id) _grf_sprite_offsets[id] = FioGetPos() - 4;
+ prev_id = id;
+ FioSkipBytes(FioReadDword());
+ }
+
+ /* Continue processing the data section. */
+ FioSeekTo(old_pos, SEEK_SET);
+ }
+}
+
+
+/**
+ * Load a real or recolour sprite.
+ * @param load_index Global sprite index.
+ * @param file_slot GRF to load from.
+ * @param file_sprite_id Sprite number in the GRF.
+ * @param container_version Container version of the GRF.
+ * @return True if a valid sprite was loaded, false on any error.
+ */
+bool LoadNextSprite(int load_index, byte file_slot, uint file_sprite_id, byte container_version)
{
size_t file_pos = FioGetPos();
/* Read sprite header. */
- uint16 num = FioReadWord();
+ uint32 num = container_version >= 2 ? FioReadDword() : FioReadWord();
if (num == 0) return false;
byte grf_type = FioReadByte();
@@ -289,9 +340,20 @@ bool LoadNextSprite(int load_index, byte file_slot, uint file_sprite_id)
}
type = ST_RECOLOUR;
data = ReadRecolourSprite(file_slot, num);
+ } else if (container_version >= 2 && grf_type == 0xFD) {
+ if (num != 4) {
+ /* Invalid sprite section include, ignore. */
+ FioSkipBytes(num);
+ return false;
+ }
+ /* It is not an error if no sprite with the provided ID is found in the sprite section. */
+ file_pos = GetGRFSpriteOffset(FioReadDword());
+ type = ST_NORMAL;
} else {
FioSkipBytes(7);
type = SkipSpriteData(grf_type, num - 8) ? ST_NORMAL : ST_INVALID;
+ /* Inline sprites are not supported for container version >= 2. */
+ if (container_version >= 2) return false;
}
if (type == ST_INVALID) return false;
@@ -315,6 +377,7 @@ bool LoadNextSprite(int load_index, byte file_slot, uint file_sprite_id)
sc->id = file_sprite_id;
sc->type = type;
sc->warned = false;
+ sc->container_ver = container_version;
return true;
}
@@ -331,6 +394,7 @@ void DupSprite(SpriteID old_spr, SpriteID new_spr)
scnew->id = scold->id;
scnew->type = scold->type;
scnew->warned = false;
+ scnew->container_ver = scold->container_ver;
}
/**
diff --git a/src/spritecache.h b/src/spritecache.h
index 02be3b4ca..803bdb32c 100644
--- a/src/spritecache.h
+++ b/src/spritecache.h
@@ -51,7 +51,9 @@ void GfxInitSpriteMem();
void GfxClearSpriteCache();
void IncreaseSpriteLRU();
-bool LoadNextSprite(int load_index, byte file_index, uint file_sprite_id);
+void ReadGRFSpriteOffsets(byte container_version);
+size_t GetGRFSpriteOffset(uint32 id);
+bool LoadNextSprite(int load_index, byte file_index, uint file_sprite_id, byte container_version);
bool SkipSpriteData(byte type, uint16 num);
void DupSprite(SpriteID old_spr, SpriteID new_spr);
diff --git a/src/spriteloader/grf.cpp b/src/spriteloader/grf.cpp
index 84ed98b94..673647ba4 100644
--- a/src/spriteloader/grf.cpp
+++ b/src/spriteloader/grf.cpp
@@ -22,6 +22,15 @@
extern const byte _palmap_w2d[];
+/** The different colour components a sprite can have. */
+enum SpriteColourComponent {
+ SCC_RGB = 1 << 0, ///< Sprite has RGB.
+ SCC_ALPHA = 1 << 1, ///< Sprite has alpha.
+ SCC_PAL = 1 << 2, ///< Sprite has palette data.
+ SCC_MASK = SCC_RGB | SCC_ALPHA | SCC_PAL, ///< Mask of valid colour bits.
+};
+DECLARE_ENUM_AS_BIT_SET(SpriteColourComponent)
+
/**
* We found a corrupted sprite. This means that the sprite itself
* contains invalid data or is too small for the given dimensions.
@@ -176,7 +185,7 @@ bool DecodeSingleSprite(SpriteLoader::Sprite *sprite, uint8 file_slot, size_t fi
return true;
}
-bool SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, uint8 file_slot, size_t file_pos, SpriteType sprite_type)
+bool LoadSpriteV1(SpriteLoader::Sprite *sprite, uint8 file_slot, size_t file_pos, SpriteType sprite_type)
{
/* Open the right file and go to the correct position */
FioSeekToFile(file_slot, file_pos);
@@ -199,3 +208,59 @@ bool SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, uint8 file_slot,
return DecodeSingleSprite(sprite, file_slot, file_pos, sprite_type, num, type);
}
+
+bool LoadSpriteV2(SpriteLoader::Sprite *sprite, uint8 file_slot, size_t file_pos, SpriteType sprite_type)
+{
+ /* Is the sprite not present/stripped in the GRF? */
+ if (file_pos == SIZE_MAX) return false;
+
+ /* Open the right file and go to the correct position */
+ FioSeekToFile(file_slot, file_pos);
+
+ uint32 id = FioReadDword();
+
+ do {
+ int64 num = FioReadDword();
+ size_t start_pos = FioGetPos();
+ byte type = FioReadByte();
+
+ /* Type 0xFF indicates either a colourmap or some other non-sprite info; we do not handle them here. */
+ if (type == 0xFF) return false;
+
+ byte colour = type & SCC_MASK;
+ byte zoom = FioReadByte();
+
+ if (colour == SCC_PAL && zoom == 0) {
+ sprite->height = FioReadWord();
+ sprite->width = FioReadWord();
+ sprite->x_offs = FioReadWord();
+ sprite->y_offs = FioReadWord();
+
+ /* Mask out colour information. */
+ type = type & ~SCC_MASK;
+
+ /* For chunked encoding we store the decompressed size in the file,
+ * otherwise we can calculate it from the image dimensions. */
+ uint decomp_size = (type & 0x08) ? FioReadDword() : sprite->width * sprite->height;
+
+ bool valid = DecodeSingleSprite(sprite, file_slot, file_pos, sprite_type, decomp_size, type);
+ if (FioGetPos() != start_pos + num) return WarnCorruptSprite(file_slot, file_pos, __LINE__);
+ return valid;
+ } else {
+ /* Not the wanted zoom level or colour depth, continue searching. */
+ FioSkipBytes(num - 2);
+ }
+
+ } while (FioReadDword() == id);
+
+ return false;
+}
+
+bool SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, uint8 file_slot, size_t file_pos, SpriteType sprite_type)
+{
+ if (this->container_ver >= 2) {
+ return LoadSpriteV2(sprite, file_slot, file_pos, sprite_type);
+ } else {
+ return LoadSpriteV1(sprite, file_slot, file_pos, sprite_type);
+ }
+}
diff --git a/src/spriteloader/grf.hpp b/src/spriteloader/grf.hpp
index be41cfd06..97e51c0a1 100644
--- a/src/spriteloader/grf.hpp
+++ b/src/spriteloader/grf.hpp
@@ -16,7 +16,9 @@
/** Sprite loader for graphics coming from a (New)GRF. */
class SpriteLoaderGrf : public SpriteLoader {
+ byte container_ver;
public:
+ SpriteLoaderGrf(byte container_ver) : container_ver(container_ver) {}
bool LoadSprite(SpriteLoader::Sprite *sprite, uint8 file_slot, size_t file_pos, SpriteType sprite_type);
};