[Scummvm-git-logs] scummvm master -> d9a29123e1abf2617289c16f2f25f4355cc79b4d
neuromancer
noreply at scummvm.org
Tue Jun 23 13:48:22 UTC 2026
This automated email contains information about 11 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
35cc52ad15 EEM: added support for EEM1 DOS demo
964ffc5b79 EEM: initial code for supporting EEM1 Mac release
7b230f43c4 EEM: font support for EEM1 Mac
2685dc2d34 EEM: sound support for EEM1 Mac
95e34f0a24 EEM: start loading the practice mystery in EEM1 Mac
de9f508c07 EEM: practice mystery starts to work in EEM1 Mac
5b53d52b05 EEM: correctly parse hotspots in EEM1 Mac
7bf463b483 EEM: load all the cases from EEM1 Mac
c2f7d05571 EEM: correctly load animations from EEM1 Mac
c17db422a8 EEM: improved playback of music from EEM1 Mac
d9a29123e1 EEM: implemented intro from EEM1 Mac
Commit: 35cc52ad15a7b97a733f65d5d1274e399236c643
https://github.com/scummvm/scummvm/commit/35cc52ad15a7b97a733f65d5d1274e399236c643
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: added support for EEM1 DOS demo
Changed paths:
engines/eem/clues.cpp
engines/eem/detection.cpp
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/ui.cpp
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index e4f3957e725..ebaf437f0d5 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -333,7 +333,7 @@ void EEMEngine::doChoosePartner() {
g_system->delayMillis(20);
}
- if (_audio) {
+ if (_audio && !isDemo()) {
if (isFloppy()) {
// Floppy _DoChoosePartner_Floppy @ 19bb:0a8e
_audio->playFloppyVoiceSlot(0x14, _partner);
@@ -639,15 +639,28 @@ void EEMEngine::doInitClues() {
_mystery._lastSite = 0;
}
- setSitePalette(isLondon() ? 0x39 : 0x22);
+ const bool demo = isDemo();
+ byte demoPalette[kPalSize];
+ const bool haveDemoPalette = demo && getSitePalette(0x22, demoPalette);
+ if (demo && haveDemoPalette) {
+ byte black[kPalSize] = {};
+ g_system->getPaletteManager()->setPalette(black, 0, 256);
+ } else {
+ setSitePalette(isLondon() ? 0x39 : 0x22);
+ }
+
Picture bg;
- const bool haveBriefingBg = _picsArchive.getPicture(0x52, bg);
+ const uint16 bgPic = demo ? (_partner == kPartnerJake ? 3 : 2) : 0x52;
+ const bool haveBriefingBg = _picsArchive.getPicture(bgPic, bg);
if (haveBriefingBg)
blitAt(bg, 0, 0);
+ if (demo && haveDemoPalette)
+ fadePaletteFromBlack(demoPalette);
+
if (isLondon())
playLondonInitCluesAnim(caseType, bg, haveBriefingBg);
- else
+ else if (!demo)
playCdFloppyInitCluesAnim(caseType, floppy, bg, haveBriefingBg);
// Briefing dialogue. CD: clue block @ ib+4 (after caseType,startSite).
diff --git a/engines/eem/detection.cpp b/engines/eem/detection.cpp
index 056915c71f6..7ffa1a8527a 100644
--- a/engines/eem/detection.cpp
+++ b/engines/eem/detection.cpp
@@ -34,6 +34,7 @@ const PlainGameDescriptor eemGames[] = {
#define GUI_OPTIONS_EEM_FLOPPY GUIO4(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_SKIP_REPEATED_CASES, GUIO_MIDIADLIB, GUIO_MIDIMT32)
#define GUI_OPTIONS_EEM_CD GUIO6(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_FIT_DIALOG_BALLOONS, GAMEOPTION_SKIP_REPEATED_CASES, GAMEOPTION_RESTORED_CONTENT, GUIO_MIDIADLIB, GUIO_MIDIMT32)
+#define GUI_OPTIONS_EEM_DEMO GUIO2(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GUIO_NOMIDI)
const ADGameDescription gameDescriptions[] = {
{
@@ -67,6 +68,16 @@ const ADGameDescription gameDescriptions[] = {
ADGF_NO_FLAGS,
GUI_OPTIONS_EEM_FLOPPY
},
+ {
+ "eem",
+ "Demo",
+ AD_ENTRY2s("EEMDEMO.EXE", "723eb5e1744f5b88562068d732990c7b", 107300,
+ "PICS.DBD", "fc6be43a0391a79263535760a8bfaecd", 322194),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_DEMO,
+ GUI_OPTIONS_EEM_DEMO
+ },
{
// Eagle Eye Mysteries in London
"eem2",
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 82613a39f66..7a3870881b8 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -306,7 +306,7 @@ void EEMEngine::advanceChainStageAfterSolve(uint mysteryNum) {
}
void EEMEngine::applySkipRepeatedCasesOption() {
- if (isLondon() || !ConfMan.getBool("skip_repeated_cases"))
+ if (isLondon() || isDemo() || !ConfMan.getBool("skip_repeated_cases"))
return;
if (_mystery.isLoaded())
return;
@@ -343,8 +343,9 @@ Common::Error EEMEngine::run() {
if (!_font.load(Common::Path("FONT.FNT")))
warning("FONT.FNT failed to load; text will not render");
- // _InitMIDI @ 20a2:013a.
- _music = new MusicPlayer(isFloppy());
+ // _InitMIDI @ 20a2:013a. The demo does not ship SAMPLE.AD or XMIDI data.
+ if (!isDemo())
+ _music = new MusicPlayer(isFloppy());
// _InitDrivers @ 1ff1:0368 (SBDIG.ADV / PASDIG.ADV).
_audio = new AudioPlayer(this);
@@ -420,12 +421,12 @@ Common::Error EEMEngine::run() {
showHighScoreLogo();
if (!shouldQuit() && !_skipIntro)
showFloppyStormLogo();
- if (!shouldQuit() && !_skipIntro && _music)
+ if (!shouldQuit() && !_skipIntro && !isDemo() && _music)
_music->playFile(Common::Path("THEME.XMI"), /* loop= */ true);
- if (!shouldQuit() && !_skipIntro)
+ if (!shouldQuit() && !_skipIntro && !isDemo())
playAnm(Common::Path("CHAT.ANM"), 120,
/* holdLastFrame= */ false);
- if (!shouldQuit() && !_skipIntro)
+ if (!shouldQuit() && !_skipIntro && !isDemo())
playAnm(Common::Path("MOVIE.ANM"), 120,
/* holdLastFrame= */ false);
} else {
@@ -499,7 +500,20 @@ screenLoop:
switch (current) {
case kScreenTitle:
_nextScreen = kScreenProfile;
- if (isFloppy()) {
+ if (isDemo()) {
+ Picture title;
+ CursorMan.showMouse(false);
+ setSitePalette(0);
+ if (_picsArchive.getPicture(1, title) && !title.surface.empty()) {
+ blitAt(title, 0, 0);
+ g_system->updateScreen();
+ waitForInput(0xFFFFFFFFu);
+ } else {
+ warning("EEM demo title PIC 1 failed to load");
+ }
+ _skipIntro = false;
+ CursorMan.showMouse(true);
+ } else if (isFloppy()) {
CursorMan.showMouse(false);
playAnm(Common::Path("TITLE.ANM"), 120,
/* holdLastFrame= */ true, /* fadeIn= */ true);
@@ -1343,14 +1357,14 @@ void EEMEngine::showFloppyStormLogo() {
g_system->getPaletteManager()->setPalette(black, 0, 256);
g_system->updateScreen();
- if (_audio)
+ if (_audio && !isDemo())
_audio->playVoc(Common::Path("THUNDER.VOC"));
fadePaletteFromBlack(target);
waitForInput(2000);
fadeCurrentPaletteToBlack();
- if (_audio)
+ if (_audio && !isDemo())
_audio->stopVoice();
}
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index a91adfc73e8..529fb549922 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -126,8 +126,11 @@ public:
const char *getGameId() const;
Common::Platform getPlatform() const;
Variant getVariant() const { return _variant; }
- bool isFloppy() const { return _variant == kVariantFloppy; }
+ bool isFloppy() const { return _variant == kVariantFloppy || isDemo(); }
bool isLondon() const { return _variant == kVariantLondonCD; }
+ bool isDemo() const {
+ return _gameDescription && (_gameDescription->flags & ADGF_DEMO);
+ }
bool hasFeature(EngineFeature f) const override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 086fd98647f..b0e9bccebef 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -1514,7 +1514,18 @@ void EEMEngine::doSetup() {
if (kNewCaseBtn.contains(mx, my)) {
saveProfile(_playerName);
- _nextScreen = kScreenChooseMystery;
+ if (isDemo()) {
+ if (_mystery.load(0, &_rng)) {
+ resetSiteArrivalState();
+ _nextScreen = kScreenInitClues;
+ } else {
+ warning("doSetup: failed to restart demo practice mystery");
+ _mystery.clear();
+ _nextScreen = kScreenAction;
+ }
+ } else {
+ _nextScreen = kScreenChooseMystery;
+ }
return;
}
@@ -1896,8 +1907,9 @@ void EEMEngine::doActionScreen() {
" Ver Recortes 3"
};
const char * const *kPickLabel = isSpanish() ? kPickLabelES : kPickLabelEN;
- // London has no BOOK3.NME, so its action menu offers four entries
- const uint numPicks = isLondon() ? (uint)kPickScrap3 : (uint)kNumPicks;
+ // London has no BOOK3.NME, and the demo ships only the practice case.
+ const uint numPicks = isDemo() ? (uint)kPickScrap1
+ : isLondon() ? (uint)kPickScrap3 : (uint)kNumPicks;
uint loT = 0, hiT = 0;
const bool anySolved1 = mysteryTierRange(1, loT, hiT) &&
@@ -1907,8 +1919,8 @@ void EEMEngine::doActionScreen() {
const bool haveTier3 = mysteryTierRange(3, loT, hiT);
const bool anySolved3 = haveTier3 && anyMysterySolved(loT, hiT);
- const bool chooseOn = _chainStage < 4;
- const bool practiceOn = _chainStage <= 1;
+ const bool chooseOn = !isDemo() && _chainStage < 4;
+ const bool practiceOn = isDemo() || _chainStage <= 1;
const bool scrap1On =
_chainStage >= 2 || (_chainStage == 1 && anySolved1);
const bool scrap2On =
Commit: 964ffc5b79afca2961fefc361b3ecc09f3b7643d
https://github.com/scummvm/scummvm/commit/964ffc5b79afca2961fefc361b3ecc09f3b7643d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: initial code for supporting EEM1 Mac release
Changed paths:
engines/eem/clues.cpp
engines/eem/detection.cpp
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/resource.cpp
engines/eem/resource.h
engines/eem/site.cpp
engines/eem/ui.cpp
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index ebaf437f0d5..6f79aeb5f81 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -289,7 +289,7 @@ void EEMEngine::doChoosePartner() {
break;
}
if (ev.type == Common::EVENT_MOUSEMOVE) {
- curMouseX = ev.mouse.x;
+ curMouseX = unscaleX(ev.mouse.x);
const uint newLevel = happinessLevel(curMouseX, isLondon());
if (newLevel != level) {
level = newLevel;
@@ -303,7 +303,7 @@ void EEMEngine::doChoosePartner() {
}
}
if (ev.type == Common::EVENT_LBUTTONDOWN) {
- const bool leftHalf = ev.mouse.x < 160;
+ const bool leftHalf = unscaleX(ev.mouse.x) < 160;
_partner = isLondon()
? (leftHalf ? kPartnerJake : kPartnerJenny)
: (leftHalf ? kPartnerJenny : kPartnerJake);
@@ -333,7 +333,7 @@ void EEMEngine::doChoosePartner() {
g_system->delayMillis(20);
}
- if (_audio && !isDemo()) {
+ if (_audio && !isDemo() && !isMacintosh()) {
if (isFloppy()) {
// Floppy _DoChoosePartner_Floppy @ 19bb:0a8e
_audio->playFloppyVoiceSlot(0x14, _partner);
diff --git a/engines/eem/detection.cpp b/engines/eem/detection.cpp
index 7ffa1a8527a..cae6ac41901 100644
--- a/engines/eem/detection.cpp
+++ b/engines/eem/detection.cpp
@@ -35,6 +35,7 @@ const PlainGameDescriptor eemGames[] = {
#define GUI_OPTIONS_EEM_FLOPPY GUIO4(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_SKIP_REPEATED_CASES, GUIO_MIDIADLIB, GUIO_MIDIMT32)
#define GUI_OPTIONS_EEM_CD GUIO6(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_FIT_DIALOG_BALLOONS, GAMEOPTION_SKIP_REPEATED_CASES, GAMEOPTION_RESTORED_CONTENT, GUIO_MIDIADLIB, GUIO_MIDIMT32)
#define GUI_OPTIONS_EEM_DEMO GUIO2(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GUIO_NOMIDI)
+#define GUI_OPTIONS_EEM_MAC GUIO2(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GUIO_NOMIDI)
const ADGameDescription gameDescriptions[] = {
{
@@ -78,6 +79,16 @@ const ADGameDescription gameDescriptions[] = {
ADGF_DEMO,
GUI_OPTIONS_EEM_DEMO
},
+ {
+ "eem",
+ "Mac",
+ AD_ENTRY2s("MysteryData", "d94c087c27e68cc299d7c5e737e458f9", 941029,
+ "PICS.DBD", "8905041070ff1352666d98cd78d5501c", 3800445),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_UNSTABLE,
+ GUI_OPTIONS_EEM_MAC
+ },
{
// Eagle Eye Mysteries in London
"eem2",
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 7a3870881b8..ce40cb5851b 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -213,9 +213,12 @@ EEMEngine::EEMEngine(OSystem *syst, const ADGameDescription *gameDesc)
ConfMan.registerDefault("skip_repeated_cases", false);
ConfMan.registerDefault("restored_content", false);
- _variant = (gameDesc && gameDesc->extra &&
- Common::String(gameDesc->extra).contains("Floppy"))
- ? kVariantFloppy : kVariantCD;
+ _variant = kVariantCD;
+ if (gameDesc && gameDesc->extra &&
+ Common::String(gameDesc->extra).contains("Floppy"))
+ _variant = kVariantFloppy;
+ if (gameDesc && gameDesc->platform == Common::kPlatformMacintosh)
+ _variant = kVariantMac;
if (gameDesc && gameDesc->gameId &&
Common::String(gameDesc->gameId) == "eem2")
_variant = kVariantLondonCD;
@@ -320,7 +323,7 @@ void EEMEngine::applySkipRepeatedCasesOption() {
}
Common::Error EEMEngine::run() {
- initGraphics(kScreenWidth, kScreenHeight);
+ initGraphics(screenWidth(), screenHeight());
if (!isFloppy() && ConfMan.getBool("restored_content")) {
Common::U32String engineDataError;
@@ -343,8 +346,8 @@ Common::Error EEMEngine::run() {
if (!_font.load(Common::Path("FONT.FNT")))
warning("FONT.FNT failed to load; text will not render");
- // _InitMIDI @ 20a2:013a. The demo does not ship SAMPLE.AD or XMIDI data.
- if (!isDemo())
+ // _InitMIDI @ 20a2:013a. The demo and Mac release do not ship DOS XMIDI data.
+ if (!isDemo() && !isMacintosh())
_music = new MusicPlayer(isFloppy());
// _InitDrivers @ 1ff1:0368 (SBDIG.ADV / PASDIG.ADV).
@@ -413,6 +416,19 @@ Common::Error EEMEngine::run() {
goto screenLoop;
}
+ if (isMacintosh()) {
+ CursorMan.showMouse(true);
+ if (!shouldQuit())
+ doProfilePicker();
+ if (!shouldQuit())
+ applyStartupTestOverrides();
+ if (!shouldQuit())
+ doChoosePartner();
+ if (!shouldQuit())
+ _nextScreen = _mystery.isLoaded() ? kScreenMap : kScreenAction;
+ goto screenLoop;
+ }
+
_skipIntro = false;
if (isFloppy()) {
if (!shouldQuit() && !_skipIntro)
@@ -682,19 +698,21 @@ void EEMEngine::setSiteHotspotCursorId(int cursorId) {
}
bool EEMEngine::openArchives() {
- if (!_picsArchive.open(Common::Path("PICS.DBD"), Common::Path("PICS.DBX"))) {
+ const bool mac = isMacintosh();
+
+ if (!_picsArchive.open(Common::Path("PICS.DBD"), Common::Path("PICS.DBX"), mac)) {
warning("PICS archive missing");
return false;
}
- if (!_aniArchive.open(Common::Path("ANI.DBD"), Common::Path("ANI.DBX"))) {
+ if (!_aniArchive.open(Common::Path("ANI.DBD"), Common::Path("ANI.DBX"), mac)) {
warning("ANI archive missing");
return false;
}
- if (!_sitesArchive.open(Common::Path("SITES.DBD"), Common::Path("SITES.DBX")))
+ if (!_sitesArchive.open(Common::Path("SITES.DBD"), Common::Path("SITES.DBX"), mac))
warning("SITES archive missing â site backgrounds disabled");
- if (!_balloonArchive.open(Common::Path("BALLOON.DBD"), Common::Path("BALLOON.DBX")))
+ if (!_balloonArchive.open(Common::Path("BALLOON.DBD"), Common::Path("BALLOON.DBX"), mac))
warning("BALLOON archive missing â clue text will lack balloons");
- if (!_buttonArchive.open(Common::Path("BUTTON.DBD"), Common::Path("BUTTON.DBX")))
+ if (!_buttonArchive.open(Common::Path("BUTTON.DBD"), Common::Path("BUTTON.DBX"), mac))
warning("BUTTON archive missing â map markers will be unlabelled");
return true;
}
@@ -711,12 +729,31 @@ bool EEMEngine::loadSitePalettes() {
warning("SITEPALS short read");
return false;
}
+ if (isMacintosh()) {
+ debugC(1, kDebugGfx, "Loaded %u Mac SITEPALS ColorTables",
+ (uint)(_sitePals.size() / (8 + 256 * 8)));
+ return true;
+ }
debugC(1, kDebugGfx, "Loaded %u SITEPALS palettes",
(uint)(_sitePals.size() / kPalSize));
return true;
}
bool EEMEngine::getSitePalette(uint num, byte *out) const {
+ if (isMacintosh()) {
+ const uint kMacColorTableSize = 8 + 256 * 8;
+ if (_sitePals.size() < (num + 1) * kMacColorTableSize)
+ return false;
+ const byte *src = _sitePals.data() + num * kMacColorTableSize + 8;
+ for (uint i = 0; i < 256; i++) {
+ const byte *entry = src + i * 8;
+ out[i * 3 + 0] = entry[2];
+ out[i * 3 + 1] = entry[4];
+ out[i * 3 + 2] = entry[6];
+ }
+ return true;
+ }
+
if (_sitePals.size() < (num + 1) * kPalSize)
return false;
const byte *src = _sitePals.data() + num * kPalSize;
@@ -862,8 +899,10 @@ void EEMEngine::playAnm(const Common::Path &path, uint frameDelayMs,
}
void EEMEngine::blitAt(const Picture &pic, int x, int y) {
- const int w = MIN<int>(pic.surface.w, kScreenWidth - x);
- const int h = MIN<int>(pic.surface.h, kScreenHeight - y);
+ x = scaleX(x);
+ y = scaleY(y);
+ const int w = MIN<int>(pic.surface.w, screenWidth() - x);
+ const int h = MIN<int>(pic.surface.h, screenHeight() - y);
if (w <= 0 || h <= 0)
return;
g_system->copyRectToScreen(pic.surface.getPixels(), pic.surface.pitch,
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 529fb549922..00ba2fc9367 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -100,6 +100,7 @@ enum Variant {
kVariantCD = 0,
kVariantFloppy = 1,
kVariantLondonCD = 2,
+ kVariantMac = 3,
};
/// `_Partner @ 29be:7918`. Selected at the partner-pick screen
@@ -111,6 +112,8 @@ enum Partner {
constexpr int kScreenWidth = 320;
constexpr int kScreenHeight = 200;
+constexpr int kMacScreenWidth = 512;
+constexpr int kMacScreenHeight = 384;
constexpr Common::Rect kPdaSiteRect (Common::Point(35, 111), 21, 25);
constexpr Common::Rect kPdaPartnerFootMapRect (Common::Point( 7, 177), 50, 23);
@@ -128,9 +131,31 @@ public:
Variant getVariant() const { return _variant; }
bool isFloppy() const { return _variant == kVariantFloppy || isDemo(); }
bool isLondon() const { return _variant == kVariantLondonCD; }
+ bool isMacintosh() const { return _variant == kVariantMac; }
bool isDemo() const {
return _gameDescription && (_gameDescription->flags & ADGF_DEMO);
}
+ int screenWidth() const { return isMacintosh() ? kMacScreenWidth : kScreenWidth; }
+ int screenHeight() const { return isMacintosh() ? kMacScreenHeight : kScreenHeight; }
+ int scaleX(int x) const {
+ return isMacintosh() ? scaleCoord(x, kMacScreenWidth, kScreenWidth) : x;
+ }
+ int scaleY(int y) const {
+ return isMacintosh() ? scaleCoord(y, kMacScreenHeight, kScreenHeight) : y;
+ }
+ int unscaleX(int x) const {
+ return isMacintosh() ? scaleCoord(x, kScreenWidth, kMacScreenWidth) : x;
+ }
+ int unscaleY(int y) const {
+ return isMacintosh() ? scaleCoord(y, kScreenHeight, kMacScreenHeight) : y;
+ }
+ Common::Point scalePoint(int x, int y) const {
+ return Common::Point(scaleX(x), scaleY(y));
+ }
+ Common::Rect scaleRect(const Common::Rect &r) const {
+ return Common::Rect(scaleX(r.left), scaleY(r.top),
+ scaleX(r.right), scaleY(r.bottom));
+ }
bool hasFeature(EngineFeature f) const override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
@@ -477,6 +502,12 @@ public:
/// (1..3) chooses one of three short one-shot MUS tracks at random.
void startLondonTravelMusic(uint8 travelKind);
private:
+ static int scaleCoord(int value, int target, int source) {
+ const bool negative = value < 0;
+ const int magnitude = negative ? -value : value;
+ const int scaled = (magnitude * target + source / 2) / source;
+ return negative ? -scaled : scaled;
+ }
Common::String _playerName; ///< Substituted into 0x80 placeholders.
diff --git a/engines/eem/resource.cpp b/engines/eem/resource.cpp
index 1371cd177e7..d9a441aef3f 100644
--- a/engines/eem/resource.cpp
+++ b/engines/eem/resource.cpp
@@ -20,6 +20,7 @@
*/
#include "common/compression/dcl.h"
+#include "common/array.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/textconsole.h"
@@ -38,8 +39,9 @@ DBDArchive::~DBDArchive() {
close();
}
-bool DBDArchive::open(const Common::Path &dbdName, const Common::Path &dbxName) {
+bool DBDArchive::open(const Common::Path &dbdName, const Common::Path &dbxName, bool bigEndian) {
close();
+ _bigEndian = bigEndian;
if (!_dbd.open(dbdName)) {
warning("DBDArchive: cannot open %s", dbdName.toString().c_str());
@@ -57,9 +59,15 @@ bool DBDArchive::open(const Common::Path &dbdName, const Common::Path &dbxName)
_index.reserve(dbxSize / 10);
while (dbx.pos() + 10 <= dbxSize) {
DBEntry entry;
- entry.offset = dbx.readUint32LE();
- entry.compressed = dbx.readUint16LE();
- entry.size = dbx.readUint32LE();
+ if (_bigEndian) {
+ entry.offset = dbx.readUint32BE();
+ entry.compressed = dbx.readUint16BE();
+ entry.size = dbx.readUint32BE();
+ } else {
+ entry.offset = dbx.readUint32LE();
+ entry.compressed = dbx.readUint16LE();
+ entry.size = dbx.readUint32LE();
+ }
_index.push_back(entry);
}
dbx.close();
@@ -73,17 +81,89 @@ void DBDArchive::close() {
if (_dbd.isOpen())
_dbd.close();
_index.clear();
+ _bigEndian = false;
}
-/// Read one 12-byte frame header + payload at the current stream position.
+bool decompressMacLZSS(Common::SeekableReadStream &stream, byte *dest,
+ uint32 packedSize, uint32 unpackedSize) {
+ Common::Array<byte> packed;
+ packed.resize(packedSize);
+ if (stream.read(packed.data(), packedSize) != packedSize) {
+ warning("decompressMacLZSS: short packed read (%u bytes)", packedSize);
+ return false;
+ }
+
+ byte dict[4096];
+ memset(dict, 0x20, sizeof(dict));
+
+ uint32 src = 0;
+ uint32 dst = 0;
+ uint16 dictPos = 0x0fee;
+ byte flags = 0;
+ byte mask = 0;
+
+ while (dst < unpackedSize && src < packedSize) {
+ if (mask == 0) {
+ flags = packed[src++];
+ mask = 1;
+ if (src > packedSize)
+ break;
+ }
+
+ if (flags & mask) {
+ if (src >= packedSize)
+ break;
+ const byte b = packed[src++];
+ dest[dst++] = b;
+ dict[dictPos] = b;
+ dictPos = (dictPos + 1) & 0x0fff;
+ } else {
+ if (src + 1 >= packedSize)
+ break;
+ const byte lo = packed[src++];
+ const byte hi = packed[src++];
+ uint16 offset = (uint16)lo | (uint16)((hi & 0xf0) << 4);
+ uint16 count = (hi & 0x0f) + 3;
+ while (count-- && dst < unpackedSize) {
+ const byte b = dict[offset];
+ offset = (offset + 1) & 0x0fff;
+ dest[dst++] = b;
+ dict[dictPos] = b;
+ dictPos = (dictPos + 1) & 0x0fff;
+ }
+ }
+
+ mask <<= 1;
+ }
+
+ if (dst != unpackedSize) {
+ warning("decompressMacLZSS: decoded %u of %u bytes", dst, unpackedSize);
+ return false;
+ }
+ return true;
+}
+
+/// Read one frame header + payload at the current stream position.
/// Shared between picture and animation loaders since the layout is identical.
-bool readFrame(Common::SeekableReadStream &stream, bool compressed, Picture &out) {
- out.flags = stream.readUint16LE();
- const uint16 height = stream.readUint16LE();
- const uint16 width = stream.readUint16LE();
- out.rowoff = stream.readUint16LE();
- out.miscflags = stream.readUint16LE();
- out.compsize = stream.readUint16LE();
+bool readFrame(Common::SeekableReadStream &stream, bool compressed, bool bigEndian, Picture &out) {
+ uint16 height;
+ uint16 width;
+
+ if (bigEndian) {
+ out.flags = stream.readUint16BE();
+ height = stream.readUint16BE();
+ width = stream.readUint16BE();
+ out.rowoff = stream.readUint16BE();
+ out.miscflags = stream.readUint16BE();
+ out.compsize = stream.readUint32BE();
+ } else {
+ out.flags = stream.readUint16LE();
+ height = stream.readUint16LE();
+ width = stream.readUint16LE();
+ out.rowoff = stream.readUint16LE();
+ out.miscflags = stream.readUint16LE();
+ out.compsize = stream.readUint16LE();
+ }
if (width == 0 || height == 0) {
warning("readFrame: zero dimensions (%ux%u)", width, height);
@@ -94,10 +174,49 @@ bool readFrame(Common::SeekableReadStream &stream, bool compressed, Picture &out
const uint32 unpacked = (uint32)width * (uint32)height;
if (!compressed) {
- if (stream.read(out.surface.getPixels(), unpacked) != unpacked) {
+ if (!bigEndian && stream.read(out.surface.getPixels(), unpacked) != unpacked) {
warning("readFrame: short raw read (%u bytes)", unpacked);
return false;
}
+ if (bigEndian) {
+ const uint32 rowBytes = (width + 1) & ~1U;
+ Common::Array<byte> row;
+ row.resize(rowBytes);
+ for (uint16 y = 0; y < height; y++) {
+ if (stream.read(row.data(), rowBytes) != rowBytes) {
+ warning("readFrame: short Mac raw row read (%u bytes)", rowBytes);
+ return false;
+ }
+ memcpy(out.surface.getBasePtr(0, y), row.data(), width);
+ }
+ }
+ return true;
+ }
+
+ if (bigEndian) {
+ if (out.compsize < 4) {
+ warning("readFrame: invalid Mac compressed size %u", out.compsize);
+ return false;
+ }
+ const uint32 decodedSize = stream.readUint32BE();
+ const uint32 packedSize = out.compsize - 4;
+
+ Common::Array<byte> decoded;
+ decoded.resize(decodedSize);
+ if (!decompressMacLZSS(stream, decoded.data(), packedSize, decodedSize)) {
+ warning("readFrame: Mac LZSS decompression failed (%u packed -> %u bytes)",
+ packedSize, decodedSize);
+ return false;
+ }
+
+ const uint32 rowBytes = (width + 1) & ~1U;
+ if (decodedSize < rowBytes * height) {
+ warning("readFrame: Mac decoded frame too short (%u < %u)",
+ decodedSize, rowBytes * height);
+ return false;
+ }
+ for (uint16 y = 0; y < height; y++)
+ memcpy(out.surface.getBasePtr(0, y), decoded.data() + y * rowBytes, width);
return true;
}
@@ -126,8 +245,8 @@ bool DBDArchive::loadEntry(uint num, Picture &out) {
}
// Leading u16 = frame count (always 1 for pictures).
- _dbd.skip(2);
- return readFrame(_dbd, entry.compressed != 0, out);
+ _bigEndian ? _dbd.readUint16BE() : _dbd.readUint16LE();
+ return readFrame(_dbd, entry.compressed != 0, _bigEndian, out);
}
bool DBDArchive::loadAnimation(uint num, Animation &out) {
@@ -142,7 +261,7 @@ bool DBDArchive::loadAnimation(uint num, Animation &out) {
return false;
}
- const uint16 frameCount = _dbd.readUint16LE();
+ const uint16 frameCount = _bigEndian ? _dbd.readUint16BE() : _dbd.readUint16LE();
if (frameCount == 0 || frameCount > 256) {
warning("DBDArchive::loadAnimation: %u has implausible frame count %u",
num, frameCount);
@@ -151,7 +270,7 @@ bool DBDArchive::loadAnimation(uint num, Animation &out) {
out.resize(frameCount);
for (uint16 i = 0; i < frameCount; i++) {
- if (!readFrame(_dbd, entry.compressed != 0, out[i])) {
+ if (!readFrame(_dbd, entry.compressed != 0, _bigEndian, out[i])) {
warning("DBDArchive::loadAnimation: frame %u/%u failed in entry %u",
i, frameCount, num);
return false;
diff --git a/engines/eem/resource.h b/engines/eem/resource.h
index 2e19340ff4a..735ed30797b 100644
--- a/engines/eem/resource.h
+++ b/engines/eem/resource.h
@@ -43,7 +43,7 @@ struct Picture {
uint16 flags = 0; ///< +0 high byte: sub-mode
uint16 rowoff = 0; ///< +6 row offset (clipped sprites)
uint16 miscflags = 0; ///< +8 high byte: transparent-mask flag
- uint16 compsize = 0; ///< +10 packed payload size on disk
+ uint32 compsize = 0; ///< Packed payload size on disk.
Graphics::ManagedSurface surface;
};
@@ -57,7 +57,7 @@ public:
~DBDArchive();
/// Open both halves; returns false if either file is missing/malformed.
- bool open(const Common::Path &dbdName, const Common::Path &dbxName);
+ bool open(const Common::Path &dbdName, const Common::Path &dbxName, bool bigEndian = false);
void close();
uint32 size() const { return _index.size(); }
@@ -76,6 +76,7 @@ public:
private:
Common::File _dbd;
Common::Array<DBEntry> _index;
+ bool _bigEndian = false;
};
} // End of namespace EEM
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index 88e6c6e919e..b06d208d0fe 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -1584,10 +1584,10 @@ void SiteScreen::renderBackground(uint siteNum) {
haveScene = _vm->getPics().getPicture(sitepic + 1, scene);
if (haveScene) {
// `_Rect_Move(0, 0, h, ..., 0x42, 0x14, 48000, h, w)`.
- const int x = 0x42;
- const int y = 0x14;
- const int w = MIN<int>(scene.surface.w, kScreenWidth - x);
- const int h = MIN<int>(scene.surface.h, kScreenHeight - y);
+ const int x = _vm->scaleX(0x42);
+ const int y = _vm->scaleY(0x14);
+ const int w = MIN<int>(scene.surface.w, _vm->screenWidth() - x);
+ const int h = MIN<int>(scene.surface.h, _vm->screenHeight() - y);
if (w > 0 && h > 0)
g_system->copyRectToScreen(scene.surface.getPixels(),
scene.surface.pitch, x, y, w, h);
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index b0e9bccebef..d0bca0e0e6a 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -204,8 +204,8 @@ void swapColors(Graphics::ManagedSurface &dst,
const Common::Rect &r, byte from, byte to) {
const int x1 = MAX<int>(0, r.left);
const int y1 = MAX<int>(0, r.top);
- const int x2 = MIN<int>(kScreenWidth, r.right);
- const int y2 = MIN<int>(kScreenHeight, r.bottom);
+ const int x2 = MIN<int>(dst.w, r.right);
+ const int y2 = MIN<int>(dst.h, r.bottom);
for (int y = y1; y < y2; y++) {
byte *row = (byte *)dst.getBasePtr(0, y);
for (int x = x1; x < x2; x++) {
@@ -419,7 +419,7 @@ int nextLiveSlot(const Common::Array<Common::Rect> &slotRects,
void copyToScreen(Graphics::ManagedSurface &scratch) {
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, scratch.w, scratch.h);
g_system->updateScreen();
}
@@ -483,21 +483,25 @@ void drawCaseBookTitle(Graphics::ManagedSurface &scratch, const EEMEngine *vm,
: Common::String::format(spanish ? "Lib. %u" : "Book %u", book);
const int titleW = vm->getFont().getStringWidth(title);
const int titleX = (0xba - titleW) / 2 + 0x3c;
- vm->getFont().drawString(&scratch, title, titleX, 12, kScreenWidth, 0xF);
+ vm->getFont().drawString(&scratch, title, vm->scaleX(titleX),
+ vm->scaleY(12), vm->scaleX(kScreenWidth), 0xF);
}
void drawNameEntryFrame(EEMEngine *vm, const Picture *bg, bool haveBG,
const Picture *peek, const Common::String &name,
const char *prompt) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(vm->screenWidth(), vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (haveBG)
scratch.simpleBlitFrom(bg->surface);
if (peek)
- blitMaskedPic(scratch, *peek, kNameEntryPeekX, kNameEntryPeekY);
- vm->getFont().drawString(&scratch, prompt, 80, 40, 240, 0xF);
- vm->getFont().drawString(&scratch, name + "_", 80, 80, 240, 0xF);
+ blitMaskedPic(scratch, *peek, vm->scaleX(kNameEntryPeekX),
+ vm->scaleY(kNameEntryPeekY));
+ vm->getFont().drawString(&scratch, prompt, vm->scaleX(80),
+ vm->scaleY(40), vm->scaleX(240), 0xF);
+ vm->getFont().drawString(&scratch, name + "_", vm->scaleX(80),
+ vm->scaleY(80), vm->scaleX(240), 0xF);
copyToScreen(scratch);
}
@@ -509,13 +513,14 @@ bool animateNameEntryPeek(EEMEngine *vm, const Picture *bg, bool haveBG,
for (int w = 1; w <= peek->surface.w; w++) {
if (pumpQuitEvents(vm))
return true;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(vm->screenWidth(), vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (haveBG)
scratch.simpleBlitFrom(bg->surface);
blitMaskedPicRightReveal(scratch, *peek,
- kNameEntryPeekX, kNameEntryPeekY, w);
+ vm->scaleX(kNameEntryPeekX),
+ vm->scaleY(kNameEntryPeekY), w);
copyToScreen(scratch);
g_system->delayMillis(10);
}
@@ -530,14 +535,14 @@ bool animateProfilePickerReveal(EEMEngine *vm, const Picture *bg,
for (int h = 1; h <= reveal->surface.h; h++) {
if (pumpQuitEvents(vm))
return true;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(vm->screenWidth(), vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (haveBG)
scratch.simpleBlitFrom(bg->surface);
blitMaskedPicBottomReveal(scratch, *reveal,
- kProfilePickerRevealX,
- kProfilePickerRevealY, h);
+ vm->scaleX(kProfilePickerRevealX),
+ vm->scaleY(kProfilePickerRevealY), h);
copyToScreen(scratch);
g_system->delayMillis(10);
}
@@ -577,14 +582,15 @@ void clampProfileScroll(int &selected, int &start, int count) {
}
void drawProfilePickerFrame(const ProfilePickerView &v) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(v.vm->screenWidth(), v.vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (v.haveBG)
scratch.simpleBlitFrom(v.bg->surface);
if (v.haveReveal)
blitMaskedPic(scratch, *v.reveal,
- kProfilePickerRevealX, kProfilePickerRevealY);
+ v.vm->scaleX(kProfilePickerRevealX),
+ v.vm->scaleY(kProfilePickerRevealY));
const int count = v.entries ? (int)v.entries->size() : 0;
for (int row = 0; row < kProfileVisibleRows; row++) {
@@ -593,9 +599,9 @@ void drawProfilePickerFrame(const ProfilePickerView &v) {
break;
const byte color = idx == v.selected ? 0xF : 0x8;
v.vm->getFont().drawString(&scratch, (*v.entries)[idx].label,
- kProfileListX,
- kProfileListY + row * kProfileLineH,
- kProfileListW, color);
+ v.vm->scaleX(kProfileListX),
+ v.vm->scaleY(kProfileListY + row * kProfileLineH),
+ v.vm->scaleX(kProfileListW), color);
}
copyToScreen(scratch);
}
@@ -687,9 +693,11 @@ void drawCaseBase(Graphics::ManagedSurface &scratch, EEMEngine *vm,
scratch.simpleBlitFrom(caseBg->surface);
if (haveRevealPic && revealPic)
blitMaskedPic(scratch, *revealPic,
- kCaseSelectionRevealX, kCaseSelectionRevealY);
+ vm->scaleX(kCaseSelectionRevealX),
+ vm->scaleY(kCaseSelectionRevealY));
drawCaseBookTitle(scratch, vm, book);
- drawCaseGreeter(scratch, kdAnim, haveKdAnim, kdAnimX, kdAnimY);
+ drawCaseGreeter(scratch, kdAnim, haveKdAnim,
+ vm->scaleX(kdAnimX), vm->scaleY(kdAnimY));
}
bool animateCaseSelectionReveal(EEMEngine *vm, const Picture *caseBg,
@@ -705,22 +713,23 @@ bool animateCaseSelectionReveal(EEMEngine *vm, const Picture *caseBg,
for (int i = 1; i <= steps; i++) {
if (pumpQuitEvents(vm))
return true;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(vm->screenWidth(), vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (haveCaseBg && caseBg)
scratch.simpleBlitFrom(caseBg->surface);
if (vm && vm->isFloppy()) {
blitMaskedPicRightReveal(scratch, *revealPic,
- kCaseSelectionRevealX,
- kCaseSelectionRevealY, i);
+ vm->scaleX(kCaseSelectionRevealX),
+ vm->scaleY(kCaseSelectionRevealY), i);
} else {
blitMaskedPicBottomReveal(scratch, *revealPic,
- kCaseSelectionRevealX,
- kCaseSelectionRevealY, i);
+ vm->scaleX(kCaseSelectionRevealX),
+ vm->scaleY(kCaseSelectionRevealY), i);
}
drawCaseBookTitle(scratch, vm, book);
- drawCaseGreeter(scratch, kdAnim, haveKdAnim, kdAnimX, kdAnimY);
+ drawCaseGreeter(scratch, kdAnim, haveKdAnim,
+ vm->scaleX(kdAnimX), vm->scaleY(kdAnimY));
copyToScreen(scratch);
g_system->delayMillis(8);
}
@@ -729,7 +738,7 @@ bool animateCaseSelectionReveal(EEMEngine *vm, const Picture *caseBg,
// `DrawList @ 1c33:040d`
void drawCaseSubmenu(const CaseSubmenuView &v) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(v.vm->screenWidth(), v.vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
drawCaseBase(scratch, v.vm, v.caseBg, v.haveCaseBg,
v.revealPic, v.haveRevealPic,
@@ -759,14 +768,16 @@ void drawCaseSubmenu(const CaseSubmenuView &v) {
color = 0x7; // normal
}
v.vm->getFont().drawString(&scratch, name,
- kListX, kListY0 + r * kLineH, kListW, color);
+ v.vm->scaleX(kListX), v.vm->scaleY(kListY0 + r * kLineH),
+ v.vm->scaleX(kListW), color);
}
// Selection arrow.
if (v.selRow >= v.topRow && v.selRow < v.topRow + (uint)kVisible) {
const int r = (int)(v.selRow - v.topRow);
v.vm->getFont().drawString(&scratch, ">",
- kListX - 6, kListY0 + r * kLineH, 6, 0xF);
+ v.vm->scaleX(kListX - 6), v.vm->scaleY(kListY0 + r * kLineH),
+ v.vm->scaleX(6), 0xF);
}
// Scrollbar thumb at (240, 45..146), proportional to scroll position.
@@ -779,22 +790,23 @@ void drawCaseSubmenu(const CaseSubmenuView &v) {
MAX<int>(1, (int)count - kVisible);
const Common::Rect thumb(240, trackY0 + pos,
250, trackY0 + pos + thumbH);
- scratch.fillRect(thumb, 0x8);
- scratch.frameRect(thumb, 0xF);
+ scratch.fillRect(v.vm->scaleRect(thumb), 0x8);
+ scratch.frameRect(v.vm->scaleRect(thumb), 0xF);
}
copyToScreen(scratch);
}
void drawActionMenuFrame(const ActionMenuView &v) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(v.vm->screenWidth(), v.vm->screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (v.haveBg && v.bg)
scratch.simpleBlitFrom(v.bg->surface);
if (v.haveDecor && v.decor)
blitMaskedPic(scratch, *v.decor,
- kActionScreenDecorX, kActionScreenDecorY);
+ v.vm->scaleX(kActionScreenDecorX),
+ v.vm->scaleY(kActionScreenDecorY));
if (v.vm->getFont().isLoaded()) {
// `DrawList @ 1c33:040d`. _TextBox @ 29be:0d00 = {58, 35, 238, 158}.
@@ -810,7 +822,8 @@ void drawActionMenuFrame(const ActionMenuView &v) {
const int y = kListY0 + r * kLineH;
if ((r & 1) == 0) {
v.vm->getFont().drawString(&scratch, v.separator,
- kListX, y, kListW, 0x7);
+ v.vm->scaleX(kListX), v.vm->scaleY(y),
+ v.vm->scaleX(kListW), 0x7);
continue;
}
const uint mp = (uint)(r >> 1);
@@ -818,7 +831,8 @@ void drawActionMenuFrame(const ActionMenuView &v) {
const byte color = isSel ? 0xF :
v.pickEnabled[mp] ? 0x7 : 0x8;
v.vm->getFont().drawString(&scratch, v.pickLabel[mp],
- kListX, y, kListW, color);
+ v.vm->scaleX(kListX), v.vm->scaleY(y),
+ v.vm->scaleX(kListW), color);
}
}
copyToScreen(scratch);
@@ -926,17 +940,19 @@ void EEMEngine::doProfilePicker() {
}
}
if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (kChooserOkRect.contains(ev.mouse.x, ev.mouse.y)) {
+ const Common::Point mouse(unscaleX(ev.mouse.x),
+ unscaleY(ev.mouse.y));
+ if (kChooserOkRect.contains(mouse.x, mouse.y)) {
committed = true;
break;
}
- if (kChooserExitRect.contains(ev.mouse.x, ev.mouse.y) ||
- kChooserNewPlayerRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserExitRect.contains(mouse.x, mouse.y) ||
+ kChooserNewPlayerRect.contains(mouse.x, mouse.y)) {
createNew = true;
committed = true;
break;
}
- if (kChooserUpArrowRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserUpArrowRect.contains(mouse.x, mouse.y)) {
if (start > 0) {
start--;
if (sel >= start + kProfileVisibleRows)
@@ -946,7 +962,7 @@ void EEMEngine::doProfilePicker() {
}
break;
}
- if (kChooserDnArrowRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserDnArrowRect.contains(mouse.x, mouse.y)) {
const int maxStart = MAX<int>(0,
(int)entries.size() - kProfileVisibleRows);
if (start < maxStart) {
@@ -958,11 +974,11 @@ void EEMEngine::doProfilePicker() {
}
break;
}
- if (kChooserHelpRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserHelpRect.contains(mouse.x, mouse.y)) {
break;
}
- if (kChooserListRect.contains(ev.mouse.x, ev.mouse.y)) {
- const int hit = (ev.mouse.y - kProfileListY) /
+ if (kChooserListRect.contains(mouse.x, mouse.y)) {
+ const int hit = (mouse.y - kProfileListY) /
kProfileLineH;
const int idx = start + hit;
if (hit >= 0 && hit < kProfileVisibleRows &&
@@ -1463,8 +1479,10 @@ void EEMEngine::doSetup() {
}
if (ev.type != Common::EVENT_LBUTTONDOWN)
continue;
- const int mx = ev.mouse.x;
- const int my = ev.mouse.y;
+ const Common::Point mouse(unscaleX(ev.mouse.x),
+ unscaleY(ev.mouse.y));
+ const int mx = mouse.x;
+ const int my = mouse.y;
if (kPartnerBtn.contains(mx, my)) {
_partner = _partner == kPartnerJake ? kPartnerJenny : kPartnerJake;
@@ -1608,7 +1626,7 @@ void EEMEngine::doSetup() {
}
void EEMEngine::setupDrawScreen() {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(screenWidth(), screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
Picture bg;
@@ -1618,17 +1636,17 @@ void EEMEngine::setupDrawScreen() {
const byte kKey = 0xFE;
const byte kBright = 0x15;
const byte kDim = 0x00;
- swapColors(scratch, kSetupKid1Rect, kKey,
+ swapColors(scratch, scaleRect(kSetupKid1Rect), kKey,
_partner == kPartnerJake ? kBright : kDim);
- swapColors(scratch, kSetupKid2Rect, kKey,
+ swapColors(scratch, scaleRect(kSetupKid2Rect), kKey,
_partner == kPartnerJenny ? kBright : kDim);
- swapColors(scratch, kSetupSoundOnRect, kKey,
+ swapColors(scratch, scaleRect(kSetupSoundOnRect), kKey,
_voiceOn ? kBright : kDim);
- swapColors(scratch, kSetupSoundOffRect, kKey,
+ swapColors(scratch, scaleRect(kSetupSoundOffRect), kKey,
_voiceOn ? kDim : kBright);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, scratch.w, scratch.h);
g_system->updateScreen();
}
@@ -1638,7 +1656,7 @@ Common::KeyCode EEMEngine::setupShowFullscreenPic(uint16 picId, bool transparent
warning("doSetup: PIC %u missing", (uint)picId);
return Common::KEYCODE_INVALID;
}
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(screenWidth(), screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
if (transparent) {
Graphics::Surface *cur = g_system->lockScreen();
@@ -1654,7 +1672,7 @@ Common::KeyCode EEMEngine::setupShowFullscreenPic(uint16 picId, bool transparent
scratch.simpleBlitFrom(pic.surface);
}
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, scratch.w, scratch.h);
g_system->updateScreen();
while (!shouldQuit()) {
Common::Event ev;
@@ -1979,22 +1997,24 @@ void EEMEngine::doActionScreen() {
ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
return;
if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (kOkRect.contains(ev.mouse.x, ev.mouse.y)) {
+ const Common::Point mouse(unscaleX(ev.mouse.x),
+ unscaleY(ev.mouse.y));
+ if (kOkRect.contains(mouse.x, mouse.y)) {
if (kPickEnabled[pick])
confirmed = true;
break;
}
- if (kExitRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kExitRect.contains(mouse.x, mouse.y)) {
exitChosen = true;
confirmed = true;
break;
}
- if (kHelpRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kHelpRect.contains(mouse.x, mouse.y)) {
continue;
}
- if (kListRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kListRect.contains(mouse.x, mouse.y)) {
const int kLineH = 10;
- const int row = (ev.mouse.y - kListRect.top) / kLineH;
+ const int row = (mouse.y - kListRect.top) / kLineH;
if ((row & 1) == 1) {
const uint mp = (uint)(row >> 1);
if (mp < numPicks && kPickEnabled[mp]) {
@@ -2192,37 +2212,39 @@ void EEMEngine::doCaseSelection() {
ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
return;
if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (kChooserOkRect.contains(ev.mouse.x, ev.mouse.y)) {
+ const Common::Point mouse(unscaleX(ev.mouse.x),
+ unscaleY(ev.mouse.y));
+ if (kChooserOkRect.contains(mouse.x, mouse.y)) {
if (selRow < listLen && !solvedFlags[selRow])
confirmed = true;
break;
}
- if (kChooserExitRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserExitRect.contains(mouse.x, mouse.y)) {
_mystery.clear();
return;
}
- if (kChooserHelpRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserHelpRect.contains(mouse.x, mouse.y)) {
saveProfile(_playerName);
_mystery.clear();
_nextScreen = kScreenProfile;
return;
}
- if (kChooserUpArrowRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserUpArrowRect.contains(mouse.x, mouse.y)) {
if (topRow > 0) {
topRow--;
dirty = true;
}
continue;
}
- if (kChooserDnArrowRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserDnArrowRect.contains(mouse.x, mouse.y)) {
topRow++;
clampCaseTopRow(topRow, listLen, kVisible);
dirty = true;
continue;
}
- if (kChooserListRect.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kChooserListRect.contains(mouse.x, mouse.y)) {
const int kLineH = 10;
- const int row = (ev.mouse.y - kChooserListRect.top) / kLineH;
+ const int row = (mouse.y - kChooserListRect.top) / kLineH;
if (row < 0 || row >= (int)kVisible)
continue;
const uint idx = topRow + (uint)row;
Commit: 7b230f43c4e5fcc23cea4d4d65e694afb2b6da75
https://github.com/scummvm/scummvm/commit/7b230f43c4e5fcc23cea4d4d65e694afb2b6da75
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: font support for EEM1 Mac
Changed paths:
engines/eem/eem.cpp
engines/eem/font.cpp
engines/eem/font.h
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index ce40cb5851b..5bdb49d22d1 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -24,7 +24,9 @@
#include "common/engine_data.h"
#include "common/error.h"
#include "common/events.h"
+#include "common/archive.h"
#include "common/file.h"
+#include "common/fs.h"
#include "common/path.h"
#include "common/system.h"
#include "common/textconsole.h"
@@ -56,6 +58,8 @@ const uint kPalEAKids = 0x25;
const uint kPalHighScore = 0x27;
const uint kPalStormLogo = 0x26; // Floppy FUN_23d2_0605
const uint kPicMousePointer = 0x50; // 0x51 is the wait cursor
+const uint16 kMacFontResource = 3214; // 'Eagle Eye 14' FONT resource
+const uint16 kMacSmallFontResource = 3209; // 'Eagle Eye 9' fallback
// EEM2 cursor table â `_main @ 1abf:0faf` loads seven cursor PICs into
// `_AnimationObjects`-adjacent slots and `_SwitchMouse @ 17ee:2c83` activates
@@ -322,6 +326,39 @@ void EEMEngine::applySkipRepeatedCasesOption() {
}
}
+static void addMacResourceSearchPaths() {
+ const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+ if (!gameDataDir.exists() || !gameDataDir.isDirectory())
+ return;
+
+ const Common::FSNode childRsrcDir = gameDataDir.getChild("rsrc");
+ if (childRsrcDir.exists() && childRsrcDir.isDirectory())
+ SearchMan.addDirectory("eem-mac-rsrc-child", childRsrcDir);
+
+ const Common::FSNode siblingRsrcDir =
+ gameDataDir.getParent().getChild("rsrc");
+ if (siblingRsrcDir.exists() && siblingRsrcDir.isDirectory())
+ SearchMan.addDirectory("eem-mac-rsrc-sibling", siblingRsrcDir);
+}
+
+static bool loadMacFont(EEMFont &font) {
+ addMacResourceSearchPaths();
+
+ const Common::Path appResourceFork("Eagle Eye Mysteries");
+ if (font.loadMacResource(appResourceFork, kMacFontResource, 14))
+ return true;
+ if (font.loadMacResource(appResourceFork, kMacSmallFontResource, 9))
+ return true;
+
+ const Common::Path nestedAppResourceFork("rsrc/Eagle Eye Mysteries");
+ if (font.loadMacResource(nestedAppResourceFork, kMacFontResource, 14))
+ return true;
+ if (font.loadMacResource(nestedAppResourceFork, kMacSmallFontResource, 9))
+ return true;
+
+ return false;
+}
+
Common::Error EEMEngine::run() {
initGraphics(screenWidth(), screenHeight());
@@ -342,8 +379,11 @@ Common::Error EEMEngine::run() {
if (!loadSitePalettes())
return Common::Error(Common::kReadingFailed, "SITEPALS load failed");
- // _LoadFont @ 1b66:023c.
- if (!_font.load(Common::Path("FONT.FNT")))
+ // _LoadFont @ 1b66:023c. The Mac release stores its Eagle Eye fonts in
+ // the application resource fork instead of a DOS FONT.FNT file.
+ if (isMacintosh() && !loadMacFont(_font))
+ warning("Mac FONT resource failed to load; text will not render");
+ else if (!isMacintosh() && !_font.load(Common::Path("FONT.FNT")))
warning("FONT.FNT failed to load; text will not render");
// _InitMIDI @ 20a2:013a. The demo and Mac release do not ship DOS XMIDI data.
diff --git a/engines/eem/font.cpp b/engines/eem/font.cpp
index 7a6fb6006e3..3dc862ede65 100644
--- a/engines/eem/font.cpp
+++ b/engines/eem/font.cpp
@@ -20,9 +20,13 @@
*/
#include "common/debug.h"
+#include "common/endian.h"
#include "common/file.h"
+#include "common/macresman.h"
+#include "common/memstream.h"
#include "common/textconsole.h"
+#include "graphics/fonts/macfont.h"
#include "graphics/surface.h"
#include "eem/detection.h"
@@ -60,7 +64,111 @@ inline byte mapChar(uint32 c) {
return c < 128 ? kCharToGlyph[c] : 0;
}
+uint32 readUint24BE(Common::SeekableReadStream &stream) {
+ const uint32 hi = stream.readByte();
+ const uint32 mid = stream.readByte();
+ const uint32 lo = stream.readByte();
+ return (hi << 16) | (mid << 8) | lo;
+}
+
+Common::SeekableReadStream *getRawMacResource(const Common::Path &path,
+ uint32 typeId, uint16 resourceId) {
+ Common::File f;
+ if (!f.open(path))
+ return nullptr;
+
+ const uint32 fileSize = (uint32)f.size();
+ if (fileSize < 16)
+ return nullptr;
+
+ const uint32 dataOffset = f.readUint32BE();
+ const uint32 mapOffset = f.readUint32BE();
+ const uint32 dataLength = f.readUint32BE();
+ const uint32 mapLength = f.readUint32BE();
+ if (dataOffset > fileSize || mapOffset > fileSize ||
+ dataLength > fileSize - dataOffset ||
+ mapLength > fileSize - mapOffset || mapLength < 28)
+ return nullptr;
+
+ f.seek(mapOffset + 24);
+ const uint16 typeListOffset = f.readUint16BE();
+ const uint32 typeListBase = mapOffset + typeListOffset;
+ const uint32 mapEnd = mapOffset + mapLength;
+ if (typeListBase + 2 > mapEnd)
+ return nullptr;
+
+ f.seek(typeListBase);
+ const uint16 typeCount = f.readUint16BE() + 1;
+ for (uint typeIndex = 0; typeIndex < typeCount; typeIndex++) {
+ const uint32 typeEntry = typeListBase + 2 + typeIndex * 8;
+ if (typeEntry + 8 > mapEnd)
+ return nullptr;
+
+ f.seek(typeEntry);
+ const uint32 curType = f.readUint32BE();
+ const uint16 resourceCount = f.readUint16BE() + 1;
+ const uint16 refListOffset = f.readUint16BE();
+ if (curType != typeId)
+ continue;
+
+ const uint32 refListBase = typeListBase + refListOffset;
+ if (refListBase > mapEnd)
+ return nullptr;
+
+ for (uint resIndex = 0; resIndex < resourceCount; resIndex++) {
+ const uint32 refEntry = refListBase + resIndex * 12;
+ if (refEntry + 12 > mapEnd)
+ return nullptr;
+
+ f.seek(refEntry);
+ const uint16 curId = f.readUint16BE();
+ f.skip(2); // resource name offset
+ f.skip(1); // attributes
+ const uint32 resourceDataOffset = readUint24BE(f);
+ f.skip(4); // handle
+
+ if (curId != resourceId)
+ continue;
+
+ const uint32 resourceData = dataOffset + resourceDataOffset;
+ if (resourceData + 4 > dataOffset + dataLength)
+ return nullptr;
+
+ f.seek(resourceData);
+ const uint32 resourceLength = f.readUint32BE();
+ if (resourceLength > dataOffset + dataLength - resourceData - 4)
+ return nullptr;
+
+ byte *data = (byte *)malloc(resourceLength);
+ if (!data)
+ return nullptr;
+ if (f.read(data, resourceLength) != resourceLength) {
+ free(data);
+ return nullptr;
+ }
+
+ return new Common::MemoryReadStream(data, resourceLength,
+ DisposeAfterUse::YES);
+ }
+ }
+
+ return nullptr;
+}
+
+EEMFont::~EEMFont() {
+ clear();
+}
+
+void EEMFont::clear() {
+ _glyphs.clear();
+ delete _macFont;
+ _macFont = nullptr;
+ _maxHeight = _maxWidth = _lineHeight = 0;
+}
+
bool EEMFont::load(const Common::Path &path) {
+ clear();
+
Common::File f;
if (!f.open(path)) {
warning("EEMFont::load: cannot open %s", path.toString().c_str());
@@ -107,7 +215,50 @@ bool EEMFont::load(const Common::Path &path) {
return true;
}
+bool EEMFont::loadMacResource(const Common::Path &path, uint16 resourceId,
+ int size) {
+ clear();
+
+ Common::MacResManager res;
+ Common::SeekableReadStream *stream = nullptr;
+ if (res.open(path) && res.hasResFork())
+ stream = res.getResource(MKTAG('F', 'O', 'N', 'T'), resourceId);
+ if (!stream)
+ stream = getRawMacResource(path, MKTAG('F', 'O', 'N', 'T'), resourceId);
+ if (!stream)
+ return false;
+
+ Graphics::MacFONTFont *font = new Graphics::MacFONTFont();
+ if (!font->loadFont(*stream, nullptr, size, 0)) {
+ delete stream;
+ delete font;
+ return false;
+ }
+ delete stream;
+
+ _macFont = font;
+ debugC(1, kDebugGfx, "Mac FONT %u loaded from %s: %dx%d",
+ resourceId, path.toString().c_str(), _macFont->getMaxCharWidth(),
+ _macFont->getFontHeight());
+ return true;
+}
+
+int EEMFont::getFontHeight() const {
+ if (_macFont)
+ return _macFont->getFontHeight();
+ return _lineHeight ? _lineHeight : _maxHeight;
+}
+
+int EEMFont::getMaxCharWidth() const {
+ if (_macFont)
+ return _macFont->getMaxCharWidth();
+ return _maxWidth;
+}
+
int EEMFont::getCharWidth(uint32 chr) const {
+ if (_macFont)
+ return _macFont->getCharWidth(chr);
+
const byte gi = mapChar(chr);
if (gi >= _glyphs.size())
return 0;
@@ -129,6 +280,12 @@ void EEMFont::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y,
uint32 color) const {
if (!dst)
return;
+
+ if (_macFont) {
+ _macFont->drawChar(dst, chr, x, y, color);
+ return;
+ }
+
const byte gi = mapChar(chr);
if (gi >= _glyphs.size())
return;
diff --git a/engines/eem/font.h b/engines/eem/font.h
index 8c77e97c1ea..3d82be00bab 100644
--- a/engines/eem/font.h
+++ b/engines/eem/font.h
@@ -29,6 +29,10 @@
#include "graphics/font.h"
+namespace Graphics {
+class MacFONTFont;
+}
+
namespace EEM {
/// One bitmap glyph, 1 bit per pixel, MSB first.
@@ -51,14 +55,14 @@ struct FontGlyph {
class EEMFont : public Graphics::Font {
public:
EEMFont() = default;
+ ~EEMFont() override;
bool load(const Common::Path &path);
- bool isLoaded() const { return !_glyphs.empty(); }
+ bool loadMacResource(const Common::Path &path, uint16 resourceId, int size);
+ bool isLoaded() const { return _macFont || !_glyphs.empty(); }
- int getFontHeight() const override {
- return _lineHeight ? _lineHeight : _maxHeight;
- }
- int getMaxCharWidth() const override { return _maxWidth; }
+ int getFontHeight() const override;
+ int getMaxCharWidth() const override;
int getCharWidth(uint32 chr) const override;
void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y,
uint32 color) const override;
@@ -70,7 +74,10 @@ public:
int width, const Common::String &s, uint32 color) const;
private:
+ void clear();
+
Common::Array<FontGlyph> _glyphs;
+ Graphics::MacFONTFont *_macFont = nullptr;
uint16 _maxHeight = 0;
uint16 _maxWidth = 0;
uint16 _lineHeight = 0; ///< First glyph height (original line stride)
Commit: 2685dc2d3444404d53bc2b2c46efc1a1c7a80676
https://github.com/scummvm/scummvm/commit/2685dc2d3444404d53bc2b2c46efc1a1c7a80676
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: sound support for EEM1 Mac
Changed paths:
engines/eem/audio.cpp
engines/eem/audio.h
engines/eem/clues.cpp
engines/eem/eem.cpp
engines/eem/font.cpp
engines/eem/music.cpp
engines/eem/music.h
engines/eem/resource.cpp
engines/eem/resource.h
diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index d791fc1883f..b84844d9712 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -20,6 +20,7 @@
*/
#include "audio/decoders/raw.h"
+#include "audio/decoders/mac_snd.h"
#include "audio/decoders/voc.h"
#include "common/compression/dcl.h"
@@ -34,11 +35,57 @@
#include "eem/audio.h"
#include "eem/detection.h"
#include "eem/eem.h"
+#include "eem/resource.h"
namespace EEM {
AudioPlayer::AudioPlayer(EEMEngine *vm) :
- _vm(vm), _mixer(g_system->getMixer()) {
+ _vm(vm), _mixer(g_system->getMixer()),
+ _isMacintosh(vm && vm->isMacintosh()) {
+}
+
+struct MacSndResource {
+ const char *name;
+ uint16 id;
+};
+
+const MacSndResource kMacSndResources[] = {
+ { "B-0003SL", 7022 }, { "B-0004SL", 7023 }, { "B-0006SL", 7021 },
+ { "DING", 7000 }, { "F-0013SL", 8018 }, { "F-0016SL", 8017 },
+ { "F-0061SL", 8015 }, { "F-0067SL", 8016 }, { "F-0140SL", 8020 },
+ { "F-0159SL", 8014 }, { "F-0161SL", 8013 }, { "F-0165SL", 8024 },
+ { "F-0166SL", 8011 }, { "F-0168SL", 8010 }, { "F-0170SL", 8009 },
+ { "F-0177SL", 8008 }, { "F-0181SL", 8006 }, { "F-0184SL", 8005 },
+ { "F-0187SL", 8004 }, { "F-0191SL", 8002 }, { "F-0194SL", 8001 },
+ { "M-0012SL", 7018 }, { "M-0014SL", 7017 }, { "M-0054SL", 7016 },
+ { "M-0075SL", 7015 }, { "M-0083SL", 7001 }, { "M-0085SL", 7002 },
+ { "M-0089SL", 7004 }, { "M-0091SL", 7005 }, { "M-0092SL", 7006 },
+ { "M-0096SL", 7008 }, { "M-0102SL", 7009 }, { "M-0104SL", 7010 },
+ { "M-0107SL", 7011 }, { "M-0113SL", 7013 }, { "M-0115SL", 7014 },
+ { "M-0163SL", 7024 }, { "NEWSCAN", 7003 }, { "NEWSSHRT", 7007 },
+ { "PHONESL", 7012 }, { "SQUAK2SL", 7019 }, { "THUNDER", 7025 },
+};
+
+Common::String macSndNameFromPath(const Common::Path &path) {
+ Common::String name = path.baseName();
+ const size_t dot = name.findLastOf('.');
+ if (dot != Common::String::npos)
+ name = name.substr(0, dot);
+ name.toUppercase();
+
+ if (name == "PHONE")
+ name = "PHONESL";
+
+ return name;
+}
+
+uint16 macSndResourceIdForPath(const Common::Path &path) {
+ const Common::String name = macSndNameFromPath(path);
+ for (uint i = 0; i < ARRAYSIZE(kMacSndResources); i++) {
+ if (name.equalsIgnoreCase(kMacSndResources[i].name))
+ return kMacSndResources[i].id;
+ }
+ return 0;
}
AudioPlayer::~AudioPlayer() {
@@ -59,6 +106,17 @@ void AudioPlayer::playVoc(const Common::Path &vocPath) {
}
stopVoice();
+ if (_isMacintosh) {
+ const uint16 resourceId = macSndResourceIdForPath(vocPath);
+ if (resourceId == 0) {
+ warning("AudioPlayer: Mac snd resource for %s missing",
+ vocPath.toString().c_str());
+ return;
+ }
+ playMacSnd(resourceId, _voiceHandle, Audio::Mixer::kSpeechSoundType);
+ return;
+ }
+
Common::File *f = new Common::File();
if (!f->open(vocPath)) {
warning("AudioPlayer: %s missing", vocPath.toString().c_str());
@@ -144,6 +202,13 @@ bool AudioPlayer::initMysterySounds(uint mysteryNum) {
cleanMysterySounds();
+ if (_isMacintosh) {
+ debugC(2, kDebugSound,
+ "AudioPlayer: Mac release has no SDB/SDX bundle for mystery %u",
+ mysteryNum);
+ return true;
+ }
+
if (!readSdxIndex(sdxPath)) {
_sdxIndex.clear();
return false;
@@ -162,6 +227,30 @@ void AudioPlayer::cleanMysterySounds() {
_currentMystery = -1;
}
+void AudioPlayer::playMacSnd(uint16 resourceId, Audio::SoundHandle &handle,
+ Audio::Mixer::SoundType type) {
+ Common::SeekableReadStream *stream =
+ openMacResource(Common::Path("EEM Sound&Music"),
+ MKTAG('s', 'n', 'd', ' '), resourceId);
+ if (!stream) {
+ warning("AudioPlayer: Mac snd resource %u missing", resourceId);
+ return;
+ }
+
+ Audio::SeekableAudioStream *audioStream =
+ Audio::makeMacSndStream(stream, DisposeAfterUse::YES);
+ if (!audioStream) {
+ delete stream;
+ warning("AudioPlayer: Mac snd resource %u is not playable", resourceId);
+ return;
+ }
+
+ _mixer->playStream(type, &handle, audioStream, -1,
+ Audio::Mixer::kMaxChannelVolume, 0,
+ DisposeAfterUse::YES);
+ debugC(1, kDebugSound, "AudioPlayer: playMacSnd(%u)", resourceId);
+}
+
// pcm must be allocated with malloc(): Audio::makeRawStream takes ownership
// and frees it via free() on stream destruction (NOT delete/delete[]).
void AudioPlayer::playPcmBuffer(byte *pcm, uint32 size, uint sampleRate,
diff --git a/engines/eem/audio.h b/engines/eem/audio.h
index fa901b971a8..dc6298190c8 100644
--- a/engines/eem/audio.h
+++ b/engines/eem/audio.h
@@ -128,6 +128,8 @@ private:
};
bool readSdxIndex(const Common::Path &sdxPath);
+ void playMacSnd(uint16 resourceId, Audio::SoundHandle &handle,
+ Audio::Mixer::SoundType type);
void playPcmBuffer(byte *pcm, uint32 size, uint sampleRate,
Audio::SoundHandle &handle,
Audio::Mixer::SoundType type);
@@ -141,6 +143,7 @@ private:
Common::Path _sdbPath;
int _currentMystery = -1;
bool _voiceEnabled = true;
+ bool _isMacintosh = false;
};
} // End of namespace EEM
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index 6f79aeb5f81..81dd1e2a326 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -333,8 +333,8 @@ void EEMEngine::doChoosePartner() {
g_system->delayMillis(20);
}
- if (_audio && !isDemo() && !isMacintosh()) {
- if (isFloppy()) {
+ if (_audio && !isDemo()) {
+ if (isFloppy() || isMacintosh()) {
// Floppy _DoChoosePartner_Floppy @ 19bb:0a8e
_audio->playFloppyVoiceSlot(0x14, _partner);
} else {
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 5bdb49d22d1..cfe99d67bec 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -386,9 +386,10 @@ Common::Error EEMEngine::run() {
else if (!isMacintosh() && !_font.load(Common::Path("FONT.FNT")))
warning("FONT.FNT failed to load; text will not render");
- // _InitMIDI @ 20a2:013a. The demo and Mac release do not ship DOS XMIDI data.
- if (!isDemo() && !isMacintosh())
- _music = new MusicPlayer(isFloppy());
+ // _InitMIDI @ 20a2:013a. The demo ships no music. The Mac release stores
+ // SMF MIDI resources in EEM Sound&Music instead of loose DOS XMIDI files.
+ if (!isDemo())
+ _music = new MusicPlayer(isFloppy(), isMacintosh());
// _InitDrivers @ 1ff1:0368 (SBDIG.ADV / PASDIG.ADV).
_audio = new AudioPlayer(this);
diff --git a/engines/eem/font.cpp b/engines/eem/font.cpp
index 3dc862ede65..a426c4961ae 100644
--- a/engines/eem/font.cpp
+++ b/engines/eem/font.cpp
@@ -22,8 +22,6 @@
#include "common/debug.h"
#include "common/endian.h"
#include "common/file.h"
-#include "common/macresman.h"
-#include "common/memstream.h"
#include "common/textconsole.h"
#include "graphics/fonts/macfont.h"
@@ -31,6 +29,7 @@
#include "eem/detection.h"
#include "eem/font.h"
+#include "eem/resource.h"
namespace EEM {
@@ -64,97 +63,6 @@ inline byte mapChar(uint32 c) {
return c < 128 ? kCharToGlyph[c] : 0;
}
-uint32 readUint24BE(Common::SeekableReadStream &stream) {
- const uint32 hi = stream.readByte();
- const uint32 mid = stream.readByte();
- const uint32 lo = stream.readByte();
- return (hi << 16) | (mid << 8) | lo;
-}
-
-Common::SeekableReadStream *getRawMacResource(const Common::Path &path,
- uint32 typeId, uint16 resourceId) {
- Common::File f;
- if (!f.open(path))
- return nullptr;
-
- const uint32 fileSize = (uint32)f.size();
- if (fileSize < 16)
- return nullptr;
-
- const uint32 dataOffset = f.readUint32BE();
- const uint32 mapOffset = f.readUint32BE();
- const uint32 dataLength = f.readUint32BE();
- const uint32 mapLength = f.readUint32BE();
- if (dataOffset > fileSize || mapOffset > fileSize ||
- dataLength > fileSize - dataOffset ||
- mapLength > fileSize - mapOffset || mapLength < 28)
- return nullptr;
-
- f.seek(mapOffset + 24);
- const uint16 typeListOffset = f.readUint16BE();
- const uint32 typeListBase = mapOffset + typeListOffset;
- const uint32 mapEnd = mapOffset + mapLength;
- if (typeListBase + 2 > mapEnd)
- return nullptr;
-
- f.seek(typeListBase);
- const uint16 typeCount = f.readUint16BE() + 1;
- for (uint typeIndex = 0; typeIndex < typeCount; typeIndex++) {
- const uint32 typeEntry = typeListBase + 2 + typeIndex * 8;
- if (typeEntry + 8 > mapEnd)
- return nullptr;
-
- f.seek(typeEntry);
- const uint32 curType = f.readUint32BE();
- const uint16 resourceCount = f.readUint16BE() + 1;
- const uint16 refListOffset = f.readUint16BE();
- if (curType != typeId)
- continue;
-
- const uint32 refListBase = typeListBase + refListOffset;
- if (refListBase > mapEnd)
- return nullptr;
-
- for (uint resIndex = 0; resIndex < resourceCount; resIndex++) {
- const uint32 refEntry = refListBase + resIndex * 12;
- if (refEntry + 12 > mapEnd)
- return nullptr;
-
- f.seek(refEntry);
- const uint16 curId = f.readUint16BE();
- f.skip(2); // resource name offset
- f.skip(1); // attributes
- const uint32 resourceDataOffset = readUint24BE(f);
- f.skip(4); // handle
-
- if (curId != resourceId)
- continue;
-
- const uint32 resourceData = dataOffset + resourceDataOffset;
- if (resourceData + 4 > dataOffset + dataLength)
- return nullptr;
-
- f.seek(resourceData);
- const uint32 resourceLength = f.readUint32BE();
- if (resourceLength > dataOffset + dataLength - resourceData - 4)
- return nullptr;
-
- byte *data = (byte *)malloc(resourceLength);
- if (!data)
- return nullptr;
- if (f.read(data, resourceLength) != resourceLength) {
- free(data);
- return nullptr;
- }
-
- return new Common::MemoryReadStream(data, resourceLength,
- DisposeAfterUse::YES);
- }
- }
-
- return nullptr;
-}
-
EEMFont::~EEMFont() {
clear();
}
@@ -219,12 +127,8 @@ bool EEMFont::loadMacResource(const Common::Path &path, uint16 resourceId,
int size) {
clear();
- Common::MacResManager res;
- Common::SeekableReadStream *stream = nullptr;
- if (res.open(path) && res.hasResFork())
- stream = res.getResource(MKTAG('F', 'O', 'N', 'T'), resourceId);
- if (!stream)
- stream = getRawMacResource(path, MKTAG('F', 'O', 'N', 'T'), resourceId);
+ Common::SeekableReadStream *stream =
+ openMacResource(path, MKTAG('F', 'O', 'N', 'T'), resourceId);
if (!stream)
return false;
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index 80f20aa9bda..fe5a5e5e0b0 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -24,17 +24,65 @@
#include "common/config-manager.h"
#include "common/debug.h"
+#include "common/endian.h"
#include "common/file.h"
+#include "common/stream.h"
#include "common/textconsole.h"
#include "eem/detection.h"
#include "eem/music.h"
+#include "eem/resource.h"
namespace EEM {
const int kMidiDriverFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
+const uint16 kInvalidMacMidiResource = 0xffff;
-MusicPlayer::MusicPlayer(bool isFloppy) : _isFloppy(isFloppy) {
+Common::String musicNameFromPath(const Common::Path &path) {
+ Common::String name = path.baseName();
+ const size_t dot = name.findLastOf('.');
+ if (dot != Common::String::npos)
+ name = name.substr(0, dot);
+ name.toUppercase();
+ return name;
+}
+
+uint16 macMidiResourceIdForFile(const Common::Path &path) {
+ const Common::String name = musicNameFromPath(path);
+ if (name == "THEME" || name == "THEME SONG")
+ return 0;
+ if (name == "WIN1" || name == "FANFARE2")
+ return 1;
+ if (name == "TRAVEL-1")
+ return 2;
+ if (name == "TRAVEL-4")
+ return 3;
+ if (name == "TRAVEL-6")
+ return 4;
+ if (name == "TRAVEL-7")
+ return 5;
+ if (name == "TRAVEL-8")
+ return 6;
+ if (name == "WRONG2")
+ return 7;
+ return kInvalidMacMidiResource;
+}
+
+uint16 macMidiResourceIdForMus(uint num) {
+ static const uint16 kTravelTracks[5] = {
+ 4, 3, 5, 2, 6 // Travel-6, Travel-4, Travel-7, Travel-1, Travel-8
+ };
+ if (num < ARRAYSIZE(kTravelTracks))
+ return kTravelTracks[num];
+ if (num == 5)
+ return 1; // WIN1.MID
+ if (num == 6)
+ return 7; // WRONG2.MID
+ return kInvalidMacMidiResource;
+}
+
+MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
+ _isFloppy(isFloppy), _isMacintosh(isMacintosh) {
// _InitMIDI @ 20a2:013a â `_AIL_register_driver` against
// ADLIB.ADV / SBFM.ADV / MT32MPU.ADV. We honour the launcher's
// "Music driver" setting and prefer MT-32 when unset.
@@ -44,25 +92,30 @@ MusicPlayer::MusicPlayer(bool isFloppy) : _isFloppy(isFloppy) {
if (musicType == MT_GM && ConfMan.getBool("native_mt32"))
musicType = MT_MT32;
- switch (musicType) {
- case MT_ADLIB:
- // _MIDIPlayFile @ 20a2:024c opens SAMPLE.AD (string at 29be:14d6)
- // and installs every patch the sequence requests via
- // `_AIL_install_timbre`.
- _milesAudioMode = true;
- _driver = Audio::MidiDriver_Miles_AdLib_create(
- Common::Path("SAMPLE.AD"), Common::Path());
- break;
- case MT_MT32:
- // MT32MPU.ADV in the original. No Miles MT-32 bank ships with
- // EEM, so use the standard MT-32 driver.
- _milesAudioMode = true;
- _driver = Audio::MidiDriver_Miles_MT32_create(Common::Path());
- break;
- default:
+ if (_isMacintosh) {
_milesAudioMode = false;
createDriver(kMidiDriverFlags);
- break;
+ } else {
+ switch (musicType) {
+ case MT_ADLIB:
+ // _MIDIPlayFile @ 20a2:024c opens SAMPLE.AD (string at 29be:14d6)
+ // and installs every patch the sequence requests via
+ // `_AIL_install_timbre`.
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_AdLib_create(
+ Common::Path("SAMPLE.AD"), Common::Path());
+ break;
+ case MT_MT32:
+ // MT32MPU.ADV in the original. No Miles MT-32 bank ships with
+ // EEM, so use the standard MT-32 driver.
+ _milesAudioMode = true;
+ _driver = Audio::MidiDriver_Miles_MT32_create(Common::Path());
+ break;
+ default:
+ _milesAudioMode = false;
+ createDriver(kMidiDriverFlags);
+ break;
+ }
}
if (_driver) {
@@ -73,7 +126,7 @@ MusicPlayer::MusicPlayer(bool isFloppy) : _isFloppy(isFloppy) {
_driver = nullptr;
} else {
// Miles AdLib handles its own reset.
- if (musicType != MT_ADLIB) {
+ if (_isMacintosh || musicType != MT_ADLIB) {
if (musicType == MT_MT32 || _nativeMT32)
_driver->sendMT32Reset();
else
@@ -97,6 +150,66 @@ void MusicPlayer::send(uint32 b) {
Audio::MidiPlayer::send(b);
}
+void MusicPlayer::startLoadedMusic(const Common::String &name, bool loop,
+ bool smf) {
+ _parser = smf ? MidiParser::createParser_SMF()
+ : MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
+ _parser->setMidiDriver(this);
+ _parser->setTimerRate(_driver->getBaseTempo());
+ _parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+ _parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
+
+ if (!_parser->loadMusic(_xmiData.data(), _xmiData.size())) {
+ warning("MusicPlayer: %s parser rejected %s",
+ smf ? "SMF" : "XMIDI", name.c_str());
+ delete _parser;
+ _parser = nullptr;
+ _xmiData.clear();
+ return;
+ }
+
+ _isLooping = loop;
+ _parser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
+ _parser->setTrack(0);
+
+ syncVolume();
+ _isPlaying = true;
+ debugC(1, kDebugSound,
+ "MusicPlayer: playing %s (%u bytes, loop=%d, miles=%d, smf=%d)",
+ name.c_str(), _xmiData.size(), loop, _milesAudioMode, smf);
+}
+
+void MusicPlayer::playMacMidiResource(uint16 resourceId, bool loop) {
+ if (resourceId == kInvalidMacMidiResource)
+ return;
+
+ Common::SeekableReadStream *stream =
+ openMacResource(Common::Path("EEM Sound&Music"),
+ MKTAG('M', 'i', 'd', 'i'), resourceId);
+ if (!stream) {
+ warning("MusicPlayer: Mac Midi resource %u missing", resourceId);
+ return;
+ }
+
+ const uint32 size = (uint32)stream->size();
+ if (size == 0) {
+ delete stream;
+ warning("MusicPlayer: Mac Midi resource %u is empty", resourceId);
+ return;
+ }
+ _xmiData.resize(size);
+ if (stream->read(_xmiData.data(), size) != size) {
+ delete stream;
+ _xmiData.clear();
+ warning("MusicPlayer: short read on Mac Midi resource %u", resourceId);
+ return;
+ }
+ delete stream;
+
+ startLoadedMusic(Common::String::format("Mac Midi %u", resourceId), loop,
+ /* smf= */ true);
+}
+
void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
if (!_driver)
return;
@@ -104,6 +217,16 @@ void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
Common::StackLock lock(_mutex);
stop();
+ if (_isMacintosh) {
+ const uint16 resourceId = macMidiResourceIdForFile(xmiPath);
+ if (resourceId == kInvalidMacMidiResource) {
+ warning("MusicPlayer: no Mac Midi mapping for %s",
+ xmiPath.toString().c_str());
+ return;
+ }
+ playMacMidiResource(resourceId, loop);
+ return;
+ }
Common::File f;
if (!f.open(xmiPath)) {
@@ -123,32 +246,17 @@ void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
return;
}
- _parser = MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
- _parser->setMidiDriver(this);
- _parser->setTimerRate(_driver->getBaseTempo());
- _parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
- _parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
+ startLoadedMusic(xmiPath.toString(), loop, /* smf= */ false);
+}
- if (!_parser->loadMusic(_xmiData.data(), _xmiData.size())) {
- warning("MusicPlayer: XMIDI parser rejected %s",
- xmiPath.toString().c_str());
- delete _parser;
- _parser = nullptr;
- _xmiData.clear();
+void MusicPlayer::playMus(uint num, bool loop) {
+ if (_isMacintosh) {
+ Common::StackLock lock(_mutex);
+ stop();
+ playMacMidiResource(macMidiResourceIdForMus(num), loop);
return;
}
- _isLooping = loop;
- _parser->property(MidiParser::mpAutoLoop, loop ? 1 : 0);
- _parser->setTrack(0);
-
- syncVolume();
- _isPlaying = true;
- debugC(1, kDebugSound, "MusicPlayer: playing %s (%u bytes, loop=%d, miles=%d)",
- xmiPath.toString().c_str(), size, loop, _milesAudioMode);
-}
-
-void MusicPlayer::playMus(uint num, bool loop) {
// CD format string "mus%05d.xmi" at 29be:1525. Floppy maps the same
// numeric slots to different filenames:
// 0..4 â travel music. Table at 2608:1399-13cd holds 5 entries
diff --git a/engines/eem/music.h b/engines/eem/music.h
index 8209df47d89..35b9ddd8e80 100644
--- a/engines/eem/music.h
+++ b/engines/eem/music.h
@@ -50,7 +50,7 @@ namespace EEM {
*/
class MusicPlayer : public Audio::MidiPlayer {
public:
- explicit MusicPlayer(bool isFloppy = false);
+ explicit MusicPlayer(bool isFloppy = false, bool isMacintosh = false);
/// _MIDIPlayFile @ 20a2:024c. loop=true mirrors
void playFile(const Common::Path &xmiPath, bool loop = false);
@@ -64,8 +64,12 @@ public:
void send(uint32 b) override;
private:
+ void playMacMidiResource(uint16 resourceId, bool loop);
+ void startLoadedMusic(const Common::String &name, bool loop, bool smf);
+
bool _milesAudioMode = false;
const bool _isFloppy;
+ const bool _isMacintosh;
Common::Array<byte> _xmiData;
};
diff --git a/engines/eem/resource.cpp b/engines/eem/resource.cpp
index d9a441aef3f..fb8161d95aa 100644
--- a/engines/eem/resource.cpp
+++ b/engines/eem/resource.cpp
@@ -22,7 +22,10 @@
#include "common/compression/dcl.h"
#include "common/array.h"
#include "common/debug.h"
+#include "common/endian.h"
#include "common/file.h"
+#include "common/macresman.h"
+#include "common/memstream.h"
#include "common/textconsole.h"
#include "graphics/pixelformat.h"
@@ -39,6 +42,111 @@ DBDArchive::~DBDArchive() {
close();
}
+static uint32 readUint24BE(Common::SeekableReadStream &stream) {
+ const uint32 hi = stream.readByte();
+ const uint32 mid = stream.readByte();
+ const uint32 lo = stream.readByte();
+ return (hi << 16) | (mid << 8) | lo;
+}
+
+static Common::SeekableReadStream *openRawMacResource(const Common::Path &path,
+ uint32 typeId,
+ uint16 resourceId) {
+ Common::File f;
+ if (!f.open(path))
+ return nullptr;
+
+ const uint32 fileSize = (uint32)f.size();
+ if (fileSize < 16)
+ return nullptr;
+
+ const uint32 dataOffset = f.readUint32BE();
+ const uint32 mapOffset = f.readUint32BE();
+ const uint32 dataLength = f.readUint32BE();
+ const uint32 mapLength = f.readUint32BE();
+ if (dataOffset > fileSize || mapOffset > fileSize ||
+ dataLength > fileSize - dataOffset ||
+ mapLength > fileSize - mapOffset || mapLength < 28)
+ return nullptr;
+
+ f.seek(mapOffset + 24);
+ const uint16 typeListOffset = f.readUint16BE();
+ const uint32 typeListBase = mapOffset + typeListOffset;
+ const uint32 mapEnd = mapOffset + mapLength;
+ if (typeListBase + 2 > mapEnd)
+ return nullptr;
+
+ f.seek(typeListBase);
+ const uint16 typeCount = f.readUint16BE() + 1;
+ for (uint typeIndex = 0; typeIndex < typeCount; typeIndex++) {
+ const uint32 typeEntry = typeListBase + 2 + typeIndex * 8;
+ if (typeEntry + 8 > mapEnd)
+ return nullptr;
+
+ f.seek(typeEntry);
+ const uint32 curType = f.readUint32BE();
+ const uint16 resourceCount = f.readUint16BE() + 1;
+ const uint16 refListOffset = f.readUint16BE();
+ if (curType != typeId)
+ continue;
+
+ const uint32 refListBase = typeListBase + refListOffset;
+ if (refListBase > mapEnd)
+ return nullptr;
+
+ for (uint resIndex = 0; resIndex < resourceCount; resIndex++) {
+ const uint32 refEntry = refListBase + resIndex * 12;
+ if (refEntry + 12 > mapEnd)
+ return nullptr;
+
+ f.seek(refEntry);
+ const uint16 curId = f.readUint16BE();
+ f.skip(2); // resource name offset
+ f.skip(1); // attributes
+ const uint32 resourceDataOffset = readUint24BE(f);
+ f.skip(4); // handle
+
+ if (curId != resourceId)
+ continue;
+
+ const uint32 resourceData = dataOffset + resourceDataOffset;
+ if (resourceData + 4 > dataOffset + dataLength)
+ return nullptr;
+
+ f.seek(resourceData);
+ const uint32 resourceLength = f.readUint32BE();
+ if (resourceLength > dataOffset + dataLength - resourceData - 4)
+ return nullptr;
+
+ byte *data = (byte *)malloc(resourceLength);
+ if (!data)
+ return nullptr;
+ if (f.read(data, resourceLength) != resourceLength) {
+ free(data);
+ return nullptr;
+ }
+
+ return new Common::MemoryReadStream(data, resourceLength,
+ DisposeAfterUse::YES);
+ }
+ }
+
+ return nullptr;
+}
+
+Common::SeekableReadStream *openMacResource(const Common::Path &path,
+ uint32 typeId,
+ uint16 resourceId) {
+ Common::MacResManager res;
+ Common::SeekableReadStream *stream = nullptr;
+ if (res.open(path) && res.hasResFork())
+ stream = res.getResource(typeId, resourceId);
+ if (stream)
+ return stream;
+
+ return openRawMacResource(path, typeId, resourceId);
+}
+
bool DBDArchive::open(const Common::Path &dbdName, const Common::Path &dbxName, bool bigEndian) {
close();
_bigEndian = bigEndian;
diff --git a/engines/eem/resource.h b/engines/eem/resource.h
index 735ed30797b..229bf707c9d 100644
--- a/engines/eem/resource.h
+++ b/engines/eem/resource.h
@@ -29,6 +29,10 @@
#include "graphics/managed_surface.h"
+namespace Common {
+class SeekableReadStream;
+}
+
namespace EEM {
struct DBEntry {
@@ -79,6 +83,10 @@ private:
bool _bigEndian = false;
};
+Common::SeekableReadStream *openMacResource(const Common::Path &path,
+ uint32 typeId,
+ uint16 resourceId);
+
} // End of namespace EEM
#endif
Commit: 95e34f0a24e6ced2700ed7b33f90176d80cb7413
https://github.com/scummvm/scummvm/commit/95e34f0a24e6ced2700ed7b33f90176d80cb7413
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: start loading the practice mystery in EEM1 Mac
Changed paths:
engines/eem/clues.cpp
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/graphics.cpp
engines/eem/mystery.cpp
engines/eem/mystery.h
engines/eem/ui.cpp
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index 81dd1e2a326..b977f53f211 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -432,8 +432,18 @@ void EEMEngine::playLondonInitCluesAnim(uint16 caseType, const Picture &bg,
// EEM1 CD/floppy case-intro animation â `_DoInitClues @ 1a35:0411`
void EEMEngine::playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
const Picture &bg, bool haveBriefingBg) {
+ const bool mac = isMacintosh();
const uint gameAni = _partner == kPartnerJake ? 0x17 : 0x3b;
const uint bookAni = _partner == kPartnerJake ? 0x18 : 0x3c;
+ const int gameX = mac
+ ? (_partner == kPartnerJake ? 0x144 : 0x146) : 0xcd;
+ const int gameY = mac
+ ? (_partner == kPartnerJake ? 0x0d8 : 0x0d6) : 0x6c;
+ const int bookX = 0;
+ const int bookY = mac
+ ? (_partner == kPartnerJake ? 0x0ca : 0x0c2) : 99;
+ const int nancyX = mac ? 0x0aa : 0x68;
+ const int nancyY = mac ? 0x114 : 0x8b;
Animation game, book, nancy;
const bool haveGame = _aniArchive.loadAnimation(gameAni, game) && !game.empty();
const bool haveBook = _aniArchive.loadAnimation(bookAni, book) && !book.empty();
@@ -459,15 +469,15 @@ void EEMEngine::playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
}
if (haveGame) {
const uint f = partnerFrameAtTick(0x17, (uint)game.size(), t);
- blitAnimFrameAnchored(scr, game[f], 0xcd, 0x6c);
+ blitAnimFrameAnchored(scr, game[f], gameX, gameY);
}
if (haveBook) {
const uint f = partnerFrameAtTick(0x18, (uint)book.size(), t);
- blitAnimFrameAnchored(scr, book[f], 0, 99);
+ blitAnimFrameAnchored(scr, book[f], bookX, bookY);
}
if (haveNancy) {
const uint f = partnerFrameAtTick(0x19, (uint)nancy.size(), t);
- blitAnimFrameAnchored(scr, nancy[f], 0x68, 0x8b);
+ blitAnimFrameAnchored(scr, nancy[f], nancyX, nancyY);
}
g_system->unlockScreen();
g_system->updateScreen();
@@ -498,7 +508,9 @@ void EEMEngine::playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
// _VidramRectCopy(0, 0x5a, 0x28, 0x6d, 16000, 48000/32000): freeze the
// lower-left book/Nancy band the original bakes into BG buffers before
// clearing registered animations.
- Graphics::ManagedSurface briefingBase(kScreenWidth, kScreenHeight,
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ Graphics::ManagedSurface briefingBase(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
briefingBase.clear();
if (haveBriefingBg) {
@@ -512,9 +524,9 @@ void EEMEngine::playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
}
{
const int preserveX = 0;
- const int preserveY = 0x5a;
- const int preserveW = 0x28 * 4;
- const int preserveH = 0x6d;
+ const int preserveY = mac ? scaleY(0x5a) : 0x5a;
+ const int preserveW = mac ? scaleX(0x28 * 4) : 0x28 * 4;
+ const int preserveH = mac ? scaleY(0x6d) : 0x6d;
const Common::Rect preserveRect(preserveX, preserveY,
preserveX + preserveW,
preserveY + preserveH);
@@ -541,23 +553,27 @@ void EEMEngine::playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
// _PlayInSequence @ 172b:2d03.
uint16 seqAni = 0xFFFF;
- uint16 seqY = 0x6c;
- if (_partner == kPartnerJake) {
+ int seqX = 0xcd;
+ int seqY = 0x6c;
+ if (mac && _partner == kPartnerJake) {
switch (caseType) {
- case 1:
- seqAni = 0x38;
- seqY = 0x6d;
- break;
- case 2:
- seqAni = 0x37;
- seqY = 0x6c;
- break;
- case 3:
- seqAni = 0x39;
- seqY = 0x6c;
- break;
- default:
- break;
+ case 1: seqAni = 0x38; seqX = 0x175; seqY = 0x0ce; break;
+ case 2: seqAni = 0x37; seqX = 0x156; seqY = 0x0d8; break;
+ case 3: seqAni = 0x39; seqX = 0x155; seqY = 0x0da; break;
+ default: break;
+ }
+ } else if (mac) {
+ switch (caseType) {
+ case 2: seqAni = 0x3a; seqX = 0x157; seqY = 0x0dd; break;
+ case 3: seqAni = 0x3d; seqX = 0x168; seqY = 0x0d4; break;
+ default: break;
+ }
+ } else if (_partner == kPartnerJake) {
+ switch (caseType) {
+ case 1: seqAni = 0x38; seqY = 0x6d; break;
+ case 2: seqAni = 0x37; seqY = 0x6c; break;
+ case 3: seqAni = 0x39; seqY = 0x6c; break;
+ default: break;
}
} else {
switch (caseType) {
@@ -582,10 +598,10 @@ void EEMEngine::playCdFloppyInitCluesAnim(uint16 caseType, bool floppy,
const Picture &fr = seq[frame];
g_system->copyRectToScreen(briefingBase.getPixels(),
briefingBase.pitch, 0, 0,
- kScreenWidth, kScreenHeight);
+ sw, sh);
// _PlayInSequence @ 172b:2d35-2d50
- const int dstX = (int)0xcd - (int)(int16)fr.miscflags;
- const int dstY = (int)seqY - (int)(int16)fr.rowoff;
+ const int dstX = seqX - (int)(int16)fr.miscflags;
+ const int dstY = seqY - (int)(int16)fr.rowoff;
blitMaskedToScreen(fr, dstX, dstY);
g_system->updateScreen();
const uint32 wakeup = g_system->getMillis() + 100;
@@ -623,10 +639,12 @@ void EEMEngine::doInitClues() {
return;
const bool floppy = isFloppy();
- const uint16 caseType = floppy ? (uint16)ib[0] : READ_LE_UINT16(ib);
+ const bool compactInit = floppy || isMacintosh();
+ const uint16 caseType = compactInit ? (uint16)ib[0] : READ_LE_UINT16(ib);
if (!floppy) {
- const uint16 startSite = READ_LE_UINT16(ib + 2);
+ const uint16 startSite = isMacintosh() ? (uint16)ib[1]
+ : READ_LE_UINT16(ib + 2);
if (startSite < Mystery::kVisitedSiteCap)
_mystery._onSites[startSite] = 1;
_mystery._siteNumber = startSite;
@@ -666,7 +684,7 @@ void EEMEngine::doInitClues() {
// Briefing dialogue. CD: clue block @ ib+4 (after caseType,startSite).
// Floppy: dialog records dispatched via FUN_22dc_05c8 @ 22dc:05c8
// (record size = 11 + textCount bytes).
- if (floppy) {
+ if (floppy || isMacintosh()) {
displayFloppyBriefing(ib);
} else {
const byte *briefingClues = ib + 4;
@@ -1177,7 +1195,13 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
// u8 sound @ +9 (high bit = play voice, low 7 bits = slot)
// u8 textCount@ +10
// u8 textIdx[]@ +11 (1 byte per â low 7 bits = NOTES idx)
- if (!rec || !isFloppy() || !_font.isLoaded() || count == 0)
+ // Mac uses the same logical fields, but widens picY/balloon/ballY to
+ // big-endian u16: 6 words, then u8 sound, u8 textCount, textIdx[].
+ const bool mac = isMacintosh();
+ const EEMFont &dialogFont =
+ (mac && _dialogFont.isLoaded()) ? _dialogFont : _font;
+ if (!rec || (!isFloppy() && !mac) || !dialogFont.isLoaded() ||
+ count == 0)
return;
const byte *notes = _mystery.noteIndex();
@@ -1185,8 +1209,11 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
if (!notes || !bufBase)
return;
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+
// Snapshot BG for between-bubble restores.
- Graphics::ManagedSurface bg(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface bg(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
{
Graphics::Surface *screen = g_system->lockScreen();
@@ -1198,37 +1225,44 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
const uint32 dsz = _mystery.dataSize();
const uint32 notesBase = (uint32)(notes - bufBase);
+ const uint noteStride = isMacintosh() ? 8 : 7;
{
const byte *r = rec;
for (uint i = 0; i < count; i++) {
- const uint8 tc = r[10];
+ const uint8 tc = mac ? r[13] : r[10];
+ const byte *textIdx = mac ? r + 14 : r + 11;
for (uint t = 0; t < tc; t++) {
- const uint8 idx = r[11 + t] & 0x7f;
+ const uint8 idx = textIdx[t] & 0x7f;
if (idx < Mystery::kCluesFoundCap)
_mystery._cluesFound[idx] = 1;
}
- r += 11 + tc;
+ r += (mac ? 14 : 11) + tc;
}
}
for (uint i = 0; i < count && !shouldQuit(); i++) {
- const uint16 picID = READ_LE_UINT16(rec + 0);
- const uint16 picX = READ_LE_UINT16(rec + 2);
- const uint8 picY = rec[4];
- const uint8 balByte = rec[5];
- const uint16 ballX = READ_LE_UINT16(rec + 6);
- const uint8 ballY = rec[8];
- const uint8 textCount= rec[10];
+ const uint16 picID = mac ? READ_BE_UINT16(rec + 0)
+ : READ_LE_UINT16(rec + 0);
+ const uint16 picX = mac ? READ_BE_UINT16(rec + 2)
+ : READ_LE_UINT16(rec + 2);
+ const uint16 picY = mac ? READ_BE_UINT16(rec + 4) : rec[4];
+ const uint16 balByte = mac ? READ_BE_UINT16(rec + 6) : rec[5];
+ const uint16 ballX = mac ? READ_BE_UINT16(rec + 8)
+ : READ_LE_UINT16(rec + 6);
+ const uint16 ballY = mac ? READ_BE_UINT16(rec + 10) : rec[8];
+ const uint8 soundByte = mac ? rec[12] : rec[9];
+ const uint8 textCount = mac ? rec[13] : rec[10];
+ const byte *textIdx = mac ? rec + 14 : rec + 11;
// byte 9: high bit = play voice slot.
- const bool playedRecordVoice = (rec[9] & 0x80) != 0 && _audio;
+ const bool playedRecordVoice = (soundByte & 0x80) != 0 && _audio;
if (playedRecordVoice) {
- const uint slot = rec[9] & 0x7f;
+ const uint slot = soundByte & 0x7f;
_audio->playFloppyVoiceSlot(slot, _partner);
}
- const uint8 b9 = rec[9];
+ const uint8 b9 = soundByte;
if ((b9 & 0x80) == 0 && b9 != 0) {
const uint logicalIdx = (uint)b9 - 1;
if (logicalIdx < Mystery::kGalleryCap) {
@@ -1242,18 +1276,18 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
Common::String fitPage;
uint16 fitXIns = 0;
uint16 fitYIns = 0;
- uint16 fitWidth = 142;
+ uint16 fitWidth = mac ? (uint16)scaleX(142) : 142;
getBalloonInsets(balByte, fitXIns, fitYIns, fitWidth);
uint fitTextLines = 0;
for (uint t = 0; t < textCount; t++) {
- const uint8 idxByte = rec[11 + t];
+ const uint8 idxByte = textIdx[t];
const uint8 idx = idxByte & 0x7f;
- const uint32 noteAbs = notesBase + (uint32)idx * 7;
+ const uint32 noteAbs = notesBase + (uint32)idx * noteStride;
if (noteAbs + 6 > dsz)
continue;
const uint16 textOff = (_partner == kPartnerJake)
- ? READ_LE_UINT16(notes + idx * 7 + 2)
- : READ_LE_UINT16(notes + idx * 7 + 4);
+ ? READ_LE_UINT16(notes + idx * noteStride + 2)
+ : READ_LE_UINT16(notes + idx * noteStride + 4);
if (textOff >= dsz)
continue;
const char *linePtr = (const char *)(bufBase + textOff);
@@ -1271,8 +1305,8 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
(idxByte & 0x80) != 0 && t + 1 < textCount;
if (!continuePage) {
Common::Array<Common::String> wrapped;
- _font.wordWrapText(fitPage, MAX<int>(8, (int)fitWidth),
- wrapped);
+ dialogFont.wordWrapText(fitPage, MAX<int>(8, (int)fitWidth),
+ wrapped);
if (wrapped.size() > fitTextLines ||
(wrapped.size() == fitTextLines &&
fitPage.size() > fitText.size())) {
@@ -1292,29 +1326,30 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
const bool haveBalloon = balByte != 0xFF &&
_balloonArchive.size() > balloonId &&
_balloonArchive.loadEntry(balloonId, balloon);
- uint16 textWidth = 142;
- uint16 textXIns = 6;
- uint16 textYIns = 4;
+ uint16 textWidth = mac ? (uint16)scaleX(142) : 142;
+ uint16 textXIns = mac ? (uint16)scaleX(6) : 6;
+ uint16 textYIns = mac ? (uint16)scaleY(4) : 4;
if (haveBalloon)
getBalloonInsets(balloonId, textXIns, textYIns, textWidth);
const int textX = ballX + textXIns;
- const int lineH = _font.getFontHeight();
+ const int lineH = dialogFont.getFontHeight();
+ const byte textColor = mac ? 0xff : 0;
bool firstPage = true;
int cursorY = ballY + textYIns;
bool skipAll = false;
for (uint t = 0; t < textCount && !shouldQuit() && !skipAll; t++) {
- const uint8 idxByte = rec[11 + t];
+ const uint8 idxByte = textIdx[t];
const uint8 idx = idxByte & 0x7f;
if (idx < Mystery::kCluesFoundCap)
_mystery._cluesFound[idx] = 1;
- const uint32 noteAbs = notesBase + (uint32)idx * 7;
+ const uint32 noteAbs = notesBase + (uint32)idx * noteStride;
if (noteAbs + 6 > dsz)
break;
const uint16 textOff = (_partner == kPartnerJake)
- ? READ_LE_UINT16(notes + idx * 7 + 2)
- : READ_LE_UINT16(notes + idx * 7 + 4);
+ ? READ_LE_UINT16(notes + idx * noteStride + 2)
+ : READ_LE_UINT16(notes + idx * noteStride + 4);
if (textOff >= dsz)
break;
const char *linePtr = (const char *)(bufBase + textOff);
@@ -1326,7 +1361,7 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
parseString(raw, _playerName, _partner);
// Render this text page.
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(*bg.surfacePtr());
@@ -1349,11 +1384,11 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
}
Common::Array<Common::String> lines;
- _font.wordWrapText(text, MAX<int>(8, (int)textWidth), lines);
+ dialogFont.wordWrapText(text, MAX<int>(8, (int)textWidth), lines);
for (uint l = 0; l < lines.size(); l++) {
- _font.drawString(&scratch, lines[l], textX,
- cursorY + (int)l * lineH,
- MAX<int>(8, (int)textWidth), 0);
+ dialogFont.drawString(&scratch, lines[l], textX,
+ cursorY + (int)l * lineH,
+ MAX<int>(8, (int)textWidth), textColor);
}
cursorY += (int)lines.size() * lineH;
@@ -1395,7 +1430,7 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
}
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
if (waitNeeded) {
@@ -1416,13 +1451,19 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
if (playedRecordVoice)
_audio->stopVoice();
- rec += 11 + textCount;
+ rec += (mac ? 14 : 11) + textCount;
}
}
void EEMEngine::displayFloppyBriefing(const byte *initBlock) {
- if (!initBlock || !isFloppy())
+ if (!initBlock || (!isFloppy() && !isMacintosh()))
return;
+ if (isMacintosh()) {
+ const uint16 nDialog = READ_LE_UINT16(initBlock + 2);
+ const byte *rec = initBlock + 4;
+ displayFloppyDialogRecords(rec, nDialog);
+ return;
+ }
const uint8 nSubjects = initBlock[1];
const uint8 nDialog = initBlock[2 + nSubjects];
const byte *rec = initBlock + 3 + nSubjects;
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index cfe99d67bec..939158f95b1 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -59,7 +59,7 @@ const uint kPalHighScore = 0x27;
const uint kPalStormLogo = 0x26; // Floppy FUN_23d2_0605
const uint kPicMousePointer = 0x50; // 0x51 is the wait cursor
const uint16 kMacFontResource = 3214; // 'Eagle Eye 14' FONT resource
-const uint16 kMacSmallFontResource = 3209; // 'Eagle Eye 9' fallback
+const uint16 kMacSmallFontResource = 3209; // Smaller speech/dialog FONT.
// EEM2 cursor table â `_main @ 1abf:0faf` loads seven cursor PICs into
// `_AnimationObjects`-adjacent slots and `_SwitchMouse @ 17ee:2c83` activates
@@ -341,24 +341,30 @@ static void addMacResourceSearchPaths() {
SearchMan.addDirectory("eem-mac-rsrc-sibling", siblingRsrcDir);
}
-static bool loadMacFont(EEMFont &font) {
+static bool loadMacFontResource(EEMFont &font, uint16 resourceId, int size) {
addMacResourceSearchPaths();
const Common::Path appResourceFork("Eagle Eye Mysteries");
- if (font.loadMacResource(appResourceFork, kMacFontResource, 14))
- return true;
- if (font.loadMacResource(appResourceFork, kMacSmallFontResource, 9))
+ if (font.loadMacResource(appResourceFork, resourceId, size))
return true;
const Common::Path nestedAppResourceFork("rsrc/Eagle Eye Mysteries");
- if (font.loadMacResource(nestedAppResourceFork, kMacFontResource, 14))
- return true;
- if (font.loadMacResource(nestedAppResourceFork, kMacSmallFontResource, 9))
+ if (font.loadMacResource(nestedAppResourceFork, resourceId, size))
return true;
return false;
}
+static bool loadMacFont(EEMFont &font) {
+ return loadMacFontResource(font, kMacFontResource, 14) ||
+ loadMacFontResource(font, kMacSmallFontResource, 9);
+}
+
+static bool loadMacDialogFont(EEMFont &font) {
+ return loadMacFontResource(font, kMacSmallFontResource, 9) ||
+ loadMacFontResource(font, kMacFontResource, 14);
+}
+
Common::Error EEMEngine::run() {
initGraphics(screenWidth(), screenHeight());
@@ -381,10 +387,14 @@ Common::Error EEMEngine::run() {
// _LoadFont @ 1b66:023c. The Mac release stores its Eagle Eye fonts in
// the application resource fork instead of a DOS FONT.FNT file.
- if (isMacintosh() && !loadMacFont(_font))
- warning("Mac FONT resource failed to load; text will not render");
- else if (!isMacintosh() && !_font.load(Common::Path("FONT.FNT")))
+ if (isMacintosh()) {
+ if (!loadMacFont(_font))
+ warning("Mac FONT resource failed to load; text will not render");
+ if (!loadMacDialogFont(_dialogFont))
+ warning("Mac dialog FONT resource failed to load");
+ } else if (!_font.load(Common::Path("FONT.FNT"))) {
warning("FONT.FNT failed to load; text will not render");
+ }
// _InitMIDI @ 20a2:013a. The demo ships no music. The Mac release stores
// SMF MIDI resources in EEM Sound&Music instead of loose DOS XMIDI files.
@@ -1122,7 +1132,7 @@ void EEMEngine::showLondonLogo(uint picId, uint palId, uint holdMs) {
}
bool EEMEngine::startLondonTrainingMystery() {
- if (_mystery.load(0, &_rng)) {
+ if (_mystery.load(0, &_rng, isMacintosh())) {
resetSiteArrivalState();
if (_audio)
_audio->initMysterySounds(0);
@@ -1612,7 +1622,7 @@ Common::Error EEMEngine::loadGameStream(Common::SeekableReadStream *stream) {
if (hasMystery) {
uint16 mysteryNum = 0;
s.syncAsUint16LE(mysteryNum);
- if (!_mystery.load(mysteryNum, &_rng)) {
+ if (!_mystery.load(mysteryNum, &_rng, isMacintosh())) {
_mystery.clear();
resetSiteArrivalState();
return Common::kReadingFailed;
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 00ba2fc9367..f99c4086b79 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -537,6 +537,7 @@ private:
DBDArchive _buttonArchive; ///< BUTTON.DBD/.DBX (`_GetButton`)
Mystery _mystery; ///< M<n>.BIN
EEMFont _font; ///< FONT.FNT (8 px)
+ EEMFont _dialogFont; ///< Mac small FONT used inside speech balloons.
Common::Array<byte> _sitePals; ///< 40 Ã 768 bytes, 6-bit VGA.
diff --git a/engines/eem/graphics.cpp b/engines/eem/graphics.cpp
index c6c5dd08d43..744121049be 100644
--- a/engines/eem/graphics.cpp
+++ b/engines/eem/graphics.cpp
@@ -772,7 +772,7 @@ void EEMEngine::setPartnerEraseBg(const Graphics::ManagedSurface *bg) {
uint16 EEMEngine::fitBalloonToText(uint16 bubNum,
const Common::String &text) {
// Opt-in via "fit_dialog_balloons", CD only
- if (isFloppy() || !ConfMan.getBool("fit_dialog_balloons"))
+ if (isFloppy() || isMacintosh() || !ConfMan.getBool("fit_dialog_balloons"))
return bubNum;
const uint16 originalId = bubNum & 0x7F;
@@ -837,9 +837,15 @@ bool EEMEngine::getBalloonInsets(uint16 bubNum, uint16 &xInset,
const uint idx = bubNum & 0x7F;
if (idx >= ARRAYSIZE(kBalloonInsetTable))
return false;
- xInset = kBalloonInsetTable[idx].x;
- yInset = kBalloonInsetTable[idx].y;
- textW = kBalloonInsetTable[idx].w;
+ if (isMacintosh()) {
+ xInset = (uint16)scaleX(kBalloonInsetTable[idx].x);
+ yInset = (uint16)scaleY(kBalloonInsetTable[idx].y);
+ textW = (uint16)scaleX(kBalloonInsetTable[idx].w);
+ } else {
+ xInset = kBalloonInsetTable[idx].x;
+ yInset = kBalloonInsetTable[idx].y;
+ textW = kBalloonInsetTable[idx].w;
+ }
return true;
}
@@ -848,8 +854,13 @@ bool EEMEngine::getBalloonIndicatorPos(uint16 bubNum, uint16 &dx,
const uint idx = bubNum & 0x7F;
if (idx >= ARRAYSIZE(kBalloonInsetTable))
return false;
- dx = kBalloonInsetTable[idx].indDX;
- dy = kBalloonInsetTable[idx].indDY;
+ if (isMacintosh()) {
+ dx = (uint16)scaleX(kBalloonInsetTable[idx].indDX);
+ dy = (uint16)scaleY(kBalloonInsetTable[idx].indDY);
+ } else {
+ dx = kBalloonInsetTable[idx].indDX;
+ dy = kBalloonInsetTable[idx].indDY;
+ }
return true;
}
diff --git a/engines/eem/mystery.cpp b/engines/eem/mystery.cpp
index 1734c804175..65e2a87c3fc 100644
--- a/engines/eem/mystery.cpp
+++ b/engines/eem/mystery.cpp
@@ -30,6 +30,91 @@
namespace EEM {
+const uint kMacMysteryCount = 55;
+const uint kMacMysteryDataTableOffset = 0x08cd;
+
+static void swapU16Range(Common::Array<byte> &data, uint start, uint end) {
+ end = MIN<uint>(end, data.size());
+ if (start >= end)
+ return;
+ for (uint pos = start; pos + 1 < end; pos += 2) {
+ const byte tmp = data[pos];
+ data[pos] = data[pos + 1];
+ data[pos + 1] = tmp;
+ }
+}
+
+static bool loadMacMysteryBlob(uint num, Common::Array<byte> &out) {
+ if (num >= kMacMysteryCount)
+ return false;
+
+ Common::File f;
+ if (!f.open(Common::Path("MysteryData"))) {
+ warning("Mystery::load: cannot open MysteryData");
+ return false;
+ }
+
+ const uint32 tableOff = kMacMysteryDataTableOffset + num * 8;
+ if (tableOff + 8 > (uint32)f.size()) {
+ warning("Mystery::load: MysteryData index %u out of range", num);
+ return false;
+ }
+
+ f.seek(tableOff);
+ const uint32 offset = f.readUint32BE();
+ const uint32 size = f.readUint32BE();
+ if (size <= 20 || offset > (uint32)f.size() ||
+ size > (uint32)f.size() - offset) {
+ warning("Mystery::load: MysteryData entry %u is invalid "
+ "(off=0x%08x size=0x%08x)", num, offset, size);
+ return false;
+ }
+
+ out.resize(size);
+ f.seek(offset);
+ if (f.read(out.data(), size) != size) {
+ warning("Mystery::load: short read on MysteryData entry %u", num);
+ out.clear();
+ return false;
+ }
+
+ return true;
+}
+
+static uint16 readBE16(const Common::Array<byte> &data, uint offset) {
+ if (offset + 2 > data.size())
+ return 0;
+ return READ_BE_UINT16(data.data() + offset);
+}
+
+static void normalizeMacMystery(Common::Array<byte> &data) {
+ if (data.size() <= 20)
+ return;
+
+ uint16 section[10];
+ for (uint i = 0; i < ARRAYSIZE(section); i++)
+ section[i] = readBE16(data, i * 2);
+
+ swapU16Range(data, 0, 20); // offset header
+
+ // Compact init block: u8 caseType, u8 startSite, u16 dialog count,
+ // followed by Mac-native big-endian dialog records. Keep the records
+ // byte-exact; their widened coordinate fields are parsed by the Mac
+ // briefing path.
+ if (section[0] + 4 <= data.size())
+ swapU16Range(data, section[0] + 2, section[0] + 4);
+
+ // Remaining structured regions. The text regions at section[6] and
+ // section[7] are raw strings and must stay byte-exact.
+ swapU16Range(data, section[1], section[2]); // clue/site chain table
+ swapU16Range(data, section[2] + 1, section[3]); // counted map entries
+ swapU16Range(data, section[3], section[4]); // site index + site data
+ swapU16Range(data, section[4], section[7]); // notebook index
+ swapU16Range(data, section[5] + 1, section[6]); // compact gallery metadata
+ swapU16Range(data, section[8], MIN<uint>(section[8] + 12, section[9]));
+ swapU16Range(data, section[9], data.size()); // solved clue block
+}
+
uint16 Mystery::readU16(uint offset) const {
if (offset + 2 > _data.size())
return 0;
@@ -45,6 +130,7 @@ void Mystery::clear() {
_numSites = 0;
_numSuspects = _numCONSITEs = _numCOFFSITEs = 0;
_isFloppy = false;
+ _isMacintosh = false;
_floppySuspectsOff = _floppyHintBlockOff = _floppyNoteIndexOff = 0;
_floppyGalleryOff = _floppyTextOff = _floppyKDTextOff = 0;
_floppySolvedOff = 0;
@@ -70,32 +156,88 @@ void Mystery::clear() {
memset(_siteReturnStack, 0, sizeof(_siteReturnStack));
}
-bool Mystery::load(uint num, Common::RandomSource *rng) {
- const Common::String fname = Common::String::format("M%u.BIN", num);
- Common::File f;
- if (!f.open(Common::Path(fname))) {
- warning("Mystery::load: cannot open %s", fname.c_str());
- return false;
- }
+bool Mystery::load(uint num, Common::RandomSource *rng, bool macintosh) {
+ Common::String fname = Common::String::format("M%u.BIN", num);
+ Common::Array<byte> staging;
+
+ if (macintosh) {
+ if (!loadMacMysteryBlob(num, staging))
+ return false;
+ normalizeMacMystery(staging);
+ fname = Common::String::format("MysteryData[%u]", num);
+ } else {
+ Common::File f;
+ if (!f.open(Common::Path(fname))) {
+ warning("Mystery::load: cannot open %s", fname.c_str());
+ return false;
+ }
- const int32 size = f.size();
- if (size <= 64) {
- warning("Mystery::load: %s too small (%d bytes)", fname.c_str(), size);
- return false;
- }
+ const int32 size = f.size();
+ if (size <= 64) {
+ warning("Mystery::load: %s too small (%d bytes)", fname.c_str(), size);
+ return false;
+ }
- // Stage to a temporary buffer so a short read leaves the previous
- // mystery state intact instead of half-clobbering `_data`.
- Common::Array<byte> staging(size);
- if (f.read(staging.data(), size) != (uint32)size) {
- warning("Mystery::load: short read on %s", fname.c_str());
- return false;
+ // Stage to a temporary buffer so a short read leaves the previous
+ // mystery state intact instead of half-clobbering `_data`.
+ staging.resize(size);
+ if (f.read(staging.data(), size) != (uint32)size) {
+ warning("Mystery::load: short read on %s", fname.c_str());
+ return false;
+ }
+ f.close();
}
- f.close();
_data = staging;
_number = num;
+ if (macintosh) {
+ _isMacintosh = true;
+
+ _initOffset = readU16(0 * 2);
+ _mapOffset = readU16(2 * 2);
+ _siteIndexOffset = readU16(3 * 2);
+ _noteOffset = readU16(4 * 2);
+ _galleryOffset = readU16(5 * 2);
+ _textOffset = readU16(7 * 2);
+ _kdTextOffset = readU16(8 * 2);
+ _solvedOffset = readU16(9 * 2);
+ _hintOffset = _kdTextOffset;
+
+ _numSites = (_mapOffset < _data.size()) ? _data[_mapOffset] : 0;
+ _numSuspects = (_galleryOffset < _data.size()) ? _data[_galleryOffset] : 0;
+ _numCONSITEs = 0;
+ _numCOFFSITEs = 0;
+ if (_numSites > kVisitedSiteCap)
+ _numSites = kVisitedSiteCap;
+
+ memset(_cluesFound, 0, sizeof(_cluesFound));
+ memset(_noteSelected, 0, sizeof(_noteSelected));
+ memset(_hotSpotsSeen, 0, sizeof(_hotSpotsSeen));
+ memset(_inGallery, 0, sizeof(_inGallery));
+ (void)rng;
+ for (uint i = 0; i < kGalleryCap; i++)
+ _newOrder[i] = (uint8)i;
+ memset(_visitedSite, 0, sizeof(_visitedSite));
+ memset(_onSites, 0, sizeof(_onSites));
+ _sawCOFFSITEs = _sawCONSITEs = _sawHelpHint = _solvedPuzzle = false;
+ _seenCOFFSITEs = _seenCONSITEs = 0;
+ _firstTry = true;
+ _searchLocationNumber = _siteNumber = 0xFFFF;
+ _lastSite = 0x1B;
+ _pendingSiteJump = 0;
+ _siteReturnDepth = 0;
+ memset(_siteReturnStack, 0, sizeof(_siteReturnStack));
+
+ debugC(1, kDebugMystery,
+ "Loaded %s (%u B): %u sites, %u suspects, init=0x%04x "
+ "map=0x%04x site=0x%04x text=0x%04x notes=0x%04x",
+ fname.c_str(), (uint)_data.size(), _numSites, _numSuspects,
+ _initOffset, _mapOffset, _siteIndexOffset, _textOffset,
+ _noteOffset);
+ return true;
+ }
+
// Floppy M*.BIN uses a different header layout from CD.
// _ReadMystery_Floppy @ 22dc:0178. Section-pointer header:
// header[+0] InitBlock byte offset (caseType byte at *(buf+initOff))
@@ -219,7 +361,7 @@ bool Mystery::load(uint num, Common::RandomSource *rng) {
debugC(1, kDebugMystery, "Loaded %s (%d B): %u sites, %u suspects, "
"CON=%u COFF=%u, init=0x%04x site=0x%04x text=0x%04x",
- fname.c_str(), size, _numSites, _numSuspects,
+ fname.c_str(), (int)_data.size(), _numSites, _numSuspects,
_numCONSITEs, _numCOFFSITEs,
_initOffset, _siteIndexOffset, _textOffset);
return true;
@@ -230,7 +372,7 @@ const byte *Mystery::siteIndexEntry(uint siteNum) const {
return nullptr;
// Floppy site index: 2-byte u16 entries (_DoSiteLoop_Floppy @ 1652:03d2).
// CD: 6-byte rows.
- const uint stride = _isFloppy ? 2 : 6;
+ const uint stride = (_isFloppy || _isMacintosh) ? 2 : 6;
const uint off = _siteIndexOffset + siteNum * stride;
if (off + stride > _data.size())
return nullptr;
@@ -316,7 +458,7 @@ void Mystery::loadFloppySiteAnimData() {
}
const byte *Mystery::hotspots(uint siteNum) const {
- if (_isFloppy) {
+ if (_isFloppy || _isMacintosh) {
const byte *site = siteData(siteNum);
if (!site || (size_t)(site - _data.data()) + 6 > _data.size())
return nullptr;
@@ -335,7 +477,7 @@ const byte *Mystery::hotspots(uint siteNum) const {
}
uint16 Mystery::hotspotCount(uint siteNum) const {
- if (_isFloppy) {
+ if (_isFloppy || _isMacintosh) {
const byte *site = siteData(siteNum);
if (!site || (size_t)(site - _data.data()) + 6 > _data.size())
return 0;
@@ -353,7 +495,7 @@ uint16 Mystery::hotspotCount(uint siteNum) const {
const char *Mystery::textAt(uint16 offset) const {
if (!isLoaded())
return "";
- const uint pos = _textOffset + offset;
+ const uint pos = _isMacintosh ? offset : _textOffset + offset;
if (pos >= _data.size())
return "";
return (const char *)(_data.data() + pos);
@@ -383,16 +525,18 @@ uint16 Mystery::noteIndexCount() const {
// NoteIndex runs from _noteOffset to start of GalleryData.
// CD entries: 4 bytes (u16 textOff; u16 points).
// Floppy entries: 7 bytes (u16 ?; u16 jakeOff; u16 jennyOff; u8 score)
- if (_galleryOffset <= _noteOffset)
+ const uint endOffset = _isMacintosh ? _textOffset : _galleryOffset;
+ if (endOffset <= _noteOffset)
return 0;
- const uint stride = _isFloppy ? 7 : 4;
- return (uint16)((_galleryOffset - _noteOffset) / stride);
+ const uint stride = _isMacintosh ? 8 : (_isFloppy ? 7 : 4);
+ return (uint16)((endOffset - _noteOffset) / stride);
}
uint Mystery::noteSectionSize() const {
- if (!isLoaded() || _galleryOffset <= _noteOffset)
+ const uint endOffset = _isMacintosh ? _textOffset : _galleryOffset;
+ if (!isLoaded() || endOffset <= _noteOffset)
return 0;
- return _galleryOffset - _noteOffset;
+ return endOffset - _noteOffset;
}
bool Mystery::noteHasNotebookText(uint clueId) const {
@@ -418,6 +562,13 @@ const byte *Mystery::kdTextIndex() const {
const byte *Mystery::mapEntry(uint siteNum) const {
if (!isLoaded() || siteNum >= _numSites)
return nullptr;
+ if (_isMacintosh) {
+ // Mac SITES section: byte[0]=count, then 12-byte entries.
+ const uint off = _mapOffset + 1 + siteNum * 12;
+ if (off + 12 > _data.size())
+ return nullptr;
+ return _data.data() + off;
+ }
if (_isFloppy) {
// Floppy SITES section (FUN_1fed_07ed): byte[0]=count, then 11-byte
// entries starting at byte 1.
diff --git a/engines/eem/mystery.h b/engines/eem/mystery.h
index 86f13dc7aeb..ca10d929e1b 100644
--- a/engines/eem/mystery.h
+++ b/engines/eem/mystery.h
@@ -59,8 +59,10 @@ public:
Mystery() = default;
~Mystery() = default;
- /// Load M<num>.BIN and reset per-mystery state. Returns false on error.
- bool load(uint num, class Common::RandomSource *rng = nullptr);
+ /// Load M<num>.BIN and reset per-mystery state. The Mac release stores
+ /// cases in the indexed MysteryData container instead of loose files.
+ bool load(uint num, class Common::RandomSource *rng = nullptr,
+ bool macintosh = false);
void clear();
@@ -238,6 +240,7 @@ private:
uint16 _cChain[kChainLen] = {};
bool _isFloppy = false;
+ bool _isMacintosh = false;
uint16 _floppySuspectsOff = 0; ///< header[+4] suspects
uint16 _floppyHintBlockOff = 0; ///< header[+6] hint -> clue table
uint16 _floppyNoteIndexOff = 0; ///< header[+8] notes (7B/clue)
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index d0bca0e0e6a..af55ce31d62 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -113,6 +113,76 @@ void blitBigMapMarker(Graphics::ManagedSurface &dstSurface, const Picture &marke
}
}
+struct BigMapEntryInfo {
+ uint16 overviewX = 0;
+ uint16 overviewY = 0;
+ uint16 detailX = 0;
+ uint16 detailY = 0;
+ uint16 buttonId = 0;
+ uint16 crime = 0;
+};
+
+bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
+ BigMapEntryInfo &out) {
+ if (!entry)
+ return false;
+
+ if (macintosh) {
+ out.detailX = READ_LE_UINT16(entry + 0x0);
+ out.detailY = READ_LE_UINT16(entry + 0x2);
+ out.buttonId = entry[0x4];
+ out.overviewX = READ_LE_UINT16(entry + 0x6);
+ out.overviewY = READ_LE_UINT16(entry + 0x8);
+ out.crime = READ_LE_UINT16(entry + 0xa);
+ return true;
+ }
+
+ if (floppy) {
+ out.detailX = READ_LE_UINT16(entry + 0x0);
+ out.detailY = READ_LE_UINT16(entry + 0x2);
+ out.buttonId = entry[0x4];
+ out.overviewX = READ_LE_UINT16(entry + 0x6);
+ out.overviewY = READ_LE_UINT16(entry + 0x8);
+ out.crime = entry[0xa];
+ return true;
+ }
+
+ out.buttonId = READ_LE_UINT16(entry + 0x0);
+ out.overviewX = READ_LE_UINT16(entry + 0x4);
+ out.overviewY = READ_LE_UINT16(entry + 0x6);
+ out.detailX = READ_LE_UINT16(entry + 0x8);
+ out.detailY = READ_LE_UINT16(entry + 0xa);
+ out.crime = READ_LE_UINT16(entry + 0xc);
+ return true;
+}
+
+bool loadMacBigMapPixels(Common::Array<byte> &mapPixels,
+ uint16 &mapW, uint16 &mapH) {
+ DBDArchive bigMapArchive;
+ if (!bigMapArchive.open(Common::Path("BIGMAP.DBD"),
+ Common::Path("BIGMAP.DBX"), true)) {
+ warning("doBigMap: BIGMAP archive missing");
+ return false;
+ }
+
+ Picture mapPic;
+ if (!bigMapArchive.loadEntry(0, mapPic) || mapPic.surface.empty()) {
+ warning("doBigMap: BIGMAP.DBD entry 0 failed to load");
+ return false;
+ }
+ if (mapPic.surface.w <= 0 || mapPic.surface.h <= 0)
+ return false;
+
+ mapW = (uint16)mapPic.surface.w;
+ mapH = (uint16)mapPic.surface.h;
+ mapPixels.resize((uint32)mapW * mapH);
+ for (uint y = 0; y < mapH; y++) {
+ const byte *src = (const byte *)mapPic.surface.getBasePtr(0, y);
+ memcpy(mapPixels.data() + y * mapW, src, mapW);
+ }
+ return true;
+}
+
bool loadLondonApproachData(uint16 approachId, LondonApproachData &out) {
Common::File f;
const Common::String name = Common::String::format("A%u.BIN", approachId);
@@ -1533,7 +1603,7 @@ void EEMEngine::doSetup() {
if (kNewCaseBtn.contains(mx, my)) {
saveProfile(_playerName);
if (isDemo()) {
- if (_mystery.load(0, &_rng)) {
+ if (_mystery.load(0, &_rng, isMacintosh())) {
resetSiteArrivalState();
_nextScreen = kScreenInitClues;
} else {
@@ -2090,7 +2160,7 @@ void EEMEngine::doActionScreen() {
}
if (pick == kPickPractice) {
- if (!_mystery.load(0, &_rng)) {
+ if (!_mystery.load(0, &_rng, isMacintosh())) {
warning("doActionScreen: failed to load practice mystery");
_mystery.clear();
resetSiteArrivalState();
@@ -2342,7 +2412,7 @@ void EEMEngine::doCaseSelection() {
return;
const uint mn = stageLo + selRow;
- if (!_mystery.load(mn, &_rng)) {
+ if (!_mystery.load(mn, &_rng, isMacintosh())) {
warning("doCaseSelection: failed to load mystery %u", mn);
_mystery.clear();
return;
@@ -3169,11 +3239,16 @@ void EEMEngine::doBigMap() {
drawBigMapOverview(0);
uint32 mapLastTick = mapStartTick;
- const Common::Rect kBigMapWindow(0, 0, 247, 192);
- const Common::Rect kSetupBtnRect = isFloppy()
+ const bool mac = isMacintosh();
+ const Common::Rect bigMapWindowBase(0, 0, 247, 192);
+ const Common::Rect kBigMapWindow =
+ mac ? scaleRect(bigMapWindowBase) : bigMapWindowBase;
+ const Common::Rect setupBtnBase = isFloppy()
? Common::Rect(251, 3, 315, 42)
: (isLondon() ? Common::Rect(252, 1, 315, 42)
: Common::Rect(252, 4, 315, 42));
+ const Common::Rect kSetupBtnRect =
+ mac ? scaleRect(setupBtnBase) : setupBtnBase;
bool wantZoom = false;
int zoomX = 0;
@@ -3196,12 +3271,17 @@ void EEMEngine::doBigMap() {
}
if (kBigMapWindow.contains(ev.mouse.x, ev.mouse.y)) {
- int sx = ev.mouse.x * 2;
- int sy = ev.mouse.y * 2;
- sx = (sx < 0x75) ? 0 : sx - 0x74;
- sy = (sy < 0x56) ? 0 : sy - 0x55;
- zoomX = sx;
- zoomY = sy;
+ if (mac) {
+ zoomX = ev.mouse.x - kBigMapWindow.left;
+ zoomY = ev.mouse.y - kBigMapWindow.top;
+ } else {
+ int sx = ev.mouse.x * 2;
+ int sy = ev.mouse.y * 2;
+ sx = (sx < 0x75) ? 0 : sx - 0x74;
+ sy = (sy < 0x56) ? 0 : sy - 0x55;
+ zoomX = sx;
+ zoomY = sy;
+ }
wantZoom = true;
break;
}
@@ -3229,28 +3309,49 @@ void EEMEngine::doBigMap() {
if (!wantZoom)
return;
- Common::File f;
- if (!f.open(Common::Path("BIGMAP.PIC"))) {
- warning("doBigMap: BIGMAP.PIC missing for detail view");
- return;
- }
- const uint16 mapH = f.readUint16LE();
- const uint16 mapW = f.readUint16LE();
- if (mapW == 0 || mapH == 0)
- return;
- Common::Array<byte> mapPixels((uint32)mapW * mapH);
- if (f.read(mapPixels.data(), mapPixels.size()) != mapPixels.size()) {
- warning("doBigMap: short read on BIGMAP.PIC for detail view");
- return;
+ uint16 mapW = 0;
+ uint16 mapH = 0;
+ Common::Array<byte> mapPixels;
+ if (mac) {
+ if (!loadMacBigMapPixels(mapPixels, mapW, mapH))
+ return;
+ } else {
+ Common::File f;
+ if (!f.open(Common::Path("BIGMAP.PIC"))) {
+ warning("doBigMap: BIGMAP.PIC missing for detail view");
+ return;
+ }
+ mapH = f.readUint16LE();
+ mapW = f.readUint16LE();
+ if (mapW == 0 || mapH == 0)
+ return;
+ mapPixels.resize((uint32)mapW * mapH);
+ if (f.read(mapPixels.data(), mapPixels.size()) != mapPixels.size()) {
+ warning("doBigMap: short read on BIGMAP.PIC for detail view");
+ return;
+ }
}
- const int kMapWinW = 0xe9; // 233
- const int kMapWinH = 0xab; // 171
- const int kMapWinX = 2;
- const int kMapWinY = 2;
-
- int scrollX = MAX<int>(0, MIN<int>(mapW - kMapWinW, zoomX));
- int scrollY = MAX<int>(0, MIN<int>(mapH - kMapWinH, zoomY));
+ const int kMapWinW = mac ? scaleX(0xe9) : 0xe9; // 233
+ const int kMapWinH = mac ? scaleY(0xab) : 0xab; // 171
+ const int kMapWinX = mac ? scaleX(2) : 2;
+ const int kMapWinY = mac ? scaleY(2) : 2;
+
+ const int maxScrollX = MAX<int>(0, (int)mapW - kMapWinW);
+ const int maxScrollY = MAX<int>(0, (int)mapH - kMapWinH);
+ int scrollX;
+ int scrollY;
+ if (mac) {
+ scrollX = zoomX * (int)mapW /
+ MAX<int>(1, kBigMapWindow.width()) - kMapWinW / 2;
+ scrollY = zoomY * (int)mapH /
+ MAX<int>(1, kBigMapWindow.height()) - kMapWinH / 2;
+ } else {
+ scrollX = zoomX;
+ scrollY = zoomY;
+ }
+ scrollX = MAX<int>(0, MIN<int>(maxScrollX, scrollX));
+ scrollY = MAX<int>(0, MIN<int>(maxScrollY, scrollY));
setSitePalette(isLondon() ? 0x3a : 0x23);
@@ -3259,24 +3360,42 @@ void EEMEngine::doBigMap() {
uint32 detailLastTick = detailStartTick;
bool returnToOverview = false;
- const Common::Rect kBigMapReturnRect(252, 43, kScreenWidth, kScreenHeight);
- const Common::Rect kArrowYUp(237, 2, 247, 11);
- const Common::Rect kArrowYDown(237, 163, 247, 172);
- const Common::Rect kArrowXLeft(2, 175, 12, 185);
- const Common::Rect kArrowXRight(224, 175, 234, 185);
- const Common::Rect kXSlider = isLondon()
+ const Common::Rect returnBase(252, 43, kScreenWidth, kScreenHeight);
+ const Common::Rect kBigMapReturnRect =
+ mac ? scaleRect(returnBase) : returnBase;
+ const Common::Rect kArrowYUp =
+ mac ? scaleRect(Common::Rect(237, 2, 247, 11))
+ : Common::Rect(237, 2, 247, 11);
+ const Common::Rect kArrowYDown =
+ mac ? scaleRect(Common::Rect(237, 163, 247, 172))
+ : Common::Rect(237, 163, 247, 172);
+ const Common::Rect kArrowXLeft =
+ mac ? scaleRect(Common::Rect(2, 175, 12, 185))
+ : Common::Rect(2, 175, 12, 185);
+ const Common::Rect kArrowXRight =
+ mac ? scaleRect(Common::Rect(224, 175, 234, 185))
+ : Common::Rect(224, 175, 234, 185);
+ const Common::Rect xSliderBase = isLondon()
? Common::Rect(15, 176, 220, 184)
: Common::Rect(15, 175, 221, 185);
- const Common::Rect kYSlider = isLondon()
+ const Common::Rect ySliderBase = isLondon()
? Common::Rect(238, 16, 246, 158)
: Common::Rect(237, 14, 247, 160);
- const Common::Rect kDetailSetupBtn = isFloppy()
+ const Common::Rect kXSlider =
+ mac ? scaleRect(xSliderBase) : xSliderBase;
+ const Common::Rect kYSlider =
+ mac ? scaleRect(ySliderBase) : ySliderBase;
+ const Common::Rect detailSetupBase = isFloppy()
? Common::Rect(251, 3, 315, 42)
: (isLondon() ? Common::Rect(251, 3, 315, 42)
: Common::Rect(252, 4, 315, 42));
- const int kArrowStep = isLondon() ? 8 : 16;
- const int kSliderRange = mapW - kMapWinW;
- const int kSliderRangeY = mapH - kMapWinH;
+ const Common::Rect kDetailSetupBtn =
+ mac ? scaleRect(detailSetupBase) : detailSetupBase;
+ const int baseArrowStep = isLondon() ? 8 : 16;
+ const int kArrowStepX = mac ? scaleX(baseArrowStep) : baseArrowStep;
+ const int kArrowStepY = mac ? scaleY(baseArrowStep) : baseArrowStep;
+ const int kSliderRange = maxScrollX;
+ const int kSliderRangeY = maxScrollY;
const Common::Point detailMouse =
g_system->getEventManager()->getMousePos();
setInteractiveMouseCursor(
@@ -3299,20 +3418,17 @@ void EEMEngine::doBigMap() {
dirty = true;
continue;
}
- const int kStep = isLondon() ? 8 : 16;
if (ev.kbd.keycode == Common::KEYCODE_LEFT) {
- scrollX = MAX<int>(0, scrollX - kStep);
+ scrollX = MAX<int>(0, scrollX - kArrowStepX);
dirty = true;
} else if (ev.kbd.keycode == Common::KEYCODE_RIGHT) {
- scrollX = MIN<int>(MAX<int>(0, mapW - kMapWinW),
- scrollX + kStep);
+ scrollX = MIN<int>(maxScrollX, scrollX + kArrowStepX);
dirty = true;
} else if (ev.kbd.keycode == Common::KEYCODE_UP) {
- scrollY = MAX<int>(0, scrollY - kStep);
+ scrollY = MAX<int>(0, scrollY - kArrowStepY);
dirty = true;
} else if (ev.kbd.keycode == Common::KEYCODE_DOWN) {
- scrollY = MIN<int>(MAX<int>(0, mapH - kMapWinH),
- scrollY + kStep);
+ scrollY = MIN<int>(maxScrollY, scrollY + kArrowStepY);
dirty = true;
}
}
@@ -3333,18 +3449,18 @@ void EEMEngine::doBigMap() {
returnToOverview = true;
break;
} else if (kArrowYUp.contains(ev.mouse.x, ev.mouse.y)) {
- scrollY = MAX<int>(0, scrollY - kArrowStep);
+ scrollY = MAX<int>(0, scrollY - kArrowStepY);
dirty = true;
} else if (kArrowYDown.contains(ev.mouse.x, ev.mouse.y)) {
scrollY = MIN<int>(MAX<int>(0, kSliderRangeY),
- scrollY + kArrowStep);
+ scrollY + kArrowStepY);
dirty = true;
} else if (kArrowXLeft.contains(ev.mouse.x, ev.mouse.y)) {
- scrollX = MAX<int>(0, scrollX - kArrowStep);
+ scrollX = MAX<int>(0, scrollX - kArrowStepX);
dirty = true;
} else if (kArrowXRight.contains(ev.mouse.x, ev.mouse.y)) {
scrollX = MIN<int>(MAX<int>(0, kSliderRange),
- scrollX + kArrowStep);
+ scrollX + kArrowStepX);
dirty = true;
} else if (kXSlider.contains(ev.mouse.x, ev.mouse.y)) {
if (kSliderRange > 0) {
@@ -3380,27 +3496,18 @@ void EEMEngine::doBigMap() {
const byte *entry = _mystery.mapEntry(i);
if (!entry)
continue;
- uint16 mx;
- uint16 my;
- uint16 buttonId;
- if (fmap) {
- mx = READ_LE_UINT16(entry + 0x0);
- my = READ_LE_UINT16(entry + 0x2);
- buttonId = (uint16)entry[0x4];
- } else {
- buttonId = READ_LE_UINT16(entry + 0x0);
- mx = READ_LE_UINT16(entry + 0x8);
- my = READ_LE_UINT16(entry + 0xa);
- }
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, fmap, mac, info))
+ continue;
Picture button;
int bw = 16;
int bh = 16;
- if (_buttonArchive.loadEntry(buttonId, button)) {
+ if (_buttonArchive.loadEntry(info.buttonId, button)) {
bw = button.surface.w;
bh = button.surface.h;
}
- const int sx = (int)mx - scrollX + kMapWinX;
- const int sy = (int)my - scrollY + kMapWinY;
+ const int sx = (int)info.detailX - scrollX + kMapWinX;
+ const int sy = (int)info.detailY - scrollY + kMapWinY;
const Common::Rect r(sx, sy, sx + bw, sy + bh);
if (r.intersects(Common::Rect(kMapWinX, kMapWinY,
kMapWinX + kMapWinW, kMapWinY + kMapWinH))) {
@@ -3666,7 +3773,10 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
}
void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ const bool mac = isMacintosh();
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
@@ -3680,9 +3790,12 @@ void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
Picture done;
Picture normal;
Picture crimeM;
- const bool haveDone = _picsArchive.getPicture(isLondon() ? 0x006 : 0x20d, done);
- const bool haveNormal = _picsArchive.getPicture(0xc5, normal);
- const bool haveCrime = _picsArchive.getPicture(0xc6, crimeM);
+ const bool haveDone = _picsArchive.getPicture(isLondon() ? 0x006 : 0x20d, done) &&
+ done.surface.w < 64 && done.surface.h < 64;
+ const bool haveNormal = _picsArchive.getPicture(0xc5, normal) &&
+ normal.surface.w < 64 && normal.surface.h < 64;
+ const bool haveCrime = _picsArchive.getPicture(0xc6, crimeM) &&
+ crimeM.surface.w < 64 && crimeM.surface.h < 64;
for (uint i = 0; i < _mystery.numSites(); i++) {
// `_DrawBigMapButtons` gates markers on the on-map flag alone, never the
@@ -3694,12 +3807,9 @@ void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
continue;
const bool floppy = _mystery.isLoaded() && isFloppy();
- const uint16 mx = floppy ? READ_LE_UINT16(entry + 0x6)
- : READ_LE_UINT16(entry + 0x4);
- const uint16 my = floppy ? READ_LE_UINT16(entry + 0x8)
- : READ_LE_UINT16(entry + 0x6);
- const uint16 crime = floppy ? (uint16)entry[0xa]
- : READ_LE_UINT16(entry + 0xc);
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, floppy, mac, info))
+ continue;
const bool isDone = (i < Mystery::kVisitedSiteCap)
&& _mystery._visitedSite[i];
@@ -3710,16 +3820,19 @@ void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
} else if (isDone && haveNormal) {
m = &normal;
useVisitedColors = true;
- } else if (crime != 0 && haveCrime) {
+ } else if (info.crime != 0 && haveCrime) {
m = &crimeM;
} else if (haveNormal) {
m = &normal;
}
if (m) {
- blitBigMapMarker(scratch, *m, (int)mx, (int)my,
+ blitBigMapMarker(scratch, *m, (int)info.overviewX,
+ (int)info.overviewY,
useVisitedColors);
} else {
+ const int mx = (int)info.overviewX;
+ const int my = (int)info.overviewY;
const Common::Rect mark(mx - 3, my - 3, mx + 4, my + 4);
scratch.fillRect(mark, 0x0F);
}
@@ -3732,11 +3845,12 @@ void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
elapsedMs, isLondon());
blitAnimFrameAnchored(scratch.surfacePtr(), mapAnim[frameIdx],
- 0xfd, 0x50);
+ mac ? scaleX(0xfd) : 0xfd,
+ mac ? scaleY(0x50) : 0x50);
}
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
}
@@ -3745,12 +3859,15 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
uint16 mapW, uint16 mapH,
uint32 elapsedMs) {
- const int kMapWinW = 0xe9;
- const int kMapWinH = 0xab;
- const int kMapWinX = 2;
- const int kMapWinY = 2;
+ const bool mac = isMacintosh();
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ const int kMapWinW = mac ? scaleX(0xe9) : 0xe9;
+ const int kMapWinH = mac ? scaleY(0xab) : 0xab;
+ const int kMapWinX = mac ? scaleX(2) : 2;
+ const int kMapWinY = mac ? scaleY(2) : 2;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
@@ -3772,24 +3889,14 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
const byte *entry = _mystery.mapEntry(i);
if (!entry)
continue;
- uint16 mx;
- uint16 my;
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, floppyMap, mac, info))
+ continue;
Picture button;
- if (floppyMap) {
- mx = READ_LE_UINT16(entry + 0x0);
- my = READ_LE_UINT16(entry + 0x2);
- const uint16 buttonId = (uint16)entry[0x4];
- if (!_buttonArchive.loadEntry(buttonId, button))
- continue;
- } else {
- const uint16 buttonId = READ_LE_UINT16(entry + 0x0);
- mx = READ_LE_UINT16(entry + 0x8);
- my = READ_LE_UINT16(entry + 0xa);
- if (!_buttonArchive.loadEntry(buttonId, button))
- continue;
- }
- const int sx = (int)mx - scrollX + kMapWinX;
- const int sy = (int)my - scrollY + kMapWinY;
+ if (!_buttonArchive.loadEntry(info.buttonId, button))
+ continue;
+ const int sx = (int)info.detailX - scrollX + kMapWinX;
+ const int sy = (int)info.detailY - scrollY + kMapWinY;
// Crop the button blit against the viewport.
const int x0 = MAX<int>(sx, kMapWinX);
const int y0 = MAX<int>(sy, kMapWinY);
@@ -3810,11 +3917,13 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
const uint frameIdx = bigMapDetailPartnerFrameAtTick(
(uint)detailAnim.size(), elapsedMs);
blitAnimFrameAnchored(scratch.surfacePtr(),
- detailAnim[frameIdx], 0x101, 0x50);
+ detailAnim[frameIdx],
+ mac ? scaleX(0x101) : 0x101,
+ mac ? scaleY(0x50) : 0x50);
}
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
}
Commit: de9f508c07ac6cfd2d9453271adcf85683964821
https://github.com/scummvm/scummvm/commit/de9f508c07ac6cfd2d9453271adcf85683964821
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: practice mystery starts to work in EEM1 Mac
Changed paths:
engines/eem/clues.cpp
engines/eem/graphics.cpp
engines/eem/mystery.cpp
engines/eem/site.cpp
engines/eem/ui.cpp
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index b977f53f211..4052bf8d31c 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -645,6 +645,11 @@ void EEMEngine::doInitClues() {
if (!floppy) {
const uint16 startSite = isMacintosh() ? (uint16)ib[1]
: READ_LE_UINT16(ib + 2);
+ if (isMacintosh()) {
+ const uint sites = _mystery.numSites();
+ for (uint s = 0; s < sites && s < Mystery::kVisitedSiteCap; s++)
+ _mystery._onSites[s] = 1;
+ }
if (startSite < Mystery::kVisitedSiteCap)
_mystery._onSites[startSite] = 1;
_mystery._siteNumber = startSite;
@@ -1471,7 +1476,8 @@ void EEMEngine::displayFloppyBriefing(const byte *initBlock) {
}
void EEMEngine::displayFloppyHotspotDialog(uint siteNum, uint hotIdx) {
- if (!_mystery.isLoaded() || !isFloppy())
+ const bool mac = isMacintosh();
+ if (!_mystery.isLoaded() || (!isFloppy() && !mac))
return;
const byte *site = _mystery.siteData(siteNum);
if (!site)
@@ -1483,18 +1489,20 @@ void EEMEngine::displayFloppyHotspotDialog(uint siteNum, uint hotIdx) {
uint32 off = dlgListOff;
for (uint h = 0; h < hotIdx; h++) {
const byte *rec = bufBase + off;
- off += 11 + rec[10];
+ off += (mac ? 14 : 11) + rec[mac ? 13 : 10];
const uint contCount = bufBase[off] & 0x7F;
off += 1;
for (uint c = 0; c < contCount; c++) {
const byte *cr = bufBase + off;
- off += 11 + cr[10];
+ off += (mac ? 14 : 11) + cr[mac ? 13 : 10];
}
}
if (off >= _mystery.dataSize())
return;
- Graphics::ManagedSurface siteBG(kScreenWidth, kScreenHeight,
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ Graphics::ManagedSurface siteBG(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
{
Graphics::Surface *screen = g_system->lockScreen();
@@ -1504,7 +1512,7 @@ void EEMEngine::displayFloppyHotspotDialog(uint siteNum, uint hotIdx) {
}
}
const byte *mainRec = bufBase + off;
- const uint mainLen = 11u + (uint)mainRec[10];
+ const uint mainLen = (mac ? 14u : 11u) + (uint)mainRec[mac ? 13 : 10];
uint contCount = 0;
uint contFlagsByte = 0;
if (off + mainLen < _mystery.dataSize()) {
@@ -1524,7 +1532,7 @@ void EEMEngine::displayFloppyHotspotDialog(uint siteNum, uint hotIdx) {
return;
g_system->copyRectToScreen(siteBG.getPixels(), siteBG.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
displayFloppyDialogRecords(bufBase + contOff, contCount, 0);
}
diff --git a/engines/eem/graphics.cpp b/engines/eem/graphics.cpp
index 744121049be..6ded96af338 100644
--- a/engines/eem/graphics.cpp
+++ b/engines/eem/graphics.cpp
@@ -122,6 +122,7 @@ bool EEMEngine::floppyHotspotSearched(uint siteIdx, uint hotspotIdx) const {
const byte *site = _mystery.siteData(siteIdx);
if (!site)
return false;
+ const bool mac = isMacintosh();
const uint16 dlgListOff = READ_LE_UINT16(site + 6);
const byte *bufBase = _mystery.blobAt(0);
const uint32 dsz = _mystery.dataSize();
@@ -129,34 +130,44 @@ bool EEMEngine::floppyHotspotSearched(uint siteIdx, uint hotspotIdx) const {
return false;
uint32 off = dlgListOff;
for (uint h = 0; h < hotspotIdx; h++) {
- if (off + 10 >= dsz)
+ const uint textCountOff = mac ? off + 13 : off + 10;
+ if (textCountOff >= dsz)
return false;
- const uint32 mainLen = 11u + (uint)bufBase[off + 10];
+ const uint32 mainLen = (mac ? 14u : 11u) +
+ (uint)bufBase[textCountOff];
off += mainLen;
if (off >= dsz)
return false;
const uint contCount = (uint)(bufBase[off] & 0x7F);
off += 1;
for (uint c = 0; c < contCount; c++) {
- if (off + 10 >= dsz)
+ const uint contTextCountOff = mac ? off + 13 : off + 10;
+ if (contTextCountOff >= dsz)
return false;
- off += 11u + (uint)bufBase[off + 10];
+ off += (mac ? 14u : 11u) +
+ (uint)bufBase[contTextCountOff];
if (off >= dsz)
return false;
}
}
- if (off + 10 >= dsz)
+ const uint textCountOff = mac ? off + 13 : off + 10;
+ if (textCountOff >= dsz)
return false;
- const uint32 mainLen = 11u + (uint)bufBase[off + 10];
+ const uint32 mainLen = (mac ? 14u : 11u) + (uint)bufBase[textCountOff];
const uint32 contFlagsOff = off + mainLen;
if (contFlagsOff >= dsz)
return false;
uint32 searchedRecOff = off;
if ((bufBase[contFlagsOff] & 0x7F) != 0)
searchedRecOff = contFlagsOff + 1;
- if (searchedRecOff + 11 >= dsz || bufBase[searchedRecOff + 10] == 0)
+ const uint searchedTextCountOff = mac ? searchedRecOff + 13
+ : searchedRecOff + 10;
+ const uint searchedTextIdxOff = mac ? searchedRecOff + 14
+ : searchedRecOff + 11;
+ if (searchedTextIdxOff >= dsz || searchedTextCountOff >= dsz ||
+ bufBase[searchedTextCountOff] == 0)
return false;
- const uint8 textIdx = bufBase[searchedRecOff + 11] & 0x7F;
+ const uint8 textIdx = bufBase[searchedTextIdxOff] & 0x7F;
return textIdx < EEM::Mystery::kCluesFoundCap &&
_mystery._cluesFound[textIdx] != 0;
}
diff --git a/engines/eem/mystery.cpp b/engines/eem/mystery.cpp
index 65e2a87c3fc..b9937e4cee4 100644
--- a/engines/eem/mystery.cpp
+++ b/engines/eem/mystery.cpp
@@ -87,6 +87,58 @@ static uint16 readBE16(const Common::Array<byte> &data, uint offset) {
return READ_BE_UINT16(data.data() + offset);
}
+static void normalizeMacSiteData(Common::Array<byte> &data,
+ const uint16 *section) {
+ if (section[2] >= data.size() || section[3] >= data.size())
+ return;
+
+ const uint numSites = MIN<uint>((uint)data[section[2]],
+ Mystery::kVisitedSiteCap);
+ Common::Array<uint16> siteOffsets;
+ siteOffsets.resize(numSites);
+ for (uint i = 0; i < numSites; i++)
+ siteOffsets[i] = readBE16(data, section[3] + i * 2);
+
+ // The site index itself is a table of big-endian offsets.
+ swapU16Range(data, section[3], section[3] + numSites * 2);
+
+ for (uint i = 0; i < numSites; i++) {
+ const uint16 siteOff = siteOffsets[i];
+ if (siteOff + 12 > data.size())
+ continue;
+
+ const uint16 dropsOff = readBE16(data, siteOff + 0);
+ const uint16 hotspotOff = readBE16(data, siteOff + 4);
+ const uint16 speakerOff = readBE16(data, siteOff + 8);
+ const uint16 nextSiteOff = readBE16(data, siteOff + 10);
+
+ // Compact site records are six offset words.
+ swapU16Range(data, siteOff, siteOff + 12);
+
+ // Drop blocks keep byte[0]=sitepic and byte[1]=count, then Mac uses
+ // count x {u16 pic, u16 x, u16 y}.
+ if (dropsOff + 2 <= data.size()) {
+ const uint count = data[dropsOff + 1];
+ swapU16Range(data, dropsOff + 2,
+ dropsOff + 2 + count * 6);
+ }
+
+ // Hotspot blocks keep byte[0]=count, then count x {u16 x1, y1, x2, y2}.
+ if (hotspotOff + 1 <= data.size()) {
+ const uint count = data[hotspotOff];
+ swapU16Range(data, hotspotOff + 1,
+ hotspotOff + 1 + count * 8);
+ }
+
+ // Speaker/pose blocks are word triples. They run until the next site
+ // data record (or the next major section for the final site).
+ uint speakerEnd = nextSiteOff;
+ if (speakerEnd <= speakerOff || speakerEnd > data.size())
+ speakerEnd = section[4];
+ swapU16Range(data, speakerOff, speakerEnd);
+ }
+}
+
static void normalizeMacMystery(Common::Array<byte> &data) {
if (data.size() <= 20)
return;
@@ -108,7 +160,7 @@ static void normalizeMacMystery(Common::Array<byte> &data) {
// section[7] are raw strings and must stay byte-exact.
swapU16Range(data, section[1], section[2]); // clue/site chain table
swapU16Range(data, section[2] + 1, section[3]); // counted map entries
- swapU16Range(data, section[3], section[4]); // site index + site data
+ normalizeMacSiteData(data, section);
swapU16Range(data, section[4], section[7]); // notebook index
swapU16Range(data, section[5] + 1, section[6]); // compact gallery metadata
swapU16Range(data, section[8], MIN<uint>(section[8] + 12, section[9]));
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index b06d208d0fe..9c58ed8a58d 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -849,9 +849,10 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
}
}
+ const bool compactSite = _vm->isFloppy() || _vm->isMacintosh();
uint16 sitepic = 0;
if (sd) {
- if (_vm->isFloppy()) {
+ if (compactSite) {
const uint16 dropsOff = READ_LE_UINT16(sd);
const byte *drops = _mystery->blobAt(dropsOff);
if (drops)
@@ -867,7 +868,7 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
renderBackground(siteNum);
if (playArrival) {
- if (_vm->isFloppy())
+ if (compactSite)
renderFloppyDrops(siteNum);
else
renderStaticDrops(siteNum);
@@ -883,7 +884,7 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
renderBackground(siteNum);
}
- if (_vm->isFloppy())
+ if (compactSite)
renderFloppyDrops(siteNum);
else
renderStaticDrops(siteNum);
@@ -906,7 +907,7 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
// _VisitedSite[_SiteNumber] = 1;
// }
// SiteIndex[+2..+3] = byte offset of entry-clue ClueBlock.
- if (firstVisit) {
+ if (firstVisit && !_vm->isMacintosh()) {
const byte *idx = _mystery->siteIndexEntry(siteNum);
if (idx) {
const uint16 clueOff = READ_LE_UINT16(idx + 2);
@@ -920,7 +921,7 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
_mystery->_visitedSite[siteNum] = 1;
// Dialog overlay leaves portrait/balloon residue; refresh & re-snapshot.
renderBackground(siteNum);
- if (_vm->isFloppy())
+ if (compactSite)
renderFloppyDrops(siteNum);
else
renderStaticDrops(siteNum);
@@ -1284,6 +1285,7 @@ void SiteScreen::renderFloppyDrops(uint siteNum) {
const byte *site = _mystery->siteData(siteNum);
if (!site)
return;
+ const bool mac = _vm && _vm->isMacintosh();
const uint16 dropsOff = READ_LE_UINT16(site);
const byte *drops = _mystery->blobAt(dropsOff);
if (!drops)
@@ -1295,10 +1297,11 @@ void SiteScreen::renderFloppyDrops(uint siteNum) {
return;
for (uint i = 0; i < count; i++) {
- const byte *e = drops + 2 + i * 5;
+ const byte *e = drops + 2 + i * (mac ? 6 : 5);
const uint16 picID = READ_LE_UINT16(e + 0);
const int16 x = (int16)READ_LE_UINT16(e + 2);
- const int16 y = (int16)e[4];
+ const int16 y = mac ? (int16)READ_LE_UINT16(e + 4)
+ : (int16)e[4];
if (picID == 0)
continue;
Picture pic;
@@ -1313,6 +1316,9 @@ void SiteScreen::renderAnimatedDrops(uint siteNum, uint32 tickMs) {
if (!_mystery)
return;
+ if (_vm && _vm->isMacintosh())
+ return;
+
if (_vm && _vm->isFloppy()) {
const byte *siteAnim = _mystery->floppySiteAnimData(siteNum);
if (!siteAnim)
@@ -1381,6 +1387,9 @@ void SiteScreen::scanColorCycles(uint siteNum) {
if (!_mystery)
return;
+ if (_vm && _vm->isMacintosh())
+ return;
+
if (_vm && _vm->isFloppy()) {
const byte *siteAnim = _mystery->floppySiteAnimData(siteNum);
if (!siteAnim)
@@ -1422,7 +1431,9 @@ void SiteScreen::applyColorCycles() {
}
void SiteScreen::captureBgSnapshot() {
- _bgSnapshot.create(kScreenWidth, kScreenHeight, Graphics::PixelFormat::createFormatCLUT8());
+ const int sw = _vm ? _vm->screenWidth() : kScreenWidth;
+ const int sh = _vm ? _vm->screenHeight() : kScreenHeight;
+ _bgSnapshot.create(sw, sh, Graphics::PixelFormat::createFormatCLUT8());
Graphics::Surface *screen = g_system->lockScreen();
if (!screen) {
_snapshotSite = -1;
@@ -1433,14 +1444,18 @@ void SiteScreen::captureBgSnapshot() {
}
void SiteScreen::restoreBgSnapshot() {
- if (_bgSnapshot.w != kScreenWidth || _bgSnapshot.h != kScreenHeight)
+ const int sw = _vm ? _vm->screenWidth() : kScreenWidth;
+ const int sh = _vm ? _vm->screenHeight() : kScreenHeight;
+ if (_bgSnapshot.w != sw || _bgSnapshot.h != sh)
return;
g_system->copyRectToScreen(_bgSnapshot.getPixels(), _bgSnapshot.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
}
void SiteScreen::syncCompositedScreen() {
- Graphics::ManagedSurface snapshot(kScreenWidth, kScreenHeight,
+ const int sw = _vm ? _vm->screenWidth() : kScreenWidth;
+ const int sh = _vm ? _vm->screenHeight() : kScreenHeight;
+ Graphics::ManagedSurface snapshot(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
Graphics::Surface *screen = g_system->lockScreen();
@@ -1450,7 +1465,7 @@ void SiteScreen::syncCompositedScreen() {
g_system->unlockScreen();
g_system->copyRectToScreen(snapshot.getPixels(), snapshot.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
}
bool SiteScreen::partnerIdleAnimParams(uint siteNum, uint16 &animId,
@@ -1459,7 +1474,16 @@ bool SiteScreen::partnerIdleAnimParams(uint siteNum, uint16 &animId,
if (!site)
return false;
const uint8 partner = _vm->getPartnerIndex();
- if (_vm->isFloppy()) {
+ if (_vm->isMacintosh()) {
+ const uint16 spkOff = READ_LE_UINT16(site + 8);
+ const byte *spk = _mystery->blobAt(spkOff);
+ if (!spk)
+ return false;
+ const uint poseOff = partner == 0 ? 0 : 6;
+ animId = READ_LE_UINT16(spk + poseOff + 0);
+ x = (int)READ_LE_UINT16(spk + poseOff + 2);
+ y = (int)READ_LE_UINT16(spk + poseOff + 4);
+ } else if (_vm->isFloppy()) {
const uint16 spkOff = READ_LE_UINT16(site + 8);
const byte *spk = _mystery->blobAt(spkOff);
if (!spk)
@@ -1521,7 +1545,7 @@ void SiteScreen::renderPartner(uint siteNum, uint32 tickMs) {
}
bool SiteScreen::renderFloppyHotspotPartnerPose(uint siteNum) {
- if (!_vm || !_vm->isFloppy() || !_mystery)
+ if (!_vm || (!_vm->isFloppy() && !_vm->isMacintosh()) || !_mystery)
return false;
const byte *site = _mystery->siteData(siteNum);
@@ -1529,9 +1553,11 @@ bool SiteScreen::renderFloppyHotspotPartnerPose(uint siteNum) {
return false;
const uint16 spkOff = READ_LE_UINT16(site + 8);
+ const bool mac = _vm->isMacintosh();
const uint poseOff = (_vm->getPartnerIndex() == kPartnerJake)
- ? 0x28 : 0x2d;
- if ((uint32)spkOff + poseOff + 5 > _mystery->dataSize())
+ ? (mac ? 0x30 : 0x28) : (mac ? 0x36 : 0x2d);
+ const uint poseSize = mac ? 6 : 5;
+ if ((uint32)spkOff + poseOff + poseSize > _mystery->dataSize())
return false;
const byte *pose = _mystery->blobAt((uint32)spkOff + poseOff);
@@ -1540,7 +1566,7 @@ bool SiteScreen::renderFloppyHotspotPartnerPose(uint siteNum) {
const uint16 animId = READ_LE_UINT16(pose + 0);
const int x = (int)READ_LE_UINT16(pose + 2);
- const int y = (int)pose[4];
+ const int y = mac ? (int)READ_LE_UINT16(pose + 4) : (int)pose[4];
Animation anim;
if (!_vm->getAni().loadAnimation(animId, anim) || anim.empty())
@@ -1567,7 +1593,7 @@ void SiteScreen::renderBackground(uint siteNum) {
const byte *site = _mystery->siteData(siteNum);
uint16 sitepic = 0;
if (site) {
- if (_vm->isFloppy()) {
+ if (_vm->isFloppy() || _vm->isMacintosh()) {
const uint16 dropsOff = READ_LE_UINT16(site);
const byte *drops = _mystery->blobAt(dropsOff);
if (drops)
@@ -1634,8 +1660,9 @@ void SiteScreen::renderHotspots(uint siteNum) {
// don't inherit the first site's seen state after travel/reload).
// Floppy = 8-byte plain rect only; searched state is derived by
// walking the dialog record list, like `_HotspotSearched_Floppy`.
+ const bool compact = _vm && (_vm->isFloppy() || _vm->isMacintosh());
const bool floppy = _vm && _vm->isFloppy();
- const uint stride = floppy ? 8 : 14;
+ const uint stride = compact ? 8 : 14;
// The floppy SITEPALS has at least one searchable site where 0xFF is
// yellow. The CD corrected that palette data; for floppy, draw with an
// existing white entry from the current palette instead of changing it.
@@ -1646,11 +1673,15 @@ void SiteScreen::renderHotspots(uint siteNum) {
const int16 y1 = (int16)READ_LE_UINT16(r + 2);
const int16 x2 = (int16)READ_LE_UINT16(r + 4);
const int16 y2 = (int16)READ_LE_UINT16(r + 6);
- const Common::Rect rect(MAX<int>(0, x1), MAX<int>(0, y1),
- MIN<int>(screen->w, x2),
- MIN<int>(screen->h, y2));
+ const int left = MAX<int>(0, x1);
+ const int top = MAX<int>(0, y1);
+ const int right = MIN<int>(screen->w, x2);
+ const int bottom = MIN<int>(screen->h, y2);
+ if (right <= left || bottom <= top)
+ continue;
+ const Common::Rect rect(left, top, right, bottom);
bool seen = false;
- if (floppy) {
+ if (compact) {
seen = _vm->floppyHotspotSearched(siteNum, i);
} else {
const uint seenKey = READ_LE_UINT16(r + 0xa);
@@ -1697,7 +1728,7 @@ int SiteScreen::hotspotAtPoint(uint siteNum, int x, int y) const {
if (!spots)
return -1;
- const uint stride = _vm && _vm->isFloppy() ? 8 : 14;
+ const uint stride = _vm && (_vm->isFloppy() || _vm->isMacintosh()) ? 8 : 14;
for (uint i = 0; i < count; i++) {
const byte *r = spots + i * stride;
const int16 x1 = (int16)READ_LE_UINT16(r + 0);
@@ -1713,7 +1744,7 @@ int SiteScreen::hotspotAtPoint(uint siteNum, int x, int y) const {
// CD hotspot row +0xc..d: cursor id for `_SwitchMouse` (EEM1 ships 0; EEM2
// uses 2/3 examine, etc.). Floppy rows are 8-byte rects with no cursor field.
int SiteScreen::hotspotCursorId(uint siteNum, int idx) const {
- if (idx < 0 || (_vm && _vm->isFloppy()))
+ if (idx < 0 || (_vm && (_vm->isFloppy() || _vm->isMacintosh())))
return 0;
const byte *spots = _mystery->hotspots(siteNum);
if (!spots || (uint)idx >= _mystery->hotspotCount(siteNum))
@@ -1777,7 +1808,7 @@ void SiteScreen::onHotspotClicked(uint siteNum, uint hotIdx) {
// Floppy: 8-byte rects only (no clue metadata @ +0xa/+8). Dialog
// records live in a separate list @ `site_data[+6]`.
- if (_vm->isFloppy()) {
+ if (_vm->isFloppy() || _vm->isMacintosh()) {
if (hotIdx < Mystery::kHotSpotsCap)
_mystery->_hotSpotsSeen[hotIdx] = 1;
_mystery->_searchLocationNumber = (uint16)hotIdx;
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index af55ce31d62..0c437773cb8 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -130,7 +130,7 @@ bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
if (macintosh) {
out.detailX = READ_LE_UINT16(entry + 0x0);
out.detailY = READ_LE_UINT16(entry + 0x2);
- out.buttonId = entry[0x4];
+ out.buttonId = entry[0x5];
out.overviewX = READ_LE_UINT16(entry + 0x6);
out.overviewY = READ_LE_UINT16(entry + 0x8);
out.crime = READ_LE_UINT16(entry + 0xa);
@@ -178,7 +178,11 @@ bool loadMacBigMapPixels(Common::Array<byte> &mapPixels,
mapPixels.resize((uint32)mapW * mapH);
for (uint y = 0; y < mapH; y++) {
const byte *src = (const byte *)mapPic.surface.getBasePtr(0, y);
- memcpy(mapPixels.data() + y * mapW, src, mapW);
+ byte *dst = mapPixels.data() + y * mapW;
+ // BIGMAP.DBD uses 0xff for dark coast/detail pixels, but the Mac
+ // detail-map palette keeps 0xff white for the UI frame/buttons.
+ for (uint x = 0; x < mapW; x++)
+ dst[x] = src[x] == 0xff ? 0x00 : src[x];
}
return true;
}
@@ -3223,6 +3227,12 @@ void EEMEngine::doBigMap() {
if (!_mystery.isLoaded())
return;
+ if (isMacintosh()) {
+ for (uint i = 0; i < _mystery.numSites() &&
+ i < Mystery::kVisitedSiteCap; i++)
+ _mystery._onSites[i] = 1;
+ }
+
if (isLondon()) {
_mystery._pendingSiteJump = 0;
_mystery._siteReturnDepth = 0;
Commit: 5b53d52b056ad25361c9586911065d4de62694d5
https://github.com/scummvm/scummvm/commit/5b53d52b056ad25361c9586911065d4de62694d5
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: correctly parse hotspots in EEM1 Mac
Changed paths:
engines/eem/music.cpp
engines/eem/site.cpp
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index fe5a5e5e0b0..988dd7ad949 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -36,6 +36,7 @@
namespace EEM {
const int kMidiDriverFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
+const int kMacMidiDriverFlags = MDT_MIDI | MDT_PREFER_GM;
const uint16 kInvalidMacMidiResource = 0xffff;
Common::String musicNameFromPath(const Common::Path &path) {
@@ -87,14 +88,16 @@ MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
// ADLIB.ADV / SBFM.ADV / MT32MPU.ADV. We honour the launcher's
// "Music driver" setting and prefer MT-32 when unset.
const MidiDriver::DeviceHandle dev =
- MidiDriver::detectDevice(kMidiDriverFlags);
+ MidiDriver::detectDevice(_isMacintosh ? kMacMidiDriverFlags
+ : kMidiDriverFlags);
MusicType musicType = MidiDriver::getMusicType(dev);
- if (musicType == MT_GM && ConfMan.getBool("native_mt32"))
+ if (!_isMacintosh && musicType == MT_GM &&
+ ConfMan.getBool("native_mt32"))
musicType = MT_MT32;
if (_isMacintosh) {
_milesAudioMode = false;
- createDriver(kMidiDriverFlags);
+ createDriver(kMacMidiDriverFlags);
} else {
switch (musicType) {
case MT_ADLIB:
@@ -126,7 +129,9 @@ MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
_driver = nullptr;
} else {
// Miles AdLib handles its own reset.
- if (_isMacintosh || musicType != MT_ADLIB) {
+ if (_isMacintosh) {
+ _driver->sendGMReset();
+ } else if (musicType != MT_ADLIB) {
if (musicType == MT_MT32 || _nativeMT32)
_driver->sendMT32Reset();
else
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index 9c58ed8a58d..b0bac98637d 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -119,6 +119,32 @@ void blitAnimFrameAnchored(Graphics::Surface *screen, const Picture &p,
anchorY - (int)(int16)p.rowoff);
}
+bool readHotspotRect(const byte *r, bool mac, Common::Rect &rect) {
+ if (mac) {
+ const int16 top = (int16)READ_LE_UINT16(r + 0);
+ const int16 left = (int16)READ_LE_UINT16(r + 2);
+ const int16 bottom = (int16)READ_LE_UINT16(r + 4);
+ const int16 right = (int16)READ_LE_UINT16(r + 6);
+ if (right <= left || bottom <= top)
+ return false;
+ rect = Common::Rect(left, top, right, bottom);
+ return true;
+ }
+
+ const int16 x1 = (int16)READ_LE_UINT16(r + 0);
+ const int16 y1 = (int16)READ_LE_UINT16(r + 2);
+ const int16 x2 = (int16)READ_LE_UINT16(r + 4);
+ const int16 y2 = (int16)READ_LE_UINT16(r + 6);
+ if (x2 <= x1 || y2 <= y1)
+ return false;
+ rect = Common::Rect(x1, y1, x2, y2);
+ return true;
+}
+
+Common::Rect siteControlRect(const EEMEngine *vm, const Common::Rect &rect) {
+ return (vm && vm->isMacintosh()) ? vm->scaleRect(rect) : rect;
+}
+
// `_ColorCycle @ 172b:2015` â rotate `_fpal[start..end]` by one slot:
// save [start], shift [start..end-1] = [start+1..end], restore saved at
// [end], then re-upload via `_Set_Palette`.
@@ -980,6 +1006,12 @@ void SiteScreen::run() {
if (_mystery->_siteReturnDepth > Mystery::kVisitedSiteCap)
_mystery->_siteReturnDepth = 0;
_snapshotSite = -1;
+ const Common::Rect pdaSiteRect =
+ siteControlRect(_vm, kPdaSiteRect);
+ const Common::Rect pdaPartnerFootMapRect =
+ siteControlRect(_vm, kPdaPartnerFootMapRect);
+ const Common::Rect pdaPartnerHeadHintRect =
+ siteControlRect(_vm, kPdaPartnerHeadHintRect);
SiteBackendActionObserverRegistration backendActionRegistration(this);
enter(cur);
Common::Point mouse = g_system->getEventManager()->getMousePos();
@@ -1000,14 +1032,15 @@ void SiteScreen::run() {
break;
case Common::EVENT_LBUTTONDOWN: {
- if (kPdaSiteRect.contains(event.mouse.x, event.mouse.y)) {
+ if (pdaSiteRect.contains(event.mouse.x, event.mouse.y)) {
notePartnerActivity();
_vm->setHotspotMouseCursor(false);
_vm->setNextScreen(kScreenNotebook);
_vm->stopMusic();
return;
}
- if (kPdaPartnerFootMapRect.contains(event.mouse.x, event.mouse.y)) {
+ if (pdaPartnerFootMapRect.contains(event.mouse.x,
+ event.mouse.y)) {
notePartnerActivity();
_vm->setHotspotMouseCursor(false);
if (_vm->isLondon() && _mystery->_siteReturnDepth != 0) {
@@ -1035,7 +1068,8 @@ void SiteScreen::run() {
_vm->stopMusic();
return;
}
- if (kPdaPartnerHeadHintRect.contains(event.mouse.x, event.mouse.y)) {
+ if (pdaPartnerHeadHintRect.contains(event.mouse.x,
+ event.mouse.y)) {
_vm->setHotspotMouseCursor(false);
_vm->doHelp();
notePartnerActivity();
@@ -1136,12 +1170,15 @@ bool SiteScreen::enterSiteAnim() {
const uint8 partner = _vm->getPartnerIndex();
const uint kSkateAni = (partner == 0) ? 6 : 0xe;
const uint kKDAni = (partner == 0) ? 7 : 0xf;
- const int kKDY = (partner == 0) ? 0x8b : 0x8e;
+ const int baseKDY = (partner == 0) ? 0x8b : 0x8e;
+ const int kKDY = _vm->isMacintosh() ? _vm->scaleY(baseKDY) : baseKDY;
+ const int sw = _vm->screenWidth();
+ const int sh = _vm->screenHeight();
Graphics::Surface *screen = g_system->lockScreen();
if (!screen)
return false;
- Graphics::ManagedSurface bg(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface bg(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
bg.simpleBlitFrom(*screen);
g_system->unlockScreen();
@@ -1155,13 +1192,13 @@ bool SiteScreen::enterSiteAnim() {
for (uint frameIdx = 0;
frameIdx < anim.size() && !_vm->shouldQuit();
frameIdx++) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(bg);
blitAnimFrameAnchored(scratch.surfacePtr(), anim[frameIdx],
0, anchorY);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
Common::Event ev;
@@ -1180,21 +1217,21 @@ bool SiteScreen::enterSiteAnim() {
if (_vm->getAni().loadAnimation(kSkateAni, skate) && !skate.empty()) {
const int spriteH = skate[0].surface.h;
const int spriteW = skate[0].surface.w;
- int x = (kScreenWidth - spriteW) & ~3; // 4-px aligned (mode-X)
- const int y = 199 - spriteH;
+ int x = (sw - spriteW) & ~3; // 4-px aligned (mode-X)
+ const int y = (_vm->isMacintosh() ? sh : 199) - spriteH;
const byte transp = (byte)(skate[0].flags >> 8);
uint frameIdx = 0;
int distSinceTick = 0;
- const int kStep = 4; // _MoveSkateBoardPixels analogue
- const int kFrameTicks = 0xc; // original switches frame at 12 px
+ const int kStep = _vm->isMacintosh() ? _vm->scaleX(4) : 4;
+ const int kFrameTicks = _vm->isMacintosh() ? _vm->scaleX(0xc) : 0xc;
while (x + spriteW > 0 && !_vm->shouldQuit()) {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(bg);
blitFrame(scratch, skate[frameIdx], x, y, transp);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
Common::Event ev;
@@ -1225,12 +1262,12 @@ bool SiteScreen::enterSiteAnim() {
const int destX = -(int)(int16)fr.miscflags;
const int destY = kKDY - (int)(int16)fr.rowoff;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(bg);
blitFrame(scratch, fr, destX, destY, transp);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
Common::Event ev;
@@ -1662,6 +1699,7 @@ void SiteScreen::renderHotspots(uint siteNum) {
// walking the dialog record list, like `_HotspotSearched_Floppy`.
const bool compact = _vm && (_vm->isFloppy() || _vm->isMacintosh());
const bool floppy = _vm && _vm->isFloppy();
+ const bool mac = _vm && _vm->isMacintosh();
const uint stride = compact ? 8 : 14;
// The floppy SITEPALS has at least one searchable site where 0xFF is
// yellow. The CD corrected that palette data; for floppy, draw with an
@@ -1669,17 +1707,12 @@ void SiteScreen::renderHotspots(uint siteNum) {
const byte searchedColor = floppy ? currentWhitePaletteIndex(0xFF) : 0xFF;
for (uint i = 0; i < count; i++) {
const byte *r = spots + i * stride;
- const int16 x1 = (int16)READ_LE_UINT16(r + 0);
- const int16 y1 = (int16)READ_LE_UINT16(r + 2);
- const int16 x2 = (int16)READ_LE_UINT16(r + 4);
- const int16 y2 = (int16)READ_LE_UINT16(r + 6);
- const int left = MAX<int>(0, x1);
- const int top = MAX<int>(0, y1);
- const int right = MIN<int>(screen->w, x2);
- const int bottom = MIN<int>(screen->h, y2);
- if (right <= left || bottom <= top)
+ Common::Rect rect;
+ if (!readHotspotRect(r, mac, rect))
+ continue;
+ rect = rect.findIntersectingRect(Common::Rect(screen->w, screen->h));
+ if (rect.isEmpty())
continue;
- const Common::Rect rect(left, top, right, bottom);
bool seen = false;
if (compact) {
seen = _vm->floppyHotspotSearched(siteNum, i);
@@ -1729,13 +1762,11 @@ int SiteScreen::hotspotAtPoint(uint siteNum, int x, int y) const {
return -1;
const uint stride = _vm && (_vm->isFloppy() || _vm->isMacintosh()) ? 8 : 14;
+ const bool mac = _vm && _vm->isMacintosh();
for (uint i = 0; i < count; i++) {
const byte *r = spots + i * stride;
- const int16 x1 = (int16)READ_LE_UINT16(r + 0);
- const int16 y1 = (int16)READ_LE_UINT16(r + 2);
- const int16 x2 = (int16)READ_LE_UINT16(r + 4);
- const int16 y2 = (int16)READ_LE_UINT16(r + 6);
- if (x >= x1 && x < x2 && y >= y1 && y < y2)
+ Common::Rect rect;
+ if (readHotspotRect(r, mac, rect) && rect.contains(x, y))
return (int)i;
}
return -1;
@@ -1756,9 +1787,9 @@ void SiteScreen::updateHotspotCursor(uint siteNum, int x, int y) {
if (!_vm)
return;
const int idx = hotspotAtPoint(siteNum, x, y);
- const bool siteControl = kPdaSiteRect.contains(x, y) ||
- kPdaPartnerFootMapRect.contains(x, y) ||
- kPdaPartnerHeadHintRect.contains(x, y);
+ const bool siteControl = siteControlRect(_vm, kPdaSiteRect).contains(x, y) ||
+ siteControlRect(_vm, kPdaPartnerFootMapRect).contains(x, y) ||
+ siteControlRect(_vm, kPdaPartnerHeadHintRect).contains(x, y);
if (_vm->isLondon()) {
// EEM2 gives some hotspots their own cursor shape; every other
// interactive zone (the site/foot/head controls, or a hotspot with no
Commit: 7bf463b4830c377b8f09627938da13f2a006f359
https://github.com/scummvm/scummvm/commit/7bf463b4830c377b8f09627938da13f2a006f359
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: load all the cases from EEM1 Mac
Changed paths:
engines/eem/eem.cpp
engines/eem/mystery.cpp
engines/eem/mystery.h
engines/eem/ui.cpp
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 939158f95b1..a3a762b7c2a 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -332,12 +332,14 @@ static void addMacResourceSearchPaths() {
return;
const Common::FSNode childRsrcDir = gameDataDir.getChild("rsrc");
- if (childRsrcDir.exists() && childRsrcDir.isDirectory())
+ if (childRsrcDir.exists() && childRsrcDir.isDirectory() &&
+ !SearchMan.hasArchive("eem-mac-rsrc-child"))
SearchMan.addDirectory("eem-mac-rsrc-child", childRsrcDir);
const Common::FSNode siblingRsrcDir =
gameDataDir.getParent().getChild("rsrc");
- if (siblingRsrcDir.exists() && siblingRsrcDir.isDirectory())
+ if (siblingRsrcDir.exists() && siblingRsrcDir.isDirectory() &&
+ !SearchMan.hasArchive("eem-mac-rsrc-sibling"))
SearchMan.addDirectory("eem-mac-rsrc-sibling", siblingRsrcDir);
}
diff --git a/engines/eem/mystery.cpp b/engines/eem/mystery.cpp
index b9937e4cee4..d8dbd96a2eb 100644
--- a/engines/eem/mystery.cpp
+++ b/engines/eem/mystery.cpp
@@ -139,6 +139,31 @@ static void normalizeMacSiteData(Common::Array<byte> &data,
}
}
+static void normalizeMacGalleryData(Common::Array<byte> &data,
+ uint16 start, uint16 end) {
+ if (start >= data.size() || end > data.size() || start >= end)
+ return;
+
+ const byte *bufEnd = data.data() + end;
+ byte *p = data.data() + start + 1;
+ const uint count = data[start];
+ for (uint i = 0; i < count; i++) {
+ if (p + 5 > bufEnd)
+ return;
+ byte tmp = p[0];
+ p[0] = p[1];
+ p[1] = tmp; // pic id
+ tmp = p[2];
+ p[2] = p[3];
+ p[3] = tmp; // alibi text offset
+ const uint clueCount = p[4];
+ p += 5;
+ if (p + clueCount > bufEnd)
+ return;
+ p += clueCount;
+ }
+}
+
static void normalizeMacMystery(Common::Array<byte> &data) {
if (data.size() <= 20)
return;
@@ -162,7 +187,7 @@ static void normalizeMacMystery(Common::Array<byte> &data) {
swapU16Range(data, section[2] + 1, section[3]); // counted map entries
normalizeMacSiteData(data, section);
swapU16Range(data, section[4], section[7]); // notebook index
- swapU16Range(data, section[5] + 1, section[6]); // compact gallery metadata
+ normalizeMacGalleryData(data, section[5], section[6]);
swapU16Range(data, section[8], MIN<uint>(section[8] + 12, section[9]));
swapU16Range(data, section[9], data.size()); // solved clue block
}
@@ -597,6 +622,9 @@ bool Mystery::noteHasNotebookText(uint clueId) const {
if (!ni || clueId >= cnt)
return false;
+ if (_isMacintosh)
+ return READ_LE_UINT16(ni + clueId * 8) != 0;
+
if (!_isFloppy)
return true;
@@ -637,13 +665,13 @@ const byte *Mystery::mapEntry(uint siteNum) const {
}
const byte *Mystery::floppySuspectEntry(uint suspectIdx) const {
- // Floppy gallery section (_DrawGallery_Floppy @ 154e:00b6).
- // byte[0]=numSuspects, then per suspect a `5 + nameLen` record:
- // u16 +0 picID (BUTTON.DBD entry)
- // u16 +2 alibi marker (0xFFFF=guilty; else hi byte indexes TEXT_BLOCK)
- // u8 +4 nameLen
- // u8 +5.. name string
- if (!_isFloppy || !isLoaded())
+ // Floppy gallery section (_DrawGallery_Floppy @ 154e:00b6) and Mac
+ // compact gallery section:
+ // u16 +0 picID
+ // u16 +2 alibi marker/text offset (0xFFFF = guilty)
+ // u8 +4 clue/name count
+ // u8 +5.. clue ids (Mac) or name bytes (floppy)
+ if ((!_isFloppy && !_isMacintosh) || !isLoaded())
return nullptr;
const byte *gd = _data.data() + _galleryOffset;
if (gd + 1 > _data.data() + _data.size())
@@ -664,7 +692,7 @@ const byte *Mystery::floppySuspectEntry(uint suspectIdx) const {
}
bool Mystery::isGuilty(uint suspectIdx) const {
- if (_isFloppy) {
+ if (_isFloppy || _isMacintosh) {
const byte *e = floppySuspectEntry(suspectIdx);
return e && READ_LE_UINT16(e + 2) == 0xFFFF;
}
@@ -676,6 +704,12 @@ bool Mystery::isGuilty(uint suspectIdx) const {
}
uint16 Mystery::alibiTextOffset(uint suspectIdx) const {
+ if (_isMacintosh) {
+ const byte *e = floppySuspectEntry(suspectIdx);
+ if (!e)
+ return 0xFFFF;
+ return READ_LE_UINT16(e + 2);
+ }
if (_isFloppy) {
const byte *e = floppySuspectEntry(suspectIdx);
if (!e)
@@ -749,7 +783,9 @@ int Mystery::selectedPoints() const {
if (!_noteSelected[i])
continue;
// CD NoteIndex entry: 4 bytes = u16 textOff + u16 points (at +2).
- const uint16 pts = READ_LE_UINT16(ni + i * 4 + 2);
+ // Mac rows are 8 bytes: notebook text, Jake text, Jenny text, points.
+ const uint16 pts = _isMacintosh ? READ_LE_UINT16(ni + i * 8 + 6)
+ : READ_LE_UINT16(ni + i * 4 + 2);
total += (int)(int16)pts;
}
return total;
@@ -820,8 +856,10 @@ int Mystery::foundPoints() const {
for (uint i = 0; i < maxIdx; i++) {
if (_cluesFound[i] == 0)
continue;
- const uint16 pts = _isFloppy ? ni[i * 7 + 6]
- : READ_LE_UINT16(ni + i * 4 + 2);
+ const uint16 pts = _isFloppy
+ ? ni[i * 7 + 6]
+ : (_isMacintosh ? READ_LE_UINT16(ni + i * 8 + 6)
+ : READ_LE_UINT16(ni + i * 4 + 2));
scores[scoreCount++] = pts;
}
diff --git a/engines/eem/mystery.h b/engines/eem/mystery.h
index ca10d929e1b..99ee5cbf7ff 100644
--- a/engines/eem/mystery.h
+++ b/engines/eem/mystery.h
@@ -77,12 +77,13 @@ public:
/// InitBlock (case briefing) at mystery + word[0].
const byte *initBlock() const;
- /// GalleryData: 0x46-byte entry per suspect; first u16 = PIC picture ID.
+ /// GalleryData. DOS CD uses 0x46-byte entries; floppy/Mac use compact
+ /// variable-stride entries accessed through floppySuspectEntry().
const byte *galleryData() const;
- /// Floppy variable-stride suspect record. Returns nullptr on CD or
- /// out-of-range. Layout: u16 picID, u16 alibiMarker (0xFFFF=guilty),
- /// u8 nameLen, nameLen bytes of name.
+ /// Compact variable-stride suspect record for floppy/Mac. Returns nullptr
+ /// on CD or out-of-range. Layout: u16 picID, u16 alibiMarker
+ /// (0xFFFF=guilty), u8 count, count bytes of name/clue data.
const byte *floppySuspectEntry(uint suspectIdx) const;
/// NoteIndex array. EEM1 CD: 4 bytes/entry (u16 textOff + u16 pts).
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 0c437773cb8..2dd491ef025 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -113,6 +113,38 @@ void blitBigMapMarker(Graphics::ManagedSurface &dstSurface, const Picture &marke
}
}
+void blitMacBigMapPartnerFrame(Graphics::ManagedSurface &dstSurface,
+ const Picture &frame, int anchorX,
+ int anchorY) {
+ const byte transp = (byte)(frame.flags >> 8);
+ const int x = anchorX - (int)(int16)frame.miscflags;
+ const int y = anchorY - (int)(int16)frame.rowoff;
+ for (int row = 0; row < frame.surface.h; row++) {
+ const int dstY = y + row;
+ if (dstY < 0 || dstY >= dstSurface.h)
+ continue;
+ const byte *src = (const byte *)frame.surface.getBasePtr(0, row);
+ byte *dst = (byte *)dstSurface.getBasePtr(0, dstY);
+ for (int col = 0; col < frame.surface.w; col++) {
+ const int dstX = x + col;
+ if (dstX < 0 || dstX >= dstSurface.w)
+ continue;
+ const byte color = src[col];
+ if (color != transp) {
+ // The map partner frames are authored against the overview
+ // ColorTable: 0 is white and 0xff is black. The detail-map
+ // ColorTable swaps those endpoints.
+ if (color == 0x00)
+ dst[dstX] = 0xff;
+ else if (color == 0xff)
+ dst[dstX] = 0x00;
+ else
+ dst[dstX] = color;
+ }
+ }
+ }
+}
+
struct BigMapEntryInfo {
uint16 overviewX = 0;
uint16 overviewY = 0;
@@ -693,8 +725,100 @@ struct ActionMenuView {
uint numPicks;
};
+Common::StringArray loadMacBookNames(uint book) {
+ // The Mac release embeds the chooser strings in the application resource
+ // data instead of shipping BOOK*.NME files.
+ static const char *const kMacBook1Names[] = {
+ "Case of the Stolen Skateboard",
+ "Case of the Roundabout Robber",
+ "Case of the Runaway Reptile",
+ "Case of the Mysterious Monster",
+ "Case of the Rock Ripoff",
+ "Case of the Pilfered Pop",
+ "Case of the Creepy Cinema",
+ "Case of the Angry Arsonist",
+ "Case of the Crazy Compass",
+ "Case of the Midnight Masquerade",
+ "Case of the Missing Mink",
+ "Case of the Basketball Blooper",
+ "Case of the Reappearing Recipe",
+ "Case of the Attacking Aliens",
+ "Case of the Dangling Diamond",
+ "Case of the Buried Booty",
+ "Case of the Questionable Quiz",
+ "Case of the Cryptic Cavern",
+ "Case of the International Idol",
+ "Case of the Counterfeit Card",
+ "Case of the Puzzling Pooches",
+ "Case of the Baffling Bones",
+ "Case of the Vanishing Violin",
+ "Case of the Antique Autograph",
+ };
+ static const char *const kMacBook2Names[] = {
+ "Case of the Stolen Skateboard",
+ "Case of the Angry Arsonist",
+ "Case of the Roundabout Robber",
+ "Case of the Cryptic Cavern",
+ "Case of the Missing Mink",
+ "Case of the International Idol",
+ "Case of the Runaway Reptile",
+ "Case of the Buried Booty",
+ "Case of the Midnight Masquerade",
+ "Case of the Pilfered Pop",
+ "Case of the Rock Ripoff",
+ "Case of the Basketball Blooper",
+ "Case of the Questionable Quiz",
+ "Case of the Baffling Bones",
+ "Case of the Vanishing Violin",
+ "Case of the Dangling Diamond",
+ "Case of the Puzzling Pooches",
+ "Case of the Mysterious Monster",
+ "Case of the Attacking Aliens",
+ "Case of the Antique Autograph",
+ "Case of the Reappearing Recipe",
+ "Case of the Creepy Cinema",
+ "Case of the Crazy Compass",
+ "Case of the Counterfeit Card",
+ };
+ static const char *const kMacBook3Names[] = {
+ "Case of the Midnight Masquerade",
+ "Case of the Puzzled Pooches",
+ "Case of the Buried Booty",
+ "Case of the International Idol",
+ "Case of the Antique Autograph",
+ "Case of the Attacking Aliens",
+ };
+
+ const char *const *src = nullptr;
+ uint count = 0;
+ switch (book) {
+ case 1:
+ src = kMacBook1Names;
+ count = ARRAYSIZE(kMacBook1Names);
+ break;
+ case 2:
+ src = kMacBook2Names;
+ count = ARRAYSIZE(kMacBook2Names);
+ break;
+ case 3:
+ src = kMacBook3Names;
+ count = ARRAYSIZE(kMacBook3Names);
+ break;
+ default:
+ break;
+ }
+
+ Common::StringArray names;
+ for (uint i = 0; i < count; i++)
+ names.push_back(src[i]);
+ return names;
+}
+
// `_DoChooseMystery @ 1a35:02b7`
-Common::StringArray loadBookNames(uint book) {
+Common::StringArray loadBookNames(uint book, bool macintosh) {
+ if (macintosh)
+ return loadMacBookNames(book);
+
Common::StringArray names;
const Common::String fname = Common::String::format("BOOK%u.NME", book);
Common::File f;
@@ -2222,7 +2346,7 @@ void EEMEngine::doCaseSelection() {
if (stageHi > kMaxMystery)
stageHi = kMaxMystery;
- const Common::StringArray names = loadBookNames(book);
+ const Common::StringArray names = loadBookNames(book, isMacintosh());
if (names.empty()) {
warning("doCaseSelection: BOOK%u.NME failed to load", book);
return;
@@ -2617,6 +2741,17 @@ Common::String EEMEngine::notebookNoteText(uint clueId, const byte *ni,
return parseString(Common::String(p, len),
_playerName, _partner);
}
+ if (isMacintosh() && bufBase) {
+ const uint16 textOff = READ_LE_UINT16(ni + clueId * 8);
+ if (textOff == 0 || textOff >= mysSz)
+ return Common::String();
+ const char *p = (const char *)(bufBase + textOff);
+ uint32 len = 0;
+ while (textOff + len < mysSz && p[len] != 0)
+ len++;
+ return parseString(Common::String(p, len),
+ _playerName, _partner);
+ }
const uint stride = isLondon() ? 2 : 4;
const uint16 textOff = READ_LE_UINT16(ni + clueId * stride);
return parseString(_mystery.textAt(textOff),
@@ -2890,13 +3025,14 @@ void EEMEngine::doGallery() {
bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
const Picture &galBg, bool haveBg) {
const bool floppyMI = isFloppy();
- const byte *suspect = floppyMI
+ const bool compactMI = floppyMI || isMacintosh();
+ const byte *suspect = compactMI
? _mystery.floppySuspectEntry(suspectIdx)
: gd + suspectIdx * 0x46;
if (!suspect)
return false;
const uint16 detailPic = READ_LE_UINT16(suspect + 0);
- const uint clueCount = floppyMI
+ const uint clueCount = compactMI
? (uint)suspect[4]
: READ_LE_UINT16(suspect + 8);
@@ -2905,7 +3041,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
const int rx = 78, ry = 93;
const int rw = 288 - 78, rh = 152 - 93;
const int lineH = _font.getFontHeight();
- const uint clueMax = floppyMI ? clueCount : 30u;
+ const uint clueMax = compactMI ? clueCount : 30u;
const byte *ni = _mystery.noteIndex();
const uint16 niCount = isLondon()
? (uint16)(_mystery.noteSectionSize() / 2)
@@ -2940,10 +3076,10 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
uint k = pageStart;
bool reachedEnd = false;
for (; k < clueCount && k < clueMax; k++) {
- const uint16 clueId = floppyMI
+ const uint16 clueId = compactMI
? (uint16)suspect[5 + k]
: READ_LE_UINT16(suspect + 0xa + k * 2);
- if (!floppyMI && clueId == 0xFFFF) {
+ if (!compactMI && clueId == 0xFFFF) {
reachedEnd = true;
break;
}
@@ -3152,6 +3288,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
g_system->getMillis());
const bool floppy = isFloppy();
+ const bool compactGallery = floppy || isMacintosh();
const GallerySlot * const slots =
floppy ? kFloppyGallerySlots : kGallerySlots;
for (uint i = 0; i < numSuspects && i < Mystery::kGalleryCap; i++) {
@@ -3165,7 +3302,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
const bool discovered = _mystery._inGallery[phys] != 0;
if (discovered) {
- const byte *entry = floppy
+ const byte *entry = compactGallery
? _mystery.floppySuspectEntry(i)
: gd + i * 0x46;
if (!entry)
@@ -3926,10 +4063,15 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
!detailAnim.empty()) {
const uint frameIdx = bigMapDetailPartnerFrameAtTick(
(uint)detailAnim.size(), elapsedMs);
- blitAnimFrameAnchored(scratch.surfacePtr(),
- detailAnim[frameIdx],
- mac ? scaleX(0x101) : 0x101,
- mac ? scaleY(0x50) : 0x50);
+ const int anchorX = mac ? scaleX(0x101) : 0x101;
+ const int anchorY = mac ? scaleY(0x50) : 0x50;
+ if (mac)
+ blitMacBigMapPartnerFrame(scratch, detailAnim[frameIdx],
+ anchorX, anchorY);
+ else
+ blitAnimFrameAnchored(scratch.surfacePtr(),
+ detailAnim[frameIdx],
+ anchorX, anchorY);
}
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
@@ -3955,6 +4097,14 @@ Common::String EEMEngine::accuseNoteText(uint clueId,
(const char *)(ctx.bufBaseNotes + textOff),
_playerName, _partner);
}
+ if (isMacintosh() && ctx.bufBaseNotes) {
+ const uint16 textOff = READ_LE_UINT16(ctx.ni + clueId * 8);
+ if (textOff == 0 || textOff >= _mystery.dataSize())
+ return Common::String();
+ return parseString(
+ (const char *)(ctx.bufBaseNotes + textOff),
+ _playerName, _partner);
+ }
const uint stride = isLondon() ? 2 : 4;
const uint16 textOff = READ_LE_UINT16(ctx.ni + clueId * stride);
return parseString(_mystery.textAt(textOff),
@@ -4698,9 +4848,10 @@ void EEMEngine::doAccuse() {
Picture alibiBg;
const bool haveAlibiBg = _picsArchive.getPicture(0x3e, alibiBg);
Picture suspect;
- const uint16 picId = gd
- ? READ_LE_UINT16(gd + (uint)picked * 0x46)
- : 0;
+ const byte *pickedSuspect = isMacintosh()
+ ? _mystery.floppySuspectEntry((uint)picked)
+ : (gd ? gd + (uint)picked * 0x46 : nullptr);
+ const uint16 picId = pickedSuspect ? READ_LE_UINT16(pickedSuspect) : 0;
const bool haveSuspect = picId != 0 &&
_picsArchive.getPicture(picId, suspect);
Picture balloon;
@@ -4788,7 +4939,7 @@ void EEMEngine::doAccuse() {
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, kScreenWidth, kScreenHeight);
g_system->updateScreen();
- if (_audio && gd) {
+ if (_audio && gd && !isMacintosh()) {
const uint16 alibiVoice =
READ_LE_UINT16(gd + (uint)picked * 0x46 + 0x00);
const uint16 jakeVoice =
@@ -5550,7 +5701,12 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
continue;
const GallerySlot &s = kGallerySlots[phys];
- const uint16 picId = READ_LE_UINT16(gd + i * 0x46);
+ const byte *entry = isMacintosh()
+ ? _mystery.floppySuspectEntry(i)
+ : gd + i * 0x46;
+ if (!entry)
+ continue;
+ const uint16 picId = READ_LE_UINT16(entry);
if (picId == 0)
continue;
Picture portrait;
Commit: c2f7d05571509f8b7f0849c9fbf4143a2862c07a
https://github.com/scummvm/scummvm/commit/c2f7d05571509f8b7f0849c9fbf4143a2862c07a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: correctly load animations from EEM1 Mac
Changed paths:
engines/eem/clues.cpp
engines/eem/graphics.cpp
engines/eem/site.cpp
engines/eem/site.h
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index 4052bf8d31c..cfefa7a4a0a 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -1338,7 +1338,10 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
getBalloonInsets(balloonId, textXIns, textYIns, textWidth);
const int textX = ballX + textXIns;
const int lineH = dialogFont.getFontHeight();
- const byte textColor = mac ? 0xff : 0;
+ MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
+ if (mac)
+ macPaletteMap = getMacSpritePaletteMap();
+ const byte textColor = mac ? macPaletteMap.black : 0;
bool firstPage = true;
int cursorY = ballY + textYIns;
@@ -1374,16 +1377,26 @@ void EEMEngine::displayFloppyDialogRecords(const byte *rec, uint count,
if (picID != 0 && picID != 0xFFFF) {
Picture pic;
if (_picsArchive.getPicture(picID, pic)) {
- scratch.transBlitFrom(pic.surface,
- Common::Point(picX, picY),
- (uint32)(byte)(pic.flags >> 8));
+ if (mac)
+ blitMacMaskedSurface(scratch.surfacePtr(), pic,
+ picX, picY, false,
+ macPaletteMap);
+ else
+ scratch.transBlitFrom(pic.surface,
+ Common::Point(picX, picY),
+ (uint32)(byte)(pic.flags >> 8));
}
}
if (haveBalloon) {
const byte transp = (byte)(balloon.flags >> 8);
- scratch.transBlitFrom(balloon.surface,
- Common::Point(ballX, ballY),
- transp, flipBall);
+ if (mac)
+ blitMacMaskedSurface(scratch.surfacePtr(), balloon,
+ ballX, ballY, flipBall,
+ macPaletteMap);
+ else
+ scratch.transBlitFrom(balloon.surface,
+ Common::Point(ballX, ballY),
+ transp, flipBall);
}
cursorY = ballY + textYIns;
}
diff --git a/engines/eem/graphics.cpp b/engines/eem/graphics.cpp
index 6ded96af338..3f616e3c70a 100644
--- a/engines/eem/graphics.cpp
+++ b/engines/eem/graphics.cpp
@@ -888,8 +888,11 @@ void EEMEngine::drawFloppyBubbleIndicator(Graphics::ManagedSurface &dst,
return;
const int x = ballX + (int)dx;
const int y = ballY + (int)dy;
- dst.transBlitFrom(pic.surface, Common::Point(x, y),
- (uint32)(byte)(pic.flags >> 8));
+ if (isMacintosh())
+ blitMacMaskedSurface(dst.surfacePtr(), pic, x, y);
+ else
+ dst.transBlitFrom(pic.surface, Common::Point(x, y),
+ (uint32)(byte)(pic.flags >> 8));
}
} // End of namespace EEM
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index b0bac98637d..5a9d6e95c87 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -83,6 +83,48 @@ bool londonTravelSitePic(const Mystery *mystery, uint siteNum, uint16 &sitePic)
return false;
}
+static bool paletteEntryIsWhite(const byte *rgb) {
+ return rgb[0] >= 0xFC && rgb[1] >= 0xFC && rgb[2] >= 0xFC;
+}
+
+static bool paletteEntryIsBlack(const byte *rgb) {
+ return rgb[0] <= 0x03 && rgb[1] <= 0x03 && rgb[2] <= 0x03;
+}
+
+static byte findPaletteEndpoint(const byte *palette, bool wantWhite,
+ byte preferred, byte alternate,
+ byte fallback) {
+ bool (*matches)(const byte *) = wantWhite ? paletteEntryIsWhite
+ : paletteEntryIsBlack;
+ if (matches(palette + preferred * 3))
+ return preferred;
+ if (matches(palette + alternate * 3))
+ return alternate;
+ for (uint i = 0; i < 256; i++) {
+ if (matches(palette + i * 3))
+ return (byte)i;
+ }
+ return fallback;
+}
+
+MacSpritePaletteMap getMacSpritePaletteMap() {
+ byte palette[256 * 3];
+ g_system->getPaletteManager()->grabPalette(palette, 0, 256);
+
+ MacSpritePaletteMap map;
+ map.white = findPaletteEndpoint(palette, true, 0x00, 0xFF, 0x00);
+ map.black = findPaletteEndpoint(palette, false, 0xFF, 0x00, 0xFF);
+ return map;
+}
+
+byte mapMacSpriteColor(byte color, const MacSpritePaletteMap &paletteMap) {
+ if (color == 0x00)
+ return paletteMap.white;
+ if (color == 0xFF)
+ return paletteMap.black;
+ return color;
+}
+
// Masked blit using `transp` = high byte of `pic.flags` (`_Rect_Move_Mask @ 1000:03fc`).
void blitFrame(Graphics::ManagedSurface &dst, const Picture &p,
int x, int y, byte transp) {
@@ -111,6 +153,39 @@ void blitMaskedSurface(Graphics::Surface *screen, const Picture &p,
keyBlitToScreen(screen, p, x, y);
}
+void blitMacMaskedSurface(Graphics::Surface *dst, const Picture &p,
+ int x, int y, bool flipX,
+ const MacSpritePaletteMap &paletteMap) {
+ if (!dst || p.surface.empty())
+ return;
+
+ const Common::Rect dstRect =
+ Common::Rect(x, y, x + p.surface.w, y + p.surface.h)
+ .findIntersectingRect(Common::Rect(dst->w, dst->h));
+ if (dstRect.isEmpty())
+ return;
+
+ const byte transp = (byte)(p.flags >> 8);
+ for (int row = dstRect.top; row < dstRect.bottom; row++) {
+ const int srcY = row - y;
+ const byte *src = (const byte *)p.surface.getBasePtr(0, srcY);
+ byte *out = (byte *)dst->getBasePtr(dstRect.left, row);
+ for (int col = dstRect.left; col < dstRect.right; col++, out++) {
+ const int relX = col - x;
+ const int srcX = flipX ? (p.surface.w - 1 - relX) : relX;
+ const byte color = src[srcX];
+ if (color != transp)
+ *out = mapMacSpriteColor(color, paletteMap);
+ }
+ }
+}
+
+void blitMacMaskedSurface(Graphics::Surface *dst, const Picture &p,
+ int x, int y, bool flipX) {
+ const MacSpritePaletteMap paletteMap = getMacSpritePaletteMap();
+ blitMacMaskedSurface(dst, p, x, y, flipX, paletteMap);
+}
+
// `_UpdateAnimations @ 172b:09c1`
void blitAnimFrameAnchored(Graphics::Surface *screen, const Picture &p,
int anchorX, int anchorY) {
@@ -119,6 +194,21 @@ void blitAnimFrameAnchored(Graphics::Surface *screen, const Picture &p,
anchorY - (int)(int16)p.rowoff);
}
+void blitMacAnimFrameAnchored(Graphics::Surface *dst, const Picture &p,
+ int anchorX, int anchorY,
+ const MacSpritePaletteMap &paletteMap) {
+ blitMacMaskedSurface(dst, p,
+ anchorX - (int)(int16)p.miscflags,
+ anchorY - (int)(int16)p.rowoff,
+ false, paletteMap);
+}
+
+void blitMacAnimFrameAnchored(Graphics::Surface *dst, const Picture &p,
+ int anchorX, int anchorY) {
+ const MacSpritePaletteMap paletteMap = getMacSpritePaletteMap();
+ blitMacAnimFrameAnchored(dst, p, anchorX, anchorY, paletteMap);
+}
+
bool readHotspotRect(const byte *r, bool mac, Common::Rect &rect) {
if (mac) {
const int16 top = (int16)READ_LE_UINT16(r + 0);
@@ -1182,6 +1272,10 @@ bool SiteScreen::enterSiteAnim() {
Graphics::PixelFormat::createFormatCLUT8());
bg.simpleBlitFrom(*screen);
g_system->unlockScreen();
+ const bool mac = _vm->isMacintosh();
+ MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
+ if (mac)
+ macPaletteMap = getMacSpritePaletteMap();
if (_vm->isLondon()) {
const uint animId = (partner == 0) ? 7 : 0xf;
@@ -1229,7 +1323,11 @@ bool SiteScreen::enterSiteAnim() {
Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(bg);
- blitFrame(scratch, skate[frameIdx], x, y, transp);
+ if (mac)
+ blitMacMaskedSurface(scratch.surfacePtr(), skate[frameIdx],
+ x, y, false, macPaletteMap);
+ else
+ blitFrame(scratch, skate[frameIdx], x, y, transp);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, sw, sh);
g_system->updateScreen();
@@ -1265,7 +1363,11 @@ bool SiteScreen::enterSiteAnim() {
Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(bg);
- blitFrame(scratch, fr, destX, destY, transp);
+ if (mac)
+ blitMacMaskedSurface(scratch.surfacePtr(), fr, destX, destY,
+ false, macPaletteMap);
+ else
+ blitFrame(scratch, fr, destX, destY, transp);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, sw, sh);
g_system->updateScreen();
@@ -1333,6 +1435,9 @@ void SiteScreen::renderFloppyDrops(uint siteNum) {
if (!screen)
return;
+ MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
+ if (mac)
+ macPaletteMap = getMacSpritePaletteMap();
for (uint i = 0; i < count; i++) {
const byte *e = drops + 2 + i * (mac ? 6 : 5);
const uint16 picID = READ_LE_UINT16(e + 0);
@@ -1344,7 +1449,10 @@ void SiteScreen::renderFloppyDrops(uint siteNum) {
Picture pic;
if (!_vm->getPics().getPicture((uint)picID, pic))
continue;
- blitMaskedSurface(screen, pic, x, y);
+ if (mac)
+ blitMacMaskedSurface(screen, pic, x, y, false, macPaletteMap);
+ else
+ blitMaskedSurface(screen, pic, x, y);
}
g_system->unlockScreen();
}
@@ -1577,7 +1685,10 @@ void SiteScreen::renderPartner(uint siteNum, uint32 tickMs) {
Graphics::Surface *screen = g_system->lockScreen();
if (!screen)
return;
- blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
+ if (_vm->isMacintosh())
+ blitMacAnimFrameAnchored(screen, anim[frameIdx], x, y);
+ else
+ blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
g_system->unlockScreen();
}
@@ -1614,7 +1725,10 @@ bool SiteScreen::renderFloppyHotspotPartnerPose(uint siteNum) {
return false;
const uint frameIdx = partnerFrameAtTick(animId, (uint)anim.size(), 0);
- blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
+ if (mac)
+ blitMacAnimFrameAnchored(screen, anim[frameIdx], x, y);
+ else
+ blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
g_system->unlockScreen();
return true;
}
diff --git a/engines/eem/site.h b/engines/eem/site.h
index f17f8ae4e20..8c65d9c0eee 100644
--- a/engines/eem/site.h
+++ b/engines/eem/site.h
@@ -67,6 +67,26 @@ uint bigMapDetailPartnerFrameAtTick(uint numFrames, uint32 elapsedMs);
void blitAnimFrameAnchored(Graphics::Surface *screen, const Picture &p,
int anchorX, int anchorY);
+struct MacSpritePaletteMap {
+ byte white;
+ byte black;
+};
+
+/// Mac UI/sprite art is authored with color 0 = white and 0xff = black,
+/// but several site ColorTables swap those endpoint slots.
+MacSpritePaletteMap getMacSpritePaletteMap();
+byte mapMacSpriteColor(byte color, const MacSpritePaletteMap &paletteMap);
+void blitMacMaskedSurface(Graphics::Surface *dst, const Picture &p,
+ int x, int y, bool flipX = false);
+void blitMacMaskedSurface(Graphics::Surface *dst, const Picture &p,
+ int x, int y, bool flipX,
+ const MacSpritePaletteMap &paletteMap);
+void blitMacAnimFrameAnchored(Graphics::Surface *dst, const Picture &p,
+ int anchorX, int anchorY);
+void blitMacAnimFrameAnchored(Graphics::Surface *dst, const Picture &p,
+ int anchorX, int anchorY,
+ const MacSpritePaletteMap &paletteMap);
+
/// Rotate one VGA palette range by one slot (STARTâEND direction).
void cyclePaletteRange(uint8 start, uint8 end);
Commit: c17db422a871b20eebb33232bf410cf6ecff5caf
https://github.com/scummvm/scummvm/commit/c17db422a871b20eebb33232bf410cf6ecff5caf
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: improved playback of music from EEM1 Mac
Changed paths:
engines/eem/music.cpp
engines/eem/music.h
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index 988dd7ad949..9d41fc64d8b 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -38,6 +38,8 @@ namespace EEM {
const int kMidiDriverFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
const int kMacMidiDriverFlags = MDT_MIDI | MDT_PREFER_GM;
const uint16 kInvalidMacMidiResource = 0xffff;
+const uint16 kInvalidMacSongResource = 0xffff;
+const byte kNoMacInstrument = 0xff;
Common::String musicNameFromPath(const Common::Path &path) {
Common::String name = path.baseName();
@@ -48,42 +50,60 @@ Common::String musicNameFromPath(const Common::Path &path) {
return name;
}
-uint16 macMidiResourceIdForFile(const Common::Path &path) {
+Common::SeekableReadStream *openMacMidiResource(uint16 resourceId) {
+ Common::SeekableReadStream *stream =
+ openMacResource(Common::Path("EEM Sound&Music"),
+ MKTAG('c', 'm', 'i', 'd'), resourceId);
+ if (stream)
+ return stream;
+
+ stream = openMacResource(Common::Path("EEM Sound&Music"),
+ MKTAG('M', 'I', 'D', 'I'), resourceId);
+ if (stream)
+ return stream;
+
+ return openMacResource(Common::Path("EEM Sound&Music"),
+ MKTAG('M', 'i', 'd', 'i'), resourceId);
+}
+
+uint16 macSongResourceIdForFile(const Common::Path &path) {
const Common::String name = musicNameFromPath(path);
if (name == "THEME" || name == "THEME SONG")
- return 0;
+ return 1000;
if (name == "WIN1" || name == "FANFARE2")
- return 1;
+ return 1001;
if (name == "TRAVEL-1")
- return 2;
+ return 1006;
if (name == "TRAVEL-4")
- return 3;
+ return 1002;
if (name == "TRAVEL-6")
- return 4;
+ return 1004;
if (name == "TRAVEL-7")
- return 5;
+ return 1003;
if (name == "TRAVEL-8")
- return 6;
+ return 1005;
if (name == "WRONG2")
- return 7;
- return kInvalidMacMidiResource;
+ return 1007;
+ return kInvalidMacSongResource;
}
-uint16 macMidiResourceIdForMus(uint num) {
+uint16 macSongResourceIdForMus(uint num) {
static const uint16 kTravelTracks[5] = {
- 4, 3, 5, 2, 6 // Travel-6, Travel-4, Travel-7, Travel-1, Travel-8
+ 1004, 1002, 1003, 1006, 1005 // Travel-6/4/7/1/8
};
if (num < ARRAYSIZE(kTravelTracks))
return kTravelTracks[num];
if (num == 5)
- return 1; // WIN1.MID
+ return 1001; // Win1
if (num == 6)
- return 7; // WRONG2.MID
- return kInvalidMacMidiResource;
+ return 1007; // Wrong2
+ return kInvalidMacSongResource;
}
MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
_isFloppy(isFloppy), _isMacintosh(isMacintosh) {
+ clearMacInstrumentMap();
+
// _InitMIDI @ 20a2:013a â `_AIL_register_driver` against
// ADLIB.ADV / SBFM.ADV / MT32MPU.ADV. We honour the launcher's
// "Music driver" setting and prefer MT-32 when unset.
@@ -145,6 +165,15 @@ MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh) :
}
void MusicPlayer::send(uint32 b) {
+ if (_isMacintosh && (b & 0xF0) == 0xC0) {
+ const byte channel = (byte)(b & 0x0F);
+ const byte rawProgram = (byte)((b >> 8) & 0x7F);
+ const byte inst = _macChannelInstrument[channel] != kNoMacInstrument
+ ? _macChannelInstrument[channel] : rawProgram;
+ const byte gmProgram = mapMacInstrumentToGM(inst, channel);
+ b = (b & 0xFFFF00FF) | ((uint32)gmProgram << 8);
+ }
+
// Miles drivers (both AdLib and MT-32) implement their own per-
// source-channel mixing and timbre installation, so forward the raw
// event.
@@ -184,13 +213,95 @@ void MusicPlayer::startLoadedMusic(const Common::String &name, bool loop,
name.c_str(), _xmiData.size(), loop, _milesAudioMode, smf);
}
+void MusicPlayer::clearMacInstrumentMap() {
+ memset(_macChannelInstrument, kNoMacInstrument,
+ sizeof(_macChannelInstrument));
+}
+
+byte MusicPlayer::mapMacInstrumentToGM(byte inst, byte channel) const {
+ if (channel == 9)
+ return 0;
+
+ switch (inst) {
+ case 2:
+ case 3:
+ return 0; // Piano
+ case 11:
+ return 16; // Organ
+ case 60:
+ return 24; // Guitar
+ case 64:
+ case 65:
+ return 33; // Bass
+ case 73:
+ case 74:
+ case 75:
+ return 73; // Flute
+ case 83:
+ case 84:
+ return 71; // Clarinet
+ default:
+ return inst < 128 ? inst : 0;
+ }
+}
+
+bool MusicPlayer::loadMacSong(uint16 resourceId, uint16 &midiId) {
+ midiId = kInvalidMacMidiResource;
+ clearMacInstrumentMap();
+
+ Common::SeekableReadStream *stream =
+ openMacResource(Common::Path("EEM Sound&Music"),
+ MKTAG('S', 'O', 'N', 'G'), resourceId);
+ if (!stream) {
+ warning("MusicPlayer: Mac SONG resource %u missing", resourceId);
+ return false;
+ }
+
+ const uint32 size = (uint32)stream->size();
+ if (size < 18) {
+ delete stream;
+ warning("MusicPlayer: Mac SONG resource %u too short", resourceId);
+ return false;
+ }
+
+ Common::Array<byte> song;
+ song.resize(size);
+ if (stream->read(song.data(), size) != size) {
+ delete stream;
+ warning("MusicPlayer: short read on Mac SONG resource %u", resourceId);
+ return false;
+ }
+ delete stream;
+
+ midiId = READ_BE_UINT16(song.data());
+ const uint16 instCount = READ_BE_UINT16(song.data() + 16);
+ uint32 pos = 18;
+ for (uint i = 0; i < instCount && pos + 4 <= size; i++, pos += 4) {
+ const uint16 channel = READ_BE_UINT16(song.data() + pos);
+ const uint16 inst = READ_BE_UINT16(song.data() + pos + 2);
+ if (channel < ARRAYSIZE(_macChannelInstrument) && inst < 128)
+ _macChannelInstrument[channel] = (byte)inst;
+ }
+
+ return true;
+}
+
+void MusicPlayer::playMacSongResource(uint16 resourceId, bool loop) {
+ if (resourceId == kInvalidMacSongResource)
+ return;
+
+ uint16 midiId = kInvalidMacMidiResource;
+ if (!loadMacSong(resourceId, midiId))
+ return;
+
+ playMacMidiResource(midiId, loop);
+}
+
void MusicPlayer::playMacMidiResource(uint16 resourceId, bool loop) {
if (resourceId == kInvalidMacMidiResource)
return;
- Common::SeekableReadStream *stream =
- openMacResource(Common::Path("EEM Sound&Music"),
- MKTAG('M', 'i', 'd', 'i'), resourceId);
+ Common::SeekableReadStream *stream = openMacMidiResource(resourceId);
if (!stream) {
warning("MusicPlayer: Mac Midi resource %u missing", resourceId);
return;
@@ -223,13 +334,13 @@ void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
stop();
if (_isMacintosh) {
- const uint16 resourceId = macMidiResourceIdForFile(xmiPath);
- if (resourceId == kInvalidMacMidiResource) {
- warning("MusicPlayer: no Mac Midi mapping for %s",
+ const uint16 resourceId = macSongResourceIdForFile(xmiPath);
+ if (resourceId == kInvalidMacSongResource) {
+ warning("MusicPlayer: no Mac SONG mapping for %s",
xmiPath.toString().c_str());
return;
}
- playMacMidiResource(resourceId, loop);
+ playMacSongResource(resourceId, loop);
return;
}
@@ -258,7 +369,7 @@ void MusicPlayer::playMus(uint num, bool loop) {
if (_isMacintosh) {
Common::StackLock lock(_mutex);
stop();
- playMacMidiResource(macMidiResourceIdForMus(num), loop);
+ playMacSongResource(macSongResourceIdForMus(num), loop);
return;
}
diff --git a/engines/eem/music.h b/engines/eem/music.h
index 35b9ddd8e80..0aec7735c32 100644
--- a/engines/eem/music.h
+++ b/engines/eem/music.h
@@ -65,12 +65,17 @@ public:
private:
void playMacMidiResource(uint16 resourceId, bool loop);
+ void playMacSongResource(uint16 resourceId, bool loop);
void startLoadedMusic(const Common::String &name, bool loop, bool smf);
+ bool loadMacSong(uint16 resourceId, uint16 &midiId);
+ void clearMacInstrumentMap();
+ byte mapMacInstrumentToGM(byte inst, byte channel) const;
bool _milesAudioMode = false;
const bool _isFloppy;
const bool _isMacintosh;
Common::Array<byte> _xmiData;
+ byte _macChannelInstrument[16] = {};
};
} // End of namespace EEM
Commit: d9a29123e1abf2617289c16f2f25f4355cc79b4d
https://github.com/scummvm/scummvm/commit/d9a29123e1abf2617289c16f2f25f4355cc79b4d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-23T15:47:52+02:00
Commit Message:
EEM: implemented intro from EEM1 Mac
Changed paths:
engines/eem/eem.cpp
engines/eem/eem.h
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index a3a762b7c2a..c9cab6db0a4 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -57,6 +57,21 @@ const uint kPicStormLogo = 0x20b; // Floppy storm-logo still
const uint kPalEAKids = 0x25;
const uint kPalHighScore = 0x27;
const uint kPalStormLogo = 0x26; // Floppy FUN_23d2_0605
+const uint kMacPicEAKidsLogo = 0x213; // FUN_000092be
+const uint kMacPicTitleRoom = 0x20d; // FUN_000096fa title room
+const uint kMacPicTitleDark = 0x20e;
+const uint kMacPicTitleFinal = 0x20f;
+const uint kMacPicTitleIn0 = 0x210;
+const uint kMacPicTitleIn1 = 0x211;
+const uint kMacPicTitleIn2 = 0x212;
+const uint kMacPicTitleLeft0 = 0x214;
+const uint kMacPicTitleLeft1 = 0x215;
+const uint kMacPicTitleLeft2 = 0x216;
+const uint kMacPicTitleRight0 = 0x217;
+const uint kMacPicTitleRight1 = 0x218;
+const uint kMacPicTitleRight2 = 0x219;
+const uint kMacPalEAKids = 0x28;
+const uint kMacPalTitle = 0x29;
const uint kPicMousePointer = 0x50; // 0x51 is the wait cursor
const uint16 kMacFontResource = 3214; // 'Eagle Eye 14' FONT resource
const uint16 kMacSmallFontResource = 3209; // Smaller speech/dialog FONT.
@@ -470,7 +485,10 @@ Common::Error EEMEngine::run() {
}
if (isMacintosh()) {
+ runMacStartup();
CursorMan.showMouse(true);
+ if (_music)
+ _music->stop();
if (!shouldQuit())
doProfilePicker();
if (!shouldQuit())
@@ -1111,6 +1129,268 @@ void EEMEngine::showHighScoreLogo() {
fadeCurrentPaletteToBlack();
}
+static void copyNativePictureToScreen(const Picture &pic) {
+ const int w = MIN<int>(pic.surface.w, kMacScreenWidth);
+ const int h = MIN<int>(pic.surface.h, kMacScreenHeight);
+ if (w <= 0 || h <= 0)
+ return;
+ g_system->copyRectToScreen(pic.surface.getPixels(), pic.surface.pitch,
+ 0, 0, w, h);
+}
+
+static void copyNativeSurfaceToScreen(const Graphics::ManagedSurface &surface) {
+ const int w = MIN<int>(surface.w, kMacScreenWidth);
+ const int h = MIN<int>(surface.h, kMacScreenHeight);
+ if (w <= 0 || h <= 0)
+ return;
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch,
+ 0, 0, w, h);
+}
+
+static void blitNativeTransparent(Graphics::ManagedSurface &dst,
+ const Picture &pic, int x, int y) {
+ if (pic.surface.empty())
+ return;
+ const byte transparent = (byte)(pic.flags >> 8);
+ dst.transBlitFrom(pic.surface, Common::Point(x, y), transparent);
+}
+
+bool EEMEngine::waitIntroDelay(uint32 maxMs) {
+ const uint32 startMs = g_system->getMillis();
+ while (!shouldQuit() && (g_system->getMillis() - startMs < maxMs)) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT ||
+ event.type == Common::EVENT_RETURN_TO_LAUNCHER)
+ return true;
+ if (event.type == Common::EVENT_LBUTTONDOWN)
+ return true;
+ if (event.type == Common::EVENT_KEYDOWN) {
+ if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ _skipIntro = true;
+ interruptAudio();
+ }
+ return true;
+ }
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(5);
+ }
+ return false;
+}
+
+void EEMEngine::showMacEAKidsLogo() {
+ Picture pic;
+ if (!_picsArchive.getPicture(kMacPicEAKidsLogo, pic)) {
+ warning("Mac EA Kids logo (%u) load failed", kMacPicEAKidsLogo);
+ return;
+ }
+ copyNativePictureToScreen(pic);
+
+ byte fpal[kPalSize];
+ if (!getSitePalette(kMacPalEAKids, fpal)) {
+ warning("Mac EA Kids palette (%u) load failed", kMacPalEAKids);
+ return;
+ }
+
+ bool aborted = false;
+ for (uint pass = 0; pass < 2 && !aborted && !shouldQuit(); pass++) {
+ const bool show = (pass != 0);
+ int delayCount = 9;
+
+ for (uint frame = 0; frame < 0x37 && !aborted && !shouldQuit(); frame++) {
+ if (show && waitIntroDelay(40)) {
+ aborted = true;
+ break;
+ }
+
+ openColorCycle(fpal, 0x01, 0x6e, show);
+ openColorCycle(fpal, 0x81, 0xee, show);
+ if (--delayCount == 0) {
+ delayCount = 9;
+ openColorCycle(fpal, 0x70, 0x80, show);
+ }
+ if (show)
+ g_system->updateScreen();
+ }
+ }
+
+ if (!aborted && !shouldQuit()) {
+ for (uint i = 0; i < 5; i++)
+ openColorCycle(fpal, 0x70, 0x80, true);
+ g_system->updateScreen();
+ waitIntroDelay(0x23 * 40);
+ }
+
+ fadeCurrentPaletteToBlack();
+}
+
+void EEMEngine::showMacStillLogo(uint picId, uint palId, uint holdMs,
+ bool playThunder) {
+ Picture pic;
+ if (!_picsArchive.getPicture(picId, pic)) {
+ warning("Mac logo PIC 0x%x load failed", picId);
+ return;
+ }
+ copyNativePictureToScreen(pic);
+
+ byte target[kPalSize];
+ if (!getSitePalette(palId, target)) {
+ warning("Mac logo palette 0x%x load failed", palId);
+ return;
+ }
+
+ byte black[kPalSize] = {};
+ g_system->getPaletteManager()->setPalette(black, 0, 256);
+ g_system->updateScreen();
+ fadePaletteFromBlack(target);
+
+ if (playThunder && _audio)
+ _audio->playVoc(Common::Path("THUNDER.VOC"));
+
+ waitIntroDelay(holdMs);
+ if (_audio)
+ _audio->stopVoice();
+ fadeCurrentPaletteToBlack();
+}
+
+void EEMEngine::showMacTitleIntro() {
+ byte target[kPalSize];
+ if (!getSitePalette(kMacPalTitle, target)) {
+ warning("Mac title palette (%u) load failed", kMacPalTitle);
+ return;
+ }
+
+ Picture room, titleDark, titleFinal;
+ if (!_picsArchive.getPicture(kMacPicTitleRoom, room) ||
+ !_picsArchive.getPicture(kMacPicTitleDark, titleDark) ||
+ !_picsArchive.getPicture(kMacPicTitleFinal, titleFinal)) {
+ warning("Mac title base pictures failed to load");
+ return;
+ }
+
+ Picture in[3], left[3], right[3];
+ const uint inIds[3] = {
+ kMacPicTitleIn0, kMacPicTitleIn1, kMacPicTitleIn2
+ };
+ const uint leftIds[3] = {
+ kMacPicTitleLeft0, kMacPicTitleLeft1, kMacPicTitleLeft2
+ };
+ const uint rightIds[3] = {
+ kMacPicTitleRight0, kMacPicTitleRight1, kMacPicTitleRight2
+ };
+ for (uint i = 0; i < 3; i++) {
+ if (!_picsArchive.getPicture(inIds[i], in[i]) ||
+ !_picsArchive.getPicture(leftIds[i], left[i]) ||
+ !_picsArchive.getPicture(rightIds[i], right[i])) {
+ warning("Mac title overlay PIC load failed");
+ return;
+ }
+ }
+
+ Graphics::ManagedSurface frame(kMacScreenWidth, kMacScreenHeight,
+ Graphics::PixelFormat::createFormatCLUT8());
+ frame.blitFrom(room.surface, Common::Point(0, 0));
+ copyNativeSurfaceToScreen(frame);
+
+ byte black[kPalSize] = {};
+ g_system->getPaletteManager()->setPalette(black, 0, 256);
+ g_system->updateScreen();
+ fadePaletteFromBlack(target);
+
+ for (int i = 0; i < 0x2c && !shouldQuit() && !_skipIntro; i++) {
+ frame.blitFrom(room.surface, Common::Point(0, 0));
+ const int revealW = (i + 1) * kMacScreenWidth / 0x2c;
+ if (revealW > 0) {
+ const Common::Rect src(0, 0, revealW, kMacScreenHeight);
+ frame.blitFrom(titleDark.surface, src, Common::Point(0, 0));
+ }
+
+ switch (i) {
+ case 15:
+ case 19:
+ blitNativeTransparent(frame, in[0], 0xb9, 0x29);
+ break;
+ case 16:
+ case 18:
+ blitNativeTransparent(frame, in[1], 0xb9, 0x29);
+ break;
+ case 17:
+ blitNativeTransparent(frame, in[2], 0xb9, 0x29);
+ break;
+ default:
+ break;
+ }
+
+ copyNativeSurfaceToScreen(frame);
+ if (waitIntroDelay(40))
+ return;
+ }
+
+ for (int i = 0x2a; i >= 0 && !shouldQuit() && !_skipIntro; i--) {
+ frame.blitFrom(titleFinal.surface, Common::Point(0, 0));
+
+ switch (i) {
+ case 7:
+ case 11:
+ blitNativeTransparent(frame, left[0], 0x39, 0xb1);
+ break;
+ case 8:
+ case 10:
+ blitNativeTransparent(frame, left[1], 0x39, 0xb1);
+ break;
+ case 9:
+ blitNativeTransparent(frame, left[2], 0x39, 0xb1);
+ break;
+ case 24:
+ case 28:
+ blitNativeTransparent(frame, right[0], 0x131, 0xb1);
+ break;
+ case 25:
+ case 27:
+ blitNativeTransparent(frame, right[1], 0x131, 0xb1);
+ break;
+ case 26:
+ blitNativeTransparent(frame, right[2], 0x131, 0xb1);
+ break;
+ default:
+ break;
+ }
+
+ copyNativeSurfaceToScreen(frame);
+ if (waitIntroDelay(40))
+ return;
+ }
+
+ frame.blitFrom(titleFinal.surface, Common::Point(0, 0));
+ copyNativeSurfaceToScreen(frame);
+ waitIntroDelay(0xFFFFFFFFu);
+}
+
+void EEMEngine::runMacStartup() {
+ CursorMan.showMouse(false);
+ _skipIntro = false;
+ debugC(1, kDebugGeneral, "EEM1 Mac: opening sequence");
+
+ if (!shouldQuit() && !_skipIntro)
+ showMacEAKidsLogo();
+ if (!shouldQuit() && !_skipIntro)
+ showMacStillLogo(kPicHighScoreLogo, kPalHighScore, 3000,
+ /* playThunder= */ false);
+ if (!shouldQuit() && !_skipIntro)
+ showMacStillLogo(kPicStormLogo, kPalStormLogo, 3000,
+ /* playThunder= */ true);
+
+ if (!shouldQuit() && !_skipIntro && _music)
+ _music->playFile(Common::Path("THEME.XMI"), /* loop= */ true);
+ if (!shouldQuit() && !_skipIntro)
+ showMacTitleIntro();
+
+ if (_music)
+ _music->stop();
+ _skipIntro = false;
+}
+
void EEMEngine::showLondonLogo(uint picId, uint palId, uint holdMs) {
Picture pic;
if (!_picsArchive.getPicture(picId, pic) || pic.surface.empty()) {
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index f99c4086b79..7c0ae46d08f 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -411,6 +411,13 @@ private:
void showHighScoreLogo();
void showFloppyStormLogo();
+ bool waitIntroDelay(uint32 maxMs);
+ void runMacStartup();
+ void showMacEAKidsLogo();
+ void showMacStillLogo(uint picId, uint palId, uint holdMs,
+ bool playThunder);
+ void showMacTitleIntro();
+
void runLondonStartup();
/// Start London mystery 0 after a freshly-created detective chooses a partner.
bool startLondonTrainingMystery();
More information about the Scummvm-git-logs
mailing list