[Scummvm-git-logs] scummvm master -> 24c327ead16461902940e03dec62a5a90ce1c719
neuromancer
noreply at scummvm.org
Sun Sep 27 14:16:33 UTC 2026
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
01b37b8a70 SCUMM: RA1: added support for demo v1.51
3113ae98e7 SCUMM: RA1: added support for demo 1994-04-13
ab41e63c79 SCUMM: RA1: added support for mac demo v1.02c
c6a90ffb60 SCUMM: RA2: added support for demo da1.06
6ad8570f34 SCUMM: RA2: added support for demo dg1.15
24c327ead1 SCUMM: RA2: added support for mac demo 1.0
Commit: 01b37b8a7090910cc5b51ec0aa01a06349bd977c
https://github.com/scummvm/scummvm/commit/01b37b8a7090910cc5b51ec0aa01a06349bd977c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-27T16:14:25+02:00
Commit Message:
SCUMM: RA1: added support for demo v1.51
Changed paths:
A engines/scumm/insane/rebel1/releases.cpp
A engines/scumm/insane/rebel1/releases.h
devtools/scumm-md5.txt
engines/scumm/detection_tables.h
engines/scumm/insane/rebel1/menu.cpp
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/render.cpp
engines/scumm/insane/rebel1/runlevels.cpp
engines/scumm/insane/rebel1/saveload.cpp
engines/scumm/module.mk
engines/scumm/scumm-md5.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index ec53bda19d4..ce415ea562c 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -450,6 +450,7 @@ ft Full Throttle
8ead2c01c48c2e3d15d6a89b86cd84ab 19697 he DOS - - - Hebrew fan translation
rebel1 Star Wars: Rebel Assault
+ 274f6026d41fa2a2990a6d6b7a561dc2 217559 en DOS Demo v1.51 Demo v1.51 CD-ROM demo -
b9ac90e4411da1b6f7dd075cc4b03c2a 214435 en DOS - v1.0 - -
e210a57b756473be705d24b9378a8233 212383 en DOS - v1.3 - -
6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 3f3977bc632..5bc9393be02 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -231,6 +231,7 @@ static const GameSettings gameVariantsTable[] = {
{"ft", "Demo", 0, GID_FT, 7, 0, MDT_NONE, GF_DEMO, UNK, GUIO4(GUIO_NOMIDI, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
{"rebel1", "", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
+ {"rebel1", "Demo v1.51", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
{"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
@@ -505,6 +506,7 @@ static const GameFilenamePattern gameFilenamesTable[] = {
{ "ft", "Vollgas Demo Data", kGenUnchanged, Common::DE_DEU, Common::kPlatformMacintosh, "Demo" },
{ "rebel1", "ASSAULT.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
+ { "rebel1", "ASSAULT.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo v1.51" },
{ "rebel1", "Rebel Assault", kGenUnchanged, UNK_LANG, Common::kPlatformMacintosh, "" },
{ "rebel1", "O1LOGO.AAA", kGenUnchanged, UNK_LANG, Common::kPlatform3DO, "" },
{ "rebel1", "SUB_AP.BIN", kGenUnchanged, UNK_LANG, Common::kPlatformSegaCD, "" },
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 86628801b66..0c40047c1ff 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -35,9 +35,8 @@
namespace Scumm {
const int kRA1MainMenuItemCount = 6;
-const int kRA1LevelSelectItemCount = 16; // 15 levels + BACK
const int kRA1LevelSelectRowsPerCol = 8;
-const int kRA1NumLevels = 15;
+const int kRA1NumLevels = Rebel1Release::kNumLevels;
const int kRA1MenuAxisThreshold = Common::JOYAXIS_MAX / 2;
const int kRA1MenuLogicalWidth = 0x140;
const int kRA1MenuFrameX = 0x32;
@@ -457,16 +456,18 @@ bool InsaneRebel1::handleMenuCommand(RA1MenuCommand command) {
return false;
if (_levelSelectActive) {
+ const int itemCount = _release.getLevelCount() + 1; // Chapters + BACK
int col = _levelSelectSel / kRA1LevelSelectRowsPerCol;
int row = _levelSelectSel % kRA1LevelSelectRowsPerCol;
+ const int rowsInColumn = MIN(kRA1LevelSelectRowsPerCol, itemCount - col * kRA1LevelSelectRowsPerCol);
switch (command) {
case kRA1MenuCommandUp:
- row = (row + kRA1LevelSelectRowsPerCol - 1) % kRA1LevelSelectRowsPerCol;
+ row = (row + rowsInColumn - 1) % rowsInColumn;
_levelSelectSel = col * kRA1LevelSelectRowsPerCol + row;
return true;
case kRA1MenuCommandDown:
- row = (row + 1) % kRA1LevelSelectRowsPerCol;
+ row = (row + 1) % rowsInColumn;
_levelSelectSel = col * kRA1LevelSelectRowsPerCol + row;
return true;
case kRA1MenuCommandLeft:
@@ -474,11 +475,11 @@ bool InsaneRebel1::handleMenuCommand(RA1MenuCommand command) {
_levelSelectSel -= kRA1LevelSelectRowsPerCol;
return true;
case kRA1MenuCommandRight:
- if (col < 1)
- _levelSelectSel += kRA1LevelSelectRowsPerCol;
+ if ((col + 1) * kRA1LevelSelectRowsPerCol < itemCount)
+ _levelSelectSel = MIN(_levelSelectSel + kRA1LevelSelectRowsPerCol, itemCount - 1);
return true;
case kRA1MenuCommandCancel:
- _levelSelectSel = kRA1LevelSelectItemCount - 1; // Back
+ _levelSelectSel = itemCount - 1; // Back
// fall through
case kRA1MenuCommandAccept:
_menuConfirmed = true;
@@ -626,7 +627,7 @@ bool InsaneRebel1::handleMenuMouse(const Common::Event &event) {
if (_levelSelectActive) {
selection = &_levelSelectSel;
- for (int i = 0; i < kRA1LevelSelectItemCount; i++) {
+ for (int i = 0; i <= _release.getLevelCount(); i++) {
const int col = i / kRA1LevelSelectRowsPerCol;
const int row = i % kRA1LevelSelectRowsPerCol;
const int frameX = (col == 0) ? kRA1LevelSelectLeftX : kRA1LevelSelectRightX;
@@ -1095,18 +1096,19 @@ void InsaneRebel1::renderLevelSelectOverlay(byte *dst, int pitch, int width, int
const int leftFrameX = kRA1LevelSelectLeftX;
const int rightFrameX = kRA1LevelSelectRightX;
const int columnW = kRA1LevelSelectColW;
+ const int levelCount = _release.getLevelCount();
int levelTextW = 0;
- for (int i = 0; i < kRA1NumLevels; i++)
- levelTextW = MAX(levelTextW, getMenuTalkTextWidth(kLevelItems[i]));
+ for (int i = 0; i < levelCount; i++)
+ levelTextW = MAX(levelTextW, getMenuTalkTextWidth(kLevelItems[_release.levels[i] - 1]));
- for (int i = 0; i < kRA1LevelSelectItemCount; i++) {
+ for (int i = 0; i <= levelCount; i++) {
const int col = i / kRA1LevelSelectRowsPerCol;
const int row = i % kRA1LevelSelectRowsPerCol;
const int frameX = (col == 0) ? leftFrameX : rightFrameX;
const int y = menuY + row * kRA1MenuRowH;
- const bool levelItem = i < kRA1NumLevels;
- const char *text = levelItem ? kLevelItems[i] : kBackItem;
+ const bool levelItem = i < levelCount;
+ const char *text = levelItem ? kLevelItems[_release.levels[i] - 1] : kBackItem;
const int textW = levelItem ? levelTextW : getMenuTalkTextWidth(text);
const int textX = frameX + (columnW - textW) / 2;
@@ -1267,7 +1269,7 @@ int InsaneRebel1::runPasscodeEntryDialog() {
for (int i = 1; i <= kRA1NumLevels; i++) {
const char *password = getChapterCompletePassword(i);
if (password && !scumm_stricmp(_textEntryBuffer, password)) {
- const int targetLevel = getRebel1PasscodeStartLevel(i);
+ const int targetLevel = _release.resolvePasscodeLevel(getRebel1PasscodeStartLevel(i));
if (targetLevel == 0)
return 0;
@@ -1284,7 +1286,7 @@ int InsaneRebel1::runPasscodeEntryDialog() {
for (int i = 1; i <= (int)ARRAYSIZE(kRebel1ThreeDOPasswords); i++) {
const char *password = kRebel1ThreeDOPasswords[i - 1];
if (!scumm_stricmp(_textEntryBuffer, password)) {
- const int targetLevel = getRebel1ThreeDOPasscodeStartLevel(i);
+ const int targetLevel = _release.resolvePasscodeLevel(getRebel1ThreeDOPasscodeStartLevel(i));
if (targetLevel == 0)
return 0;
@@ -1384,7 +1386,7 @@ int InsaneRebel1::runLevelSelectMenu() {
if (!_unlockAllLevels)
return 0;
- _levelSelectSel = CLIP(_startLevel - 1, 0, kRA1NumLevels - 1);
+ _levelSelectSel = MAX(_release.findLevel(_startLevel), 0);
_levelSelectActive = true;
while (!shouldAbortGameFlow()) {
@@ -1394,9 +1396,9 @@ int InsaneRebel1::runLevelSelectMenu() {
break;
if (_menuConfirmed) {
- if (_levelSelectSel < kRA1NumLevels) {
+ if (_levelSelectSel < _release.getLevelCount()) {
_levelSelectActive = false;
- return _levelSelectSel + 1; // 1-based level number
+ return _release.levels[_levelSelectSel];
}
// BACK
_levelSelectActive = false;
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 3f042b3b229..bddeba8c7db 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -288,7 +288,7 @@ const char *InsaneRebel1::uiStr(int id) const {
// that fails validation keeps its English fallback.
void InsaneRebel1::loadLocalizedUiStrings() {
Common::File f;
- if (!f.open("ASSAULT.EXE"))
+ if (!f.open(_release.executable))
return;
int32 size = (int32)f.size();
@@ -457,10 +457,10 @@ void InsaneRebel1::loadLocalizedUiStrings() {
for (int i = 0; i < kR1StrUiCount; i++)
if (!_uiStrings[i].empty())
loaded++;
- debugC(DEBUG_INSANE, "InsaneRebel1: %d/%d UI strings loaded from ASSAULT.EXE", loaded, kR1StrUiCount);
+ debugC(DEBUG_INSANE, "InsaneRebel1: %d/%d UI strings loaded from %s", loaded, kR1StrUiCount, _release.executable);
}
-InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
+InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm), _release(getRebel1Release(scumm->_game.variant)) {
Insane::_vm = scumm;
// Rebel Assault skips ScummEngine::resetScumm(), which normally clears this state.
for (int i = 0; i < kScummActionCount; i++)
@@ -524,7 +524,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_activeInputSource = kInputSourceMouse;
_currentLevel = 0;
- _resumeLevel = 1;
+ _resumeLevel = _release.levels[0];
_activeSaveSlot = -1;
_loadRequested = false;
_flyControlMode = 0;
@@ -576,6 +576,8 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_level7WarningFrames = 0;
_level7WarningThreshold = 0;
_levelGameplayPhase = 0;
+ _level9PathLoopOffset = -1;
+ _level9SelectedPath = -1;
_level14Play2BSplicePending = false;
_level14Play2BSpliced = false;
_level14Play2BSpliceFrame = 0;
@@ -594,7 +596,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_optionsSel = 0;
_levelSelectActive = false;
_levelSelectSel = 0;
- _startLevel = 1;
+ _startLevel = _release.levels[0];
_optRookieOneFemale = false;
_optMusicEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType);
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 40bd8709062..d96ae57a85d 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -29,6 +29,7 @@
#include "scumm/insane/rebel/rebel_audio.h"
#include "scumm/insane/rebel/rebel_gamepad.h"
#include "scumm/insane/rebel/rebel_touch.h"
+#include "scumm/insane/rebel1/releases.h"
#include "scumm/smush/rebel/smush_player_ra1.h"
namespace Scumm {
@@ -333,6 +334,7 @@ public:
private:
ScummEngine_v7 *_vm;
+ const Rebel1Release &_release;
// Sprite and font banks.
RA1SpriteBank _shipBank;
@@ -418,6 +420,7 @@ private:
void updateOnFootSequence();
void updateOnFootAimVariant();
void finishOnFootFrame();
+ void updateLevel9PathSelector(int32 curFrame, int32 maxFrame);
int16 _onFootCharX;
int16 _onFootCharY;
int16 _onFootAnimCounter;
@@ -529,7 +532,7 @@ private:
static const int16 kMaxHealth = 98;
static const int16 kDeathTimerInit = 30;
static const int16 kDamageCooldownInit = 10;
- enum { kNumLevels = 15 };
+ enum { kNumLevels = Rebel1Release::kNumLevels };
// Streamed SMUSH audio.
RebelAudio _audio;
@@ -568,6 +571,8 @@ private:
int16 _level7WarningFrames;
int16 _level7WarningThreshold;
int _levelGameplayPhase;
+ int32 _level9PathLoopOffset;
+ int16 _level9SelectedPath;
bool _level14Play2BSplicePending;
bool _level14Play2BSpliced;
int32 _level14Play2BSpliceFrame;
diff --git a/engines/scumm/insane/rebel1/releases.cpp b/engines/scumm/insane/rebel1/releases.cpp
new file mode 100644
index 00000000000..4bdd970e401
--- /dev/null
+++ b/engines/scumm/insane/rebel1/releases.cpp
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/textconsole.h"
+#include "scumm/insane/rebel1/releases.h"
+
+namespace Scumm {
+
+// Variants match detection_tables.h. Keep release-specific resources and chapter
+// order here so menus, progression and saves use the same content description.
+static const Rebel1Release kReleases[] = {
+ {
+ "", "ASSAULT.EXE", "OPEN/O1LOGO.ANM", "OPEN/O1OPEN.ANM", "FIN/FNFINAL.ANM", true,
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
+ },
+ {
+ // CD-ROM Demo v1.51: the original dispatcher skips chapters 3-9 and
+ // 11-15, their transitions, and the retail ending.
+ "Demo v1.51", "ASSAULT.EXE", "OPEN/O1LOGO.ANM", "OPEN/O1OPEN.ANM", nullptr, false,
+ { 1, 2, 10 }
+ }
+};
+
+int Rebel1Release::getLevelCount() const {
+ int count = 0;
+ while (count < kNumLevels && levels[count])
+ ++count;
+ return count;
+}
+
+int Rebel1Release::findLevel(int level) const {
+ for (int i = 0; i < getLevelCount(); ++i) {
+ if (levels[i] == level)
+ return i;
+ }
+ return -1;
+}
+
+int Rebel1Release::resolvePasscodeLevel(int level) const {
+ // The original sampler advances passcodes past omitted chapters. A code
+ // beyond its last chapter returns to the menu instead of playing the ending.
+ if (level < 1 || level > kNumLevels + 1)
+ return 0;
+ for (; level <= kNumLevels; ++level) {
+ if (findLevel(level) >= 0)
+ return level;
+ }
+ return ending ? kNumLevels + 1 : 0;
+}
+
+const Rebel1Release &getRebel1Release(const char *variant) {
+ if (!variant)
+ variant = "";
+ for (const Rebel1Release &release : kReleases) {
+ if (!strcmp(variant, release.variant))
+ return release;
+ }
+ error("Unknown Rebel Assault release '%s'", variant);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/insane/rebel1/releases.h b/engines/scumm/insane/rebel1/releases.h
new file mode 100644
index 00000000000..33c01b43352
--- /dev/null
+++ b/engines/scumm/insane/rebel1/releases.h
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SCUMM_INSANE_REBEL1_RELEASES_H
+#define SCUMM_INSANE_REBEL1_RELEASES_H
+
+#include "common/scummsys.h"
+
+namespace Scumm {
+
+struct Rebel1Release {
+ enum { kNumLevels = 15 };
+
+ const char *variant;
+ const char *executable;
+ const char *logo;
+ const char *intro;
+ const char *ending;
+ bool chapterTransitions;
+ // Chapter numbers in playback order. Unused entries are zero.
+ byte levels[kNumLevels];
+
+ int getLevelCount() const;
+ int findLevel(int level) const;
+ int resolvePasscodeLevel(int level) const;
+};
+
+const Rebel1Release &getRebel1Release(const char *variant);
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index d5acbc79fc2..675f0fd9678 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -630,10 +630,14 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
shotOverlayHandled = _gameOp0BOverlayRenderedThisFrame;
drawGameOp0BTargetingAfterFetch = _gameOp0BOverlayRenderedThisFrame;
} else if (onFootMode) {
- if (_currentLevel == 8 && onFootAimMode && !onFootSequenceMode && _killCount > 0) {
- _fireCooldown = _playerFired ? 1 : 0;
- _vm->_smushVideoShouldFinish = true;
- return;
+ if (_currentLevel == 8 && onFootAimMode && !onFootSequenceMode) {
+ if (_level9PathLoopOffset >= 0) {
+ updateLevel9PathSelector(curFrame, maxFrame);
+ } else if (_killCount > 0) {
+ _fireCooldown = _playerFired ? 1 : 0;
+ _vm->_smushVideoShouldFinish = true;
+ return;
+ }
}
if (onFootSequenceMode)
@@ -899,6 +903,25 @@ void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height)
_lastHitTarget = 0;
}
+void InsaneRebel1::updateLevel9PathSelector(int32 curFrame, int32 maxFrame) {
+ if (_level9SelectedPath >= 0)
+ return;
+
+ if (_killCount > 0) {
+ // The DOS L9 loop reveals object 8 for the left tunnel (target 3),
+ // or object 7 for the right tunnel (target 4), then hides both targets.
+ // Remember the target hit before further mouse movement can change it.
+ _level9SelectedPath = isFrameObjectPrimarySet(4) ? 1 : 0;
+ clearFrameObjectPrimaryBits(0, _level9SelectedPath == 0 ? 0x01 : 0x02);
+ _frameObjectState[0] |= 0x30;
+ _gameplayFlags75fe |= 4;
+ } else if (maxFrame >= 9 && curFrame >= maxFrame + 1 - 10) {
+ // The original loops ten frames before the end until a path is chosen.
+ // Keep the stored background and input state when seeking back to frame 1.
+ _player->seekSan(nullptr, _level9PathLoopOffset - 8, 1);
+ }
+}
+
void InsaneRebel1::handleLevel14Play2BSplice(int32 curFrame, int32 maxFrame) {
if (_currentLevel != 13 || _levelGameplayPhase != 2 || _level14Play2BSpliced ||
_level14Play2BSplicePending || maxFrame < 0x0F)
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index c9a28805994..4adc3bf27ea 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -205,6 +205,9 @@ void InsaneRebel1::resetLevelAttemptState(int16 flyControlMode, int16 gameplayPh
}
void InsaneRebel1::playLevelTransitionCutscene(int level) {
+ if (!_release.chapterTransitions)
+ return;
+
switch (level) {
case 4:
// FALCON/BIGGS/WEDGE passcode group.
@@ -250,11 +253,13 @@ void InsaneRebel1::clearVideoBuffer() {
}
void InsaneRebel1::playIntroSequence() {
- playCinematic("OPEN/O1LOGO.ANM");
+ if (_release.logo)
+ playCinematic(_release.logo);
if (shouldAbortGameFlow())
return;
- playCinematic("OPEN/O1OPEN.ANM");
+ if (_release.intro)
+ playCinematic(_release.intro);
}
bool InsaneRebel1::runLevel1() {
@@ -812,7 +817,13 @@ bool InsaneRebel1::runLevel9() {
const int randPath1 = getOriginalRouteBit();
const int randPath2 = getOriginalRouteBit();
const int randPath3 = getOriginalRouteBit();
- auto playLevel9PathSelector = [&](const char *filename) {
+ auto playLevel9PathSelector = [&](const char *filename, bool hasExitAnimation) {
+ // L9PLAY6 stores both exit animations after its idle loop. Frame 1
+ // restores the STOR background without repeating frame 0's GAME reset.
+ const int32 loopOffset = hasExitAnimation ? findAnimFrameChunkOffset(_vm, filename, 1) : -1;
+ if (hasExitAnimation && loopOffset < 0)
+ error("Unable to find the path selector loop in %s", filename);
+
while (!shouldAbortGameFlow()) {
_onFootCharX = 0;
_onFootCharY = 0;
@@ -822,10 +833,15 @@ bool InsaneRebel1::runLevel9() {
_posAccumY = 0;
_killCount = 0;
_lastHitTarget = 0;
+ _level9SelectedPath = -1;
+ _level9PathLoopOffset = loopOffset;
playInteractiveVideo(filename);
+ _level9PathLoopOffset = -1;
if (shouldAbortGameFlow() || _health < 0)
return -1;
+ if (_level9SelectedPath >= 0)
+ return (int)_level9SelectedPath;
if (_interactiveVideoCheatSkipped)
return (_shipPosX < kRA1CenterX) ? 0 : 1;
if (_killCount > 0)
@@ -867,7 +883,7 @@ bool InsaneRebel1::runLevel9() {
return false;
loadTuningForLevel(0x0C);
- const int side1 = playLevel9PathSelector("LVL9/L9PLAY2.ANM");
+ const int side1 = playLevel9PathSelector("LVL9/L9PLAY2.ANM", false);
if (shouldAbortGameFlow())
return false;
if (_health < 0)
@@ -911,7 +927,7 @@ bool InsaneRebel1::runLevel9() {
return false;
loadTuningForLevel(0x0C);
- const int side2 = playLevel9PathSelector("LVL9/L9PLAY4.ANM");
+ const int side2 = playLevel9PathSelector("LVL9/L9PLAY4.ANM", false);
if (shouldAbortGameFlow())
return false;
if (_health < 0)
@@ -941,7 +957,9 @@ bool InsaneRebel1::runLevel9() {
return false;
loadTuningForLevel(0x0C);
- const int side3 = playLevel9PathSelector("LVL9/L9PLAY6.ANM");
+ // Sega CD keeps the exit animations in separate L9PLAY6A/B clips.
+ const int side3 = playLevel9PathSelector("LVL9/L9PLAY6.ANM",
+ _vm->_game.platform != Common::kPlatformSegaCD);
if (shouldAbortGameFlow())
return false;
if (_health < 0)
@@ -1359,8 +1377,11 @@ void InsaneRebel1::runGame() {
&InsaneRebel1::runLevel15
};
const int numLevels = ARRAYSIZE(kLevelRunners);
+ const int availableLevels = _release.getLevelCount();
auto runLevelsFrom = [&](int startLevel, bool resetRunState) {
- int firstLevel = CLIP<int>(startLevel, 1, numLevels);
+ int firstLevelIndex = _release.findLevel(startLevel);
+ if (firstLevelIndex < 0)
+ return;
while (!_vm->shouldQuit()) {
_loadRequested = false;
@@ -1375,9 +1396,10 @@ void InsaneRebel1::runGame() {
}
resetRunState = false;
- for (int level = firstLevel;
- level <= numLevels && completed && !shouldAbortGameFlow();
- ++level) {
+ for (int levelIndex = firstLevelIndex;
+ levelIndex < availableLevels && completed && !shouldAbortGameFlow();
+ ++levelIndex) {
+ const int level = _release.levels[levelIndex];
_resumeLevel = level;
playLevelTransitionCutscene(level);
if (shouldAbortGameFlow())
@@ -1390,21 +1412,24 @@ void InsaneRebel1::runGame() {
completed = (this->*kLevelRunners[level - 1])();
if (completed) {
lastCompletedLevel = level;
- if (level < numLevels) {
- _startLevel = level + 1;
+ if (levelIndex + 1 < availableLevels) {
+ _startLevel = _release.levels[levelIndex + 1];
_resumeLevel = _startLevel;
autosaveProgress();
+ } else if (!_release.ending) {
+ _startLevel = _release.levels[0];
}
}
}
if (_loadRequested) {
- firstLevel = getCurrentSaveLevel();
+ firstLevelIndex = _release.findLevel(getCurrentSaveLevel());
continue;
}
- if (!shouldAbortGameFlow() && completed && lastCompletedLevel == numLevels)
- playCinematic("FIN/FNFINAL.ANM");
+ if (!shouldAbortGameFlow() && completed &&
+ lastCompletedLevel == _release.levels[availableLevels - 1] && _release.ending)
+ playCinematic(_release.ending);
if (!shouldAbortGameFlow())
runHighScoreNameEntry();
_currentLevel = 0;
@@ -1448,12 +1473,12 @@ void InsaneRebel1::runGame() {
const int passcodeLevel = runPasscodeEntryDialog();
if (passcodeLevel >= 1 && passcodeLevel <= numLevels)
runLevelsFrom(passcodeLevel, true);
- else if (passcodeLevel == numLevels + 1) {
+ else if (passcodeLevel == numLevels + 1 && _release.ending) {
_health = kMaxHealth;
_lives = 3;
_score = 0;
_prevScore = 0;
- playCinematic("FIN/FNFINAL.ANM");
+ playCinematic(_release.ending);
if (!shouldAbortGameFlow())
runHighScoreNameEntry();
_currentLevel = 0;
@@ -1468,8 +1493,8 @@ void InsaneRebel1::runGame() {
}
case 5:
showHighScores();
- if (!shouldAbortGameFlow())
- playCinematic("OPEN/O1OPEN.ANM");
+ if (!shouldAbortGameFlow() && _release.intro)
+ playCinematic(_release.intro);
break;
case 6:
return;
@@ -1514,6 +1539,8 @@ void InsaneRebel1::setupInteractiveVideoState(int32 startFrame) {
resetFrameObjectState();
resetGamepadReticleAim();
}
+ if (_level9PathLoopOffset >= 0)
+ _frameObjectState[0] |= 0x03; // Hide L9PLAY6's mutually exclusive exit objects 7 and 8.
_vm->_smushVideoShouldFinish = false;
splayer->setPreserveVideoStateOnNextPlay(preserveVideoState);
splayer->setCurVideoFlags(0x28);
diff --git a/engines/scumm/insane/rebel1/saveload.cpp b/engines/scumm/insane/rebel1/saveload.cpp
index 61d7ab8bef5..4bc3cac8c42 100644
--- a/engines/scumm/insane/rebel1/saveload.cpp
+++ b/engines/scumm/insane/rebel1/saveload.cpp
@@ -159,7 +159,11 @@ bool InsaneRebel1::readSaveState(int slot, SaveState &state, Common::String *des
return false;
}
- state.resumeLevel = CLIP<int>(in->readSint16LE(), 1, kNumLevels);
+ state.resumeLevel = in->readSint16LE();
+ if (_release.findLevel(state.resumeLevel) < 0) {
+ warning("RA1: chapter %d is not available in this release", state.resumeLevel);
+ return false;
+ }
state.lives = MAX<int>(in->readSint16LE(), 0);
state.score = in->readSint32LE();
state.prevScore = in->readSint32LE();
@@ -236,7 +240,7 @@ void InsaneRebel1::autosaveProgress() {
SaveState oldState;
Common::String oldDesc;
if (readSaveState(slot, oldState, &oldDesc)) {
- if (oldState.resumeLevel > state.resumeLevel ||
+ if (_release.findLevel(oldState.resumeLevel) > _release.findLevel(state.resumeLevel) ||
(oldState.resumeLevel == state.resumeLevel && oldState.lives >= state.lives)) {
debugC(DEBUG_INSANE, "skipping autosave slot=%d level=%d lives=%d; existing level=%d lives=%d",
slot, state.resumeLevel, state.lives, oldState.resumeLevel, oldState.lives);
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 46c1673a742..5436864add9 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -151,6 +151,7 @@ MODULE_OBJS += \
insane/rebel/rebel_gamepad.o \
insane/rebel/rebel_touch.o \
insane/rebel1/rebel.o \
+ insane/rebel1/releases.o \
insane/rebel1/audio.o \
insane/rebel1/iact.o \
insane/rebel1/levels.o \
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 7b499df44fe..f275d63b600 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Thu Jul 23 05:28:13 2026
+ This file was generated by the md5table tool on Sun Sep 27 07:03:59 2026
DO NOT EDIT MANUALLY!
*/
@@ -150,6 +150,7 @@ static const MD5Table md5table[] = {
{ "25a7ce1cda4c7181c33c1c87a7cf6c7a", "monkey", "VGA", "VGA", 8357, Common::FR_FRA, Common::kPlatformDOS },
{ "261cd0da05b12917458d1c816a9d2ec9", "monkey", "CD", "CD", 8955, Common::PL_POL, Common::kPlatformDOS },
{ "2723fea3dae0cb47768c424b145ae0e7", "tentacle", "Floppy", "Floppy", 7932, Common::EN_ANY, Common::kPlatformDOS },
+ { "274f6026d41fa2a2990a6d6b7a561dc2", "rebel1", "Demo v1.51", "Demo v1.51", 217559, Common::EN_ANY, Common::kPlatformDOS },
{ "275786f0f6894d1fe1ab5f1d103bb814", "indy3", "VGA", "VGA", 6295, Common::RU_RUS, Common::kPlatformDOS },
{ "27b2ef1653089fe5b897d9cc89ce784f", "balloon", "HE 80", "", -1, Common::RU_RUS, Common::kPlatformWindows },
{ "27b3a4224ad63d5b04627595c1c1a025", "zak", "V2", "V2", -1, Common::IT_ITA, Common::kPlatformAmiga },
Commit: 3113ae98e7cd273828f001c0167f80d4ead16dad
https://github.com/scummvm/scummvm/commit/3113ae98e7cd273828f001c0167f80d4ead16dad
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-27T16:14:25+02:00
Commit Message:
SCUMM: RA1: added support for demo 1994-04-13
Changed paths:
devtools/scumm-md5.txt
engines/scumm/detection.h
engines/scumm/detection_internal.h
engines/scumm/detection_tables.h
engines/scumm/insane/rebel1/iact.cpp
engines/scumm/insane/rebel1/menu.cpp
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/releases.cpp
engines/scumm/insane/rebel1/releases.h
engines/scumm/insane/rebel1/render.cpp
engines/scumm/insane/rebel1/runlevels.cpp
engines/scumm/metaengine.cpp
engines/scumm/scumm-md5.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index ce415ea562c..159d286c16c 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -451,6 +451,7 @@ ft Full Throttle
rebel1 Star Wars: Rebel Assault
274f6026d41fa2a2990a6d6b7a561dc2 217559 en DOS Demo v1.51 Demo v1.51 CD-ROM demo -
+ 551a7c06bf5098b32d5fe7641d695df8 226750 en DOS Demo 1994-04-13 Demo 1994-04-13 CD-ROM demo (chapters 8 and 10) -
b9ac90e4411da1b6f7dd075cc4b03c2a 214435 en DOS - v1.0 - -
e210a57b756473be705d24b9378a8233 212383 en DOS - v1.3 - -
6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
diff --git a/engines/scumm/detection.h b/engines/scumm/detection.h
index 8f5d11f0dc6..87c12d3b401 100644
--- a/engines/scumm/detection.h
+++ b/engines/scumm/detection.h
@@ -44,6 +44,7 @@ namespace Scumm {
#define GAMEOPTION_REBEL2_NO_DAMAGE GUIO_GAMEOPTIONS13
#define GAMEOPTION_REBEL2_YODA_MODE GUIO_GAMEOPTIONS14
#define GAMEOPTION_REBEL1_NO_DAMAGE GUIO_GAMEOPTIONS15
+#define GAMEOPTION_REBEL1_RESTORED_CONTENT GUIO_GAMEOPTIONS16
/**
* Descriptor of a specific SCUMM game. Used internally to store
diff --git a/engines/scumm/detection_internal.h b/engines/scumm/detection_internal.h
index e6b00513396..ed243123194 100644
--- a/engines/scumm/detection_internal.h
+++ b/engines/scumm/detection_internal.h
@@ -647,6 +647,11 @@ static void detectGames(const Common::FSList &fslist, Common::List<DetectorResul
if (gfp->genMethod == kGenRoomNumSteam || gfp->genMethod == kGenDiskNumSteam)
continue;
+ // REBEL.EXE is also the launcher for retail Rebel Assault and other
+ // demos. Require a known executable before using a demo's embedded tables.
+ if (!scumm_stricmp(gfp->gameid, "rebel1") && file.equalsIgnoreCase("REBEL.EXE"))
+ continue;
+
// ____ _ ____
// | _ \ __ _ _ __| |_ |___ \ *
// | |_) / _` | '__| __| __) |
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 5bc9393be02..5d5815d2214 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -232,6 +232,7 @@ static const GameSettings gameVariantsTable[] = {
{"rebel1", "", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
{"rebel1", "Demo v1.51", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
+ {"rebel1", "Demo 1994-04-13", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO4(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE, GAMEOPTION_REBEL1_RESTORED_CONTENT)},
{"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
@@ -507,6 +508,7 @@ static const GameFilenamePattern gameFilenamesTable[] = {
{ "rebel1", "ASSAULT.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
{ "rebel1", "ASSAULT.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo v1.51" },
+ { "rebel1", "REBEL.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo 1994-04-13" },
{ "rebel1", "Rebel Assault", kGenUnchanged, UNK_LANG, Common::kPlatformMacintosh, "" },
{ "rebel1", "O1LOGO.AAA", kGenUnchanged, UNK_LANG, Common::kPlatform3DO, "" },
{ "rebel1", "SUB_AP.BIN", kGenUnchanged, UNK_LANG, Common::kPlatformSegaCD, "" },
diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index 683657b0946..df21534b7b7 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -461,9 +461,10 @@ inline bool isLevel14Phase2DamageLatch(uint16 code) {
}
}
-inline bool hasLevel8WalkerHazardRoute0(uint16 frame, int16 viewX, int16 viewY) {
+inline bool hasLevel8WalkerHazardRoute0(uint16 frame, int16 viewX, int16 viewY, bool earlyShot) {
switch (frame) {
case 0x00CD:
+ return earlyShot && viewX <= 0x28;
case 0x00EF:
return viewX <= 0x28;
case 0x0294:
@@ -512,10 +513,10 @@ inline bool hasLevel8WalkerHazardRoute2(uint16 frame, int16 viewX, int16 viewY)
}
}
-inline bool hasLevel8WalkerPlayerHit(int route, uint16 frame, int16 viewX, int16 viewY) {
+inline bool hasLevel8WalkerPlayerHit(int route, uint16 frame, int16 viewX, int16 viewY, bool earlyShot) {
switch (CLIP<int>(route, 0, 2)) {
case 0:
- return hasLevel8WalkerHazardRoute0(frame, viewX, viewY);
+ return hasLevel8WalkerHazardRoute0(frame, viewX, viewY, earlyShot);
case 1:
return hasLevel8WalkerHazardRoute1(frame, viewX, viewY);
case 2:
@@ -1517,7 +1518,7 @@ void InsaneRebel1::updateGameOp0BPhysics() {
if (_currentLevel == 7) {
const uint16 walkerFrame = (uint16)_currentSmushFrame;
level8WalkerPlayerHit = hasLevel8WalkerPlayerHit(_levelRouteIndex, walkerFrame,
- _perspectiveX, _perspectiveY);
+ _perspectiveX, _perspectiveY, _release.walker->earlyShot);
// Player collision and boss damage are tracked separately.
if (level8WalkerPlayerHit)
_damageFlags |= 0x20;
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 0c40047c1ff..157d4d37c48 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -297,15 +297,19 @@ static void drawRebel1MenuFrame(byte *dst, int pitch, int width, int height, int
}
int InsaneRebel1::getMainMenuItemCount() const {
- return _unlockAllLevels ? kRA1MainMenuItemCount : kRA1MainMenuItemCount - 1;
+ return kRA1MainMenuItemCount - (_unlockAllLevels ? 0 : 1) - (_release.passcodes ? 0 : 1);
}
int InsaneRebel1::getMainMenuResultForSelection(int selection) const {
if (selection < 0 || selection >= getMainMenuItemCount())
return kRA1MainMenuItemCount;
- if (_unlockAllLevels || selection < 3)
- return selection + 1;
- return selection + 2;
+ for (int result = 1; result <= kRA1MainMenuItemCount; ++result) {
+ if ((result == 3 && !_release.passcodes) || (result == 4 && !_unlockAllLevels))
+ continue;
+ if (selection-- == 0)
+ return result;
+ }
+ return kRA1MainMenuItemCount;
}
void InsaneRebel1::setVirtualKeyboardVisible(bool visible) {
@@ -1129,14 +1133,6 @@ void InsaneRebel1::renderMainMenuItems(byte *dst, int pitch, int width, int heig
uiStr(kR1StrMenuContinueDemo),
uiStr(kR1StrMenuExitToDos)
};
- const char *kMenuItemsLocked[kRA1MainMenuItemCount - 1] = {
- uiStr(kR1StrMenuNewGame),
- uiStr(kR1StrMenuGameOptions),
- uiStr(kR1StrMenuEnterPasscode),
- uiStr(kR1StrMenuContinueDemo),
- uiStr(kR1StrMenuExitToDos)
- };
- const char *const *menuItems = _unlockAllLevels ? kMenuItems : kMenuItemsLocked;
const int mainMenuItemCount = getMainMenuItemCount();
const char *menuTitle = uiStr(kR1StrMainMenuTitle);
@@ -1145,11 +1141,12 @@ void InsaneRebel1::renderMainMenuItems(byte *dst, int pitch, int width, int heig
drawMenuTitleText(dst, pitch, width, height, titleX, 30, menuTitle);
for (int i = 0; i < mainMenuItemCount; i++) {
- const int textW = getMenuTalkTextWidth(menuItems[i]);
+ const char *text = kMenuItems[getMainMenuResultForSelection(i) - 1];
+ const int textW = getMenuTalkTextWidth(text);
const int textX = getRebel1MenuCenteredX(textW);
const int y = 0x3c + i * kRA1MenuRowH;
- drawMenuTalkText(dst, pitch, width, height, textX, y, menuItems[i]);
+ drawMenuTalkText(dst, pitch, width, height, textX, y, text);
if (i == _menuSelection)
drawRebel1MenuFrame(dst, pitch, width, height,
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index bddeba8c7db..e90075a2695 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -41,7 +41,7 @@ namespace Scumm {
// Per-difficulty tuning tables from assault_data_3.bin (also loadable from C:\rebltune.txt)
// 21 sub-levels x 3 difficulties x 13 fields
// Fields: roll, lift, slide, drift, snap, miss, wham, shot, kill, time, levelPts, bonus, flags
-const int16 kTuningTable[21][3][13] = {
+const Rebel1TuningTable kTuningTable = {
// Sub-level 0: "1A" (Flight Training part 1)
{
{ 100, 100, 60, 110, 0, 0, 15, 0, 0, 5, 500, 100, 2048 }, // Easy
@@ -169,25 +169,38 @@ const int16 kTuningTable[21][3][13] = {
{ 0, 0, 0, 0, 2, 22, 35, 4, 75, 10, 1500, 500, 2050 }, // Hard
},
};
-const int kNumTunedLevels = 21;
+const int kNumTunedLevels = ARRAYSIZE(kTuningTable);
+
+void InsaneRebel1::loadTuningData() {
+ memcpy(_tuningTable, kTuningTable, sizeof(_tuningTable));
+ if (!_release.tuning.offset)
+ return;
+
+ Common::File f;
+ if (!f.open(_release.executable))
+ error("Unable to open %s for Rebel Assault tuning data", _release.executable);
+
+ if (!_release.readTuningData(f, _tuningTable))
+ error("Unable to read Rebel Assault tuning data from %s", _release.executable);
+}
void InsaneRebel1::loadTuningForLevel(int level) {
int d = CLIP(_difficulty, 0, 2);
int l = CLIP(level, 0, kNumTunedLevels - 1);
- _tuning.roll = kTuningTable[l][d][0];
- _tuning.lift = kTuningTable[l][d][1];
- _tuning.slide = kTuningTable[l][d][2];
- _tuning.drift = kTuningTable[l][d][3];
- _tuning.snap = kTuningTable[l][d][4];
- _tuning.miss = kTuningTable[l][d][5];
- _tuning.wham = kTuningTable[l][d][6];
- _tuning.shot = kTuningTable[l][d][7];
- _tuning.kill = kTuningTable[l][d][8];
- _tuning.time = kTuningTable[l][d][9];
- _tuning.levelPts = kTuningTable[l][d][10];
- _tuning.bonus = kTuningTable[l][d][11];
- _tuning.flags = kTuningTable[l][d][12];
+ _tuning.roll = _tuningTable[l][d][0];
+ _tuning.lift = _tuningTable[l][d][1];
+ _tuning.slide = _tuningTable[l][d][2];
+ _tuning.drift = _tuningTable[l][d][3];
+ _tuning.snap = _tuningTable[l][d][4];
+ _tuning.miss = _tuningTable[l][d][5];
+ _tuning.wham = _tuningTable[l][d][6];
+ _tuning.shot = _tuningTable[l][d][7];
+ _tuning.kill = _tuningTable[l][d][8];
+ _tuning.time = _tuningTable[l][d][9];
+ _tuning.levelPts = _tuningTable[l][d][10];
+ _tuning.bonus = _tuningTable[l][d][11];
+ _tuning.flags = _tuningTable[l][d][12];
resetGameplayFlagsFromTuning();
_protectedTargetA = 0;
_protectedTargetB = 0;
@@ -282,7 +295,7 @@ const char *InsaneRebel1::uiStr(int id) const {
return kRebel1UiFallback[id];
}
-// All localized DOS releases keep their UI text in ASSAULT.EXE as NUL-terminated
+// DOS releases keep their UI text in the executable as NUL-terminated
// strings interleaved with the (language-neutral) data file names, in the same
// local order across builds. Extract them anchored on those neighbors; anything
// that fails validation keeps its English fallback.
@@ -348,12 +361,18 @@ void InsaneRebel1::loadLocalizedUiStrings() {
return true;
};
- // Main menu: title and the five original items follow OPEN\O1OPTION.ANM.
+ // The early demo has only START GAME, GAME OPTIONS and CONTINUE DEMO.
int idx = find("OPEN\\O1OPTION.ANM", 0);
if (idx >= 0) {
assign(kR1StrMainMenuTitle, idx + 1);
- for (int i = 0; i < 5; i++)
- assign(kR1StrMenuNewGame + i, idx + 2 + i);
+ assign(kR1StrMenuNewGame, idx + 2);
+ assign(kR1StrMenuGameOptions, idx + 3);
+ if (_release.passcodes) {
+ for (int i = 2; i < 5; i++)
+ assign(kR1StrMenuNewGame + i, idx + 2 + i);
+ } else {
+ assign(kR1StrMenuContinueDemo, idx + 4);
+ }
}
// High score table title follows OPEN\O1SCORE.ANM.
@@ -406,6 +425,14 @@ void InsaneRebel1::loadLocalizedUiStrings() {
// calibration block.
if (!assign(kR1StrNewHighScore, pw - 4) && paren >= 0)
assign(kR1StrNewHighScore, paren - 7);
+ } else if (!_release.passcodes) {
+ // The early demo places these three strings after the options movie.
+ idx = find("OPEN\\O1HWARE.ANM", 0);
+ if (idx >= 0) {
+ assign(kR1StrCompletionBonusFmt, idx + 1);
+ assign(kR1StrBonusFmt, idx + 2);
+ assign(kR1StrChapterComplete, idx + 3);
+ }
}
// Level result strings, each anchored on the video played before them.
@@ -460,7 +487,8 @@ void InsaneRebel1::loadLocalizedUiStrings() {
debugC(DEBUG_INSANE, "InsaneRebel1: %d/%d UI strings loaded from %s", loaded, kR1StrUiCount, _release.executable);
}
-InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm), _release(getRebel1Release(scumm->_game.variant)) {
+InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm),
+ _release(getRebel1Release(scumm->_game.variant, ConfMan.getBool("rebel1_restored_content"))) {
Insane::_vm = scumm;
// Rebel Assault skips ScummEngine::resetScumm(), which normally clears this state.
for (int i = 0; i < kScummActionCount; i++)
@@ -492,6 +520,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm), _relea
_driftParam = 0;
_difficulty = 0;
+ loadTuningData();
loadTuningForLevel(0);
_perspectiveX = 0;
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index d96ae57a85d..9de3046f833 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -334,7 +334,7 @@ public:
private:
ScummEngine_v7 *_vm;
- const Rebel1Release &_release;
+ const Rebel1Release _release;
// Sprite and font banks.
RA1SpriteBank _shipBank;
@@ -502,7 +502,9 @@ private:
int16 flags;
};
TuningParams _tuning;
+ Rebel1TuningTable _tuningTable;
+ void loadTuningData();
void loadTuningForLevel(int level);
void resetGameplayFlagsFromTuning();
@@ -722,11 +724,6 @@ private:
int16 _walkerBranchChoice;
bool _walkerRoundReplay;
- // Attack window frame numbers per route.
- static const int16 kWalkerAttackWindow1[3];
- static const int16 kWalkerAttackWindow2[3];
- static const int16 kWalkerAttackWindow3[3];
-
static const int kFrameObjectStateBytes = 300;
byte _frameObjectState[kFrameObjectStateBytes];
};
diff --git a/engines/scumm/insane/rebel1/releases.cpp b/engines/scumm/insane/rebel1/releases.cpp
index 4bdd970e401..aace005d32f 100644
--- a/engines/scumm/insane/rebel1/releases.cpp
+++ b/engines/scumm/insane/rebel1/releases.cpp
@@ -19,23 +19,46 @@
*
*/
+#include "common/stream.h"
#include "common/textconsole.h"
#include "scumm/insane/rebel1/releases.h"
namespace Scumm {
+static const Rebel1WalkerData kRetailWalker = {
+ { { 2588, 2323, 877 }, { 1709, 1444, -2 }, { 262, -2, -2 } },
+ 2, true
+};
+
+static const Rebel1WalkerData kDemo940413Walker = {
+ { { 2591, 2324, 878 }, { 1712, 1445, -1 }, { 265, -1, -1 } },
+ 1, false
+};
+
+static const byte kDemo940413RestoredLevels[Rebel1Release::kNumLevels] = { 3, 6, 8, 10 };
+
// Variants match detection_tables.h. Keep release-specific resources and chapter
// order here so menus, progression and saves use the same content description.
static const Rebel1Release kReleases[] = {
{
- "", "ASSAULT.EXE", "OPEN/O1LOGO.ANM", "OPEN/O1OPEN.ANM", "FIN/FNFINAL.ANM", true,
- { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
+ "", "ASSAULT.EXE", "OPEN/O1LOGO.ANM", nullptr, "OPEN/O1OPEN.ANM", "FIN/FNFINAL.ANM", true, true,
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, nullptr,
+ 0x7fff, false, { 0, 0, 0 }, &kRetailWalker
},
{
// CD-ROM Demo v1.51: the original dispatcher skips chapters 3-9 and
// 11-15, their transitions, and the retail ending.
- "Demo v1.51", "ASSAULT.EXE", "OPEN/O1LOGO.ANM", "OPEN/O1OPEN.ANM", nullptr, false,
- { 1, 2, 10 }
+ "Demo v1.51", "ASSAULT.EXE", "OPEN/O1LOGO.ANM", nullptr, "OPEN/O1OPEN.ANM", nullptr, false, true,
+ { 1, 2, 10 }, nullptr,
+ 0x7fff, false, { 0, 0, 0 }, &kRetailWalker
+ },
+ {
+ // The dispatcher at 0x3466 starts at chapter 8, skips 9, and returns
+ // to the menu after 10. Chapters 3 and 6 have complete resources and
+ // handlers, but are only reachable through the original debug keys.
+ "Demo 1994-04-13", "REBEL.EXE", "OPEN/O1LOGO.ANM", "OPEN/O1DEMO.ANM", "OPEN/O1OPEN.ANM", nullptr, false, false,
+ { 8, 10 }, kDemo940413RestoredLevels,
+ 1 << (6 - 1), true, { 0x289dc, 0x1a, 0x222 }, &kDemo940413Walker
}
};
@@ -57,7 +80,7 @@ int Rebel1Release::findLevel(int level) const {
int Rebel1Release::resolvePasscodeLevel(int level) const {
// The original sampler advances passcodes past omitted chapters. A code
// beyond its last chapter returns to the menu instead of playing the ending.
- if (level < 1 || level > kNumLevels + 1)
+ if (!passcodes || level < 1 || level > kNumLevels + 1)
return 0;
for (; level <= kNumLevels; ++level) {
if (findLevel(level) >= 0)
@@ -66,12 +89,36 @@ int Rebel1Release::resolvePasscodeLevel(int level) const {
return ending ? kNumLevels + 1 : 0;
}
-const Rebel1Release &getRebel1Release(const char *variant) {
+bool Rebel1Release::readTuningData(Common::SeekableReadStream &in, Rebel1TuningTable &table) const {
+ const int64 requiredSize = (int64)tuning.offset +
+ (ARRAYSIZE(table) - 1) * tuning.levelStride +
+ (ARRAYSIZE(table[0]) - 1) * tuning.difficultyStride + sizeof(table[0][0]);
+ if (!tuning.offset || in.size() < requiredSize)
+ return false;
+
+ for (uint level = 0; level < ARRAYSIZE(table); ++level) {
+ for (uint difficulty = 0; difficulty < ARRAYSIZE(table[level]); ++difficulty) {
+ const int64 offset = (int64)tuning.offset +
+ level * tuning.levelStride + difficulty * tuning.difficultyStride;
+ if (!in.seek(offset))
+ return false;
+ for (uint field = 0; field < ARRAYSIZE(table[level][difficulty]); ++field)
+ table[level][difficulty][field] = in.readSint16LE();
+ }
+ }
+ return !in.err() && !in.eos();
+}
+
+Rebel1Release getRebel1Release(const char *variant, bool restoredContent) {
if (!variant)
variant = "";
for (const Rebel1Release &release : kReleases) {
- if (!strcmp(variant, release.variant))
- return release;
+ if (!strcmp(variant, release.variant)) {
+ Rebel1Release result = release;
+ if (restoredContent && release.restoredLevels)
+ memcpy(result.levels, release.restoredLevels, sizeof(result.levels));
+ return result;
+ }
}
error("Unknown Rebel Assault release '%s'", variant);
}
diff --git a/engines/scumm/insane/rebel1/releases.h b/engines/scumm/insane/rebel1/releases.h
index 33c01b43352..05487a303f2 100644
--- a/engines/scumm/insane/rebel1/releases.h
+++ b/engines/scumm/insane/rebel1/releases.h
@@ -24,26 +24,57 @@
#include "common/scummsys.h"
+namespace Common {
+class SeekableReadStream;
+}
+
namespace Scumm {
+typedef int16 Rebel1TuningTable[21][3][13];
+
+struct Rebel1TuningLayout {
+ uint32 offset; // File offset in the executable; zero uses the retail defaults.
+ uint16 levelStride;
+ uint16 difficultyStride;
+};
+
+struct Rebel1WalkerData {
+ // Window ends indexed by [window][route], in ANM-local frames. Negative entries are disabled.
+ int16 attackWindows[3][3];
+ // Destination frame after the extra source frame at the splice.
+ int16 routeStartFrame;
+ bool earlyShot;
+};
+
struct Rebel1Release {
enum { kNumLevels = 15 };
const char *variant;
const char *executable;
const char *logo;
+ const char *introNotice;
const char *intro;
const char *ending;
bool chapterTransitions;
+ bool passcodes;
// Chapter numbers in playback order. Unused entries are zero.
byte levels[kNumLevels];
+ // Optional replacement chapter order when restored content is enabled.
+ const byte *restoredLevels;
+ // Bit N enables the target/path bonus for chapter N + 1.
+ uint16 chapterBonusMask;
+ // Early chapter 6 awards one or three tuning bonuses at 20/30 kills.
+ bool tieredAsteroidChaseBonus;
+ Rebel1TuningLayout tuning;
+ const Rebel1WalkerData *walker;
int getLevelCount() const;
int findLevel(int level) const;
int resolvePasscodeLevel(int level) const;
+ bool readTuningData(Common::SeekableReadStream &in, Rebel1TuningTable &table) const;
};
-const Rebel1Release &getRebel1Release(const char *variant);
+Rebel1Release getRebel1Release(const char *variant, bool restoredContent = false);
} // End of namespace Scumm
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index 675f0fd9678..59104dd4653 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -1443,7 +1443,7 @@ static const char *const kChapterCompletePasswords[] = {
};
const char *InsaneRebel1::getChapterCompletePassword(int passwordIndex) const {
- if (passwordIndex < 1 || passwordIndex > (int)ARRAYSIZE(kChapterCompletePasswords))
+ if (!_release.passcodes || passwordIndex < 1 || passwordIndex > (int)ARRAYSIZE(kChapterCompletePasswords))
return nullptr;
return kChapterCompletePasswords[passwordIndex - 1];
@@ -1802,11 +1802,6 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
_hudDirtyFlag = 0xFF;
}
-// Each route has up to 3 attack windows, indexed by ANM-local frame. -2 means disabled.
-const int16 InsaneRebel1::kWalkerAttackWindow1[3] = { 2588, 2323, 877 };
-const int16 InsaneRebel1::kWalkerAttackWindow2[3] = { 1709, 1444, -2 };
-const int16 InsaneRebel1::kWalkerAttackWindow3[3] = { 262, -2, -2 };
-
void InsaneRebel1::updateLevel8WalkerState() {
if (_walkerHealth >= 11) {
_walkerHealth = (int16)(100 - (_killCount + (_killCount >> 2)));
@@ -1822,16 +1817,12 @@ void InsaneRebel1::updateLevel8WalkerState() {
int route = CLIP(_levelRouteIndex, 0, 2);
uint16 fc = (uint16)_currentSmushFrame;
- const int16 *windows[3] = {
- &kWalkerAttackWindow1[route],
- &kWalkerAttackWindow2[route],
- &kWalkerAttackWindow3[route]
- };
+ const int16 (*windows)[3] = _release.walker->attackWindows;
int16 frameNum = (int16)fc;
bool inWindow = false;
for (int w = 0; w < 3; w++) {
- int16 windowEnd = *windows[w];
+ int16 windowEnd = windows[w][route];
if (windowEnd < 0) continue;
if (frameNum > windowEnd - 100 && frameNum <= windowEnd) {
inWindow = true;
@@ -1847,7 +1838,7 @@ void InsaneRebel1::updateLevel8WalkerState() {
bool inDirectionalPhase = false;
for (int w = 0; w < 3; w++) {
- int16 windowEnd = *windows[w];
+ int16 windowEnd = windows[w][route];
if (windowEnd < 0) continue;
if (frameNum > windowEnd - 0x32 && frameNum <= windowEnd) {
inDirectionalPhase = true;
@@ -1866,7 +1857,7 @@ void InsaneRebel1::updateLevel8WalkerState() {
}
for (int w = 0; w < 3; w++) {
- int16 windowEnd = *windows[w];
+ int16 windowEnd = windows[w][route];
if (windowEnd < 0) continue;
if (fc != (uint16)windowEnd) continue;
@@ -1886,9 +1877,10 @@ void InsaneRebel1::updateLevel8WalkerState() {
_pendingRouteIndex = newRoute;
// The original makes this choice in the post-frame callback, after
// that frame's splice check. It renders one more source frame before
- // switching, advancing the destination's frame-1 start by that frame.
+ // switching. The early demo requests destination frame 0, retail 1;
+ // both starts advance by the extra source frame.
_pendingRouteCutoverFrame = _currentSmushFrame + 1;
- _pendingRouteStartFrame = 2;
+ _pendingRouteStartFrame = _release.walker->routeStartFrame;
debugC(DEBUG_INSANE, "L8 branch: route=%d -> %d at localFrame=%u shipX=%d resumeLocalFrame=%d cutoverFrame=%d",
route, newRoute, (unsigned)fc, _shipPosX,
(int)_pendingRouteStartFrame, (int)_pendingRouteCutoverFrame);
@@ -1916,17 +1908,13 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
}
int route = CLIP(_levelRouteIndex, 0, 2);
- const int16 *windows[3] = {
- &kWalkerAttackWindow1[route],
- &kWalkerAttackWindow2[route],
- &kWalkerAttackWindow3[route]
- };
+ const int16 (*windows)[3] = _release.walker->attackWindows;
int16 frameNum = (int16)(uint16)_currentSmushFrame;
bool inWindow = false;
bool inDirectionalPhase = false;
for (int w = 0; w < 3; w++) {
- int16 windowEnd = *windows[w];
+ int16 windowEnd = windows[w][route];
if (windowEnd < 0) continue;
if (frameNum > windowEnd - 100 && frameNum <= windowEnd) {
inWindow = true;
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 4adc3bf27ea..14bae7650f7 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -115,6 +115,13 @@ void InsaneRebel1::playChapterCompleteCinematic(const char *filename, int16 unlo
const char *bonusLabel1, const char *detailText1, int bonusValue1,
const char *bonusLabel2, const char *detailText2, int bonusValue2,
int passwordIndex) {
+ if (!_release.passcodes)
+ passwordIndex = 0;
+ if (!(_release.chapterBonusMask & (1u << (unlockedChapter - 1)))) {
+ bonusLabel1 = bonusLabel2 = nullptr;
+ detailText1 = detailText2 = nullptr;
+ bonusValue1 = bonusValue2 = 0;
+ }
beginChapterSummaryOverlay(revealOffsetFromEnd, stopOffsetFromEnd,
bonusLabel1, detailText1, bonusValue1,
bonusLabel2, detailText2, bonusValue2,
@@ -258,6 +265,11 @@ void InsaneRebel1::playIntroSequence() {
if (shouldAbortGameFlow())
return;
+ if (_release.introNotice)
+ playCinematic(_release.introNotice);
+ if (shouldAbortGameFlow())
+ return;
+
if (_release.intro)
playCinematic(_release.intro);
}
@@ -623,8 +635,10 @@ bool InsaneRebel1::runLevel6() {
if (_health >= 0) {
char accuracyText[80];
- formatTargetAccuracy(accuracyText, sizeof(accuracyText), _killCount, 0x27, true);
- const int bonus = calculateThresholdBonus(_killCount, 0x26, 0x0C, _tuning.bonus);
+ const bool tieredBonus = _release.tieredAsteroidChaseBonus;
+ formatTargetAccuracy(accuracyText, sizeof(accuracyText), _killCount, 0x27, !tieredBonus);
+ const int bonus = tieredBonus ? (_killCount >= 30 ? 3 : _killCount >= 20 ? 1 : 0) * _tuning.bonus :
+ calculateThresholdBonus(_killCount, 0x26, 0x0C, _tuning.bonus);
playChapterCompleteCinematic("LVL6/L6END.ANM", 6, 0x4B, 5,
" ", accuracyText, bonus, nullptr, nullptr, 0, _difficulty + 4);
return !shouldAbortGameFlow();
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index b04e07257be..73fff74df48 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -958,6 +958,15 @@ const ExtraGuiOption enableRebel1NoDamage = {
0
};
+const ExtraGuiOption enableRebel1RestoredContent = {
+ _s("Restored content"),
+ _s("Play extra chapters included in the demo's game data but omitted from its original sequence"),
+ "rebel1_restored_content",
+ false,
+ 0,
+ 0
+};
+
const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &target) const {
ExtraGuiOptions options;
// Query the GUI options
@@ -1017,6 +1026,9 @@ const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &
if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL1_NO_DAMAGE)) {
options.push_back(enableRebel1NoDamage);
}
+ if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL1_RESTORED_CONTENT)) {
+ options.push_back(enableRebel1RestoredContent);
+ }
if (target.empty() || gameid == "comi") {
options.push_back(comiObjectLabelsOption);
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index f275d63b600..7aec3765e19 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Sun Sep 27 07:03:59 2026
+ This file was generated by the md5table tool on Sun Sep 27 07:57:06 2026
DO NOT EDIT MANUALLY!
*/
@@ -292,6 +292,7 @@ static const MD5Table md5table[] = {
{ "52a4bae0746a11d7b1e8554e91a6645c", "zak", "V2", "V2", 1916, Common::FR_FRA, Common::kPlatformDOS },
{ "53e94115b55dd51d4b8ff0871aa1df1e", "spyfox", "", "Demo", 20103, Common::EN_USA, Common::kPlatformUnknown },
{ "54a936ad06161ff7bfefcb96200f7bff", "monkey", "VGA Demo", "Demo", 7617, Common::EN_ANY, Common::kPlatformAmiga },
+ { "551a7c06bf5098b32d5fe7641d695df8", "rebel1", "Demo 1994-04-13", "Demo 1994-04-13", 226750, Common::EN_ANY, Common::kPlatformDOS },
{ "55518cd73cf9c6d23ea29c51ee06bdfe", "ft", "", "", -1, Common::IT_ITA, Common::kPlatformUnknown },
{ "55923e34363c8ec6541368101d84a3bf", "soccer", "", "", 67178, Common::EN_GRB, Common::kPlatformWindows },
{ "55b4494051b06e95bcaa0d6d8f6fb757", "atlantis", "Steam", "Humble", 286720, Common::EN_ANY, Common::kPlatformWindows },
Commit: ab41e63c79ad8d88ca1c9b52ee6736722b03b905
https://github.com/scummvm/scummvm/commit/ab41e63c79ad8d88ca1c9b52ee6736722b03b905
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-27T16:14:25+02:00
Commit Message:
SCUMM: RA1: added support for mac demo v1.02c
Changed paths:
devtools/scumm-md5.txt
engines/scumm/detection_tables.h
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/releases.cpp
engines/scumm/insane/rebel1/releases.h
engines/scumm/scumm-md5.h
engines/scumm/scumm.cpp
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 159d286c16c..276c3b6a7b1 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -452,6 +452,7 @@ ft Full Throttle
rebel1 Star Wars: Rebel Assault
274f6026d41fa2a2990a6d6b7a561dc2 217559 en DOS Demo v1.51 Demo v1.51 CD-ROM demo -
551a7c06bf5098b32d5fe7641d695df8 226750 en DOS Demo 1994-04-13 Demo 1994-04-13 CD-ROM demo (chapters 8 and 10) -
+ 4e9f4571f5859ea50f1e505a1f15c408 451096 en Mac Demo v1.02c Demo v1.02c 68k demo application (chapters 6 and 9) -
b9ac90e4411da1b6f7dd075cc4b03c2a 214435 en DOS - v1.0 - -
e210a57b756473be705d24b9378a8233 212383 en DOS - v1.3 - -
6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 5d5815d2214..564d3213c51 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -233,6 +233,7 @@ static const GameSettings gameVariantsTable[] = {
{"rebel1", "", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
{"rebel1", "Demo v1.51", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
{"rebel1", "Demo 1994-04-13", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO4(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE, GAMEOPTION_REBEL1_RESTORED_CONTENT)},
+ {"rebel1", "Demo v1.02c", 0, GID_REBEL1, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformMacintosh, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
{"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index e90075a2695..17452f84537 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -300,6 +300,9 @@ const char *InsaneRebel1::uiStr(int id) const {
// local order across builds. Extract them anchored on those neighbors; anything
// that fails validation keeps its English fallback.
void InsaneRebel1::loadLocalizedUiStrings() {
+ if (!_release.executable)
+ return;
+
Common::File f;
if (!f.open(_release.executable))
return;
diff --git a/engines/scumm/insane/rebel1/releases.cpp b/engines/scumm/insane/rebel1/releases.cpp
index aace005d32f..e79d581a4f9 100644
--- a/engines/scumm/insane/rebel1/releases.cpp
+++ b/engines/scumm/insane/rebel1/releases.cpp
@@ -59,6 +59,14 @@ static const Rebel1Release kReleases[] = {
"Demo 1994-04-13", "REBEL.EXE", "OPEN/O1LOGO.ANM", "OPEN/O1DEMO.ANM", "OPEN/O1OPEN.ANM", nullptr, false, false,
{ 8, 10 }, kDemo940413RestoredLevels,
1 << (6 - 1), true, { 0x289dc, 0x1a, 0x222 }, &kDemo940413Walker
+ },
+ {
+ // Macintosh v1.02c: CODE 3's dispatcher redirects chapters 1-5 to 6,
+ // 7-8 to 9, and 10-15 to the menu. The included ending is not played.
+ // Chapter assets and the tuning rows for 6/9 match the retail defaults.
+ "Demo v1.02c", nullptr, "OPEN/O1LOGO.ANM", nullptr, "OPEN/O1OPEN.ANM", nullptr, false, false,
+ { 6, 9 }, nullptr,
+ 0x7fff, false, { 0, 0, 0 }, &kRetailWalker
}
};
diff --git a/engines/scumm/insane/rebel1/releases.h b/engines/scumm/insane/rebel1/releases.h
index 05487a303f2..1dd29af0583 100644
--- a/engines/scumm/insane/rebel1/releases.h
+++ b/engines/scumm/insane/rebel1/releases.h
@@ -50,6 +50,7 @@ struct Rebel1Release {
enum { kNumLevels = 15 };
const char *variant;
+ // DOS executable supplying UI strings/tuning; null uses built-in defaults.
const char *executable;
const char *logo;
const char *introNotice;
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 7aec3765e19..ab62d96030c 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Sun Sep 27 07:57:06 2026
+ This file was generated by the md5table tool on Sun Sep 27 12:22:39 2026
DO NOT EDIT MANUALLY!
*/
@@ -273,6 +273,7 @@ static const MD5Table md5table[] = {
{ "4dc780f1bc587a193ce8a97652791438", "loom", "EGA", "EGA", 5748, Common::EN_ANY, Common::kPlatformAmiga },
{ "4e5867848ee61bc30d157e2c94eee9b4", "PuttTime", "HE 90", "Demo", 18394, Common::EN_USA, Common::kPlatformUnknown },
{ "4e859d3ef1e146b41e7d93c35cd6cc62", "puttzoo", "HE 100", "Lite", -1, Common::EN_ANY, Common::kPlatformIOS },
+ { "4e9f4571f5859ea50f1e505a1f15c408", "rebel1", "Demo v1.02c", "Demo v1.02c", 451096, Common::EN_ANY, Common::kPlatformMacintosh },
{ "4edbf9d03550f7ba01e7f34d69b678dd", "spyfox", "HE 98.5", "Demo", 20141, Common::NL_NLD, Common::kPlatformUnknown },
{ "4f04b321a95d4315ce6d65f8e1dd0368", "maze", "HE 80", "", 23479, Common::EN_USA, Common::kPlatformUnknown },
{ "4f138ac6f9b2ac5a41bc68b2c3296064", "freddi4", "HE 99", "", 44233, Common::FR_FRA, Common::kPlatformUnknown },
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index d816ca77d5f..f4ec7c544a2 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -1069,8 +1069,10 @@ Common::Error ScummEngine::init() {
SearchMan.addSubDirectoryMatching(gameDataDir, "data");
}
- if (_game.id == GID_REBEL1 && _game.platform == Common::kPlatformMacintosh)
+ if (_game.id == GID_REBEL1 && _game.platform == Common::kPlatformMacintosh) {
SearchMan.addSubDirectoryMatching(gameDataDir, "REBEL", 0, 2);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "REBELMAC", 0, 2);
+ }
#endif
// Extra directories needed for the Steam versions
Commit: c6a90ffb60f6cb8db238ba647cdf7b31b9df219b
https://github.com/scummvm/scummvm/commit/c6a90ffb60f6cb8db238ba647cdf7b31b9df219b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-27T16:14:25+02:00
Commit Message:
SCUMM: RA2: added support for demo da1.06
Changed paths:
A engines/scumm/insane/rebel2/releases.cpp
A engines/scumm/insane/rebel2/releases.h
devtools/scumm-md5.txt
engines/scumm/detection.h
engines/scumm/detection_internal.h
engines/scumm/detection_tables.h
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/menu.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/runlevels.cpp
engines/scumm/metaengine.cpp
engines/scumm/module.mk
engines/scumm/scumm-md5.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 276c3b6a7b1..28439b7b4e0 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -469,6 +469,7 @@ rebel2 Star Wars: Rebel Assault II: The Hidden Empire
ae607f6ed270311d7dbc033d3e943d3e 750723120 us PSX PlayStation - Disc 1 raw image -
e977ed046b88e75b645491caeff37b0c 313689 en DOS Demo Demo - -
+ 967409fc4c12b39fb3cf47f45de2623c 646373 en DOS Demo DA1.06 Demo DA1.06 Playable demo (chapter 6) -
dig The Dig
d8323015ecb8b10bf53474f6e6b0ae33 16304 All All - - - Fingolfin
diff --git a/engines/scumm/detection.h b/engines/scumm/detection.h
index 87c12d3b401..2214472fef6 100644
--- a/engines/scumm/detection.h
+++ b/engines/scumm/detection.h
@@ -45,6 +45,7 @@ namespace Scumm {
#define GAMEOPTION_REBEL2_YODA_MODE GUIO_GAMEOPTIONS14
#define GAMEOPTION_REBEL1_NO_DAMAGE GUIO_GAMEOPTIONS15
#define GAMEOPTION_REBEL1_RESTORED_CONTENT GUIO_GAMEOPTIONS16
+#define GAMEOPTION_REBEL2_RESTORED_CONTENT GUIO_GAMEOPTIONS17
/**
* Descriptor of a specific SCUMM game. Used internally to store
diff --git a/engines/scumm/detection_internal.h b/engines/scumm/detection_internal.h
index ed243123194..812cc76c550 100644
--- a/engines/scumm/detection_internal.h
+++ b/engines/scumm/detection_internal.h
@@ -652,6 +652,11 @@ static void detectGames(const Common::FSList &fslist, Common::List<DetectorResul
if (!scumm_stricmp(gfp->gameid, "rebel1") && file.equalsIgnoreCase("REBEL.EXE"))
continue;
+ // RA2.EXE is shared by retail and several playable demos. Only a
+ // known executable identifies the available chapters and tuning.
+ if (!scumm_stricmp(gfp->gameid, "rebel2") && file.equalsIgnoreCase("RA2.EXE"))
+ continue;
+
// ____ _ ____
// | _ \ __ _ _ __| |_ |___ \ *
// | |_) / _` | '__| __| __) |
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 564d3213c51..68205b5c85f 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -51,6 +51,7 @@ static const char *const directoryGlobs[] = {
"Resources", // Mac SE/Remastered versions
"OPEN", // 3DO version of rebel1
"Rebel Assault PowerPC", // Mac version of rebel1
+ "LAUNCH", // DOS versions of rebel2
0
};
@@ -237,6 +238,7 @@ static const GameSettings gameVariantsTable[] = {
{"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
+ {"rebel2", "Demo DA1.06", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO6(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE, GAMEOPTION_REBEL2_RESTORED_CONTENT)},
{"rebel2", "PlayStation", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_UNSTABLE | GF_16BIT_COLOR, Common::kPlatformPSX, GUIO5(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"dig", "", 0, GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO5(GUIO_NOMIDI, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
@@ -516,6 +518,7 @@ static const GameFilenamePattern gameFilenamesTable[] = {
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo" },
+ { "rebel2", "RA2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo DA1.06" },
{ "rebel2", "SLUS_003.81", kGenUnchanged, Common::EN_USA, Common::kPlatformPSX, "PlayStation" },
{ "comi", "comi.la%d", kGenDiskNum, UNK_LANG, UNK, 0 },
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 20fe3ba6027..0538023cdd7 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -86,8 +86,8 @@ Common::String InsaneRebel2::getLevelPrefix(int levelId) {
void InsaneRebel2::runGame() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- if (_vm->_game.features & GF_DEMO) {
- splayer->play("OPEN/O_DEMO.SAN", 15);
+ if (_release.nonInteractiveVideo) {
+ splayer->play(_release.nonInteractiveVideo, 15);
return;
}
@@ -142,7 +142,7 @@ void InsaneRebel2::runGame() {
int selectedLevel = _selectedChapter + 1;
debugC(DEBUG_INSANE, "InsaneRebel2: Starting chapter %d (level %d)", _selectedChapter + 1, selectedLevel);
- if (selectedLevel == 16) {
+ if (selectedLevel == 16 && _release.ending) {
playEndingSequence();
}
@@ -150,10 +150,12 @@ void InsaneRebel2::runGame() {
int result = runLevel(selectedLevel);
if (result == kLevelNextLevel) {
- updatePilotProgress(selectedLevel,
+ selectedLevel = _release.getNextChapter(selectedLevel);
+ if (!selectedLevel)
+ break;
+ updatePilotProgress(selectedLevel - 1,
_playerScore, _playerLives, 0, _playerRating);
- selectedLevel++;
- if (selectedLevel > 15) {
+ if (selectedLevel == 16) {
playEndingSequence();
break;
}
@@ -325,7 +327,11 @@ void InsaneRebel2::prepareLevelEndStats(int levelId, int accuracy, int flightErr
return;
}
- const Rebel2LevelEndParams &p = kRebel2LevelEndParams[levelId];
+ Rebel2LevelEndParams p = kRebel2LevelEndParams[levelId];
+ if (levelId == 6 && _release.skipMiningFacilityAttack) {
+ p.titleStartBeforeEnd = 120;
+ p.titleEndBeforeEnd = 10;
+ }
const bool hasAccuracy = (accuracy >= 0 && p.accLow >= 0 && p.accHigh >= 0);
const bool hasFlightErrors = (flightErrors >= 0 && p.errLow >= 0 && p.errHigh >= 0);
const int ratingAward = calculateLevelEndRating(
@@ -382,6 +388,8 @@ void InsaneRebel2::playLevelEnd(int levelId, int accuracy, int flightErrors, boo
Common::String dir = getLevelDir(levelId);
Common::String prefix = getLevelPrefix(levelId);
Common::String filename = Common::String::format("%s/%sEND.SAN", dir.c_str(), prefix.c_str());
+ if (levelId == 6 && _release.skipMiningFacilityAttack)
+ filename = "LEV06/06END_B.SAN";
debugC(DEBUG_INSANE, "Playing level %d end: %s", levelId, filename.c_str());
@@ -510,7 +518,7 @@ int InsaneRebel2::runLevel(int levelId) {
debugC(DEBUG_INSANE, "Starting level %d", levelId);
- if (levelId < 1 || levelId > 15) {
+ if (levelId < 1 || levelId > 15 || !_release.isChapterAvailable(levelId)) {
warning("Rebel2: Invalid level ID %d", levelId);
return kLevelReturnToMenu;
}
@@ -536,7 +544,8 @@ int InsaneRebel2::runLevel(int levelId) {
CursorMan.showMouse(false);
g_system->lockMouse(true);
- if (_activePilot >= 0 && _activePilot < _numPilots && _pilots[_activePilot].damage[levelId - 1] < 0xFF) {
+ if (!_release.unlockAvailableLevels && _activePilot >= 0 && _activePilot < _numPilots &&
+ _pilots[_activePilot].damage[levelId - 1] < 0xFF) {
_playerLives = _pilots[_activePilot].lives[levelId - 1];
_playerScore = _pilots[_activePilot].score[levelId - 1];
_playerRating = _pilots[_activePilot].rating[levelId - 1];
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index b20bccd4d8a..92f064b51e1 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -69,6 +69,7 @@ bool InsaneRebel2::isMenuTextInputActive() const {
return _gameState == kStateChapterSelect &&
_chapterSelection >= 0 &&
_chapterSelection < 16 &&
+ _release.isChapterAvailable(_chapterSelection + 1) &&
!_chapterUnlocked[_chapterSelection];
}
@@ -90,8 +91,18 @@ void InsaneRebel2::updateMenuVirtualKeyboard() {
void InsaneRebel2::unlockAllChapters() {
debugC(DEBUG_INSANE, "Unlocking all chapters for testing");
for (int i = 0; i < 16; i++) {
- _chapterUnlocked[i] = true;
- _levelUnlocked[i] = true;
+ _chapterUnlocked[i] = _release.isChapterAvailable(i + 1);
+ _levelUnlocked[i] = _chapterUnlocked[i];
+ }
+}
+
+void InsaneRebel2::updateChapterUnlocks() {
+ for (int i = 0; i < 16; i++) {
+ // 0xFF is PilotData::init()'s "never played" marker.
+ const bool reached = (_activePilot >= 0 && _activePilot < _numPilots) ?
+ _pilots[_activePilot].damage[i] < 0xFF : i + 1 == _release.levels[0];
+ _chapterUnlocked[i] = _release.isChapterAvailable(i + 1) &&
+ (_debugUnlockAll || _release.unlockAvailableLevels || reached);
}
}
@@ -664,6 +675,12 @@ int InsaneRebel2::runChapterSelect() {
}
if (_chapterSelection >= 0 && _chapterSelection < 16) {
+ if (!_release.isChapterAvailable(_chapterSelection + 1)) {
+ _passwordInput.clear();
+ playSfx(3, 127, 0);
+ continue;
+ }
+
if (_chapterUnlocked[_chapterSelection]) {
_selectedChapter = _chapterSelection;
debugC(DEBUG_INSANE, "Chapter %d selected (unlocked)", _selectedChapter + 1);
@@ -915,6 +932,8 @@ Common::String InsaneRebel2::getChapterPassword(int level, int difficulty) {
void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width, int height) {
if (_chapterSelection < 0 || _chapterSelection >= 16)
return;
+ if (!_release.isChapterAvailable(_chapterSelection + 1))
+ return;
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer)
@@ -930,7 +949,9 @@ void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width,
int32 pilotLives = 0;
int32 pilotScore = 0;
int16 pilotRating = 0;
- if (_activePilot >= 0 && _activePilot < _numPilots) {
+ if (_release.unlockAvailableLevels) {
+ pilotLives = 3;
+ } else if (_activePilot >= 0 && _activePilot < _numPilots) {
pilotLives = _pilots[_activePilot].lives[_chapterSelection];
pilotScore = _pilots[_activePilot].score[_chapterSelection];
pilotRating = _pilots[_activePilot].rating[_chapterSelection];
@@ -996,7 +1017,7 @@ int InsaneRebel2::runLevelSelect() {
_levelSelection = 0;
_levelItemCount = _numPilots + 4;
- _selectedLevel = 1;
+ _selectedLevel = _release.levels[0];
_menuRepeatDelay = 0;
_gameState = kStatePilotSelect;
_pilotMenuMode = kPilotModeSelect;
@@ -1032,9 +1053,7 @@ int InsaneRebel2::runLevelSelect() {
savePilots();
_activePilot = _pilotEditIndex;
- for (int i = 0; i < 16; i++) {
- _chapterUnlocked[i] = _debugUnlockAll || (_pilots[_activePilot].damage[i] < 0xFF);
- }
+ updateChapterUnlocks();
}
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 9761d3aa758..91d08b83082 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -159,7 +159,8 @@ bool InsaneRebel2::isSkippableVideoState() const {
return _gameState != kStateGameplay || _rebelHandler == 0;
}
-InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
+InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) :
+ _release(getRebel2Release(scumm->_game.variant, ConfMan.getBool("rebel2_restored_content"))) {
_vm = scumm;
// Rebel Assault II skips ScummEngine::resetScumm(), which normally clears this state.
for (int i = 0; i < kScummActionCount; i++)
@@ -508,7 +509,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_menuRepeatDelay = 0;
_menuSelectionConfirmed = false;
for (i = 0; i < 16; i++) {
- _levelUnlocked[i] = (i == 0);
+ _levelUnlocked[i] = (i + 1 == _release.levels[0]);
}
_chapterSelection = 0;
@@ -520,10 +521,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_noDamage = ConfMan.getBool("rebel2_no_damage");
_rebelYodaMode = ConfMan.getBool("rebel2_yoda_mode");
- for (i = 0; i < 16; i++) {
- _chapterUnlocked[i] = _debugUnlockAll || (i == 0);
- }
-
_previewOffsetX = -90;
_previewOffsetY = 75;
@@ -534,10 +531,11 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_pilots[i].init();
}
loadPilots();
+ updateChapterUnlocks();
_levelSelection = 0;
_levelItemCount = _numPilots + 4;
- _selectedLevel = 1;
+ _selectedLevel = _release.levels[0];
_difficultySelection = 2;
_pilotMenuMode = kPilotModeSelect;
_pilotNameInput = "";
@@ -1327,7 +1325,10 @@ int InsaneRebel2::getDifficultyRow() const {
}
InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
- return kDifficultyTable[CLIP(_difficulty, 0, 5)][getDifficultyRow()];
+ const int difficulty = CLIP(_difficulty, 0, 5);
+ const int levelType = getDifficultyRow();
+ const LevelDifficultyParams *params = _release.getDifficultyOverride(difficulty, levelType);
+ return params ? *params : kDifficultyTable[difficulty][levelType];
}
bool InsaneRebel2::applyPlayerDamage(int damage) {
@@ -1547,7 +1548,7 @@ int InsaneRebel2::createNewPilot() {
return -1;
int idx = _numPilots;
- _pilots[idx].init();
+ _pilots[idx].init(_release.levels[0], _release.unlockAvailableLevels ? 3 : 4);
_numPilots++;
return idx;
}
@@ -1587,6 +1588,8 @@ void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives,
return;
if (levelIndex < 0 || levelIndex >= kNumLevels)
return;
+ if (!_release.isChapterAvailable(levelIndex + 1))
+ return;
PilotData &pilot = _pilots[_activePilot];
@@ -1613,9 +1616,7 @@ bool InsaneRebel2::selectPilot(int index) {
_activePilot = index;
_difficulty = _pilots[_activePilot].difficulty;
- // 0xFF is PilotData::init()'s "never played" marker.
- for (int i = 0; i < 16; i++)
- _chapterUnlocked[i] = _debugUnlockAll || (_pilots[_activePilot].damage[i] < 0xFF);
+ updateChapterUnlocks();
return true;
}
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 880451c70a8..cf6f3adbbf6 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -30,6 +30,7 @@
#include "scumm/insane/rebel/rebel_audio.h"
#include "scumm/insane/rebel/rebel_gamepad.h"
#include "scumm/insane/rebel/rebel_touch.h"
+#include "scumm/insane/rebel2/releases.h"
#include "common/keyboard.h"
#include "common/list.h"
@@ -55,6 +56,8 @@ public:
InsaneRebel2(ScummEngine_v7 *scumm);
~InsaneRebel2();
+ const Rebel2Release _release;
+
bool notifyEvent(const Common::Event &event) override;
Common::Queue<Common::Event> _menuEventQueue;
@@ -131,6 +134,7 @@ public:
bool _noDamage;
void unlockAllChapters();
+ void updateChapterUnlocks();
int runChapterSelect();
void drawChapterSelectOverlay(byte *renderBitmap, int pitch, int width, int height);
int processChapterSelectInput();
@@ -195,18 +199,19 @@ public:
int16 rating[kNumLevels];
int16 difficulty;
- void init() {
+ void init(int firstLevel = 1, int initialLives = 4) {
memset(name, 0, sizeof(name));
memset(rating, 0, sizeof(rating));
difficulty = 2;
- score[0] = 0;
- lives[0] = 4;
- damage[0] = 0;
- for (int i = 1; i < kNumLevels; i++) {
+ for (int i = 0; i < kNumLevels; i++) {
score[i] = 0;
lives[i] = 0xFF;
damage[i] = 0xFF;
}
+ if (firstLevel >= 1 && firstLevel <= kNumLevels) {
+ lives[firstLevel - 1] = initialLives;
+ damage[firstLevel - 1] = 0;
+ }
}
};
@@ -890,25 +895,7 @@ public:
int _difficulty;
// Per-level difficulty parameters.
- struct LevelDifficultyParams {
- int16 laserDelay;
- int16 snapDistance;
- int16 missDamage;
- int16 dodgeDamage;
- int16 shotDamage;
- int16 specialDamage;
- int16 shotAccuracy;
- int16 hitPoints;
- int16 dodgePoints;
- int16 timePoints;
- int16 levelPoints;
- int16 specialPoints;
- int16 flags;
- int16 rollRate;
- int16 liftRate;
- int16 slideRate;
- int16 driftRate;
- };
+ typedef Rebel2DifficultyParams LevelDifficultyParams;
static const LevelDifficultyParams kDifficultyTable[6][17];
diff --git a/engines/scumm/insane/rebel2/releases.cpp b/engines/scumm/insane/rebel2/releases.cpp
new file mode 100644
index 00000000000..75eb507a2d5
--- /dev/null
+++ b/engines/scumm/insane/rebel2/releases.cpp
@@ -0,0 +1,117 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "scumm/insane/rebel2/releases.h"
+
+namespace Scumm {
+
+// DA1.06's difficulty table starts at offset 0x4798 in LE object 3.
+// Rows have 17 words; each difficulty occupies 0x242 bytes. Types 5 and 6
+// cover the shield attack and reactor attack respectively.
+static const Rebel2DifficultyOverride kDemoDA106Difficulty[] = {
+ { 5, {
+ { 5, 5, -1, 19, 2, 15, 75, 25, 50, 2, 500, -1, 8, -1, -1, -1, -1 },
+ { 5, 3, -1, 25, 4, 15, 75, 50, 100, 4, 1000, -1, 16, -1, -1, -1, -1 },
+ { 5, 1, -1, 30, 6, 15, 75, 75, 150, 6, 1500, -1, 0, -1, -1, -1, -1 },
+ { 5, 0, 220, 50, 15, 15, 79, 100, 200, 8, 2000, -1, 4, -1, -1, -1, -1 },
+ { 5, 1, -1, 30, 6, 15, 75, 75, 150, 6, 1500, -1, 0, -1, -1, -1, -1 },
+ { 5, 1, -1, 30, 6, 15, 75, 75, 150, 6, 1500, -1, 0, -1, -1, -1, -1 }
+ } },
+ { 6, {
+ { 5, 5, 180, 27, 3, -1, 75, 25, 50, 2, 500, 250, 8, 120, 120, 120, 75 },
+ { 5, 3, 190, 35, 4, -1, 75, 50, 100, 4, 1000, 500, 16, 140, 140, 140, 90 },
+ { 5, 1, 200, 50, 4, -1, 75, 75, 150, 6, 1500, 750, 0, 160, 160, 160, 105 },
+ { 5, 0, 220, 90, 15, -1, 90, 100, 200, 8, 2000, 1000, 4, 180, 180, 180, 140 },
+ { 5, 1, 200, 50, 4, -1, 75, 75, 150, 6, 1500, 750, 0, 160, 160, 160, 105 },
+ { 5, 1, 200, 50, 4, -1, 75, 75, 150, 6, 1500, 750, 0, 160, 160, 160, 105 }
+ } }
+};
+
+// Variants match detection_tables.h. Availability also limits passwords,
+// saved pilot progress and the unlock-all option.
+static const Rebel2Release kReleases[] = {
+ {
+ "", "SYSTM/GAME.TRS", nullptr,
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ true, false, false, nullptr, 0
+ },
+ {
+ "Demo", "SYSTM/GAME.TRS", "OPEN/O_DEMO.SAN", { 0 },
+ false, false, false, nullptr, 0
+ },
+ {
+ // RBL2DEMO.EXE selects GAME_E.TRS. The original chapter-6 handler
+ // (LE object 1, 0x10d10) skips the shield attack and uses 06END_B.
+ "Demo DA1.06", "SYSTM/GAME_E.TRS", nullptr, { 6 },
+ false, true, true, kDemoDA106Difficulty, ARRAYSIZE(kDemoDA106Difficulty)
+ }
+};
+
+bool Rebel2Release::isChapterAvailable(int chapter) const {
+ if (chapter == kFinale)
+ return ending;
+ if (chapter < 1 || chapter > kNumLevels)
+ return false;
+ for (int i = 0; i < kNumLevels && levels[i]; ++i) {
+ if (levels[i] == chapter)
+ return true;
+ }
+ return false;
+}
+
+int Rebel2Release::getNextChapter(int chapter) const {
+ for (int i = 0; i < kNumLevels && levels[i]; ++i) {
+ if (levels[i] == chapter) {
+ if (i + 1 < kNumLevels && levels[i + 1])
+ return levels[i + 1];
+ return ending ? kFinale : 0;
+ }
+ }
+ return 0;
+}
+
+const Rebel2DifficultyParams *Rebel2Release::getDifficultyOverride(int difficulty, int levelType) const {
+ if (difficulty < 0 || difficulty >= 6)
+ return nullptr;
+ for (uint i = 0; i < difficultyOverrideCount; ++i) {
+ if (difficultyOverrides[i].levelType == levelType)
+ return &difficultyOverrides[i].difficulty[difficulty];
+ }
+ return nullptr;
+}
+
+Rebel2Release getRebel2Release(const char *variant, bool restoredContent) {
+ if (!variant)
+ variant = "";
+ for (const Rebel2Release &release : kReleases) {
+ if (!strcmp(variant, release.variant)) {
+ Rebel2Release result = release;
+ if (restoredContent)
+ result.skipMiningFacilityAttack = false;
+ return result;
+ }
+ }
+ error("Unknown Rebel Assault II release '%s'", variant);
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/insane/rebel2/releases.h b/engines/scumm/insane/rebel2/releases.h
new file mode 100644
index 00000000000..d2eabfb9884
--- /dev/null
+++ b/engines/scumm/insane/rebel2/releases.h
@@ -0,0 +1,78 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SCUMM_INSANE_REBEL2_RELEASES_H
+#define SCUMM_INSANE_REBEL2_RELEASES_H
+
+#include "common/scummsys.h"
+
+namespace Scumm {
+
+struct Rebel2DifficultyParams {
+ int16 laserDelay;
+ int16 snapDistance;
+ int16 missDamage;
+ int16 dodgeDamage;
+ int16 shotDamage;
+ int16 specialDamage;
+ int16 shotAccuracy;
+ int16 hitPoints;
+ int16 dodgePoints;
+ int16 timePoints;
+ int16 levelPoints;
+ int16 specialPoints;
+ int16 flags;
+ int16 rollRate;
+ int16 liftRate;
+ int16 slideRate;
+ int16 driftRate;
+};
+
+struct Rebel2DifficultyOverride {
+ byte levelType;
+ Rebel2DifficultyParams difficulty[6];
+};
+
+struct Rebel2Release {
+ enum { kNumLevels = 15, kFinale = 16 };
+
+ const char *variant;
+ const char *strings;
+ const char *nonInteractiveVideo;
+ // Chapter numbers in playback order; unused entries are zero.
+ byte levels[kNumLevels];
+ bool ending;
+ bool unlockAvailableLevels;
+ // The DA1.06 demo starts chapter 6 at the attack on the reactor.
+ bool skipMiningFacilityAttack;
+ const Rebel2DifficultyOverride *difficultyOverrides;
+ uint difficultyOverrideCount;
+
+ bool isChapterAvailable(int chapter) const;
+ int getNextChapter(int chapter) const;
+ const Rebel2DifficultyParams *getDifficultyOverride(int difficulty, int levelType) const;
+};
+
+Rebel2Release getRebel2Release(const char *variant, bool restoredContent = false);
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index ca223022b9e..9f01bf9382c 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -894,7 +894,7 @@ int InsaneRebel2::runLevel5() {
}
int InsaneRebel2::runLevel6() {
- int phase1Score = 0;
+ int phase1Score = _playerScore;
int phase1FlightErrors = 0;
playLevelBegin(6);
@@ -907,57 +907,58 @@ int InsaneRebel2::runLevel6() {
_rebelPhaseState = 0xffffffff;
- _rebelLevelType = 5;
- _currentPhase = 1;
- _rebelKillCounter = 0;
- _rebelHitCounter = 0;
-
- debugC(DEBUG_INSANE, "Level 6 Phase 1 (shield attack run)");
- resetShieldGauge();
- _rebelShieldGateActive = true;
-
- if (!playLevelSegment("LEV06/06PLAY1.SAN", 0x28)) {
- _rebelShieldGateActive = false;
- return kLevelQuit;
- }
+ if (!_release.skipMiningFacilityAttack) {
+ _rebelLevelType = 5;
+ _currentPhase = 1;
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
- while (_playerShield > 0 && !_rebelShieldDestroyed && !_vm->shouldQuit()) {
+ debugC(DEBUG_INSANE, "Level 6 Phase 1 (shield attack run)");
resetShieldGauge();
- if (!playLevelSegment("LEV06/06PLAY1B.SAN", 0x468)) {
+ _rebelShieldGateActive = true;
+
+ if (!playLevelSegment("LEV06/06PLAY1.SAN", 0x28)) {
_rebelShieldGateActive = false;
return kLevelQuit;
}
- }
- _rebelShieldGateActive = false;
- if (_playerShield <= 0) {
- debugC(DEBUG_INSANE, "Level 6 Phase 1 death at frame %d", _deathFrame);
- playLevelDeathVariant(6, 1, _deathFrame);
- if (_vm->shouldQuit())
- return kLevelQuit;
+ while (_playerShield > 0 && !_rebelShieldDestroyed && !_vm->shouldQuit()) {
+ resetShieldGauge();
+ if (!playLevelSegment("LEV06/06PLAY1B.SAN", 0x468)) {
+ _rebelShieldGateActive = false;
+ return kLevelQuit;
+ }
+ }
+ _rebelShieldGateActive = false;
- _playerLives--;
- if (_playerLives <= 0) {
- playLevelGameOver(6);
- return kLevelGameOver;
+ if (_playerShield <= 0) {
+ debugC(DEBUG_INSANE, "Level 6 Phase 1 death at frame %d", _deathFrame);
+ playLevelDeathVariant(6, 1, _deathFrame);
+ if (_vm->shouldQuit())
+ return kLevelQuit;
+
+ _playerLives--;
+ if (_playerLives <= 0) {
+ playLevelGameOver(6);
+ return kLevelGameOver;
+ }
+
+ playLevelRetryVariant(6, 1);
+ if (_vm->shouldQuit())
+ return kLevelQuit;
+ _playerShield = 255; // full shield on retry (original resets damage before replaying)
+ _playerDamage = 0;
+ continue;
}
- playLevelRetryVariant(6, 1);
- if (_vm->shouldQuit())
+ phase1Score = _playerScore;
+ phase1FlightErrors = _rebelHitCounter;
+ _rebelHandler = 0;
+ _rebelStatusBarSprite = 0;
+ if (!playLevelSegment("LEV06/06POST1.SAN", 0x28, false))
return kLevelQuit;
- _playerShield = 255; // full shield on retry (original resets damage before replaying)
- _playerDamage = 0;
- continue;
}
- phase1Score = _playerScore;
- phase1FlightErrors = _rebelHitCounter;
-
- _rebelHandler = 0;
- _rebelStatusBarSprite = 0;
- if (!playLevelSegment("LEV06/06POST1.SAN", 0x28, false))
- return kLevelQuit;
-
while (!_vm->shouldQuit()) {
_rebelLevelType = 6;
_currentPhase = 2;
@@ -973,7 +974,7 @@ int InsaneRebel2::runLevel6() {
if (_playerShield > 0) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
- int flightErrors = phase1FlightErrors + _rebelHitCounter;
+ int flightErrors = _release.skipMiningFacilityAttack ? -1 : phase1FlightErrors + _rebelHitCounter;
debugC(DEBUG_INSANE, "Level 6 completed!");
playLevelEnd(6, accuracy, flightErrors, false);
_levelUnlocked[6] = true;
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 73fff74df48..d6bf12f63d6 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -940,6 +940,15 @@ static const ExtraGuiOption enableRebel2YodaMode = {
0
};
+static const ExtraGuiOption enableRebel2RestoredContent = {
+ _s("Restored content"),
+ _s("Play additional sections included in the demo's game data but omitted from its original sequence"),
+ "rebel2_restored_content",
+ false,
+ 0,
+ 0
+};
+
const ExtraGuiOption enableRebel1UnlockAll = {
_s("Unlock all levels"),
_s("All levels will be available without requiring passwords"),
@@ -1020,6 +1029,9 @@ const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &
if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL2_YODA_MODE)) {
options.push_back(enableRebel2YodaMode);
}
+ if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL2_RESTORED_CONTENT)) {
+ options.push_back(enableRebel2RestoredContent);
+ }
if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL1_UNLOCK_ALL)) {
options.push_back(enableRebel1UnlockAll);
}
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 5436864add9..c8ab8c301ff 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -160,6 +160,7 @@ MODULE_OBJS += \
insane/rebel1/runlevels.o \
insane/rebel1/saveload.o \
insane/rebel2/rebel.o \
+ insane/rebel2/releases.o \
insane/rebel2/audio.o \
insane/rebel2/iact.o \
insane/rebel2/levels.o \
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index ab62d96030c..20a80064dd4 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Sun Sep 27 12:22:39 2026
+ This file was generated by the md5table tool on Sun Sep 27 12:38:49 2026
DO NOT EDIT MANUALLY!
*/
@@ -511,6 +511,7 @@ static const MD5Table md5table[] = {
{ "95818b178d473c989ac753574e8892aa", "readtime", "", "Demo", 15052, Common::EN_ANY, Common::kPlatformUnknown },
{ "95b3806e043be08668c54c3ffe98650f", "BluesABCTime", "", "", 26391, Common::EN_ANY, Common::kPlatformUnknown },
{ "95be99181bd0f10fef4872c2d4a771cb", "zak", "V1", "", 1914, Common::DE_DEU, Common::kPlatformC64 },
+ { "967409fc4c12b39fb3cf47f45de2623c", "rebel2", "Demo DA1.06", "Demo DA1.06", 646373, Common::EN_ANY, Common::kPlatformDOS },
{ "9684c161258d68e0d464d6cab7024b9c", "spyfox2", "", "Mini Game", 14689, Common::IT_ITA, Common::kPlatformWindows },
{ "96a3069a3c63caa7329588ce1fef41ee", "spyozon", "", "", -1, Common::RU_RUS, Common::kPlatformUnknown },
{ "9708cf716ed8bcc9ff3fcfc69413b746", "puttputt", "HE 62", "", 8117, Common::EN_ANY, Common::kPlatformDOS },
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index a346b4896ce..1d9168639e3 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -632,7 +632,7 @@ public:
bool SmushPlayerRebel2::handleGameSetupStrings() {
ScummFile *theFile = _vm->instantiateScummFile();
- _vm->openFile(*theFile, "SYSTM/GAME.TRS");
+ _vm->openFile(*theFile, static_cast<InsaneRebel2 *>(_insane)->_release.strings);
if (!theFile->isOpen()) {
delete theFile;
return true; // handled (no strings available)
Commit: 6ad8570f34888b7d2f510d79cfbf1c710f11be8a
https://github.com/scummvm/scummvm/commit/6ad8570f34888b7d2f510d79cfbf1c710f11be8a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-27T16:14:25+02:00
Commit Message:
SCUMM: RA2: added support for demo dg1.15
Changed paths:
devtools/scumm-md5.txt
engines/scumm/detection_tables.h
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/releases.cpp
engines/scumm/insane/rebel2/releases.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/scumm-md5.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 28439b7b4e0..a7664f07b51 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -470,6 +470,7 @@ rebel2 Star Wars: Rebel Assault II: The Hidden Empire
e977ed046b88e75b645491caeff37b0c 313689 en DOS Demo Demo - -
967409fc4c12b39fb3cf47f45de2623c 646373 en DOS Demo DA1.06 Demo DA1.06 Playable demo (chapter 6) -
+ 8e0788210af982fbbc43c244ac6f9581 663561 en DOS Demo DG1.15 Demo DG1.15 The Dreighton Encounter (chapters 1, 2 and 4) -
dig The Dig
d8323015ecb8b10bf53474f6e6b0ae33 16304 All All - - - Fingolfin
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 68205b5c85f..7fdba161d30 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -239,6 +239,7 @@ static const GameSettings gameVariantsTable[] = {
{"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo DA1.06", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO6(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE, GAMEOPTION_REBEL2_RESTORED_CONTENT)},
+ {"rebel2", "Demo DG1.15", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "PlayStation", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_UNSTABLE | GF_16BIT_COLOR, Common::kPlatformPSX, GUIO5(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"dig", "", 0, GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO5(GUIO_NOMIDI, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 0538023cdd7..9e53b9b390e 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -151,8 +151,11 @@ void InsaneRebel2::runGame() {
if (result == kLevelNextLevel) {
selectedLevel = _release.getNextChapter(selectedLevel);
- if (!selectedLevel)
+ if (!selectedLevel) {
+ if (_release.completionVideo && !_vm->shouldQuit())
+ playCinematic(_release.completionVideo, 0x08);
break;
+ }
updatePilotProgress(selectedLevel - 1,
_playerScore, _playerLives, 0, _playerRating);
if (selectedLevel == 16) {
@@ -200,7 +203,7 @@ void InsaneRebel2::playMissionBriefing() {
splayer->play("OPEN/O_LEVEL.SAN", 15);
}
-void InsaneRebel2::playCinematic(const char *filename) {
+void InsaneRebel2::playCinematic(const char *filename, int16 flags) {
restoreDamageFlashPalette();
resetVideoAudio();
_gameplaySectionActive = false;
@@ -208,7 +211,7 @@ void InsaneRebel2::playCinematic(const char *filename) {
_rebelStatusBarSprite = 0;
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- splayer->setCurVideoFlags(0x28);
+ splayer->setCurVideoFlags(flags);
splayer->play(filename, 15);
}
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index cf6f3adbbf6..cce7e1b0b59 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -335,7 +335,7 @@ public:
int getRandomVariant(int max);
Common::String selectDeathVideoVariant(int levelId, int phase, int frame);
- void playCinematic(const char *filename);
+ void playCinematic(const char *filename, int16 flags = 0x28);
// Text is progressively revealed during [fadeInFrame, fadeOutFrame)
void playVideoWithText(const char *filename, int textID, int textX, int textY,
diff --git a/engines/scumm/insane/rebel2/releases.cpp b/engines/scumm/insane/rebel2/releases.cpp
index 75eb507a2d5..080e672d815 100644
--- a/engines/scumm/insane/rebel2/releases.cpp
+++ b/engines/scumm/insane/rebel2/releases.cpp
@@ -51,19 +51,26 @@ static const Rebel2DifficultyOverride kDemoDA106Difficulty[] = {
// saved pilot progress and the unlock-all option.
static const Rebel2Release kReleases[] = {
{
- "", "SYSTM/GAME.TRS", nullptr,
+ "", "SYSTM/GAME.TRS", nullptr, nullptr,
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
- true, false, false, nullptr, 0
+ true, false, false, false, nullptr, 0
},
{
- "Demo", "SYSTM/GAME.TRS", "OPEN/O_DEMO.SAN", { 0 },
- false, false, false, nullptr, 0
+ "Demo", "SYSTM/GAME.TRS", "OPEN/O_DEMO.SAN", nullptr, { 0 },
+ false, false, false, false, nullptr, 0
},
{
// RBL2DEMO.EXE selects GAME_E.TRS. The original chapter-6 handler
// (LE object 1, 0x10d10) skips the shield attack and uses 06END_B.
- "Demo DA1.06", "SYSTM/GAME_E.TRS", nullptr, { 6 },
- false, true, true, kDemoDA106Difficulty, ARRAYSIZE(kDemoDA106Difficulty)
+ "Demo DA1.06", "SYSTM/GAME_E.TRS", nullptr, nullptr, { 6 },
+ false, true, true, false, kDemoDA106Difficulty, ARRAYSIZE(kDemoDA106Difficulty)
+ },
+ {
+ // GAME.TRS selects chapters 1, 2 and 4 in playable-demo mode.
+ // LE object 1, 0xf860 advances passwords past missing chapters;
+ // 0x12eb0 plays O_PLAYDE instead of the retail finale.
+ "Demo DG1.15", "SYSTM/GAME.TRS", nullptr, "OPEN/O_PLAYDE.SAN", { 1, 2, 4 },
+ false, false, false, true, nullptr, 0
}
};
@@ -90,6 +97,16 @@ int Rebel2Release::getNextChapter(int chapter) const {
return 0;
}
+int Rebel2Release::getCompletionPasswordChapter(int chapter) const {
+ if (!advanceCompletionPasswords || !isChapterAvailable(chapter))
+ return chapter;
+
+ // Passwords unlock the chapter following their retail chapter number.
+ // DG1.15 shows the finale password after its last included chapter.
+ const int nextChapter = getNextChapter(chapter);
+ return nextChapter ? nextChapter - 1 : kNumLevels;
+}
+
const Rebel2DifficultyParams *Rebel2Release::getDifficultyOverride(int difficulty, int levelType) const {
if (difficulty < 0 || difficulty >= 6)
return nullptr;
diff --git a/engines/scumm/insane/rebel2/releases.h b/engines/scumm/insane/rebel2/releases.h
index d2eabfb9884..f979c1ba12a 100644
--- a/engines/scumm/insane/rebel2/releases.h
+++ b/engines/scumm/insane/rebel2/releases.h
@@ -57,17 +57,20 @@ struct Rebel2Release {
const char *variant;
const char *strings;
const char *nonInteractiveVideo;
+ const char *completionVideo;
// Chapter numbers in playback order; unused entries are zero.
byte levels[kNumLevels];
bool ending;
bool unlockAvailableLevels;
// The DA1.06 demo starts chapter 6 at the attack on the reactor.
bool skipMiningFacilityAttack;
+ bool advanceCompletionPasswords;
const Rebel2DifficultyOverride *difficultyOverrides;
uint difficultyOverrideCount;
bool isChapterAvailable(int chapter) const;
int getNextChapter(int chapter) const;
+ int getCompletionPasswordChapter(int chapter) const;
const Rebel2DifficultyParams *getDifficultyOverride(int difficulty, int levelType) const;
};
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 656ad55fc71..6680e061f5c 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2707,7 +2707,8 @@ void InsaneRebel2::renderLevelEndStatsOverlay(byte *renderBitmap, int pitch, int
if (!titleFmt || !titleFmt[0])
titleFmt = getRebel2LevelEndFallbackString(190);
- Common::String password = getChapterPassword(_levelEndStats.levelId, _difficulty);
+ const int passwordChapter = _release.getCompletionPasswordChapter(_levelEndStats.levelId);
+ Common::String password = getChapterPassword(passwordChapter, _difficulty);
Common::String titleText = Common::String::format(titleFmt, password.c_str());
int visibleChars = curFrame + 10 - titleStart;
if (visibleChars > 0xbe)
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 20a80064dd4..2c40e90565f 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Sun Sep 27 12:38:49 2026
+ This file was generated by the md5table tool on Sun Sep 27 12:52:41 2026
DO NOT EDIT MANUALLY!
*/
@@ -481,6 +481,7 @@ static const MD5Table md5table[] = {
{ "8d6a88fc58b72785621712fcffc03141", "atlantis", "", "CD", 12035, Common::HE_ISR, Common::kPlatformDOS },
{ "8dd4d590685c19bf651b5016e749ead2", "PuttTime", "HE 99", "Mini Game", 18458, Common::FR_FRA, Common::kPlatformWindows },
{ "8de13897f0121c79d29a2377159f9ad0", "socks", "HE 99", "Updated", 13668, Common::EN_ANY, Common::kPlatformWindows },
+ { "8e0788210af982fbbc43c244ac6f9581", "rebel2", "Demo DG1.15", "Demo DG1.15", 663561, Common::EN_ANY, Common::kPlatformDOS },
{ "8e1272e5c12ac35e29e3e4c50ec4a525", "rebel1", "", "", 526296, Common::EN_ANY, Common::kPlatformMacintosh },
{ "8e3241ddd6c8dadf64305e8740d45e13", "balloon", "HE 100", "Updated", -1, Common::EN_ANY, Common::kPlatformUnknown },
{ "8e4ee4db46954bfe2912e259a16fad82", "monkey2", "", "", 11135, Common::FR_FRA, Common::kPlatformDOS },
Commit: 24c327ead16461902940e03dec62a5a90ce1c719
https://github.com/scummvm/scummvm/commit/24c327ead16461902940e03dec62a5a90ce1c719
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-27T16:14:25+02:00
Commit Message:
SCUMM: RA2: added support for mac demo 1.0
Changed paths:
A engines/scumm/insane/rebel2/mac_archive.cpp
A engines/scumm/insane/rebel2/mac_archive.h
devtools/scumm-md5.txt
engines/scumm/detection_internal.h
engines/scumm/detection_tables.h
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/releases.cpp
engines/scumm/insane/rebel2/releases.h
engines/scumm/module.mk
engines/scumm/scumm-md5.h
engines/scumm/scumm.cpp
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index a7664f07b51..02562e81c87 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -471,6 +471,7 @@ rebel2 Star Wars: Rebel Assault II: The Hidden Empire
e977ed046b88e75b645491caeff37b0c 313689 en DOS Demo Demo - -
967409fc4c12b39fb3cf47f45de2623c 646373 en DOS Demo DA1.06 Demo DA1.06 Playable demo (chapter 6) -
8e0788210af982fbbc43c244ac6f9581 663561 en DOS Demo DG1.15 Demo DG1.15 The Dreighton Encounter (chapters 1, 2 and 4) -
+ d89f31e209c0642b4d20ccf615840401 128658792 en Mac Demo v1.0 Demo v1.0 Mac bundle (chapter 6) -
dig The Dig
d8323015ecb8b10bf53474f6e6b0ae33 16304 All All - - - Fingolfin
diff --git a/engines/scumm/detection_internal.h b/engines/scumm/detection_internal.h
index 812cc76c550..8c59c384ce8 100644
--- a/engines/scumm/detection_internal.h
+++ b/engines/scumm/detection_internal.h
@@ -653,8 +653,9 @@ static void detectGames(const Common::FSList &fslist, Common::List<DetectorResul
continue;
// RA2.EXE is shared by retail and several playable demos. Only a
- // known executable identifies the available chapters and tuning.
- if (!scumm_stricmp(gfp->gameid, "rebel2") && file.equalsIgnoreCase("RA2.EXE"))
+ // known executable or Mac bundle identifies the chapters and tuning.
+ if (!scumm_stricmp(gfp->gameid, "rebel2") &&
+ (baseFile.equalsIgnoreCase("RA2.EXE") || platform == Common::kPlatformMacintosh))
continue;
// ____ _ ____
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 7fdba161d30..6c3dbdd7a6a 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -240,6 +240,7 @@ static const GameSettings gameVariantsTable[] = {
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo DA1.06", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO6(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE, GAMEOPTION_REBEL2_RESTORED_CONTENT)},
{"rebel2", "Demo DG1.15", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
+ {"rebel2", "Demo v1.0", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO | GF_TESTING, Common::kPlatformMacintosh, GUIO6(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE, GAMEOPTION_REBEL2_RESTORED_CONTENT)},
{"rebel2", "PlayStation", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_UNSTABLE | GF_16BIT_COLOR, Common::kPlatformPSX, GUIO5(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"dig", "", 0, GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO5(GUIO_NOMIDI, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
@@ -520,6 +521,7 @@ static const GameFilenamePattern gameFilenamesTable[] = {
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo" },
{ "rebel2", "RA2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo DA1.06" },
+ { "rebel2", "Rebel Assault II Demo Data", kGenUnchanged, UNK_LANG, Common::kPlatformMacintosh, "Demo v1.0" },
{ "rebel2", "SLUS_003.81", kGenUnchanged, Common::EN_USA, Common::kPlatformPSX, "PlayStation" },
{ "comi", "comi.la%d", kGenDiskNum, UNK_LANG, UNK, 0 },
diff --git a/engines/scumm/insane/rebel2/mac_archive.cpp b/engines/scumm/insane/rebel2/mac_archive.cpp
new file mode 100644
index 00000000000..7bba0ad204e
--- /dev/null
+++ b/engines/scumm/insane/rebel2/mac_archive.cpp
@@ -0,0 +1,112 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/archive.h"
+#include "common/array.h"
+#include "scumm/file.h"
+#include "scumm/insane/rebel2/mac_archive.h"
+
+namespace Scumm {
+
+// RA2 uses the SCUMM Mac container format, but its members are flat names
+// rather than the DOS paths used by its movies, fonts and sounds.
+class Rebel2MacArchive : public Common::Archive {
+public:
+ Rebel2MacArchive(const ScummEngine *vm, const Common::Path &filename) : _vm(vm), _filename(filename) {}
+ bool load();
+ bool hasFile(const Common::Path &path) const override;
+ int listMembers(Common::ArchiveMemberList &list) const override;
+ const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
+ Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
+
+private:
+ const ScummEngine *_vm;
+ Common::Path _filename;
+ Common::Array<Common::String> _members;
+};
+
+bool Rebel2MacArchive::load() {
+ ScummFile file(_vm);
+ if (!file.open(_filename) || file.size() < 8)
+ return false;
+
+ const uint32 offset = file.readUint32BE();
+ const uint32 length = file.readUint32BE();
+ if (length % 40 || uint64(offset) + length > uint64(file.size()) || !file.seek(offset))
+ return false;
+
+ // Index the names before mounting the archive. hasFile() must not open
+ // the bundle through SearchMan, which will also search this archive.
+ for (uint32 i = 0; i < length / 40; ++i) {
+ const uint32 start = file.readUint32BE();
+ const uint32 size = file.readUint32BE();
+ char name[33];
+ if (file.read(name, 32) != 32 || uint64(start) + size > uint64(file.size()))
+ return false;
+ name[32] = 0;
+ if (!name[0])
+ return false;
+ _members.push_back(name);
+ }
+ return !file.err();
+}
+
+bool Rebel2MacArchive::hasFile(const Common::Path &path) const {
+ const Common::String name = path.baseName();
+ for (const Common::String &member : _members) {
+ if (member.equalsIgnoreCase(name))
+ return true;
+ }
+ return false;
+}
+
+int Rebel2MacArchive::listMembers(Common::ArchiveMemberList &list) const {
+ for (const Common::String &member : _members)
+ list.push_back(Common::ArchiveMemberPtr(new Common::GenericArchiveMember(member, *this)));
+ return _members.size();
+}
+
+const Common::ArchiveMemberPtr Rebel2MacArchive::getMember(const Common::Path &path) const {
+ if (!hasFile(path))
+ return Common::ArchiveMemberPtr();
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
+}
+
+Common::SeekableReadStream *Rebel2MacArchive::createReadStreamForMember(const Common::Path &path) const {
+ if (!hasFile(path))
+ return nullptr;
+
+ ScummFile *file = new ScummFile(_vm);
+ if (file->open(_filename) && file->openSubFile(Common::Path(path.baseName())))
+ return file;
+ delete file;
+ return nullptr;
+}
+
+Common::Archive *createRebel2MacArchive(const ScummEngine *vm, const Common::Path &filename) {
+ Rebel2MacArchive *archive = new Rebel2MacArchive(vm, filename);
+ if (archive->load())
+ return archive;
+ delete archive;
+ return nullptr;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/insane/rebel2/mac_archive.h b/engines/scumm/insane/rebel2/mac_archive.h
new file mode 100644
index 00000000000..23a9e09738b
--- /dev/null
+++ b/engines/scumm/insane/rebel2/mac_archive.h
@@ -0,0 +1,39 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SCUMM_INSANE_REBEL2_MAC_ARCHIVE_H
+#define SCUMM_INSANE_REBEL2_MAC_ARCHIVE_H
+
+#include "common/path.h"
+
+namespace Common {
+class Archive;
+}
+
+namespace Scumm {
+
+class ScummEngine;
+
+Common::Archive *createRebel2MacArchive(const ScummEngine *vm, const Common::Path &filename);
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 91d08b83082..c4c591033b0 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -22,6 +22,7 @@
#include "engines/engine.h"
+#include "common/archive.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "common/events.h"
@@ -45,6 +46,7 @@
#include "scumm/smush/rebel/font_rebel2.h"
#include "scumm/insane/rebel2/rebel.h"
+#include "scumm/insane/rebel2/mac_archive.h"
#include "scumm/insane/rebel2/shared.h"
#include "common/config-manager.h"
@@ -162,6 +164,13 @@ bool InsaneRebel2::isSkippableVideoState() const {
InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) :
_release(getRebel2Release(scumm->_game.variant, ConfMan.getBool("rebel2_restored_content"))) {
_vm = scumm;
+ if (_release.container) {
+ Common::Archive *archive = createRebel2MacArchive(_vm, _release.container);
+ if (!archive)
+ error("Cannot open Rebel Assault II data bundle '%s'", _release.container);
+ SearchMan.add("rebel2-mac-data", archive, 1);
+ }
+
// Rebel Assault II skips ScummEngine::resetScumm(), which normally clears this state.
for (int i = 0; i < kScummActionCount; i++)
_vm->_actionMap[i] = false;
@@ -627,6 +636,9 @@ InsaneRebel2::~InsaneRebel2() {
free(_rebelEmbeddedHud[i].pixels);
_rebelEmbeddedHud[i].pixels = nullptr;
}
+
+ if (_release.container)
+ SearchMan.remove("rebel2-mac-data");
}
bool InsaneRebel2::isHiRes() const {
diff --git a/engines/scumm/insane/rebel2/releases.cpp b/engines/scumm/insane/rebel2/releases.cpp
index 080e672d815..29488f52c06 100644
--- a/engines/scumm/insane/rebel2/releases.cpp
+++ b/engines/scumm/insane/rebel2/releases.cpp
@@ -28,7 +28,9 @@ namespace Scumm {
// DA1.06's difficulty table starts at offset 0x4798 in LE object 3.
// Rows have 17 words; each difficulty occupies 0x242 bytes. Types 5 and 6
// cover the shield attack and reactor attack respectively.
-static const Rebel2DifficultyOverride kDemoDA106Difficulty[] = {
+// Mac v1.0 has the same combat values at 0x5a70 in its unpacked PEF data
+// section. Its unused flight columns are -1; neither section uses flight controls.
+static const Rebel2DifficultyOverride kChapter6DemoDifficulty[] = {
{ 5, {
{ 5, 5, -1, 19, 2, 15, 75, 25, 50, 2, 500, -1, 8, -1, -1, -1, -1 },
{ 5, 3, -1, 25, 4, 15, 75, 50, 100, 4, 1000, -1, 16, -1, -1, -1, -1 },
@@ -51,26 +53,32 @@ static const Rebel2DifficultyOverride kDemoDA106Difficulty[] = {
// saved pilot progress and the unlock-all option.
static const Rebel2Release kReleases[] = {
{
- "", "SYSTM/GAME.TRS", nullptr, nullptr,
+ "", nullptr, "SYSTM/GAME.TRS", nullptr, nullptr,
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
true, false, false, false, nullptr, 0
},
{
- "Demo", "SYSTM/GAME.TRS", "OPEN/O_DEMO.SAN", nullptr, { 0 },
+ "Demo", nullptr, "SYSTM/GAME.TRS", "OPEN/O_DEMO.SAN", nullptr, { 0 },
false, false, false, false, nullptr, 0
},
{
// RBL2DEMO.EXE selects GAME_E.TRS. The original chapter-6 handler
// (LE object 1, 0x10d10) skips the shield attack and uses 06END_B.
- "Demo DA1.06", "SYSTM/GAME_E.TRS", nullptr, nullptr, { 6 },
- false, true, true, false, kDemoDA106Difficulty, ARRAYSIZE(kDemoDA106Difficulty)
+ "Demo DA1.06", nullptr, "SYSTM/GAME_E.TRS", nullptr, nullptr, { 6 },
+ false, true, true, false, kChapter6DemoDifficulty, ARRAYSIZE(kChapter6DemoDifficulty)
},
{
// GAME.TRS selects chapters 1, 2 and 4 in playable-demo mode.
// LE object 1, 0xf860 advances passwords past missing chapters;
// 0x12eb0 plays O_PLAYDE instead of the retail finale.
- "Demo DG1.15", "SYSTM/GAME.TRS", nullptr, "OPEN/O_PLAYDE.SAN", { 1, 2, 4 },
+ "Demo DG1.15", nullptr, "SYSTM/GAME.TRS", nullptr, "OPEN/O_PLAYDE.SAN", { 1, 2, 4 },
false, false, false, true, nullptr, 0
+ },
+ {
+ // Macintosh v1.0's chapter-6 handler (PEF code section, 0x17fe8)
+ // also skips the shield attack in demo mode and uses 06END_B.
+ "Demo v1.0", "Rebel Assault II Demo Data", "SYSTM/GAME_E.TRS", nullptr, nullptr, { 6 },
+ false, true, true, false, kChapter6DemoDifficulty, ARRAYSIZE(kChapter6DemoDifficulty)
}
};
diff --git a/engines/scumm/insane/rebel2/releases.h b/engines/scumm/insane/rebel2/releases.h
index f979c1ba12a..a5049575842 100644
--- a/engines/scumm/insane/rebel2/releases.h
+++ b/engines/scumm/insane/rebel2/releases.h
@@ -55,6 +55,7 @@ struct Rebel2Release {
enum { kNumLevels = 15, kFinale = 16 };
const char *variant;
+ const char *container;
const char *strings;
const char *nonInteractiveVideo;
const char *completionVideo;
@@ -62,7 +63,7 @@ struct Rebel2Release {
byte levels[kNumLevels];
bool ending;
bool unlockAvailableLevels;
- // The DA1.06 demo starts chapter 6 at the attack on the reactor.
+ // The chapter-6 demos start at the attack on the reactor.
bool skipMiningFacilityAttack;
bool advanceCompletionPasswords;
const Rebel2DifficultyOverride *difficultyOverrides;
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index c8ab8c301ff..b0f7e3eb3aa 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -164,6 +164,7 @@ MODULE_OBJS += \
insane/rebel2/audio.o \
insane/rebel2/iact.o \
insane/rebel2/levels.o \
+ insane/rebel2/mac_archive.o \
insane/rebel2/menu.o \
insane/rebel2/render.o \
insane/rebel2/runlevels.o \
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 2c40e90565f..bb445084e06 100644
--- a/engines/scumm/scumm-md5.h
+++ b/engines/scumm/scumm-md5.h
@@ -1,5 +1,5 @@
/*
- This file was generated by the md5table tool on Sun Sep 27 12:52:41 2026
+ This file was generated by the md5table tool on Sun Sep 27 13:12:00 2026
DO NOT EDIT MANUALLY!
*/
@@ -716,6 +716,7 @@ static const MD5Table md5table[] = {
{ "d7ab7cd6105546016e6a0d46fb36b964", "pajama", "HE 100", "Demo", 18958, Common::EN_ANY, Common::kPlatformUnknown },
{ "d7b247c26bf1f01f8f7daf142be84de3", "balloon", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "d8323015ecb8b10bf53474f6e6b0ae33", "dig", "", "", 16304, Common::UNK_LANG, Common::kPlatformUnknown },
+ { "d89f31e209c0642b4d20ccf615840401", "rebel2", "Demo v1.0", "Demo v1.0", 128658792, Common::EN_ANY, Common::kPlatformMacintosh },
{ "d917f311a448e3cc7239c31bddb00dd2", "samnmax", "", "CD", 9080, Common::EN_ANY, Common::kPlatformUnknown },
{ "d93cc8be628ed5d3b3a29188fc7105d3", "dig", "Steam", "Steam", 1061296, Common::UNK_LANG, Common::kPlatformMacintosh },
{ "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformDOS },
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index f4ec7c544a2..75040205bc9 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -1188,6 +1188,9 @@ Common::Error ScummEngine::init() {
_filenamePattern.genMethod = kGenRoomNum;
} else if (_game.id == GID_REBEL1 || _game.id == GID_REBEL2) {
_fileHandle = new ScummFile(this);
+ // RA2 must disable the SCUMM GUI before Mac screen initialization.
+ if (_game.id == GID_REBEL2)
+ _useOriginalGUI = false;
} else if (_game.platform == Common::kPlatformMacintosh) {
// The mac versions of Indy4, Sam&Max, DOTT, FT and The Dig used a
// special meta (container) file format to store the actual SCUMM data
@@ -1876,8 +1879,6 @@ void ScummEngine_v7::setupScumm(const Common::Path &macResourceFile) {
setupScummVars();
- _useOriginalGUI = false;
-
_sound = new Sound(this, _mixer, false);
// Rebel Assault 2 doesn't use iMUSE for audio.
_musicEngine = _imuseDigital = nullptr;
More information about the Scummvm-git-logs
mailing list