33cb13dee5
Co-conspirators: Franklin Wei, Benjamin Brown -------------------------------------------------------------------- This work is based on: - Fabien Sanglard's "Fabother World" based on - Piotr Padkowski's newRaw interpreter which was based on - Gregory Montoir's reverse engineering of - Eric Chahi's assembly code -------------------------------------------------------------------- Progress: * The plugin runs pretty nicely (with sound!) on most color targets * Keymaps for color LCD targets are complete * The manual entry is finished * Grayscale/monochrome support is NOT PLANNED - the game looks horrible in grayscale! :p -------------------------------------------------------------------- Notes: * The original game strings were built-in to the executable, and were copyrighted and could not be used. * This port ships with an alternate set of strings by default, but can load the "official" strings from a file at runtime. -------------------------------------------------------------------- To be done (in descending order of importance): * vertical stride compatibility <30% done> * optimization <10% done> Change-Id: I3155b0d97c2ac470cb8a2040f40d4139ddcebfa5 Reviewed-on: http://gerrit.rockbox.org/1077 Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
763 lines
24 KiB
C
763 lines
24 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2014 Franklin Wei, Benjamin Brown
|
|
* Copyright (C) 2004 Gregory Montoir
|
|
*
|
|
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "plugin.h"
|
|
#include "vm.h"
|
|
#include "mixer.h"
|
|
#include "resource.h"
|
|
#include "video.h"
|
|
#include "serializer.h"
|
|
#include "sfxplayer.h"
|
|
#include "sys.h"
|
|
#include "parts.h"
|
|
#include "file.h"
|
|
|
|
static const uint16_t vm_frequenceTable[] = {
|
|
0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C,
|
|
0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE,
|
|
0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110,
|
|
0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE,
|
|
0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD
|
|
};
|
|
|
|
void vm_create(struct VirtualMachine* m, struct Mixer *mix, struct Resource* res, struct SfxPlayer *ply, struct Video *vid, struct System *stub)
|
|
{
|
|
m->res = res;
|
|
m->video = vid;
|
|
m->sys = stub;
|
|
m->mixer = mix;
|
|
m->player = ply;
|
|
}
|
|
|
|
void vm_init(struct VirtualMachine* m) {
|
|
|
|
rb->memset(m->vmVariables, 0, sizeof(m->vmVariables));
|
|
m->vmVariables[0x54] = 0x81;
|
|
m->vmVariables[VM_VARIABLE_RANDOM_SEED] = *rb->current_tick;
|
|
|
|
m->_fastMode = false;
|
|
m->player->_markVar = &m->vmVariables[VM_VARIABLE_MUS_MARK];
|
|
}
|
|
|
|
void vm_op_movConst(struct VirtualMachine* m) {
|
|
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_movConst(0x%02X, %d)", variableId, value);
|
|
m->vmVariables[variableId] = value;
|
|
}
|
|
|
|
void vm_op_mov(struct VirtualMachine* m) {
|
|
uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_mov(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
|
|
m->vmVariables[dstVariableId] = m->vmVariables[srcVariableId];
|
|
}
|
|
|
|
void vm_op_add(struct VirtualMachine* m) {
|
|
uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_add(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
|
|
m->vmVariables[dstVariableId] += m->vmVariables[srcVariableId];
|
|
}
|
|
|
|
void vm_op_addConst(struct VirtualMachine* m) {
|
|
if (m->res->currentPartId == 0x3E86 && m->_scriptPtr.pc == m->res->segBytecode + 0x6D48) {
|
|
warning("vm_op_addConst() hack for non-stop looping gun sound bug");
|
|
// the script 0x27 slot 0x17 doesn't stop the gun sound from looping, I
|
|
// don't really know why ; for now, let's play the 'stopping sound' like
|
|
// the other scripts do
|
|
// (0x6D43) jmp(0x6CE5)
|
|
// (0x6D46) break
|
|
// (0x6D47) VAR(6) += -50
|
|
vm_snd_playSound(m, 0x5B, 1, 64, 1);
|
|
}
|
|
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_addConst(0x%02X, %d)", variableId, value);
|
|
m->vmVariables[variableId] += value;
|
|
}
|
|
|
|
void vm_op_call(struct VirtualMachine* m) {
|
|
|
|
uint16_t offset = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
uint8_t sp = m->_stackPtr;
|
|
|
|
debug(DBG_VM, "vm_op_call(0x%X)", offset);
|
|
m->_scriptStackCalls[sp] = m->_scriptPtr.pc - m->res->segBytecode;
|
|
if (m->_stackPtr == 0xFF) {
|
|
error("vm_op_call() ec=0x%X stack overflow", 0x8F);
|
|
}
|
|
++m->_stackPtr;
|
|
m->_scriptPtr.pc = m->res->segBytecode + offset ;
|
|
}
|
|
|
|
void vm_op_ret(struct VirtualMachine* m) {
|
|
debug(DBG_VM, "vm_op_ret()");
|
|
if (m->_stackPtr == 0) {
|
|
error("vm_op_ret() ec=0x%X stack underflow", 0x8F);
|
|
}
|
|
--m->_stackPtr;
|
|
uint8_t sp = m->_stackPtr;
|
|
m->_scriptPtr.pc = m->res->segBytecode + m->_scriptStackCalls[sp];
|
|
}
|
|
|
|
void vm_op_pauseThread(struct VirtualMachine* m) {
|
|
debug(DBG_VM, "vm_op_pauseThread()");
|
|
m->gotoNextThread = true;
|
|
}
|
|
|
|
void vm_op_jmp(struct VirtualMachine* m) {
|
|
uint16_t pcOffset = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_jmp(0x%02X)", pcOffset);
|
|
m->_scriptPtr.pc = m->res->segBytecode + pcOffset;
|
|
}
|
|
|
|
void vm_op_setSetVect(struct VirtualMachine* m) {
|
|
uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t pcOffsetRequested = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_setSetVect(0x%X, 0x%X)", threadId, pcOffsetRequested);
|
|
m->threadsData[REQUESTED_PC_OFFSET][threadId] = pcOffsetRequested;
|
|
}
|
|
|
|
void vm_op_jnz(struct VirtualMachine* m) {
|
|
uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_jnz(0x%02X)", i);
|
|
--m->vmVariables[i];
|
|
if (m->vmVariables[i] != 0) {
|
|
vm_op_jmp(m);
|
|
} else {
|
|
scriptPtr_fetchWord(&m->_scriptPtr);
|
|
}
|
|
}
|
|
|
|
#define BYPASS_PROTECTION
|
|
void vm_op_condJmp(struct VirtualMachine* m) {
|
|
|
|
//printf("Jump : %X \n",m->_scriptPtr.pc-m->res->segBytecode);
|
|
//FCS Whoever wrote this is patching the bytecode on the fly. This is ballzy !!
|
|
#ifdef BYPASS_PROTECTION
|
|
|
|
if (m->res->currentPartId == GAME_PART_FIRST && m->_scriptPtr.pc == m->res->segBytecode + 0xCB9) {
|
|
|
|
// (0x0CB8) condJmp(0x80, VAR(41), VAR(30), 0xCD3)
|
|
*(m->_scriptPtr.pc + 0x00) = 0x81;
|
|
*(m->_scriptPtr.pc + 0x03) = 0x0D;
|
|
*(m->_scriptPtr.pc + 0x04) = 0x24;
|
|
// (0x0D4E) condJmp(0x4, VAR(50), 6, 0xDBC)
|
|
*(m->_scriptPtr.pc + 0x99) = 0x0D;
|
|
*(m->_scriptPtr.pc + 0x9A) = 0x5A;
|
|
debug(DBG_VM, "vm_op_condJmp() bypassing protection");
|
|
debug(DBG_VM, "bytecode has been patched");
|
|
|
|
//vm_bypassProtection(m);
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
int16_t b = m->vmVariables[scriptPtr_fetchByte(&m->_scriptPtr)];
|
|
uint8_t c = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
int16_t a;
|
|
|
|
if (opcode & 0x80) {
|
|
a = m->vmVariables[c];
|
|
} else if (opcode & 0x40) {
|
|
a = c * 256 + scriptPtr_fetchByte(&m->_scriptPtr);
|
|
} else {
|
|
a = c;
|
|
}
|
|
debug(DBG_VM, "vm_op_condJmp(%d, 0x%02X, 0x%02X)", opcode, b, a);
|
|
|
|
// Check if the conditional value is met.
|
|
bool expr = false;
|
|
switch (opcode & 7) {
|
|
case 0: // jz
|
|
expr = (b == a);
|
|
break;
|
|
case 1: // jnz
|
|
expr = (b != a);
|
|
break;
|
|
case 2: // jg
|
|
expr = (b > a);
|
|
break;
|
|
case 3: // jge
|
|
expr = (b >= a);
|
|
break;
|
|
case 4: // jl
|
|
expr = (b < a);
|
|
break;
|
|
case 5: // jle
|
|
expr = (b <= a);
|
|
break;
|
|
default:
|
|
warning("vm_op_condJmp() invalid condition %d", (opcode & 7));
|
|
break;
|
|
}
|
|
|
|
if (expr) {
|
|
vm_op_jmp(m);
|
|
} else {
|
|
scriptPtr_fetchWord(&m->_scriptPtr);
|
|
}
|
|
|
|
}
|
|
|
|
void vm_op_setPalette(struct VirtualMachine* m) {
|
|
uint16_t paletteId = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_changePalette(%d)", paletteId);
|
|
m->video->paletteIdRequested = paletteId >> 8;
|
|
}
|
|
|
|
void vm_op_resetThread(struct VirtualMachine* m) {
|
|
|
|
uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
// FCS: WTF, this is cryptic as hell !!
|
|
// int8_t n = (i & 0x3F) - threadId; //0x3F = 0011 1111
|
|
// The following is so much clearer
|
|
|
|
//Make sure i is within [0-VM_NUM_THREADS-1]
|
|
i = i & (VM_NUM_THREADS - 1) ;
|
|
int8_t n = i - threadId;
|
|
|
|
if (n < 0) {
|
|
warning("vm_op_m->resetThread() ec=0x%X (n < 0)", 0x880);
|
|
return;
|
|
}
|
|
++n;
|
|
uint8_t a = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
debug(DBG_VM, "vm_op_m->resetThread(%d, %d, %d)", threadId, i, a);
|
|
|
|
if (a == 2) {
|
|
uint16_t *p = &m->threadsData[REQUESTED_PC_OFFSET][threadId];
|
|
while (n--) {
|
|
*p++ = 0xFFFE;
|
|
}
|
|
} else if (a < 2) {
|
|
uint8_t *p = &m->vmIsChannelActive[REQUESTED_STATE][threadId];
|
|
while (n--) {
|
|
*p++ = a;
|
|
}
|
|
}
|
|
}
|
|
|
|
void vm_op_selectVideoPage(struct VirtualMachine* m) {
|
|
uint8_t frameBufferId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_selectVideoPage(%d)", frameBufferId);
|
|
video_changePagePtr1(m->video, frameBufferId);
|
|
}
|
|
|
|
void vm_op_fillVideoPage(struct VirtualMachine* m) {
|
|
uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t color = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_fillVideoPage(%d, %d)", pageId, color);
|
|
video_fillPage(m->video, pageId, color);
|
|
}
|
|
|
|
void vm_op_copyVideoPage(struct VirtualMachine* m) {
|
|
uint8_t srcPageId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t dstPageId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_copyVideoPage(%d, %d)", srcPageId, dstPageId);
|
|
video_copyPage(m->video, srcPageId, dstPageId, m->vmVariables[VM_VARIABLE_SCROLL_Y]);
|
|
}
|
|
|
|
|
|
static uint32_t lastTimeStamp = 0;
|
|
void vm_op_blitFramebuffer(struct VirtualMachine* m) {
|
|
|
|
uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_blitFramebuffer(%d)", pageId);
|
|
vm_inp_handleSpecialKeys(m);
|
|
|
|
/* Nasty hack....was this present in the original assembly ??!! */
|
|
if (m->res->currentPartId == GAME_PART_FIRST && m->vmVariables[0x67] == 1)
|
|
m->vmVariables[0xDC] = 0x21;
|
|
|
|
if (!m->_fastMode) {
|
|
|
|
int32_t delay = sys_getTimeStamp(m->sys) - lastTimeStamp;
|
|
int32_t timeToSleep = m->vmVariables[VM_VARIABLE_PAUSE_SLICES] * 20 - delay;
|
|
|
|
/* The bytecode will set m->vmVariables[VM_VARIABLE_PAUSE_SLICES] from 1 to 5 */
|
|
/* The virtual machine hence indicates how long the image should be displayed. */
|
|
|
|
if (timeToSleep > 0)
|
|
{
|
|
sys_sleep(m->sys, timeToSleep);
|
|
}
|
|
|
|
lastTimeStamp = sys_getTimeStamp(m->sys);
|
|
}
|
|
|
|
/* WTF ? */
|
|
m->vmVariables[0xF7] = 0;
|
|
|
|
video_updateDisplay(m->video, pageId);
|
|
}
|
|
|
|
void vm_op_killThread(struct VirtualMachine* m) {
|
|
debug(DBG_VM, "vm_op_killThread()");
|
|
m->_scriptPtr.pc = m->res->segBytecode + 0xFFFF;
|
|
m->gotoNextThread = true;
|
|
}
|
|
|
|
void vm_op_drawString(struct VirtualMachine* m) {
|
|
uint16_t stringId = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
uint16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t color = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
debug(DBG_VM, "vm_op_drawString(0x%03X, %d, %d, %d)", stringId, x, y, color);
|
|
|
|
video_drawString(m->video, color, x, y, stringId);
|
|
}
|
|
|
|
void vm_op_sub(struct VirtualMachine* m) {
|
|
uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t j = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_sub(0x%02X, 0x%02X)", i, j);
|
|
m->vmVariables[i] -= m->vmVariables[j];
|
|
}
|
|
|
|
void vm_op_and(struct VirtualMachine* m) {
|
|
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t n = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_and(0x%02X, %d)", variableId, n);
|
|
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] & n;
|
|
}
|
|
|
|
void vm_op_or(struct VirtualMachine* m) {
|
|
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_or(0x%02X, %d)", variableId, value);
|
|
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] | value;
|
|
}
|
|
|
|
void vm_op_shl(struct VirtualMachine* m) {
|
|
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t leftShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_shl(0x%02X, %d)", variableId, leftShiftValue);
|
|
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] << leftShiftValue;
|
|
}
|
|
|
|
void vm_op_shr(struct VirtualMachine* m) {
|
|
uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint16_t rightShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_shr(0x%02X, %d)", variableId, rightShiftValue);
|
|
m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] >> rightShiftValue;
|
|
}
|
|
|
|
void vm_op_playSound(struct VirtualMachine* m) {
|
|
uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
uint8_t freq = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t vol = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
uint8_t channel = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_playSound(0x%X, %d, %d, %d)", resourceId, freq, vol, channel);
|
|
vm_snd_playSound(m, resourceId, freq, vol, channel);
|
|
}
|
|
|
|
void vm_op_updateMemList(struct VirtualMachine* m) {
|
|
|
|
uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_updateMemList(%d)", resourceId);
|
|
|
|
if (resourceId == 0) {
|
|
player_stop(m->player);
|
|
mixer_stopAll(m->mixer);
|
|
res_invalidateRes(m->res);
|
|
} else {
|
|
res_loadPartsOrMemoryEntry(m->res, resourceId);
|
|
}
|
|
}
|
|
|
|
void vm_op_playMusic(struct VirtualMachine* m) {
|
|
uint16_t resNum = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
uint16_t delay = scriptPtr_fetchWord(&m->_scriptPtr);
|
|
uint8_t pos = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
debug(DBG_VM, "vm_op_playMusic(0x%X, %d, %d)", resNum, delay, pos);
|
|
vm_snd_playMusic(m, resNum, delay, pos);
|
|
}
|
|
|
|
void vm_initForPart(struct VirtualMachine* m, uint16_t partId) {
|
|
|
|
player_stop(m->player);
|
|
mixer_stopAll(m->mixer);
|
|
|
|
/* WTF is that ? */
|
|
m->vmVariables[0xE4] = 0x14;
|
|
|
|
res_setupPart(m->res, partId);
|
|
|
|
/* Set all thread to inactive (pc at 0xFFFF or 0xFFFE ) */
|
|
rb->memset((uint8_t *)m->threadsData, 0xFF, sizeof(m->threadsData));
|
|
|
|
rb->memset((uint8_t *)m->vmIsChannelActive, 0, sizeof(m->vmIsChannelActive));
|
|
|
|
int firstThreadId = 0;
|
|
m->threadsData[PC_OFFSET][firstThreadId] = 0;
|
|
}
|
|
|
|
/*
|
|
This is called every frames in the infinite loop.
|
|
*/
|
|
void vm_checkThreadRequests(struct VirtualMachine* m) {
|
|
|
|
/* Check if a part switch has been requested. */
|
|
if (m->res->requestedNextPart != 0) {
|
|
vm_initForPart(m, m->res->requestedNextPart);
|
|
m->res->requestedNextPart = 0;
|
|
}
|
|
|
|
|
|
/* Check if a state update has been requested for any thread during the previous VM execution: */
|
|
/* - Pause */
|
|
/* - Jump */
|
|
|
|
/* JUMP: */
|
|
/* Note: If a jump has been requested, the jump destination is stored */
|
|
/* in m->threadsData[REQUESTED_PC_OFFSET]. Otherwise m->threadsData[REQUESTED_PC_OFFSET] == 0xFFFF */
|
|
|
|
/* PAUSE: */
|
|
/* Note: If a pause has been requested it is stored in m->vmIsChannelActive[REQUESTED_STATE][i] */
|
|
|
|
for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
|
|
|
|
m->vmIsChannelActive[CURR_STATE][threadId] = m->vmIsChannelActive[REQUESTED_STATE][threadId];
|
|
|
|
uint16_t n = m->threadsData[REQUESTED_PC_OFFSET][threadId];
|
|
|
|
if (n != VM_NO_SETVEC_REQUESTED) {
|
|
|
|
m->threadsData[PC_OFFSET][threadId] = (n == 0xFFFE) ? VM_INACTIVE_THREAD : n;
|
|
m->threadsData[REQUESTED_PC_OFFSET][threadId] = VM_NO_SETVEC_REQUESTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
void vm_hostFrame(struct VirtualMachine* m) {
|
|
|
|
/* Run the Virtual Machine for every active threads (one vm frame). */
|
|
/* Inactive threads are marked with a thread instruction pointer set to 0xFFFF (VM_INACTIVE_THREAD). */
|
|
/* A thread must feature a break opcode so the interpreter can move to the next thread. */
|
|
|
|
for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
|
|
|
|
if (m->vmIsChannelActive[CURR_STATE][threadId])
|
|
continue;
|
|
|
|
uint16_t n = m->threadsData[PC_OFFSET][threadId];
|
|
|
|
if (n != VM_INACTIVE_THREAD) {
|
|
|
|
/* Set the script pointer to the right location. */
|
|
/* script pc is used in executeThread in order */
|
|
/* to get the next opcode. */
|
|
m->_scriptPtr.pc = m->res->segBytecode + n;
|
|
m->_stackPtr = 0;
|
|
|
|
m->gotoNextThread = false;
|
|
debug(DBG_VM, "vm_hostFrame() i=0x%02X n=0x%02X *p=0x%02X", threadId, n, *m->_scriptPtr.pc);
|
|
vm_executeThread(m);
|
|
|
|
/* Since .pc is going to be modified by this next loop iteration, we need to save it. */
|
|
m->threadsData[PC_OFFSET][threadId] = m->_scriptPtr.pc - m->res->segBytecode;
|
|
|
|
|
|
debug(DBG_VM, "vm_hostFrame() i=0x%02X pos=0x%X", threadId, m->threadsData[PC_OFFSET][threadId]);
|
|
if (m->sys->input.quit) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define COLOR_BLACK 0xFF
|
|
#define DEFAULT_ZOOM 0x40
|
|
|
|
|
|
void vm_executeThread(struct VirtualMachine* m) {
|
|
|
|
while (!m->gotoNextThread) {
|
|
uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
/* 1000 0000 is set */
|
|
if (opcode & 0x80)
|
|
{
|
|
uint16_t off = ((opcode << 8) | scriptPtr_fetchByte(&m->_scriptPtr)) * 2;
|
|
m->res->_useSegVideo2 = false;
|
|
int16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
int16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
int16_t h = y - 199;
|
|
if (h > 0) {
|
|
y = 199;
|
|
x += h;
|
|
}
|
|
debug(DBG_VIDEO, "vid_opcd_0x80 : opcode=0x%X off=0x%X x=%d y=%d", opcode, off, x, y);
|
|
|
|
/* This switch the polygon database to "cinematic" and probably draws a black polygon */
|
|
/* over all the screen. */
|
|
video_setDataBuffer(m->video, m->res->segCinematic, off);
|
|
struct Point temp;
|
|
temp.x = x;
|
|
temp.y = y;
|
|
video_readAndDrawPolygon(m->video, COLOR_BLACK, DEFAULT_ZOOM, &temp);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* 0100 0000 is set */
|
|
if (opcode & 0x40)
|
|
{
|
|
int16_t x, y;
|
|
uint16_t off = scriptPtr_fetchWord(&m->_scriptPtr) * 2;
|
|
x = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
m->res->_useSegVideo2 = false;
|
|
|
|
if (!(opcode & 0x20))
|
|
{
|
|
if (!(opcode & 0x10)) /* 0001 0000 is set */
|
|
{
|
|
x = (x << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
|
|
} else {
|
|
x = m->vmVariables[x];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (opcode & 0x10) { /* 0001 0000 is set */
|
|
x += 0x100;
|
|
}
|
|
}
|
|
|
|
y = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
if (!(opcode & 8)) /* 0000 1000 is set */
|
|
{
|
|
if (!(opcode & 4)) { /* 0000 0100 is set */
|
|
y = (y << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
|
|
} else {
|
|
y = m->vmVariables[y];
|
|
}
|
|
}
|
|
|
|
uint16_t zoom = scriptPtr_fetchByte(&m->_scriptPtr);
|
|
|
|
if (!(opcode & 2)) /* 0000 0010 is set */
|
|
{
|
|
if (!(opcode & 1)) /* 0000 0001 is set */
|
|
{
|
|
--m->_scriptPtr.pc;
|
|
zoom = 0x40;
|
|
}
|
|
else
|
|
{
|
|
zoom = m->vmVariables[zoom];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (opcode & 1) { /* 0000 0001 is set */
|
|
m->res->_useSegVideo2 = true;
|
|
--m->_scriptPtr.pc;
|
|
zoom = 0x40;
|
|
}
|
|
}
|
|
debug(DBG_VIDEO, "vid_opcd_0x40 : off=0x%X x=%d y=%d", off, x, y);
|
|
video_setDataBuffer(m->video, m->res->_useSegVideo2 ? m->res->_segVideo2 : m->res->segCinematic, off);
|
|
struct Point temp;
|
|
temp.x = x;
|
|
temp.y = y;
|
|
video_readAndDrawPolygon(m->video, 0xFF, zoom, &temp);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (opcode > 0x1A)
|
|
{
|
|
error("vm_executeThread() ec=0x%X invalid opcode=0x%X", 0xFFF, opcode);
|
|
}
|
|
else
|
|
{
|
|
(vm_opcodeTable[opcode])(m);
|
|
}
|
|
|
|
rb->yield();
|
|
}
|
|
}
|
|
|
|
void vm_inp_updatePlayer(struct VirtualMachine* m) {
|
|
|
|
sys_processEvents(m->sys);
|
|
|
|
if (m->res->currentPartId == 0x3E89) {
|
|
char c = m->sys->input.lastChar;
|
|
if (c == 8 || /*c == 0xD |*/ c == 0 || (c >= 'a' && c <= 'z')) {
|
|
m->vmVariables[VM_VARIABLE_LAST_KEYCHAR] = c & ~0x20;
|
|
m->sys->input.lastChar = 0;
|
|
}
|
|
}
|
|
|
|
int16_t lr = 0;
|
|
int16_t mask = 0;
|
|
int16_t ud = 0;
|
|
|
|
if (m->sys->input.dirMask & DIR_RIGHT) {
|
|
lr = 1;
|
|
mask |= 1;
|
|
}
|
|
if (m->sys->input.dirMask & DIR_LEFT) {
|
|
lr = -1;
|
|
mask |= 2;
|
|
}
|
|
if (m->sys->input.dirMask & DIR_DOWN) {
|
|
ud = 1;
|
|
mask |= 4;
|
|
}
|
|
|
|
m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = ud;
|
|
|
|
if (m->sys->input.dirMask & DIR_UP) {
|
|
m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = -1;
|
|
}
|
|
|
|
if (m->sys->input.dirMask & DIR_UP) { /* inpJump */
|
|
ud = -1;
|
|
mask |= 8;
|
|
}
|
|
|
|
m->vmVariables[VM_VARIABLE_HERO_POS_JUMP_DOWN] = ud;
|
|
m->vmVariables[VM_VARIABLE_HERO_POS_LEFT_RIGHT] = lr;
|
|
m->vmVariables[VM_VARIABLE_HERO_POS_MASK] = mask;
|
|
int16_t button = 0;
|
|
|
|
if (m->sys->input.button) {
|
|
button = 1;
|
|
mask |= 0x80;
|
|
}
|
|
|
|
m->vmVariables[VM_VARIABLE_HERO_ACTION] = button;
|
|
m->vmVariables[VM_VARIABLE_HERO_ACTION_POS_MASK] = mask;
|
|
}
|
|
|
|
void vm_inp_handleSpecialKeys(struct VirtualMachine* m) {
|
|
|
|
if (m->sys->input.pause) {
|
|
|
|
if (m->res->currentPartId != GAME_PART1 && m->res->currentPartId != GAME_PART2) {
|
|
m->sys->input.pause = false;
|
|
while (!m->sys->input.pause) {
|
|
sys_processEvents(m->sys);
|
|
sys_sleep(m->sys, 200);
|
|
}
|
|
}
|
|
m->sys->input.pause = false;
|
|
}
|
|
|
|
if (m->sys->input.code) {
|
|
m->sys->input.code = false;
|
|
if (m->res->currentPartId != GAME_PART_LAST && m->res->currentPartId != GAME_PART_FIRST) {
|
|
m->res->requestedNextPart = GAME_PART_LAST;
|
|
}
|
|
}
|
|
|
|
/* User has inputted a bad code, the "ERROR" screen is showing */
|
|
if (m->vmVariables[0xC9] == 1) {
|
|
debug(DBG_VM, "vm_inp_handleSpecialKeys() unhandled case (m->vmVariables[0xC9] == 1)");
|
|
}
|
|
|
|
}
|
|
|
|
void vm_snd_playSound(struct VirtualMachine* m, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel) {
|
|
|
|
debug(DBG_SND, "snd_playSound(0x%X, %d, %d, %d)", resNum, freq, vol, channel);
|
|
|
|
struct MemEntry *me = &m->res->_memList[resNum];
|
|
|
|
if (me->state != MEMENTRY_STATE_LOADED)
|
|
return;
|
|
|
|
|
|
if (vol == 0) {
|
|
mixer_stopChannel(m->mixer, channel);
|
|
} else {
|
|
struct MixerChunk mc;
|
|
rb->memset(&mc, 0, sizeof(mc));
|
|
mc.data = me->bufPtr + 8; /* skip header */
|
|
mc.len = READ_BE_UINT16(me->bufPtr) * 2;
|
|
mc.loopLen = READ_BE_UINT16(me->bufPtr + 2) * 2;
|
|
if (mc.loopLen != 0) {
|
|
mc.loopPos = mc.len;
|
|
}
|
|
assert(freq < 40);
|
|
mixer_playChannel(m->mixer, channel & 3, &mc, vm_frequenceTable[freq], MIN(vol, 0x3F));
|
|
}
|
|
|
|
}
|
|
|
|
void vm_snd_playMusic(struct VirtualMachine* m, uint16_t resNum, uint16_t delay, uint8_t pos) {
|
|
|
|
debug(DBG_SND, "snd_playMusic(0x%X, %d, %d)", resNum, delay, pos);
|
|
|
|
if (resNum != 0) {
|
|
player_loadSfxModule(m->player, resNum, delay, pos);
|
|
player_start(m->player);
|
|
} else if (delay != 0) {
|
|
player_setEventsDelay(m->player, delay);
|
|
} else {
|
|
player_stop(m->player);
|
|
}
|
|
}
|
|
|
|
void vm_saveOrLoad(struct VirtualMachine* m, struct Serializer *ser) {
|
|
struct Entry entries[] = {
|
|
SE_ARRAY(m->vmVariables, 0x100, SES_INT16, VER(1)),
|
|
SE_ARRAY(m->_scriptStackCalls, 0x100, SES_INT16, VER(1)),
|
|
SE_ARRAY(m->threadsData, 0x40 * 2, SES_INT16, VER(1)),
|
|
SE_ARRAY(m->vmIsChannelActive, 0x40 * 2, SES_INT8, VER(1)),
|
|
SE_END()
|
|
};
|
|
ser_saveOrLoadEntries(ser, entries);
|
|
}
|
|
|
|
void vm_bypassProtection(struct VirtualMachine* m)
|
|
{
|
|
File f;
|
|
file_create(&f, true);
|
|
if (!file_open(&f, "bank0e", res_getDataDir(m->res), "rb")) {
|
|
warning("Unable to bypass protection: add bank0e file to datadir");
|
|
} else {
|
|
struct Serializer s;
|
|
ser_create(&s, &f, SM_LOAD, m->res->_memPtrStart, 2);
|
|
vm_saveOrLoad(m, &s);
|
|
res_saveOrLoad(m->res, &s);
|
|
video_saveOrLoad(m->video, &s);
|
|
player_saveOrLoad(m->player, &s);
|
|
mixer_saveOrLoad(m->mixer, &s);
|
|
}
|
|
file_close(&f);
|
|
}
|