mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-18 07:53:12 +00:00
698ca464d7
Fixes bugs in trac #11029: - Slot 1 loading when user selects slot 0 in ScummVM UI - Slot 1 loading when slot 0 specified on command line - QFG4 slot 0 not appearing in game's original Restore UI
582 lines
18 KiB
C++
582 lines
18 KiB
C++
/* 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "common/savefile.h"
|
|
#include "common/stream.h"
|
|
#include "common/memstream.h"
|
|
|
|
#include "sci/sci.h"
|
|
#include "sci/engine/file.h"
|
|
#include "sci/engine/kernel.h"
|
|
#include "sci/engine/savegame.h"
|
|
#include "sci/engine/selector.h"
|
|
#include "sci/engine/state.h"
|
|
|
|
namespace Sci {
|
|
|
|
#ifdef ENABLE_SCI32
|
|
uint32 MemoryDynamicRWStream::read(void *dataPtr, uint32 dataSize) {
|
|
// Read at most as many bytes as are still available...
|
|
if (dataSize > _size - _pos) {
|
|
dataSize = _size - _pos;
|
|
_eos = true;
|
|
}
|
|
memcpy(dataPtr, _ptr, dataSize);
|
|
|
|
_ptr += dataSize;
|
|
_pos += dataSize;
|
|
|
|
return dataSize;
|
|
}
|
|
|
|
SaveFileRewriteStream::SaveFileRewriteStream(const Common::String &fileName,
|
|
Common::SeekableReadStream *inFile,
|
|
kFileOpenMode mode,
|
|
bool compress) :
|
|
MemoryDynamicRWStream(DisposeAfterUse::YES),
|
|
_fileName(fileName),
|
|
_compress(compress) {
|
|
const bool truncate = (mode == kFileOpenModeCreate);
|
|
const bool seekToEnd = (mode == kFileOpenModeOpenOrCreate);
|
|
|
|
if (!truncate && inFile) {
|
|
const uint s = inFile->size();
|
|
ensureCapacity(s);
|
|
inFile->read(_data, s);
|
|
if (seekToEnd) {
|
|
seek(0, SEEK_END);
|
|
}
|
|
_changed = false;
|
|
} else {
|
|
_changed = true;
|
|
}
|
|
}
|
|
|
|
SaveFileRewriteStream::~SaveFileRewriteStream() {
|
|
commit();
|
|
}
|
|
|
|
void SaveFileRewriteStream::commit() {
|
|
if (!_changed) {
|
|
return;
|
|
}
|
|
|
|
Common::ScopedPtr<Common::WriteStream> outFile(g_sci->getSaveFileManager()->openForSaving(_fileName, _compress));
|
|
outFile->write(_data, _size);
|
|
_changed = false;
|
|
}
|
|
#endif
|
|
|
|
uint findFreeFileHandle(EngineState *s) {
|
|
// Find a free file handle
|
|
uint handle = 1; // Ignore _fileHandles[0]
|
|
while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
|
|
handle++;
|
|
|
|
if (handle == s->_fileHandles.size()) {
|
|
// Hit size limit => Allocate more space
|
|
s->_fileHandles.resize(s->_fileHandles.size() + 1);
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
/*
|
|
* Note on how file I/O is implemented: In ScummVM, one can not create/write
|
|
* arbitrary data files, simply because many of our target platforms do not
|
|
* support this. The only files one can create are savestates. But SCI has an
|
|
* opcode to create and write to seemingly 'arbitrary' files. This is mainly
|
|
* used in LSL3 for LARRY3.DRV (which is a game data file, not a driver, used
|
|
* for persisting the results of the "age quiz" across restarts) and in LSL5
|
|
* for MEMORY.DRV (which is again a game data file and contains the game's
|
|
* password, XOR encrypted).
|
|
* To implement that opcode, we combine the SaveFileManager with regular file
|
|
* code, similarly to how the SCUMM HE engine does it.
|
|
*
|
|
* To handle opening a file called "foobar", what we do is this: First, we
|
|
* create an 'augmented file name', by prepending the game target and a dash,
|
|
* so if we running game target sq1sci, the name becomes "sq1sci-foobar".
|
|
* Next, we check if such a file is known to the SaveFileManager. If so, we
|
|
* we use that for reading/writing, delete it, whatever.
|
|
*
|
|
* If no such file is present but we were only asked to *read* the file,
|
|
* we fallback to looking for a regular file called "foobar", and open that
|
|
* for reading only.
|
|
*/
|
|
|
|
reg_t file_open(EngineState *s, const Common::String &filename, kFileOpenMode mode, bool unwrapFilename) {
|
|
Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH);
|
|
englishName.toLowercase();
|
|
|
|
Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName;
|
|
Common::SeekableReadStream *inFile = 0;
|
|
Common::WriteStream *outFile = 0;
|
|
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
|
|
|
bool isCompressed = true;
|
|
const SciGameId gameId = g_sci->getGameId();
|
|
|
|
// QFG Characters are saved via the CharSave object.
|
|
// We leave them uncompressed so that they can be imported in later QFG
|
|
// games, even when using the original interpreter.
|
|
// We check for room numbers in here, because the file suffix can be changed by the user.
|
|
// Rooms/Scripts: QFG1(EGA/VGA): 601, QFG2: 840, QFG3/4: 52
|
|
switch (gameId) {
|
|
case GID_QFG1:
|
|
case GID_QFG1VGA:
|
|
if (s->currentRoomNumber() == 601)
|
|
isCompressed = false;
|
|
break;
|
|
case GID_QFG2:
|
|
if (s->currentRoomNumber() == 840)
|
|
isCompressed = false;
|
|
break;
|
|
case GID_QFG3:
|
|
case GID_QFG4:
|
|
if (s->currentRoomNumber() == 52)
|
|
isCompressed = false;
|
|
break;
|
|
#ifdef ENABLE_SCI32
|
|
// Hoyle5 has no save games, but creates very simple text-based game options
|
|
// files that do not need to be compressed
|
|
case GID_HOYLE5:
|
|
// Phantasmagoria game scripts create their own save files, so they are
|
|
// interoperable with the original interpreter just by renaming them as long
|
|
// as they are not compressed. They are also never larger than a couple
|
|
// hundred bytes, so compression does not do much here anyway
|
|
case GID_PHANTASMAGORIA:
|
|
isCompressed = false;
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#ifdef ENABLE_SCI32
|
|
bool isRewritableFile;
|
|
switch (g_sci->getGameId()) {
|
|
case GID_PHANTASMAGORIA:
|
|
isRewritableFile = (filename == "phantsg.dir" || filename == "chase.dat" || filename == "tmp.dat");
|
|
break;
|
|
case GID_PQSWAT:
|
|
isRewritableFile = (filename == "swat.dat");
|
|
break;
|
|
default:
|
|
isRewritableFile = false;
|
|
}
|
|
|
|
if (isRewritableFile) {
|
|
debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str());
|
|
|
|
inFile = saveFileMan->openForLoading(wrappedName);
|
|
// If no matching savestate exists: fall back to reading from a regular
|
|
// file
|
|
if (!inFile)
|
|
inFile = SearchMan.createReadStreamForMember(englishName);
|
|
|
|
if (mode == kFileOpenModeOpenOrFail && !inFile) {
|
|
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeOpenOrFail): failed to open file '%s'", englishName.c_str());
|
|
return SIGNAL_REG;
|
|
}
|
|
|
|
SaveFileRewriteStream *stream;
|
|
stream = new SaveFileRewriteStream(wrappedName, inFile, mode, isCompressed);
|
|
|
|
delete inFile;
|
|
|
|
inFile = stream;
|
|
outFile = stream;
|
|
} else
|
|
#endif
|
|
if (mode == kFileOpenModeOpenOrFail) {
|
|
// Try to open file, abort if not possible
|
|
inFile = saveFileMan->openForLoading(wrappedName);
|
|
// If no matching savestate exists: fall back to reading from a regular
|
|
// file
|
|
if (!inFile)
|
|
inFile = SearchMan.createReadStreamForMember(englishName);
|
|
|
|
if (!inFile)
|
|
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeOpenOrFail): failed to open file '%s'", englishName.c_str());
|
|
} else if (mode == kFileOpenModeCreate) {
|
|
// Create the file, destroying any content it might have had
|
|
outFile = saveFileMan->openForSaving(wrappedName, isCompressed);
|
|
if (!outFile)
|
|
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeCreate): failed to create file '%s'", englishName.c_str());
|
|
} else if (mode == kFileOpenModeOpenOrCreate) {
|
|
// Try to open file, create it if it doesn't exist
|
|
outFile = saveFileMan->openForSaving(wrappedName, isCompressed);
|
|
if (!outFile)
|
|
debugC(kDebugLevelFile, " -> file_open(kFileOpenModeCreate): failed to create file '%s'", englishName.c_str());
|
|
|
|
// QfG1 opens the character export file with kFileOpenModeCreate first,
|
|
// closes it immediately and opens it again with this here. Perhaps
|
|
// other games use this for read access as well. I guess changing this
|
|
// whole code into using virtual files and writing them after close
|
|
// would be more appropriate.
|
|
} else {
|
|
error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str());
|
|
}
|
|
|
|
if (!inFile && !outFile) { // Failed
|
|
debugC(kDebugLevelFile, " -> file_open() failed");
|
|
return SIGNAL_REG;
|
|
}
|
|
|
|
uint handle = findFreeFileHandle(s);
|
|
|
|
s->_fileHandles[handle]._in = inFile;
|
|
s->_fileHandles[handle]._out = outFile;
|
|
s->_fileHandles[handle]._name = englishName;
|
|
|
|
debugC(kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle);
|
|
return make_reg(0, handle);
|
|
}
|
|
|
|
FileHandle *getFileFromHandle(EngineState *s, uint handle) {
|
|
if ((handle == 0) || ((handle >= kVirtualFileHandleStart) && (handle <= kVirtualFileHandleEnd))) {
|
|
error("Attempt to use invalid file handle (%d)", handle);
|
|
return 0;
|
|
}
|
|
|
|
if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) {
|
|
warning("Attempt to use invalid/unused file handle %d", handle);
|
|
return 0;
|
|
}
|
|
|
|
return &s->_fileHandles[handle];
|
|
}
|
|
|
|
int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
|
|
FileHandle *f = getFileFromHandle(s, handle);
|
|
if (!f)
|
|
return 0;
|
|
|
|
if (!f->_in) {
|
|
error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
|
|
return 0;
|
|
}
|
|
int readBytes = 0;
|
|
if (maxsize > 1) {
|
|
memset(dest, 0, maxsize);
|
|
f->_in->readLine(dest, maxsize);
|
|
readBytes = Common::strnlen(dest, maxsize); // FIXME: sierra sci returned byte count and didn't react on NUL characters
|
|
// The returned string must not have an ending LF
|
|
if (readBytes > 0) {
|
|
if (dest[readBytes - 1] == 0x0A)
|
|
dest[readBytes - 1] = 0;
|
|
}
|
|
} else {
|
|
*dest = 0;
|
|
}
|
|
|
|
debugC(kDebugLevelFile, " -> FGets'ed \"%s\"", dest);
|
|
return readBytes;
|
|
}
|
|
|
|
static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) {
|
|
if (l.date != r.date)
|
|
return (l.date > r.date);
|
|
return (l.time > r.time);
|
|
}
|
|
|
|
bool fillSavegameDesc(const Common::String &filename, SavegameDesc &desc) {
|
|
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
|
Common::ScopedPtr<Common::SeekableReadStream> in(saveFileMan->openForLoading(filename));
|
|
if (!in) {
|
|
return false;
|
|
}
|
|
|
|
SavegameMetadata meta;
|
|
if (!get_savegame_metadata(in.get(), meta) || meta.name.empty()) {
|
|
return false;
|
|
}
|
|
|
|
const int id = strtol(filename.end() - 3, NULL, 10);
|
|
desc.id = id;
|
|
// We need to fix date in here, because we save DDMMYYYY instead of
|
|
// YYYYMMDD, so sorting wouldn't work
|
|
desc.date = ((meta.saveDate & 0xFFFF) << 16) | ((meta.saveDate & 0xFF0000) >> 8) | ((meta.saveDate & 0xFF000000) >> 24);
|
|
desc.time = meta.saveTime;
|
|
desc.version = meta.version;
|
|
desc.gameVersion = meta.gameVersion;
|
|
desc.script0Size = meta.script0Size;
|
|
desc.gameObjectOffset = meta.gameObjectOffset;
|
|
#ifdef ENABLE_SCI32
|
|
if (g_sci->getGameId() == GID_SHIVERS) {
|
|
desc.lowScore = meta.lowScore;
|
|
desc.highScore = meta.highScore;
|
|
} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
|
|
desc.avatarId = meta.avatarId;
|
|
}
|
|
#endif
|
|
|
|
if (meta.name.lastChar() == '\n')
|
|
meta.name.deleteLastChar();
|
|
|
|
// At least Phant2 requires use of strncpy, since it creates save game
|
|
// names of exactly kMaxSaveNameLength
|
|
strncpy(desc.name, meta.name.c_str(), kMaxSaveNameLength);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Create an array containing all found savedgames, sorted by creation date
|
|
void listSavegames(Common::Array<SavegameDesc> &saves) {
|
|
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
|
Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
|
|
|
|
for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
|
|
const Common::String &filename = *iter;
|
|
|
|
#ifdef ENABLE_SCI32
|
|
// exclude new game and autosave slots, except for QFG4,
|
|
// whose autosave should appear as a normal saved game
|
|
if (g_sci->getGameId() != GID_QFG4) {
|
|
const int id = strtol(filename.end() - 3, NULL, 10);
|
|
if (id == kNewGameId || id == kAutoSaveId) {
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SavegameDesc desc;
|
|
if (fillSavegameDesc(filename, desc)) {
|
|
saves.push_back(desc);
|
|
}
|
|
}
|
|
|
|
// Sort the list by creation date of the saves
|
|
Common::sort(saves.begin(), saves.end(), _savegame_sort_byDate);
|
|
}
|
|
|
|
// Find a savedgame according to virtualId and return the position within our array
|
|
int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) {
|
|
for (uint saveNr = 0; saveNr < saves.size(); saveNr++) {
|
|
if (saves[saveNr].id == savegameId)
|
|
return saveNr;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#ifdef ENABLE_SCI32
|
|
Common::MemoryReadStream *makeCatalogue(const uint maxNumSaves, const uint gameNameSize, const Common::String &fileNamePattern, const bool ramaFormat) {
|
|
enum {
|
|
kGameIdSize = sizeof(int16),
|
|
kNumSavesSize = sizeof(int16),
|
|
kFreeSlotSize = sizeof(int16),
|
|
kTerminatorSize = kGameIdSize,
|
|
kTerminator = 0xFFFF
|
|
};
|
|
|
|
Common::Array<SavegameDesc> games;
|
|
listSavegames(games);
|
|
|
|
const uint numSaves = MIN(games.size(), maxNumSaves);
|
|
const uint fileNameSize = fileNamePattern.empty() ? 0 : 12;
|
|
const uint entrySize = kGameIdSize + fileNameSize + gameNameSize;
|
|
uint dataSize = numSaves * entrySize + kTerminatorSize;
|
|
if (ramaFormat) {
|
|
dataSize += kNumSavesSize + kFreeSlotSize * maxNumSaves;
|
|
}
|
|
|
|
byte *out = (byte *)malloc(dataSize);
|
|
const byte *const data = out;
|
|
|
|
Common::Array<bool> usedSlots;
|
|
if (ramaFormat) {
|
|
WRITE_LE_UINT16(out, numSaves);
|
|
out += kNumSavesSize;
|
|
usedSlots.resize(maxNumSaves);
|
|
}
|
|
|
|
for (uint i = 0; i < numSaves; ++i) {
|
|
const SavegameDesc &save = games[i];
|
|
const uint16 id = save.id - kSaveIdShift;
|
|
if (!ramaFormat) {
|
|
WRITE_LE_UINT16(out, id);
|
|
out += kGameIdSize;
|
|
}
|
|
if (fileNameSize) {
|
|
const Common::String fileName = Common::String::format(fileNamePattern.c_str(), id);
|
|
strncpy(reinterpret_cast<char *>(out), fileName.c_str(), fileNameSize);
|
|
out += fileNameSize;
|
|
}
|
|
// Game names can be up to exactly gameNameSize
|
|
strncpy(reinterpret_cast<char *>(out), save.name, gameNameSize);
|
|
out += gameNameSize;
|
|
if (ramaFormat) {
|
|
WRITE_LE_UINT16(out, id);
|
|
out += kGameIdSize;
|
|
|
|
assert(id < maxNumSaves);
|
|
usedSlots[id] = true;
|
|
}
|
|
}
|
|
|
|
if (ramaFormat) {
|
|
// A table indicating which save game slots are occupied
|
|
for (uint i = 0; i < usedSlots.size(); ++i) {
|
|
WRITE_LE_UINT16(out, !usedSlots[i]);
|
|
out += kFreeSlotSize;
|
|
}
|
|
}
|
|
|
|
WRITE_LE_UINT16(out, kTerminator);
|
|
|
|
return new Common::MemoryReadStream(data, dataSize, DisposeAfterUse::YES);
|
|
}
|
|
#endif
|
|
|
|
FileHandle::FileHandle() : _in(0), _out(0) {
|
|
}
|
|
|
|
FileHandle::~FileHandle() {
|
|
close();
|
|
}
|
|
|
|
void FileHandle::close() {
|
|
// NB: It is possible _in and _out are both non-null, but
|
|
// then they point to the same object.
|
|
if (_in)
|
|
delete _in;
|
|
else
|
|
delete _out;
|
|
_in = 0;
|
|
_out = 0;
|
|
_name.clear();
|
|
}
|
|
|
|
bool FileHandle::isOpen() const {
|
|
return _in || _out;
|
|
}
|
|
|
|
|
|
void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) {
|
|
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
|
Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask);
|
|
if (!foundFiles.empty()) {
|
|
// Sort all filenames alphabetically
|
|
Common::sort(foundFiles.begin(), foundFiles.end());
|
|
|
|
Common::StringArray::iterator it;
|
|
Common::StringArray::iterator it_end = foundFiles.end();
|
|
bool titleAdded = false;
|
|
|
|
for (it = foundFiles.begin(); it != it_end; it++) {
|
|
Common::String regularFilename = *it;
|
|
Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1);
|
|
|
|
Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename);
|
|
int32 testfileSize = testfile->size();
|
|
delete testfile;
|
|
if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game.
|
|
continue; // and we dont want to have those in the list
|
|
|
|
if (!titleAdded) {
|
|
_files.push_back(title);
|
|
_virtualFiles.push_back("");
|
|
titleAdded = true;
|
|
}
|
|
|
|
// We need to remove the prefix for display purposes
|
|
_files.push_back(wrappedFilename);
|
|
// but remember the actual name as well
|
|
_virtualFiles.push_back(regularFilename);
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::String DirSeeker::getVirtualFilename(uint fileNumber) {
|
|
if (fileNumber >= _virtualFiles.size())
|
|
error("invalid virtual filename access");
|
|
return _virtualFiles[fileNumber];
|
|
}
|
|
|
|
reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) {
|
|
// Verify that we are given a valid buffer
|
|
if (!buffer.getSegment()) {
|
|
error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str());
|
|
return NULL_REG;
|
|
}
|
|
_outbuffer = buffer;
|
|
_files.clear();
|
|
_virtualFiles.clear();
|
|
|
|
int QfGImport = g_sci->inQfGImportRoom();
|
|
if (QfGImport) {
|
|
_files.clear();
|
|
addAsVirtualFiles("-QfG1-", "qfg1-*");
|
|
addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*");
|
|
if (QfGImport > 2)
|
|
addAsVirtualFiles("-QfG2-", "qfg2-*");
|
|
if (QfGImport > 3)
|
|
addAsVirtualFiles("-QfG3-", "qfg3-*");
|
|
|
|
if (QfGImport == 3) {
|
|
// QfG3 sorts the filelisting itself, we can't let that happen otherwise our
|
|
// virtual list would go out-of-sync
|
|
reg_t savedHeros = segMan->findObjectByName("savedHeros");
|
|
if (!savedHeros.isNull())
|
|
writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0);
|
|
}
|
|
|
|
} else {
|
|
// Prefix the mask
|
|
const Common::String wrappedMask = g_sci->wrapFilename(mask);
|
|
|
|
// Obtain a list of all files matching the given mask
|
|
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
|
_files = saveFileMan->listSavefiles(wrappedMask);
|
|
}
|
|
|
|
// Reset the list iterator and write the first match to the output buffer,
|
|
// if any.
|
|
_iter = _files.begin();
|
|
return nextFile(segMan);
|
|
}
|
|
|
|
reg_t DirSeeker::nextFile(SegManager *segMan) {
|
|
if (_iter == _files.end()) {
|
|
return NULL_REG;
|
|
}
|
|
|
|
Common::String string;
|
|
|
|
if (_virtualFiles.empty()) {
|
|
// Strip the prefix, if we don't got a virtual filelisting
|
|
const Common::String wrappedString = *_iter;
|
|
string = g_sci->unwrapFilename(wrappedString);
|
|
} else {
|
|
string = *_iter;
|
|
}
|
|
if (string.size() > 12)
|
|
string = Common::String(string.c_str(), 12);
|
|
segMan->strcpy(_outbuffer, string.c_str());
|
|
|
|
// Return the result and advance the list iterator :)
|
|
++_iter;
|
|
return _outbuffer;
|
|
}
|
|
|
|
} // End of namespace Sci
|