[Scummvm-git-logs] scummvm branch-2-8 -> 02365a0d468e529bdbc6e979a871c6bfea4957cd
moralrecordings
noreply at scummvm.org
Fri Feb 16 10:20:29 UTC 2024
This automated email contains information about 23 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
477ed25e18 DIRECTOR: Add patcher for individual script handlers
ec00da33cb DIRECTOR: Fix the commandKey behaviour for Windows
0510250240 DIRECTOR: Add property breakpoints to debugger
96a3452546 DIRECTOR: Remove channel copying bodge
4c73a8bcfe DIRECTOR: Fix use-after-free in EXE loader
6231a61c95 DIRECTOR: Fix bounds of kTransPushUp
50a085b9c0 DIRECTOR: LINGO: Allow reversed bound args to b_spriteBox
dfc4557c7a DIRECTOR: Rework event ordering to be closer to original
eb02c0c522 DIRECTOR: Don't render transitions when movie is paused
5b07f52c2f DIRECTOR: Clean up Score methods that previously needed frameId
19bf3107df DIRECTOR: Persist editable status on sprites if cast is the same
459b0b4991 DIRECTOR: Re-add sprite copy before applying delta frames
35b03fe160 DIRECTOR: Fix regression with tempo selection
62779d5fac DIRECTOR: Add Encyclopedia of Clamps to detection list
6319baad99 DIRECTOR: Fix MacArchive::getFileSize()
b52de552fa DIRECTOR: Prevent repeats in DirectorSound::playExternalSound
c51dee3730 DIRECTOR: Clear cast ID in D2 when sprite is a shape
cb69ac6255 DIRECTOR: Cap palette cycling framerate to _fpsLimit
aa4512f85c DIRECTOR: Allow calls to b_label with integer argument
38208e3688 DIRECTOR: Fix handling of relative path markers
3be280d3f8 DIRECTOR: Add CD loading delay quirk
5f5343b8b9 DIRECTOR: Fix Channel::setClean resetting the same video
02365a0d46 DIRECTOR: Fix return value of b_power
Commit: 477ed25e180e8186a0cda51b6e72608ffd200c5b
https://github.com/scummvm/scummvm/commit/477ed25e180e8186a0cda51b6e72608ffd200c5b
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:38:39+08:00
Commit Message:
DIRECTOR: Add patcher for individual script handlers
This gives the ability to replace function handlers in a compiled Lingo
script with a replacement from the quirks table. The replacement can be
written in Lingo; no need to worry about injecting bytecode.
As an example, here's a replacement for Cosmology of Kyoto's text input
handler. The original was notorious for requiring an exact match against a
word list containing typos, along with extra spaces and punctuation. The
new one is more forgiving, and always supports the answers "yes" and
"no".
Changed paths:
engines/director/cast.cpp
engines/director/lingo/lingo-patcher.cpp
engines/director/lingo/lingo.cpp
engines/director/lingo/lingo.h
diff --git a/engines/director/cast.cpp b/engines/director/cast.cpp
index 90a753f8294..343f90268db 100644
--- a/engines/director/cast.cpp
+++ b/engines/director/cast.cpp
@@ -48,6 +48,7 @@
#include "director/castmember/sound.h"
#include "director/castmember/text.h"
#include "director/castmember/transition.h"
+#include "director/lingo/lingo-codegen.h"
namespace Director {
@@ -1088,6 +1089,7 @@ void Cast::loadLingoContext(Common::SeekableReadStreamEndian &stream) {
error("Cast::loadLingoContext: Script already defined for type %s, id %d", scriptType2str(script->_scriptType), script->_id);
}
_lingoArchive->scriptContexts[script->_scriptType][script->_id] = script;
+ _lingoArchive->patchScriptHandler(script->_scriptType, CastMemberID(script->_id, _castLibID));
} else {
// Keep track of scripts that are not in scriptContexts
// Those scripts need to be cleaned up on ~LingoArchive
diff --git a/engines/director/lingo/lingo-patcher.cpp b/engines/director/lingo/lingo-patcher.cpp
index 2e31b8829a0..18cce8e4b55 100644
--- a/engines/director/lingo/lingo-patcher.cpp
+++ b/engines/director/lingo/lingo-patcher.cpp
@@ -247,6 +247,128 @@ struct ScriptPatch {
{nullptr, nullptr, kPlatformUnknown, nullptr, kNoneScript, 0, 0, 0, nullptr, nullptr}
};
+/*
+ * Cosmology of Kyoto has a text entry system, however for the English version
+ * at least you are very unlikely to guess the correct sequence of letters that
+ * constitute a valid answer. This is an attempt to make things fairer by removing
+ * the need for precise whitespace and punctuation. As a fallback, "yes" should
+ * always mean a yes response, and "no" should always mean a no response.
+ */
+
+const char *kyotoTextEntryFix = " \
+on scrubInput inputString \r\
+ set result = \"\" \r\
+ repeat with x = 1 to the number of chars in inputString \r\
+ if chars(inputString, x, x) = \" \" then continue \r\
+ else if chars(inputString, x, x) = \".\" then continue \r\
+ else if chars(inputString, x, x) = \"!\" then continue \r\
+ else if chars(inputString, x, x) = \"?\" then continue \r\
+ else if chars(inputString, x, x) = \"ã\" then continue \r\
+ else \r\
+ set result = result & char x of inputString \r\
+ end if \r\
+ end repeat \r\
+ return result \r\
+end \r\
+\r\
+on checkkaiwa kaiwatrue, kaiwafalse \r\
+ global myparadata \r\
+ if (keyCode() <> 36) and (keyCode() <> 76) then \r\
+ exit \r\
+ end if \r\
+ put \"Original YES options: \" & kaiwatrue \r\
+ put \"Original NO options: \" & kaiwafalse \r\
+ -- pre-scrub all input and choices to remove effect of whitespace/punctuation \r\
+ set kaiwaans = scrubInput(field \"KaiwaWindow\") \r\
+ set kaiwatrue = scrubInput(kaiwatrue) \r\
+ set kaiwafalse = scrubInput(kaiwafalse) \r\
+ -- yes and no should always give consistent results \r\
+ if kaiwaans = \"yes\" then \r\
+ return \"YES\" \r\
+ else if kaiwaans = \"no\" then \r\
+ return \"NO\" \r\
+ end if \r\
+ repeat with y = 1 to the number of items in kaiwatrue \r\
+ if item y of kaiwatrue starts kaiwaans then \r\
+ when keyDown then CheckQuit \r\
+ put EMPTY into field \"KaiwaWindow\" \r\
+ return \"YES\" \r\
+ end if \r\
+ end repeat \r\
+ repeat with n = 1 to the number of items in kaiwafalse \r\
+ if item n of kaiwafalse starts kaiwaans then \r\
+ when keyDown then CheckQuit \r\
+ put EMPTY into field \"KaiwaWindow\" \r\
+ return \"NO\" \r\
+ end if \r\
+ end repeat \r\
+ set kaiwafool to scrubInput(\"ãã»ãã ã,ã°ã,ããã,ãã£ã,ãã,æ»ã,ãããã,ãããã,\" & \"fool,simpleton,stupid person,kill,Shut up!,Get out of my hair!\") \r\
+ repeat with f = 1 to the number of items in kaiwafool \r\
+ if item f of kaiwafool starts kaiwaans then \r\
+ myparadata(maddparadata, 2, 1) \r\
+ when keyDown then CheckQuit \r\
+ put EMPTY into field \"KaiwaWindow\" \r\
+ return \"error\" \r\
+ end if \r\
+ end repeat \r\
+ when keyDown then CheckQuit \r\
+ put EMPTY into field \"KaiwaWindow\" \r\
+ return \"error\" \r\
+end \r\
+";
+
+struct ScriptHandlerPatch {
+ const char *gameId;
+ const char *extra;
+ Common::Platform platform; // Specify kPlatformUnknown for skipping platform check
+ const char *movie;
+ ScriptType type;
+ uint16 id;
+ uint16 castLib;
+ const char *handlerBody;
+} const scriptHandlerPatches[] = {
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\dd_dairi\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\findfldr\\shared.dxr", kMovieScript, 802, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\ichi\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\jigoku\\shared.dxr", kMovieScript, 840, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\kusamura\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\map01\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\map02\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\map03\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\map04\\shared.dxr", kMovieScript, 906, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\opening\\shared.dxr", kMovieScript, 802, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\rajoumon\\shared.dxr", kMovieScript, 840, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {"kyoto", nullptr, kPlatformWindows, "ck_data\\rokudou\\shared.dxr", kMovieScript, 846, DEFAULT_CAST_LIB, kyotoTextEntryFix},
+ {nullptr, nullptr, kPlatformUnknown, nullptr, kNoneScript, 0, 0, nullptr},
+
+};
+
+void LingoArchive::patchScriptHandler(ScriptType type, CastMemberID id) {
+ const ScriptHandlerPatch *patch = scriptHandlerPatches;
+ Common::String movie = g_director->getCurrentPath() + cast->getMacName();
+
+ // So far, we have not many patches, so do linear lookup
+ while (patch->gameId) {
+ // First, we do cheap comparisons
+ if (patch->type != type || patch->id != id.member || patch->castLib != id.castLib ||
+ (patch->platform != kPlatformUnknown && patch->platform != g_director->getPlatform())) {
+ patch++;
+ continue;
+ }
+
+ // Now expensive ones
+ U32String moviename = punycode_decode(patch->movie);
+ if (movie.compareToIgnoreCase(moviename) || strcmp(patch->gameId, g_director->getGameId())
+ || (patch->extra && strcmp(patch->extra, g_director->getExtra()))) {
+ patch++;
+ continue;
+ }
+ patchCode(Common::U32String(patch->handlerBody), patch->type, patch->id);
+ patch++;
+ }
+}
+
+
Common::U32String LingoCompiler::patchLingoCode(const Common::U32String &line, LingoArchive *archive, ScriptType type, CastMemberID id, int linenum) {
if (!archive)
return line;
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index fe3fc6f004c..6fddfce4009 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -338,6 +338,30 @@ Symbol Lingo::getHandler(const Common::String &name) {
return sym;
}
+
+void LingoArchive::patchCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName, uint32 preprocFlags) {
+ debugC(1, kDebugCompile, "Patching code for type %s(%d) with id %d in '%s%s'\n"
+ "***********\n%s\n\n***********", scriptType2str(type), type, id, utf8ToPrintable(g_director->getCurrentPath()).c_str(), utf8ToPrintable(cast->getMacName()).c_str(), formatStringForDump(code.encode()).c_str());
+ if (!getScriptContext(type, id)) {
+ // If there's no existing script context, don't try and patch it.
+ warning("Script not defined for type %d, id %d", type, id);
+ return;
+ }
+
+ ScriptContext *sc = g_lingo->_compiler->compileLingo(code, nullptr, type, CastMemberID(id, cast->_castLibID), scriptName, false, preprocFlags);
+
+ if (sc) {
+ for (auto &it : sc->_functionHandlers) {
+ it._value.ctx = scriptContexts[type][id];
+ scriptContexts[type][id]->_functionHandlers[it._key] = it._value;
+ functionHandlers[it._key] = it._value;
+ }
+ sc->_functionHandlers.clear();
+ delete sc;
+ }
+}
+
+
void LingoArchive::addCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName, uint32 preprocFlags) {
debugC(1, kDebugCompile, "Add code for type %s(%d) with id %d in '%s%s'\n"
"***********\n%s\n\n***********", scriptType2str(type), type, id, utf8ToPrintable(g_director->getCurrentPath()).c_str(), utf8ToPrintable(cast->getMacName()).c_str(), formatStringForDump(code.encode()).c_str());
diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h
index 88ed62afae8..d7b7e6040ea 100644
--- a/engines/director/lingo/lingo.h
+++ b/engines/director/lingo/lingo.h
@@ -292,10 +292,14 @@ struct LingoArchive {
Common::String formatFunctionList(const char *prefix);
void addCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName = nullptr, uint32 preprocFlags = kLPPNone);
+ void patchCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName = nullptr, uint32 preprocFlags = kLPPNone);
void removeCode(ScriptType type, uint16 id);
void replaceCode(const Common::U32String &code, ScriptType type, uint16 id, const char *scriptName = nullptr);
void addCodeV4(Common::SeekableReadStreamEndian &stream, uint16 lctxIndex, const Common::String &archName, uint16 version);
void addNamesV4(Common::SeekableReadStreamEndian &stream);
+
+ // lingo-patcher.cpp
+ void patchScriptHandler(ScriptType type, CastMemberID id);
};
struct LingoState {
Commit: ec00da33cb829582165dd69db57f02786dda3fa1
https://github.com/scummvm/scummvm/commit/ec00da33cb829582165dd69db57f02786dda3fa1
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:38:39+08:00
Commit Message:
DIRECTOR: Fix the commandKey behaviour for Windows
According to the Lingo manual, "the commandKey" checks the status of the
Ctrl key when running on Windows.
Fixes opening the save and exit screens in Cosmology of Kyoto for
Windows.
Changed paths:
engines/director/lingo/lingo-the.cpp
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 9ed034cd8f6..5bcfdd484da 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -21,6 +21,7 @@
#include "common/config-manager.h"
#include "common/fs.h"
+#include "common/platform.h"
#include "director/types.h"
#include "graphics/macgui/macbutton.h"
@@ -430,7 +431,10 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
d = 1;
break;
case kTheCommandDown:
- d = (movie->_keyFlags & Common::KBD_META) ? 1 : 0;
+ if (g_director->getPlatform() == Common::kPlatformWindows)
+ d = (movie->_keyFlags & Common::KBD_CTRL) ? 1 : 0;
+ else
+ d = (movie->_keyFlags & Common::KBD_META) ? 1 : 0;
break;
case kTheControlDown:
d = (movie->_keyFlags & Common::KBD_CTRL) ? 1 : 0;
Commit: 0510250240eb7eefdfce343c6872906e0d47a2f7
https://github.com/scummvm/scummvm/commit/0510250240eb7eefdfce343c6872906e0d47a2f7
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:38:39+08:00
Commit Message:
DIRECTOR: Add property breakpoints to debugger
Changed paths:
engines/director/debugger.cpp
engines/director/debugger.h
engines/director/lingo/lingo-bytecode.cpp
engines/director/lingo/lingo-the.cpp
diff --git a/engines/director/debugger.cpp b/engines/director/debugger.cpp
index 7c0366c493d..65545766b14 100644
--- a/engines/director/debugger.cpp
+++ b/engines/director/debugger.cpp
@@ -94,6 +94,8 @@ Debugger::Debugger(): GUI::Debugger() {
registerCmd("bf", WRAP_METHOD(Debugger, cmdBpFrame));
registerCmd("bpentity", WRAP_METHOD(Debugger, cmdBpEntity));
registerCmd("be", WRAP_METHOD(Debugger, cmdBpEntity));
+ registerCmd("bpprop", WRAP_METHOD(Debugger, cmdBpProp));
+ registerCmd("bp", WRAP_METHOD(Debugger, cmdBpProp));
registerCmd("bpvar", WRAP_METHOD(Debugger, cmdBpVar));
registerCmd("bv", WRAP_METHOD(Debugger, cmdBpVar));
registerCmd("bpevent", WRAP_METHOD(Debugger, cmdBpEvent));
@@ -122,6 +124,8 @@ Debugger::Debugger(): GUI::Debugger() {
_bpCheckMoviePath = false;
_bpNextMovieMatch = false;
_bpMatchScriptId = 0;
+ _bpCheckPropRead = false;
+ _bpCheckPropWrite = false;
_bpCheckVarRead = false;
_bpCheckVarWrite = false;
_bpCheckEntityRead = false;
@@ -185,6 +189,8 @@ bool Debugger::cmdHelp(int argc, const char **argv) {
debugPrintf(" bpentity / be [entityName] [r/w/rw] - Create a breakpoint on a Lingo \"the\" entity being accessed in a specific way\n");
debugPrintf(" bpentity / be [entityName:fieldName] - Create a breakpoint on a Lingo \"the\" field being read or modified\n");
debugPrintf(" bpentity / be [entityName:fieldName] [r/w/rw] - Create a breakpoint on a Lingo \"the\" field being accessed in a specific way\n");
+ debugPrintf(" bpprop / bp [varName] - Create a breakpoint on a Lingo object property being read or modified\n");
+ debugPrintf(" bpprop / bp [varName] [r/w/rw] - Create a breakpoint on a Lingo object property being accessed in a specific way\n");
debugPrintf(" bpvar / bv [varName] - Create a breakpoint on a Lingo variable being read or modified\n");
debugPrintf(" bpvar / bv [varName] [r/w/rw] - Create a breakpoint on a Lingo variable being accessed in a specific way\n");
debugPrintf(" bpevent / bn [eventName] - Create a breakpoint on a Lingo event\n");
@@ -215,6 +221,11 @@ Common::String Breakpoint::format() {
case kBreakpointMovieFrame:
result += Common::String::format("Movie %s:%d", moviePath.c_str(), frameOffset);
break;
+ case kBreakpointProperty:
+ result += "Property "+ varName + ":";
+ result += varRead ? "r" : "";
+ result += varWrite ? "w" : "";
+ break;
case kBreakpointVariable:
result += "Variable "+ varName + ":";
result += varRead ? "r" : "";
@@ -763,6 +774,34 @@ bool Debugger::cmdBpEntity(int argc, const char **argv) {
return true;
}
+bool Debugger::cmdBpProp(int argc, const char **argv) {
+ if (argc == 2 || argc == 3) {
+ Breakpoint bp;
+ bp.type = kBreakpointProperty;
+ bp.varName = argv[1];
+ if (argc == 3) {
+ Common::String props = argv[2];
+ bp.varRead = props.contains("r") || props.contains("R");
+ bp.varWrite = props.contains("w") || props.contains("W");
+ if (!(bp.varRead || bp.varWrite)) {
+ debugPrintf("Must specify r, w, or rw.");
+ return true;
+ }
+ } else {
+ bp.varRead = true;
+ bp.varWrite = true;
+ }
+ bp.id = _bpNextId;
+ _bpNextId++;
+ _breakpoints.push_back(bp);
+ bpUpdateState();
+ debugPrintf("Added %s\n", bp.format().c_str());
+ } else {
+ debugPrintf("Must specify a property.\n");
+ }
+ return true;
+}
+
bool Debugger::cmdBpVar(int argc, const char **argv) {
if (argc == 2 || argc == 3) {
Breakpoint bp;
@@ -992,6 +1031,8 @@ void Debugger::bpUpdateState() {
_bpMatchScriptId = 0;
_bpMatchMoviePath.clear();
_bpMatchFrameOffsets.clear();
+ _bpCheckPropRead = false;
+ _bpCheckPropWrite = false;
_bpCheckVarRead = false;
_bpCheckVarWrite = false;
_bpCheckEntityRead = false;
@@ -1036,6 +1077,9 @@ void Debugger::bpUpdateState() {
_bpMatchMoviePath = it.moviePath;
_bpMatchFrameOffsets.setVal(it.frameOffset, nullptr);
}
+ } else if (it.type == kBreakpointProperty) {
+ _bpCheckPropRead |= it.varRead;
+ _bpCheckPropWrite |= it.varWrite;
} else if (it.type == kBreakpointVariable) {
_bpCheckVarRead |= it.varRead;
_bpCheckVarWrite |= it.varWrite;
@@ -1223,6 +1267,40 @@ void Debugger::builtinHook(const Symbol &funcSym) {
bpTest(builtinMatch);
}
+void Debugger::propReadHook(const Common::String &name) {
+ if (name.empty())
+ return;
+ if (_bpCheckPropRead) {
+ for (auto &it : _breakpoints) {
+ if (it.type == kBreakpointProperty && it.varRead && it.varName.equalsIgnoreCase(name)) {
+ debugPrintf("Hit a breakpoint:\n");
+ debugPrintf("%s\n", it.format().c_str());
+ cmdScriptFrame(0, nullptr);
+ attach();
+ g_system->updateScreen();
+ break;
+ }
+ }
+ }
+}
+
+void Debugger::propWriteHook(const Common::String &name) {
+ if (name.empty())
+ return;
+ if (_bpCheckPropWrite) {
+ for (auto &it : _breakpoints) {
+ if (it.type == kBreakpointProperty && it.varWrite && it.varName.equalsIgnoreCase(name)) {
+ debugPrintf("Hit a breakpoint:\n");
+ debugPrintf("%s\n", it.format().c_str());
+ cmdScriptFrame(0, nullptr);
+ attach();
+ g_system->updateScreen();
+ break;
+ }
+ }
+ }
+}
+
void Debugger::varReadHook(const Common::String &name) {
if (name.empty())
return;
diff --git a/engines/director/debugger.h b/engines/director/debugger.h
index bfdea47549d..ad6cf533cef 100644
--- a/engines/director/debugger.h
+++ b/engines/director/debugger.h
@@ -39,6 +39,7 @@ enum BreakpointType {
kBreakpointVariable = 4,
kBreakpointEntity = 5,
kBreakpointEvent = 6,
+ kBreakpointProperty = 7,
};
struct Breakpoint {
@@ -74,6 +75,8 @@ public:
void pushContextHook();
void popContextHook();
void builtinHook(const Symbol &funcSym);
+ void propReadHook(const Common::String &varName);
+ void propWriteHook(const Common::String &varName);
void varReadHook(const Common::String &varName);
void varWriteHook(const Common::String &varName);
void entityReadHook(int entity, int field);
@@ -107,6 +110,7 @@ private:
bool cmdBpMovie(int argc, const char **argv);
bool cmdBpFrame(int argc, const char **argv);
bool cmdBpEntity(int argc, const char **argv);
+ bool cmdBpProp(int argc, const char **argv);
bool cmdBpVar(int argc, const char **argv);
bool cmdBpEvent(int argc, const char **argv);
bool cmdBpDel(int argc, const char **argv);
@@ -149,6 +153,8 @@ private:
Common::String _bpMatchMoviePath;
Common::HashMap<uint, void *> _bpMatchFuncOffsets;
Common::HashMap<uint, void *> _bpMatchFrameOffsets;
+ bool _bpCheckPropRead;
+ bool _bpCheckPropWrite;
bool _bpCheckVarRead;
bool _bpCheckVarWrite;
bool _bpCheckEntityRead;
diff --git a/engines/director/lingo/lingo-bytecode.cpp b/engines/director/lingo/lingo-bytecode.cpp
index a4ffdfc3fd4..37d1395f49b 100644
--- a/engines/director/lingo/lingo-bytecode.cpp
+++ b/engines/director/lingo/lingo-bytecode.cpp
@@ -26,6 +26,7 @@
#include "director/director.h"
#include "director/cast.h"
+#include "director/debugger.h"
#include "director/movie.h"
#include "director/window.h"
#include "director/castmember/castmember.h"
@@ -595,6 +596,7 @@ void LC::cb_theassign() {
if (g_lingo->_state->me.type == OBJECT) {
// Don't bother checking if the property is defined, leave that to the object.
// For D3-style anonymous objects/factories, you are allowed to define whatever properties you like.
+ g_debugger->propWriteHook(name);
g_lingo->_state->me.u.obj->setProp(name, value);
} else {
warning("cb_theassign: no me object");
@@ -622,6 +624,7 @@ void LC::cb_thepush() {
if (g_lingo->_state->me.type == OBJECT) {
if (g_lingo->_state->me.u.obj->hasProp(name)) {
g_lingo->push(g_lingo->_state->me.u.obj->getProp(name));
+ g_debugger->propReadHook(name);
return;
}
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 5bcfdd484da..e437ff58c43 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -1948,6 +1948,7 @@ void Lingo::getObjectProp(Datum &obj, Common::String &propName) {
g_lingo->lingoError("Lingo::getObjectProp: Object <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str());
}
g_lingo->push(d);
+ g_debugger->propReadHook(propName);
return;
}
if (obj.type == PARRAY) {
@@ -1956,6 +1957,7 @@ void Lingo::getObjectProp(Datum &obj, Common::String &propName) {
d = obj.u.parr->arr[index - 1].v;
}
g_lingo->push(d);
+ g_debugger->propReadHook(propName);
return;
}
if (obj.type == RECT) {
@@ -1971,6 +1973,7 @@ void Lingo::getObjectProp(Datum &obj, Common::String &propName) {
g_lingo->lingoError("Lingo::getObjectProp: Rect <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str());
}
g_lingo->push(d);
+ g_debugger->propReadHook(propName);
return;
}
if (obj.type == CASTREF) {
@@ -2067,6 +2070,7 @@ void Lingo::setObjectProp(Datum &obj, Common::String &propName, Datum &val) {
if (obj.type == OBJECT) {
if (obj.u.obj->hasProp(propName)) {
obj.u.obj->setProp(propName, val);
+ g_debugger->propWriteHook(propName);
} else {
g_lingo->lingoError("Lingo::setObjectProp: Object <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str());
}
@@ -2078,6 +2082,7 @@ void Lingo::setObjectProp(Datum &obj, Common::String &propName, Datum &val) {
PCell cell = PCell(propName, val);
obj.u.parr->arr.push_back(cell);
}
+ g_debugger->propWriteHook(propName);
} else if (obj.type == CASTREF) {
Movie *movie = _vm->getCurrentMovie();
if (!movie) {
Commit: 96a345254647e13655fdaa853794e8e834c68f36
https://github.com/scummvm/scummvm/commit/96a345254647e13655fdaa853794e8e834c68f36
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:38:40+08:00
Commit Message:
DIRECTOR: Remove channel copying bodge
It is unclear what problem it was intended to solve, but it no longer
works now that 04cd1dccf9458e75fb146bffe1545fbfa5ed1862 has fixed the
frame loader to not reload if the frame hasn't changed.
Fixes movie playback in The Dark Eye.
Changed paths:
engines/director/score.cpp
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 47d73002a32..5ab119f71dd 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -397,9 +397,6 @@ void Score::update() {
return;
}
- for (uint ch = 0; ch < _channels.size(); ch++)
- *_currentFrame->_sprites[ch] = *_channels[ch]->_sprite;
-
uint32 nextFrameNumberToLoad = _curFrameNumber;
if (!_vm->_playbackPaused) {
Commit: 4c73a8bcfe299466792cc5750cc027079f4f47ec
https://github.com/scummvm/scummvm/commit/4c73a8bcfe299466792cc5750cc027079f4f47ec
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:23+08:00
Commit Message:
DIRECTOR: Fix use-after-free in EXE loader
Changed paths:
engines/director/resource.cpp
diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp
index 49459fa3c5e..70f9f2660a8 100644
--- a/engines/director/resource.cpp
+++ b/engines/director/resource.cpp
@@ -277,6 +277,8 @@ Archive *DirectorEngine::loadEXE(const Common::Path &movie) {
if (initialTag == MKTAG('R', 'I', 'F', 'X') || initialTag == MKTAG('X', 'F', 'I', 'R')) {
// we've encountered a movie saved from Director, not a projector.
result = loadEXERIFX(exeStream, 0);
+ // ownership handed to loadEXERIFX
+ exeStream = nullptr;
} else if (initialTag == MKTAG('R', 'I', 'F', 'F') || initialTag == MKTAG('F', 'F', 'I', 'R')) { // This is just a normal movie
result = new RIFFArchive();
@@ -285,6 +287,8 @@ Archive *DirectorEngine::loadEXE(const Common::Path &movie) {
delete result;
return nullptr;
}
+ // ownership handed to RIFFArchive
+ exeStream = nullptr;
} else {
Common::WinResources *exe = Common::WinResources::createFromEXE(movie.toString());
if (!exe) {
@@ -328,17 +332,13 @@ Archive *DirectorEngine::loadEXE(const Common::Path &movie) {
delete exeStream;
return nullptr;
}
-
- if (result) {
- result->setPathName(movie.toString(g_director->_dirSeparator));
- }
-
- return result;
+ // ownership passed to an EXE loader
+ exeStream = nullptr;
}
if (result)
result->setPathName(movie.toString(g_director->_dirSeparator));
- else
+ else if (exeStream)
delete exeStream;
return result;
Commit: 6231a61c95b7742e461190a49bdf8423b1a9c03a
https://github.com/scummvm/scummvm/commit/6231a61c95b7742e461190a49bdf8423b1a9c03a
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:24+08:00
Commit Message:
DIRECTOR: Fix bounds of kTransPushUp
Fixes artifacting when changing the dials in scene03 of Team Xtreme:
Operation Weather Disaster.
Changed paths:
engines/director/transitions.cpp
diff --git a/engines/director/transitions.cpp b/engines/director/transitions.cpp
index b39e93f2cc6..f0385ca5228 100644
--- a/engines/director/transitions.cpp
+++ b/engines/director/transitions.cpp
@@ -390,6 +390,8 @@ void Window::playTransition(uint frame, uint16 transDuration, uint8 transArea, u
case kTransPushUp: // 14
rto.translate(0, h - t.yStepSize * i);
+ rfrom.bottom -= h - clipRect.findIntersectingRect(rto).height();
+ rto.clip(clipRect);
_composeSurface->blitFrom(nextFrame, rfrom, Common::Point(rto.left, rto.top));
rfrom.translate(0, t.yStepSize * i);
Commit: 50a085b9c04dde297347c1a76635d7624c8aeabd
https://github.com/scummvm/scummvm/commit/50a085b9c04dde297347c1a76635d7624c8aeabd
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:24+08:00
Commit Message:
DIRECTOR: LINGO: Allow reversed bound args to b_spriteBox
Fixes the targeting lines in the storm chasing minigame of Team Xtreme:
Operation Weather Disaster.
Changed paths:
engines/director/channel.cpp
engines/director/lingo/lingo-builtins.cpp
diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index 06ea905cc62..95ccd6a0f3e 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -559,7 +559,7 @@ void Channel::replaceSprite(Sprite *nextSprite) {
}
void Channel::setWidth(int w) {
- if (!(_sprite->_cast && _sprite->_cast->_type == kCastShape) && !_sprite->_stretch)
+ if (!(_sprite->_stretch || (_sprite->_cast && _sprite->_cast->_type == kCastShape)))
return;
_width = MAX<int>(w, 0);
@@ -568,7 +568,7 @@ void Channel::setWidth(int w) {
}
void Channel::setHeight(int h) {
- if (!(_sprite->_cast && _sprite->_cast->_type == kCastShape) && !_sprite->_stretch)
+ if (!(_sprite->_stretch || (_sprite->_cast && _sprite->_cast->_type == kCastShape)))
return;
_height = MAX<int>(h, 0);
@@ -577,7 +577,7 @@ void Channel::setHeight(int h) {
}
void Channel::setBbox(int l, int t, int r, int b) {
- if (!(_sprite->_cast && _sprite->_cast->_type == kCastShape) && !_sprite->_stretch)
+ if (!(_sprite->_stretch || (_sprite->_cast && _sprite->_cast->_type == kCastShape)))
return;
_width = r - l;
_height = b - t;
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index a1ae67f0d78..20c9d208d65 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -2711,11 +2711,16 @@ void LB::b_spriteBox(int nargs) {
if (!channel)
return;
- // This automatically sets the sctretch mode
+ // This automatically sets the stretch mode
channel->_sprite->_stretch = true;
g_director->getCurrentWindow()->addDirtyRect(channel->getBbox());
- channel->setBbox(l, t, r, b);
+ channel->setBbox(
+ l < r ? l : r,
+ t < b ? t : b,
+ r > l ? r : l,
+ b > t ? b : t
+ );
if (channel->_sprite->_cast)
channel->_sprite->_cast->setModified(true);
channel->_dirty = true;
Commit: dfc4557c7a671c57cad207d2f3f97a992512eaa3
https://github.com/scummvm/scummvm/commit/dfc4557c7a671c57cad207d2f3f97a992512eaa3
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:24+08:00
Commit Message:
DIRECTOR: Rework event ordering to be closer to original
Confirmed to work with:
- darkeye (race condition when viewing cutscenes or switching scenes in
the menu)
- gadget (race condition after climbing the stairs in GA28)
- the7colors (intro cutscene timed with b_delay)
- wallobee (hotspot select gated with b_delay)
Changed paths:
engines/director/lingo/lingo-builtins.cpp
engines/director/lingo/lingo-events.cpp
engines/director/lingo/lingo.cpp
engines/director/score.cpp
engines/director/score.h
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 20c9d208d65..7884be7e3c5 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -1401,8 +1401,7 @@ void LB::b_nothing(int nargs) {
void LB::b_delay(int nargs) {
Datum d = g_lingo->pop();
- g_director->getCurrentMovie()->getScore()->_nextFrameTime = g_system->getMillis() + (float)d.asInt() / 60 * 1000;
- debugC(5, kDebugLoading, "b_delay(): delaying %d ticks, next frame time at %d", d.asInt(), g_director->getCurrentMovie()->getScore()->_nextFrameTime);
+ g_director->getCurrentMovie()->getScore()->setDelay(d.asInt());
}
void LB::b_do(int nargs) {
diff --git a/engines/director/lingo/lingo-events.cpp b/engines/director/lingo/lingo-events.cpp
index f9b3d95d499..4fa2840be86 100644
--- a/engines/director/lingo/lingo-events.cpp
+++ b/engines/director/lingo/lingo-events.cpp
@@ -92,7 +92,7 @@ ScriptType Lingo::event2script(LEvent ev) {
switch (ev) {
//case kEventStartMovie: // We are precompiling it now
// return kMovieScript;
- case kEventEnterFrame:
+ case kEventExitFrame:
return kScoreScript;
default:
return kNoneScript;
@@ -172,15 +172,18 @@ void Movie::queueFrameEvent(Common::Queue<LingoEvent> &queue, LEvent event, int
if (!script)
return;
- // Scopeless statements (ie one lined lingo commands) are executed at enterFrame
- // A score script can have both scopeless and scoped lingo. (eg. porting from D3.1 to D4)
- if (event == kEventEnterFrame && script->_eventHandlers.contains(kEventGeneric)) {
- queue.push(LingoEvent(kEventGeneric, eventId, kScoreScript, scriptId, true, 0));
- }
-
if (script->_eventHandlers.contains(event)) {
queue.push(LingoEvent(event, eventId, kScoreScript, scriptId, false, 0));
}
+ // Scopeless statements (ie one lined lingo commands) are executed at exitFrame
+ // A score script can have both scopeless and scoped lingo. (eg. porting from D3.1 to D4)
+ // In the event of both being specified in the ScoreScript, the scopeless handler is ignored.
+
+ if (event == kEventExitFrame && script->_eventHandlers.contains(kEventGeneric) &&
+ !(script->_eventHandlers.contains(kEventExitFrame) || script->_eventHandlers.contains(kEventEnterFrame))) {
+ queue.push(LingoEvent(kEventGeneric, eventId, kScoreScript, scriptId, false, 0));
+ }
+
}
void Movie::queueMovieEvent(Common::Queue<LingoEvent> &queue, LEvent event, int eventId) {
@@ -261,7 +264,7 @@ void Movie::queueEvent(Common::Queue<LingoEvent> &queue, LEvent event, int targe
}
break;
- case kEventEnterFrame:
+ case kEventExitFrame:
queueFrameEvent(queue, event, eventId);
break;
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index 6fddfce4009..4258f1a43cf 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -1502,6 +1502,7 @@ void Lingo::executePerFrameHook(int frame, int subframe) {
for (uint i = 0; i < _actorList.u.farr->arr.size(); i++) {
Datum actor = _actorList.u.farr->arr[i];
Symbol method = actor.u.obj->getMethod("stepFrame");
+ debugC(1, kDebugLingoExec, "Executing perFrameHook : <%s>, frame %d, subframe %d", actor.asString(true).c_str(), frame, subframe);
if (method.nargs == 1)
push(actor);
LC::call(method, method.nargs, false);
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 5ab119f71dd..5ebf21bbff8 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -72,6 +72,7 @@ Score::Score(Movie *movie) {
_nextFrame = 0;
_currentLabel = 0;
_nextFrameTime = 0;
+ _nextFrameDelay = 0;
_lastTempo = 0;
_waitForChannel = 0;
_waitForVideoChannel = 0;
@@ -273,6 +274,7 @@ void Score::startPlay() {
_curFrameNumber = 1;
_playState = kPlayStarted;
_nextFrameTime = 0;
+ _nextFrameDelay = 0;
if (!_currentFrame) {
warning("Score::startLoop(): Movie has no frames");
@@ -323,80 +325,48 @@ void Score::stopPlay() {
_lingo->executePerFrameHook(-1, 0);
}
-void Score::update() {
- if (_activeFade) {
- if (!_soundManager->fadeChannel(_activeFade))
- _activeFade = 0;
+void Score::setDelay(uint32 ticks) {
+ // the score will continually loop at the exitFrame handler,
+ // even if the handler sets a new delay value only the first one
+ // will be acknowledged.
+ if (!_nextFrameDelay) {
+ _nextFrameDelay = g_system->getMillis() + (ticks * 1000 / 60);
+ debugC(5, kDebugLoading, "Score::setDelay(): delaying %d ticks, next frame time at %d", ticks, _nextFrameDelay);
}
+}
- if (!debugChannelSet(-1, kDebugFast)) {
- bool keepWaiting = false;
+bool Score::isWaitingForNextFrame() {
+ bool keepWaiting = false;
- debugC(8, kDebugLoading, "Score::update(): nextFrameTime: %d, time: %d", _nextFrameTime, g_system->getMillis(false));
- if (_waitForChannel) {
- if (_soundManager->isChannelActive(_waitForChannel)) {
- keepWaiting = true;
- } else {
- _waitForChannel = 0;
- }
- } else if (_waitForClick) {
- if (g_system->getMillis() >= _nextFrameTime + 1000) {
- _waitForClickCursor = !_waitForClickCursor;
- renderCursor(_movie->getWindow()->getMousePos());
- _nextFrameTime = g_system->getMillis();
- }
- keepWaiting = true;
- } else if (_waitForVideoChannel) {
- Channel *movieChannel = _channels[_waitForVideoChannel];
- if (movieChannel->isActiveVideo() && movieChannel->_movieRate != 0.0) {
- keepWaiting = true;
- } else {
- _waitForVideoChannel = 0;
- }
- } else if (g_system->getMillis() < _nextFrameTime) {
+ debugC(8, kDebugLoading, "Score::isWaitingForNextFrame(): nextFrameTime: %d, time: %d", _nextFrameTime, g_system->getMillis(false));
+ if (_waitForChannel) {
+ if (_soundManager->isChannelActive(_waitForChannel)) {
keepWaiting = true;
+ } else {
+ _waitForChannel = 0;
}
-
- if (keepWaiting) {
- if (_movie->_videoPlayback) {
- updateWidgets(true);
- _window->render();
- }
-
- // Don't process frozen script if we use jump instructions
- // like "go to frame", or open a new movie.
- if (!_nextFrame || _nextFrame == _curFrameNumber) {
- processFrozenScripts();
- }
-
- return;
- }
- }
-
- // For previous frame
- if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
- // When Lingo::func_goto* is called, _nextFrame is set
- // and _skipFrameAdvance is set to true.
- // exitFrame is not called in this case.
- if (!_vm->_skipFrameAdvance && _vm->getVersion() >= 400) {
- _movie->processEvent(kEventExitFrame);
+ } else if (_waitForClick) {
+ if (g_system->getMillis() >= _nextFrameTime + 1000) {
+ _waitForClickCursor = !_waitForClickCursor;
+ renderCursor(_movie->getWindow()->getMousePos());
+ _nextFrameTime = g_system->getMillis();
}
-
- // If there is a transition, the perFrameHook is called
- // after each transition subframe instead.
- if (_currentFrame->_mainChannels.transType == 0 && _currentFrame->_mainChannels.trans.isNull()) {
- _lingo->executePerFrameHook(_curFrameNumber, 0);
+ keepWaiting = true;
+ } else if (_waitForVideoChannel) {
+ Channel *movieChannel = _channels[_waitForVideoChannel];
+ if (movieChannel->isActiveVideo() && movieChannel->_movieRate != 0.0) {
+ keepWaiting = true;
+ } else {
+ _waitForVideoChannel = 0;
}
+ } else if (g_system->getMillis() < _nextFrameTime) {
+ keepWaiting = true;
}
- _vm->_skipFrameAdvance = false;
-
- // the exitFrame event handler may have stopped this movie
- if (_playState == kPlayStopped) {
- processFrozenScripts();
- return;
- }
+ return keepWaiting;
+}
+void Score::updateCurrentFrame() {
uint32 nextFrameNumberToLoad = _curFrameNumber;
if (!_vm->_playbackPaused) {
@@ -420,7 +390,6 @@ void Score::update() {
_playState = kPlayStopped;
window->setNextMovie(ref.movie);
window->_nextMovie.frameI = ref.frameI;
- processFrozenScripts();
return;
}
nextFrameNumberToLoad = ref.frameI;
@@ -444,10 +413,13 @@ void Score::update() {
}
if (_curFrameNumber != nextFrameNumberToLoad) {
- // this updates _curFrameNumber
+ // this copies in the frame data and updates _curFrameNumber
loadFrame(nextFrameNumberToLoad, true);
}
+ return;
+}
+void Score::updateNextFrameTime() {
byte tempo = _currentFrame->_mainChannels.scoreCachedTempo;
// puppetTempo is overridden by changes in score tempo
if (_currentFrame->_mainChannels.tempo || tempo != _lastTempo) {
@@ -469,34 +441,34 @@ void Score::update() {
if (tempo >= 256 - maxDelay) {
// Delay
_nextFrameTime = g_system->getMillis() + (256 - tempo) * 1000;
- debugC(5, kDebugLoading, "Score::update(): setting _nextFrameTime to %d based on a delay of %d", _nextFrameTime, 256 - tempo);
+ debugC(5, kDebugLoading, "Score::updateNextFrameTime(): setting _nextFrameTime to %d based on a delay of %d", _nextFrameTime, 256 - tempo);
} else if (tempo <= 120) {
// FPS
_currentFrameRate = tempo;
if (g_director->_fpsLimit)
_currentFrameRate = MIN(g_director->_fpsLimit, _currentFrameRate);
_nextFrameTime = g_system->getMillis() + 1000.0 / (float)_currentFrameRate;
- debugC(5, kDebugLoading, "Score::update(): setting _nextFrameTime to %d based on a framerate of %d", _nextFrameTime, _currentFrameRate);
+ debugC(5, kDebugLoading, "Score::updateNextFrameTime(): setting _nextFrameTime to %d based on a framerate of %d", _nextFrameTime, _currentFrameRate);
} else {
if (tempo == 128) {
_waitForClick = true;
_waitForClickCursor = false;
renderCursor(_movie->getWindow()->getMousePos());
- debugC(5, kDebugLoading, "Score::update(): waiting for mouse click before next frame");
+ debugC(5, kDebugLoading, "Score::updateNextFrameTime(): waiting for mouse click before next frame");
} else if (!waitForClickOnly && tempo == 135) {
// Wait for sound channel 1
_waitForChannel = 1;
- debugC(5, kDebugLoading, "Score::update(): waiting for sound channel 1 before next frame");
+ debugC(5, kDebugLoading, "Score::updateNextFrameTime(): waiting for sound channel 1 before next frame");
} else if (!waitForClickOnly && tempo == 134) {
// Wait for sound channel 2
_waitForChannel = 2;
- debugC(5, kDebugLoading, "Score::update(): waiting for sound channel 2 before next frame");
+ debugC(5, kDebugLoading, "Score::updateNextFrameTime(): waiting for sound channel 2 before next frame");
} else if (!waitForClickOnly && tempo >= 136 && tempo <= 135 + _numChannelsDisplayed) {
// Wait for a digital video in a channel to finish playing
_waitForVideoChannel = tempo - 135;
- debugC(5, kDebugLoading, "Score::update(): waiting for video in channel %d before next frame", _waitForVideoChannel);
+ debugC(5, kDebugLoading, "Score::updateNextFrameTime(): waiting for video in channel %d before next frame", _waitForVideoChannel);
} else {
- warning("Unhandled tempo instruction: %d", tempo);
+ warning("Score::updateNextFrameTime(): Unhandled tempo instruction: %d", tempo);
}
_nextFrameTime = g_system->getMillis();
}
@@ -507,11 +479,115 @@ void Score::update() {
if (debugChannelSet(-1, kDebugSlow))
_nextFrameTime += 1000;
+}
+
+void Score::update() {
+ if (_activeFade) {
+ if (!_soundManager->fadeChannel(_activeFade))
+ _activeFade = 0;
+ }
+
+ if (!debugChannelSet(-1, kDebugFast)) {
+ // end update cycle if we're still waiting for the next frame
+ if (isWaitingForNextFrame()) {
+ if (_movie->_videoPlayback) {
+ updateWidgets(true);
+ _window->render();
+ }
+
+ // Don't process frozen script if we use jump instructions
+ // like "go to frame", or open a new movie.
+ if (!_nextFrame || _nextFrame == _curFrameNumber) {
+ processFrozenScripts();
+ }
+ return;
+ }
+ }
+
+ // For previous frame
+ if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
+ // When Lingo::func_goto* is called, _nextFrame is set
+ // and _skipFrameAdvance is set to true.
+ // exitFrame is not called in this case.
+ if (!_vm->_skipFrameAdvance) {
+ // Exit the current frame. This can include scopeless ScoreScripts.
+ _movie->processEvent(kEventExitFrame);
+ }
+ }
+
+ _vm->_skipFrameAdvance = false;
+
+ // Check for delay
+ if (g_system->getMillis() < _nextFrameDelay) {
+ if (_movie->_videoPlayback) {
+ updateWidgets(true);
+ _window->render();
+ }
+
+ // Don't process frozen script if we use jump instructions
+ // like "go to frame", or open a new movie.
+ if (!_nextFrame || _nextFrame == _curFrameNumber) {
+ processFrozenScripts();
+ }
+
+ return;
+ }
+ _nextFrameDelay = 0;
+
+ // the exitFrame event handler may have stopped this movie
+ if (_playState == kPlayStopped) {
+ processFrozenScripts();
+ return;
+ }
+
+ // change current frame and load frame data, if required
+ updateCurrentFrame();
+
+ // set the delay time/condition until the next frame
+ updateNextFrameTime();
debugC(1, kDebugLoading, "****************************** Current frame: %d, time: %d", _curFrameNumber, g_system->getMillis(false));
g_debugger->frameHook();
- _lingo->executeImmediateScripts(_currentFrame);
+ // movie could have been stopped by a window switch or a debug flag
+ if (_playState == kPlayStopped) {
+ processFrozenScripts();
+ return;
+ }
+
+ uint32 count = _window->frozenLingoStateCount();
+
+ // new frame, first call the perFrameHook (if one exists)
+ if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
+ // Call the perFrameHook as soon as a frame switch is done.
+ // If there is a transition, the perFrameHook is called
+ // after each transition subframe instead of here.
+ if (_currentFrame->_mainChannels.transType == 0 && _currentFrame->_mainChannels.trans.isNull()) {
+ _lingo->executePerFrameHook(_curFrameNumber, 0);
+ }
+ }
+ if (_window->frozenLingoStateCount() > count)
+ return;
+
+ // check to see if we've hit the recursion limit
+ if (_vm->getVersion() >= 400 && _window->frozenLingoStateCount() >= 2) {
+ debugC(1, kDebugLoading, "Score::update(): hitting depth limit for D4 scripts, defrosting");
+ processFrozenScripts();
+ return;
+ } else if (_window->frozenLingoStateCount() >= 64) {
+ warning("Score::update(): Stopping runaway script recursion. By this point D3 will have run out of stack space");
+ processFrozenScripts();
+ return;
+ }
+
+ // then call the stepMovie hook (if one exists)
+ // skip the first frame
+ count = _window->frozenLingoStateCount();
+ if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
+ _movie->processEvent(kEventStepMovie);
+ }
+ if (_window->frozenLingoStateCount() > count)
+ return;
if (_vm->getVersion() >= 600) {
// _movie->processEvent(kEventBeginSprite);
@@ -524,36 +600,19 @@ void Score::update() {
renderFrame(_curFrameNumber);
_window->_newMovieStarted = false;
- // Enter and exit from previous frame
- if (!_vm->_playbackPaused) {
- uint32 count = _window->frozenLingoStateCount();
- // Triggers the frame script in D2-3, explicit enterFrame handlers in D4+
- // D4 will only process recursive enterFrame handlers to a depth of 2.
- // Any more will be ignored.
- if ((_vm->getVersion() >= 400)) {
- if (count < 2 || _window->recursiveEnterFrameCount() < 2)
- _movie->processEvent(kEventEnterFrame);
- else {
- warning("Score::update(): ignoring recursive enterFrame handler, frozenLingoStateCount: %d, enterFrames: %d", count, _window->recursiveEnterFrameCount());
- }
- } else if ((_vm->getVersion() < 400) || _movie->_allowOutdatedLingo) {
- // Force a flush of any frozen scripts before raising enterFrame
- if (!processFrozenScripts())
- return;
- _movie->processEvent(kEventEnterFrame);
- if ((_vm->getVersion() >= 300) || _movie->_allowOutdatedLingo) {
- // Movie version of enterFrame, for D3 only. The D3 Interactivity Manual claims
- // "This handler executes before anything else when the playback head moves."
- // but this is incorrect. The frame script is executed first.
- _movie->processEvent(kEventStepMovie);
- }
- }
- // If another frozen state gets triggered, wait another update() before thawing
- if ((_vm->getVersion() < 400) && _window->frozenLingoStateCount() > 0)
- return;
- else if ((_vm->getVersion() >= 400) && _window->frozenLingoStateCount() > count)
- return;
+ // then call the enterFrame hook (if one exists)
+ count = _window->frozenLingoStateCount();
+ if (!_vm->_playbackPaused && _vm->getVersion() >= 400) {
+ _movie->processEvent(kEventEnterFrame);
}
+ if (_window->frozenLingoStateCount() > count)
+ return;
+
+ // then execute any immediate scripts, i.e. handlers attached to sprites
+ count = _window->frozenLingoStateCount();
+ _lingo->executeImmediateScripts(_currentFrame);
+ if (_window->frozenLingoStateCount() > count)
+ return;
// Attempt to thaw and continue any frozen execution after startMovie and enterFrame.
// If they don't complete (i.e. another freezing event like a "go to frame"),
diff --git a/engines/director/score.h b/engines/director/score.h
index c988dec2289..3ce859c3d45 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -93,6 +93,7 @@ public:
void startPlay();
void step();
void stopPlay();
+ void setDelay(uint32 ticks);
void setCurrentFrame(uint16 frameId) { _nextFrame = frameId; }
uint16 getCurrentFrameNum() { return _curFrameNumber; }
@@ -137,6 +138,9 @@ public:
Common::String formatChannelInfo();
private:
+ bool isWaitingForNextFrame();
+ void updateCurrentFrame();
+ void updateNextFrameTime();
void update();
void playQueuedSound();
@@ -175,6 +179,7 @@ public:
PlayState _playState;
uint32 _nextFrameTime;
+ uint32 _nextFrameDelay;
int _lastTempo;
int _waitForChannel;
int _waitForVideoChannel;
Commit: eb02c0c5223017059a059590acd9b8a7c1afb292
https://github.com/scummvm/scummvm/commit/eb02c0c5223017059a059590acd9b8a7c1afb292
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:25+08:00
Commit Message:
DIRECTOR: Don't render transitions when movie is paused
Fixes rendering of menu screen on Cosmology of Kyoto.
Changed paths:
engines/director/score.cpp
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 5ebf21bbff8..9026f3049eb 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -644,6 +644,9 @@ void Score::renderFrame(uint16 frameId, RenderMode mode) {
if (_skipTransition) {
_window->render();
_skipTransition = false;
+ } else if (g_director->_playbackPaused) {
+ renderSprites(frameId, mode);
+ _window->render();
} else if (!renderTransition(frameId)) {
bool skip = renderPrePaletteCycle(frameId, mode);
setLastPalette(frameId);
Commit: 5b07f52c2ff0b4c9902e6d9a1be7ee42de3262f4
https://github.com/scummvm/scummvm/commit/5b07f52c2ff0b4c9902e6d9a1be7ee42de3262f4
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:25+08:00
Commit Message:
DIRECTOR: Clean up Score methods that previously needed frameId
Changed paths:
engines/director/channel.cpp
engines/director/channel.h
engines/director/lingo/lingo-builtins.cpp
engines/director/lingo/lingo-the.cpp
engines/director/lingo/xlibs/unittest.cpp
engines/director/score.cpp
engines/director/score.h
engines/director/transitions.cpp
diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index 95ccd6a0f3e..cdf585b5d09 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -378,7 +378,7 @@ void Channel::setCast(CastMemberID memberID) {
_sprite->setAutoPuppet(kAPCast, true);
}
-void Channel::setClean(Sprite *nextSprite, int spriteId, bool partial) {
+void Channel::setClean(Sprite *nextSprite, bool partial) {
if (!nextSprite)
return;
diff --git a/engines/director/channel.h b/engines/director/channel.h
index ab01d24ad0d..ace25307bf8 100644
--- a/engines/director/channel.h
+++ b/engines/director/channel.h
@@ -62,7 +62,7 @@ public:
void setBbox(int l, int t, int r, int b);
void setPosition(int x, int y, bool force = false);
void setCast(CastMemberID memberID);
- void setClean(Sprite *nextSprite, int spriteId, bool partial = false);
+ void setClean(Sprite *nextSprite, bool partial = false);
bool getEditable();
void setEditable(bool editable);
void replaceSprite(Sprite *nextSprite);
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 7884be7e3c5..c0ee93c5dc2 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -2823,7 +2823,7 @@ void LB::b_updateStage(int nargs) {
movie->getWindow()->render();
// play any puppet sounds that have been queued
- score->playSoundChannel(score->getCurrentFrameNum(), true);
+ score->playSoundChannel(true);
if (score->_cursorDirty) {
score->renderCursor(movie->getWindow()->getMousePos());
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index e437ff58c43..9259827250a 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -1157,7 +1157,7 @@ void Lingo::setTheEntity(int entity, Datum &id, int field, Datum &d) {
g_director->getCurrentWindow()->setStageColor(g_director->transformColor(d.asInt()));
// Redraw the stage
- score->renderSprites(score->getCurrentFrameNum(), kRenderForceUpdate);
+ score->renderSprites(kRenderForceUpdate);
g_director->getCurrentWindow()->render();
break;
case kTheSwitchColorDepth:
@@ -1646,7 +1646,7 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) {
break;
case kTheRect:
if (d.type == RECT || (d.type == ARRAY && d.u.farr->arr.size() >= 4)) {
- score->renderSprites(score->getCurrentFrameNum(), kRenderForceUpdate);
+ score->renderSprites(kRenderForceUpdate);
channel->setBbox(
d.u.farr->arr[0].u.i, d.u.farr->arr[1].u.i,
d.u.farr->arr[2].u.i, d.u.farr->arr[3].u.i
diff --git a/engines/director/lingo/xlibs/unittest.cpp b/engines/director/lingo/xlibs/unittest.cpp
index e12666a2f8e..3b8f1ebed23 100644
--- a/engines/director/lingo/xlibs/unittest.cpp
+++ b/engines/director/lingo/xlibs/unittest.cpp
@@ -86,7 +86,7 @@ void UnitTest::m_UTScreenshot(int nargs) {
// force a full screen redraw before taking the screenshot
Score *score = g_director->getCurrentMovie()->getScore();
- score->renderSprites(score->getCurrentFrameNum(), kRenderForceUpdate);
+ score->renderSprites(kRenderForceUpdate);
Window *window = g_director->getCurrentWindow();
window->render();
Graphics::ManagedSurface *windowSurface = window->getSurface();
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 9026f3049eb..7384a06f54c 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -645,19 +645,19 @@ void Score::renderFrame(uint16 frameId, RenderMode mode) {
_window->render();
_skipTransition = false;
} else if (g_director->_playbackPaused) {
- renderSprites(frameId, mode);
+ renderSprites(mode);
_window->render();
} else if (!renderTransition(frameId)) {
- bool skip = renderPrePaletteCycle(frameId, mode);
- setLastPalette(frameId);
- renderSprites(frameId, mode);
+ bool skip = renderPrePaletteCycle(mode);
+ setLastPalette();
+ renderSprites(mode);
_window->render();
if (!skip)
- renderPaletteCycle(frameId, mode);
+ renderPaletteCycle(mode);
}
- playSoundChannel(frameId, false);
+ playSoundChannel(false);
playQueuedSound(); // this is currently only used in FPlayXObj
if (_cursorDirty) {
@@ -673,20 +673,20 @@ bool Score::renderTransition(uint16 frameId) {
TransParams *tp = _window->_puppetTransition;
if (tp) {
- setLastPalette(frameId);
+ setLastPalette();
_window->playTransition(frameId, tp->duration, tp->area, tp->chunkSize, tp->type, currentFrame->_mainChannels.scoreCachedPaletteId);
delete _window->_puppetTransition;
_window->_puppetTransition = nullptr;
return true;
} else if (currentFrame->_mainChannels.transType) {
- setLastPalette(frameId);
+ setLastPalette();
_window->playTransition(frameId, currentFrame->_mainChannels.transDuration, currentFrame->_mainChannels.transArea, currentFrame->_mainChannels.transChunkSize, currentFrame->_mainChannels.transType, currentFrame->_mainChannels.scoreCachedPaletteId);
return true;
} else if (!currentFrame->_mainChannels.trans.isNull()) {
CastMember *member = _movie->getCastMember(currentFrame->_mainChannels.trans);
if (member && member->_type == kCastTransition) {
TransitionCastMember *trans = static_cast<TransitionCastMember *>(member);
- setLastPalette(frameId);
+ setLastPalette();
_window->playTransition(frameId, trans->_durationMillis, trans->_area, trans->_chunkSize, trans->_transType, currentFrame->_mainChannels.scoreCachedPaletteId);
return true;
}
@@ -694,7 +694,7 @@ bool Score::renderTransition(uint16 frameId) {
return false;
}
-void Score::renderSprites(uint16 frameId, RenderMode mode) {
+void Score::renderSprites(RenderMode mode) {
if (_window->_newMovieStarted)
mode = kRenderForceUpdate;
@@ -726,7 +726,7 @@ void Score::renderSprites(uint16 frameId, RenderMode mode) {
nextSprite->setCast(nextSprite->_castId);
}
- channel->setClean(nextSprite, i);
+ channel->setClean(nextSprite);
// Check again to see if a video has just been started by setClean.
if (channel->isActiveVideo())
_movie->_videoPlayback = true;
@@ -742,7 +742,7 @@ void Score::renderSprites(uint16 frameId, RenderMode mode) {
debugC(5, kDebugImages, "Score::renderSprites(): CH: %-3d: No sprite", i);
}
} else {
- channel->setClean(nextSprite, i, true);
+ channel->setClean(nextSprite, true);
}
// update editable text channel after we render the sprites. because for the current frame, we may get those sprites only when we finished rendering
@@ -752,7 +752,7 @@ void Score::renderSprites(uint16 frameId, RenderMode mode) {
}
}
-bool Score::renderPrePaletteCycle(uint16 frameId, RenderMode mode) {
+bool Score::renderPrePaletteCycle(RenderMode mode) {
if (_puppetPalette)
return false;
@@ -868,7 +868,7 @@ bool Score::renderPrePaletteCycle(uint16 frameId, RenderMode mode) {
return false;
}
-void Score::setLastPalette(uint16 frameId) {
+void Score::setLastPalette() {
if (_puppetPalette)
return;
@@ -909,7 +909,7 @@ bool Score::isPaletteColorCycling() {
return _currentFrame->_mainChannels.palette.colorCycling;
}
-void Score::renderPaletteCycle(uint16 frameId, RenderMode mode) {
+void Score::renderPaletteCycle(RenderMode mode) {
if (_puppetPalette)
return;
@@ -1438,7 +1438,7 @@ Channel *Score::getChannelById(uint16 id) {
return _channels[id];
}
-void Score::playSoundChannel(uint16 frameId, bool puppetOnly) {
+void Score::playSoundChannel(bool puppetOnly) {
debugC(5, kDebugSound, "playSoundChannel(): Sound1 %s Sound2 %s", _currentFrame->_mainChannels.sound1.asString().c_str(), _currentFrame->_mainChannels.sound2.asString().c_str());
DirectorSound *sound = _window->getSoundManager();
diff --git a/engines/director/score.h b/engines/director/score.h
index 3ce859c3d45..ad52b7159ec 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -123,17 +123,17 @@ public:
bool renderTransition(uint16 frameId);
void renderFrame(uint16 frameId, RenderMode mode = kRenderModeNormal);
- void renderSprites(uint16 frameId, RenderMode mode = kRenderModeNormal);
- bool renderPrePaletteCycle(uint16 frameId, RenderMode mode = kRenderModeNormal);
- void setLastPalette(uint16 frameId);
+ void renderSprites(RenderMode mode = kRenderModeNormal);
+ bool renderPrePaletteCycle(RenderMode mode = kRenderModeNormal);
+ void setLastPalette();
bool isPaletteColorCycling();
- void renderPaletteCycle(uint16 frameId, RenderMode mode = kRenderModeNormal);
+ void renderPaletteCycle(RenderMode mode = kRenderModeNormal);
void renderCursor(Common::Point pos, bool forceUpdate = false);
void updateWidgets(bool hasVideoPlayback);
void invalidateRectsForMember(CastMember *member);
- void playSoundChannel(uint16 frameId, bool puppetOnly);
+ void playSoundChannel(bool puppetOnly);
Common::String formatChannelInfo();
diff --git a/engines/director/transitions.cpp b/engines/director/transitions.cpp
index f0385ca5228..2e6a00e19a3 100644
--- a/engines/director/transitions.cpp
+++ b/engines/director/transitions.cpp
@@ -191,7 +191,7 @@ void Window::playTransition(uint frame, uint16 transDuration, uint8 transArea, u
Score *score = g_director->getCurrentMovie()->getScore();
if (t.area) {
// Changed area transition
- score->renderSprites(t.frame);
+ score->renderSprites();
if (_dirtyRects.size() == 0)
return;
@@ -217,7 +217,7 @@ void Window::playTransition(uint frame, uint16 transDuration, uint8 transArea, u
render(false, &nextFrame);
} else {
// Full stage transition
- score->renderSprites(t.frame, kRenderForceUpdate);
+ score->renderSprites(kRenderForceUpdate);
render(true, &nextFrame);
clipRect = _innerDims;
Commit: 19bf3107df89ed5927f9fcb85b1355d5dc512aef
https://github.com/scummvm/scummvm/commit/19bf3107df89ed5927f9fcb85b1355d5dc512aef
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:25+08:00
Commit Message:
DIRECTOR: Persist editable status on sprites if cast is the same
Fixes text box deselecting when talking to the cops in Spaceship
Warlock.
Changed paths:
engines/director/channel.cpp
diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index cdf585b5d09..0016522e01c 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -530,6 +530,12 @@ void Channel::replaceSprite(Sprite *nextSprite) {
((DigitalVideoCastMember *)_sprite->_cast)->rewindVideo();
}
+ // If the cast member is the same, persist the editable flag
+ bool editable = nextSprite->_editable;
+ if (_sprite->_castId == nextSprite->_castId) {
+ editable = _sprite->_editable;
+ }
+
int width = _width;
int height = _height;
bool immediate = _sprite->_immediate;
@@ -539,6 +545,8 @@ void Channel::replaceSprite(Sprite *nextSprite) {
// Persist the immediate flag
_sprite->_immediate = immediate;
+ _sprite->_editable = editable;
+
// TODO: auto expand text size is meaning less for us, not all text
// since we are using initialRect for the text cast member now, then the sprite size is meaning less for us.
// thus, we keep the _sprite size here
Commit: 459b0b4991feaef917e3f2e4738344c138903e8d
https://github.com/scummvm/scummvm/commit/459b0b4991feaef917e3f2e4738344c138903e8d
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:25+08:00
Commit Message:
DIRECTOR: Re-add sprite copy before applying delta frames
Changed paths:
engines/director/score.cpp
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 7384a06f54c..92a0ac582be 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -413,6 +413,13 @@ void Score::updateCurrentFrame() {
}
if (_curFrameNumber != nextFrameNumberToLoad) {
+ // Load the current sprite information into the _currentFrame data store.
+ // This is specifically because of delta updates; loading the next frame
+ // in the score applies delta changes to _currentFrame, and ideally we want
+ // those deltas to be applied over the top of whatever the current state is.
+ for (uint ch = 0; ch < _channels.size(); ch++)
+ *_currentFrame->_sprites[ch] = *_channels[ch]->_sprite;
+
// this copies in the frame data and updates _curFrameNumber
loadFrame(nextFrameNumberToLoad, true);
}
Commit: 35b03fe1605b2b07733b3ac1661e62eebf0986da
https://github.com/scummvm/scummvm/commit/35b03fe1605b2b07733b3ac1661e62eebf0986da
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:25+08:00
Commit Message:
DIRECTOR: Fix regression with tempo selection
Restores all of the alternative "wait for" types of tempo instruction.
Changed paths:
engines/director/score.cpp
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 92a0ac582be..e013bb32ed1 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -427,7 +427,7 @@ void Score::updateCurrentFrame() {
}
void Score::updateNextFrameTime() {
- byte tempo = _currentFrame->_mainChannels.scoreCachedTempo;
+ byte tempo = _currentFrame->_mainChannels.tempo ? _currentFrame->_mainChannels.tempo : _currentFrame->_mainChannels.scoreCachedTempo;
// puppetTempo is overridden by changes in score tempo
if (_currentFrame->_mainChannels.tempo || tempo != _lastTempo) {
_puppetTempo = 0;
Commit: 62779d5facb7c03b0a910e023c3b8a0ff0bdd87d
https://github.com/scummvm/scummvm/commit/62779d5facb7c03b0a910e023c3b8a0ff0bdd87d
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:41:25+08:00
Commit Message:
DIRECTOR: Add Encyclopedia of Clamps to detection list
Changed paths:
engines/director/detection_tables.h
diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h
index 272222fdb36..2075124d244 100644
--- a/engines/director/detection_tables.h
+++ b/engines/director/detection_tables.h
@@ -110,6 +110,7 @@ static const PlainGameDescriptor directorGames[] = {
{ "chemicus", "Chemicus: Journey to the Other Side" },
{ "chopsuey", "Chop Suey" },
{ "circus", "Circus!" },
+ { "clamps", "The Interactive Encyclopedia of Clamps" },
{ "connections", "Connections" },
{ "cracking", "Cracking the Conspiracy" },
{ "criticalpath", "Critical Path" },
@@ -6024,6 +6025,9 @@ static const DirectorGameDescription gameDescriptions[] = {
MACGAME1_l("chuchutataki", "", "xn--w8j9aa6za4nb00ac", "c148f66ae3511fb88733102aa27efe7e", 106172, Common::JA_JPN, 501),
WINGAME1t_l("chuchutataki", "", "TYUUTYUU.EXE", "bfb442750a6a264825fac3cc12a69d71", 1411220, Common::JA_JPN, 501),
+ MACGAME1("clamps", "", "Clamp256", "tr:b69f9cb79da512852ed383a1cfef14b9", 718125, 500),
+ WINGAME1("clamps", "", "clamps.exe", "t:6c8184462730c86387dff6e8c50e236a", 1394481, 500),
+
// Series includes Schubert, Tchaikovsky, Mozart, Vivaldi and probably more
WINGAME1_l("clasplusbee2", "", "BEE2_95.EXE", "1a7acbba10a7246ba58c1d53fc7203f5", 1694421, Common::ES_ESP, 501),
Commit: 6319baad999743bbec5f0b6dd1f136959655da64
https://github.com/scummvm/scummvm/commit/6319baad999743bbec5f0b6dd1f136959655da64
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:43:20+08:00
Commit Message:
DIRECTOR: Fix MacArchive::getFileSize()
Changed paths:
engines/director/archive.cpp
engines/director/archive.h
engines/director/lingo/lingo-the.cpp
diff --git a/engines/director/archive.cpp b/engines/director/archive.cpp
index 0d63c825ccb..e2ca60402bc 100644
--- a/engines/director/archive.cpp
+++ b/engines/director/archive.cpp
@@ -110,7 +110,7 @@ void Archive::listUnaccessedChunks() {
debugC(5, kDebugLoading, "Unaccessed Chunks in '%s':\n%s", _pathName.c_str(), s.c_str());
}
-int Archive::getFileSize() {
+uint32 Archive::getFileSize() {
if (!_stream)
return 0;
@@ -301,6 +301,13 @@ MacArchive::~MacArchive() {
delete _resFork;
}
+uint32 MacArchive::getFileSize() {
+ if (!_resFork)
+ return 0;
+
+ return _resFork->getResForkDataSize();
+}
+
void MacArchive::close() {
Archive::close();
delete _resFork;
diff --git a/engines/director/archive.h b/engines/director/archive.h
index 6553437566c..1dd80aa4a27 100644
--- a/engines/director/archive.h
+++ b/engines/director/archive.h
@@ -62,7 +62,7 @@ public:
Common::String getPathName() const { return _pathName; }
Common::String getFileName() const;
void setPathName(const Common::String &name) { _pathName = name; }
- int getFileSize();
+ virtual uint32 getFileSize();
bool isOpen() const { return _stream != 0; }
@@ -102,6 +102,7 @@ public:
MacArchive();
~MacArchive() override;
+ uint32 getFileSize() override;
void close() override;
bool openFile(const Common::Path &path) override;
bool openStream(Common::SeekableReadStream *stream, uint32 startOffset = 0) override;
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 9259827250a..e0e66f8bfe3 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -719,7 +719,7 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
d = 0; // Let's pretend the movie is compactified
break;
case kTheMovieFileSize:
- d = movie->getArchive()->getFileSize();
+ d = (int)movie->getArchive()->getFileSize();
break;
case kTheMoviePath:
case kThePathName:
Commit: b52de552fa73c3c863382c613a43718fb3e1e353
https://github.com/scummvm/scummvm/commit/b52de552fa73c3c863382c613a43718fb3e1e353
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:43:22+08:00
Commit Message:
DIRECTOR: Prevent repeats in DirectorSound::playExternalSound
This should give the same behaviour as DirectorSound::playCastMember.
Fixes sounds playing twice if the animation takes too long in Spaceship
Warlock (e.g. the hovertaxi landing in the demo).
Changed paths:
engines/director/sound.cpp
diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index 3c15fc3d59e..f89cb512323 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -431,7 +431,8 @@ void DirectorSound::playExternalSound(uint16 menu, uint16 submenu, uint8 soundCh
return;
SoundID soundId(kSoundExternal, menu, submenu);
- if (isChannelActive(soundChannel) && isLastPlayedSound(soundChannel, soundId))
+ // If the sound is the same ID ast the last played, do nothing.
+ if (isLastPlayedSound(soundChannel, soundId))
return;
if (menu < kMinSampledMenu || menu > kMaxSampledMenu) {
Commit: c51dee3730e04dadb3ccbd5fb0b7ad0291315cdd
https://github.com/scummvm/scummvm/commit/c51dee3730e04dadb3ccbd5fb0b7ad0291315cdd
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:43:22+08:00
Commit Message:
DIRECTOR: Clear cast ID in D2 when sprite is a shape
Fixes various graphical artifacts, e.g. movie ENG:J11 at 230
of Spaceship Warlock having a big steam cloud in the top-left instead of
the left-turn hotspot, the Invictus logo in Over-Ring-Under having a
large white box to the right of the text.
Changed paths:
engines/director/frame.cpp
diff --git a/engines/director/frame.cpp b/engines/director/frame.cpp
index 97de843d10e..04ca634ed7d 100644
--- a/engines/director/frame.cpp
+++ b/engines/director/frame.cpp
@@ -327,7 +327,9 @@ void readSpriteDataD2(Common::SeekableReadStreamEndian &stream, Sprite &sprite,
case 6:
if (sprite.isQDShape()) {
sprite._pattern = stream.readUint16();
+ sprite._castId = CastMemberID(0, 0);
} else {
+ sprite._pattern = 0;
sprite._castId = CastMemberID(stream.readUint16(), DEFAULT_CAST_LIB);
}
break;
Commit: cb69ac6255a5751ab297e9c714b3c52731621410
https://github.com/scummvm/scummvm/commit/cb69ac6255a5751ab297e9c714b3c52731621410
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:43:22+08:00
Commit Message:
DIRECTOR: Cap palette cycling framerate to _fpsLimit
Changed paths:
engines/director/score.cpp
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index e013bb32ed1..22619d4b0d8 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -776,6 +776,9 @@ bool Score::renderPrePaletteCycle(RenderMode mode) {
if (debugChannelSet(-1, kDebugFast))
frameRate = 30;
+ if (g_director->_fpsLimit)
+ frameRate = MIN((int)g_director->_fpsLimit, frameRate);
+
int frameDelay = 1000 / 60;
int fadeFrames = kFadeColorFrames[frameRate - 1];
if (_vm->getVersion() >= 500)
@@ -939,6 +942,10 @@ void Score::renderPaletteCycle(RenderMode mode) {
if (speed == 0)
return;
+ // Apply the global FPS limit if required
+ if (g_director->_fpsLimit)
+ speed = MIN((int)g_director->_fpsLimit, speed);
+
if (debugChannelSet(-1, kDebugFast))
speed = 30;
@@ -963,7 +970,7 @@ void Score::renderPaletteCycle(RenderMode mode) {
// Do a full color cycle in one frame transition
int steps = lastColor - firstColor + 1;
- debugC(2, kDebugImages, "Score::renderPaletteCycle(): color cycle palette %s, from colors %d to %d, over %d steps %d times", currentPalette.asString().c_str(), firstColor, lastColor, steps, _currentFrame->_mainChannels.palette.cycleCount);
+ debugC(2, kDebugImages, "Score::renderPaletteCycle(): color cycle palette %s, from colors %d to %d, over %d steps %d times (delay: %d ms)", currentPalette.asString().c_str(), firstColor, lastColor, steps, _currentFrame->_mainChannels.palette.cycleCount, delay);
for (int i = 0; i < _currentFrame->_mainChannels.palette.cycleCount; i++) {
for (int j = 0; j < steps; j++) {
uint32 startTime = g_system->getMillis();
@@ -1096,6 +1103,9 @@ void Score::renderPaletteCycle(RenderMode mode) {
if (debugChannelSet(-1, kDebugFast))
frameRate = 30;
+ if (g_director->_fpsLimit)
+ frameRate = MIN((int)g_director->_fpsLimit, frameRate);
+
int frameDelay = 1000 / 60;
int fadeFrames = kFadeColorFrames[frameRate - 1];
if (_vm->getVersion() >= 500)
Commit: aa4512f85ce219bff64bb1f0178327e4237463f5
https://github.com/scummvm/scummvm/commit/aa4512f85ce219bff64bb1f0178327e4237463f5
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T17:43:22+08:00
Commit Message:
DIRECTOR: Allow calls to b_label with integer argument
Fixes the story playback mode in Astrid Lindgren's Pippi.
Changed paths:
engines/director/lingo/lingo-builtins.cpp
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index c0ee93c5dc2..43182efbebe 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -2276,10 +2276,15 @@ void LB::b_installMenu(int nargs) {
}
void LB::b_label(int nargs) {
+ // label functions as marker when the input is an int
Datum d = g_lingo->pop();
- uint16 label = g_lingo->func_label(d);
-
- g_lingo->push(label);
+ int marker;
+ if (d.type == STRING) {
+ marker = g_lingo->func_label(d);
+ } else {
+ marker = g_lingo->func_marker(d.asInt());
+ }
+ g_lingo->push(marker);
}
void LB::b_marker(int nargs) {
Commit: 38208e3688bd775f086b7d45e2ecf036703d266e
https://github.com/scummvm/scummvm/commit/38208e3688bd775f086b7d45e2ecf036703d266e
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T18:06:26+08:00
Commit Message:
DIRECTOR: Fix handling of relative path markers
It is possible to use relative path syntax (e.g. multiple colons in Mac
path syntax, . or .. in Windows path syntax) to move up the directory
tree in relation to the current path. Because resolvePath() works from a
fixed base, we have to rectify any paths with relative markers so they
become an absolute path, which is then resolved in the usual way.
Fixes entering the bridge for the Asteroid or Terra missions in
Spaceship Warlock; both of these movies need to jump to a movie
named BBalcView in a different folder, and without rectification it will
select BBalcView in the current (WARLOCKSHIP) folder which is used for
the first mission only.
Changed paths:
engines/director/lingo/lingo-builtins.cpp
engines/director/util.cpp
engines/director/util.h
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 43182efbebe..e68f46af1bf 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -1210,7 +1210,9 @@ void LB::b_getNthFileNameInFolder(int nargs) {
return;
}
- Common::Path path = findPath(pathRaw, true, true, true);
+ // getNthFileNameInFolder requires an absolute path as an input.
+ // relative paths will not match anything.
+ Common::Path path = findAbsolutePath(pathRaw, true);
// for directory, we either return the correct path, which we can access recursively.
// or we get a wrong path, which will lead us to a non-exist file node
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index 46d00fc156c..4e32d542fb5 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -441,7 +441,7 @@ const char *recIndent() {
return tabs[recLevel];
}
-bool isAbsolutePath(Common::String &path) {
+bool isAbsolutePath(const Common::String &path) {
// Starts with Mac directory notation for the game root
if (path.hasPrefix(Common::String("@") + g_director->_dirSeparator))
return true;
@@ -454,7 +454,56 @@ bool isAbsolutePath(Common::String &path) {
return false;
}
-Common::Path toSafePath(Common::String &path) {
+bool isPathWithRelativeMarkers(const Common::String &path) {
+ if (path.contains("::"))
+ return true;
+ if (path.hasPrefix(".\\") || path.hasSuffix("\\.") || path.contains("\\.\\"))
+ return true;
+ if (path.hasPrefix("..\\") || path.hasSuffix("\\..") || path.contains("\\..\\"))
+ return true;
+ return false;
+}
+
+
+Common::String rectifyRelativePath(const Common::String &path, const Common::Path &base) {
+ Common::StringArray components = base.splitComponents();
+ uint32 idx = 0;
+
+ while (idx < path.size()) {
+ uint32 start = idx;
+ while (idx < path.size() && path[idx] != ':' && path[idx] != '\\')
+ idx++;
+ Common::String comp = path.substr(start, idx - start);
+ if (comp.equals("..") && !components.empty()) {
+ components.pop_back();
+ } else if (!comp.empty() && !comp.equals(".")) {
+ components.push_back(comp);
+ }
+ if (idx >= path.size())
+ break;
+
+ if (path[idx] == ':') {
+ idx += 1;
+ while (idx < path.size() && path[idx] == ':') {
+ if (!components.empty())
+ components.pop_back();
+ idx += 1;
+ }
+ continue;
+ }
+
+ if (path[idx] == '\\') {
+ idx += 1;
+ continue;
+ }
+ }
+ Common::String result = "@:" + Common::Path::joinComponents(components).toString(g_director->_dirSeparator);
+ debug(9, "rectifyRelativePath(): '%s' + '%s' => '%s'", base.toString(g_director->_dirSeparator).c_str(), path.c_str(), result.c_str());
+ return result;
+}
+
+
+Common::Path toSafePath(const Common::String &path) {
// Encode a Director raw path as a platform-independent path.
// This needs special care, as Mac filenames allow using '/' in them!
// - Scrub the pathname to be relative with the correct dir separator
@@ -473,7 +522,7 @@ Common::Path toSafePath(Common::String &path) {
return result;
}
-Common::String convertPath(Common::String &path) {
+Common::String convertPath(const Common::String &path) {
if (path.empty())
return path;
@@ -522,7 +571,7 @@ Common::String unixToMacPath(const Common::String &path) {
return res;
}
-Common::String getPath(Common::String path, Common::String cwd) {
+Common::String getPath(const Common::String &path, const Common::String &cwd) {
const char *s;
if ((s = strrchr(path.c_str(), g_director->_dirSeparator))) {
return Common::String(path.c_str(), s + 1);
@@ -531,7 +580,7 @@ Common::String getPath(Common::String path, Common::String cwd) {
return cwd; // The path is not altered
}
-Common::String convert83Path(Common::String &path) {
+Common::String convert83Path(const Common::String &path) {
Common::String addedexts;
Common::String convPath;
@@ -566,9 +615,9 @@ Common::String convert83Path(Common::String &path) {
return convPath;
}
-Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool directory) {
+Common::Path resolveFSPath(const Common::String &path, const Common::Path &base, bool directory) {
// Path is the raw input from Director. Scrub it to be a clean relative path.
- path = convertPath(path);
+ Common::String converted = convertPath(path);
// Absolute path to the game directory
Common::Path gamePath = Common::Path(g_director->getGameDataDir()->getPath());
@@ -582,7 +631,7 @@ Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool direct
Common::FSNode filesystem(testPath);
// Split this into a component list for iteration.
- Common::StringTokenizer directory_list(path, Common::String(g_director->_dirSeparator));
+ Common::StringTokenizer directory_list(converted, Common::String(g_director->_dirSeparator));
// newPath is our final result; construct this based on successful filesystem tests
Common::Path newPath = Common::Path(base);
if (!base.empty())
@@ -635,7 +684,7 @@ Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool direct
return Common::Path();
}
-Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory, const char **exts) {
+Common::Path resolvePath(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::Path result = resolveFSPath(path, base, directory);
if (!result.empty()) {
return result;
@@ -707,8 +756,8 @@ Common::Path resolvePath(Common::String &path, Common::Path &base, bool director
return Common::Path();
}
-Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory, const char **exts) {
- path = convertPath(path);
+Common::Path resolvePartialPath(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
+ Common::String converted = convertPath(path);
Common::Path result;
Common::StringArray baseTokens = base.splitComponents();
@@ -717,7 +766,7 @@ Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool d
Common::Path testBase = Common::Path::joinComponents(baseTokens);
// Try removing leading components of the target path
- Common::StringArray tokens = Common::StringTokenizer(path, Common::String(g_director->_dirSeparator)).split();
+ Common::StringArray tokens = Common::StringTokenizer(converted, Common::String(g_director->_dirSeparator)).split();
while (tokens.size()) {
Common::String subpath;
@@ -744,7 +793,7 @@ Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool d
return result;
}
-Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts) {
+Common::Path resolvePathWithFuzz(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::Path result = resolvePath(path, base, directory, exts);
if (result.empty()) {
// Try again with all non-FAT compatible characters stripped
@@ -761,7 +810,7 @@ Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool
return result;
}
-Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts) {
+Common::Path resolvePartialPathWithFuzz(const Common::String &path, const Common::Path &base, bool directory, const char **exts) {
Common::Path result = resolvePartialPath(path, base, directory, exts);
if (result.empty()) {
// Try again with all non-FAT compatible characters stripped
@@ -778,32 +827,52 @@ Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base
return result;
}
-Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths, bool directory, const char **exts) {
+Common::Path findAbsolutePath(const Common::String &path, bool directory, const char **exts) {
Common::Path result, base;
- debugN(9, "%s", recIndent());
- debug(9, "findPath(): beginning search for \"%s\"", path.c_str());
- // For an absolute path, first check it relative to the filesystem
if (isAbsolutePath(path)) {
debugN(9, "%s", recIndent());
- debug(9, "findPath(): searching absolute path");
+ debug(9, "findAbsolutePath(): searching absolute path");
result = resolvePathWithFuzz(path, base, directory, exts);
if (!result.empty()) {
debugN(9, "%s", recIndent());
- debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
- return result;
+ debug(9, "findAbsolutePath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
}
}
+ return result;
+}
+
+Common::Path findPath(const Common::Path &path, bool currentFolder, bool searchPaths, bool directory, const char **exts) {
+ return findPath(path.toString(g_director->_dirSeparator), currentFolder, searchPaths, directory, exts);
+}
+
+Common::Path findPath(const Common::String &path, bool currentFolder, bool searchPaths, bool directory, const char **exts) {
+ Common::Path result, base;
+ debugN(9, "%s", recIndent());
+ debug(9, "findPath(): beginning search for \"%s\"", path.c_str());
+
+ Common::String currentPath = g_director->getCurrentPath();
+ Common::Path current = resolvePath(currentPath, base, true, exts);
+
+ Common::String testPath = path;
+ // If the path contains relative elements, rectify it with respect to the current folder
+ if (isPathWithRelativeMarkers(testPath)) {
+ testPath = rectifyRelativePath(testPath, current);
+ }
+
+ // For an absolute path, first check it relative to the filesystem
+ result = findAbsolutePath(testPath, directory, exts);
+ if (!result.empty()) {
+ return result;
+ }
if (currentFolder) {
- Common::String currentPath = g_director->getCurrentPath();
- Common::Path current = resolvePath(currentPath, base, true, exts);
debugN(9, "%s", recIndent());
debug(9, "findPath(): searching current folder %s", current.toString().c_str());
base = current;
- result = resolvePartialPathWithFuzz(path, base, directory, exts);
+ result = resolvePartialPathWithFuzz(testPath, base, directory, exts);
if (!result.empty()) {
debugN(9, "%s", recIndent());
- debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+ debug(9, "findPath(): resolved \"%s\" -> \"%s\"", testPath.c_str(), result.toString().c_str());
return result;
}
}
@@ -812,10 +881,10 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
debugN(9, "%s", recIndent());
debug(9, "findPath(): searching game root path");
base = Common::Path();
- result = resolvePartialPathWithFuzz(path, base, directory, exts);
+ result = resolvePartialPathWithFuzz(testPath, base, directory, exts);
if (!result.empty()) {
debugN(9, "%s", recIndent());
- debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+ debug(9, "findPath(): resolved \"%s\" -> \"%s\"", testPath.c_str(), result.toString().c_str());
return result;
}
@@ -841,10 +910,10 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
}
debugN(9, "%s", recIndent());
debug(9, "findPath(): searching search path folder %s", searchIn.c_str());
- result = resolvePartialPathWithFuzz(path, base, directory, exts);
+ result = resolvePartialPathWithFuzz(testPath, base, directory, exts);
if (!result.empty()) {
debugN(9, "%s", recIndent());
- debug(9, "findPath(): resolved \"%s\" -> \"%s\"", path.c_str(), result.toString().c_str());
+ debug(9, "findPath(): resolved \"%s\" -> \"%s\"", testPath.c_str(), result.toString().c_str());
return result;
}
}
@@ -855,7 +924,7 @@ Common::Path findPath(Common::String &path, bool currentFolder, bool searchPaths
return Common::Path();
}
-Common::Path findMoviePath(Common::String &path, bool currentFolder, bool searchPaths) {
+Common::Path findMoviePath(const Common::String &path, bool currentFolder, bool searchPaths) {
const char *extsD3[] = { ".MMM", nullptr };
const char *extsD4[] = { ".DIR", ".DXR", ".EXE", nullptr };
const char *extsD5[] = { ".DIR", ".DXR", ".CST", ".CXT", ".EXE", nullptr };
@@ -876,7 +945,7 @@ Common::Path findMoviePath(Common::String &path, bool currentFolder, bool search
return result;
}
-Common::Path findAudioPath(Common::String &path, bool currentFolder, bool searchPaths) {
+Common::Path findAudioPath(const Common::String &path, bool currentFolder, bool searchPaths) {
const char *exts[] = { ".AIF", ".WAV", nullptr };
Common::Path result = findPath(path, currentFolder, searchPaths, false, exts);
diff --git a/engines/director/util.h b/engines/director/util.h
index 8ae168ff38b..2558c31a377 100644
--- a/engines/director/util.h
+++ b/engines/director/util.h
@@ -33,23 +33,25 @@ namespace Director {
int castNumToNum(const char *str);
char *numToCastNum(int num);
-bool isAbsolutePath(Common::String &path);
+bool isAbsolutePath(const Common::String &path);
-Common::Path toSafePath(Common::String &path);
-Common::String convertPath(Common::String &path);
+Common::Path toSafePath(const Common::String &path);
+Common::String convertPath(const Common::String &path);
Common::String unixToMacPath(const Common::String &path);
-Common::String getPath(Common::String path, Common::String cwd);
-
-Common::Path resolveFSPath(Common::String &path, Common::Path &base, bool directory);
-Common::Path resolvePath(Common::String &path, Common::Path &base, bool directory, const char **exts);
-Common::Path resolvePartialPath(Common::String &path, Common::Path &base, bool directory, const char **exts);
-Common::Path resolvePathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts);
-Common::Path resolvePartialPathWithFuzz(Common::String &path, Common::Path &base, bool directory, const char **exts);
-Common::Path findPath(Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false, const char **exts = nullptr);
-Common::Path findMoviePath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
-Common::Path findAudioPath(Common::String &path, bool currentFolder = true, bool searchPaths = true);
+Common::String getPath(const Common::String &path, const Common::String &cwd);
+
+Common::Path resolveFSPath(const Common::String &path, const Common::Path &base, bool directory);
+Common::Path resolvePath(const Common::String &path, const Common::Path &base, bool directory, const char **exts);
+Common::Path resolvePartialPath(const Common::String &path, const Common::Path &base, bool directory, const char **exts);
+Common::Path resolvePathWithFuzz(const Common::String &path, const Common::Path &base, bool directory, const char **exts);
+Common::Path resolvePartialPathWithFuzz(const Common::String &path, const Common::Path &base, bool directory, const char **exts);
+Common::Path findAbsolutePath(const Common::String &path, bool directory = false, const char **exts = nullptr);
+Common::Path findPath(const Common::Path &path, bool currentFolder = true, bool searchPaths = true, bool directory = false, const char **exts = nullptr);
+Common::Path findPath(const Common::String &path, bool currentFolder = true, bool searchPaths = true, bool directory = false, const char **exts = nullptr);
+Common::Path findMoviePath(const Common::String &path, bool currentFolder = true, bool searchPaths = true);
+Common::Path findAudioPath(const Common::String &path, bool currentFolder = true, bool searchPaths = true);
bool hasExtension(Common::String filename);
Commit: 3be280d3f8f5fdad8948309ca8105a024bc38817
https://github.com/scummvm/scummvm/commit/3be280d3f8f5fdad8948309ca8105a024bc38817
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T18:09:09+08:00
Commit Message:
DIRECTOR: Add CD loading delay quirk
Spaceship Warlock is optimised for very slow disk reads; quite a lot of
movie changes will be preceded by a music cue and a still image, knowing
that the system will take a couple of seconds to read the next file.
As a compromise, add a fake delay of [file size]*1000/150000 ms to movie
switches. This can be short-circuited by clicking the mouse, so it is
still possible to navigate around quickly.
Changed paths:
engines/director/director.cpp
engines/director/director.h
engines/director/events.cpp
engines/director/game-quirks.cpp
engines/director/util.cpp
engines/director/window.cpp
diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index 62cfa5a329f..4889ddb0a8a 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -101,6 +101,8 @@ DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gam
_forceDate.tm_mon = -1;
_forceDate.tm_year = -1;
_forceDate.tm_wday = -1;
+ _loadSlowdownFactor = 0;
+ _loadSlowdownCooldownTime = 0;
_wm = nullptr;
diff --git a/engines/director/director.h b/engines/director/director.h
index 666ed71adf8..0018ded905f 100644
--- a/engines/director/director.h
+++ b/engines/director/director.h
@@ -228,12 +228,13 @@ public:
bool desktopEnabled();
// events.cpp
- bool processEvents(bool captureClick = false);
+ bool processEvents(bool captureClick = false, bool skipWindowManager = false);
void processEventQUIT();
uint32 getMacTicks();
// game-quirks.cpp
void gameQuirks(const char *target, Common::Platform platform);
+ void loadSlowdownCooloff(uint32 delay = 2000);
void delayMillis(uint32 delay);
@@ -282,6 +283,8 @@ public:
// used for quirks
byte _fpsLimit;
TimeDate _forceDate;
+ uint32 _loadSlowdownFactor;
+ uint32 _loadSlowdownCooldownTime;
private:
byte _currentPalette[768];
diff --git a/engines/director/events.cpp b/engines/director/events.cpp
index dc1215a874e..ae9c0109feb 100644
--- a/engines/director/events.cpp
+++ b/engines/director/events.cpp
@@ -40,14 +40,14 @@ namespace Director {
uint32 DirectorEngine::getMacTicks() { return (g_system->getMillis() * 60 / 1000.) - _tickBaseline; }
-bool DirectorEngine::processEvents(bool captureClick) {
- debugC(3, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
- debugC(3, kDebugEvents, "@@@@ Processing events");
- debugC(3, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
+bool DirectorEngine::processEvents(bool captureClick, bool skipWindowManager) {
+ debugC(5, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ debugC(5, kDebugEvents, "@@@@ Processing events");
+ debugC(5, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
- if (!_wm->processEvent(event)) {
+ if (skipWindowManager || !_wm->processEvent(event)) {
// We only want to handle these events if the event
// wasn't handled by the window manager.
switch (event.type) {
@@ -71,7 +71,7 @@ bool DirectorEngine::processEvents(bool captureClick) {
if (captureClick)
return true;
break;
- case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
if (captureClick)
return true;
break;
@@ -261,6 +261,7 @@ bool Movie::processEvent(Common::Event &event) {
_currentHiliteChannelId = 0;
_mouseDownWasInButton = false;
+ g_director->loadSlowdownCooloff();
return true;
case Common::EVENT_KEYDOWN:
@@ -276,6 +277,7 @@ bool Movie::processEvent(Common::Event &event) {
_lastTimeOut = _lastEventTime;
queueUserEvent(kEventKeyDown);
+ g_director->loadSlowdownCooloff();
return true;
case Common::EVENT_KEYUP:
diff --git a/engines/director/game-quirks.cpp b/engines/director/game-quirks.cpp
index 2f46608f633..fa2808992c6 100644
--- a/engines/director/game-quirks.cpp
+++ b/engines/director/game-quirks.cpp
@@ -100,6 +100,11 @@ struct SaveFilePath {
};
+static void quirkWarlock() {
+ g_director->_loadSlowdownFactor = 150000; // emulate a 1x CD drive
+ g_director->_fpsLimit = 15;
+}
+
static void quirkLimit15FPS() {
g_director->_fpsLimit = 15;
}
@@ -175,6 +180,12 @@ struct Quirk {
Common::Platform platform;
void (*quirk)();
} quirks[] = {
+ // Spaceship Warlock is designed to run as quickly as possible on a
+ // single speed CD drive; there's often content just before a movie
+ // transition which would otherwise get skipped past.
+ { "warlock", Common::kPlatformMacintosh, &quirkWarlock },
+ { "warlock", Common::kPlatformWindows, &quirkWarlock },
+
// Eastern Mind sets the score to play back at a high frame rate,
// however the developers were using slow hardware, so some
// animations play back much faster than intended.
@@ -253,6 +264,11 @@ void DirectorEngine::gameQuirks(const char *target, Common::Platform platform) {
}
}
+void DirectorEngine::loadSlowdownCooloff(uint32 delay) {
+ if (_loadSlowdownFactor)
+ _loadSlowdownCooldownTime = g_system->getMillis() + delay;
+}
+
/*****************
* CachedArchive
*****************/
diff --git a/engines/director/util.cpp b/engines/director/util.cpp
index 4e32d542fb5..f5f364c4e42 100644
--- a/engines/director/util.cpp
+++ b/engines/director/util.cpp
@@ -499,6 +499,7 @@ Common::String rectifyRelativePath(const Common::String &path, const Common::Pat
}
Common::String result = "@:" + Common::Path::joinComponents(components).toString(g_director->_dirSeparator);
debug(9, "rectifyRelativePath(): '%s' + '%s' => '%s'", base.toString(g_director->_dirSeparator).c_str(), path.c_str(), result.c_str());
+ warning("rectifyRelativePath(): '%s' + '%s' => '%s'", base.toString(g_director->_dirSeparator).c_str(), path.c_str(), result.c_str());
return result;
}
diff --git a/engines/director/window.cpp b/engines/director/window.cpp
index edc231648f6..36ee8424808 100644
--- a/engines/director/window.cpp
+++ b/engines/director/window.cpp
@@ -418,6 +418,32 @@ bool Window::loadNextMovie() {
return false;
probeResources(mov);
+
+ // Artificial delay for games that expect slow media, e.g. Spaceship Warlock
+ if (g_director->_loadSlowdownFactor && !debugChannelSet(-1, kDebugFast)) {
+ // Check that we're not cooling down from skipping a delay.
+ if (g_system->getMillis() > g_director->_loadSlowdownCooldownTime) {
+ uint32 delay = mov->getFileSize() * 1000 / g_director->_loadSlowdownFactor;
+ debugC(5, kDebugLoading, "Slowing load of next movie by %d ms", delay);
+ while (delay != 0) {
+ uint32 dec = MIN((uint32)10, delay);
+ // Skip delay if mouse is clicked
+ if (g_director->processEvents(true, true)) {
+ g_director->loadSlowdownCooloff();
+ break;
+ }
+ g_director->_wm->replaceCursor(Graphics::kMacCursorWatch);
+ g_director->draw();
+ g_system->delayMillis(dec);
+ delay -= dec;
+ }
+ }
+ // If this movie switch is within the cooldown time,
+ // don't add a delay. This is to allow for rapid navigation.
+ // User input events will call loadSlowdownCooloff() and
+ // extend the cooldown time.
+ }
+
_currentMovie = new Movie(this);
_currentMovie->setArchive(mov);
Commit: 5f5343b8b9870a462644d5ec56ba18ca9297cb3f
https://github.com/scummvm/scummvm/commit/5f5343b8b9870a462644d5ec56ba18ca9297cb3f
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T18:09:14+08:00
Commit Message:
DIRECTOR: Fix Channel::setClean resetting the same video
Fixes introductory movies in The Dark Eye.
Changed paths:
engines/director/channel.cpp
diff --git a/engines/director/channel.cpp b/engines/director/channel.cpp
index 0016522e01c..944c9ec2ab3 100644
--- a/engines/director/channel.cpp
+++ b/engines/director/channel.cpp
@@ -396,7 +396,7 @@ void Channel::setClean(Sprite *nextSprite, bool partial) {
if (nextSprite) {
if (nextSprite->_cast && (_dirty || _sprite->_castId != nextSprite->_castId)) {
- if (nextSprite->_cast->_type == kCastDigitalVideo) {
+ if (_sprite->_castId != nextSprite->_castId && nextSprite->_cast->_type == kCastDigitalVideo) {
Common::String path = nextSprite->_cast->getCast()->getVideoPath(nextSprite->_castId.member);
if (!path.empty()) {
Commit: 02365a0d468e529bdbc6e979a871c6bfea4957cd
https://github.com/scummvm/scummvm/commit/02365a0d468e529bdbc6e979a871c6bfea4957cd
Author: Scott Percival (code at moral.net.au)
Date: 2024-02-16T18:09:14+08:00
Commit Message:
DIRECTOR: Fix return value of b_power
Fixes the puzzle minigame in Astrid Lindgren's Pippi.
Changed paths:
engines/director/lingo/lingo-builtins.cpp
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index e68f46af1bf..48185fae302 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -436,7 +436,7 @@ void LB::b_power(int nargs) {
Datum d1 = g_lingo->pop();
Datum d2 = g_lingo->pop();
Datum res(pow(d2.asFloat(), d1.asFloat()));
- g_lingo->push(d1);
+ g_lingo->push(res);
}
void LB::b_random(int nargs) {
More information about the Scummvm-git-logs
mailing list