Opened 5 months ago

Last modified 4 weeks ago

#14737 new feature request

SCUMM/HE: Blue's Birthday - Carrying over Red/Yellow CD savegames (fix included)

Reported by: fusefib Owned by:
Priority: normal Component: Engine: SCUMM
Version: Keywords:
Cc: Game: Blue's Birthday Adventure

Description (last modified by fusefib)

Blue's Clues: Blue's Birthday Adventure is a two-CD game with four pathways, two on each disc. The original behavior is that savefiles are partially common for both CDs. In the original game, the following savefiles are generated:

(for Yellow CD)
Blues1.nam
[PlayerName1].bca
[PlayerName1].sga
[PlayerName1].sgb
...
(for Red CD)
Blues1.nam
[PlayerName1].bca
[PlayerName1].sgc
[PlayerName1].sgd
...

Blues1.nam and [PlayerName1].bca are shared between the CDs and contain the game profile, i.e. sign-in information and overall game progress across e.g. minigames from all four pathways. The first has name/color/birthday in INI-format (when uncompressed); the other is a light, player-specific game progress file. When you create a sign-in profile and proceed, both get generated. [PlayerName1] is whatever gets chosen.

The .sga to .sgd files are pathway-specific savefiles that get generated once you enter a pathway and the game saves progress.

ScummVM generates the same type of savefiles, albeit compressed, though its behavior is to prefix the files with the game entry's target. Targets get incremented if one adds, ID-wise, the same detected game again. With the Yellow CD and Red CD being considered separate games with one default gameID, in a typical scenario we get "BluesBirthday-" and "BluesBirthday-1-" as default savegame prefixes, thus:

BluesBirthday-Blues1.nam
BluesBirthday-[PlayerName1].bca
BluesBirthday-1-Blues1.nam
BluesBirthday-1-[PlayerName1].bca

The result is needlessly getting separately loaded game profiles and progress by dint of not loading the same files, when ScummVM would otherwise manage common savefiles for both game entries just fine.

Here is a proposed fix to easily restore original functionality:

/engines/scumm/he/script_v60he.cpp#L155

    // Prepend the target name
    filePath = _targetName + '-' + filePath;

->

    if (_game.id == GID_BIRTHDAYYELLOW || _game.id == GID_BIRTHDAYRED) {
        // Prepend generic name for shared game profile between targets
        // for Yellow CD and Red CD of Blue's Birthday Adventure
        filePath = "BluesBirthday-" + filePath;
    } else {
        // Prepend the target name
        filePath = _targetName + '-' + filePath;
    }

Per MD5s and detection entries, there is only one known Red version and Yellow version of the full game and demos don't create these files, so probably zero compatibility issues on that account.

The gameID-based prefix remains useful so users can distinguish these savegames among other ScummVM saves as well as partial overlap with the existing default naming.

A minor drawback is the other target's (e.g. "BluesBirthday-1") progress gets reset to zero, unless the user manually swaps the savesets which would still exist. But the inconvenience is minor.

Change History (5)

comment:1 by fusefib, 5 months ago

Description: modified (diff)

comment:2 by fusefib, 5 months ago

Description: modified (diff)

comment:3 by fusefib, 4 weeks ago

Description: modified (diff)

comment:4 by fusefib, 4 weeks ago

Description: modified (diff)

Here are some additional thoughts on this matter:

    if ((_game.id == GID_BIRTHDAYYELLOW || _game.id == GID_BIRTHDAYRED) && _targetName.find("BluesBirthday") != 0) {
        // Prepend generic name for shared game profile between targets
        // for Yellow CD and Red CD of Blue's Birthday Adventure,
        // but only if target name begins with "BluesBirthday" (default).
        filePath = "BluesBirthday-" + filePath;
    } else {
        // Prepend the target name
        filePath = _targetName + '-' + filePath;
    }

This adds a placeholder condition that the target name must start with "BluesBirthday" - this allow the user to manually change target's name (ID: in the GUI) to revert back to the old ways of per-target savegames, if that's ever desired.

However, for a better UX solution and one that allows using old savegames, this condition should probably be changed to a GUI-accessible game configuration key that's disabled by default (e.g. boolean "avoidSharedBirthdaySaves").

Sev suggested a mass-rename, so below is a partial solution. It's pretty seamless and doesn't bother the user with a GUI prompt.

void processBirthdaySaveFiles() {
    // Condition 1: Check game ID and that target name begins with "BluesBirthday"
    if ((_game.id != GID_BIRTHDAYYELLOW && _game.id != GID_BIRTHDAYRED) || _targetName.find("BluesBirthday") != 0) {
        return;
    }

    // Condition 2: Check for existing shared savegame files
    if (!_saveFileMan->listSavefiles("BluesBirthday-Blues1.nam").empty()) {
        if (!_saveFileMan->listSavefiles(_targetName + "-Blues1.nam").empty() && _targetName != "BluesBirthday") {
            debug("Old savegame files for target/ID '%s' detected, but ScummVM is using shared savegame files instead.", _targetName.c_str());
            return;
        } else {
            return;
        }
    }

    // Condition 3: Check if target name matches BluesBirthday or no existing target save files
    if (_targetName == "BluesBirthday" || _saveFileMan->listSavefiles(_targetName + "-Blues1.nam").empty()) {
        return;
    }

    // Rename save files
    Common::StringArray targetFilenames = _saveFileMan->listSavefiles(_targetName + "-Blues1.nam");
    targetFilenames.append(_saveFileMan->listSavefiles(_targetName + "-*.bca"));
    targetFilenames.append(_saveFileMan->listSavefiles(_targetName + "-*.sg?"));

    Common::String oldName, newName;

    for (uint i = 0; i < targetFilenames.size(); ++i) {
        oldName = targetFilenames[i];

        newName = oldName;
        newName.replace(_targetName, "BluesBirthday");

        if (_saveFileMan->renameSavefile(oldName, newName)) {
            debug("Renamed %s to %s.", oldName.c_str(), newName.c_str());
        } else {
            warning("Error %i (%s) occurred while renaming %s to %s", _saveFileMan->getError().getCode(),
                _saveFileMan->getErrorDesc().c_str(), oldName.c_str(), newName.c_str());
            return;
        }
    }
}

Basically:

  • if non-gameID-target savegames exist and shared savegames exist: warn in console, but don't do anything (i.e. run shared savegames);
  • if non-gameID-target savegames exist and shared savegames don't exist: rename target savegames to shared savegames;
  • otherwise, don't do anything.

It's in untested doodled form, and I'm not sure where it's best wired in. It should be done once every time the target starts.

As suggested with the filePath part, the _targetName.find("BluesBirthday") != 0 condition should probably be changed to checking some avoidSharedBirthdaySaves bool thing, and this should become a disabled-by-default option in the GUI. This way, users can choose to utilize their target's existing savegames by checking 'Avoid shared savegames (not recommended)' in the Game Options or something like that.

comment:5 by fusefib, 4 weeks ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.