diff options
Diffstat (limited to 'apps/plugins/xworld/vm.c')
| -rw-r--r-- | apps/plugins/xworld/vm.c | 763 |
1 files changed, 763 insertions, 0 deletions
diff --git a/apps/plugins/xworld/vm.c b/apps/plugins/xworld/vm.c new file mode 100644 index 0000000..de632d7 --- /dev/null +++ b/apps/plugins/xworld/vm.c @@ -0,0 +1,763 @@ +/*************************************************************************** + * __________ __ ___. + * 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); +} |