[Scummvm-git-logs] scummvm master -> 572e7ace90e5157e9540623c63dda7760dd8899a
neuromancer
noreply at scummvm.org
Wed Jul 22 20:28:00 UTC 2026
This automated email contains information about 8 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
bc003485a1 SCUMM: RA2: added language-specific detection code
e6927b61f3 SCUMM: RA2: use high resolution mode by default
f50c2afe71 SCUMM: RA1: added language-specific detection code
a675548a73 SCUMM: RA2: added spanish release detection code
f522260a58 SCUMM: RA1: added support for 3DO release
4b7d5e6348 SCUMM: RA1: initial support for the SegaCD release
583f57338b SCUMM: RA1: skip L7 in SegaCD as there are is such mission
572e7ace90 SCUMM: RA1: magenta entries inherit the previous Sega palette
Commit: bc003485a1869ff4d80ae443a910884dba3b067a
https://github.com/scummvm/scummvm/commit/bc003485a1869ff4d80ae443a910884dba3b067a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA2: added language-specific detection code
Changed paths:
devtools/scumm-md5.txt
engines/scumm/detection_internal.h
engines/scumm/scumm-md5.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 72b3a55490b..e7625327a42 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -455,7 +455,7 @@ rebel1 Star Wars: Rebel Assault
6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
rebel2 Star Wars: Rebel Assault II: The Hidden Empire
- 6b73a08c535c0544785d73ff812908a0 9430 en DOS - - - -
+ 6b73a08c535c0544785d73ff812908a0 9430 All DOS - - - -
e977ed046b88e75b645491caeff37b0c 313689 en DOS Demo Demo - -
diff --git a/engines/scumm/detection_internal.h b/engines/scumm/detection_internal.h
index 4b1258592e8..60d0c2f64f2 100644
--- a/engines/scumm/detection_internal.h
+++ b/engines/scumm/detection_internal.h
@@ -239,6 +239,30 @@ static Common::Language detectLanguage(const Common::FSList &fslist, byte id, co
return Common::KO_KOR;
}
+ if (id == GID_REBEL2) {
+ Common::FSNode systmDir;
+ Common::FSList systmList;
+ Common::File trs;
+ if (searchFSNode(fslist, "SYSTM", systmDir)
+ && systmDir.isDirectory()
+ && systmDir.getChildren(systmList, Common::FSNode::kListFilesOnly)
+ && searchFSNode(systmList, "GAME.TRS", langFile)
+ && trs.open(langFile)) {
+ switch (trs.size()) {
+ case 46746: // d9aced0c3fcb8f6a0045dcd4cbf12590
+ return Common::EN_ANY;
+ case 48097: // 66353d7250f680b28992459c355caa17
+ return Common::IT_ITA;
+ case 50094: // 004fb2fd15f84a1f81cc362d73811c9c
+ return Common::DE_DEU;
+ case 60976: // c53823d48beca122c45a83d35027a0e7
+ return Common::FR_FRA;
+ default:
+ break;
+ }
+ }
+ }
+
return originalLanguage;
}
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index ec658bb719f..24c4c65b281 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 Wed Jun 24 21:59:49 2026
+ This file was generated by the md5table tool on Wed Jul 22 12:10:11 2026
DO NOT EDIT MANUALLY!
*/
@@ -373,7 +373,7 @@ static const MD5Table md5table[] = {
{ "6b27dbcd8d5697d5c918eeca0f68ef6a", "puttrace", "HE CUP", "Preview", 3901484, Common::UNK_LANG, Common::kPlatformUnknown },
{ "6b3ec67da214f558dc5ceaa2acd47453", "indy3", "EGA", "EGA", 5361, Common::EN_ANY, Common::kPlatformDOS },
{ "6b5a3fef241e90d4b2e77f1e222773ee", "maniac", "NES", "", 2082, Common::SV_SWE, Common::kPlatformNES },
- { "6b73a08c535c0544785d73ff812908a0", "rebel2", "", "", 9430, Common::EN_ANY, Common::kPlatformDOS },
+ { "6b73a08c535c0544785d73ff812908a0", "rebel2", "", "", 9430, Common::UNK_LANG, Common::kPlatformDOS },
{ "6bb0fe2d7816c5528c773f2898e4e0ff", "rebel1", "", "v1.7", 212915, Common::EN_ANY, Common::kPlatformDOS },
{ "6bca7a1a96d16e52b8f3c42b50dbdca3", "fbear", "HE 62", "", -1, Common::JA_JPN, Common::kPlatform3DO },
{ "6bf70eee5de3d24d2403e0dd3d267e8a", "spyfox", "", "", 49221, Common::EN_USA, Common::kPlatformWindows },
Commit: e6927b61f3ac1c9eec47ee56130ed51bfeb927fe
https://github.com/scummvm/scummvm/commit/e6927b61f3ac1c9eec47ee56130ed51bfeb927fe
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA2: use high resolution mode by default
Changed paths:
engines/scumm/metaengine.cpp
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 3ed790a2d3c..b84c9233dc1 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -882,7 +882,7 @@ static const ExtraGuiOption enableRebel2HiRes = {
_s("High resolution mode"),
_s("Run the game in 640x400 high resolution mode instead of 320x200"),
"rebel2_hires",
- false,
+ true,
0,
0
};
Commit: f50c2afe7193809aa47274a33e970692adf136da
https://github.com/scummvm/scummvm/commit/f50c2afe7193809aa47274a33e970692adf136da
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA1: added language-specific detection code
Changed paths:
devtools/scumm-md5.txt
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/scumm-md5.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index e7625327a42..885e47dabdb 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -452,7 +452,9 @@ ft Full Throttle
rebel1 Star Wars: Rebel Assault
b9ac90e4411da1b6f7dd075cc4b03c2a 214435 en DOS - v1.0 - -
e210a57b756473be705d24b9378a8233 212383 en DOS - v1.3 - -
- 6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
+ 6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
+ 02236dcfb0cac59457153fc296233825 213171 fr DOS - v1.7 - -
+ b978b92affdfe5744711ca0cb3548720 213115 de DOS - v1.7 - -
rebel2 Star Wars: Rebel Assault II: The Hidden Empire
6b73a08c535c0544785d73ff812908a0 9430 All DOS - - - -
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 6947fb4f0a9..0f55b7da6ec 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -1009,8 +1009,9 @@ void InsaneRebel1::drawMenuTitleText(byte *dst, int pitch, int width, int height
void InsaneRebel1::renderHighScoresOverlay(byte *dst, int pitch, int width, int height) {
// entries fade in one per frame. We show all immediately.
- const int titleW = getMenuTalkTextWidth("TOP PILOTS");
- drawMenuTalkText(dst, pitch, width, height, getRebel1MenuCenteredX(titleW), 10, "TOP PILOTS");
+ const char *title = uiStr(kR1StrTopPilots);
+ const int titleW = getMenuTalkTextWidth(title);
+ drawMenuTalkText(dst, pitch, width, height, getRebel1MenuCenteredX(titleW), 10, title);
for (int i = 0; i < kHighScoreCount; i++) {
const int y = 25 + i * 14;
@@ -1029,26 +1030,24 @@ void InsaneRebel1::renderOptionsOverlay(byte *dst, int pitch, int width, int hei
_optTextEnabled = ConfMan.getBool("subtitles");
_optVolume = CLIP<int>(ConfMan.getInt("music_volume") / 2, 0, 127);
- const char *kDiffNames[3] = { "EASY", "NORMAL", "HARD" };
-
- const int titleW = getFontBankStringWidth("GAME OPTIONS");
- drawMenuTitleText(dst, pitch, width, height, getRebel1MenuCenteredX(titleW), 15, "GAME OPTIONS");
+ const char *title = uiStr(kR1StrOptionsTitle);
+ const int titleW = getFontBankStringWidth(title);
+ drawMenuTitleText(dst, pitch, width, height, getRebel1MenuCenteredX(titleW), 15, title);
// Build dynamic option strings for each row
- char diffLine[64], volLine[64];
- Common::sprintf_s(diffLine, "DIFFICULTY IS %s", kDiffNames[CLIP(_difficulty, 0, 2)]);
- Common::sprintf_s(volLine, "VOLUME AT %d PERCENT", (_optVolume * 100) / 127);
+ char volLine[64];
+ Common::sprintf_s(volLine, uiStr(kR1StrOptVolumeFmt), (_optVolume * 100) / 127);
const char *optItems[kOptionsItemCount] = {
- "EXIT MENU",
- _optRookieOneFemale ? "ROOKIE1 IS FEMALE" : "ROOKIE1 IS MALE",
- _optMusicEnabled ? "MUSIC IS ON" : "MUSIC IS OFF",
- _optSfxEnabled ? "SFX AND VOICE ARE ON" : "SFX AND VOICE ARE OFF",
- _optTextEnabled ? "DIALOGUE TEXT IS ON" : "DIALOGUE TEXT IS OFF",
- _optControlsYFlip ? "Y AXIS IS INVERTED" : "Y AXIS IS NORMAL",
+ uiStr(kR1StrOptExitMenu),
+ uiStr(_optRookieOneFemale ? kR1StrOptRookieFemale : kR1StrOptRookieMale),
+ uiStr(_optMusicEnabled ? kR1StrOptMusicOn : kR1StrOptMusicOff),
+ uiStr(_optSfxEnabled ? kR1StrOptSfxOn : kR1StrOptSfxOff),
+ uiStr(_optTextEnabled ? kR1StrOptTextOn : kR1StrOptTextOff),
+ uiStr(_optControlsYFlip ? kR1StrOptYFlipped : kR1StrOptYNormal),
_optRapidFire ? "RAPID FIRE IS ON" : "RAPID FIRE IS OFF",
volLine,
- diffLine
+ uiStr(kR1StrOptDiffEasy + CLIP(_difficulty, 0, 2))
};
for (int i = 0; i < kOptionsItemCount; i++) {
@@ -1114,27 +1113,28 @@ void InsaneRebel1::renderLevelSelectOverlay(byte *dst, int pitch, int width, int
}
void InsaneRebel1::renderMainMenuItems(byte *dst, int pitch, int width, int height) {
- const char *const kMenuItems[kRA1MainMenuItemCount] = {
- "START NEW GAME",
- "GAME OPTIONS",
- "ENTER PASSCODE",
+ const char *kMenuItems[kRA1MainMenuItemCount] = {
+ uiStr(kR1StrMenuNewGame),
+ uiStr(kR1StrMenuGameOptions),
+ uiStr(kR1StrMenuEnterPasscode),
"LEVEL SELECT",
- "CONTINUE DEMO",
- "EXIT TO DOS"
+ uiStr(kR1StrMenuContinueDemo),
+ uiStr(kR1StrMenuExitToDos)
};
- const char *const kMenuItemsLocked[kRA1MainMenuItemCount - 1] = {
- "START NEW GAME",
- "GAME OPTIONS",
- "ENTER PASSCODE",
- "CONTINUE DEMO",
- "EXIT TO DOS"
+ 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 int titleW = getFontBankStringWidth("MAIN MENU");
+ const char *menuTitle = uiStr(kR1StrMainMenuTitle);
+ const int titleW = getFontBankStringWidth(menuTitle);
const int titleX = getRebel1MenuCenteredX(titleW);
- drawMenuTitleText(dst, pitch, width, height, titleX, 30, "MAIN MENU");
+ drawMenuTitleText(dst, pitch, width, height, titleX, 30, menuTitle);
for (int i = 0; i < mainMenuItemCount; i++) {
const int textW = getMenuTalkTextWidth(menuItems[i]);
@@ -1194,13 +1194,13 @@ void InsaneRebel1::renderTextEntryOverlay(byte *dst, int pitch, int width, int h
};
if (_textEntryPasscodeMode) {
- drawCenteredTalkText(0x4b, "ENTER PASSCODE");
+ drawCenteredTalkText(0x4b, uiStr(kR1StrEnterPasscodeTitle));
drawCenteredTalkText(0x5f, _textEntryBuffer);
} else {
char scoreText[40];
Common::sprintf_s(scoreText, "SCORE: %ld", (long)_score);
drawCenteredTalkText(0x37, scoreText);
- drawCenteredTalkText(0x4b, "NEW HIGH SCORE");
+ drawCenteredTalkText(0x4b, uiStr(kR1StrNewHighScore));
drawCenteredTalkText(0x5f, _textEntryBuffer);
}
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 5ec62f2f5cb..d3ab2cde2d0 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -20,6 +20,7 @@
*/
#include "common/config-manager.h"
+#include "common/file.h"
#include "common/system.h"
#include "common/events.h"
#include "common/endian.h"
@@ -208,6 +209,257 @@ void InsaneRebel1::resetGameplayFlagsFromTuning() {
_gameplayFlags75ff = tuningFlags >> 8;
}
+// English fallbacks for the UI strings normally extracted from ASSAULT.EXE.
+// Order must match Rebel1UiStringId in rebel.h.
+static const char *const kRebel1UiFallback[kR1StrUiCount] = {
+ "MAIN MENU",
+ "START NEW GAME",
+ "GAME OPTIONS",
+ "ENTER PASSCODE",
+ "CONTINUE DEMO",
+ "EXIT TO DOS",
+ "TOP PILOTS",
+ "ENTER PASSCODE",
+ "NEW HIGH SCORE",
+ "GAME OPTIONS",
+ "EXIT MENU",
+ "ROOKIE1 IS FEMALE",
+ "ROOKIE1 IS MALE",
+ "MUSIC IS ON",
+ "MUSIC IS OFF",
+ "SFX AND VOICE ARE ON",
+ "SFX AND VOICE ARE OFF",
+ "DIALOGUE TEXT IS ON",
+ "DIALOGUE TEXT IS OFF",
+ "Y AXIS IS INVERTED",
+ "Y AXIS IS NORMAL",
+ "VOLUME AT %hd PERCENT",
+ "DIFFICULTY IS EASY",
+ "DIFFICULTY IS NORMAL",
+ "DIFFICULTY IS HARD",
+ "Chapter Complete",
+ "Completion bonus: %d",
+ "Bonus: %d",
+ "Password: %s",
+ "Path Taken: Hard",
+ "Path Taken: Easy",
+ "Target Accuracy: Perfect",
+ "Target Accuracy: %d percent",
+ "Part II",
+ "Part I",
+ "Torpedo Hit",
+ "Torpedo Missed",
+ "Torpedo on mark",
+ "<<SHOOT TARGETS FOR BONUS",
+ "<<WALKER %d%%",
+ "<<TIME %d"
+};
+
+// The printf conversions used by a format string ("%%" doesn't count), e.g.
+// "hd" for "<VOLUME AT %hd PERCENT". Used to reject extracted strings whose
+// conversions don't match the fallback's before they reach sprintf.
+static Common::String rebel1FormatSpecs(const char *s) {
+ Common::String out;
+ for (const char *p = s; *p; p++) {
+ if (*p != '%')
+ continue;
+ p++;
+ if (*p == '%' || *p == '\0')
+ continue;
+ while (*p == 'h' || *p == 'l')
+ out += *p++;
+ if (*p)
+ out += *p;
+ }
+ return out;
+}
+
+const char *InsaneRebel1::uiStr(int id) const {
+ if (id < 0 || id >= kR1StrUiCount)
+ return "";
+ if (!_uiStrings[id].empty())
+ return _uiStrings[id].c_str();
+ return kRebel1UiFallback[id];
+}
+
+// All localized DOS releases keep their UI text in ASSAULT.EXE 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.
+void InsaneRebel1::loadLocalizedUiStrings() {
+ Common::File f;
+ if (!f.open("ASSAULT.EXE"))
+ return;
+
+ int32 size = (int32)f.size();
+ if (size <= 0 || size > 2 * 1024 * 1024)
+ return;
+
+ byte *data = new byte[size];
+ if (f.read(data, size) != (uint32)size) {
+ delete[] data;
+ return;
+ }
+
+ // Collect NUL-terminated printable strings (DOS codepage bytes allowed).
+ Common::Array<Common::String> strs;
+ int32 runStart = -1;
+ for (int32 i = 0; i < size; i++) {
+ const byte b = data[i];
+ const bool printable = (b >= 0x20 && b <= 0x7E) || (b >= 0x80 && b <= 0xFE);
+ if (printable) {
+ if (runStart < 0)
+ runStart = i;
+ } else {
+ if (b == 0 && runStart >= 0 && i - runStart >= 2)
+ strs.push_back(Common::String((const char *)data + runStart, i - runStart));
+ runStart = -1;
+ }
+ }
+ delete[] data;
+
+ auto find = [&strs](const Common::String &needle, int from = 0) -> int {
+ for (uint i = (uint)MAX(from, 0); i < strs.size(); i++)
+ if (strs[i] == needle)
+ return (int)i;
+ return -1;
+ };
+
+ auto assign = [&](int id, int idx) -> bool {
+ if (id < 0 || id >= kR1StrUiCount || idx < 0 || idx >= (int)strs.size())
+ return false;
+ Common::String s = strs[idx];
+ const char *fb = kRebel1UiFallback[id];
+ if (fb[0] == '<') {
+ // HUD strings keep their '<' layer markup.
+ if (s[0] != '<')
+ return false;
+ } else {
+ while (!s.empty() && s[0] == '<')
+ s.deleteChar(0);
+ }
+ if (s.size() < 2 || s.size() > 60)
+ return false;
+ if (s.contains('\\'))
+ return false;
+ if (rebel1FormatSpecs(s.c_str()) != rebel1FormatSpecs(fb))
+ return false;
+ _uiStrings[id] = s;
+ return true;
+ };
+
+ // Main menu: title and the five original items follow OPEN\O1OPTION.ANM.
+ int idx = find("OPEN\\O1OPTION.ANM");
+ if (idx >= 0) {
+ assign(kR1StrMainMenuTitle, idx + 1);
+ for (int i = 0; i < 5; i++)
+ assign(kR1StrMenuNewGame + i, idx + 2 + i);
+ }
+
+ // High score table title follows OPEN\O1SCORE.ANM.
+ idx = find("OPEN\\O1SCORE.ANM");
+ if (idx >= 0)
+ assign(kR1StrTopPilots, idx + 1);
+
+ // The joystick calibration coordinate format "(%d,%d)" sits between the
+ // passcode/calibration strings and the options menu block.
+ const int calib = find("OPEN\\O1CALIB.ANM");
+ int paren = (calib >= 0) ? find("(%d,%d)", calib) : -1;
+ if (paren >= 0 && paren > calib + 40)
+ paren = -1;
+ if (paren >= 0) {
+ // 4 calibration instructions and the screen title precede it.
+ assign(kR1StrEnterPasscodeTitle, paren - 6);
+
+ int j = paren + 1;
+ while (j < (int)strs.size() && strs[j].hasPrefix("<<"))
+ j++;
+ if (assign(kR1StrOptionsTitle, j)) {
+ for (int i = 0; i < 15 && j + 1 + i < (int)strs.size(); i++) {
+ if (!strs[j + 1 + i].hasPrefix("<"))
+ break;
+ assign(kR1StrOptExitMenu + i, j + 1 + i);
+ }
+ }
+ }
+
+ // Chapter summary block: the only "...: %s" string is the password line,
+ // preceded by the title and the two bonus formats.
+ int pw = -1;
+ for (uint i = 0; i < strs.size(); i++) {
+ const Common::String &s = strs[i];
+ if (s.size() >= 7 && s.hasSuffix(": %s") && !s.contains('\\') && !s.hasPrefix("<") &&
+ rebel1FormatSpecs(s.c_str()) == "s") {
+ if (pw >= 0) {
+ pw = -1; // ambiguous
+ break;
+ }
+ pw = (int)i;
+ }
+ }
+ if (pw >= 0) {
+ assign(kR1StrPasswordFmt, pw);
+ assign(kR1StrChapterComplete, pw - 1);
+ assign(kR1StrBonusFmt, pw - 2);
+ assign(kR1StrCompletionBonusFmt, pw - 3);
+ // v1.7 keeps NEW HIGH SCORE with this block; v1.0 keeps it with the
+ // calibration block.
+ if (!assign(kR1StrNewHighScore, pw - 4) && paren >= 0)
+ assign(kR1StrNewHighScore, paren - 7);
+ }
+
+ // Level result strings, each anchored on the video played before them.
+ idx = find("LVL1\\L1END.ANM");
+ if (idx >= 0) {
+ assign(kR1StrPathHard, idx + 1);
+ assign(kR1StrPathEasy, idx + 2);
+ assign(kR1StrAccuracyPerfect, idx + 3);
+ assign(kR1StrAccuracyFmt, idx + 4);
+ assign(kR1StrPartII, idx + 5);
+ assign(kR1StrPartI, idx + 6);
+ }
+ idx = find("LVL4\\L4END2.ANM");
+ if (idx >= 0) {
+ assign(kR1StrTorpedoHit, idx + 1);
+ assign(kR1StrTorpedoMissed, idx + 2);
+ }
+ idx = find("LVL5\\L5PLAY2.ANM");
+ if (idx >= 0)
+ assign(kR1StrShootTargets, idx + 1);
+ idx = find("LVL8\\L8PLAY.ANM");
+ if (idx >= 0) {
+ assign(kR1StrWalkerFmt, idx + 1);
+ assign(kR1StrTimeFmt, idx + 2);
+ }
+ idx = find("LVL15\\L15END1.ANM");
+ if (idx >= 0)
+ assign(kR1StrTorpedoOnMark, idx + 1);
+
+ // Chapter title cards: title and "Chapter N" label follow the chapter's
+ // intro video name.
+ for (int n = 1; n <= 15; n++) {
+ Common::String anchor = (n == 1) ? Common::String("LVL1\\L1HANGAR.ANM")
+ : Common::String::format("LVL%d\\L%dINTRO.ANM", n, n);
+ idx = find(anchor);
+ if (idx < 0 || idx + 2 >= (int)strs.size())
+ continue;
+ const Common::String &title = strs[idx + 1];
+ const Common::String &label = strs[idx + 2];
+ if (title.size() >= 2 && title.size() <= 40 && !title.contains('\\') &&
+ !title.contains('%') && title[0] != '<')
+ _chapterTitles[n - 1] = title;
+ if (label.size() >= 2 && label.size() <= 40 && !label.contains('\\') &&
+ !label.contains('%') && label[0] != '<')
+ _chapterLabels[n - 1] = label;
+ }
+
+ int loaded = 0;
+ 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);
+}
+
InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
Insane::_vm = scumm;
@@ -435,6 +687,8 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
warning("InsaneRebel1::InsaneRebel1(): failed to load targeting font bank (TECHFONT/TALKFONT)");
}
+ loadLocalizedUiStrings();
+
initAudio(11025);
memset(_sfxData, 0, sizeof(_sfxData));
memset(_sfxSize, 0, sizeof(_sfxSize));
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index bf7f6969681..3dd4d01e138 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -51,6 +51,53 @@ enum RA1MenuCommand {
kRA1MenuCommandSelect6
};
+// UI strings extracted from ASSAULT.EXE so localized releases show their own
+// text. Order must match kRebel1UiFallback in rebel.cpp.
+enum Rebel1UiStringId {
+ kR1StrMainMenuTitle = 0,
+ kR1StrMenuNewGame,
+ kR1StrMenuGameOptions,
+ kR1StrMenuEnterPasscode,
+ kR1StrMenuContinueDemo,
+ kR1StrMenuExitToDos,
+ kR1StrTopPilots,
+ kR1StrEnterPasscodeTitle,
+ kR1StrNewHighScore,
+ kR1StrOptionsTitle,
+ kR1StrOptExitMenu,
+ kR1StrOptRookieFemale,
+ kR1StrOptRookieMale,
+ kR1StrOptMusicOn,
+ kR1StrOptMusicOff,
+ kR1StrOptSfxOn,
+ kR1StrOptSfxOff,
+ kR1StrOptTextOn,
+ kR1StrOptTextOff,
+ kR1StrOptYFlipped,
+ kR1StrOptYNormal,
+ kR1StrOptVolumeFmt,
+ kR1StrOptDiffEasy,
+ kR1StrOptDiffNormal,
+ kR1StrOptDiffHard,
+ kR1StrChapterComplete,
+ kR1StrCompletionBonusFmt,
+ kR1StrBonusFmt,
+ kR1StrPasswordFmt,
+ kR1StrPathHard,
+ kR1StrPathEasy,
+ kR1StrAccuracyPerfect,
+ kR1StrAccuracyFmt,
+ kR1StrPartII,
+ kR1StrPartI,
+ kR1StrTorpedoHit,
+ kR1StrTorpedoMissed,
+ kR1StrTorpedoOnMark,
+ kR1StrShootTargets,
+ kR1StrWalkerFmt,
+ kR1StrTimeFmt,
+ kR1StrUiCount
+};
+
// Sprite bank for RA1 NUT files.
struct RA1Sprite {
int16 xoffs;
@@ -385,6 +432,14 @@ private:
void beginLevelTitleOverlay(int level);
void drawLevelTitleOverlay(byte *dst, int pitch, int width, int height, int32 curFrame, int32 maxFrame);
+ // Localized UI text, loaded from ASSAULT.EXE (falls back to English).
+ Common::String _uiStrings[kR1StrUiCount];
+ Common::String _chapterLabels[15];
+ Common::String _chapterTitles[15];
+ void loadLocalizedUiStrings();
+ const char *uiStr(int id) const;
+ void formatTargetAccuracy(char *dst, size_t dstSize, int kills, int targetCount, bool perfectText) const;
+
static const int kChapterSummaryTextSize = 80;
struct ChapterSummaryState {
bool active;
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index 9a34d3975dd..4ee2a284189 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -997,10 +997,11 @@ void InsaneRebel1::renderLevel5Part2Overlay(byte *dst, int pitch, int width, int
overlayY = (int16)(0x8C - ((overlayY - 0x8C) >> 2));
if (curFrame < 0x32) {
- const char instructionText[] = "<<SHOOT TARGETS FOR BONUS";
- const int revealChars = CLIP<int>((int)curFrame, 0, (int)sizeof(instructionText) - 1);
- char revealedText[sizeof(instructionText)];
- Common::strlcpy(revealedText, instructionText, revealChars + 1);
+ const char *instructionText = uiStr(kR1StrShootTargets);
+ const int instructionLen = (int)strlen(instructionText);
+ const int revealChars = CLIP<int>((int)curFrame, 0, instructionLen);
+ char revealedText[64];
+ Common::strlcpy(revealedText, instructionText, MIN<int>(revealChars + 1, sizeof(revealedText)));
drawFontBankString(dst, pitch, width, height,
viewportX + overlayX - 0x27, viewportY + overlayY, revealedText);
} else {
@@ -1365,8 +1366,10 @@ void InsaneRebel1::drawLevelTitleOverlay(byte *dst, int pitch, int width, int he
return;
}
- const char *title = kLevelTitles[_introTextLevel].titleText;
- const char *subtitle = kLevelTitles[_introTextLevel].subtitleText;
+ const char *title = _chapterLabels[_introTextLevel].empty() ?
+ kLevelTitles[_introTextLevel].titleText : _chapterLabels[_introTextLevel].c_str();
+ const char *subtitle = _chapterTitles[_introTextLevel].empty() ?
+ kLevelTitles[_introTextLevel].subtitleText : _chapterTitles[_introTextLevel].c_str();
int titleW = getFontBankStringWidth(title);
int subtitleW = getFontBankStringWidth(subtitle);
@@ -1427,18 +1430,18 @@ void InsaneRebel1::drawChapterSummaryOverlay(byte *dst, int pitch, int width, in
const int centerX = width / 2;
const int titleChars = MAX<int>(0, (int)(curFrame - revealBaseFrame));
drawCenteredRebel1String(this, dst, pitch, width, height,
- centerX, 5, "Chapter Complete", titleChars);
+ centerX, 5, uiStr(kR1StrChapterComplete), titleChars);
if (revealBaseFrame + 0x0F < curFrame) {
- char completionText[40];
- Common::sprintf_s(completionText, "Completion bonus: %d", (int)_tuning.levelPts);
+ char completionText[64];
+ Common::sprintf_s(completionText, uiStr(kR1StrCompletionBonusFmt), (int)_tuning.levelPts);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x19, completionText);
}
if (_chapterSummary.hasBonus1 &&
revealBaseFrame + 0x28 < curFrame && curFrame < revealBaseFrame + 0x46) {
- char bonusText[32];
- Common::sprintf_s(bonusText, "Bonus: %d", _chapterSummary.bonusValue1);
+ char bonusText[64];
+ Common::sprintf_s(bonusText, uiStr(kR1StrBonusFmt), _chapterSummary.bonusValue1);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x32, _chapterSummary.bonusLabel1);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x46, _chapterSummary.detailText1);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x5A, bonusText);
@@ -1446,8 +1449,8 @@ void InsaneRebel1::drawChapterSummaryOverlay(byte *dst, int pitch, int width, in
if (_chapterSummary.hasBonus2 &&
revealBaseFrame + 0x55 < curFrame && curFrame < revealBaseFrame + 0x73) {
- char bonusText[32];
- Common::sprintf_s(bonusText, "Bonus: %d", _chapterSummary.bonusValue2);
+ char bonusText[64];
+ Common::sprintf_s(bonusText, uiStr(kR1StrBonusFmt), _chapterSummary.bonusValue2);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x32, _chapterSummary.bonusLabel2);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x46, _chapterSummary.detailText2);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x5A, bonusText);
@@ -1457,8 +1460,8 @@ void InsaneRebel1::drawChapterSummaryOverlay(byte *dst, int pitch, int width, in
revealBaseFrame + 10 < curFrame) {
const char *password = getChapterCompletePassword(_chapterSummary.passwordIndex);
if (password) {
- char passwordText[40];
- Common::sprintf_s(passwordText, "Password: %s", password);
+ char passwordText[64];
+ Common::sprintf_s(passwordText, uiStr(kR1StrPasswordFmt), password);
drawCenteredRebel1String(this, dst, pitch, width, height, centerX, 0x73, passwordText);
}
}
@@ -1871,8 +1874,8 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
projX = (int16)(0x61 - ((projX - 0x61) >> 2));
projY = (int16)(0x8D - ((projY - 0x8D) >> 2));
- char walkerStr[24];
- Common::sprintf_s(walkerStr, "<<WALKER %d%%", (int)_walkerHealth);
+ char walkerStr[64];
+ Common::sprintf_s(walkerStr, uiStr(kR1StrWalkerFmt), (int)_walkerHealth);
drawFontBankString(dst, pitch, width, height,
viewportX + projX, viewportY + projY, walkerStr);
}
@@ -1907,8 +1910,8 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
projX = (int16)(0x62 - ((projX - 0x62) >> 2));
projY = (int16)(0x9C - ((projY - 0x9C) >> 2));
- char timerStr[16];
- Common::sprintf_s(timerStr, "<<TIME %d", (int)_walkerTimer);
+ char timerStr[64];
+ Common::sprintf_s(timerStr, uiStr(kR1StrTimeFmt), (int)_walkerTimer);
drawFontBankString(dst, pitch, width, height,
viewportX + projX, viewportY + projY, timerStr);
}
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 347d092a792..29507d3518b 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -125,11 +125,11 @@ int32 findAnimFrameChunkOffsetByGameCounter(ScummEngine_v7 *vm, const char *file
return result;
}
-static void formatTargetAccuracy(char *dst, size_t dstSize, int kills, int targetCount, bool perfectText) {
+void InsaneRebel1::formatTargetAccuracy(char *dst, size_t dstSize, int kills, int targetCount, bool perfectText) const {
if (perfectText && kills >= targetCount)
- Common::sprintf_s(dst, dstSize, "Target Accuracy: Perfect");
+ Common::sprintf_s(dst, dstSize, "%s", uiStr(kR1StrAccuracyPerfect));
else
- Common::sprintf_s(dst, dstSize, "Target Accuracy: %d percent", (kills * 100) / targetCount);
+ Common::sprintf_s(dst, dstSize, uiStr(kR1StrAccuracyFmt), (kills * 100) / targetCount);
}
static int calculateThresholdBonus(int kills, int perfectThreshold, int perKillThreshold, int perKillBonus) {
@@ -376,14 +376,14 @@ bool InsaneRebel1::runLevel1() {
break;
if (_killCount > 4 || _interactiveVideoCheatSkipped) {
- const char *pathText = _rightPathSelected ? "Path Taken: Hard" : "Path Taken: Easy";
+ const char *pathText = uiStr(_rightPathSelected ? kR1StrPathHard : kR1StrPathEasy);
const int pathBonus = _rightPathSelected ? _tuning.bonus * 3 : 0;
char accuracyText[80];
formatTargetAccuracy(accuracyText, sizeof(accuracyText), _killCount, 0x0E, true);
const int targetBonus = calculateThresholdBonus(_killCount, 0x0D, 5, _tuning.bonus);
playChapterCompleteCinematic("LVL1/L1END.ANM", 1, 0x78, 5,
- "Part I", pathText, pathBonus,
- "Part II", accuracyText, targetBonus);
+ uiStr(kR1StrPartI), pathText, pathBonus,
+ uiStr(kR1StrPartII), accuracyText, targetBonus);
return !shouldAbortGameFlow();
}
@@ -552,7 +552,7 @@ bool InsaneRebel1::runLevel4() {
if (_health >= 0 && shieldGeneratorsDestroyed) {
const bool torpedoHit = (_killCount != 0);
playChapterCompleteCinematic(torpedoHit ? "LVL4/L4END1.ANM" : "LVL4/L4END2.ANM",
- 4, 0x69, 5, " ", torpedoHit ? "Torpedo Hit" : "Torpedo Missed",
+ 4, 0x69, 5, " ", uiStr(torpedoHit ? kR1StrTorpedoHit : kR1StrTorpedoMissed),
torpedoHit ? _tuning.bonus : 0);
return !shouldAbortGameFlow();
}
@@ -1379,7 +1379,7 @@ bool InsaneRebel1::runLevel15() {
formatTargetAccuracy(accuracyText, sizeof(accuracyText), _killCount, 0x58, false);
playChapterCompleteCinematic("LVL15/L15END1.ANM", 15, 0x122, 0xA5,
"Part I", accuracyText, targetBonus,
- "Part II", "Torpedo on mark", 10000, _difficulty + 13);
+ uiStr(kR1StrPartII), uiStr(kR1StrTorpedoOnMark), 10000, _difficulty + 13);
return !shouldAbortGameFlow();
}
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 24c4c65b281..b2dc16ffbd2 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 Wed Jul 22 12:10:11 2026
+ This file was generated by the md5table tool on Wed Jul 22 12:44:10 2026
DO NOT EDIT MANUALLY!
*/
@@ -22,6 +22,7 @@ struct MD5Table {
static const MD5Table md5table[] = {
{ "008e76ec3ae58d0add637ea7aa299a2c", "freddi3", "", "", -1, Common::FR_FRA, Common::kPlatformMacintosh },
{ "01e51615b26e8ddf6ae88dd86e435d4a", "freddi", "HE 71", "", -1, Common::EN_ANY, Common::kPlatformWindows },
+ { "02236dcfb0cac59457153fc296233825", "rebel1", "", "v1.7", 213171, Common::FR_FRA, Common::kPlatformDOS },
{ "02cae0e7ff8504f73618391873d5781a", "freddi3", "HE 98.5", "", -1, Common::DE_DEU, Common::kPlatformWindows },
{ "0305e850382b812fec6e5998ef88a966", "pajama", "", "Demo", -1, Common::NL_NLD, Common::kPlatformUnknown },
{ "03173f0df12062d84721062666155efa", "farm", "HE 71", "Demo", 34312, Common::EN_ANY, Common::kPlatformWindows },
@@ -604,6 +605,7 @@ static const MD5Table md5table[] = {
{ "b886b0a5d909c7158a914e1d7c1c6c65", "loom", "EGA", "EGA", -1, Common::FR_FRA, Common::kPlatformDOS },
{ "b8955d7d23b4972229060d1592489fef", "freddicove", "HE 100", "", 41182, Common::NL_NLD, Common::kPlatformUnknown },
{ "b95347da0052a623f7a4bb15f425d897", "pajama3", "", "Demo", 13911, Common::IT_ITA, Common::kPlatformWindows },
+ { "b978b92affdfe5744711ca0cb3548720", "rebel1", "", "v1.7", 213115, Common::DE_DEU, Common::kPlatformDOS },
{ "b9ac90e4411da1b6f7dd075cc4b03c2a", "rebel1", "", "v1.0", 214435, Common::EN_ANY, Common::kPlatformDOS },
{ "b9ba19ce376efc69be78ef3baef8d2b9", "monkey", "Mac", "", 8955, Common::EN_ANY, Common::kPlatformMacintosh },
{ "b9bb68c5d2c9b6e2d9c513a29a754a57", "puttmoon", "", "", 7828, Common::EN_ANY, Common::kPlatformDOS },
Commit: a675548a73eb09cad04dce31cbaa534aee058726
https://github.com/scummvm/scummvm/commit/a675548a73eb09cad04dce31cbaa534aee058726
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA2: added spanish release detection code
Changed paths:
engines/scumm/detection_internal.h
diff --git a/engines/scumm/detection_internal.h b/engines/scumm/detection_internal.h
index 60d0c2f64f2..81ca30b735a 100644
--- a/engines/scumm/detection_internal.h
+++ b/engines/scumm/detection_internal.h
@@ -253,6 +253,8 @@ static Common::Language detectLanguage(const Common::FSList &fslist, byte id, co
return Common::EN_ANY;
case 48097: // 66353d7250f680b28992459c355caa17
return Common::IT_ITA;
+ case 49750: // a4d2d985548cdd29523db5b117ca1b3d
+ return Common::ES_ESP;
case 50094: // 004fb2fd15f84a1f81cc362d73811c9c
return Common::DE_DEU;
case 60976: // c53823d48beca122c45a83d35027a0e7
Commit: f522260a5886099825d0b158cd54af4458174b55
https://github.com/scummvm/scummvm/commit/f522260a5886099825d0b158cd54af4458174b55
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA1: added support for 3DO release
Changed paths:
devtools/scumm-md5.txt
engines/scumm/detection_tables.h
engines/scumm/scumm-md5.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 885e47dabdb..80ba3075efc 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -455,6 +455,7 @@ rebel1 Star Wars: Rebel Assault
6bb0fe2d7816c5528c773f2898e4e0ff 212915 en DOS - v1.7 - -
02236dcfb0cac59457153fc296233825 213171 fr DOS - v1.7 - -
b978b92affdfe5744711ca0cb3548720 213115 de DOS - v1.7 - -
+ bb1d02798011e5906c4c726be4d63544 1908 en 3DO - - - -
rebel2 Star Wars: Rebel Assault II: The Hidden Empire
6b73a08c535c0544785d73ff812908a0 9430 All DOS - - - -
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 29c5118c8b2..2859350ad90 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -49,6 +49,7 @@ static const char *const directoryGlobs[] = {
"Contents", // Mac Steam versions
"MacOS", // Mac Steam versions
"Resources", // Mac SE/Remastered versions
+ "OPEN", // 3DO version of rebel1
0
};
@@ -502,6 +503,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", "O1LOGO.AAA", kGenUnchanged, UNK_LANG, Common::kPlatform3DO, "" },
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo" },
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index b2dc16ffbd2..5d58a3c5625 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 Wed Jul 22 12:44:10 2026
+ This file was generated by the md5table tool on Wed Jul 22 13:13:22 2026
DO NOT EDIT MANUALLY!
*/
@@ -613,6 +613,7 @@ static const MD5Table md5table[] = {
{ "ba888e6831517597859e91aa173f945c", "spyfox", "", "Demo", 20103, Common::FR_FRA, Common::kPlatformUnknown },
{ "ba90a1290f45f116407841862b8539f6", "spyfox2", "", "Demo", 14689, Common::IT_ITA, Common::kPlatformWindows },
{ "bab0fb81dcb12b8930c5d850b8f2a7de", "balloon", "HE 80", "", 12800, Common::DE_DEU, Common::kPlatformWindows },
+ { "bb1d02798011e5906c4c726be4d63544", "rebel1", "", "", 1908, Common::EN_ANY, Common::kPlatform3DO },
{ "bbadf7309c4a2c2763e4bbba3c3be634", "freddi3", "", "Demo", 22718, Common::FR_FRA, Common::kPlatformUnknown },
{ "bc4700bc0e12879f6d25d14d6be6cfdd", "spyfox2", "", "", 51286, Common::DE_DEU, Common::kPlatformUnknown },
{ "bd126753de619a495f9f22adc951c8d5", "monkey2", "", "", 11135, Common::IT_ITA, Common::kPlatformDOS },
Commit: 4b7d5e6348fb945c0a861b4be1fd8cb6a69e33c0
https://github.com/scummvm/scummvm/commit/4b7d5e6348fb945c0a861b4be1fd8cb6a69e33c0
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA1: initial support for the SegaCD release
Changed paths:
devtools/scumm-md5.txt
engines/scumm/charset.cpp
engines/scumm/detection_tables.h
engines/scumm/insane/rebel1/levels.cpp
engines/scumm/scumm-md5.h
engines/scumm/smush/rebel/codec_ra1.cpp
engines/scumm/smush/rebel/codec_ra1.h
engines/scumm/smush/rebel/smush_player_ra1.cpp
engines/scumm/smush/rebel/smush_player_ra1.h
engines/scumm/smush/smush_player.h
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 80ba3075efc..3ade3d6c066 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -456,6 +456,7 @@ rebel1 Star Wars: Rebel Assault
02236dcfb0cac59457153fc296233825 213171 fr DOS - v1.7 - -
b978b92affdfe5744711ca0cb3548720 213115 de DOS - v1.7 - -
bb1d02798011e5906c4c726be4d63544 1908 en 3DO - - - -
+ d77be4d8a3e609092703508e09f1ed5d 132904 ja SEGA - - - -
rebel2 Star Wars: Rebel Assault II: The Hidden Empire
6b73a08c535c0544785d73ff812908a0 9430 All DOS - - - -
diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp
index 81c14ae2972..42910601596 100644
--- a/engines/scumm/charset.cpp
+++ b/engines/scumm/charset.cpp
@@ -59,6 +59,10 @@ void ScummEngine::loadCJKFont() {
_useMultiFont = false; // Korean Multi-Font
+ // Sega CD Rebel Assault uses its SMUSH subtitle font.
+ if (_game.id == GID_REBEL1 && _game.platform == Common::kPlatformSegaCD)
+ return;
+
// Special case for Korean
if (isScummvmKorTarget()) {
loadKorFont();
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 2859350ad90..789e13a3102 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -504,6 +504,7 @@ static const GameFilenamePattern gameFilenamesTable[] = {
{ "rebel1", "ASSAULT.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
{ "rebel1", "O1LOGO.AAA", kGenUnchanged, UNK_LANG, Common::kPlatform3DO, "" },
+ { "rebel1", "SUB_AP.BIN", kGenUnchanged, UNK_LANG, Common::kPlatformSegaCD, "" },
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "" },
{ "rebel2", "REBEL2.EXE", kGenUnchanged, UNK_LANG, Common::kPlatformDOS, "Demo" },
diff --git a/engines/scumm/insane/rebel1/levels.cpp b/engines/scumm/insane/rebel1/levels.cpp
index 77a4fbdb136..c947b5c2cec 100644
--- a/engines/scumm/insane/rebel1/levels.cpp
+++ b/engines/scumm/insane/rebel1/levels.cpp
@@ -117,7 +117,8 @@ bool InsaneRebel1::loadRA1Nut(const char *filename, RA1SpriteBank &bank, bool wa
continue;
}
- int codec = READ_LE_UINT16(data + fobjOffset + 8);
+ // FOBJ stores the codec and its parameter in separate bytes.
+ const int codec = data[fobjOffset + 8];
bank.sprites[i].xoffs = READ_LE_INT16(data + fobjOffset + 10);
bank.sprites[i].yoffs = READ_LE_INT16(data + fobjOffset + 12);
bank.sprites[i].width = READ_LE_UINT16(data + fobjOffset + 14);
@@ -140,6 +141,13 @@ bool InsaneRebel1::loadRA1Nut(const char *filename, RA1SpriteBank &bank, bool wa
smushDecodeRA1Transparent(decPtr, fobjData, 0, 0,
bank.sprites[i].width, bank.sprites[i].height, bank.sprites[i].width,
fobjDataSize);
+ } else if (codec == SMUSH_CODEC_SEGACD_RLE || codec == SMUSH_CODEC_SEGACD_OPAQUE) {
+ // Codecs 31/32 use palette-banked 4bpp pixels.
+ const byte paletteBase = data[fobjOffset + 9];
+ bank.sprites[i].data = decPtr;
+ smushDecodeRA1SegaCDRLE(decPtr, fobjData, 0, 0,
+ bank.sprites[i].width, bank.sprites[i].height, bank.sprites[i].width,
+ bank.sprites[i].height, fobjDataSize, codec == SMUSH_CODEC_SEGACD_RLE, paletteBase);
} else {
bank.sprites[i].width = 0;
bank.sprites[i].height = 0;
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 5d58a3c5625..d5b332ff223 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 Wed Jul 22 13:13:22 2026
+ This file was generated by the md5table tool on Wed Jul 22 13:34:38 2026
DO NOT EDIT MANUALLY!
*/
@@ -703,6 +703,7 @@ static const MD5Table md5table[] = {
{ "d6dd0646404768a63e963891a96daadd", "atlantis", "Mac Floppy", "Floppy", 12035, Common::EN_ANY, Common::kPlatformMacintosh },
{ "d73c851b942af44deb9b6d5f416a0972", "freddi3", "HE 99", "Demo", 22779, Common::HE_ISR, Common::kPlatformWindows },
{ "d74122362a77ec24525fdd50297dfd82", "freddi4", "", "", -1, Common::FR_FRA, Common::kPlatformMacintosh },
+ { "d77be4d8a3e609092703508e09f1ed5d", "rebel1", "", "", 132904, Common::JA_JPN, Common::kPlatformSegaCD },
{ "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 },
diff --git a/engines/scumm/smush/rebel/codec_ra1.cpp b/engines/scumm/smush/rebel/codec_ra1.cpp
index 1b2ab04b528..d7b87963532 100644
--- a/engines/scumm/smush/rebel/codec_ra1.cpp
+++ b/engines/scumm/smush/rebel/codec_ra1.cpp
@@ -27,6 +27,61 @@
namespace Scumm {
+// Codecs 31/32 use the codec 1/3 line RLE over low-nibble-first packed 4bpp
+// pixels. Codec 31 leaves color zero transparent.
+void smushDecodeRA1SegaCDRLE(byte *dst, const byte *src, int left, int top, int width, int height,
+ int pitch, int bufHeight, int dataSize, bool transparent, byte paletteBase) {
+ if (dst == nullptr || src == nullptr || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
+ return;
+
+ const byte *srcEnd = src + dataSize;
+ for (int row = 0; row < height && srcEnd - src >= 2; row++) {
+ const int lineSize = READ_LE_UINT16(src);
+ const byte *lineData = src + 2;
+ const byte *lineEnd = lineData + MIN<int>(lineSize, srcEnd - lineData);
+ const int dstY = top + row;
+ byte *rowDst = (dstY >= 0 && dstY < bufHeight) ? dst + dstY * pitch : nullptr;
+ int x = 0;
+
+ while (x < width && lineData < lineEnd) {
+ const byte code = *lineData++;
+ const int num = (code >> 1) + 1;
+ const bool isRun = (code & 1) != 0;
+ byte runByte = 0;
+ if (isRun) {
+ if (lineData >= lineEnd)
+ break;
+ runByte = *lineData++;
+ }
+
+ for (int k = 0; k < num && x < width; k++) {
+ byte packed;
+ if (isRun) {
+ packed = runByte;
+ } else {
+ if (lineData >= lineEnd)
+ break;
+ packed = *lineData++;
+ }
+ const byte nibbles[2] = { (byte)(packed & 0x0F), (byte)(packed >> 4) };
+ for (int d = 0; d < 2 && x < width; d++, x++) {
+ const byte nibble = nibbles[d];
+ if (rowDst && !(transparent && nibble == 0)) {
+ const int dstX = left + x;
+ if (dstX >= 0 && dstX < pitch)
+ rowDst[dstX] = paletteBase + nibble;
+ }
+ }
+ }
+ }
+
+ const int rowSize = 2 + lineSize;
+ if (rowSize > srcEnd - src)
+ break;
+ src += rowSize;
+ }
+}
+
// RLE with transparency on pixel 0.
void smushDecodeRA1Transparent(byte *dst, const byte *src, int left, int top, int width, int height,
int pitch, int dataSize, int sourceSkipX) {
@@ -212,7 +267,7 @@ void smushDecodeRA1Scatter(byte *dst, const byte *src, int left, int top, int bu
}
}
-// Codecs 4/5 use 4x4 tile lookup tables.
+// Codecs 4/5/33/34 use 4x4 glyph tables.
static uint8 s_ra1C4Tbl[2][256][16];
static uint16 s_ra1C4Param = 0xFFFF;
@@ -254,6 +309,49 @@ static void ra1Codec4GenTiles(uint16 param1) {
}
}
+// Sega codecs use four 64-glyph families over an 8x8 color-pair grid.
+static void ra1CodecSegaGenTiles(uint16 param1) {
+ uint8 *dst = &s_ra1C4Tbl[0][0][0];
+ for (int family = 0; family < 4; family++) {
+ for (int i = 0; i < 8; i++) {
+ for (int k = 0; k < 8; k++) {
+ const int cy = i + param1;
+ const int cx = k + param1;
+ const int c0 = param1 + ((i + k) >> 1);
+ const int c1 = (c0 + cy) >> 1;
+ const int c2 = (c0 + cx) >> 1;
+
+ switch (family) {
+ case 0:
+ *dst++ = c0; *dst++ = c0; *dst++ = c1; *dst++ = cy;
+ *dst++ = c0; *dst++ = c0; *dst++ = c1; *dst++ = cy;
+ *dst++ = c2; *dst++ = c2; *dst++ = c0; *dst++ = c1;
+ *dst++ = cx; *dst++ = cx; *dst++ = c2; *dst++ = c0;
+ break;
+ case 1:
+ *dst++ = cy; *dst++ = cy; *dst++ = cy; *dst++ = cy;
+ *dst++ = c0; *dst++ = c0; *dst++ = c0; *dst++ = c0;
+ *dst++ = c2; *dst++ = c2; *dst++ = c2; *dst++ = c2;
+ *dst++ = cx; *dst++ = cx; *dst++ = cx; *dst++ = cx;
+ break;
+ case 2:
+ *dst++ = cy; *dst++ = cy; *dst++ = c1; *dst++ = c0;
+ *dst++ = cy; *dst++ = cy; *dst++ = c1; *dst++ = c0;
+ *dst++ = c1; *dst++ = c1; *dst++ = c0; *dst++ = c2;
+ *dst++ = c0; *dst++ = c0; *dst++ = c2; *dst++ = cx;
+ break;
+ case 3:
+ *dst++ = cy; *dst++ = c0; *dst++ = c2; *dst++ = cx;
+ *dst++ = cy; *dst++ = c0; *dst++ = c2; *dst++ = cx;
+ *dst++ = cy; *dst++ = c0; *dst++ = c2; *dst++ = cx;
+ *dst++ = cy; *dst++ = c0; *dst++ = c2; *dst++ = cx;
+ break;
+ }
+ }
+ }
+ }
+}
+
static bool ra1Codec4LoadTiles(const byte *&src, int &remaining, uint16 param2, uint8 clr) {
uint8 *dst = &s_ra1C4Tbl[1][0][0];
int loop = param2 * 8;
@@ -275,9 +373,15 @@ void smushDecodeRA1Block(byte *dst, const byte *src, int left, int top, int widt
const int mx = pitch;
const int my = bufHeight;
- if (s_ra1C4Param != param) {
- ra1Codec4GenTiles(param);
- s_ra1C4Param = param;
+ const bool segaGlyphs = codec == 33 || codec == 34;
+ const bool hasSkipGlyph = codec == 4 || codec == 33;
+ const uint16 tableParam = param | (segaGlyphs ? 0x100 : 0);
+ if (s_ra1C4Param != tableParam) {
+ if (segaGlyphs)
+ ra1CodecSegaGenTiles(param);
+ else
+ ra1Codec4GenTiles(param);
+ s_ra1C4Param = tableParam;
}
int remaining = dataSize;
const byte *data = src;
@@ -309,7 +413,7 @@ void smushDecodeRA1Block(byte *dst, const byte *src, int left, int top, int widt
return;
byte idx = *data++;
remaining--;
- if (bit == 0 && idx == 0x80 && codec != 5)
+ if (bit == 0 && idx == 0x80 && hasSkipGlyph)
continue;
if (y >= my || (y + 4) < 0 || (x + 4) < 0 || x >= mx)
continue;
diff --git a/engines/scumm/smush/rebel/codec_ra1.h b/engines/scumm/smush/rebel/codec_ra1.h
index 4aeb572b18d..02de34a0cff 100644
--- a/engines/scumm/smush/rebel/codec_ra1.h
+++ b/engines/scumm/smush/rebel/codec_ra1.h
@@ -35,6 +35,8 @@ void smushDecodeRA1AdditiveLineUpdate(byte *dst, const byte *src, int left, int
void smushDecodeRA1Scatter(byte *dst, const byte *src, int left, int top, int bufWidth, int bufHeight, int pitch, int dataSize);
void smushDecodeRA1Block(byte *dst, const byte *src, int left, int top, int width, int height,
int pitch, int bufHeight, int dataSize, uint8 param, uint16 parm2, int codec);
+void smushDecodeRA1SegaCDRLE(byte *dst, const byte *src, int left, int top, int width, int height,
+ int pitch, int bufHeight, int dataSize, bool transparent, byte paletteBase);
} // End of namespace Scumm
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index 33b0f2967de..bf01626994e 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -155,9 +155,14 @@ void SmushPlayerRebel1::initGamePlayerFields() {
_ra1FadeFrameHeight = 0;
_ra1FadeFrameValid = false;
_ra1UseFadeFrame = false;
+ _segaCDFont = nullptr;
+ _segaCDFontSize = 0;
}
void SmushPlayerRebel1::destroyGamePlayerFields() {
+ free(_segaCDFont);
+ _segaCDFont = nullptr;
+ _segaCDFontSize = 0;
free(_ra1ObjOverlayData);
_ra1ObjOverlayData = nullptr;
_ra1ObjOverlayDataSize = 0;
@@ -545,7 +550,15 @@ bool SmushPlayerRebel1::handleGameAdjustCoords(int codec, int &left, int &top, i
return false;
// Block codecs consume column-major tiles while clipping destination pixels.
- if (codec == SMUSH_CODEC_RA1_DELTA || codec == SMUSH_CODEC_RA1_BLOCK) {
+ if (codec == SMUSH_CODEC_RA1_DELTA || codec == SMUSH_CODEC_RA1_BLOCK ||
+ codec == SMUSH_CODEC_SEGACD_BLOCK33 || codec == SMUSH_CODEC_SEGACD_BLOCK34) {
+ left += _fobjOffsetX;
+ top += _fobjOffsetY;
+ return false;
+ }
+
+ // Packed Sega RLE clips internally.
+ if (codec == SMUSH_CODEC_SEGACD_RLE || codec == SMUSH_CODEC_SEGACD_OPAQUE) {
left += _fobjOffsetX;
top += _fobjOffsetY;
return false;
@@ -587,6 +600,8 @@ bool SmushPlayerRebel1::handleGameCodecDecode(int codec, const uint8 *src, int l
return true;
case SMUSH_CODEC_RA1_DELTA:
case SMUSH_CODEC_RA1_BLOCK:
+ case SMUSH_CODEC_SEGACD_BLOCK33:
+ case SMUSH_CODEC_SEGACD_BLOCK34:
smushDecodeRA1Block(_dst, src, left, top, width, height, pitch,
(_dst == _specialBuffer) ? _height : _vm->_screenHeight,
dataSize, param, parm2, codec);
@@ -595,6 +610,12 @@ bool SmushPlayerRebel1::handleGameCodecDecode(int codec, const uint8 *src, int l
smushDecodeRA1SkipCopy(_dst, src, left, top, width, height, pitch,
pitch, (_dst == _specialBuffer) ? _height : _vm->_screenHeight, dataSize);
return true;
+ case SMUSH_CODEC_SEGACD_RLE:
+ case SMUSH_CODEC_SEGACD_OPAQUE:
+ smushDecodeRA1SegaCDRLE(_dst, src, left, top, width, height, pitch,
+ (_dst == _specialBuffer) ? _height : _vm->_screenHeight,
+ dataSize, codec == SMUSH_CODEC_SEGACD_RLE, param);
+ return true;
case SMUSH_CODEC_SKIP_RLE: {
const int bufWidth = pitch;
const int bufHeight = (_dst == _specialBuffer) ? _height : _vm->_screenHeight;
@@ -1164,10 +1185,126 @@ SmushFont *SmushPlayerRebel1::ra1GetFont(int font) {
return _sf[font];
}
+// MAI_AP.BIN stores 16x16 1bpp glyphs at 0x5f04, indexed as
+// (highByte - 0x80) * 128 + lowByte - 0x80.
+static const int32 kSegaCDFontOffset = 0x5f04;
+static const int kSegaCDGlyphBytes = 32; // 16x16, 2 bytes per row
+static const int kSegaCDGlyphSize = 16;
+
+void SmushPlayerRebel1::ra1LoadSegaCDFont() {
+ if (_segaCDFont)
+ return;
+ ScummFile *f = _vm->instantiateScummFile();
+ _vm->openFile(*f, "MAI_AP.BIN");
+ if (f->isOpen()) {
+ _segaCDFontSize = (int32)f->size();
+ if (_segaCDFontSize > 0 && _segaCDFontSize <= 1024 * 1024) {
+ _segaCDFont = (byte *)malloc(_segaCDFontSize);
+ if (_segaCDFont && f->read(_segaCDFont, _segaCDFontSize) != (uint32)_segaCDFontSize) {
+ free(_segaCDFont);
+ _segaCDFont = nullptr;
+ _segaCDFontSize = 0;
+ }
+ }
+ }
+ delete f;
+}
+
+void SmushPlayerRebel1::ra1HandleTextSegaCD(int32 subSize, Common::SeekableReadStream &b) {
+ if (subSize < 8 || !_dst || _width <= 0 || _height <= 0)
+ return;
+
+ ra1LoadSegaCDFont();
+ if (!_segaCDFont)
+ return;
+
+ const int anchorX = b.readSint32BE();
+ const int startY = b.readSint32BE();
+ const int32 bodyLen = subSize - 8;
+ if (bodyLen <= 0)
+ return;
+ byte *body = (byte *)malloc(bodyLen);
+ if (!body)
+ return;
+ if (b.read(body, bodyLen) != (uint32)bodyLen) {
+ free(body);
+ return;
+ }
+
+ // Choose contrasting colors from the current palette.
+ int fg = 0, sh = 0, bestLum = -1, worstLum = 0x10000;
+ for (int i = 1; i < 256; i++) {
+ const int lum = _pal[i * 3] + _pal[i * 3 + 1] + _pal[i * 3 + 2];
+ if (lum > bestLum) {
+ bestLum = lum;
+ fg = i;
+ }
+ if (lum < worstLum) {
+ worstLum = lum;
+ sh = i;
+ }
+ }
+
+ // Lines are marked by 20 00 3C and contain two-byte glyph codes.
+ int cursorY = startY;
+ int32 p = 0;
+ while (p < bodyLen) {
+ Common::Array<uint16> glyphs;
+ while (p < bodyLen) {
+ byte c = body[p];
+ if (c >= 0x80 && p + 1 < bodyLen) {
+ glyphs.push_back((uint16)((c << 8) | body[p + 1]));
+ p += 2;
+ } else if (c == 0x3c && !glyphs.empty()) {
+ break; // start of the next line
+ } else {
+ p++; // skip control bytes (0x20, 0x00, '<')
+ }
+ }
+ if (glyphs.empty())
+ continue;
+
+ const int lineWidth = (int)glyphs.size() * kSegaCDGlyphSize;
+ int drawX = anchorX - lineWidth / 2;
+ for (uint g = 0; g < glyphs.size(); g++) {
+ const int hi = (glyphs[g] >> 8) - 0x80;
+ const int lo = (glyphs[g] & 0xFF) - 0x80;
+ const int32 idx = hi * 128 + lo;
+ const int32 off = kSegaCDFontOffset + idx * kSegaCDGlyphBytes;
+ if (idx < 0 || off < 0 || off + kSegaCDGlyphBytes > _segaCDFontSize) {
+ drawX += kSegaCDGlyphSize;
+ continue;
+ }
+ for (int row = 0; row < kSegaCDGlyphSize; row++) {
+ const uint16 bits = (_segaCDFont[off + row * 2] << 8) | _segaCDFont[off + row * 2 + 1];
+ for (int col = 0; col < kSegaCDGlyphSize; col++) {
+ if (!((bits >> (15 - col)) & 1))
+ continue;
+ const int px = drawX + col;
+ const int py = cursorY + row;
+ if (px + 1 >= 0 && px + 1 < _width && py + 1 >= 0 && py + 1 < _height)
+ _dst[(py + 1) * _width + (px + 1)] = (byte)sh;
+ if (px >= 0 && px < _width && py >= 0 && py < _height)
+ _dst[py * _width + px] = (byte)fg;
+ }
+ }
+ drawX += kSegaCDGlyphSize;
+ }
+ cursorY += kSegaCDGlyphSize + 2;
+ }
+
+ free(body);
+}
+
void SmushPlayerRebel1::ra1HandleText(int32 subSize, Common::SeekableReadStream &b) {
if (subSize < 8 || !_dst || _width <= 0 || _height <= 0)
return;
+ if (_vm->_game.platform == Common::kPlatformSegaCD) {
+ ra1HandleTextSegaCD(subSize, b);
+ return;
+ }
+
InsaneRebel1 *rebel1 = static_cast<InsaneRebel1 *>(_insane);
if (!rebel1)
return;
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.h b/engines/scumm/smush/rebel/smush_player_ra1.h
index e34acd792eb..d77a9c989d3 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.h
+++ b/engines/scumm/smush/rebel/smush_player_ra1.h
@@ -65,6 +65,8 @@ private:
void ra1HandleFade(int32 subSize, Common::SeekableReadStream &b);
SmushFont *ra1GetFont(int font);
void ra1HandleText(int32 subSize, Common::SeekableReadStream &b);
+ void ra1HandleTextSegaCD(int32 subSize, Common::SeekableReadStream &b);
+ void ra1LoadSegaCDFont();
void ra1HandleFrameAudioChunk(uint32 subType, int32 subSize, Common::SeekableReadStream &b);
void ra1FeedAudio(uint32 subType, uint8 *srcBuf, int groupId, int volume, int pan, int16 flags);
void ra1HandleGameFrameChunk(int32 subSize, Common::SeekableReadStream &b, bool fastForwarding);
@@ -74,6 +76,9 @@ private:
Common::SeekableReadStream &b, bool fastForwarding);
void ra1InitAudioTrackSizes();
+ byte *_segaCDFont;
+ int32 _segaCDFontSize;
+
// Clean frame buffer used as the next delta source.
byte *_ra1CleanFrame;
int32 _ra1CleanFrameSize;
diff --git a/engines/scumm/smush/smush_player.h b/engines/scumm/smush/smush_player.h
index fe55b5e7ec1..e4ee345163b 100644
--- a/engines/scumm/smush/smush_player.h
+++ b/engines/scumm/smush/smush_player.h
@@ -86,6 +86,11 @@ namespace Scumm {
#define SMUSH_CODEC_SKIP_RLE 23 // RA1: additive line-update overlay; RA2: skip/copy with embedded RLE
#define SMUSH_CODEC_DELTA_BLOCKS 37
#define SMUSH_CODEC_LINE_UPDATE2 44 // RA2: Variant of codec 21
+
+#define SMUSH_CODEC_SEGACD_RLE 31 // RA1 Sega CD: transparent 4bpp line RLE
+#define SMUSH_CODEC_SEGACD_OPAQUE 32 // RA1 Sega CD: opaque 4bpp line RLE
+#define SMUSH_CODEC_SEGACD_BLOCK33 33 // RA1 Sega CD: codec 4 with Sega glyph table
+#define SMUSH_CODEC_SEGACD_BLOCK34 34 // RA1 Sega CD: codec 5 with Sega glyph table and no skip glyph
#define SMUSH_CODEC_RA2_BOMP 45 // RA2: BOMP RLE with variable header
#define SMUSH_CODEC_DELTA_GLYPHS 47
Commit: 583f57338b5ad2f471f6a7f0a22edd5b92114249
https://github.com/scummvm/scummvm/commit/583f57338b5ad2f471f6a7f0a22edd5b92114249
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA1: skip L7 in SegaCD as there are is such mission
Changed paths:
engines/scumm/insane/rebel1/runlevels.cpp
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 29507d3518b..e863db47cd6 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -1436,6 +1436,10 @@ void InsaneRebel1::runGame() {
if (shouldAbortGameFlow())
break;
+ // The Sega CD release omits level 7 but retains its chapter transition.
+ if (_vm->_game.platform == Common::kPlatformSegaCD && level == 7)
+ continue;
+
completed = (this->*kLevelRunners[level - 1])();
if (completed) {
lastCompletedLevel = level;
Commit: 572e7ace90e5157e9540623c63dda7760dd8899a
https://github.com/scummvm/scummvm/commit/572e7ace90e5157e9540623c63dda7760dd8899a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-22T22:27:46+02:00
Commit Message:
SCUMM: RA1: magenta entries inherit the previous Sega palette
Changed paths:
engines/scumm/smush/rebel/smush_player_ra1.cpp
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index bf01626994e..3c6e27f1ee1 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -367,6 +367,18 @@ SmushFont *SmushPlayerRebel1::getGameFont(int font) {
}
void SmushPlayerRebel1::adjustGamePalette() {
+ if (_vm->_game.platform == Common::kPlatformSegaCD) {
+ // Magenta entries inherit the previous Sega palette.
+ for (int i = 0; i < 256; ++i) {
+ byte *color = _pal + i * 3;
+ if (color[0] == 0xff && color[1] == 0 && color[2] == 0xff) {
+ color[0] = _shiftedDeltaPal[i * 3] >> 7;
+ color[1] = _shiftedDeltaPal[i * 3 + 1] >> 7;
+ color[2] = _shiftedDeltaPal[i * 3 + 2] >> 7;
+ }
+ }
+ }
+
for (int i = 0; i < ARRAYSIZE(_pal); ++i)
_shiftedDeltaPal[i] = _pal[i] << 7;
memset(_deltaPal, 0, sizeof(_deltaPal));
More information about the Scummvm-git-logs
mailing list