/* 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. * */ #include "glk/archetype/interpreter.h" #include "glk/archetype/archetype.h" #include "glk/archetype/misc.h" #include "glk/archetype/saveload.h" #include "glk/archetype/timestamp.h" #include "glk/archetype/wrap.h" namespace Glk { namespace Archetype { int MainObject; void interpreter_init() { MainObject = 0; } StringPtr MakeNewDynStr(const String &s) { return NewDynStr(s); } int find_message(const String &message) { void *p; for (uint i = 0; i < g_vm->Vocabulary.size(); ++i) { if (!index_xarray(g_vm->Vocabulary, i, p)) g_vm->writeln("Internal error - cannot index element %d of Vocabulary", i); else if (message == *((StringPtr)p)) return i; } return -1; } bool convert_to(AclType target_type, ResultType &the_scalar) { int code; char dir_from = '\0'; int the_number = -1; String s1; void *p; bool boolval = false; if (target_type == the_scalar._kind) return true; switch (target_type) { case QUOTE_LIT: case TEXT_LIT: case IDENT: case RESERVED: return false; default: break; } switch (the_scalar._kind) { case NUMERIC: dir_from = 'N'; the_number = the_scalar._data._numeric.acl_int; break; case MESSAGE: dir_from = 'S'; if (index_xarray(g_vm->Vocabulary, the_scalar._data._msgTextQuote.index, p)) s1 = *(StringPtr)p; break; case TEXT_LIT: case QUOTE_LIT: dir_from = 'S'; if (index_xarray(g_vm->Literals, the_scalar._data._msgTextQuote.index, p)) s1 = *(StringPtr)p; break; case STR_PTR: // string memory will be disposed ONLY if successful convert dir_from = 'S'; s1 = *the_scalar._data._str.acl_str; break; case IDENT: //with the_scalar do begin dir_from = 'S'; switch (the_scalar._data._ident.ident_kind) { case ENUMERATE_ID: dir_from = 'N'; the_number = the_scalar._data._ident.ident_int; break; case OBJECT_ID: if (the_scalar._data._ident.ident_int == 0) s1 = "system"; else if (index_xarray(g_vm->Object_ID_List, the_scalar._data._ident.ident_int, p)) { if (p == nullptr) s1 = "null"; else s1 = *(StringPtr)p; } else { return false; } break; case TYPE_ID: if (the_scalar._data._ident.ident_int == 0) s1 = "null"; else if (index_xarray(g_vm->Type_ID_List, the_scalar._data._ident.ident_int, p)) s1 = *(StringPtr)p; else return false; break; case ATTRIBUTE_ID: if (index_xarray(g_vm->Attribute_ID_List, the_scalar._data._ident.ident_int, p)) s1 = *((StringPtr)p); else return false; break; default: break; } break; case RESERVED: if (the_scalar._data._reserved.keyword == RW_TRUE || the_scalar._data._reserved.keyword == RW_FALSE) { dir_from = 'B'; boolval = (the_scalar._data._reserved.keyword == RW_TRUE); } else { return false; } break; default: break; } if (target_type == STR_PTR || target_type == MESSAGE) { if (the_scalar._kind == STR_PTR) FreeDynStr(the_scalar._data._str.acl_str); // we know this will succeed the_scalar._kind = target_type; switch (dir_from) { case 'N': s1 = String::format("%d", the_number); break; case 'B': s1 = boolval ? "TRUE" : "false"; break; default: break; } if (target_type == MESSAGE) the_scalar._data._msgTextQuote.index = find_message(s1); else the_scalar._data._str.acl_str = NewDynStr(s1); return true; } else { // numeric conversions switch (dir_from) { case 'N': the_scalar._kind = NUMERIC; the_scalar._data._numeric.acl_int = the_number; return true; case 'B': the_scalar._kind = NUMERIC; the_scalar._data._numeric.acl_int = boolval ? 1 : 0; break; case 'S': s1.trim(); the_number = s1.val(&code); if (code != 0) { return false; } else { // successful if (the_scalar._kind == STR_PTR) FreeDynStr(the_scalar._data._str.acl_str); // memory no longer needed the_scalar._kind = NUMERIC; the_scalar._data._numeric.acl_int = the_number; } return true; default: break; } } return false; } void undefine(ResultType &result) { result._kind = RESERVED; result._data._reserved.keyword = RW_UNDEFINED; } void cleanup(ResultType &result) { if (result._kind == STR_PTR) FreeDynStr(result._data._str.acl_str); result._kind = RESERVED; result._data._reserved.keyword = RW_UNDEFINED; } void copy_result(ResultType &r1, const ResultType &r2) { cleanup(r1); r1 = r2; if (r1._kind == STR_PTR) r1._data._str.acl_str = NewDynStr(*r2._data._str.acl_str); } bool result_compare(short comparison, ResultType &r1, ResultType &r2) { bool verdict = false; // Try numeric reckoning first, then string reckoning if (convert_to(NUMERIC, r1) && convert_to(NUMERIC, r2)) { switch (comparison) { case OP_EQ: case OP_NE: verdict = r1._data._numeric.acl_int == r2._data._numeric.acl_int; break; case OP_LT: verdict = r1._data._numeric.acl_int < r2._data._numeric.acl_int; break; case OP_LE: verdict = r1._data._numeric.acl_int <= r2._data._numeric.acl_int; break; case OP_GT: verdict = r1._data._numeric.acl_int > r2._data._numeric.acl_int; break; case OP_GE: verdict = r1._data._numeric.acl_int >= r2._data._numeric.acl_int; break; default: break; } } else if (convert_to(STR_PTR, r1) && convert_to(STR_PTR, r2)) { switch (comparison) { case OP_EQ: case OP_NE: verdict = *r1._data._str.acl_str == *r2._data._str.acl_str; break; case OP_LT: verdict = *r1._data._str.acl_str < *r2._data._str.acl_str; break; case OP_LE: verdict = *r1._data._str.acl_str <= *r2._data._str.acl_str; break; case OP_GT: verdict = *r1._data._str.acl_str > *r2._data._str.acl_str; break; case OP_GE: verdict = *r1._data._str.acl_str >= *r2._data._str.acl_str; break; default: break; } } else if (r1._kind == r2._kind) { switch (r1._kind) { case RESERVED: switch (comparison) { case OP_EQ: case OP_NE: verdict = r1._data._reserved.keyword == r2._data._reserved.keyword; break; default: break; } case IDENT: if (r1._data._ident.ident_kind == r2._data._ident.ident_kind) { switch (comparison) { case OP_EQ: case OP_NE: verdict = r1._data._ident.ident_int == r2._data._ident.ident_int; break; default: break; } } break; default: break; } } if (comparison == OP_NE) return !verdict; else return verdict; } bool assignment(ResultType &target, ResultType &value) { ExprPtr e; if (target._kind != ATTR_PTR) { wraperr("Warning: attempted assignment to a non-attribute"); return false; } else { e = (ExprPtr)target._data._attr.acl_attr->data; // If the current expression starts with an operator, we know it isn't a flat result // and must therefore be disposed of before proceeding. Otherwise simply clean up // the previous expression node if (e->_kind != OPER) { cleanup(*e); } else { dispose_expr(e); e = (ExprPtr)malloc(sizeof(ExprNode)); undefine(*e); } } copy_result(*e, value); target._data._attr.acl_attr->data = e; return true; } void write_result(ResultType &result) { ResultType r1; undefine(r1); if (result._kind == STR_PTR) wrapout(*result._data._str.acl_str, false); else if (result._kind == RESERVED) wrapout(Reserved_Wds[result._data._reserved.keyword], false); else { if (result._kind == ATTR_PTR) copy_result(r1, *(ResultType *)result._data._attr.acl_attr->data); else copy_result(r1, result); if (convert_to(STR_PTR, r1)) wrapout(*r1._data._str.acl_str, false); cleanup(r1); } } void display_result(ResultType &result) { String enclose; switch (result._kind) { case STR_PTR: case TEXT_LIT: enclose = "\""; break; case QUOTE_LIT: enclose = " "; wrapout(">>", false); break; case MESSAGE: enclose = "\'"; default: enclose = ' '; } if (enclose != " ") wrapout(enclose, false); write_result(result); if (enclose != " ") wrapout(enclose, false); } void display_expr(ExprTree the_tree) { if (the_tree->_kind != OPER) { display_result(*the_tree); } else { if (Binary[the_tree->_data._oper.op_name]) { wrapout(" (", false); display_expr(the_tree->_data._oper.left); wrapout(") ", false); } wrapout(Operators[the_tree->_data._oper.op_name], false); wrapout(" (", false); display_expr(the_tree->_data._oper.right); wrapout(") ", false); } } bool load_game(Common::ReadStream *f_in) { int length; char ch = '\0'; double fileVersion; // First, check the initial version string against that in the misc unit length = strlen(VERSION_STUB); for (int i = 0; i < length; ++i) { ch = (char)f_in->readByte(); if (ch != VERSION_STUB[i]) { g_vm->writeln("This file is not an Archetype file."); return false; } } // Get the textual version of the game's version. This is easier than dealing with // the Pascal Real type the game files use for their version number String versionStr; while (!f_in->eos() && (ch = (char)f_in->readByte()) != '\n') versionStr += ch; fileVersion = atof(versionStr.c_str()); // Bleed off string version information while (!f_in->eos() && ch != 26) ch = f_in->readByte(); // Skip over 6 byte Real encoded version number (void)f_in->readUint32LE(); (void)f_in->readUint16LE(); if (fileVersion > VERSION_NUM) { g_vm->writeln("This version of PERFORM is %.1f; file version is %.1f", VERSION_NUM, fileVersion); g_vm->writeln("Cannot PERFORM this file."); return false; } // Get encryption information Encryption = (EncryptionType)f_in->readByte(); // Read the timestamp. It is used to verify saved game states, and also to prime the encryption GTimeStamp = f_in->readUint32LE(); // Initialize the encrypter. This is done by using the global time stamp as a starting point // and using the Encryption variable to decide the method. Be careful here; the PURPLE // or Dynamic encryption works differently in that we have to set Encryption to UNPURPLE // (since we're decoding) and then back to PURPLE again in case they save any game states. // See load_game_state in the GAMESTAT unit for similar machinations if (Encryption == PURPLE) Encryption = UNPURPLE; cryptinit(Encryption, GTimeStamp); // Where's the main object? MainObject = f_in->readUint16LE(); load_obj_list(f_in, g_vm->Object_List); load_obj_list(f_in, g_vm->Type_List); load_text_list(f_in, g_vm->Literals); load_text_list(f_in, g_vm->Vocabulary); if (Encryption == DEBUGGING_ON) { g_vm->writeln("Loading debugging information"); load_id_info(f_in); } if (Encryption == UNPURPLE) Encryption = PURPLE; return true; } } // End of namespace Archetype } // End of namespace Glk