STARK: Add loading of replacement PNG files for the background images

The replacement PNG files can have larger dimensions when compared to
the original XMG images, enabling the creation of a high resolution mod.

The game looks for the replacement files in the game directory and then
in the xarc subdirectory of the directory containing the archive in
which the xmg picture to be replaced is located. For instance:
'1e/00/xarc/fountain_layercenter.png' should be used for the Venice park
background.
This commit is contained in:
Bastien Bouclet 2019-01-14 20:29:54 +01:00
parent 8b3b309200
commit 45c5cf0c80
13 changed files with 166 additions and 39 deletions

View file

@ -27,7 +27,8 @@ enum {
kDebugArchive = 1 << 0, kDebugArchive = 1 << 0,
kDebugXMG = 1 << 1, kDebugXMG = 1 << 1,
kDebugXRC = 1 << 2, kDebugXRC = 1 << 2,
kDebugUnknown = 1 << 3 kDebugModding = 1 << 3,
kDebugUnknown = 1 << 4
}; };
#endif // STARK_DEBUG_H #endif // STARK_DEBUG_H

View file

@ -27,6 +27,7 @@
#include "common/savefile.h" #include "common/savefile.h"
#include "common/system.h" #include "common/system.h"
#include "common/translation.h"
namespace Stark { namespace Stark {
@ -296,11 +297,27 @@ static const ADFileBasedFallback fileBasedFallback[] = {
{NULL, {NULL}} {NULL, {NULL}}
};*/ };*/
#define GAMEOPTION_ASSETS_MOD GUIO_GAMEOPTIONS1
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_ASSETS_MOD,
{
_s("Load modded assets"),
_s("Enable loading of external replacement assets."),
"enable_assets_mod",
true
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
class StarkMetaEngine : public AdvancedMetaEngine { class StarkMetaEngine : public AdvancedMetaEngine {
public: public:
StarkMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), starkGames) { StarkMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), starkGames, optionsList) {
_singleId = "stark"; _singleId = "stark";
_guiOptions = GUIO1(GUIO_NOMIDI); _guiOptions = GUIO2(GUIO_NOMIDI, GAMEOPTION_ASSETS_MOD);
} }
const char *getName() const override { const char *getName() const override {

View file

@ -33,55 +33,64 @@
namespace Stark { namespace Stark {
namespace Formats { namespace Formats {
XMGDecoder::XMGDecoder() : XMGDecoder::XMGDecoder(Common::ReadStream *stream) :
_width(0), _width(0),
_height(0), _height(0),
_currX(0), _currX(0),
_currY(0), _currY(0),
_stream(nullptr), _stream(stream),
_transColor(0) { _transColor(0) {
} }
Graphics::Surface *XMGDecoder::decode(Common::ReadStream *stream) { Graphics::Surface *XMGDecoder::decode(Common::ReadStream *stream) {
XMGDecoder dec; XMGDecoder dec(stream);
return dec.decodeImage(stream); dec.readHeader();
return dec.decodeImage();
} }
Graphics::Surface *XMGDecoder::decodeImage(Common::ReadStream *stream) { void XMGDecoder::readSize(Common::ReadStream *stream, uint &width, uint &height) {
_stream = stream; XMGDecoder dec(stream);
dec.readHeader();
width = dec._width;
height = dec._height;
}
void XMGDecoder::readHeader() {
// Read the file version // Read the file version
uint32 version = stream->readUint32LE(); uint32 version = _stream->readUint32LE();
if (version != 3) { if (version != 3) {
error("Stark::XMG: File version unknown: %d", version); error("Stark::XMG: File version unknown: %d", version);
} }
// Read the transparency color (RGBA) // Read the transparency color (RGBA)
_transColor = stream->readUint32LE(); _transColor = _stream->readUint32LE();
// Read the image size // Read the image size
_width = stream->readUint32LE(); _width = _stream->readUint32LE();
_height = stream->readUint32LE(); _height = _stream->readUint32LE();
debugC(10, kDebugXMG, "Stark::XMG: Version=%d, TransparencyColor=0x%08x, size=%dx%d", version, _transColor, _width, _height); debugC(10, kDebugXMG, "Stark::XMG: Version=%d, TransparencyColor=0x%08x, size=%dx%d", version, _transColor, _width, _height);
// Read the scan length // Read the scan length
uint32 scanLen = stream->readUint32LE(); uint32 scanLen = _stream->readUint32LE();
if (scanLen != 3 * _width) { if (scanLen != 3 * _width) {
error("Stark::XMG: The scan length (%d) doesn't match the width bytes (%d)", scanLen, 3 * _width); error("Stark::XMG: The scan length (%d) doesn't match the width bytes (%d)", scanLen, 3 * _width);
} }
// Unknown // Unknown
uint32 unknown2 = stream->readUint32LE(); uint32 unknown2 = _stream->readUint32LE();
debugC(kDebugUnknown, "Stark::XMG: unknown2 = %08x = %d", unknown2, unknown2); debugC(kDebugUnknown, "Stark::XMG: unknown2 = %08x = %d", unknown2, unknown2);
uint32 unknown3 = stream->readUint32LE(); uint32 unknown3 = _stream->readUint32LE();
debugC(kDebugUnknown, "Stark::XMG: unknown3 = %08x = %d", unknown3, unknown3); debugC(kDebugUnknown, "Stark::XMG: unknown3 = %08x = %d", unknown3, unknown3);
}
Graphics::Surface *XMGDecoder::decodeImage() {
// Create the destination surface // Create the destination surface
Graphics::Surface *surface = new Graphics::Surface(); Graphics::Surface *surface = new Graphics::Surface();
surface->create(_width, _height, Gfx::Driver::getRGBAPixelFormat()); surface->create(_width, _height, Gfx::Driver::getRGBAPixelFormat());
_currX = 0, _currY = 0; _currX = 0, _currY = 0;
while (!stream->eos()) { while (!_stream->eos()) {
if (_currX >= _width) { if (_currX >= _width) {
assert(_currX == _width); assert(_currX == _width);
_currX = 0; _currX = 0;
@ -91,12 +100,12 @@ Graphics::Surface *XMGDecoder::decodeImage(Common::ReadStream *stream) {
} }
// Read the number and mode of the tiles // Read the number and mode of the tiles
byte op = stream->readByte(); byte op = _stream->readByte();
uint16 count; uint16 count;
if ((op & 0xC0) != 0xC0) { if ((op & 0xC0) != 0xC0) {
count = op & 0x3F; count = op & 0x3F;
} else { } else {
count = ((op & 0xF) << 8) + stream->readByte(); count = ((op & 0xF) << 8) + _stream->readByte();
op <<= 2; op <<= 2;
} }
op &= 0xC0; op &= 0xC0;

View file

@ -38,16 +38,18 @@ namespace Formats {
class XMGDecoder { class XMGDecoder {
public: public:
static Graphics::Surface *decode(Common::ReadStream *stream); static Graphics::Surface *decode(Common::ReadStream *stream);
static void readSize(Common::ReadStream *stream, uint32 &width, uint32 &height);
private: private:
XMGDecoder(); explicit XMGDecoder(Common::ReadStream *stream);
struct Block { struct Block {
uint32 a1, a2; uint32 a1, a2;
uint32 b1, b2; uint32 b1, b2;
}; };
Graphics::Surface *decodeImage(Common::ReadStream *stream); void readHeader();
Graphics::Surface *decodeImage();
Block decodeBlock(byte op); Block decodeBlock(byte op);
void drawBlock(const Block &block, Graphics::Surface *surface); void drawBlock(const Block &block, Graphics::Surface *surface);

View file

@ -23,9 +23,12 @@
#include "engines/stark/resources/image.h" #include "engines/stark/resources/image.h"
#include "common/debug.h" #include "common/debug.h"
#include "image/png.h"
#include "engines/stark/debug.h"
#include "engines/stark/formats/xrc.h" #include "engines/stark/formats/xrc.h"
#include "engines/stark/services/archiveloader.h" #include "engines/stark/services/archiveloader.h"
#include "engines/stark/services/settings.h"
#include "engines/stark/services/services.h" #include "engines/stark/services/services.h"
#include "engines/stark/visual/effects/bubbles.h" #include "engines/stark/visual/effects/bubbles.h"
#include "engines/stark/visual/effects/fireflies.h" #include "engines/stark/visual/effects/fireflies.h"
@ -208,15 +211,48 @@ void ImageStill::initVisual() {
return; // No file to load return; // No file to load
} }
Common::ReadStream *stream = StarkArchiveLoader->getFile(_filename, _archiveName); Common::ReadStream *xmgStream = StarkArchiveLoader->getFile(_filename, _archiveName);
VisualImageXMG *xmg = new VisualImageXMG(StarkGfx); VisualImageXMG *visual = new VisualImageXMG(StarkGfx);
xmg->load(stream);
xmg->setHotSpot(_hotspot);
_visual = xmg; if (StarkSettings->isAssetsModEnabled() && loadPNGOverride(visual)) {
visual->readOriginalSize(xmgStream);
} else {
visual->load(xmgStream);
}
delete stream; visual->setHotSpot(_hotspot);
_visual = visual;
delete xmgStream;
}
bool ImageStill::loadPNGOverride(VisualImageXMG *visual) const {
if (!_filename.hasSuffixIgnoreCase(".xmg")) {
return false;
}
Common::String pngFilename = Common::String(_filename.c_str(), _filename.size() - 4) + ".png";
Common::String pngFilePath = StarkArchiveLoader->getExternalFilePath(pngFilename, _archiveName);
debugC(kDebugModding, "Attempting to load %s", pngFilePath.c_str());
Common::SeekableReadStream *pngStream = SearchMan.createReadStreamForMember(pngFilePath);
if (!pngStream) {
return false;
}
if (!visual->loadPNG(pngStream)) {
warning("Failed to load %s. It is not a valid PNG file.", pngFilePath.c_str());
delete pngStream;
return false;
}
debugC(kDebugModding, "Loaded %s", pngFilePath.c_str());
delete pngStream;
return true;
} }
void ImageStill::printData() { void ImageStill::printData() {

View file

@ -31,6 +31,7 @@
namespace Stark { namespace Stark {
class Visual; class Visual;
class VisualImageXMG;
namespace Formats { namespace Formats {
class XRCReadStream; class XRCReadStream;
} }
@ -109,6 +110,8 @@ protected:
// Image API // Image API
void initVisual() override; void initVisual() override;
bool loadPNGOverride(Stark::VisualImageXMG *visual) const;
bool _noName; bool _noName;
}; };

View file

@ -139,7 +139,7 @@ Common::String ArchiveLoader::buildArchiveName(Resources::Level *level, Resource
return archive; return archive;
} }
Common::SeekableReadStream *ArchiveLoader::getExternalFile(const Common::String &fileName, const Common::String &archiveName) const { Common::String ArchiveLoader::getExternalFilePath(const Common::String &fileName, const Common::String &archiveName) const {
static const char separator = '/'; static const char separator = '/';
// Build a path of the type 45/00/ // Build a path of the type 45/00/
@ -149,7 +149,11 @@ Common::SeekableReadStream *ArchiveLoader::getExternalFile(const Common::String
} }
filePath += "xarc/" + fileName; filePath += "xarc/" + fileName;
// Open the file return filePath;
}
Common::SeekableReadStream *ArchiveLoader::getExternalFile(const Common::String &fileName, const Common::String &archiveName) const {
Common::String filePath = getExternalFilePath(fileName, archiveName);
return SearchMan.createReadStreamForMember(filePath); return SearchMan.createReadStreamForMember(filePath);
} }

View file

@ -88,6 +88,7 @@ public:
/** Retrieve a file relative to a specified archive */ /** Retrieve a file relative to a specified archive */
Common::SeekableReadStream *getExternalFile(const Common::String &fileName, const Common::String &archiveName) const; Common::SeekableReadStream *getExternalFile(const Common::String &fileName, const Common::String &archiveName) const;
Common::String getExternalFilePath(const Common::String &fileName, const Common::String &archiveName) const;
private: private:
class LoadedArchive { class LoadedArchive {

View file

@ -84,4 +84,8 @@ void Settings::setIntSetting(IntSettingIndex index, int value) {
_mixer->setVolumeForSoundType(type, value); _mixer->setVolumeForSoundType(type, value);
} }
bool Settings::isAssetsModEnabled() const {
return ConfMan.getBool("enable_assets_mod");
}
} // End of namespace Stark } // End of namespace Stark

View file

@ -94,6 +94,9 @@ public:
/** Check whether the book of secrets is enabled */ /** Check whether the book of secrets is enabled */
bool hasBookOfSecrets() { return ConfMan.hasKey("xoBfOsterceS"); } bool hasBookOfSecrets() { return ConfMan.hasKey("xoBfOsterceS"); }
/** Should the game try to load external replacement assets? */
bool isAssetsModEnabled() const;
private: private:
Audio::Mixer *_mixer; Audio::Mixer *_mixer;
bool _hasLowRes; bool _hasLowRes;

View file

@ -65,6 +65,7 @@ StarkEngine::StarkEngine(OSystem *syst, const ADGameDescription *gameDesc) :
DebugMan.addDebugChannel(kDebugArchive, "Archive", "Debug the archive loading"); DebugMan.addDebugChannel(kDebugArchive, "Archive", "Debug the archive loading");
DebugMan.addDebugChannel(kDebugXMG, "XMG", "Debug the loading of XMG images"); DebugMan.addDebugChannel(kDebugXMG, "XMG", "Debug the loading of XMG images");
DebugMan.addDebugChannel(kDebugXRC, "XRC", "Debug the loading of XRC resource trees"); DebugMan.addDebugChannel(kDebugXRC, "XRC", "Debug the loading of XRC resource trees");
DebugMan.addDebugChannel(kDebugModding, "Modding", "Debug the loading of modded assets");
DebugMan.addDebugChannel(kDebugUnknown, "Unknown", "Debug unknown values on the data"); DebugMan.addDebugChannel(kDebugUnknown, "Unknown", "Debug unknown values on the data");
} }

View file

@ -23,6 +23,7 @@
#include "engines/stark/visual/image.h" #include "engines/stark/visual/image.h"
#include "graphics/surface.h" #include "graphics/surface.h"
#include "image/png.h"
#include "engines/stark/formats/xmg.h" #include "engines/stark/formats/xmg.h"
#include "engines/stark/gfx/driver.h" #include "engines/stark/gfx/driver.h"
@ -35,7 +36,9 @@ VisualImageXMG::VisualImageXMG(Gfx::Driver *gfx) :
Visual(TYPE), Visual(TYPE),
_gfx(gfx), _gfx(gfx),
_texture(nullptr), _texture(nullptr),
_surface(nullptr) { _surface(nullptr),
_originalWidth(0),
_originalHeight(0) {
_surfaceRenderer = _gfx->createSurfaceRenderer(); _surfaceRenderer = _gfx->createSurfaceRenderer();
} }
@ -58,6 +61,29 @@ void VisualImageXMG::load(Common::ReadStream *stream) {
// Decode the XMG // Decode the XMG
_surface = Formats::XMGDecoder::decode(stream); _surface = Formats::XMGDecoder::decode(stream);
_texture = _gfx->createTexture(_surface); _texture = _gfx->createTexture(_surface);
_originalWidth = _surface->w;
_originalHeight = _surface->h;
}
void VisualImageXMG::readOriginalSize(Common::ReadStream *stream) {
Formats::XMGDecoder::readSize(stream, _originalWidth, _originalHeight);
}
bool VisualImageXMG::loadPNG(Common::SeekableReadStream *stream) {
assert(!_surface && !_texture);
// Decode the XMG
Image::PNGDecoder pngDecoder;
if (!pngDecoder.loadStream(*stream)) {
return false;
}
_surface = pngDecoder.getSurface()->convertTo(Gfx::Driver::getRGBAPixelFormat());
_texture = _gfx->createTexture(_surface);
_texture->setSamplingFilter(Gfx::Texture::kLinear);
return true;
} }
void VisualImageXMG::render(const Common::Point &position, bool useOffset) { void VisualImageXMG::render(const Common::Point &position, bool useOffset) {
@ -68,11 +94,11 @@ void VisualImageXMG::render(const Common::Point &position, bool useOffset, bool
Common::Point drawPos = useOffset ? position - _hotspot : position; Common::Point drawPos = useOffset ? position - _hotspot : position;
if (!unscaled) { if (!unscaled) {
uint width = _gfx->scaleWidthOriginalToCurrent(_texture->width()); uint width = _gfx->scaleWidthOriginalToCurrent(_originalWidth);
uint height = _gfx->scaleHeightOriginalToCurrent(_texture->height()); uint height = _gfx->scaleHeightOriginalToCurrent(_originalHeight);
_surfaceRenderer->render(_texture, drawPos, width, height); _surfaceRenderer->render(_texture, drawPos, width, height);
} else { } else {
_surfaceRenderer->render(_texture, drawPos); _surfaceRenderer->render(_texture, drawPos, _originalWidth, _originalHeight);
} }
} }
@ -83,23 +109,27 @@ void VisualImageXMG::setFadeLevel(float fadeLevel) {
bool VisualImageXMG::isPointSolid(const Common::Point &point) const { bool VisualImageXMG::isPointSolid(const Common::Point &point) const {
assert(_surface); assert(_surface);
if (_surface->w < 32 || _surface->h < 32) { if (_originalWidth < 32 || _originalHeight < 32) {
return true; // Small images are always solid return true; // Small images are always solid
} }
Common::Point scaledPoint;
scaledPoint.x = point.x * _surface->w / _originalWidth;
scaledPoint.y = point.y * _surface->h / _originalHeight;
scaledPoint.x = CLIP<uint16>(scaledPoint.x, 0, _surface->w);
scaledPoint.y = CLIP<uint16>(scaledPoint.y, 0, _surface->h);
// Maybe implement this method in some other way to avoid having to keep the surface in memory // Maybe implement this method in some other way to avoid having to keep the surface in memory
const byte *ptr = (const byte *) _surface->getBasePtr(point.x, point.y); const byte *ptr = (const byte *) _surface->getBasePtr(scaledPoint.x, scaledPoint.y);
return *(ptr + 3) == 0xFF; return *(ptr + 3) == 0xFF;
} }
int VisualImageXMG::getWidth() const { int VisualImageXMG::getWidth() const {
assert(_surface); return _originalWidth;
return _surface->w;
} }
int VisualImageXMG::getHeight() const { int VisualImageXMG::getHeight() const {
assert(_surface); return _originalHeight;
return _surface->h;
} }
const Graphics::Surface *VisualImageXMG::getSurface() const { const Graphics::Surface *VisualImageXMG::getSurface() const {

View file

@ -50,7 +50,21 @@ public:
explicit VisualImageXMG(Gfx::Driver *gfx); explicit VisualImageXMG(Gfx::Driver *gfx);
~VisualImageXMG() override; ~VisualImageXMG() override;
/**
* Load the pixel data from a XMG image
*/
void load(Common::ReadStream *stream); void load(Common::ReadStream *stream);
/**
* Load the size from an XMG image
*/
void readOriginalSize(Common::ReadStream *stream);
/**
* Load the pixel data from a PNG image
*/
bool loadPNG(Common::SeekableReadStream *stream);
void render(const Common::Point &position, bool useOffset); void render(const Common::Point &position, bool useOffset);
void render(const Common::Point &position, bool useOffset, bool unscaled); void render(const Common::Point &position, bool useOffset, bool unscaled);
@ -83,6 +97,8 @@ private:
Gfx::Texture *_texture; Gfx::Texture *_texture;
Graphics::Surface *_surface; Graphics::Surface *_surface;
Common::Point _hotspot; Common::Point _hotspot;
uint _originalWidth;
uint _originalHeight;
}; };
} // End of namespace Stark } // End of namespace Stark