A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 Franklin Wei, Benjamin Brown
11 * Copyright (C) 2004 Gregory Montoir
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "plugin.h"
24#include "vm.h"
25#include "mixer.h"
26#include "resource.h"
27#include "video.h"
28#include "serializer.h"
29#include "sfxplayer.h"
30#include "sys.h"
31#include "parts.h"
32#include "file.h"
33
34static const uint16_t vm_frequenceTable[] = {
35 0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C,
36 0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE,
37 0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110,
38 0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE,
39 0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD
40};
41
42void vm_create(struct VirtualMachine* m, struct Mixer *mix, struct Resource* res, struct SfxPlayer *ply, struct Video *vid, struct System *stub)
43{
44 m->res = res;
45 m->video = vid;
46 m->sys = stub;
47 m->mixer = mix;
48 m->player = ply;
49}
50
51void vm_init(struct VirtualMachine* m) {
52
53 rb->memset(m->vmVariables, 0, sizeof(m->vmVariables));
54 m->vmVariables[0x54] = 0x81;
55 m->vmVariables[VM_VARIABLE_RANDOM_SEED] = *rb->current_tick % 0x10000;
56
57 /* rawgl has these, but they don't seem to do anything */
58 //m->vmVariables[0xBC] = 0x10;
59 //m->vmVariables[0xC6] = 0x80;
60 //m->vmVariables[0xF2] = 4000;
61 //m->vmVariables[0xDC] = 33;
62
63 m->_fastMode = false;
64 m->player->_markVar = &m->vmVariables[VM_VARIABLE_MUS_MARK];
65}
66
67void vm_op_movConst(struct VirtualMachine* m) {
68 uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
69 int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
70 debug(DBG_VM, "vm_op_movConst(0x%02X, %d)", variableId, value);
71 m->vmVariables[variableId] = value;
72}
73
74void vm_op_mov(struct VirtualMachine* m) {
75 uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
76 uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
77 debug(DBG_VM, "vm_op_mov(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
78 m->vmVariables[dstVariableId] = m->vmVariables[srcVariableId];
79}
80
81void vm_op_add(struct VirtualMachine* m) {
82 uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
83 uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
84 debug(DBG_VM, "vm_op_add(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
85 m->vmVariables[dstVariableId] += m->vmVariables[srcVariableId];
86}
87
88void vm_op_addConst(struct VirtualMachine* m) {
89 if (m->res->currentPartId == 0x3E86 && m->_scriptPtr.pc == m->res->segBytecode + 0x6D48) {
90 //warning("vm_op_addConst() hack for non-stop looping gun sound bug");
91 // the script 0x27 slot 0x17 doesn't stop the gun sound from looping, I
92 // don't really know why ; for now, let's play the 'stopping sound' like
93 // the other scripts do
94 // (0x6D43) jmp(0x6CE5)
95 // (0x6D46) break
96 // (0x6D47) VAR(6) += -50
97 vm_snd_playSound(m, 0x5B, 1, 64, 1);
98 }
99 uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
100 int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
101 debug(DBG_VM, "vm_op_addConst(0x%02X, %d)", variableId, value);
102 m->vmVariables[variableId] += value;
103}
104
105void vm_op_call(struct VirtualMachine* m) {
106
107 uint16_t offset = scriptPtr_fetchWord(&m->_scriptPtr);
108 uint8_t sp = m->_stackPtr;
109
110 debug(DBG_VM, "vm_op_call(0x%X)", offset);
111 m->_scriptStackCalls[sp] = m->_scriptPtr.pc - m->res->segBytecode;
112 if (m->_stackPtr == 0xFF) {
113 error("vm_op_call() ec=0x%X stack overflow", 0x8F);
114 }
115 ++m->_stackPtr;
116 m->_scriptPtr.pc = m->res->segBytecode + offset ;
117}
118
119void vm_op_ret(struct VirtualMachine* m) {
120 debug(DBG_VM, "vm_op_ret()");
121 if (m->_stackPtr == 0) {
122 error("vm_op_ret() ec=0x%X stack underflow", 0x8F);
123 }
124 --m->_stackPtr;
125 uint8_t sp = m->_stackPtr;
126 m->_scriptPtr.pc = m->res->segBytecode + m->_scriptStackCalls[sp];
127}
128
129void vm_op_pauseThread(struct VirtualMachine* m) {
130 debug(DBG_VM, "vm_op_pauseThread()");
131 m->gotoNextThread = true;
132}
133
134void vm_op_jmp(struct VirtualMachine* m) {
135 uint16_t pcOffset = scriptPtr_fetchWord(&m->_scriptPtr);
136 debug(DBG_VM, "vm_op_jmp(0x%02X)", pcOffset);
137 m->_scriptPtr.pc = m->res->segBytecode + pcOffset;
138}
139
140void vm_op_setSetVect(struct VirtualMachine* m) {
141 uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
142 uint16_t pcOffsetRequested = scriptPtr_fetchWord(&m->_scriptPtr);
143 debug(DBG_VM, "vm_op_setSetVect(0x%X, 0x%X)", threadId, pcOffsetRequested);
144 m->threadsData[REQUESTED_PC_OFFSET][threadId] = pcOffsetRequested;
145}
146
147void vm_op_jnz(struct VirtualMachine* m) {
148 uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
149 debug(DBG_VM, "vm_op_jnz(0x%02X)", i);
150 --m->vmVariables[i];
151 if (m->vmVariables[i] != 0) {
152 vm_op_jmp(m);
153 } else {
154 scriptPtr_fetchWord(&m->_scriptPtr);
155 }
156}
157
158#define BYPASS_PROTECTION
159void vm_op_condJmp(struct VirtualMachine* m) {
160
161 //debug(DBG_VM, "Jump : %X \n",m->_scriptPtr.pc-m->res->segBytecode);
162//FCS Whoever wrote this is patching the bytecode on the fly. This is ballzy !!
163#if 0
164 if (m->res->currentPartId == GAME_PART_FIRST && m->_scriptPtr.pc == m->res->segBytecode + 0xCB9) {
165
166 // (0x0CB8) condJmp(0x80, VAR(41), VAR(30), 0xCD3)
167 *(m->_scriptPtr.pc + 0x00) = 0x81;
168 *(m->_scriptPtr.pc + 0x03) = 0x0D;
169 *(m->_scriptPtr.pc + 0x04) = 0x24;
170 // (0x0D4E) condJmp(0x4, VAR(50), 6, 0xDBC)
171 *(m->_scriptPtr.pc + 0x99) = 0x0D;
172 *(m->_scriptPtr.pc + 0x9A) = 0x5A;
173 debug(DBG_VM, "vm_op_condJmp() bypassing protection");
174 debug(DBG_VM, "bytecode has been patched");
175
176 //warning("bypassing protection");
177
178 //vm_bypassProtection(m);
179 }
180
181
182#endif
183
184 uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
185 uint8_t var = scriptPtr_fetchByte(&m->_scriptPtr);
186 int16_t b = m->vmVariables[var];
187 uint8_t c = scriptPtr_fetchByte(&m->_scriptPtr);
188 int16_t a;
189
190 if (opcode & 0x80) {
191 a = m->vmVariables[c];
192 } else if (opcode & 0x40) {
193 a = c * 256 + scriptPtr_fetchByte(&m->_scriptPtr);
194 } else {
195 a = c;
196 }
197 debug(DBG_VM, "vm_op_condJmp(%d, 0x%02X, 0x%02X)", opcode, b, a);
198
199 // Check if the conditional value is met.
200 bool expr = false;
201 switch (opcode & 7) {
202 case 0: // jz
203 expr = (b == a);
204
205#ifdef BYPASS_PROTECTION
206 /* always succeed in code wheel verification */
207 if (m->res->currentPartId == GAME_PART_FIRST && var == 0x29 && (opcode & 0x80) != 0) {
208
209 m->vmVariables[0x29] = m->vmVariables[0x1E];
210 m->vmVariables[0x2A] = m->vmVariables[0x1F];
211 m->vmVariables[0x2B] = m->vmVariables[0x20];
212 m->vmVariables[0x2C] = m->vmVariables[0x21];
213 // counters
214 m->vmVariables[0x32] = 6;
215 m->vmVariables[0x64] = 20;
216 expr = true;
217 //warning("Script::op_condJmp() bypassing protection");
218 }
219#endif
220 break;
221 case 1: // jnz
222 expr = (b != a);
223 break;
224 case 2: // jg
225 expr = (b > a);
226 break;
227 case 3: // jge
228 expr = (b >= a);
229 break;
230 case 4: // jl
231 expr = (b < a);
232 break;
233 case 5: // jle
234 expr = (b <= a);
235 break;
236 default:
237 warning("vm_op_condJmp() invalid condition %d", (opcode & 7));
238 break;
239 }
240
241 if (expr) {
242 vm_op_jmp(m);
243 } else {
244 scriptPtr_fetchWord(&m->_scriptPtr);
245 }
246
247}
248
249void vm_op_setPalette(struct VirtualMachine* m) {
250 uint16_t paletteId = scriptPtr_fetchWord(&m->_scriptPtr);
251 debug(DBG_VM, "vm_op_changePalette(%d)", paletteId);
252 m->video->paletteIdRequested = paletteId >> 8;
253}
254
255void vm_op_resetThread(struct VirtualMachine* m) {
256
257 uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
258 uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
259
260 // FCS: WTF, this is cryptic as hell !!
261 // int8_t n = (i & 0x3F) - threadId; //0x3F = 0011 1111
262 // The following is so much clearer
263
264 //Make sure i is within [0-VM_NUM_THREADS-1]
265 i = i & (VM_NUM_THREADS - 1) ;
266 int8_t n = i - threadId;
267
268 if (n < 0) {
269 warning("vm_op_m->resetThread() ec=0x%X (n < 0)", 0x880);
270 return;
271 }
272 ++n;
273 uint8_t a = scriptPtr_fetchByte(&m->_scriptPtr);
274
275 debug(DBG_VM, "vm_op_m->resetThread(%d, %d, %d)", threadId, i, a);
276
277 if (a == 2) {
278 uint16_t *p = &m->threadsData[REQUESTED_PC_OFFSET][threadId];
279 while (n--) {
280 *p++ = 0xFFFE;
281 }
282 } else if (a < 2) {
283 uint8_t *p = &m->vmIsChannelActive[REQUESTED_STATE][threadId];
284 while (n--) {
285 *p++ = a;
286 }
287 }
288}
289
290void vm_op_selectVideoPage(struct VirtualMachine* m) {
291 uint8_t frameBufferId = scriptPtr_fetchByte(&m->_scriptPtr);
292 debug(DBG_VM, "vm_op_selectVideoPage(%d)", frameBufferId);
293 video_changePagePtr1(m->video, frameBufferId);
294}
295
296void vm_op_fillVideoPage(struct VirtualMachine* m) {
297 uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
298 uint8_t color = scriptPtr_fetchByte(&m->_scriptPtr);
299 debug(DBG_VM, "vm_op_fillVideoPage(%d, %d)", pageId, color);
300 video_fillPage(m->video, pageId, color);
301}
302
303void vm_op_copyVideoPage(struct VirtualMachine* m) {
304 uint8_t srcPageId = scriptPtr_fetchByte(&m->_scriptPtr);
305 uint8_t dstPageId = scriptPtr_fetchByte(&m->_scriptPtr);
306 debug(DBG_VM, "vm_op_copyVideoPage(%d, %d)", srcPageId, dstPageId);
307 video_copyPage(m->video, srcPageId, dstPageId, m->vmVariables[VM_VARIABLE_SCROLL_Y]);
308}
309
310
311static uint32_t lastTimeStamp = 0;
312void vm_op_blitFramebuffer(struct VirtualMachine* m) {
313
314 uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
315 debug(DBG_VM, "vm_op_blitFramebuffer(%d)", pageId);
316 vm_inp_handleSpecialKeys(m);
317
318 /* Nasty hack....was this present in the original assembly ??!! */
319 if (m->res->currentPartId == GAME_PART_FIRST && m->vmVariables[0x67] == 1)
320 m->vmVariables[0xDC] = 0x21;
321
322 if (!m->_fastMode) {
323
324 int32_t delay = sys_getTimeStamp(m->sys) - lastTimeStamp;
325 int32_t timeToSleep = m->vmVariables[VM_VARIABLE_PAUSE_SLICES] * 20 - delay;
326
327 /* The bytecode will set m->vmVariables[VM_VARIABLE_PAUSE_SLICES] from 1 to 5 */
328 /* The virtual machine hence indicates how long the image should be displayed. */
329
330 if (timeToSleep > 0)
331 {
332 sys_sleep(m->sys, timeToSleep);
333 }
334
335 lastTimeStamp = sys_getTimeStamp(m->sys);
336 }
337
338 /* WTF ? */
339 m->vmVariables[0xF7] = 0;
340
341 video_updateDisplay(m->video, pageId);
342}
343
344void vm_op_killThread(struct VirtualMachine* m) {
345 debug(DBG_VM, "vm_op_killThread()");
346 m->_scriptPtr.pc = m->res->segBytecode + 0xFFFF;
347 m->gotoNextThread = true;
348}
349
350void vm_op_drawString(struct VirtualMachine* m) {
351 uint16_t stringId = scriptPtr_fetchWord(&m->_scriptPtr);
352 uint16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
353 uint16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
354 uint16_t color = scriptPtr_fetchByte(&m->_scriptPtr);
355
356 debug(DBG_VM, "vm_op_drawString(0x%03X, %d, %d, %d)", stringId, x, y, color);
357
358 video_drawString(m->video, color, x, y, stringId);
359}
360
361void vm_op_sub(struct VirtualMachine* m) {
362 uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
363 uint8_t j = scriptPtr_fetchByte(&m->_scriptPtr);
364 debug(DBG_VM, "vm_op_sub(0x%02X, 0x%02X)", i, j);
365 m->vmVariables[i] -= m->vmVariables[j];
366}
367
368void vm_op_and(struct VirtualMachine* m) {
369 uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
370 uint16_t n = scriptPtr_fetchWord(&m->_scriptPtr);
371 debug(DBG_VM, "vm_op_and(0x%02X, %d)", variableId, n);
372 m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] & n;
373}
374
375void vm_op_or(struct VirtualMachine* m) {
376 uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
377 uint16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
378 debug(DBG_VM, "vm_op_or(0x%02X, %d)", variableId, value);
379 m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] | value;
380}
381
382void vm_op_shl(struct VirtualMachine* m) {
383 uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
384 uint16_t leftShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
385 debug(DBG_VM, "vm_op_shl(0x%02X, %d)", variableId, leftShiftValue);
386 m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] << leftShiftValue;
387}
388
389void vm_op_shr(struct VirtualMachine* m) {
390 uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
391 uint16_t rightShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
392 debug(DBG_VM, "vm_op_shr(0x%02X, %d)", variableId, rightShiftValue);
393 m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] >> rightShiftValue;
394}
395
396void vm_op_playSound(struct VirtualMachine* m) {
397 uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
398 uint8_t freq = scriptPtr_fetchByte(&m->_scriptPtr);
399 uint8_t vol = scriptPtr_fetchByte(&m->_scriptPtr);
400 uint8_t channel = scriptPtr_fetchByte(&m->_scriptPtr);
401 debug(DBG_VM, "vm_op_playSound(0x%X, %d, %d, %d)", resourceId, freq, vol, channel);
402 vm_snd_playSound(m, resourceId, freq, vol, channel);
403}
404
405void vm_op_updateMemList(struct VirtualMachine* m) {
406
407 uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
408 debug(DBG_VM, "vm_op_updateMemList(%d)", resourceId);
409
410 if (resourceId == 0) {
411 player_stop(m->player);
412 mixer_stopAll(m->mixer);
413 res_invalidateRes(m->res);
414 } else {
415 res_loadPartsOrMemoryEntry(m->res, resourceId);
416 }
417}
418
419void vm_op_playMusic(struct VirtualMachine* m) {
420 uint16_t resNum = scriptPtr_fetchWord(&m->_scriptPtr);
421 uint16_t delay = scriptPtr_fetchWord(&m->_scriptPtr);
422 uint8_t pos = scriptPtr_fetchByte(&m->_scriptPtr);
423 debug(DBG_VM, "vm_op_playMusic(0x%X, %d, %d)", resNum, delay, pos);
424 vm_snd_playMusic(m, resNum, delay, pos);
425}
426
427void vm_initForPart(struct VirtualMachine* m, uint16_t partId) {
428
429 player_stop(m->player);
430 mixer_stopAll(m->mixer);
431
432 /* WTF is that ? */
433 m->vmVariables[0xE4] = 0x14;
434
435 res_setupPart(m->res, partId);
436
437 /* Set all thread to inactive (pc at 0xFFFF or 0xFFFE ) */
438 rb->memset((uint8_t *)m->threadsData, 0xFF, sizeof(m->threadsData));
439
440 rb->memset((uint8_t *)m->vmIsChannelActive, 0, sizeof(m->vmIsChannelActive));
441
442 int firstThreadId = 0;
443 m->threadsData[PC_OFFSET][firstThreadId] = 0;
444}
445
446/*
447 This is called every frames in the infinite loop.
448*/
449void vm_checkThreadRequests(struct VirtualMachine* m) {
450
451 /* Check if a part switch has been requested. */
452 if (m->res->requestedNextPart != 0) {
453 vm_initForPart(m, m->res->requestedNextPart);
454 m->res->requestedNextPart = 0;
455 }
456
457
458 /* Check if a state update has been requested for any thread during the previous VM execution: */
459 /* - Pause */
460 /* - Jump */
461
462 /* JUMP: */
463 /* Note: If a jump has been requested, the jump destination is stored */
464 /* in m->threadsData[REQUESTED_PC_OFFSET]. Otherwise m->threadsData[REQUESTED_PC_OFFSET] == 0xFFFF */
465
466 /* PAUSE: */
467 /* Note: If a pause has been requested it is stored in m->vmIsChannelActive[REQUESTED_STATE][i] */
468
469 for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
470
471 m->vmIsChannelActive[CURR_STATE][threadId] = m->vmIsChannelActive[REQUESTED_STATE][threadId];
472
473 uint16_t n = m->threadsData[REQUESTED_PC_OFFSET][threadId];
474
475 if (n != VM_NO_SETVEC_REQUESTED) {
476
477 m->threadsData[PC_OFFSET][threadId] = (n == 0xFFFE) ? VM_INACTIVE_THREAD : n;
478 m->threadsData[REQUESTED_PC_OFFSET][threadId] = VM_NO_SETVEC_REQUESTED;
479 }
480 }
481}
482
483void vm_hostFrame(struct VirtualMachine* m) {
484
485 /* Run the Virtual Machine for every active threads (one vm frame). */
486 /* Inactive threads are marked with a thread instruction pointer set to 0xFFFF (VM_INACTIVE_THREAD). */
487 /* A thread must feature a break opcode so the interpreter can move to the next thread. */
488
489 for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
490
491 if (m->vmIsChannelActive[CURR_STATE][threadId])
492 continue;
493
494 uint16_t n = m->threadsData[PC_OFFSET][threadId];
495
496 if (n != VM_INACTIVE_THREAD) {
497
498 /* Set the script pointer to the right location. */
499 /* script pc is used in executeThread in order */
500 /* to get the next opcode. */
501 m->_scriptPtr.pc = m->res->segBytecode + n;
502 m->_stackPtr = 0;
503
504 m->gotoNextThread = false;
505 debug(DBG_VM, "vm_hostFrame() i=0x%02X n=0x%02X *p=0x%02X", threadId, n, *m->_scriptPtr.pc);
506 vm_executeThread(m);
507
508 /* Since .pc is going to be modified by this next loop iteration, we need to save it. */
509 m->threadsData[PC_OFFSET][threadId] = m->_scriptPtr.pc - m->res->segBytecode;
510
511
512 debug(DBG_VM, "vm_hostFrame() i=0x%02X pos=0x%X", threadId, m->threadsData[PC_OFFSET][threadId]);
513 if (m->sys->input.quit) {
514 break;
515 }
516 }
517 }
518}
519
520#define COLOR_BLACK 0xFF
521#define DEFAULT_ZOOM 0x40
522
523
524void vm_executeThread(struct VirtualMachine* m) {
525
526 while (!m->gotoNextThread) {
527 uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
528
529 /* 1000 0000 is set */
530 if (opcode & 0x80)
531 {
532 uint16_t off = ((opcode << 8) | scriptPtr_fetchByte(&m->_scriptPtr)) * 2;
533 m->res->_useSegVideo2 = false;
534 int16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
535 int16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
536 int16_t h = y - 199;
537 if (h > 0) {
538 y = 199;
539 x += h;
540 }
541 debug(DBG_VIDEO, "vid_opcd_0x80 : opcode=0x%X off=0x%X x=%d y=%d", opcode, off, x, y);
542
543 /* This switch the polygon database to "cinematic" and probably draws a black polygon */
544 /* over all the screen. */
545 video_setDataBuffer(m->video, m->res->segCinematic, off);
546 struct Point temp;
547 temp.x = x;
548 temp.y = y;
549 video_readAndDrawPolygon(m->video, COLOR_BLACK, DEFAULT_ZOOM, &temp);
550
551 continue;
552 }
553
554 /* 0100 0000 is set */
555 if (opcode & 0x40)
556 {
557 int16_t x, y;
558 uint16_t off = scriptPtr_fetchWord(&m->_scriptPtr) * 2;
559 x = scriptPtr_fetchByte(&m->_scriptPtr);
560
561 m->res->_useSegVideo2 = false;
562
563 if (!(opcode & 0x20))
564 {
565 if (!(opcode & 0x10)) /* 0001 0000 is set */
566 {
567 x = (x << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
568 } else {
569 x = m->vmVariables[x];
570 }
571 }
572 else
573 {
574 if (opcode & 0x10) { /* 0001 0000 is set */
575 x += 0x100;
576 }
577 }
578
579 y = scriptPtr_fetchByte(&m->_scriptPtr);
580
581 if (!(opcode & 8)) /* 0000 1000 is set */
582 {
583 if (!(opcode & 4)) { /* 0000 0100 is set */
584 y = (y << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
585 } else {
586 y = m->vmVariables[y];
587 }
588 }
589
590 uint16_t zoom = scriptPtr_fetchByte(&m->_scriptPtr);
591
592 if (!(opcode & 2)) /* 0000 0010 is set */
593 {
594 if (!(opcode & 1)) /* 0000 0001 is set */
595 {
596 --m->_scriptPtr.pc;
597 zoom = 0x40;
598 }
599 else
600 {
601 zoom = m->vmVariables[zoom];
602 }
603 }
604 else
605 {
606
607 if (opcode & 1) { /* 0000 0001 is set */
608 m->res->_useSegVideo2 = true;
609 --m->_scriptPtr.pc;
610 zoom = 0x40;
611 }
612 }
613 debug(DBG_VIDEO, "vid_opcd_0x40 : off=0x%X x=%d y=%d", off, x, y);
614 video_setDataBuffer(m->video, m->res->_useSegVideo2 ? m->res->_segVideo2 : m->res->segCinematic, off);
615 struct Point temp;
616 temp.x = x;
617 temp.y = y;
618 video_readAndDrawPolygon(m->video, 0xFF, zoom, &temp);
619
620 continue;
621 }
622
623
624 if (opcode > 0x1A)
625 {
626 error("vm_executeThread() ec=0x%X invalid opcode=0x%X", 0xFFF, opcode);
627 }
628 else
629 {
630 (vm_opcodeTable[opcode])(m);
631 }
632 }
633}
634
635void vm_inp_updatePlayer(struct VirtualMachine* m) {
636
637 sys_processEvents(m->sys);
638
639 if (m->res->currentPartId == 0x3E89) {
640 char c = m->sys->input.lastChar;
641 if (c == 8 || /*c == 0xD |*/ c == 0 || (c >= 'a' && c <= 'z')) {
642 m->vmVariables[VM_VARIABLE_LAST_KEYCHAR] = c & ~0x20;
643 m->sys->input.lastChar = 0;
644 }
645 }
646
647 int16_t lr = 0;
648 int16_t mask = 0;
649 int16_t ud = 0;
650
651 if (m->sys->input.dirMask & DIR_RIGHT) {
652 lr = 1;
653 mask |= 1;
654 }
655 if (m->sys->input.dirMask & DIR_LEFT) {
656 lr = -1;
657 mask |= 2;
658 }
659 if (m->sys->input.dirMask & DIR_DOWN) {
660 ud = 1;
661 mask |= 4;
662 }
663
664 m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = ud;
665
666 if (m->sys->input.dirMask & DIR_UP) {
667 m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = -1;
668 }
669
670 if (m->sys->input.dirMask & DIR_UP) { /* inpJump */
671 ud = -1;
672 mask |= 8;
673 }
674
675 m->vmVariables[VM_VARIABLE_HERO_POS_JUMP_DOWN] = ud;
676 m->vmVariables[VM_VARIABLE_HERO_POS_LEFT_RIGHT] = lr;
677 m->vmVariables[VM_VARIABLE_HERO_POS_MASK] = mask;
678 int16_t button = 0;
679
680 if (m->sys->input.button) {
681 button = 1;
682 mask |= 0x80;
683 }
684
685 m->vmVariables[VM_VARIABLE_HERO_ACTION] = button;
686 m->vmVariables[VM_VARIABLE_HERO_ACTION_POS_MASK] = mask;
687}
688
689void vm_inp_handleSpecialKeys(struct VirtualMachine* m) {
690
691 if (m->sys->input.pause) {
692
693 if (m->res->currentPartId != GAME_PART1 && m->res->currentPartId != GAME_PART2) {
694 m->sys->input.pause = false;
695 while (!m->sys->input.pause) {
696 sys_processEvents(m->sys);
697 sys_sleep(m->sys, 200);
698 }
699 }
700 m->sys->input.pause = false;
701 }
702
703 if (m->sys->input.code) {
704 m->sys->input.code = false;
705 if (m->res->currentPartId != GAME_PART_LAST && m->res->currentPartId != GAME_PART_FIRST) {
706 m->res->requestedNextPart = GAME_PART_LAST;
707 }
708 }
709
710 /* User has inputted a bad code, the "ERROR" screen is showing */
711 if (m->vmVariables[0xC9] == 1) {
712 debug(DBG_VM, "vm_inp_handleSpecialKeys() unhandled case (m->vmVariables[0xC9] == 1)");
713 }
714
715}
716
717void vm_snd_playSound(struct VirtualMachine* m, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel) {
718
719 debug(DBG_SND, "snd_playSound(0x%X, %d, %d, %d)", resNum, freq, vol, channel);
720
721 struct MemEntry *me = &m->res->_memList[resNum];
722
723 if (me->state != MEMENTRY_STATE_LOADED)
724 return;
725
726
727 if (vol == 0) {
728 mixer_stopChannel(m->mixer, channel);
729 } else {
730 struct MixerChunk mc;
731 rb->memset(&mc, 0, sizeof(mc));
732 mc.data = me->bufPtr + 8; /* skip header */
733 mc.len = READ_BE_UINT16(me->bufPtr) * 2;
734 mc.loopLen = READ_BE_UINT16(me->bufPtr + 2) * 2;
735 if (mc.loopLen != 0) {
736 mc.loopPos = mc.len;
737 }
738 assert(freq < 40);
739 mixer_playChannel(m->mixer, channel & 3, &mc, vm_frequenceTable[freq], MIN(vol, 0x3F));
740 }
741
742}
743
744void vm_snd_playMusic(struct VirtualMachine* m, uint16_t resNum, uint16_t delay, uint8_t pos) {
745
746 debug(DBG_SND, "snd_playMusic(0x%X, %d, %d)", resNum, delay, pos);
747
748 if (resNum != 0) {
749 player_loadSfxModule(m->player, resNum, delay, pos);
750 player_start(m->player);
751 } else if (delay != 0) {
752 player_setEventsDelay(m->player, delay);
753 } else {
754 player_stop(m->player);
755 }
756}
757
758void vm_saveOrLoad(struct VirtualMachine* m, struct Serializer *ser) {
759 struct Entry entries[] = {
760 SE_ARRAY(m->vmVariables, 0x100, SES_INT16, VER(1)),
761 SE_ARRAY(m->_scriptStackCalls, 0x100, SES_INT16, VER(1)),
762 SE_ARRAY(m->threadsData, 0x40 * 2, SES_INT16, VER(1)),
763 SE_ARRAY(m->vmIsChannelActive, 0x40 * 2, SES_INT8, VER(1)),
764 SE_END()
765 };
766 ser_saveOrLoadEntries(ser, entries);
767}
768
769void vm_bypassProtection(struct VirtualMachine* m)
770{
771 File f;
772 file_create(&f, true);
773 if (!file_open(&f, "bank0e", res_getDataDir(m->res), "rb")) {
774 warning("Unable to bypass protection: add bank0e file to datadir");
775 } else {
776 struct Serializer s;
777 ser_create(&s, &f, SM_LOAD, m->res->_memPtrStart, 2);
778 vm_saveOrLoad(m, &s);
779 res_saveOrLoad(m->res, &s);
780 video_saveOrLoad(m->video, &s);
781 player_saveOrLoad(m->player, &s);
782 mixer_saveOrLoad(m->mixer, &s);
783 }
784 file_close(&f);
785}