[Scummvm-git-logs] scummvm branch-3-0 -> 97ac425c31c2b236e8d16be8965aa2c4f4d6ba2d
sluicebox
noreply at scummvm.org
Tue Nov 25 04:12:01 UTC 2025
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
446df47e84 PRIVATE: Clear inventory flag in LoseInventory()
c61291f3ef PRIVATE: New save format with versioning
97ac425c31 PRIVATE: Remove incompatible saves from listings
Commit: 446df47e84e397ef937cde06cfba954322e4428a
https://github.com/scummvm/scummvm/commit/446df47e84e397ef937cde06cfba954322e4428a
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-11-24T20:02:46-08:00
Commit Message:
PRIVATE: Clear inventory flag in LoseInventory()
We now track inventory flags so that LoseInventory() can clear them.
This removes an item from Marlowe's inventory, instead of just removing
it from the casebook.
This alters the save format, breaking all saves.
The next commit alters the save format further, but also adds versioning
so that we can gracefully handle this and make future changes.
Changed paths:
engines/private/funcs.cpp
engines/private/private.cpp
engines/private/private.h
diff --git a/engines/private/funcs.cpp b/engines/private/funcs.cpp
index 6a1178e1d55..20078fa1031 100644
--- a/engines/private/funcs.cpp
+++ b/engines/private/funcs.cpp
@@ -439,20 +439,16 @@ static void fInventory(ArgArray args) {
g_private->playSound(g_private->getTakeLeaveSound(), 1, false, false);
}
} else {
+ Common::String flag;
if (v1.type == NAME) {
- v1.u.sym = g_private->maps.lookupVariable(v1.u.sym->name);
if (strcmp(c.u.str, "\"REMOVE\"") == 0) {
- v1.u.sym->u.val = 0;
- if (g_private->inInventory(bmp))
- g_private->inventory.remove(bmp);
+ g_private->removeInventory(bmp);
} else {
- v1.u.sym->u.val = 1;
- if (!g_private->inInventory(bmp))
- g_private->inventory.push_back(bmp);
+ flag = *(v1.u.sym->name);
+ g_private->addInventory(bmp, flag);
}
} else {
- if (!g_private->inInventory(bmp))
- g_private->inventory.push_back(bmp);
+ g_private->addInventory(bmp, flag);
}
if (v2.type == NAME) {
v2.u.sym = g_private->maps.lookupVariable(v2.u.sym->name);
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 1267e0a87ab..27c041bfce8 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -964,9 +964,7 @@ void PrivateEngine::selectMask(Common::Point mousePos) {
if (m.flag1 != nullptr) { // TODO: check this
// an item was taken
if (_toTake) {
- if (!inInventory(m.inventoryItem))
- inventory.push_back(m.inventoryItem);
- setSymbol(m.flag1, 1);
+ addInventory(m.inventoryItem, *(m.flag1->name));
playSound(getTakeSound(), 1, false, false);
_toTake = false;
_haveTakenItem = true;
@@ -1178,31 +1176,57 @@ void PrivateEngine::addMemory(const Common::String &path) {
}
bool PrivateEngine::inInventory(const Common::String &bmp) const {
- for (NameList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) {
- if (*it == bmp)
+ for (InvList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) {
+ if (it->diaryImage == bmp)
return true;
}
return false;
}
+void PrivateEngine::addInventory(const Common::String &bmp, Common::String &flag) {
+ // set game flag
+ if (!flag.empty()) {
+ Symbol *sym = maps.lookupVariable(&flag);
+ setSymbol(sym, 1);
+ }
+
+ // add to casebook
+ if (!inInventory(bmp)) {
+ InventoryItem i;
+ i.diaryImage = bmp;
+ i.flag = flag;
+ inventory.push_back(i);
+ }
+}
+
+void PrivateEngine::removeInventory(const Common::String &bmp) {
+ for (InvList::iterator it = inventory.begin(); it != inventory.end(); ++it) {
+ if (it->diaryImage == bmp) {
+ // clear game flag
+ if (!it->flag.empty()) {
+ Symbol *sym = maps.lookupVariable(&(it->flag));
+ setSymbol(sym, 0);
+ }
+ // remove from casebook
+ inventory.erase(it);
+ break;
+ }
+ }
+}
+
void PrivateEngine::removeRandomInventory() {
// This logic was extracted from the executable.
// Examples:
// 0-3 items: 0 items removed
// 4-6 items: 1 item removed
// 7-10 items: 2 items removed
- //
- // TODO: Clear the inventory flag for the item.
- // We are currently only removing items from the diary. We need to also
- // remove them from Marlowe's inventory by clearing their item flag.
- // We can do this once item flags are stored and included in save files.
uint numberOfItemsToRemove = (inventory.size() * 30) / 100;
for (uint i = 0; i < numberOfItemsToRemove; i++) {
uint indexToRemove = _rnd->getRandomNumber(inventory.size() - 1);
uint index = 0;
for (InvList::iterator it = inventory.begin(); it != inventory.end(); ++it) {
if (index == indexToRemove) {
- inventory.erase(it);
+ removeInventory(it->diaryImage);
break;
}
index++;
@@ -1555,7 +1579,10 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
inventory.clear();
uint32 size = stream->readUint32LE();
for (uint32 i = 0; i < size; ++i) {
- inventory.push_back(stream->readString());
+ InventoryItem inv;
+ inv.diaryImage = stream->readString();
+ inv.flag = stream->readString();
+ inventory.push_back(inv);
}
_haveTakenItem = (inventory.size() > 1); // TODO: include this in save format
@@ -1671,8 +1698,10 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
}
stream->writeUint32LE(inventory.size());
- for (NameList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) {
- stream->writeString(*it);
+ for (InvList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) {
+ stream->writeString(it->diaryImage);
+ stream->writeByte(0);
+ stream->writeString(it->flag);
stream->writeByte(0);
}
@@ -2459,8 +2488,8 @@ void PrivateEngine::loadLocations(const Common::Rect &rect) {
void PrivateEngine::loadInventory(uint32 x, const Common::Rect &r1, const Common::Rect &r2) {
int16 offset = 0;
- for (NameList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) {
- Graphics::Surface *surface = loadMask(*it, r1.left, r1.top + offset, true);
+ for (InvList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) {
+ Graphics::Surface *surface = loadMask(it->diaryImage, r1.left, r1.top + offset, true);
surface->free();
delete surface;
offset += 20;
diff --git a/engines/private/private.h b/engines/private/private.h
index cbe9678d03f..deb6908d080 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -147,6 +147,11 @@ typedef struct DiaryPage {
int locationID;
} DiaryPage;
+typedef struct InventoryItem {
+ Common::String diaryImage;
+ Common::String flag;
+} InventoryItem;
+
// funcs
typedef struct FuncTable {
@@ -163,7 +168,7 @@ typedef Common::List<ExitInfo> ExitList;
typedef Common::List<MaskInfo> MaskList;
typedef Common::List<Common::String> SoundList;
typedef Common::List<PhoneInfo> PhoneList;
-typedef Common::List<Common::String> InvList;
+typedef Common::List<InventoryItem> InvList;
typedef Common::List<Common::Rect *> RectList;
// arrays
@@ -352,6 +357,8 @@ public:
// Diary
InvList inventory;
bool inInventory(const Common::String &bmp) const;
+ void addInventory(const Common::String &bmp, Common::String &flag);
+ void removeInventory(const Common::String &bmp);
void removeRandomInventory();
Common::String _diaryLocPrefix;
void loadLocations(const Common::Rect &);
Commit: c61291f3efd202b46be3ea9893f830a480db3162
https://github.com/scummvm/scummvm/commit/c61291f3efd202b46be3ea9893f830a480db3162
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-11-24T20:02:56-08:00
Commit Message:
PRIVATE: New save format with versioning
Changed paths:
A engines/private/savegame.cpp
A engines/private/savegame.h
engines/private/module.mk
engines/private/private.cpp
diff --git a/engines/private/module.mk b/engines/private/module.mk
index b1f187dbde7..44de2aabf26 100644
--- a/engines/private/module.mk
+++ b/engines/private/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
lexer.o \
metaengine.o \
private.o \
+ savegame.o \
symbol.o
MODULE_DIRS += \
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 27c041bfce8..327c24bb878 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -43,6 +43,7 @@
#include "private/decompiler.h"
#include "private/grammar.h"
#include "private/private.h"
+#include "private/savegame.h"
#include "private/tokens.h"
namespace Private {
@@ -1558,8 +1559,23 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
stopSound(true);
destroyVideo();
- Common::Serializer s(stream, nullptr);
debugC(1, kPrivateDebugFunction, "loadGameStream");
+
+ // Read and validate metadata header
+ SavegameMetadata meta;
+ if (!readSavegameMetadata(stream, meta)) {
+ return Common::kReadingFailed;
+ }
+
+ // Log unexpected language or platform
+ if (meta.language != _language) {
+ warning("Save language %d different than game %d", meta.language, _language);
+ }
+ if (meta.platform != _platform) {
+ warning("Save platform %d different than game %d", meta.platform, _platform);
+ }
+
+ Common::Serializer s(stream, nullptr);
int val;
for (NameList::iterator it = maps.variableList.begin(); it != maps.variableList.end(); ++it) {
@@ -1584,7 +1600,8 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
inv.flag = stream->readString();
inventory.push_back(inv);
}
- _haveTakenItem = (inventory.size() > 1); // TODO: include this in save format
+ _toTake = (stream->readByte() == 1);
+ _haveTakenItem = (stream->readByte() == 1);
// Diary pages
_diaryPages.clear();
@@ -1614,6 +1631,15 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
addDossier(page1, page2);
}
+ // Police Bust
+ _policeBustEnabled = (stream->readByte() == 1);
+ _policeSirenPlayed = (stream->readByte() == 1);
+ _numberOfClicks = stream->readSint32LE();
+ _numberClicksAfterSiren = stream->readSint32LE();
+ _policeBustMovieIndex = stream->readSint32LE();
+ _policeBustMovie = stream->readString();
+ _policeBustPreviousSetting = stream->readString();
+
// Radios
size = stream->readUint32LE();
_AMRadio.clear();
@@ -1685,6 +1711,13 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
if (isAutosave)
return Common::kNoError;
+ // Metadata
+ SavegameMetadata meta;
+ meta.version = kCurrentSavegameVersion;
+ meta.language = _language;
+ meta.platform = _platform;
+ writeSavegameMetadata(stream, meta);
+
// Variables
for (NameList::const_iterator it = maps.variableList.begin(); it != maps.variableList.end(); ++it) {
const Private::Symbol *sym = maps.variables.getVal(*it);
@@ -1704,6 +1737,8 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
stream->writeString(it->flag);
stream->writeByte(0);
}
+ stream->writeByte(_toTake ? 1 : 0);
+ stream->writeByte(_haveTakenItem ? 1 : 0);
stream->writeUint32LE(_diaryPages.size());
for (uint i = 0; i < _diaryPages.size(); i++) {
@@ -1732,6 +1767,17 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
stream->writeByte(0);
}
+ // Police Bust
+ stream->writeByte(_policeBustEnabled ? 1 : 0);
+ stream->writeByte(_policeSirenPlayed ? 1 : 0);
+ stream->writeSint32LE(_numberOfClicks);
+ stream->writeSint32LE(_numberClicksAfterSiren);
+ stream->writeSint32LE(_policeBustMovieIndex);
+ stream->writeString(_policeBustMovie);
+ stream->writeByte(0);
+ stream->writeString(_policeBustPreviousSetting);
+ stream->writeByte(0);
+
// Radios
stream->writeUint32LE(_AMRadio.size());
for (SoundList::const_iterator it = _AMRadio.begin(); it != _AMRadio.end(); ++it) {
@@ -2457,13 +2503,7 @@ void PrivateEngine::loadLocations(const Common::Rect &rect) {
locationID++;
}
Common::sort(visitedLocations.begin(), visitedLocations.end(), [&locationIDs](const Symbol *a, const Symbol *b) {
- if (a->u.val != b->u.val) {
- return a->u.val < b->u.val;
- } else {
- // backwards compatibility for older saves files that stored 1
- // for visited locations and displayed them in a fixed order.
- return locationIDs[a] < locationIDs[b];
- }
+ return a->u.val < b->u.val;
});
// Load the sorted visited locations
diff --git a/engines/private/savegame.cpp b/engines/private/savegame.cpp
new file mode 100644
index 00000000000..a73366b421a
--- /dev/null
+++ b/engines/private/savegame.cpp
@@ -0,0 +1,67 @@
+/* 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/debug.h"
+#include "common/stream.h"
+
+#include "engines/private/savegame.h"
+
+namespace Private {
+
+static const uint32 kSavegameHeader = MKTAG('P','E','Y','E');
+
+void writeSavegameMetadata(Common::WriteStream *stream, const SavegameMetadata &meta) {
+ stream->writeUint32BE(kSavegameHeader);
+ stream->writeUint16LE(meta.version);
+ stream->writeSByte(meta.language);
+ stream->writeSByte(meta.platform);
+}
+
+bool readSavegameMetadata(Common::SeekableReadStream *stream, SavegameMetadata &meta) {
+ byte buffer[8];
+ stream->read(buffer, 8);
+ if (stream->eos() || stream->err()) {
+ return false;
+ }
+
+ uint32 header = READ_BE_UINT32(buffer);
+ if (header != kSavegameHeader) {
+ debugN(1, "Save does not have metadata header");
+ return false;
+ }
+
+ meta.version = READ_LE_UINT16(buffer + 4);
+ if (meta.version < kMinimumSavegameVersion) {
+ debugN("Save version %d lower than minimum %d", meta.version, kMinimumSavegameVersion);
+ return false;
+ }
+ if (meta.version > kCurrentSavegameVersion) {
+ debugN("Save version %d newer than current %d", meta.version, kCurrentSavegameVersion);
+ return false;
+ }
+
+ meta.language = (Common::Language)buffer[6];
+ meta.platform = (Common::Platform)buffer[7];
+
+ return true;
+}
+
+} // End of namespace Private
diff --git a/engines/private/savegame.h b/engines/private/savegame.h
new file mode 100644
index 00000000000..588bd103f48
--- /dev/null
+++ b/engines/private/savegame.h
@@ -0,0 +1,64 @@
+/* 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 PRIVATE_SAVEGAME_H
+#define PRIVATE_SAVEGAME_H
+
+#include "common/language.h"
+#include "common/platform.h"
+
+namespace Common {
+class SeekableReadStream;
+class WriteStream;
+}
+
+namespace Private {
+
+// Savegame format history:
+//
+// Version - new/changed feature
+// =============================
+// 1 - Metadata header and more game state (November 2025)
+//
+// Earlier versions did not have a header and not supported.
+
+const uint16 kCurrentSavegameVersion = 1;
+const uint16 kMinimumSavegameVersion = 1;
+
+struct SavegameMetadata {
+ uint16 version;
+ Common::Language language;
+ Common::Platform platform;
+};
+
+/**
+ * Write the header to a savegame.
+ */
+void writeSavegameMetadata(Common::WriteStream *stream, const SavegameMetadata &meta);
+
+/**
+ * Read the header from a savegame.
+ */
+bool readSavegameMetadata(Common::SeekableReadStream *stream, SavegameMetadata &meta);
+
+} // End of namespace Private
+
+#endif
Commit: 97ac425c31c2b236e8d16be8965aa2c4f4d6ba2d
https://github.com/scummvm/scummvm/commit/97ac425c31c2b236e8d16be8965aa2c4f4d6ba2d
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-11-24T20:10:41-08:00
Commit Message:
PRIVATE: Remove incompatible saves from listings
Changed paths:
engines/private/metaengine.cpp
diff --git a/engines/private/metaengine.cpp b/engines/private/metaengine.cpp
index c45f54a2d95..bcf2aa24882 100644
--- a/engines/private/metaengine.cpp
+++ b/engines/private/metaengine.cpp
@@ -21,10 +21,12 @@
#include "engines/advancedDetector.h"
#include "graphics/scaler.h"
+#include "common/savefile.h"
#include "common/translation.h"
#include "private/private.h"
#include "private/detection.h"
+#include "private/savegame.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
@@ -68,6 +70,7 @@ public:
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
void getSavegameThumbnail(Graphics::Surface &thumb) override;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
};
@@ -88,6 +91,35 @@ void PrivateMetaEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
}
}
+/**
+ * querySaveMetaInfos override that filters out saves with incompatible formats.
+ *
+ * The Private Eye save format was significantly changed to add more engine state.
+ * Older saves are incompatible, and we might have to change the format again.
+ * Save files now contain a version number in their header so that we can detect
+ * that a save is compatible, and not present incompatible saves to users.
+ */
+SaveStateDescriptor PrivateMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ using namespace Private;
+
+ SaveStateDescriptor desc = MetaEngine::querySaveMetaInfos(target, slot);
+ if (desc.getSaveSlot() == -1) {
+ return desc;
+ }
+
+ // Only saves with compatible metadata headers are allowed.
+ Common::ScopedPtr<Common::InSaveFile> f(g_system->getSavefileManager()->openForLoading(
+ getSavegameFile(slot, target)));
+ if (f) {
+ SavegameMetadata meta;
+ if (!readSavegameMetadata(f.get(), meta)) {
+ return SaveStateDescriptor();
+ }
+ }
+
+ return desc;
+}
+
Common::KeymapArray PrivateMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace Private;
More information about the Scummvm-git-logs
mailing list