[Scummvm-git-logs] scummvm master -> 111c6a8596889f4b904a89f90a502ecff1f36c41
neuromancer
noreply at scummvm.org
Fri Jul 31 13:24:50 UTC 2026
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
111c6a8596 SCUMM: RA2: allow to load pilots directly from the load menu
Commit: 111c6a8596889f4b904a89f90a502ecff1f36c41
https://github.com/scummvm/scummvm/commit/111c6a8596889f4b904a89f90a502ecff1f36c41
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-31T15:24:33+02:00
Commit Message:
SCUMM: RA2: allow to load pilots directly from the load menu
Changed paths:
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/saveload.cpp
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index ec758c094c3..20fe3ba6027 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/events.h"
#include "common/system.h"
#include "common/util.h"
@@ -90,30 +91,48 @@ void InsaneRebel2::runGame() {
return;
}
- playIntroSequence();
+ // Launching straight into a pilot from the launcher skips the intro.
+ if (ConfMan.hasKey("save_slot"))
+ loadGameState(ConfMan.getInt("save_slot"), true);
- while (!_vm->shouldQuit()) {
- int menuResult = runMainMenu();
-
- if (menuResult == kMenuQuit || _vm->shouldQuit())
- break;
+ if (!_pilotLoadRequested)
+ playIntroSequence();
- if (menuResult == kMenuResumeDemo) {
- playIntroSequence();
- if (!_vm->shouldQuit())
- showTopPilots();
- continue;
- }
+ while (!_vm->shouldQuit()) {
+ int menuResult = kMenuNewGame;
- if (menuResult == kMenuNewGame) {
- int pilotResult = runLevelSelect();
+ // A loaded pilot goes straight to the chapter selection.
+ if (_pilotLoadRequested) {
+ _pilotLoadRequested = false;
+ } else {
+ menuResult = runMainMenu();
- if (pilotResult == kLevelSelectQuit || _vm->shouldQuit())
+ if (menuResult == kMenuQuit || _vm->shouldQuit())
break;
- if (pilotResult == kLevelSelectBack)
+ if (menuResult == kMenuResumeDemo) {
+ playIntroSequence();
+ if (!_vm->shouldQuit())
+ showTopPilots();
continue;
+ }
+
+ if (menuResult == kMenuNewGame) {
+ int pilotResult = runLevelSelect();
+ if (pilotResult == kLevelSelectQuit || _vm->shouldQuit())
+ break;
+
+ // A load during the pilot menu already picked the pilot.
+ if (pilotResult == kLevelSelectBack && !_pilotLoadRequested)
+ continue;
+
+ if (_pilotLoadRequested)
+ _pilotLoadRequested = false;
+ }
+ }
+
+ if (menuResult == kMenuNewGame) {
int chapterResult = runChapterSelect();
if (chapterResult == kChapterSelectQuit || _vm->shouldQuit())
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 59cd9529d1c..a4d1a537894 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -1038,12 +1038,7 @@ int InsaneRebel2::runLevelSelect() {
debugC(DEBUG_INSANE, "Pilot selection: %d (numPilots=%d)", _levelSelection, _numPilots);
if (_levelSelection < _numPilots) {
- _activePilot = _levelSelection;
- _difficulty = _pilots[_activePilot].difficulty;
-
- for (int i = 0; i < 16; i++) {
- _chapterUnlocked[i] = _debugUnlockAll || (_pilots[_activePilot].damage[i] < 0xFF);
- }
+ selectPilot(_levelSelection);
debugC(DEBUG_INSANE, "Pilot '%s' selected (slot %d, difficulty %d)",
_pilots[_activePilot].name, _activePilot, _difficulty);
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 03ccda4446e..e4c832de509 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -28,6 +28,7 @@
#include "common/savefile.h"
#include "common/util.h"
#include "graphics/paletteman.h"
+#include "graphics/thumbnail.h"
#include "audio/mixer.h"
@@ -513,6 +514,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_numPilots = 0;
_activePilot = 0;
+ _pilotLoadRequested = false;
for (i = 0; i < kMaxPilots; i++) {
_pilots[i].init();
}
@@ -1373,6 +1375,51 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
const uint32 kPilotSaveMagic = MKTAG('R', 'A', '2', 'P');
const uint16 kPilotSaveVersion = 2;
+// Pilots occupy the regular ScummVM save slots, so they need the standard SCUMM
+// header too: without it every slot reads as an invalid savegame.
+const uint32 kPilotScummSaveVersion = 124;
+
+bool writeRebel2SavegameHeader(Common::WriteStream *out, const Common::String &desc) {
+ char name[32];
+ memset(name, 0, sizeof(name));
+ Common::strlcpy(name, desc.c_str(), sizeof(name));
+
+ out->writeUint32BE(MKTAG('S', 'C', 'V', 'M'));
+ out->writeUint32LE(0);
+ out->writeUint32LE(kPilotScummSaveVersion);
+ out->write(name, sizeof(name));
+ return !out->err();
+}
+
+// A stream starting with the pilot magic is a pre-header file; rewind it.
+bool readRebel2SavegameHeader(Common::SeekableReadStream *in, uint32 *version) {
+ const uint32 tag = in->readUint32BE();
+ if (tag == kPilotSaveMagic) {
+ in->seek(0);
+ if (version)
+ *version = 0;
+ return true;
+ }
+ if (tag != MKTAG('S', 'C', 'V', 'M'))
+ return false;
+
+ in->readUint32LE(); // size, unused
+ uint32 hdrVersion = in->readUint32LE();
+ if (hdrVersion > 0xFFFFFF)
+ hdrVersion = SWAP_BYTES_32(hdrVersion);
+ if (hdrVersion < VER(52) || hdrVersion > kPilotScummSaveVersion)
+ return false;
+
+ char name[32];
+ in->read(name, sizeof(name));
+ if (in->err())
+ return false;
+
+ if (version)
+ *version = hdrVersion;
+ return true;
+}
+
bool InsaneRebel2::loadPilots() {
_numPilots = 0;
@@ -1382,6 +1429,25 @@ bool InsaneRebel2::loadPilots() {
if (!sf)
break; // Slots are contiguous
+ uint32 scummVersion = 0;
+ if (!readRebel2SavegameHeader(sf, &scummVersion)) {
+ delete sf;
+ break;
+ }
+
+ // Only present once the slot carries a SCUMM header.
+ if (scummVersion >= VER(52) && !Graphics::skipThumbnail(*sf)) {
+ delete sf;
+ break;
+ }
+ if (scummVersion >= VER(56)) {
+ SaveStateMetaInfos infos;
+ if (!_vm->loadInfos(sf, &infos)) {
+ delete sf;
+ break;
+ }
+ }
+
uint32 magic = sf->readUint32BE();
if (magic != kPilotSaveMagic) {
delete sf;
@@ -1425,6 +1491,14 @@ bool InsaneRebel2::savePilots() {
continue;
}
+ // The pilot name doubles as the save description in the load screen.
+ Common::String desc(_pilots[i].name);
+ if (desc.empty())
+ desc = Common::String::format("Pilot %d", i + 1);
+ writeRebel2SavegameHeader(sf, desc);
+ Graphics::saveThumbnail(*sf);
+ _vm->saveInfos(sf);
+
sf->writeUint32BE(kPilotSaveMagic);
sf->writeUint16LE(kPilotSaveVersion);
@@ -1500,6 +1574,15 @@ void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives,
return;
PilotData &pilot = _pilots[_activePilot];
+
+ // Entry `levelIndex` holds the state the next level starts from; damage
+ // below 0xFF means it was already reached, so a replay has nothing to add.
+ if (pilot.damage[levelIndex] < 0xFF) {
+ debugC(DEBUG_INSANE, "RA2: level %d replayed, keeping score %d/lives %d over %d/%d",
+ levelIndex, pilot.score[levelIndex], pilot.lives[levelIndex], score, lives);
+ return;
+ }
+
pilot.score[levelIndex] = score;
pilot.lives[levelIndex] = lives;
pilot.damage[levelIndex] = damage;
@@ -1508,6 +1591,42 @@ void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives,
savePilots();
}
+bool InsaneRebel2::selectPilot(int index) {
+ if (index < 0 || index >= _numPilots)
+ return false;
+
+ _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);
+
+ return true;
+}
+
+Common::Error InsaneRebel2::loadGameState(int slot, bool startupLoad) {
+ // Re-read so a slot written by another session is picked up.
+ loadPilots();
+
+ if (!selectPilot(slot)) {
+ warning("RA2: slot %d does not hold a pilot", slot);
+ return Common::kReadingFailed;
+ }
+
+ _pilotLoadRequested = true;
+
+ // Unwind the running menu video so runGame() reaches the chapter selection.
+ if (!startupLoad) {
+ _menuSelectionConfirmed = false;
+ _vm->_smushVideoShouldFinish = true;
+ }
+
+ debugC(DEBUG_INSANE, "RA2: loaded pilot '%s' from slot %d (difficulty %d)",
+ _pilots[_activePilot].name, slot, _difficulty);
+ return Common::kNoError;
+}
+
int32 InsaneRebel2::processMouse() {
int32 buttons = 0;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index a9858d8f441..6eaf222a63b 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -220,6 +220,13 @@ public:
void updatePilotProgress(int levelIndex, int32 score, int32 lives, int32 damage, int32 rating);
+ // Activates a pilot and derives its unlocked chapters, as the pilot menu does.
+ bool selectPilot(int index);
+
+ // Loading a pilot drops straight into the chapter selection.
+ Common::Error loadGameState(int slot, bool startupLoad = false);
+ bool _pilotLoadRequested;
+
enum LevelSelectResult {
kLevelSelectBack = 0,
kLevelSelectPlay = 1,
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 55af040617a..8bad50a6ddc 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -44,6 +44,7 @@
#ifdef ENABLE_SCUMM_7_8
#include "scumm/insane/rebel1/rebel.h"
+#include "scumm/insane/rebel2/rebel.h"
#endif
#include "backends/audiocd/audiocd.h"
@@ -86,6 +87,12 @@ Common::Error ScummEngine::loadGameState(int slot) {
if (rebel)
return rebel->loadGameState(slot);
}
+
+ if (_game.id == GID_REBEL2) {
+ InsaneRebel2 *rebel = (InsaneRebel2 *)((ScummEngine_v7 *)this)->getInsane();
+ if (rebel)
+ return rebel->loadGameState(slot);
+ }
#endif
requestLoad(slot);
@@ -97,7 +104,7 @@ bool ScummEngine::canLoadGameStateCurrently(Common::U32String *msg) {
return false;
#ifdef ENABLE_SCUMM_7_8
- if (_game.id == GID_REBEL1)
+ if (_game.id == GID_REBEL1 || _game.id == GID_REBEL2)
return true;
#endif
@@ -162,6 +169,10 @@ Common::Error ScummEngine::saveGameState(int slot, const Common::String &desc, b
if (rebel)
return rebel->saveGameState(slot, desc, isAutosave);
}
+
+ // RA2 writes the active pilot itself, and pilot slots must stay contiguous.
+ if (_game.id == GID_REBEL2)
+ return Common::kNoError;
#endif
requestSave(slot, desc);
@@ -175,6 +186,14 @@ bool ScummEngine::canSaveGameStateCurrently(Common::U32String *msg) {
#ifdef ENABLE_SCUMM_7_8
if (_game.id == GID_REBEL1)
return true;
+
+ // No save interface: progress is written as levels are completed.
+ if (_game.id == GID_REBEL2) {
+ if (msg)
+ *msg = _("This game does not support saving from the menu. Progress is saved automatically when a level is completed");
+
+ return false;
+ }
#endif
// Disallow saving in v0-v3 games when a 'prequel' to a cutscene is shown.
More information about the Scummvm-git-logs
mailing list