/* 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" #include "sci/engine/intmap.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 //#define GC_DEBUG // Debug garbage collection //#define GC_DEBUG_VERBOSE // Debug garbage verbosely #undef DEBUG_SEG_MANAGER // Define to turn on debugging #define INVALID_SCRIPT_ID -1 SegManager::SegManager(bool sci1_1) { id_seg_map = new IntMapper(); reserved_id = INVALID_SCRIPT_ID; id_seg_map->checkKey(reserved_id, true); // reserve entry 0 for INVALID_SCRIPT_ID reserved_id--; // reserved_id runs in the reversed direction to make sure no one will use it. _heap.push_back(0); Clones_seg_id = 0; Lists_seg_id = 0; Nodes_seg_id = 0; Hunks_seg_id = 0; exports_wide = 0; isSci1_1 = sci1_1; } // Destroy the object, free the memorys if allocated before SegManager::~SegManager() { // Free memory for (uint i = 0; i < _heap.size(); i++) { if (_heap[i]) deallocate(i, false); } delete id_seg_map; } int SegManager::findFreeId(int *id) { bool was_added = false; int retval = 0; while (!was_added) { retval = id_seg_map->checkKey(reserved_id, true, &was_added); *id = reserved_id--; if (reserved_id < -1000000) reserved_id = -10; // Make sure we don't underflow } return retval; } MemObject *SegManager::allocNonscriptSegment(MemObjectType type, SegmentId *segid) { // Allocates a non-script segment int id; *segid = findFreeId(&id); return memObjAllocate(*segid, id, type); } // allocate a memory for script from heap // Parameters: (EngineState *) s: The state to operate on // (int) script_nr: The script number to load // Returns : 0 - allocation failure // 1 - allocated successfully // seg_id - allocated segment id Script *SegManager::allocateScript(EngineState *s, int script_nr, SegmentId *seg_id) { bool was_added; MemObject *mem; *seg_id = id_seg_map->checkKey(script_nr, true, &was_added); if (!was_added) { return (Script *)_heap[*seg_id]; } // allocate the MemObject mem = memObjAllocate(*seg_id, script_nr, MEM_OBJ_SCRIPT); if (!mem) { sciprintf("%s, %d, Not enough memory, ", __FILE__, __LINE__); return NULL; } return (Script *)mem; } void SegManager::setScriptSize(Script &scr, EngineState *s, int script_nr) { Resource *script = s->resmgr->findResource(kResourceTypeScript, script_nr, 0); Resource *heap = s->resmgr->findResource(kResourceTypeHeap, script_nr, 0); scr.script_size = script->size; scr.heap_size = 0; // Set later if (!script || (s->version >= SCI_VERSION_1_1 && !heap)) { error("SegManager::setScriptSize: failed to load %s", !script ? "script" : "heap"); } if (s->flags & GF_SCI0_OLD) { scr.buf_size = script->size + READ_LE_UINT16(script->data) * 2; //locals_size = READ_LE_UINT16(script->data) * 2; } else if (s->version < SCI_VERSION_1_1) { scr.buf_size = script->size; } else { scr.buf_size = script->size + heap->size; scr.heap_size = heap->size; // Ensure that the start of the heap resource can be word-aligned. if (script->size & 2) { scr.buf_size++; scr.script_size++; } if (scr.buf_size > 65535) { sciprintf("Script and heap sizes combined exceed 64K.\n" "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!\n"); return; } } } int SegManager::initialiseScript(Script &scr, EngineState *s, int script_nr) { // allocate the script.buf setScriptSize(scr, s, script_nr); scr.buf = (byte *)malloc(scr.buf_size); dbgPrint("scr.buf ", scr.buf); if (!scr.buf) { scr.freeScript(); sciprintf("SegManager: Not enough memory space for script size"); scr.buf_size = 0; return 0; } // Initialize objects scr.locals_offset = 0; scr.locals_block = NULL; scr._codeBlocks.clear(); scr.nr = script_nr; scr._markedAsDeleted = false; scr.relocated = 0; scr.obj_indices = new IntMapper(); if (s->version >= SCI_VERSION_1_1) scr.heap_start = scr.buf + scr.script_size; else scr.heap_start = scr.buf; return 1; } int SegManager::deallocate(SegmentId seg, bool recursive) { MemObject *mobj; VERIFY(check(seg), "invalid seg id"); mobj = _heap[seg]; id_seg_map->removeKey(mobj->getSegMgrId()); if (mobj->getType() == MEM_OBJ_SCRIPT) { Script *scr = (Script *)mobj; if (recursive && scr->locals_segment) deallocate(scr->locals_segment, recursive); } delete mobj; _heap[seg] = NULL; return 1; } bool SegManager::scriptIsMarkedAsDeleted(SegmentId seg) { Script *scr = getScriptIfLoaded(seg); if (!scr) return false; return scr->_markedAsDeleted; } int SegManager::deallocateScript(int script_nr) { SegmentId seg = segGet(script_nr); deallocate(seg, true); return 1; } MemObject *MemObject::createMemObject(MemObjectType type) { MemObject *mem = 0; switch (type) { case MEM_OBJ_SCRIPT: mem = new Script(); break; case MEM_OBJ_CLONES: mem = new CloneTable(); break; case MEM_OBJ_LOCALS: mem = new LocalVariables(); break; case MEM_OBJ_SYS_STRINGS: mem = new SystemStrings(); break; case MEM_OBJ_STACK: mem = new DataStack(); break; case MEM_OBJ_HUNK: mem = new HunkTable(); break; case MEM_OBJ_STRING_FRAG: mem = new StringFrag(); break; case MEM_OBJ_LISTS: mem = new ListTable(); break; case MEM_OBJ_NODES: mem = new NodeTable(); break; case MEM_OBJ_DYNMEM: mem = new DynMem(); break; default: error("Unknown MemObject type %d", type); break; } assert(mem); mem->_type = type; return mem; } MemObject *SegManager::memObjAllocate(SegmentId segid, int hash_id, MemObjectType type) { MemObject *mem = MemObject::createMemObject(type); if (!mem) { sciprintf("SegManager: invalid mobj "); return NULL; } if (segid >= (int)_heap.size()) { assert(segid == (int)_heap.size()); _heap.push_back(0); } mem->_segmgrId = hash_id; // hook it to the heap _heap[segid] = mem; return mem; } void Script::freeScript() { free(buf); buf = NULL; buf_size = 0; _objects.clear(); delete obj_indices; obj_indices = 0; _codeBlocks.clear(); } // memory operations void Script::mcpyInOut(int dst, const void *src, size_t n) { if (buf) { assert(dst + n <= buf_size); memcpy(buf + dst, src, n); } } int16 Script::getHeap(uint16 offset) const { VERIFY(offset + 1 < (int)buf_size, "invalid offset\n"); return READ_LE_UINT16(buf + offset); // return (buf[offset] | (buf[offset+1]) << 8); } // return the seg if script_id is valid and in the map, else -1 SegmentId SegManager::segGet(int script_id) const { return id_seg_map->lookupKey(script_id); } Script *SegManager::getScript(const SegmentId seg) { // FIXME: We accept segment 0, but that is actually an invalid segment... if (seg < 0 || (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() != MEM_OBJ_SCRIPT) { error("SegManager::getScript(): seg id %x refers to type %d != MEM_OBJ_SCRIPT", seg, _heap[seg]->getType()); } return (Script *)_heap[seg]; } Script *SegManager::getScriptIfLoaded(const SegmentId seg) { // FIXME: We accept segment 0, but that is actually an invalid segment... if (seg < 0 || (uint)seg >= _heap.size() || !_heap[seg] || _heap[seg]->getType() != MEM_OBJ_SCRIPT) return 0; return (Script *)_heap[seg]; } // validate the seg // return: // false - invalid seg // true - valid seg bool SegManager::check(SegmentId seg) { // FIXME: We accept segment 0, but that is actually an invalid segment... if (seg < 0 || (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; } bool SegManager::scriptIsLoaded(SegmentId seg) { return getScriptIfLoaded(seg) != 0; } void Script::incrementLockers() { lockers++; } void Script::decrementLockers() { if (lockers > 0) lockers--; } int Script::getLockers() const { return lockers; } void Script::setLockers(int lockers_) { lockers = lockers_; } void Script::setExportTableOffset(int offset) { if (offset) { export_table = (uint16 *)(buf + offset + 2); exports_nr = READ_LE_UINT16((byte *)(export_table - 1)); } else { export_table = NULL; exports_nr = 0; } } void SegManager::setExportWidth(int flag) { exports_wide = flag; } void Script::setSynonymsOffset(int offset) { synonyms = buf + offset; } byte *Script::getSynonyms() const { return synonyms; } void Script::setSynonymsNr(int n) { synonyms_nr = n; } int Script::getSynonymsNr() const { return synonyms_nr; } int SegManager::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) { sciprintf("Error: Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location); return 0; } block[idx].segment = segment; // Perform relocation if (isSci1_1) block[idx].offset += getScript(segment)->script_size; return 1; } int SegManager::relocateLocal(Script *scr, SegmentId segment, int location) { if (scr->locals_block) return relocateBlock(scr->locals_block->_locals, scr->locals_offset, segment, location); else return 0; // No hands, no cookies } int SegManager::relocateObject(Object *obj, SegmentId segment, int location) { return relocateBlock(obj->_variables, obj->pos.offset, segment, location); } void SegManager::scriptAddCodeBlock(reg_t location) { Script *scr = getScript(location.segment); CodeBlock cb; cb.pos = location; cb.size = READ_LE_UINT16(scr->buf + location.offset - 2); scr->_codeBlocks.push_back(cb); } void SegManager::scriptRelocate(reg_t block) { Script *scr = getScript(block.segment); VERIFY(block.offset < (uint16)scr->buf_size && READ_LE_UINT16(scr->buf + block.offset) * 2 + block.offset < (uint16)scr->buf_size, "Relocation block outside of script\n"); int count = READ_LE_UINT16(scr->buf + block.offset); for (int i = 0; i <= count; i++) { int pos = READ_LE_UINT16(scr->buf + block.offset + 2 + (i * 2)); if (!pos) continue; // FIXME: A hack pending investigation if (!relocateLocal(scr, block.segment, pos)) { bool done = false; uint k; for (k = 0; !done && k < scr->_objects.size(); k++) { if (relocateObject(&scr->_objects[k], block.segment, pos)) done = true; } for (k = 0; !done && k < scr->_codeBlocks.size(); k++) { if (pos >= scr->_codeBlocks[k].pos.offset && pos < scr->_codeBlocks[k].pos.offset + scr->_codeBlocks[k].size) done = true; } if (!done) { sciprintf("While processing relocation block %04x:%04x:\n", PRINT_REG(block)); sciprintf("Relocation failed for index %04x (%d/%d)\n", pos, i + 1, count); if (scr->locals_block) sciprintf("- locals: %d at %04x\n", scr->locals_block->_locals.size(), scr->locals_offset); else sciprintf("- No locals\n"); for (k = 0; k < scr->_objects.size(); k++) sciprintf("- obj#%d at %04x w/ %d vars\n", k, scr->_objects[k].pos.offset, scr->_objects[k]._variables.size()); // SQ3 script 71 has broken relocation entries. // Since this is mainstream, we can't break out as we used to do. sciprintf("Trying to continue anyway...\n"); // BREAKPOINT(); } } } } void SegManager::heapRelocate(reg_t block) { Script *scr = getScript(block.segment); VERIFY(block.offset < (uint16)scr->heap_size && READ_LE_UINT16(scr->heap_start + block.offset) * 2 + block.offset < (uint16)scr->buf_size, "Relocation block outside of script\n"); if (scr->relocated) return; scr->relocated = 1; int count = READ_LE_UINT16(scr->heap_start + block.offset); for (int i = 0; i < count; i++) { int pos = READ_LE_UINT16(scr->heap_start + block.offset + 2 + (i * 2)) + scr->script_size; if (!relocateLocal(scr, block.segment, pos)) { bool done = false; uint k; for (k = 0; !done && k < scr->_objects.size(); k++) { if (relocateObject(&scr->_objects[k], block.segment, pos)) done = true; } if (!done) { sciprintf("While processing relocation block %04x:%04x:\n", PRINT_REG(block)); sciprintf("Relocation failed for index %04x (%d/%d)\n", pos, i + 1, count); if (scr->locals_block) sciprintf("- locals: %d at %04x\n", scr->locals_block->_locals.size(), scr->locals_offset); else sciprintf("- No locals\n"); for (k = 0; k < scr->_objects.size(); k++) sciprintf("- obj#%d at %04x w/ %d vars\n", k, scr->_objects[k].pos.offset, scr->_objects[k]._variables.size()); sciprintf("Triggering breakpoint...\n"); BREAKPOINT(); } } } } #define INST_LOOKUP_CLASS(id) ((id == 0xffff) ? NULL_REG : get_class_address(s, id, SCRIPT_GET_LOCK, NULL_REG)) reg_t get_class_address(EngineState *s, int classnr, int lock, reg_t caller); Object *SegManager::scriptObjInit0(EngineState *s, reg_t obj_pos) { Object *obj; int id; unsigned int base = obj_pos.offset - SCRIPT_OBJECT_MAGIC_OFFSET; reg_t temp; Script *scr = getScript(obj_pos.segment); VERIFY(base < scr->buf_size, "Attempt to initialize object beyond end of script\n"); temp = make_reg(obj_pos.segment, base); id = scr->obj_indices->checkKey(base, true); if ((uint)id == scr->_objects.size()) scr->_objects.push_back(Object()); obj = &scr->_objects[id]; VERIFY(base + SCRIPT_FUNCTAREAPTR_OFFSET < scr->buf_size, "Function area pointer stored beyond end of script\n"); { byte *data = (byte *)(scr->buf + base); int funct_area = READ_LE_UINT16(data + SCRIPT_FUNCTAREAPTR_OFFSET); int variables_nr; int functions_nr; int is_class; int i; obj->flags = 0; obj->pos = temp; VERIFY(base + funct_area < scr->buf_size, "Function area pointer references beyond end of script"); variables_nr = READ_LE_UINT16(data + SCRIPT_SELECTORCTR_OFFSET); functions_nr = READ_LE_UINT16(data + funct_area - 2); is_class = READ_LE_UINT16(data + SCRIPT_INFO_OFFSET) & SCRIPT_INFO_CLASS; VERIFY(base + funct_area + functions_nr * 2 // add again for classes, since those also store selectors + (is_class ? functions_nr * 2 : 0) < scr->buf_size, "Function area extends beyond end of script"); obj->_variables.resize(variables_nr); obj->methods_nr = functions_nr; obj->base = scr->buf; obj->base_obj = data; obj->base_method = (uint16 *)(data + funct_area); obj->base_vars = NULL; for (i = 0; i < variables_nr; i++) obj->_variables[i] = make_reg(0, READ_LE_UINT16(data + (i * 2))); } return obj; } Object *SegManager::scriptObjInit11(EngineState *s, reg_t obj_pos) { Object *obj; int id; int base = obj_pos.offset; Script *scr = getScript(obj_pos.segment); VERIFY(base < (uint16)scr->buf_size, "Attempt to initialize object beyond end of script\n"); id = scr->obj_indices->checkKey(obj_pos.offset, true); if ((uint)id == scr->_objects.size()) scr->_objects.push_back(Object()); obj = &scr->_objects[id]; VERIFY(base + SCRIPT_FUNCTAREAPTR_OFFSET < (uint16)scr->buf_size, "Function area pointer stored beyond end of script\n"); { byte *data = (byte *)(scr->buf + base); uint16 *funct_area = (uint16 *)(scr->buf + READ_LE_UINT16(data + 6)); uint16 *prop_area = (uint16 *)(scr->buf + READ_LE_UINT16(data + 4)); int variables_nr; int functions_nr; int is_class; int i; obj->flags = 0; obj->pos = obj_pos; VERIFY((byte *) funct_area < scr->buf + scr->buf_size, "Function area pointer references beyond end of script"); variables_nr = READ_LE_UINT16(data + 2); functions_nr = READ_LE_UINT16(funct_area); is_class = READ_LE_UINT16(data + 14) & SCRIPT_INFO_CLASS; obj->base_method = funct_area; obj->base_vars = prop_area; VERIFY(((byte *) funct_area + functions_nr) < scr->buf + scr->buf_size, "Function area extends beyond end of script"); obj->variable_names_nr = variables_nr; obj->_variables.resize(variables_nr); obj->methods_nr = functions_nr; obj->base = scr->buf; obj->base_obj = data; for (i = 0; i < variables_nr; i++) obj->_variables[i] = make_reg(0, READ_LE_UINT16(data + (i * 2))); } return obj; } Object *SegManager::scriptObjInit(EngineState *s, reg_t obj_pos) { if (!isSci1_1) return scriptObjInit0(s, obj_pos); else return scriptObjInit11(s, obj_pos); } LocalVariables *SegManager::allocLocalsSegment(Script *scr, int count) { if (!count) { // No locals scr->locals_segment = 0; scr->locals_block = NULL; return NULL; } else { LocalVariables *locals; if (scr->locals_segment) { locals = (LocalVariables *)_heap[scr->locals_segment]; VERIFY(locals != NULL, "Re-used locals segment was NULL'd out"); VERIFY(locals->getType() == MEM_OBJ_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 *)allocNonscriptSegment(MEM_OBJ_LOCALS, &scr->locals_segment); scr->locals_block = locals; locals->script_id = scr->nr; locals->_locals.resize(count); return locals; } } void SegManager::scriptInitialiseLocalsZero(SegmentId seg, int count) { Script *scr = getScript(seg); scr->locals_offset = -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->buf_size, "Locals beyond end of script\n"); if (isSci1_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->locals_offset = location.offset; if (!(location.offset + count * 2 + 1 < scr->buf_size)) { sciprintf("Locals extend beyond end of script: offset %04x, count %x vs size %x\n", location.offset, count, (uint)scr->buf_size); count = (scr->buf_size - 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->exports_nr; 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->export_table + i)); if ((location < scr->heap_size - 1) && (READ_LE_UINT16(scr->heap_start + location) == SCRIPT_OBJECT_MAGIC_NUMBER)) { WRITE_LE_UINT16((byte *)(scr->export_table + i), location + scr->heap_start - scr->buf); } else { // Otherwise it's probably a function export, // and we don't need to do anything. } } } void SegManager::scriptInitialiseObjectsSci11(EngineState *s, SegmentId seg) { Script *scr = getScript(seg); byte *seeker = scr->heap_start + 4 + READ_LE_UINT16(scr->heap_start + 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)s->_classtable.size()) { sciprintf("Invalid species %d(0x%x) not in interval [0,%d) while instantiating script %d\n", species, species, s->_classtable.size(), scr->nr); script_debug_flag = script_error_flag = 1; return; } s->_classtable[species].script = scr->nr; s->_classtable[species].reg.segment = seg; s->_classtable[species].reg.offset = classpos; } seeker += READ_LE_UINT16(seeker + 2) * 2; } seeker = scr->heap_start + 4 + READ_LE_UINT16(scr->heap_start + 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 = scriptObjInit(s, reg); #if 0 if (obj->_variables[5].offset != 0xffff) { obj->_variables[5] = INST_LOOKUP_CLASS(obj->_variables[5].offset); base_obj = obj_get(s, obj->_variables[5]); obj->variable_names_nr = base_obj->variables_nr; obj->base_obj = base_obj->base_obj; } #endif // Copy base from species class, as we need its selector IDs obj->_variables[SCRIPT_SUPERCLASS_SELECTOR] = INST_LOOKUP_CLASS(obj->_variables[SCRIPT_SUPERCLASS_SELECTOR].offset); // 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->_variables[SCRIPT_CLASSSCRIPT_SELECTOR] = 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) { MemObject *mobj = allocNonscriptSegment(MEM_OBJ_STACK, segid); DataStack *retval = (DataStack *)mobj; retval->entries = (reg_t *)calloc(size, sizeof(reg_t)); retval->nr = size; return retval; } SystemStrings *SegManager::allocateSysStrings(SegmentId *segid) { return (SystemStrings *)allocNonscriptSegment(MEM_OBJ_SYS_STRINGS, segid); } SegmentId SegManager::allocateStringFrags() { SegmentId segid; allocNonscriptSegment(MEM_OBJ_STRING_FRAG, &segid); return segid; } uint16 SegManager::validateExportFunc(int pubfunct, SegmentId seg) { Script *scr = getScript(seg); if (scr->exports_nr <= pubfunct) { sciprintf("pubfunct is invalid"); return 0; } if (exports_wide) pubfunct *= 2; uint16 offset = READ_LE_UINT16((byte *)(scr->export_table + pubfunct)); VERIFY(offset < scr->buf_size, "invalid export function pointer"); return offset; } void SegManager::free_hunk_entry(reg_t addr) { HunkTable *ht = (HunkTable *)GET_SEGMENT(*this, addr.segment, MEM_OBJ_HUNK); if (!ht) { sciprintf("Attempt to free Hunk from address %04x:%04x: Invalid segment type\n", PRINT_REG(addr)); return; } ht->freeEntry(addr.offset); } Hunk *SegManager::alloc_hunk_entry(const char *hunk_type, int size, reg_t *reg) { Hunk *h = alloc_Hunk(reg); if (!h) return NULL; h->mem = malloc(size); h->size = size; h->type = hunk_type; return h; } Clone *SegManager::alloc_Clone(reg_t *addr) { CloneTable *table; int offset; if (!Clones_seg_id) { table = (CloneTable *)allocNonscriptSegment(MEM_OBJ_CLONES, &(Clones_seg_id)); } else table = (CloneTable *)_heap[Clones_seg_id]; offset = table->allocEntry(); *addr = make_reg(Clones_seg_id, offset); return &(table->_table[offset]); } List *SegManager::alloc_List(reg_t *addr) { ListTable *table; int offset; if (!Lists_seg_id) allocNonscriptSegment(MEM_OBJ_LISTS, &(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::alloc_Node(reg_t *addr) { NodeTable *table; int offset; if (!Nodes_seg_id) allocNonscriptSegment(MEM_OBJ_NODES, &(Nodes_seg_id)); table = (NodeTable *)_heap[Nodes_seg_id]; offset = table->allocEntry(); *addr = make_reg(Nodes_seg_id, offset); return &(table->_table[offset]); } Hunk *SegManager::alloc_Hunk(reg_t *addr) { HunkTable *table; int offset; if (!Hunks_seg_id) allocNonscriptSegment(MEM_OBJ_HUNK, &(Hunks_seg_id)); table = (HunkTable *)_heap[Hunks_seg_id]; offset = table->allocEntry(); *addr = make_reg(Hunks_seg_id, offset); return &(table->_table[offset]); } byte *MemObject::dereference(reg_t pointer, int *size) { error("Error: Trying to dereference pointer %04x:%04x to inappropriate segment", PRINT_REG(pointer)); return NULL; } byte *Script::dereference(reg_t pointer, int *size) { if (pointer.offset > buf_size) { sciprintf("Error: Attempt to dereference invalid pointer %04x:%04x into script segment (script size=%d)\n", PRINT_REG(pointer), (uint)buf_size); return NULL; } if (size) *size = buf_size - pointer.offset; return (byte *)(buf + pointer.offset); } byte *LocalVariables::dereference(reg_t pointer, int *size) { // FIXME: The following doesn't seem to be endian safe. // To fix this, we'd have to always treat the reg_t // values stored here as in the little endian format. int count = _locals.size() * sizeof(reg_t); byte *base = (byte *)&_locals[0]; if (size) *size = count; return base + pointer.offset; } byte *DataStack::dereference(reg_t pointer, int *size) { int count = nr * sizeof(reg_t); byte *base = (byte *)entries; if (size) *size = count; return base + pointer.offset; } byte *DynMem::dereference(reg_t pointer, int *size) { int count = _size; byte *base = (byte *)_buf; if (size) *size = count; return base + pointer.offset; } byte *SystemStrings::dereference(reg_t pointer, int *size) { if (size) *size = strings[pointer.offset].max_size; if (pointer.offset < SYS_STRINGS_MAX && strings[pointer.offset].name) return (byte *)(strings[pointer.offset].value); // This occurs in KQ5CD when interacting with certain objects warning("Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); return NULL; } byte *SegManager::dereference(reg_t pointer, int *size) { if (!pointer.segment || (pointer.segment >= _heap.size()) || !_heap[pointer.segment]) { // This occurs in KQ5CD when interacting with certain objects warning("Attempt to dereference invalid pointer %04x:%04x", PRINT_REG(pointer)); return NULL; /* Invalid */ } MemObject *mobj = _heap[pointer.segment]; return mobj->dereference(pointer, size); } unsigned char *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) { SegmentId seg; MemObject *mobj = allocNonscriptSegment(MEM_OBJ_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 = strdup(descr); return (unsigned char *)(d._buf); } const char *SegManager::getDescription(reg_t addr) { if (addr.segment >= _heap.size()) return ""; MemObject *mobj = _heap[addr.segment]; switch (mobj->getType()) { case MEM_OBJ_DYNMEM: return (*(DynMem *)mobj)._description; default: return ""; } } int SegManager::freeDynmem(reg_t addr) { if (addr.segment <= 0 || addr.segment >= _heap.size() || !_heap[addr.segment] || _heap[addr.segment]->getType() != MEM_OBJ_DYNMEM) return 1; // error deallocate(addr.segment, true); return 0; // OK } void SegManager::dbgPrint(const char* msg, void *i) { #ifdef DEBUG_SEG_MANAGER char buf[1000]; sprintf(buf, "%s = [0x%x], dec:[%d]", msg, i, i); perror(buf); #endif } //-------------------- script -------------------- reg_t Script::findCanonicAddress(SegManager *segmgr, reg_t addr) { addr.offset = 0; return addr; } void Script::freeAtAddress(SegManager *segmgr, reg_t addr) { /* sciprintf("[GC] Freeing script %04x:%04x\n", PRINT_REG(addr)); if (locals_segment) sciprintf("[GC] Freeing locals %04x:0000\n", locals_segment); */ if (_markedAsDeleted) segmgr->deallocateScript(nr); } void Script::listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) { (*note)(param, make_reg(segId, 0)); } void Script::listAllOutgoingReferences(EngineState *s, reg_t addr, void *param, NoteCallback note) { Script *script = this; if (addr.offset <= script->buf_size && addr.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && RAW_IS_OBJECT(script->buf + addr.offset)) { int idx = RAW_GET_CLASS_INDEX(script, addr); if (idx >= 0 && (uint)idx < script->_objects.size()) { // Note all local variables, if we have a local variable environment if (script->locals_segment) (*note)(param, make_reg(script->locals_segment, 0)); Object &obj = script->_objects[idx]; for (uint i = 0; i < obj._variables.size(); i++) (*note)(param, obj._variables[i]); } else { warning("Request for outgoing script-object reference at %04x:%04x yielded invalid index %d", PRINT_REG(addr), idx); } } else { /* fprintf(stderr, "Unexpected request for outgoing script-object references at %04x:%04x\n", PRINT_REG(addr));*/ /* Happens e.g. when we're looking into strings */ } } //-------------------- clones -------------------- template void Table::listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) { for (uint i = 0; i < _table.size(); i++) if (isValidEntry(i)) (*note)(param, make_reg(segId, i)); } void CloneTable::listAllOutgoingReferences(EngineState *s, reg_t addr, void *param, NoteCallback note) { CloneTable *clone_table = this; Clone *clone; // assert(addr.segment == _segId); if (!clone_table->isValidEntry(addr.offset)) { fprintf(stderr, "Unexpected request for outgoing references from clone at %04x:%04x\n", PRINT_REG(addr)); // BREAKPOINT(); return; } clone = &(clone_table->_table[addr.offset]); // Emit all member variables (including references to the 'super' delegate) for (uint i = 0; i < clone->_variables.size(); i++) (*note)(param, clone->_variables[i]); // Note that this also includes the 'base' object, which is part of the script and therefore also emits the locals. (*note)(param, clone->pos); //sciprintf("[GC] Reporting clone-pos %04x:%04x\n", PRINT_REG(clone->pos)); } void CloneTable::freeAtAddress(SegManager *segmgr, reg_t addr) { CloneTable *clone_table = this; Object *victim_obj; // assert(addr.segment == _segId); victim_obj = &(clone_table->_table[addr.offset]); #ifdef GC_DEBUG if (!(victim_obj->flags & OBJECT_FLAG_FREED)) sciprintf("[GC] Warning: Clone %04x:%04x not reachable and not freed (freeing now)\n", PRINT_REG(addr)); #ifdef GC_DEBUG_VERBOSE else sciprintf("[GC-DEBUG] Clone %04x:%04x: Freeing\n", PRINT_REG(addr)); #endif #endif /* sciprintf("[GC] Clone %04x:%04x: Freeing\n", PRINT_REG(addr)); sciprintf("[GC] Clone had pos %04x:%04x\n", PRINT_REG(victim_obj->pos)); */ clone_table->freeEntry(addr.offset); } //-------------------- locals -------------------- reg_t LocalVariables::findCanonicAddress(SegManager *segmgr, reg_t addr) { // Reference the owning script SegmentId owner_seg = segmgr->segGet(script_id); assert(owner_seg >= 0); return make_reg(owner_seg, 0); } void LocalVariables::listAllOutgoingReferences(EngineState *s, reg_t addr, void *param, NoteCallback note) { // assert(addr.segment == _segId); for (uint i = 0; i < _locals.size(); i++) (*note)(param, _locals[i]); } //-------------------- stack -------------------- reg_t DataStack::findCanonicAddress(SegManager *segmgr, reg_t addr) { addr.offset = 0; return addr; } void DataStack::listAllOutgoingReferences(EngineState *s, reg_t addr, void *param, NoteCallback note) { fprintf(stderr, "Emitting %d stack entries\n", nr); for (int i = 0; i < nr; i++) (*note)(param, entries[i]); fprintf(stderr, "DONE"); } //-------------------- lists -------------------- void ListTable::freeAtAddress(SegManager *segmgr, reg_t sub_addr) { freeEntry(sub_addr.offset); } void ListTable::listAllOutgoingReferences(EngineState *s, reg_t addr, void *param, NoteCallback note) { if (!isValidEntry(addr.offset)) { warning("Invalid list referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); return; } List *list = &(_table[addr.offset]); note(param, list->first); note(param, list->last); // We could probably get away with just one of them, but // let's be conservative here. } //-------------------- nodes -------------------- void NodeTable::freeAtAddress(SegManager *segmgr, reg_t sub_addr) { freeEntry(sub_addr.offset); } void NodeTable::listAllOutgoingReferences(EngineState *s, reg_t addr, void *param, NoteCallback note) { if (!isValidEntry(addr.offset)) { warning("Invalid node referenced for outgoing references: %04x:%04x", PRINT_REG(addr)); return; } Node *node = &(_table[addr.offset]); // We need all four here. Can't just stick with 'pred' OR 'succ' because node operations allow us // to walk around from any given node note(param, node->pred); note(param, node->succ); note(param, node->key); note(param, node->value); } //-------------------- hunk -------------------- //-------------------- dynamic memory -------------------- reg_t DynMem::findCanonicAddress(SegManager *segmgr, reg_t addr) { addr.offset = 0; return addr; } void DynMem::listAllDeallocatable(SegmentId segId, void *param, NoteCallback note) { (*note)(param, make_reg(segId, 0)); } } // End of namespace Sci