inline static void BTC_memory_account_mark(NewGC *gc, mpage *page, void *ptr) { GCDEBUG((DEBUGOUTF, "BTC_memory_account_mark: %p/%p\n", page, ptr)); if(page->size_class) { if(page->size_class > 1) { /* big page */ objhead *info = BIG_PAGE_TO_OBJHEAD(page); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, gcBYTES_TO_WORDS(page->size)); push_ptr(gc, TAG_AS_BIG_PAGE_PTR(ptr)); } } else { /* medium page */ objhead *info = MED_OBJHEAD(ptr, page->size); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size); ptr = OBJHEAD_TO_OBJPTR(info); push_ptr(gc, ptr); } } } else { objhead *info = OBJPTR_TO_OBJHEAD(ptr); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size); push_ptr(gc, ptr); } } }
inline static void BTC_memory_account_mark(NewGC *gc, mpage *page, void *ptr, int is_a_master_page) { GCDEBUG((DEBUGOUTF, "BTC_memory_account_mark: %p/%p\n", page, ptr)); /* In the case of is_a_master_page, whether this place is charged is a little random: there's no guarantee that the btc_mark values are in sync, and there are races among places. Approximations are ok for accounting, though, as long as the probably for completely wrong accounting is very low. At the same time, we need to synchronize enough so that two places with different new_btc_mark values don't send each other into infinite loops (with the btc_mark value bouncing back and forth) or overcounting. We synchronize enough by having a single new_btc_mark value for master pages, and we stall if the value isn't what this place wants. */ if (is_a_master_page) check_master_btc_mark(gc, page); if(page->size_class) { if(page->size_class > 1) { /* big page */ objhead *info = BIG_PAGE_TO_OBJHEAD(page); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, gcBYTES_TO_WORDS(page->size), is_a_master_page); push_ptr(gc, TAG_AS_BIG_PAGE_PTR(ptr)); } } else { /* medium page */ objhead *info = MED_OBJHEAD(ptr, page->size); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size, is_a_master_page); ptr = OBJHEAD_TO_OBJPTR(info); push_ptr(gc, ptr); } } } else { objhead *info = OBJPTR_TO_OBJHEAD(ptr); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size, 0); push_ptr(gc, ptr); } } }
inline static void BTC_memory_account_mark(NewGC *gc, mpage *page, void *ptr, int is_a_master_page) { GCDEBUG((DEBUGOUTF, "BTC_memory_account_mark: %p/%p\n", page, ptr)); /* In the case of is_a_master_page, whether this place is charged is a little random: there's no guarantee that the btc_mark values are in sync, and there are races among places. Approximations are ok for accounting, though, as long as the probably for completely wrong accounting is very low. */ if(page->size_class) { if(page->size_class > 1) { /* big page */ objhead *info = BIG_PAGE_TO_OBJHEAD(page); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, gcBYTES_TO_WORDS(page->size), is_a_master_page); push_ptr(gc, TAG_AS_BIG_PAGE_PTR(ptr)); } } else { /* medium page */ objhead *info = MED_OBJHEAD(ptr, page->size); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size, is_a_master_page); ptr = OBJHEAD_TO_OBJPTR(info); push_ptr(gc, ptr); } } } else { objhead *info = OBJPTR_TO_OBJHEAD(ptr); if(info->btc_mark == gc->old_btc_mark) { info->btc_mark = gc->new_btc_mark; account_memory(gc, gc->current_mark_owner, info->size, 0); push_ptr(gc, ptr); } } }
int preload(char *path, size_t *size) { int file = open(path, O_RDONLY); struct stat statrec; if (file < 0) { fprintf(stderr, "Failed to open file \"%s\" (%d)\n", path, errno); return 1; } if (fstat(file, &statrec) != 0) { close(file); fprintf(stderr, "Failed to stat file \"%s\" (%d)\n", path, errno); return 2; } size_t length = statrec.st_size; if (length == 0) { close(file); fprintf(stderr, "Skipping empty file \"%s\"\n", path); return 0; } void *map = mmap(NULL, length, PROT_READ | PROT_EXEC, MAP_SHARED, file, 0); if (map == MAP_FAILED) { close(file); fprintf(stderr, "Failed to map file \"%s\" (%d)\n", path, errno); return 3; } int mlock_error = mlock(map, length); if (mlock_error != 0) { close(file); print_mlock_error(path, errno); return 4; } close(file); push_ptr(map, length); *size = length; return 0; }
int Logic::runScript2(byte *scriptData, byte *objectData, byte *offsetPtr) { // Interestingly, unlike our BASS engine the stack is a local variable. // I don't know whether or not this is relevant to the working of the // BS2 engine. int32 stack[STACK_SIZE]; int32 stackPtr = 0; uint32 offset = READ_LE_UINT32(offsetPtr); ResHeader header; header.read(scriptData); scriptData += ResHeader::size() + ObjectHub::size(); // The script data format: // int32_TYPE 1 Size of variable space in bytes // ... The variable space // int32_TYPE 1 numberOfScripts // int32_TYPE numberOfScripts The offsets for each script // Initialise some stuff uint32 ip = 0; // Code pointer int scriptNumber; // Get the start of variables and start of code byte *localVars = scriptData + 4; byte *code = scriptData + READ_LE_UINT32(scriptData) + 4; uint32 noScripts = READ_LE_UINT32(code); code += 4; byte *offsetTable = code; if (offset < noScripts) { ip = READ_LE_UINT32(offsetTable + offset * 4); scriptNumber = offset; debug(8, "Starting script %d from %d", scriptNumber, ip); } else { uint i; ip = offset; for (i = 1; i < noScripts; i++) { if (READ_LE_UINT32(offsetTable + 4 * i) >= ip) break; } scriptNumber = i - 1; debug(8, "Resuming script %d from %d", scriptNumber, ip); } // There are a couple of known script bugs related to interacting with // certain objects. We try to work around a few of them. bool checkMopBug = false; bool checkPyramidBug = false; bool checkElevatorBug = false; if (scriptNumber == 2) { if (strcmp((char *)header.name, "mop_73") == 0) checkMopBug = true; else if (strcmp((char *)header.name, "titipoco_81") == 0) checkPyramidBug = true; else if (strcmp((char *)header.name, "lift_82") == 0) checkElevatorBug = true; } code += noScripts * 4; // Code should now be pointing at an identifier and a checksum byte *checksumBlock = code; code += 4 * 3; if (READ_LE_UINT32(checksumBlock) != 12345678) { error("Invalid script in object %s", header.name); return 0; } int32 codeLen = READ_LE_UINT32(checksumBlock + 4); int32 checksum = 0; for (int i = 0; i < codeLen; i++) checksum += (unsigned char) code[i]; if (checksum != (int32)READ_LE_UINT32(checksumBlock + 8)) { debug(1, "Checksum error in object %s", header.name); // This could be bad, but there has been a report about someone // who had problems running the German version because of // checksum errors. Could there be a version where checksums // weren't properly calculated? } bool runningScript = true; int parameterReturnedFromMcodeFunction = 0; // Allow scripts to return things int savedStartOfMcode = 0; // For saving start of mcode commands while (runningScript) { int i; int32 a, b; int curCommand, parameter, value; // Command and parameter variables int retVal; int caseCount; bool foundCase; byte *ptr; curCommand = code[ip++]; switch (curCommand) { // Script-related opcodes case CP_END_SCRIPT: // End the script runningScript = false; // WORKAROUND: The dreaded pyramid bug makes the torch // untakeable when you speak to Titipoco. This is // because one of the conditions for the torch to be // takeable is that Titipoco isn't doing anything out // of the ordinary. Global variable 913 has to be 0 to // signify that he is in his "idle" state. // // Unfortunately, simply the act of speaking to him // sets variable 913 to 1 (probably to stop him from // turning around every now and then). The script may // then go on to set the variable to different values // to trigger various behaviours in him, but if you // have run out of these cases the script won't ever // set it back to 0 again. // // So if his click hander finishes, and variable 913 is // 1, we set it back to 0 manually. if (checkPyramidBug && readVar(913) == 1) { warning("Working around pyramid bug: Resetting Titipoco's state"); writeVar(913, 0); } // WORKAROUND: The not-so-known-but-should-be-dreaded // elevator bug. // // The click handler for the top of the elevator only // handles using the elevator, not examining it. When // examining it, the mouse cursor is removed but never // restored. if (checkElevatorBug && readVar(RIGHT_BUTTON)) { warning("Working around elevator bug: Restoring mouse pointer"); fnAddHuman(NULL); } debug(9, "CP_END_SCRIPT"); break; case CP_QUIT: // Quit out for a cycle WRITE_LE_UINT32(offsetPtr, ip); debug(9, "CP_QUIT"); return 0; case CP_TERMINATE: // Quit out immediately without affecting the offset // pointer debug(9, "CP_TERMINATE"); return 3; case CP_RESTART_SCRIPT: // Start the script again ip = FROM_LE_32(offsetTable[scriptNumber]); debug(9, "CP_RESTART_SCRIPT"); break; // Stack-related opcodes case CP_PUSH_INT32: // Push a long word value on to the stack Read32ip(parameter); push(parameter); debug(9, "CP_PUSH_INT32: %d", parameter); break; case CP_PUSH_LOCAL_VAR32: // Push the contents of a local variable Read16ip(parameter); push(READ_LE_UINT32(localVars + parameter)); debug(9, "CP_PUSH_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, READ_LE_UINT32(localVars + parameter)); break; case CP_PUSH_GLOBAL_VAR32: // Push a global variable Read16ip(parameter); push(readVar(parameter)); debug(9, "CP_PUSH_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, readVar(parameter)); break; case CP_PUSH_LOCAL_ADDR: // Push the address of a local variable // From what I understand, some scripts store data // (e.g. mouse pointers) in their local variable space // from the very beginning, and use this mechanism to // pass that data to the opcode function. I don't yet // know the conceptual difference between this and the // CP_PUSH_DEREFERENCED_STRUCTURE opcode. Read16ip(parameter); push_ptr(localVars + parameter); debug(9, "CP_PUSH_LOCAL_ADDR: &localVars[%d] => %p", parameter / 4, localVars + parameter); break; case CP_PUSH_STRING: // Push the address of a string on to the stack // Get the string size Read8ip(parameter); // ip now points to the string ptr = code + ip; push_ptr(ptr); debug(9, "CP_PUSH_STRING: \"%s\"", ptr); ip += (parameter + 1); break; case CP_PUSH_DEREFERENCED_STRUCTURE: // Push the address of a dereferenced structure Read32ip(parameter); ptr = objectData + 4 + ResHeader::size() + ObjectHub::size() + parameter; push_ptr(ptr); debug(9, "CP_PUSH_DEREFERENCED_STRUCTURE: %d => %p", parameter, ptr); break; case CP_POP_LOCAL_VAR32: // Pop a value into a local word variable Read16ip(parameter); value = pop(); WRITE_LE_UINT32(localVars + parameter, value); debug(9, "CP_POP_LOCAL_VAR32: localVars[%d] = %d", parameter / 4, value); break; case CP_POP_GLOBAL_VAR32: // Pop a global variable Read16ip(parameter); value = pop(); // WORKAROUND for bug #1214168: The not-at-all dreaded // mop bug. // // At the London Docks, global variable 1003 keeps // track of Nico: // // 0: Hiding behind the first crate. // 1: Hiding behind the second crate. // 2: Standing in plain view on the deck. // 3: Hiding on the roof. // // The bug happens when trying to pick up the mop while // hiding on the roof. Nico climbs down, the mop is // picked up, but the variable remains set to 3. // Visually, everything looks ok. But as far as the // scripts are concerned, she's still hiding up on the // roof. This is not fatal, but leads to a number of // glitches until the state is corrected. E.g. trying // to climb back up the ladder will cause Nico to climb // down again. // // Global variable 1017 keeps track of the mop. Setting // it to 2 means that the mop has been picked up. We // use that as the signal that Nico's state needs to be // updated as well. if (checkMopBug && parameter == 1017 && readVar(1003) != 2) { warning("Working around mop bug: Setting Nico's state"); writeVar(1003, 2); } writeVar(parameter, value); debug(9, "CP_POP_GLOBAL_VAR32: scriptsVars[%d] = %d", parameter, value); break; case CP_ADDNPOP_LOCAL_VAR32: Read16ip(parameter); value = READ_LE_UINT32(localVars + parameter) + pop(); WRITE_LE_UINT32(localVars + parameter, value); debug(9, "CP_ADDNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value); break; case CP_SUBNPOP_LOCAL_VAR32: Read16ip(parameter); value = READ_LE_UINT32(localVars + parameter) - pop(); WRITE_LE_UINT32(localVars + parameter, value); debug(9, "CP_SUBNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value); break; case CP_ADDNPOP_GLOBAL_VAR32: // Add and pop a global variable Read16ip(parameter); value = readVar(parameter) + pop(); writeVar(parameter, value); debug(9, "CP_ADDNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value); break; case CP_SUBNPOP_GLOBAL_VAR32: // Sub and pop a global variable Read16ip(parameter); value = readVar(parameter) - pop(); writeVar(parameter, value); debug(9, "CP_SUBNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value); break; // Jump opcodes case CP_SKIPONTRUE: // Skip if the value on the stack is true Read32ipLeaveip(parameter); value = pop(); if (!value) { ip += 4; debug(9, "CP_SKIPONTRUE: %d (IS FALSE (NOT SKIPPED))", parameter); } else { ip += parameter; debug(9, "CP_SKIPONTRUE: %d (IS TRUE (SKIPPED))", parameter); } break; case CP_SKIPONFALSE: // Skip if the value on the stack is false Read32ipLeaveip(parameter); value = pop(); if (value) { ip += 4; debug(9, "CP_SKIPONFALSE: %d (IS TRUE (NOT SKIPPED))", parameter); } else { ip += parameter; debug(9, "CP_SKIPONFALSE: %d (IS FALSE (SKIPPED))", parameter); } break; case CP_SKIPALWAYS: // skip a block Read32ipLeaveip(parameter); ip += parameter; debug(9, "CP_SKIPALWAYS: %d", parameter); break; case CP_SWITCH: // switch value = pop(); Read32ip(caseCount); // Search the cases foundCase = false; for (i = 0; i < caseCount && !foundCase; i++) { if (value == (int32)READ_LE_UINT32(code + ip)) { // We have found the case, so lets // jump to it foundCase = true; ip += READ_LE_UINT32(code + ip + 4); } else ip += 4 * 2; } // If we found no matching case then use the default if (!foundCase) ip += READ_LE_UINT32(code + ip); debug(9, "CP_SWITCH: [SORRY, NO DEBUG INFO]"); break; case CP_SAVE_MCODE_START: // Save the start position on an mcode instruction in // case we need to restart it again savedStartOfMcode = ip - 1; debug(9, "CP_SAVE_MCODE_START"); break; case CP_CALL_MCODE: // Call an mcode routine Read16ip(parameter); assert(parameter < ARRAYSIZE(opcodes)); // amount to adjust stack by (no of parameters) Read8ip(value); debug(9, "CP_CALL_MCODE: '%s', %d", opcodes[parameter].desc, value); stackPtr -= value; assert(stackPtr >= 0); retVal = (this->*opcodes[parameter].proc)(&stack[stackPtr]); switch (retVal & 7) { case IR_STOP: // Quit out for a cycle WRITE_LE_UINT32(offsetPtr, ip); return 0; case IR_CONT: // Continue as normal break; case IR_TERMINATE: // Return without updating the offset return 2; case IR_REPEAT: // Return setting offset to start of this // function call WRITE_LE_UINT32(offsetPtr, savedStartOfMcode); return 0; case IR_GOSUB: // that's really neat WRITE_LE_UINT32(offsetPtr, ip); return 2; default: error("Bad return code (%d) from '%s'", retVal & 7, opcodes[parameter].desc); } parameterReturnedFromMcodeFunction = retVal >> 3; break; case CP_JUMP_ON_RETURNED: // Jump to a part of the script depending on // the return value from an mcode routine // Get the maximum value Read8ip(parameter); debug(9, "CP_JUMP_ON_RETURNED: %d => %d", parameterReturnedFromMcodeFunction, READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4)); ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4); break; // Operators case OP_ISEQUAL: b = pop(); a = pop(); push(a == b); debug(9, "OP_ISEQUAL: RESULT = %d", a == b); break; case OP_NOTEQUAL: b = pop(); a = pop(); push(a != b); debug(9, "OP_NOTEQUAL: RESULT = %d", a != b); break; case OP_GTTHAN: b = pop(); a = pop(); push(a > b); debug(9, "OP_GTTHAN: RESULT = %d", a > b); break; case OP_LSTHAN: b = pop(); a = pop(); push(a < b); debug(9, "OP_LSTHAN: RESULT = %d", a < b); break; case OP_GTTHANE: b = pop(); a = pop(); push(a >= b); debug(9, "OP_GTTHANE: RESULT = %d", a >= b); break; case OP_LSTHANE: b = pop(); a = pop(); push(a <= b); debug(9, "OP_LSTHANE: RESULT = %d", a <= b); break; case OP_PLUS: b = pop(); a = pop(); push(a + b); debug(9, "OP_PLUS: RESULT = %d", a + b); break; case OP_MINUS: b = pop(); a = pop(); push(a - b); debug(9, "OP_MINUS: RESULT = %d", a - b); break; case OP_TIMES: b = pop(); a = pop(); push(a * b); debug(9, "OP_TIMES: RESULT = %d", a * b); break; case OP_DIVIDE: b = pop(); a = pop(); push(a / b); debug(9, "OP_DIVIDE: RESULT = %d", a / b); break; case OP_ANDAND: b = pop(); a = pop(); push(a && b); debug(9, "OP_ANDAND: RESULT = %d", a && b); break; case OP_OROR: b = pop(); a = pop(); push(a || b); debug(9, "OP_OROR: RESULT = %d", a || b); break; // Debugging opcodes, I think case CP_DEBUGON: debug(9, "CP_DEBUGON"); break; case CP_DEBUGOFF: debug(9, "CP_DEBUGOFF"); break; case CP_TEMP_TEXT_PROCESS: Read32ip(parameter); debug(9, "CP_TEMP_TEXT_PROCESS: %d", parameter); break; default: error("Invalid script command %d", curCommand); return 3; } } return 1; }