SCI: Moved all the script-related code inside script.cpp/.h, and all script opcode-related code inside vm.cpp/.h
svn-id: r50396
This commit is contained in:
parent
0436f2823d
commit
bb992b0b93
9 changed files with 817 additions and 790 deletions
|
@ -35,127 +35,353 @@
|
|||
|
||||
namespace Sci {
|
||||
|
||||
#define END Script_None
|
||||
Script::Script() : SegmentObj(SEG_TYPE_SCRIPT) {
|
||||
_nr = 0;
|
||||
_buf = NULL;
|
||||
_bufSize = 0;
|
||||
_scriptSize = 0;
|
||||
_heapSize = 0;
|
||||
|
||||
opcode_format g_opcode_formats[128][4] = {
|
||||
/*00*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_None},
|
||||
/*04*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_None},
|
||||
/*08*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_None},
|
||||
/*0C*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_None},
|
||||
/*10*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_None},
|
||||
/*14*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END},
|
||||
/*18*/
|
||||
{Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None},
|
||||
/*1C*/
|
||||
{Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END},
|
||||
/*20*/
|
||||
{Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END},
|
||||
/*24 (24=ret)*/
|
||||
{Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid},
|
||||
/*28*/
|
||||
{Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END},
|
||||
/*2C*/
|
||||
{Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid},
|
||||
/*30*/
|
||||
{Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END},
|
||||
/*34*/
|
||||
{Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END},
|
||||
/*38*/
|
||||
{Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None},
|
||||
/*3C*/
|
||||
{Script_None}, {Script_None}, {Script_None}, {Script_Word},
|
||||
/*40-4F*/
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
/*50-5F*/
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
/*60-6F*/
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
/*70-7F*/
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
|
||||
{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}
|
||||
};
|
||||
#undef END
|
||||
_synonyms = NULL;
|
||||
_heapStart = NULL;
|
||||
_exportTable = NULL;
|
||||
|
||||
// TODO: script_adjust_opcode_formats should probably be part of the
|
||||
// constructor (?) of a VirtualMachine or a ScriptManager class.
|
||||
void script_adjust_opcode_formats() {
|
||||
if (g_sci->_features->detectLofsType() != SCI_VERSION_0_EARLY) {
|
||||
g_opcode_formats[op_lofsa][0] = Script_Offset;
|
||||
g_opcode_formats[op_lofss][0] = Script_Offset;
|
||||
}
|
||||
_localsOffset = 0;
|
||||
_localsSegment = 0;
|
||||
_localsBlock = NULL;
|
||||
_localsCount = 0;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// In SCI32, some arguments are now words instead of bytes
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
g_opcode_formats[op_calle][2] = Script_Word;
|
||||
g_opcode_formats[op_callk][1] = Script_Word;
|
||||
g_opcode_formats[op_super][1] = Script_Word;
|
||||
g_opcode_formats[op_send][0] = Script_Word;
|
||||
g_opcode_formats[op_self][0] = Script_Word;
|
||||
g_opcode_formats[op_call][1] = Script_Word;
|
||||
g_opcode_formats[op_callb][1] = Script_Word;
|
||||
}
|
||||
#endif
|
||||
_markedAsDeleted = false;
|
||||
}
|
||||
|
||||
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);
|
||||
Script::~Script() {
|
||||
freeScript();
|
||||
}
|
||||
|
||||
reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) {
|
||||
if (classnr == 0xffff)
|
||||
return NULL_REG;
|
||||
void Script::freeScript() {
|
||||
free(_buf);
|
||||
_buf = NULL;
|
||||
_bufSize = 0;
|
||||
|
||||
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;
|
||||
_objects.clear();
|
||||
}
|
||||
|
||||
void Script::init(int script_nr, ResourceManager *resMan) {
|
||||
Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0);
|
||||
|
||||
_localsOffset = 0;
|
||||
_localsBlock = NULL;
|
||||
_localsCount = 0;
|
||||
|
||||
_markedAsDeleted = false;
|
||||
|
||||
_nr = script_nr;
|
||||
_buf = 0;
|
||||
_heapStart = 0;
|
||||
|
||||
_scriptSize = script->size;
|
||||
_bufSize = script->size;
|
||||
_heapSize = 0;
|
||||
|
||||
_lockers = 1;
|
||||
|
||||
if (getSciVersion() == SCI_VERSION_0_EARLY) {
|
||||
_bufSize += READ_LE_UINT16(script->data) * 2;
|
||||
} else if (getSciVersion() >= SCI_VERSION_1_1) {
|
||||
/**
|
||||
* In SCI11, the heap was in a separate space from the script.
|
||||
* We append it to the end of the script, and adjust addressing accordingly.
|
||||
* However, since we address the heap with a 16-bit pointer, the combined
|
||||
* size of the stack and the heap must be 64KB. So far this has worked
|
||||
* for SCI11, SCI2 and SCI21 games. SCI3 games use a different script format,
|
||||
* and theoretically they can exceed the 64KB boundary using relocation.
|
||||
*/
|
||||
Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, script_nr), 0);
|
||||
_bufSize += heap->size;
|
||||
_heapSize = heap->size;
|
||||
|
||||
// Ensure that the start of the heap resource can be word-aligned.
|
||||
if (script->size & 2) {
|
||||
_bufSize++;
|
||||
_scriptSize++;
|
||||
}
|
||||
|
||||
// As mentioned above, the script and the heap together should not exceed 64KB
|
||||
if (script->size + heap->size > 65535)
|
||||
error("Script and heap sizes combined exceed 64K. This means a fundamental "
|
||||
"design bug was made regarding SCI1.1 and newer games.\nPlease "
|
||||
"report this error to the ScummVM team");
|
||||
}
|
||||
}
|
||||
|
||||
void Script::load(ResourceManager *resMan) {
|
||||
Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0);
|
||||
assert(script != 0);
|
||||
|
||||
_buf = (byte *)malloc(_bufSize);
|
||||
assert(_buf);
|
||||
|
||||
assert(_bufSize >= script->size);
|
||||
memcpy(_buf, script->data, script->size);
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_1_1) {
|
||||
Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0);
|
||||
assert(heap != 0);
|
||||
|
||||
_heapStart = _buf + _scriptSize;
|
||||
|
||||
assert(_bufSize - _scriptSize <= heap->size);
|
||||
memcpy(_heapStart, heap->data, heap->size);
|
||||
}
|
||||
|
||||
_exportTable = 0;
|
||||
_numExports = 0;
|
||||
_synonyms = 0;
|
||||
_numSynonyms = 0;
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_1_1) {
|
||||
if (READ_LE_UINT16(_buf + 1 + 5) > 0) { // does the script have an export table?
|
||||
_exportTable = (const uint16 *)(_buf + 1 + 5 + 2);
|
||||
_numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1);
|
||||
}
|
||||
_localsOffset = _scriptSize + 4;
|
||||
_localsCount = READ_SCI11ENDIAN_UINT16(_buf + _localsOffset - 2);
|
||||
} 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;", 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;
|
||||
_exportTable = (const uint16 *)findBlock(SCI_OBJ_EXPORTS);
|
||||
if (_exportTable) {
|
||||
_numExports = READ_SCI11ENDIAN_UINT16(_exportTable + 1);
|
||||
_exportTable += 3; // skip header plus 2 bytes (_exportTable is a uint16 pointer)
|
||||
}
|
||||
_synonyms = findBlock(SCI_OBJ_SYNONYMS);
|
||||
if (_synonyms) {
|
||||
_numSynonyms = READ_SCI11ENDIAN_UINT16(_synonyms + 2) / 4;
|
||||
_synonyms += 4; // skip header
|
||||
}
|
||||
const byte* localsBlock = findBlock(SCI_OBJ_LOCALVARS);
|
||||
if (localsBlock) {
|
||||
_localsOffset = localsBlock - _buf + 4;
|
||||
_localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1; // half block size
|
||||
}
|
||||
}
|
||||
|
||||
if (getSciVersion() > SCI_VERSION_0_EARLY) {
|
||||
// Does the script actually have locals? If not, set the locals offset to 0
|
||||
if (!_localsCount)
|
||||
_localsOffset = 0;
|
||||
|
||||
if (_localsOffset + _localsCount * 2 + 1 >= (int)_bufSize) {
|
||||
error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, _bufSize);
|
||||
_localsCount = (_bufSize - _localsOffset) >> 1;
|
||||
}
|
||||
} else {
|
||||
// Old script block. There won't be a localvar block in this case.
|
||||
// Instead, the script starts with a 16 bit int specifying the
|
||||
// number of locals we need; these are then allocated and zeroed.
|
||||
_localsCount = READ_LE_UINT16(_buf);
|
||||
_localsOffset = -_localsCount * 2; // Make sure it's invalid
|
||||
}
|
||||
}
|
||||
|
||||
Object *Script::allocateObject(uint16 offset) {
|
||||
return &_objects[offset];
|
||||
}
|
||||
|
||||
Object *Script::getObject(uint16 offset) {
|
||||
if (_objects.contains(offset))
|
||||
return &_objects[offset];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const Object *Script::getObject(uint16 offset) const {
|
||||
if (_objects.contains(offset))
|
||||
return &_objects[offset];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
Object *Script::scriptObjInit(reg_t obj_pos, bool fullObjectInit) {
|
||||
Object *obj;
|
||||
|
||||
if (getSciVersion() < SCI_VERSION_1_1 && fullObjectInit)
|
||||
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 + kOffsetFunctionArea < (int)_bufSize, "Function area pointer stored beyond end of script\n");
|
||||
|
||||
obj->init(_buf, obj_pos, fullObjectInit);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void Script::scriptObjRemove(reg_t obj_pos) {
|
||||
if (getSciVersion() < SCI_VERSION_1_1)
|
||||
obj_pos.offset += 8;
|
||||
|
||||
_objects.erase(obj_pos.toUint16());
|
||||
}
|
||||
|
||||
// This helper function is used by Script::relocateLocal and Object::relocate
|
||||
// Duplicate in segment.cpp and script.cpp
|
||||
static bool relocateBlock(Common::Array<reg_t> &block, int block_location, SegmentId segment, int location, size_t scriptSize) {
|
||||
int rel = location - block_location;
|
||||
|
||||
if (rel < 0)
|
||||
return false;
|
||||
|
||||
uint idx = rel >> 1;
|
||||
|
||||
if (idx >= block.size())
|
||||
return false;
|
||||
|
||||
if (rel & 1) {
|
||||
error("Attempt to relocate odd variable #%d.5e (relative to %04x)\n", idx, block_location);
|
||||
return false;
|
||||
}
|
||||
block[idx].segment = segment; // Perform relocation
|
||||
if (getSciVersion() >= SCI_VERSION_1_1)
|
||||
block[idx].offset += scriptSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Script::relocateLocal(SegmentId segment, int location) {
|
||||
if (_localsBlock)
|
||||
return relocateBlock(_localsBlock->_locals, _localsOffset, segment, location, _scriptSize);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void Script::relocate(reg_t block) {
|
||||
byte *heap = _buf;
|
||||
uint16 heapSize = (uint16)_bufSize;
|
||||
uint16 heapOffset = 0;
|
||||
|
||||
if (getSciVersion() >= SCI_VERSION_1_1) {
|
||||
heap = _heapStart;
|
||||
heapSize = (uint16)_heapSize;
|
||||
heapOffset = _scriptSize;
|
||||
}
|
||||
|
||||
VERIFY(block.offset < (uint16)heapSize && READ_SCI11ENDIAN_UINT16(heap + block.offset) * 2 + block.offset < (uint16)heapSize,
|
||||
"Relocation block outside of script\n");
|
||||
|
||||
int count = READ_SCI11ENDIAN_UINT16(heap + block.offset);
|
||||
int exportIndex = 0;
|
||||
int pos = 0;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset;
|
||||
// This occurs in SCI01/SCI1 games where usually one export value
|
||||
// is zero. It seems that in this situation, we should skip the
|
||||
// export and move to the next one, though the total count of valid
|
||||
// exports remains the same
|
||||
if (!pos) {
|
||||
exportIndex++;
|
||||
pos = READ_SCI11ENDIAN_UINT16(heap + block.offset + 2 + (exportIndex * 2)) + heapOffset;
|
||||
if (!pos)
|
||||
error("Script::relocate(): Consecutive zero exports found");
|
||||
}
|
||||
|
||||
// In SCI0-SCI1, script local variables, objects and code are relocated. We only relocate
|
||||
// locals and objects here, and ignore relocation of code blocks. In SCI1.1 and newer
|
||||
// versions, only locals and objects are relocated.
|
||||
if (!relocateLocal(block.segment, pos)) {
|
||||
// Not a local? It's probably an object or code block. If it's an object, relocate it.
|
||||
const ObjMap::iterator end = _objects.end();
|
||||
for (ObjMap::iterator it = _objects.begin(); it != end; ++it)
|
||||
if (it->_value.relocate(block.segment, pos, _scriptSize))
|
||||
break;
|
||||
}
|
||||
|
||||
exportIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void Script::incrementLockers() {
|
||||
_lockers++;
|
||||
}
|
||||
|
||||
void Script::decrementLockers() {
|
||||
if (_lockers > 0)
|
||||
_lockers--;
|
||||
}
|
||||
|
||||
int Script::getLockers() const {
|
||||
return _lockers;
|
||||
}
|
||||
|
||||
void Script::setLockers(int lockers) {
|
||||
_lockers = lockers;
|
||||
}
|
||||
|
||||
uint16 Script::validateExportFunc(int pubfunct) {
|
||||
bool exportsAreWide = (g_sci->_features->detectLofsType() == SCI_VERSION_1_MIDDLE);
|
||||
|
||||
if (_numExports <= pubfunct) {
|
||||
error("validateExportFunc(): pubfunct is invalid");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (exportsAreWide)
|
||||
pubfunct *= 2;
|
||||
uint16 offset = READ_SCI11ENDIAN_UINT16(_exportTable + pubfunct);
|
||||
VERIFY(offset < _bufSize, "invalid export function pointer");
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
byte *Script::findBlock(int type) {
|
||||
byte *buf = _buf;
|
||||
bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
|
||||
|
||||
if (oldScriptHeader)
|
||||
buf += 2;
|
||||
|
||||
do {
|
||||
int seekerType = READ_LE_UINT16(buf);
|
||||
|
||||
if (seekerType == 0)
|
||||
break;
|
||||
if (seekerType == type)
|
||||
return buf;
|
||||
|
||||
int seekerSize = READ_LE_UINT16(buf + 2);
|
||||
assert(seekerSize > 0);
|
||||
buf += seekerSize;
|
||||
} while (1);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// memory operations
|
||||
|
||||
void Script::mcpyInOut(int dst, const void *src, size_t n) {
|
||||
if (_buf) {
|
||||
assert(dst + n <= _bufSize);
|
||||
memcpy(_buf + dst, src, n);
|
||||
}
|
||||
}
|
||||
|
||||
bool Script::isValidOffset(uint16 offset) const {
|
||||
return offset < _bufSize;
|
||||
}
|
||||
|
||||
SegmentRef Script::dereference(reg_t pointer) {
|
||||
if (pointer.offset > _bufSize) {
|
||||
error("Script::dereference(): Attempt to dereference invalid pointer %04x:%04x into script segment (script size=%d)",
|
||||
PRINT_REG(pointer), (uint)_bufSize);
|
||||
return SegmentRef();
|
||||
}
|
||||
|
||||
SegmentRef ret;
|
||||
ret.isRaw = true;
|
||||
ret.maxSize = _bufSize - pointer.offset;
|
||||
ret.raw = _buf + pointer.offset;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SegManager::scriptInitialiseLocals(SegmentId segmentId) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue