Files
scummvm/engines/sci/engine/seg_manager.cpp
T
Colin Snover 741ac22e17 SCI: Fix pointer invalidation caused by array storage moves
When objects are added to a SegmentObjTable, it may cause the
internal storage for the table to expand and move to a new
region of memory. When this happens, all pointers to objects held
by a SegmentObjTable of the same type would be invalidated, due
to an implementation detail that should not be exposed. To prevent
this, objects are now allocated separately on the heap, so even if
the table's storage moves due to insertions, the objects owned by
the table will not, so references remain valid for the lifetime of
the object.
2016-08-13 15:41:31 -05:00

1137 lines
30 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 "sci/sci.h"
#include "sci/engine/seg_manager.h"
#include "sci/engine/state.h"
#include "sci/engine/script.h"
namespace Sci {
SegManager::SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher)
: _resMan(resMan), _scriptPatcher(scriptPatcher) {
_heap.push_back(0);
_clonesSegId = 0;
_listsSegId = 0;
_nodesSegId = 0;
_hunksSegId = 0;
_saveDirPtr = NULL_REG;
_parserPtr = NULL_REG;
#ifdef ENABLE_SCI32
_arraysSegId = 0;
_stringSegId = 0;
_bitmapSegId = 0;
#endif
createClassTable();
}
SegManager::~SegManager() {
resetSegMan();
}
void SegManager::resetSegMan() {
// Free memory
for (uint i = 0; i < _heap.size(); i++) {
if (_heap[i])
deallocate(i);
}
_heap.clear();
// And reinitialize
_heap.push_back(0);
_clonesSegId = 0;
_listsSegId = 0;
_nodesSegId = 0;
_hunksSegId = 0;
#ifdef ENABLE_SCI32
_arraysSegId = 0;
_stringSegId = 0;
_bitmapSegId = 0;
#endif
// Reinitialize class table
_classTable.clear();
createClassTable();
}
void SegManager::initSysStrings() {
if (getSciVersion() <= SCI_VERSION_1_1) {
// We need to allocate system strings in one segment, for compatibility reasons
allocDynmem(512, "system strings", &_saveDirPtr);
_parserPtr = make_reg(_saveDirPtr.getSegment(), _saveDirPtr.getOffset() + 256);
#ifdef ENABLE_SCI32
} else {
SciString *saveDirString = allocateString(&_saveDirPtr);
saveDirString->setSize(256);
saveDirString->setValue(0, 0);
_parserPtr = NULL_REG; // no SCI2 game had a parser
#endif
}
}
SegmentId SegManager::findFreeSegment() const {
// The following is a very crude approach: We find a free segment id by
// scanning from the start. This can be slow if the number of segments
// becomes large. Optimizations are possible and easy, but I'll refrain
// from attempting any until we determine we actually need it.
uint seg = 1;
while (seg < _heap.size() && _heap[seg]) {
++seg;
}
assert(seg < 65536);
return seg;
}
SegmentObj *SegManager::allocSegment(SegmentObj *mem, SegmentId *segid) {
// Find a free segment
SegmentId id = findFreeSegment();
if (segid)
*segid = id;
if (!mem)
error("SegManager: invalid mobj");
// ... and put it into the (formerly) free segment.
if (id >= (int)_heap.size()) {
assert(id == (int)_heap.size());
_heap.push_back(0);
}
_heap[id] = mem;
return mem;
}
Script *SegManager::allocateScript(int script_nr, SegmentId *segid) {
// Check if the script already has an allocated segment. If it
// does, return that segment.
*segid = _scriptSegMap.getVal(script_nr, 0);
if (*segid > 0) {
return (Script *)_heap[*segid];
}
// allocate the SegmentObj
SegmentObj *mem = allocSegment(new Script(), segid);
// Add the script to the "script id -> segment id" hashmap
_scriptSegMap[script_nr] = *segid;
return (Script *)mem;
}
SegmentId SegManager::getActualSegment(SegmentId seg) const {
if (getSciVersion() <= SCI_VERSION_2_1_LATE) {
return seg;
} else {
// Return the lower 14 bits of the segment
return (seg & 0x3FFF);
}
}
void SegManager::deallocate(SegmentId seg) {
SegmentId actualSegment = getActualSegment(seg);
if (actualSegment < 1 || (uint)actualSegment >= _heap.size())
error("Attempt to deallocate an invalid segment ID");
SegmentObj *mobj = _heap[actualSegment];
if (!mobj)
error("Attempt to deallocate an already freed segment");
if (mobj->getType() == SEG_TYPE_SCRIPT) {
Script *scr = (Script *)mobj;
_scriptSegMap.erase(scr->getScriptNumber());
if (scr->getLocalsSegment()) {
// Check if the locals segment has already been deallocated.
// If the locals block has been stored in a segment with an ID
// smaller than the segment ID of the script itself, it will be
// already freed at this point. This can happen when scripts are
// uninstantiated and instantiated again: they retain their own
// segment ID, but are allocated a new locals segment, which can
// have an ID smaller than the segment of the script itself.
if (_heap[scr->getLocalsSegment()])
deallocate(scr->getLocalsSegment());
}
}
delete mobj;
_heap[actualSegment] = NULL;
}
bool SegManager::isHeapObject(reg_t pos) const {
const Object *obj = getObject(pos);
if (obj == NULL || (obj && obj->isFreed()))
return false;
Script *scr = getScriptIfLoaded(pos.getSegment());
return !(scr && scr->isMarkedAsDeleted());
}
void SegManager::deallocateScript(int script_nr) {
deallocate(getScriptSegment(script_nr));
}
Script *SegManager::getScript(const SegmentId seg) {
SegmentId actualSegment = getActualSegment(seg);
if (actualSegment < 1 || (uint)actualSegment >= _heap.size()) {
error("SegManager::getScript(): seg id %x out of bounds", actualSegment);
}
if (!_heap[actualSegment]) {
error("SegManager::getScript(): seg id %x is not in memory", actualSegment);
}
if (_heap[actualSegment]->getType() != SEG_TYPE_SCRIPT) {
error("SegManager::getScript(): seg id %x refers to type %d != SEG_TYPE_SCRIPT", actualSegment, _heap[actualSegment]->getType());
}
return (Script *)_heap[actualSegment];
}
Script *SegManager::getScriptIfLoaded(const SegmentId seg) const {
SegmentId actualSegment = getActualSegment(seg);
if (actualSegment < 1 || (uint)actualSegment >= _heap.size() || !_heap[actualSegment] || _heap[actualSegment]->getType() != SEG_TYPE_SCRIPT)
return 0;
return (Script *)_heap[actualSegment];
}
SegmentId SegManager::findSegmentByType(int type) const {
for (uint i = 0; i < _heap.size(); i++)
if (_heap[i] && _heap[i]->getType() == type)
return i;
return 0;
}
SegmentObj *SegManager::getSegmentObj(SegmentId seg) const {
SegmentId actualSegment = getActualSegment(seg);
if (actualSegment < 1 || (uint)actualSegment >= _heap.size() || !_heap[actualSegment])
return 0;
return _heap[actualSegment];
}
SegmentType SegManager::getSegmentType(SegmentId seg) const {
SegmentId actualSegment = getActualSegment(seg);
if (actualSegment < 1 || (uint)actualSegment >= _heap.size() || !_heap[actualSegment])
return SEG_TYPE_INVALID;
return _heap[actualSegment]->getType();
}
SegmentObj *SegManager::getSegment(SegmentId seg, SegmentType type) const {
SegmentId actualSegment = getActualSegment(seg);
return getSegmentType(actualSegment) == type ? _heap[actualSegment] : NULL;
}
Object *SegManager::getObject(reg_t pos) const {
SegmentObj *mobj = getSegmentObj(pos.getSegment());
Object *obj = NULL;
if (mobj != NULL) {
if (mobj->getType() == SEG_TYPE_CLONES) {
CloneTable &ct = *(CloneTable *)mobj;
if (ct.isValidEntry(pos.getOffset()))
obj = &(ct[pos.getOffset()]);
else
warning("getObject(): Trying to get an invalid object");
} else if (mobj->getType() == SEG_TYPE_SCRIPT) {
Script *scr = (Script *)mobj;
if (pos.getOffset() <= scr->getBufSize() && pos.getOffset() >= (uint)-SCRIPT_OBJECT_MAGIC_OFFSET
&& scr->offsetIsObject(pos.getOffset())) {
obj = scr->getObject(pos.getOffset());
}
}
}
return obj;
}
const char *SegManager::getObjectName(reg_t pos) {
const Object *obj = getObject(pos);
if (!obj)
return "<no such object>";
reg_t nameReg = obj->getNameSelector();
if (nameReg.isNull())
return "<no name>";
const char *name = 0;
if (nameReg.getSegment())
name = derefString(nameReg);
if (!name) {
// Crazy Nick Laura Bow is missing some object names needed for the static
// selector vocabulary
if (g_sci->getGameId() == GID_CNICK_LAURABOW && pos == make_reg(1, 0x2267))
return "Character";
else
return "<invalid name>";
}
return name;
}
reg_t SegManager::findObjectByName(const Common::String &name, int index) {
Common::Array<reg_t> result;
uint i;
// Now all values are available; iterate over all objects.
for (i = 0; i < _heap.size(); i++) {
const SegmentObj *mobj = _heap[i];
if (!mobj)
continue;
reg_t objpos = make_reg(i, 0);
if (mobj->getType() == SEG_TYPE_SCRIPT) {
// It's a script, scan all objects in it
const Script *scr = (const Script *)mobj;
const ObjMap &objects = scr->getObjectMap();
for (ObjMap::const_iterator it = objects.begin(); it != objects.end(); ++it) {
objpos.setOffset(it->_value.getPos().getOffset());
if (name == getObjectName(objpos))
result.push_back(objpos);
}
} else if (mobj->getType() == SEG_TYPE_CLONES) {
// It's clone table, scan all objects in it
const CloneTable *ct = (const CloneTable *)mobj;
for (uint idx = 0; idx < ct->size(); ++idx) {
if (!ct->isValidEntry(idx))
continue;
objpos.setOffset(idx);
if (name == getObjectName(objpos))
result.push_back(objpos);
}
}
}
if (result.empty())
return NULL_REG;
if (result.size() > 1 && index < 0) {
debug("findObjectByName(%s): multiple matches:", name.c_str());
for (i = 0; i < result.size(); i++)
debug(" %3x: [%04x:%04x]", i, PRINT_REG(result[i]));
return NULL_REG; // Ambiguous
}
if (index < 0)
return result[0];
else if (result.size() <= (uint)index)
return NULL_REG; // Not found
return result[index];
}
// return the seg if script_id is valid and in the map, else 0
SegmentId SegManager::getScriptSegment(int script_id) const {
return _scriptSegMap.getVal(script_id, 0);
}
SegmentId SegManager::getScriptSegment(int script_nr, ScriptLoadType load) {
SegmentId segment;
if ((load & SCRIPT_GET_LOAD) == SCRIPT_GET_LOAD)
instantiateScript(script_nr);
segment = getScriptSegment(script_nr);
if (segment > 0) {
if ((load & SCRIPT_GET_LOCK) == SCRIPT_GET_LOCK)
getScript(segment)->incrementLockers();
}
return segment;
}
DataStack *SegManager::allocateStack(int size, SegmentId *segid) {
SegmentObj *mobj = allocSegment(new DataStack(), segid);
DataStack *retval = (DataStack *)mobj;
retval->_entries = (reg_t *)calloc(size, sizeof(reg_t));
retval->_capacity = size;
// SSCI initializes the stack with "S" characters (uppercase S in SCI0-SCI1,
// lowercase s in SCI0 and SCI11) - probably stands for "stack"
byte filler = (getSciVersion() >= SCI_VERSION_01 && getSciVersion() <= SCI_VERSION_1_LATE) ? 'S' : 's';
for (int i = 0; i < size; i++)
retval->_entries[i] = make_reg(0, filler);
return retval;
}
void SegManager::freeHunkEntry(reg_t addr) {
if (addr.isNull()) {
warning("Attempt to free a Hunk from a null address");
return;
}
HunkTable *ht = (HunkTable *)getSegment(addr.getSegment(), SEG_TYPE_HUNK);
if (!ht) {
warning("Attempt to free Hunk from address %04x:%04x: Invalid segment type %d", PRINT_REG(addr), getSegmentType(addr.getSegment()));
return;
}
ht->freeEntryContents(addr.getOffset());
}
reg_t SegManager::allocateHunkEntry(const char *hunk_type, int size) {
HunkTable *table;
int offset;
if (!_hunksSegId)
allocSegment(new HunkTable(), &(_hunksSegId));
table = (HunkTable *)_heap[_hunksSegId];
offset = table->allocEntry();
reg_t addr = make_reg(_hunksSegId, offset);
Hunk *h = &table->at(offset);
if (!h)
return NULL_REG;
h->mem = malloc(size);
h->size = size;
h->type = hunk_type;
return addr;
}
byte *SegManager::getHunkPointer(reg_t addr) {
HunkTable *ht = (HunkTable *)getSegment(addr.getSegment(), SEG_TYPE_HUNK);
if (!ht || !ht->isValidEntry(addr.getOffset())) {
// Valid SCI behavior, e.g. when loading/quitting
return NULL;
}
return (byte *)ht->at(addr.getOffset()).mem;
}
Clone *SegManager::allocateClone(reg_t *addr) {
CloneTable *table;
int offset;
if (!_clonesSegId)
table = (CloneTable *)allocSegment(new CloneTable(), &(_clonesSegId));
else
table = (CloneTable *)_heap[_clonesSegId];
offset = table->allocEntry();
*addr = make_reg(_clonesSegId, offset);
return &table->at(offset);
}
List *SegManager::allocateList(reg_t *addr) {
ListTable *table;
int offset;
if (!_listsSegId)
allocSegment(new ListTable(), &(_listsSegId));
table = (ListTable *)_heap[_listsSegId];
offset = table->allocEntry();
*addr = make_reg(_listsSegId, offset);
return &table->at(offset);
}
Node *SegManager::allocateNode(reg_t *addr) {
NodeTable *table;
int offset;
if (!_nodesSegId)
allocSegment(new NodeTable(), &(_nodesSegId));
table = (NodeTable *)_heap[_nodesSegId];
offset = table->allocEntry();
*addr = make_reg(_nodesSegId, offset);
return &table->at(offset);
}
reg_t SegManager::newNode(reg_t value, reg_t key) {
reg_t nodeRef;
Node *n = allocateNode(&nodeRef);
n->pred = n->succ = NULL_REG;
n->key = key;
n->value = value;
return nodeRef;
}
List *SegManager::lookupList(reg_t addr) {
if (getSegmentType(addr.getSegment()) != SEG_TYPE_LISTS) {
error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr));
return NULL;
}
ListTable &lt = *(ListTable *)_heap[addr.getSegment()];
if (!lt.isValidEntry(addr.getOffset())) {
error("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr));
return NULL;
}
return &(lt[addr.getOffset()]);
}
Node *SegManager::lookupNode(reg_t addr, bool stopOnDiscarded) {
if (addr.isNull())
return NULL; // Non-error null
SegmentType type = getSegmentType(addr.getSegment());
if (type != SEG_TYPE_NODES) {
error("Attempt to use non-node %04x:%04x (type %d) as list node", PRINT_REG(addr), type);
return NULL;
}
NodeTable &nt = *(NodeTable *)_heap[addr.getSegment()];
if (!nt.isValidEntry(addr.getOffset())) {
if (!stopOnDiscarded)
return NULL;
error("Attempt to use invalid or discarded reference %04x:%04x as list node", PRINT_REG(addr));
return NULL;
}
return &(nt[addr.getOffset()]);
}
SegmentRef SegManager::dereference(reg_t pointer) {
SegmentRef ret;
if (!pointer.getSegment() || (pointer.getSegment() >= _heap.size()) || !_heap[pointer.getSegment()]) {
// This occurs in KQ5CD when interacting with certain objects
warning("SegManager::dereference(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer));
return ret; /* Invalid */
}
SegmentObj *mobj = _heap[pointer.getSegment()];
return mobj->dereference(pointer);
}
static void *derefPtr(SegManager *segMan, reg_t pointer, int entries, bool wantRaw) {
SegmentRef ret = segMan->dereference(pointer);
if (!ret.isValid())
return NULL;
if (ret.isRaw != wantRaw) {
warning("Dereferencing pointer %04x:%04x (type %d) which is %s, but expected %s", PRINT_REG(pointer),
segMan->getSegmentType(pointer.getSegment()),
ret.isRaw ? "raw" : "not raw",
wantRaw ? "raw" : "not raw");
}
if (!wantRaw && ret.skipByte) {
warning("Unaligned pointer read: %04x:%04x expected with word alignment", PRINT_REG(pointer));
return NULL;
}
if (entries > ret.maxSize) {
warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(pointer));
return NULL;
}
if (ret.isRaw)
return ret.raw;
else
return ret.reg;
}
byte *SegManager::derefBulkPtr(reg_t pointer, int entries) {
return (byte *)derefPtr(this, pointer, entries, true);
}
reg_t *SegManager::derefRegPtr(reg_t pointer, int entries) {
return (reg_t *)derefPtr(this, pointer, 2*entries, false);
}
char *SegManager::derefString(reg_t pointer, int entries) {
return (char *)derefPtr(this, pointer, entries, true);
}
// Helper functions for getting/setting characters in string fragments
static inline char getChar(const SegmentRef &ref, uint offset) {
if (ref.skipByte)
offset++;
reg_t val = ref.reg[offset / 2];
// segment 0xFFFF means that the scripts are using uninitialized temp-variable space
// we can safely ignore this, if it isn't one of the first 2 chars.
// foreign lsl3 uses kFileIO(readraw) and then immediately uses kReadNumber right at the start
if (val.getSegment() != 0)
if (!((val.getSegment() == 0xFFFF) && (offset > 1)))
warning("Attempt to read character from non-raw data");
bool oddOffset = offset & 1;
if (g_sci->isBE())
oddOffset = !oddOffset;
return (oddOffset ? val.getOffset() >> 8 : val.getOffset() & 0xff);
}
static inline void setChar(const SegmentRef &ref, uint offset, byte value) {
if (ref.skipByte)
offset++;
reg_t *val = ref.reg + offset / 2;
val->setSegment(0);
bool oddOffset = offset & 1;
if (g_sci->isBE())
oddOffset = !oddOffset;
if (oddOffset)
val->setOffset((val->getOffset() & 0x00ff) | (value << 8));
else
val->setOffset((val->getOffset() & 0xff00) | value);
}
// TODO: memcpy, strcpy and strncpy could maybe be folded into a single function
void SegManager::strncpy(reg_t dest, const char* src, size_t n) {
SegmentRef dest_r = dereference(dest);
if (!dest_r.isValid()) {
warning("Attempt to strncpy to invalid pointer %04x:%04x", PRINT_REG(dest));
return;
}
if (dest_r.isRaw) {
// raw -> raw
if (n == 0xFFFFFFFFU)
::strcpy((char *)dest_r.raw, src);
else
::strncpy((char *)dest_r.raw, src, n);
} else {
// raw -> non-raw
for (uint i = 0; i < n; i++) {
setChar(dest_r, i, src[i]);
if (!src[i])
break;
}
// Put an ending NUL to terminate the string
if ((size_t)dest_r.maxSize > n)
setChar(dest_r, n, 0);
}
}
void SegManager::strncpy(reg_t dest, reg_t src, size_t n) {
if (src.isNull()) {
// Clear target string instead.
if (n > 0)
strcpy(dest, "");
return; // empty text
}
SegmentRef dest_r = dereference(dest);
const SegmentRef src_r = dereference(src);
if (!src_r.isValid()) {
warning("Attempt to strncpy from invalid pointer %04x:%04x", PRINT_REG(src));
// Clear target string instead.
if (n > 0)
strcpy(dest, "");
return;
}
if (!dest_r.isValid()) {
warning("Attempt to strncpy to invalid pointer %04x:%04x", PRINT_REG(dest));
return;
}
if (src_r.isRaw) {
// raw -> *
strncpy(dest, (const char*)src_r.raw, n);
} else if (dest_r.isRaw && !src_r.isRaw) {
// non-raw -> raw
for (uint i = 0; i < n; i++) {
char c = getChar(src_r, i);
dest_r.raw[i] = c;
if (!c)
break;
}
} else {
// non-raw -> non-raw
for (uint i = 0; i < n; i++) {
char c = getChar(src_r, i);
setChar(dest_r, i, c);
if (!c)
break;
}
}
}
void SegManager::strcpy(reg_t dest, const char* src) {
strncpy(dest, src, 0xFFFFFFFFU);
}
void SegManager::strcpy(reg_t dest, reg_t src) {
strncpy(dest, src, 0xFFFFFFFFU);
}
void SegManager::memcpy(reg_t dest, const byte* src, size_t n) {
SegmentRef dest_r = dereference(dest);
if (!dest_r.isValid()) {
warning("Attempt to memcpy to invalid pointer %04x:%04x", PRINT_REG(dest));
return;
}
if ((int)n > dest_r.maxSize) {
warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(dest));
return;
}
if (dest_r.isRaw) {
// raw -> raw
::memcpy((char *)dest_r.raw, src, n);
} else {
// raw -> non-raw
for (uint i = 0; i < n; i++)
setChar(dest_r, i, src[i]);
}
}
void SegManager::memcpy(reg_t dest, reg_t src, size_t n) {
SegmentRef dest_r = dereference(dest);
const SegmentRef src_r = dereference(src);
if (!dest_r.isValid()) {
warning("Attempt to memcpy to invalid pointer %04x:%04x", PRINT_REG(dest));
return;
}
if ((int)n > dest_r.maxSize) {
warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(dest));
return;
}
if (!src_r.isValid()) {
warning("Attempt to memcpy from invalid pointer %04x:%04x", PRINT_REG(src));
return;
}
if ((int)n > src_r.maxSize) {
warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(src));
return;
}
if (src_r.isRaw) {
// raw -> *
memcpy(dest, src_r.raw, n);
} else if (dest_r.isRaw) {
// * -> raw
memcpy(dest_r.raw, src, n);
} else {
// non-raw -> non-raw
for (uint i = 0; i < n; i++) {
char c = getChar(src_r, i);
setChar(dest_r, i, c);
}
}
}
void SegManager::memcpy(byte *dest, reg_t src, size_t n) {
const SegmentRef src_r = dereference(src);
if (!src_r.isValid()) {
warning("Attempt to memcpy from invalid pointer %04x:%04x", PRINT_REG(src));
return;
}
if ((int)n > src_r.maxSize) {
warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(src));
return;
}
if (src_r.isRaw) {
// raw -> raw
::memcpy(dest, src_r.raw, n);
} else {
// non-raw -> raw
for (uint i = 0; i < n; i++) {
char c = getChar(src_r, i);
dest[i] = c;
}
}
}
size_t SegManager::strlen(reg_t str) {
if (str.isNull())
return 0; // empty text
SegmentRef str_r = dereference(str);
if (!str_r.isValid()) {
warning("Attempt to call strlen on invalid pointer %04x:%04x", PRINT_REG(str));
return 0;
}
if (str_r.isRaw) {
return ::strlen((const char *)str_r.raw);
} else {
int i = 0;
while (getChar(str_r, i))
i++;
return i;
}
}
Common::String SegManager::getString(reg_t pointer, int entries) {
Common::String ret;
if (pointer.isNull())
return ret; // empty text
SegmentRef src_r = dereference(pointer);
if (!src_r.isValid()) {
warning("SegManager::getString(): Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer));
return ret;
}
if (entries > src_r.maxSize) {
warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(pointer));
return ret;
}
if (src_r.isRaw)
ret = (char *)src_r.raw;
else {
uint i = 0;
for (;;) {
char c = getChar(src_r, i);
if (!c)
break;
i++;
ret += c;
};
}
return ret;
}
byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) {
SegmentId seg;
SegmentObj *mobj = allocSegment(new DynMem(), &seg);
*addr = make_reg(seg, 0);
DynMem &d = *(DynMem *)mobj;
d._size = size;
// Original SCI only zeroed out heap memory on initialize
// They didn't do it again for every allocation
if (size) {
d._buf = (byte *)calloc(size, 1);
} else {
d._buf = NULL;
}
d._description = descr;
return (byte *)(d._buf);
}
bool SegManager::freeDynmem(reg_t addr) {
if (addr.getSegment() < 1 || addr.getSegment() >= _heap.size() ||
!_heap[addr.getSegment()] || _heap[addr.getSegment()]->getType() != SEG_TYPE_DYNMEM)
return false; // error
deallocate(addr.getSegment());
return true; // OK
}
#ifdef ENABLE_SCI32
SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) {
ArrayTable *table;
int offset;
if (!_arraysSegId) {
table = (ArrayTable *)allocSegment(new ArrayTable(), &(_arraysSegId));
} else
table = (ArrayTable *)_heap[_arraysSegId];
offset = table->allocEntry();
*addr = make_reg(_arraysSegId, offset);
return &table->at(offset);
}
SciArray<reg_t> *SegManager::lookupArray(reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY)
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()];
if (!arrayTable.isValidEntry(addr.getOffset()))
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
return &(arrayTable[addr.getOffset()]);
}
void SegManager::freeArray(reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY)
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
ArrayTable &arrayTable = *(ArrayTable *)_heap[addr.getSegment()];
if (!arrayTable.isValidEntry(addr.getOffset()))
error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
arrayTable[addr.getOffset()].destroy();
arrayTable.freeEntry(addr.getOffset());
}
SciString *SegManager::allocateString(reg_t *addr) {
StringTable *table;
int offset;
if (!_stringSegId) {
table = (StringTable *)allocSegment(new StringTable(), &(_stringSegId));
} else
table = (StringTable *)_heap[_stringSegId];
offset = table->allocEntry();
*addr = make_reg(_stringSegId, offset);
return &table->at(offset);
}
SciString *SegManager::lookupString(reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING)
error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()];
if (!stringTable.isValidEntry(addr.getOffset()))
error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
return &(stringTable[addr.getOffset()]);
}
void SegManager::freeString(reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING)
error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()];
if (!stringTable.isValidEntry(addr.getOffset()))
error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
stringTable[addr.getOffset()].destroy();
stringTable.freeEntry(addr.getOffset());
}
#pragma mark -
#pragma mark Bitmaps
SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 paletteSize, const bool remap, const bool gc) {
BitmapTable *table;
int offset;
if (!_bitmapSegId) {
table = (BitmapTable *)allocSegment(new BitmapTable(), &(_bitmapSegId));
} else {
table = (BitmapTable *)_heap[_bitmapSegId];
}
offset = table->allocEntry();
*addr = make_reg(_bitmapSegId, offset);
SciBitmap &bitmap = table->at(offset);
bitmap.create(width, height, skipColor, displaceX, displaceY, scaledWidth, scaledHeight, paletteSize, remap, gc);
return &bitmap;
}
SciBitmap *SegManager::lookupBitmap(const reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_BITMAP)
error("Attempt to use non-bitmap %04x:%04x as bitmap", PRINT_REG(addr));
BitmapTable &bitmapTable = *(BitmapTable *)_heap[addr.getSegment()];
if (!bitmapTable.isValidEntry(addr.getOffset()))
error("Attempt to use invalid entry %04x:%04x as bitmap", PRINT_REG(addr));
return &(bitmapTable.at(addr.getOffset()));
}
void SegManager::freeBitmap(const reg_t addr) {
if (_heap[addr.getSegment()]->getType() != SEG_TYPE_BITMAP)
error("Attempt to free non-bitmap %04x:%04x as bitmap", PRINT_REG(addr));
BitmapTable &bitmapTable = *(BitmapTable *)_heap[addr.getSegment()];
if (!bitmapTable.isValidEntry(addr.getOffset()))
error("Attempt to free invalid entry %04x:%04x as bitmap", PRINT_REG(addr));
bitmapTable.freeEntry(addr.getOffset());
}
#pragma mark -
#endif
void SegManager::createClassTable() {
Resource *vocab996 = _resMan->findResource(ResourceId(kResourceTypeVocab, 996), 1);
if (!vocab996)
error("SegManager: failed to open vocab 996");
int totalClasses = vocab996->size >> 2;
_classTable.resize(totalClasses);
for (uint16 classNr = 0; classNr < totalClasses; classNr++) {
uint16 scriptNr = READ_SCI11ENDIAN_UINT16(vocab996->data + classNr * 4 + 2);
_classTable[classNr].reg = NULL_REG;
_classTable[classNr].script = scriptNr;
}
_resMan->unlockResource(vocab996);
}
reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment) {
if (classnr == 0xffff)
return NULL_REG;
if (classnr < 0 || (int)_classTable.size() <= classnr || _classTable[classnr].script < 0) {
error("[VM] Attempt to dereference class %x, which doesn't exist (max %x)", classnr, _classTable.size());
return NULL_REG;
} else {
Class *the_class = &_classTable[classnr];
if (!the_class->reg.getSegment()) {
getScriptSegment(the_class->script, lock);
if (!the_class->reg.getSegment()) {
error("[VM] Trying to instantiate class %x by instantiating script 0x%x (%03d) failed;", classnr, the_class->script, the_class->script);
return NULL_REG;
}
} else
if (callerSegment != the_class->reg.getSegment())
getScript(the_class->reg.getSegment())->incrementLockers();
return the_class->reg;
}
}
int SegManager::instantiateScript(int scriptNum) {
SegmentId segmentId = getScriptSegment(scriptNum);
Script *scr = getScriptIfLoaded(segmentId);
if (scr) {
if (!scr->isMarkedAsDeleted()) {
scr->incrementLockers();
return segmentId;
} else {
scr->freeScript();
}
} else {
scr = allocateScript(scriptNum, &segmentId);
}
scr->load(scriptNum, _resMan, _scriptPatcher);
scr->initializeLocals(this);
scr->initializeClasses(this);
scr->initializeObjects(this, segmentId);
return segmentId;
}
void SegManager::uninstantiateScript(int script_nr) {
SegmentId segmentId = getScriptSegment(script_nr);
Script *scr = getScriptIfLoaded(segmentId);
if (!scr || scr->isMarkedAsDeleted()) { // Is it already unloaded?
//warning("unloading script 0x%x requested although not loaded", script_nr);
// This is perfectly valid SCI behavior
return;
}
scr->decrementLockers(); // One less locker
if (scr->getLockers() > 0)
return;
// Free all classtable references to this script
for (uint i = 0; i < classTableSize(); i++)
if (getClass(i).reg.getSegment() == segmentId)
setClassOffset(i, NULL_REG);
if (getSciVersion() < SCI_VERSION_1_1)
uninstantiateScriptSci0(script_nr);
// FIXME: Add proper script uninstantiation for SCI 1.1
if (!scr->getLockers()) {
// The actual script deletion seems to be done by SCI scripts themselves
scr->markDeleted();
debugC(kDebugLevelScripts, "Unloaded script 0x%x.", script_nr);
}
}
void SegManager::uninstantiateScriptSci0(int script_nr) {
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
SegmentId segmentId = getScriptSegment(script_nr);
Script *scr = getScript(segmentId);
reg_t reg = make_reg(segmentId, oldScriptHeader ? 2 : 0);
int objType, objLength = 0;
// Make a pass over the object in order to uninstantiate all superclasses
do {
reg.incOffset(objLength); // Step over the last checked object
objType = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.getOffset()));
if (!objType)
break;
objLength = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.getOffset() + 2));
reg.incOffset(4); // Step over header
if ((objType == SCI_OBJ_OBJECT) || (objType == SCI_OBJ_CLASS)) { // object or class?
reg.incOffset(8); // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET)
int16 superclass = READ_SCI11ENDIAN_UINT16(scr->getBuf(reg.getOffset() + 2));
if (superclass >= 0) {
int superclass_script = getClass(superclass).script;
if (superclass_script == script_nr) {
if (scr->getLockers())
scr->decrementLockers(); // Decrease lockers if this is us ourselves
} else {
uninstantiateScript(superclass_script);
}
// Recurse to assure that the superclass lockers number gets decreased
}
reg.incOffset(SCRIPT_OBJECT_MAGIC_OFFSET);
} // if object or class
reg.incOffset(-4); // Step back on header
} while (objType != 0);
}
} // End of namespace Sci