649 lines
19 KiB
C++

/* 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 "common/system.h"
#include "common/file.h"
#include "sci/scicore/resource.h"
#include "sci/engine/state.h"
#include "sci/scicore/versions.h"
#include "sci/engine/kernel.h"
#include "sci/engine/kernel_types.h"
#include "sci/gfx/gfx_widgets.h"
#include "sci/gfx/gfx_state_internal.h" // required for GfxPort, GfxVisual
#include "sci/gfx/menubar.h"
namespace Sci {
static int _init_vocabulary(EngineState *s) { // initialize vocabulary and related resources
s->parser_lastmatch_word = SAID_NO_MATCH;
s->parser_rules = NULL;
sciprintf("Initializing vocabulary\n");
if ((s->resmgr->_sciVersion < SCI_VERSION_01_VGA) && vocab_get_words(s->resmgr, s->_parserWords)) {
vocab_get_suffixes(s->resmgr, s->_parserSuffixes);
if (vocab_get_branches(s->resmgr, s->_parserBranches))
// Now build a GNF grammar out of this
s->parser_rules = vocab_build_gnf(s->_parserBranches);
} else {
sciprintf("Assuming that this game does not use a parser.\n");
s->parser_rules = NULL;
}
s->opcodes = vocabulary_get_opcodes(s->resmgr);
if (!vocabulary_get_snames(s->resmgr, s->version, s->_selectorNames)) {
sciprintf("_init_vocabulary(): Could not retrieve selector names (vocab.997)!\n");
return 1;
}
script_map_selectors(s, &(s->selector_map));
// Maps a few special selectors for later use
return 0;
}
extern int _allocd_rules;
static void _sci1_alloc_system_colors(EngineState *s) {
gfx_color_t black = { PaletteEntry(0, 0, 0), 0, 0, 0, GFX_MASK_VISUAL };
gfxop_set_system_color(s->gfx_state, 0, &black);
}
int _reset_graphics_input(EngineState *s) {
Resource *resource;
int font_nr;
gfx_color_t transparent = { PaletteEntry(), 0, -1, -1, 0 };
sciprintf("Initializing graphics\n");
if (s->resmgr->_sciVersion <= SCI_VERSION_01) {
int i;
for (i = 0; i < 16; i++) {
if (gfxop_set_color(s->gfx_state, &(s->ega_colors[i]), gfx_sci0_image_colors[sci0_palette][i].r,
gfx_sci0_image_colors[sci0_palette][i].g, gfx_sci0_image_colors[sci0_palette][i].b, 0, -1, -1)) {
return 1;
}
gfxop_set_system_color(s->gfx_state, i, &(s->ega_colors[i]));
}
} else {
_sci1_alloc_system_colors(s);
// Check for Amiga palette file.
Common::File file;
if (file.open("spal")) {
s->gfx_state->gfxResMan->setStaticPalette(gfxr_read_pal1_amiga(file));
file.close();
} else {
resource = s->resmgr->findResource(kResourceTypePalette, 999, 1);
if (resource) {
if (s->version < SCI_VERSION(1, 001, 000))
s->gfx_state->gfxResMan->setStaticPalette(gfxr_read_pal1(999, resource->data, resource->size));
else
s->gfx_state->gfxResMan->setStaticPalette(gfxr_read_pal11(999, resource->data, resource->size));
s->resmgr->unlockResource(resource, 999, kResourceTypePalette);
} else {
sciprintf("Couldn't find the default palette!\n");
}
}
}
gfxop_fill_box(s->gfx_state, gfx_rect(0, 0, 320, 200), s->ega_colors[0]); // Fill screen black
gfxop_update(s->gfx_state);
s->mouse_pointer_view = s->mouse_pointer_loop = s->mouse_pointer_cel = -1; // No mouse pointer resource
s->save_mouse_pointer_view = s->save_mouse_pointer_loop = s->save_mouse_pointer_cel = -1; // No mouse pointer resource
gfxop_set_pointer_position(s->gfx_state, Common::Point(160, 150));
s->mouse_pointer_view = s->mouse_pointer_loop = s->mouse_pointer_cel = -1; // No mouse pointer resource
s->save_mouse_pointer_view = s->save_mouse_pointer_loop = s->save_mouse_pointer_cel = -1; // No mouse pointer resource
s->pic_is_new = 0;
s->pic_visible_map = GFX_MASK_NONE; // Other values only make sense for debugging
s->dyn_views = NULL; // no DynViews
s->drop_views = NULL; // And, consequently, no list for dropped views
s->priority_first = 42; // Priority zone 0 ends here
if (s->version < SCI_VERSION_FTU_PRIORITY_14_ZONES)
s->priority_last = 200;
else
s->priority_last = 190;
font_nr = -1;
do {
resource = s->resmgr->testResource(kResourceTypeFont, ++font_nr);
} while ((!resource) && (font_nr < sci_max_resource_nr[s->resmgr->_sciVersion]));
if (!resource) {
sciprintf("No text font was found.\n");
return 1;
}
s->visual = gfxw_new_visual(s->gfx_state, font_nr);
s->wm_port = gfxw_new_port(s->visual, NULL, s->gfx_state->pic_port_bounds, s->ega_colors[0], transparent);
s->iconbar_port = gfxw_new_port(s->visual, NULL, gfx_rect(0, 0, 320, 200), s->ega_colors[0], transparent);
s->iconbar_port->_flags |= GFXW_FLAG_NO_IMPLICIT_SWITCH;
if (s->resmgr->_sciVersion >= SCI_VERSION_01_VGA) {
// This bit sets the foreground and background colors in VGA SCI games
gfx_color_t fgcolor;
gfx_color_t bgcolor;
memset(&fgcolor, 0, sizeof(gfx_color_t));
memset(&bgcolor, 0, sizeof(gfx_color_t));
#if 0
fgcolor.visual = s->gfx_state->resstate->static_palette[0];
fgcolor.mask = GFX_MASK_VISUAL;
bgcolor.visual = s->gfx_state->resstate->static_palette[255];
bgcolor.mask = GFX_MASK_VISUAL;
#endif
s->titlebar_port = gfxw_new_port(s->visual, NULL, gfx_rect(0, 0, 320, 10), fgcolor, bgcolor);
} else {
s->titlebar_port = gfxw_new_port(s->visual, NULL, gfx_rect(0, 0, 320, 10), s->ega_colors[0], s->ega_colors[15]);
}
s->titlebar_port->_color.mask |= GFX_MASK_PRIORITY;
s->titlebar_port->_color.priority = 11;
s->titlebar_port->_bgcolor.mask |= GFX_MASK_PRIORITY;
s->titlebar_port->_bgcolor.priority = 11;
s->titlebar_port->_flags |= GFXW_FLAG_NO_IMPLICIT_SWITCH;
// but this is correct
s->picture_port = gfxw_new_port(s->visual, NULL, s->gfx_state->pic_port_bounds, s->ega_colors[0], transparent);
s->pics_drawn_nr = 0;
s->visual->add(GFXWC(s->visual), s->wm_port);
s->visual->add(GFXWC(s->visual), s->titlebar_port);
s->visual->add(GFXWC(s->visual), s->picture_port);
s->visual->add(GFXWC(s->visual), s->iconbar_port);
// Add ports to visual
s->port = s->picture_port; // Currently using the picture port
#if 0
s->titlebar_port->_bgcolor.mask |= GFX_MASK_PRIORITY;
s->titlebar_port->_bgcolor.priority = 11; // Standard priority for the titlebar port
#endif
return 0;
}
int game_init_graphics(EngineState *s) {
#ifdef CUSTOM_GRAPHICS_OPTIONS
#ifndef WITH_PIC_SCALING
if (s->gfx_state->options->pic0_unscaled == 0)
warning("Pic scaling was disabled; your version of ScummVM has no support for scaled pic drawing built in.");
s->gfx_state->options->pic0_unscaled = 1;
#endif
#endif
return _reset_graphics_input(s);
}
static void _free_graphics_input(EngineState *s) {
sciprintf("Freeing graphics\n");
delete s->visual;
s->wm_port = s->titlebar_port = s->picture_port = NULL;
s->visual = NULL;
s->dyn_views = NULL;
s->port = NULL;
free(s->pics);
s->pics = NULL;
}
int game_init_sound(EngineState *s, int sound_flags) {
if (s->resmgr->_sciVersion >= SCI_VERSION_01)
sound_flags |= SFX_STATE_FLAG_MULTIPLAY;
s->sfx_init_flags = sound_flags;
sfx_init(&s->sound, s->resmgr, sound_flags);
return 0;
}
/* Maps a class ID to the script the corresponding class is contained in
Returns the script number suggested by vocab.996, or -1 if there's none */
static int suggested_script(Resource *res, unsigned int classId) {
int offset;
if (!res || classId >= res->size >> 2)
return -1;
offset = 2 + (classId << 2);
return (int16)READ_LE_UINT16(res->data + offset);
}
#if 0
// Unreferenced - removed
int test_cursor_style(EngineState *s) {
int resource_nr = 0;
int ok = 0;
do {
ok |= s->resmgr->testResource(kResourceTypeCursor, resource_nr++) != NULL;
} while (resource_nr < 1000 && !ok);
return ok;
}
#endif
int create_class_table_sci11(EngineState *s) {
int scriptnr;
unsigned int seeker_offset;
char *seeker_ptr;
int classnr;
Resource *vocab996 = s->resmgr->findResource(kResourceTypeVocab, 996, 1);
if (!vocab996)
s->_classtable.resize(20);
else
s->_classtable.resize(vocab996->size >> 2);
for (scriptnr = 0; scriptnr < 1000; scriptnr++) {
Resource *heap = s->resmgr->findResource(kResourceTypeHeap, scriptnr, 0);
if (heap) {
int global_vars = READ_LE_UINT16(heap->data + 2);
seeker_ptr = (char*)heap->data + 4 + global_vars * 2;
seeker_offset = 4 + global_vars * 2;
while (READ_LE_UINT16((byte*)seeker_ptr) == SCRIPT_OBJECT_MAGIC_NUMBER) {
if (READ_LE_UINT16((byte*)seeker_ptr + 14) & SCRIPT_INFO_CLASS) {
classnr = READ_LE_UINT16((byte*)seeker_ptr + 10);
if (classnr >= (int)s->_classtable.size()) {
if (classnr >= SCRIPT_MAX_CLASSTABLE_SIZE) {
warning("Invalid class number 0x%x in script.%d(0x%x), offset %04x\n",
classnr, scriptnr, scriptnr, seeker_offset);
return 1;
}
s->_classtable.resize(classnr + 1); // Adjust maximum number of entries
}
s->_classtable[classnr].reg.offset = seeker_offset;
s->_classtable[classnr].reg.segment = 0;
s->_classtable[classnr].script = scriptnr;
}
seeker_ptr += READ_LE_UINT16((byte*)seeker_ptr + 2) * 2;
seeker_offset += READ_LE_UINT16((byte*)seeker_ptr + 2) * 2;
}
}
}
return 0;
}
static int create_class_table_sci0(EngineState *s) {
int scriptnr;
unsigned int seeker;
int classnr;
int magic_offset; // For strange scripts in older SCI versions
Resource *vocab996 = s->resmgr->findResource(kResourceTypeVocab, 996, 1);
if (!vocab996)
s->_classtable.resize(20);
else
s->_classtable.resize(vocab996->size >> 2);
for (scriptnr = 0; scriptnr < 1000; scriptnr++) {
int objtype = 0;
Resource *script = s->resmgr->findResource(kResourceTypeScript, scriptnr, 0);
if (script) {
if (s->version < SCI_VERSION_FTU_NEW_SCRIPT_HEADER)
magic_offset = seeker = 2;
else
magic_offset = seeker = 0;
do {
while (seeker < script->size) {
unsigned int lastseeker = seeker;
objtype = (int16)READ_LE_UINT16(script->data + seeker);
if (objtype == sci_obj_class || objtype == sci_obj_terminator)
break;
seeker += (int16)READ_LE_UINT16(script->data + seeker + 2);
if (seeker <= lastseeker) {
warning("Script version is invalid");
s->_classtable.clear();
return SCI_ERROR_INVALID_SCRIPT_VERSION;
}
}
if (objtype == sci_obj_class) {
int sugg_script;
seeker -= SCRIPT_OBJECT_MAGIC_OFFSET; // Adjust position; script home is base +8 bytes
classnr = (int16)READ_LE_UINT16(script->data + seeker + 4 + SCRIPT_SPECIES_OFFSET);
if (classnr >= (int)s->_classtable.size()) {
if (classnr >= SCRIPT_MAX_CLASSTABLE_SIZE) {
warning("Invalid class number 0x%x in script.%d(0x%x), offset %04x\n",
classnr, scriptnr, scriptnr, seeker);
return 1;
}
s->_classtable.resize(classnr + 1); // Adjust maximum number of entries
}
sugg_script = suggested_script(vocab996, classnr);
// First, test whether the script hasn't been claimed, or if it's been claimed by the wrong script
if (sugg_script == -1 || scriptnr == sugg_script /*|| !s->_classtable[classnr].reg.segment*/) {
// Now set the home script of the class
s->_classtable[classnr].reg.offset = seeker + 4 - magic_offset;
s->_classtable[classnr].reg.segment = 0;
s->_classtable[classnr].script = scriptnr;
}
seeker += SCRIPT_OBJECT_MAGIC_OFFSET; // Re-adjust position
seeker += (int16)READ_LE_UINT16(script->data + seeker + 2); // Move to next
}
} while (objtype != sci_obj_terminator && seeker <= script->size);
}
}
s->resmgr->unlockResource(vocab996, 996, kResourceTypeVocab);
vocab996 = NULL;
return 0;
}
// Architectural stuff: Init/Unintialize engine
int script_init_engine(EngineState *s, sci_version_t version) {
int result;
s->max_version = SCI_VERSION(9, 999, 999);
s->min_version = 0; //Set no real limits
s->version = SCI_VERSION_DEFAULT_SCI0;
s->kernel_opt_flags = 0;
if (!version) {
s->version_lock_flag = 0;
} else {
s->version = version;
s->version_lock_flag = 1; // Lock version
}
script_detect_versions(s);
if (s->version >= SCI_VERSION(1, 001, 000))
result = create_class_table_sci11(s);
else
result = create_class_table_sci0(s);
s->seg_manager = new SegManager(s->version >= SCI_VERSION(1, 001, 000));
s->gc_countdown = GC_INTERVAL - 1;
if (result) {
sciprintf("Failed to initialize class table\n");
return 1;
}
s->script_000_segment = script_get_segment(s, 0, SCRIPT_GET_LOCK);
if (s->script_000_segment <= 0) {
sciprintf("Failed to instantiate script.000\n");
return 1;
}
s->script_000 = s->seg_manager->getScript(s->script_000_segment, SEG_ID);
s->sys_strings = s->seg_manager->allocateSysStrings(&s->sys_strings_segment);
s->string_frag_segment = s->seg_manager->allocateStringFrags();
// Allocate static buffer for savegame and CWD directories
SystemString *str = &s->sys_strings->strings[SYS_STRING_SAVEDIR];
str->name = strdup("savedir");
str->max_size = MAX_SAVE_DIR_SIZE;
str->value = (reg_t*)sci_malloc(sizeof(reg_t)*MAX_SAVE_DIR_SIZE);
str->value->segment = s->string_frag_segment; // Set to empty string
str->value->offset = 0;
s->save_dir_copy = make_reg(s->sys_strings_segment, SYS_STRING_SAVEDIR);
s->save_dir_edit_offset = 0;
s->r_acc = s->r_prev = NULL_REG;
s->r_amp_rest = 0;
s->_executionStack.clear(); // Start without any execution stack
s->execution_stack_base = -1; // No vm is running yet
s->execution_stack_pos = -1; // Start at execution stack position 0
vocabulary_get_knames(s->resmgr, s->_kernelNames);
script_map_kernel(s);
// Maps the kernel functions
if (_init_vocabulary(s))
return 1;
if (s->selector_map.cantBeHere != -1)
version_require_later_than(s, SCI_VERSION_FTU_INVERSE_CANBEHERE);
s->restarting_flags = SCI_GAME_IS_NOT_RESTARTING;
s->bp_list = NULL; // No breakpoints defined
s->have_bp = 0;
if (s->version >= SCI_VERSION_FTU_LOFS_ABSOLUTE &&
s->version < SCI_VERSION(1, 001, 000))
s->seg_manager->setExportWidth(1);
else
s->seg_manager->setExportWidth(0);
sciprintf("Engine initialized\n");
s->pic_priority_table = NULL;
s->pics = NULL;
s->pics_nr = 0;
return 0;
}
void script_set_gamestate_save_dir(EngineState *s, const char *path) {
SystemString *str = &s->sys_strings->strings[SYS_STRING_SAVEDIR];
strncpy((char *)str->value, path, str->max_size);
str->value[str->max_size - 1].segment = s->string_frag_segment; // Make sure to terminate
str->value[str->max_size - 1].offset &= 0xff00; // Make sure to terminate
}
void internal_stringfrag_strncpy(EngineState *s, reg_t *dest, reg_t *src, int len);
#if 0
// Unreferenced - removed
void script_set_gamestate_save_dir(EngineState *s, reg_t path) {
SystemString *str = &s->sys_strings->strings[SYS_STRING_SAVEDIR];
reg_t *srcbuf = kernel_dereference_reg_pointer(s, path, 1);
internal_stringfrag_strncpy(s, str->value, srcbuf, MAX_SAVE_DIR_SIZE);
}
#endif
void script_free_vm_memory(EngineState *s) {
sciprintf("Freeing VM memory\n");
s->save_dir_copy_buf = NULL;
s->_classtable.clear();
// Close all opened file handles
s->_fileHandles.clear();
s->_fileHandles.resize(5);
}
void script_free_engine(EngineState *s) {
script_free_vm_memory(s);
sciprintf("Freeing state-dependant data\n");
s->_kfuncTable.clear();
s->_parserWords.clear();
vocab_free_suffixes(s->resmgr, s->_parserSuffixes);
s->_parserBranches.clear();
vocab_free_rule_list(s->parser_rules);
s->_selectorNames.clear();
s->_kernelNames.clear();
vocabulary_free_opcodes(s->opcodes);
s->opcodes = NULL;
}
void script_free_breakpoints(EngineState *s) {
Breakpoint *bp, *bp_next;
// Free breakpoint list
bp = s->bp_list;
while (bp) {
bp_next = bp->next;
if (bp->type == BREAK_SELECTOR)
free(bp->data.name);
free(bp);
bp = bp_next;
}
s->bp_list = NULL;
}
/*************************************************************/
/* Game instance stuff: Init/Unitialize state-dependant data */
/*************************************************************/
int game_init(EngineState *s) {
// FIXME Use new VM instantiation code all over the place"
reg_t game_obj; // Address of the game object
dstack_t *stack;
stack = s->seg_manager->allocateStack(VM_STACK_SIZE, &s->stack_segment);
s->stack_base = stack->entries;
s->stack_top = s->stack_base + VM_STACK_SIZE;
if (!script_instantiate(s, 0)) {
sciprintf("game_init(): Could not instantiate script 0\n");
return 1;
}
s->parser_valid = 0; // Invalidate parser
s->parser_event = NULL_REG; // Invalidate parser event
s->_synonyms.clear(); // No synonyms
if (s->gfx_state && _reset_graphics_input(s))
return 1;
s->successor = NULL; // No successor
s->_statusBarText.clear(); // Status bar is blank
s->status_bar_foreground = 0;
s->status_bar_background = s->resmgr->_sciVersion >= SCI_VERSION_01_VGA ? 255 : 15;
SystemString *str = &s->sys_strings->strings[SYS_STRING_PARSER_BASE];
str->name = strdup("parser-base");
str->max_size = MAX_PARSER_BASE;
str->value = (reg_t*)sci_malloc(MAX_PARSER_BASE + 1);
str->value[0].segment = s->string_frag_segment; // Set to empty string
str->value[0].offset = 0; // Set to empty string
s->parser_base = make_reg(s->sys_strings_segment, SYS_STRING_PARSER_BASE);
s->game_start_time = g_system->getMillis();
s->last_wait_time = s->game_start_time;
s->debug_mode = 0x0; // Disable all debugging
s->onscreen_console = 0; // No onscreen console unless explicitly requested
srand(g_system->getMillis()); // Initialize random number generator
// script_dissect(0, s->_selectorNames);
game_obj = script_lookup_export(s, 0, 0);
// The first entry in the export table of script 0 points to the game object
const char *tmp = obj_get_name(s, game_obj);
if (!tmp) {
sciprintf("Error: script.000, export 0 ("PREG") does not\n"
" yield an object with a name -> sanity check failed\n", PRINT_REG(game_obj));
return 1;
}
s->_gameName = tmp;
sciprintf(" \"%s\" at "PREG"\n", s->_gameName.c_str(), PRINT_REG(game_obj));
s->game_obj = game_obj;
// Mark parse tree as unused
s->parser_nodes[0].type = PARSE_TREE_NODE_LEAF;
s->parser_nodes[0].content.value = 0;
s->_menubar = new Menubar(); // Create menu bar
if (s->sfx_init_flags & SFX_STATE_FLAG_NOSOUND)
game_init_sound(s, 0);
return 0;
}
int game_exit(EngineState *s) {
s->_executionStack.clear();
if (!s->successor) {
sfx_exit(&s->sound);
// Reinit because some other code depends on having a valid state
game_init_sound(s, SFX_STATE_FLAG_NOSOUND);
}
delete s->seg_manager;
s->_synonyms.clear();
sciprintf("Freeing miscellaneous data...\n");
// TODO Free parser segment here
// TODO Free scripts here
delete s->_menubar;
_free_graphics_input(s);
return 0;
}
} // End of namespace Sci