[Scummvm-git-logs] scummvm master -> f20484a3341ecd8fed5e5f2243a189ed68ef006c
bluegr
noreply at scummvm.org
Mon Sep 14 18:29:48 UTC 2026
This automated email contains information about 14 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
e875e2cd27 MADE: Minor improvements to script dump
41bed23b25 MADE: Preliminary work on V3.10 database games
8421403901 MADE: add RLE decompression to movie frames
49e9753f7b MADE: Add support for two V3.1 external script funcs
8e46a15765 MADE: break pmvplayer play() into separate routines.
7c38eac7c7 MADE: Support sfMovieCall script function
1c965a458d MADE: use dynamic buffers for rleDecompress()
2dd0cca291 MADE: allow arg-passing to ScriptInterpreter::runScript()
9480d70015 MADE: Do not create screen mask for RSB*NDE
837afb7282 MADE: Fixes based on PR feedback
e3f9c3ee49 MADE: allow non-constant frame size in PMV player
9174c3fe8c MADE: Cleanup based on PR feedback
4de50b7f26 MADE: PR cleanup, fix MHED framerate / sound and sfMovieCall
f20484a334 MADE: improved 3.1 script dump
Commit: e875e2cd274e3d6bd7f621b4e27d8c5e5ea39202
https://github.com/scummvm/scummvm/commit/e875e2cd274e3d6bd7f621b4e27d8c5e5ea39202
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Minor improvements to script dump
Print byte-arrays and word-arrays, attempting to detect ASCII in byte-arrays as well. Dump object contents as ASCII.
Changed paths:
engines/made/database.cpp
engines/made/database.h
engines/made/script.cpp
engines/made/script.h
diff --git a/engines/made/database.cpp b/engines/made/database.cpp
index e2e530fd48f..a63ac0e51c6 100644
--- a/engines/made/database.cpp
+++ b/engines/made/database.cpp
@@ -68,10 +68,6 @@ bool Object::isObject() {
return getClass() < 0x7FFE;
}
-bool Object::isVector() {
- return getClass() == 0x7FFF;
-}
-
int16 Object::getVectorSize() {
if (getClass() == 0x7FFF || getClass() == 0x7FFE) {
return getSize();
@@ -188,24 +184,31 @@ int ObjectV1::load(Common::SeekableReadStream &source) {
int ObjectV3::load(Common::SeekableReadStream &source) {
+ if (_freeData && _objData)
+ delete[] _objData;
+
_freeData = true;
- source.readUint16LE(); // skip flags
- uint16 type = source.readUint16LE();
+
+ byte header[6];
+ source.read(header, 6);
+
+ // uint16 flags = READ_LE_UINT16(header);
+ uint16 type = READ_LE_UINT16(header + 2);
if (type == 0x7FFF) {
- _objSize = source.readUint16LE();
+ _objSize = READ_LE_UINT16(header + 4);
} else if (type == 0x7FFE) {
- _objSize = source.readUint16LE() * 2;
+ _objSize = READ_LE_UINT16(header + 4) * 2;
} else if (type < 0x7FFE) {
- byte count1 = source.readByte();
- byte count2 = source.readByte();
+ byte count1 = header[4];
+ byte count2 = header[5];
_objSize = (count1 + count2) * 2;
}
- source.seek(-6, SEEK_CUR);
_objSize += 6;
_objData = new byte[_objSize];
- source.read(_objData, _objSize);
- return _objSize;
+ memcpy(_objData, header, 6);
+ source.read(_objData + 6, _objSize - 6);
+ return _objSize;
}
int ObjectV3::load(byte *source) {
@@ -408,8 +411,10 @@ void GameDatabaseV2::load(Common::SeekableReadStream &sourceS) {
uint32 textOffs = 0, objectsOffs = 0, objectsSize = 0, textSize;
uint16 objectCount = 0, varObjectCount = 0;
- sourceS.readUint16LE(); // skip sub-version
- sourceS.skip(18); // skip program name
+ uint16 subVersion = sourceS.readUint16LE();
+ char dbName[19] = "";
+ sourceS.read(dbName, 18);
+ debug(2, "databaseVersion = %d, databaseSubVersion = %d, databaseName = %s", version, subVersion, dbName);
if (version == 40) {
sourceS.readUint16LE(); // skip unused
@@ -526,7 +531,7 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i
Object *obj = getObject(objectIndex);
if (obj->getClass() >= 0x7FFE) {
- error("GameDatabaseV2::findObjectProperty(%04X, %04X) Not an object", objectIndex, propertyId);
+ error("GameDatabaseV2::findObjectProperty(%04X, %04X) Not an object (type=%04x)", objectIndex, propertyId, obj->getClass());
}
int16 *prop = (int16 *)obj->getData();
@@ -548,10 +553,6 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i
// Now check in the object hierarchy of the given object
int16 parentObjectIndex = obj->getClass();
- if (parentObjectIndex == 0) {
- return nullptr;
- }
-
while (parentObjectIndex != 0) {
obj = getObject(parentObjectIndex);
@@ -608,9 +609,13 @@ void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
if (strncmp(header, "ADVSYS", 6))
warning ("Unexpected database header, expected ADVSYS");
- /*uint32 unk = */sourceS.readUint32LE();
+ uint16 version = sourceS.readUint16LE();
+ uint16 subVersion = sourceS.readUint16LE();
+ char dbName[19] = "";
+ sourceS.read(dbName, 18);
+ debug(2, "databaseVersion = %d, databaseSubVersion = %d, databaseName = %s", version, subVersion, dbName);
- sourceS.skip(20);
+ sourceS.readUint16LE(); // unknown, always 1?
uint32 objectIndexOffs = sourceS.readUint32LE();
uint16 objectCount = sourceS.readUint16LE();
@@ -637,7 +642,7 @@ void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
// Constant objects are loaded from disk, while variable objects exist
// in the _gameState buffer.
if (objectOffsets[i] & 1) {
- sourceS.seek(objectsOffs + objectOffsets[i] - 1);
+ sourceS.seek(objectsOffs + objectOffsets[i] ^ 1);
obj->load(sourceS);
} else {
obj->load(_gameState + objectOffsets[i]);
@@ -754,7 +759,7 @@ int16 GameDatabaseV3::loadgame(const char *filename, int16 version) {
int16 *GameDatabaseV3::findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
Object *obj = getObject(objectIndex);
if (obj->getClass() >= 0x7FFE) {
- error("GameDatabaseV3::findObjectProperty(%04X, %04X) Not an object", objectIndex, propertyId);
+ error("GameDatabaseV3::findObjectProperty(%04X, %04X) Not an object (type=%04X)", objectIndex, propertyId, obj->getClass());
}
int16 *prop = (int16 *)obj->getData();
@@ -781,10 +786,6 @@ int16 *GameDatabaseV3::findObjectProperty(int16 objectIndex, int16 propertyId, i
// Now check in the object hierarchy of the given object
int16 parentObjectIndex = obj->getClass();
- if (parentObjectIndex == 0) {
- return nullptr;
- }
-
while (parentObjectIndex != 0) {
obj = getObject(parentObjectIndex);
@@ -828,6 +829,8 @@ int16 *GameDatabaseV3::findObjectProperty(int16 objectIndex, int16 propertyId, i
}
+ debug(1, "findObjectProperty(%04X, %04X) Property not found", objectIndex, propertyId);
+
return nullptr;
}
diff --git a/engines/made/database.h b/engines/made/database.h
index 8cdef0848be..b0b664c8cd3 100644
--- a/engines/made/database.h
+++ b/engines/made/database.h
@@ -55,7 +55,6 @@ public:
void setString(const char *str);
bool isObject();
- bool isVector();
int16 getVectorSize();
int16 getVectorItem(int16 index);
diff --git a/engines/made/script.cpp b/engines/made/script.cpp
index 2dd82738d6b..216fb05d693 100644
--- a/engines/made/script.cpp
+++ b/engines/made/script.cpp
@@ -169,6 +169,8 @@ byte ScriptInterpreter::readByte() {
}
int16 ScriptInterpreter::readInt16() {
+ // Scripts are processed byte-by-byte,
+ // so embedded values (constants, etc) are universally LE
int16 temp = (int16)READ_LE_UINT16(_codeIp);
_codeIp += 2;
debug(4, "readInt16() value = %04X", temp);
@@ -670,6 +672,9 @@ void ScriptInterpreter::dumpScript(int16 objectIndex, int *opcodeStats, int *ext
valueType = 2;
value = *code++;
break;
+ default:
+ error("Unknown signature '%c'", sig);
+ return;
}
Common::String tempStr;
@@ -688,6 +693,9 @@ void ScriptInterpreter::dumpScript(int16 objectIndex, int *opcodeStats, int *ext
tempStr = Common::String::format("invalid: %d", value);
}
break;
+ default:
+ error("Unknown valuetype '%d'", valueType);
+ return;
}
codeLine += tempStr;
}
@@ -696,11 +704,53 @@ void ScriptInterpreter::dumpScript(int16 objectIndex, int *opcodeStats, int *ext
error("ScriptInterpreter::dumpScript(%d) Unknown opcode %02X", objectIndex, opcode);
}
}
- debug(1, "-------------------------------------------");
+}
+
+void ScriptInterpreter::dumpObject(int16 objectIndex) {
+
+ debug(1, "Dumping object %04X", objectIndex);
+
+ Object *obj = _vm->_dat->getObject(objectIndex);
+ debug(1, "Flags = %d[%04x], Size = %d[%04x]", obj->getFlags(), obj->getFlags(), obj->getSize(), obj->getSize());
+ debug(1, "Class = %d[%04x]", obj->getClass(), obj->getClass());
+ if (obj->getClass() == 0x7FFF) {
+ // byte array
+ bool looksLikeAscii = true;
+ Common::String bArray = "byteArray = [";
+ for (int i = 0; i < obj->getVectorSize(); i++) {
+ int16 c = obj->getVectorItem(i);
+ if (i == obj->getVectorSize() - 1) {
+ if (c != 0)
+ looksLikeAscii = false;
+ } else {
+ if (c != 10 && (c < 32 || c > 126))
+ looksLikeAscii = false;
+ }
+ bArray += Common::String::format("%d,", c);
+ }
+ bArray += "]";
+ debug(1, "%s", bArray.c_str());
+
+ if (looksLikeAscii)
+ debug(1, "ASCII = '%s'", obj->getData());
+ } else if (obj->getClass() == 0x7FFE) {
+ // word array
+ Common::String bArray = "wordArray = [";
+ for (int i = 0; i < obj->getVectorSize(); i++)
+ bArray += Common::String::format("%d,", obj->getVectorItem(i));
+ bArray += " ]";
+ debug(1, "%s", bArray.c_str());
+ } else {
+ debug(1, "Raw Data for object %04X (count1 = %d, count2 = %d)", objectIndex, obj->getCount1(), obj->getCount2());
+ Common::String bArray = "";
+ for (byte *i = obj->getData(); i < obj->getData() + obj->getSize(); i++)
+ bArray += Common::String::format("%02X", *i);
+ debug(1, "%s", bArray.c_str());
+ }
}
void ScriptInterpreter::dumpAllScripts() {
- int *opcodeStats = new int[_commandsMax - 1];
+ int *opcodeStats = new int[_commandsMax];
int *externStats = new int[_functions->getCount()];
for (int i = 0; i < _commandsMax; i++)
@@ -710,14 +760,21 @@ void ScriptInterpreter::dumpAllScripts() {
for (uint objectIndex = 1; objectIndex <= _vm->_dat->getObjectCount(); objectIndex++) {
Object *obj = _vm->_dat->getObject(objectIndex);
+
+ debug(1, "-------------------------------------------");
+
// Check if it's a byte array which might contain code
- if (obj->getClass() != 0x7FFF)
+ if (obj->getClass() != 0x7FFF) {
+ dumpObject(objectIndex);
continue;
+ }
// Code objects aren't excplicitly marked as such, we need to check if
// the last byte is a cmd_return opcode.
byte *retByte = obj->getData() + obj->getSize() - 1;
if (*retByte == 0x1F) {
dumpScript(objectIndex, opcodeStats, externStats);
+ } else {
+ dumpObject(objectIndex);
}
}
diff --git a/engines/made/script.h b/engines/made/script.h
index e0e229160f7..f37382a1dab 100644
--- a/engines/made/script.h
+++ b/engines/made/script.h
@@ -74,8 +74,11 @@ public:
ScriptInterpreter(MadeEngine *vm);
~ScriptInterpreter();
void runScript(int16 scriptObjectIndex);
+#ifdef DUMP_SCRIPTS
void dumpScript(int16 objectIndex, int *opcodeStats, int *externStats);
+ void dumpObject(int16 objectIndex);
void dumpAllScripts();
+#endif
protected:
MadeEngine *_vm;
Commit: 41bed23b2544a8ee845a385c0e36a0ecd48bc771
https://github.com/scummvm/scummvm/commit/41bed23b2544a8ee845a385c0e36a0ecd48bc771
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Preliminary work on V3.10 database games
Add initial support for MADE engine games using V3.10 of the interpreter. This version is used for edutainment game "Richard Scarry's Best Neighborhood Disc Ever!" and its demo, but also for the MacOS port of Return to Zork (in big-endian).
The demo of RSBESTNDE is somewhat playable, but some outstanding issues remain:
* changes to the PMV movie format
* three new extended opcodes, of which "MovieCall" and "SoundFile" are used extensively
Changed paths:
engines/made/database.cpp
engines/made/database.h
engines/made/detection.cpp
engines/made/detection.h
engines/made/detection_tables.h
engines/made/made.cpp
engines/made/made.h
engines/made/metaengine.cpp
engines/made/pmvplayer.cpp
engines/made/script.cpp
engines/made/scriptfuncs.cpp
engines/made/scriptfuncs.h
diff --git a/engines/made/database.cpp b/engines/made/database.cpp
index a63ac0e51c6..7aace430862 100644
--- a/engines/made/database.cpp
+++ b/engines/made/database.cpp
@@ -253,6 +253,50 @@ byte *ObjectV3::getData() {
}
+int ObjectV3_1::load(Common::SeekableReadStream &source) {
+
+ if (_freeData && _objData)
+ delete[] _objData;
+
+ _freeData = true;
+
+ byte header[6];
+ source.read(header, 6);
+
+ // uint16 flags = READ_LE_UINT16(header);
+ uint16 type = READ_LE_UINT16(header + 2);
+ if (type == 0x7FFF) {
+ // array of Bytes
+ _objSize = READ_LE_UINT16(header + 4);
+ } else if (type == 0x7FFE) {
+ // array of Words
+ _objSize = READ_LE_UINT16(header + 4) * 2;
+ } else if (type < 0x7FFE) {
+ // Object (array of Word -> Word)
+ _objSize = READ_LE_UINT16(header + 4) * 4;
+ }
+ _objSize += 6;
+ _objData = new byte[_objSize];
+ memcpy(_objData, header, 6);
+ source.read(_objData + 6, _objSize - 6);
+
+ return _objSize;
+}
+
+int ObjectV3_1::load(byte *source) {
+ _objData = source;
+ _freeData = false;
+ if (getClass() < 0x7FFE) {
+ _objSize = getSize() * 2;
+ } else {
+ _objSize = getSize();
+ }
+ _objSize += 6;
+ return _objSize;
+}
+
+
+/* GameDatabase */
GameDatabase::GameDatabase(MadeEngine *vm) : _vm(vm) {
_gameState = nullptr;
@@ -625,7 +669,7 @@ void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
uint32 objectsSize = sourceS.readUint32LE();
_mainCodeObjectIndex = sourceS.readUint16LE();
- debug(2, "objectIndexOffs = %08X; objectCount = %d; gameStateOffs = %08X; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", objectIndexOffs, objectCount, _gameStateOffs, _gameStateSize, objectsOffs, objectsSize);
+ debug(2, "objectIndexOffs = %08X; objectCount = %d; gameStateOffs = %08X; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d; _mainCodeObjectIndex = %04X\n", objectIndexOffs, objectCount, _gameStateOffs, _gameStateSize, objectsOffs, objectsSize, _mainCodeObjectIndex);
_gameState = new byte[_gameStateSize];
sourceS.seek(_gameStateOffs);
@@ -840,4 +884,98 @@ const char *GameDatabaseV3::getString(uint16 offset) {
return nullptr;
}
+void GameDatabaseV3_1::load(Common::SeekableReadStream &sourceS) {
+ char header[6];
+ sourceS.read(header, 6);
+ if (strncmp(header, "ADVSYS", 6))
+ warning("Unexpected database header, expected ADVSYS");
+
+ uint16 version = sourceS.readUint16LE();
+ uint16 subVersion = sourceS.readUint16LE();
+ char dbName[19] = "";
+ sourceS.read(dbName, 18);
+ debug(2, "databaseVersion = %d, databaseSubVersion = %d, databaseName = %s", version, subVersion, dbName);
+
+ sourceS.readUint16LE(); // unknown, always 1?
+
+ uint32 objectIndexOffs = sourceS.readUint32LE();
+ uint16 objectCount = sourceS.readUint16LE();
+ _gameStateOffs = sourceS.readUint32LE();
+ _gameStateSize = sourceS.readUint32LE();
+ uint32 objectsOffs = sourceS.readUint32LE();
+ uint32 objectsSize = sourceS.readUint32LE();
+ _mainCodeObjectIndex = sourceS.readUint16LE();
+
+ debug(2, "objectIndexOffs = %08X; objectCount = %d; gameStateOffs = %08X; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d; _mainCodeObjectIndex = %04X\n", objectIndexOffs, objectCount, _gameStateOffs, _gameStateSize, objectsOffs, objectsSize, _mainCodeObjectIndex);
+
+ _gameState = new byte[_gameStateSize];
+ sourceS.seek(_gameStateOffs);
+ sourceS.read(_gameState, _gameStateSize);
+
+ Common::Array<uint32> objectOffsets;
+ sourceS.seek(objectIndexOffs);
+ for (uint32 i = 0; i < objectCount; i++)
+ objectOffsets.push_back(sourceS.readUint32LE());
+
+ for (uint32 i = 0; i < objectCount; i++) {
+ Object *obj = new ObjectV3_1();
+ // The LSB indicates if it's a constant or variable object.
+ // Constant objects are loaded from disk, while variable objects exist
+ // in the _gameState buffer.
+ if (objectOffsets[i] & 1) {
+ sourceS.seek(objectsOffs + objectOffsets[i] ^ 1);
+ obj->load(sourceS);
+ } else {
+ obj->load(_gameState + objectOffsets[i]);
+ }
+ _objects.push_back(obj);
+ }
+}
+
+int16 *GameDatabaseV3_1::findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
+
+ debug(4, "findObjectProperty(%04X, %04X): Beginning search...", objectIndex, propertyId);
+
+ do {
+ Object *obj = getObject(objectIndex);
+
+ if (obj->getClass() >= 0x7FFE) {
+ error("GameDatabaseV3::findObjectProperty(%04X, %04X) Not an object (type=%04X)", objectIndex, propertyId, obj->getClass());
+ }
+
+ // 3.1 objects are a set of int16 (key) -> int16 (value) entries
+ uint16 count = obj->getSize();
+
+ // Check each "key" on the object for a match.
+ while (count-- > 0) {
+
+ int16 *prop = (int16 *)obj->getData() + (2 * count);
+ uint16 readProp = READ_LE_UINT16(prop);
+
+ if ((readProp & 0x3FFF) == propertyId) {
+ if (readProp & 0x4000) {
+ // A match w/ high-bit set indicates you go look in the Gamestate.
+ // "Value" is an index into the gamestate... return a ptr into gamestate.
+ propertyFlag = 1;
+ return (int16 *)_gameState + READ_LE_UINT16(prop + 1);
+ } else {
+ // A match without high-bit indicates the value is right here (literal).
+ // Return a ptr to the value.
+ propertyFlag = obj->getFlags() & 1;
+ return (prop + 1);
+ }
+ }
+ prop++;
+ }
+
+ // Objects are a hierarchy: if no match is found on the current object,
+ // check its "class" (parent).
+ objectIndex = obj->getClass();
+ } while (objectIndex != 0);
+
+ debug(1, "findObjectProperty(%04X, %04X) Property not found", objectIndex, propertyId);
+
+ return nullptr;
+}
+
} // End of namespace Made
diff --git a/engines/made/database.h b/engines/made/database.h
index b0b664c8cd3..f4a7bcec284 100644
--- a/engines/made/database.h
+++ b/engines/made/database.h
@@ -34,6 +34,9 @@ namespace Made {
class MadeEngine;
+
+/* Object */
+
class Object {
public:
@@ -92,8 +95,8 @@ public:
class ObjectV3 : public Object {
public:
- int load(Common::SeekableReadStream &source) override;
- int load(byte *source) override;
+ virtual int load(Common::SeekableReadStream &source) override;
+ virtual int load(byte *source) override;
int save(Common::WriteStream &dest) override;
uint16 getFlags() override;
uint16 getClass() override;
@@ -108,6 +111,15 @@ public:
};
+class ObjectV3_1 : public ObjectV3 {
+public:
+ int load(Common::SeekableReadStream &source) override;
+ int load(byte *source) override;
+};
+
+
+/* GameDatabase */
+
class GameDatabase {
public:
@@ -180,7 +192,7 @@ protected:
class GameDatabaseV3 : public GameDatabase {
public:
GameDatabaseV3(MadeEngine *vm);
- int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) override;
+ virtual int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) override;
const char *getString(uint16 offset) override;
bool getSavegameDescription(const char *filename, Common::String &description, int16 version) override;
int16 savegame(const char *filename, const char *description, int16 version) override;
@@ -188,10 +200,18 @@ public:
protected:
char *_gameText;
uint32 _gameStateOffs;
- void load(Common::SeekableReadStream &sourceS) override;
+ virtual void load(Common::SeekableReadStream &sourceS) override;
void reloadFromStream(Common::SeekableReadStream &sourceS) override;
};
+class GameDatabaseV3_1 : public GameDatabaseV3 {
+public:
+ GameDatabaseV3_1(MadeEngine *vm) : GameDatabaseV3(vm) {}
+ int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) override;
+protected:
+ void load(Common::SeekableReadStream &sourceS) override;
+};
+
} // End of namespace Made
#endif /* MADE_H */
diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp
index 8652a86f294..ac5f6ea5eba 100644
--- a/engines/made/detection.cpp
+++ b/engines/made/detection.cpp
@@ -31,6 +31,8 @@ static const PlainGameDescriptor madeGames[] = {
{"rtz", "Return to Zork"},
{"lgop2", "Leather Goddesses of Phobos 2"},
{"rodney", "Rodney's Funscreen"},
+ {"rsbestnde", "Richard Scarry's Best Neighborhood Disc Ever!"},
+ {"rsbusynde", "Richard Scarry's Busiest Neighborhood Disc Ever!"},
{nullptr, nullptr}
};
@@ -67,6 +69,7 @@ ADDetectedGame MadeMetaEngineDetection::fallbackDetect(const FileMap &allFiles,
Made::g_fallbackDesc.gameID = 0;
Made::g_fallbackDesc.features = 0;
Made::g_fallbackDesc.version = 3;
+ Made::g_fallbackDesc.subVersion = 0;
//return (const ADGameDescription *)&Made::g_fallbackDesc;
return ADDetectedGame();
diff --git a/engines/made/detection.h b/engines/made/detection.h
index 43287316816..7e43841011b 100644
--- a/engines/made/detection.h
+++ b/engines/made/detection.h
@@ -27,10 +27,12 @@
namespace Made {
enum MadeGameID {
- GID_RTZ = 0,
- GID_MANHOLE = 1,
- GID_LGOP2 = 2,
- GID_RODNEY = 3
+ GID_RTZ = 0,
+ GID_MANHOLE = 1,
+ GID_LGOP2 = 2,
+ GID_RODNEY = 3,
+ GID_RSBESTNDE = 4,
+ GID_RSBUSYNDE = 5
};
enum MadeGameFeatures {
@@ -48,7 +50,8 @@ struct MadeGameDescription {
int gameID;
int gameType;
uint32 features;
- uint16 version;
+ uint8 version;
+ uint8 subVersion;
};
#define GAMEOPTION_INTRO_MUSIC_DIGITAL GUIO_GAMEOPTIONS1
diff --git a/engines/made/detection_tables.h b/engines/made/detection_tables.h
index daf9c3e5c78..349ada62a8f 100644
--- a/engines/made/detection_tables.h
+++ b/engines/made/detection_tables.h
@@ -49,7 +49,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -67,7 +67,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -84,7 +84,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -101,7 +101,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -118,7 +118,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -136,7 +136,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -157,7 +157,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -175,7 +175,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -193,7 +193,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -211,7 +211,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -229,7 +229,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -247,7 +247,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -265,7 +265,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -288,7 +288,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -305,7 +305,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_FLOPPY,
- 3,
+ 3, 0,
},
{
@@ -326,7 +326,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_DEMO,
- 3,
+ 3, 0,
},
{
@@ -343,7 +343,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -361,7 +361,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -380,7 +380,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD_COMPRESSED,
- 3,
+ 3, 0,
},
{
@@ -398,7 +398,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -416,7 +416,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -434,7 +434,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RTZ,
0,
GF_CD,
- 3,
+ 3, 0,
},
// The Manhole: Masterpiece Edition is not a MADE engine and cannot be
@@ -453,7 +453,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_MANHOLE,
0,
GF_CD,
- 2,
+ 2, 0,
},
// Bugreport #5855
@@ -470,7 +470,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_MANHOLE,
0,
GF_CD,
- 2,
+ 2, 0,
},
{
@@ -487,7 +487,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_MANHOLE,
0,
GF_CD,
- 2,
+ 2, 0,
},
{
@@ -504,7 +504,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_MANHOLE,
0,
GF_FLOPPY,
- 1,
+ 1, 0,
},
{
@@ -521,7 +521,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_MANHOLE,
0,
GF_CD,
- 3,
+ 3, 0,
},
{
@@ -540,7 +540,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_MANHOLE,
0,
GF_FLOPPY,
- 3,
+ 3, 0,
},
{
@@ -557,7 +557,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_LGOP2,
0,
GF_FLOPPY,
- 2,
+ 2, 0,
},
{
@@ -575,7 +575,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_LGOP2,
0,
GF_FLOPPY,
- 2,
+ 2, 0,
},
{
@@ -593,7 +593,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_LGOP2,
0,
GF_FLOPPY,
- 2,
+ 2, 0,
},
{
@@ -611,7 +611,7 @@ static const MadeGameDescription gameDescriptions[] = {
GID_LGOP2,
0,
GF_FLOPPY,
- 2,
+ 2, 0,
},
{
@@ -629,10 +629,64 @@ static const MadeGameDescription gameDescriptions[] = {
GID_RODNEY,
0,
GF_FLOPPY,
- 2,
+ 2, 0,
},
- { AD_TABLE_END_MARKER, 0, 0, 0, 0 }
+ {
+ // Richard Scarry's Best Neighborhood Disc Ever!
+ // Hybrid CD, this is the MS-DOS version
+ {
+ "rsbestnde",
+ "",
+ AD_ENTRY1s("best.dat", "9c36e7ee85df0d049a106196683e0134", 38400),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_RSBESTNDE,
+ 0,
+ 0,
+ 3, 1,
+ },
+
+ {
+ // Richard Scarry's Best Neighborhood Disc Ever! demo
+ // This is found on the Activision 1.1 RTZ CD-ROM for MS-DOS
+ {
+ "rsbestnde",
+ "Demo",
+ AD_ENTRY1s("bestdemo.dat", "6ef50ec61799d37ed75eada90a76d0e7", 12800),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_DEMO | ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_RSBESTNDE,
+ 0,
+ GF_DEMO,
+ 3, 1,
+ },
+
+ {
+ // Richard Scarry's Busiest Neighborhood Disc Ever!
+ // MS-DOS version
+ {
+ "rsbusynde",
+ "",
+ AD_ENTRY1s("busy.dat", "df793b06adb7944684e63f2af08bd48b", 43520),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GID_RSBUSYNDE,
+ 0,
+ 0,
+ 3, 1,
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0, 0, 0, 0 }
};
/**
@@ -652,7 +706,7 @@ static MadeGameDescription g_fallbackDesc = {
0,
0,
0,
- 0,
+ 0, 0,
};
} // End of namespace Made
diff --git a/engines/made/made.cpp b/engines/made/made.cpp
index 1cc74098b01..aa2c576143f 100644
--- a/engines/made/made.cpp
+++ b/engines/made/made.cpp
@@ -73,6 +73,8 @@ MadeEngine::MadeEngine(OSystem *syst, const MadeGameDescription *gameDesc) : Eng
_dat = new GameDatabaseV2(this);
} else if (getGameID() == GID_RTZ) {
_dat = new GameDatabaseV3(this);
+ } else if (getGameID() == GID_RSBESTNDE || getGameID() == GID_RSBUSYNDE) {
+ _dat = new GameDatabaseV3_1(this);
} else {
error("Unknown GameID");
}
@@ -111,7 +113,9 @@ MadeEngine::MadeEngine(OSystem *syst, const MadeGameDescription *gameDesc) : Eng
_soundRate = 8000;
break;
case GID_RTZ:
- // Return to Zork sets it itself via a script function
+ case GID_RSBESTNDE:
+ case GID_RSBUSYNDE:
+ // Return to Zork and the Scarry games sets it via script function
break;
default:
break;
@@ -465,6 +469,17 @@ Common::Error MadeEngine::run() {
delete exe;
}
}
+ } else if (getGameID() == GID_RSBESTNDE) {
+ if (getFeatures() & GF_DEMO) {
+ _dat->open("bestdemo.dat");
+ _res->open("bestdemo.prj");
+ } else {
+ _dat->open("best.dat");
+ _res->open("best.prj");
+ }
+ } else if (getGameID() == GID_RSBUSYNDE) {
+ _dat->open("busy.dat");
+ _res->open("busy.prj");
} else {
error ("Unknown MADE game");
}
diff --git a/engines/made/made.h b/engines/made/made.h
index a266cb09aeb..b20fa235a79 100644
--- a/engines/made/made.h
+++ b/engines/made/made.h
@@ -83,7 +83,8 @@ public:
const MadeGameDescription *_gameDescription;
uint32 getGameID() const;
uint32 getFeatures() const;
- uint16 getVersion() const;
+ uint8 getVersion() const;
+ uint8 getSubVersion() const;
Common::Platform getPlatform() const;
Common::Language getLanguage() const;
diff --git a/engines/made/metaengine.cpp b/engines/made/metaengine.cpp
index 6128445c29f..3e655132621 100644
--- a/engines/made/metaengine.cpp
+++ b/engines/made/metaengine.cpp
@@ -84,10 +84,14 @@ Common::Platform MadeEngine::getPlatform() const {
return _gameDescription->desc.platform;
}
-uint16 MadeEngine::getVersion() const {
+uint8 MadeEngine::getVersion() const {
return _gameDescription->version;
}
+uint8 MadeEngine::getSubVersion() const {
+ return _gameDescription->subVersion;
+}
+
Common::Language MadeEngine::getLanguage() const {
return _gameDescription->desc.language;
}
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index bd3efba5bd6..c622f647b77 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -126,7 +126,7 @@ bool PmvPlayer::play(const char *filename) {
_surface = nullptr;
_fd = new Common::File();
- if (!_fd->open(filename)) {
+ if (!_fd->open(Common::Path(filename, '\\'))) {
delete _fd;
return false;
}
diff --git a/engines/made/script.cpp b/engines/made/script.cpp
index 216fb05d693..630415e93d8 100644
--- a/engines/made/script.cpp
+++ b/engines/made/script.cpp
@@ -743,7 +743,9 @@ void ScriptInterpreter::dumpObject(int16 objectIndex) {
} else {
debug(1, "Raw Data for object %04X (count1 = %d, count2 = %d)", objectIndex, obj->getCount1(), obj->getCount2());
Common::String bArray = "";
- for (byte *i = obj->getData(); i < obj->getData() + obj->getSize(); i++)
+ // NOTE: for 3.1 objects, use the commented line here instead
+ //for (byte *i = obj->getData(); i < obj->getData() + obj->getSize() * 4; i++)
+ for (byte *i = obj->getData(); i < obj->getData() + (obj->getCount1() + obj->getCount2()) * 2; i++)
bArray += Common::String::format("%02X", *i);
debug(1, "%s", bArray.c_str());
}
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 6d3637757b6..02772c92bc2 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -105,7 +105,7 @@ void ScriptFunctions::setupExternalsTable() {
if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_RODNEY) {
External(sfAddScreenMask);
External(sfSetSpriteMask);
- } else if (_vm->getGameID() == GID_RTZ) {
+ } else if (_vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RSBESTNDE || _vm->getGameID() == GID_RSBUSYNDE) {
External(sfSetClipArea);
External(sfSetSpriteClip);
}
@@ -114,7 +114,8 @@ void ScriptFunctions::setupExternalsTable() {
External(sfStopSound);
External(sfPlayVoice);
- if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RODNEY) {
+ if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ ||
+ _vm->getGameID() == GID_RODNEY || _vm->getGameID() == GID_RSBESTNDE || _vm->getGameID() == GID_RSBUSYNDE) {
External(sfPlayCd);
External(sfStopCd);
External(sfGetCdStatus);
@@ -122,7 +123,7 @@ void ScriptFunctions::setupExternalsTable() {
External(sfPlayCdSegment);
}
- if (_vm->getGameID() == GID_RTZ) {
+ if (_vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RSBESTNDE || _vm->getGameID() == GID_RSBUSYNDE) {
External(sfPrintf);
External(sfClearMono);
External(sfGetSoundEnergy);
@@ -168,6 +169,11 @@ void ScriptFunctions::setupExternalsTable() {
External(sfIsSlowSystem);
}
+ if (_vm->getGameID() == GID_RSBESTNDE || _vm->getGameID() == GID_RSBUSYNDE) {
+ External(sfMovieCall);
+ External(sfCursorXY);
+ External(sfSoundFile);
+ }
}
#undef External
@@ -1142,4 +1148,22 @@ int16 ScriptFunctions::sfIsSlowSystem(int16 argc, int16 *argv) {
return _vm->_introMusicDigital ? 0 : 1;
}
+int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
+ warning("Unimplemented opcode: sfMovieCall");
+
+ return 0;
+}
+
+int16 ScriptFunctions::sfCursorXY(int16 argc, int16 *argv) {
+ warning("Unimplemented opcode: sfCursorXY");
+
+ return 0;
+}
+
+int16 ScriptFunctions::sfSoundFile(int16 argc, int16 *argv) {
+ warning("Unimplemented opcode: sfSoundFile");
+
+ return 0;
+}
+
} // End of namespace Made
diff --git a/engines/made/scriptfuncs.h b/engines/made/scriptfuncs.h
index b39a703d318..cc40f137646 100644
--- a/engines/made/scriptfuncs.h
+++ b/engines/made/scriptfuncs.h
@@ -173,7 +173,9 @@ protected:
int16 sfSetSoundVolume(int16 argc, int16 *argv);
int16 sfGetSynthType(int16 argc, int16 *argv);
int16 sfIsSlowSystem(int16 argc, int16 *argv);
-
+ int16 sfMovieCall(int16 argc, int16 *argv);
+ int16 sfCursorXY(int16 argc, int16 *argv);
+ int16 sfSoundFile(int16 argc, int16 *argv);
};
} // End of namespace Made
Commit: 8421403901d3ad2938450d31f61ca56b63c4268c
https://github.com/scummvm/scummvm/commit/8421403901d3ad2938450d31f61ca56b63c4268c
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: add RLE decompression to movie frames
MADE V3.1 games add a layer of RLE compression to video frames. Decompressing these first (according to the flags fields) allows RSBESTNDE / RSBUSYNDE to play videos correctly.
Changed paths:
engines/made/graphics.cpp
engines/made/graphics.h
engines/made/pmvplayer.cpp
diff --git a/engines/made/graphics.cpp b/engines/made/graphics.cpp
index 9ecc5d77693..225af42dcc4 100644
--- a/engines/made/graphics.cpp
+++ b/engines/made/graphics.cpp
@@ -63,6 +63,31 @@ void ValueReader::resetNibbleSwitch() {
_nibbleSwitch = false;
}
+byte *rleDecompress(byte *source, const byte *end, const int maxSize) {
+ byte *dest = new byte[maxSize];
+ byte *o = dest;
+
+ while (source < end) {
+ byte val = *source;
+ source++;
+ if (val < 0x80) {
+ // copy
+ val++;
+ memcpy(o, source, val);
+ o += val;
+ source += val;
+ } else {
+ // repeat
+ val = 257 - val;
+ memset(o, *source, val);
+ o += val;
+ source++;
+ }
+ }
+
+ return dest;
+}
+
void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame) {
const int offsets[] = {
@@ -189,16 +214,34 @@ void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, u
}
-void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize) {
+void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs,
+ uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
+ byte cmdFlags, byte pixelFlags, byte maskFlags) {
uint16 width = surface.w;
uint16 height = surface.h;
- uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4;
-
- byte *cmdBuffer = source + cmdOffs;
- byte *maskBuffer = source + maskOffs;
- byte *pixelBuffer = source + pixelOffs;
-
+ uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4, bh = ((height + 3) / 4) * 4;
+
+ // RLE decompress the buffers as needed (remember to free later!)
+ byte *cmdBuffer;
+ if (cmdFlags & 1)
+ cmdBuffer = rleDecompress(source + cmdOffs, source + cmdOffs + cmdSize, 4800);
+ else
+ cmdBuffer = source + cmdOffs;
+
+ byte *pixelBuffer;
+ if (pixelFlags & 1)
+ pixelBuffer = rleDecompress(source + pixelOffs, source + pixelOffs + pixelSize, bw * bh);
+ else
+ pixelBuffer = source + pixelOffs;
+
+ byte *maskBuffer;
+ if (maskFlags & 1)
+ maskBuffer = rleDecompress(source + maskOffs, source + maskOffs + maskSize, bw * bh);
+ else
+ maskBuffer = source + maskOffs;
+
+ //
byte *destPtr = (byte *)surface.getPixels();
byte bitBuf[40];
@@ -208,12 +251,13 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
if (bitBufLastCount == 0)
bitBufLastCount = 8;
- debug(1, "width = %d; bw = %d", width, bw);
-
+ byte *pCmdBuf = cmdBuffer;
+ byte *pPixelBuf = pixelBuffer;
+ byte *pMaskBuf = maskBuffer;
while (height > 0) {
- memcpy(bitBuf, cmdBuffer, lineSize);
- cmdBuffer += lineSize;
+ memcpy(bitBuf, pCmdBuf, lineSize);
+ pCmdBuf += lineSize;
for (uint16 bitBufOfs = 0; bitBufOfs < lineSize; bitBufOfs += 2) {
@@ -235,16 +279,16 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
switch (cmd) {
case 0:
- pixels[0] = *pixelBuffer++;
+ pixels[0] = *pPixelBuf++;
for (int i = 0; i < 16; i++)
block[i] = pixels[0];
break;
case 1:
- pixels[0] = *pixelBuffer++;
- pixels[1] = *pixelBuffer++;
- mask = READ_LE_UINT16(maskBuffer);
- maskBuffer += 2;
+ pixels[0] = *pPixelBuf++;
+ pixels[1] = *pPixelBuf++;
+ mask = READ_LE_UINT16(pMaskBuf);
+ pMaskBuf += 2;
for (int i = 0; i < 16; i++) {
block[i] = pixels[mask & 1];
mask >>= 1;
@@ -252,12 +296,12 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
break;
case 2:
- pixels[0] = *pixelBuffer++;
- pixels[1] = *pixelBuffer++;
- pixels[2] = *pixelBuffer++;
- pixels[3] = *pixelBuffer++;
- mask = READ_LE_UINT32(maskBuffer);
- maskBuffer += 4;
+ pixels[0] = *pPixelBuf++;
+ pixels[1] = *pPixelBuf++;
+ pixels[2] = *pPixelBuf++;
+ pixels[3] = *pPixelBuf++;
+ mask = READ_LE_UINT32(pMaskBuf);
+ pMaskBuf += 4;
for (int i = 0; i < 16; i++) {
block[i] = pixels[mask & 3];
mask >>= 2;
@@ -297,6 +341,13 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
}
+ // cleanup RLE buffers
+ if (cmdFlags & 1)
+ delete[] cmdBuffer;
+ if (maskFlags & 1)
+ delete[] maskBuffer;
+ if (pixelFlags & 1)
+ delete[] pixelBuffer;
}
} // End of namespace Made
diff --git a/engines/made/graphics.h b/engines/made/graphics.h
index 03bf7615e81..e3f06b8d7c1 100644
--- a/engines/made/graphics.h
+++ b/engines/made/graphics.h
@@ -44,7 +44,9 @@ protected:
};
void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame = false);
-void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize);
+void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs,
+ uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
+ byte cmdFlags, byte pixelFlags, byte maskFlags);
} // End of namespace Made
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index c622f647b77..3cddca69e0c 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -223,19 +223,20 @@ bool PmvPlayer::play(const char *filename) {
break;
soundChunkOfs = READ_LE_UINT32(frameData + 8);
+ uint32 imageDataOfs = READ_LE_UINT32(frameData + 12) - 8;
palChunkOfs = READ_LE_UINT32(frameData + 16);
// Handle audio
if (soundChunkOfs) {
audioData = frameData + soundChunkOfs - 8;
- chunkSize = READ_LE_UINT16(audioData + 4);
+ uint16 soundChunkSize = READ_LE_UINT16(audioData + 4);
chunkCount = READ_LE_UINT16(audioData + 6);
- debug(1, "chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, chunkSize, chunkCount * chunkSize);
+ debug(1, "chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, soundChunkSize, chunkCount * soundChunkSize);
- soundSize = chunkCount * chunkSize;
+ soundSize = chunkCount * soundChunkSize;
soundData = (byte *)malloc(soundSize);
- decompressSound(audioData + 8, soundData, chunkSize, chunkCount, nullptr, soundDecoderData);
+ decompressSound(audioData + 8, soundData, soundChunkSize, chunkCount, nullptr, soundDecoderData);
_audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
}
@@ -248,25 +249,36 @@ bool PmvPlayer::play(const char *filename) {
}
// Handle video
- imageData = frameData + READ_LE_UINT32(frameData + 12) - 8;
+ imageData = frameData + imageDataOfs;
// frameNum @0
+ uint32 imageChunkSize = READ_LE_UINT32(imageData) + 4;
+ // uint32 unknown = READ_LE_UINT32(imageData); // zero?
width = READ_LE_UINT16(imageData + 8);
height = READ_LE_UINT16(imageData + 10);
+
cmdOffs = READ_LE_UINT16(imageData + 12);
+ uint16 cmdFlags = READ_LE_UINT16(imageData + 14);
+
pixelOffs = READ_LE_UINT16(imageData + 16);
+ uint16 pixelFlags = READ_LE_UINT16(imageData + 18);
+
maskOffs = READ_LE_UINT16(imageData + 20);
+ uint16 maskFlags = READ_LE_UINT16(imageData + 22);
+
lineSize = READ_LE_UINT16(imageData + 24);
- debug(2, "width = %d; height = %d; cmdOffs = %04X; pixelOffs = %04X; maskOffs = %04X; lineSize = %d\n",
- width, height, cmdOffs, pixelOffs, maskOffs, lineSize);
+ debug(2, "width = %d; height = %d; cmdOffs = %04X; cmdFlags = %04X; pixelOffs = %04X; pixelFlags = %04X; maskOffs = %04X; maskFlags = %04X; lineSize = %d\n",
+ width, height, cmdOffs, cmdFlags, pixelOffs, pixelFlags, maskOffs, maskFlags, lineSize);
if (!_surface) {
_surface = new Graphics::Surface();
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
}
- decompressMovieImage(imageData, *_surface, cmdOffs, pixelOffs, maskOffs, lineSize);
+ decompressMovieImage(imageData, *_surface, cmdOffs, pixelOffs, maskOffs,
+ pixelOffs - cmdOffs, maskOffs - pixelOffs, imageChunkSize - maskOffs, lineSize,
+ cmdFlags, pixelFlags, maskFlags);
if (firstTime) {
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle, _audioStream);
Commit: 49e9753f7bdcb02a2467a637a07a336be8654d5d
https://github.com/scummvm/scummvm/commit/49e9753f7bdcb02a2467a637a07a336be8654d5d
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Add support for two V3.1 external script funcs
Add support for two external script function calls added by V3.1 games.
* CursorXY: warp mouse cursor to the provided X/Y coordinates
* SoundFile: load, decompress and play externally named sound file
Changed paths:
engines/made/pmvplayer.cpp
engines/made/resource.cpp
engines/made/scriptfuncs.cpp
engines/made/scriptfuncs.h
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 3cddca69e0c..1a3cf32e583 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -232,7 +232,7 @@ bool PmvPlayer::play(const char *filename) {
uint16 soundChunkSize = READ_LE_UINT16(audioData + 4);
chunkCount = READ_LE_UINT16(audioData + 6);
- debug(1, "chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, soundChunkSize, chunkCount * soundChunkSize);
+ debug(2, "SOUND: chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, soundChunkSize, chunkCount * soundChunkSize);
soundSize = chunkCount * soundChunkSize;
soundData = (byte *)malloc(soundSize);
diff --git a/engines/made/resource.cpp b/engines/made/resource.cpp
index cf7945c71fe..e6a0ef9d979 100644
--- a/engines/made/resource.cpp
+++ b/engines/made/resource.cpp
@@ -253,7 +253,9 @@ SoundResource::~SoundResource() {
}
void SoundResource::load(byte *source, int size) {
- uint16 chunkCount = READ_LE_UINT16(source + 8);
+ //uint32 iffType = READ_LE_UINT32(source);
+ //uint32 iffSize = READ_LE_UINT32(source + 4);
+ uint16 chunkCount = READ_LE_UINT16(source + 8); // perhaps should instead be UINT32?
uint16 chunkSize = READ_LE_UINT16(source + 12);
_soundSize = chunkCount * chunkSize;
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 02772c92bc2..eb4ea86257c 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -30,6 +30,7 @@
#include "backends/audiocd/audiocd.h"
+#include "common/file.h"
#include "common/config-manager.h"
#include "graphics/wincursor.h"
@@ -48,6 +49,39 @@ ScriptFunctions::~ScriptFunctions() {
delete _externalFuncs[i];
}
+// An inner function for playing a sound resource, either from a resource file
+// or loaded externally
+void ScriptFunctions::playSound(SoundResource *soundRes, bool externalFile) {
+ _vm->_autoStopSound = false;
+ stopSound();
+
+ _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle,
+ soundRes->getAudioStream(_vm->_soundRate, false), -1, _gameAudioVolume);
+ _vm->_soundEnergyArray = soundRes->getSoundEnergyArray();
+ _vm->_soundEnergyIndex = 0;
+ _soundStarted = true;
+ _soundWasPlaying = true;
+ _soundResource = soundRes;
+ _soundExternalFile = externalFile;
+
+ // The sound length in milliseconds for purpose of checking if the
+ // sound is still playing. This is 100 ms shorter than the actual
+ // length (see sfSoundPlaying).
+ uint32 soundLength = (_soundResource->getSoundSize() * 1000 / _vm->_soundRate);
+ _soundCheckLength = soundLength > 100 ? soundLength - 100 : 0;
+}
+
+void ScriptFunctions::stopSound() {
+ _vm->_mixer->stopHandle(_audioStreamHandle);
+ if (_soundStarted) {
+ if (_soundExternalFile)
+ delete _soundResource;
+ else
+ _vm->_res->freeResource(_soundResource);
+ _soundStarted = false;
+ }
+}
+
typedef Common::Functor2Mem<int16, int16*, int16, ScriptFunctions> ExternalScriptFunc;
#define External(x) \
_externalFuncs.push_back(new ExternalScriptFunc(this, &ScriptFunctions::x)); \
@@ -254,21 +288,10 @@ int16 ScriptFunctions::sfPlaySound(int16 argc, int16 *argv) {
soundNum = argv[1];
_vm->_autoStopSound = (argv[0] == 1);
}
- _soundWasPlaying = true;
- if (soundNum > 0) {
- SoundResource *soundRes = _vm->_res->getSound(soundNum);
- _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle,
- soundRes->getAudioStream(_vm->_soundRate, false), -1, _gameAudioVolume);
- _vm->_soundEnergyArray = soundRes->getSoundEnergyArray();
- _vm->_soundEnergyIndex = 0;
- _soundStarted = true;
- _soundResource = soundRes;
- // The sound length in milliseconds for purpose of checking if the
- // sound is still playing. This is 100 ms shorter than the actual
- // length (see sfSoundPlaying).
- uint32 soundLength = (_soundResource->getSoundSize() * 1000 / _vm->_soundRate);
- _soundCheckLength = soundLength > 100 ? soundLength - 100 : 0;
- }
+
+ if (soundNum > 0)
+ playSound(_vm->_res->getSound(soundNum), false);
+
return 0;
}
@@ -720,16 +743,6 @@ int16 ScriptFunctions::sfSoundPlaying(int16 argc, int16 *argv) {
}
-void ScriptFunctions::stopSound() {
- _vm->_mixer->stopHandle(_audioStreamHandle);
- if (_soundStarted) {
- _vm->_res->freeResource(_soundResource);
- _soundStarted = false;
- }
-
-}
-
-
int16 ScriptFunctions::sfStopSound(int16 argc, int16 *argv) {
stopSound();
_vm->_autoStopSound = false;
@@ -1149,19 +1162,40 @@ int16 ScriptFunctions::sfIsSlowSystem(int16 argc, int16 *argv) {
}
int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
- warning("Unimplemented opcode: sfMovieCall");
-
- return 0;
+ warning("Unimplemented opcode: sfMovieCall (%d, %d)", argv[0], argv[1]);
+ // TODO: This is incorrect: it is supposed to play the movie in the _background_
+ // rather than pausing the script and playing in the foreground
+ return sfPlayMovie(argc, argv);
}
int16 ScriptFunctions::sfCursorXY(int16 argc, int16 *argv) {
- warning("Unimplemented opcode: sfCursorXY");
-
+ g_system->warpMouse(argv[1], argv[0]);
return 0;
}
int16 ScriptFunctions::sfSoundFile(int16 argc, int16 *argv) {
- warning("Unimplemented opcode: sfSoundFile");
+ // Loads an external sound file (i.e. not from PRJ) and plays it
+ const char *soundName = _vm->_dat->getObjectString(argv[0]);
+
+ debug(1, "Playing external sound '%s'", soundName);
+
+ Common::File *_fd = new Common::File();
+ if (!_fd->open(Common::Path(soundName, '\\'))) {
+ delete _fd;
+ warning("Failed to open sound file '%s", soundName);
+ return 0;
+ }
+ byte *srcBuf = new byte[_fd->size()];
+ _fd->read(srcBuf, _fd->size());
+
+ // wrap this in a SoundResource
+ SoundResource *soundRes = new SoundResource();
+ soundRes->load(srcBuf, _fd->size());
+
+ // safe to free source now
+ delete[] srcBuf;
+
+ playSound(soundRes, true);
return 0;
}
diff --git a/engines/made/scriptfuncs.h b/engines/made/scriptfuncs.h
index cc40f137646..82df6b936c8 100644
--- a/engines/made/scriptfuncs.h
+++ b/engines/made/scriptfuncs.h
@@ -50,6 +50,9 @@ public:
void setupExternalsTable();
const char* getFuncName(int index) { return _externalFuncNames[index]; }
int getCount() const { return _externalFuncs.size(); }
+
+private:
+ void playSound(SoundResource *soundRes, bool externalFile);
void stopSound();
protected:
@@ -59,6 +62,7 @@ protected:
SoundResource* _soundResource;
bool _soundStarted;
bool _soundWasPlaying;
+ bool _soundExternalFile;
// The sound length in milliseconds for purpose of checking if the sound is
// still playing.
int _soundCheckLength;
Commit: 8e46a15765a01fa753ae0490a6ec9b5508489dd8
https://github.com/scummvm/scummvm/commit/8e46a15765a01fa753ae0490a6ec9b5508489dd8
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: break pmvplayer play() into separate routines.
sfMovieCall extension requires the ability to play one frame at a time.
Split play() into separate load / decode / close, and wrap the previous
behavion in play() by calling the new sub-functions.
Changed paths:
engines/made/pmvplayer.cpp
engines/made/pmvplayer.h
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 1a3cf32e583..e24000515ab 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -112,26 +112,27 @@ enum IntroTextFrame {
#endif
-PmvPlayer::PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer) : _fd(nullptr), _vm(vm), _mixer(mixer) {
- _audioStream = nullptr;
- _surface = nullptr;
- _aborted = false;
-}
+PmvPlayer::PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer) : _fd(nullptr), _vm(vm), _mixer(mixer),
+ _audioStream(nullptr), _surface(nullptr), frameData(nullptr), soundDecoderData(nullptr) {}
PmvPlayer::~PmvPlayer() {
+ // make sure to clean up any currently playing video at exit
+ close();
}
-bool PmvPlayer::play(const char *filename) {
- _aborted = false;
- _surface = nullptr;
+bool PmvPlayer::load(const char* filename) {
+ // open file. sometimes the script uses slashes for subdir,
+ // this should make it OS agnostic
_fd = new Common::File();
if (!_fd->open(Common::Path(filename, '\\'))) {
+ warning("Failed to open movie file '%s'", filename);
delete _fd;
return false;
}
- uint32 chunkType, chunkSize, prevChunkSize = 0;
+ // expected IFF blocks at start of a PMV
+ uint32 chunkType, chunkSize;
readChunk(chunkType, chunkSize); // "MOVE"
if (chunkType != MKTAG('M','O','V','E')) {
@@ -147,12 +148,12 @@ bool PmvPlayer::play(const char *filename) {
return false;
}
- uint frameDelay = _fd->readUint16LE();
+ frameDelay = _fd->readUint16LE();
_fd->skip(4); // always 0?
- uint frameCount = _fd->readUint16LE();
+ frameCount = _fd->readUint16LE();
_fd->skip(4); // always 0?
- uint soundFreq = _fd->readUint16LE();
+ soundFreq = _fd->readUint16LE();
// Note: There seem to be weird sound frequencies in PMV videos.
// Not sure why, but leaving those original frequencies intact
// results to sound being choppy. Therefore, we set them to more
@@ -169,107 +170,102 @@ bool PmvPlayer::play(const char *filename) {
debug(2, "%i ", unk);
}
- _mixer->stopAll();
-
- // Read palette
+ // Read and set initial palette
_fd->read(_paletteRGB, 768);
_vm->_screen->setRGBPalette(_paletteRGB);
- uint32 frameNumber = 0;
- uint16 chunkCount = 0;
- uint32 soundSize = 0;
- uint32 soundChunkOfs = 0, palChunkOfs = 0;
- uint32 palSize = 0;
- byte *frameData = nullptr, *audioData, *soundData, *palData, *imageData;
- bool firstTime = true;
-
- uint32 skipFrames = 0;
-
- uint32 bytesRead;
- uint16 width, height, cmdOffs, pixelOffs, maskOffs, lineSize;
-
// TODO: Sound can still be a little choppy. A bug in the decoder or -
// perhaps more likely - do we have to implement double buffering to
// get it to work well?
+ _mixer->stopAll();
_audioStream = Audio::makeQueuingAudioStream(soundFreq, false);
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle, _audioStream);
- SoundDecoderData *soundDecoderData = new SoundDecoderData();
+ soundDecoderData = new SoundDecoderData();
// First cutscene after the opening credits finish
if (strcmp(filename, "FWIZ01X1.PMV") == 0) {
_vm->_openingCreditsOpen = false;
}
- while (!_vm->shouldQuit() && !_aborted && !_fd->eos() && frameNumber < frameCount) {
+ // ready to go!
+ frameNumber = 0;
- int32 frameTime = _vm->getTotalPlayTime();
+ return true;
+}
- readChunk(chunkType, chunkSize);
- if (chunkType != MKTAG('M','F','R','M')) {
- warning("Unknown chunk type");
- }
+bool PmvPlayer::decode_frame() {
+ // Decode one frame, updating 'destinations' with new contents
- // Only reallocate the frame data buffer if its size has changed
- if (prevChunkSize != chunkSize || !frameData) {
- delete[] frameData;
- frameData = new byte[chunkSize];
- }
+ uint32 chunkType, chunkSize;
- prevChunkSize = chunkSize;
+ readChunk(chunkType, chunkSize);
+ if (chunkType != MKTAG('M','F','R','M')) {
+ warning("Unknown chunk type");
+ return false;
+ }
- bytesRead = _fd->read(frameData, chunkSize);
+ // Only reallocate the frame data buffer if it needs to grow
+ if (frameDataSize < chunkSize || !frameData) {
+ delete[] frameData;
- if (bytesRead < chunkSize || _fd->eos())
- break;
+ frameDataSize = chunkSize;
+ frameData = new byte[frameDataSize];
+ }
- soundChunkOfs = READ_LE_UINT32(frameData + 8);
- uint32 imageDataOfs = READ_LE_UINT32(frameData + 12) - 8;
- palChunkOfs = READ_LE_UINT32(frameData + 16);
+ uint32 bytesRead = _fd->read(frameData, chunkSize);
+ if (bytesRead < chunkSize || _fd->eos())
+ return false;
- // Handle audio
- if (soundChunkOfs) {
- audioData = frameData + soundChunkOfs - 8;
- uint16 soundChunkSize = READ_LE_UINT16(audioData + 4);
- chunkCount = READ_LE_UINT16(audioData + 6);
+ uint32 soundChunkOfs = READ_LE_UINT32(frameData + 8);
+ uint32 imageDataOfs = READ_LE_UINT32(frameData + 12);
+ uint32 palChunkOfs = READ_LE_UINT32(frameData + 16);
- debug(2, "SOUND: chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, soundChunkSize, chunkCount * soundChunkSize);
+ // Handle audio
+ if (soundChunkOfs) {
+ byte *audioData = frameData + soundChunkOfs - 8;
+ uint16 soundChunkSize = READ_LE_UINT16(audioData + 4);
+ uint16 chunkCount = READ_LE_UINT16(audioData + 6);
- soundSize = chunkCount * soundChunkSize;
- soundData = (byte *)malloc(soundSize);
- decompressSound(audioData + 8, soundData, soundChunkSize, chunkCount, nullptr, soundDecoderData);
- _audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- }
+ debug(2, "SOUND: chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, soundChunkSize, chunkCount * soundChunkSize);
- // Handle palette
- if (palChunkOfs) {
- palData = frameData + palChunkOfs - 8;
- palSize = READ_LE_UINT32(palData + 4);
- decompressPalette(palData + 8, _paletteRGB, palSize);
- _vm->_screen->setRGBPalette(_paletteRGB);
- }
+ uint32 soundSize = chunkCount * soundChunkSize;
+ byte *soundData = (byte *)malloc(soundSize);
+ decompressSound(audioData + 8, soundData, soundChunkSize, chunkCount, nullptr, soundDecoderData);
+ _audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
+ }
- // Handle video
- imageData = frameData + imageDataOfs;
+ // Handle palette changes
+ if (palChunkOfs) {
+ byte *palData = frameData + palChunkOfs - 8;
+ uint32 palSize = READ_LE_UINT32(palData + 4);
+ decompressPalette(palData + 8, _paletteRGB, palSize);
+ _vm->_screen->setRGBPalette(_paletteRGB);
+ }
+
+ // Handle video
+ if (imageDataOfs) {
+ byte *imageData = frameData + imageDataOfs - 8;
// frameNum @0
uint32 imageChunkSize = READ_LE_UINT32(imageData) + 4;
// uint32 unknown = READ_LE_UINT32(imageData); // zero?
- width = READ_LE_UINT16(imageData + 8);
- height = READ_LE_UINT16(imageData + 10);
+ uint16 width = READ_LE_UINT16(imageData + 8);
+ uint16 height = READ_LE_UINT16(imageData + 10);
- cmdOffs = READ_LE_UINT16(imageData + 12);
+ uint16 cmdOffs = READ_LE_UINT16(imageData + 12);
uint16 cmdFlags = READ_LE_UINT16(imageData + 14);
- pixelOffs = READ_LE_UINT16(imageData + 16);
+ uint16 pixelOffs = READ_LE_UINT16(imageData + 16);
uint16 pixelFlags = READ_LE_UINT16(imageData + 18);
- maskOffs = READ_LE_UINT16(imageData + 20);
+ uint16 maskOffs = READ_LE_UINT16(imageData + 20);
uint16 maskFlags = READ_LE_UINT16(imageData + 22);
- lineSize = READ_LE_UINT16(imageData + 24);
+ uint16 lineSize = READ_LE_UINT16(imageData + 24);
debug(2, "width = %d; height = %d; cmdOffs = %04X; cmdFlags = %04X; pixelOffs = %04X; pixelFlags = %04X; maskOffs = %04X; maskFlags = %04X; lineSize = %d\n",
- width, height, cmdOffs, cmdFlags, pixelOffs, pixelFlags, maskOffs, maskFlags, lineSize);
+ width, height, cmdOffs, cmdFlags, pixelOffs, pixelFlags, maskOffs, maskFlags, lineSize);
if (!_surface) {
_surface = new Graphics::Surface();
@@ -277,116 +273,148 @@ bool PmvPlayer::play(const char *filename) {
}
decompressMovieImage(imageData, *_surface, cmdOffs, pixelOffs, maskOffs,
- pixelOffs - cmdOffs, maskOffs - pixelOffs, imageChunkSize - maskOffs, lineSize,
- cmdFlags, pixelFlags, maskFlags);
+ pixelOffs - cmdOffs, maskOffs - pixelOffs, imageChunkSize - maskOffs, lineSize,
+ cmdFlags, pixelFlags, maskFlags);
- if (firstTime) {
- _mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle, _audioStream);
- skipFrames = 0;
- firstTime = false;
- }
+ _vm->_system->copyRectToScreen(_surface->getPixels(), _surface->pitch,
+ (320 - _surface->w) / 2, (200 - _surface->h) / 2, _surface->w, _surface->h);
+ }
- handleEvents();
- updateScreen();
+ frameNumber++;
- if (skipFrames == 0) {
- uint32 soundElapsedTime = _vm->_mixer->getElapsedTime(_audioStreamHandle).msecs();
- int32 waitTime = (frameNumber * frameDelay) -
- soundElapsedTime - (_vm->getTotalPlayTime() - frameTime);
+ return true;
+}
- if (waitTime < 0) {
- skipFrames = -waitTime / frameDelay;
- warning("Video A/V sync broken, skipping %d frame(s)", skipFrames + 1);
- } else if (waitTime > 0)
- g_system->delayMillis(waitTime);
+void PmvPlayer::close() {
+ // tear down video
+ if (_surface)
+ _surface->free();
+ delete _surface;
+ _surface = nullptr;
- } else
- skipFrames--;
+ delete[] frameData;
+ frameData = nullptr;
+ frameDataSize = 0;
+ // tear down audio
+ delete soundDecoderData;
+ soundDecoderData = nullptr;
+ if (_audioStream)
+ _audioStream->finish();
+ _mixer->stopHandle(_audioStreamHandle);
+ // delete _audioStream;
+ _audioStream = nullptr;
+
+ // close file
+ delete _fd;
+ _fd = nullptr;
+}
+
+bool PmvPlayer::play(const char *filename) {
+ bool aborted = false;
+
+ if (load(filename)) {
+ int32 pmvStartTime = _vm->getTotalPlayTime();
+
+ while (!_vm->shouldQuit() && ! aborted && !_fd->eos() && frameNumber < frameCount) {
+ // Decode and stage the next audio / video frame
+ if (!decode_frame()) {
+ break;
+ }
+
+ // delay until time has passed, then flip screen
+ int32 delayTime = (frameNumber - 1) * frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (delayTime < 0)
+ warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / frameDelay) + 1);
+ else
+ g_system->delayMillis(delayTime);
+
+ _vm->_system->updateScreen();
+
#ifdef USE_TTS
- if (strcmp(filename, "fintro00.pmv") == 0 || strcmp(filename, "fintro01.pmv") == 0) {
- const char **texts;
-
- switch (_vm->getLanguage()) {
- case Common::EN_ANY:
- if (frameNumber < kDesign) {
- texts = introOpeningLines;
- } else {
+ if (strcmp(filename, "fintro00.pmv") == 0 || strcmp(filename, "fintro01.pmv") == 0) {
+ const char **texts;
+
+ switch (_vm->getLanguage()) {
+ case Common::EN_ANY:
+ if (frameNumber < kDesign) {
+ texts = introOpeningLines;
+ } else {
+ texts = openingCreditsEnglish;
+ }
+ break;
+ case Common::DE_DEU:
+ texts = openingCreditsGerman;
+ break;
+ case Common::IT_ITA:
+ texts = openingCreditsItalian;
+ break;
+ case Common::FR_FRA:
+ texts = openingCreditsFrench;
+ break;
+ case Common::JA_JPN:
+ texts = openingCreditsJapanese;
+ break;
+ case Common::KO_KOR:
+ texts = openingCreditsEnglish;
+ break;
+ default:
texts = openingCreditsEnglish;
}
- break;
- case Common::DE_DEU:
- texts = openingCreditsGerman;
- break;
- case Common::IT_ITA:
- texts = openingCreditsItalian;
- break;
- case Common::FR_FRA:
- texts = openingCreditsFrench;
- break;
- case Common::JA_JPN:
- texts = openingCreditsJapanese;
- break;
- case Common::KO_KOR:
- texts = openingCreditsEnglish;
- break;
- default:
- texts = openingCreditsEnglish;
- }
- int index = -1;
+ int index = -1;
+
+ switch (frameNumber) {
+ case kStandingByHouse:
+ case kDesign:
+ index = 0;
+ break;
+ case kBehindHouse:
+ case kArtDirection:
+ index = 1;
+ break;
+ case kGoSouthwest:
+ case kTechnicalDirection:
+ index = 2;
+ break;
+ case kWestOfHouse:
+ case kScreenplay:
+ index = 3;
+ break;
+ case kOpenMailbox:
+ case kMusic:
+ index = 4;
+ break;
+ case kProducer:
+ index = 5;
+ }
- switch (frameNumber) {
- case kStandingByHouse:
- case kDesign:
- index = 0;
- break;
- case kBehindHouse:
- case kArtDirection:
- index = 1;
- break;
- case kGoSouthwest:
- case kTechnicalDirection:
- index = 2;
- break;
- case kWestOfHouse:
- case kScreenplay:
- index = 3;
- break;
- case kOpenMailbox:
- case kMusic:
- index = 4;
- break;
- case kProducer:
- index = 5;
+ if (index != -1 && (_vm->getLanguage() == Common::EN_ANY || frameNumber >= kDesign)) {
+ _vm->sayText(texts[index], Common::TextToSpeechManager::QUEUE);
+ }
}
+#endif
- if (index != -1 && (_vm->getLanguage() == Common::EN_ANY || frameNumber >= kDesign)) {
- _vm->sayText(texts[index], Common::TextToSpeechManager::QUEUE);
+ // Check and handle events - user can press ESC to exit early
+ Common::Event event;
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ aborted = true;
+ _vm->stopTextToSpeech();
+ }
+ break;
+ default:
+ break;
+ }
}
}
-#endif
-
- frameNumber++;
+ close();
}
- delete soundDecoderData;
- delete[] frameData;
-
- _audioStream->finish();
- _mixer->stopHandle(_audioStreamHandle);
-
- //delete _audioStream;
- delete _fd;
-
- if(_surface)
- _surface->free();
-
- delete _surface;
-
- return !_aborted;
-
+ return !aborted;
}
void PmvPlayer::readChunk(uint32 &chunkType, uint32 &chunkSize) {
@@ -400,28 +428,6 @@ void PmvPlayer::readChunk(uint32 &chunkType, uint32 &chunkSize) {
}
-void PmvPlayer::handleEvents() {
- Common::Event event;
- while (_vm->_system->getEventManager()->pollEvent(event)) {
- switch (event.type) {
- case Common::EVENT_KEYDOWN:
- if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
- _aborted = true;
- _vm->stopTextToSpeech();
- }
- break;
- default:
- break;
- }
- }
-}
-
-void PmvPlayer::updateScreen() {
- _vm->_system->copyRectToScreen(_surface->getPixels(), _surface->pitch,
- (320 - _surface->w) / 2, (200 - _surface->h) / 2, _surface->w, _surface->h);
- _vm->_system->updateScreen();
-}
-
void PmvPlayer::decompressPalette(byte *palData, byte *outPal, uint32 palDataSize) {
byte *palDataEnd = palData + palDataSize;
while (palData < palDataEnd) {
diff --git a/engines/made/pmvplayer.h b/engines/made/pmvplayer.h
index 720130bfea9..cea8969d01e 100644
--- a/engines/made/pmvplayer.h
+++ b/engines/made/pmvplayer.h
@@ -23,6 +23,7 @@
#define MADE_PMVPLAYER_H
#include "audio/mixer.h"
+#include "made/sound.h"
namespace Common {
class File;
@@ -44,23 +45,42 @@ class PmvPlayer {
public:
PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer);
~PmvPlayer();
- // Returns true if the movie was played till the end
+
+ // individual file actions
+ bool load(const char *filename);
+ bool decode_frame();
+ void close();
+
+ // wrapper for all three above - Returns true if the movie was played till the end
bool play(const char *filename);
+
+ // info about currently playing movie
+ uint16 frameDelay, frameCount, frameNumber, soundFreq;
+
protected:
+ // ptrs to caller objects
MadeEngine *_vm;
Audio::Mixer *_mixer;
+
+ // currently open file
Common::File *_fd;
- Audio::QueuingAudioStream *_audioStream;
- Audio::SoundHandle _audioStreamHandle;
+
+ // decode destination objects
+ // image
byte _paletteRGB[768];
Graphics::Surface *_surface;
- bool _aborted;
+ byte *frameData;
+ uint32 frameDataSize;
+ // audio
+ Audio::QueuingAudioStream *_audioStream;
+ Audio::SoundHandle _audioStreamHandle;
+ SoundDecoderData *soundDecoderData;
+
+ // helper funcs
void readChunk(uint32 &chunkType, uint32 &chunkSize);
- void handleEvents();
- void updateScreen();
void decompressPalette(byte *palData, byte *outPal, uint32 palDataSize);
};
}
-#endif
+#endif /* MADE_PMVPLAYER_H */
Commit: 7c38eac7c78f9c6986d351bda1f2225999cbdf81
https://github.com/scummvm/scummvm/commit/7c38eac7c78f9c6986d351bda1f2225999cbdf81
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Support sfMovieCall script function
Adds support for the sfMovieCall script function, which plays a
movie, but also executes a script function on every frame.
This makes the full versions of RSBUSYNDE / RSBESTNDE playable.
Changed paths:
engines/made/made.cpp
engines/made/pmvplayer.h
engines/made/script.cpp
engines/made/script.h
engines/made/scriptfuncs.cpp
diff --git a/engines/made/made.cpp b/engines/made/made.cpp
index aa2c576143f..dc908abfeb2 100644
--- a/engines/made/made.cpp
+++ b/engines/made/made.cpp
@@ -500,7 +500,8 @@ Common::Error MadeEngine::run() {
if (! _useWinCursors)
_screen->setDefaultMouseCursor();
- _script->runScript(_dat->getMainCodeObjectIndex());
+ int16 scriptReturn = _script->runScript(_dat->getMainCodeObjectIndex());
+ debug(3, "Main script return code: %04X (%d)", scriptReturn, scriptReturn);
#endif
if (_music)
diff --git a/engines/made/pmvplayer.h b/engines/made/pmvplayer.h
index cea8969d01e..dd022522257 100644
--- a/engines/made/pmvplayer.h
+++ b/engines/made/pmvplayer.h
@@ -54,6 +54,8 @@ public:
// wrapper for all three above - Returns true if the movie was played till the end
bool play(const char *filename);
+ // currently open file
+ Common::File *_fd;
// info about currently playing movie
uint16 frameDelay, frameCount, frameNumber, soundFreq;
@@ -62,9 +64,6 @@ protected:
MadeEngine *_vm;
Audio::Mixer *_mixer;
- // currently open file
- Common::File *_fd;
-
// decode destination objects
// image
byte _paletteRGB[768];
diff --git a/engines/made/script.cpp b/engines/made/script.cpp
index 630415e93d8..aaa4364c5b8 100644
--- a/engines/made/script.cpp
+++ b/engines/made/script.cpp
@@ -125,6 +125,7 @@ ScriptInterpreter::ScriptInterpreter(MadeEngine *vm) : _vm(vm) {
_runningScriptObjectIndex = 0;
_codeBase = nullptr;
_codeIp = nullptr;
+ _running = false;
#undef COMMAND
}
@@ -133,7 +134,7 @@ ScriptInterpreter::~ScriptInterpreter() {
delete _functions;
}
-void ScriptInterpreter::runScript(int16 scriptObjectIndex) {
+int16 ScriptInterpreter::runScript(int16 scriptObjectIndex) {
uint32 opcodeSleepCounter = 0;
@@ -143,8 +144,9 @@ void ScriptInterpreter::runScript(int16 scriptObjectIndex) {
_codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData();
_codeIp = _codeBase;
+ _running = true;
- while (!_vm->shouldQuit()) {
+ while (_running && !_vm->shouldQuit()) {
byte opcode = readByte();
if (opcode >= 1 && opcode <= _commandsMax) {
@@ -160,8 +162,11 @@ void ScriptInterpreter::runScript(int16 scriptObjectIndex) {
_vm->_screen->updateScreenAndWait(5);
opcodeSleepCounter = 0;
}
-
}
+
+ int16 retval = _stack.pop();
+ _stack.pop(); // pop junk value and discard
+ return retval;
}
byte ScriptInterpreter::readByte() {
@@ -380,9 +385,7 @@ void ScriptInterpreter::cmd_return() {
// Check if returning from main function
if (_localStackPos == kScriptStackSize) {
- _vm->quitGame();
- // Make sure the "quit" event is handled immediately
- _vm->handleEvents();
+ _running = false;
return;
}
diff --git a/engines/made/script.h b/engines/made/script.h
index f37382a1dab..057ca968805 100644
--- a/engines/made/script.h
+++ b/engines/made/script.h
@@ -45,7 +45,7 @@ public:
_stackPos = kScriptStackSize;
}
~ScriptStack() {}
- inline int16 top() { return _stack[_stackPos]; }
+ inline int16 top() const { return _stack[_stackPos]; }
inline int16 pop() {
if (_stackPos == kScriptStackSize)
error("ScriptStack::pop() Stack underflow");
@@ -57,7 +57,7 @@ public:
_stack[--_stackPos] = value;
}
inline void setTop(int16 value) { _stack[_stackPos] = value; }
- inline int16 peek(int16 index) { return _stack[index]; }
+ inline int16 peek(int16 index) const { return _stack[index]; }
inline void poke(int16 index, int16 value) { _stack[index] = value; }
inline void alloc(int16 count) { _stackPos -= count; }
inline void free(int16 count) { _stackPos += count; }
@@ -73,7 +73,7 @@ class ScriptInterpreter {
public:
ScriptInterpreter(MadeEngine *vm);
~ScriptInterpreter();
- void runScript(int16 scriptObjectIndex);
+ int16 runScript(int16 scriptObjectIndex);
#ifdef DUMP_SCRIPTS
void dumpScript(int16 objectIndex, int *opcodeStats, int *externStats);
void dumpObject(int16 objectIndex);
@@ -86,6 +86,7 @@ protected:
int16 _localStackPos;
int16 _runningScriptObjectIndex;
byte *_codeBase, *_codeIp;
+ bool _running;
ScriptFunctions *_functions;
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index eb4ea86257c..e5d81a90174 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -25,6 +25,7 @@
#include "made/music.h"
#include "made/database.h"
#include "made/pmvplayer.h"
+#include "made/script.h"
#include "audio/sine.h"
@@ -1162,10 +1163,40 @@ int16 ScriptFunctions::sfIsSlowSystem(int16 argc, int16 *argv) {
}
int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
- warning("Unimplemented opcode: sfMovieCall (%d, %d)", argv[0], argv[1]);
- // TODO: This is incorrect: it is supposed to play the movie in the _background_
- // rather than pausing the script and playing in the foreground
- return sfPlayMovie(argc, argv);
+ // Plays a movie, but also calls a script function on every frame.
+ // The script function returns a uint16 which indicates
+ // whether to keep playing the movie, or abort early.
+ const char *filename = _vm->_dat->getObjectString(argv[1]);
+
+ if (_vm->_pmvPlayer->load(filename)) {
+ // set up a script interpreter for calling the sub
+ ScriptInterpreter *si = new ScriptInterpreter(_vm);
+ int16 playing = true;
+
+ int32 pmvStartTime = _vm->getTotalPlayTime() + _vm->_pmvPlayer->frameDelay;
+
+ while (!_vm->shouldQuit() && !_vm->_pmvPlayer->_fd->eos() && _vm->_pmvPlayer->frameNumber < _vm->_pmvPlayer->frameCount && playing) {
+ // Decode and stage the next audio / video frame
+ if (!_vm->_pmvPlayer->decode_frame())
+ break;
+
+ // delay until time has passed, then flip screen
+ int32 delayTime = (_vm->_pmvPlayer->frameNumber - 1) * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (delayTime < 0)
+ warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / _vm->_pmvPlayer->frameDelay) + 1);
+ else
+ g_system->delayMillis(delayTime);
+
+ // call subroutine each frame
+ playing = si->runScript(argv[0]);
+ debug(3, "Call script return code: %04X (%d)", playing, playing);
+ }
+
+ _vm->_pmvPlayer->close();
+ return true;
+ }
+
+ return false;
}
int16 ScriptFunctions::sfCursorXY(int16 argc, int16 *argv) {
Commit: 1c965a458dbac87f29c71a5e0726f675df2517c0
https://github.com/scummvm/scummvm/commit/1c965a458dbac87f29c71a5e0726f675df2517c0
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: use dynamic buffers for rleDecompress()
RLE decompression output size is unpredictable and the
previous calculation for fixed buffers was incorrect in
most cases. Use dynamic arrays instead, with static
scope so they do not need to be re-allocated or deleted
on every frame.
Also silences a warning about A/V sync on the first frame
of every movie.
Changed paths:
engines/made/graphics.cpp
engines/made/pmvplayer.cpp
engines/made/scriptfuncs.cpp
diff --git a/engines/made/graphics.cpp b/engines/made/graphics.cpp
index 225af42dcc4..29d9592a8b6 100644
--- a/engines/made/graphics.cpp
+++ b/engines/made/graphics.cpp
@@ -21,6 +21,7 @@
#include "made/graphics.h"
+#include <common/array.h>
#include "common/endian.h"
#include "common/textconsole.h"
#include "common/debug.h"
@@ -63,29 +64,30 @@ void ValueReader::resetNibbleSwitch() {
_nibbleSwitch = false;
}
-byte *rleDecompress(byte *source, const byte *end, const int maxSize) {
- byte *dest = new byte[maxSize];
- byte *o = dest;
+static uint rleDecompress(byte *source, const byte *end, Common::Array<byte> &dest) {
+ dest.resize(0);
while (source < end) {
- byte val = *source;
+ byte count = *source;
source++;
- if (val < 0x80) {
+ if (count < 0x80) {
// copy
- val++;
- memcpy(o, source, val);
- o += val;
- source += val;
+ for (count++; count > 0; count--) {
+ dest.push_back(*source);
+ source++;
+ }
} else {
// repeat
- val = 257 - val;
- memset(o, *source, val);
- o += val;
+ for (count = 257 - count; count > 0; count --)
+ dest.push_back(*source);
source++;
}
}
- return dest;
+ if (source > end)
+ warning("graphics::rleDecompress(): input stream over-read by %d bytes!", end - source);
+
+ return dest.size();
}
void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame) {
@@ -218,27 +220,38 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
byte cmdFlags, byte pixelFlags, byte maskFlags) {
+ // RLE decompression buffers
+ static Common::Array<byte> cmdArray = Common::Array<byte>(4800);
+ static Common::Array<byte> pixelArray = Common::Array<byte>();
+ static Common::Array<byte> maskArray = Common::Array<byte>();
+
+ if ((maskFlags & 0b11111110) || (pixelFlags & 0b11111110) || (cmdFlags & 0b11111110))
+ error("decompressMovieImage() Unsupported flags: cmdFlags = %02X; maskFlags = %02X, pixelFlags = %02X", cmdFlags, maskFlags, pixelFlags);
+
uint16 width = surface.w;
uint16 height = surface.h;
- uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4, bh = ((height + 3) / 4) * 4;
+ uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4;
- // RLE decompress the buffers as needed (remember to free later!)
+ // RLE decompress the buffers as needed
byte *cmdBuffer;
- if (cmdFlags & 1)
- cmdBuffer = rleDecompress(source + cmdOffs, source + cmdOffs + cmdSize, 4800);
- else
+ if (cmdFlags & 1) {
+ rleDecompress(source + cmdOffs, source + cmdOffs + cmdSize, cmdArray);
+ cmdBuffer = cmdArray.data();
+ } else
cmdBuffer = source + cmdOffs;
byte *pixelBuffer;
- if (pixelFlags & 1)
- pixelBuffer = rleDecompress(source + pixelOffs, source + pixelOffs + pixelSize, bw * bh);
- else
+ if (pixelFlags & 1) {
+ rleDecompress(source + pixelOffs, source + pixelOffs + pixelSize, pixelArray);
+ pixelBuffer = pixelArray.data();
+ } else
pixelBuffer = source + pixelOffs;
byte *maskBuffer;
- if (maskFlags & 1)
- maskBuffer = rleDecompress(source + maskOffs, source + maskOffs + maskSize, bw * bh);
- else
+ if (maskFlags & 1) {
+ rleDecompress(source + maskOffs, source + maskOffs + maskSize, maskArray);
+ maskBuffer = maskArray.data();
+ } else
maskBuffer = source + maskOffs;
//
@@ -279,12 +292,14 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
switch (cmd) {
case 0:
+ // solid color fill of block
pixels[0] = *pPixelBuf++;
for (int i = 0; i < 16; i++)
block[i] = pixels[0];
break;
case 1:
+ // 2-color block
pixels[0] = *pPixelBuf++;
pixels[1] = *pPixelBuf++;
mask = READ_LE_UINT16(pMaskBuf);
@@ -296,6 +311,7 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
break;
case 2:
+ // 4 colors
pixels[0] = *pPixelBuf++;
pixels[1] = *pPixelBuf++;
pixels[2] = *pPixelBuf++;
@@ -308,10 +324,9 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
}
break;
- case 3:
- break;
-
+ // case 3:
default:
+ // "Skip" (delta frame optimization)
break;
}
@@ -340,14 +355,6 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
height -= 4;
}
-
- // cleanup RLE buffers
- if (cmdFlags & 1)
- delete[] cmdBuffer;
- if (maskFlags & 1)
- delete[] maskBuffer;
- if (pixelFlags & 1)
- delete[] pixelBuffer;
}
} // End of namespace Made
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index e24000515ab..39d8842d808 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -249,7 +249,7 @@ bool PmvPlayer::decode_frame() {
// frameNum @0
uint32 imageChunkSize = READ_LE_UINT32(imageData) + 4;
- // uint32 unknown = READ_LE_UINT32(imageData); // zero?
+ // uint32 unknown = READ_LE_UINT32(imageData + 4); // zero?
uint16 width = READ_LE_UINT16(imageData + 8);
uint16 height = READ_LE_UINT16(imageData + 10);
@@ -314,7 +314,7 @@ bool PmvPlayer::play(const char *filename) {
bool aborted = false;
if (load(filename)) {
- int32 pmvStartTime = _vm->getTotalPlayTime();
+ uint32 pmvStartTime = _vm->getTotalPlayTime();
while (!_vm->shouldQuit() && ! aborted && !_fd->eos() && frameNumber < frameCount) {
// Decode and stage the next audio / video frame
@@ -323,11 +323,13 @@ bool PmvPlayer::play(const char *filename) {
}
// delay until time has passed, then flip screen
- int32 delayTime = (frameNumber - 1) * frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
- if (delayTime < 0)
- warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / frameDelay) + 1);
- else
- g_system->delayMillis(delayTime);
+ if (frameNumber > 1) {
+ int32 delayTime = (frameNumber - 1) * frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (delayTime < 0)
+ warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / frameDelay) + 1);
+ else
+ g_system->delayMillis(delayTime);
+ }
_vm->_system->updateScreen();
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index e5d81a90174..d490fe4ace5 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -1173,7 +1173,7 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
ScriptInterpreter *si = new ScriptInterpreter(_vm);
int16 playing = true;
- int32 pmvStartTime = _vm->getTotalPlayTime() + _vm->_pmvPlayer->frameDelay;
+ int32 pmvStartTime = _vm->getTotalPlayTime();
while (!_vm->shouldQuit() && !_vm->_pmvPlayer->_fd->eos() && _vm->_pmvPlayer->frameNumber < _vm->_pmvPlayer->frameCount && playing) {
// Decode and stage the next audio / video frame
@@ -1181,11 +1181,13 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
break;
// delay until time has passed, then flip screen
- int32 delayTime = (_vm->_pmvPlayer->frameNumber - 1) * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
- if (delayTime < 0)
- warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / _vm->_pmvPlayer->frameDelay) + 1);
- else
- g_system->delayMillis(delayTime);
+ if (_vm->_pmvPlayer->frameNumber > 1) {
+ int32 delayTime = (_vm->_pmvPlayer->frameNumber - 1) * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (delayTime < 0)
+ warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / _vm->_pmvPlayer->frameDelay) + 1);
+ else
+ g_system->delayMillis(delayTime);
+ }
// call subroutine each frame
playing = si->runScript(argv[0]);
Commit: 2dd0cca2919de1f4ccdc27420ca0ba68aeb67dca
https://github.com/scummvm/scummvm/commit/2dd0cca2919de1f4ccdc27420ca0ba68aeb67dca
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: allow arg-passing to ScriptInterpreter::runScript()
Some scripts (specifically, sfMovieCall-invoked) want to
read arguments passed in. A new ScriptInterpreter has no
call frame setup, and so attempting to read args would
peek out-of-bounds, returning uninitialized data instead.
This commit changes runScript to set up a correct-looking
call frame on the stack before executing, where args are
put on the stack at the expected place.
sfMovieCall sends one argument to the sub-function now,
which is the ID of the playing movie. It isn't clear to
me if this is correct or not: the script seems to check
the value with ">= 0", so perhaps it is meant to be the
remaining frame-count of the movie or the duration left.
Regardless, sending a predictable positive value every
time instead of uninitialized garbage fixes intermittent
errors with RSBESTNDE / RSBUSYNDE skipping every movie
at the first frame.
Changed paths:
engines/made/script.cpp
engines/made/script.h
engines/made/scriptfuncs.cpp
diff --git a/engines/made/script.cpp b/engines/made/script.cpp
index aaa4364c5b8..3c6bf28f4c0 100644
--- a/engines/made/script.cpp
+++ b/engines/made/script.cpp
@@ -121,6 +121,7 @@ ScriptInterpreter::ScriptInterpreter(MadeEngine *vm) : _vm(vm) {
_functions = new ScriptFunctions(_vm);
_functions->setupExternalsTable();
+ _entryStackPos = 0;
_localStackPos = 0;
_runningScriptObjectIndex = 0;
_codeBase = nullptr;
@@ -134,13 +135,21 @@ ScriptInterpreter::~ScriptInterpreter() {
delete _functions;
}
-int16 ScriptInterpreter::runScript(int16 scriptObjectIndex) {
+int16 ScriptInterpreter::runScript(int16 scriptObjectIndex, int16 argc, int16 *argv) {
uint32 opcodeSleepCounter = 0;
_runningScriptObjectIndex = scriptObjectIndex;
- _localStackPos = _stack.getStackPos();
+ // push optional args and other starting info of a cmd_call
+ for (int16 i = argc - 1; i >= 0; i --)
+ _stack.push(argv[i]);
+ _stack.push(argc);
+ _stack.push(0); // codeIp - codeBase
+ _stack.push(scriptObjectIndex);
+ _stack.push(0); // stack usage
+
+ _entryStackPos = _localStackPos = _stack.getStackPos();
_codeBase = _vm->_dat->getObject(_runningScriptObjectIndex)->getData();
_codeIp = _codeBase;
@@ -164,8 +173,8 @@ int16 ScriptInterpreter::runScript(int16 scriptObjectIndex) {
}
}
- int16 retval = _stack.pop();
- _stack.pop(); // pop junk value and discard
+ int16 retval = _stack.top();
+ _stack.setStackPos(kScriptStackSize);
return retval;
}
@@ -384,7 +393,7 @@ void ScriptInterpreter::cmd_exit() {
void ScriptInterpreter::cmd_return() {
// Check if returning from main function
- if (_localStackPos == kScriptStackSize) {
+ if (_localStackPos == _entryStackPos) {
_running = false;
return;
}
diff --git a/engines/made/script.h b/engines/made/script.h
index 057ca968805..a303859bcdc 100644
--- a/engines/made/script.h
+++ b/engines/made/script.h
@@ -57,12 +57,30 @@ public:
_stack[--_stackPos] = value;
}
inline void setTop(int16 value) { _stack[_stackPos] = value; }
- inline int16 peek(int16 index) const { return _stack[index]; }
- inline void poke(int16 index, int16 value) { _stack[index] = value; }
+ inline int16 peek(int16 index) const {
+ if (index >= kScriptStackSize)
+ error("ScriptStack::peek(%d) Stack underflow", index);
+ else if (index < 0)
+ error("ScriptStack::peek(%d) Stack overflow", index);
+ return _stack[index];
+ }
+ inline void poke(int16 index, int16 value) {
+ if (index >= kScriptStackSize)
+ error("ScriptStack::poke(%d) Stack underflow", index);
+ else if (index < 0)
+ error("ScriptStack::poke(%d) Stack overflow", index);
+ _stack[index] = value;
+ }
inline void alloc(int16 count) { _stackPos -= count; }
inline void free(int16 count) { _stackPos += count; }
inline int16 getStackPos() const { return _stackPos; }
- inline void setStackPos(int16 stackPtr) { _stackPos = stackPtr; }
+ inline void setStackPos(int16 stackPtr) {
+ if (stackPtr > kScriptStackSize)
+ error("ScriptStack::setStackPos(%d) Stack underflow", stackPtr);
+ else if (stackPtr < 0)
+ error("ScriptStack::setStackPos(%d) Stack overflow", stackPtr);
+ _stackPos = stackPtr;
+ }
inline int16 *getStackPtr() { return &_stack[_stackPos]; }
protected:
int16 _stack[kScriptStackSize];
@@ -73,7 +91,7 @@ class ScriptInterpreter {
public:
ScriptInterpreter(MadeEngine *vm);
~ScriptInterpreter();
- int16 runScript(int16 scriptObjectIndex);
+ int16 runScript(int16 scriptObjectIndex, int16 argc = 0, int16 *argv = nullptr);
#ifdef DUMP_SCRIPTS
void dumpScript(int16 objectIndex, int *opcodeStats, int *externStats);
void dumpObject(int16 objectIndex);
@@ -83,7 +101,7 @@ protected:
MadeEngine *_vm;
ScriptStack _stack;
- int16 _localStackPos;
+ int16 _entryStackPos, _localStackPos;
int16 _runningScriptObjectIndex;
byte *_codeBase, *_codeIp;
bool _running;
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index d490fe4ace5..432b7c9f7ae 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -1190,7 +1190,8 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
}
// call subroutine each frame
- playing = si->runScript(argv[0]);
+ // pass the movie ID as the one arg
+ playing = si->runScript(argv[0], 1, &argv[1]);
debug(3, "Call script return code: %04X (%d)", playing, playing);
}
Commit: 9480d700152c727b0b602fb3912d48c64e1e073a
https://github.com/scummvm/scummvm/commit/9480d700152c727b0b602fb3912d48c64e1e073a
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Do not create screen mask for RSB*NDE
Screen mask is only used with V2 games and not needed here.
Fixes up some other minor GID checked behaviors as well.
Changed paths:
engines/made/screen.cpp
engines/made/scriptfuncs.cpp
diff --git a/engines/made/screen.cpp b/engines/made/screen.cpp
index 2920a78a2a1..2d9b64ea1fd 100644
--- a/engines/made/screen.cpp
+++ b/engines/made/screen.cpp
@@ -68,7 +68,7 @@ Screen::Screen(MadeEngine *vm) : _vm(vm) {
_clipArea.destSurface = _workScreen;
// Screen mask is only needed in v2 games
- if (_vm->getGameID() != GID_RTZ) {
+ if (_vm->getGameID() != GID_RTZ && _vm->getGameID() != GID_RSBESTNDE && _vm->getGameID() != GID_RSBUSYNDE) {
_screenMask = new Graphics::Surface();
_screenMask->create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
_maskDrawCtx.clipRect = Common::Rect(320, 200);
@@ -123,7 +123,7 @@ Screen::~Screen() {
delete _backgroundScreen;
delete _workScreen;
- if (_vm->getGameID() != GID_RTZ)
+ if (_vm->getGameID() != GID_RTZ && _vm->getGameID() != GID_RSBESTNDE && _vm->getGameID() != GID_RSBUSYNDE)
delete _screenMask;
delete _fx;
}
@@ -131,7 +131,7 @@ Screen::~Screen() {
void Screen::clearScreen() {
_backgroundScreen->fillRect(Common::Rect(0, 0, 320, 200), 0);
_workScreen->fillRect(Common::Rect(0, 0, 320, 200), 0);
- if (_vm->getGameID() != GID_RTZ)
+ if (_vm->getGameID() != GID_RTZ && _vm->getGameID() != GID_RSBESTNDE && _vm->getGameID() != GID_RSBUSYNDE)
_screenMask->fillRect(Common::Rect(0, 0, 320, 200), 0);
_mask = 0;
_needPalette = true;
@@ -202,7 +202,8 @@ void Screen::drawSurface(Graphics::Surface *sourceSurface, int x, int y, int16 f
source = (byte *)sourceSurface->getBasePtr(0, startY);
dest = (byte *)clipInfo.destSurface->getBasePtr(x, y);
- if (_vm->getGameID() != GID_RTZ)
+ // only set up maskp if game uses masking and mask requested in f.call
+ if (mask && (_vm->getGameID() != GID_RTZ && _vm->getGameID() != GID_RSBESTNDE && _vm->getGameID() != GID_RSBUSYNDE))
maskp = (byte *)_maskDrawCtx.destSurface->getBasePtr(x, y);
int32 sourcePitch, linePtrAdd, sourceAdd;
@@ -226,16 +227,14 @@ void Screen::drawSurface(Graphics::Surface *sourceSurface, int x, int y, int16 f
for (int16 yc = 0; yc < clipHeight; yc++) {
linePtr = source + sourceAdd;
for (int16 xc = 0; xc < clipWidth; xc++) {
- if (*linePtr && (_vm->getGameID() == GID_RTZ || (mask == 0 || (maskp && maskp[xc] == 0)))) {
- if (*linePtr)
- dest[xc] = *linePtr;
- }
+ if (*linePtr && (! maskp || maskp[xc] == 0))
+ dest[xc] = *linePtr;
linePtr += linePtrAdd;
}
source += sourcePitch;
dest += clipInfo.destSurface->pitch;
- if (_vm->getGameID() != GID_RTZ)
+ if (maskp)
maskp += _maskDrawCtx.destSurface->pitch;
}
@@ -907,7 +906,7 @@ void Screen::printTextEx(const char *text, int16 x, int16 y, int16 fontNum, int1
setFont(oldFontNum);
_fontDrawCtx = oldFontDrawCtx;
- if (_vm->getGameID() != GID_RTZ && _vm->getGameID() != GID_LGOP2) {
+ if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
_vm->sayText(text);
}
}
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 432b7c9f7ae..260089330d7 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -451,12 +451,10 @@ int16 ScriptFunctions::sfSetScreenLock(int16 argc, int16 *argv) {
}
int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {
- if (_vm->getGameID() == GID_RTZ) {
- // Unused in RTZ
- return 0;
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
+ if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
return _vm->_screen->addToSpriteList(argv[2], argv[1], argv[0]);
} else {
+ // Unused in RTZ, RSBESTNDE, RSBUSYNDE
return 0;
}
}
@@ -472,7 +470,7 @@ int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfDrawSprite(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
return _vm->_screen->drawSprite(argv[2], argv[1], argv[0]);
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
+ } else if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
SpriteListItem item = _vm->_screen->getFromSpriteList(argv[2]);
int16 channelIndex = _vm->_screen->drawSprite(item.index, argv[1] - item.xofs, argv[0] - item.yofs);
_vm->_screen->setChannelUseMask(channelIndex);
@@ -489,6 +487,7 @@ int16 ScriptFunctions::sfDrawSprite(int16 argc, int16 *argv) {
return 0;
} else {
+ // Unused in RSBESTNDE, RSBUSYNDE
return 0;
}
}
@@ -545,6 +544,7 @@ int16 ScriptFunctions::sfDrawText(int16 argc, int16 *argv) {
} else if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
text = _vm->_dat->getString(argv[argc - 1]);
}
+ // Unused in RSBESTNDE, RSBUSYNDE
if (text) {
Common::String finalText;
Commit: 837afb7282fd7385dc001b0b33143a09c5aab6d5
https://github.com/scummvm/scummvm/commit/837afb7282fd7385dc001b0b33143a09c5aab6d5
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Fixes based on PR feedback
Changed paths:
engines/made/database.cpp
engines/made/pmvplayer.cpp
engines/made/scriptfuncs.cpp
diff --git a/engines/made/database.cpp b/engines/made/database.cpp
index 7aace430862..941d008971e 100644
--- a/engines/made/database.cpp
+++ b/engines/made/database.cpp
@@ -287,6 +287,8 @@ int ObjectV3_1::load(byte *source) {
_objData = source;
_freeData = false;
if (getClass() < 0x7FFE) {
+ _objSize = getSize() * 4;
+ } else if (getClass() == 0x7FFE) {
_objSize = getSize() * 2;
} else {
_objSize = getSize();
@@ -686,7 +688,7 @@ void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
// Constant objects are loaded from disk, while variable objects exist
// in the _gameState buffer.
if (objectOffsets[i] & 1) {
- sourceS.seek(objectsOffs + objectOffsets[i] ^ 1);
+ sourceS.seek(objectsOffs + (objectOffsets[i] ^ 1));
obj->load(sourceS);
} else {
obj->load(_gameState + objectOffsets[i]);
@@ -923,7 +925,7 @@ void GameDatabaseV3_1::load(Common::SeekableReadStream &sourceS) {
// Constant objects are loaded from disk, while variable objects exist
// in the _gameState buffer.
if (objectOffsets[i] & 1) {
- sourceS.seek(objectsOffs + objectOffsets[i] ^ 1);
+ sourceS.seek(objectsOffs + (objectOffsets[i] ^ 1));
obj->load(sourceS);
} else {
obj->load(_gameState + objectOffsets[i]);
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 39d8842d808..c96b2cd931f 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -113,7 +113,7 @@ enum IntroTextFrame {
#endif
PmvPlayer::PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer) : _fd(nullptr), _vm(vm), _mixer(mixer),
- _audioStream(nullptr), _surface(nullptr), frameData(nullptr), soundDecoderData(nullptr) {}
+ _audioStream(nullptr), _surface(nullptr), frameDataSize(0), frameData(nullptr), soundDecoderData(nullptr) {}
PmvPlayer::~PmvPlayer() {
// make sure to clean up any currently playing video at exit
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 260089330d7..294572cc379 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -53,11 +53,8 @@ ScriptFunctions::~ScriptFunctions() {
// An inner function for playing a sound resource, either from a resource file
// or loaded externally
void ScriptFunctions::playSound(SoundResource *soundRes, bool externalFile) {
- _vm->_autoStopSound = false;
- stopSound();
-
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle,
- soundRes->getAudioStream(_vm->_soundRate, false), -1, _gameAudioVolume);
+ soundRes->getAudioStream(_vm->_soundRate, false), -1, _gameAudioVolume);
_vm->_soundEnergyArray = soundRes->getSoundEnergyArray();
_vm->_soundEnergyIndex = 0;
_soundStarted = true;
Commit: e3f9c3ee49b4c15fcf4f3bf7fecc455471a90bbe
https://github.com/scummvm/scummvm/commit/e3f9c3ee49b4c15fcf4f3bf7fecc455471a90bbe
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: allow non-constant frame size in PMV player
PMV frame sizes are not always constant during a movie,
and a smaller frame should blit to the top-left corner
of the previous frame, leaving the right or bottom margin
untouched. Change decompressMovieImage to accept a width
and height argument, then use those when blitting. Fixes
corrupted screen in RSBESTNDE (Demo) credits.
Also removes `static` scope from byte arrays in RLE
decompression functions. They are now allocated as-needed
each frame and freed after use.
Changed paths:
engines/made/graphics.cpp
engines/made/graphics.h
engines/made/pmvplayer.cpp
diff --git a/engines/made/graphics.cpp b/engines/made/graphics.cpp
index 29d9592a8b6..798f7bbcfbb 100644
--- a/engines/made/graphics.cpp
+++ b/engines/made/graphics.cpp
@@ -21,7 +21,7 @@
#include "made/graphics.h"
-#include <common/array.h>
+#include "common/array.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "common/debug.h"
@@ -65,8 +65,6 @@ void ValueReader::resetNibbleSwitch() {
}
static uint rleDecompress(byte *source, const byte *end, Common::Array<byte> &dest) {
- dest.resize(0);
-
while (source < end) {
byte count = *source;
source++;
@@ -85,7 +83,7 @@ static uint rleDecompress(byte *source, const byte *end, Common::Array<byte> &de
}
if (source > end)
- warning("graphics::rleDecompress(): input stream over-read by %d bytes!", end - source);
+ warning("graphics::rleDecompress(): input stream over-read by %d bytes!", source - end);
return dest.size();
}
@@ -216,25 +214,26 @@ void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, u
}
-void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs,
- uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
- byte cmdFlags, byte pixelFlags, byte maskFlags) {
-
- // RLE decompression buffers
- static Common::Array<byte> cmdArray = Common::Array<byte>(4800);
- static Common::Array<byte> pixelArray = Common::Array<byte>();
- static Common::Array<byte> maskArray = Common::Array<byte>();
+void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 width, uint16 height,
+ uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs,
+ uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
+ byte cmdFlags, byte pixelFlags, byte maskFlags) {
- if ((maskFlags & 0b11111110) || (pixelFlags & 0b11111110) || (cmdFlags & 0b11111110))
+ if ((maskFlags & ~1) || (pixelFlags & ~1) || (cmdFlags & ~1))
error("decompressMovieImage() Unsupported flags: cmdFlags = %02X; maskFlags = %02X, pixelFlags = %02X", cmdFlags, maskFlags, pixelFlags);
- uint16 width = surface.w;
- uint16 height = surface.h;
+ // RLE decompression buffers
+ // Reserved sizes for pixelArray and maskArray are guesses based on input params
+ Common::Array<byte> cmdArray;
+ Common::Array<byte> pixelArray;
+ Common::Array<byte> maskArray;
+
uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4;
// RLE decompress the buffers as needed
byte *cmdBuffer;
if (cmdFlags & 1) {
+ cmdArray.reserve(((height + 3) / 4) * lineSize);
rleDecompress(source + cmdOffs, source + cmdOffs + cmdSize, cmdArray);
cmdBuffer = cmdArray.data();
} else
@@ -242,6 +241,7 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
byte *pixelBuffer;
if (pixelFlags & 1) {
+ pixelArray.reserve(pixelSize);
rleDecompress(source + pixelOffs, source + pixelOffs + pixelSize, pixelArray);
pixelBuffer = pixelArray.data();
} else
@@ -249,6 +249,7 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
byte *maskBuffer;
if (maskFlags & 1) {
+ maskArray.reserve(maskSize);
rleDecompress(source + maskOffs, source + maskOffs + maskSize, maskArray);
maskBuffer = maskArray.data();
} else
@@ -333,8 +334,8 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOf
if (cmd != 3) {
uint16 blockPos = 0;
uint32 maxW = MIN(4, surface.w - bx);
- uint32 maxH = (MIN(4, surface.h - by) + by) * width;
- for (uint32 yc = by * width; yc < maxH; yc += width) {
+ uint32 maxH = (MIN(4, surface.h - by) + by) * surface.w;
+ for (uint32 yc = by * surface.w; yc < maxH; yc += surface.w) {
for (uint32 xc = 0; xc < maxW; xc++) {
destPtr[(bx + xc) + yc] = block[xc + blockPos];
}
diff --git a/engines/made/graphics.h b/engines/made/graphics.h
index e3f06b8d7c1..59ecf9c2a32 100644
--- a/engines/made/graphics.h
+++ b/engines/made/graphics.h
@@ -44,9 +44,10 @@ protected:
};
void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame = false);
-void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs,
- uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
- byte cmdFlags, byte pixelFlags, byte maskFlags);
+void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 width, uint16 height,
+ uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs,
+ uint16 cmdSize, uint16 pixelSize, uint16 maskSize, uint16 lineSize,
+ byte cmdFlags, byte pixelFlags, byte maskFlags);
} // End of namespace Made
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index c96b2cd931f..f7ea665a776 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -270,9 +270,14 @@ bool PmvPlayer::decode_frame() {
if (!_surface) {
_surface = new Graphics::Surface();
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ } else if (_surface->w < width || _surface->h < height) {
+ warning("Movie surface too small for current frame! (Was: %d x %d, now: %d x %d)", _surface->w, _surface->h, width, height);
+ delete _surface;
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
}
- decompressMovieImage(imageData, *_surface, cmdOffs, pixelOffs, maskOffs,
+ decompressMovieImage(imageData, *_surface, width, height, cmdOffs, pixelOffs, maskOffs,
pixelOffs - cmdOffs, maskOffs - pixelOffs, imageChunkSize - maskOffs, lineSize,
cmdFlags, pixelFlags, maskFlags);
Commit: 9174c3fe8cf29991dd38794aa9420fa6f5e1445b
https://github.com/scummvm/scummvm/commit/9174c3fe8cf29991dd38794aa9420fa6f5e1445b
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: Cleanup based on PR feedback
Make some items auto-scope (stack) so they are cleaned up properly,
consolidate PMVPlayer::load() error handling, fix movie frame number
counting, call stopSound() before sfSoundFile
Changed paths:
engines/made/graphics.cpp
engines/made/pmvplayer.cpp
engines/made/scriptfuncs.cpp
diff --git a/engines/made/graphics.cpp b/engines/made/graphics.cpp
index 798f7bbcfbb..9ca6d2e664d 100644
--- a/engines/made/graphics.cpp
+++ b/engines/made/graphics.cpp
@@ -268,7 +268,7 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 width
byte *pCmdBuf = cmdBuffer;
byte *pPixelBuf = pixelBuffer;
byte *pMaskBuf = maskBuffer;
- while (height > 0) {
+ for (int row = 0; row < height; row += 4) {
memcpy(bitBuf, pCmdBuf, lineSize);
pCmdBuf += lineSize;
@@ -333,8 +333,8 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 width
if (cmd != 3) {
uint16 blockPos = 0;
- uint32 maxW = MIN(4, surface.w - bx);
- uint32 maxH = (MIN(4, surface.h - by) + by) * surface.w;
+ uint32 maxW = MIN(4, width - bx);
+ uint32 maxH = (MIN(4, height - by) + by) * surface.w;
for (uint32 yc = by * surface.w; yc < maxH; yc += surface.w) {
for (uint32 xc = 0; xc < maxW; xc++) {
destPtr[(bx + xc) + yc] = block[xc + blockPos];
@@ -352,9 +352,6 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 width
}
}
-
- height -= 4;
-
}
}
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index f7ea665a776..4da93ce18a4 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -127,8 +127,7 @@ bool PmvPlayer::load(const char* filename) {
_fd = new Common::File();
if (!_fd->open(Common::Path(filename, '\\'))) {
warning("Failed to open movie file '%s'", filename);
- delete _fd;
- return false;
+ goto error;
}
// expected IFF blocks at start of a PMV
@@ -137,15 +136,13 @@ bool PmvPlayer::load(const char* filename) {
readChunk(chunkType, chunkSize); // "MOVE"
if (chunkType != MKTAG('M','O','V','E')) {
warning("Unexpected PMV video header, expected 'MOVE'");
- delete _fd;
- return false;
+ goto error;
}
readChunk(chunkType, chunkSize); // "MHED"
if (chunkType != MKTAG('M','H','E','D')) {
warning("Unexpected PMV video header, expected 'MHED'");
- delete _fd;
- return false;
+ goto error;
}
frameDelay = _fd->readUint16LE();
@@ -192,6 +189,12 @@ bool PmvPlayer::load(const char* filename) {
frameNumber = 0;
return true;
+
+error:
+ delete _fd;
+ _fd = nullptr;
+
+ return false;
}
bool PmvPlayer::decode_frame() {
@@ -285,8 +288,6 @@ bool PmvPlayer::decode_frame() {
(320 - _surface->w) / 2, (200 - _surface->h) / 2, _surface->w, _surface->h);
}
- frameNumber++;
-
return true;
}
@@ -328,8 +329,8 @@ bool PmvPlayer::play(const char *filename) {
}
// delay until time has passed, then flip screen
- if (frameNumber > 1) {
- int32 delayTime = (frameNumber - 1) * frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (frameNumber > 0) {
+ int32 delayTime = frameNumber * frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
if (delayTime < 0)
warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / frameDelay) + 1);
else
@@ -402,6 +403,8 @@ bool PmvPlayer::play(const char *filename) {
}
#endif
+ frameNumber++;
+
// Check and handle events - user can press ESC to exit early
Common::Event event;
while (_vm->_system->getEventManager()->pollEvent(event)) {
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 294572cc379..a7f2b45ea23 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -78,6 +78,7 @@ void ScriptFunctions::stopSound() {
_vm->_res->freeResource(_soundResource);
_soundStarted = false;
}
+ _vm->_autoStopSound = false;
}
typedef Common::Functor2Mem<int16, int16*, int16, ScriptFunctions> ExternalScriptFunc;
@@ -235,10 +236,8 @@ int16 ScriptFunctions::sfClearScreen(int16 argc, int16 *argv) {
if (_vm->_screen->isScreenLocked())
return 0;
- if (_vm->_autoStopSound) {
+ if (_vm->_autoStopSound)
stopSound();
- _vm->_autoStopSound = false;
- }
_vm->_screen->clearScreen();
return 0;
}
@@ -280,7 +279,6 @@ int16 ScriptFunctions::sfSetVisualEffect(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfPlaySound(int16 argc, int16 *argv) {
int16 soundNum = argv[0];
- _vm->_autoStopSound = false;
stopSound();
if (argc > 1) {
soundNum = argv[1];
@@ -743,7 +741,7 @@ int16 ScriptFunctions::sfSoundPlaying(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfStopSound(int16 argc, int16 *argv) {
stopSound();
- _vm->_autoStopSound = false;
+
return 0;
}
@@ -1167,7 +1165,7 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
if (_vm->_pmvPlayer->load(filename)) {
// set up a script interpreter for calling the sub
- ScriptInterpreter *si = new ScriptInterpreter(_vm);
+ ScriptInterpreter si(_vm);
int16 playing = true;
int32 pmvStartTime = _vm->getTotalPlayTime();
@@ -1178,8 +1176,8 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
break;
// delay until time has passed, then flip screen
- if (_vm->_pmvPlayer->frameNumber > 1) {
- int32 delayTime = (_vm->_pmvPlayer->frameNumber - 1) * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (_vm->_pmvPlayer->frameNumber > 0) {
+ int32 delayTime = _vm->_pmvPlayer->frameNumber * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
if (delayTime < 0)
warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / _vm->_pmvPlayer->frameDelay) + 1);
else
@@ -1188,8 +1186,10 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
// call subroutine each frame
// pass the movie ID as the one arg
- playing = si->runScript(argv[0], 1, &argv[1]);
+ playing = si.runScript(argv[0], 1, &argv[1]);
debug(3, "Call script return code: %04X (%d)", playing, playing);
+
+ _vm->_pmvPlayer->frameNumber++;
}
_vm->_pmvPlayer->close();
@@ -1206,22 +1206,22 @@ int16 ScriptFunctions::sfCursorXY(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfSoundFile(int16 argc, int16 *argv) {
// Loads an external sound file (i.e. not from PRJ) and plays it
+ stopSound();
const char *soundName = _vm->_dat->getObjectString(argv[0]);
debug(1, "Playing external sound '%s'", soundName);
- Common::File *_fd = new Common::File();
- if (!_fd->open(Common::Path(soundName, '\\'))) {
- delete _fd;
+ Common::File _fd;
+ if (!_fd.open(Common::Path(soundName, '\\'))) {
warning("Failed to open sound file '%s", soundName);
return 0;
}
- byte *srcBuf = new byte[_fd->size()];
- _fd->read(srcBuf, _fd->size());
+ byte *srcBuf = new byte[_fd.size()];
+ _fd.read(srcBuf, _fd.size());
// wrap this in a SoundResource
SoundResource *soundRes = new SoundResource();
- soundRes->load(srcBuf, _fd->size());
+ soundRes->load(srcBuf, _fd.size());
// safe to free source now
delete[] srcBuf;
Commit: 4de50b7f2683ca1dd5fc9eb49e5129584d5faf18
https://github.com/scummvm/scummvm/commit/4de50b7f2683ca1dd5fc9eb49e5129584d5faf18
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: PR cleanup, fix MHED framerate / sound and sfMovieCall
Some simplification based on PR feedback: GameDatabaseV3 now will
accept both 3.00 and 3.10 files, differentiated by version ID
loaded from file.
Fixes MHED parsing in PMV to read framerate from correct field,
eliminating sound sample rate patch as it is no longer needed with
the correct frameDelay.
sfMovieCall passes movie frame number to callback function instead
of movie ID, this seems to match what the original engines do.
Changed paths:
engines/made/database.cpp
engines/made/database.h
engines/made/detection_tables.h
engines/made/graphics.cpp
engines/made/pmvplayer.cpp
engines/made/pmvplayer.h
engines/made/scriptfuncs.cpp
diff --git a/engines/made/database.cpp b/engines/made/database.cpp
index 941d008971e..9dbd002b98e 100644
--- a/engines/made/database.cpp
+++ b/engines/made/database.cpp
@@ -657,6 +657,11 @@ void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
uint16 version = sourceS.readUint16LE();
uint16 subVersion = sourceS.readUint16LE();
+
+ // V3.00 databases are 209, V3.10 is 210
+ if (version != 209 && version != 210)
+ warning("Unknown database version, known versions are 209 and 210");
+
char dbName[19] = "";
sourceS.read(dbName, 18);
debug(2, "databaseVersion = %d, databaseSubVersion = %d, databaseName = %s", version, subVersion, dbName);
@@ -683,7 +688,11 @@ void GameDatabaseV3::load(Common::SeekableReadStream &sourceS) {
objectOffsets.push_back(sourceS.readUint32LE());
for (uint32 i = 0; i < objectCount; i++) {
- Object *obj = new ObjectV3();
+ Object *obj;
+ if (version == 210)
+ obj = new ObjectV3_1();
+ else
+ obj = new ObjectV3();
// The LSB indicates if it's a constant or variable object.
// Constant objects are loaded from disk, while variable objects exist
// in the _gameState buffer.
@@ -886,54 +895,6 @@ const char *GameDatabaseV3::getString(uint16 offset) {
return nullptr;
}
-void GameDatabaseV3_1::load(Common::SeekableReadStream &sourceS) {
- char header[6];
- sourceS.read(header, 6);
- if (strncmp(header, "ADVSYS", 6))
- warning("Unexpected database header, expected ADVSYS");
-
- uint16 version = sourceS.readUint16LE();
- uint16 subVersion = sourceS.readUint16LE();
- char dbName[19] = "";
- sourceS.read(dbName, 18);
- debug(2, "databaseVersion = %d, databaseSubVersion = %d, databaseName = %s", version, subVersion, dbName);
-
- sourceS.readUint16LE(); // unknown, always 1?
-
- uint32 objectIndexOffs = sourceS.readUint32LE();
- uint16 objectCount = sourceS.readUint16LE();
- _gameStateOffs = sourceS.readUint32LE();
- _gameStateSize = sourceS.readUint32LE();
- uint32 objectsOffs = sourceS.readUint32LE();
- uint32 objectsSize = sourceS.readUint32LE();
- _mainCodeObjectIndex = sourceS.readUint16LE();
-
- debug(2, "objectIndexOffs = %08X; objectCount = %d; gameStateOffs = %08X; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d; _mainCodeObjectIndex = %04X\n", objectIndexOffs, objectCount, _gameStateOffs, _gameStateSize, objectsOffs, objectsSize, _mainCodeObjectIndex);
-
- _gameState = new byte[_gameStateSize];
- sourceS.seek(_gameStateOffs);
- sourceS.read(_gameState, _gameStateSize);
-
- Common::Array<uint32> objectOffsets;
- sourceS.seek(objectIndexOffs);
- for (uint32 i = 0; i < objectCount; i++)
- objectOffsets.push_back(sourceS.readUint32LE());
-
- for (uint32 i = 0; i < objectCount; i++) {
- Object *obj = new ObjectV3_1();
- // The LSB indicates if it's a constant or variable object.
- // Constant objects are loaded from disk, while variable objects exist
- // in the _gameState buffer.
- if (objectOffsets[i] & 1) {
- sourceS.seek(objectsOffs + (objectOffsets[i] ^ 1));
- obj->load(sourceS);
- } else {
- obj->load(_gameState + objectOffsets[i]);
- }
- _objects.push_back(obj);
- }
-}
-
int16 *GameDatabaseV3_1::findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) {
debug(4, "findObjectProperty(%04X, %04X): Beginning search...", objectIndex, propertyId);
diff --git a/engines/made/database.h b/engines/made/database.h
index f4a7bcec284..bf343cc7ba3 100644
--- a/engines/made/database.h
+++ b/engines/made/database.h
@@ -200,7 +200,7 @@ public:
protected:
char *_gameText;
uint32 _gameStateOffs;
- virtual void load(Common::SeekableReadStream &sourceS) override;
+ void load(Common::SeekableReadStream &sourceS) override;
void reloadFromStream(Common::SeekableReadStream &sourceS) override;
};
@@ -208,8 +208,6 @@ class GameDatabaseV3_1 : public GameDatabaseV3 {
public:
GameDatabaseV3_1(MadeEngine *vm) : GameDatabaseV3(vm) {}
int16 *findObjectProperty(int16 objectIndex, int16 propertyId, int16 &propertyFlag) override;
-protected:
- void load(Common::SeekableReadStream &sourceS) override;
};
} // End of namespace Made
diff --git a/engines/made/detection_tables.h b/engines/made/detection_tables.h
index 349ada62a8f..9fc28dfcf1c 100644
--- a/engines/made/detection_tables.h
+++ b/engines/made/detection_tables.h
@@ -548,7 +548,7 @@ static const MadeGameDescription gameDescriptions[] = {
{
"lgop2",
"",
- AD_ENTRY1("lgop2.dat", "8137996db200ff67e8f172ff106f2e48"),
+ AD_ENTRY1s("lgop2.dat", "8137996db200ff67e8f172ff106f2e48", 280280),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
diff --git a/engines/made/graphics.cpp b/engines/made/graphics.cpp
index 9ca6d2e664d..65b29ad2727 100644
--- a/engines/made/graphics.cpp
+++ b/engines/made/graphics.cpp
@@ -231,29 +231,18 @@ void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 width
uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4;
// RLE decompress the buffers as needed
- byte *cmdBuffer;
- if (cmdFlags & 1) {
- cmdArray.reserve(((height + 3) / 4) * lineSize);
- rleDecompress(source + cmdOffs, source + cmdOffs + cmdSize, cmdArray);
- cmdBuffer = cmdArray.data();
- } else
- cmdBuffer = source + cmdOffs;
-
- byte *pixelBuffer;
- if (pixelFlags & 1) {
- pixelArray.reserve(pixelSize);
- rleDecompress(source + pixelOffs, source + pixelOffs + pixelSize, pixelArray);
- pixelBuffer = pixelArray.data();
- } else
- pixelBuffer = source + pixelOffs;
-
- byte *maskBuffer;
- if (maskFlags & 1) {
- maskArray.reserve(maskSize);
- rleDecompress(source + maskOffs, source + maskOffs + maskSize, maskArray);
- maskBuffer = maskArray.data();
- } else
- maskBuffer = source + maskOffs;
+#define GET_BUFFER(name, reserveSize) \
+ byte *name##Buffer; \
+ if (name##Flags & 1) { \
+ name##Array.reserve(reserveSize); \
+ rleDecompress(source + name##Offs, source + name##Offs + name##Size, name##Array); \
+ name##Buffer = name##Array.data(); \
+ } else \
+ name##Buffer = source + name##Offs;
+
+ GET_BUFFER(cmd, ((height + 3) / 4) * lineSize)
+ GET_BUFFER(pixel, pixelSize)
+ GET_BUFFER(mask, maskSize)
//
byte *destPtr = (byte *)surface.getPixels();
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 4da93ce18a4..eee47d62299 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -127,7 +127,9 @@ bool PmvPlayer::load(const char* filename) {
_fd = new Common::File();
if (!_fd->open(Common::Path(filename, '\\'))) {
warning("Failed to open movie file '%s'", filename);
- goto error;
+ delete _fd;
+ _fd = nullptr;
+ return false;
}
// expected IFF blocks at start of a PMV
@@ -136,35 +138,38 @@ bool PmvPlayer::load(const char* filename) {
readChunk(chunkType, chunkSize); // "MOVE"
if (chunkType != MKTAG('M','O','V','E')) {
warning("Unexpected PMV video header, expected 'MOVE'");
- goto error;
+ delete _fd;
+ _fd = nullptr;
+ return false;
}
readChunk(chunkType, chunkSize); // "MHED"
if (chunkType != MKTAG('M','H','E','D')) {
warning("Unexpected PMV video header, expected 'MHED'");
- goto error;
+ delete _fd;
+ _fd = nullptr;
+ return false;
}
- frameDelay = _fd->readUint16LE();
- _fd->skip(4); // always 0?
- frameCount = _fd->readUint16LE();
+ uint16 unknownMHED = _fd->readUint16LE(); // always 98 + streamCount (below)
_fd->skip(4); // always 0?
+ frameCount = _fd->readUint32LE();
+ _fd->skip(2); // "low speed frameskip" indicator - if set,
+ // seems to allow player to skip blit here on slow systems
+ // see ScriptFuncs::sfIsSlowSystem, potentially related
soundFreq = _fd->readUint16LE();
- // Note: There seem to be weird sound frequencies in PMV videos.
- // Not sure why, but leaving those original frequencies intact
- // results to sound being choppy. Therefore, we set them to more
- // "common" values here (11025 instead of 11127 and 22050 instead
- // of 22254)
- if (soundFreq == 11127)
- soundFreq = 11025;
-
- if (soundFreq == 22254)
- soundFreq = 22050;
-
- for (int i = 0; i < 22; i++) {
- int unk = _fd->readUint16LE();
- debug(2, "%i ", unk);
+ // Sound freq is 11127 or 22254hz, these are common Mac (Plus) rates...
+
+ frameDelay = 1000 / _fd->readUint16LE(); // FPS, which we turn into ms-per-frame
+ uint16 streamCount = _fd->readUint16LE(); // number of streams (video + audio, video + audio + palette, etc)
+
+ debug(2, "PMV load(%s): %d frames, %d ms-per-frame, %d hz, %d streams, unk=%d",
+ filename, frameCount, frameDelay, soundFreq, streamCount, unknownMHED);
+
+ for (int i = 0; i < 20; i++) {
+ int streamType = _fd->readUint16LE();
+ debug(2, "%i ", streamType);
}
// Read and set initial palette
@@ -189,12 +194,6 @@ bool PmvPlayer::load(const char* filename) {
frameNumber = 0;
return true;
-
-error:
- delete _fd;
- _fd = nullptr;
-
- return false;
}
bool PmvPlayer::decode_frame() {
@@ -316,6 +315,25 @@ void PmvPlayer::close() {
_fd = nullptr;
}
+static bool handleEvents(Made::MadeEngine *_vm) {
+ bool aborted = false;
+ // Check and handle events - user can press ESC to exit early
+ Common::Event event;
+ while (_vm->_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ aborted = true;
+ _vm->stopTextToSpeech();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return aborted;
+}
+
bool PmvPlayer::play(const char *filename) {
bool aborted = false;
@@ -405,20 +423,7 @@ bool PmvPlayer::play(const char *filename) {
frameNumber++;
- // Check and handle events - user can press ESC to exit early
- Common::Event event;
- while (_vm->_system->getEventManager()->pollEvent(event)) {
- switch (event.type) {
- case Common::EVENT_KEYDOWN:
- if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
- aborted = true;
- _vm->stopTextToSpeech();
- }
- break;
- default:
- break;
- }
- }
+ aborted = handleEvents(_vm);
}
close();
diff --git a/engines/made/pmvplayer.h b/engines/made/pmvplayer.h
index dd022522257..3a3798b4910 100644
--- a/engines/made/pmvplayer.h
+++ b/engines/made/pmvplayer.h
@@ -57,7 +57,8 @@ public:
// currently open file
Common::File *_fd;
// info about currently playing movie
- uint16 frameDelay, frameCount, frameNumber, soundFreq;
+ uint32 frameCount, frameNumber;
+ uint16 frameDelay, soundFreq;
protected:
// ptrs to caller objects
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index a7f2b45ea23..48f49843e24 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -1171,25 +1171,30 @@ int16 ScriptFunctions::sfMovieCall(int16 argc, int16* argv) {
int32 pmvStartTime = _vm->getTotalPlayTime();
while (!_vm->shouldQuit() && !_vm->_pmvPlayer->_fd->eos() && _vm->_pmvPlayer->frameNumber < _vm->_pmvPlayer->frameCount && playing) {
- // Decode and stage the next audio / video frame
- if (!_vm->_pmvPlayer->decode_frame())
- break;
-
- // delay until time has passed, then flip screen
- if (_vm->_pmvPlayer->frameNumber > 0) {
- int32 delayTime = _vm->_pmvPlayer->frameNumber * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
- if (delayTime < 0)
- warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / _vm->_pmvPlayer->frameDelay) + 1);
- else
- g_system->delayMillis(delayTime);
- }
// call subroutine each frame
- // pass the movie ID as the one arg
- playing = si.runScript(argv[0], 1, &argv[1]);
+ // pass the frame number as sole arg
+ int16 frameNumber = _vm->_pmvPlayer->frameNumber;
+ playing = si.runScript(argv[0], 1, &frameNumber);
debug(3, "Call script return code: %04X (%d)", playing, playing);
- _vm->_pmvPlayer->frameNumber++;
+ if (playing) {
+ // Decode and stage the next audio / video frame
+ if (!_vm->_pmvPlayer->decode_frame())
+ break;
+
+ // delay until time has passed, then flip screen
+ if (_vm->_pmvPlayer->frameNumber > 0) {
+ int32 delayTime = _vm->_pmvPlayer->frameNumber * _vm->_pmvPlayer->frameDelay - (_vm->getTotalPlayTime() - pmvStartTime);
+ if (delayTime < 0)
+ warning("Video A/V sync broken - running behind %d ms (%d frames)!", -delayTime, (-delayTime / _vm->_pmvPlayer->frameDelay) + 1);
+ else
+ g_system->delayMillis(delayTime);
+ }
+
+ // _vm->_system->updateScreen(); // this is already triggered by the script callback
+ _vm->_pmvPlayer->frameNumber++;
+ }
}
_vm->_pmvPlayer->close();
Commit: f20484a3341ecd8fed5e5f2243a189ed68ef006c
https://github.com/scummvm/scummvm/commit/f20484a3341ecd8fed5e5f2243a189ed68ef006c
Author: Greg Kennedy (kennedy.greg at gmail.com)
Date: 2026-09-14T21:29:37+03:00
Commit Message:
MADE: improved 3.1 script dump
Changed paths:
engines/made/script.cpp
diff --git a/engines/made/script.cpp b/engines/made/script.cpp
index 3c6bf28f4c0..0827ffdd4c4 100644
--- a/engines/made/script.cpp
+++ b/engines/made/script.cpp
@@ -755,9 +755,14 @@ void ScriptInterpreter::dumpObject(int16 objectIndex) {
} else {
debug(1, "Raw Data for object %04X (count1 = %d, count2 = %d)", objectIndex, obj->getCount1(), obj->getCount2());
Common::String bArray = "";
- // NOTE: for 3.1 objects, use the commented line here instead
- //for (byte *i = obj->getData(); i < obj->getData() + obj->getSize() * 4; i++)
- for (byte *i = obj->getData(); i < obj->getData() + (obj->getCount1() + obj->getCount2()) * 2; i++)
+
+ byte *limit;
+ if (_vm->getVersion() == 3 && _vm->getSubVersion() == 1)
+ limit = obj->getData() + obj->getSize() * 4;
+ else
+ limit = obj->getData() + (obj->getCount1() + obj->getCount2()) * 2;
+
+ for (byte *i = obj->getData(); i < limit; i++)
bArray += Common::String::format("%02X", *i);
debug(1, "%s", bArray.c_str());
}
More information about the Scummvm-git-logs
mailing list