[Scummvm-git-logs] scummvm branch-2-9 -> b016b0afe33b4ab801e6e1d8ddb45a4fd8e23bb7
sluicebox
noreply at scummvm.org
Tue Apr 29 17:27:51 UTC 2025
This automated email contains information about 70 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
76f55c62d4 SCI: Fix Talker timer rollover in LSL5, SQ1, BRAIN1
789d8442db SCI: Improve grammar for GK2 fan subtitles prompt
aeb7bb8c99 AGI: PREAGI: Cleanup
f8fae0d545 AGI: PREAGI: Add initial handling for WINNIE CoCo
074bb11268 AGI: PREAGI: Cleanup WINNIE game loop
8f18144a48 AGI: PREAGI: Cleanup sound code
46970323a4 AGI: PREAGI: Improve WINNIE event handling
bbb7e00a64 AGI: PREAGI: Improve TROLL intro event handling
193ae873c3 AGI: PREAGI: Improve MICKEY event handling
8173f1ee09 AGI: PREAGI: Fix MICKEY crystal animation
ebeacfa9d9 AGI: PREAGI: Fix MICKEY object placement
6b356e2316 AGI: PREAGI: Fix MICKEY saves always loading to Earth
3068b384fe AGI: PREAGI: Fix MICKEY crystal persistence
dbd61917f7 AGI: PREAGI: Fix MICKEY crystal appearing before room change
ed9e939faa AGI: PREAGI: Fix MICKEY ship button behavior
03d13330e8 AGI: PREAGI: Fix MICKEY game over handling
1d77508d05 AGI: PREAGI: Cleanup MICKEY code
e1d399b4dd AGI: Remove PictureMgr::setPattern(), used by MICKEY
0c9fea96a6 AGI: Remove PictureMgr::setPictureData(), used by TROLL
6196f18317 AGI: Remove AgiPictureVersion::AGIPIC_256
1ad5fdbecb AGI: Remove AgiPictureFlags::kPicFf3Cont
4fe7ab4616 AGI: Move PictureMgr::unloadPicture() to AgiEngine
aa9a1bb5b8 AGI: Remove PictureMgr::clear(), used by TROLL
ede71263fb AGI: Cleanup PictureMgr
062845110b AGI: Update PictureMgr
4424f8b586 AGI: PREAGI: Clear screen after MICKEY logo
6a3dbc4256 AGI: PREAGI: Fix MICKEY intro flash on CGA and Hercules
c7807db2e0 AGI: PREAGI: Improve event handling during sounds
77129d6852 SCI: Fix GCC Compiler Shadowing Warnings
a88e1b25c2 SCI: Add KQ5, SQ4 360k disk detection entries
10282bbaca SCI: Update KQ5 detection entry description
7ba60b09ec AGI: Move misplaced picture opcode handler
aebee756c8 AGI: Create separate PREAGI picture version and draw function
408c1111cd AGI: Implement PREAGI picture patterns
b258e78502 AGI: Add Pictures debug channel
661bd57436 AGI: Implement PREAGI picture coordinate clipping
43a43cf3c0 AGI: Fix PREAGI missing flood fills
0be62c0f69 AGI: Fix PREAGI memory corruption when drawing pictures
2232dbdb32 SCI: Add KQ5, LSL5, SQ4 German EGA detection entries
c05ef83e6f AGI: Implement AGIv1 set.bit and clear.bit opcodes
59bfdd8745 AGI: PREAGI: Add missing left space to TROLL menus
3e0dc72b3a SCI: Fix SQ5 introduction speeds
8e42e82369 AGI: PREAGI: Fix TROLL array size
b7df034703 AGI: PREAGI: Fix TROLL menu string truncation
741f6202d2 AGI: Remove unused picture version
0543ed4be7 AGI: PREAGI: Move PictureMgr to game engine classes
6efbdcffa6 AGI: PREAGI: Subclass PictureMgr for MICKEY and WINNIE
250327316c AGI: PREAGI: Subclass PictureMgr for TROLL
1a3c632b47 AGI: PREAGI: Implement TROLL flood fill
f01a114c49 AGI: Add support for early KQ1 pictures
ef74701e8f AGI: Document PictureMgr
f53d9ad336 AGI: Update picture comments
bf9f564660 SCI: Fix KQ4 Amiga sound signals
98fb478ccd AGI: PREAGI: Fix gcc warning
c63c694a85 SCI: Fix KQ1 script lockup when drowning in cave
0b6743f6b2 AGI: PREAGI: Initialize members
21af31a3ee AGI: Add `diskdump` debug command
a3fc4b1e17 SCI: Fix LSL1 lockup when casino doors close
2929aabbf7 AGI: Move AgiLoader definitions to loader.h
7f82a75d8b AGI: Create AgiLoader method for locating disk images
2f6183dcfa AGI: Add KQ1-Early PC loader and detection
ea31107327 AGI: Add KQ1-Early Apple II loader and detection
5e7826c473 AGI: Update misc AGIv1 opcode details
6aedb0a75e AGI: Initialize member
df6cb98d2d SCI32: Fix LSL6HIRES Interface Help cursor
e85295359e SCI: Fix LSL6 CD Interface Help cursor
33950cda7e AGI: Add detection for early version of XMASCARD
e16358bae3 AGI: Add support for v2.230 view mirror data
9bd9686c33 SCI: Fix QFG1 trip wire lockup
b016b0afe3 SCI: Disable "ride unicorn at night" for KQ4 demo
Commit: 76f55c62d4688a4659ac0ac63f6ee66f383155f4
https://github.com/scummvm/scummvm/commit/76f55c62d4688a4659ac0ac63f6ee66f383155f4
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:46-07:00
Commit Message:
SCI: Fix Talker timer rollover in LSL5, SQ1, BRAIN1
Fixes bug #15303
Thanks to @eriktorbjorn for reporting this
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index ee497b5bd99..a5fa16bf985 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -812,6 +812,44 @@ static const uint16 sciPatchTimerRollover[] = {
PATCH_END
};
+// Several SCI Version 1 games use a Talker class that doesn't handle kGetTime
+// rollover correctly. Talker:init calculates the message's end-time in ticks
+// (1/60ths of a second) and Talker:doit compares this to the current time
+// with a naive signed comparison. When kGetTime approaches $8000, the end-time
+// appears negative and Talker:doit prematurely closes the message.
+//
+// We fix this by replacing the comparison with the correct logic from later
+// versions. We restructure this to fit within the limited space:
+//
+// Existing: GetTime > ticks
+// Correct: GetTime - ticks > 0
+// Optimized: 0 > ticks - GetTime
+//
+// Applies to: Castle of Dr. Brain, LSL1 PC English, SQ1
+// Responsible method: Talker:doit
+// Fixes bug: #15303
+static const uint16 sciSignatureTalkerRollover[] = {
+ 0x76, // push0
+ SIG_MAGICDWORD,
+ 0x43, 0x42, 0x00, // callk GetTime 00
+ 0x36, // push
+ 0x63, SIG_ADDTOOFFSET(+1), // pToa ticks
+ 0x1e, // gt? [ GetTime > ticks ]
+ 0x30, SIG_ADDTOOFFSET(+1), 0x00, // bnt
+ SIG_END
+};
+
+static const uint16 sciPatchTalkerRollover[] = {
+ 0x76, // push0
+ 0x67, PATCH_GETORIGINALBYTE(+6), // pTos ticks
+ 0x76, // push0
+ 0x43, 0x42, 0x00, // callk GetTime 00
+ 0x04, // sub [ ticks - GetTime ]
+ 0x1e, // gt? [ 0 > ticks - GetTime ]
+ 0x31, PATCH_GETORIGINALBYTE(+9), // bnt
+ PATCH_END
+};
+
// ===========================================================================
// Conquests of Camelot
// At the bazaar in Jerusalem, it's possible to see a girl taking a shower.
@@ -1280,6 +1318,8 @@ static const SciScriptPatcherEntry camelotSignatures[] = {
// script, description, signature patch
static const SciScriptPatcherEntry castleBrainSignatures[] = {
{ true, 802, "disable speed test", 1, sci01SpeedTestGlobalSignature, sci01SpeedTestGlobalPatch },
+ { true, 280, "talker rollover", 1, sciSignatureTalkerRollover, sciPatchTalkerRollover },
+ { true, 928, "talker rollover", 1, sciSignatureTalkerRollover, sciPatchTalkerRollover },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -9475,6 +9515,7 @@ static const SciScriptPatcherEntry larry5Signatures[] = {
{ true, 280, "English-only: fix green card limo bug", 1, larry5SignatureGreenCardLimoBug, larry5PatchGreenCardLimoBug },
{ true, 380, "German-only: Enlarge Patti Textbox", 1, larry5SignatureGermanEndingPattiTalker, larry5PatchGermanEndingPattiTalker },
{ true, 500, "speed up palette animation", 1, larry5SignatureRoom500PaletteAnimation, larry5PatchRoom500PaletteAnimation },
+ { true, 928, "talker rollover", 1, sciSignatureTalkerRollover, sciPatchTalkerRollover },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -24153,6 +24194,7 @@ static const SciScriptPatcherEntry sq1vgaSignatures[] = {
{ true, 703, "deltaur messages", 1, sq1vgaSignatureDeltaurMessages3, sq1vgaPatchDeltaurMessages },
{ true, 704, "spider droid timing issue", 1, sq1vgaSignatureSpiderDroidTiming, sq1vgaPatchSpiderDroidTiming },
{ true, 803, "disable speed test", 1, sci01SpeedTestLocalSignature, sci01SpeedTestLocalPatch },
+ { true, 928, "talker rollover", 1, sciSignatureTalkerRollover, sciPatchTalkerRollover },
{ true, 989, "rename russian Sound class", 1, sq1vgaSignatureRussianSoundName, sq1vgaPatchRussianSoundName },
{ true, 992, "rename russian Motion class", 1, sq1vgaSignatureRussianMotionName, sq1vgaPatchRussianMotionName },
{ true, 994, "rename russian Rm class", 1, sq1vgaSignatureRussianRmName, sq1vgaPatchRussianRmName },
Commit: 789d8442dbe43d0f6a1b3e79a8133166a132cc58
https://github.com/scummvm/scummvm/commit/789d8442dbe43d0f6a1b3e79a8133166a132cc58
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2025-04-29T10:26:46-07:00
Commit Message:
SCI: Improve grammar for GK2 fan subtitles prompt
Changed paths:
engines/sci/sci.cpp
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 3ce06ddc64d..307463afdcc 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -547,7 +547,7 @@ void SciEngine::suggestDownloadGK2SubTitlesPatch() {
downloadMessage = "";
}
- int result = showScummVMDialog(_("GK2 has a fan made subtitles, available thanks to the good persons at SierraHelp.\n\n"
+ int result = showScummVMDialog(_("GK2 has fan made subtitles, available thanks to the good people at SierraHelp.\n\n"
"Installation:\n"
"- download http://www.sierrahelp.com/Files/Patches/GabrielKnight/GK2Subtitles.zip\n" +
downloadMessage +
Commit: aeb7bb8c9942c5f1b2adbcfe77ca399e3145aa6a
https://github.com/scummvm/scummvm/commit/aeb7bb8c9942c5f1b2adbcfe77ca399e3145aa6a
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:46-07:00
Commit Message:
AGI: PREAGI: Cleanup
Changed paths:
engines/agi/agi.cpp
engines/agi/preagi/preagi.cpp
engines/agi/preagi/preagi.h
engines/agi/preagi/winnie.cpp
engines/agi/preagi/winnie.h
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 16bf6246d32..552193a69d1 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -371,31 +371,15 @@ void AgiBase::initRenderMode() {
// If render mode is explicitly set, force rendermode
switch (configRenderMode) {
case Common::kRenderCGA:
- _renderMode = Common::kRenderCGA;
- break;
case Common::kRenderEGA:
- _renderMode = Common::kRenderEGA;
- break;
case Common::kRenderVGA:
- _renderMode = Common::kRenderVGA;
- break;
case Common::kRenderHercG:
- _renderMode = Common::kRenderHercG;
- break;
case Common::kRenderHercA:
- _renderMode = Common::kRenderHercA;
- break;
case Common::kRenderAmiga:
- _renderMode = Common::kRenderAmiga;
- break;
case Common::kRenderApple2GS:
- _renderMode = Common::kRenderApple2GS;
- break;
case Common::kRenderAtariST:
- _renderMode = Common::kRenderAtariST;
- break;
case Common::kRenderMacintosh:
- _renderMode = Common::kRenderMacintosh;
+ _renderMode = configRenderMode;
break;
default:
break;
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
index d7c978520bc..e824182c4cf 100644
--- a/engines/agi/preagi/preagi.cpp
+++ b/engines/agi/preagi/preagi.cpp
@@ -141,7 +141,8 @@ void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) {
break;
}
- for (int iChar = 0; iChar < (int)strlen(buffer); iChar++) {
+ const int stringLength = (int)strlen(buffer);
+ for (int iChar = 0; iChar < stringLength; iChar++) {
int code = buffer[iChar];
switch (code) {
@@ -167,11 +168,6 @@ void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) {
}
}
-void PreAgiEngine::drawStrMiddle(int row, int attr, const char *buffer) {
- int col = (25 / 2) - (strlen(buffer) / 2); // 25 = 320 / 8 (maximum column)
- drawStr(row, col, attr, buffer);
-}
-
void PreAgiEngine::clearTextArea() {
int start = IDI_MAX_ROW_PIC;
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
index 56f510e71f2..a4c666790c9 100644
--- a/engines/agi/preagi/preagi.h
+++ b/engines/agi/preagi/preagi.h
@@ -63,9 +63,7 @@ protected:
PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc);
~PreAgiEngine() override;
- int getGameId() {
- return _gameId;
- }
+ int getGameId() const { return _gameId; }
PictureMgr *_picture;
@@ -79,7 +77,7 @@ protected:
int loadGame(const Common::String &fileName, bool checkId = true) { return -1; }
// Game
- Common::String getTargetName() { return _targetName; }
+ Common::String getTargetName() const { return _targetName; }
// Screen
void clearScreen(int attr, bool overrideDefault = true);
@@ -94,10 +92,9 @@ protected:
// Text
void drawStr(int row, int col, int attr, const char *buffer);
- void drawStrMiddle(int row, int attr, const char *buffer);
void clearTextArea();
void clearRow(int row);
- void XOR80(char *buffer);
+ static void XOR80(char *buffer);
void printStr(const char *szMsg);
void printStrXOR(char *szMsg);
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 1f42ddb7d4c..381a0b1a137 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -35,8 +35,6 @@
namespace Agi {
void WinnieEngine::parseRoomHeader(WTP_ROOM_HDR *roomHdr, byte *buffer, int len) {
- int i;
-
Common::MemoryReadStreamEndian readS(buffer, len, _isBigEndian);
roomHdr->roomNumber = readS.readByte();
@@ -45,7 +43,7 @@ void WinnieEngine::parseRoomHeader(WTP_ROOM_HDR *roomHdr, byte *buffer, int len)
roomHdr->fileLen = readS.readUint16();
roomHdr->reserved0 = readS.readUint16();
- for (i = 0; i < IDI_WTP_MAX_DIR; i++)
+ for (int i = 0; i < IDI_WTP_MAX_DIR; i++)
roomHdr->roomNew[i] = readS.readByte();
roomHdr->objX = readS.readByte();
@@ -53,35 +51,33 @@ void WinnieEngine::parseRoomHeader(WTP_ROOM_HDR *roomHdr, byte *buffer, int len)
roomHdr->reserved1 = readS.readUint16();
- for (i = 0; i < IDI_WTP_MAX_BLOCK; i++)
+ for (int i = 0; i < IDI_WTP_MAX_BLOCK; i++)
roomHdr->ofsDesc[i] = readS.readUint16();
- for (i = 0; i < IDI_WTP_MAX_BLOCK; i++)
+ for (int i = 0; i < IDI_WTP_MAX_BLOCK; i++)
roomHdr->ofsBlock[i] = readS.readUint16();
- for (i = 0; i < IDI_WTP_MAX_STR; i++)
+ for (int i = 0; i < IDI_WTP_MAX_STR; i++)
roomHdr->ofsStr[i] = readS.readUint16();
roomHdr->reserved2 = readS.readUint32();
- for (i = 0; i < IDI_WTP_MAX_BLOCK; i++)
+ for (int i = 0; i < IDI_WTP_MAX_BLOCK; i++)
for (byte j = 0; j < IDI_WTP_MAX_BLOCK; j++)
roomHdr->opt[i].ofsOpt[j] = readS.readUint16();
}
void WinnieEngine::parseObjHeader(WTP_OBJ_HDR *objHdr, byte *buffer, int len) {
- int i;
-
Common::MemoryReadStreamEndian readS(buffer, len, _isBigEndian);
// these two values are always little endian, even on Amiga
objHdr->fileLen = readS.readUint16LE();
objHdr->objId = readS.readUint16LE();
- for (i = 0; i < IDI_WTP_MAX_OBJ_STR_END; i++)
+ for (int i = 0; i < IDI_WTP_MAX_OBJ_STR_END; i++)
objHdr->ofsEndStr[i] = readS.readUint16();
- for (i = 0; i < IDI_WTP_MAX_OBJ_STR; i++)
+ for (int i = 0; i < IDI_WTP_MAX_OBJ_STR; i++)
objHdr->ofsStr[i] = readS.readUint16();
objHdr->ofsPic = readS.readUint16();
@@ -592,8 +588,6 @@ void WinnieEngine::takeObj(int iRoom) {
// returns true if object was dropped in the right room
bool WinnieEngine::dropObj(int iRoom) {
- int iCode;
-
if (getObjInRoom(iRoom)) {
// there already is an object in the room, can't drop
printStr(IDS_WTP_CANT_DROP);
@@ -604,6 +598,7 @@ bool WinnieEngine::dropObj(int iRoom) {
_gameStateWinnie.fGame[0x0d] = 0;
}
+ int iCode;
if (isRightObj(iRoom, _gameStateWinnie.iObjHave, &iCode)) {
// object has been dropped in the right place
playSound(IDI_WTP_SND_DROP_OK);
diff --git a/engines/agi/preagi/winnie.h b/engines/agi/preagi/winnie.h
index 41170ddd3c4..8cc7bed0c05 100644
--- a/engines/agi/preagi/winnie.h
+++ b/engines/agi/preagi/winnie.h
@@ -328,7 +328,6 @@ private:
void drawRoomPic();
int parser(int, int, uint8 *);
int getObjInRoom(int);
- bool getSelOkBack();
void getMenuSel(char *, int *, int[]);
void keyHelp();
void clrMenuSel(int *, int[]);
Commit: f8fae0d545d69a50cf074d6229044d4da4e9e8a6
https://github.com/scummvm/scummvm/commit/f8fae0d545d69a50cf074d6229044d4da4e9e8a6
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:46-07:00
Commit Message:
AGI: PREAGI: Add initial handling for WINNIE CoCo
Changed paths:
engines/agi/preagi/winnie.cpp
engines/agi/preagi/winnie.h
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 381a0b1a137..d0a7d775858 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -70,9 +70,15 @@ void WinnieEngine::parseRoomHeader(WTP_ROOM_HDR *roomHdr, byte *buffer, int len)
void WinnieEngine::parseObjHeader(WTP_OBJ_HDR *objHdr, byte *buffer, int len) {
Common::MemoryReadStreamEndian readS(buffer, len, _isBigEndian);
- // these two values are always little endian, even on Amiga
- objHdr->fileLen = readS.readUint16LE();
- objHdr->objId = readS.readUint16LE();
+ if (getPlatform() == Common::kPlatformAmiga) {
+ // these two fields are little endian on Amiga
+ objHdr->fileLen = readS.readUint16LE();
+ objHdr->objId = readS.readUint16LE();
+ } else {
+ // endianness is consistent on other platforms
+ objHdr->fileLen = readS.readUint16();
+ objHdr->objId = readS.readUint16();
+ }
for (int i = 0; i < IDI_WTP_MAX_OBJ_STR_END; i++)
objHdr->ofsEndStr[i] = readS.readUint16();
@@ -94,6 +100,8 @@ uint32 WinnieEngine::readRoom(int iRoom, uint8 *buffer, WTP_ROOM_HDR &roomHdr) {
fileName = Common::Path(Common::String::format(IDS_WTP_ROOM_C64, iRoom));
else if (getPlatform() == Common::kPlatformApple2)
fileName = Common::Path(Common::String::format(IDS_WTP_ROOM_APPLE, iRoom));
+ else if (getPlatform() == Common::kPlatformCoCo)
+ fileName = Common::Path(Common::String::format(IDS_WTP_ROOM_COCO, iRoom));
Common::File file;
if (!file.open(fileName)) {
@@ -127,6 +135,8 @@ uint32 WinnieEngine::readObj(int iObj, uint8 *buffer) {
fileName = Common::Path(Common::String::format(IDS_WTP_OBJ_C64, iObj));
else if (getPlatform() == Common::kPlatformApple2)
fileName = Common::Path(Common::String::format(IDS_WTP_OBJ_APPLE, iObj));
+ else if (getPlatform() == Common::kPlatformCoCo)
+ fileName = Common::Path(Common::String::format(IDS_WTP_OBJ_COCO, iObj));
Common::File file;
if (!file.open(fileName)) {
@@ -149,13 +159,27 @@ uint32 WinnieEngine::readObj(int iObj, uint8 *buffer) {
void WinnieEngine::randomize() {
int iObj = 0;
int iRoom = 0;
- bool done;
+
+ // Object 1's file is missing, empty, or corrupt on several platforms.
+ bool skipObject1 = false;
+ switch (getPlatform()) {
+ case Common::kPlatformApple2:
+ case Common::kPlatformC64:
+ case Common::kPlatformCoCo:
+ skipObject1 = true;
+ break;
+ default:
+ break;
+ }
for (int i = 0; i < IDI_WTP_MAX_OBJ_MISSING; i++) {
- done = false;
+ bool done = false;
while (!done) {
iObj = rnd(IDI_WTP_MAX_OBJ); // 1-40
+ if (iObj == 1 && skipObject1) {
+ continue;
+ }
done = true;
for (int j = 0; j < IDI_WTP_MAX_OBJ_MISSING; j++) {
@@ -391,12 +415,14 @@ int WinnieEngine::parser(int pc, int index, uint8 *buffer) {
opcode = *(buffer + pc++);
iNewRoom = opcode;
- // Apple II is missing a zero terminator in one script block of
- // Christopher Robin's tree house. The room file was fixed in
- // later versions, and the Apple II version behaves correctly,
+ // Apple II & C64 are missing a zero terminator in a script block
+ // of Christopher Robin's tree house. The room file was fixed in
+ // later versions, and the A2 and C64 versions behave correctly,
// so the code must contain a workaround to prevent executing
// the next script block before exiting the room.
- if (_room == 38 && getPlatform() == Common::kPlatformApple2) {
+ if (_room == 38 &&
+ (getPlatform() == Common::kPlatformApple2 ||
+ getPlatform() == Common::kPlatformC64)) {
_room = iNewRoom;
return IDI_WTP_PAR_GOTO; // change rooms immediately
}
@@ -1478,18 +1504,29 @@ void WinnieEngine::init() {
_tiggerOrMist = false; // tigger appears first
stopTimer(); // timer starts after intro
- if (getPlatform() != Common::kPlatformAmiga) {
- _isBigEndian = false;
- _roomOffset = IDI_WTP_OFS_ROOM;
- _objOffset = IDI_WTP_OFS_OBJ;
- } else {
+ switch (getPlatform()) {
+ case Common::kPlatformAmiga:
+ case Common::kPlatformCoCo:
_isBigEndian = true;
_roomOffset = 0;
_objOffset = 0;
+ break;
+ default:
+ _isBigEndian = false;
+ _roomOffset = IDI_WTP_OFS_ROOM;
+ _objOffset = IDI_WTP_OFS_OBJ;
+ break;
}
- if (getPlatform() == Common::kPlatformC64 || getPlatform() == Common::kPlatformApple2)
+ switch (getPlatform()) {
+ case Common::kPlatformApple2:
+ case Common::kPlatformC64:
+ case Common::kPlatformCoCo:
_picture->setPictureVersion(AGIPIC_C64);
+ break;
+ default:
+ break;
+ }
hotspotNorth = Common::Rect(20, 0, (IDI_WTP_PIC_WIDTH + 10) * 2, 10);
hotspotSouth = Common::Rect(20, IDI_WTP_PIC_HEIGHT - 10, (IDI_WTP_PIC_WIDTH + 10) * 2, IDI_WTP_PIC_HEIGHT);
@@ -1501,9 +1538,15 @@ Common::Error WinnieEngine::go() {
init();
randomize();
- // The intro is not supported on these platforms yet
- if (getPlatform() != Common::kPlatformC64 && getPlatform() != Common::kPlatformApple2)
+ switch (getPlatform()) {
+ case Common::kPlatformAmiga:
+ case Common::kPlatformDOS:
intro();
+ break;
+ default:
+ warning("intro not implemented");
+ break;
+ }
gameLoop();
diff --git a/engines/agi/preagi/winnie.h b/engines/agi/preagi/winnie.h
index 8cc7bed0c05..c149483385c 100644
--- a/engines/agi/preagi/winnie.h
+++ b/engines/agi/preagi/winnie.h
@@ -32,10 +32,12 @@ namespace Agi {
#define IDS_WTP_ROOM_AMIGA "rooms/room.%d"
#define IDS_WTP_ROOM_C64 "room%02d"
#define IDS_WTP_ROOM_APPLE "room%d.obj"
+#define IDS_WTP_ROOM_COCO "room%02d"
#define IDS_WTP_OBJ_DOS "obj.%02d"
#define IDS_WTP_OBJ_AMIGA "objects/object.%d"
#define IDS_WTP_OBJ_C64 "object%02d"
#define IDS_WTP_OBJ_APPLE "object%d.obj"
+#define IDS_WTP_OBJ_COCO "obj%02d"
#define IDS_WTP_SND_DOS "snd.%02d"
#define IDS_WTP_SND_AMIGA "Sounds"
#define IDS_WTP_SND_C64 "sound.obj"
Commit: 074bb11268d414a4ce82e7ec1eb21d6e9cd7f214
https://github.com/scummvm/scummvm/commit/074bb11268d414a4ce82e7ec1eb21d6e9cd7f214
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:47-07:00
Commit Message:
AGI: PREAGI: Cleanup WINNIE game loop
Changed paths:
engines/agi/preagi/winnie.cpp
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index d0a7d775858..ae883c1258e 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -1134,13 +1134,13 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {
void WinnieEngine::gameLoop() {
WTP_ROOM_HDR hdr;
uint8 *roomdata = (uint8 *)malloc(4096);
- int iBlock;
uint8 decodePhase = 0;
startTimer();
while (!shouldQuit()) {
- if (decodePhase == 0) {
+ switch (decodePhase) {
+ case 0:
if (!_gameStateWinnie.nObjMiss && (_room == IDI_WTP_ROOM_PICNIC)) {
_room = IDI_WTP_ROOM_PARTY;
stopTimer();
@@ -1150,18 +1150,16 @@ void WinnieEngine::gameLoop() {
drawRoomPic();
_system->updateScreen();
decodePhase = 1;
- }
-
- if (decodePhase == 1) {
+ break;
+ case 1:
if (getObjInRoom(_room)) {
printObjStr(getObjInRoom(_room), IDI_WTP_OBJ_DESC);
getSelection(kSelAnyKey);
}
decodePhase = 2;
- }
-
- if (decodePhase == 2) {
- for (iBlock = 0; iBlock < IDI_WTP_MAX_BLOCK; iBlock++) {
+ break;
+ case 2:
+ for (int iBlock = 0; iBlock < IDI_WTP_MAX_BLOCK; iBlock++) {
if (parser(hdr.ofsDesc[iBlock] - _roomOffset, iBlock, roomdata) == IDI_WTP_PAR_BACK) {
decodePhase = 1;
break;
@@ -1169,10 +1167,9 @@ void WinnieEngine::gameLoop() {
}
if (decodePhase == 2)
decodePhase = 3;
- }
-
- if (decodePhase == 3) {
- for (iBlock = 0; iBlock < IDI_WTP_MAX_BLOCK; iBlock++) {
+ break;
+ case 3:
+ for (int iBlock = 0; iBlock < IDI_WTP_MAX_BLOCK; iBlock++) {
int result = parser(hdr.ofsBlock[iBlock] - _roomOffset, iBlock, roomdata);
if (result == IDI_WTP_PAR_GOTO) {
decodePhase = 0;
@@ -1187,6 +1184,9 @@ void WinnieEngine::gameLoop() {
break;
}
}
+ break;
+ default:
+ break;
}
}
Commit: 8f18144a4805954e92d0117dc0bb04b09816661b
https://github.com/scummvm/scummvm/commit/8f18144a4805954e92d0117dc0bb04b09816661b
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:47-07:00
Commit Message:
AGI: PREAGI: Cleanup sound code
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/preagi.cpp
engines/agi/preagi/preagi.h
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 49db9570240..180b679cebb 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -664,7 +664,7 @@ void MickeyEngine::playNote(MSA_SND_NOTE note) {
// Pause
_system->delayMillis((uint)(note.length / IDI_SND_TIMER_RESOLUTION));
} else {
- PreAgiEngine::playNote(IDI_SND_OSCILLATOR_FREQUENCY / note.counter, (int32)(note.length / IDI_SND_TIMER_RESOLUTION));
+ playSpeakerNote(IDI_SND_OSCILLATOR_FREQUENCY / note.counter, (int32)(note.length / IDI_SND_TIMER_RESOLUTION));
}
}
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
index e824182c4cf..cf6e7a3b0a8 100644
--- a/engines/agi/preagi/preagi.cpp
+++ b/engines/agi/preagi/preagi.cpp
@@ -275,15 +275,11 @@ int PreAgiEngine::getSelection(SelectionTypes type) {
return 0;
}
-void PreAgiEngine::playNote(int16 frequency, int32 length) {
+void PreAgiEngine::playSpeakerNote(int16 frequency, int32 length) {
_speakerStream->play(Audio::PCSpeaker::kWaveFormSquare, frequency, length);
- waitForTimer(length);
-}
-
-void PreAgiEngine::waitForTimer(int msec_delay) {
- uint32 start_time = _system->getMillis();
- while (_system->getMillis() < start_time + msec_delay) {
+ uint32 startTime = _system->getMillis();
+ while (_system->getMillis() - startTime < (uint32)length) {
_system->updateScreen();
_system->delayMillis(10);
}
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
index a4c666790c9..fb3837a84be 100644
--- a/engines/agi/preagi/preagi.h
+++ b/engines/agi/preagi/preagi.h
@@ -56,7 +56,6 @@ class PreAgiEngine : public AgiBase {
protected:
void initialize() override;
- void pollTimer() {}
int getKeypress() override { return 0; }
bool isKeypress() override { return false; }
void clearKeyQueue() override {}
@@ -101,8 +100,7 @@ protected:
// Saved Games
Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
- void playNote(int16 frequency, int32 length);
- void waitForTimer(int msec_delay);
+ void playSpeakerNote(int16 frequency, int32 length);
private:
int _defaultColor;
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 4edb2e202c8..960681705eb 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -468,16 +468,15 @@ void TrollEngine::playTune(int tune, int len) {
if (!_soundOn)
return;
- int freq, duration;
int ptr = _tunes[tune - 1];
for (int i = 0; i < len; i++) {
- freq = READ_LE_UINT16(_gameData + ptr);
+ int freq = READ_LE_UINT16(_gameData + ptr);
ptr += 2;
- duration = READ_LE_UINT16(_gameData + ptr);
+ int duration = READ_LE_UINT16(_gameData + ptr);
ptr += 2;
- playNote(freq, duration);
+ playSpeakerNote(freq, duration);
}
}
Commit: 46970323a40d2ea991e5b9237480cf58313e5781
https://github.com/scummvm/scummvm/commit/46970323a40d2ea991e5b9237480cf58313e5781
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:47-07:00
Commit Message:
AGI: PREAGI: Improve WINNIE event handling
Changed paths:
engines/agi/preagi/preagi.cpp
engines/agi/preagi/preagi.h
engines/agi/preagi/winnie.cpp
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
index cf6e7a3b0a8..688cf349d11 100644
--- a/engines/agi/preagi/preagi.cpp
+++ b/engines/agi/preagi/preagi.cpp
@@ -285,4 +285,22 @@ void PreAgiEngine::playSpeakerNote(int16 frequency, int32 length) {
}
}
+void PreAgiEngine::wait(uint32 delay) {
+ Common::Event event;
+ uint32 startTime = _system->getMillis();
+
+ while (!shouldQuit()) {
+ // process events
+ while (_eventMan->pollEvent(event)) {
+ }
+
+ if (_system->getMillis() - startTime >= delay) {
+ return;
+ }
+
+ _system->updateScreen();
+ _system->delayMillis(10);
+ }
+}
+
} // End of namespace Agi
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
index fb3837a84be..29aaa9a14fb 100644
--- a/engines/agi/preagi/preagi.h
+++ b/engines/agi/preagi/preagi.h
@@ -101,6 +101,7 @@ protected:
Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
void playSpeakerNote(int16 frequency, int32 length);
+ void wait(uint32 delay);
private:
int _defaultColor;
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index ae883c1258e..2ce3dcaf30b 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -212,17 +212,11 @@ void WinnieEngine::randomize() {
void WinnieEngine::intro() {
drawPic(IDS_WTP_FILE_LOGO);
printStr(IDS_WTP_INTRO_0);
- _system->updateScreen();
- _system->delayMillis(0x640);
-
- if (getPlatform() == Common::kPlatformAmiga)
- _gfx->clearDisplay(0);
+ wait(1600);
drawPic(IDS_WTP_FILE_TITLE);
-
printStr(IDS_WTP_INTRO_1);
- _system->updateScreen();
- _system->delayMillis(0x640);
+ wait(1600);
if (!playSound(IDI_WTP_SND_POOH_0))
return;
@@ -1298,10 +1292,19 @@ bool WinnieEngine::playSound(ENUM_WTP_SOUND iSound) {
// Loop until the sound is done
bool skippedSound = false;
while (!shouldQuit() && _game.sounds[0]->isPlaying()) {
+ // process all events to keep window responsive and to
+ // allow interruption by mouse button or key press.
Common::Event event;
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN:
+ // don't interrupt if a modifier is pressed
+ if (event.kbd.flags & Common::KBD_NON_STICKY) {
+ continue;
+ }
+ // fall through
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONUP:
_sound->stopSound();
skippedSound = true;
break;
@@ -1310,6 +1313,7 @@ bool WinnieEngine::playSound(ENUM_WTP_SOUND iSound) {
}
}
+ _system->updateScreen();
_system->delayMillis(10);
}
Commit: bbb7e00a643972efbb0c0b90cae10ce404cc078c
https://github.com/scummvm/scummvm/commit/bbb7e00a643972efbb0c0b90cae10ce404cc078c
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:47-07:00
Commit Message:
AGI: PREAGI: Improve TROLL intro event handling
Changed paths:
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 960681705eb..c41835935cf 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -196,10 +196,16 @@ void TrollEngine::waitAnyKeyIntro() {
while (!shouldQuit()) {
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ // don't interrupt if a modifier is pressed
+ if (event.kbd.flags & Common::KBD_NON_STICKY) {
+ continue;
+ }
+ // fall through
case Common::EVENT_RETURN_TO_LAUNCHER:
case Common::EVENT_QUIT:
case Common::EVENT_LBUTTONUP:
- case Common::EVENT_KEYDOWN:
+ case Common::EVENT_RBUTTONUP:
return;
default:
break;
@@ -212,11 +218,9 @@ void TrollEngine::waitAnyKeyIntro() {
// fall through
case 0:
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_2);
- _system->updateScreen();
break;
case 100:
drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_3);
- _system->updateScreen();
break;
default:
break;
@@ -356,8 +360,7 @@ void TrollEngine::intro() {
clearScreen(0x2F);
drawStr(9, 10, kColorDefault, IDS_TRO_INTRO_0);
drawStr(14, 15, kColorDefault, IDS_TRO_INTRO_1);
- _system->updateScreen();
- _system->delayMillis(3200);
+ wait(3200);
CursorMan.showMouse(true);
Commit: 193ae873c39a58a1f13f76a695461c803f8ad43e
https://github.com/scummvm/scummvm/commit/193ae873c39a58a1f13f76a695461c803f8ad43e
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:47-07:00
Commit Message:
AGI: PREAGI: Improve MICKEY event handling
Changed paths:
engines/agi/preagi/mickey.cpp
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 180b679cebb..a893bf2de0d 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -700,11 +700,16 @@ void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) {
if (iSound == IDI_MSA_SND_THEME) {
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ // don't interrupt if a modifier is pressed
+ if (event.kbd.flags & Common::KBD_NON_STICKY) {
+ continue;
+ }
+ // fall through
case Common::EVENT_RETURN_TO_LAUNCHER:
case Common::EVENT_QUIT:
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONUP:
- case Common::EVENT_KEYDOWN:
delete[] buffer;
return;
default:
@@ -2203,9 +2208,14 @@ void MickeyEngine::waitAnyKey(bool anim) {
while (!shouldQuit()) {
while (_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ // don't interrupt if a modifier is pressed
+ if (event.kbd.flags & Common::KBD_NON_STICKY) {
+ continue;
+ }
+ // fall through
case Common::EVENT_RETURN_TO_LAUNCHER:
case Common::EVENT_QUIT:
- case Common::EVENT_KEYDOWN:
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONUP:
return;
Commit: 8173f1ee09c37465329c6ba38a17f58314b79643
https://github.com/scummvm/scummvm/commit/8173f1ee09c37465329c6ba38a17f58314b79643
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:48-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY crystal animation
Fixes crystal animation not looping, fixes the next room picture
not being completely drawn.
Regression from: 1ef27b3e5b0b2ef955af87c8ac6ff0458299e7bc
Removes the hard-coded Mickey-crystal logic from PictureMgr;
MickeyEngine is now responsible for animating crystals.
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/mickey.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index d226c52a790..7d17cc3aea7 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -52,7 +52,7 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_yOffset = 0;
_flags = 0;
- _currentStep = 0;
+ _maxStep = 0;
}
void PictureMgr::putVirtPixel(int x, int y) {
@@ -482,8 +482,6 @@ void PictureMgr::drawPictureV15() {
void PictureMgr::drawPictureV2() {
bool nibbleMode = false;
- bool mickeyCrystalAnimation = false;
- int mickeyIteration = 0;
debugC(8, kDebugLevelMain, "Drawing V2/V3 picture");
@@ -492,10 +490,7 @@ void PictureMgr::drawPictureV2() {
nibbleMode = true;
}
- if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI) {
- mickeyCrystalAnimation = true;
- }
-
+ int step = 0;
while (_dataOffset < _dataSize) {
byte curByte = getNextByte();
@@ -559,24 +554,10 @@ void PictureMgr::drawPictureV2() {
break;
}
- // This is used by Mickey for the crystal animation
- // One frame of the crystal animation is shown on each iteration, based on _currentStep
- if (mickeyCrystalAnimation) {
- if (_currentStep == mickeyIteration) {
- int16 storedXOffset = _xOffset;
- int16 storedYOffset = _yOffset;
- // Note that picture coordinates are correct for Mickey only
- showPic(10, 0, _width, _height);
- _xOffset = storedXOffset;
- _yOffset = storedYOffset;
- _currentStep++;
- if (_currentStep > 14) // crystal animation is 15 frames
- _currentStep = 0;
- // reset the picture step flag - it will be set when the next frame of the crystal animation is drawn
- _flags &= ~kPicFStep;
- return; // return back to the game loop
- }
- mickeyIteration++;
+ // Limit drawing to the optional maximum number of opcodes
+ step++;
+ if (step == _maxStep) {
+ return;
}
}
}
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 37d19237f8e..55be9bbe583 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -54,10 +54,9 @@ enum AgiPictureVersion {
enum AgiPictureFlags {
kPicFNone = (1 << 0),
kPicFCircle = (1 << 1),
- kPicFStep = (1 << 2),
- kPicFf3Stop = (1 << 3),
- kPicFf3Cont = (1 << 4),
- kPicFTrollMode = (1 << 5)
+ kPicFf3Stop = (1 << 2),
+ kPicFf3Cont = (1 << 3),
+ kPicFTrollMode = (1 << 4)
};
class AgiBase;
@@ -134,6 +133,9 @@ public:
_height = h;
}
+ void setMaxStep(int maxStep) { _maxStep = maxStep; }
+ int getMaxStep() const { return _maxStep; }
+
private:
int16 _resourceNr;
uint8 *_data;
@@ -157,7 +159,7 @@ private:
int16 _yOffset;
int _flags;
- int _currentStep;
+ int _maxStep; // Max opcodes to draw, zero for all. Used by preagi (Mickey)
};
} // End of namespace Agi
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index a893bf2de0d..af4690349f0 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -740,9 +740,18 @@ void MickeyEngine::drawObj(ENUM_MSA_OBJECT iObj, int x0, int y0) {
file.read(buffer, size);
file.close();
- if (iObj == IDI_MSA_OBJECT_CRYSTAL)
- _picture->setPictureFlags(kPicFStep);
+ int maxStep = 0; // default: draw all opcodes
+ if (iObj == IDI_MSA_OBJECT_CRYSTAL) {
+ // Handle crystal animation. Each "frame" is the picture
+ // drawn with an additional opcode until it wraps around.
+ // The crystal has 14 opcodes followed by the terminator.
+ maxStep = _picture->getMaxStep() + 1;
+ if (maxStep == 15) {
+ maxStep = 1;
+ }
+ }
+ _picture->setMaxStep(maxStep);
_picture->setOffset(x0, y0);
_picture->decodePictureFromBuffer(buffer, size, false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
_picture->setOffset(0, 0);
@@ -763,6 +772,7 @@ void MickeyEngine::drawPic(int iPic) {
file.close();
// Note that decodePicture clears the screen
+ _picture->setMaxStep(0);
_picture->setOffset(10, 0);
_picture->decodePictureFromBuffer(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
_picture->setOffset(0, 0);
@@ -808,6 +818,7 @@ void MickeyEngine::drawRoomAnimation() {
_picture->setPictureData(objLight);
_picture->setPictureFlags(kPicFCircle);
+ _picture->setMaxStep(0);
_picture->drawPicture();
}
_picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
Commit: ebeacfa9d981f40504e71e1b2d1127217f21f390
https://github.com/scummvm/scummvm/commit/ebeacfa9d981f40504e71e1b2d1127217f21f390
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:48-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY object placement
Fixes all objects being drawn 10 pixels to the left
Regression from: 1ef27b3e5b0b2ef955af87c8ac6ff0458299e7bc
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index af4690349f0..2ec72d0135c 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -752,10 +752,10 @@ void MickeyEngine::drawObj(ENUM_MSA_OBJECT iObj, int x0, int y0) {
}
_picture->setMaxStep(maxStep);
- _picture->setOffset(x0, y0);
+ _picture->setOffset(IDI_MSA_PIC_X0 + x0, IDI_MSA_PIC_Y0 + y0);
_picture->decodePictureFromBuffer(buffer, size, false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
_picture->setOffset(0, 0);
- _picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+ _picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
void MickeyEngine::drawPic(int iPic) {
@@ -773,10 +773,10 @@ void MickeyEngine::drawPic(int iPic) {
// Note that decodePicture clears the screen
_picture->setMaxStep(0);
- _picture->setOffset(10, 0);
+ _picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
_picture->decodePictureFromBuffer(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
_picture->setOffset(0, 0);
- _picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+ _picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
void MickeyEngine::drawRoomAnimation() {
@@ -819,10 +819,10 @@ void MickeyEngine::drawRoomAnimation() {
_picture->setPictureData(objLight);
_picture->setPictureFlags(kPicFCircle);
_picture->setMaxStep(0);
+ _picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
_picture->drawPicture();
}
- _picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
-
+ _picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
_gameStateMickey.nFrame--;
if (_gameStateMickey.nFrame < 0)
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index a92fe82e36f..dfa563c2106 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -99,6 +99,8 @@ const char IDS_MSA_INSERT_DISK[][40] = {
#define IDI_MSA_PIC_WIDTH 140
#define IDI_MSA_PIC_HEIGHT 159
+#define IDI_MSA_PIC_X0 10
+#define IDI_MSA_PIC_Y0 0
// pictures
Commit: 6b356e23160e38b25b9e13cfe83e655a01895f17
https://github.com/scummvm/scummvm/commit/6b356e23160e38b25b9e13cfe83e655a01895f17
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:48-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY saves always loading to Earth
Fixes loading a saved game and always returning to Earth instead of the
planet the game was saved on.
Unclear if this was original behavior, but we already save the current
planet and load it back, so it seems wrong to discard that and send the
player to a different planet instead.
Changed paths:
engines/agi/preagi/mickey.cpp
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 2ec72d0135c..23e811d6fe3 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -1013,14 +1013,11 @@ bool MickeyEngine::loadGame() {
}
saveVersion = infile->readByte();
- if (saveVersion < 2) {
- warning("The planet data in this save game is corrupted. Load aborted");
+ if (saveVersion != MSA_SAVEGAME_VERSION) { // currently only one valid version
+ warning("MickeyEngine::loadGame unknown save version: %d", saveVersion);
return false;
}
- if (saveVersion != MSA_SAVEGAME_VERSION)
- warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, MSA_SAVEGAME_VERSION);
-
_gameStateMickey.iRoom = infile->readByte();
_gameStateMickey.iPlanet = infile->readByte();
_gameStateMickey.iDisk = infile->readByte();
@@ -1426,9 +1423,7 @@ void MickeyEngine::intro() {
_gameStateMickey.fIntro = true;
if (chooseY_N(IDO_MSA_LOAD_GAME[0], true)) {
if (loadGame()) {
- _gameStateMickey.iPlanet = IDI_MSA_PLANET_EARTH;
_gameStateMickey.fIntro = false;
- _gameStateMickey.iRoom = IDI_MSA_PIC_SHIP_CORRIDOR;
return;
}
}
Commit: 3068b384fefbefb60dc64a91cfe0950203cce201
https://github.com/scummvm/scummvm/commit/3068b384fefbefb60dc64a91cfe0950203cce201
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:48-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY crystal persistence
Fixes crystal reappearing on Earth, and other edge cases.
The fHasXtal flag was being used to track two conflicting things:
1. If the player picked up a crystal but hadn't flipped the switch yet.
2. If the current planet still has a crystal.
Now the flag is only used for the former, and the latter is derived
from stable game state.
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 23e811d6fe3..6377f134116 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -849,7 +849,7 @@ void MickeyEngine::drawRoomAnimation() {
// draw crystal
if (_gameStateMickey.iRoom == IDI_MSA_XTAL_ROOM_XY[_gameStateMickey.iPlanet][0]) {
- if (!_gameStateMickey.fHasXtal) {
+ if (isCrystalOnCurrentPlanet()) {
switch (_gameStateMickey.iPlanet) {
case IDI_MSA_PLANET_VENUS:
if (_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] != 2)
@@ -1301,7 +1301,7 @@ void MickeyEngine::gameOver() {
}
void MickeyEngine::flipSwitch() {
- if (_gameStateMickey.fHasXtal || _gameStateMickey.nXtals) {
+ if (_gameStateMickey.nXtals) {
if (!_gameStateMickey.fStoryShown)
printStory();
@@ -1784,14 +1784,14 @@ bool MickeyEngine::parse(int cmd, int arg) {
// MERCURY
case IDI_MSA_ACTION_GET_XTAL_MERCURY:
- if (_gameStateMickey.fHasXtal) {
- _gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
- printDatMessage(32);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
if (_gameStateMickey.fItem[IDI_MSA_ITEM_SUNGLASSES]) {
_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
}
printDatMessage(arg);
+ } else {
+ _gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 2;
+ printDatMessage(32);
}
break;
case IDI_MSA_ACTION_GIVE_SUNGLASSES:
@@ -1822,10 +1822,10 @@ bool MickeyEngine::parse(int cmd, int arg) {
return true;
case IDI_MSA_ACTION_GET_XTAL_SATURN:
- if (_gameStateMickey.fHasXtal) {
- printDatMessage(29);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
getXtal(arg);
+ } else {
+ printDatMessage(29);
}
break;
case IDI_MSA_ACTION_LEAVE_ISLAND:
@@ -1838,13 +1838,13 @@ bool MickeyEngine::parse(int cmd, int arg) {
// PLUTO
case IDI_MSA_ACTION_GET_XTAL_PLUTO:
- if (_gameStateMickey.fHasXtal) {
- printDatMessage(19);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
if (_gameStateMickey.fItem[IDI_MSA_ITEM_BONE]) {
_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
}
printDatMessage(arg);
+ } else {
+ printDatMessage(19);
}
break;
case IDI_MSA_ACTION_GIVE_BONE:
@@ -1870,9 +1870,7 @@ bool MickeyEngine::parse(int cmd, int arg) {
}
break;
case IDI_MSA_ACTION_GET_XTAL_JUPITER:
- if (_gameStateMickey.fHasXtal) {
- printDatMessage(15);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
switch (_gameStateMickey.nRocks) {
case 0:
if (_gameStateMickey.fItem[IDI_MSA_ITEM_ROCK]) {
@@ -1892,6 +1890,8 @@ bool MickeyEngine::parse(int cmd, int arg) {
default:
break;
}
+ } else {
+ printDatMessage(15);
}
break;
case IDI_MSA_ACTION_THROW_ROCK:
@@ -1928,17 +1928,17 @@ bool MickeyEngine::parse(int cmd, int arg) {
return true;
case IDI_MSA_ACTION_PLUTO_DIG:
- if (_gameStateMickey.fHasXtal) {
- printDatMessage(21);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
getXtal(arg);
+ } else {
+ printDatMessage(21);
}
break;
case IDI_MSA_ACTION_GET_XTAL_MARS:
- if (_gameStateMickey.fHasXtal) {
- printDatMessage(23);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
printDatMessage(arg);
+ } else {
+ printDatMessage(23);
}
break;
@@ -1984,13 +1984,13 @@ bool MickeyEngine::parse(int cmd, int arg) {
}
break;
case IDI_MSA_ACTION_GET_XTAL_URANUS:
- if (_gameStateMickey.fHasXtal) {
- printDatMessage(34);
- } else {
+ if (isCrystalOnCurrentPlanet()) {
if (_gameStateMickey.fItem[IDI_MSA_ITEM_CROWBAR]) {
_gameStateMickey.iRmMenu[_gameStateMickey.iRoom] = 1;
}
printDatMessage(arg);
+ } else {
+ printDatMessage(34);
}
break;
case IDI_MSA_ACTION_USE_CROWBAR_1:
@@ -2019,7 +2019,6 @@ bool MickeyEngine::parse(int cmd, int arg) {
if ((_gameStateMickey.nXtals == IDI_MSA_MAX_PLANET) && (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH))
gameOver();
if ((_gameStateMickey.iPlanet == _gameStateMickey.iPlanetXtal[_gameStateMickey.nXtals]) || (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH)) {
- _gameStateMickey.fHasXtal = false;
_gameStateMickey.iRoom = IDI_MSA_HOME_PLANET[_gameStateMickey.iPlanet];
if (_gameStateMickey.iPlanet != IDI_MSA_PLANET_EARTH)
@@ -2367,4 +2366,22 @@ Common::Error MickeyEngine::go() {
return Common::kNoError;
}
+bool MickeyEngine::isCrystalOnCurrentPlanet() const {
+ // Earth is a special case, because the planet list may not have been
+ // initialized yet. Earth is always the first planet, so if no crystals
+ // have been gotten yet, then earth's crystal must still be there.
+ if (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH) {
+ return (_gameStateMickey.nXtals == 0);
+ }
+
+ if (_gameStateMickey.fPlanetsInitialized) {
+ for (uint8 i = 1; i < IDI_MSA_MAX_DAT; i++) {
+ if (_gameStateMickey.iPlanetXtal[i] == _gameStateMickey.iPlanet) {
+ return (_gameStateMickey.nXtals <= i);
+ }
+ }
+ }
+ return false;
+}
+
} // End of namespace Agi
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index dfa563c2106..a107e7bad28 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -754,6 +754,8 @@ protected:
return false;
}
}
+
+ bool isCrystalOnCurrentPlanet() const;
};
} // End of namespace Agi
Commit: dbd61917f766f8eeb8bd8fa83b2f6466618cca01
https://github.com/scummvm/scummvm/commit/dbd61917f766f8eeb8bd8fa83b2f6466618cca01
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:49-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY crystal appearing before room change
Changed paths:
engines/agi/preagi/mickey.cpp
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 6377f134116..1ce12eb58bb 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -1816,9 +1816,12 @@ bool MickeyEngine::parse(int cmd, int arg) {
break;
case IDI_MSA_ACTION_USE_MATTRESS:
- _gameStateMickey.iRoom = IDI_MSA_PIC_SATURN_ISLAND;
-
printDatMessage(arg);
+
+ // must set room after printDatMessage, or else the crystal from
+ // the next room will appear and animate while still displaying
+ // the picture for the current room
+ _gameStateMickey.iRoom = IDI_MSA_PIC_SATURN_ISLAND;
return true;
case IDI_MSA_ACTION_GET_XTAL_SATURN:
Commit: ed9e939faa7250c6aa5d8d22970710658b5f9973
https://github.com/scummvm/scummvm/commit/ed9e939faa7250c6aa5d8d22970710658b5f9973
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:49-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY ship button behavior
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 1ce12eb58bb..3c8c54681dc 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -1195,8 +1195,7 @@ void MickeyEngine::saveGame() {
void MickeyEngine::showPlanetInfo() {
for (int i = 0; i < 4; i++) {
- printExeStr(IDO_MSA_PLANET_INFO[_gameStateMickey.iPlanet][i]);
- waitAnyKey();
+ printExeMsg(IDO_MSA_PLANET_INFO[_gameStateMickey.iPlanet][i]);
}
}
@@ -1268,7 +1267,7 @@ void MickeyEngine::pressOB(int iButton) {
}
// print pressed buttons
- printLine("MICKEY HAS PRESSED: ");
+ printExeStr(IDO_MSA_MICKEY_HAS_PRESSED);
drawStr(20, 22, IDA_DEFAULT, szButtons);
waitAnyKey();
}
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index a107e7bad28..617bd54d015 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -618,6 +618,7 @@ const int IDO_MSA_NEXT_PIECE[IDI_MSA_MAX_PLANET][5] = {
#define IDO_MSA_PRESS_1_TO_9 0x7530
#define IDO_MSA_PRESS_YES_OR_NO 0x480D
+#define IDO_MSA_MICKEY_HAS_PRESSED 0x5D90
#define IDO_MSA_TOO_MANY_BUTTONS_PRESSED 0x5DF7
#define IDO_MSA_XL30_SPEAKING 0x4725
Commit: 03d13330e80996d79d94e35cd58d91d2e9cedf4d
https://github.com/scummvm/scummvm/commit/03d13330e80996d79d94e35cd58d91d2e9cedf4d
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:49-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY game over handling
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 3c8c54681dc..8e4d86bdc15 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -1279,10 +1279,6 @@ void MickeyEngine::insertDisk(int iDisk) {
}
void MickeyEngine::gameOver() {
- // We shouldn't run the game over segment if we're quitting.
- if (shouldQuit())
- return;
-
drawPic(IDI_MSA_PIC_EARTH_SHIP_LEAVING);
printExeMsg(IDO_MSA_GAME_OVER[3]);
playSound(IDI_MSA_SND_GAME_OVER);
@@ -1296,7 +1292,7 @@ void MickeyEngine::gameOver() {
printExeMsg(IDO_MSA_GAME_OVER[7]);
}
- waitAnyKey();
+ _isGameOver = true;
}
void MickeyEngine::flipSwitch() {
@@ -2018,8 +2014,11 @@ bool MickeyEngine::parse(int cmd, int arg) {
break;
case IDI_MSA_ACTION_GO_PLANET:
if (!_gameStateMickey.fShipDoorOpen) {
- if ((_gameStateMickey.nXtals == IDI_MSA_MAX_PLANET) && (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH))
+ if ((_gameStateMickey.nXtals == IDI_MSA_MAX_PLANET) && (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH)) {
gameOver();
+ return true;
+ }
+
if ((_gameStateMickey.iPlanet == _gameStateMickey.iPlanetXtal[_gameStateMickey.nXtals]) || (_gameStateMickey.iPlanet == IDI_MSA_PLANET_EARTH)) {
_gameStateMickey.iRoom = IDI_MSA_HOME_PLANET[_gameStateMickey.iPlanet];
@@ -2256,6 +2255,7 @@ void MickeyEngine::debugGotoRoom(int room) {
}
MickeyEngine::MickeyEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+ _isGameOver = false;
setDebugger(new MickeyConsole(this));
}
@@ -2323,7 +2323,7 @@ Common::Error MickeyEngine::go() {
intro();
// Game loop
- while (!shouldQuit()) {
+ while (!shouldQuit() && !_isGameOver) {
drawRoom();
if (_gameStateMickey.fIntro) {
@@ -2363,8 +2363,6 @@ Common::Error MickeyEngine::go() {
_gameStateMickey.nFrame = 0;
}
- gameOver();
-
return Common::kNoError;
}
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index 617bd54d015..f4e881a510b 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -694,6 +694,7 @@ public:
protected:
MSA_GAME _gameStateMickey;
bool _clickToMove;
+ bool _isGameOver;
int getDat(int);
void readExe(int, uint8 *, long);
Commit: 1d77508d055612e817f74d63911abdff3bfceec2
https://github.com/scummvm/scummvm/commit/1d77508d055612e817f74d63911abdff3bfceec2
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:49-07:00
Commit Message:
AGI: PREAGI: Cleanup MICKEY code
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/winnie.cpp
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 8e4d86bdc15..03453f1ac38 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -136,15 +136,13 @@ int MickeyEngine::choose1to9(int ofsPrompt) {
}
void MickeyEngine::printStr(char *buffer) {
- int pc = 1;
- int nRows, iCol, iRow;
-
- nRows = *buffer + IDI_MSA_ROW_MENU_0;
-
clearTextArea();
- for (iRow = IDI_MSA_ROW_MENU_0; iRow < nRows; iRow++) {
- iCol = *(buffer + pc++);
+ int pc = 1;
+ const int nRows = *buffer + IDI_MSA_ROW_MENU_0;
+
+ for (int iRow = IDI_MSA_ROW_MENU_0; iRow < nRows; iRow++) {
+ int iCol = *(buffer + pc++);
drawStr(iRow, iCol, IDA_DEFAULT, buffer + pc);
pc += strlen(buffer + pc) + 1;
}
@@ -255,26 +253,12 @@ bool MickeyEngine::checkMenu() {
}
void MickeyEngine::drawMenu(MSA_MENU &menu, int sel0, int sel1) {
- int iWord;
- int iRow;
- int sel;
- uint8 attr;
-
- // draw menu
-
clearTextArea();
- for (iRow = 0; iRow < 2; iRow++) {
- for (iWord = 0; iWord < menu.row[iRow].count; iWord++) {
- if (iRow)
- sel = sel1;
- else
- sel = sel0;
-
- if (iWord == sel)
- attr = IDA_DEFAULT_REV;
- else
- attr = IDA_DEFAULT;
+ for (int iRow = 0; iRow < 2; iRow++) {
+ for (int iWord = 0; iWord < menu.row[iRow].count; iWord++) {
+ int sel = (iRow == 0) ? sel0 : sel1;
+ uint8 attr = (iWord == sel) ? IDA_DEFAULT_REV : IDA_DEFAULT;
drawStr(IDI_MSA_ROW_MENU_0 + iRow, menu.row[iRow].entry[iWord].x0,
attr, (char *)menu.row[iRow].entry[iWord].szText);
@@ -286,7 +270,6 @@ void MickeyEngine::drawMenu(MSA_MENU &menu, int sel0, int sel1) {
}
void MickeyEngine::getMouseMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow, int x, int y) {
- int iWord;
int *sel = nullptr;
switch (iRow) {
@@ -302,7 +285,7 @@ void MickeyEngine::getMouseMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int
return;
}
- for (iWord = 0; iWord < menu.row[iRow].count; iWord++) {
+ for (int iWord = 0; iWord < menu.row[iRow].count; iWord++) {
if ((x >= menu.row[iRow].entry[iWord].x0) &&
(x < (int)(menu.row[iRow].entry[iWord].x0 +
strlen((char *)menu.row[iRow].entry[iWord].szText)))) {
@@ -315,7 +298,6 @@ void MickeyEngine::getMouseMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int
bool MickeyEngine::getMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow) {
Common::Event event;
int *sel = nullptr;
- int nWords;
int x, y;
int goIndex = -1, northIndex = -1, southIndex = -1, eastIndex = -1, westIndex = -1;
@@ -329,7 +311,7 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU &menu, int *sel0, int *sel1, int iRow)
default:
break;
}
- nWords = menu.row[iRow].count;
+ int nWords = menu.row[iRow].count;
_clickToMove = false;
for (int i = 0; i <= menu.row[0].count; i++)
@@ -587,19 +569,15 @@ void MickeyEngine::getMenuSel(char *buffer, int *sel0, int *sel1) {
}
void MickeyEngine::centerMenu(MSA_MENU *menu) {
- int iWord;
- int iRow;
- int w, x;
-
- for (iRow = 0; iRow < 2; iRow++) {
- w = 0;
- for (iWord = 0; iWord < menu->row[iRow].count; iWord++) {
+ for (int iRow = 0; iRow < 2; iRow++) {
+ int w = 0;
+ for (int iWord = 0; iWord < menu->row[iRow].count; iWord++) {
w += strlen((char *)menu->row[iRow].entry[iWord].szText);
}
w += menu->row[iRow].count - 1;
- x = (40 - w) / 2; // FIX
+ int x = (40 - w) / 2; // FIX
- for (iWord = 0; iWord < menu->row[iRow].count; iWord++) {
+ for (int iWord = 0; iWord < menu->row[iRow].count; iWord++) {
menu->row[iRow].entry[iWord].x0 = x;
x += strlen((char *)menu->row[iRow].entry[iWord].szText) + 1;
}
@@ -607,11 +585,6 @@ void MickeyEngine::centerMenu(MSA_MENU *menu) {
}
void MickeyEngine::patchMenu(MSA_MENU *menu) {
- uint8 buffer[512];
- uint8 menubuf[sizeof(MSA_MENU)];
- int nPatches;
- int pBuf = 0;
-
// change planet name in ship airlock menu
if (_gameStateMickey.iRoom == IDI_MSA_PIC_SHIP_AIRLOCK) {
Common::strcpy_s(menu->row[1].entry[2].szText, IDS_MSA_NAME_PLANET[_gameStateMickey.iPlanet]);
@@ -624,9 +597,11 @@ void MickeyEngine::patchMenu(MSA_MENU *menu) {
}
// copy menu to menubuf
+ uint8 menubuf[sizeof(MSA_MENU)];
memcpy(menubuf, menu, sizeof(menubuf));
// read patches
+ uint8 buffer[512];
readOfsData(
IDOFS_MSA_MENU_PATCHES,
_gameStateMickey.nRmMenu[_gameStateMickey.iRoom] + _gameStateMickey.iRmMenu[_gameStateMickey.iRoom] - 1,
@@ -634,7 +609,8 @@ void MickeyEngine::patchMenu(MSA_MENU *menu) {
);
// get number of patches
- nPatches = buffer[pBuf++];
+ int pBuf = 0;
+ int nPatches = buffer[pBuf++];
// patch menubuf
for (int iPatch = 0; iPatch < nPatches; iPatch++) {
@@ -754,7 +730,6 @@ void MickeyEngine::drawObj(ENUM_MSA_OBJECT iObj, int x0, int y0) {
_picture->setMaxStep(maxStep);
_picture->setOffset(IDI_MSA_PIC_X0 + x0, IDI_MSA_PIC_Y0 + y0);
_picture->decodePictureFromBuffer(buffer, size, false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
- _picture->setOffset(0, 0);
_picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
@@ -775,7 +750,6 @@ void MickeyEngine::drawPic(int iPic) {
_picture->setMaxStep(0);
_picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
_picture->decodePictureFromBuffer(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
- _picture->setOffset(0, 0);
_picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
@@ -804,12 +778,10 @@ void MickeyEngine::drawRoomAnimation() {
case IDI_MSA_PIC_SHIP_URANUS: {
// draw blinking ship lights
- uint8 iColor = 0;
-
_picture->setPattern(2, 0);
for (int i = 0; i < 12; i++) {
- iColor = _gameStateMickey.nFrame + i;
+ uint8 iColor = _gameStateMickey.nFrame + i;
if (iColor > 15)
iColor -= 15;
@@ -871,10 +843,6 @@ void MickeyEngine::drawRoomAnimation() {
}
void MickeyEngine::drawRoom() {
- uint8 buffer[512];
- int pBuf = 0;
- int nObjs;
-
// Draw room picture
if (_gameStateMickey.iRoom == IDI_MSA_PIC_TITLE) {
drawPic(IDI_MSA_PIC_TITLE);
@@ -894,10 +862,12 @@ void MickeyEngine::drawRoom() {
// Draw room objects
if (_gameStateMickey.iRoom < IDI_MSA_MAX_ROOM &&
_gameStateMickey.iRmObj[_gameStateMickey.iRoom] != IDI_MSA_OBJECT_NONE) {
+ uint8 buffer[512];
readOfsData(IDO_MSA_ROOM_OBJECT_XY_OFFSETS,
_gameStateMickey.iRmObj[_gameStateMickey.iRoom], buffer, sizeof(buffer));
- nObjs = buffer[pBuf++];
+ int pBuf = 0;
+ int nObjs = buffer[pBuf++];
for (int iObj = 0; iObj < nObjs; iObj++) {
drawObj((ENUM_MSA_OBJECT)buffer[pBuf], buffer[pBuf + 1], buffer[pBuf + 2]);
@@ -910,23 +880,18 @@ void MickeyEngine::drawRoom() {
}
// Straight mapping, CGA colors to CGA
-const byte BCGColorMappingCGAToCGA[4] = {
+static const byte BCGColorMappingCGAToCGA[4] = {
0, 1, 2, 3
};
// Mapping table to map CGA colors to EGA
-const byte BCGColorMappingCGAToEGA[4] = {
+static const byte BCGColorMappingCGAToEGA[4] = {
0, 11, 13, 15
};
void MickeyEngine::drawLogo() {
const int width = 80;
const int height = 85 * 2;
- byte color1, color2, color3, color4;
- byte *fileBuffer = nullptr;
- uint32 fileBufferSize = 0;
- byte *dataBuffer;
- byte curByte;
const byte *BCGColorMapping = BCGColorMappingCGAToEGA;
// disable color mapping in case we are in CGA mode
@@ -938,8 +903,8 @@ void MickeyEngine::drawLogo() {
if (!infile.open(IDS_MSA_PATH_LOGO))
return;
- fileBufferSize = infile.size();
- fileBuffer = new byte[fileBufferSize];
+ uint32 fileBufferSize = infile.size();
+ byte *fileBuffer = new byte[fileBufferSize];
infile.read(fileBuffer, fileBufferSize);
infile.close();
@@ -948,15 +913,15 @@ void MickeyEngine::drawLogo() {
// Show BCG picture
// It's basically uncompressed CGA 4-color data (4 pixels per byte)
- dataBuffer = fileBuffer;
+ byte *dataBuffer = fileBuffer;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- curByte = *dataBuffer++;
+ byte curByte = *dataBuffer++;
- color1 = BCGColorMapping[(curByte >> 6) & 0x03];
- color2 = BCGColorMapping[(curByte >> 4) & 0x03];
- color3 = BCGColorMapping[(curByte >> 2) & 0x03];
- color4 = BCGColorMapping[(curByte >> 0) & 0x03];
+ byte color1 = BCGColorMapping[(curByte >> 6) & 0x03];
+ byte color2 = BCGColorMapping[(curByte >> 4) & 0x03];
+ byte color3 = BCGColorMapping[(curByte >> 2) & 0x03];
+ byte color4 = BCGColorMapping[(curByte >> 0) & 0x03];
_gfx->putPixelOnDisplay(x * 4 + 0, y, color1);
_gfx->putPixelOnDisplay(x * 4 + 1, y, color2);
@@ -990,12 +955,10 @@ bool MickeyEngine::loadGame() {
Common::InSaveFile *infile;
char szFile[256] = {0};
bool diskerror = true;
- int sel;
- int saveVersion = 0;
int i = 0;
while (diskerror) {
- sel = choose1to9(IDO_MSA_LOAD_GAME[1]);
+ int sel = choose1to9(IDO_MSA_LOAD_GAME[1]);
if (!sel)
return false;
@@ -1009,12 +972,14 @@ bool MickeyEngine::loadGame() {
} else {
if (infile->readUint32BE() != MKTAG('M', 'I', 'C', 'K')) {
warning("MickeyEngine::loadGame wrong save game format");
+ delete infile;
return false;
}
- saveVersion = infile->readByte();
+ byte saveVersion = infile->readByte();
if (saveVersion != MSA_SAVEGAME_VERSION) { // currently only one valid version
warning("MickeyEngine::loadGame unknown save version: %d", saveVersion);
+ delete infile;
return false;
}
@@ -1087,7 +1052,6 @@ void MickeyEngine::saveGame() {
Common::OutSaveFile *outfile;
char szFile[256] = {0};
bool diskerror = true;
- int sel;
int i = 0;
bool fOldDisk = chooseY_N(IDO_MSA_SAVE_GAME[0], false);
@@ -1101,7 +1065,7 @@ void MickeyEngine::saveGame() {
return;
while (diskerror) {
- sel = choose1to9(IDO_MSA_SAVE_GAME[3]);
+ int sel = choose1to9(IDO_MSA_SAVE_GAME[3]);
if (!sel)
return;
@@ -1202,13 +1166,12 @@ void MickeyEngine::showPlanetInfo() {
void MickeyEngine::printStory() {
char buffer[IDI_MSA_LEN_STORY] = {0};
char szLine[41] = {0};
- int iRow;
int pBuf = 0;
readExe(IDO_MSA_GAME_STORY, (uint8 *)buffer, sizeof(buffer));
clearScreen(IDA_DEFAULT);
- for (iRow = 0; iRow < 25; iRow++) {
+ for (int iRow = 0; iRow < 25; iRow++) {
Common::strlcpy(szLine, buffer + pBuf, 41);
drawStr(iRow, 0, IDA_DEFAULT, szLine);
pBuf += strlen(szLine) + 1;
@@ -1216,7 +1179,7 @@ void MickeyEngine::printStory() {
waitAnyKey();
clearScreen(IDA_DEFAULT);
- for (iRow = 0; iRow < 21; iRow++) {
+ for (int iRow = 0; iRow < 21; iRow++) {
Common::strlcpy(szLine, buffer + pBuf, 41);
drawStr(iRow, 0, IDA_DEFAULT, szLine);
pBuf += strlen(szLine) + 1;
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 2ce3dcaf30b..0019a074b85 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -1209,7 +1209,6 @@ void WinnieEngine::drawPic(const char *szName) {
_picture->setOffset(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0);
_picture->decodePictureFromBuffer(buffer, size, true, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
- _picture->setOffset(0, 0);
_picture->showPic(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
free(buffer);
@@ -1226,7 +1225,6 @@ void WinnieEngine::drawObjPic(int iObj, int x0, int y0) {
_picture->setOffset(x0, y0);
_picture->decodePictureFromBuffer(buffer + objhdr.ofsPic - _objOffset, objSize, false, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
- _picture->setOffset(0, 0);
_picture->showPic(10, 0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
free(buffer);
@@ -1246,7 +1244,6 @@ void WinnieEngine::drawRoomPic() {
// draw room picture
_picture->setOffset(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0);
_picture->decodePictureFromBuffer(buffer + roomhdr.ofsPic - _roomOffset, 4096, true, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
- _picture->setOffset(0, 0);
_picture->showPic(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
// draw object picture
Commit: e1d399b4dde53d515ed705cfeec26ea596ede7dc
https://github.com/scummvm/scummvm/commit/e1d399b4dde53d515ed705cfeec26ea596ede7dc
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:50-07:00
Commit Message:
AGI: Remove PictureMgr::setPattern(), used by MICKEY
This was a custom interface that was only used by Mickey's spaceship
lights, but it had no effect on the outcome.
MickeyEngine called setPattern() to initialize the pattern code and
pattern number before drawing each spaceship light picture from memory.
But the pattern code can also be initialized with pic opcode F9, and
that's what MickeyEngine's hard-coded picture data already starts with.
For the pattern number, MickeyEngine only initialized it to zero,
and that's already the default when using the normal interfaces.
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/mickey.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 7d17cc3aea7..e7f42c7fef5 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -293,8 +293,6 @@ void PictureMgr::plotPattern(int x, int y) {
pen_x = pen_final_x;
}
-
- return;
}
/**************************************************************************
@@ -995,12 +993,6 @@ void PictureMgr::showPicWithTransition() {
_gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT);
}
-// preagi needed functions (for plotPattern)
-void PictureMgr::setPattern(uint8 code, uint8 num) {
- _patCode = code;
- _patNum = num;
-}
-
void PictureMgr::setPictureVersion(AgiPictureVersion version) {
_pictureVersion = version;
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 55be9bbe583..02ea5e3291c 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -74,6 +74,7 @@ public:
private:
void xCorner(bool skipOtherCoords = false);
void yCorner(bool skipOtherCoords = false);
+ void plotPattern(int x, int y);
void plotBrush();
byte getNextByte();
@@ -112,10 +113,6 @@ public:
void showPic(int16 x, int16 y, int16 pic_width, int16 pic_height); // <-- for preAGI games
void showPicWithTransition();
- void plotPattern(int x, int y); // public because it's used directly by preagi
-
- void setPattern(uint8 code, uint8 num);
-
void setPictureVersion(AgiPictureVersion version);
void setPictureData(uint8 *data, int len = 4096);
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 03453f1ac38..804739c29a0 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -754,10 +754,6 @@ void MickeyEngine::drawPic(int iPic) {
}
void MickeyEngine::drawRoomAnimation() {
- uint8 objLight[] = {
- 0xF0, 1, 0xF9, 2, 43, 45, 0xFF
- };
-
switch (_gameStateMickey.iRoom) {
case IDI_MSA_PIC_EARTH_SHIP:
case IDI_MSA_PIC_VENUS_SHIP:
@@ -777,22 +773,27 @@ void MickeyEngine::drawRoomAnimation() {
case IDI_MSA_PIC_SHIP_MARS:
case IDI_MSA_PIC_SHIP_URANUS: {
// draw blinking ship lights
-
- _picture->setPattern(2, 0);
+ uint8 lightPicture[] = {
+ 0xF0, 1, // Set Color: 1
+ 0xF9, 2, 43, 45, // Set Pattern: 2, plot at 43,45
+ 0xFF // End
+ };
for (int i = 0; i < 12; i++) {
uint8 iColor = _gameStateMickey.nFrame + i;
if (iColor > 15)
iColor -= 15;
- objLight[1] = iColor;
- objLight[4] += 7;
+ // FIXME: this is not the correct animation pattern.
+ // the lights do not simply advance in a sequence from
+ // left to right in the original, they do something else.
+ lightPicture[1] = iColor; // change light color
+ lightPicture[4] += 7; // increase x coordinate
- _picture->setPictureData(objLight);
_picture->setPictureFlags(kPicFCircle);
_picture->setMaxStep(0);
_picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
- _picture->drawPicture();
+ _picture->decodePictureFromBuffer(lightPicture, sizeof(lightPicture), false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
_picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
Commit: 0c9fea96a655e635dd8db2da2d3a03b3fb5a6982
https://github.com/scummvm/scummvm/commit/0c9fea96a655e635dd8db2da2d3a03b3fb5a6982
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:50-07:00
Commit Message:
AGI: Remove PictureMgr::setPictureData(), used by TROLL
All preagi games now use PictureMgr::decodePictureFromBuffer()
- Removes PictureMgr::setPictureData()
- Removes PictureMgr::setDimensions()
- PictureMgr::drawPicture() is no longer public
- Troll now uses correct dimensions when showing pictures
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index e7f42c7fef5..05bb6d367a1 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -1002,12 +1002,4 @@ void PictureMgr::setPictureVersion(AgiPictureVersion version) {
_minCommand = 0xf0;
}
-void PictureMgr::setPictureData(uint8 *data, int len) {
- _data = data;
- _dataSize = len;
- _dataOffset = 0;
- _dataOffsetNibble = false;
- _flags = 0;
-}
-
} // End of namespace Agi
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 02ea5e3291c..e82eba66a86 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -87,8 +87,9 @@ public:
void decodePicture(int16 resourceNr, bool clearScreen, bool agi256 = false, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void unloadPicture(int picNr);
- void drawPicture();
+
private:
+ void drawPicture();
void drawPictureC64();
void drawPictureV1();
void drawPictureV15();
@@ -114,7 +115,6 @@ public:
void showPicWithTransition();
void setPictureVersion(AgiPictureVersion version);
- void setPictureData(uint8 *data, int len = 4096);
void setPictureFlags(int flags) { _flags = flags; }
@@ -125,11 +125,6 @@ public:
_yOffset = offY;
}
- void setDimensions(int w, int h) {
- _width = w;
- _height = h;
- }
-
void setMaxStep(int maxStep) { _maxStep = maxStep; }
int getMaxStep() const { return _maxStep; }
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index c41835935cf..29100d4eae4 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -129,32 +129,27 @@ bool TrollEngine::getMenuSel(const char *szMenu, int *iSel, int nSel) {
// Graphics
void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
- _picture->setDimensions(IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
-
if (clr) {
clearScreen(0x0f, false);
- _picture->clear();
}
- _picture->setPictureData(_gameData + IDO_TRO_FRAMEPIC);
- _picture->drawPicture();
-
- _picture->setPictureData(_gameData + _pictureOffsets[iPic]);
-
- int addFlag = 0;
-
- if (troll)
- addFlag = kPicFTrollMode;
+ // draw the frame picture
+ _picture->decodePictureFromBuffer(_gameData + IDO_TRO_FRAMEPIC, 4096, clr, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
+ // draw the picture
+ int flags = 0;
if (f3IsCont) {
- _picture->setPictureFlags(kPicFf3Cont | addFlag);
+ flags |= kPicFf3Cont;
} else {
- _picture->setPictureFlags(kPicFf3Stop | addFlag);
+ flags |= kPicFf3Stop;
}
+ if (troll) {
+ flags |= kPicFTrollMode;
+ }
+ _picture->setPictureFlags(flags);
+ _picture->decodePictureFromBuffer(_gameData + _pictureOffsets[iPic], 4096, false, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
- _picture->drawPicture();
-
- _picture->showPic(); // TODO: *HAVE* to add coordinates + height/width!!
+ _picture->showPic(0, 0, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
_system->updateScreen();
}
@@ -426,7 +421,6 @@ int TrollEngine::drawRoom(char *menu) {
bool contFlag = false;
if (_currentRoom == 1) {
- _picture->setDimensions(IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
clearScreen(0x00, false);
_picture->clear();
} else {
Commit: 6196f183177501dbaafdbda9688e8022f263472b
https://github.com/scummvm/scummvm/commit/6196f183177501dbaafdbda9688e8022f263472b
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:50-07:00
Commit Message:
AGI: Remove AgiPictureVersion::AGIPIC_256
Unreachable code; this is not how AGI256 pics are drawn
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 05bb6d367a1..f488120622c 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -339,9 +339,6 @@ void PictureMgr::drawPicture() {
case AGIPIC_V2:
drawPictureV2();
break;
- case AGIPIC_256:
- drawPictureAGI256();
- break;
default:
break;
}
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index e82eba66a86..cd7ecbc721c 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -47,8 +47,7 @@ enum AgiPictureVersion {
AGIPIC_C64,
AGIPIC_V1,
AGIPIC_V15,
- AGIPIC_V2,
- AGIPIC_256
+ AGIPIC_V2
};
enum AgiPictureFlags {
Commit: 1ad5fdbecbf231daff5ab60a05b623724fa37cb6
https://github.com/scummvm/scummvm/commit/1ad5fdbecbf231daff5ab60a05b623724fa37cb6
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:50-07:00
Commit Message:
AGI: Remove AgiPictureFlags::kPicFf3Cont
It's unused, because it's the opposite of kPicFf3Stop
Changed paths:
engines/agi/picture.h
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index cd7ecbc721c..fd1f0bd6771 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -54,8 +54,7 @@ enum AgiPictureFlags {
kPicFNone = (1 << 0),
kPicFCircle = (1 << 1),
kPicFf3Stop = (1 << 2),
- kPicFf3Cont = (1 << 3),
- kPicFTrollMode = (1 << 4)
+ kPicFTrollMode = (1 << 3)
};
class AgiBase;
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 29100d4eae4..35a05ce84ca 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -138,9 +138,8 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
// draw the picture
int flags = 0;
- if (f3IsCont) {
- flags |= kPicFf3Cont;
- } else {
+ if (!f3IsCont) {
+ // stop on opcode F3
flags |= kPicFf3Stop;
}
if (troll) {
Commit: 4fe7ab46160231ca50dccfb3397955f6e965fc0d
https://github.com/scummvm/scummvm/commit/4fe7ab46160231ca50dccfb3397955f6e965fc0d
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:51-07:00
Commit Message:
AGI: Move PictureMgr::unloadPicture() to AgiEngine
Changed paths:
engines/agi/agi.cpp
engines/agi/agi.h
engines/agi/picture.cpp
engines/agi/picture.h
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 552193a69d1..e902a2b1b64 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -305,7 +305,7 @@ void AgiEngine::unloadResource(int16 resourceType, int16 resourceNr) {
unloadLogic(resourceNr);
break;
case RESOURCETYPE_PICTURE:
- _picture->unloadPicture(resourceNr);
+ unloadPicture(resourceNr);
break;
case RESOURCETYPE_VIEW:
unloadView(resourceNr);
@@ -318,6 +318,14 @@ void AgiEngine::unloadResource(int16 resourceType, int16 resourceNr) {
}
}
+void AgiEngine::unloadPicture(int16 picNr) {
+ if (_game.dirPic[picNr].flags & RES_LOADED) {
+ free(_game.pictures[picNr].rdata);
+ _game.pictures[picNr].rdata = nullptr;
+ _game.dirPic[picNr].flags &= ~RES_LOADED;
+ }
+}
+
struct GameSettings {
const char *gameid;
const char *description;
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index b8d18521251..42bb3d6bf25 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -991,6 +991,10 @@ public:
bool testController(uint8 cont);
bool testCompareStrings(uint8 s1, uint8 s2);
+ // Picture
+private:
+ void unloadPicture(int16 picNr);
+
// View
private:
void updateView(ScreenObjEntry *screenObj);
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index f488120622c..c4af4ddc218 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -913,21 +913,6 @@ void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearSc
drawPicture(); // Draw 16 color picture.
}
-/**
- * Unload an AGI picture resource.
- * This function unloads an AGI picture resource and deallocates
- * resource data.
- * @param picNr AGI picture resource number
- */
-void PictureMgr::unloadPicture(int picNr) {
- // remove visual buffer & priority buffer if they exist
- if (_vm->_game.dirPic[picNr].flags & RES_LOADED) {
- free(_vm->_game.pictures[picNr].rdata);
- _vm->_game.pictures[picNr].rdata = nullptr;
- _vm->_game.dirPic[picNr].flags &= ~RES_LOADED;
- }
-}
-
void PictureMgr::clear() {
_gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
}
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index fd1f0bd6771..7fb74d0894d 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -84,7 +84,6 @@ public:
void decodePicture(int16 resourceNr, bool clearScreen, bool agi256 = false, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
- void unloadPicture(int picNr);
private:
void drawPicture();
Commit: aa9a1bb5b8705c1e252ea7e98d513c05c66998e2
https://github.com/scummvm/scummvm/commit/aa9a1bb5b8705c1e252ea7e98d513c05c66998e2
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:51-07:00
Commit Message:
AGI: Remove PictureMgr::clear(), used by TROLL
Had no effect
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index c4af4ddc218..b920a434f13 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -907,16 +907,12 @@ void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearSc
_height = height;
if (clearScreen) {
- clear();
+ _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
}
drawPicture(); // Draw 16 color picture.
}
-void PictureMgr::clear() {
- _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
-}
-
void PictureMgr::showPic() {
debugC(8, kDebugLevelMain, "Show picture!");
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 7fb74d0894d..6fe59da7378 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -115,8 +115,6 @@ public:
void setPictureFlags(int flags) { _flags = flags; }
- void clear();
-
void setOffset(int offX, int offY) {
_xOffset = offX;
_yOffset = offY;
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 35a05ce84ca..bb225e22d7d 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -134,6 +134,7 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
}
// draw the frame picture
+ _picture->setPictureFlags(kPicFNone);
_picture->decodePictureFromBuffer(_gameData + IDO_TRO_FRAMEPIC, 4096, clr, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
// draw the picture
@@ -421,7 +422,6 @@ int TrollEngine::drawRoom(char *menu) {
if (_currentRoom == 1) {
clearScreen(0x00, false);
- _picture->clear();
} else {
if (_currentRoom != 42) {
Commit: ede71263fbda0209a08c2a03b3017223fda66b4e
https://github.com/scummvm/scummvm/commit/ede71263fbda0209a08c2a03b3017223fda66b4e
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:51-07:00
Commit Message:
AGI: Cleanup PictureMgr
Changed paths:
engines/agi/agi.cpp
engines/agi/op_cmd.cpp
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/mickey.cpp
engines/agi/preagi/troll.cpp
engines/agi/preagi/winnie.cpp
engines/agi/saveload.cpp
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index e902a2b1b64..17cc4ccbdc9 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -544,7 +544,7 @@ void AgiEngine::redrawScreen() {
_gfx->setPalette(true); // set graphics mode palette
_text->charAttrib_Set(_text->_textAttrib.foreground, _text->_textAttrib.background);
_gfx->clearDisplay(0);
- _picture->showPic();
+ _picture->showPicture();
_text->statusDraw();
_text->promptRedraw();
}
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 10036406a9e..93147282c80 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -1185,7 +1185,7 @@ void cmdShowPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
vm->setFlag(VM_FLAG_OUTPUT_MODE, false);
vm->_text->closeWindow();
- vm->_picture->showPicWithTransition();
+ vm->_picture->showPictureWithTransition();
state->pictureShown = true;
debugC(6, kDebugLevelScripts, "--- end of show pic ---");
@@ -2310,6 +2310,8 @@ void cmdAgi256LoadPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
vm->loadResource(RESOURCETYPE_PICTURE, resourceNr);
// Draw the picture. Similar to void cmdDrawPic.
+ // Must not clear the screen; AGI256 uses the priority
+ // screen from the previously drawn picture.
vm->_picture->decodePicture(resourceNr, false, true);
spritesMgr->drawAllSpriteLists();
state->pictureShown = false;
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index b920a434f13..1cfcce9109c 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -56,14 +56,13 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
}
void PictureMgr::putVirtPixel(int x, int y) {
- byte drawMask = 0;
-
if (x < 0 || y < 0 || x >= _width || y >= _height)
return;
x += _xOffset;
y += _yOffset;
+ byte drawMask;
if (_priOn)
drawMask |= GFX_SCREEN_MASK_PRIORITY;
if (_scrOn)
@@ -319,6 +318,8 @@ void PictureMgr::plotBrush() {
** Draw AGI picture
**************************************************************************/
void PictureMgr::drawPicture() {
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
_patCode = 0;
_patNum = 0;
_priOn = false;
@@ -345,9 +346,9 @@ void PictureMgr::drawPicture() {
}
void PictureMgr::drawPictureC64() {
- debugC(8, kDebugLevelMain, "Drawing C64 picture");
+ debugC(8, kDebugLevelMain, "Drawing Apple II / C64 / CoCo picture");
- _scrColor = 0x0;
+ _scrColor = 0;
while (_dataOffset < _dataSize) {
byte curByte = getNextByte();
@@ -476,14 +477,10 @@ void PictureMgr::drawPictureV15() {
}
void PictureMgr::drawPictureV2() {
- bool nibbleMode = false;
-
debugC(8, kDebugLevelMain, "Drawing V2/V3 picture");
- if (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) {
- // check, if this resource uses nibble mode (0xF0 + 0xF2 commands take nibbles instead of bytes)
- nibbleMode = true;
- }
+ // AGIv3 nibble parameters are indicated by a flag in the picture's directory entry
+ bool nibbleMode = (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) != 0;
int step = 0;
while (_dataOffset < _dataSize) {
@@ -842,7 +839,7 @@ bool PictureMgr::draw_FillCheck(int16 x, int16 y) {
}
/**
- * Decode an AGI picture resource.
+ * Decode an AGI picture resource. Used by regular AGI games.
* This function decodes an AGI picture resource into the correct slot
* and draws it on the AGI screen, optionally clearing the screen before
* drawing.
@@ -851,27 +848,18 @@ bool PictureMgr::draw_FillCheck(int16 x, int16 y) {
* @param agi256 load an AGI256 picture resource
*/
void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256, int16 width, int16 height) {
- _patCode = 0;
- _patNum = 0;
- _priOn = _scrOn = false;
- _scrColor = 0xF;
- _priColor = 0x4;
-
_resourceNr = resourceNr;
_data = _vm->_game.pictures[resourceNr].rdata;
_dataSize = _vm->_game.dirPic[resourceNr].len;
- _dataOffset = 0;
- _dataOffsetNibble = false;
-
_width = width;
_height = height;
- if (clearScreen && !agi256) { // 256 color pictures should always fill the whole screen, so no clearing for them.
- _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
+ if (clearScreen) {
+ _gfx->clear(15, 4); // white, priority 4
}
if (!agi256) {
- drawPicture(); // Draw 16 color picture.
+ drawPicture();
} else {
drawPictureAGI256();
}
@@ -883,7 +871,7 @@ void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256,
}
/**
- * Decode an AGI picture resource.
+ * Decode an AGI picture resource. Used by preAGI.
* This function decodes an AGI picture resource into the correct slot
* and draws it on the AGI screen, optionally clearing the screen before
* drawing.
@@ -892,51 +880,29 @@ void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256,
* @param clear clear AGI screen before drawing
*/
void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width, int16 height) {
- _patCode = 0;
- _patNum = 0;
- _priOn = _scrOn = false;
- _scrColor = 0xF;
- _priColor = 0x4;
-
_data = data;
_dataSize = length;
- _dataOffset = 0;
- _dataOffsetNibble = false;
-
_width = width;
_height = height;
if (clearScreen) {
- _gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white).
+ _gfx->clear(15, 4); // white, priority 4
}
- drawPicture(); // Draw 16 color picture.
+ drawPicture();
}
-void PictureMgr::showPic() {
- debugC(8, kDebugLevelMain, "Show picture!");
+void PictureMgr::showPicture(int16 x, int16 y, int16 width, int16 height) {
+ debugC(8, kDebugLevelMain, "Show picture");
- _gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT);
+ _gfx->render_Block(x, y, width, height);
}
-/**
- * Show AGI picture.
- * This function copies a ``hidden'' AGI picture to the output device.
- */
-void PictureMgr::showPic(int16 x, int16 y, int16 pic_width, int16 pic_height) {
- _width = pic_width;
- _height = pic_height;
-
- debugC(8, kDebugLevelMain, "Show picture!");
-
- _gfx->render_Block(x, y, pic_width, pic_height);
-}
-
-void PictureMgr::showPicWithTransition() {
+void PictureMgr::showPictureWithTransition() {
_width = SCRIPT_WIDTH;
_height = SCRIPT_HEIGHT;
- debugC(8, kDebugLevelMain, "Show picture!");
+ debugC(8, kDebugLevelMain, "Show picture");
if (!_vm->_game.automaticRestoreGame) {
// only do transitions when we are not restoring a saved game
@@ -955,7 +921,6 @@ void PictureMgr::showPicWithTransition() {
_gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT, false);
_gfx->transition_Amiga();
return;
- break;
case Common::kRenderAtariST:
// Platform Atari ST used a different transition, looks "high-res" (full 320x168)
_gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT, false);
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 6fe59da7378..46f0b2f7226 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -42,19 +42,18 @@ struct AgiPicture {
AgiPicture() { reset(); }
};
-// AGI picture version
enum AgiPictureVersion {
- AGIPIC_C64,
- AGIPIC_V1,
- AGIPIC_V15,
- AGIPIC_V2
+ AGIPIC_C64, // Winnie (Apple II, C64, CoCo)
+ AGIPIC_V1, // Currently unused
+ AGIPIC_V15, // Troll (DOS)
+ AGIPIC_V2 // AGIv2, AGIv3, Winnie (DOS, Amiga), Mickey (DOS)
};
enum AgiPictureFlags {
kPicFNone = (1 << 0),
- kPicFCircle = (1 << 1),
- kPicFf3Stop = (1 << 2),
- kPicFTrollMode = (1 << 3)
+ kPicFCircle = (1 << 1), // Mickey, spaceship lights (not drawn accurately)
+ kPicFf3Stop = (1 << 2), // Troll, certain pictures
+ kPicFTrollMode = (1 << 3) // Troll, drawing the Troll
};
class AgiBase;
@@ -70,6 +69,7 @@ public:
int16 getResourceNr() const { return _resourceNr; };
private:
+ void putVirtPixel(int x, int y);
void xCorner(bool skipOtherCoords = false);
void yCorner(bool skipOtherCoords = false);
void plotPattern(int x, int y);
@@ -80,8 +80,6 @@ private:
byte getNextNibble();
public:
- void putVirtPixel(int x, int y);
-
void decodePicture(int16 resourceNr, bool clearScreen, bool agi256 = false, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
@@ -107,9 +105,8 @@ private:
void draw_Fill();
public:
- void showPic(); // <-- for regular AGI games
- void showPic(int16 x, int16 y, int16 pic_width, int16 pic_height); // <-- for preAGI games
- void showPicWithTransition();
+ void showPicture(int16 x = 0, int16 y = 0, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
+ void showPictureWithTransition();
void setPictureVersion(AgiPictureVersion version);
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 804739c29a0..edd64aead8b 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -730,7 +730,7 @@ void MickeyEngine::drawObj(ENUM_MSA_OBJECT iObj, int x0, int y0) {
_picture->setMaxStep(maxStep);
_picture->setOffset(IDI_MSA_PIC_X0 + x0, IDI_MSA_PIC_Y0 + y0);
_picture->decodePictureFromBuffer(buffer, size, false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
- _picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+ _picture->showPicture(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
void MickeyEngine::drawPic(int iPic) {
@@ -750,7 +750,7 @@ void MickeyEngine::drawPic(int iPic) {
_picture->setMaxStep(0);
_picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
_picture->decodePictureFromBuffer(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
- _picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+ _picture->showPicture(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
void MickeyEngine::drawRoomAnimation() {
@@ -795,7 +795,7 @@ void MickeyEngine::drawRoomAnimation() {
_picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
_picture->decodePictureFromBuffer(lightPicture, sizeof(lightPicture), false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
}
- _picture->showPic(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
+ _picture->showPicture(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
_gameStateMickey.nFrame--;
if (_gameStateMickey.nFrame < 0)
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index bb225e22d7d..35d760e8bcc 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -149,7 +149,7 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
_picture->setPictureFlags(flags);
_picture->decodePictureFromBuffer(_gameData + _pictureOffsets[iPic], 4096, false, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
- _picture->showPic(0, 0, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
+ _picture->showPicture(0, 0, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
_system->updateScreen();
}
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 0019a074b85..7c92bf58a47 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -1209,7 +1209,7 @@ void WinnieEngine::drawPic(const char *szName) {
_picture->setOffset(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0);
_picture->decodePictureFromBuffer(buffer, size, true, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
- _picture->showPic(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+ _picture->showPicture(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
free(buffer);
}
@@ -1225,7 +1225,7 @@ void WinnieEngine::drawObjPic(int iObj, int x0, int y0) {
_picture->setOffset(x0, y0);
_picture->decodePictureFromBuffer(buffer + objhdr.ofsPic - _objOffset, objSize, false, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
- _picture->showPic(10, 0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+ _picture->showPicture(10, 0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
free(buffer);
}
@@ -1244,7 +1244,7 @@ void WinnieEngine::drawRoomPic() {
// draw room picture
_picture->setOffset(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0);
_picture->decodePictureFromBuffer(buffer + roomhdr.ofsPic - _roomOffset, 4096, true, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
- _picture->showPic(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
+ _picture->showPicture(IDI_WTP_PIC_X0, IDI_WTP_PIC_Y0, IDI_WTP_PIC_WIDTH, IDI_WTP_PIC_HEIGHT);
// draw object picture
drawObjPic(iObj, IDI_WTP_PIC_X0 + roomhdr.objX, IDI_WTP_PIC_Y0 + roomhdr.objY);
diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp
index 2d776256956..4c960c3b8fa 100644
--- a/engines/agi/saveload.cpp
+++ b/engines/agi/saveload.cpp
@@ -744,7 +744,7 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
_sprites->eraseSprites();
_sprites->buildAllSpriteLists();
_sprites->drawAllSpriteLists();
- _picture->showPicWithTransition();
+ _picture->showPictureWithTransition();
_game.pictureShown = true;
_text->statusDraw();
_text->promptRedraw();
Commit: 062845110b8414d1cd84d2e8935eb4575902b7d1
https://github.com/scummvm/scummvm/commit/062845110b8414d1cd84d2e8935eb4575902b7d1
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:51-07:00
Commit Message:
AGI: Update PictureMgr
Changed paths:
engines/agi/picture.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 1cfcce9109c..9e0eb7d528d 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -62,7 +62,7 @@ void PictureMgr::putVirtPixel(int x, int y) {
x += _xOffset;
y += _yOffset;
- byte drawMask;
+ byte drawMask= 0;
if (_priOn)
drawMask |= GFX_SCREEN_MASK_PRIORITY;
if (_scrOn)
@@ -534,6 +534,9 @@ void PictureMgr::drawPictureV2() {
case 0xfa:
plotBrush();
break;
+ // FIXME: There is no opcode FC. A refactor in 2016 moved it to this
+ // function and removed the comment that it was for V1 or V1.5.
+ // Determine where this should go (if anywhere) before removing.
case 0xfc:
draw_SetColor();
draw_SetPriority();
Commit: 4424f8b5863454ffd3a1354943fe4380cdf931b8
https://github.com/scummvm/scummvm/commit/4424f8b5863454ffd3a1354943fe4380cdf931b8
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:52-07:00
Commit Message:
AGI: PREAGI: Clear screen after MICKEY logo
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index edd64aead8b..49d5ad9bc15 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -890,7 +890,7 @@ static const byte BCGColorMappingCGAToEGA[4] = {
0, 11, 13, 15
};
-void MickeyEngine::drawLogo() {
+bool MickeyEngine::drawLogo() {
const int width = 80;
const int height = 85 * 2;
const byte *BCGColorMapping = BCGColorMappingCGAToEGA;
@@ -901,16 +901,18 @@ void MickeyEngine::drawLogo() {
// read logos.bcg
Common::File infile;
- if (!infile.open(IDS_MSA_PATH_LOGO))
- return;
+ if (!infile.open(IDS_MSA_PATH_LOGO)) {
+ warning("%s: file not found", IDS_MSA_PATH_LOGO);
+ return false;
+ }
uint32 fileBufferSize = infile.size();
+ if (fileBufferSize < (width * height / 4)) {
+ warning("%s: truncated file: %d", IDS_MSA_PATH_LOGO, fileBufferSize);
+ return false;
+ }
byte *fileBuffer = new byte[fileBufferSize];
infile.read(fileBuffer, fileBufferSize);
- infile.close();
-
- if (fileBufferSize < (width * height / 4))
- error("logos.bcg: unexpected end of file");
// Show BCG picture
// It's basically uncompressed CGA 4-color data (4 pixels per byte)
@@ -934,6 +936,7 @@ void MickeyEngine::drawLogo() {
_gfx->copyDisplayToScreen();
delete[] fileBuffer;
+ return true;
}
void MickeyEngine::animate() {
@@ -1362,8 +1365,10 @@ void MickeyEngine::inventory() {
void MickeyEngine::intro() {
// Draw Sierra logo
- drawLogo(); // Original does not even show this, so we skip it too
- waitAnyKey(); // Not in the original, but needed so that the logo is visible
+ if (drawLogo()) { // Original does not show the logo, we do if available
+ waitAnyKey(); // Not in the original, but needed so that the logo is visible
+ _gfx->clearDisplay(0); // Logo is larger than picture area, clear entire screen
+ }
// draw title picture
_gameStateMickey.iRoom = IDI_MSA_PIC_TITLE;
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index f4e881a510b..906a42c6b11 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -721,7 +721,7 @@ protected:
void playSound(ENUM_MSA_SOUND);
void drawRoomAnimation();
void drawRoom();
- void drawLogo();
+ bool drawLogo();
void animate();
void printRoomDesc();
bool loadGame();
Commit: 6a3dbc425605b648e175a94ed4f7fa423b934a88
https://github.com/scummvm/scummvm/commit/6a3dbc425605b648e175a94ed4f7fa423b934a88
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:52-07:00
Commit Message:
AGI: PREAGI: Fix MICKEY intro flash on CGA and Hercules
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/preagi.cpp
engines/agi/preagi/preagi.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 49d5ad9bc15..6cbf34ad543 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -1414,7 +1414,7 @@ void MickeyEngine::intro() {
playSound(IDI_MSA_SND_PRESS_BLUE);
//Set screen to white
- _gfx->clearDisplay(15);
+ _gfx->clearDisplay(getWhite());
_gfx->updateScreen();
_system->delayMillis(IDI_MSA_ANIM_DELAY);
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
index 688cf349d11..49778448320 100644
--- a/engines/agi/preagi/preagi.cpp
+++ b/engines/agi/preagi/preagi.cpp
@@ -99,6 +99,18 @@ void PreAgiEngine::clearGfxScreen(int attr) {
_gfx->drawDisplayRect(0, 0, DISPLAY_DEFAULT_WIDTH - 1, IDI_MAX_ROW_PIC * 8 - 1, (attr & 0xF0) / 0x10);
}
+byte PreAgiEngine::getWhite() const {
+ switch (_renderMode) {
+ case Common::kRenderCGA:
+ return 3;
+ case Common::kRenderHercA:
+ case Common::kRenderHercG:
+ return 1;
+ default:
+ return 15;
+ }
+}
+
// String functions
void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) {
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
index 29aaa9a14fb..bf2653bf6d9 100644
--- a/engines/agi/preagi/preagi.h
+++ b/engines/agi/preagi/preagi.h
@@ -82,6 +82,7 @@ protected:
void clearScreen(int attr, bool overrideDefault = true);
void clearGfxScreen(int attr);
void setDefaultTextColor(int attr) { _defaultColor = attr; }
+ byte getWhite() const;
// Keyboard
int getSelection(SelectionTypes type);
Commit: c7807db2e0740823985cf86d5b7706cd72762a2f
https://github.com/scummvm/scummvm/commit/c7807db2e0740823985cf86d5b7706cd72762a2f
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:52-07:00
Commit Message:
AGI: PREAGI: Improve event handling during sounds
Games can now control event processing and interrupt behavior
during sounds and waits.
Fixes Mickey unresponsiveness during sounds and introduction.
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
engines/agi/preagi/preagi.cpp
engines/agi/preagi/preagi.h
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 6cbf34ad543..118a6847e91 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -635,18 +635,19 @@ void MickeyEngine::printDatMessage(int iStr) {
// Sound
-void MickeyEngine::playNote(MSA_SND_NOTE note) {
- if (!note.counter) {
- // Pause
- _system->delayMillis((uint)(note.length / IDI_SND_TIMER_RESOLUTION));
- } else {
- playSpeakerNote(IDI_SND_OSCILLATOR_FREQUENCY / note.counter, (int32)(note.length / IDI_SND_TIMER_RESOLUTION));
+bool MickeyEngine::playNote(MSA_SND_NOTE note, WaitOptions options) {
+ int16 frequency = 0;
+ if (note.counter != 0) {
+ frequency = IDI_SND_OSCILLATOR_FREQUENCY / note.counter;
}
+ int32 lengthMs = (int32)(note.length / IDI_SND_TIMER_RESOLUTION);
+ return playSpeakerNote(frequency, lengthMs, options);
}
-void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) {
+bool MickeyEngine::playSound(ENUM_MSA_SOUND iSound, WaitOptions options) {
+ bool completed = true;
if (!getFlag(VM_FLAG_SOUND_ON))
- return;
+ return completed;
Common::Event event;
MSA_SND_NOTE note;
@@ -658,7 +659,10 @@ void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) {
for (int iNote = 0; iNote < 6; iNote++) {
note.counter = rnd(59600) + 59;
note.length = 4;
- playNote(note);
+ if (!playNote(note, options)) {
+ completed = false;
+ break;
+ }
}
break;
default:
@@ -669,36 +673,19 @@ void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) {
if (!note.counter && !note.length)
break;
- playNote(note);
+ if (!playNote(note, options)) {
+ completed = false;
+ break;
+ }
pBuf += 3;
-
- if (iSound == IDI_MSA_SND_THEME) {
- while (_system->getEventManager()->pollEvent(event)) {
- switch (event.type) {
- case Common::EVENT_KEYDOWN:
- // don't interrupt if a modifier is pressed
- if (event.kbd.flags & Common::KBD_NON_STICKY) {
- continue;
- }
- // fall through
- case Common::EVENT_RETURN_TO_LAUNCHER:
- case Common::EVENT_QUIT:
- case Common::EVENT_LBUTTONUP:
- case Common::EVENT_RBUTTONUP:
- delete[] buffer;
- return;
- default:
- break;
- }
- }
- }
}
break;
}
delete[] buffer;
+ return completed;
}
// Graphics
@@ -801,7 +788,11 @@ void MickeyEngine::drawRoomAnimation() {
if (_gameStateMickey.nFrame < 0)
_gameStateMickey.nFrame = 15;
- playSound(IDI_MSA_SND_PRESS_BLUE);
+ // play the spaceship beep but don't process events during playback.
+ // this sound plays during menu usage, so events must not be consumed
+ // while waiting or else inputs will be dropped. playing this sound
+ // does create an input lag, but that is what happened in the original.
+ playSound(IDI_MSA_SND_PRESS_BLUE, kWaitBlock);
}
break;
@@ -1374,14 +1365,15 @@ void MickeyEngine::intro() {
_gameStateMickey.iRoom = IDI_MSA_PIC_TITLE;
drawRoom();
- // show copyright and play theme
+ // show copyright
printExeMsg(IDO_MSA_COPYRIGHT);
// Quit if necessary
if (shouldQuit())
return;
- playSound(IDI_MSA_SND_THEME);
+ // play theme
+ playSound(IDI_MSA_SND_THEME, kWaitAllowInterrupt);
// load game
_gameStateMickey.fIntro = true;
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index 906a42c6b11..13ac1c9ef1a 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -717,8 +717,8 @@ protected:
void patchMenu(MSA_MENU *);
void printDatString(int);
void printDatMessage(int);
- void playNote(MSA_SND_NOTE);
- void playSound(ENUM_MSA_SOUND);
+ bool playNote(MSA_SND_NOTE note, WaitOptions options);
+ bool playSound(ENUM_MSA_SOUND iSound, WaitOptions options = kWaitProcessEvents);
void drawRoomAnimation();
void drawRoom();
bool drawLogo();
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
index 49778448320..19cd455e204 100644
--- a/engines/agi/preagi/preagi.cpp
+++ b/engines/agi/preagi/preagi.cpp
@@ -287,32 +287,72 @@ int PreAgiEngine::getSelection(SelectionTypes type) {
return 0;
}
-void PreAgiEngine::playSpeakerNote(int16 frequency, int32 length) {
- _speakerStream->play(Audio::PCSpeaker::kWaveFormSquare, frequency, length);
+bool PreAgiEngine::playSpeakerNote(int16 frequency, int32 length, WaitOptions options) {
+ // play note, unless this is a pause
+ if (frequency != 0) {
+ _speakerStream->play(Audio::PCSpeaker::kWaveFormSquare, frequency, length);
+ }
- uint32 startTime = _system->getMillis();
- while (_system->getMillis() - startTime < (uint32)length) {
- _system->updateScreen();
- _system->delayMillis(10);
+ // wait for note length
+ bool completed = wait(length, options);
+
+ // stop note if the wait was interrupted
+ if (!completed) {
+ if (frequency != 0) {
+ _speakerStream->stop();
+ }
}
+
+ return completed;
}
-void PreAgiEngine::wait(uint32 delay) {
+// A wait function that updates the screen, optionally allows events to be
+// processed, and optionally allows keyboard and mouse events to interrupt
+// the wait. Processing events keeps the program window responsive, but for
+// very short delays it may be better to not process events so that they
+// are buffered and not lost.
+bool PreAgiEngine::wait(uint32 delay, WaitOptions options) {
Common::Event event;
uint32 startTime = _system->getMillis();
+ bool processEvents = (options & kWaitProcessEvents) != 0;
+ bool allowInterrupt = (options == kWaitAllowInterrupt);
+
while (!shouldQuit()) {
// process events
- while (_eventMan->pollEvent(event)) {
+ if (processEvents) {
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ // don't interrupt if a modifier is pressed
+ if (event.kbd.flags & Common::KBD_NON_STICKY) {
+ continue;
+ }
+ // fall through
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONUP:
+ if (!allowInterrupt) {
+ continue;
+ }
+ // fall through
+ case Common::EVENT_RETURN_TO_LAUNCHER:
+ case Common::EVENT_QUIT:
+ return false; // interrupted by quit or input
+ default:
+ break;
+ }
+ }
}
if (_system->getMillis() - startTime >= delay) {
- return;
+ return true; // delay completed
}
_system->updateScreen();
_system->delayMillis(10);
}
+
+ return false; // interrupted by quit
}
} // End of namespace Agi
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
index bf2653bf6d9..e30ed3ee1d6 100644
--- a/engines/agi/preagi/preagi.h
+++ b/engines/agi/preagi/preagi.h
@@ -50,6 +50,13 @@ enum SelectionTypes {
kSelBackspace
};
+// Options for controlling behavior during waits and sound playback
+enum WaitOptions {
+ kWaitBlock = 0x00, // no event processing, cannot be interrupted
+ kWaitProcessEvents = 0x01, // process events, stops on quit
+ kWaitAllowInterrupt = 0x03 // process events, stops on input or quit
+};
+
class PreAgiEngine : public AgiBase {
int _gameId;
@@ -101,8 +108,8 @@ protected:
// Saved Games
Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
- void playSpeakerNote(int16 frequency, int32 length);
- void wait(uint32 delay);
+ bool playSpeakerNote(int16 frequency, int32 length, WaitOptions options);
+ bool wait(uint32 delay, WaitOptions options = kWaitProcessEvents);
private:
int _defaultColor;
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 35d760e8bcc..3741fc21053 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -472,7 +472,10 @@ void TrollEngine::playTune(int tune, int len) {
int duration = READ_LE_UINT16(_gameData + ptr);
ptr += 2;
- playSpeakerNote(freq, duration);
+ // Play note without processing events.
+ // The sounds are so short in this game that we don't
+ // need to process events while playing notes.
+ playSpeakerNote(freq, duration, kWaitBlock);
}
}
Commit: 77129d68524515ab7bcbecd20b9a548fa92b7580
https://github.com/scummvm/scummvm/commit/77129d68524515ab7bcbecd20b9a548fa92b7580
Author: D G Turner (digitall at scummvm.org)
Date: 2025-04-29T10:26:52-07:00
Commit Message:
SCI: Fix GCC Compiler Shadowing Warnings
Changed paths:
engines/sci/graphics/paint16.cpp
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index 266d8d9c1ac..df7062f37db 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -56,8 +56,8 @@ GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *ca
// The original KQ6WinCD interpreter saves the hires drawing information in a linked list. This is used to redraw the hires cels after disposing a window.
struct HiresDrawData {
- HiresDrawData(HiresDrawData *chain, reg_t hiresHandle, GuiResourceId id, int16 loop, int16 cel, uint16 left, uint16 top, uint16 pal, byte prio, bool needsWorkaround)
- : handle(hiresHandle), viewId(id), lpNo(loop), celNo(cel), leftPos(left), topPos(top), palNo(pal), prio(prio), waFlag(needsWorkaround), prev(nullptr), next(chain) {
+ HiresDrawData(HiresDrawData *chain, reg_t hiresHandle, GuiResourceId id, int16 loop, int16 cel, uint16 left, uint16 top, uint16 pal, byte priority, bool needsWorkaround)
+ : handle(hiresHandle), viewId(id), lpNo(loop), celNo(cel), leftPos(left), topPos(top), palNo(pal), prio(priority), waFlag(needsWorkaround), prev(nullptr), next(chain) {
if (chain)
chain->prev = this;
}
Commit: a88e1b25c2967d76dfeee33c1fb8759ba14534dd
https://github.com/scummvm/scummvm/commit/a88e1b25c2967d76dfeee33c1fb8759ba14534dd
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:52-07:00
Commit Message:
SCI: Add KQ5, SQ4 360k disk detection entries
Thanks to @ns394 for reporting
Changed paths:
engines/sci/detection_tables.h
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index c199a96890d..66041ca8116 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -2049,7 +2049,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
- // King's Quest V DOS 0.000.062 EGA (5 x 5.25" disks)
+ // King's Quest 5 DOS 0.000.062 EGA (5 x 5.25" disks)
// Supplied by ssburnout in bug report #5254
{"kq5", "EGA", {
{"resource.map", 0, "ef4fdc72ca7aef62054e8b075d7960d8", 7596},
@@ -2061,6 +2061,25 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
+ // King's Quest 5 DOS 0.000.062 EGA (12 x 5.25" 360k disks)
+ // Supplied by ns394 in bug report #15571
+ {"kq5", "EGA", {
+ {"resource.map", 0, "c6c167ee097517f10eb5825678a4d9e0", 6876},
+ {"resource.000", 0, "281c51f7ebbaf9d6507ef3442165069e", 180936},
+ {"resource.001", 0, "0087ecc427b29c9d6e97215a1c401403", 351165},
+ {"resource.002", 0, "446a416628584d0e0ecb5fd999fec26b", 355432},
+ {"resource.003", 0, "b8caa1a0ebd9533a7bdef8b1777acd48", 355008},
+ {"resource.004", 0, "121e271ef5e9a453a14f084171e2829c", 355459},
+ {"resource.005", 0, "ea0a562146887a98be994a2ed6199e90", 308649},
+ {"resource.006", 0, "9f17cf9064908491e611befae4334460", 352973},
+ {"resource.007", 0, "877143d23a5e01bb68942deabed99a2b", 308216},
+ {"resource.008", 0, "5f7c8b08393546048305f3eccf09e971", 272561},
+ {"resource.009", 0, "a0aad5edec946254bb33ed18ea84886c", 286519},
+ {"resource.010", 0, "caa65f21e62c82d994a617e1e1058f9a", 302497},
+ {"resource.011", 0, "c21a052c1a64c3e470705ac9a7f490d6", 329490},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
+
// King's Quest 5 DOS Spanish Floppy EGA
// Game version 0.000.133 from about box, 1.000 from VERSION file
// SCI interpreter version 1.000.575
@@ -5907,7 +5926,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
- // Space Quest IV DOS 1.060 EGA (6 x 3.5" disks)
+ // Space Quest 4 DOS 1.060 EGA (6 x 3.5" disks)
// Supplied by ssburnout in bug report #5255
{"sq4", "EGA", {
{"resource.map", 0, "4f59814d23a3721f251140fdcfebe35d", 5556},
@@ -5920,6 +5939,23 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
+ // Space Quest 4 DOS 1.065 EGA (10 x 5.25" 360k disks)
+ // Supplied by ns394 in bug report #15572
+ {"sq4", "EGA", {
+ {"resource.map", 0, "b0d425ab4fce54ec238b64c19ba3851e", 5148},
+ {"resource.000", 0, "419bdd9ad892755a9e684fd763529d78", 197195},
+ {"resource.001", 0, "30501118992807731d0076c1f8e0d994", 357345},
+ {"resource.002", 0, "258dff2f8aaf54017e99f06b63cc5c66", 354516},
+ {"resource.003", 0, "bcc897eb283728974a3227c390d2f1e8", 357792},
+ {"resource.004", 0, "b2dcebcb1cf15dfa93a98a1642761e65", 358648},
+ {"resource.005", 0, "d24bcd1250f5eff93e9cf525f53b9ec2", 355496},
+ {"resource.006", 0, "b9a9ed4d1efd2efd8e7c4653d85da472", 354625},
+ {"resource.007", 0, "4eb28ffa51bfde6eac6324771831ac9d", 360330},
+ {"resource.008", 0, "f238547863b3a69fcfc37571c5af432c", 356975},
+ {"resource.009", 0, "c47651a70c7209e5bac8ea726a47f6fc", 88103},
+ AD_LISTEND},
+ Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
+
// Space Quest 4 - German DOS (from Tobis87, also includes english language)
// SCI interpreter version 1.000.200 (just a guess)
{"sq4", "", {
Commit: 10282bbacaaa0bdf360f7dfbdbcfe7e003d4ebab
https://github.com/scummvm/scummvm/commit/10282bbacaaa0bdf360f7dfbdbcfe7e003d4ebab
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:53-07:00
Commit Message:
SCI: Update KQ5 detection entry description
Changed paths:
engines/sci/detection_tables.h
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 66041ca8116..cfedd2dab9e 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -2061,7 +2061,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
- // King's Quest 5 DOS 0.000.062 EGA (12 x 5.25" 360k disks)
+ // King's Quest 5 DOS 0.000.062 EGA (15 x 5.25" 360k disks)
// Supplied by ns394 in bug report #15571
{"kq5", "EGA", {
{"resource.map", 0, "c6c167ee097517f10eb5825678a4d9e0", 6876},
@@ -2077,6 +2077,9 @@ static const struct ADGameDescription SciGameDescriptions[] = {
{"resource.009", 0, "a0aad5edec946254bb33ed18ea84886c", 286519},
{"resource.010", 0, "caa65f21e62c82d994a617e1e1058f9a", 302497},
{"resource.011", 0, "c21a052c1a64c3e470705ac9a7f490d6", 329490},
+ //{"resource.012", 0, "1d3b7540f8b93b6e6b70c69c539202ba", 324620},
+ //{"resource.013", 0, "178fa65916d63415e46e610f86b0ba8b", 350464},
+ //{"resource.014", 0, "36a031cadaa9e2ad110157c8b6de9f4d", 319184},
AD_LISTEND},
Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, GUIO_STD16 },
Commit: 7ba60b09ec20c6f0b0c3d224f70f8efd5a6f5d80
https://github.com/scummvm/scummvm/commit/7ba60b09ec20c6f0b0c3d224f70f8efd5a6f5d80
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:53-07:00
Commit Message:
AGI: Move misplaced picture opcode handler
This is a V1 opcode that was moved to the wrong function in 2016.
See: 8a595e7771aa89d06876e13d7ab6751e26da8982
Confirmed in the TrollVM source that this code originated in.
With this understood, I am reverting my change to fix SQ1 Apple II.
Its pictures contain FC bytes that had no effect in the original because
they were correctly ignored. SQ1's FC bytes crashed this opcode handler,
so I added validation, because I didn't realize that the handler itself
was the mistake. See: 91916981cf9ab2e9f1f7fc9116bdd8f9b9c7bdd7
Changed paths:
engines/agi/picture.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 9e0eb7d528d..c8b019eacc5 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -379,7 +379,6 @@ void PictureMgr::drawPictureC64() {
_scrOn = true;
break;
case 0xe6: // plot brush
- // TODO: should this be getNextParamByte()?
_patCode = getNextByte();
plotBrush();
break;
@@ -420,6 +419,11 @@ void PictureMgr::drawPictureV1() {
case 0xfb:
draw_LineShort();
break;
+ case 0xfc:
+ draw_SetColor();
+ draw_SetPriority();
+ draw_Fill();
+ break;
case 0xff: // end of data
return;
default:
@@ -525,7 +529,6 @@ void PictureMgr::drawPictureV2() {
draw_Fill();
break;
case 0xf9:
- // TODO: should this be getNextParamByte()?
_patCode = getNextByte();
if (_vm->getGameType() == GType_PreAGI)
@@ -534,14 +537,6 @@ void PictureMgr::drawPictureV2() {
case 0xfa:
plotBrush();
break;
- // FIXME: There is no opcode FC. A refactor in 2016 moved it to this
- // function and removed the comment that it was for V1 or V1.5.
- // Determine where this should go (if anywhere) before removing.
- case 0xfc:
- draw_SetColor();
- draw_SetPriority();
- draw_Fill();
- break;
case 0xff: // end of data
return;
default:
@@ -597,8 +592,7 @@ void PictureMgr::drawPictureAGI256() {
}
void PictureMgr::draw_SetColor() {
- if (!getNextParamByte(_scrColor))
- return;
+ _scrColor = getNextByte();
// For CGA, replace the color with its mixture color
if (_vm->_renderMode == Common::kRenderCGA) {
@@ -607,7 +601,7 @@ void PictureMgr::draw_SetColor() {
}
void PictureMgr::draw_SetPriority() {
- getNextParamByte(_priColor);
+ _priColor = getNextByte();
}
// this gets a nibble instead of a full byte
Commit: aebee756c8fcb6b917b7863102ab98c79e57efa9
https://github.com/scummvm/scummvm/commit/aebee756c8fcb6b917b7863102ab98c79e57efa9
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:53-07:00
Commit Message:
AGI: Create separate PREAGI picture version and draw function
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/mickey.cpp
engines/agi/preagi/winnie.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index c8b019eacc5..39a07b7828f 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -337,6 +337,9 @@ void PictureMgr::drawPicture() {
case AGIPIC_V15:
drawPictureV15();
break;
+ case AGIPIC_PREAGI:
+ drawPicturePreAGI();
+ break;
case AGIPIC_V2:
drawPictureV2();
break;
@@ -480,13 +483,62 @@ void PictureMgr::drawPictureV15() {
}
}
+void PictureMgr::drawPicturePreAGI() {
+ debugC(8, kDebugLevelMain, "Drawing PreAGI picture");
+
+ int step = 0;
+ while (_dataOffset < _dataSize) {
+ byte curByte = getNextByte();
+
+ switch (curByte) {
+ case 0xf0:
+ draw_SetColor();
+ _scrOn = true;
+ break;
+ case 0xf1:
+ _scrOn = false;
+ break;
+ case 0xf4:
+ yCorner();
+ break;
+ case 0xf5:
+ xCorner();
+ break;
+ case 0xf6:
+ draw_LineAbsolute();
+ break;
+ case 0xf7:
+ draw_LineShort();
+ break;
+ case 0xf8:
+ draw_Fill();
+ break;
+ case 0xf9:
+ _patCode = getNextByte();
+ plotBrush();
+ break;
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
+ break;
+ }
+
+ // Limit drawing to the optional maximum number of opcodes.
+ // Used by Mickey for crystal animation.
+ step++;
+ if (step == _maxStep) {
+ return;
+ }
+ }
+}
+
void PictureMgr::drawPictureV2() {
debugC(8, kDebugLevelMain, "Drawing V2/V3 picture");
// AGIv3 nibble parameters are indicated by a flag in the picture's directory entry
bool nibbleMode = (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) != 0;
- int step = 0;
while (_dataOffset < _dataSize) {
byte curByte = getNextByte();
@@ -530,9 +582,6 @@ void PictureMgr::drawPictureV2() {
break;
case 0xf9:
_patCode = getNextByte();
-
- if (_vm->getGameType() == GType_PreAGI)
- plotBrush();
break;
case 0xfa:
plotBrush();
@@ -543,12 +592,6 @@ void PictureMgr::drawPictureV2() {
warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
break;
}
-
- // Limit drawing to the optional maximum number of opcodes
- step++;
- if (step == _maxStep) {
- return;
- }
}
}
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 46f0b2f7226..b9bc3e85243 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -46,7 +46,8 @@ enum AgiPictureVersion {
AGIPIC_C64, // Winnie (Apple II, C64, CoCo)
AGIPIC_V1, // Currently unused
AGIPIC_V15, // Troll (DOS)
- AGIPIC_V2 // AGIv2, AGIv3, Winnie (DOS, Amiga), Mickey (DOS)
+ AGIPIC_PREAGI, // Winnie (DOS, Amiga), Mickey (DOS)
+ AGIPIC_V2 // AGIv2, AGIv3
};
enum AgiPictureFlags {
@@ -88,6 +89,7 @@ private:
void drawPictureC64();
void drawPictureV1();
void drawPictureV15();
+ void drawPicturePreAGI();
void drawPictureV2();
void drawPictureAGI256();
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 118a6847e91..7829adf43d6 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -2275,6 +2275,8 @@ void MickeyEngine::init() {
#endif
setFlag(VM_FLAG_SOUND_ON, true); // enable sound
+
+ _picture->setPictureVersion(AGIPIC_PREAGI);
}
Common::Error MickeyEngine::go() {
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 7c92bf58a47..515fed28c0f 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -1526,6 +1526,7 @@ void WinnieEngine::init() {
_picture->setPictureVersion(AGIPIC_C64);
break;
default:
+ _picture->setPictureVersion(AGIPIC_PREAGI);
break;
}
Commit: 408c1111cd16bba3dca784297ea732d096a40055
https://github.com/scummvm/scummvm/commit/408c1111cd16bba3dca784297ea732d096a40055
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:53-07:00
Commit Message:
AGI: Implement PREAGI picture patterns
- Fixes Winnie items
- Fixes Mickey spaceship lights
- Fixes Mickey star map
Fixes bug #5730
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/mickey.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 39a07b7828f..40014da5ee2 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -187,9 +187,9 @@ void PictureMgr::plotPattern(int x, int y) {
0, 1, 4, 9, 16, 25, 37, 50
};
- static uint16 circle_data[] = {
+ static const uint16 circle_data[] = {
0x8000,
- 0xE000, 0xE000, 0xE000,
+ 0x0000, 0xE000, 0x0000,
0x7000, 0xF800, 0x0F800, 0x0F800, 0x7000,
0x3800, 0x7C00, 0x0FE00, 0x0FE00, 0x0FE00, 0x7C00, 0x3800,
0x1C00, 0x7F00, 0x0FF80, 0x0FF80, 0x0FF80, 0x0FF80, 0x0FF80, 0x7F00, 0x1C00,
@@ -215,12 +215,6 @@ void PictureMgr::plotPattern(int x, int y) {
circle_ptr = &circle_data[circle_list[pen_size]];
- // SGEORGE : Fix v3 picture data for drawing circles. Manifests in goldrush
- if (_pictureVersion == AGIPIC_V2) {
- circle_data[1] = 0;
- circle_data[3] = 0;
- }
-
// setup the X position
// = pen_x - pen.size/2
@@ -254,22 +248,9 @@ void PictureMgr::plotPattern(int x, int y) {
temp16 = temp16 << 1;
pen_width = temp16; // width of shape?
- bool circleCond;
- int counterStep;
- int ditherCond;
-
- if (_flags & kPicFCircle)
- _patCode |= 0x10;
-
- if (_vm->getGameType() == GType_PreAGI) {
- circleCond = ((_patCode & 0x10) == 0);
- counterStep = 3;
- ditherCond = 0x03;
- } else {
- circleCond = ((_patCode & 0x10) != 0);
- counterStep = 4;
- ditherCond = 0x02;
- }
+ bool circleCond = ((_patCode & 0x10) != 0);
+ int counterStep = 4;
+ int ditherCond = 0x02;
for (; pen_y < pen_final_y; pen_y++) {
circle_word = *circle_ptr++;
@@ -314,6 +295,62 @@ void PictureMgr::plotBrush() {
}
}
+void PictureMgr::plotBrush_PreAGI() {
+ _patCode = getNextByte();
+ if (_patCode > 12) {
+ _patCode = 12;
+ }
+
+ for (;;) {
+ byte x, y;
+ if (!(getNextParamByte(x) && getNextParamByte(y)))
+ break;
+
+ plotPattern_PreAGI(x, y);
+ }
+}
+
+void PictureMgr::plotPattern_PreAGI(byte x, byte y) {
+ // PreAGI patterns are 13 solid circles
+ static const byte circleData[] = {
+ 0x00,
+ 0x01, 0x01,
+ 0x01, 0x02, 0x02,
+ 0x01, 0x02, 0x03, 0x03,
+ 0x02, 0x03, 0x04, 0x04, 0x04,
+ 0x02, 0x03, 0x04, 0x05, 0x05, 0x05,
+ 0x02, 0x04, 0x05, 0x05, 0x06, 0x06, 0x06,
+ 0x02, 0x04, 0x05, 0x06, 0x06, 0x07, 0x07, 0x07,
+ 0x02, 0x04, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08, 0x08,
+ 0x03, 0x05, 0x06, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x03, 0x05, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x03, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c
+ };
+
+ int circleDataIndex = (_patCode * (_patCode + 1)) / 2;
+
+ // draw the circle by drawing its vertical lines two at a time, starting at the
+ // left and right edges and working inwards. circles have odd widths, so the
+ // final iteration draws the middle line twice.
+ for (int i = _patCode; i >= 0; i--) {
+ const byte height = circleData[circleDataIndex++];
+ int16 x1, y1, x2, y2;
+
+ // left vertical line
+ x1 = x - i;
+ x2 = x1;
+ y1 = y - height;
+ y2 = y + height;
+ draw_Line(x1, y1, x2, y2);
+
+ // right vertical line
+ x1 = x + i;
+ x2 = x1;
+ draw_Line(x1, y1, x2, y2);
+ }
+}
+
/**************************************************************************
** Draw AGI picture
**************************************************************************/
@@ -382,8 +419,7 @@ void PictureMgr::drawPictureC64() {
_scrOn = true;
break;
case 0xe6: // plot brush
- _patCode = getNextByte();
- plotBrush();
+ plotBrush_PreAGI();
break;
case 0xff: // end of data
return;
@@ -514,8 +550,7 @@ void PictureMgr::drawPicturePreAGI() {
draw_Fill();
break;
case 0xf9:
- _patCode = getNextByte();
- plotBrush();
+ plotBrush_PreAGI();
break;
case 0xff: // end of data
return;
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index b9bc3e85243..5cb92772cef 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -52,9 +52,8 @@ enum AgiPictureVersion {
enum AgiPictureFlags {
kPicFNone = (1 << 0),
- kPicFCircle = (1 << 1), // Mickey, spaceship lights (not drawn accurately)
- kPicFf3Stop = (1 << 2), // Troll, certain pictures
- kPicFTrollMode = (1 << 3) // Troll, drawing the Troll
+ kPicFf3Stop = (1 << 1), // Troll, certain pictures
+ kPicFTrollMode = (1 << 2) // Troll, drawing the Troll
};
class AgiBase;
@@ -75,6 +74,8 @@ private:
void yCorner(bool skipOtherCoords = false);
void plotPattern(int x, int y);
void plotBrush();
+ void plotPattern_PreAGI(byte x, byte y);
+ void plotBrush_PreAGI();
byte getNextByte();
bool getNextParamByte(byte &b);
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 7829adf43d6..784554d0695 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -762,7 +762,7 @@ void MickeyEngine::drawRoomAnimation() {
// draw blinking ship lights
uint8 lightPicture[] = {
0xF0, 1, // Set Color: 1
- 0xF9, 2, 43, 45, // Set Pattern: 2, plot at 43,45
+ 0xF9, 2, 44, 45, // Set Pattern: 2, plot at 44,45
0xFF // End
};
@@ -777,7 +777,6 @@ void MickeyEngine::drawRoomAnimation() {
lightPicture[1] = iColor; // change light color
lightPicture[4] += 7; // increase x coordinate
- _picture->setPictureFlags(kPicFCircle);
_picture->setMaxStep(0);
_picture->setOffset(IDI_MSA_PIC_X0, IDI_MSA_PIC_Y0);
_picture->decodePictureFromBuffer(lightPicture, sizeof(lightPicture), false, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);
Commit: b258e785022eadade3e3236302c2b86387d043ae
https://github.com/scummvm/scummvm/commit/b258e785022eadade3e3236302c2b86387d043ae
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:54-07:00
Commit Message:
AGI: Add Pictures debug channel
Changed paths:
engines/agi/agi.h
engines/agi/detection.cpp
engines/agi/picture.cpp
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 42bb3d6bf25..5e4a7dc68a8 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -153,13 +153,14 @@ enum kDebugLevels {
kDebugLevelMain = 1 << 0,
kDebugLevelResources = 1 << 1,
kDebugLevelSprites = 1 << 2,
- kDebugLevelInventory = 1 << 3,
- kDebugLevelInput = 1 << 4,
- kDebugLevelMenu = 1 << 5,
- kDebugLevelScripts = 1 << 6,
- kDebugLevelSound = 1 << 7,
- kDebugLevelText = 1 << 8,
- kDebugLevelSavegame = 1 << 9
+ kDebugLevelPictures = 1 << 3,
+ kDebugLevelInventory = 1 << 4,
+ kDebugLevelInput = 1 << 5,
+ kDebugLevelMenu = 1 << 6,
+ kDebugLevelScripts = 1 << 7,
+ kDebugLevelSound = 1 << 8,
+ kDebugLevelText = 1 << 9,
+ kDebugLevelSavegame = 1 << 10
};
/**
diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp
index 9d5c0ba9317..6999c0accc3 100644
--- a/engines/agi/detection.cpp
+++ b/engines/agi/detection.cpp
@@ -37,6 +37,7 @@
static const DebugChannelDef debugFlagList[] = {
{Agi::kDebugLevelMain, "Main", "Generic debug level"},
{Agi::kDebugLevelResources, "Resources", "Resources debugging"},
+ {Agi::kDebugLevelPictures, "Pictures", "Pictures debugging"},
{Agi::kDebugLevelSprites, "Sprites", "Sprites debugging"},
{Agi::kDebugLevelInventory, "Inventory", "Inventory debugging"},
{Agi::kDebugLevelInput, "Input", "Input events debugging"},
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 40014da5ee2..a4c33abbeb2 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -386,7 +386,7 @@ void PictureMgr::drawPicture() {
}
void PictureMgr::drawPictureC64() {
- debugC(8, kDebugLevelMain, "Drawing Apple II / C64 / CoCo picture");
+ debugC(kDebugLevelPictures, "Drawing Apple II / C64 / CoCo picture");
_scrColor = 0;
@@ -431,7 +431,7 @@ void PictureMgr::drawPictureC64() {
}
void PictureMgr::drawPictureV1() {
- debugC(8, kDebugLevelMain, "Drawing V1 picture");
+ debugC(kDebugLevelPictures, "Drawing V1 picture");
while (_dataOffset < _dataSize) {
byte curByte = getNextByte();
@@ -473,7 +473,7 @@ void PictureMgr::drawPictureV1() {
}
void PictureMgr::drawPictureV15() {
- debugC(8, kDebugLevelMain, "Drawing V1.5 picture");
+ debugC(kDebugLevelPictures, "Drawing V1.5 picture");
while (_dataOffset < _dataSize) {
byte curByte = getNextByte();
@@ -520,7 +520,7 @@ void PictureMgr::drawPictureV15() {
}
void PictureMgr::drawPicturePreAGI() {
- debugC(8, kDebugLevelMain, "Drawing PreAGI picture");
+ debugC(kDebugLevelPictures, "Drawing PreAGI picture");
int step = 0;
while (_dataOffset < _dataSize) {
@@ -569,7 +569,7 @@ void PictureMgr::drawPicturePreAGI() {
}
void PictureMgr::drawPictureV2() {
- debugC(8, kDebugLevelMain, "Drawing V2/V3 picture");
+ debugC(kDebugLevelPictures, "Drawing V2/V3 picture");
// AGIv3 nibble parameters are indicated by a flag in the picture's directory entry
bool nibbleMode = (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) != 0;
@@ -637,7 +637,7 @@ void PictureMgr::drawPictureAGI256() {
byte *dataPtr = _data;
byte *dataEndPtr = _data + _dataSize;
- debugC(8, kDebugLevelMain, "Drawing AGI256 picture");
+ debugC(kDebugLevelPictures, "Drawing AGI256 picture");
while (dataPtr < dataEndPtr) {
byte color = *dataPtr++;
@@ -968,7 +968,7 @@ void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearSc
}
void PictureMgr::showPicture(int16 x, int16 y, int16 width, int16 height) {
- debugC(8, kDebugLevelMain, "Show picture");
+ debugC(kDebugLevelPictures, "Show picture");
_gfx->render_Block(x, y, width, height);
}
@@ -977,7 +977,7 @@ void PictureMgr::showPictureWithTransition() {
_width = SCRIPT_WIDTH;
_height = SCRIPT_HEIGHT;
- debugC(8, kDebugLevelMain, "Show picture");
+ debugC(kDebugLevelPictures, "Show picture");
if (!_vm->_game.automaticRestoreGame) {
// only do transitions when we are not restoring a saved game
Commit: 661bd57436aa466ef1922ecdb6effc74580ac869
https://github.com/scummvm/scummvm/commit/661bd57436aa466ef1922ecdb6effc74580ac869
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:54-07:00
Commit Message:
AGI: Implement PREAGI picture coordinate clipping
- Fixes Winnie wall color in first room
- Fixes Winnie water color behind bridge
- Fixes Winnie rabbit hole color
- Fixes Eeyore shack color
Fixes bug #14549
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index a4c33abbeb2..2f47af5b72e 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -100,6 +100,38 @@ byte PictureMgr::getNextNibble() {
}
}
+bool PictureMgr::getNextXCoordinate(byte &x) {
+ if (!(getNextParamByte(x))) {
+ return false;
+ }
+
+ if (_pictureVersion == AGIPIC_PREAGI) {
+ if (x >= _width) {
+ debugC(kDebugLevelPictures, "preagi: clipping x from %d to %d", x, _width - 1);
+ x = _width - 1; // 139
+ }
+ }
+ return true;
+}
+
+bool PictureMgr::getNextYCoordinate(byte &y) {
+ if (!(getNextParamByte(y))) {
+ return false;
+ }
+
+ if (_pictureVersion == AGIPIC_PREAGI) {
+ if (y > _height) {
+ debugC(kDebugLevelPictures, "preagi: clipping y from %d to %d", y, _height);
+ y = _height; // 159
+ }
+ }
+ return true;
+}
+
+bool PictureMgr::getNextCoordinates(byte &x, byte &y) {
+ return getNextXCoordinate(x) && getNextYCoordinate(y);
+}
+
/**************************************************************************
** xCorner
**
@@ -108,13 +140,13 @@ byte PictureMgr::getNextNibble() {
void PictureMgr::xCorner(bool skipOtherCoords) {
byte x1, x2, y1, y2, dummy;
- if (!(getNextParamByte(x1) && getNextParamByte(y1)))
+ if (!getNextCoordinates(x1, y1))
return;
putVirtPixel(x1, y1);
for (;;) {
- if (!getNextParamByte(x2))
+ if (!getNextXCoordinate(x2))
break;
if (skipOtherCoords)
@@ -128,7 +160,7 @@ void PictureMgr::xCorner(bool skipOtherCoords) {
if (!getNextParamByte(dummy))
break;
- if (!getNextParamByte(y2))
+ if (!getNextYCoordinate(y2))
break;
draw_Line(x1, y1, x1, y2);
@@ -144,7 +176,7 @@ void PictureMgr::xCorner(bool skipOtherCoords) {
void PictureMgr::yCorner(bool skipOtherCoords) {
byte x1, x2, y1, y2, dummy;
- if (!(getNextParamByte(x1) && getNextParamByte(y1)))
+ if (!getNextCoordinates(x1, y1))
return;
putVirtPixel(x1, y1);
@@ -154,12 +186,12 @@ void PictureMgr::yCorner(bool skipOtherCoords) {
if (!getNextParamByte(dummy))
break;
- if (!getNextParamByte(y2))
+ if (!getNextYCoordinate(y2))
break;
draw_Line(x1, y1, x1, y2);
y1 = y2;
- if (!getNextParamByte(x2))
+ if (!getNextXCoordinate(x2))
break;
if (skipOtherCoords)
@@ -288,7 +320,7 @@ void PictureMgr::plotBrush() {
}
byte x1, y1;
- if (!(getNextParamByte(x1) && getNextParamByte(y1)))
+ if (!getNextCoordinates(x1, y1))
break;
plotPattern(x1, y1);
@@ -303,7 +335,7 @@ void PictureMgr::plotBrush_PreAGI() {
for (;;) {
byte x, y;
- if (!(getNextParamByte(x) && getNextParamByte(y)))
+ if (!getNextCoordinates(x, y))
break;
plotPattern_PreAGI(x, y);
@@ -792,7 +824,7 @@ void PictureMgr::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) {
void PictureMgr::draw_LineShort() {
byte x1, y1, disp;
- if (!(getNextParamByte(x1) && getNextParamByte(y1)))
+ if (!getNextCoordinates(x1, y1))
return;
putVirtPixel(x1, y1);
@@ -823,13 +855,13 @@ void PictureMgr::draw_LineShort() {
void PictureMgr::draw_LineAbsolute() {
byte x1, y1, x2, y2;
- if (!(getNextParamByte(x1) && getNextParamByte(y1)))
+ if (!getNextCoordinates(x1, y1))
return;
putVirtPixel(x1, y1);
for (;;) {
- if (!(getNextParamByte(x2) && getNextParamByte(y2)))
+ if (!getNextCoordinates(x2, y2))
break;
draw_Line(x1, y1, x2, y2);
@@ -840,10 +872,24 @@ void PictureMgr::draw_LineAbsolute() {
// flood fill
void PictureMgr::draw_Fill() {
- byte x1, y1;
+ byte x, y;
+
+ while (getNextCoordinates(x, y)) {
+ // PreAGI: getNextCoordinates clips to (139, 159), and then
+ // flood fill checks if y >= 159 and decrements to 158.
+ // The flood fill check is not in in Apple II/C64/CoCo
+ // versions of Winnie, as can be seen by the table edge
+ // being a different color than Winnie's shirt in the first
+ // room, but the same color in DOS/Amiga (picture 28).
+ if (_pictureVersion == AGIPIC_PREAGI) {
+ if (y >= _height) { // 159
+ debugC(kDebugLevelPictures, "preagi: fill clipping y from %d to %d", y, _height - 1);
+ y = _height - 1; // 158
+ }
+ }
- while (getNextParamByte(x1) && getNextParamByte(y1))
- draw_Fill(x1, y1);
+ draw_Fill(x, y);
+ }
}
void PictureMgr::draw_Fill(int16 x, int16 y) {
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 5cb92772cef..035f719d9b5 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -81,6 +81,10 @@ private:
bool getNextParamByte(byte &b);
byte getNextNibble();
+ bool getNextXCoordinate(byte &x);
+ bool getNextYCoordinate(byte &y);
+ bool getNextCoordinates(byte &x, byte &y);
+
public:
void decodePicture(int16 resourceNr, bool clearScreen, bool agi256 = false, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
Commit: 43a43cf3c08d04b1c20c8127ecac0b5cef9b3c01
https://github.com/scummvm/scummvm/commit/43a43cf3c08d04b1c20c8127ecac0b5cef9b3c01
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:54-07:00
Commit Message:
AGI: Fix PREAGI missing flood fills
Fixes Roo in Winnie the Pooh
Changed paths:
engines/agi/picture.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 2f47af5b72e..5aeedc0061a 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -578,9 +578,15 @@ void PictureMgr::drawPicturePreAGI() {
case 0xf7:
draw_LineShort();
break;
- case 0xf8:
+ case 0xf8: {
+ // The screen-on flag does not prevent PreAGI flood fills.
+ // Winnie picture 7 (Roo) contains F1 before several fills.
+ byte prevScrOn = _scrOn;
+ _scrOn = true;
draw_Fill();
+ _scrOn = prevScrOn;
break;
+ }
case 0xf9:
plotBrush_PreAGI();
break;
Commit: 0be62c0f69ead10fc90d8b1562e441d50ae55bdb
https://github.com/scummvm/scummvm/commit/0be62c0f69ead10fc90d8b1562e441d50ae55bdb
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:54-07:00
Commit Message:
AGI: Fix PREAGI memory corruption when drawing pictures
Fixes memory corruption in Winnie the Pooh when a
tall object is drawn at the bottom of the screen.
The existing boundary checks in these functions occur
before the PreAGI offsets are applied.
Changed paths:
engines/agi/picture.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 5aeedc0061a..a5d3a85f74d 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -62,6 +62,12 @@ void PictureMgr::putVirtPixel(int x, int y) {
x += _xOffset;
y += _yOffset;
+ // validate coordinate after applying preagi offset.
+ // winnie objects go past the bottom of the screen.
+ if (x >= SCRIPT_WIDTH || y >= SCRIPT_HEIGHT) {
+ return;
+ }
+
byte drawMask= 0;
if (_priOn)
drawMask |= GFX_SCREEN_MASK_PRIORITY;
@@ -950,6 +956,12 @@ bool PictureMgr::draw_FillCheck(int16 x, int16 y) {
x += _xOffset;
y += _yOffset;
+ // validate coordinate after applying preagi offset.
+ // winnie objects go past the bottom of the screen.
+ if (x >= SCRIPT_WIDTH || y >= SCRIPT_HEIGHT) {
+ return false;
+ }
+
byte screenColor = _gfx->getColor(x, y);
byte screenPriority = _gfx->getPriority(x, y);
Commit: 2232dbdb32df58f2eb4260447ea982525e3e18a3
https://github.com/scummvm/scummvm/commit/2232dbdb32df58f2eb4260447ea982525e3e18a3
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:55-07:00
Commit Message:
SCI: Add KQ5, LSL5, SQ4 German EGA detection entries
Thanks to @ns394 for reporting
Changed paths:
engines/sci/detection_tables.h
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index cfedd2dab9e..25b51b8353f 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -2135,7 +2135,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::ES_ESP, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
- // King's Quest 5 - German DOS Floppy EGA (5.25" disks)
+ // King's Quest 5 - German DOS Floppy EGA (5.25" 640k disks)
// Game version 0.000.121 from about box, 1.000 from VERSION file
// SCI interpreter version 1.000.575
{"kq5", "EGA", {
@@ -2152,6 +2152,19 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
+ // King's Quest 5 - German DOS EGA (5.25" 1.2M disks)
+ // Supplied by ns394 in bug report #15636
+ {"kq5", "EGA", {
+ {"resource.map", 0, "b7f88d338f165437709b5f9878fe27b3", 7998},
+ {"resource.000", 0, "1a82e9253c727fef5c8ed655d1a42486", 471539},
+ {"resource.001", 0, "326c1b81779fddd84a990e74f8fb4e27", 1193566},
+ {"resource.002", 0, "c734b48f9cf3d18370cf09778904ed10", 317970},
+ {"resource.003", 0, "646f6f84c0ed677e0c77fd145f04ce3e", 1110748},
+ {"resource.004", 0, "b08822ecdbf6d926dc24291614134f97", 1209355},
+ {"resource.005", 0, "28b41e5ffd872ceec011a832c89aff23", 912051},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
+
// King's Quest 5 - German DOS Floppy VGA (supplied by markcoolio in bug report #4290, also includes english language)
// SCI interpreter version 1.000.060
{"kq5", "", {
@@ -3526,6 +3539,21 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
+ // Larry 5 - German DOS EGA (3.5" 720k disks)
+ // Supplied by ns394 in bug report #15634
+ {"lsl5", "EGA", {
+ {"resource.map", 0, "d0dbfd29402def6adce3052433979275", 6603},
+ {"resource.000", 0, "4c00c14b8181ad47076a51d86097d97e", 313454},
+ {"resource.001", 0, "1d631b16ffba2484fcb41af8f0016010", 445473},
+ {"resource.002", 0, "c2cb2dec12e26f6243bc1b78e4e84940", 636986},
+ {"resource.003", 0, "f8e876302a3aba5bcaab5c51db6b6532", 723502},
+ {"resource.004", 0, "16f4d8fb1b526125edaca4fc6cbb7530", 548747},
+ {"resource.005", 0, "6043b2cc23d663e6a01b25bd0e4de55e", 581585},
+ {"resource.006", 0, "f6046a8445422f17d40b1b10ab21ebf3", 593549},
+ {"resource.007", 0, "640ee65595d40372ef95462f2c1ae28a", 619199},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
+
// Larry 5 - French DOS (provided by richiefs in bug report #4214)
// Executable scanning reports "1.lsl5.019"
// SCI interpreter version 1.000.510 (just a guess)
@@ -5973,6 +6001,17 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
+ // Space Quest 4 - German DOS EGA (5.25" 1.2M disks)
+ // Supplied by ns394 in bug report #15635
+ {"sq4", "EGA", {
+ {"resource.map", 0, "8fa159f2c25e7bafbbefc9d998b6e90f", 5571},
+ {"resource.000", 0, "5f6a1fff40584ee807efd547899b1ba5", 249229},
+ {"resource.001", 0, "a836bd3bc9574f2371f8b6f74a082313", 995949},
+ {"resource.002", 0, "bba9d6e685809fb7e482c9b33c1cac3f", 1140960},
+ {"resource.003", 0, "5b541ef2feb38b999cc331b5e4b6df8a", 1092315},
+ AD_LISTEND},
+ Common::DE_DEU, Common::kPlatformDOS, ADGF_ADDENGLISH, GUIO_STD16 },
+
// Space Quest 4 - Italian DOS Floppy (from glorifindel, also includes english language)
// SCI interpreter version 1.000.200 (just a guess)
{"sq4", "", {
Commit: c05ef83e6f0fbe6d747baf39c7e5d1c2bab5fa2d
https://github.com/scummvm/scummvm/commit/c05ef83e6f0fbe6d747baf39c7e5d1c2bab5fa2d
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:55-07:00
Commit Message:
AGI: Implement AGIv1 set.bit and clear.bit opcodes
Changed paths:
engines/agi/op_cmd.cpp
engines/agi/opcodes.cpp
engines/agi/opcodes.h
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 93147282c80..057a4e30b39 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -2299,6 +2299,22 @@ void cmdNewRoomVV1(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
vm->setVar(13, 1);
}
+void cmdSetBit(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
+ uint16 bit = parameter[0];
+ uint16 varNr = parameter[1];
+
+ byte varVal = vm->getVar(varNr);
+ vm->setVar(varNr, varVal | (1 << bit));
+}
+
+void cmdClearBit(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
+ uint16 bit = parameter[0];
+ uint16 varNr = parameter[1];
+
+ byte varVal = vm->getVar(varNr);
+ vm->setVar(varNr, varVal & ~(1 << bit));
+}
+
// The AGI256 interpreter modified opcode 170 to load 256 color pictures
void cmdAgi256LoadPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
// Load the picture. Similar to void cmdLoadPic.
diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp
index 1ad3b709cb1..237b03295f8 100644
--- a/engines/agi/opcodes.cpp
+++ b/engines/agi/opcodes.cpp
@@ -148,8 +148,8 @@ static const AgiOpCodeDefinitionEntry opCodesV1[] = {
{ "load.view", "n", &cmdLoadView }, // 5D
{ "...", "", &cmdUnknown }, // 5E unused in KQ2 or BC
{ "...", "", &cmdUnknown }, // 5F BC script 102 when attempting to fill flask
- { "setbit", "nv", &cmdUnknown }, // 60
- { "...", "nv", &cmdUnknown }, // 61 # clearbit, unused in KQ2 or BC
+ { "set.bit", "nv", &cmdSetBit }, // 60 BC
+ { "clear.bit", "nv", &cmdClearBit }, // 61
{ "set.upper.left", "nn", &cmdSetUpperLeft } // 62 BC Apple II
};
diff --git a/engines/agi/opcodes.h b/engines/agi/opcodes.h
index 4d3f745736e..26bf778eb49 100644
--- a/engines/agi/opcodes.h
+++ b/engines/agi/opcodes.h
@@ -225,6 +225,8 @@ void cmdAdjEgoMoveToXY(AgiGame *state, AgiEngine *vm, uint8 *p);
void cmdSetSpeed(AgiGame *state, AgiEngine *vm, uint8 *p);
void cmdSetItemView(AgiGame *state, AgiEngine *vm, uint8 *p);
void cmdCallV1(AgiGame *state, AgiEngine *vm, uint8 *p);
+void cmdSetBit(AgiGame *state, AgiEngine *vm, uint8 *p);
+void cmdClearBit(AgiGame *state, AgiEngine *vm, uint8 *p);
void cmdUnknown(AgiGame *state, AgiEngine *vm, uint8 *p);
void condEqual(AgiGame *state, AgiEngine *vm, uint8 *p);
Commit: 59bfdd874522e9ace111f779c62ee4d900e692e0
https://github.com/scummvm/scummvm/commit/59bfdd874522e9ace111f779c62ee4d900e692e0
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:55-07:00
Commit Message:
AGI: PREAGI: Add missing left space to TROLL menus
Changed paths:
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 3741fc21053..1081a6dc9dd 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -445,7 +445,9 @@ int TrollEngine::drawRoom(char *menu) {
_system->updateScreen();
int n = 0;
- strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39);
+ menu[0] = ' '; // leading space
+ menu[1] = '\0';
+ strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 38);
for (int i = 0; i < 3; i++) {
if (_roomDescs[_roomPicture - 1].options[i]) {
@@ -550,8 +552,6 @@ void TrollEngine::gameLoop() {
memset(_inventory, 0, sizeof(_inventory));
while (!done && !shouldQuit()) {
- *menu = 0;
-
currentOption = 0;
numberOfOptions = drawRoom(menu);
Commit: 3e0dc72b3a2c19799056f275d6c99e04b6f94255
https://github.com/scummvm/scummvm/commit/3e0dc72b3a2c19799056f275d6c99e04b6f94255
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:55-07:00
Commit Message:
SCI: Fix SQ5 introduction speeds
Fixes comets not animating in the star field, bug #15622
Fixes speed and timing when exiting simulator, bug #15610
The introduction scenes are heavily dependent on both CPU speed and the
undefined lag that occurs in the original interpreter when animating
many actors at once. Now our speed throttler adjusts for this.
Thanks to @GermanTribun and @eriktorbjorn for reporting.
Big thanks to @PickledDog for testing and recording on a 386 and a 486.
Changed paths:
engines/sci/engine/kmisc.cpp
engines/sci/engine/vm.h
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 2af7dc587d1..0c102a205a4 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -112,8 +112,7 @@ reg_t kGameIsRestarting(EngineState *s, int argc, reg_t *argv) {
uint32 neededSleep = g_sci->_speedThrottleDelay; // 30 ms (kSpeedThrottleDefaultDelay)
- // WORKAROUNDS for scripts that are polling too quickly in scenes that
- // are not animating much
+ // WORKAROUNDS for scripts that require specific speed throttling behavior
switch (g_sci->getGameId()) {
case GID_CASTLEBRAIN:
// In Castle of Dr. Brain, memory color matching puzzle in the first
@@ -139,6 +138,40 @@ reg_t kGameIsRestarting(EngineState *s, int argc, reg_t *argv) {
neededSleep = 60;
}
break;
+ case GID_SQ5:
+ switch (s->currentRoomNumber()) {
+ case 104:
+ // Introduction: star field. Requires extra speed throttling to achieve
+ // comet animations and intended timing. Comets and fast stars move at
+ // unthrottled speed, but the comet's animation cycle speed is based on
+ // clock time and unsynchronized with movement. On a 386/33, the comets
+ // animate and become streaks, but even a 486/50 is too fast for this.
+ // The comets move so fast that they reach their destination before they
+ // can animate beyond their initial 1x1 pixel cel. Bug #15622
+ s->_throttleTrigger = true;
+ neededSleep = 90;
+ break;
+ case 110: {
+ // Introduction: exiting the simulator. Requires extra speed throttling to
+ // achieve intended timing. All actors in this room have unthrottled speed.
+ // This may have been to compensate for the interpreter lag when animating
+ // so many actors. Without that lag, and the CPU this scene was timed for,
+ // everything moves and animates too fast. However, we must not apply extra
+ // throttling to the second half of this scene, or else ego will walk slowly.
+ // The original interpreter did not lag here, because it removed the earlier
+ // actors from the cast. We detect this by querying the cast size. Bug #15610
+ const reg_t cast = s->variables[VAR_GLOBAL][kGlobalVarCast];
+ const uint16 castSize = readSelectorValue(s->_segMan, cast, SELECTOR(size));
+ if (castSize != 5) { // only throttle before ego begins walking
+ s->_throttleTrigger = true;
+ neededSleep = 90;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ break;
// Don't throttle SCI1.1 speed test rooms. Prevents delays at startup.
// We generically patch these scripts to calculate a passing result,
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index b114fb1849a..74dd6a1e7a6 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -145,6 +145,7 @@ enum GlobalVar {
kGlobalVarCurrentRoom = 2,
kGlobalVarSpeed = 3, // SCI16
kGlobalVarQuit = 4,
+ kGlobalVarCast = 5,
kGlobalVarSounds = 8,
kGlobalVarPlanes = 10, // SCI32
kGlobalVarCurrentRoomNo = 11,
Commit: 8e42e82369ff62f8a7d55eb9a3ab9f7851fc83c8
https://github.com/scummvm/scummvm/commit/8e42e82369ff62f8a7d55eb9a3ab9f7851fc83c8
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:55-07:00
Commit Message:
AGI: PREAGI: Fix TROLL array size
TrollEngine::_nonTrollRooms was declared with a data offset instead
of a room count, making it a 15,609 byte array instead of 9.
Changed paths:
engines/agi/preagi/troll.cpp
engines/agi/preagi/troll.h
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 1081a6dc9dd..502dc47ce36 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -698,7 +698,7 @@ void TrollEngine::fillOffsets() {
_items[i].name[15] = 0;
}
- for (i = 0; i < IDO_TRO_NONTROLLROOMS; i++)
+ for (i = 0; i < IDI_TRO_NUM_NONTROLL; i++)
_nonTrollRooms[i] = _gameData[IDO_TRO_NONTROLLROOMS + i];
_tunes[0] = 0x3BFD;
diff --git a/engines/agi/preagi/troll.h b/engines/agi/preagi/troll.h
index 7c4c765c4c8..8dff12de243 100644
--- a/engines/agi/preagi/troll.h
+++ b/engines/agi/preagi/troll.h
@@ -214,7 +214,7 @@ private:
int _options[IDI_TRO_NUM_OPTIONS];
Item _items[IDI_TRO_MAX_TREASURE];
int _roomConnects[IDI_TRO_NUM_OPTIONS];
- int _nonTrollRooms[IDO_TRO_NONTROLLROOMS];
+ int _nonTrollRooms[IDI_TRO_NUM_NONTROLL];
int _tunes[6];
};
Commit: b7df034703563543cfadc214fd5a4df703dffa11
https://github.com/scummvm/scummvm/commit/b7df034703563543cfadc214fd5a4df703dffa11
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:56-07:00
Commit Message:
AGI: PREAGI: Fix TROLL menu string truncation
Follow-up to: 94c91b5439e1fce2e8a53a640dc793c03f8b23a9
Fixes the period character being overwritten with a newline character
on menu descriptions that are 39 characters long, as they are displayed
with 1 leading space character. The menu code was building fixed-width
strings and placing newline characters at the end of each, but preagi
fixed-width menu strings automatically wrap to the next line.
Once the missing leading-space character was fixed, it exposed that
there was no room for the new line characters.
Changed paths:
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 502dc47ce36..d3573f9ee82 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -447,13 +447,14 @@ int TrollEngine::drawRoom(char *menu) {
int n = 0;
menu[0] = ' '; // leading space
menu[1] = '\0';
- strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 38);
+ strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39);
for (int i = 0; i < 3; i++) {
if (_roomDescs[_roomPicture - 1].options[i]) {
- strncat(menu, Common::String::format("\n %d.", i + 1).c_str(), 5);
+ strncat(menu, Common::String::format(" %d.", i + 1).c_str(), 4);
strncat(menu, (char *)_gameData + _options[_roomDescs[_roomPicture - 1].options[i] - 1], 35);
+ strncat(menu, " ", 1);
n = i + 1;
}
Commit: 741f6202d2ed7182cd09776f31c2d462522d7c07
https://github.com/scummvm/scummvm/commit/741f6202d2ed7182cd09776f31c2d462522d7c07
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:56-07:00
Commit Message:
AGI: Remove unused picture version
This code was from TrollVM in 2003, where it was unused and undocumented
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index a5d3a85f74d..5ca3c10ec82 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -406,9 +406,6 @@ void PictureMgr::drawPicture() {
case AGIPIC_C64:
drawPictureC64();
break;
- case AGIPIC_V1:
- drawPictureV1();
- break;
case AGIPIC_V15:
drawPictureV15();
break;
@@ -468,48 +465,6 @@ void PictureMgr::drawPictureC64() {
}
}
-void PictureMgr::drawPictureV1() {
- debugC(kDebugLevelPictures, "Drawing V1 picture");
-
- while (_dataOffset < _dataSize) {
- byte curByte = getNextByte();
-
- switch (curByte) {
- case 0xf1:
- draw_SetColor();
- _scrOn = true;
- _priOn = false;
- break;
- case 0xf3:
- draw_SetColor();
- _scrOn = true;
- draw_SetPriority();
- _priOn = true;
- break;
- case 0xfa:
- _scrOn = false;
- _priOn = true;
- draw_LineAbsolute();
- _scrOn = true;
- _priOn = false;
- break;
- case 0xfb:
- draw_LineShort();
- break;
- case 0xfc:
- draw_SetColor();
- draw_SetPriority();
- draw_Fill();
- break;
- case 0xff: // end of data
- return;
- default:
- warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
- break;
- }
- }
-}
-
void PictureMgr::drawPictureV15() {
debugC(kDebugLevelPictures, "Drawing V1.5 picture");
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 035f719d9b5..b0ec83c0bc1 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -44,7 +44,6 @@ struct AgiPicture {
enum AgiPictureVersion {
AGIPIC_C64, // Winnie (Apple II, C64, CoCo)
- AGIPIC_V1, // Currently unused
AGIPIC_V15, // Troll (DOS)
AGIPIC_PREAGI, // Winnie (DOS, Amiga), Mickey (DOS)
AGIPIC_V2 // AGIv2, AGIv3
@@ -92,7 +91,6 @@ public:
private:
void drawPicture();
void drawPictureC64();
- void drawPictureV1();
void drawPictureV15();
void drawPicturePreAGI();
void drawPictureV2();
Commit: 0543ed4be7d51979d4c88dc0182a8a41cb1eb549
https://github.com/scummvm/scummvm/commit/0543ed4be7d51979d4c88dc0182a8a41cb1eb549
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:56-07:00
Commit Message:
AGI: PREAGI: Move PictureMgr to game engine classes
Changed paths:
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
engines/agi/preagi/preagi.cpp
engines/agi/preagi/preagi.h
engines/agi/preagi/troll.cpp
engines/agi/preagi/troll.h
engines/agi/preagi/winnie.cpp
engines/agi/preagi/winnie.h
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index 784554d0695..ce33019a6eb 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -2215,15 +2215,19 @@ void MickeyEngine::debugGotoRoom(int room) {
}
MickeyEngine::MickeyEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+ _picture = nullptr;
_isGameOver = false;
setDebugger(new MickeyConsole(this));
}
MickeyEngine::~MickeyEngine() {
+ delete _picture;
//_console deleted by Engine
}
void MickeyEngine::init() {
+ _picture = new PictureMgr(this, _gfx);
+
uint8 buffer[512];
// clear game struct
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index 13ac1c9ef1a..d8839c76193 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -692,6 +692,8 @@ public:
void drawObj(ENUM_MSA_OBJECT, int, int);
protected:
+ PictureMgr *_picture;
+
MSA_GAME _gameStateMickey;
bool _clickToMove;
bool _isGameOver;
diff --git a/engines/agi/preagi/preagi.cpp b/engines/agi/preagi/preagi.cpp
index 19cd455e204..db05873d1a9 100644
--- a/engines/agi/preagi/preagi.cpp
+++ b/engines/agi/preagi/preagi.cpp
@@ -46,7 +46,6 @@ void PreAgiEngine::initialize() {
_font = new GfxFont(this);
_gfx = new GfxMgr(this, _font);
- _picture = new PictureMgr(this, _gfx);
_font->init();
@@ -78,7 +77,6 @@ PreAgiEngine::~PreAgiEngine() {
delete _speakerStream;
delete _speakerHandle;
- delete _picture;
delete _gfx;
delete _font;
}
diff --git a/engines/agi/preagi/preagi.h b/engines/agi/preagi/preagi.h
index e30ed3ee1d6..3355f999517 100644
--- a/engines/agi/preagi/preagi.h
+++ b/engines/agi/preagi/preagi.h
@@ -71,8 +71,6 @@ protected:
~PreAgiEngine() override;
int getGameId() const { return _gameId; }
- PictureMgr *_picture;
-
void clearImageStack() override {}
void recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
int16 p4, int16 p5, int16 p6, int16 p7) override {}
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index d3573f9ee82..685edda8303 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -31,9 +31,11 @@
namespace Agi {
TrollEngine::TrollEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+ _picture = nullptr;
}
TrollEngine::~TrollEngine() {
+ delete _picture;
}
// User Interface
@@ -713,6 +715,7 @@ void TrollEngine::fillOffsets() {
// Init
void TrollEngine::init() {
+ _picture = new PictureMgr(this, _gfx);
_picture->setPictureVersion(AGIPIC_V15);
//SetScreenPar(320, 200, (char *)ibm_fontdata);
diff --git a/engines/agi/preagi/troll.h b/engines/agi/preagi/troll.h
index 8dff12de243..041c4626466 100644
--- a/engines/agi/preagi/troll.h
+++ b/engines/agi/preagi/troll.h
@@ -164,6 +164,8 @@ public:
Common::Error go() override;
private:
+ PictureMgr *_picture;
+
int _roomPicture;
int _treasuresLeft;
int _currentRoom;
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 515fed28c0f..7a6cce55980 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -1468,13 +1468,17 @@ void WinnieEngine::debugCurRoom() {
}
WinnieEngine::WinnieEngine(OSystem *syst, const AGIGameDescription *gameDesc) : PreAgiEngine(syst, gameDesc) {
+ _picture = nullptr;
setDebugger(new WinnieConsole(this));
}
WinnieEngine::~WinnieEngine() {
+ delete _picture;
}
void WinnieEngine::init() {
+ _picture = new PictureMgr(this, _gfx);
+
// Initialize sound
switch (MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK | MDT_PCJR))) {
diff --git a/engines/agi/preagi/winnie.h b/engines/agi/preagi/winnie.h
index c149483385c..4f197b847cc 100644
--- a/engines/agi/preagi/winnie.h
+++ b/engines/agi/preagi/winnie.h
@@ -305,6 +305,8 @@ public:
void debugCurRoom();
private:
+ PictureMgr *_picture;
+
WTP_SAVE_GAME _gameStateWinnie;
int _room;
int _mist;
Commit: 6efbdcffa64bf33a3c2336d79f7cbd964b417bf2
https://github.com/scummvm/scummvm/commit/6efbdcffa64bf33a3c2336d79f7cbd964b417bf2
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:56-07:00
Commit Message:
AGI: PREAGI: Subclass PictureMgr for MICKEY and WINNIE
Changed paths:
A engines/agi/preagi/picture_mickey_winnie.cpp
A engines/agi/preagi/picture_mickey_winnie.h
engines/agi/module.mk
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/mickey.cpp
engines/agi/preagi/mickey.h
engines/agi/preagi/winnie.cpp
engines/agi/preagi/winnie.h
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index de26a8709fa..0dc795d6599 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -41,6 +41,7 @@ MODULE_OBJS := \
words.o \
preagi/preagi.o \
preagi/mickey.o \
+ preagi/picture_mickey_winnie.o \
preagi/troll.o \
preagi/winnie.o
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 5ca3c10ec82..b8f229492e2 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -48,23 +48,12 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_pictureVersion = AGIPIC_V2;
_width = 0;
_height = 0;
- _xOffset = 0;
- _yOffset = 0;
_flags = 0;
- _maxStep = 0;
}
-void PictureMgr::putVirtPixel(int x, int y) {
- if (x < 0 || y < 0 || x >= _width || y >= _height)
- return;
-
- x += _xOffset;
- y += _yOffset;
-
- // validate coordinate after applying preagi offset.
- // winnie objects go past the bottom of the screen.
- if (x >= SCRIPT_WIDTH || y >= SCRIPT_HEIGHT) {
+void PictureMgr::putVirtPixel(int16 x, int16 y) {
+ if (!getGraphicsCoordinates(x, y)) {
return;
}
@@ -107,37 +96,21 @@ byte PictureMgr::getNextNibble() {
}
bool PictureMgr::getNextXCoordinate(byte &x) {
- if (!(getNextParamByte(x))) {
- return false;
- }
-
- if (_pictureVersion == AGIPIC_PREAGI) {
- if (x >= _width) {
- debugC(kDebugLevelPictures, "preagi: clipping x from %d to %d", x, _width - 1);
- x = _width - 1; // 139
- }
- }
- return true;
+ return getNextParamByte(x);
}
bool PictureMgr::getNextYCoordinate(byte &y) {
- if (!(getNextParamByte(y))) {
- return false;
- }
-
- if (_pictureVersion == AGIPIC_PREAGI) {
- if (y > _height) {
- debugC(kDebugLevelPictures, "preagi: clipping y from %d to %d", y, _height);
- y = _height; // 159
- }
- }
- return true;
+ return getNextParamByte(y);
}
bool PictureMgr::getNextCoordinates(byte &x, byte &y) {
return getNextXCoordinate(x) && getNextYCoordinate(y);
}
+bool PictureMgr::getGraphicsCoordinates(int16 &x, int16 &y) {
+ return (0 <= x && x < _width && 0 <= y && y < _height);
+}
+
/**************************************************************************
** xCorner
**
@@ -215,7 +188,7 @@ void PictureMgr::yCorner(bool skipOtherCoords) {
** Draws pixels, circles, squares, or splatter brush patterns depending
** on the pattern code.
**************************************************************************/
-void PictureMgr::plotPattern(int x, int y) {
+void PictureMgr::plotPattern(byte x, byte y) {
static const uint16 binary_list[] = {
0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,
0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1
@@ -333,62 +306,6 @@ void PictureMgr::plotBrush() {
}
}
-void PictureMgr::plotBrush_PreAGI() {
- _patCode = getNextByte();
- if (_patCode > 12) {
- _patCode = 12;
- }
-
- for (;;) {
- byte x, y;
- if (!getNextCoordinates(x, y))
- break;
-
- plotPattern_PreAGI(x, y);
- }
-}
-
-void PictureMgr::plotPattern_PreAGI(byte x, byte y) {
- // PreAGI patterns are 13 solid circles
- static const byte circleData[] = {
- 0x00,
- 0x01, 0x01,
- 0x01, 0x02, 0x02,
- 0x01, 0x02, 0x03, 0x03,
- 0x02, 0x03, 0x04, 0x04, 0x04,
- 0x02, 0x03, 0x04, 0x05, 0x05, 0x05,
- 0x02, 0x04, 0x05, 0x05, 0x06, 0x06, 0x06,
- 0x02, 0x04, 0x05, 0x06, 0x06, 0x07, 0x07, 0x07,
- 0x02, 0x04, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08, 0x08,
- 0x03, 0x05, 0x06, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
- 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a,
- 0x03, 0x05, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b,
- 0x03, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c
- };
-
- int circleDataIndex = (_patCode * (_patCode + 1)) / 2;
-
- // draw the circle by drawing its vertical lines two at a time, starting at the
- // left and right edges and working inwards. circles have odd widths, so the
- // final iteration draws the middle line twice.
- for (int i = _patCode; i >= 0; i--) {
- const byte height = circleData[circleDataIndex++];
- int16 x1, y1, x2, y2;
-
- // left vertical line
- x1 = x - i;
- x2 = x1;
- y1 = y - height;
- y2 = y + height;
- draw_Line(x1, y1, x2, y2);
-
- // right vertical line
- x1 = x + i;
- x2 = x1;
- draw_Line(x1, y1, x2, y2);
- }
-}
-
/**************************************************************************
** Draw AGI picture
**************************************************************************/
@@ -403,15 +320,9 @@ void PictureMgr::drawPicture() {
_priColor = 4;
switch (_pictureVersion) {
- case AGIPIC_C64:
- drawPictureC64();
- break;
case AGIPIC_V15:
drawPictureV15();
break;
- case AGIPIC_PREAGI:
- drawPicturePreAGI();
- break;
case AGIPIC_V2:
drawPictureV2();
break;
@@ -420,51 +331,6 @@ void PictureMgr::drawPicture() {
}
}
-void PictureMgr::drawPictureC64() {
- debugC(kDebugLevelPictures, "Drawing Apple II / C64 / CoCo picture");
-
- _scrColor = 0;
-
- while (_dataOffset < _dataSize) {
- byte curByte = getNextByte();
-
- if ((curByte >= 0xF0) && (curByte <= 0xFE)) {
- _scrColor = curByte & 0x0F;
- continue;
- }
-
- switch (curByte) {
- case 0xe0: // x-corner
- xCorner();
- break;
- case 0xe1: // y-corner
- yCorner();
- break;
- case 0xe2: // dynamic draw lines
- draw_LineShort();
- break;
- case 0xe3: // absolute draw lines
- draw_LineAbsolute();
- break;
- case 0xe4: // fill
- draw_SetColor();
- draw_Fill();
- break;
- case 0xe5: // enable screen drawing
- _scrOn = true;
- break;
- case 0xe6: // plot brush
- plotBrush_PreAGI();
- break;
- case 0xff: // end of data
- return;
- default:
- warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
- break;
- }
- }
-}
-
void PictureMgr::drawPictureV15() {
debugC(kDebugLevelPictures, "Drawing V1.5 picture");
@@ -512,61 +378,6 @@ void PictureMgr::drawPictureV15() {
}
}
-void PictureMgr::drawPicturePreAGI() {
- debugC(kDebugLevelPictures, "Drawing PreAGI picture");
-
- int step = 0;
- while (_dataOffset < _dataSize) {
- byte curByte = getNextByte();
-
- switch (curByte) {
- case 0xf0:
- draw_SetColor();
- _scrOn = true;
- break;
- case 0xf1:
- _scrOn = false;
- break;
- case 0xf4:
- yCorner();
- break;
- case 0xf5:
- xCorner();
- break;
- case 0xf6:
- draw_LineAbsolute();
- break;
- case 0xf7:
- draw_LineShort();
- break;
- case 0xf8: {
- // The screen-on flag does not prevent PreAGI flood fills.
- // Winnie picture 7 (Roo) contains F1 before several fills.
- byte prevScrOn = _scrOn;
- _scrOn = true;
- draw_Fill();
- _scrOn = prevScrOn;
- break;
- }
- case 0xf9:
- plotBrush_PreAGI();
- break;
- case 0xff: // end of data
- return;
- default:
- warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
- break;
- }
-
- // Limit drawing to the optional maximum number of opcodes.
- // Used by Mickey for crystal animation.
- step++;
- if (step == _maxStep) {
- return;
- }
- }
-}
-
void PictureMgr::drawPictureV2() {
debugC(kDebugLevelPictures, "Drawing V2/V3 picture");
@@ -842,19 +653,6 @@ void PictureMgr::draw_Fill() {
byte x, y;
while (getNextCoordinates(x, y)) {
- // PreAGI: getNextCoordinates clips to (139, 159), and then
- // flood fill checks if y >= 159 and decrements to 158.
- // The flood fill check is not in in Apple II/C64/CoCo
- // versions of Winnie, as can be seen by the table edge
- // being a different color than Winnie's shirt in the first
- // room, but the same color in DOS/Amiga (picture 28).
- if (_pictureVersion == AGIPIC_PREAGI) {
- if (y >= _height) { // 159
- debugC(kDebugLevelPictures, "preagi: fill clipping y from %d to %d", y, _height - 1);
- y = _height - 1; // 158
- }
- }
-
draw_Fill(x, y);
}
}
@@ -905,15 +703,7 @@ void PictureMgr::draw_Fill(int16 x, int16 y) {
}
bool PictureMgr::draw_FillCheck(int16 x, int16 y) {
- if (x < 0 || x >= _width || y < 0 || y >= _height)
- return false;
-
- x += _xOffset;
- y += _yOffset;
-
- // validate coordinate after applying preagi offset.
- // winnie objects go past the bottom of the screen.
- if (x >= SCRIPT_WIDTH || y >= SCRIPT_HEIGHT) {
+ if (!getGraphicsCoordinates(x, y)) {
return false;
}
@@ -1032,11 +822,7 @@ void PictureMgr::showPictureWithTransition() {
void PictureMgr::setPictureVersion(AgiPictureVersion version) {
_pictureVersion = version;
-
- if (version == AGIPIC_C64)
- _minCommand = 0xe0;
- else
- _minCommand = 0xf0;
+ _minCommand = 0xf0;
}
} // End of namespace Agi
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index b0ec83c0bc1..1a25491f985 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -43,9 +43,7 @@ struct AgiPicture {
};
enum AgiPictureVersion {
- AGIPIC_C64, // Winnie (Apple II, C64, CoCo)
AGIPIC_V15, // Troll (DOS)
- AGIPIC_PREAGI, // Winnie (DOS, Amiga), Mickey (DOS)
AGIPIC_V2 // AGIv2, AGIv3
};
@@ -64,35 +62,34 @@ class PictureMgr {
public:
PictureMgr(AgiBase *agi, GfxMgr *gfx);
+ virtual ~PictureMgr() { }
int16 getResourceNr() const { return _resourceNr; };
-private:
- void putVirtPixel(int x, int y);
+protected:
+ void putVirtPixel(int16 x, int16 y);
void xCorner(bool skipOtherCoords = false);
void yCorner(bool skipOtherCoords = false);
- void plotPattern(int x, int y);
- void plotBrush();
- void plotPattern_PreAGI(byte x, byte y);
- void plotBrush_PreAGI();
+ virtual void plotPattern(byte x, byte y);
+ virtual void plotBrush();
byte getNextByte();
bool getNextParamByte(byte &b);
byte getNextNibble();
- bool getNextXCoordinate(byte &x);
- bool getNextYCoordinate(byte &y);
+ virtual bool getNextXCoordinate(byte &x);
+ virtual bool getNextYCoordinate(byte &y);
bool getNextCoordinates(byte &x, byte &y);
+ virtual bool getGraphicsCoordinates(int16 &x, int16 &y);
+
public:
void decodePicture(int16 resourceNr, bool clearScreen, bool agi256 = false, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
-private:
- void drawPicture();
- void drawPictureC64();
+protected:
+ virtual void drawPicture();
void drawPictureV15();
- void drawPicturePreAGI();
void drawPictureV2();
void drawPictureAGI256();
@@ -106,7 +103,7 @@ private:
void draw_LineAbsolute();
bool draw_FillCheck(int16 x, int16 y);
- void draw_Fill(int16 x, int16 y);
+ virtual void draw_Fill(int16 x, int16 y);
void draw_Fill();
public:
@@ -117,15 +114,7 @@ public:
void setPictureFlags(int flags) { _flags = flags; }
- void setOffset(int offX, int offY) {
- _xOffset = offX;
- _yOffset = offY;
- }
-
- void setMaxStep(int maxStep) { _maxStep = maxStep; }
- int getMaxStep() const { return _maxStep; }
-
-private:
+protected:
int16 _resourceNr;
uint8 *_data;
uint32 _dataSize;
@@ -144,11 +133,8 @@ private:
AgiPictureVersion _pictureVersion;
int16 _width;
int16 _height;
- int16 _xOffset;
- int16 _yOffset;
int _flags;
- int _maxStep; // Max opcodes to draw, zero for all. Used by preagi (Mickey)
};
} // End of namespace Agi
diff --git a/engines/agi/preagi/mickey.cpp b/engines/agi/preagi/mickey.cpp
index ce33019a6eb..93b91fe935f 100644
--- a/engines/agi/preagi/mickey.cpp
+++ b/engines/agi/preagi/mickey.cpp
@@ -26,6 +26,7 @@
#include "graphics/cursorman.h"
#include "agi/preagi/preagi.h"
+#include "agi/preagi/picture_mickey_winnie.h"
#include "agi/preagi/mickey.h"
#include "agi/graphics.h"
@@ -2226,7 +2227,7 @@ MickeyEngine::~MickeyEngine() {
}
void MickeyEngine::init() {
- _picture = new PictureMgr(this, _gfx);
+ _picture = new PictureMgr_Mickey_Winnie(this, _gfx);
uint8 buffer[512];
@@ -2278,8 +2279,6 @@ void MickeyEngine::init() {
#endif
setFlag(VM_FLAG_SOUND_ON, true); // enable sound
-
- _picture->setPictureVersion(AGIPIC_PREAGI);
}
Common::Error MickeyEngine::go() {
diff --git a/engines/agi/preagi/mickey.h b/engines/agi/preagi/mickey.h
index d8839c76193..5fa2a2132cb 100644
--- a/engines/agi/preagi/mickey.h
+++ b/engines/agi/preagi/mickey.h
@@ -677,6 +677,7 @@ struct MSA_GAME {
};
class PreAgiEngine;
+class PictureMgr_Mickey_Winnie;
class MickeyEngine : public PreAgiEngine {
public:
@@ -692,7 +693,7 @@ public:
void drawObj(ENUM_MSA_OBJECT, int, int);
protected:
- PictureMgr *_picture;
+ PictureMgr_Mickey_Winnie *_picture;
MSA_GAME _gameStateMickey;
bool _clickToMove;
diff --git a/engines/agi/preagi/picture_mickey_winnie.cpp b/engines/agi/preagi/picture_mickey_winnie.cpp
new file mode 100644
index 00000000000..36e1dd32b4b
--- /dev/null
+++ b/engines/agi/preagi/picture_mickey_winnie.cpp
@@ -0,0 +1,293 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/graphics.h"
+#include "agi/picture.h"
+
+#include "agi/preagi/picture_mickey_winnie.h"
+
+namespace Agi {
+
+PictureMgr_Mickey_Winnie::PictureMgr_Mickey_Winnie(AgiBase *agi, GfxMgr *gfx) :
+ PictureMgr(agi, gfx) {
+
+ switch (agi->getPlatform()) {
+ case Common::kPlatformAmiga:
+ case Common::kPlatformDOS:
+ _isDosOrAmiga = true;
+ break;
+ default:
+ _isDosOrAmiga = false;
+ _minCommand = 0xe0;
+ break;
+ }
+
+ _xOffset = 0;
+ _yOffset = 0;
+ _maxStep = 0;
+}
+
+void PictureMgr_Mickey_Winnie::drawPicture() {
+ debugC(kDebugLevelPictures, "Drawing picture");
+
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
+ _patCode = 0;
+ _patNum = 0;
+ _priOn = false;
+ _scrOn = false;
+ _priColor = 4;
+
+ if (_isDosOrAmiga) {
+ _scrColor = 15;
+ drawPicture_DOS_Amiga();
+ } else {
+ _scrColor = 0;
+ drawPicture_A2_C64_CoCo();
+ }
+}
+
+void PictureMgr_Mickey_Winnie::drawPicture_DOS_Amiga() {
+ int step = 0;
+ while (_dataOffset < _dataSize) {
+ byte curByte = getNextByte();
+
+ switch (curByte) {
+ case 0xf0:
+ draw_SetColor();
+ _scrOn = true;
+ break;
+ case 0xf1:
+ _scrOn = false;
+ break;
+ case 0xf4:
+ yCorner();
+ break;
+ case 0xf5:
+ xCorner();
+ break;
+ case 0xf6:
+ draw_LineAbsolute();
+ break;
+ case 0xf7:
+ draw_LineShort();
+ break;
+ case 0xf8: {
+ // The screen-on flag does not prevent PreAGI flood fills.
+ // Winnie picture 7 (Roo) contains F1 before several fills.
+ byte prevScrOn = _scrOn;
+ _scrOn = true;
+ PictureMgr::draw_Fill();
+ _scrOn = prevScrOn;
+ break;
+ }
+ case 0xf9:
+ plotBrush();
+ break;
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode %02x at %04x", curByte, _dataOffset - 1);
+ break;
+ }
+
+ // Limit drawing to the optional maximum number of opcodes.
+ // Used by Mickey for crystal animation.
+ step++;
+ if (step == _maxStep) {
+ return;
+ }
+ }
+}
+
+void PictureMgr_Mickey_Winnie::drawPicture_A2_C64_CoCo() {
+ while (_dataOffset < _dataSize) {
+ byte curByte = getNextByte();
+
+ if ((curByte >= 0xF0) && (curByte <= 0xFE)) {
+ _scrColor = curByte & 0x0F;
+ continue;
+ }
+
+ switch (curByte) {
+ case 0xe0: // x-corner
+ xCorner();
+ break;
+ case 0xe1: // y-corner
+ yCorner();
+ break;
+ case 0xe2: // dynamic draw lines
+ draw_LineShort();
+ break;
+ case 0xe3: // absolute draw lines
+ draw_LineAbsolute();
+ break;
+ case 0xe4: // fill
+ draw_SetColor();
+ PictureMgr::draw_Fill();
+ break;
+ case 0xe5: // enable screen drawing
+ _scrOn = true;
+ break;
+ case 0xe6: // plot brush
+ plotBrush();
+ break;
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode %02x at %04x", curByte, _dataOffset - 1);
+ break;
+ }
+ }
+}
+
+void PictureMgr_Mickey_Winnie::plotBrush() {
+ _patCode = getNextByte();
+ if (_patCode > 12) {
+ _patCode = 12;
+ }
+
+ for (;;) {
+ byte x, y;
+ if (!getNextCoordinates(x, y))
+ break;
+
+ plotPattern(x, y);
+ }
+}
+
+void PictureMgr_Mickey_Winnie::plotPattern(byte x, byte y) {
+ // PreAGI patterns are 13 solid circles
+ static const byte circleData[] = {
+ 0x00,
+ 0x01, 0x01,
+ 0x01, 0x02, 0x02,
+ 0x01, 0x02, 0x03, 0x03,
+ 0x02, 0x03, 0x04, 0x04, 0x04,
+ 0x02, 0x03, 0x04, 0x05, 0x05, 0x05,
+ 0x02, 0x04, 0x05, 0x05, 0x06, 0x06, 0x06,
+ 0x02, 0x04, 0x05, 0x06, 0x06, 0x07, 0x07, 0x07,
+ 0x02, 0x04, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08, 0x08,
+ 0x03, 0x05, 0x06, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x03, 0x05, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x03, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c
+ };
+
+ int circleDataIndex = (_patCode * (_patCode + 1)) / 2;
+
+ // draw the circle by drawing its vertical lines two at a time, starting at the
+ // left and right edges and working inwards. circles have odd widths, so the
+ // final iteration draws the middle line twice.
+ for (int i = _patCode; i >= 0; i--) {
+ const byte height = circleData[circleDataIndex++];
+ int16 x1, y1, x2, y2;
+
+ // left vertical line
+ x1 = x - i;
+ x2 = x1;
+ y1 = y - height;
+ y2 = y + height;
+ draw_Line(x1, y1, x2, y2);
+
+ // right vertical line
+ x1 = x + i;
+ x2 = x1;
+ draw_Line(x1, y1, x2, y2);
+ }
+}
+
+/**
+ * Flood fills from a start position, with a clipped height.
+ */
+void PictureMgr_Mickey_Winnie::draw_Fill(int16 x, int16 y) {
+ // Flood fill does extra height clipping, and pictures rely on this.
+ // The get-coordinates routine clips to (139, 159) and then the
+ // flood fill routine checks if y >= 159 and decrements to 158.
+ // The flood fill clip is not in in Apple II/C64/CoCo versions
+ // of Winnie, as can be seen by the table edge being a different
+ // color than Winnie's shirt in the first room, but the same
+ // color as the shirt in DOS/Amiga. (Picture 28)
+ if (_isDosOrAmiga) {
+ if (y >= _height) { // 159
+ debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'y', y, _height - 1);
+ y = _height - 1; // 158
+ }
+ }
+
+ PictureMgr::draw_Fill(x, y);
+}
+
+/**
+ * Gets the next x coordinate in the current picture instruction,
+ * and clip it to the picture width. Many Winnie pictures contain
+ * out of bounds coordinates and rely on this clipping.
+ */
+bool PictureMgr_Mickey_Winnie::getNextXCoordinate(byte &x) {
+ if (!getNextParamByte(x)) {
+ return false;
+ }
+
+ if (_isDosOrAmiga) { // TODO: is this check in A2/C64/CoCo?
+ if (x >= _width) { // 140
+ debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'x', x, _width - 1);
+ x = _width - 1; // 139
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Gets the next y coordinate in the current picture instruction,
+ * and clip it to the picture height. Many Winnie pictures contain
+ * out of bounds coordinates and rely on this clipping.
+ */
+bool PictureMgr_Mickey_Winnie::getNextYCoordinate(byte &y) {
+ if (!getNextParamByte(y)) {
+ return false;
+ }
+
+ if (_isDosOrAmiga) { // TODO: is this check in A2/C64/CoCo?
+ // note that this is a different clip than for the x coordinate
+ if (y > _height) { // 159
+ debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'y', y, _height);
+ y = _height; // 159
+ }
+ }
+
+ return true;
+}
+
+bool PictureMgr_Mickey_Winnie::getGraphicsCoordinates(int16 &x, int16 &y) {
+ if (!PictureMgr::getGraphicsCoordinates(x, y)) {
+ return false;
+ }
+
+ x += _xOffset;
+ y += _yOffset;
+
+ // validate that the offset coordinates are within the screen's boundaries
+ return (x < SCRIPT_WIDTH && y < SCRIPT_HEIGHT);
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/preagi/picture_mickey_winnie.h b/engines/agi/preagi/picture_mickey_winnie.h
new file mode 100644
index 00000000000..169072af36a
--- /dev/null
+++ b/engines/agi/preagi/picture_mickey_winnie.h
@@ -0,0 +1,62 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PREAGI_PICTURE_MICKEY_WINNIE_H
+#define AGI_PREAGI_PICTURE_MICKEY_WINNIE_H
+
+namespace Agi {
+
+class PictureMgr_Mickey_Winnie : public PictureMgr {
+public:
+ PictureMgr_Mickey_Winnie(AgiBase *agi, GfxMgr *gfx);
+
+ void drawPicture() override;
+ void drawPicture_DOS_Amiga();
+ void drawPicture_A2_C64_CoCo();
+
+ void plotPattern(byte x, byte y) override;
+ void plotBrush() override;
+
+ void draw_Fill(int16 x, int16 y) override;
+
+ bool getNextXCoordinate(byte &x) override;
+ bool getNextYCoordinate(byte &y) override;
+
+ bool getGraphicsCoordinates(int16 &x, int16 &y) override;
+
+ void setOffset(int xOffset, int yOffset) {
+ _xOffset = xOffset;
+ _yOffset = yOffset;
+ }
+
+ void setMaxStep(int maxStep) { _maxStep = maxStep; }
+ int getMaxStep() const { return _maxStep; }
+
+private:
+ bool _isDosOrAmiga;
+ int16 _xOffset;
+ int16 _yOffset;
+ int _maxStep; // Max opcodes to draw, zero for all. Used by Mickey
+};
+
+} // End of namespace Agi
+
+#endif
diff --git a/engines/agi/preagi/winnie.cpp b/engines/agi/preagi/winnie.cpp
index 7a6cce55980..57eb23f9a17 100644
--- a/engines/agi/preagi/winnie.cpp
+++ b/engines/agi/preagi/winnie.cpp
@@ -20,6 +20,7 @@
*/
#include "agi/preagi/preagi.h"
+#include "agi/preagi/picture_mickey_winnie.h"
#include "agi/preagi/winnie.h"
#include "agi/graphics.h"
@@ -1477,7 +1478,7 @@ WinnieEngine::~WinnieEngine() {
}
void WinnieEngine::init() {
- _picture = new PictureMgr(this, _gfx);
+ _picture = new PictureMgr_Mickey_Winnie(this, _gfx);
// Initialize sound
@@ -1523,17 +1524,6 @@ void WinnieEngine::init() {
break;
}
- switch (getPlatform()) {
- case Common::kPlatformApple2:
- case Common::kPlatformC64:
- case Common::kPlatformCoCo:
- _picture->setPictureVersion(AGIPIC_C64);
- break;
- default:
- _picture->setPictureVersion(AGIPIC_PREAGI);
- break;
- }
-
hotspotNorth = Common::Rect(20, 0, (IDI_WTP_PIC_WIDTH + 10) * 2, 10);
hotspotSouth = Common::Rect(20, IDI_WTP_PIC_HEIGHT - 10, (IDI_WTP_PIC_WIDTH + 10) * 2, IDI_WTP_PIC_HEIGHT);
hotspotEast = Common::Rect(IDI_WTP_PIC_WIDTH * 2, 0, (IDI_WTP_PIC_WIDTH + 10) * 2, IDI_WTP_PIC_HEIGHT);
diff --git a/engines/agi/preagi/winnie.h b/engines/agi/preagi/winnie.h
index 4f197b847cc..d7244724c89 100644
--- a/engines/agi/preagi/winnie.h
+++ b/engines/agi/preagi/winnie.h
@@ -293,6 +293,7 @@ struct WTP_SAVE_GAME {
};
class PreAgiEngine;
+class PictureMgr_Mickey_Winnie;
class WinnieEngine : public PreAgiEngine {
public:
@@ -305,7 +306,7 @@ public:
void debugCurRoom();
private:
- PictureMgr *_picture;
+ PictureMgr_Mickey_Winnie *_picture;
WTP_SAVE_GAME _gameStateWinnie;
int _room;
Commit: 250327316c932d7f9351fd4707e85a11209c3295
https://github.com/scummvm/scummvm/commit/250327316c932d7f9351fd4707e85a11209c3295
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:57-07:00
Commit Message:
AGI: PREAGI: Subclass PictureMgr for TROLL
Changed paths:
A engines/agi/preagi/picture_troll.cpp
A engines/agi/preagi/picture_troll.h
engines/agi/module.mk
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/troll.cpp
engines/agi/preagi/troll.h
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index 0dc795d6599..fce6dbb921c 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -42,6 +42,7 @@ MODULE_OBJS := \
preagi/preagi.o \
preagi/mickey.o \
preagi/picture_mickey_winnie.o \
+ preagi/picture_troll.o \
preagi/troll.o \
preagi/winnie.o
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index b8f229492e2..2525ce726d2 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -45,7 +45,6 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_minCommand = 0xf0;
- _pictureVersion = AGIPIC_V2;
_width = 0;
_height = 0;
@@ -310,6 +309,8 @@ void PictureMgr::plotBrush() {
** Draw AGI picture
**************************************************************************/
void PictureMgr::drawPicture() {
+ debugC(kDebugLevelPictures, "Drawing picture %d", _resourceNr);
+
_dataOffset = 0;
_dataOffsetNibble = false;
_patCode = 0;
@@ -319,68 +320,6 @@ void PictureMgr::drawPicture() {
_scrColor = 15;
_priColor = 4;
- switch (_pictureVersion) {
- case AGIPIC_V15:
- drawPictureV15();
- break;
- case AGIPIC_V2:
- drawPictureV2();
- break;
- default:
- break;
- }
-}
-
-void PictureMgr::drawPictureV15() {
- debugC(kDebugLevelPictures, "Drawing V1.5 picture");
-
- while (_dataOffset < _dataSize) {
- byte curByte = getNextByte();
-
- switch (curByte) {
- case 0xf0:
- // happens in all Troll's Tale pictures
- // TODO: figure out what it was meant for
- break;
- case 0xf1:
- draw_SetColor();
- _scrOn = true;
- break;
- case 0xf3:
- if (_flags & kPicFf3Stop)
- return;
- break;
- case 0xf8:
- yCorner(true);
- break;
- case 0xf9:
- xCorner(true);
- break;
- case 0xfa:
- // TODO: is this really correct?
- draw_LineAbsolute();
- break;
- case 0xfb:
- // TODO: is this really correct?
- draw_LineAbsolute();
- break;
- case 0xfe:
- draw_SetColor();
- _scrOn = true;
- draw_Fill();
- break;
- case 0xff: // end of data
- return;
- default:
- warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
- break;
- }
- }
-}
-
-void PictureMgr::drawPictureV2() {
- debugC(kDebugLevelPictures, "Drawing V2/V3 picture");
-
// AGIv3 nibble parameters are indicated by a flag in the picture's directory entry
bool nibbleMode = (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) != 0;
@@ -434,7 +373,7 @@ void PictureMgr::drawPictureV2() {
case 0xff: // end of data
return;
default:
- warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1);
+ warning("Unknown picture opcode %02x at %04x", curByte, _dataOffset - 1);
break;
}
}
@@ -820,9 +759,4 @@ void PictureMgr::showPictureWithTransition() {
_gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT);
}
-void PictureMgr::setPictureVersion(AgiPictureVersion version) {
- _pictureVersion = version;
- _minCommand = 0xf0;
-}
-
} // End of namespace Agi
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index 1a25491f985..b49f5627a78 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -42,11 +42,6 @@ struct AgiPicture {
AgiPicture() { reset(); }
};
-enum AgiPictureVersion {
- AGIPIC_V15, // Troll (DOS)
- AGIPIC_V2 // AGIv2, AGIv3
-};
-
enum AgiPictureFlags {
kPicFNone = (1 << 0),
kPicFf3Stop = (1 << 1), // Troll, certain pictures
@@ -89,8 +84,6 @@ public:
protected:
virtual void drawPicture();
- void drawPictureV15();
- void drawPictureV2();
void drawPictureAGI256();
void draw_SetColor();
@@ -110,8 +103,6 @@ public:
void showPicture(int16 x = 0, int16 y = 0, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void showPictureWithTransition();
- void setPictureVersion(AgiPictureVersion version);
-
void setPictureFlags(int flags) { _flags = flags; }
protected:
@@ -130,7 +121,6 @@ protected:
uint8 _minCommand;
- AgiPictureVersion _pictureVersion;
int16 _width;
int16 _height;
diff --git a/engines/agi/preagi/picture_troll.cpp b/engines/agi/preagi/picture_troll.cpp
new file mode 100644
index 00000000000..123c46b2b84
--- /dev/null
+++ b/engines/agi/preagi/picture_troll.cpp
@@ -0,0 +1,90 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/graphics.h"
+#include "agi/picture.h"
+
+#include "agi/preagi/picture_troll.h"
+
+namespace Agi {
+
+PictureMgr_Troll::PictureMgr_Troll(AgiBase *agi, GfxMgr *gfx) :
+ PictureMgr(agi, gfx) {
+}
+
+void PictureMgr_Troll::drawPicture() {
+ debugC(kDebugLevelPictures, "Drawing picture");
+
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
+ _patCode = 0;
+ _patNum = 0;
+ _priOn = false;
+ _scrOn = false;
+ _priColor = 4;
+ _scrColor = 15;
+
+ while (_dataOffset < _dataSize) {
+ byte curByte = getNextByte();
+
+ switch (curByte) {
+ case 0xf0:
+ // happens in all Troll's Tale pictures
+ // TODO: figure out what it was meant for
+ break;
+ case 0xf1:
+ draw_SetColor();
+ _scrOn = true;
+ break;
+ case 0xf3:
+ if (_flags & kPicFf3Stop)
+ return;
+ break;
+ case 0xf8:
+ yCorner(true);
+ break;
+ case 0xf9:
+ xCorner(true);
+ break;
+ case 0xfa:
+ // TODO: is this really correct?
+ draw_LineAbsolute();
+ break;
+ case 0xfb:
+ // TODO: is this really correct?
+ draw_LineAbsolute();
+ break;
+ case 0xfe:
+ draw_SetColor();
+ _scrOn = true;
+ draw_Fill();
+ break;
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode %02x at %04x", curByte, _dataOffset - 1);
+ break;
+ }
+ }
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/preagi/picture_troll.h b/engines/agi/preagi/picture_troll.h
new file mode 100644
index 00000000000..e52617ee2b3
--- /dev/null
+++ b/engines/agi/preagi/picture_troll.h
@@ -0,0 +1,36 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PREAGI_PICTURE_TROLL_H
+#define AGI_PREAGI_PICTURE_TROLL_H
+
+namespace Agi {
+
+class PictureMgr_Troll : public PictureMgr {
+public:
+ PictureMgr_Troll(AgiBase *agi, GfxMgr *gfx);
+
+ void drawPicture() override;
+};
+
+} // End of namespace Agi
+
+#endif
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 685edda8303..8c52fb497fd 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -20,6 +20,7 @@
*/
#include "agi/preagi/preagi.h"
+#include "agi/preagi/picture_troll.h"
#include "agi/preagi/troll.h"
#include "agi/graphics.h"
@@ -715,8 +716,7 @@ void TrollEngine::fillOffsets() {
// Init
void TrollEngine::init() {
- _picture = new PictureMgr(this, _gfx);
- _picture->setPictureVersion(AGIPIC_V15);
+ _picture = new PictureMgr_Troll(this, _gfx);
//SetScreenPar(320, 200, (char *)ibm_fontdata);
const int gaps[] = { 0x3A40, 0x4600, 0x4800, 0x5800, 0x5a00, 0x6a00,
diff --git a/engines/agi/preagi/troll.h b/engines/agi/preagi/troll.h
index 041c4626466..699b241c2b9 100644
--- a/engines/agi/preagi/troll.h
+++ b/engines/agi/preagi/troll.h
@@ -156,6 +156,8 @@ struct Item {
char name[16];
};
+class PictureMgr_Troll;
+
class TrollEngine : public PreAgiEngine {
public:
TrollEngine(OSystem *syst, const AGIGameDescription *gameDesc);
@@ -164,7 +166,7 @@ public:
Common::Error go() override;
private:
- PictureMgr *_picture;
+ PictureMgr_Troll *_picture;
int _roomPicture;
int _treasuresLeft;
Commit: 1a3c632b47914a4f6b210e88bc9ea8abcd0905bb
https://github.com/scummvm/scummvm/commit/1a3c632b47914a4f6b210e88bc9ea8abcd0905bb
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:57-07:00
Commit Message:
AGI: PREAGI: Implement TROLL flood fill
Fixes missing or incomplete flood fills when drawing the Troll
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/picture_troll.cpp
engines/agi/preagi/picture_troll.h
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 2525ce726d2..500f7ce31bd 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -47,8 +47,6 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_width = 0;
_height = 0;
-
- _flags = 0;
}
void PictureMgr::putVirtPixel(int16 x, int16 y) {
@@ -608,19 +606,19 @@ void PictureMgr::draw_Fill(int16 x, int16 y) {
while (!stack.empty()) {
Common::Point p = stack.pop();
- if (!draw_FillCheck(p.x, p.y))
+ if (!draw_FillCheck(p.x, p.y, false))
continue;
// Scan for left border
uint c;
- for (c = p.x - 1; draw_FillCheck(c, p.y); c--)
+ for (c = p.x - 1; draw_FillCheck(c, p.y, true); c--)
;
bool newspanUp = true;
bool newspanDown = true;
- for (c++; draw_FillCheck(c, p.y); c++) {
+ for (c++; draw_FillCheck(c, p.y, true); c++) {
putVirtPixel(c, p.y);
- if (draw_FillCheck(c, p.y - 1)) {
+ if (draw_FillCheck(c, p.y - 1, false)) {
if (newspanUp) {
stack.push(Common::Point(c, p.y - 1));
newspanUp = false;
@@ -629,7 +627,7 @@ void PictureMgr::draw_Fill(int16 x, int16 y) {
newspanUp = true;
}
- if (draw_FillCheck(c, p.y + 1)) {
+ if (draw_FillCheck(c, p.y + 1, false)) {
if (newspanDown) {
stack.push(Common::Point(c, p.y + 1));
newspanDown = false;
@@ -641,7 +639,7 @@ void PictureMgr::draw_Fill(int16 x, int16 y) {
}
}
-bool PictureMgr::draw_FillCheck(int16 x, int16 y) {
+bool PictureMgr::draw_FillCheck(int16 x, int16 y, bool horizontalCheck) {
if (!getGraphicsCoordinates(x, y)) {
return false;
}
@@ -649,9 +647,6 @@ bool PictureMgr::draw_FillCheck(int16 x, int16 y) {
byte screenColor = _gfx->getColor(x, y);
byte screenPriority = _gfx->getPriority(x, y);
- if (_flags & kPicFTrollMode)
- return ((screenColor != 11) && (screenColor != _scrColor));
-
if (!_priOn && _scrOn && _scrColor != 15)
return (screenColor == 15);
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index b49f5627a78..a222e50f6ea 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -42,19 +42,10 @@ struct AgiPicture {
AgiPicture() { reset(); }
};
-enum AgiPictureFlags {
- kPicFNone = (1 << 0),
- kPicFf3Stop = (1 << 1), // Troll, certain pictures
- kPicFTrollMode = (1 << 2) // Troll, drawing the Troll
-};
-
class AgiBase;
class GfxMgr;
class PictureMgr {
- AgiBase *_vm;
- GfxMgr *_gfx;
-
public:
PictureMgr(AgiBase *agi, GfxMgr *gfx);
virtual ~PictureMgr() { }
@@ -95,17 +86,18 @@ protected:
void draw_LineShort();
void draw_LineAbsolute();
- bool draw_FillCheck(int16 x, int16 y);
+ virtual bool draw_FillCheck(int16 x, int16 y, bool horizontalCheck);
virtual void draw_Fill(int16 x, int16 y);
- void draw_Fill();
+ virtual void draw_Fill();
public:
void showPicture(int16 x = 0, int16 y = 0, int16 width = _DEFAULT_WIDTH, int16 height = _DEFAULT_HEIGHT);
void showPictureWithTransition();
- void setPictureFlags(int flags) { _flags = flags; }
-
protected:
+ AgiBase *_vm;
+ GfxMgr *_gfx;
+
int16 _resourceNr;
uint8 *_data;
uint32 _dataSize;
@@ -123,8 +115,6 @@ protected:
int16 _width;
int16 _height;
-
- int _flags;
};
} // End of namespace Agi
diff --git a/engines/agi/preagi/picture_troll.cpp b/engines/agi/preagi/picture_troll.cpp
index 123c46b2b84..1f2c01a665d 100644
--- a/engines/agi/preagi/picture_troll.cpp
+++ b/engines/agi/preagi/picture_troll.cpp
@@ -56,7 +56,7 @@ void PictureMgr_Troll::drawPicture() {
_scrOn = true;
break;
case 0xf3:
- if (_flags & kPicFf3Stop)
+ if (_stopOnF3)
return;
break;
case 0xf8:
@@ -74,8 +74,6 @@ void PictureMgr_Troll::drawPicture() {
draw_LineAbsolute();
break;
case 0xfe:
- draw_SetColor();
- _scrOn = true;
draw_Fill();
break;
case 0xff: // end of data
@@ -87,4 +85,84 @@ void PictureMgr_Troll::drawPicture() {
}
}
+/**
+ * Flood fills from a series of start positions.
+ *
+ * Troll's Tale contains two separate flood fill implementations to handle the
+ * special case of drawing the Troll. The game sets a global before drawing to
+ * to activate Troll mode. We implement this by overriding this method and
+ * the check method.
+ */
+void PictureMgr_Troll::draw_Fill() {
+ draw_SetColor();
+ _scrOn = true;
+
+ byte x, y;
+ if (_scrColor == 15) { // white
+ // White flood fills are only allowed when drawing the Troll, otherwise they
+ // are completely ignored. Several room pictures contain white flood fills.
+ while (getNextCoordinates(x, y)) {
+ if (_trollMode) {
+ PictureMgr::draw_Fill(x, y);
+ }
+ }
+ } else {
+ // When not drawing the Troll, do a regular flood fill.
+ // When drawing the Troll, first fill with white, and then fill normally.
+ byte fillColor = _scrColor;
+ while (getNextCoordinates(x, y)) {
+ if (_trollMode) {
+ _scrColor = 15; // white
+ PictureMgr::draw_Fill(x, y);
+ _scrColor = fillColor;
+ }
+ PictureMgr::draw_Fill(x, y);
+ }
+ }
+}
+
+/**
+ * Checks if flood fill is allowed at a position.
+ *
+ * Troll's Tale contains two separate flood fill implementations to handle the
+ * special case of drawing the Troll. The game sets a global before drawing to
+ * to activate Troll mode.
+ *
+ * The Troll is a large picture with flood fills that is drawn over many busy
+ * room pictures, and always in the same location. This is a problem because the
+ * picture format is only meant to fill white areas. Sierra handled this by
+ * reserving a color for the Troll's lines (11, light blue) and implementing a
+ * second set of routines that fill white and treat the Troll's color as a
+ * boundary, and sometimes white as well. When drawing the Troll, a non-white
+ * fill is preceded by a special white fill to clear the area. This does not
+ * work well if there are existing white pixels, and rooms do have these.
+ * The Troll has incomplete fills in these rooms, but this is original behavior.
+ * In some rooms, such as those with white checkered floors, the results are
+ * so bad that Sierra added them to the list of rooms the Troll never visits.
+ *
+ * We implement Troll mode without a second flood fill algorithm; instead we
+ * override the check method, and our AGI algorithm in the base class provides
+ * the context so we know which of the two color checks to use.
+ */
+bool PictureMgr_Troll::draw_FillCheck(int16 x, int16 y, bool horizontalCheck) {
+ if (_trollMode && _scrColor == 15) {
+ if (!getGraphicsCoordinates(x, y)) {
+ return false;
+ }
+
+ byte screenColor = _gfx->getColor(x, y);
+
+ // when filling white during troll mode...
+ if (horizontalCheck) {
+ // horizontal checks only stop on troll line color
+ return (screenColor != 11);
+ } else {
+ // all other checks stop on troll line color or white
+ return (screenColor != 11) && (screenColor != 15);
+ }
+ }
+
+ return PictureMgr::draw_FillCheck(x, y, horizontalCheck);
+}
+
} // End of namespace Agi
diff --git a/engines/agi/preagi/picture_troll.h b/engines/agi/preagi/picture_troll.h
index e52617ee2b3..e389be9c3c8 100644
--- a/engines/agi/preagi/picture_troll.h
+++ b/engines/agi/preagi/picture_troll.h
@@ -29,6 +29,16 @@ public:
PictureMgr_Troll(AgiBase *agi, GfxMgr *gfx);
void drawPicture() override;
+
+ void draw_Fill() override;
+ bool draw_FillCheck(int16 x, int16 y, bool horizontalCheck) override;
+
+ void setStopOnF3(bool stopOnF3) { _stopOnF3 = stopOnF3; }
+ void setTrollMode(bool trollMode) { _trollMode = trollMode; }
+
+private:
+ bool _stopOnF3;
+ bool _trollMode;
};
} // End of namespace Agi
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 8c52fb497fd..24b15b42e7b 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -137,19 +137,13 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {
}
// draw the frame picture
- _picture->setPictureFlags(kPicFNone);
+ _picture->setStopOnF3(false);
+ _picture->setTrollMode(false);
_picture->decodePictureFromBuffer(_gameData + IDO_TRO_FRAMEPIC, 4096, clr, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
// draw the picture
- int flags = 0;
- if (!f3IsCont) {
- // stop on opcode F3
- flags |= kPicFf3Stop;
- }
- if (troll) {
- flags |= kPicFTrollMode;
- }
- _picture->setPictureFlags(flags);
+ _picture->setStopOnF3(!f3IsCont);
+ _picture->setTrollMode(troll);
_picture->decodePictureFromBuffer(_gameData + _pictureOffsets[iPic], 4096, false, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
_picture->showPicture(0, 0, IDI_TRO_PIC_WIDTH, IDI_TRO_PIC_HEIGHT);
Commit: f01a114c4924d8d19ad1c5d31a14a4f27ad2bbd3
https://github.com/scummvm/scummvm/commit/f01a114c4924d8d19ad1c5d31a14a4f27ad2bbd3
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:57-07:00
Commit Message:
AGI: Add support for early KQ1 pictures
Changed paths:
A engines/agi/picture_gal.cpp
A engines/agi/picture_gal.h
engines/agi/module.mk
engines/agi/picture.cpp
engines/agi/picture.h
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index fce6dbb921c..b79da837f3c 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -26,6 +26,7 @@ MODULE_OBJS := \
op_dbg.o \
op_test.o \
picture.o \
+ picture_gal.o \
saveload.o \
sound.o \
sound_2gs.o \
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 500f7ce31bd..51f44a25225 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -673,7 +673,7 @@ void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256,
_height = height;
if (clearScreen) {
- _gfx->clear(15, 4); // white, priority 4
+ _gfx->clear(15, getInitialPriorityColor()); // white, priority 4 or 1
}
if (!agi256) {
@@ -704,7 +704,7 @@ void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearSc
_height = height;
if (clearScreen) {
- _gfx->clear(15, 4); // white, priority 4
+ _gfx->clear(15, getInitialPriorityColor()); // white, priority 4 or 1
}
drawPicture();
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index a222e50f6ea..cb28e72ff5b 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -53,6 +53,8 @@ public:
int16 getResourceNr() const { return _resourceNr; };
protected:
+ virtual byte getInitialPriorityColor() const { return 4; }
+
void putVirtPixel(int16 x, int16 y);
void xCorner(bool skipOtherCoords = false);
void yCorner(bool skipOtherCoords = false);
@@ -82,7 +84,7 @@ protected:
void draw_SetNibbleColor();
void draw_SetNibblePriority();
- void draw_Line(int16 x1, int16 y1, int16 x2, int16 y2);
+ virtual void draw_Line(int16 x1, int16 y1, int16 x2, int16 y2);
void draw_LineShort();
void draw_LineAbsolute();
diff --git a/engines/agi/picture_gal.cpp b/engines/agi/picture_gal.cpp
new file mode 100644
index 00000000000..e083fc08d5b
--- /dev/null
+++ b/engines/agi/picture_gal.cpp
@@ -0,0 +1,267 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/graphics.h"
+#include "agi/picture.h"
+
+#include "agi/picture_gal.h"
+
+namespace Agi {
+
+// PictureMgr_GAL decodes and draws picture resources in early King's Quest 1.
+//
+// This "Game Adaptation Language" format was used in PC Booters and Apple II.
+//
+// This format supports lines and flood fills, and visual and priority screens.
+//
+// As this is the format that evolved into AGI, it is quite similar apart from
+// the specific opcodes. The major difference is the line drawing routine;
+// it produces different results than AGI. Flood fills implicitly rely on this.
+// When KQ1 was ported to AGI, the new lines prevented some fills from working,
+// so they just added more. There are still a few white pixels they missed.
+//
+// As with Troll's Tale, room pictures depend on the game first drawing a frame
+// within the entire picture area. When KQ1 was converted to AGI, this frame
+// was added to each individual picture resource.
+
+PictureMgr_GAL::PictureMgr_GAL(AgiBase *agi, GfxMgr *gfx) :
+ PictureMgr(agi, gfx) {
+}
+
+void PictureMgr_GAL::drawPicture() {
+ debugC(kDebugLevelPictures, "Drawing picture %d", _resourceNr);
+
+ drawBlackFrame();
+
+ _dataOffset = 0;
+ _dataOffsetNibble = false;
+ _patCode = 0;
+ _patNum = 0;
+ _priOn = true; // initially off in AGI
+ _scrOn = false;
+ _scrColor = 15;
+ _priColor = 1;
+
+ // GAL toggles the current screen between visual and priority
+ // with opcode F0. This affects opcodes F4-F7, but the rest of
+ // the opcodes are explicit about which screen(s) they draw to.
+ byte prevScrOn = _scrOn;
+ byte prevPriOn = _priOn;
+
+ while (_dataOffset < _dataSize) {
+ byte curByte = getNextByte();
+
+ switch (curByte) {
+ case 0xf0: // toggle current screen
+ draw_SetScreens(!_scrOn, !_priOn);
+ break;
+ case 0xf1:
+ draw_SetColor();
+ break;
+ case 0xf2:
+ draw_SetPriority();
+ break;
+ case 0xf3:
+ draw_SetColor();
+ draw_SetPriority();
+ break;
+
+ // Line operations drawn to both visual and priority screens
+ case 0xf4:
+ draw_SetScreens(true, true, prevScrOn, prevPriOn);
+ yCorner();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+ case 0xf5:
+ draw_SetScreens(true, true, prevScrOn, prevPriOn);
+ xCorner();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+ case 0xf6:
+ draw_SetScreens(true, true, prevScrOn, prevPriOn);
+ draw_LineAbsolute();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+ case 0xf7:
+ draw_SetScreens(true, true, prevScrOn, prevPriOn);
+ draw_LineShort();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+
+ // Line operations drawn to the current screen
+ case 0xf8:
+ yCorner();
+ break;
+ case 0xf9:
+ xCorner();
+ break;
+ case 0xfa:
+ draw_LineAbsolute();
+ break;
+ case 0xfb:
+ draw_LineShort();
+ break;
+
+ // Fill operations drawn to one or both screens
+ case 0xfc:
+ draw_SetScreens(true, true, prevScrOn, prevPriOn);
+ draw_SetColor();
+ draw_SetPriority();
+ draw_Fill();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+ case 0xfd:
+ draw_SetScreens(false, true, prevScrOn, prevPriOn);
+ draw_SetPriority();
+ draw_Fill();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+ case 0xfe:
+ draw_SetScreens(true, false, prevScrOn, prevPriOn);
+ draw_SetColor();
+ draw_Fill();
+ draw_SetScreens(prevScrOn, prevPriOn);
+ break;
+
+ case 0xff: // end of data
+ return;
+ default:
+ warning("Unknown picture opcode %02x at %04x", curByte, _dataOffset - 1);
+ break;
+ }
+ }
+}
+
+/**
+ * Sets the status of the visual and priority screens.
+ */
+void PictureMgr_GAL::draw_SetScreens(byte scrOn, byte priOn) {
+ _scrOn = scrOn;
+ _priOn = priOn;
+}
+
+/**
+ * Sets the status of the visual and priority screens,
+ * and returns their previous values.
+ */
+void PictureMgr_GAL::draw_SetScreens(byte scrOn, byte priOn, byte &prevScrOn, byte &prevPriOn) {
+ prevScrOn = _scrOn;
+ prevPriOn = _priOn;
+ _scrOn = scrOn;
+ _priOn = priOn;
+}
+
+/**
+ * Draws a hard-coded black frame in both screens.
+ * All room pictures require this to draw correctly.
+ *
+ * Original data: F3 00 00 F5 00 00 9F A7 00 00 FF
+ */
+void PictureMgr_GAL::drawBlackFrame() {
+ _scrOn = true;
+ _scrColor = 0;
+ _priOn = true;
+ _priColor = 0;
+ draw_Line(0, 0, _width - 1, 0);
+ draw_Line(_width - 1, 0, _width - 1, _height - 1);
+ draw_Line(_width - 1, _height - 1, 0, _height - 1);
+ draw_Line(0, _height - 1, 0, 0);
+}
+
+/**
+ * Draws a horizontal, vertical, or diagonal line using the GAL drawing routine.
+ *
+ * This routine produces different diagonal lines than the AGI routine.
+ */
+void PictureMgr_GAL::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) {
+ x1 = CLIP<int16>(x1, 0, _width - 1);
+ x2 = CLIP<int16>(x2, 0, _width - 1);
+ y1 = CLIP<int16>(y1, 0, _height - 1);
+ y2 = CLIP<int16>(y2, 0, _height - 1);
+
+ const byte width = (x2 > x1) ? (x2 - x1) : (x1 - x2);
+ const byte height = (y2 > y1) ? (y2 - y1) : (y1 - y2);
+
+ byte x = 0;
+ byte y = 0;
+ if (width > height) {
+ while (x != width) {
+ x++;
+ y = (x * height) / width;
+ if (((x * height) % width) * 2 > width) {
+ y++;
+ }
+
+ byte pixelX = (x2 > x1) ? (x1 + x) : (x1 - x);
+ byte pixelY = (y2 > y1) ? (y1 + y) : (y1 - y);
+ putVirtPixel(pixelX, pixelY);
+ }
+ } else {
+ while (y != height) {
+ y++;
+ x = (y * width) / height;
+ if (((y * width) % height) * 2 > height) {
+ x++;
+ }
+
+ byte pixelX = (x2 > x1) ? (x1 + x) : (x1 - x);
+ byte pixelY = (y2 > y1) ? (y1 + y) : (y1 - y);
+ putVirtPixel(pixelX, pixelY);
+ }
+ }
+}
+
+/**
+ * Gets the next x coordinate in the current picture instruction,
+ * and clip it to the picture width.
+ */
+bool PictureMgr_GAL::getNextXCoordinate(byte &x) {
+ if (!getNextParamByte(x)) {
+ return false;
+ }
+
+ if (x >= _width) { // 160
+ debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'x', x, _width - 1);
+ x = _width - 1; // 159
+ }
+
+ return true;
+}
+
+/**
+ * Gets the next y coordinate in the current picture instruction,
+ * and clip it to the picture height.
+ */
+bool PictureMgr_GAL::getNextYCoordinate(byte &y) {
+ if (!getNextParamByte(y)) {
+ return false;
+ }
+
+ if (y >= _height) { // 168
+ debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'y', y, _height);
+ y = _height - 1; // 167
+ }
+
+ return true;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/picture_gal.h b/engines/agi/picture_gal.h
new file mode 100644
index 00000000000..de97586280a
--- /dev/null
+++ b/engines/agi/picture_gal.h
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_PICTURE_GAL_H
+#define AGI_PICTURE_GAL_H
+
+namespace Agi {
+
+class PictureMgr_GAL : public PictureMgr {
+public:
+ PictureMgr_GAL(AgiBase *agi, GfxMgr *gfx);
+
+protected:
+ byte getInitialPriorityColor() const override { return 1; }
+
+ void drawPicture() override;
+
+ void draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) override;
+
+ bool getNextXCoordinate(byte &x) override;
+ bool getNextYCoordinate(byte &y) override;
+
+private:
+ void draw_SetScreens(byte scrOn, byte priOn);
+ void draw_SetScreens(byte scrOn, byte priOn, byte &prevScrOn, byte &prevPriOn);
+
+ void drawBlackFrame();
+};
+
+} // End of namespace Agi
+
+#endif
Commit: ef74701e8fd7adfd5fe674c7542c5b2da4311723
https://github.com/scummvm/scummvm/commit/ef74701e8fd7adfd5fe674c7542c5b2da4311723
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:57-07:00
Commit Message:
AGI: Document PictureMgr
Changed paths:
engines/agi/picture.cpp
engines/agi/picture.h
engines/agi/preagi/picture_mickey_winnie.cpp
engines/agi/preagi/picture_troll.cpp
diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp
index 51f44a25225..70ce126d9bf 100644
--- a/engines/agi/picture.cpp
+++ b/engines/agi/picture.cpp
@@ -25,6 +25,24 @@
#include "common/textconsole.h"
namespace Agi {
+
+// PictureMgr decodes and draws AGI picture resources.
+//
+// AGI pictures are vector-based, and contain the visual and priority screens.
+// Drawing instructions begin with an opcode byte within the range F0-FF.
+// Opcode parameters are each one byte, with the exception of AGIv3 nibble
+// compression. Some opcodes take a variable number of parameters. The end of
+// an instruction is detected by the next byte with a value in the opcode range.
+// If an instruction has extra bytes, or a picture contains an unknown opcode
+// byte, then these bytes ignored. Pictures end with opcode FF.
+//
+// AGIv3 introduced a compression scheme where two opcode parameters were
+// each reduced to one nibble; this is indicated by a flag in the picture's
+// resource directory entry.
+//
+// AGI's picture format evolved from variants used in earlier Sierra games.
+// We implement support for these formats as subclasses of PictureMgr.
+// In this way, we treat AGI as the baseline to be overridden as needed.
PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_vm = agi;
@@ -49,6 +67,9 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {
_height = 0;
}
+/**
+ * Draws a pixel to the visual and/or control screen.
+ */
void PictureMgr::putVirtPixel(int16 x, int16 y) {
if (!getGraphicsCoordinates(x, y)) {
return;
@@ -63,6 +84,9 @@ void PictureMgr::putVirtPixel(int16 x, int16 y) {
_gfx->putPixel(x, y, drawMask, _scrColor, _priColor);
}
+/**
+ * Gets the next byte in the picture data.
+ */
byte PictureMgr::getNextByte() {
if (!_dataOffsetNibble) {
return _data[_dataOffset++];
@@ -72,6 +96,11 @@ byte PictureMgr::getNextByte() {
}
}
+/**
+ * Gets the next byte in the current picture instruction.
+ * If the next byte in the picture data is an opcode, then this
+ * function returns false and the data offset is not advanced.
+ */
bool PictureMgr::getNextParamByte(byte &b) {
byte value = getNextByte();
if (value >= _minCommand) {
@@ -82,6 +111,9 @@ bool PictureMgr::getNextParamByte(byte &b) {
return true;
}
+/**
+ * Gets the next nibble in the picture data.
+ */
byte PictureMgr::getNextNibble() {
if (!_dataOffsetNibble) {
_dataOffsetNibble = true;
@@ -92,27 +124,57 @@ byte PictureMgr::getNextNibble() {
}
}
+/**
+ * Gets the next x coordinate in the current picture instruction.
+ *
+ * Subclasses override this to implement coordinate clipping.
+ */
bool PictureMgr::getNextXCoordinate(byte &x) {
return getNextParamByte(x);
}
+/**
+ * Gets the next y coordinate in the current picture instruction.
+ *
+ * Subclasses override this to implement coordinate clipping.
+ */
bool PictureMgr::getNextYCoordinate(byte &y) {
return getNextParamByte(y);
}
+/**
+ * Gets the next x and y coordinates in the current picture instruction.
+ *
+ * Returns false if both coordinates are not present. If only an x coordinate is
+ * present, then the data offset is only advanced by one, and the x coordinate
+ * will be ignored.
+ */
bool PictureMgr::getNextCoordinates(byte &x, byte &y) {
return getNextXCoordinate(x) && getNextYCoordinate(y);
}
+/**
+ * Validates picture coordinates and translates them to GfxMgr coordinates.
+ *
+ * Subclasses can override this to implement the PreAGI offset feature that
+ * allowed a picture to be drawn at an arbitrary point on the screen.
+ * Returns false if picture coordinates are out of bounds, or for subclasses,
+ * if the PreAGI offset would place the coordinate outside of GfxMgr's screen.
+ */
bool PictureMgr::getGraphicsCoordinates(int16 &x, int16 &y) {
return (0 <= x && x < _width && 0 <= y && y < _height);
}
-/**************************************************************************
-** xCorner
-**
-** Draws an xCorner (drawing action 0xF5)
-**************************************************************************/
+/**
+ * xCorner
+ *
+ * Draws a series of lines with right angles between them.
+ * The first two bytes are the start point, followed by alternating
+ * x and y coordinates for subsequent points.
+ *
+ * Set skipOtherCoords to ignore extra coordinates in Troll's pictures.
+ * Troll includes both the x and y coordinate of each point.
+ */
void PictureMgr::xCorner(bool skipOtherCoords) {
byte x1, x2, y1, y2, dummy;
@@ -144,11 +206,16 @@ void PictureMgr::xCorner(bool skipOtherCoords) {
}
}
-/**************************************************************************
-** yCorner
-**
-** Draws an yCorner (drawing action 0xF4)
-**************************************************************************/
+/**
+ * yCorner
+ *
+ * Draws a series of lines with right angles between them.
+ * The first two bytes are the start point, followed by alternating
+ * y and x coordinates for subsequent points.
+ *
+ * Set skipOtherCoords to ignore extra coordinates in Troll's pictures.
+ * Troll includes both the x and y coordinate of each point.
+ */
void PictureMgr::yCorner(bool skipOtherCoords) {
byte x1, x2, y1, y2, dummy;
@@ -179,12 +246,14 @@ void PictureMgr::yCorner(bool skipOtherCoords) {
}
}
-/**************************************************************************
-** plotPattern
-**
-** Draws pixels, circles, squares, or splatter brush patterns depending
-** on the pattern code.
-**************************************************************************/
+/**
+ * plotPattern
+ *
+ * Draws a circle or square. Size and optional splatter brush pattern
+ * are determined by the current pattern code.
+ *
+ * This routine is originally from NAGI.
+ */
void PictureMgr::plotPattern(byte x, byte y) {
static const uint16 binary_list[] = {
0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,
@@ -283,11 +352,11 @@ void PictureMgr::plotPattern(byte x, byte y) {
}
}
-/**************************************************************************
-** plotBrush
-**
-** Plots points and various brush patterns.
-**************************************************************************/
+/**
+ * plotBrush
+ *
+ * Plots the current brush pattern.
+ */
void PictureMgr::plotBrush() {
for (;;) {
if (_patCode & 0x20) {
@@ -303,9 +372,9 @@ void PictureMgr::plotBrush() {
}
}
-/**************************************************************************
-** Draw AGI picture
-**************************************************************************/
+/**
+ * Draws the current picture to the visual and priority screens.
+ */
void PictureMgr::drawPicture() {
debugC(kDebugLevelPictures, "Drawing picture %d", _resourceNr);
@@ -377,7 +446,10 @@ void PictureMgr::drawPicture() {
}
}
-void PictureMgr::drawPictureAGI256() {
+/**
+ * Draws the current AGI256 picture to the visual screen.
+ */
+void PictureMgr::drawPicture_AGI256() {
const uint32 maxFlen = _width * _height;
int16 x = 0;
int16 y = 0;
@@ -416,6 +488,9 @@ void PictureMgr::drawPictureAGI256() {
warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", _resourceNr, _width, _height);
}
+/**
+ * Sets the visual screen color to the next byte in the picture data.
+ */
void PictureMgr::draw_SetColor() {
_scrColor = getNextByte();
@@ -425,12 +500,18 @@ void PictureMgr::draw_SetColor() {
}
}
+/**
+ * Sets the priority screen color to the next byte in the picture data.
+ */
void PictureMgr::draw_SetPriority() {
_priColor = getNextByte();
}
-// this gets a nibble instead of a full byte
-// used by some V3 games, special resource flag RES_PICTURE_V3_NIBBLE_PARM is set
+/**
+ * Sets the visual screen color to the next nibble in the picture data.
+ * Used in AGIv3 to compress the set-color instructions when the flag
+ * RES_PICTURE_V3_NIBBLE_PARM is set in the picture's directory entry.
+ */
void PictureMgr::draw_SetNibbleColor() {
_scrColor = getNextNibble();
@@ -440,18 +521,22 @@ void PictureMgr::draw_SetNibbleColor() {
}
}
+/**
+ * Sets the priority screen color to the next nibble in the picture data.
+ * Used in AGIv3 to compress the set-color instructions when the flag
+ * RES_PICTURE_V3_NIBBLE_PARM is set in the picture's directory entry.
+ */
void PictureMgr::draw_SetNibblePriority() {
_priColor = getNextNibble();
}
/**
- * Draw an AGI line.
- * A line drawing routine sent by Joshua Neal, modified by Stuart George
- * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.)
- * @param x1 x coordinate of start point
- * @param y1 y coordinate of start point
- * @param x2 x coordinate of end point
- * @param y2 y coordinate of end point
+ * Draws a horizontal, vertical, or diagonal line.
+ *
+ * This routine is originally from Sarien. Original comment:
+ *
+ * A line drawing routine sent by Joshua Neal, modified by Stuart George
+ * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.)
*/
void PictureMgr::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) {
x1 = CLIP<int16>(x1, 0, _width - 1);
@@ -533,8 +618,9 @@ void PictureMgr::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) {
}
/**
- * Draw a relative AGI line.
- * Draws short lines relative to last position. (drawing action 0xF7)
+ * draw_LineShort
+ *
+ * Draws short lines between positions in relative coordinates.
*/
void PictureMgr::draw_LineShort() {
byte x1, y1, disp;
@@ -562,11 +648,11 @@ void PictureMgr::draw_LineShort() {
}
}
-/**************************************************************************
-** absoluteLine
-**
-** Draws long lines to actual locations (cf. relative) (drawing action 0xF6)
-**************************************************************************/
+/**
+ * draw_LineAbsolute
+ *
+ * Draws lines between positions in absolute coordinates.
+ */
void PictureMgr::draw_LineAbsolute() {
byte x1, y1, x2, y2;
@@ -585,7 +671,9 @@ void PictureMgr::draw_LineAbsolute() {
}
}
-// flood fill
+/**
+ * Flood fills from a series of start positions.
+ */
void PictureMgr::draw_Fill() {
byte x, y;
@@ -594,6 +682,9 @@ void PictureMgr::draw_Fill() {
}
}
+/**
+ * Flood fills from a start position.
+ */
void PictureMgr::draw_Fill(int16 x, int16 y) {
if (!_scrOn && !_priOn)
return;
@@ -639,6 +730,13 @@ void PictureMgr::draw_Fill(int16 x, int16 y) {
}
}
+/**
+ * Checks if flood fill is allowed at a position.
+ *
+ * horizontalCheck indicates if the flood fill algorithm is scanning the current
+ * line horizontally for a boundary. This is used by PictureMgr_Troll to handle
+ * Troll's Tale custom flood fill behavior when drawing the Troll over pictures.
+ */
bool PictureMgr::draw_FillCheck(int16 x, int16 y, bool horizontalCheck) {
if (!getGraphicsCoordinates(x, y)) {
return false;
@@ -657,13 +755,11 @@ bool PictureMgr::draw_FillCheck(int16 x, int16 y, bool horizontalCheck) {
}
/**
- * Decode an AGI picture resource. Used by regular AGI games.
- * This function decodes an AGI picture resource into the correct slot
- * and draws it on the AGI screen, optionally clearing the screen before
- * drawing.
- * @param n AGI picture resource number
- * @param clear clear AGI screen before drawing
- * @param agi256 load an AGI256 picture resource
+ * Draws a picture by resource number to the visual and control screens.
+ * This interface is used by AGI games and GAL (KQ1 early).
+ *
+ * The picture resource must already be loaded. This function sets the current
+ * picture and optionally clears the screens before drawing.
*/
void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256, int16 width, int16 height) {
_resourceNr = resourceNr;
@@ -679,7 +775,7 @@ void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256,
if (!agi256) {
drawPicture();
} else {
- drawPictureAGI256();
+ drawPicture_AGI256();
}
if (clearScreen) {
@@ -689,13 +785,11 @@ void PictureMgr::decodePicture(int16 resourceNr, bool clearScreen, bool agi256,
}
/**
- * Decode an AGI picture resource. Used by preAGI.
- * This function decodes an AGI picture resource into the correct slot
- * and draws it on the AGI screen, optionally clearing the screen before
- * drawing.
- * @param data the AGI Picture data
- * @param length the size of the picture data buffer
- * @param clear clear AGI screen before drawing
+ * Draws a picture from a buffer to the visual and control screens.
+ * This interface is used by PreAGI games.
+ *
+ * This function sets the current picture and optionally clears the screens
+ * before drawing.
*/
void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearScreen, int16 width, int16 height) {
_data = data;
@@ -710,12 +804,25 @@ void PictureMgr::decodePictureFromBuffer(byte *data, uint32 length, bool clearSc
drawPicture();
}
+/**
+ * Renders a drawn picture from the active screen to the display screen.
+ *
+ * The active screen is usually the visual screen, but this can be toggled
+ * to the priority screen in debug modes.
+ */
void PictureMgr::showPicture(int16 x, int16 y, int16 width, int16 height) {
debugC(kDebugLevelPictures, "Show picture");
_gfx->render_Block(x, y, width, height);
}
+/**
+ * Renders a drawn picture from the active screen to the display screen
+ * with transition effects. The effect is determined by the render mode.
+ *
+ * The active screen is usually the visual screen, but this can be toggled
+ * to the priority screen in debug modes.
+ */
void PictureMgr::showPictureWithTransition() {
_width = SCRIPT_WIDTH;
_height = SCRIPT_HEIGHT;
diff --git a/engines/agi/picture.h b/engines/agi/picture.h
index cb28e72ff5b..395955fe2e9 100644
--- a/engines/agi/picture.h
+++ b/engines/agi/picture.h
@@ -77,7 +77,7 @@ public:
protected:
virtual void drawPicture();
- void drawPictureAGI256();
+ void drawPicture_AGI256();
void draw_SetColor();
void draw_SetPriority();
diff --git a/engines/agi/preagi/picture_mickey_winnie.cpp b/engines/agi/preagi/picture_mickey_winnie.cpp
index 36e1dd32b4b..2f994dbe0fb 100644
--- a/engines/agi/preagi/picture_mickey_winnie.cpp
+++ b/engines/agi/preagi/picture_mickey_winnie.cpp
@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-
+
#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/picture.h"
@@ -27,6 +27,42 @@
namespace Agi {
+// PictureMgr_Mickey_Winnie decodes and draws picture resources in Mickey's
+// Space Adventure (DOS) and Winnie the Pooh (DOS/Amiga/A2/C64/CoCo).
+//
+// Mickey and Winnie DOS/Amiga use the same format. The picture code in
+// their executables appears to be the same.
+//
+// The A2/C64/CoCo versions of Winnie use a completely different format, but
+// they do support the same features. These games start in ScummVM but they
+// don't really work yet. TODO: display the right colors, figure out the line
+// differences, support these versions.
+//
+// Both formats support lines, flood fills, and patterns. No priority screen.
+//
+// Unique features to these formats:
+//
+// 1. Pictures can be drawn on top of others at arbitrary locations. Used to
+// draw items in rooms, and to draw room pictures with a buffer on each side
+// in DOS/Amiga. The pictures don't fill the screen width because they were
+// designed for the Apple II.
+//
+// 2. Mickey's crystals animate. Most of the work is done in MickeyEngine;
+// this class just allows the engine to set a maximum number of picture
+// instructions to execute. Unclear if this is same effect as the original.
+//
+// 3. The pattern opcode draws solid circles in up to 17 sizes.
+//
+// Mickey features animating spaceship lights, but the engine handles that.
+// The lights are a picture whose instructions are modified before drawing.
+//
+// TODO: There are extremely minor inaccuracies in several Winnie pictures.
+// The F1 opcode's effects are not fully understood, and it creates subtle
+// discrepancies. It may be related to dithering. However, so few pictures
+// contain F3, and even fewer are effected by ignoring it or not, and only
+// by a few pixels, that it doesn't matter except for completeness.
+// See: picture 34 door handles (Rabbit's kitchen)
+
PictureMgr_Mickey_Winnie::PictureMgr_Mickey_Winnie(AgiBase *agi, GfxMgr *gfx) :
PictureMgr(agi, gfx) {
@@ -160,6 +196,11 @@ void PictureMgr_Mickey_Winnie::drawPicture_A2_C64_CoCo() {
}
}
+/**
+ * plotBrush (PreAGI)
+ *
+ * Plots the given brush pattern. All brushes are solid circles.
+ */
void PictureMgr_Mickey_Winnie::plotBrush() {
_patCode = getNextByte();
if (_patCode > 12) {
@@ -175,6 +216,11 @@ void PictureMgr_Mickey_Winnie::plotBrush() {
}
}
+/**
+ * plotPattern
+ *
+ * Draws a solid circle. Size is determined by the pattern code.
+ */
void PictureMgr_Mickey_Winnie::plotPattern(byte x, byte y) {
// PreAGI patterns are 13 solid circles
static const byte circleData[] = {
@@ -278,11 +324,20 @@ bool PictureMgr_Mickey_Winnie::getNextYCoordinate(byte &y) {
return true;
}
+/**
+ * Validates picture coordinates and translates them to GfxMgr coordinates.
+ *
+ * This function applies the current picture object and validates that the
+ * graphics coordinates are within GfxMgr's boundaries. Validation is necessary
+ * because Winnie places tall objects at the bottom of the screen in several
+ * rooms, and GfxMgr does not validate coordinates.
+ */
bool PictureMgr_Mickey_Winnie::getGraphicsCoordinates(int16 &x, int16 &y) {
+ // validate that the coordinates are within the picture's boundaries
if (!PictureMgr::getGraphicsCoordinates(x, y)) {
return false;
}
-
+
x += _xOffset;
y += _yOffset;
diff --git a/engines/agi/preagi/picture_troll.cpp b/engines/agi/preagi/picture_troll.cpp
index 1f2c01a665d..d71e93110c3 100644
--- a/engines/agi/preagi/picture_troll.cpp
+++ b/engines/agi/preagi/picture_troll.cpp
@@ -26,6 +26,26 @@
#include "agi/preagi/picture_troll.h"
namespace Agi {
+
+// PictureMgr_Troll decodes and draws Troll's Tale picture resources.
+//
+// Troll's Tale supports lines and flood fills. There is no priority screen.
+//
+// There are two unique picture features:
+//
+// 1. The F3 opcode can dynamically act as a no-op or terminator (FF).
+// This allows pictures to have an optional set of instructions for
+// drawing or hiding a room's object or the king's crown.
+//
+// 2. A custom flood fill technique is used for drawing the Troll over
+// room pictures. Normally, flood fill requires an empty (white) area.
+//
+// One quirk is that the xCorner and yCorner instructions contain a redundant
+// coordinate, even though it is ignored because it is derived from the others.
+//
+// Each room picture depends on the game first drawing a frame within the entire
+// picture area. This is not decorative; the flood fill routines rely on this
+// border because they do not do boundary test, and pictures are drawn for it.
PictureMgr_Troll::PictureMgr_Troll(AgiBase *agi, GfxMgr *gfx) :
PictureMgr(agi, gfx) {
@@ -47,30 +67,24 @@ void PictureMgr_Troll::drawPicture() {
byte curByte = getNextByte();
switch (curByte) {
- case 0xf0:
- // happens in all Troll's Tale pictures
- // TODO: figure out what it was meant for
- break;
+ case 0xf0: // F0 is in all Troll's Tale pictures, but it is a no-op.
+ break; // Confirmed in disassembly of opcode table.
case 0xf1:
draw_SetColor();
_scrOn = true;
break;
- case 0xf3:
+ case 0xf3: // F3 would impersonate opcode F0 (no-op) or FF (terminator)
if (_stopOnF3)
return;
break;
case 0xf8:
- yCorner(true);
+ yCorner(true); // skip extra (redundant) coordinates when parsing
break;
case 0xf9:
- xCorner(true);
- break;
- case 0xfa:
- // TODO: is this really correct?
- draw_LineAbsolute();
+ xCorner(true); // skip extra (redundant) coordinates when parsing
break;
- case 0xfb:
- // TODO: is this really correct?
+ case 0xfa: // FA and FB are both used, but they are the same.
+ case 0xfb: // Confirmed in disassembly of opcode table.
draw_LineAbsolute();
break;
case 0xfe:
Commit: f53d9ad3361ab1826f20938e3935c5d4a47e81ae
https://github.com/scummvm/scummvm/commit/f53d9ad3361ab1826f20938e3935c5d4a47e81ae
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:58-07:00
Commit Message:
AGI: Update picture comments
Changed paths:
engines/agi/preagi/picture_mickey_winnie.cpp
engines/agi/preagi/picture_troll.cpp
diff --git a/engines/agi/preagi/picture_mickey_winnie.cpp b/engines/agi/preagi/picture_mickey_winnie.cpp
index 2f994dbe0fb..3b4a372d735 100644
--- a/engines/agi/preagi/picture_mickey_winnie.cpp
+++ b/engines/agi/preagi/picture_mickey_winnie.cpp
@@ -59,7 +59,7 @@ namespace Agi {
// TODO: There are extremely minor inaccuracies in several Winnie pictures.
// The F1 opcode's effects are not fully understood, and it creates subtle
// discrepancies. It may be related to dithering. However, so few pictures
-// contain F3, and even fewer are effected by ignoring it or not, and only
+// contain F3, and even fewer are affected by ignoring it or not, and only
// by a few pixels, that it doesn't matter except for completeness.
// See: picture 34 door handles (Rabbit's kitchen)
@@ -293,7 +293,7 @@ bool PictureMgr_Mickey_Winnie::getNextXCoordinate(byte &x) {
return false;
}
- if (_isDosOrAmiga) { // TODO: is this check in A2/C64/CoCo?
+ if (_isDosOrAmiga) {
if (x >= _width) { // 140
debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'x', x, _width - 1);
x = _width - 1; // 139
@@ -313,7 +313,7 @@ bool PictureMgr_Mickey_Winnie::getNextYCoordinate(byte &y) {
return false;
}
- if (_isDosOrAmiga) { // TODO: is this check in A2/C64/CoCo?
+ if (_isDosOrAmiga) {
// note that this is a different clip than for the x coordinate
if (y > _height) { // 159
debugC(kDebugLevelPictures, "clipping %c from %d to %d", 'y', y, _height);
diff --git a/engines/agi/preagi/picture_troll.cpp b/engines/agi/preagi/picture_troll.cpp
index d71e93110c3..1681873981f 100644
--- a/engines/agi/preagi/picture_troll.cpp
+++ b/engines/agi/preagi/picture_troll.cpp
@@ -35,7 +35,7 @@ namespace Agi {
//
// 1. The F3 opcode can dynamically act as a no-op or terminator (FF).
// This allows pictures to have an optional set of instructions for
-// drawing or hiding a room's object or the king's crown.
+// drawing or hiding a room's object or the king's crown.
//
// 2. A custom flood fill technique is used for drawing the Troll over
// room pictures. Normally, flood fill requires an empty (white) area.
Commit: bf9f5646602549f0121bec367f6b44e64530197f
https://github.com/scummvm/scummvm/commit/bf9f5646602549f0121bec367f6b44e64530197f
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:58-07:00
Commit Message:
SCI: Fix KQ4 Amiga sound signals
Fixes bug #15660
Limits the workaround for KQ4 broken signals to just the introduction.
We now know that it's the only sound that requires this, and that the
Amiga version added signal 127 to almost every sound.
See: 08b9c26752174a247d5aa06da7b498c818def1a7
Thanks to @athrxx for locating behavior in Amiga driver disassembly
Thanks to @prietveld for reporting
Changed paths:
engines/sci/sound/midiparser_sci.cpp
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index 468e3e5e88b..4f04bb3e2d3 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -729,12 +729,16 @@ bool MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) {
if (info.basic.param1 == kSetSignalLoop) {
_loopTick = _position._playTick;
// kSetSignalLoop (127) is not passed on to scripts, except in SCI_VERSION_0_EARLY.
- // We also pass it to all versions of KQ4 because the scripts expect this. Sierra didn't
- // update them when they changed the driver behavior. Introduction script 222 waits
- // on signal 127 in sound 106 to start the game, causing later versions to wait forever.
+ // We also pass it to all versions of KQ4 when playing the introduction sound,
+ // because the KQ4 scripts expect it, and Sierra did not update the scripts when
+ // they changed the driver behavior. Script 222 waits on signal 127 in sound 106
+ // to start the game, causing later versions to wait forever.
// Now the introduction correctly ends when the music does in all versions.
- if (_soundVersion > SCI_VERSION_0_EARLY && g_sci->getGameId() != GID_KQ4) {
- return true;
+ // We must only apply this to sound 106, because Amiga adds signal 127 to others.
+ if (_soundVersion > SCI_VERSION_0_EARLY) {
+ if (!(g_sci->getGameId() == GID_KQ4 && _pSnd->resourceId == 106)) {
+ return true;
+ }
}
}
Commit: 98fb478ccdca7bea8864f43a5f32308ea8934318
https://github.com/scummvm/scummvm/commit/98fb478ccdca7bea8864f43a5f32308ea8934318
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:58-07:00
Commit Message:
AGI: PREAGI: Fix gcc warning
Changed paths:
engines/agi/preagi/troll.cpp
diff --git a/engines/agi/preagi/troll.cpp b/engines/agi/preagi/troll.cpp
index 24b15b42e7b..de2f9047baa 100644
--- a/engines/agi/preagi/troll.cpp
+++ b/engines/agi/preagi/troll.cpp
@@ -451,7 +451,8 @@ int TrollEngine::drawRoom(char *menu) {
strncat(menu, Common::String::format(" %d.", i + 1).c_str(), 4);
strncat(menu, (char *)_gameData + _options[_roomDescs[_roomPicture - 1].options[i] - 1], 35);
- strncat(menu, " ", 1);
+ menu[(i + 2) * 40 - 1] = ' ';
+ menu[(i + 2) * 40] = '\0';
n = i + 1;
}
Commit: c63c694a85d643ced5d92c29312e64fb592c9acb
https://github.com/scummvm/scummvm/commit/c63c694a85d643ced5d92c29312e64fb592c9acb
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:58-07:00
Commit Message:
SCI: Fix KQ1 script lockup when drowning in cave
Fixes bug #15667
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index a5fa16bf985..3bb1b595c6a 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -107,6 +107,7 @@ static const char *const selectorNameTable[] = {
"type", // system selector
"client", // system selector
"state", // system selector
+ "illegalBits", // system selector
"localize", // Freddy Pharkas
"roomFlags", // Iceman
"put", // Police Quest 1 VGA
@@ -250,6 +251,7 @@ enum ScriptPatcherSelectors {
SELECTOR_type,
SELECTOR_client,
SELECTOR_state,
+ SELECTOR_illegalBits,
SELECTOR_localize,
SELECTOR_roomFlags,
SELECTOR_put,
@@ -5338,6 +5340,53 @@ static const SciScriptPatcherEntry jonesSignatures[] = {
// ===========================================================================
// King's Quest 1
+// When swimming for too long in the cave pool beneath the well, the drowning
+// script can get stuck and not display the death message. This occurs when ego
+// is at certain x positions, such as 71. When sinking, the drowning script
+// sets ego:illegalBits to 0 so that ego will fall without interference from
+// priority lines. rm52:doit reverts this by setting ego:illegalBits to $8000
+// because it thinks ego is swimming, causing ego to get stuck on a priority
+// line when too far left. The script detects swimming by testing ego's view,
+// but this is incomplete because view 6 also contains ego's drowning loops.
+//
+// We fix this by not setting ego:illegalBits to $8000 when the drowning script
+// is running. The separate script for drowning while swimming underwater is
+// unaffected, because it sets a timer to trigger its death message.
+//
+// Applies to: All versions
+// Responsible method: rm52:doit
+// Fixes bug: #15667
+static const uint16 kq1SignatureDrowning[] = {
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa drowning
+ SIG_ADDTOOFFSET(+61),
+ SIG_MAGICDWORD,
+ 0x30, SIG_UINT16(0x000d), // bnt 000d
+ 0x39, SIG_SELECTOR8(illegalBits), // pushi illegalBits
+ 0x78, // push1
+ 0x38, SIG_UINT16(0x8000), // pushi 8000
+ 0x81, 0x00, // lag 00
+ 0x4a, 0x06, // send 06 [ ego illegalBits: $8000 ]
+ 0x32, SIG_UINT16(0x0008), // jmp 0008
+ 0x39, SIG_SELECTOR8(illegalBits), // pushi illegalBits
+ 0x78, // push1
+ 0x76, // push0 [ illegalBits: 0 ]
+ SIG_END
+};
+
+static const uint16 kq1PatchDrowning[] = {
+ PATCH_ADDTOOFFSET(+64),
+ 0x72, PATCH_GETORIGINALUINT16ADJUST(1, -64), // lofsa drowning
+ 0x67, 0x08, // pTos script
+ 0x1c, // ne? [ acc = 1 if not drowning, else 0 ]
+ 0x36, // push
+ 0x35, 0x0f, // ldi 0f
+ 0x0e, // shl [ acc = $8000 if not drowning, else 0 ]
+ 0x33, 0x04, // jmp 04
+ PATCH_ADDTOOFFSET(+7),
+ 0x36, // push [ illegalBits = $8000 or 0 ]
+ PATCH_END
+};
+
// In the demo, the leprechaun dance runs awkwardly fast on modern computers.
// The demo script increases the speed from the default (5 or 6) to fastest (1)
// for this scene only. This appears to be an attempt to speed up the dance and
@@ -5366,6 +5415,7 @@ static const uint16 kq1PatchDemoDanceSpeed[] = {
// script, description, signature patch
static const SciScriptPatcherEntry kq1Signatures[] = {
+ { true, 52, "drowning", 1, kq1SignatureDrowning, kq1PatchDrowning },
{ true, 77, "demo: dance speed", 1, kq1SignatureDemoDanceSpeed, kq1PatchDemoDanceSpeed },
{ true, 99, "demo: disable speed test", 1, sci01SpeedTestGlobalSignature, sci01SpeedTestGlobalPatch },
{ true, 777, "disable speed test", 1, sci01SpeedTestGlobalSignature, sci01SpeedTestGlobalPatch },
Commit: 0b6743f6b2b311748118b388fce15e892492fb17
https://github.com/scummvm/scummvm/commit/0b6743f6b2b311748118b388fce15e892492fb17
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:59-07:00
Commit Message:
AGI: PREAGI: Initialize members
Changed paths:
engines/agi/preagi/picture_troll.cpp
diff --git a/engines/agi/preagi/picture_troll.cpp b/engines/agi/preagi/picture_troll.cpp
index 1681873981f..dcbd5ab9833 100644
--- a/engines/agi/preagi/picture_troll.cpp
+++ b/engines/agi/preagi/picture_troll.cpp
@@ -49,6 +49,8 @@ namespace Agi {
PictureMgr_Troll::PictureMgr_Troll(AgiBase *agi, GfxMgr *gfx) :
PictureMgr(agi, gfx) {
+ _stopOnF3 = false;
+ _trollMode = false;
}
void PictureMgr_Troll::drawPicture() {
Commit: 21af31a3eea45c41747ebbec7bbc9f4bd6411027
https://github.com/scummvm/scummvm/commit/21af31a3eea45c41747ebbec7bbc9f4bd6411027
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:59-07:00
Commit Message:
AGI: Add `diskdump` debug command
Changed paths:
engines/agi/console.cpp
engines/agi/console.h
diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp
index c8d006715f9..24919316401 100644
--- a/engines/agi/console.cpp
+++ b/engines/agi/console.cpp
@@ -27,6 +27,8 @@
#include "agi/preagi/mickey.h"
#include "agi/preagi/winnie.h"
+#include "common/file.h"
+
namespace Agi {
Console::Console(AgiEngine *vm) : GUI::Debugger() {
@@ -54,6 +56,7 @@ Console::Console(AgiEngine *vm) : GUI::Debugger() {
registerCmd("vmvars", WRAP_METHOD(Console, Cmd_VmVars));
registerCmd("vmflags", WRAP_METHOD(Console, Cmd_VmFlags));
registerCmd("disableautosave", WRAP_METHOD(Console, Cmd_DisableAutomaticSave));
+ registerCmd("diskdump", WRAP_METHOD(Console, Cmd_DiskDump));
}
bool Console::Cmd_SetVar(int argc, const char **argv) {
@@ -606,6 +609,80 @@ bool Console::Cmd_DisableAutomaticSave(int argc, const char **argv) {
return true;
}
+bool Console::Cmd_DiskDump(int argc, const char **argv) {
+ static const char *resTypes[4] = { "logic", "picture", "view", "sound" };
+
+ if (!(argc == 3 || (argc == 2 && strcmp(argv[1], "*") == 0))) {
+ debugPrintf("Dumps the specified resource to disk as a file\n");
+ debugPrintf("Usage: %s <resource type> <resource number>\n", argv[0]);
+ debugPrintf(" <resource type> may be logic, picture, view, sound, or '*' for all resources\n");
+ debugPrintf(" <resource number> may be '*' to dump all resources of given type\n");
+ return true;
+ }
+
+ int resType = -1; // -1 == all
+ if (strcmp(argv[1], "*") != 0) {
+ for (int i = 0; i < ARRAYSIZE(resTypes); i++) {
+ if (scumm_stricmp(argv[1], resTypes[i]) == 0) {
+ resType = i;
+ break;
+ }
+ }
+ if (resType == -1) {
+ debugPrintf("Resource type '%s' is not valid\n", argv[1]);
+ return true;
+ }
+ }
+
+ int resNr = -1; // -1 == all
+ if (argc >= 3 && strcmp(argv[2], "*") != 0) {
+ if (!parseInteger(argv[2], resNr)) {
+ return true;
+ }
+ if (!(0 <= resNr && resNr < MAX_DIRECTORY_ENTRIES)) {
+ debugPrintf("Invalid resource number: %d\n", resNr);
+ return true;
+ }
+ }
+
+ AgiDir *resDirs[4] = { _vm->_game.dirLogic, _vm->_game.dirPic, _vm->_game.dirView, _vm->_game.dirSound };
+ for (int t = 0; t < ARRAYSIZE(resDirs); t++) {
+ if (resType != -1 && resType != t) {
+ continue;
+ }
+
+ AgiDir *resDir = resDirs[t];
+ for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
+ if (resNr != -1 && resNr != i) {
+ continue;
+ }
+
+ if (resDir[i].offset == _EMPTY) {
+ if (resNr != -1) {
+ debugPrintf("Resource does not exist: %s.%03d\n", resTypes[t], i);
+ }
+ continue;
+ }
+
+ Common::String fileName = Common::String::format("%s.%03d", resTypes[t], i);
+ byte *resData = _vm->_loader->loadVolumeResource(&resDir[i]);
+ if (resData != nullptr) {
+ Common::DumpFile file;
+ if (file.open(Common::Path(fileName))) {
+ file.write(resData, resDir[i].len);
+ debugPrintf("%s has been dumped to disk\n", fileName.c_str());
+ } else {
+ debugPrintf("Error dumping %s to disk\n", fileName.c_str());
+ }
+ free(resData);
+ } else {
+ debugPrintf("Error dumping %s to disk\n", fileName.c_str());
+ }
+ }
+ }
+ return true;
+}
+
bool Console::parseInteger(const char *argument, int &result) {
char *endPtr = nullptr;
int idxLen = strlen(argument);
diff --git a/engines/agi/console.h b/engines/agi/console.h
index 2dd2ef78155..58233f55b82 100644
--- a/engines/agi/console.h
+++ b/engines/agi/console.h
@@ -66,6 +66,7 @@ private:
bool Cmd_VmVars(int argc, const char **argv);
bool Cmd_VmFlags(int argc, const char **argv);
bool Cmd_DisableAutomaticSave(int argc, const char **argv);
+ bool Cmd_DiskDump(int argc, const char **argv);
bool parseInteger(const char *argument, int &result);
Commit: a3fc4b1e171f70b447091edb23b21dde14a0095e
https://github.com/scummvm/scummvm/commit/a3fc4b1e171f70b447091edb23b21dde14a0095e
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:26:59-07:00
Commit Message:
SCI: Fix LSL1 lockup when casino doors close
Fixes bug #15701
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 3bb1b595c6a..34493ea1da8 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -9160,9 +9160,49 @@ static const uint16 larry1PatchBuyApple[] = {
PATCH_END
};
+// When entering the casino from certain angles and speeds, ego can get stuck in
+// hands-off mode when the doors close. This bug also occurs in the original.
+// The sToCasino script sets hands-off mode, moves ego, closes the doors, and
+// then waits on ego and the doors, but the doors are control obstacles that
+// can interrupt ego's motion and prevent the script from completing.
+//
+// We fix this by clearing ego:illegalBits when sToCasino enters hands-off mode
+// so that ego ignores the door obstacles. On the final state (2) we restore
+// ego:illegalBits to $8000 before changing rooms.
+//
+// Applies to: All versions
+// Responsible method: sToCasino:changeState
+// Fixes bug: #15701
+static const uint16 larry1SignatureCasinoDoors[] = {
+ 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of switch ]
+ 0x3c, // dup
+ 0x35, SIG_MAGICDWORD, 0x01, // ldi 01 [ state 1 ]
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x0005), // bnt 0005
+ 0x35, 0x00, // ldi 00
+ 0x32, // jmp [ end of switch ]
+ SIG_ADDTOOFFSET(+9),
+ 0x38, SIG_SELECTOR16(newRoom), // pushi newRoom
+ SIG_END
+};
+
+static const uint16 larry1PatchCasinoDoors[] = {
+ 0x32, PATCH_UINT16(0x0000), // jmp 0000 [ fall through ]
+ 0x3c, // dup
+ 0x35, 0x0e, // ldi 0e
+ 0x0e, // shl
+ 0x39, PATCH_SELECTOR8(illegalBits),// pushi illegalBits
+ 0x78, // push1
+ 0x36, // push
+ 0x81, 0x00, // lag 00
+ 0x4a, 0x06, // send 06 [ ego illegalBits: (state << 14) ]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry larry1Signatures[] = {
{ true, 300, "Spanish: buy apple from barrel man", 1, larry1SignatureBuyApple, larry1PatchBuyApple },
+ { true, 300, "casino doors", 1, larry1SignatureCasinoDoors, larry1PatchCasinoDoors },
{ true, 350, "elevator polygon size", 1, larry1SignatureElevatorPolygon, larry1PatchElevatorPolygon },
{ true, 803, "disable speed test", 1, sci01SpeedTestLocalSignature, sci01SpeedTestLocalPatch },
SCI_SIGNATUREENTRY_TERMINATOR
Commit: 2929aabbf7e0cc4f02cc8c207104505132077218
https://github.com/scummvm/scummvm/commit/2929aabbf7e0cc4f02cc8c207104505132077218
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:00-07:00
Commit Message:
AGI: Move AgiLoader definitions to loader.h
Changed paths:
A engines/agi/loader.h
engines/agi/agi.cpp
engines/agi/agi.h
engines/agi/console.cpp
engines/agi/loader_a2.cpp
engines/agi/loader_v1.cpp
engines/agi/loader_v2.cpp
engines/agi/loader_v3.cpp
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 17cc4ccbdc9..746b2b8bc2c 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -42,6 +42,7 @@
#include "agi/font.h"
#include "agi/graphics.h"
#include "agi/inv.h"
+#include "agi/loader.h"
#include "agi/sprite.h"
#include "agi/text.h"
#include "agi/keyboard.h"
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index 5e4a7dc68a8..cbfe622a5b4 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -541,149 +541,7 @@ struct AgiGame {
}
};
-struct AgiDiskVolume {
- uint32 disk;
- uint32 offset;
-
- AgiDiskVolume() : disk(_EMPTY), offset(0) {}
- AgiDiskVolume(uint32 d, uint32 o) : disk(d), offset(o) {}
-};
-
-/**
- * Apple II version of the format for LOGDIR, VIEWDIR, etc.
- * See AgiLoader_A2::loadDir for more details.
- */
-enum A2DirVersion {
- A2DirVersionOld, // 4 bits for volume, 8 for track
- A2DirVersionNew, // 5 bits for volume, 7 for track
-};
-
-class AgiLoader {
-public:
- AgiLoader(AgiEngine *vm) : _vm(vm) {}
- virtual ~AgiLoader() {}
-
- /**
- * Performs one-time initializations, such as locating files
- * with dynamic names.
- */
- virtual void init() {}
-
- /**
- * Loads all AGI directory entries from disk and and populates
- * the AgiDir arrays in AgiGame with them.
- */
- virtual int loadDirs() = 0;
-
- /**
- * Loads a volume resource from disk.
- */
- virtual uint8 *loadVolumeResource(AgiDir *agid) = 0;
-
- /**
- * Loads AgiEngine::_objects from disk.
- */
- virtual int loadObjects() = 0;
-
- /**
- * Loads AgiBase::_words from disk.
- */
- virtual int loadWords() = 0;
-
-protected:
- AgiEngine *_vm;
-};
-
-class AgiLoader_A2 : public AgiLoader {
-public:
- AgiLoader_A2(AgiEngine *vm) : AgiLoader(vm) {}
- ~AgiLoader_A2() override;
-
- void init() override;
- int loadDirs() override;
- uint8 *loadVolumeResource(AgiDir *agid) override;
- int loadObjects() override;
- int loadWords() override;
-
-private:
- Common::Array<Common::SeekableReadStream *> _disks;
- Common::Array<AgiDiskVolume> _volumes;
- AgiDir _logDir;
- AgiDir _picDir;
- AgiDir _viewDir;
- AgiDir _soundDir;
- AgiDir _objects;
- AgiDir _words;
-
- int readDiskOne(Common::SeekableReadStream &stream, Common::Array<uint32> &volumeMap);
- static bool readInitDir(Common::SeekableReadStream &stream, byte index, AgiDir &agid);
- static bool readDir(Common::SeekableReadStream &stream, int position, AgiDir &agid);
- static bool readVolumeMap(Common::SeekableReadStream &stream, uint32 position, uint32 bufferLength, Common::Array<uint32> &volumeMap);
-
- A2DirVersion detectDirVersion(Common::SeekableReadStream &stream);
- bool loadDir(AgiDir *dir, Common::SeekableReadStream &disk, uint32 dirOffset, uint32 dirLength, A2DirVersion dirVersion);
-};
-
-class AgiLoader_v1 : public AgiLoader {
-public:
- AgiLoader_v1(AgiEngine *vm) : AgiLoader(vm) {}
-
- void init() override;
- int loadDirs() override;
- uint8 *loadVolumeResource(AgiDir *agid) override;
- int loadObjects() override;
- int loadWords() override;
-
-private:
- Common::Array<Common::String> _imageFiles;
- Common::Array<AgiDiskVolume> _volumes;
- AgiDir _logDir;
- AgiDir _picDir;
- AgiDir _viewDir;
- AgiDir _soundDir;
- AgiDir _objects;
- AgiDir _words;
-
- bool readDiskOneV1(Common::SeekableReadStream &stream);
- bool readDiskOneV2001(Common::SeekableReadStream &stream, int &vol0Offset);
- static bool readInitDirV1(Common::SeekableReadStream &stream, byte index, AgiDir &agid);
- static bool readInitDirV2001(Common::SeekableReadStream &stream, byte index, AgiDir &agid);
-
- bool loadDir(AgiDir *dir, Common::File &disk, uint32 dirOffset, uint32 dirLength);
-};
-
-class AgiLoader_v2 : public AgiLoader {
-private:
- bool _hasV3VolumeFormat;
-
- int loadDir(AgiDir *agid, const char *fname);
- bool detectV3VolumeFormat();
-
-public:
- AgiLoader_v2(AgiEngine *vm) : _hasV3VolumeFormat(false), AgiLoader(vm) {}
-
- int loadDirs() override;
- uint8 *loadVolumeResource(AgiDir *agid) override;
- int loadObjects() override;
- int loadWords() override;
-};
-
-class AgiLoader_v3 : public AgiLoader {
-private:
- Common::String _name; /**< prefix in directory and/or volume file names (`GR' for goldrush) */
-
- int loadDir(AgiDir *agid, Common::File *fp, uint32 offs, uint32 len);
-
-public:
- AgiLoader_v3(AgiEngine *vm) : AgiLoader(vm) {}
-
- void init() override;
- int loadDirs() override;
- uint8 *loadVolumeResource(AgiDir *agid) override;
- int loadObjects() override;
- int loadWords() override;
-};
-
+class AgiLoader;
class GfxFont;
class GfxMgr;
class SpritesMgr;
diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp
index 24919316401..ec18fb447c6 100644
--- a/engines/agi/console.cpp
+++ b/engines/agi/console.cpp
@@ -22,6 +22,7 @@
#include "agi/agi.h"
#include "agi/opcodes.h"
#include "agi/graphics.h"
+#include "agi/loader.h"
#include "agi/preagi/preagi.h"
#include "agi/preagi/mickey.h"
diff --git a/engines/agi/loader.h b/engines/agi/loader.h
new file mode 100644
index 00000000000..2f5bebbbabd
--- /dev/null
+++ b/engines/agi/loader.h
@@ -0,0 +1,172 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AGI_LOADER_H
+#define AGI_LOADER_H
+
+namespace Agi {
+
+class AgiLoader {
+public:
+ AgiLoader(AgiEngine *vm) : _vm(vm) {}
+ virtual ~AgiLoader() {}
+
+ /**
+ * Performs one-time initializations, such as locating files
+ * with dynamic names.
+ */
+ virtual void init() {}
+
+ /**
+ * Loads all AGI directory entries from disk and and populates
+ * the AgiDir arrays in AgiGame with them.
+ */
+ virtual int loadDirs() = 0;
+
+ /**
+ * Loads a volume resource from disk.
+ */
+ virtual uint8 *loadVolumeResource(AgiDir *agid) = 0;
+
+ /**
+ * Loads AgiEngine::_objects from disk.
+ */
+ virtual int loadObjects() = 0;
+
+ /**
+ * Loads AgiBase::_words from disk.
+ */
+ virtual int loadWords() = 0;
+
+protected:
+ AgiEngine *_vm;
+};
+
+struct AgiDiskVolume {
+ uint32 disk;
+ uint32 offset;
+
+ AgiDiskVolume() : disk(_EMPTY), offset(0) {}
+ AgiDiskVolume(uint32 d, uint32 o) : disk(d), offset(o) {}
+};
+
+/**
+ * Apple II version of the format for LOGDIR, VIEWDIR, etc.
+ * See AgiLoader_A2::loadDir for more details.
+ */
+enum A2DirVersion {
+ A2DirVersionOld, // 4 bits for volume, 8 for track
+ A2DirVersionNew, // 5 bits for volume, 7 for track
+};
+
+class AgiLoader_A2 : public AgiLoader {
+public:
+ AgiLoader_A2(AgiEngine *vm) : AgiLoader(vm) {}
+ ~AgiLoader_A2() override;
+
+ void init() override;
+ int loadDirs() override;
+ uint8 *loadVolumeResource(AgiDir *agid) override;
+ int loadObjects() override;
+ int loadWords() override;
+
+private:
+ Common::Array<Common::SeekableReadStream *> _disks;
+ Common::Array<AgiDiskVolume> _volumes;
+ AgiDir _logDir;
+ AgiDir _picDir;
+ AgiDir _viewDir;
+ AgiDir _soundDir;
+ AgiDir _objects;
+ AgiDir _words;
+
+ int readDiskOne(Common::SeekableReadStream &stream, Common::Array<uint32> &volumeMap);
+ static bool readInitDir(Common::SeekableReadStream &stream, byte index, AgiDir &agid);
+ static bool readDir(Common::SeekableReadStream &stream, int position, AgiDir &agid);
+ static bool readVolumeMap(Common::SeekableReadStream &stream, uint32 position, uint32 bufferLength, Common::Array<uint32> &volumeMap);
+
+ A2DirVersion detectDirVersion(Common::SeekableReadStream &stream) const;
+ static bool loadDir(AgiDir *dir, Common::SeekableReadStream &disk, uint32 dirOffset, uint32 dirLength, A2DirVersion dirVersion);
+};
+
+class AgiLoader_v1 : public AgiLoader {
+public:
+ AgiLoader_v1(AgiEngine *vm) : AgiLoader(vm) {}
+
+ void init() override;
+ int loadDirs() override;
+ uint8 *loadVolumeResource(AgiDir *agid) override;
+ int loadObjects() override;
+ int loadWords() override;
+
+private:
+ Common::Array<Common::String> _imageFiles;
+ Common::Array<AgiDiskVolume> _volumes;
+ AgiDir _logDir;
+ AgiDir _picDir;
+ AgiDir _viewDir;
+ AgiDir _soundDir;
+ AgiDir _objects;
+ AgiDir _words;
+
+ bool readDiskOneV1(Common::SeekableReadStream &stream);
+ bool readDiskOneV2001(Common::SeekableReadStream &stream, int &vol0Offset);
+ static bool readInitDirV1(Common::SeekableReadStream &stream, byte index, AgiDir &agid);
+ static bool readInitDirV2001(Common::SeekableReadStream &stream, byte index, AgiDir &agid);
+
+ bool loadDir(AgiDir *dir, Common::File &disk, uint32 dirOffset, uint32 dirLength);
+};
+
+class AgiLoader_v2 : public AgiLoader {
+private:
+ bool _hasV3VolumeFormat;
+
+ int loadDir(AgiDir *agid, const char *fname);
+ bool detectV3VolumeFormat();
+
+public:
+ AgiLoader_v2(AgiEngine *vm) : _hasV3VolumeFormat(false), AgiLoader(vm) {}
+
+ int loadDirs() override;
+ uint8 *loadVolumeResource(AgiDir *agid) override;
+ int loadObjects() override;
+ int loadWords() override;
+};
+
+class AgiLoader_v3 : public AgiLoader {
+private:
+ Common::String _name; /**< prefix in directory and/or volume file names (`GR' for goldrush) */
+
+ int loadDir(AgiDir *agid, Common::File *fp, uint32 offs, uint32 len);
+
+public:
+ AgiLoader_v3(AgiEngine *vm) : AgiLoader(vm) {}
+
+ void init() override;
+ int loadDirs() override;
+ uint8 *loadVolumeResource(AgiDir *agid) override;
+ int loadObjects() override;
+ int loadWords() override;
+};
+
+} // End of namespace Agi
+
+#endif /* AGI_LOADER_H */
diff --git a/engines/agi/loader_a2.cpp b/engines/agi/loader_a2.cpp
index 9fb0fe5daeb..92653e7817f 100644
--- a/engines/agi/loader_a2.cpp
+++ b/engines/agi/loader_a2.cpp
@@ -21,6 +21,7 @@
#include "agi/agi.h"
#include "agi/disk_image.h"
+#include "agi/loader.h"
#include "agi/words.h"
#include "common/config-manager.h"
@@ -354,7 +355,7 @@ int AgiLoader_A2::loadDirs() {
return success ? errOK : errBadResource;
}
-A2DirVersion AgiLoader_A2::detectDirVersion(Common::SeekableReadStream &stream) {
+A2DirVersion AgiLoader_A2::detectDirVersion(Common::SeekableReadStream &stream) const {
// A2 DIR format:
// old new
// volume 4 bits 5 bits
@@ -366,7 +367,7 @@ A2DirVersion AgiLoader_A2::detectDirVersion(Common::SeekableReadStream &stream)
// It must exist in the new format, but can't exist in the old.
// In the new format it's the first resource in volume 1.
// In the old format it would be track 128, which is invalid.
- AgiDir *dirs[4] = { &_logDir, &_picDir, &_viewDir, &_soundDir };
+ const AgiDir *dirs[4] = { &_logDir, &_picDir, &_viewDir, &_soundDir };
for (int d = 0; d < 4; d++) {
stream.seek(dirs[d]->offset);
uint16 dirEntryCount = MIN<uint32>(dirs[d]->len / 3, MAX_DIRECTORY_ENTRIES);
diff --git a/engines/agi/loader_v1.cpp b/engines/agi/loader_v1.cpp
index 8291ac5ab8b..dbb653964b0 100644
--- a/engines/agi/loader_v1.cpp
+++ b/engines/agi/loader_v1.cpp
@@ -21,6 +21,7 @@
#include "agi/agi.h"
#include "agi/disk_image.h"
+#include "agi/loader.h"
#include "agi/words.h"
#include "common/config-manager.h"
diff --git a/engines/agi/loader_v2.cpp b/engines/agi/loader_v2.cpp
index cf686c04822..4cb46567ba3 100644
--- a/engines/agi/loader_v2.cpp
+++ b/engines/agi/loader_v2.cpp
@@ -22,6 +22,7 @@
#include "common/textconsole.h"
#include "agi/agi.h"
+#include "agi/loader.h"
#include "agi/lzw.h"
#include "agi/words.h"
diff --git a/engines/agi/loader_v3.cpp b/engines/agi/loader_v3.cpp
index db920bc2e4f..97530330306 100644
--- a/engines/agi/loader_v3.cpp
+++ b/engines/agi/loader_v3.cpp
@@ -20,6 +20,7 @@
*/
#include "agi/agi.h"
+#include "agi/loader.h"
#include "agi/lzw.h"
#include "agi/words.h"
Commit: 7f82a75d8b79e0bcc755149808b39452bae8faaf
https://github.com/scummvm/scummvm/commit/7f82a75d8b79e0bcc755149808b39452bae8faaf
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:00-07:00
Commit Message:
AGI: Create AgiLoader method for locating disk images
Changed paths:
A engines/agi/loader.cpp
engines/agi/loader.h
engines/agi/loader_a2.cpp
engines/agi/loader_v1.cpp
engines/agi/module.mk
diff --git a/engines/agi/loader.cpp b/engines/agi/loader.cpp
new file mode 100644
index 00000000000..0287b2e08ce
--- /dev/null
+++ b/engines/agi/loader.cpp
@@ -0,0 +1,62 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/loader.h"
+
+#include "common/config-manager.h"
+#include "common/fs.h"
+
+namespace Agi {
+
+void AgiLoader::getPotentialDiskImages(
+ const char * const *imageExtensions,
+ size_t imageExtensionCount,
+ Common::Array<Common::Path> &imageFiles,
+ FileMap &fileMap) {
+
+ // get all files in game directory
+ Common::FSList allFiles;
+ Common::FSNode dir(ConfMan.getPath("path"));
+ if (!dir.getChildren(allFiles, Common::FSNode::kListFilesOnly)) {
+ warning("invalid game path: %s", dir.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ return;
+ }
+
+ // build array of files with provided disk image extensions
+ for (const Common::FSNode &file : allFiles) {
+ for (size_t i = 0; i < imageExtensionCount; i++) {
+ if (file.getName().hasSuffixIgnoreCase(imageExtensions[i])) {
+ Common::Path path = file.getPath();
+ imageFiles.push_back(path);
+ fileMap[path] = file;
+ break;
+ }
+ }
+ }
+
+ // sort potential image files by name.
+ // this is an important step for consistent results,
+ // and because the first disk is likely to be first.
+ Common::sort(imageFiles.begin(), imageFiles.end());
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/loader.h b/engines/agi/loader.h
index 2f5bebbbabd..afd23471e26 100644
--- a/engines/agi/loader.h
+++ b/engines/agi/loader.h
@@ -58,6 +58,13 @@ public:
protected:
AgiEngine *_vm;
+
+ typedef Common::HashMap<Common::Path, Common::FSNode, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> FileMap;
+ static void getPotentialDiskImages(
+ const char * const *imageExtensions,
+ size_t imageExtensionCount,
+ Common::Array<Common::Path> &imageFiles,
+ FileMap &fileMap);
};
struct AgiDiskVolume {
diff --git a/engines/agi/loader_a2.cpp b/engines/agi/loader_a2.cpp
index 92653e7817f..3e52ffa198c 100644
--- a/engines/agi/loader_a2.cpp
+++ b/engines/agi/loader_a2.cpp
@@ -24,7 +24,6 @@
#include "agi/loader.h"
#include "agi/words.h"
-#include "common/config-manager.h"
#include "common/formats/disk_image.h"
#include "common/fs.h"
#include "common/memstream.h"
@@ -59,8 +58,6 @@ namespace Agi {
// AgiMetaEngineDetection also scans for usable disk images. It finds the LOGDIR
// file inside disk one, hashes LOGDIR, and matches against the detection table.
-typedef Common::HashMap<Common::Path, Common::FSNode, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> FileMap;
-
AgiLoader_A2::~AgiLoader_A2() {
for (uint d = 0; d < _disks.size(); d++) {
delete _disks[d];
@@ -68,30 +65,10 @@ AgiLoader_A2::~AgiLoader_A2() {
}
void AgiLoader_A2::init() {
- // get all files in game directory
- Common::FSList allFiles;
- Common::FSNode dir(ConfMan.getPath("path"));
- if (!dir.getChildren(allFiles, Common::FSNode::kListFilesOnly)) {
- warning("AgiLoader_A2: invalid game path: %s", dir.getPath().toString(Common::Path::kNativeSeparator).c_str());
- return;
- }
-
- // build array of files with a2 disk image extensions
+ // build sorted array of files with image extensions
Common::Array<Common::Path> imageFiles;
FileMap fileMap;
- for (const Common::FSNode &file : allFiles) {
- for (int i = 0; i < ARRAYSIZE(a2DiskImageExtensions); i++) {
- if (file.getName().hasSuffixIgnoreCase(a2DiskImageExtensions[i])) {
- Common::Path path = file.getPath();
- imageFiles.push_back(path);
- fileMap[path] = file;
- break;
- }
- }
- }
-
- // sort potential image files by name
- Common::sort(imageFiles.begin(), imageFiles.end());
+ getPotentialDiskImages(a2DiskImageExtensions, ARRAYSIZE(a2DiskImageExtensions), imageFiles, fileMap);
// find disk one by reading potential images until successful
int diskCount = 0;
diff --git a/engines/agi/loader_v1.cpp b/engines/agi/loader_v1.cpp
index dbb653964b0..36e8be6c07f 100644
--- a/engines/agi/loader_v1.cpp
+++ b/engines/agi/loader_v1.cpp
@@ -24,7 +24,6 @@
#include "agi/loader.h"
#include "agi/words.h"
-#include "common/config-manager.h"
#include "common/fs.h"
namespace Agi {
@@ -50,32 +49,11 @@ namespace Agi {
// AgiMetaEngineDetection also scans for usable disk images. It finds the LOGDIR
// file inside disk one, hashes LOGDIR, and matches against the detection table.
-typedef Common::HashMap<Common::Path, Common::FSNode, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> FileMap;
-
void AgiLoader_v1::init() {
- // get all files in game directory
- Common::FSList allFiles;
- Common::FSNode dir(ConfMan.getPath("path"));
- if (!dir.getChildren(allFiles, Common::FSNode::kListFilesOnly)) {
- warning("AgiLoader_v1: invalid game path: %s", dir.getPath().toString(Common::Path::kNativeSeparator).c_str());
- return;
- }
-
- // build array of files with pc disk image extensions
+ // build sorted array of files with image extensions
Common::Array<Common::Path> imageFiles;
FileMap fileMap;
- for (const Common::FSNode &file : allFiles) {
- for (int i = 0; i < ARRAYSIZE(pcDiskImageExtensions); i++) {
- if (file.getName().hasSuffixIgnoreCase(pcDiskImageExtensions[i])) {
- Common::Path path = file.getPath();
- imageFiles.push_back(path);
- fileMap[path] = file;
- }
- }
- }
-
- // sort potential image files by name
- Common::sort(imageFiles.begin(), imageFiles.end());
+ getPotentialDiskImages(pcDiskImageExtensions, ARRAYSIZE(pcDiskImageExtensions), imageFiles, fileMap);
// find disk one by reading potential images until successful
uint diskOneIndex;
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index b79da837f3c..5bb986a11c8 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -11,6 +11,7 @@ MODULE_OBJS := \
graphics.o \
inv.o \
keyboard.o \
+ loader.o \
loader_a2.o \
loader_v1.o \
loader_v2.o \
Commit: 2f6183dcfab3edaed10efe3887921137fdc32480
https://github.com/scummvm/scummvm/commit/2f6183dcfab3edaed10efe3887921137fdc32480
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:00-07:00
Commit Message:
AGI: Add KQ1-Early PC loader and detection
Changed paths:
A engines/agi/loader_gal.cpp
engines/agi/agi.cpp
engines/agi/agi.h
engines/agi/detection.cpp
engines/agi/detection_tables.h
engines/agi/disk_image.h
engines/agi/loader.h
engines/agi/metaengine.cpp
engines/agi/module.mk
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 746b2b8bc2c..ad8bef1bea2 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -169,6 +169,13 @@ int AgiEngine::agiInit() {
applyVolumeToMixer();
+ // Error on Game Adaptation Language, because it is not implemented yet.
+ // This allows testing the GAL components that have been developed, such
+ // as the resource loader, with our debug console.
+ if (getGameType() == GType_GAL) {
+ error("Game Adaptation Language not implemented yet");
+ }
+
return ec;
}
@@ -521,7 +528,9 @@ void AgiEngine::initialize() {
_text->charAttrib_Set(15, 0);
- if (getPlatform() == Common::kPlatformApple2) {
+ if (getGameType() == GType_GAL) {
+ _loader = new GalLoader(this);
+ } else if (getPlatform() == Common::kPlatformApple2) {
_loader = new AgiLoader_A2(this);
} else if (getVersion() <= 0x2001) {
_loader = new AgiLoader_v1(this);
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index cbfe622a5b4..a46f38d18d0 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -99,10 +99,11 @@ namespace Agi {
enum AgiGameType {
GType_PreAGI = 0,
- GType_V1 = 1,
- GType_V2 = 2,
- GType_V3 = 3,
- GType_A2 = 4
+ GType_V1 = 1,
+ GType_V2 = 2,
+ GType_V3 = 3,
+ GType_A2 = 4,
+ GType_GAL = 5
};
enum AgiGameFeatures {
diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp
index 6999c0accc3..865b653004f 100644
--- a/engines/agi/detection.cpp
+++ b/engines/agi/detection.cpp
@@ -19,7 +19,6 @@
*
*/
-
#include "common/config-manager.h"
#include "common/system.h"
#include "common/debug.h"
@@ -129,6 +128,8 @@ private:
static Common::String getLogDirHashFromA2DiskImage(Common::SeekableReadStream &stream);
static Common::String getLogDirHashFromDiskImage(Common::SeekableReadStream &stream, uint32 position);
+
+ static Common::String getGalDirHashFromDiskImage(Common::SeekableReadStream &stream);
};
ADDetectedGame AgiMetaEngineDetection::fallbackDetect(const FileMap &allFilesXXX, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
@@ -359,9 +360,10 @@ void AgiMetaEngineDetection::getPotentialDiskImages(
}
/**
- * Detects a PC Booter game by searching for 360k floppy images, reading LOGDIR,
- * hashing LOGDIR, and comparing to DOS GType_V1 entries in the detection table.
- * See AgiLoader_v1 in loader_v1.cpp for more details.
+ * Detects a PC Booter game by searching for 360k floppy images, reading LOGDIR
+ * or GAL's directory, hashing, and comparing to DOS GType_V1 and GType_GAL
+ * entries in the detection table.
+ * See AgiLoader_v1 and GalLoader for more details.
*/
ADDetectedGame AgiMetaEngineDetection::detectPcDiskImageGame(const FileMap &allFiles, uint32 skipADFlags) {
// build array of files with pc disk image extensions
@@ -378,6 +380,8 @@ ADDetectedGame AgiMetaEngineDetection::detectPcDiskImageGame(const FileMap &allF
// attempt to locate and hash logdir using both possible inidir disk locations
Common::String logdirHash1 = getLogDirHashFromPcDiskImageV1(*stream);
Common::String logdirHash2 = getLogDirHashFromPcDiskImageV2001(*stream);
+ // attempt to locate and hash GAL directory
+ Common::String galDirHash = getGalDirHashFromDiskImage(*stream);
delete stream;
if (!logdirHash1.empty()) {
@@ -386,19 +390,28 @@ ADDetectedGame AgiMetaEngineDetection::detectPcDiskImageGame(const FileMap &allF
if (!logdirHash2.empty()) {
debug(3, "pc disk logdir hash: %s, %s", logdirHash2.c_str(), imageFile.baseName().c_str());
}
+ if (!galDirHash.empty()) {
+ debug(3, "pc disk gal dir hash: %s, %s", galDirHash.c_str(), imageFile.baseName().c_str());
+ }
- // if logdir hash found then compare against hashes of DOS GType_V1 entries
- if (!logdirHash1.empty() || !logdirHash2.empty()) {
+ // if hash found then compare against hashes of DOS GType_V1 entries
+ if (!logdirHash1.empty() || !logdirHash2.empty() || !galDirHash.empty()) {
for (const AGIGameDescription *game = gameDescriptions; game->desc.gameId != nullptr; game++) {
- if (game->desc.platform == Common::kPlatformDOS && game->gameType == GType_V1 && !(game->desc.flags & skipADFlags)) {
+ if (game->desc.platform == Common::kPlatformDOS &&
+ (game->gameType == GType_V1 || game->gameType == GType_GAL) &&
+ !(game->desc.flags & skipADFlags)) {
+
const ADGameFileDescription *file;
for (file = game->desc.filesDescriptions; file->fileName != nullptr; file++) {
- // select the logdir hash to use by the game's interpreter version
- Common::String &logdirHash = (game->version < 0x2001) ? logdirHash1 : logdirHash2;
- if (file->md5 != nullptr && !logdirHash.empty() && file->md5 == logdirHash) {
+ // select the hash hash to use
+ Common::String &hash = (game->gameType == GType_V1) ?
+ ((game->version < 0x2001) ? logdirHash1 : logdirHash2) :
+ galDirHash;
+
+ if (file->md5 != nullptr && !hash.empty() && file->md5 == hash) {
debug(3, "disk image match: %s, %s, %s", game->desc.gameId, game->desc.extra, imageFile.baseName().c_str());
- // logdir hash match found
+ // hash match found
ADDetectedGame detectedGame(&game->desc);
FileProperties fileProps;
fileProps.md5 = file->md5;
@@ -579,6 +592,44 @@ Common::String AgiMetaEngineDetection::getLogDirHashFromDiskImage(Common::Seekab
return Common::computeStreamMD5AsString(stream, logDirSize);
}
+Common::String AgiMetaEngineDetection::getGalDirHashFromDiskImage(Common::SeekableReadStream &stream) {
+ static const uint16 dirPositions[] = { GAL_DIR_POSITION_PCJR, GAL_DIR_POSITION_PC };
+ for (int i = 0; i < 2; i++) {
+ stream.seek(dirPositions[i]);
+
+ // read logic 0 position
+ byte b0 = stream.readByte();
+ byte b1 = stream.readByte();
+ byte b2 = stream.readByte();
+ byte b3 = stream.readByte();
+ uint16 offset = ((b1 & 0x80) << 1) | b0;
+ uint16 sector = ((b2 & 0x03) << 8) | b3;
+ uint32 logicPosition = (sector * 512) + offset;
+
+ // read logic 0 header, calculate length
+ stream.seek(logicPosition);
+ uint32 logicSize = 8;
+ for (int j = 0; j < 4; j++) {
+ logicSize += stream.readUint16LE();
+ }
+ if (stream.eos()) {
+ continue;
+ }
+
+ // confirm that logic ends in terminator
+ stream.seek(logicPosition + logicSize -1);
+ byte logicTerminator = stream.readByte();
+ if (stream.eos() || logicTerminator != 0xff) {
+ continue;
+ }
+
+ // hash the directory
+ stream.seek(dirPositions[i]);
+ return Common::computeStreamMD5AsString(stream, GAL_DIR_SIZE);
+ }
+ return "";
+}
+
} // end of namespace Agi
REGISTER_PLUGIN_STATIC(AGI_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Agi::AgiMetaEngineDetection);
diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h
index 520db710649..d033f28ccf8 100644
--- a/engines/agi/detection_tables.h
+++ b/engines/agi/detection_tables.h
@@ -117,7 +117,7 @@ namespace Agi {
#define A2_CP(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformApple2,GType_A2,GAMEOPTIONS_DEFAULT_CP,ADGF_UNSTABLE)
#define BOOTER(id,extra,md5,ver,gid) GAME_LVFPN(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V1,GAMEOPTIONS_DEFAULT)
#define BOOTER_UNSTABLE(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V1,GAMEOPTIONS_DEFAULT,ADGF_UNSTABLE)
-#define BOOTER_UNSUPPORTED(id,msg,fname,md5,size,ver,gid) GAME_LVFPN_FLAGS(id,msg,fname,md5,size,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V1,GAMEOPTIONS_DEFAULT,ADGF_UNSUPPORTED)
+#define BOOTER_GAL(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_GAL,GAMEOPTIONS_DEFAULT,ADGF_UNSUPPORTED)
#define GAME(id,extra,md5,ver,gid) GAME_LVFPN(id,extra,"logdir",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V2,GAMEOPTIONS_DEFAULT)
#define GAME3(id,extra,fname,md5,ver,gid) GAME_LVFPN(id,extra,fname,md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V3,GAMEOPTIONS_DEFAULT)
#define GAME3_CP(id,extra,fname,md5,ver,gid) GAME_LVFPN(id,extra,fname,md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V3,GAMEOPTIONS_DEFAULT_CP)
@@ -343,13 +343,19 @@ static const AGIGameDescription gameDescriptions[] = {
// King's Quest 1 (Mac) 2.0C 3/26/87
GAME_P("kq1", "2.0C 1987-03-26", "d4c4739d4ac63f7dbd29255425077d48", 0x2440, GID_KQ1, Common::kPlatformMacintosh),
- // King's Quest 1 (IBM PCjr) 1.00 1502265 5/10/84
- BOOTER_UNSUPPORTED("kq1", "Early King\'s Quest releases are not currently supported.",
- "kq1.img", "127675735f9d2c148738c1e96ea9d2cf", 368640, 0x1120, GID_KQ1),
+ // King's Quest 1 (IBM PCjr) 1984-05-10
+ BOOTER_GAL("kq1", "Early King\'s Quest releases are not currently supported.", "0d1cca805d08438a1dc83431b7348fe3", 0x1000, GID_KQ1),
- // King's Quest 1 (Tandy 1000) 01.01.00 5/24/84
- BOOTER_UNSUPPORTED("kq1", "Early King\'s Quest releases are not currently supported.",
- "kq1.img", "0a22131d0eaf66d955afecfdc83ef9d6", 368640, 0x1120, GID_KQ1),
+ // King's Quest 1 (IBM PC CGA) 1984-05-30
+ BOOTER_GAL("kq1", "Early King\'s Quest releases are not currently supported.", "6ba3a845502508c99a6cb2eed92f030d", 0x1000, GID_KQ1),
+
+ // King's Quest 1 (IBM PC CGA+RGB) 1984-08-16
+ BOOTER_GAL("kq1", "Early King\'s Quest releases are not currently supported.", "f44abc925bbfee1fede7ba42708a6d00", 0x1000, GID_KQ1),
+
+ // King's Quest 1 (Tandy 1000) 01.01.00 1985-05-24
+ // King's Quest 1 (Tandy 1000 + IBM PCjr) 1985-09-04
+ // These versions have identical resources and resource directories
+ BOOTER_GAL("kq1", "Early King\'s Quest releases are not currently supported.", "5be8342f00f7d951d0a4ee2e5c9f5b31", 0x1000, GID_KQ1),
// King's Quest 1 (PC 5.25"/3.5") 2.0F [AGI 2.917]
GAME("kq1", "2.0F 1987-05-05 5.25\"/3.5\"", "10ad66e2ecbd66951534a50aedcd0128", 0x2917, GID_KQ1),
diff --git a/engines/agi/disk_image.h b/engines/agi/disk_image.h
index 941c081d5cf..bb5a01a5e6b 100644
--- a/engines/agi/disk_image.h
+++ b/engines/agi/disk_image.h
@@ -98,6 +98,17 @@ static const char * const a2DiskImageExtensions[] = { ".do", ".dsk", ".img", ".n
#define A2_BC_DISK_COUNT 5
#define A2_BC_VOLUME_COUNT 9
+// GAL disk image values and helpers for GalLoader and AgiMetaEngineDetection
+
+#define GAL_LOGIC_COUNT 84
+#define GAL_PICTURE_COUNT 84
+#define GAL_VIEW_COUNT 110
+#define GAL_SOUND_COUNT 10
+
+#define GAL_DIR_POSITION_PCJR 0x0500
+#define GAL_DIR_POSITION_PC 0x1400
+#define GAL_DIR_SIZE 948
+
Common::SeekableReadStream *openPCDiskImage(const Common::Path &path, const Common::FSNode &node);
Common::SeekableReadStream *openA2DiskImage(const Common::Path &path, const Common::FSNode &node, bool loadAllTracks = true);
diff --git a/engines/agi/loader.h b/engines/agi/loader.h
index afd23471e26..028a0047acc 100644
--- a/engines/agi/loader.h
+++ b/engines/agi/loader.h
@@ -174,6 +174,24 @@ public:
int loadWords() override;
};
+class GalLoader : public AgiLoader {
+public:
+ GalLoader(AgiEngine *vm) : AgiLoader(vm) {}
+
+ void init() override;
+ int loadDirs() override;
+ uint8 *loadVolumeResource(AgiDir *agid) override;
+ int loadObjects() override;
+ int loadWords() override;
+
+private:
+ Common::String _imageFile;
+ int _dirOffset;
+
+ static bool isDirectory(Common::SeekableReadStream &stream, uint32 dirOffset);
+ static uint32 readDirectoryEntry(Common::SeekableReadStream &stream, uint32 *sectorCount);
+};
+
} // End of namespace Agi
#endif /* AGI_LOADER_H */
diff --git a/engines/agi/loader_gal.cpp b/engines/agi/loader_gal.cpp
new file mode 100644
index 00000000000..62b24d74e1c
--- /dev/null
+++ b/engines/agi/loader_gal.cpp
@@ -0,0 +1,256 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/disk_image.h"
+#include "agi/loader.h"
+#include "agi/words.h"
+
+#include "common/fs.h"
+
+namespace Agi {
+
+// GalLoader reads KQ1 PC Booter floppy disk images.
+//
+// All disks are 360k. The only supported image format is "raw". There are no
+// headers, footers, or metadata. Each image file must be exactly 368,640 bytes.
+//
+// All KQ1 PC booter versions are only one disk.
+//
+// The disks do not use a standard file system. Instead, file locations are
+// stored in a directory structure at known locations.
+//
+// File detection is done a little differently. Instead of requiring hard-coded
+// names for the image files, we scan the game directory for the first usable
+// disk image file. The only naming requirement is that the image has a known
+// file extension.
+//
+// AgiMetaEngineDetection also scans for usable disk images. It finds and hashes
+// the logic directory inside the disk, and matches against the detection table.
+
+/**
+ * Locates the disk image and the disk offset of the resource directory
+ */
+void GalLoader::init() {
+ // build sorted array of files with image extensions
+ Common::Array<Common::Path> imageFiles;
+ FileMap fileMap;
+ getPotentialDiskImages(pcDiskImageExtensions, ARRAYSIZE(pcDiskImageExtensions), imageFiles, fileMap);
+
+ // find the disk by reading potential images until successful
+ for (uint i = 0; i < imageFiles.size(); i++) {
+ const Common::Path &imageFile = imageFiles[i];
+ Common::SeekableReadStream *stream = openPCDiskImage(imageFile, fileMap[imageFile]);
+ if (stream == nullptr) {
+ continue;
+ }
+
+ // look for the directory in both locations
+ if (isDirectory(*stream, GAL_DIR_POSITION_PCJR)) {
+ _imageFile = imageFile.baseName();
+ _dirOffset = GAL_DIR_POSITION_PCJR;
+ } else if (isDirectory(*stream, GAL_DIR_POSITION_PC)) {
+ _imageFile = imageFile.baseName();
+ _dirOffset = GAL_DIR_POSITION_PC;
+ }
+
+ delete stream;
+ if (!_imageFile.empty()) {
+ break;
+ }
+ }
+
+ if (_imageFile.empty()) {
+ warning("GalLoader: disk not found");
+ }
+}
+
+/**
+ * Identifies the directory by validating the first few logic entries
+ */
+bool GalLoader::isDirectory(Common::SeekableReadStream &stream, uint32 dirOffset) {
+ for (int i = 0; i < 10; i++) {
+ stream.seek(dirOffset + (i * 4));
+
+ uint32 sectorCount;
+ uint32 logicOffset = readDirectoryEntry(stream, §orCount);
+
+ stream.seek(logicOffset);
+ uint32 logicSize = 8;
+ for (int j = 0; j < 4; j++) {
+ logicSize += stream.readUint16LE();
+ }
+
+ if (stream.eos()) {
+ return false;
+ }
+
+ stream.seek(logicOffset + logicSize - 1);
+ byte logicTerminator = stream.readByte();
+ if (stream.eos() || logicTerminator != 0xff) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Reads a directory entry.
+ *
+ * Returns the disk offset and the resource size in sectors.
+ */
+uint32 GalLoader::readDirectoryEntry(Common::SeekableReadStream &stream, uint32 *sectorCount) {
+ // 9 bit offset (last bit is MSB)
+ // 6 bit zero
+ // 5 bit sector count
+ // 2 bit zero
+ // 10 bit sector
+ byte b0 = stream.readByte();
+ byte b1 = stream.readByte();
+ byte b2 = stream.readByte();
+ byte b3 = stream.readByte();
+
+ uint16 offset = ((b1 & 0x80) << 1) | b0;
+ uint16 sector = ((b2 & 0x03) << 8) | b3;
+
+ *sectorCount = ((b1 & 0x01) << 4) | (b2 >> 4);
+ return (sector * 512) + offset;
+}
+
+int GalLoader::loadDirs() {
+ // if init didn't find disk then fail
+ if (_imageFile.empty()) {
+ return errFilesNotFound;
+ }
+
+ // open disk
+ Common::File disk;
+ if (!disk.open(Common::Path(_imageFile))) {
+ return errBadFileOpen;
+ }
+
+ // load logic and picture directory entries.
+ // pictures do not have directory entries. each picture immediately follows
+ // its logic. if there is no real picture then it is just the FF terminator.
+ uint32 sectorCount;
+ for (int i = 0; i < 84; i++) {
+ disk.seek(_dirOffset + (i * 4));
+ uint32 logicOffset = readDirectoryEntry(disk, §orCount);
+
+ // seek to logic and calculate length from header
+ disk.seek(logicOffset);
+ uint32 logicLength = 8;
+ for (int j = 0; j < 4; j++) {
+ logicLength += disk.readUint16LE();
+ }
+ if (disk.eos()) {
+ return errBadResource;
+ }
+
+ // scan for picture terminator after logic
+ uint32 pictureOffset = logicOffset + logicLength;
+ disk.seek(pictureOffset);
+ uint32 pictureLength = 0;
+ while (true) {
+ byte terminator = disk.readByte();
+ if (disk.eos()) {
+ return errBadResource;
+ }
+ if (terminator == 0xff) {
+ pictureLength = disk.pos() - pictureOffset;
+ break;
+ }
+ }
+
+ _vm->_game.dirLogic[i].offset = logicOffset;
+ _vm->_game.dirLogic[i].len = logicLength;
+ _vm->_game.dirPic[i].offset = pictureOffset;
+ _vm->_game.dirPic[i].len = pictureLength;
+ }
+
+ // load sound directory entries
+ for (int i = 0; i < 10; i++) {
+ disk.seek(_dirOffset + ((90 + i) * 4));
+ uint32 soundOffset = readDirectoryEntry(disk, §orCount);
+
+ // seek to sound and calculate length from header
+ disk.seek(soundOffset);
+ uint32 soundLength = 8;
+ for (int j = 0; j < 4; j++) {
+ soundLength += disk.readUint16LE();
+ }
+ if (disk.eos()) {
+ return errBadResource;
+ }
+
+ _vm->_game.dirSound[i].offset = soundOffset;
+ _vm->_game.dirSound[i].len = soundLength;
+ }
+
+ // load view directory entries
+ for (int i = 0; i < 110; i++) {
+ disk.seek(_dirOffset + ((128 + i) * 4));
+ uint32 viewOffset = readDirectoryEntry(disk, §orCount);
+
+ // seek to view and calculate length from header
+ disk.seek(viewOffset);
+ uint32 viewLength = 2 + disk.readUint16LE();
+ if (disk.eos()) {
+ return errBadResource;
+ }
+
+ _vm->_game.dirView[i].offset = viewOffset;
+ _vm->_game.dirView[i].len = viewLength;
+ }
+
+ return errOK;
+}
+
+uint8 *GalLoader::loadVolumeResource(AgiDir *agid) {
+ Common::File disk;
+ if (!disk.open(Common::Path(_imageFile))) {
+ warning("GalLoader: unable to open disk image: %s", _imageFile.c_str());
+ return nullptr;
+ }
+
+ // read resource
+ uint8 *data = (uint8 *)calloc(1, agid->len);
+ disk.seek(agid->offset);
+ if (disk.read(data, agid->len) != agid->len) {
+ warning("GalLoader: error reading %d bytes at offset %d", agid->len, agid->offset);
+ free(data);
+ return nullptr;
+ }
+
+ return data;
+}
+
+// TODO
+int GalLoader::loadObjects() {
+ return errOK;
+}
+
+// TODO
+int GalLoader::loadWords() {
+ return errOK;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/metaengine.cpp b/engines/agi/metaengine.cpp
index 4bf41a0b7b0..2d10bc543cf 100644
--- a/engines/agi/metaengine.cpp
+++ b/engines/agi/metaengine.cpp
@@ -259,6 +259,7 @@ Common::Error AgiMetaEngine::createInstance(OSystem *syst, Engine **engine, cons
case Agi::GType_V2:
case Agi::GType_V3:
case Agi::GType_A2:
+ case Agi::GType_GAL:
*engine = new Agi::AgiEngine(syst, gd);
break;
default:
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index 5bb986a11c8..b471c081eb8 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -13,6 +13,7 @@ MODULE_OBJS := \
keyboard.o \
loader.o \
loader_a2.o \
+ loader_gal.o \
loader_v1.o \
loader_v2.o \
loader_v3.o \
Commit: ea31107327841763c065bfd568f963191300b7f5
https://github.com/scummvm/scummvm/commit/ea31107327841763c065bfd568f963191300b7f5
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:00-07:00
Commit Message:
AGI: Add KQ1-Early Apple II loader and detection
Changed paths:
A engines/agi/loader_gal_a2.cpp
engines/agi/agi.cpp
engines/agi/detection.cpp
engines/agi/detection_tables.h
engines/agi/disk_image.h
engines/agi/loader.h
engines/agi/module.mk
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index ad8bef1bea2..7280068fa63 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -529,7 +529,11 @@ void AgiEngine::initialize() {
_text->charAttrib_Set(15, 0);
if (getGameType() == GType_GAL) {
- _loader = new GalLoader(this);
+ if (getPlatform() == Common::kPlatformApple2) {
+ _loader = new GalLoader_A2(this);
+ } else {
+ _loader = new GalLoader(this);
+ }
} else if (getPlatform() == Common::kPlatformApple2) {
_loader = new AgiLoader_A2(this);
} else if (getVersion() <= 0x2001) {
diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp
index 865b653004f..23b6ead8fba 100644
--- a/engines/agi/detection.cpp
+++ b/engines/agi/detection.cpp
@@ -129,7 +129,8 @@ private:
static Common::String getLogDirHashFromDiskImage(Common::SeekableReadStream &stream, uint32 position);
- static Common::String getGalDirHashFromDiskImage(Common::SeekableReadStream &stream);
+ static Common::String getGalDirHashFromPcDiskImage(Common::SeekableReadStream &stream);
+ static Common::String getGalDirHashFromA2DiskImage(Common::SeekableReadStream &stream);
};
ADDetectedGame AgiMetaEngineDetection::fallbackDetect(const FileMap &allFilesXXX, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
@@ -380,8 +381,8 @@ ADDetectedGame AgiMetaEngineDetection::detectPcDiskImageGame(const FileMap &allF
// attempt to locate and hash logdir using both possible inidir disk locations
Common::String logdirHash1 = getLogDirHashFromPcDiskImageV1(*stream);
Common::String logdirHash2 = getLogDirHashFromPcDiskImageV2001(*stream);
- // attempt to locate and hash GAL directory
- Common::String galDirHash = getGalDirHashFromDiskImage(*stream);
+ // attempt to locate and hash GAL directory
+ Common::String galDirHash = getGalDirHashFromPcDiskImage(*stream);
delete stream;
if (!logdirHash1.empty()) {
@@ -505,10 +506,12 @@ ADDetectedGame AgiMetaEngineDetection::detectA2DiskImageGame(const FileMap &allF
Common::String logdirHashInitdir = getLogDirHashFromA2DiskImage(*stream);
Common::String logdirHashBc = getLogDirHashFromDiskImage(*stream, A2_BC_LOGDIR_POSITION);
Common::String logdirHashKq2 = getLogDirHashFromDiskImage(*stream, A2_KQ2_LOGDIR_POSITION);
+ // attempt to locate and hash GAL directory.
+ Common::String logdirHashKq1 = getGalDirHashFromA2DiskImage(*stream);
delete stream;
if (!logdirHashInitdir.empty()) {
- debug(3, "disk image logdir hash: %s, %s", logdirHashInitdir.c_str(), imageFile.baseName().c_str());
+ debug(3, "disk image initdir hash: %s, %s", logdirHashInitdir.c_str(), imageFile.baseName().c_str());
}
if (!logdirHashBc.empty()) {
debug(3, "disk image logdir hash: %s, %s", logdirHashBc.c_str(), imageFile.baseName().c_str());
@@ -518,7 +521,7 @@ ADDetectedGame AgiMetaEngineDetection::detectA2DiskImageGame(const FileMap &allF
}
// if logdir hash found then compare against hashes of Apple II entries
- if (!logdirHashInitdir.empty() || !logdirHashBc.empty() || !logdirHashKq2.empty()) {
+ if (!logdirHashInitdir.empty() || !logdirHashBc.empty() || !logdirHashKq2.empty() || !logdirHashKq1.empty()) {
for (const AGIGameDescription *game = gameDescriptions; game->desc.gameId != nullptr; game++) {
if (game->desc.platform == Common::kPlatformApple2 && !(game->desc.flags & skipADFlags)) {
const ADGameFileDescription *file;
@@ -526,6 +529,7 @@ ADDetectedGame AgiMetaEngineDetection::detectA2DiskImageGame(const FileMap &allF
// select the logdir hash to use
Common::String &logdirHash = (game->gameID == GID_BC) ? logdirHashBc :
(game->gameID == GID_KQ2) ? logdirHashKq2 :
+ (game->gameID == GID_KQ1) ? logdirHashKq1 :
logdirHashInitdir;
if (file->md5 != nullptr && !logdirHash.empty() && file->md5 == logdirHash) {
debug(3, "disk image match: %s, %s, %s", game->desc.gameId, game->desc.extra, imageFile.baseName().c_str());
@@ -592,7 +596,7 @@ Common::String AgiMetaEngineDetection::getLogDirHashFromDiskImage(Common::Seekab
return Common::computeStreamMD5AsString(stream, logDirSize);
}
-Common::String AgiMetaEngineDetection::getGalDirHashFromDiskImage(Common::SeekableReadStream &stream) {
+Common::String AgiMetaEngineDetection::getGalDirHashFromPcDiskImage(Common::SeekableReadStream &stream) {
static const uint16 dirPositions[] = { GAL_DIR_POSITION_PCJR, GAL_DIR_POSITION_PC };
for (int i = 0; i < 2; i++) {
stream.seek(dirPositions[i]);
@@ -630,6 +634,12 @@ Common::String AgiMetaEngineDetection::getGalDirHashFromDiskImage(Common::Seekab
return "";
}
+Common::String AgiMetaEngineDetection::getGalDirHashFromA2DiskImage(Common::SeekableReadStream &stream) {
+ // hash the directory
+ stream.seek(GAL_A2_LOGDIR_POSITION);
+ return Common::computeStreamMD5AsString(stream, GAL_A2_LOGDIR_SIZE);
+}
+
} // end of namespace Agi
REGISTER_PLUGIN_STATIC(AGI_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Agi::AgiMetaEngineDetection);
diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h
index d033f28ccf8..71e49c63733 100644
--- a/engines/agi/detection_tables.h
+++ b/engines/agi/detection_tables.h
@@ -115,6 +115,7 @@ namespace Agi {
#define A2(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformApple2,GType_A2,GAMEOPTIONS_DEFAULT,ADGF_UNSTABLE)
#define A2_CP(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformApple2,GType_A2,GAMEOPTIONS_DEFAULT_CP,ADGF_UNSTABLE)
+#define A2_GAL(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformApple2,GType_GAL,GAMEOPTIONS_DEFAULT,ADGF_UNSUPPORTED)
#define BOOTER(id,extra,md5,ver,gid) GAME_LVFPN(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V1,GAMEOPTIONS_DEFAULT)
#define BOOTER_UNSTABLE(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V1,GAMEOPTIONS_DEFAULT,ADGF_UNSTABLE)
#define BOOTER_GAL(id,extra,md5,ver,gid) GAME_LVFPN_FLAGS(id,extra,"*",md5,AD_NO_SIZE,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_GAL,GAMEOPTIONS_DEFAULT,ADGF_UNSUPPORTED)
@@ -343,6 +344,9 @@ static const AGIGameDescription gameDescriptions[] = {
// King's Quest 1 (Mac) 2.0C 3/26/87
GAME_P("kq1", "2.0C 1987-03-26", "d4c4739d4ac63f7dbd29255425077d48", 0x2440, GID_KQ1, Common::kPlatformMacintosh),
+ // King's Quest 1 (Apple II)
+ A2_GAL("kq1", "Early King\'s Quest releases are not currently supported.", "a59f92b2d6e4fd245a8d51acdc58fc6d", 0x1000, GID_KQ1),
+
// King's Quest 1 (IBM PCjr) 1984-05-10
BOOTER_GAL("kq1", "Early King\'s Quest releases are not currently supported.", "0d1cca805d08438a1dc83431b7348fe3", 0x1000, GID_KQ1),
diff --git a/engines/agi/disk_image.h b/engines/agi/disk_image.h
index bb5a01a5e6b..741e4ec6f8b 100644
--- a/engines/agi/disk_image.h
+++ b/engines/agi/disk_image.h
@@ -109,6 +109,19 @@ static const char * const a2DiskImageExtensions[] = { ".do", ".dsk", ".img", ".n
#define GAL_DIR_POSITION_PC 0x1400
#define GAL_DIR_SIZE 948
+// GAL disk image values and helpers for GalLoader_A2 and AgiMetaEngineDetection
+
+#define GAL_A2_LOGIC_COUNT 81
+#define GAL_A2_PICTURE_COUNT 85
+#define GAL_A2_VIEW_COUNT 110
+
+#define GAL_A2_LOGDIR_POSITION A2_DISK_POSITION(18, 7, 2)
+#define GAL_A2_PICDIR_POSITION A2_DISK_POSITION(18, 6, 2)
+#define GAL_A2_VIEWDIR_POSITION A2_DISK_POSITION(18, 8, 2)
+#define GAL_A2_WORDS_POSITION A2_DISK_POSITION(17, 8, 0)
+#define GAL_A2_LOGDIR_SIZE (GAL_A2_LOGIC_COUNT * 3)
+#define GAL_A2_DISK_COUNT 3
+
Common::SeekableReadStream *openPCDiskImage(const Common::Path &path, const Common::FSNode &node);
Common::SeekableReadStream *openA2DiskImage(const Common::Path &path, const Common::FSNode &node, bool loadAllTracks = true);
diff --git a/engines/agi/loader.h b/engines/agi/loader.h
index 028a0047acc..aca9406ef07 100644
--- a/engines/agi/loader.h
+++ b/engines/agi/loader.h
@@ -192,6 +192,27 @@ private:
static uint32 readDirectoryEntry(Common::SeekableReadStream &stream, uint32 *sectorCount);
};
+class GalLoader_A2 : public AgiLoader {
+public:
+ GalLoader_A2(AgiEngine *vm) : AgiLoader(vm) {}
+ ~GalLoader_A2();
+
+ void init() override;
+ int loadDirs() override;
+ uint8 *loadVolumeResource(AgiDir *agid) override;
+ int loadObjects() override;
+ int loadWords() override;
+
+private:
+ Common::Array<Common::SeekableReadStream *> _disks;
+
+ static bool readDiskOne(Common::SeekableReadStream &disk, AgiDir *logicDir);
+ static bool readDirectoryEntry(Common::SeekableReadStream &stream, AgiDir &dirEntry);
+ static bool validateDisk(Common::SeekableReadStream &disk, byte diskIndex, AgiDir *logicDir);
+
+ static bool loadDir(AgiDir *dir, Common::SeekableReadStream &disk, uint32 dirOffset, uint32 dirCount);
+};
+
} // End of namespace Agi
#endif /* AGI_LOADER_H */
diff --git a/engines/agi/loader_gal_a2.cpp b/engines/agi/loader_gal_a2.cpp
new file mode 100644
index 00000000000..c3cd32362b3
--- /dev/null
+++ b/engines/agi/loader_gal_a2.cpp
@@ -0,0 +1,297 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "agi/agi.h"
+#include "agi/disk_image.h"
+#include "agi/loader.h"
+#include "agi/words.h"
+
+#include "common/fs.h"
+
+namespace Agi {
+
+// GalLoader_A2 reads KQ1 Apple II floppy disk images.
+//
+// Floppy disks have two sides; each side is a disk with its own image file.
+// All disk sides are 140k with 35 tracks and 16 sectors per track.
+//
+// KQ1 has three disk sides (labeled A, B, C) on two physical disks.
+//
+// Multiple disk image formats are supported; see Common::DiskImage. The file
+// extension determines the format. For example: .do, .dsk, .nib, .woz.
+//
+// The disks do not use a standard file system. Instead, file locations are
+// stored in directory structures at known locations.
+//
+// File detection is done a little differently. Instead of requiring hard-coded
+// names for the image files, we scan the game directory for the first usable
+// image of disk one, and then continue scanning until all disks are found.
+// The directory from disk one is used to identify each disk by its content.
+// The only naming requirement is that the images have a known file extension.
+//
+// AgiMetaEngineDetection also scans for usable disk images. It finds and hashes
+// the logic directory inside disk one, and matches against the detection table.
+
+GalLoader_A2::~GalLoader_A2() {
+ for (uint d = 0; d < _disks.size(); d++) {
+ delete _disks[d];
+ }
+}
+
+/**
+ * Locates the three disk images.
+ */
+void GalLoader_A2::init() {
+ // build sorted array of files with image extensions
+ Common::Array<Common::Path> imageFiles;
+ FileMap fileMap;
+ getPotentialDiskImages(a2DiskImageExtensions, ARRAYSIZE(a2DiskImageExtensions), imageFiles, fileMap);
+
+ // find disk one by reading potential images until successful
+ _disks.clear();
+ AgiDir logicDir[GAL_A2_LOGIC_COUNT];
+ uint diskOneIndex;
+ for (diskOneIndex = 0; diskOneIndex < imageFiles.size(); diskOneIndex++) {
+ const Common::Path &imageFile = imageFiles[diskOneIndex];
+ Common::SeekableReadStream *stream = openA2DiskImage(imageFile, fileMap[imageFile]);
+ if (stream == nullptr) {
+ warning("GalLoader_A2: unable to open disk image: %s", imageFile.baseName().c_str());
+ continue;
+ }
+
+ // read image as disk one
+ if (readDiskOne(*stream, logicDir)) {
+ debugC(3, "GalLoader_A2: disk one found: %s", imageFile.baseName().c_str());
+ _disks.resize(GAL_A2_DISK_COUNT);
+ _disks[0] = stream;
+ break;
+ } else {
+ delete stream;
+ }
+ }
+
+ // if disk one wasn't found, we're done
+ if (_disks.empty()) {
+ warning("GalLoader_A2: disk one not found");
+ return;
+ }
+
+ // find all other disks by comparing their contents to the logic directory.
+ int disksFound = 1;
+ for (uint i = 1; i < imageFiles.size() && disksFound < GAL_A2_DISK_COUNT; i++) {
+ uint imageFileIndex = (diskOneIndex + i) % imageFiles.size();
+ Common::Path &imageFile = imageFiles[imageFileIndex];
+
+ Common::SeekableReadStream *stream = openA2DiskImage(imageFile, fileMap[imageFile]);
+ if (stream == nullptr) {
+ continue;
+ }
+
+ // check each disk
+ bool diskFound = false;
+ for (int d = 1; d < GAL_A2_DISK_COUNT; d++) {
+ // has disk already been found?
+ if (_disks[d] != nullptr) {
+ continue;
+ }
+
+ if (validateDisk(*stream, d, logicDir)) {
+ _disks[d] = stream;
+ disksFound++;
+ diskFound = true;
+ break;
+ }
+ }
+
+ if (!diskFound) {
+ delete stream;
+ }
+ }
+}
+
+/**
+ * Reads a disk image as disk one by attempting to parse the logic directory
+ * and then validating that all the expected logic resources exist.
+ */
+bool GalLoader_A2::readDiskOne(Common::SeekableReadStream &disk, AgiDir *logicDir) {
+ disk.seek(GAL_A2_LOGDIR_POSITION);
+
+ // attempt to read logic directory
+ for (int i = 0; i < GAL_A2_LOGIC_COUNT; i++) {
+ if (!readDirectoryEntry(disk, logicDir[i])) {
+ return false;
+ }
+ }
+
+ // validate that all disk one logics exist
+ return validateDisk(disk, 0, logicDir);
+}
+
+/**
+ * Reads a directory entry.
+ */
+bool GalLoader_A2::readDirectoryEntry(Common::SeekableReadStream &stream, AgiDir &dirEntry) {
+ // GAL A2 DIR format:
+ // track 8 bits
+ // disk 4 bits (0 for all disks, else 1-3)
+ // sector 4 bits
+ // offset 8 bits
+ byte b0 = stream.readByte();
+ byte b1 = stream.readByte();
+ byte b2 = stream.readByte();
+
+ byte disk = b1 >> 4;
+ byte sector = b1 & 0x0f;
+ uint32 position = A2_DISK_POSITION(b0, sector, b2);
+
+ // use the first disk for resources that are on all disks
+ if (disk > 0) {
+ disk--;
+ }
+
+ // validate entry
+ if (!(disk <= 2 && position < A2_DISK_SIZE)) {
+ return false;
+ }
+
+ dirEntry.volume = disk;
+ dirEntry.offset = position;
+ return true;
+}
+
+/**
+ * Tests if a disk contains all of the expected logic resources.
+ */
+bool GalLoader_A2::validateDisk(Common::SeekableReadStream &disk, byte diskIndex, AgiDir *logicDir) {
+ for (int i = 0; i < GAL_A2_LOGIC_COUNT; i++) {
+ // Only validate logics on this disk
+ if (logicDir[i].volume != diskIndex) {
+ continue;
+ }
+
+ // Do not use logic 64 to validate a disk. Its logic header contains
+ // an incorrect length that is one byte too large. This would fail our
+ // validation method below of comparing the resource length in the A2
+ // header to the length in the logic header.
+ if (i == 64) {
+ continue;
+ }
+
+ // A2 resources begin with a header consisting of the resource length.
+ // Logic resources begin with a header consisting of four lengths; one
+ // for each section of the logic. If a logic exists at this location,
+ // then the A2 length will equal the calculated logic length.
+ disk.seek(logicDir[i].offset);
+ uint16 resourceLength = disk.readUint16LE();
+ uint32 logicLength = 8;
+ for (int j = 0; j < 4; j++) {
+ logicLength += disk.readUint16LE();
+ }
+ if (disk.eos() ||
+ resourceLength != logicLength ||
+ !(logicDir[i].offset + 2 + resourceLength <= A2_DISK_SIZE)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Load logic, pic, and view directories. KQ1-A2 has no sound resources.
+ */
+int GalLoader_A2::loadDirs() {
+ // if init didn't find disks then fail
+ if (_disks.empty()) {
+ return errFilesNotFound;
+ }
+ for (uint d = 0; d < _disks.size(); d++) {
+ if (_disks[d] == nullptr) {
+ warning("AgiLoader_A2: disk %d not found", d);
+ return errFilesNotFound;
+ }
+ }
+
+ // directories are on disk one
+ Common::SeekableReadStream &disk = *_disks[0];
+
+ bool success = true;
+ success &= loadDir(_vm->_game.dirLogic, disk, GAL_A2_LOGDIR_POSITION, GAL_A2_LOGIC_COUNT);
+ success &= loadDir(_vm->_game.dirPic, disk, GAL_A2_PICDIR_POSITION, GAL_A2_PICTURE_COUNT);
+ success &= loadDir(_vm->_game.dirView, disk, GAL_A2_VIEWDIR_POSITION, GAL_A2_VIEW_COUNT);
+ return success ? errOK : errBadResource;
+}
+
+/**
+ * Loads a resource directory.
+ */
+bool GalLoader_A2::loadDir(AgiDir *dir, Common::SeekableReadStream &disk, uint32 dirOffset, uint32 dirCount) {
+ disk.seek(dirOffset);
+ for (uint32 i = 0; i < dirCount; i++) {
+ // Skip pictures 0 and 81. These pictures do not exist, but the entries
+ // contain junk bytes. This did not matter in the original because they
+ // never loaded, but if we read them then they will fail validation.
+ if ((i == 0 || i == 81) && dirOffset == GAL_A2_PICDIR_POSITION) {
+ disk.skip(3);
+ continue;
+ }
+
+ if (!readDirectoryEntry(disk, dir[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+uint8 *GalLoader_A2::loadVolumeResource(AgiDir *agid) {
+ if (agid->volume >= _disks.size()) {
+ warning("GalLoader_A2: invalid volume: %d", agid->volume);
+ return nullptr;
+ }
+
+ Common::SeekableReadStream &disk = *_disks[agid->volume];
+
+ // seek to resource and read header (resource length)
+ disk.seek(agid->offset);
+ agid->len = disk.readUint16LE();
+
+ uint8 *data = (uint8 *)calloc(1, agid->len);
+ if (disk.read(data, agid->len) != agid->len) {
+ warning("GalLoader_A2: error reading %d bytes at volume %d offset %d", agid->len, agid->volume, agid->offset);
+ free(data);
+ return nullptr;
+ }
+
+ return data;
+}
+
+// TODO
+int GalLoader_A2::loadObjects() {
+ return errOK;
+}
+
+// TODO
+int GalLoader_A2::loadWords() {
+ // words location: GAL_A2_WORDS_POSITION
+ // two byte header with resource length.
+ return errOK;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index b471c081eb8..34c16d86be6 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -14,6 +14,7 @@ MODULE_OBJS := \
loader.o \
loader_a2.o \
loader_gal.o \
+ loader_gal_a2.o \
loader_v1.o \
loader_v2.o \
loader_v3.o \
Commit: 5e7826c473a85ed0e70bb477bf05f6e0d7b05272
https://github.com/scummvm/scummvm/commit/5e7826c473a85ed0e70bb477bf05f6e0d7b05272
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:01-07:00
Commit Message:
AGI: Update misc AGIv1 opcode details
Changed paths:
engines/agi/op_cmd.cpp
engines/agi/opcodes.cpp
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 057a4e30b39..dad102d3ff0 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -41,16 +41,10 @@ void cmdIncrement(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
uint16 varNr = parameter[0];
byte varVal = vm->getVar(varNr);
- if (vm->getVersion() < 0x2000) {
- if (varVal < 0xf0) {
- varVal++;
- vm->setVar(varNr, varVal);
- }
- } else {
- if (varVal != 0xff) {
- varVal++;
- vm->setVar(varNr, varVal);
- }
+ byte maxValue = (vm->getVersion() < 0x2000) ? 0xf0 : 0xff;
+ if (varVal < maxValue) {
+ varVal++;
+ vm->setVar(varNr, varVal);
}
}
@@ -577,7 +571,9 @@ void cmdNormalCycle(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
screenObj->cycle = kCycleNormal;
- screenObj->flags |= fCycling;
+ if (vm->getVersion() >= 0x2000) {
+ screenObj->flags |= fCycling;
+ }
}
void cmdReverseCycle(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
@@ -585,7 +581,9 @@ void cmdReverseCycle(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
screenObj->cycle = kCycleReverse;
- screenObj->flags |= fCycling;
+ if (vm->getVersion() >= 0x2000) {
+ screenObj->flags |= fCycling;
+ }
}
void cmdSetDir(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp
index 237b03295f8..a6af4a95dd4 100644
--- a/engines/agi/opcodes.cpp
+++ b/engines/agi/opcodes.cpp
@@ -63,7 +63,7 @@ static const AgiOpCodeDefinitionEntry opCodesV1[] = {
{ "subv", "vv", &cmdSubV }, // 08
{ "load.view", "n", &cmdLoadView }, // 09
{ "animate.obj", "n", &cmdAnimateObj }, // 0A
- { "new.room", "n", &cmdNewRoom }, // 0B
+ { "new.room", "n", &cmdNewRoom }, // 0B TODO
{ "draw.pic", "v", &cmdDrawPicV1 }, // 0C
{ "print", "s", &cmdPrint }, // 0D TODO
{ "status", "", &cmdStatus }, // 0E TODO
@@ -133,22 +133,22 @@ static const AgiOpCodeDefinitionEntry opCodesV1[] = {
{ "show.obj", "n", &cmdShowObj }, // 4E # show.obj (KQ2)
{ "load.logics", "n", &cmdLoadLogic }, // 4F # load.global.logics
{ "display", "nnns", &cmdDisplay }, // 50 TODO: 4 vs 3 args
- { "prevent.input???", "", &cmdUnknown }, // 51
- { "...", "", &cmdUnknown }, // 52 # nop
+ { "prevent.input", "", &cmdUnknown }, // 51 TODO: disables input by clearing a global, reset on new.room
+ { "...", "", &cmdUnknown }, // 52 nop, 0 args
{ "text.screen", "n", &cmdUnknown }, // 53
{ "graphics", "", &cmdUnknown }, // 54
{ "stop.motion", "", &cmdStopMotion }, // 55
{ "discard.view", "n", &cmdDiscardView }, // 56
{ "discard.pic", "v", &cmdDiscardPic }, // 57
{ "set.item.view", "nn", &cmdSetItemView }, // 58
- { "...", "", &cmdUnknown }, // 59 # reverse.cycle, unused in KQ2 or BC
+ { "reverse.cycle", "n", &cmdReverseCycle }, // 59
{ "last.cel", "nv", &cmdLastCel }, // 5A
{ "set.cel.v", "nv", &cmdSetCelF }, // 5B
- { "...", "", &cmdUnknown }, // 5C # normal.cycle, unused in KQ2 or BC
- { "load.view", "n", &cmdLoadView }, // 5D
- { "...", "", &cmdUnknown }, // 5E unused in KQ2 or BC
+ { "normal.cycle", "n", &cmdNormalCycle }, // 5C
+ { "load.view", "n", &cmdLoadView }, // 5D duplicate opcode: same table entry as 09
+ { "...", "n", &cmdUnknown }, // 5E nop, 1 arg
{ "...", "", &cmdUnknown }, // 5F BC script 102 when attempting to fill flask
- { "set.bit", "nv", &cmdSetBit }, // 60 BC
+ { "set.bit", "nv", &cmdSetBit }, // 60
{ "clear.bit", "nv", &cmdClearBit }, // 61
{ "set.upper.left", "nn", &cmdSetUpperLeft } // 62 BC Apple II
};
Commit: 6aedb0a75e71581917439b79014cee0a30cebd29
https://github.com/scummvm/scummvm/commit/6aedb0a75e71581917439b79014cee0a30cebd29
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:01-07:00
Commit Message:
AGI: Initialize member
Changed paths:
engines/agi/loader.h
diff --git a/engines/agi/loader.h b/engines/agi/loader.h
index aca9406ef07..969df381e5e 100644
--- a/engines/agi/loader.h
+++ b/engines/agi/loader.h
@@ -176,7 +176,7 @@ public:
class GalLoader : public AgiLoader {
public:
- GalLoader(AgiEngine *vm) : AgiLoader(vm) {}
+ GalLoader(AgiEngine *vm) : _dirOffset(0), AgiLoader(vm) {}
void init() override;
int loadDirs() override;
Commit: df6cb98d2d1aaed6eabf24feb2b9f613d22b6de9
https://github.com/scummvm/scummvm/commit/df6cb98d2d1aaed6eabf24feb2b9f613d22b6de9
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:01-07:00
Commit Message:
SCI32: Fix LSL6HIRES Interface Help cursor
Fixes bug #14591
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 34493ea1da8..713a21a45f7 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -9864,12 +9864,51 @@ static const uint16 larry6HiresPhoneOperatorPatch[] = {
PATCH_END
};
+// The Interface Help feature does not display its help cursor, but it instructs
+// the player to expect this. The script attempts to set the cursor, but it is
+// immediately reverted by the narration script. This is a script bug that
+// originated in the low resolution version; it only worked in text mode and
+// was incompatible with CD narration.
+//
+// We fix this by temporarily setting the global cursor variables to helpCusor
+// during Interface Help. This causes LarryTalker to use helpCursor instead of
+// waitCursor or the previous cursor. Sierra fixed this in the Mac version by
+// changing LarryTalker, so we limit this patch to PC versions by using little
+// endian values in the signature.
+//
+// Applies to: All PC versions
+// Responsible method: nClickHelp:doit
+// Fixes bug: #14591
+static const uint16 larry6HiresHelpCusorSignature[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_SELECTOR16(setCursor), // pushi setCusor
+ 0x78, // push1
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa helpCusor
+ 0x36, // push
+ 0x81, 0x01, // lag 01
+ 0x4a, 0x06, 0x00, // send 06 [ LSL6 setCursor: helpCursor ]
+ SIG_ADDTOOFFSET(+213),
+ 0x48, // ret
+ SIG_END
+};
+
+static const uint16 larry6HiresHelpCusorPatch[] = {
+ 0x89, 0x15, // lsg 15 [ store gWaitCursor on stack ]
+ PATCH_GETORIGINALBYTES(4, 3), // lofsa helpCursor
+ 0xa0, PATCH_UINT16(0x0015), // sag 0015 [ gWaitCursor = helpCursor ]
+ 0xa0, PATCH_UINT16(0x0013), // sag 0013 [ gTheCursor = helpCursor ]
+ PATCH_GETORIGINALBYTES(13, 213),
+ 0xa9, 0x15, // ssg 15 [ restore gWaitCursor from stack ]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry larry6HiresSignatures[] = {
{ true, 0, "disable mac volume restore", 1, larry6HiresMacVolumeRestoreSignature, larry6HiresMacVolumeRestorePatch },
{ true, 71, "disable volume reset on startup (1/2)", 1, sci2VolumeResetSignature, sci2VolumeResetPatch },
{ true, 71, "disable volume reset on startup (2/2)", 1, larry6HiresVolumeResetSignature, larry6HiresVolumeResetPatch },
{ true, 71, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
+ { true, 75, "fix help cursor", 1, larry6HiresHelpCusorSignature, larry6HiresHelpCusorPatch },
{ true, 270, "fix incorrect setScale call", 1, larry6HiresSetScaleSignature, larry6HiresSetScalePatch },
{ true, 330, "fix whale oil lamp lockup", 1, larry6HiresWhaleOilLampSignature, larry6HiresWhaleOilLampPatch },
{ true, 610, "phone operator crash", 1, larry6HiresPhoneOperatorSignature, larry6HiresPhoneOperatorPatch },
Commit: e85295359eb6153adf2138e9d82ec1a751234c65
https://github.com/scummvm/scummvm/commit/e85295359eb6153adf2138e9d82ec1a751234c65
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:02-07:00
Commit Message:
SCI: Fix LSL6 CD Interface Help cursor
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 713a21a45f7..6039f1fcd67 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -9618,7 +9618,7 @@ static const SciScriptPatcherEntry larry5Signatures[] = {
// course also in sierra sci).
// Applies to at least: German PC-CD
// Responsible method: unknown
-static const uint16 larry6SignatureDeathDialog[] = {
+static const uint16 larry6DeathDialogSignature[] = {
SIG_MAGICDWORD,
0x3e, SIG_UINT16(0x0133), // link 0133 (offset 0x20)
0x35, 0xff, // ldi ff
@@ -9642,7 +9642,7 @@ static const uint16 larry6SignatureDeathDialog[] = {
SIG_END
};
-static const uint16 larry6PatchDeathDialog[] = {
+static const uint16 larry6DeathDialogPatch[] = {
0x3e, 0x00, 0x02, // link 0200
PATCH_ADDTOOFFSET(+687),
0x5a, PATCH_UINT16(0x0004), PATCH_UINT16(0x0140), // lea temp[0140]
@@ -9653,9 +9653,48 @@ static const uint16 larry6PatchDeathDialog[] = {
PATCH_END
};
+// The Interface Help feature does not display its help cursor, but it instructs
+// the player to expect this. This bug occurs when CD speech is enabled. The
+// script attempts to set the cursor, but it is immediately reverted by the
+// narration script.
+//
+// We fix this by temporarily setting the game to text mode during Interface
+// Help. This causes the Narrator class to not change the cursor. This patch
+// overwrites the code in nClickHelp that saves and restores the cursor. This
+// code is redundant because nClickHelp's caller immediately resets the cursor.
+//
+// Applies to: All PC versions
+// Responsible method: nClickHelp:doit
+static const uint16 larry6HelpCursorSignature[] = {
+ SIG_MAGICDWORD,
+ 0x81, 0x13, // lag 13
+ 0xa5, 0x02, // sat 02 [ temp2 = gTheCursor ]
+ 0x38, SIG_SELECTOR16(setCursor), // pushi setCursor
+ 0x78, // push1
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa helpCursor
+ 0x36, // push
+ SIG_ADDTOOFFSET(+171),
+ 0x38, SIG_SELECTOR16(setCursor), // pushi setCursor
+ SIG_END
+};
+
+static const uint16 larry6HelpCursorPatch[] = {
+ 0x89, 0x5a, // lsg 5a [ store msgType on the stack ]
+ 0x78, // push1
+ 0xa9, 0x5a, // ssg 5a [ msgType = 1 (text) ]
+ 0x38, PATCH_SELECTOR16(setCursor), // pushi setCursor
+ 0x78, // push1
+ 0x74, PATCH_GETORIGINALUINT16(9), // lofss helpCursor
+ PATCH_ADDTOOFFSET(+171),
+ 0xa9, 0x15, // ssg 5a [ restore msgType from stack ]
+ 0x48, // ret
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry larry6Signatures[] = {
- { true, 82, "death dialog memory corruption", 1, larry6SignatureDeathDialog, larry6PatchDeathDialog },
+ { true, 75, "fix help cursor", 1, larry6HelpCursorSignature, larry6HelpCursorPatch },
+ { true, 82, "death dialog memory corruption", 1, larry6DeathDialogSignature, larry6DeathDialogPatch },
{ true, 99, "disable speed test", 1, sci11SpeedTestSignature, sci11SpeedTestPatch },
{ true, 928, "Narrator lockup fix", 1, sciNarratorLockupSignature, sciNarratorLockupPatch },
SCI_SIGNATUREENTRY_TERMINATOR
@@ -9870,7 +9909,7 @@ static const uint16 larry6HiresPhoneOperatorPatch[] = {
// originated in the low resolution version; it only worked in text mode and
// was incompatible with CD narration.
//
-// We fix this by temporarily setting the global cursor variables to helpCusor
+// We fix this by temporarily setting the global cursor variables to helpCursor
// during Interface Help. This causes LarryTalker to use helpCursor instead of
// waitCursor or the previous cursor. Sierra fixed this in the Mac version by
// changing LarryTalker, so we limit this patch to PC versions by using little
@@ -9879,11 +9918,11 @@ static const uint16 larry6HiresPhoneOperatorPatch[] = {
// Applies to: All PC versions
// Responsible method: nClickHelp:doit
// Fixes bug: #14591
-static const uint16 larry6HiresHelpCusorSignature[] = {
+static const uint16 larry6HiresHelpCursorSignature[] = {
SIG_MAGICDWORD,
- 0x38, SIG_SELECTOR16(setCursor), // pushi setCusor
+ 0x38, SIG_SELECTOR16(setCursor), // pushi setCursor
0x78, // push1
- 0x72, SIG_ADDTOOFFSET(+2), // lofsa helpCusor
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa helpCursor
0x36, // push
0x81, 0x01, // lag 01
0x4a, 0x06, 0x00, // send 06 [ LSL6 setCursor: helpCursor ]
@@ -9892,7 +9931,7 @@ static const uint16 larry6HiresHelpCusorSignature[] = {
SIG_END
};
-static const uint16 larry6HiresHelpCusorPatch[] = {
+static const uint16 larry6HiresHelpCursorPatch[] = {
0x89, 0x15, // lsg 15 [ store gWaitCursor on stack ]
PATCH_GETORIGINALBYTES(4, 3), // lofsa helpCursor
0xa0, PATCH_UINT16(0x0015), // sag 0015 [ gWaitCursor = helpCursor ]
@@ -9908,7 +9947,7 @@ static const SciScriptPatcherEntry larry6HiresSignatures[] = {
{ true, 71, "disable volume reset on startup (1/2)", 1, sci2VolumeResetSignature, sci2VolumeResetPatch },
{ true, 71, "disable volume reset on startup (2/2)", 1, larry6HiresVolumeResetSignature, larry6HiresVolumeResetPatch },
{ true, 71, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
- { true, 75, "fix help cursor", 1, larry6HiresHelpCusorSignature, larry6HiresHelpCusorPatch },
+ { true, 75, "fix help cursor", 1, larry6HiresHelpCursorSignature, larry6HiresHelpCursorPatch },
{ true, 270, "fix incorrect setScale call", 1, larry6HiresSetScaleSignature, larry6HiresSetScalePatch },
{ true, 330, "fix whale oil lamp lockup", 1, larry6HiresWhaleOilLampSignature, larry6HiresWhaleOilLampPatch },
{ true, 610, "phone operator crash", 1, larry6HiresPhoneOperatorSignature, larry6HiresPhoneOperatorPatch },
Commit: 33950cda7e4beae3ef27f9d75672c9d3e514731a
https://github.com/scummvm/scummvm/commit/33950cda7e4beae3ef27f9d75672c9d3e514731a
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:02-07:00
Commit Message:
AGI: Add detection for early version of XMASCARD
Thanks to @vvdleun for reporting this
Trac #15764
Changed paths:
engines/agi/detection_tables.h
diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h
index 71e49c63733..f84a1e2cb2f 100644
--- a/engines/agi/detection_tables.h
+++ b/engines/agi/detection_tables.h
@@ -874,8 +874,14 @@ static const AGIGameDescription gameDescriptions[] = {
"wintitle.pic", "cc8d2ae52e18700843f466a23d62c773", 6144,
"room62", "813de04fd57063a402272c603be1188a", 1338, 0x0000, GID_WINNIE, Common::kPlatformCoCo),
+ // Xmas Card 1986 (PC) [AGI 2.230]
+ // Includes a scene of Tandy presents being unwrapped.
+ // Does not include demo scenes for AGI games.
+ // Only known game with this interpreter, uses a unique view format.
+ GAME("xmascard", "1986-10-13 [version 1]", "b77b968a84e8cb70a6c9972879f485f4", 0x2230, GID_XMASCARD),
+
// Xmas Card 1986 (PC) [AGI 2.272]
- GAME("xmascard", "1986-11-13 [version 1]", "3067b8d5957e2861e069c3c0011bd43d", 0x2272, GID_XMASCARD),
+ GAME("xmascard", "1986-11-13 [version 2]", "3067b8d5957e2861e069c3c0011bd43d", 0x2272, GID_XMASCARD),
// Xmas Card 1986 (CoCo3 360k) [AGI 2.072]
// Appears to be an unofficial port
Commit: e16358bae3730cc08e7f87152d317ddac1101734
https://github.com/scummvm/scummvm/commit/e16358bae3730cc08e7f87152d317ddac1101734
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:02-07:00
Commit Message:
AGI: Add support for v2.230 view mirror data
Used by early version of XMASCARD
Fixes error in Santa scene (room 2)
Trac #15764
Changed paths:
engines/agi/view.cpp
diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp
index 5467b1e9c7d..77e76c8bdc6 100644
--- a/engines/agi/view.cpp
+++ b/engines/agi/view.cpp
@@ -184,25 +184,29 @@ int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr)
error("unexpected end of view data for view %d", viewNr);
// loop-header:
- // celCount:BYTE
+ // celCount:BYTE [ upper nibble used as mirror data in 2.230 ]
// relativeCelOffset[0]:WORD
// relativeCelOffset[1]:WORD
// etc.
- byte loopHeaderCelCount = resourceData[loopOffset];
-
- loopData->celCount = loopHeaderCelCount;
+ byte loopHeaderCelCountByte = resourceData[loopOffset];
+ const bool isMirrorDataInLoopHeader = (getVersion() == 0x2230);
+ if (isMirrorDataInLoopHeader) {
+ loopData->celCount = loopHeaderCelCountByte & 0x0f;
+ } else {
+ loopData->celCount = loopHeaderCelCountByte;
+ }
loopData->cel = nullptr;
// Check, if at least the cel-offsets for current loop are available
- if (resourceSize < (loopOffset + 1 + (loopHeaderCelCount * 2)))
+ if (resourceSize < (loopOffset + 1 + (loopData->celCount * 2)))
error("unexpected end of view data for view %d", viewNr);
- if (loopHeaderCelCount) {
+ if (loopData->celCount) {
// Allocate space for cel-information of current loop
- AgiViewCel *celData = new AgiViewCel[loopHeaderCelCount];
+ AgiViewCel *celData = new AgiViewCel[loopData->celCount];
loopData->cel = celData;
- for (int16 celNr = 0; celNr < loopHeaderCelCount; celNr++) {
+ for (int16 celNr = 0; celNr < loopData->celCount; celNr++) {
uint16 celOffset = READ_LE_UINT16(resourceData + loopOffset + 1 + (celNr * 2));
celOffset += loopOffset; // cel offset is relative to loop offset, so adjust accordingly
@@ -237,12 +241,27 @@ int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr)
celHeaderClearKey = apple2ViewColorMap[celHeaderClearKey];
}
- if (celHeaderTransparencyMirror & 0x80) {
- // mirror bit is set
- byte celHeaderMirrorLoop = (celHeaderTransparencyMirror >> 4) & 0x07;
- if (celHeaderMirrorLoop != loopNr) {
- // only set to mirrored in case we are not the original loop
- celHeaderMirrored = true;
+ if (isMirrorDataInLoopHeader) {
+ // 2.230 (early version of xmascard): mirror data is in loop header.
+ if (loopHeaderCelCountByte & 0x80) {
+ // mirror bit is set
+ // there is a second mirror bit whose purpose is currently unknown;
+ // both bits are set in every xmascard loop with mirror data.
+ byte celHeaderMirrorLoop = (loopHeaderCelCountByte >> 4) & 0x03;
+ if (celHeaderMirrorLoop != loopNr) {
+ // only set to mirrored in case we are not the original loop
+ celHeaderMirrored = true;
+ }
+ }
+ } else {
+ // 2.272+: mirror data is in cel header
+ if (celHeaderTransparencyMirror & 0x80) {
+ // mirror bit is set
+ byte celHeaderMirrorLoop = (celHeaderTransparencyMirror >> 4) & 0x07;
+ if (celHeaderMirrorLoop != loopNr) {
+ // only set to mirrored in case we are not the original loop
+ celHeaderMirrored = true;
+ }
}
}
} else {
Commit: 9bd9686c33dd5e0091be402bcb0d4534ef27d9d1
https://github.com/scummvm/scummvm/commit/9bd9686c33dd5e0091be402bcb0d4534ef27d9d1
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-04-29T10:27:02-07:00
Commit Message:
SCI: Fix QFG1 trip wire lockup
Fixes bug #15736
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 6039f1fcd67..149a5d5dcf9 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -14831,9 +14831,66 @@ static const uint16 qfg1egaPatchPickSafeMessage2[] = {
PATCH_END
};
+// In the brigands' fortress (room 94), opening the jack in the box and then
+// tripping over the wire next to it locks up the game. The `egoTripsSouth`
+// script is missing a call to egoObj:ignoreActors, unlike `egoTripsNorth`.
+// Without this, the jack in the box causes egoObj:canBeHere to return false.
+//
+// We fix this by adding the missing call to ignoreActors. This bug occurs in
+// the original, but only in some versions, and inconsistently, because another
+// unrelated bug can cancel it out. Due to a script typo and a compiler bug,
+// Actor:canBeHere reads an invalid property and tests out of bounds memory to
+// determine if kCanBeHere is called to perform collision tests. We normalize
+// this behavior in `validate_property`.
+//
+// Applies to: All versions
+// Responsible method: egoTripsSouth:changeState(0)
+// Fixes bug: #15736
+static const uint16 qfg1egaSignatureTripWire[] = {
+ SIG_MAGICDWORD,
+ 0x3c, // dup
+ 0x35, 0x00, // ldi 00
+ 0x1a, // eq?
+ 0x30, // bnt [ state 1 ]
+ SIG_ADDTOOFFSET(+71),
+ 0x4a, 0x30, // send 30 [ egoObj ... ]
+ SIG_END
+};
+
+static const uint16 qfg1egaPatchTripWire[] = {
+ 0x2e, PATCH_GETORIGINALUINT16ADJUST(5, +4), // bt [ state 1 ]
+ 0x38, PATCH_SELECTOR16(ignoreActors), // pushi ignoreActors
+ 0x36, // push0
+ PATCH_ADDTOOFFSET(+69),
+ 0x4a, 0x34, // send 34 [ egoObj ignoreActors: ... ]
+ PATCH_END
+};
+
+static const uint16 qfg1egaSignatureTripWirePC98[] = {
+ SIG_MAGICDWORD,
+ 0x3c, // dup
+ 0x35, 0x00, // ldi 00
+ 0x1a, // eq?
+ 0x30, // bnt [ state 1 ]
+ SIG_ADDTOOFFSET(+72),
+ 0x4a, 0x30, // send 30 [ egoObj ... ]
+ SIG_END
+};
+
+static const uint16 qfg1egaPatchTripWirePC98[] = {
+ 0x2e, PATCH_GETORIGINALUINT16ADJUST(5, +4), // bt [ state 1 ]
+ 0x38, PATCH_SELECTOR16(ignoreActors), // pushi ignoreActors
+ 0x36, // push0
+ PATCH_ADDTOOFFSET(+70),
+ 0x4a, 0x34, // send 34 [ egoObj ignoreActors: ... ]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry qfg1egaSignatures[] = {
{ true, 54, "throw rock at nest while running", 1, qfg1egaSignatureThrowRockAtNest, qfg1egaPatchThrowRockAtNest },
+ { true, 94, "trip wire", 1, qfg1egaSignatureTripWirePC98, qfg1egaPatchTripWirePC98 },
+ { true, 189, "trip wire", 1, qfg1egaSignatureTripWire, qfg1egaPatchTripWire },
{ true, 289, "pick safe message", 1, qfg1egaSignaturePickSafeMessage1, qfg1egaPatchPickSafeMessage1 },
{ true, 299, "disable speed test", 1, sci01SpeedTestGlobalSignature, sci01SpeedTestGlobalPatch },
{ true, 321, "pick safe message", 1, qfg1egaSignaturePickSafeMessage2, qfg1egaPatchPickSafeMessage2 },
Commit: b016b0afe33b4ab801e6e1d8ddb45a4fd8e23bb7
https://github.com/scummvm/scummvm/commit/b016b0afe33b4ab801e6e1d8ddb45a4fd8e23bb7
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2025-04-29T10:27:03-07:00
Commit Message:
SCI: Disable "ride unicorn at night" for KQ4 demo
This patch is not compatible with the demo, and will cause it to crash
with a "Send to invalid selector" error.
Changed paths:
engines/sci/engine/script_patches.cpp
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 149a5d5dcf9..db8dd082531 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -5674,7 +5674,7 @@ static const SciScriptPatcherEntry kq4Signatures[] = {
{ true, 99, "fix speed test overflow", 1, sci0SpeedTestOverflowSignature, sci0SpeedTestOverflowPatch },
{ true, 994, "restore fix", 1, kq4SignatureRestoreFix1, kq4PatchRestoreFix1 },
{ true, 994, "restore fix", 1, kq4SignatureRestoreFix2, kq4PatchRestoreFix2 },
- { true, 994, "ride unicorn at night", 1, kq4SignatureUnicornNightRide, kq4PatchUnicornNightRide },
+ { false, 994, "ride unicorn at night", 1, kq4SignatureUnicornNightRide, kq4PatchUnicornNightRide },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -26623,6 +26623,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) {
if (!g_sci->getResMan()->testResource(ResourceId(kResourceTypeView, 653))) {
enablePatch(signatureTable, "missing waterfall view");
}
+ if (!g_sci->isDemo()) {
+ enablePatch(signatureTable, "ride unicorn at night");
+ }
break;
case GID_KQ5:
if (g_sci->_features->useAltWinGMSound()) {
More information about the Scummvm-git-logs
mailing list