mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
Handlers inside scoped script types (e.g. CastScript) should not be discoverable via getHandler. Fixes menu screen in Derrat Sorcerum.
1753 lines
58 KiB
C++
1753 lines
58 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/file.h"
|
|
#include "common/substream.h"
|
|
|
|
#include "director/director.h"
|
|
#include "director/cast.h"
|
|
#include "director/debugger.h"
|
|
#include "director/movie.h"
|
|
#include "director/window.h"
|
|
#include "director/castmember/castmember.h"
|
|
#include "director/castmember/script.h"
|
|
#include "director/lingo/lingo-code.h"
|
|
#include "director/lingo/lingo-codegen.h"
|
|
#include "director/lingo/lingo-builtins.h"
|
|
#include "director/lingo/lingo-bytecode.h"
|
|
#include "director/lingo/lingo-the.h"
|
|
|
|
namespace Director {
|
|
|
|
static const LingoV4Bytecode lingoV4[] = {
|
|
{ 0x01, LC::c_procret, "" },
|
|
{ 0x02, LC::c_procret, "" },
|
|
{ 0x03, LC::cb_zeropush, "" },
|
|
{ 0x04, LC::c_mul, "" },
|
|
{ 0x05, LC::c_add, "" },
|
|
{ 0x06, LC::c_sub, "" },
|
|
{ 0x07, LC::c_div, "" },
|
|
{ 0x08, LC::c_mod, "" },
|
|
{ 0x09, LC::c_negate, "" },
|
|
{ 0x0a, LC::c_ampersand, "" },
|
|
{ 0x0b, LC::c_concat, "" },
|
|
{ 0x0c, LC::c_lt, "" },
|
|
{ 0x0d, LC::c_le, "" },
|
|
{ 0x0e, LC::c_neq, "" },
|
|
{ 0x0f, LC::c_eq, "" },
|
|
{ 0x10, LC::c_gt, "" },
|
|
{ 0x11, LC::c_ge, "" },
|
|
{ 0x12, LC::c_and, "" },
|
|
{ 0x13, LC::c_or, "" },
|
|
{ 0x14, LC::c_not, "" },
|
|
{ 0x15, LC::c_contains, "" },
|
|
{ 0x16, LC::c_starts, "" },
|
|
{ 0x17, LC::c_of, "" },
|
|
{ 0x18, LC::cb_hilite, "" },
|
|
{ 0x19, LC::c_intersects, "" },
|
|
{ 0x1a, LC::c_within, "" },
|
|
{ 0x1b, LC::c_field, "" },
|
|
{ 0x1c, LC::c_tell, "" },
|
|
{ 0x1d, LC::c_telldone, "" },
|
|
{ 0x1e, LC::cb_list, "" },
|
|
{ 0x1f, LC::cb_proplist, "" },
|
|
|
|
{ 0x41, LC::c_intpush, "B" },
|
|
{ 0x42, LC::c_argcnoretpush,"b" },
|
|
{ 0x43, LC::c_argcpush, "b" },
|
|
// 0x44, push a constant
|
|
{ 0x45, LC::c_namepush, "bN" },
|
|
{ 0x46, LC::cb_varrefpush, "bN" },
|
|
{ 0x48, LC::cb_globalpush, "bN" }, // used in event scripts
|
|
{ 0x49, LC::cb_globalpush, "bN" },
|
|
{ 0x4a, LC::cb_thepush, "bN" },
|
|
{ 0x4b, LC::cb_varpush, "bpaN" },
|
|
{ 0x4c, LC::cb_varpush, "bpvN" },
|
|
{ 0x4e, LC::cb_globalassign,"bN" }, // used in event scripts
|
|
{ 0x4f, LC::cb_globalassign,"bN" },
|
|
{ 0x50, LC::cb_theassign, "bN" },
|
|
{ 0x51, LC::cb_varassign, "bpaN" },
|
|
{ 0x52, LC::cb_varassign, "bpvN" },
|
|
{ 0x53, LC::c_jump, "jb" },
|
|
{ 0x54, LC::c_jump, "jbn" },
|
|
{ 0x55, LC::c_jumpifz, "jb" },
|
|
{ 0x56, LC::cb_localcall, "b" },
|
|
{ 0x57, LC::cb_call, "bN" },
|
|
{ 0x58, LC::cb_objectcall, "b" },
|
|
{ 0x59, LC::cb_v4assign, "b" },
|
|
{ 0x5a, LC::cb_v4assign2, "b" },
|
|
{ 0x5b, LC::cb_delete, "b" },
|
|
{ 0x5c, LC::cb_v4theentitypush, "b" },
|
|
{ 0x5d, LC::cb_v4theentityassign, "b" },
|
|
{ 0x5f, LC::cb_thepush2, "bN" },
|
|
{ 0x60, LC::cb_theassign2, "bN" },
|
|
{ 0x61, LC::cb_objectfieldpush, "bN" },
|
|
{ 0x62, LC::cb_objectfieldassign, "bN" },
|
|
{ 0x63, LC::cb_call, "bN" }, // tellcall
|
|
{ 0x64, LC::c_stackpeek, "b" },
|
|
{ 0x65, LC::c_stackdrop, "b" },
|
|
{ 0x66, LC::cb_v4theentitynamepush, "bN" },
|
|
|
|
{ 0x81, LC::c_intpush, "W" },
|
|
{ 0x82, LC::c_argcnoretpush,"w" },
|
|
{ 0x83, LC::c_argcpush, "w" },
|
|
// 0x84, push a constant
|
|
{ 0x85, LC::c_namepush, "wN" },
|
|
{ 0x86, LC::cb_varrefpush, "wN" },
|
|
{ 0x88, LC::cb_globalpush, "wN" }, // used in event scripts
|
|
{ 0x89, LC::cb_globalpush, "wN" },
|
|
{ 0x8a, LC::cb_thepush, "wN" },
|
|
{ 0x8b, LC::cb_varpush, "wpaN" },
|
|
{ 0x8c, LC::cb_varpush, "wpvN" },
|
|
{ 0x8e, LC::cb_globalassign,"wN" }, // used in event scripts
|
|
{ 0x8f, LC::cb_globalassign,"wN" },
|
|
{ 0x90, LC::cb_theassign, "wN" },
|
|
{ 0x91, LC::cb_varassign, "wpaN" },
|
|
{ 0x92, LC::cb_varassign, "wpvN" },
|
|
{ 0x93, LC::c_jump, "jw" },
|
|
{ 0x94, LC::c_jump, "jwn" },
|
|
{ 0x95, LC::c_jumpifz, "jw" },
|
|
{ 0x96, LC::cb_localcall, "w" },
|
|
{ 0x97, LC::cb_call, "wN" },
|
|
{ 0x98, LC::cb_objectcall, "w" },
|
|
{ 0x99, LC::cb_v4assign, "w" },
|
|
{ 0x9a, LC::cb_v4assign2, "w" },
|
|
{ 0x9c, LC::cb_v4theentitypush, "w" },
|
|
{ 0x9d, LC::cb_v4theentityassign, "w" },
|
|
{ 0x9f, LC::cb_thepush2, "wN" },
|
|
{ 0xa0, LC::cb_theassign2, "wN" },
|
|
{ 0xa1, LC::cb_objectfieldpush, "wN" },
|
|
{ 0xa2, LC::cb_objectfieldassign, "wN" },
|
|
{ 0xa3, LC::cb_call, "wN" }, // tellcall
|
|
{ 0xa4, LC::c_stackpeek, "w" },
|
|
{ 0xa5, LC::c_stackdrop, "w" },
|
|
{ 0xa6, LC::cb_v4theentitynamepush, "wN" },
|
|
{ 0, nullptr, nullptr }
|
|
};
|
|
|
|
static const LingoV4TheEntity lingoV4TheEntity[] = {
|
|
{ 0x00, 0x00, kTheFloatPrecision, kTheNOField, true, kTEANOArgs },
|
|
{ 0x00, 0x01, kTheMouseDownScript, kTheNOField, true, kTEANOArgs },
|
|
{ 0x00, 0x02, kTheMouseUpScript, kTheNOField, true, kTEANOArgs },
|
|
{ 0x00, 0x03, kTheKeyDownScript, kTheNOField, true, kTEANOArgs },
|
|
{ 0x00, 0x04, kTheKeyUpScript, kTheNOField, true, kTEANOArgs },
|
|
{ 0x00, 0x05, kTheTimeoutScript, kTheNOField, true, kTEANOArgs },
|
|
{ 0x00, 0x06, kTheTime, kTheShort, false, kTEANOArgs },
|
|
{ 0x00, 0x07, kTheTime, kTheAbbr, false, kTEANOArgs },
|
|
{ 0x00, 0x08, kTheTime, kTheLong, false, kTEANOArgs },
|
|
{ 0x00, 0x09, kTheDate, kTheShort, false, kTEANOArgs },
|
|
{ 0x00, 0x0a, kTheDate, kTheAbbr, false, kTEANOArgs },
|
|
{ 0x00, 0x0b, kTheDate, kTheLong, false, kTEANOArgs },
|
|
{ 0x00, 0x0c, kTheChars, kTheLast, false, kTEAString },
|
|
{ 0x00, 0x0d, kTheWords, kTheLast, false, kTEAString },
|
|
{ 0x00, 0x0e, kTheItems, kTheLast, false, kTEAString },
|
|
{ 0x00, 0x0f, kTheLines, kTheLast, false, kTEAString },
|
|
|
|
{ 0x01, 0x01, kTheChars, kTheNumber, false, kTEAString },
|
|
{ 0x01, 0x02, kTheWords, kTheNumber, false, kTEAString },
|
|
{ 0x01, 0x03, kTheItems, kTheNumber, false, kTEAString },
|
|
{ 0x01, 0x04, kTheLines, kTheNumber, false, kTEAString },
|
|
|
|
{ 0x02, 0x01, kTheMenu, kTheName, false, kTEAMenuId },
|
|
{ 0x02, 0x02, kTheMenuItems, kTheNumber, false, kTEAMenuId },
|
|
|
|
{ 0x03, 0x01, kTheMenuItem, kTheName, true, kTEAMenuIdItemId },
|
|
{ 0x03, 0x02, kTheMenuItem, kTheCheckMark, true, kTEAMenuIdItemId },
|
|
{ 0x03, 0x03, kTheMenuItem, kTheEnabled, true, kTEAMenuIdItemId },
|
|
{ 0x03, 0x04, kTheMenuItem, kTheScript, true, kTEAMenuIdItemId },
|
|
|
|
{ 0x04, 0x01, kTheSoundEntity, kTheVolume, true, kTEAItemId },
|
|
|
|
{ 0x06, 0x01, kTheSprite, kTheType, true, kTEAItemId },
|
|
{ 0x06, 0x02, kTheSprite, kTheBackColor, true, kTEAItemId },
|
|
{ 0x06, 0x03, kTheSprite, kTheBottom, true, kTEAItemId },
|
|
{ 0x06, 0x04, kTheSprite, kTheCastNum, true, kTEAItemId },
|
|
{ 0x06, 0x05, kTheSprite, kTheConstraint, true, kTEAItemId },
|
|
{ 0x06, 0x06, kTheSprite, kTheCursor, true, kTEAItemId },
|
|
{ 0x06, 0x07, kTheSprite, kTheForeColor, true, kTEAItemId },
|
|
{ 0x06, 0x08, kTheSprite, kTheHeight, true, kTEAItemId },
|
|
{ 0x06, 0x09, kTheSprite, kTheImmediate, true, kTEAItemId },
|
|
{ 0x06, 0x0a, kTheSprite, kTheInk, true, kTEAItemId },
|
|
{ 0x06, 0x0b, kTheSprite, kTheLeft, true, kTEAItemId },
|
|
{ 0x06, 0x0c, kTheSprite, kTheLineSize, true, kTEAItemId },
|
|
{ 0x06, 0x0d, kTheSprite, kTheLocH, true, kTEAItemId },
|
|
{ 0x06, 0x0e, kTheSprite, kTheLocV, true, kTEAItemId },
|
|
{ 0x06, 0x0f, kTheSprite, kTheMovieRate, true, kTEAItemId },
|
|
{ 0x06, 0x10, kTheSprite, kTheMovieTime, true, kTEAItemId },
|
|
{ 0x06, 0x11, kTheSprite, kThePattern, true, kTEAItemId },
|
|
{ 0x06, 0x12, kTheSprite, kThePuppet, true, kTEAItemId },
|
|
{ 0x06, 0x13, kTheSprite, kTheRight, true, kTEAItemId },
|
|
{ 0x06, 0x14, kTheSprite, kTheStartTime, true, kTEAItemId },
|
|
{ 0x06, 0x15, kTheSprite, kTheStopTime, true, kTEAItemId },
|
|
{ 0x06, 0x16, kTheSprite, kTheStretch, true, kTEAItemId },
|
|
{ 0x06, 0x17, kTheSprite, kTheTop, true, kTEAItemId },
|
|
{ 0x06, 0x18, kTheSprite, kTheTrails, true, kTEAItemId },
|
|
{ 0x06, 0x19, kTheSprite, kTheVisible, true, kTEAItemId },
|
|
{ 0x06, 0x1a, kTheSprite, kTheVolume, true, kTEAItemId },
|
|
{ 0x06, 0x1b, kTheSprite, kTheWidth, true, kTEAItemId },
|
|
{ 0x06, 0x1c, kTheSprite, kTheBlend, true, kTEAItemId },
|
|
{ 0x06, 0x1d, kTheSprite, kTheScriptNum, true, kTEAItemId },
|
|
{ 0x06, 0x1e, kTheSprite, kTheMoveableSprite, true, kTEAItemId },
|
|
{ 0x06, 0x1f, kTheSprite, kTheEditableText, true, kTEAItemId },
|
|
{ 0x06, 0x20, kTheSprite, kTheScoreColor, true, kTEAItemId },
|
|
{ 0x06, 0x21, kTheSprite, kTheLoc, true, kTEAItemId },
|
|
{ 0x06, 0x22, kTheSprite, kTheRect, true, kTEAItemId },
|
|
{ 0x06, 0x23, kTheSprite, kTheMemberNum, true, kTEAItemId }, // D5
|
|
{ 0x06, 0x24, kTheSprite, kTheCastLibNum, true, kTEAItemId }, // D5
|
|
{ 0x06, 0x25, kTheSprite, kTheMember, true, kTEAItemId }, // D5
|
|
// scriptInstanceList
|
|
// currentTime
|
|
// mostRecentCuePoint
|
|
// tweened
|
|
// name
|
|
|
|
{ 0x07, 0x01, kTheBeepOn, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x02, kTheButtonStyle, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x03, kTheCenterStage, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x04, kTheCheckBoxAccess, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x05, kTheCheckBoxType, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x06, kTheColorDepth, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x07, kTheColorQD, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x08, kTheExitLock, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x09, kTheFixStageSize, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x0a, kTheFullColorPermit, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x0b, kTheImageDirect, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x0c, kTheDoubleClick, kTheNOField, true, kTEANOArgs },
|
|
// { 0x07, 0x0d, ???, kTheNOField, true, kTEANOArgs }, // key
|
|
{ 0x07, 0x0e, kTheLastClick, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x0f, kTheLastEvent, kTheNOField, true, kTEANOArgs },
|
|
// { 0x07, 0x10, ???, kTheNOField, true, kTEANOArgs }, // keyCode
|
|
{ 0x07, 0x11, kTheLastKey, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x12, kTheLastRoll, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x13, kTheTimeoutLapsed, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x14, kTheMultiSound, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x15, kThePauseState, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x16, kTheQuickTimePresent, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x17, kTheSelEnd, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x18, kTheSelStart, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x19, kTheSoundEnabled, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x1a, kTheSoundLevel, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x1b, kTheStageColor, kTheNOField, true, kTEANOArgs },
|
|
// { 0x07, 0x1c, ????, kTheNOField, true, kTEANOArgs }, // indicates dontPassEvent was called
|
|
{ 0x07, 0x1d, kTheSwitchColorDepth, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x1e, kTheTimeoutKeyDown, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x1f, kTheTimeoutLength, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x20, kTheTimeoutMouse, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x21, kTheTimeoutPlay, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x22, kTheTimer, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x23, kThePreLoadRAM, kTheNOField, true, kTEANOArgs },
|
|
{ 0x07, 0x24, kTheVideoForWindowsPresent, kTheNOField, true, kTEANOArgs }, // D5
|
|
// netPresent
|
|
// safePlayer
|
|
// soundKeepDevice
|
|
// soundMixMedia
|
|
|
|
{ 0x08, 0x01, kThePerFrameHook, kTheNOField, true, kTEANOArgs },
|
|
{ 0x08, 0x02, kTheCastMembers, kTheNumber, false, kTEANOArgs },
|
|
{ 0x08, 0x03, kTheMenus, kTheNumber, false, kTEANOArgs },
|
|
{ 0x08, 0x04, kTheCastLibs, kTheNumber, false, kTEANOArgs }, // D5
|
|
{ 0x08, 0x05, kTheXtras, kTheNumber, false, kTEANOArgs }, // D5
|
|
|
|
{ 0x09, 0x01, kTheCast, kTheName, true, kTEAItemId },
|
|
{ 0x09, 0x02, kTheCast, kTheText, true, kTEAItemId },
|
|
{ 0x09, 0x03, kTheCast, kTheTextStyle, true, kTEAItemId },
|
|
{ 0x09, 0x04, kTheCast, kTheTextFont, true, kTEAItemId },
|
|
{ 0x09, 0x05, kTheCast, kTheTextHeight, true, kTEAItemId },
|
|
{ 0x09, 0x06, kTheCast, kTheTextAlign, true, kTEAItemId },
|
|
{ 0x09, 0x07, kTheCast, kTheTextSize, true, kTEAItemId },
|
|
{ 0x09, 0x08, kTheCast, kThePicture, true, kTEAItemId },
|
|
{ 0x09, 0x09, kTheCast, kTheHilite, true, kTEAItemId },
|
|
{ 0x09, 0x0a, kTheCast, kTheNumber, true, kTEAItemId },
|
|
{ 0x09, 0x0b, kTheCast, kTheSize, true, kTEAItemId },
|
|
{ 0x09, 0x11, kTheCast, kTheForeColor, true, kTEAItemId },
|
|
{ 0x09, 0x12, kTheCast, kTheBackColor, true, kTEAItemId },
|
|
{ 0x09, 0x13, kTheCast, kTheType, true, kTEAItemId }, // D5
|
|
|
|
// the chunk of cast
|
|
{ 0x0a, 0x03, kTheChunk, kTheTextStyle, true, kTEAChunk },
|
|
{ 0x0a, 0x04, kTheChunk, kTheTextFont, true, kTEAChunk },
|
|
{ 0x0a, 0x05, kTheChunk, kTheTextHeight, true, kTEAChunk },
|
|
{ 0x0a, 0x07, kTheChunk, kTheTextSize, true, kTEAChunk },
|
|
{ 0x0a, 0x11, kTheChunk, kTheForeColor, true, kTEAChunk },
|
|
|
|
{ 0x0b, 0x01, kTheField, kTheName, true, kTEAItemId },
|
|
{ 0x0b, 0x02, kTheField, kTheText, true, kTEAItemId },
|
|
{ 0x0b, 0x03, kTheField, kTheTextStyle, true, kTEAItemId },
|
|
{ 0x0b, 0x04, kTheField, kTheTextFont, true, kTEAItemId },
|
|
{ 0x0b, 0x05, kTheField, kTheTextHeight, true, kTEAItemId },
|
|
{ 0x0b, 0x06, kTheField, kTheTextAlign, true, kTEAItemId },
|
|
{ 0x0b, 0x07, kTheField, kTheTextSize, true, kTEAItemId },
|
|
{ 0x0b, 0x09, kTheField, kTheHilite, true, kTEAItemId },
|
|
{ 0x0b, 0x11, kTheField, kTheForeColor, true, kTEAItemId },
|
|
|
|
// the chunk of field
|
|
{ 0x0c, 0x03, kTheChunk, kTheTextStyle, true, kTEAChunk },
|
|
{ 0x0c, 0x04, kTheChunk, kTheTextFont, true, kTEAChunk },
|
|
{ 0x0c, 0x05, kTheChunk, kTheTextHeight, true, kTEAChunk },
|
|
{ 0x0c, 0x07, kTheChunk, kTheTextSize, true, kTEAChunk },
|
|
{ 0x0c, 0x11, kTheChunk, kTheForeColor, true, kTEAChunk },
|
|
|
|
{ 0x0d, 0x0c, kTheCast, kTheLoop, true, kTEAItemId },
|
|
{ 0x0d, 0x0d, kTheCast, kTheDuration, true, kTEAItemId },
|
|
{ 0x0d, 0x0e, kTheCast, kTheController, true, kTEAItemId },
|
|
{ 0x0d, 0x0f, kTheCast, kTheDirectToStage, true, kTEAItemId },
|
|
{ 0x0d, 0x10, kTheCast, kTheSound, true, kTEAItemId },
|
|
|
|
{ 0xff, 0, 0, 0, false, kTEANOArgs }
|
|
};
|
|
|
|
|
|
void Lingo::initBytecode() {
|
|
// All new bytecodes must have respective entry in funcDescr[]
|
|
// array in lingo-code.cpp, which is used for decompilation
|
|
//
|
|
// Check that all opcodes have entries
|
|
Common::HashMap<inst, bool> list;
|
|
bool bailout = false;
|
|
|
|
// Build reverse hashmap
|
|
for (auto &it : _functions)
|
|
list[(inst)it._key] = true;
|
|
|
|
for (const LingoV4Bytecode *op = lingoV4; op->opcode; op++) {
|
|
_lingoV4[op->opcode] = op;
|
|
|
|
if (!list.contains(op->func)) {
|
|
warning("Lingo::initBytecode(): Missing prototype for opcode 0x%02x", op->opcode);
|
|
bailout = true;
|
|
}
|
|
}
|
|
|
|
if (bailout)
|
|
error("Lingo::initBytecode(): Add entries to funcDescr[] in lingo-code.cpp");
|
|
|
|
for (const LingoV4TheEntity *ent = lingoV4TheEntity; ent->bank != 0xff; ent++) {
|
|
_lingoV4TheEntity[(ent->bank << 8) + ent->firstArg] = ent;
|
|
}
|
|
}
|
|
|
|
Datum Lingo::findVarV4(int varType, const Datum &id) {
|
|
Datum res;
|
|
switch (varType) {
|
|
case 1: // global
|
|
case 2: // global
|
|
case 3: // property/instance
|
|
if (id.type == SYMBOL) {
|
|
res = id;
|
|
res.type = (varType == 3) ? PROPREF : GLOBALREF;
|
|
} else {
|
|
warning("BUILDBOT: findVarV4: expected ID for var type %d to be SYMBOL, got %s", varType, id.type2str());
|
|
}
|
|
break;
|
|
case 4: // arg
|
|
case 5: // local
|
|
{
|
|
Common::Array<CFrame *> &callstack = _state->callstack;
|
|
if (callstack.empty()) {
|
|
warning("BUILDBOT: findVarV4: no call frame");
|
|
return res;
|
|
}
|
|
int stride = 6;
|
|
if (g_director->getVersion() >= 500) {
|
|
stride = 8;
|
|
}
|
|
if (id.asInt() % stride != 0) {
|
|
warning("BUILDBOT: findVarV4: invalid var ID %d for var type %d (not divisible by %d)", id.asInt(), varType, stride);
|
|
return res;
|
|
}
|
|
int varIndex = id.asInt() / stride;
|
|
Common::Array<Common::String> *varNames = (varType == 4)
|
|
? callstack.back()->sp.argNames
|
|
: callstack.back()->sp.varNames;
|
|
|
|
if (varIndex < (int)varNames->size()) {
|
|
res = (*varNames)[varIndex];
|
|
res.type = LOCALREF;
|
|
} else {
|
|
warning("BUILDBOT: findVarV4: invalid var ID %d for var type %d (too high)", id.asInt(), varType);
|
|
}
|
|
}
|
|
break;
|
|
case 6: // field
|
|
if (g_director->getVersion() < 500) {
|
|
res = id.asMemberID();
|
|
} else {
|
|
Datum member = g_lingo->pop();
|
|
res = g_lingo->toCastMemberID(member, id);
|
|
}
|
|
res.type = FIELDREF;
|
|
break;
|
|
default:
|
|
warning("BUILDBOT: findVarV4: unhandled var type %d", varType);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void LC::cb_unk() {
|
|
uint opcode = g_lingo->readInt();
|
|
warning("STUB: opcode 0x%02x", opcode);
|
|
}
|
|
|
|
void LC::cb_unk1() {
|
|
uint opcode = g_lingo->readInt();
|
|
uint arg1 = g_lingo->readInt();
|
|
warning("STUB: opcode 0x%02x (%d)", opcode, arg1);
|
|
}
|
|
|
|
void LC::cb_unk2() {
|
|
uint opcode = g_lingo->readInt();
|
|
uint arg1 = g_lingo->readInt();
|
|
uint arg2 = g_lingo->readInt();
|
|
warning("STUB: opcode 0x%02x (%d, %d)", opcode, arg1, arg2);
|
|
}
|
|
|
|
void LC::cb_delete() {
|
|
int varType = g_lingo->readInt();
|
|
Datum varID = g_lingo->pop();
|
|
Datum var = g_lingo->findVarV4(varType, varID);
|
|
Datum chunkRef = readChunkRef(var);
|
|
g_lingo->push(chunkRef);
|
|
LC::c_delete();
|
|
}
|
|
|
|
void LC::cb_hilite() {
|
|
Datum fieldID = g_lingo->pop().asMemberID();
|
|
fieldID.type = FIELDREF;
|
|
Datum chunkRef = readChunkRef(fieldID);
|
|
g_lingo->push(chunkRef);
|
|
LC::c_hilite();
|
|
}
|
|
|
|
void LC::cb_localcall() {
|
|
int functionId = g_lingo->readInt();
|
|
|
|
Datum nargs = g_lingo->pop();
|
|
if ((nargs.type == ARGC) || (nargs.type == ARGCNORET)) {
|
|
Common::String name = g_lingo->_state->context->_functionNames[functionId];
|
|
if (debugChannelSet(3, kDebugLingoExec))
|
|
g_lingo->printArgs(name.c_str(), nargs.u.i, "localcall:");
|
|
|
|
LC::call(name, nargs.u.i, nargs.type == ARGC);
|
|
|
|
} else {
|
|
warning("cb_localcall: first arg should be of type ARGC or ARGCNORET, not %s", nargs.type2str());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void LC::cb_objectcall() {
|
|
int varType = g_lingo->readInt();
|
|
Datum varId = g_lingo->pop();
|
|
Datum nargs = g_lingo->pop();
|
|
|
|
Datum var = g_lingo->findVarV4(varType, varId);
|
|
if (!var.isVarRef()) {
|
|
warning("cb_objectcall: first arg did not resolve to variable");
|
|
return;
|
|
}
|
|
|
|
if ((nargs.type != ARGC) && (nargs.type != ARGCNORET)) {
|
|
warning("cb_objectcall: second arg should be of type ARGC or ARGCNORET, not %s", nargs.type2str());
|
|
return;
|
|
}
|
|
|
|
if (nargs.u.i > 0) {
|
|
Datum &firstArg = g_lingo->_state->stack[g_lingo->_state->stack.size() - nargs.u.i];
|
|
// The first arg could be either a method name or a variable name
|
|
if (firstArg.type == SYMBOL) {
|
|
firstArg.type = VARREF;
|
|
}
|
|
}
|
|
|
|
LC::call(*var.u.s, nargs.u.i, nargs.type == ARGC);
|
|
}
|
|
|
|
|
|
void LC::cb_v4assign() {
|
|
int arg = g_lingo->readInt();
|
|
int op = (arg >> 4) & 0xF;
|
|
int varType = arg & 0xF;
|
|
Datum varId = g_lingo->pop();
|
|
|
|
Datum var = g_lingo->findVarV4(varType, varId);
|
|
g_lingo->push(var);
|
|
|
|
switch (op) {
|
|
case 1:
|
|
// put value into var
|
|
LC::c_assign();
|
|
break;
|
|
case 2:
|
|
// put value after var
|
|
LC::c_putafter();
|
|
break;
|
|
case 3:
|
|
// put value before var
|
|
LC::c_putbefore();
|
|
break;
|
|
default:
|
|
warning("cb_v4assign: unknown operator %d", op);
|
|
g_lingo->pop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void LC::cb_list() {
|
|
Datum nargs = g_lingo->pop();
|
|
if ((nargs.type != ARGC) && (nargs.type != ARGCNORET)) {
|
|
error("cb_list: first arg should be of type ARGC or ARGCNORET, not %s", nargs.type2str());
|
|
}
|
|
LB::b_list(nargs.u.i);
|
|
}
|
|
|
|
|
|
void LC::cb_proplist() {
|
|
Datum nargs = g_lingo->pop();
|
|
if ((nargs.type != ARGC) && (nargs.type != ARGCNORET)) {
|
|
error("cb_proplist: first arg should be of type ARGC or ARGCNORET, not %s", nargs.type2str());
|
|
}
|
|
int arraySize = nargs.u.i;
|
|
if (arraySize % 2) {
|
|
warning("cb_proplist: list should have an even number of entries, ignoring the last one");
|
|
}
|
|
|
|
Datum result;
|
|
result.type = PARRAY;
|
|
result.u.parr = new PArray;
|
|
arraySize /= 2;
|
|
|
|
for (int i = 0; i < arraySize; i++) {
|
|
Datum v = g_lingo->pop();
|
|
Datum p = g_lingo->pop();
|
|
|
|
PCell cell = PCell(p, v);
|
|
result.u.parr->arr.insert_at(0, cell);
|
|
};
|
|
|
|
if (nargs.u.i % 2)
|
|
g_lingo->pop();
|
|
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
|
|
void LC::cb_call() {
|
|
Common::String name = g_lingo->readString();
|
|
|
|
Datum nargs = g_lingo->pop();
|
|
if ((nargs.type == ARGC) || (nargs.type == ARGCNORET)) {
|
|
LC::call(name, nargs.u.i, nargs.type == ARGC);
|
|
|
|
} else {
|
|
warning("cb_call: first arg should be of type ARGC or ARGCNORET, not %s", nargs.type2str());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void LC::cb_globalpush() {
|
|
Common::String name = g_lingo->readString();
|
|
Datum target(name);
|
|
target.type = GLOBALREF;
|
|
debugC(3, kDebugLingoExec, "cb_globalpush: pushing %s to stack", name.c_str());
|
|
Datum result = g_lingo->varFetch(target);
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
|
|
void LC::cb_globalassign() {
|
|
Common::String name = g_lingo->readString();
|
|
Datum target(name);
|
|
target.type = GLOBALREF;
|
|
debugC(3, kDebugLingoExec, "cb_globalassign: assigning to %s", name.c_str());
|
|
Datum source = g_lingo->pop();
|
|
g_lingo->varAssign(target, source);
|
|
}
|
|
|
|
void LC::cb_objectfieldassign() {
|
|
Common::String fieldName = g_lingo->readString();
|
|
Datum value = g_lingo->pop();
|
|
Datum object = g_lingo->pop();
|
|
g_lingo->setObjectProp(object, fieldName, value);
|
|
}
|
|
|
|
void LC::cb_objectfieldpush() {
|
|
Common::String fieldName = g_lingo->readString();
|
|
Datum object = g_lingo->pop();
|
|
g_lingo->getObjectProp(object, fieldName);
|
|
}
|
|
|
|
void LC::cb_varrefpush() {
|
|
Common::String name = g_lingo->readString();
|
|
Datum result(name);
|
|
result.type = SYMBOL;
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
void LC::cb_theassign() {
|
|
// cb_theassign is for setting script/factory-level properties
|
|
Common::String name = g_lingo->readString();
|
|
Datum value = g_lingo->pop();
|
|
if (g_lingo->_state->me.type == OBJECT) {
|
|
// Don't bother checking if the property is defined, leave that to the object.
|
|
// For D3-style anonymous objects/factories, you are allowed to define whatever properties you like.
|
|
g_debugger->propWriteHook(name);
|
|
g_lingo->_state->me.u.obj->setProp(name, value);
|
|
} else {
|
|
warning("cb_theassign: no me object");
|
|
}
|
|
}
|
|
|
|
void LC::cb_theassign2() {
|
|
// cb_theassign2 is for setting movie-level properties
|
|
Common::String name = g_lingo->readString();
|
|
Datum value = g_lingo->pop();
|
|
|
|
if (g_lingo->_theEntities.contains(name)) {
|
|
const TheEntity *entity = g_lingo->_theEntities[name];
|
|
Datum id;
|
|
id.u.i = 0;
|
|
id.type = VOID;
|
|
g_lingo->setTheEntity(entity->entity, id, kTEANOArgs, value);
|
|
} else {
|
|
warning("LC::cb_theassign2 Can't assign theEntity: (%s)", name.c_str());
|
|
}
|
|
}
|
|
|
|
void LC::cb_thepush() {
|
|
Common::String name = g_lingo->readString();
|
|
if (g_lingo->_state->me.type == OBJECT) {
|
|
if (g_lingo->_state->me.u.obj->hasProp(name)) {
|
|
g_lingo->push(g_lingo->_state->me.u.obj->getProp(name));
|
|
g_debugger->propReadHook(name);
|
|
return;
|
|
}
|
|
|
|
if (name.equals("me")) {
|
|
g_lingo->push(g_lingo->_state->me);
|
|
g_debugger->propReadHook(name);
|
|
return;
|
|
}
|
|
|
|
warning("cb_thepush: me object has no property '%s', type: %d", name.c_str(), g_lingo->_state->me.type);
|
|
} else {
|
|
debugC(1, kDebugLingoExec, "cb_thepush: attempted to access property '%s' with no me object, returning VOID", name.c_str());
|
|
}
|
|
g_lingo->pushVoid();
|
|
}
|
|
|
|
void LC::cb_thepush2() {
|
|
Datum result;
|
|
Common::String name = g_lingo->readString();
|
|
if (g_lingo->_theEntities.contains(name)) {
|
|
const TheEntity *entity = g_lingo->_theEntities[name];
|
|
Datum id;
|
|
id.u.i = 0;
|
|
id.type = VOID;
|
|
debugC(3, kDebugLingoExec, "cb_thepush: pushing value of entity %s to stack", name.c_str());
|
|
result = g_lingo->getTheEntity(entity->entity, id, kTEANOArgs);
|
|
} else {
|
|
warning("LC::cb_thepush2 Can't find theEntity: (%s)", name.c_str());
|
|
result.type = VOID;
|
|
}
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
void LC::cb_varpush() {
|
|
Common::String name = g_lingo->readString();
|
|
Datum target(name);
|
|
target.type = LOCALREF;
|
|
debugC(3, kDebugLingoExec, "cb_varpush: pushing %s to stack", name.c_str());
|
|
Datum result = g_lingo->varFetch(target);
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
|
|
void LC::cb_varassign() {
|
|
Common::String name = g_lingo->readString();
|
|
Datum target(name);
|
|
target.type = LOCALREF;
|
|
debugC(3, kDebugLingoExec, "cb_varassign: assigning to %s", name.c_str());
|
|
Datum source = g_lingo->pop();
|
|
// Local variables should be initialised by the script, no varCreate here
|
|
g_lingo->varAssign(target, source);
|
|
}
|
|
|
|
|
|
void LC::cb_v4assign2() {
|
|
int arg = g_lingo->readInt();
|
|
int op = (arg >> 4) & 0xF;
|
|
int varType = arg & 0xF;
|
|
Datum varId = g_lingo->pop();
|
|
|
|
Datum var = g_lingo->findVarV4(varType, varId);
|
|
Datum ref = readChunkRef(var);
|
|
g_lingo->push(ref);
|
|
|
|
switch (op) {
|
|
case 1:
|
|
// put value into chunk
|
|
LC::c_assign();
|
|
break;
|
|
case 2:
|
|
// put value after chunk
|
|
LC::c_putafter();
|
|
break;
|
|
case 3:
|
|
// put value before chunk
|
|
LC::c_putbefore();
|
|
break;
|
|
default:
|
|
warning("cb_v4assign2: unknown operator %d", op);
|
|
g_lingo->pop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void LC::cb_v4theentitypush() {
|
|
int bank = g_lingo->readInt();
|
|
|
|
int firstArg = g_lingo->pop().asInt();
|
|
Datum result;
|
|
result.u.s = nullptr;
|
|
result.type = VOID;
|
|
|
|
int key = (bank << 8) + firstArg;
|
|
if (g_lingo->_lingoV4TheEntity.contains(key)) {
|
|
debugC(3, kDebugLingoExec, "cb_v4theentitypush: mapping 0x%02x, 0x%02x", bank, firstArg);
|
|
int entity = g_lingo->_lingoV4TheEntity[key]->entity;
|
|
int field = g_lingo->_lingoV4TheEntity[key]->field;
|
|
switch (g_lingo->_lingoV4TheEntity[key]->type) {
|
|
case kTEANOArgs:
|
|
{
|
|
Datum id;
|
|
id.u.i = 0;
|
|
id.type = VOID;
|
|
debugC(3, kDebugLingoExec, "cb_v4theentitypush: calling getTheEntity(%s, VOID, %s)", g_lingo->entity2str(entity), g_lingo->field2str(field));
|
|
result = g_lingo->getTheEntity(entity, id, field);
|
|
}
|
|
break;
|
|
case kTEAItemId:
|
|
{
|
|
Datum id = g_lingo->pop();
|
|
if ((entity == kTheCast || entity == kTheField) && g_director->getVersion() >= 500) {
|
|
// For "the member", D5 and above have a lib ID followed by a member ID
|
|
// Pre-resolve them here.
|
|
Datum member = g_lingo->pop();
|
|
CastMemberID resolved = g_lingo->toCastMemberID(member, id);
|
|
id = Datum(resolved);
|
|
}
|
|
debugC(3, kDebugLingoExec, "cb_v4theentitypush: calling getTheEntity(%s, %s, %s)", g_lingo->entity2str(entity), id.asString(true).c_str(), g_lingo->field2str(field));
|
|
result = g_lingo->getTheEntity(entity, id, field);
|
|
}
|
|
break;
|
|
case kTEAMenuId:
|
|
{
|
|
Datum id = g_lingo->pop();
|
|
debugC(3, kDebugLingoExec, "cb_v4theentitypush: calling getTheEntity(%s, %s, %s)", g_lingo->entity2str(entity), id.asString(true).c_str(), g_lingo->field2str(field));
|
|
if (id.type == INT) {
|
|
int menuId = id.u.i;
|
|
id.u.menu = new MenuReference();
|
|
id.u.menu->menuIdNum = menuId;
|
|
} else if (id.type == STRING) {
|
|
Common::String *menuId = id.u.s;
|
|
id.u.menu = new MenuReference();
|
|
id.u.menu->menuIdStr = menuId;
|
|
} else {
|
|
warning("LC::cb_v4theentitypush : Unknown type of menu Reference %d of entity type %d", id.type, g_lingo->_lingoV4TheEntity[key]->type);
|
|
break;
|
|
}
|
|
id.type = MENUREF;
|
|
|
|
result = g_lingo->getTheEntity(entity, id, field);
|
|
}
|
|
break;
|
|
case kTEAString:
|
|
{
|
|
Datum stringArg = g_lingo->pop();
|
|
ChunkType chunkType = kChunkChar;
|
|
switch (entity) {
|
|
case kTheChars:
|
|
chunkType = kChunkChar;
|
|
break;
|
|
case kTheWords:
|
|
chunkType = kChunkWord;
|
|
break;
|
|
case kTheLines:
|
|
chunkType = kChunkLine;
|
|
break;
|
|
case kTheItems:
|
|
chunkType = kChunkItem;
|
|
break;
|
|
}
|
|
Datum chunkRef = LC::lastChunk(chunkType, stringArg);
|
|
switch (field) {
|
|
case kTheLast:
|
|
result = chunkRef.eval();
|
|
break;
|
|
case kTheNumber:
|
|
result = chunkRef.u.cref->startChunk;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case kTEAMenuIdItemId:
|
|
{
|
|
/*Datum menuId = */g_lingo->pop();
|
|
/*Datum itemId = */g_lingo->pop();
|
|
warning("cb_v4theentitypush: STUB: kTEAMenuIdItemId");
|
|
}
|
|
break;
|
|
case kTEAChunk:
|
|
{
|
|
Datum fieldRef = g_lingo->pop().asMemberID();
|
|
fieldRef.type = FIELDREF;
|
|
Datum chunkRef = readChunkRef(fieldRef);
|
|
result = g_lingo->getTheEntity(entity, chunkRef, field);
|
|
}
|
|
break;
|
|
default:
|
|
warning("cb_v4theentitypush: unknown call type %d", g_lingo->_lingoV4TheEntity[key]->type);
|
|
break;
|
|
}
|
|
} else {
|
|
warning("cb_v4theentitypush: BUILDBOT: unhandled mapping 0x%02x 0x%02x", bank, firstArg);
|
|
}
|
|
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
|
|
void LC::cb_v4theentitynamepush() {
|
|
Datum nargs = g_lingo->pop();
|
|
if ((nargs.type == ARGC) || (nargs.type == ARGCNORET)) {
|
|
if (nargs.u.i > 0) {
|
|
warning("cb_v4theentitynamepush: expecting argc to be 0, not %d", nargs.u.i);
|
|
for (int i = 0; i < nargs.u.i; i++) {
|
|
g_lingo->pop();
|
|
}
|
|
}
|
|
} else {
|
|
warning("cb_v4theentitynamepush: first arg should be of type ARGC or ARGCNORET, not %s", nargs.type2str());
|
|
}
|
|
|
|
Common::String name = g_lingo->readString();
|
|
|
|
Datum id;
|
|
id.u.s = nullptr;
|
|
id.type = VOID;
|
|
|
|
if (!g_lingo->_theEntities.contains(name)) {
|
|
warning("BUILDBOT: cb_v4theentitynamepush: missing the entity %s", name.c_str());
|
|
g_lingo->push(Datum());
|
|
return;
|
|
}
|
|
const TheEntity *entity = g_lingo->_theEntities[name];
|
|
|
|
debugC(3, kDebugLingoExec, "cb_v4theentitynamepush: %s", name.c_str());
|
|
debugC(3, kDebugLingoExec, "cb_v4theentitynamepush: calling getTheEntity(%s, VOID, kTheNOField)", g_lingo->entity2str(entity->entity));
|
|
Datum result = g_lingo->getTheEntity(entity->entity, id, kTheNOField);
|
|
|
|
g_lingo->push(result);
|
|
}
|
|
|
|
|
|
void LC::cb_v4theentityassign() {
|
|
int bank = g_lingo->readInt();
|
|
|
|
int firstArg = g_lingo->pop().asInt();
|
|
Datum value = g_lingo->pop();
|
|
Datum result;
|
|
result.u.s = nullptr;
|
|
result.type = VOID;
|
|
|
|
int key = (bank << 8) + firstArg;
|
|
if (!g_lingo->_lingoV4TheEntity.contains(key)) {
|
|
warning("cb_v4theentityassign: unhandled mapping 0x%02x 0x%02x", bank, firstArg);
|
|
|
|
return;
|
|
}
|
|
|
|
debugC(3, kDebugLingoExec, "cb_v4theentityassign: mapping 0x%02x, 0x%02x", bank, firstArg);
|
|
|
|
if (!g_lingo->_lingoV4TheEntity[key]->writable) {
|
|
warning("cb_v4theentityassign: non-writable mapping 0x%02x 0x%02x", bank, firstArg);
|
|
|
|
return;
|
|
}
|
|
|
|
int entity = g_lingo->_lingoV4TheEntity[key]->entity;
|
|
int field = g_lingo->_lingoV4TheEntity[key]->field;
|
|
switch (g_lingo->_lingoV4TheEntity[key]->type) {
|
|
case kTEANOArgs:
|
|
{
|
|
Datum id;
|
|
id.u.s = nullptr;
|
|
id.type = VOID;
|
|
debugC(3, kDebugLingoExec, "cb_v4theentityassign: calling setTheEntity(%s, VOID, %s, %s)", g_lingo->entity2str(entity), g_lingo->field2str(field), value.asString(true).c_str());
|
|
g_lingo->setTheEntity(entity, id, field, value);
|
|
}
|
|
break;
|
|
case kTEAItemId:
|
|
{
|
|
Datum id = g_lingo->pop();
|
|
if ((entity == kTheCast || entity == kTheField) && g_director->getVersion() >= 500) {
|
|
// For "the member", D5 and above have a lib ID followed by a member ID
|
|
// Pre-resolve them here.
|
|
Datum member = g_lingo->pop();
|
|
CastMemberID resolved = g_lingo->toCastMemberID(member, id);
|
|
id = Datum(resolved);
|
|
}
|
|
debugC(3, kDebugLingoExec, "cb_v4theentityassign: calling setTheEntity(%s, %s, %s, %s)", g_lingo->entity2str(entity), id.asString(true).c_str(), g_lingo->field2str(field), value.asString(true).c_str());
|
|
g_lingo->setTheEntity(entity, id, field, value);
|
|
}
|
|
break;
|
|
case kTEAMenuId:
|
|
{
|
|
Datum id = g_lingo->pop();
|
|
if (id.type == INT) {
|
|
int menuId = id.u.i;
|
|
id.u.menu = new MenuReference();
|
|
id.u.menu->menuIdNum = menuId;
|
|
} else if (id.type == STRING) {
|
|
Common::String *menuId = id.u.s;
|
|
id.u.menu = new MenuReference();
|
|
id.u.menu->menuIdStr = menuId;
|
|
} else {
|
|
warning("LC::cb_v4theentityassign : Unknown type of menu Reference %d of entity type %d", id.type, g_lingo->_lingoV4TheEntity[key]->type);
|
|
break;
|
|
}
|
|
id.type = MENUREF;
|
|
debugC(3, kDebugLingoExec, "cb_v4theentityassign: calling setTheEntity(%s, %s, %s, %s)", g_lingo->entity2str(entity), id.asString(true).c_str(), g_lingo->field2str(field), value.asString(true).c_str());
|
|
g_lingo->setTheEntity(entity, id, field, value);
|
|
}
|
|
break;
|
|
case kTEAString:
|
|
{
|
|
/*Datum stringArg = */g_lingo->pop();
|
|
warning("cb_v4theentityassign: STUB: kTEAString");
|
|
}
|
|
break;
|
|
case kTEAMenuIdItemId:
|
|
{
|
|
Datum menuId = g_lingo->pop();
|
|
Datum itemId = g_lingo->pop();
|
|
Datum menuDatum;
|
|
menuDatum.type = MENUREF;
|
|
menuDatum.u.menu = new MenuReference();
|
|
if (menuId.type == INT) {
|
|
menuDatum.u.menu->menuIdNum = menuId.u.i;
|
|
} else if (menuId.type == STRING) {
|
|
menuDatum.u.menu->menuIdStr = menuId.u.s;
|
|
} else {
|
|
warning("LC::cb_v4theentityassign : Unknown type of menu Reference %d of entity type %d", menuId.type, g_lingo->_lingoV4TheEntity[key]->type);
|
|
break;
|
|
}
|
|
if (itemId.type == INT) {
|
|
menuDatum.u.menu->menuItemIdNum = itemId.u.i;
|
|
} else if (itemId.type == STRING) {
|
|
menuDatum.u.menu->menuItemIdStr = itemId.u.s;
|
|
} else {
|
|
warning("LC::cb_v4theentityassign : Unknown type of menuItem Reference %d of entity type %d", itemId.type, g_lingo->_lingoV4TheEntity[key]->type);
|
|
break;
|
|
}
|
|
g_lingo->setTheEntity(entity, menuDatum, field, value);
|
|
}
|
|
break;
|
|
case kTEAChunk:
|
|
{
|
|
Datum fieldRef = g_lingo->pop().asMemberID();
|
|
fieldRef.type = FIELDREF;
|
|
Datum chunkRef = readChunkRef(fieldRef);
|
|
g_lingo->setTheEntity(entity, chunkRef, field, value);
|
|
}
|
|
break;
|
|
default:
|
|
warning("cb_v4theentityassign: unknown call type %d", g_lingo->_lingoV4TheEntity[key]->type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LC::cb_zeropush() {
|
|
Datum d(0);
|
|
g_lingo->push(d);
|
|
}
|
|
|
|
ScriptContext *LingoCompiler::compileLingoV4(Common::SeekableReadStreamEndian &stream, uint16 lctxIndex, LingoArchive *archive, const Common::String &archName, uint16 version) {
|
|
if (stream.size() < 0x5c) {
|
|
warning("Lscr header too small");
|
|
return nullptr;
|
|
}
|
|
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Lscr header:");
|
|
stream.hexdump(0x5c);
|
|
}
|
|
|
|
// read the Lscr header!
|
|
// documentation:
|
|
// https://docs.google.com/document/d/1jDBXE4Wv1AEga-o1Wi8xtlNZY4K2fHxW2Xs8RgARrqk/edit
|
|
// https://github.com/Earthquake-Project/Format-Documentation/blob/master/structure/scripting/FormatNotes_Scripts.txt
|
|
// https://github.com/Earthquake-Project/ProjectorRays/blob/master/src/chunk/Script.ts
|
|
// (none of the above are totally complete)
|
|
|
|
// unk1
|
|
for (uint32 i = 0; i < 0x8; i++) {
|
|
stream.readByte();
|
|
}
|
|
|
|
// offset 8
|
|
/* uint32 length = */ stream.readUint32();
|
|
/* uint32 length2 = */ stream.readUint32();
|
|
uint16 codeStoreOffset = stream.readUint16();
|
|
|
|
/* uint16 scriptId = */ stream.readUint16() /* + 1 */;
|
|
// This field *should* match the script's index in Lctx, but this
|
|
// is unreliable. (e.g. script 261 in DATA/LEVEL1.DIR in betterd-win
|
|
// has this field incorrectly set to 263 instead of 261.)
|
|
// Thus, ignore it and simply use the script's index in Lctx.
|
|
uint16 scriptId = lctxIndex;
|
|
|
|
// unk2
|
|
for (uint32 i = 0; i < 0x10; i++) {
|
|
stream.readByte();
|
|
}
|
|
|
|
// offset 36
|
|
/* uint16 unk3 = */ stream.readUint16();
|
|
uint32 scriptFlags = stream.readUint32();
|
|
debugC(1, kDebugCompile, "Script flags (%d 0x%x):", scriptFlags, scriptFlags);
|
|
debugC(1, kDebugCompile, "unused: %d funcsGlobal: %d varsGlobal: %d unk3: %d", (scriptFlags & kScriptFlagUnused) != 0, (scriptFlags & kScriptFlagFuncsGlobal) != 0, (scriptFlags & kScriptFlagVarsGlobal) != 0, (scriptFlags & kScriptFlagUnk3) != 0);
|
|
debugC(1, kDebugCompile, "factoryDef: %d unk5: %d unk6: %d unk7: %d", (scriptFlags & kScriptFlagFactoryDef) != 0, (scriptFlags & kScriptFlagUnk5) != 0, (scriptFlags & kScriptFlagUnk6) != 0, (scriptFlags & kScriptFlagUnk7) != 0);
|
|
debugC(1, kDebugCompile, "hasFactory: %d eventScript: %d eventScript2: %d unkB: %d", (scriptFlags & kScriptFlagHasFactory) != 0, (scriptFlags & kScriptFlagEventScript) != 0, (scriptFlags & kScriptFlagEventScript2) != 0, (scriptFlags & kScriptFlagUnkB) != 0);
|
|
debugC(1, kDebugCompile, "unkC: %d unkD: %d unkE: %d unkF: %d", (scriptFlags & kScriptFlagUnkC) != 0, (scriptFlags & kScriptFlagUnkD) != 0, (scriptFlags & kScriptFlagUnkE) != 0, (scriptFlags & kScriptFlagUnkF) != 0);
|
|
|
|
if (scriptFlags & kScriptFlagUnused) {
|
|
warning("Script %d is unused", scriptId);
|
|
return nullptr;
|
|
}
|
|
|
|
// unk4
|
|
for (uint32 i = 0; i < 0x4; i++) {
|
|
stream.readByte();
|
|
}
|
|
/* uint16 _assemblyId = */ stream.readUint16();
|
|
// The script is coupled with to a Cast via the script ID.
|
|
// This _assemblyId isn't always correct.
|
|
|
|
int16 factoryNameId = stream.readSint16();
|
|
|
|
// offset 50 - contents map
|
|
|
|
/* uint16 eventMapCount = */ stream.readUint16();
|
|
/* uint32 eventMapOffset = */ stream.readUint32();
|
|
/* uint32 eventMapFlags = */ stream.readUint32();
|
|
// The event map is an int16 array used to quickly access events.
|
|
// Its first item is the index of the mouseDown handler or -1,
|
|
// its second item is the index of the mouseUp handler or -1, etc.
|
|
// eventMapFlags & (1 << 0) indicates there is a mouseDown handler,
|
|
// eventMapFlags & (1 << 1) indicates there is a mouseUp handler, etc.
|
|
// We probably don't need to read this since we already did something
|
|
// similar with _eventHandlers.
|
|
|
|
uint16 propertiesCount = stream.readUint16();
|
|
uint32 propertiesOffset = stream.readUint32();
|
|
uint16 globalsCount = stream.readUint16();
|
|
uint32 globalsOffset = stream.readUint32();
|
|
uint16 functionsCount = stream.readUint16();
|
|
uint32 functionsOffset = stream.readUint32();
|
|
uint16 constsCount = stream.readUint16();
|
|
uint32 constsOffset = stream.readUint32();
|
|
/* uint16 constsStoreCount = */ stream.readUint32();
|
|
uint32 constsStoreOffset = stream.readUint32();
|
|
|
|
// initialise the script
|
|
ScriptType scriptType = kCastScript;
|
|
Common::String castName;
|
|
CastMember *member = archive->cast->getCastMemberByScriptId(scriptId);
|
|
if (member) {
|
|
if (member->_type == kCastLingoScript)
|
|
scriptType = ((ScriptCastMember *)member)->_scriptType;
|
|
|
|
_assemblyId = member->getID();
|
|
CastMemberInfo *info = member->getInfo();
|
|
if (info)
|
|
castName = info->name;
|
|
} else {
|
|
warning("Script %d has no associated cast member", scriptId);
|
|
scriptType = kNoneScript;
|
|
}
|
|
|
|
_assemblyArchive = archive;
|
|
|
|
ScriptContext *sc = nullptr;
|
|
Common::String factoryName;
|
|
if (scriptFlags & kScriptFlagFactoryDef) {
|
|
if (0 <= factoryNameId && factoryNameId < (int16)archive->names.size()) {
|
|
factoryName = archive->names[factoryNameId];
|
|
} else {
|
|
warning("Factory %d has unknown name id %d, skipping define", scriptId, factoryNameId);
|
|
return nullptr;
|
|
}
|
|
debugC(1, kDebugCompile, "Add V4 script %d: factory '%s'", scriptId, factoryName.c_str());
|
|
|
|
sc = _assemblyContext = new ScriptContext(factoryName, scriptType, _assemblyId);
|
|
registerFactory(factoryName);
|
|
} else {
|
|
debugC(1, kDebugCompile, "Add V4 script %d: %s %d", scriptId, scriptType2str(scriptType), _assemblyId);
|
|
|
|
sc = _assemblyContext = new ScriptContext(!castName.empty() ? castName : Common::String::format("%d", _assemblyId), scriptType, _assemblyId, archive->cast->_castLibID);
|
|
}
|
|
|
|
// initialise each property
|
|
if ((uint32)stream.size() < propertiesOffset + propertiesCount * 2) {
|
|
warning("Lscr properties store missing");
|
|
return nullptr;
|
|
}
|
|
stream.seek(propertiesOffset);
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Lscr property list:");
|
|
stream.hexdump(propertiesCount * 2);
|
|
}
|
|
for (uint16 i = 0; i < propertiesCount; i++) {
|
|
int16 index = stream.readSint16();
|
|
if (index == -1) {
|
|
debugC(5, kDebugLoading, "[end of list]");
|
|
break;
|
|
} else if (0 <= index && index < (int16)archive->names.size()) {
|
|
const char *name = archive->names[index].c_str();
|
|
debugC(5, kDebugLoading, "%d: %s", i, name);
|
|
_assemblyContext->setProp(name, Datum(), true);
|
|
} else {
|
|
warning("Property %d has unknown name id %d, skipping define", i, index);
|
|
}
|
|
}
|
|
|
|
// initialise each global variable
|
|
if ((uint32)stream.size() < globalsOffset + globalsCount * 2) {
|
|
warning("Lscr globals store missing");
|
|
return nullptr;
|
|
}
|
|
|
|
stream.seek(globalsOffset);
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Lscr globals list:");
|
|
stream.hexdump(globalsCount * 2);
|
|
}
|
|
for (uint16 i = 0; i < globalsCount; i++) {
|
|
int16 index = stream.readSint16();
|
|
if (0 <= index && index < (int16)archive->names.size()) {
|
|
const char *name = archive->names[index].c_str();
|
|
if (!g_lingo->_globalvars.contains(name)) {
|
|
g_lingo->_globalvars[name] = Datum();
|
|
debugC(5, kDebugLoading, "%d: %s", i, name);
|
|
} else {
|
|
debugC(5, kDebugLoading, "%d: %s (already defined)", i, name);
|
|
}
|
|
} else {
|
|
warning("Global %d has unknown name id %d, skipping define", i, index);
|
|
}
|
|
}
|
|
|
|
// preload all the constants!
|
|
// these are stored as a reference table of 6 byte entries, followed by a storage area.
|
|
|
|
// copy the storage area first.
|
|
uint32 constsStoreSize = stream.size() - constsStoreOffset;
|
|
|
|
if ((uint32)stream.size() < constsStoreOffset) {
|
|
warning("Lscr consts store missing");
|
|
return nullptr;
|
|
}
|
|
|
|
stream.seek(constsStoreOffset);
|
|
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Lscr consts store:");
|
|
stream.hexdump(constsStoreSize);
|
|
}
|
|
|
|
byte *constsStore = (byte *)malloc(constsStoreSize);
|
|
stream.read(constsStore, constsStoreSize);
|
|
|
|
// read each entry in the reference table.
|
|
stream.seek(constsOffset);
|
|
int constsIndexSize = MAX((int)constsStoreOffset - (int)constsOffset, 0);
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Lscr consts index:");
|
|
stream.hexdump(constsIndexSize);
|
|
}
|
|
for (uint16 i = 0; i < constsCount; i++) {
|
|
Datum constant;
|
|
uint32 constType = 0;
|
|
if (version >= kFileVer500) {
|
|
constType = stream.readUint32();
|
|
} else {
|
|
constType = (uint32)stream.readUint16();
|
|
}
|
|
|
|
uint32 value = stream.readUint32();
|
|
switch (constType) {
|
|
case 1: // String type
|
|
{
|
|
Common::String str;
|
|
uint32 pointer = value;
|
|
if (pointer + 4 > constsStoreSize) {
|
|
error("Constant string is too small");
|
|
break;
|
|
}
|
|
uint32 length = READ_BE_UINT32(&constsStore[pointer]);
|
|
pointer += 4;
|
|
uint32 end = pointer + length;
|
|
if (end > constsStoreSize) {
|
|
error("Constant string is too large");
|
|
break;
|
|
}
|
|
while (pointer < end) {
|
|
if (constsStore[pointer] == '\0') {
|
|
break;
|
|
} else {
|
|
str += constsStore[pointer];
|
|
}
|
|
pointer += 1;
|
|
}
|
|
if (pointer > constsStoreSize) {
|
|
warning("Constant string has no null terminator");
|
|
break;
|
|
}
|
|
constant.type = STRING;
|
|
constant.u.s = new Common::String(archive->cast->decodeString(str), Common::kUtf8);
|
|
}
|
|
break;
|
|
case 4: // Integer type
|
|
constant.type = INT;
|
|
constant.u.i = (int)value;
|
|
break;
|
|
case 9: // Float type
|
|
{
|
|
constant.type = FLOAT;
|
|
if (value + 4 > constsStoreSize) {
|
|
warning("Constant float end offset is out of bounds");
|
|
break;
|
|
}
|
|
|
|
uint32 pointer = value;
|
|
uint32 length = READ_BE_UINT32(&constsStore[pointer]);
|
|
pointer += 4;
|
|
uint32 end = pointer + length;
|
|
if (end > constsStoreSize) {
|
|
error("Constant float is too large");
|
|
break;
|
|
}
|
|
|
|
if (length == 10) {
|
|
// Floats are stored as an "80 bit IEEE Standard 754 floating
|
|
// point number (Standard Apple Numeric Environment [SANE] data type
|
|
// Extended).
|
|
constant.u.f = readAppleFloat80(&constsStore[pointer]);
|
|
} else if (length == 8) {
|
|
constant.u.f = READ_BE_FLOAT64(&constsStore[pointer]);
|
|
} else {
|
|
error("Constant float expected to be 8 or 10 bytes but got %d", length);
|
|
}
|
|
|
|
}
|
|
break;
|
|
default:
|
|
warning("Unknown constant type %d", constType);
|
|
break;
|
|
}
|
|
_assemblyContext->_constants.push_back(constant);
|
|
}
|
|
free(constsStore);
|
|
|
|
// parse each function!
|
|
// these are stored as a code storage area, followed by a reference table of 42 byte entries.
|
|
|
|
// copy the storage area first.
|
|
if ((uint32)stream.size() < functionsOffset) {
|
|
warning("Lscr functions store missing");
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 codeStoreSize = functionsOffset - codeStoreOffset;
|
|
stream.seek(codeStoreOffset);
|
|
byte *codeStore = (byte *)malloc(codeStoreSize);
|
|
stream.read(codeStore, codeStoreSize);
|
|
|
|
Common::DumpFile out;
|
|
bool skipdump = false;
|
|
|
|
if (ConfMan.getBool("dump_scripts")) {
|
|
Common::Path buf;
|
|
if (scriptFlags & kScriptFlagFactoryDef) {
|
|
buf = dumpFactoryName(encodePathForDump(archName).c_str(), factoryName.c_str(), "lscr");
|
|
} else {
|
|
buf = dumpScriptName(encodePathForDump(archName).c_str(), scriptType, _assemblyId, "lscr");
|
|
}
|
|
|
|
if (!out.open(buf, true)) {
|
|
warning("Lingo::addCodeV4(): Can not open dump file %s", buf.toString(Common::Path::kNativeSeparator).c_str());
|
|
skipdump = true;
|
|
} else {
|
|
warning("Lingo::addCodeV4(): Dumping Lscr to %s", buf.toString(Common::Path::kNativeSeparator).c_str());
|
|
}
|
|
}
|
|
|
|
// read each entry in the function table.
|
|
stream.seek(functionsOffset);
|
|
for (uint16 i = 0; i < functionsCount; i++) {
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Function %d header:", i);
|
|
stream.hexdump(0x2a);
|
|
}
|
|
|
|
int16 nameIndex = stream.readUint16();
|
|
stream.readUint16();
|
|
uint32 length = stream.readUint32();
|
|
uint32 startOffset = stream.readUint32();
|
|
uint16 argCount = stream.readUint16();
|
|
uint32 argOffset = stream.readUint32();
|
|
uint16 varCount = stream.readUint16();
|
|
uint32 varOffset = stream.readUint32();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
|
|
if (startOffset < codeStoreOffset) {
|
|
warning("Function %d start offset is out of bounds!", i);
|
|
continue;
|
|
} else if (startOffset + length >= codeStoreOffset + codeStoreSize) {
|
|
warning("Function %d end offset is out of bounds", i);
|
|
continue;
|
|
}
|
|
|
|
// Fetch argument name list
|
|
Common::Array<Common::String> *argNames = new Common::Array<Common::String>;
|
|
Common::HashMap<uint16, uint16> argMap;
|
|
if (argOffset < codeStoreOffset) {
|
|
warning("Function %d argument names start offset is out of bounds!", i);
|
|
} else if (argOffset + argCount*2 >= codeStoreOffset + codeStoreSize) {
|
|
warning("Function %d argument names end offset is out of bounds!", i);
|
|
} else {
|
|
debugC(5, kDebugLoading, "Function %d argument list:", i);
|
|
uint16 namePointer = argOffset - codeStoreOffset;
|
|
for (int j = 0; j < argCount; j++) {
|
|
int16 index = READ_BE_INT16(&codeStore[namePointer]);
|
|
namePointer += 2;
|
|
Common::String name;
|
|
if (0 <= index && index < (int16)archive->names.size()) {
|
|
name = archive->names[index];
|
|
argMap[j] = index;
|
|
} else if (j == 0 && (scriptFlags & kScriptFlagFactoryDef)) {
|
|
name = "me";
|
|
|
|
// find or add "me" in the names list
|
|
for (index = 0; index < (int16)archive->names.size(); index++) {
|
|
if (archive->names[index].equalsIgnoreCase(name))
|
|
break;
|
|
}
|
|
if (index == (int16)archive->names.size())
|
|
archive->names.push_back(name);
|
|
|
|
argMap[j] = index;
|
|
} else {
|
|
name = Common::String::format("arg_%d", j);
|
|
warning("Argument has unknown name id %d, using name %s", index, name.c_str());
|
|
}
|
|
debugC(5, kDebugLoading, "%d: %s", j, name.c_str());
|
|
argNames->push_back(name);
|
|
}
|
|
}
|
|
|
|
// Fetch variable name list
|
|
Common::Array<Common::String> *varNames = new Common::Array<Common::String>;
|
|
Common::HashMap<uint16, uint16> varMap;
|
|
if (varOffset < codeStoreOffset) {
|
|
warning("Function %d variable names start offset is out of bounds!", i);
|
|
} else if (varOffset + varCount*2 >= codeStoreOffset + codeStoreSize) {
|
|
warning("Function %d variable names end offset is out of bounds!", i);
|
|
} else {
|
|
debugC(5, kDebugLoading, "Function %d variable list:", i);
|
|
uint16 namePointer = varOffset - codeStoreOffset;
|
|
for (int j = 0; j < varCount; j++) {
|
|
int16 index = READ_BE_INT16(&codeStore[namePointer]);
|
|
namePointer += 2;
|
|
Common::String name;
|
|
if (0 <= index && index < (int16)archive->names.size()) {
|
|
name = archive->names[index];
|
|
varMap[j] = index;
|
|
} else {
|
|
name = Common::String::format("var_%d", j);
|
|
warning("Variable has unknown name id %d, using name %s", j, name.c_str());
|
|
}
|
|
debugC(5, kDebugLoading, "%d: %s", j, name.c_str());
|
|
varNames->push_back(name);
|
|
}
|
|
}
|
|
|
|
_currentAssembly = new ScriptData;
|
|
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Function %d code:", i);
|
|
Common::hexdump(codeStore, length, 16, startOffset);
|
|
}
|
|
|
|
uint32 pointer = startOffset - codeStoreOffset;
|
|
Common::Array<uint32> offsetList;
|
|
Common::Array<uint32> jumpList;
|
|
Common::Array<uint32> byteOffsets;
|
|
|
|
// Size of an entry in the consts index.
|
|
int constEntrySize = 0;
|
|
if (version >= kFileVer500) {
|
|
// For D5 this is uint32 type + uint32 offset
|
|
constEntrySize = 8;
|
|
} else {
|
|
// For D4 this is uint16 type + uint32 offset
|
|
constEntrySize = 6;
|
|
}
|
|
|
|
while (pointer < startOffset + length - codeStoreOffset) {
|
|
uint8 opcode = codeStore[pointer];
|
|
pointer += 1;
|
|
|
|
if (opcode == 0x44 || opcode == 0x84) {
|
|
// Opcode for pushing a value from the constants table.
|
|
// Rewrite these to inline the constant into our bytecode.
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
int arg = 0;
|
|
if (opcode == 0x84) {
|
|
arg = (uint16)READ_BE_UINT16(&codeStore[pointer]);
|
|
pointer += 2;
|
|
} else {
|
|
arg = (uint8)codeStore[pointer];
|
|
pointer += 1;
|
|
}
|
|
// The argument is a byte offset to an entry in the consts index.
|
|
// As such, it should be an exact multiple of the entry size.
|
|
if (arg % constEntrySize) {
|
|
warning("Opcode 0x%02x arg %d not a multiple of %d", opcode, arg, constEntrySize);
|
|
}
|
|
arg /= constEntrySize;
|
|
Datum constant = _assemblyContext->_constants[arg];
|
|
switch (constant.type) {
|
|
case INT:
|
|
code1(LC::c_intpush);
|
|
break;
|
|
case FLOAT:
|
|
code1(LC::c_floatpush);
|
|
break;
|
|
case STRING:
|
|
code1(LC::c_stringpush);
|
|
break;
|
|
default:
|
|
error("Unknown constant type %d", constant.type);
|
|
break;
|
|
}
|
|
if (opcode == 0x84) {
|
|
offsetList.push_back(_currentAssembly->size());
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
} else {
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
}
|
|
switch (constant.type) {
|
|
case INT:
|
|
codeInt(constant.u.i);
|
|
break;
|
|
case FLOAT:
|
|
codeFloat(constant.u.f);
|
|
break;
|
|
case STRING:
|
|
codeString(constant.u.s->c_str());
|
|
break;
|
|
default:
|
|
error("Unknown constant type %d", constant.type);
|
|
break;
|
|
}
|
|
} else if (g_lingo->_lingoV4.contains(opcode)) {
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
code1(g_lingo->_lingoV4[opcode]->func);
|
|
|
|
size_t argc = strlen(g_lingo->_lingoV4[opcode]->proto);
|
|
if (argc) {
|
|
bool codeName = false;
|
|
int arg = 0;
|
|
for (uint c = 0; c < argc; c++) {
|
|
switch (g_lingo->_lingoV4[opcode]->proto[c]) {
|
|
case 'b':
|
|
// read one uint8 as an argument
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
arg = (uint8)codeStore[pointer];
|
|
pointer += 1;
|
|
break;
|
|
case 'B':
|
|
// read one int8 as an argument
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
arg = (int8)codeStore[pointer];
|
|
pointer += 1;
|
|
break;
|
|
case 'w':
|
|
// read one uint16 as an argument
|
|
offsetList.push_back(_currentAssembly->size());
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
arg = (uint16)READ_BE_UINT16(&codeStore[pointer]);
|
|
pointer += 2;
|
|
break;
|
|
case 'W':
|
|
// read one int16 as an argument
|
|
offsetList.push_back(_currentAssembly->size());
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
arg = (int16)READ_BE_INT16(&codeStore[pointer]);
|
|
pointer += 2;
|
|
break;
|
|
case 'n':
|
|
// argument is negative
|
|
arg *= -1;
|
|
break;
|
|
case 'p':
|
|
// argument is some kind of denormalised offset
|
|
if (arg % constEntrySize) {
|
|
warning("Argument %d was expected to be a multiple of %d", arg, constEntrySize);
|
|
}
|
|
arg /= constEntrySize;
|
|
break;
|
|
case 'a':
|
|
// argument is a function argument ID
|
|
if (argMap.contains(arg)) {
|
|
arg = argMap[arg];
|
|
} else {
|
|
warning("No argument name found for ID %d", arg);
|
|
arg = -1;
|
|
}
|
|
break;
|
|
case 'v':
|
|
// argument is a local variable ID
|
|
if (varMap.contains(arg)) {
|
|
arg = varMap[arg];
|
|
} else {
|
|
warning("No variable name found for ID %d", arg);
|
|
arg = -1;
|
|
}
|
|
break;
|
|
case 'j':
|
|
// argument refers to a code offset; fix alignment in post
|
|
jumpList.push_back(offsetList.size());
|
|
break;
|
|
case 'N':
|
|
// argument is a name in the name table
|
|
codeName = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (codeName) {
|
|
codeString(_assemblyArchive->getName(arg).c_str());
|
|
} else {
|
|
codeInt(arg);
|
|
}
|
|
}
|
|
} else {
|
|
// unimplemented instruction
|
|
if (opcode < 0x40) { // 1 byte instruction
|
|
debugC(5, kDebugCompile, "Unimplemented opcode: 0x%02x", opcode);
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
code1(LC::cb_unk);
|
|
codeInt(opcode);
|
|
} else if (opcode < 0x80) { // 2 byte instruction
|
|
debugC(5, kDebugCompile, "Unimplemented opcode: 0x%02x (%d)", opcode, (uint)codeStore[pointer]);
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
code1(LC::cb_unk1);
|
|
codeInt(opcode);
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
codeInt((uint)codeStore[pointer]);
|
|
pointer += 1;
|
|
} else { // 3 byte instruction
|
|
debugC(5, kDebugCompile, "Unimplemented opcode: 0x%02x (%d, %d)", opcode, (uint)codeStore[pointer], (uint)codeStore[pointer+1]);
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
code1(LC::cb_unk2);
|
|
codeInt(opcode);
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
codeInt((uint)codeStore[pointer]);
|
|
offsetList.push_back(_currentAssembly->size());
|
|
byteOffsets.push_back(_currentAssembly->size());
|
|
codeInt((uint)codeStore[pointer+1]);
|
|
pointer += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add backstop
|
|
code1(STOP);
|
|
|
|
// Rewrite every offset flagged as a jump based on the new code alignment.
|
|
for (uint j = 0; j < jumpList.size(); j++) {
|
|
int originalJumpAddressLoc = jumpList[j];
|
|
int originalJumpInstructionLoc = originalJumpAddressLoc-1;
|
|
int jumpAddressPc = offsetList[originalJumpAddressLoc];
|
|
int jump = (int)READ_UINT32(&((*_currentAssembly)[jumpAddressPc]));
|
|
int oldTarget = originalJumpInstructionLoc + jump;
|
|
if ((oldTarget >= 0) && (oldTarget < (int)offsetList.size())) {
|
|
int newJump = offsetList[oldTarget];
|
|
WRITE_UINT32(&((*_currentAssembly)[jumpAddressPc]), newJump - jumpAddressPc + 1);
|
|
} else {
|
|
warning("Jump of %d from position %d is outside the function!", jump, originalJumpAddressLoc);
|
|
}
|
|
}
|
|
|
|
// Attach to handlers
|
|
|
|
Common::String functionName;
|
|
if (0 <= nameIndex && nameIndex < (int16)archive->names.size()) {
|
|
functionName = archive->names[nameIndex];
|
|
} else if (i == 0 && (scriptFlags & kScriptFlagEventScript)) {
|
|
// event script (lingo not contained within a handler)
|
|
functionName = g_lingo->_eventHandlerTypes[kEventGeneric];
|
|
}
|
|
|
|
Symbol sym;
|
|
if (!functionName.empty()) {
|
|
debugC(5, kDebugLoading, "Function %d binding: %s()", i, functionName.c_str());
|
|
sym = _assemblyContext->define(functionName, _currentAssembly, argNames, varNames);
|
|
} else {
|
|
warning("Function has unknown name id %d, skipping define", nameIndex);
|
|
sym.name = new Common::String();
|
|
sym.type = HANDLER;
|
|
sym.u.defn = _currentAssembly;
|
|
sym.nargs = argCount;
|
|
sym.maxArgs = argCount;
|
|
sym.argNames = argNames;
|
|
sym.varNames = varNames;
|
|
}
|
|
|
|
_assemblyContext->_functionByteOffsets[functionName] = byteOffsets;
|
|
|
|
if (!skipdump && ConfMan.getBool("dump_scripts")) {
|
|
out.writeString(g_lingo->formatFunctionBody(sym));
|
|
}
|
|
|
|
_assemblyContext->_functionNames.push_back(*sym.name);
|
|
_currentAssembly = nullptr;
|
|
}
|
|
|
|
if (!_assemblyContext->isFactory()) {
|
|
// Register this context's functions with the containing archive.
|
|
if (scriptType == kScoreScript || scriptType == kMovieScript) {
|
|
for (auto &it : _assemblyContext->_functionHandlers) {
|
|
if (!_assemblyArchive->functionHandlers.contains(it._key)) {
|
|
_assemblyArchive->functionHandlers[it._key] = it._value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!skipdump && ConfMan.getBool("dump_scripts")) {
|
|
out.flush();
|
|
out.close();
|
|
}
|
|
|
|
free(codeStore);
|
|
_assemblyArchive = nullptr;
|
|
_assemblyContext = nullptr;
|
|
_assemblyId = -1;
|
|
|
|
return sc;
|
|
}
|
|
|
|
void LingoArchive::addCodeV4(Common::SeekableReadStreamEndian &stream, uint16 lctxIndex, const Common::String &archName, uint16 version) {
|
|
ScriptContext *ctx = g_lingo->_compiler->compileLingoV4(stream, lctxIndex, this, archName, version);
|
|
if (ctx) {
|
|
lctxContexts[lctxIndex] = ctx;
|
|
ctx->incRefCount();
|
|
}
|
|
}
|
|
|
|
void LingoArchive::addNamesV4(Common::SeekableReadStreamEndian &stream) {
|
|
debugC(1, kDebugCompile, "Add V4 script name index");
|
|
|
|
if (stream.size() < 0x14) {
|
|
warning("Lnam header too small");
|
|
return;
|
|
}
|
|
|
|
// read the Lnam header!
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
debugC(5, kDebugLoading, "Lnam header:");
|
|
stream.hexdump(0x14);
|
|
}
|
|
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
stream.readUint16();
|
|
|
|
uint32 size = stream.readUint32(); // size of Lnam
|
|
stream.readUint32(); // size of Lnam again
|
|
uint16 offset = stream.readUint16();
|
|
uint16 count = stream.readUint16();
|
|
|
|
if ((uint32)stream.size() != size) {
|
|
warning("Lnam content missing");
|
|
return;
|
|
}
|
|
|
|
stream.seek(offset);
|
|
|
|
names.clear();
|
|
|
|
for (uint16 i = 0; i < count; i++) {
|
|
Common::String name = stream.readPascalString();
|
|
|
|
names.push_back(name);
|
|
debugC(5, kDebugLoading, "%d: \"%s\"", i, name.c_str());
|
|
}
|
|
|
|
}
|
|
|
|
} // end of namespace Director
|