/* 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/engine/state.h" #include "sci/engine/kernel.h" namespace Sci { #ifdef DISABLE_VALIDATIONS #define sane_nodep(a, b) 1 #define sane_listp(a, b) 1 #else static int sane_nodep(EngineState *s, reg_t addr) { int have_prev = 0; reg_t prev = addr; do { Node *node = s->_segMan->lookupNode(addr); if (!node) return 0; if ((have_prev) && node->pred != prev) return 0; prev = addr; addr = node->succ; have_prev = 1; } while (!addr.isNull()); return 1; } int sane_listp(EngineState *s, reg_t addr) { List *l = s->_segMan->lookupList(addr); int empties = 0; if (l->first.isNull()) ++empties; if (l->last.isNull()) ++empties; // Either none or both must be set if (empties == 1) return 0; if (!empties) { Node *node_a, *node_z; node_a = s->_segMan->lookupNode(l->first); node_z = s->_segMan->lookupNode(l->last); if (!node_a || !node_z) return 0; if (!node_a->pred.isNull()) return 0; if (!node_z->succ.isNull()) return 0; return sane_nodep(s, l->first); } return 1; // Empty list is fine } #endif reg_t kNewList(EngineState *s, int argc, reg_t *argv) { reg_t listbase; List *l; l = s->_segMan->allocateList(&listbase); l->first = l->last = NULL_REG; debugC(2, kDebugLevelNodes, "New listbase at %04x:%04x\n", PRINT_REG(listbase)); return listbase; // Return list base address } reg_t kDisposeList(EngineState *s, int argc, reg_t *argv) { List *l = s->_segMan->lookupList(argv[0]); if (!l) { // FIXME: This should be an error, but it's turned to a warning for now warning("Attempt to dispose non-list at %04x:%04x", PRINT_REG(argv[0])); return NULL_REG; } if (!sane_listp(s, argv[0])) warning("List at %04x:%04x is not sane anymore", PRINT_REG(argv[0])); /* if (!l->first.isNull()) { reg_t n_addr = l->first; while (!n_addr.isNull()) { // Free all nodes Node *n = s->_segMan->lookupNode(n_addr); s->_segMan->free_Node(n_addr); n_addr = n->succ; } } s->_segMan->free_list(argv[0]); */ return s->r_acc; } reg_t _k_new_node(EngineState *s, reg_t value, reg_t key) { reg_t nodebase; Node *n = s->_segMan->allocateNode(&nodebase); if (!n) { error("[Kernel] Out of memory while creating a node"); return NULL_REG; } n->pred = n->succ = NULL_REG; n->key = key; n->value = value; return nodebase; } reg_t kNewNode(EngineState *s, int argc, reg_t *argv) { s->r_acc = _k_new_node(s, argv[0], argv[1]); debugC(2, kDebugLevelNodes, "New nodebase at %04x:%04x\n", PRINT_REG(s->r_acc)); return s->r_acc; } reg_t kFirstNode(EngineState *s, int argc, reg_t *argv) { if (argv[0].isNull()) return NULL_REG; List *l = s->_segMan->lookupList(argv[0]); if (l && !sane_listp(s, argv[0])) warning("List at %04x:%04x is not sane anymore", PRINT_REG(argv[0])); if (l) return l->first; else return NULL_REG; } reg_t kLastNode(EngineState *s, int argc, reg_t *argv) { List *l = s->_segMan->lookupList(argv[0]); if (l && !sane_listp(s, argv[0])) warning("List at %04x:%04x is not sane anymore", PRINT_REG(argv[0])); if (l) return l->last; else return NULL_REG; } reg_t kEmptyList(EngineState *s, int argc, reg_t *argv) { List *l = s->_segMan->lookupList(argv[0]); if (!l || !sane_listp(s, argv[0])) warning("List at %04x:%04x is invalid or not sane anymore", PRINT_REG(argv[0])); return make_reg(0, ((l) ? l->first.isNull() : 0)); } void _k_add_to_front(EngineState *s, reg_t listbase, reg_t nodebase) { List *l = s->_segMan->lookupList(listbase); Node *new_n = s->_segMan->lookupNode(nodebase); debugC(2, kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x\n", PRINT_REG(nodebase), PRINT_REG(listbase)); // FIXME: This should be an error, but it's turned to a warning for now if (!new_n) warning("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodebase), PRINT_REG(listbase)); if (!l || !sane_listp(s, listbase)) warning("List at %04x:%04x is not sane anymore", PRINT_REG(listbase)); new_n->succ = l->first; new_n->pred = NULL_REG; // Set node to be the first and last node if it's the only node of the list if (l->first.isNull()) l->last = nodebase; else { Node *old_n = s->_segMan->lookupNode(l->first); old_n->pred = nodebase; } l->first = nodebase; } void _k_add_to_end(EngineState *s, reg_t listbase, reg_t nodebase) { List *l = s->_segMan->lookupList(listbase); Node *new_n = s->_segMan->lookupNode(nodebase); debugC(2, kDebugLevelNodes, "Adding node %04x:%04x to end of list %04x:%04x\n", PRINT_REG(nodebase), PRINT_REG(listbase)); // FIXME: This should be an error, but it's turned to a warning for now if (!new_n) warning("Attempt to add non-node (%04x:%04x) to list at %04x:%04x", PRINT_REG(nodebase), PRINT_REG(listbase)); if (!l || !sane_listp(s, listbase)) warning("List at %04x:%04x is not sane anymore", PRINT_REG(listbase)); new_n->succ = NULL_REG; new_n->pred = l->last; // Set node to be the first and last node if it's the only node of the list if (l->last.isNull()) l->first = nodebase; else { Node *old_n = s->_segMan->lookupNode(l->last); old_n->succ = nodebase; } l->last = nodebase; } reg_t kNextNode(EngineState *s, int argc, reg_t *argv) { Node *n = s->_segMan->lookupNode(argv[0]); if (!sane_nodep(s, argv[0])) { warning("List node at %04x:%04x is not sane anymore", PRINT_REG(argv[0])); return NULL_REG; } return n->succ; } reg_t kPrevNode(EngineState *s, int argc, reg_t *argv) { Node *n = s->_segMan->lookupNode(argv[0]); if (!sane_nodep(s, argv[0])) warning("List node at %04x:%04x is not sane anymore", PRINT_REG(argv[0])); return n->pred; } reg_t kNodeValue(EngineState *s, int argc, reg_t *argv) { Node *n = s->_segMan->lookupNode(argv[0]); if (!sane_nodep(s, argv[0])) { warning("List node at %04x:%04x is not sane", PRINT_REG(argv[0])); return NULL_REG; } return n->value; } reg_t kAddToFront(EngineState *s, int argc, reg_t *argv) { _k_add_to_front(s, argv[0], argv[1]); return s->r_acc; } reg_t kAddAfter(EngineState *s, int argc, reg_t *argv) { List *l = s->_segMan->lookupList(argv[0]); Node *firstnode = argv[1].isNull() ? NULL : s->_segMan->lookupNode(argv[1]); Node *newnode = s->_segMan->lookupNode(argv[2]); if (!l || !sane_listp(s, argv[0])) warning("List at %04x:%04x is not sane anymore", PRINT_REG(argv[0])); // FIXME: This should be an error, but it's turned to a warning for now if (!newnode) { warning("New 'node' %04x:%04x is not a node", PRINT_REG(argv[2])); return NULL_REG; } if (argc != 3) { warning("kAddAfter: Haven't got 3 arguments, aborting"); return NULL_REG; } if (firstnode) { // We're really appending after reg_t oldnext = firstnode->succ; newnode->pred = argv[1]; firstnode->succ = argv[2]; newnode->succ = oldnext; if (oldnext.isNull()) // Appended after last node? // Set new node as last list node l->last = argv[2]; else s->_segMan->lookupNode(oldnext)->pred = argv[2]; } else { // !firstnode _k_add_to_front(s, argv[0], argv[2]); // Set as initial list node } return s->r_acc; } reg_t kAddToEnd(EngineState *s, int argc, reg_t *argv) { _k_add_to_end(s, argv[0], argv[1]); return s->r_acc; } reg_t kFindKey(EngineState *s, int argc, reg_t *argv) { reg_t node_pos; reg_t key = argv[1]; reg_t list_pos = argv[0]; debugC(2, kDebugLevelNodes, "Looking for key %04x:%04x in list %04x:%04x\n", PRINT_REG(key), PRINT_REG(list_pos)); if (!sane_listp(s, list_pos)) warning("List at %04x:%04x is not sane anymore", PRINT_REG(list_pos)); node_pos = s->_segMan->lookupList(list_pos)->first; debugC(2, kDebugLevelNodes, "First node at %04x:%04x\n", PRINT_REG(node_pos)); while (!node_pos.isNull()) { Node *n = s->_segMan->lookupNode(node_pos); if (n->key == key) { debugC(2, kDebugLevelNodes, " Found key at %04x:%04x\n", PRINT_REG(node_pos)); return node_pos; } node_pos = n->succ; debugC(2, kDebugLevelNodes, "NextNode at %04x:%04x\n", PRINT_REG(node_pos)); } debugC(2, kDebugLevelNodes, "Looking for key without success\n"); return NULL_REG; } reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) { reg_t node_pos = kFindKey(s, 2, argv); Node *n; List *l = s->_segMan->lookupList(argv[0]); if (node_pos.isNull()) return NULL_REG; // Signal falure n = s->_segMan->lookupNode(node_pos); if (l->first == node_pos) l->first = n->succ; if (l->last == node_pos) l->last = n->pred; if (!n->pred.isNull()) s->_segMan->lookupNode(n->pred)->succ = n->succ; if (!n->succ.isNull()) s->_segMan->lookupNode(n->succ)->pred = n->pred; //s->_segMan->free_Node(node_pos); return make_reg(0, 1); // Signal success } struct sort_temp_t { reg_t key, value; reg_t order; }; int sort_temp_cmp(const void *p1, const void *p2) { const sort_temp_t *st1 = (const sort_temp_t *)p1; const sort_temp_t *st2 = (const sort_temp_t *)p2; if (st1->order.segment < st1->order.segment || (st1->order.segment == st1->order.segment && st1->order.offset < st2->order.offset)) return -1; if (st1->order.segment > st2->order.segment || (st1->order.segment == st2->order.segment && st1->order.offset > st2->order.offset)) return 1; return 0; } reg_t kSort(EngineState *s, int argc, reg_t *argv) { SegManager *segMan = s->_segMan; reg_t source = argv[0]; reg_t dest = argv[1]; reg_t order_func = argv[2]; int input_size = (int16)GET_SEL32V(segMan, source, size); int i; reg_t input_data = GET_SEL32(segMan, source, elements); reg_t output_data = GET_SEL32(segMan, dest, elements); List *list; Node *node; if (!input_size) return s->r_acc; if (output_data.isNull()) { list = s->_segMan->allocateList(&output_data); list->first = list->last = NULL_REG; PUT_SEL32(segMan, dest, elements, output_data); } PUT_SEL32V(segMan, dest, size, input_size); list = s->_segMan->lookupList(input_data); node = s->_segMan->lookupNode(list->first); sort_temp_t *temp_array = (sort_temp_t *)malloc(sizeof(sort_temp_t) * input_size); i = 0; while (node) { invoke_selector(INV_SEL(order_func, doit, kStopOnInvalidSelector), 1, node->value); temp_array[i].key = node->key; temp_array[i].value = node->value; temp_array[i].order = s->r_acc; i++; node = s->_segMan->lookupNode(node->succ); } qsort(temp_array, input_size, sizeof(sort_temp_t), sort_temp_cmp); for (i = 0;i < input_size;i++) { reg_t lNode = _k_new_node(s, temp_array[i].key, temp_array[i].value); _k_add_to_end(s, output_data, lNode); } free(temp_array); return s->r_acc; } // SCI32 list functions #ifdef ENABLE_SCI32 reg_t kListAt(EngineState *s, int argc, reg_t *argv) { if (argc != 2) { warning("kListAt called with %d parameters", argc); return NULL_REG; } List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; Node *curNode = s->_segMan->lookupNode(curAddress); reg_t curObject = curNode->value; int16 listIndex = argv[1].toUint16(); int curIndex = 0; while (curIndex != listIndex) { if (curNode->succ.isNull()) { // end of the list? return NULL_REG; } curAddress = curNode->succ; curNode = s->_segMan->lookupNode(curAddress); curObject = curNode->value; curIndex++; } return curObject; } reg_t kListIndexOf(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; Node *curNode = s->_segMan->lookupNode(curAddress); reg_t curObject; uint16 curIndex = 0; while (curNode) { curObject = curNode->value; if (curObject == argv[1]) return make_reg(0, curIndex); curAddress = curNode->succ; curNode = s->_segMan->lookupNode(curAddress); curIndex++; } return SIGNAL_REG; } reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; Node *curNode = s->_segMan->lookupNode(curAddress); reg_t curObject; Selector slc = argv[1].toUint16(); ObjVarRef address; while (curNode) { curObject = curNode->value; // First, check if the target selector is a variable if (lookup_selector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { // This can only happen with 3 params (list, target selector, variable) if (argc != 3) { warning("kListEachElementDo: Attempted to modify a variable selector with %d params", argc); } else { write_selector(s->_segMan, curObject, slc, argv[2]); } } else { // FIXME: Yes, this is an ugly hack... if (argc == 2) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 0); } else if (argc == 3) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 1, argv[2]); } else if (argc == 4) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 2, argv[2], argv[3]); } else if (argc == 5) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 3, argv[2], argv[3], argv[4]); } else { warning("kListEachElementDo: called with %d params", argc); } } // Lookup node again, since the nodetable it was in may have been reallocated curNode = s->_segMan->lookupNode(curAddress); curAddress = curNode->succ; curNode = s->_segMan->lookupNode(curAddress); } return s->r_acc; } reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; Node *curNode = s->_segMan->lookupNode(curAddress); reg_t curObject; Selector slc = argv[1].toUint16(); ObjVarRef address; s->r_acc = NULL_REG; // reset the accumulator while (curNode) { curObject = curNode->value; // First, check if the target selector is a variable if (lookup_selector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { // Can this happen with variable selectors? warning("kListFirstTrue: Attempted to access a variable selector"); } else { // FIXME: Yes, this is an ugly hack... if (argc == 2) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 0); } else if (argc == 3) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 1, argv[2]); } else if (argc == 4) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 2, argv[2], argv[3]); } else if (argc == 5) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 3, argv[2], argv[3], argv[4]); } else { warning("kListFirstTrue: called with %d params", argc); } // Check if the result is true if (!s->r_acc.isNull()) return curObject; } // Lookup node again, since the nodetable it was in may have been reallocated curNode = s->_segMan->lookupNode(curAddress); curAddress = curNode->succ; curNode = s->_segMan->lookupNode(curAddress); } // No selector returned true return NULL_REG; } reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; Node *curNode = s->_segMan->lookupNode(curAddress); reg_t curObject; Selector slc = argv[1].toUint16(); ObjVarRef address; s->r_acc = make_reg(0, 1); // reset the accumulator while (curNode) { curObject = curNode->value; // First, check if the target selector is a variable if (lookup_selector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) { // Can this happen with variable selectors? warning("kListAllTrue: Attempted to access a variable selector"); } else { // FIXME: Yes, this is an ugly hack... if (argc == 2) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 0); } else if (argc == 3) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 1, argv[2]); } else if (argc == 4) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 2, argv[2], argv[3]); } else if (argc == 5) { invoke_selector(s, curObject, slc, kContinueOnInvalidSelector, argv, argc, 3, argv[2], argv[3], argv[4]); } else { warning("kListAllTrue: called with %d params", argc); } // Check if the result isn't true if (s->r_acc.isNull()) break; } // Lookup node again, since the nodetable it was in may have been reallocated curNode = s->_segMan->lookupNode(curAddress); curAddress = curNode->succ; curNode = s->_segMan->lookupNode(curAddress); } return s->r_acc; } // In SCI2.1, all the list functions were merged in one reg_t kList(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: return kNewList(s, argc - 1, argv + 1); case 1: return kDisposeList(s, argc - 1, argv + 1); case 2: return kNewNode(s, argc - 1, argv + 1); case 3: return kFirstNode(s, argc - 1, argv + 1); case 4: return kLastNode(s, argc - 1, argv + 1); case 5: return kEmptyList(s, argc - 1, argv + 1); case 6: return kNextNode(s, argc - 1, argv + 1); case 7: return kPrevNode(s, argc - 1, argv + 1); case 8: return kNodeValue(s, argc - 1, argv + 1); case 9: return kAddAfter(s, argc - 1, argv + 1); case 10: return kAddToFront(s, argc - 1, argv + 1); case 11: return kAddToEnd(s, argc - 1, argv + 1); case 12: warning("kList: unimplemented subfunction kAddBefore"); //return kAddBefore(s, argc - 1, argv + 1); return NULL_REG; case 13: warning("kList: unimplemented subfunction kMoveToFront"); //return kMoveToFront(s, argc - 1, argv + 1); return NULL_REG; case 14: warning("kList: unimplemented subfunction kMoveToEnd"); //return kMoveToEnd(s, argc - 1, argv + 1); return NULL_REG; case 15: return kFindKey(s, argc - 1, argv + 1); case 16: return kDeleteKey(s, argc - 1, argv + 1); case 17: return kListAt(s, argc - 1, argv + 1); case 18: return kListIndexOf(s, argc - 1, argv + 1); case 19: return kListEachElementDo(s, argc - 1, argv + 1); case 20: return kListFirstTrue(s, argc - 1, argv + 1); case 21: return kListAllTrue(s, argc - 1, argv + 1); case 22: return kSort(s, argc - 1, argv + 1); default: warning("kList: Unhandled case %d", argv[0].toUint16()); return NULL_REG; } } #endif } // End of namespace Sci