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:
parent
8b3b309200
commit
45c5cf0c80
13 changed files with 166 additions and 39 deletions
|
@ -27,7 +27,8 @@ enum {
|
|||
kDebugArchive = 1 << 0,
|
||||
kDebugXMG = 1 << 1,
|
||||
kDebugXRC = 1 << 2,
|
||||
kDebugUnknown = 1 << 3
|
||||
kDebugModding = 1 << 3,
|
||||
kDebugUnknown = 1 << 4
|
||||
};
|
||||
|
||||
#endif // STARK_DEBUG_H
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "common/savefile.h"
|
||||
#include "common/system.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
namespace Stark {
|
||||
|
||||
|
@ -296,11 +297,27 @@ static const ADFileBasedFallback fileBasedFallback[] = {
|
|||
{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 {
|
||||
public:
|
||||
StarkMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), starkGames) {
|
||||
StarkMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), starkGames, optionsList) {
|
||||
_singleId = "stark";
|
||||
_guiOptions = GUIO1(GUIO_NOMIDI);
|
||||
_guiOptions = GUIO2(GUIO_NOMIDI, GAMEOPTION_ASSETS_MOD);
|
||||
}
|
||||
|
||||
const char *getName() const override {
|
||||
|
|
|
@ -33,55 +33,64 @@
|
|||
namespace Stark {
|
||||
namespace Formats {
|
||||
|
||||
XMGDecoder::XMGDecoder() :
|
||||
XMGDecoder::XMGDecoder(Common::ReadStream *stream) :
|
||||
_width(0),
|
||||
_height(0),
|
||||
_currX(0),
|
||||
_currY(0),
|
||||
_stream(nullptr),
|
||||
_stream(stream),
|
||||
_transColor(0) {
|
||||
}
|
||||
|
||||
Graphics::Surface *XMGDecoder::decode(Common::ReadStream *stream) {
|
||||
XMGDecoder dec;
|
||||
return dec.decodeImage(stream);
|
||||
XMGDecoder dec(stream);
|
||||
dec.readHeader();
|
||||
return dec.decodeImage();
|
||||
}
|
||||
|
||||
Graphics::Surface *XMGDecoder::decodeImage(Common::ReadStream *stream) {
|
||||
_stream = stream;
|
||||
void XMGDecoder::readSize(Common::ReadStream *stream, uint &width, uint &height) {
|
||||
XMGDecoder dec(stream);
|
||||
dec.readHeader();
|
||||
|
||||
width = dec._width;
|
||||
height = dec._height;
|
||||
}
|
||||
|
||||
void XMGDecoder::readHeader() {
|
||||
// Read the file version
|
||||
uint32 version = stream->readUint32LE();
|
||||
uint32 version = _stream->readUint32LE();
|
||||
if (version != 3) {
|
||||
error("Stark::XMG: File version unknown: %d", version);
|
||||
}
|
||||
|
||||
// Read the transparency color (RGBA)
|
||||
_transColor = stream->readUint32LE();
|
||||
_transColor = _stream->readUint32LE();
|
||||
|
||||
// Read the image size
|
||||
_width = stream->readUint32LE();
|
||||
_height = stream->readUint32LE();
|
||||
_width = _stream->readUint32LE();
|
||||
_height = _stream->readUint32LE();
|
||||
debugC(10, kDebugXMG, "Stark::XMG: Version=%d, TransparencyColor=0x%08x, size=%dx%d", version, _transColor, _width, _height);
|
||||
|
||||
// Read the scan length
|
||||
uint32 scanLen = stream->readUint32LE();
|
||||
uint32 scanLen = _stream->readUint32LE();
|
||||
if (scanLen != 3 * _width) {
|
||||
error("Stark::XMG: The scan length (%d) doesn't match the width bytes (%d)", scanLen, 3 * _width);
|
||||
}
|
||||
|
||||
// Unknown
|
||||
uint32 unknown2 = stream->readUint32LE();
|
||||
uint32 unknown2 = _stream->readUint32LE();
|
||||
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);
|
||||
}
|
||||
|
||||
Graphics::Surface *XMGDecoder::decodeImage() {
|
||||
// Create the destination surface
|
||||
Graphics::Surface *surface = new Graphics::Surface();
|
||||
surface->create(_width, _height, Gfx::Driver::getRGBAPixelFormat());
|
||||
|
||||
_currX = 0, _currY = 0;
|
||||
while (!stream->eos()) {
|
||||
while (!_stream->eos()) {
|
||||
if (_currX >= _width) {
|
||||
assert(_currX == _width);
|
||||
_currX = 0;
|
||||
|
@ -91,12 +100,12 @@ Graphics::Surface *XMGDecoder::decodeImage(Common::ReadStream *stream) {
|
|||
}
|
||||
|
||||
// Read the number and mode of the tiles
|
||||
byte op = stream->readByte();
|
||||
byte op = _stream->readByte();
|
||||
uint16 count;
|
||||
if ((op & 0xC0) != 0xC0) {
|
||||
count = op & 0x3F;
|
||||
} else {
|
||||
count = ((op & 0xF) << 8) + stream->readByte();
|
||||
count = ((op & 0xF) << 8) + _stream->readByte();
|
||||
op <<= 2;
|
||||
}
|
||||
op &= 0xC0;
|
||||
|
|
|
@ -38,16 +38,18 @@ namespace Formats {
|
|||
class XMGDecoder {
|
||||
public:
|
||||
static Graphics::Surface *decode(Common::ReadStream *stream);
|
||||
static void readSize(Common::ReadStream *stream, uint32 &width, uint32 &height);
|
||||
|
||||
private:
|
||||
XMGDecoder();
|
||||
explicit XMGDecoder(Common::ReadStream *stream);
|
||||
|
||||
struct Block {
|
||||
uint32 a1, a2;
|
||||
uint32 b1, b2;
|
||||
};
|
||||
|
||||
Graphics::Surface *decodeImage(Common::ReadStream *stream);
|
||||
void readHeader();
|
||||
Graphics::Surface *decodeImage();
|
||||
Block decodeBlock(byte op);
|
||||
void drawBlock(const Block &block, Graphics::Surface *surface);
|
||||
|
||||
|
|
|
@ -23,9 +23,12 @@
|
|||
#include "engines/stark/resources/image.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "image/png.h"
|
||||
|
||||
#include "engines/stark/debug.h"
|
||||
#include "engines/stark/formats/xrc.h"
|
||||
#include "engines/stark/services/archiveloader.h"
|
||||
#include "engines/stark/services/settings.h"
|
||||
#include "engines/stark/services/services.h"
|
||||
#include "engines/stark/visual/effects/bubbles.h"
|
||||
#include "engines/stark/visual/effects/fireflies.h"
|
||||
|
@ -208,15 +211,48 @@ void ImageStill::initVisual() {
|
|||
return; // No file to load
|
||||
}
|
||||
|
||||
Common::ReadStream *stream = StarkArchiveLoader->getFile(_filename, _archiveName);
|
||||
Common::ReadStream *xmgStream = StarkArchiveLoader->getFile(_filename, _archiveName);
|
||||
|
||||
VisualImageXMG *xmg = new VisualImageXMG(StarkGfx);
|
||||
xmg->load(stream);
|
||||
xmg->setHotSpot(_hotspot);
|
||||
VisualImageXMG *visual = new VisualImageXMG(StarkGfx);
|
||||
|
||||
_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() {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
namespace Stark {
|
||||
|
||||
class Visual;
|
||||
class VisualImageXMG;
|
||||
namespace Formats {
|
||||
class XRCReadStream;
|
||||
}
|
||||
|
@ -109,6 +110,8 @@ protected:
|
|||
// Image API
|
||||
void initVisual() override;
|
||||
|
||||
bool loadPNGOverride(Stark::VisualImageXMG *visual) const;
|
||||
|
||||
bool _noName;
|
||||
};
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ Common::String ArchiveLoader::buildArchiveName(Resources::Level *level, Resource
|
|||
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 = '/';
|
||||
|
||||
// Build a path of the type 45/00/
|
||||
|
@ -149,7 +149,11 @@ Common::SeekableReadStream *ArchiveLoader::getExternalFile(const Common::String
|
|||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ public:
|
|||
|
||||
/** Retrieve a file relative to a specified archive */
|
||||
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:
|
||||
class LoadedArchive {
|
||||
|
|
|
@ -84,4 +84,8 @@ void Settings::setIntSetting(IntSettingIndex index, int value) {
|
|||
_mixer->setVolumeForSoundType(type, value);
|
||||
}
|
||||
|
||||
bool Settings::isAssetsModEnabled() const {
|
||||
return ConfMan.getBool("enable_assets_mod");
|
||||
}
|
||||
|
||||
} // End of namespace Stark
|
||||
|
|
|
@ -94,6 +94,9 @@ public:
|
|||
/** Check whether the book of secrets is enabled */
|
||||
bool hasBookOfSecrets() { return ConfMan.hasKey("xoBfOsterceS"); }
|
||||
|
||||
/** Should the game try to load external replacement assets? */
|
||||
bool isAssetsModEnabled() const;
|
||||
|
||||
private:
|
||||
Audio::Mixer *_mixer;
|
||||
bool _hasLowRes;
|
||||
|
|
|
@ -65,6 +65,7 @@ StarkEngine::StarkEngine(OSystem *syst, const ADGameDescription *gameDesc) :
|
|||
DebugMan.addDebugChannel(kDebugArchive, "Archive", "Debug the archive loading");
|
||||
DebugMan.addDebugChannel(kDebugXMG, "XMG", "Debug the loading of XMG images");
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "engines/stark/visual/image.h"
|
||||
|
||||
#include "graphics/surface.h"
|
||||
#include "image/png.h"
|
||||
|
||||
#include "engines/stark/formats/xmg.h"
|
||||
#include "engines/stark/gfx/driver.h"
|
||||
|
@ -35,7 +36,9 @@ VisualImageXMG::VisualImageXMG(Gfx::Driver *gfx) :
|
|||
Visual(TYPE),
|
||||
_gfx(gfx),
|
||||
_texture(nullptr),
|
||||
_surface(nullptr) {
|
||||
_surface(nullptr),
|
||||
_originalWidth(0),
|
||||
_originalHeight(0) {
|
||||
_surfaceRenderer = _gfx->createSurfaceRenderer();
|
||||
}
|
||||
|
||||
|
@ -58,6 +61,29 @@ void VisualImageXMG::load(Common::ReadStream *stream) {
|
|||
// Decode the XMG
|
||||
_surface = Formats::XMGDecoder::decode(stream);
|
||||
_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) {
|
||||
|
@ -68,11 +94,11 @@ void VisualImageXMG::render(const Common::Point &position, bool useOffset, bool
|
|||
Common::Point drawPos = useOffset ? position - _hotspot : position;
|
||||
|
||||
if (!unscaled) {
|
||||
uint width = _gfx->scaleWidthOriginalToCurrent(_texture->width());
|
||||
uint height = _gfx->scaleHeightOriginalToCurrent(_texture->height());
|
||||
uint width = _gfx->scaleWidthOriginalToCurrent(_originalWidth);
|
||||
uint height = _gfx->scaleHeightOriginalToCurrent(_originalHeight);
|
||||
_surfaceRenderer->render(_texture, drawPos, width, height);
|
||||
} 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 {
|
||||
assert(_surface);
|
||||
|
||||
if (_surface->w < 32 || _surface->h < 32) {
|
||||
if (_originalWidth < 32 || _originalHeight < 32) {
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
int VisualImageXMG::getWidth() const {
|
||||
assert(_surface);
|
||||
return _surface->w;
|
||||
return _originalWidth;
|
||||
}
|
||||
|
||||
int VisualImageXMG::getHeight() const {
|
||||
assert(_surface);
|
||||
return _surface->h;
|
||||
return _originalHeight;
|
||||
}
|
||||
|
||||
const Graphics::Surface *VisualImageXMG::getSurface() const {
|
||||
|
|
|
@ -50,7 +50,21 @@ public:
|
|||
explicit VisualImageXMG(Gfx::Driver *gfx);
|
||||
~VisualImageXMG() override;
|
||||
|
||||
/**
|
||||
* Load the pixel data from a XMG image
|
||||
*/
|
||||
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, bool unscaled);
|
||||
|
||||
|
@ -83,6 +97,8 @@ private:
|
|||
Gfx::Texture *_texture;
|
||||
Graphics::Surface *_surface;
|
||||
Common::Point _hotspot;
|
||||
uint _originalWidth;
|
||||
uint _originalHeight;
|
||||
};
|
||||
|
||||
} // End of namespace Stark
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue