/* 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. * * $URL$ * $Id$ * */ #include "sci/sci.h" #include "sci/engine/seg_manager.h" #include "sci/engine/state.h" namespace Sci { /** * Verify the the given condition is true, output the message if condition is false, and exit. * @param cond condition to be verified * @param msg the message to be printed if condition fails */ #define VERIFY( cond, msg ) if (!(cond)) {\ error("%s, line, %d, %s", __FILE__, __LINE__, msg); \ } #define DEFAULT_SCRIPTS 32 #define DEFAULT_OBJECTS 8 // default # of objects per script #define DEFAULT_OBJECTS_INCREMENT 4 // Number of additional objects to instantiate if we're running out of them #undef DEBUG_segMan // Define to turn on debugging SegManager::SegManager(ResourceManager *resMan) { _heap.push_back(0); Clones_seg_id = 0; Lists_seg_id = 0; Nodes_seg_id = 0; Hunks_seg_id = 0; #ifdef ENABLE_SCI32 Arrays_seg_id = 0; String_seg_id = 0; #endif _exportsAreWide = false; _resMan = resMan; createClassTable(); } // Destroy the object, free the memorys if allocated before SegManager::~SegManager() { resetSegMan(); } void SegManager::resetSegMan() { // Free memory for (uint i = 0; i < _heap.size(); i++) { if (_heap[i]) deallocate(i, false); } _heap.clear(); // And reinitialize _heap.push_back(0); Clones_seg_id = 0; Lists_seg_id = 0; Nodes_seg_id = 0; Hunks_seg_id = 0; // Reinitialize class table _classtable.clear(); createClassTable(); } SegmentId SegManager::findFreeSegment() const { // FIXME: This 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 refrain from doing any until this // refactoring is complete. // One very simple yet probably effective approach would be to add // "int firstFreeSegment", which is updated when allocating/freeing segments. // There are some scenarios where it performs badly, but we should first check // whether those occur at all. If so, there are easy refinements that deal // with this. Finally, we could switch to a more elaborate scheme, // such as keeping track of all free segments. But I doubt this is necessary. 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 *segid = findFreeSegment(); if (!mem) error("SegManager: invalid mobj"); // ... and put it into the (formerly) free segment. if (*segid >= (int)_heap.size()) { assert(*segid == (int)_heap.size()); _heap.push_back(0); } _heap[*segid] = mem; return mem; } Script *SegManager::allocateScript(int script_nr, SegmentId *seg_id) { // Check if the script already has an allocated segment. If it // does, return that segment. *seg_id = _scriptSegMap.getVal(script_nr, 0); if (*seg_id > 0) { return (Script *)_heap[*seg_id]; } // allocate the SegmentObj SegmentObj *mem = allocSegment(new Script(), seg_id); // Add the script to the "script id -> segment id" hashmap _scriptSegMap[script_nr] = *seg_id; return (Script *)mem; } void Script::setScriptSize(int script_nr, ResourceManager *resMan) { Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0); Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, script_nr), 0); bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY); _scriptSize = script->size; _heapSize = 0; // Set later if (!script || (getSciVersion() >= SCI_VERSION_1_1 && !heap)) { error("SegManager::setScriptSize: failed to load %s", !script ? "script" : "heap"); } if (oldScriptHeader) { _bufSize = script->size + READ_LE_UINT16(script->data) * 2; //locals_size = READ_LE_UINT16(script->data) * 2; } else if (getSciVersion() < SCI_VERSION_1_1) { _bufSize = script->size; } else { _bufSize = script->size + heap->size; _heapSize = heap->size; // Ensure that the start of the heap resource can be word-aligned. if (script->size & 2) { _bufSize++; _scriptSize++; } if (_bufSize > 65535) { error("Script and heap sizes combined exceed 64K." "This means a fundamental design bug was made in SCI\n" "regarding SCI1.1 games.\nPlease report this so it can be" "fixed in the next major version"); return; } } } int SegManager::deallocate(SegmentId seg, bool recursive) { SegmentObj *mobj; VERIFY(check(seg), "invalid seg id"); mobj = _heap[seg]; if (mobj->getType() == SEG_TYPE_SCRIPT) { Script *scr = (Script *)mobj; _scriptSegMap.erase(scr->_nr); if (recursive && scr->_localsSegment) deallocate(scr->_localsSegment, recursive); } delete mobj; _heap[seg] = NULL; return 1; } bool SegManager::isHeapObject(reg_t pos) { Object *obj = getObject(pos); if (obj == NULL || (obj && obj->isFreed())) return false; Script *scr = getScriptIfLoaded(pos.segment); return !(scr && scr->_markedAsDeleted); } void SegManager::deallocateScript(int script_nr) { SegmentId seg = getScriptSegment(script_nr); deallocate(seg, true); } Script *SegManager::getScript(const SegmentId seg) { if (seg < 1 || (uint)seg >= _heap.size()) { error("SegManager::getScript(): seg id %x out of bounds", seg); } if (!_heap[seg]) { error("SegManager::getScript(): seg id %x is not in memory", seg); } if (_heap[seg]->getType() != SEG_TYPE_SCRIPT) { error("SegManager::getScript(): seg id %x refers to type %d != SEG_TYPE_SCRIPT", seg, _heap[seg]->getType()); } return (Script *)_heap[seg]; } Script *SegManager::getScriptIfLoaded(const SegmentId seg) { if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg] || _heap[seg]->getType() != SEG_TYPE_SCRIPT) return 0; return (Script *)_heap[seg]; } SegmentId SegManager::findSegmentByType(int type) { for (uint i = 0; i < _heap.size(); i++) if (_heap[i] && _heap[i]->getType() == type) return i; return 0; } SegmentObj *SegManager::getSegmentObj(SegmentId seg) { if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg]) return 0; return _heap[seg]; } SegmentType SegManager::getSegmentType(SegmentId seg) { if (seg < 1 || (uint)seg >= _heap.size() || !_heap[seg]) return SEG_TYPE_INVALID; return _heap[seg]->getType(); } Object *SegManager::getObject(reg_t pos) { SegmentObj *mobj = getSegmentObj(pos.segment); Object *obj = NULL; if (mobj != NULL) { if (mobj->getType() == SEG_TYPE_CLONES) { CloneTable *ct = (CloneTable *)mobj; if (ct->isValidEntry(pos.offset)) obj = &(ct->_table[pos.offset]); else warning("getObject(): Trying to get an invalid object"); } else if (mobj->getType() == SEG_TYPE_SCRIPT) { Script *scr = (Script *)mobj; if (pos.offset <= scr->_bufSize && pos.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && RAW_IS_OBJECT(scr->_buf + pos.offset)) { obj = scr->getObject(pos.offset); } } } return obj; } const char *SegManager::getObjectName(reg_t pos) { Object *obj = getObject(pos); if (!obj) return ""; reg_t nameReg = obj->getNameSelector(); if (nameReg.isNull()) return ""; const char *name = derefString(nameReg); if (!name) return ""; return name; } reg_t SegManager::findObjectByName(const Common::String &name, int index) { reg_t retVal = NULL_REG; // Now all values are available; iterate over all objects. int timesFound = 0; for (uint i = 0; i < _heap.size(); i++) { SegmentObj *mobj = _heap[i]; int idx = 0; int max_index = 0; ObjMap::iterator it; Script *scr = 0; CloneTable *ct = 0; if (mobj) { if (mobj->getType() == SEG_TYPE_SCRIPT) { scr = (Script *)mobj; max_index = scr->_objects.size(); it = scr->_objects.begin(); } else if (mobj->getType() == SEG_TYPE_CLONES) { ct = (CloneTable *)mobj; max_index = ct->_table.size(); } } // It's a script or a clone table, scan all objects in it for (; idx < max_index; ++idx) { Object *obj = NULL; reg_t objpos; objpos.offset = 0; objpos.segment = i; if (mobj->getType() == SEG_TYPE_SCRIPT) { obj = &(it->_value); objpos.offset = obj->getPos().offset; ++it; } else if (mobj->getType() == SEG_TYPE_CLONES) { if (!ct->isValidEntry(idx)) continue; obj = &(ct->_table[idx]); objpos.offset = idx; } const char *objname = getObjectName(objpos); if (name == objname) { // Found a match! if ((index < 0) && (timesFound > 0)) { if (timesFound == 1) { // First time we realized the ambiguity printf("Ambiguous:\n"); printf(" %3x: [%04x:%04x] %s\n", 0, PRINT_REG(retVal), name.c_str()); } printf(" %3x: [%04x:%04x] %s\n", timesFound, PRINT_REG(objpos), name.c_str()); } if (index < 0 || timesFound == index) retVal = objpos; ++timesFound; } } } if (!timesFound) return NULL_REG; if (timesFound > 1 && index < 0) { printf("Ambiguous: Aborting.\n"); return NULL_REG; // Ambiguous } if (timesFound <= index) return NULL_REG; // Not found return retVal; } // validate the seg // return: // false - invalid seg // true - valid seg bool SegManager::check(SegmentId seg) { if (seg < 1 || (uint)seg >= _heap.size()) { return false; } if (!_heap[seg]) { warning("SegManager: seg %x is removed from memory, but not removed from hash_map", seg); return false; } return true; } void SegManager::setExportAreWide(bool flag) { _exportsAreWide = flag; } int Script::relocateBlock(Common::Array &block, int block_location, SegmentId segment, int location) { int rel = location - block_location; if (rel < 0) return 0; uint idx = rel >> 1; if (idx >= block.size()) return 0; if (rel & 1) { warning("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); return 0; } block[idx].segment = segment; // Perform relocation if (getSciVersion() >= SCI_VERSION_1_1) block[idx].offset += _scriptSize; return 1; } int Script::relocateLocal(SegmentId segment, int location) { if (_localsBlock) return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location); else return 0; // No hands, no cookies } int Script::relocateObject(Object &obj, SegmentId segment, int location) { return relocateBlock(obj._variables, obj.getPos().offset, segment, location); } void Script::scriptAddCodeBlock(reg_t location) { CodeBlock cb; cb.pos = location; cb.size = READ_LE_UINT16(_buf + location.offset - 2); _codeBlocks.push_back(cb); } void Script::scriptRelocate(reg_t block) { VERIFY(block.offset < (uint16)_bufSize && READ_LE_UINT16(_buf + block.offset) * 2 + block.offset < (uint16)_bufSize, "Relocation block outside of script\n"); int count = READ_LE_UINT16(_buf + block.offset); for (int i = 0; i <= count; i++) { int pos = READ_LE_UINT16(_buf + block.offset + 2 + (i * 2)); if (!pos) continue; // FIXME: A hack pending investigation if (!relocateLocal(block.segment, pos)) { bool done = false; uint k; ObjMap::iterator it; const ObjMap::iterator end = _objects.end(); for (it = _objects.begin(); !done && it != end; ++it) { if (relocateObject(it->_value, block.segment, pos)) done = true; } for (k = 0; !done && k < _codeBlocks.size(); k++) { if (pos >= _codeBlocks[k].pos.offset && pos < _codeBlocks[k].pos.offset + _codeBlocks[k].size) done = true; } if (!done) { printf("While processing relocation block %04x:%04x:\n", PRINT_REG(block)); printf("Relocation failed for index %04x (%d/%d)\n", pos, i + 1, count); if (_localsBlock) printf("- locals: %d at %04x\n", _localsBlock->_locals.size(), _localsOffset); else printf("- No locals\n"); for (it = _objects.begin(), k = 0; it != end; ++it, ++k) printf("- obj#%d at %04x w/ %d vars\n", k, it->_value.getPos().offset, it->_value.getVarCount()); // SQ3 script 71 has broken relocation entries. printf("Trying to continue anyway...\n"); } } } } void Script::heapRelocate(reg_t block) { VERIFY(block.offset < (uint16)_heapSize && READ_LE_UINT16(_heapStart + block.offset) * 2 + block.offset < (uint16)_bufSize, "Relocation block outside of script\n"); if (_relocated) return; _relocated = true; int count = READ_LE_UINT16(_heapStart + block.offset); for (int i = 0; i < count; i++) { int pos = READ_LE_UINT16(_heapStart + block.offset + 2 + (i * 2)) + _scriptSize; if (!relocateLocal(block.segment, pos)) { bool done = false; uint k; ObjMap::iterator it; const ObjMap::iterator end = _objects.end(); for (it = _objects.begin(); !done && it != end; ++it) { if (relocateObject(it->_value, block.segment, pos)) done = true; } if (!done) { printf("While processing relocation block %04x:%04x:\n", PRINT_REG(block)); printf("Relocation failed for index %04x (%d/%d)\n", pos, i + 1, count); if (_localsBlock) printf("- locals: %d at %04x\n", _localsBlock->_locals.size(), _localsOffset); else printf("- No locals\n"); for (it = _objects.begin(), k = 0; it != end; ++it, ++k) printf("- obj#%d at %04x w/ %d vars\n", k, it->_value.getPos().offset, it->_value.getVarCount()); error("Breakpoint in %s, line %d", __FILE__, __LINE__); } } } } // 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) script_instantiate(_resMan, this, script_nr); segment = getScriptSegment(script_nr); if (segment > 0) { if ((load & SCRIPT_GET_LOCK) == SCRIPT_GET_LOCK) getScript(segment)->incrementLockers(); } return segment; } reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) { 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.segment) { getScriptSegment(the_class->script, lock); if (!the_class->reg.segment) { error("[VM] Trying to instantiate class %x by instantiating script 0x%x (%03d) failed;" " Entering debugger.", classnr, the_class->script, the_class->script); return NULL_REG; } } else if (caller.segment != the_class->reg.segment) getScript(the_class->reg.segment)->incrementLockers(); return the_class->reg; } } Object *Script::scriptObjInit(reg_t obj_pos) { Object *obj; if (getSciVersion() < SCI_VERSION_1_1) obj_pos.offset += 8; // magic offset (SCRIPT_OBJECT_MAGIC_OFFSET) VERIFY(obj_pos.offset < _bufSize, "Attempt to initialize object beyond end of script\n"); obj = allocateObject(obj_pos.offset); VERIFY(obj_pos.offset + SCRIPT_FUNCTAREAPTR_OFFSET < (int)_bufSize, "Function area pointer stored beyond end of script\n"); obj->init(_buf, obj_pos); return obj; } LocalVariables *SegManager::allocLocalsSegment(Script *scr, int count) { if (!count) { // No locals scr->_localsSegment = 0; scr->_localsBlock = NULL; return NULL; } else { LocalVariables *locals; if (scr->_localsSegment) { locals = (LocalVariables *)_heap[scr->_localsSegment]; VERIFY(locals != NULL, "Re-used locals segment was NULL'd out"); VERIFY(locals->getType() == SEG_TYPE_LOCALS, "Re-used locals segment did not consist of local variables"); VERIFY(locals->script_id == scr->_nr, "Re-used locals segment belonged to other script"); } else locals = (LocalVariables *)allocSegment(new LocalVariables(), &scr->_localsSegment); scr->_localsBlock = locals; locals->script_id = scr->_nr; locals->_locals.resize(count); return locals; } } void SegManager::scriptInitialiseLocalsZero(SegmentId seg, int count) { Script *scr = getScript(seg); scr->_localsOffset = -count * 2; // Make sure it's invalid allocLocalsSegment(scr, count); } void SegManager::scriptInitialiseLocals(reg_t location) { Script *scr = getScript(location.segment); unsigned int count; VERIFY(location.offset + 1 < (uint16)scr->_bufSize, "Locals beyond end of script\n"); if (getSciVersion() >= SCI_VERSION_1_1) count = READ_LE_UINT16(scr->_buf + location.offset - 2); else count = (READ_LE_UINT16(scr->_buf + location.offset - 2) - 4) >> 1; // half block size scr->_localsOffset = location.offset; if (!(location.offset + count * 2 + 1 < scr->_bufSize)) { warning("Locals extend beyond end of script: offset %04x, count %x vs size %x", location.offset, count, (uint)scr->_bufSize); count = (scr->_bufSize - location.offset) >> 1; } LocalVariables *locals = allocLocalsSegment(scr, count); if (locals) { uint i; byte *base = (byte *)(scr->_buf + location.offset); for (i = 0; i < count; i++) locals->_locals[i].offset = READ_LE_UINT16(base + i * 2); } } void SegManager::scriptRelocateExportsSci11(SegmentId seg) { Script *scr = getScript(seg); for (int i = 0; i < scr->_numExports; i++) { /* We are forced to use an ugly heuristic here to distinguish function exports from object/class exports. The former kind points into the script resource, the latter into the heap resource. */ uint16 location = READ_LE_UINT16((byte *)(scr->_exportTable + i)); if ((location < scr->_heapSize - 1) && (READ_LE_UINT16(scr->_heapStart + location) == SCRIPT_OBJECT_MAGIC_NUMBER)) { WRITE_LE_UINT16((byte *)(scr->_exportTable + i), location + scr->_heapStart - scr->_buf); } else { // Otherwise it's probably a function export, // and we don't need to do anything. } } } void SegManager::scriptInitialiseObjectsSci11(SegmentId seg) { Script *scr = getScript(seg); byte *seeker = scr->_heapStart + 4 + READ_LE_UINT16(scr->_heapStart + 2) * 2; while (READ_LE_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { if (READ_LE_UINT16(seeker + 14) & SCRIPT_INFO_CLASS) { int classpos = seeker - scr->_buf; int species = READ_LE_UINT16(seeker + 10); if (species < 0 || species >= (int)_classtable.size()) { error("Invalid species %d(0x%x) not in interval [0,%d) while instantiating script %d", species, species, _classtable.size(), scr->_nr); return; } _classtable[species].reg.segment = seg; _classtable[species].reg.offset = classpos; } seeker += READ_LE_UINT16(seeker + 2) * 2; } seeker = scr->_heapStart + 4 + READ_LE_UINT16(scr->_heapStart + 2) * 2; while (READ_LE_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { reg_t reg; Object *obj; reg.segment = seg; reg.offset = seeker - scr->_buf; obj = scr->scriptObjInit(reg); #if 0 if (obj->_variables[5].offset != 0xffff) { obj->_variables[5] = INST_LOOKUP_CLASS(obj->_variables[5].offset); baseObj = getObject(obj->_variables[5]); obj->variable_names_nr = baseObj->variables_nr; obj->_baseObj = baseObj->_baseObj; } #endif // Copy base from species class, as we need its selector IDs obj->setSuperClassSelector( getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG)); // Set the -classScript- selector to the script number. // FIXME: As this selector is filled in at run-time, it is likely // that it is supposed to hold a pointer. The Obj::isKindOf method // uses this selector together with -propDict- to compare classes. // For the purpose of Obj::isKindOf, using the script number appears // to be sufficient. obj->setClassScriptSelector(make_reg(0, scr->_nr)); seeker += READ_LE_UINT16(seeker + 2) * 2; } } /* static char *SegManager::dynprintf(char *msg, ...) { va_list argp; char *buf = (char *)malloc(strlen(msg) + 100); va_start(argp, msg); vsprintf(buf, msg, argp); va_end(argp); return buf; } */ 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; return retval; } SystemStrings *SegManager::allocateSysStrings(SegmentId *segid) { return (SystemStrings *)allocSegment(new SystemStrings(), segid); } uint16 SegManager::validateExportFunc(int pubfunct, SegmentId seg) { Script *scr = getScript(seg); if (scr->_numExports <= pubfunct) { warning("validateExportFunc(): pubfunct is invalid"); return 0; } if (_exportsAreWide) pubfunct *= 2; uint16 offset = READ_LE_UINT16((byte *)(scr->_exportTable + pubfunct)); VERIFY(offset < scr->_bufSize, "invalid export function pointer"); return offset; } void SegManager::freeHunkEntry(reg_t addr) { HunkTable *ht = (HunkTable *)GET_SEGMENT(*this, addr.segment, SEG_TYPE_HUNK); if (!ht) { warning("Attempt to free Hunk from address %04x:%04x: Invalid segment type", PRINT_REG(addr)); return; } ht->freeEntry(addr.offset); } Hunk *SegManager::allocateHunkEntry(const char *hunk_type, int size, reg_t *addr) { HunkTable *table; int offset; if (!Hunks_seg_id) allocSegment(new HunkTable(), &(Hunks_seg_id)); table = (HunkTable *)_heap[Hunks_seg_id]; offset = table->allocEntry(); *addr = make_reg(Hunks_seg_id, offset); Hunk *h = &(table->_table[offset]); if (!h) return NULL; h->mem = malloc(size); h->size = size; h->type = hunk_type; return h; } Clone *SegManager::allocateClone(reg_t *addr) { CloneTable *table; int offset; if (!Clones_seg_id) table = (CloneTable *)allocSegment(new CloneTable(), &(Clones_seg_id)); else table = (CloneTable *)_heap[Clones_seg_id]; offset = table->allocEntry(); *addr = make_reg(Clones_seg_id, offset); return &(table->_table[offset]); } void SegManager::reconstructClones() { for (uint i = 0; i < _heap.size(); i++) { if (_heap[i]) { SegmentObj *mobj = _heap[i]; if (mobj->getType() == SEG_TYPE_CLONES) { CloneTable *ct = (CloneTable *)mobj; for (uint j = 0; j < ct->_table.size(); j++) { // Check if the clone entry is used uint entryNum = (uint)ct->first_free; bool isUsed = true; while (entryNum != ((uint) CloneTable::HEAPENTRY_INVALID)) { if (entryNum == j) { isUsed = false; break; } entryNum = ct->_table[entryNum].next_free; } if (!isUsed) continue; CloneTable::Entry &seeker = ct->_table[j]; Object *baseObj = getObject(seeker.getSpeciesSelector()); seeker.cloneFromObject(baseObj); if (!baseObj) warning("Clone entry without a base class: %d", j); } // end for } // end if } // end if } // end for } List *SegManager::allocateList(reg_t *addr) { ListTable *table; int offset; if (!Lists_seg_id) allocSegment(new ListTable(), &(Lists_seg_id)); table = (ListTable *)_heap[Lists_seg_id]; offset = table->allocEntry(); *addr = make_reg(Lists_seg_id, offset); return &(table->_table[offset]); } Node *SegManager::allocateNode(reg_t *addr) { NodeTable *table; int offset; if (!Nodes_seg_id) allocSegment(new NodeTable(), &(Nodes_seg_id)); table = (NodeTable *)_heap[Nodes_seg_id]; offset = table->allocEntry(); *addr = make_reg(Nodes_seg_id, offset); return &(table->_table[offset]); } List *SegManager::lookupList(reg_t addr) { if (getSegmentType(addr.segment) != SEG_TYPE_LISTS) { warning("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } ListTable *lt = (ListTable *)_heap[addr.segment]; if (!lt->isValidEntry(addr.offset)) { warning("Attempt to use non-list %04x:%04x as list", PRINT_REG(addr)); return NULL; } return &(lt->_table[addr.offset]); } Node *SegManager::lookupNode(reg_t addr) { if (addr.isNull()) return NULL; // Non-error null if (getSegmentType(addr.segment) != SEG_TYPE_NODES) { // FIXME: This occurs right at the beginning of SQ4, when walking north from the first screen. It doesn't // seem to have any apparent ill-effects, though, so it's been changed to non-fatal, for now //error("%s, L%d: Attempt to use non-node %04x:%04x as list node", __FILE__, __LINE__, PRINT_REG(addr)); warning("Attempt to use non-node %04x:%04x as list node", PRINT_REG(addr)); return NULL; } NodeTable *nt = (NodeTable *)_heap[addr.segment]; if (!nt->isValidEntry(addr.offset)) { warning("Attempt to use non-node %04x:%04x as list node", PRINT_REG(addr)); return NULL; } return &(nt->_table[addr.offset]); } SegmentRef SegManager::dereference(reg_t pointer) { SegmentRef ret; if (!pointer.segment || (pointer.segment >= _heap.size()) || !_heap[pointer.segment]) { // 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.segment]; return mobj->dereference(pointer); } static void *_kernel_dereference_pointer(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->_heap[pointer.segment]->getType(), ret.isRaw ? "raw" : "not raw", wantRaw ? "raw" : "not raw"); } if (entries > ret.maxSize) { warning("Trying to dereference pointer %04x:%04x beyond end of segment", PRINT_REG(pointer)); return NULL; } return ret.raw; } byte *SegManager::derefBulkPtr(reg_t pointer, int entries) { return (byte *)_kernel_dereference_pointer(this, pointer, entries, true); } reg_t *SegManager::derefRegPtr(reg_t pointer, int entries) { #ifdef ENABLE_SCI32 // HACK: Due to a limitation in the SegManager, we don't know if the pointer needs to be // word aligned. If it's a new style array, then it is just accessing the arrays from a // table and this doesn't need to be true. if (pointer.offset & 1 && pointer.segment != Arrays_seg_id) { #else if (pointer.offset & 1) { #endif warning("Unaligned pointer read: %04x:%04x expected with word alignment", PRINT_REG(pointer)); return NULL; } return (reg_t *)_kernel_dereference_pointer(this, pointer, 2*entries, false); } char *SegManager::derefString(reg_t pointer, int entries) { return (char *)_kernel_dereference_pointer(this, pointer, entries, true); } // 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 reg_t* d = dest_r.reg; while (n > 0) { d->segment = 0; // STRINGFRAG_SEGMENT? if (n > 1 && src[0]) { d->offset = (src[0] & 0x00ff) | (src[1] << 8); } else { d->offset &= 0xff00; d->offset |= src[0] & 0x00ff; break; } if (!src[1]) break; src += 2; n -= 2; d++; } } } void SegManager::strncpy(reg_t dest, reg_t src, size_t n) { 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 const reg_t* s = src_r.reg; char *d = (char*)dest_r.raw; reg_t x; while (n > 0) { x = *s++; *d++ = x.offset & 0x00ff; if (n > 1 && x.offset & 0x00ff) { *d++ = x.offset >> 8; } else { break; } if (!(x.offset >> 8)) break; n -= 2; } } else { // non-raw -> non-raw const reg_t* s = src_r.reg; reg_t* d = dest_r.reg; reg_t x; while (n > 0) { x = *s++; if (n > 1 && x.offset & 0x00ff) { *d++ = x; } else { d->offset &= 0xff00; d->offset |= x.offset & 0x00ff; break; } if (!(x.offset & 0xff00)) break; n -= 2; } } } 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 reg_t* d = dest_r.reg; while (n > 0) { d->segment = 0; // STRINGFRAG_SEGMENT? if (n > 1) { d->offset = (src[0] & 0x00ff) | (src[1] << 8); } else { d->offset &= 0xff00; d->offset |= src[0] & 0x00ff; break; } src += 2; n -= 2; d++; } } } 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 const reg_t* s = src_r.reg; reg_t* d = dest_r.reg; reg_t x; while (n > 0) { x = *s++; if (n > 1) { *d++ = x; } else { d->offset &= 0xff00; d->offset |= x.offset & 0x00ff; break; } n -= 2; } } } 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 const reg_t* s = src_r.reg; reg_t x; while (n > 0) { x = *s++; *dest++ = x.offset & 0x00ff; if (n > 1) { *dest++ = x.offset >> 8; } else { break; } n -= 2; } } } size_t SegManager::strlen(reg_t str) { 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 { const reg_t* s = str_r.reg; size_t n = 0; while ((s->offset & 0x00ff) && (s->offset >> 8)) { ++s; n += 2; } if (s->offset & 0x00ff) n++; return n; } } Common::String SegManager::getString(reg_t pointer, int entries) { Common::String ret; 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 { const reg_t* s = src_r.reg; char c; do { c = s->offset & 0x00ff; if (c) { ret += c; c = s->offset >> 8; if (c) { ret += c; s++; } } } while (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; if (size == 0) d._buf = NULL; else d._buf = (byte *)malloc(size); d._description = descr; return (byte *)(d._buf); } int SegManager::freeDynmem(reg_t addr) { if (addr.segment < 1 || addr.segment >= _heap.size() || !_heap[addr.segment] || _heap[addr.segment]->getType() != SEG_TYPE_DYNMEM) return 1; // error deallocate(addr.segment, true); return 0; // OK } 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_LE_UINT16(vocab996->data + classNr * 4 + 2); _classtable[classNr].reg = NULL_REG; _classtable[classNr].script = scriptNr; } _resMan->unlockResource(vocab996); } #ifdef ENABLE_SCI32 SciArray *SegManager::allocateArray(reg_t *addr) { ArrayTable *table; int offset; if (!Arrays_seg_id) { table = (ArrayTable *)allocSegment(new ArrayTable(), &(Arrays_seg_id)); } else table = (ArrayTable *)_heap[Arrays_seg_id]; offset = table->allocEntry(); *addr = make_reg(Arrays_seg_id, offset); return &(table->_table[offset]); } SciArray *SegManager::lookupArray(reg_t addr) { if (_heap[addr.segment]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); ArrayTable *arrayTable = (ArrayTable *)_heap[addr.segment]; if (!arrayTable->isValidEntry(addr.offset)) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); return &(arrayTable->_table[addr.offset]); } void SegManager::freeArray(reg_t addr) { if (_heap[addr.segment]->getType() != SEG_TYPE_ARRAY) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); ArrayTable *arrayTable = (ArrayTable *)_heap[addr.segment]; if (!arrayTable->isValidEntry(addr.offset)) error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr)); arrayTable->freeEntry(addr.offset); } SciString *SegManager::allocateString(reg_t *addr) { StringTable *table; int offset; if (!String_seg_id) { table = (StringTable *)allocSegment(new StringTable(), &(String_seg_id)); } else table = (StringTable *)_heap[String_seg_id]; offset = table->allocEntry(); *addr = make_reg(String_seg_id, offset); return &(table->_table[offset]); } SciString *SegManager::lookupString(reg_t addr) { if (_heap[addr.segment]->getType() != SEG_TYPE_STRING) error("Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); StringTable *stringTable = (StringTable *)_heap[addr.segment]; if (!stringTable->isValidEntry(addr.offset)) error("Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); return &(stringTable->_table[addr.offset]); } void SegManager::freeString(reg_t addr) { if (_heap[addr.segment]->getType() != SEG_TYPE_STRING) error("Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); StringTable *stringTable = (StringTable *)_heap[addr.segment]; if (!stringTable->isValidEntry(addr.offset)) error("Attempt to use non-string %04x:%04x as string", PRINT_REG(addr)); stringTable->freeEntry(addr.offset); } #endif } // End of namespace Sci