[Scummvm-git-logs] scummvm master -> 038b33c3fea1427337aa775720b0ae91a6b212e9
sev-
noreply at scummvm.org
Mon Feb 9 22:34:56 UTC 2026
This automated email contains information about 273 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
9722246926 PHOENIXVR: add stub
609f9b7aa8 PHOENIXVR: add parsing code skeleton
f59be037b0 PHOENIXVR: add warp/text hierarchy
e48bb28c54 PHOENIXVR: add command list, fix warp parser so tst regions are read
baaab22707 PHOENIXVR: more parse stubs
c8d58900e2 PHOENIXVR: move parseCommand to the parser
2095293777 PHOENIXVR: add Scope
3738b605f4 PHOENIXVR: enought stubs to parse script1
f9f6593c8b PHOENIXVR: add plugin scope
10bcf0e609 PHOENIXVR: implement conditional
5723dc7a6b PHOENIXVR: implement all script stubs enough to compile script1
e3c7a3f95f PHOENIXVR: add region set stub, rework script loading, unified init/goto
649faa8506 PHOENIXVR: add region set reader
390e179083 PHOENIXVR: read variable.txt and implement ifand/ifor/cmp
16d127786b PHOENIXVR: retrieve region for cursor command
578a5b080e PHOENIXVR: use g_engine
db8106f1c9 PHOENIXVR: implement mouse cursors
24e84b670c PHOENIXVR: add test slot stub
bc75ca086b PHOENIXVR: ignore cursor for different vr
bac5e4d0b9 PHOENIXVR: implement VR unpacker
431fca932a PHOENIXVR: decode vr and convert yuv to rgb
ee328d3ed7 PHOENIXVR: more RE on picture compression
97679de76a PHOENIXVR: add colorkey for cursor bitmaps
ccf6d6961d PHOENIXVR: fix dc coefficient pointer
48b439d68b PHOENIXVR: model reader after original code
0d926b57af PHOENIXVR: use region set to process click
31149492fd PHOENIXVR: add DCT2DIII class
709668630f PHOENIXVR: make warps case insensitive
8885fa52df PHOENIXVR: inverse dc coefficients, found where normalisation happens
5c9aef64cc PHOENIXVR: remove dct coefficients logging
2e8a8e7392 PHOENIXVR: support 256x6144 pictures
58cefb16b0 PHOENIXVR: add quantisation structure to premultiply quantisation coefficients
4253110718 PHOENIXVR: fix DC zero point
fe08538805 PHOENIXVR: relatively working dynamic range
db5308062d PHOENIXVR: add executeTest() api and call it from changecurseur
1da8b39a2a PHOENIXVR: remove dc coefficient modification, it's signed int8
d3df6fede6 PHOENIXVR: move picture management code to VR
e133651092 PHOENIXVR: implement add/sub
2064d00a19 PHOENIXVR: add end which should quit if no next script loaded
cc4a6632f1 PHOENIXVR: add simple mouse handling and arrange faces
f9743d0019 PHOENIXVR: add real scrolling and calculate angles per columns
940ad58a5c PHOENIXVR: add limit for frame duration
7b648c8ae1 PHOENIXVR: add separate projections for x/y
0e9737e78f PHOENIXVR: map vector to cube face
99209f8d8f PHOENIXVR: add RectF
5c32c05f94 PHOENIXVR: add fmod to angles
034c3fcbf0 PHOENIXVR: lookup vr angles in vr mode
d9f34e8e55 PHOENIXVR: add mixer stub
e5a283d6c5 PHOENIXVR: calculate position once
f9485b3add PHOENIXVR: populate cursor array from region set
f8b84254a5 PHOENIXVR: add containsVR() with required transformations
a80c3d8730 PHOENIXVR: add Angle structure to handle wrapping
0e838ea559 PHOENIXVR: vr fix up
1b0c6366d6 PHOENIXVR: shut noisy log up
1b3948ac15 PHOENIXVR: expose transformation for vr, remove containVR
df282ca3e6 PHOENIXVR: add keyword() to parser to distinguish between prefix and keyword, implement setAngle
cf09616954 PHOENIXVR: scripting using 8192 fixed point ints for angles
b51137429d PHOENIXVR: correct angley for pi/2
38f0aedde9 PHOENIXVR: support 2 default cursors
de885c7143 PHOENIXVR: move dct tables to separate file
042ce50a0e PHOENIXVR: pass fov from engine
b178268c7b PHOENIXVR: remove vr x-angle hack, moved to RectF::translate
0e0a48eb08 PHOENIXVR: break after first region
abb811b995 VIDEO: add 4xm decoder stub
d9251402c2 PHOENIXVR: move rendering logic to tick(), add playMovie stub
0826a8a615 PHOENIXVR: add perspective projection
08950506ec PHOENIXVR: add AngleX/AngleY, proper clipping and fix rectangle matching
fe56b07521 PHOENIXVR: add 3d sound stubs
a054e025cc PHOENIXVR: add 3d sound support
fc91b33f04 PHOENIXVR: turn the scene for 90° so no correction needed
056b09347d PHOENIXVR: add lockkey support
f9a6e87576 PHOENIXVR: fix conditional prefix for command
4cd8b084cf PHOENIXVR: convert decoded picture to the current pixel format
57843fe940 VIDEO: 4XM: move frame related code back to decoder
3bbd158e61 AUDIO: 4XM: add adpcm codec implementation
29a4920d4f VIDEO: 4XM: decode ADPCM audio data
43fd11a7c4 PHOENIXVR: wait until next frame is available
dfdaafbd88 AUDIO: 4XM: produce interlaced samples
a8078c0231 PHOENIXVR: use endOfVideo as end of video marker
cad08976e7 VIDEO: 4XM: do not use audio for sync (it's in frame after video), parse audio packet
215d188e3a VIDEO: 4XM: use queueing audio stream and simplify audio track
6904853041 PHOENIXVR: use needsUpdate()
5102e01b02 VIDEO: 4XM: implement PCM audio tracks
dae7769aa8 AUDIO: ADPCM: store predictors in ctor
364dda63d3 VIDEO: 4XM: add ADPCM support
638af51c12 AUDIO: 4XM: store planar data and read it from readBuffer
418e2ba497 VIDEO: 4XM: add ADPCM support
7ca5e834d4 PHOENIXVR: add i/p/c frame headers parsing
2287e2c4c6 PHOENIXVR: enabled play movie, it's necessary for game scripts to work
09d56114ea PHOENIXVR: implement timer logic
61595fdf37 VIDEO: move huffman/bitstream code to 4xm_utils
3b781fbd25 PHOENIXVR: implement contains with wrapping (still somewhat wrong)
723ad43537 PHOENIXVR: remove wrap to the new location by space key
9b024d5afb PHOENIXVR: add rectf/pointf toString
422b5ee30b PHOENIXVR: add warp console command
3274daf1e4 PHOENIXVR: always sort region coordinates (original engine do this for both 2d/3d)
a220f5b206 PHOENIXVR: add REed method of checking region, fix y angle ranges
c0f2959ba3 PHOENIXVR: fix color keying
a1982e42f1 PHOENIXVR: allow setCursor out of bounds
8a480c6ec9 PHOENIXVR: parse animation
dc33b0c0a0 PHOENIXVR: implement animation unpacking
76136ea497 PHOENIXVR: add warnings for non-existent commands
4d6265c08d PHOENIXVR: add angle to radians coefficient
da84ee45a9 PHOENIXVR: implement Play_AnimBloc_Number
13b798d1ed PHOENIXVR: implement test save slot function
4a8292b488 PHOENIXVR: initial save state implementation
79480dbbfa PHOENIXVR: fix y direction
3459ddb9cb PHOENIXVR: add bitstream size and unpack dc prefix stream
17791a0ef6 PHOENIXVR: pass compressed huff size for static picture
e5f562cf1e PHOENIXVR: rename next to freq
19e517e153 PHOENIXVR: simplify huffman
9dbf22f80f PHOENIXVR: make bitstream generic and unpack 4xm bitstream
c556eca84f PHOENIXVR: add fast idct
edd3b385e2 VIDEO: 4XM: decode DC/AC coefficients
8c931e5b15 VIDEO: 4XM: fix iframe unpacking
66b76eb5a9 VIDEO: 4XM: add scaffolding for p frames
97d16ebe7d VIDEO: 4XM: factor huffman decoder out
458adff2cc PHOENIXVR: factor unpack() out
b175531464 PHOENIXVR: remove wordSize
a3b50c0215 PHOENIXVR: attempt to reconstruct frequencies
ac600ee492 PHOENIXVR: add mcdc
fe98af3851 VIDEO: 4XM: implement p frame scaffolding
97a447c4fa VIDEO: 4XM: fix version
8cda95f370 VIDEO: 4XM: fix running condition for huffman tree
1f48d79761 VIDEO: 4XM: add code decoder
9873f19118 VIDEO: 4MX: dump huffman tables
3f9718a541 VIDEO: 4XM: check streams overrun
514b71b76f VIDEO: 4XM: fix order/unit of bitstream for pframe
fb9531ebc2 VIDEO: 4XM: collect cframe
c3321f1671 VIDEO: 4XM: read video trackIdx
82abc38095 VIDEO: 4XM: swap bitstream LE to BE
101c95e30b VIDEO: 4XM: drop version <= 1
f36dd2988b VIDEO: 4XM: re-enable cfrm
bbef0a2008 VIDEO: 4XM: rewrite mcdc a bit, use template scaling parameter
f6b147d424 VIDEO: 4XM: fix motion vector sign
eae51786ed PHOENIXVR: remove custom dct, use 4xm
027e2a087b PHOENIXVR: fix inventory and right click
d4c71c988d PHOENIXVR: return non-existing tests
abc03a4802 PHOENIXVR: implement return/resetLockKey
67c1094681 PHOENIXVR: reduce log noise from vr code
a17647055b PHOENIXVR: add CD directory to SearchMan
9e954b4b23 PHOENIXVR: store original var order
481f6fadc2 PHOENIXVR: loading original save (semi stub)
c32324a407 PHOENIXVR: set next script while loading
3bd88d48a2 PHOENIXVR: implement loading, change next/prev to indices
e2bc292d9e PHOENIXVR: fix loading code enough to load original saves
a862ebf861 PHOENIXVR: fix empty animation chunks
a8b6167fd4 PHOENIXVR: actually set variable from state loading code
b8d8231987 PHOENIXVR: implement cmp ops
1e4b3c851d PHOENIXVR: added guard against overwriting previous warp
bac1f1c2c8 PHOENIXVR: implement while(wait)
f11956ce30 PHOENIXVR: change warning to debug
aec46a1b66 VIDEO: 4XM: remove noisy debug logs
4f428466f9 PHOENIXVR: remove log about video frame/animation
b0e48f18c4 PHOENIXVR: log about not taking ifand branch
6fbd74aed6 PHOENIXVR: store cursors in cache
b3d8e4aa67 PHOENIXVR: do not reset conditional on every plugin command, plugin-endplugin is a single scope
52f7bc8f74 PHOENIXVR: made isLoading immutable, temp fix for save.vr
f18ec84dfe PHOENIXVR: remove InitSlots warning
f4d777e7e3 PHOENIXVR: make playMovie synchronous
99966914f9 PHOENIXVR: load samples/samples3d
3b02d320c0 PHOENIXVR: implement SetContextLabel
53cec7d67f PHOENIXVR: add saveSaveSlot stub
a2c1ee0d3e PHOENIXVR: reimplement cursor table - fixes all known cursor issues so far
aa64bc004c PHOENIXVR: do not write static.bmp
64a1e40689 PHOENIXVR: implement angle range limits
795283644a PHOENIXVR: partially implemented context capture
9fc57807dc PHOENIXVR: store _lockKey slots as is (also load them from save)
425aacf0a3 PHOENIXVR: implement state capture, fix 3d sound entries order
8c1b49962f PHOENIXVR: stop all sounds on load
fe11e41707 PHOENIXVR: remove inactive sounds, store sounds in captured state
417b54e18d PHOENIXVR: remove stream load/save method
f7db662aba PHOENIXVR: implement save
beb835cafa PHOENIXVR: do not trigger empty warps for non-assigned key codes
26167dd22c PHOENIXVR: handle relative mouse position, lock mouse and remove wrapMouse
305237e8f2 PHOENIXVR: remove DIB hex dump
1aed7caa52 PHOENIXVR: remove face id mapping (save/load slot does not match loading screen index)
86967a5ab8 PHOENIXVR: allow compression, it compresses save files > 60x roughly
c0f5a15061 PHOENIXVR: fix thumbnail scaling/conversion
e2a184b6c4 PHOENIXVR: do not save non-looped sounds
ec96e62b29 PHOENIXVR: simplify cursor loading
ca5cef7a10 PHOENIXVR: ignore .VR cursors (original saves have them, but there's no RIFF chunks apart from PIC3D in there)
bbf563aa1a PHOENIXVR: clean up prev warp
ff2b940974 VIDEO: 4XM: use unsigned for dc
f2b20ff19e PHOENIXVR: fix tile split for save slots thumbnails
ed1520c4f9 PHOENIXVR: call resetLockKey
7da3a859bb PHOENIXVR: add list save/query save skeleton
6c871179a5 PHOENIXVR: add game state object
1e75108802 PHOENIXVR: disable save from launcher, saving requires CaptureState called from script
459c720e33 VIDEO: 4XM: do not calculate dst/src if we don't need them
89af1aa20b VIDEO: 4XM: remove restrict, dst/src can overlap
eb7ea22f43 VIDEO: 4XM: use word size for mcdc access, separate cases
966bd667a9 PHOENIXVR: stop sound handle before erasing
eac9ab5b1b PHOENIXVR: add hover argument to test
987bcfaf9f PHOENIXVR: implement mouse hover leave
ad15fcacf1 PHOENIXVR: output regions - helps navigate dark maze
0e2da70dc6 PHOENIXVR: use case insensitive subdirectory search (fixes end of CD1)
9871c6dc6b PHOENIXVR: add SaveVariable plugin
8f5391f1cc PHOENIXVR: call init script before returning to loaded warp
5cf48dc93c PHOENIXVR: save clear call
ffd31bbf8d PHOENIXVR: add 'r' hotkey to show 2d/3d regions
4fb6cfb8a0 PHOENIXVR: add BIGF compiler.dat parser
9792a1846a PHOENIXVR: terminate script after gotowarp
23f567d98d PHOENIXVR: schedule next test from ChangeCurseur and timer - fixes disc puzzles in shed and lab
4ac7067f9d PHOENIXVR: render vr before wait - fixes some animation-wait sequences (missing light animation on brain puzzle)
cae1c6bce7 PHOENIXVR: implement save/load variable state
2e7bbb890a PHOENIXVR: implement rollover (text labels)
a72e0bbe0e PHOENIXVR: implement LABEL/GOSUB
1017542462 PHOENIXVR: skip LoadVariable if there's no snapshot
76eede809a PHOENIXVR: do not override cursors if any was found, use default cursors later
2787806b92 PHOENIXVR: allow multiline text
2d1485b7fb PHOENIXVR: fix invalid location
3eae2d0dc3 PHOENIXVR: kill timer when loading the game
a9fc04c948 PHOENIXVR: scale thumbnails to 160x~120
8e02ec8e7e PHOENIXVR: do not use overlay format - it breaks cli --list-saves
49ab208a56 PHOENIXVR: allow loading from launcher
1ffb2d661c PHOENIXVR: implement can{Save|Load}GameStateCurrently
1e4317641d PHOENIXVR: clear cursors before loading next script
840f1f6b22 PHOENIXVR: change mdcit ac[0] to match original
3e0322b7e9 PHOENIXVR: cleaning up animations, figured out arguments meaning
53ea5bc171 PHOENIXVR: support animation with multiple frames and speed
21c345d83a PHOENIXVR: add until() support for 2d/3d
06c90092ea PHOENIXVR: original engine does sar 13 when apply quantisation
ced855013a PHOENIXVR: compensate 13 bit shift with multiplication (hack)
0e7a875384 PHOENIXVR: fix quit()
5dd3d3ece8 PHOENIXVR: fix crash after autosave conflict
0fd60c1764 VIDEO: 4XM: remove unused private members
21267f98bb VIDEO: 4XM: use sequential pointer access for video decoding
42c9f5824b VIDEO: 4XM: Fix compliation
b3adb579c1 PHOENIXVR: Fix compilation
403845aa68 PHONEXIVR: Fix warning with struct/class mismatch
5888d3cd03 PHOENIXVR: Fix portability issue with file offset printing
041a150f7b TESTBED: Added 4xm to the list of supported videos in the video playback test
154b6c523c VIDEO: 4XM: remove setPixel, use sequential macroblock access in i frame decoding code
0935e05bc2 VIDEO: 4XM: use double buffering and p-frame re-use the frame before previous, this seems to reduce dot artefacts a lot
3ab928b79d VIDEO: 4XM: use LE audio flag only on LE platforms
378e94d6b4 AUDIO: ADPCM: 4XM: allow custom shift, pass 4 for 4XM
bfcbb66374 VIDEO: 4XM: fix ADPCM clicks
6ba1ba9d22 VIDEO: 4XM: use 32 bit multiplication - fix the remaining visual artefacts of decoding
9d6491ea6a VIDEO: 4XM: revisited binary format and fix all audio issues
efe1d38c28 VIDEO: 4XM: add shift argument to idct
224bcb3f81 PHOENIXVR: improve idct accuracy by 1 bit
e1f355abfe VIDEO: 4XM: remove noisy log
0d768f9fab PHOENIXVR: remove planar YUV from VR decoder, simplify code
504c06e833 PHOENIXVR: use managed surfac where possible
9512d73ca3 PHOENIXVR: add variable.txt to detection
45da78d1b1 PHOENIXVR: add credits
0beab84bd3 PHOENIXVR: switch detection to textes.txt
0b0cb1f9b7 Add highres 16bit flags to configure
0cf0adb659 PHOENIXVR: change quantisation tables types to byte
f3aedf0b97 VIDEO: 4XM: use packetized audio stream
b971082cee PHOENIXVR: remove intermediate conversion for video, use simpleBlitFrom
74d40a6029 PHOENIXVR: add fallback font if freetype is not available
d7dbc21bba PHOENIXVR: support 16 bit pixel formats, use preferred hw format for the engine
2d687c415b PHOENIXVR: remove custom bitstream
749e902598 PHOENIXVR: detect messenger (2000) game
c8b842d63e PHOENIXVR: make variables.txt optional
61e438cb95 PHOENIXVR: add enough plugin stubs to run messenger/cameron files
da1d559131 PHOENIXVR: rename cameronlochness to lochness
e29b9f8f49 PHOENIXVR: remove searchman hack, add ::resolve to handle paths
aae2671a9b COMMON: HUFFMAN: add fromFrequencies()
d81ac175f7 PHOENIXVR: replace M_PI with float constants
f244bae560 PHOENIXVR: use Common::Huffman
919a04fc14 PHOENIXVR: use key mapper
577a8bf8d3 PHOENIXVR: clear mouseRel in 2d mode
f10733f7e4 PHOENIXVR: JANITORIAL: fix compilation warnings/errors
012c9b3256 PHOENIXVR: fix assertion when you press RETURN very early in the game
cdaa5455c5 PHOENIXVR: remove unpackHuffman, use huffman decoder directly
a8b8f36224 PHOENIXVR: if animation speed is 0, set dst var to value instantly
4f1406eabc PHOENIXVR: initialize default cursors
b25153a87c PHOENIXVR: remove switch with one case in parser
a350839866 PHOENIXVR: disable autosave and save
038b33c3fe PHOENIXVR: fix hints (press H), add noise effect
Commit: 9722246926ac0e699a1ca66c2d234f8d0d573a3c
https://github.com/scummvm/scummvm/commit/9722246926ac0e699a1ca66c2d234f8d0d573a3c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:20+01:00
Commit Message:
PHOENIXVR: add stub
Changed paths:
A engines/phoenixvr/POTFILES
A engines/phoenixvr/configure.engine
A engines/phoenixvr/console.cpp
A engines/phoenixvr/console.h
A engines/phoenixvr/credits.pl
A engines/phoenixvr/detection.cpp
A engines/phoenixvr/detection.h
A engines/phoenixvr/detection_tables.h
A engines/phoenixvr/metaengine.cpp
A engines/phoenixvr/metaengine.h
A engines/phoenixvr/module.mk
A engines/phoenixvr/pakf.cpp
A engines/phoenixvr/pakf.h
A engines/phoenixvr/phoenixvr.cpp
A engines/phoenixvr/phoenixvr.h
A engines/phoenixvr/script.cpp
A engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/POTFILES b/engines/phoenixvr/POTFILES
new file mode 100644
index 00000000000..fc6f577dc7b
--- /dev/null
+++ b/engines/phoenixvr/POTFILES
@@ -0,0 +1 @@
+engines/phoenixvr/metaengine.cpp
diff --git a/engines/phoenixvr/configure.engine b/engines/phoenixvr/configure.engine
new file mode 100644
index 00000000000..16eaf0d97a1
--- /dev/null
+++ b/engines/phoenixvr/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
+add_engine phoenixvr "PhoenixVR" no "" "" "" ""
diff --git a/engines/phoenixvr/console.cpp b/engines/phoenixvr/console.cpp
new file mode 100644
index 00000000000..8d9c238c2bf
--- /dev/null
+++ b/engines/phoenixvr/console.cpp
@@ -0,0 +1,38 @@
+/* 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 "phoenixvr/console.h"
+
+namespace PhoenixVR {
+
+Console::Console() : GUI::Debugger() {
+ registerCmd("test", WRAP_METHOD(Console, Cmd_test));
+}
+
+Console::~Console() {
+}
+
+bool Console::Cmd_test(int argc, const char **argv) {
+ debugPrintf("Test\n");
+ return true;
+}
+
+} // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/console.h b/engines/phoenixvr/console.h
new file mode 100644
index 00000000000..4be62344b7a
--- /dev/null
+++ b/engines/phoenixvr/console.h
@@ -0,0 +1,41 @@
+
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_CONSOLE_H
+#define PHOENIXVR_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace PhoenixVR {
+
+class Console : public GUI::Debugger {
+private:
+ bool Cmd_test(int argc, const char **argv);
+
+public:
+ Console();
+ ~Console() override;
+};
+
+} // End of namespace PhoenixVR
+
+#endif // PHOENIXVR_CONSOLE_H
diff --git a/engines/phoenixvr/credits.pl b/engines/phoenixvr/credits.pl
new file mode 100644
index 00000000000..151c9a5835d
--- /dev/null
+++ b/engines/phoenixvr/credits.pl
@@ -0,0 +1,3 @@
+begin_section("PhoenixVR");
+ add_person("Name 1", "Handle 1", "");
+end_section();
diff --git a/engines/phoenixvr/detection.cpp b/engines/phoenixvr/detection.cpp
new file mode 100644
index 00000000000..8879a8e06dc
--- /dev/null
+++ b/engines/phoenixvr/detection.cpp
@@ -0,0 +1,44 @@
+/* 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 "phoenixvr/detection.h"
+#include "base/plugins.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/md5.h"
+#include "common/str-array.h"
+#include "common/translation.h"
+#include "common/util.h"
+#include "phoenixvr/detection_tables.h"
+
+const DebugChannelDef PhoenixVRMetaEngineDetection::debugFlagList[] = {
+ {PhoenixVR::kDebugGraphics, "Graphics", "Graphics debug level"},
+ {PhoenixVR::kDebugPath, "Path", "Pathfinding debug level"},
+ {PhoenixVR::kDebugFilePath, "FilePath", "File path debug level"},
+ {PhoenixVR::kDebugScan, "Scan", "Scan for unrecognised games"},
+ {PhoenixVR::kDebugScript, "Script", "Enable debug script dump"},
+ DEBUG_CHANNEL_END};
+
+PhoenixVRMetaEngineDetection::PhoenixVRMetaEngineDetection() : AdvancedMetaEngineDetection(
+ PhoenixVR::gameDescriptions, PhoenixVR::phoenixvrGames) {
+}
+
+REGISTER_PLUGIN_STATIC(PHOENIXVR_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, PhoenixVRMetaEngineDetection);
diff --git a/engines/phoenixvr/detection.h b/engines/phoenixvr/detection.h
new file mode 100644
index 00000000000..93e61725b76
--- /dev/null
+++ b/engines/phoenixvr/detection.h
@@ -0,0 +1,69 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_DETECTION_H
+#define PHOENIXVR_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace PhoenixVR {
+
+enum PhoenixVRDebugChannels {
+ kDebugGraphics = 1,
+ kDebugPath,
+ kDebugScan,
+ kDebugFilePath,
+ kDebugScript,
+};
+
+extern const PlainGameDescriptor phoenixvrGames[];
+
+extern const ADGameDescription gameDescriptions[];
+
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+
+} // End of namespace PhoenixVR
+
+class PhoenixVRMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
+ static const DebugChannelDef debugFlagList[];
+
+public:
+ PhoenixVRMetaEngineDetection();
+ ~PhoenixVRMetaEngineDetection() override {}
+
+ const char *getName() const override {
+ return "phoenixvr";
+ }
+
+ const char *getEngineName() const override {
+ return "PhoenixVR";
+ }
+
+ const char *getOriginalCopyright() const override {
+ return "PhoenixVR (C)";
+ }
+
+ const DebugChannelDef *getDebugChannels() const override {
+ return debugFlagList;
+ }
+};
+
+#endif // PHOENIXVR_DETECTION_H
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
new file mode 100644
index 00000000000..85bd8af1631
--- /dev/null
+++ b/engines/phoenixvr/detection_tables.h
@@ -0,0 +1,39 @@
+/* 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/>.
+ *
+ */
+
+namespace PhoenixVR {
+
+const PlainGameDescriptor phoenixvrGames[] = {
+ {"necrono", "Necronomicon: The Dawning of Darkness"},
+ {0, 0}};
+
+const ADGameDescription gameDescriptions[] = {
+ {"necrono",
+ nullptr,
+ AD_ENTRY1s("script.pak", "86294b9c445c3e06e24269c84036a207", 223),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DROPPLATFORM,
+ GUIO1(GUIO_NONE)},
+
+ AD_TABLE_END_MARKER};
+
+} // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
new file mode 100644
index 00000000000..4dba22b041e
--- /dev/null
+++ b/engines/phoenixvr/metaengine.cpp
@@ -0,0 +1,64 @@
+/* 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/translation.h"
+
+#include "phoenixvr/detection.h"
+#include "phoenixvr/metaengine.h"
+#include "phoenixvr/phoenixvr.h"
+
+namespace PhoenixVR {
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ {GAMEOPTION_ORIGINAL_SAVELOAD,
+ {_s("Use original save/load screens"),
+ _s("Use the original save/load screens instead of the ScummVM ones"),
+ "original_menus",
+ false,
+ 0,
+ 0}},
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR};
+
+} // End of namespace PhoenixVR
+
+const char *PhoenixVRMetaEngine::getName() const {
+ return "phoenixvr";
+}
+
+const ADExtraGuiOptionsMap *PhoenixVRMetaEngine::getAdvancedExtraGuiOptions() const {
+ return PhoenixVR::optionsList;
+}
+
+Common::Error PhoenixVRMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ *engine = new PhoenixVR::PhoenixVREngine(syst, desc);
+ return Common::kNoError;
+}
+
+bool PhoenixVRMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return checkExtendedSaves(f) ||
+ (f == kSupportsLoadingDuringStartup);
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(PHOENIXVR)
+REGISTER_PLUGIN_DYNAMIC(PHOENIXVR, PLUGIN_TYPE_ENGINE, PhoenixVRMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(PHOENIXVR, PLUGIN_TYPE_ENGINE, PhoenixVRMetaEngine);
+#endif
diff --git a/engines/phoenixvr/metaengine.h b/engines/phoenixvr/metaengine.h
new file mode 100644
index 00000000000..c621ee6c1b5
--- /dev/null
+++ b/engines/phoenixvr/metaengine.h
@@ -0,0 +1,43 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_METAENGINE_H
+#define PHOENIXVR_METAENGINE_H
+
+#include "engines/advancedDetector.h"
+
+class PhoenixVRMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
+public:
+ const char *getName() const override;
+
+ Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
+
+ /**
+ * Determine whether the engine supports the specified MetaEngine feature.
+ *
+ * Used by e.g. the launcher to determine whether to enable the Load button.
+ */
+ bool hasFeature(MetaEngineFeature f) const override;
+
+ const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+};
+
+#endif // PHOENIXVR_METAENGINE_H
diff --git a/engines/phoenixvr/module.mk b/engines/phoenixvr/module.mk
new file mode 100644
index 00000000000..1b3168766b2
--- /dev/null
+++ b/engines/phoenixvr/module.mk
@@ -0,0 +1,19 @@
+MODULE := engines/phoenixvr
+
+MODULE_OBJS = \
+ pakf.o \
+ phoenixvr.o \
+ console.o \
+ metaengine.o \
+ script.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_PHOENIXVR), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o
diff --git a/engines/phoenixvr/pakf.cpp b/engines/phoenixvr/pakf.cpp
new file mode 100644
index 00000000000..904dba61505
--- /dev/null
+++ b/engines/phoenixvr/pakf.cpp
@@ -0,0 +1,97 @@
+/* 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 "phoenixvr/pakf.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+
+namespace PhoenixVR {
+
+namespace {
+void unpack_3(uint8 *dstBegin, uint32 dstSize, Common::SeekableReadStream &input) {
+ auto *dstEnd = dstBegin + dstSize;
+ auto *dst = dstBegin;
+ while (true) {
+ byte n = input.readByte();
+ if (input.eos())
+ break;
+ if (n & 0x80) {
+ int offset;
+ byte len = (n & 0x3f) + 1;
+ if (n & 0x40)
+ offset = input.readByte() + 1;
+ else {
+ offset = input.readByte();
+ offset <<= 8;
+ offset |= input.readByte();
+ offset += 1;
+ }
+ if (dst - offset < dstBegin)
+ error("invalid decompression offset");
+ if (dst + len > dstEnd)
+ error("invalid decompression size");
+ while (len--) {
+ *dst = dst[-offset];
+ ++dst;
+ }
+ } else {
+ unsigned len = n + 1;
+ if (dst + len > dstEnd)
+ error("invalid decompression size");
+ while (len--) {
+ *dst++ = input.readByte();
+ }
+ }
+ }
+}
+} // namespace
+
+Common::SeekableReadStream *unpack(Common::SeekableReadStream &input) {
+ input.seek(0);
+ byte header[0x24];
+ input.read(header, sizeof(header));
+ if (header[0] != 'P' || header[1] != 'A' || header[2] != 'K' || header[3] != 'F')
+ error("invalid PAKF signature");
+
+ Common::MemoryReadStreamEndian ms(header + 4, sizeof(header) - 4, false);
+ auto fsize = ms.readUint32();
+ if (fsize != input.size())
+ error("invalid PAKF size");
+
+ ms.skip(16); // original name
+ auto method = ms.readUint32();
+ auto csize = ms.readUint32();
+ auto usize = ms.readUint32();
+ debug("method %u, compressed size: %u, uncompressed size: %u", method, csize, usize);
+ Common::SharedPtr<byte> mem(new uint8[usize], Common::ArrayDeleter<byte>());
+ switch (method) {
+ case 3:
+ unpack_3(mem.get(), usize, input);
+ break;
+ default:
+ error("invalid compression method %u\n", method);
+ }
+
+ return new Common::MemoryReadStream(mem, usize);
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/pakf.h b/engines/phoenixvr/pakf.h
new file mode 100644
index 00000000000..7a6501e8c8e
--- /dev/null
+++ b/engines/phoenixvr/pakf.h
@@ -0,0 +1,31 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_PAKF_H
+#define PHOENIXVR_PAKF_H
+
+#include "common/stream.h"
+
+namespace PhoenixVR {
+Common::SeekableReadStream *unpack(Common::SeekableReadStream &input);
+}
+
+#endif
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
new file mode 100644
index 00000000000..311b8e3c85e
--- /dev/null
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -0,0 +1,107 @@
+/* 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 "phoenixvr/phoenixvr.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "common/file.h"
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/framelimiter.h"
+#include "graphics/paletteman.h"
+#include "phoenixvr/console.h"
+#include "phoenixvr/detection.h"
+#include "phoenixvr/pakf.h"
+#include "phoenixvr/script.h"
+
+namespace PhoenixVR {
+
+PhoenixVREngine *g_engine;
+
+PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
+ _gameDescription(gameDesc), _randomSource("PhoenixVR"), _pixelFormat(Graphics::PixelFormat::createFormatRGB24()) {
+ g_engine = this;
+}
+
+PhoenixVREngine::~PhoenixVREngine() {
+ delete _screen;
+}
+
+uint32 PhoenixVREngine::getFeatures() const {
+ return _gameDescription->flags;
+}
+
+Common::String PhoenixVREngine::getGameId() const {
+ return _gameDescription->gameId;
+}
+
+void PhoenixVREngine::load(const Common::Path &scriptFile) {
+ Common::File file;
+ if (!file.open(scriptFile))
+ error("can't open script file");
+ Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
+ Common::ScopedPtr<Script> script(new Script(*scriptStream));
+}
+
+Common::Error PhoenixVREngine::run() {
+ initGraphics(640, 480, &_pixelFormat);
+ _screen = new Graphics::Screen();
+ load("script.pak");
+
+ // Set the engine's debugger console
+ setDebugger(new Console());
+
+ // If a savegame was selected from the launcher, load it
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot != -1)
+ (void)loadGameState(saveSlot);
+
+ Common::Event e;
+
+ Graphics::FrameLimiter limiter(g_system, 60);
+ while (!shouldQuit()) {
+ while (g_system->getEventManager()->pollEvent(e)) {
+ }
+
+ // Delay for a bit. All events loops should have a delay
+ // to prevent the system being unduly loaded
+ limiter.delayBeforeSwap();
+ _screen->update();
+ limiter.startFrame();
+ }
+
+ return Common::kNoError;
+}
+
+Common::Error PhoenixVREngine::syncGame(Common::Serializer &s) {
+ // The Serializer has methods isLoading() and isSaving()
+ // if you need to specific steps; for example setting
+ // an array size after reading it's length, whereas
+ // for saving it would write the existing array's length
+ int dummy = 0;
+ s.syncAsUint32LE(dummy);
+
+ return Common::kNoError;
+}
+
+} // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
new file mode 100644
index 00000000000..64938ec00ba
--- /dev/null
+++ b/engines/phoenixvr/phoenixvr.h
@@ -0,0 +1,111 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_H
+#define PHOENIXVR_H
+
+#include "common/error.h"
+#include "common/fs.h"
+#include "common/hash-str.h"
+#include "common/random.h"
+#include "common/scummsys.h"
+#include "common/serializer.h"
+#include "common/system.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "engines/savestate.h"
+#include "graphics/screen.h"
+
+#include "phoenixvr/detection.h"
+
+namespace PhoenixVR {
+
+struct PhoenixVRGameDescription;
+
+class PhoenixVREngine : public Engine {
+private:
+ const ADGameDescription *_gameDescription;
+ Common::RandomSource _randomSource;
+ Graphics::PixelFormat _pixelFormat;
+
+protected:
+ // Engine APIs
+ Common::Error run() override;
+
+public:
+ Graphics::Screen *_screen = nullptr;
+
+public:
+ PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc);
+ ~PhoenixVREngine() override;
+
+ uint32 getFeatures() const;
+
+ /**
+ * Returns the game Id
+ */
+ Common::String getGameId() const;
+
+ /**
+ * Gets a random number
+ */
+ uint32 getRandomNumber(uint maxNum) {
+ return _randomSource.getRandomNumber(maxNum);
+ }
+
+ bool hasFeature(EngineFeature f) const override {
+ return (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime) ||
+ (f == kSupportsReturnToLauncher);
+ };
+
+ bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
+ return true;
+ }
+ bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
+ return true;
+ }
+
+ /**
+ * Uses a serializer to allow implementing savegame
+ * loading and saving using a single method
+ */
+ Common::Error syncGame(Common::Serializer &s);
+
+ Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
+ Common::Serializer s(nullptr, stream);
+ return syncGame(s);
+ }
+ Common::Error loadGameStream(Common::SeekableReadStream *stream) override {
+ Common::Serializer s(stream, nullptr);
+ return syncGame(s);
+ }
+
+private:
+ void load(const Common::Path &scriptFile);
+};
+
+extern PhoenixVREngine *g_engine;
+#define SHOULD_QUIT ::PhoenixVR::g_engine->shouldQuit()
+
+} // End of namespace PhoenixVR
+
+#endif // PHOENIXVR_H
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
new file mode 100644
index 00000000000..ed80e3f04b4
--- /dev/null
+++ b/engines/phoenixvr/script.cpp
@@ -0,0 +1,15 @@
+#include "phoenixvr/script.h"
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+namespace PhoenixVR {
+
+Script::Script(Common::SeekableReadStream &s) {
+ while (!s.eos()) {
+ auto line = s.readLine();
+ debug("LINE %s\n", line.c_str());
+ }
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
new file mode 100644
index 00000000000..cf5619b8c15
--- /dev/null
+++ b/engines/phoenixvr/script.h
@@ -0,0 +1,36 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_SCRIPT_H
+#define PHOENIXVR_SCRIPT_H
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace PhoenixVR {
+class Script {
+public:
+ Script(Common::SeekableReadStream &s);
+};
+} // namespace PhoenixVR
+
+#endif
Commit: 609f9b7aa88f7df9e7d99ec336e34b94df851465
https://github.com/scummvm/scummvm/commit/609f9b7aa88f7df9e7d99ec336e34b94df851465
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:20+01:00
Commit Message:
PHOENIXVR: add parsing code skeleton
Changed paths:
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index ed80e3f04b4..a3fc48f3bbe 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -5,11 +5,105 @@
namespace PhoenixVR {
+namespace {
+class Parser {
+ const Common::String &_line;
+ uint _lineno;
+ uint _pos;
+
+public:
+ Parser(const Common::String &line, uint lineno) : _line(line), _lineno(lineno), _pos(0) {}
+
+ void skip() {
+ while (_pos < _line.size() && Common::isSpace(_line[_pos]))
+ ++_pos;
+ if (_pos < _line.size() && _line[_pos] == ';')
+ _pos = _line.size();
+ }
+
+ bool atEnd() const { return _pos >= _line.size(); }
+ int peek() const { return _pos < _line.size() ? _line[_pos] : 0; }
+ int next() { return _pos < _line.size() ? _line[_pos++] : 0; }
+ void expect(int ch) {
+ skip();
+ if (_pos >= _line.size() || _line[_pos] != ch)
+ error("expected '%c'", ch);
+ ++_pos;
+ skip();
+ }
+
+ bool maybe(const Common::String &prefix) {
+ skip();
+ bool yes = scumm_strnicmp(_line.c_str() + _pos, prefix.c_str(), prefix.size()) == 0;
+ if (yes) {
+ _pos += prefix.size();
+ skip();
+ }
+ return yes;
+ }
+
+ Common::String nextWord() {
+ skip();
+ auto begin = _pos;
+ while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && !Common::isPunct(_line[_pos]))
+ ++_pos;
+ auto end = _pos;
+ skip();
+ return _line.substr(begin, end - begin);
+ }
+};
+} // namespace
+
+void Script::Test::parseLine(const Common::String &line, uint lineno) {
+ debug("test parser: %u: %s\n", lineno, line.c_str());
+}
+
Script::Script(Common::SeekableReadStream &s) {
+ uint lineno = 1;
while (!s.eos()) {
auto line = s.readLine();
- debug("LINE %s\n", line.c_str());
+ parseLine(line, lineno++);
}
}
+void Script::parseLine(const Common::String &line, uint lineno) {
+ if (line.empty())
+ return;
+
+ Parser p(line, lineno);
+ p.skip();
+ if (p.atEnd())
+ return;
+
+ switch (p.peek()) {
+ case '[': {
+ p.next();
+ if (p.maybe("bool]=")) {
+ auto name = p.nextWord();
+ debug("getting bool flag %s\n", name.c_str());
+ } else if (p.maybe("warp]=")) {
+ auto vr = p.nextWord();
+ auto test = p.nextWord();
+ debug("got warp %s %s", vr.c_str(), test.c_str());
+ _currentWarp.reset(new Warp{vr, test, {}});
+ _warps[vr] = _currentWarp;
+ } else if (p.maybe("test]=")) {
+ if (!_currentWarp)
+ error("test without warp");
+ _currentTest.reset(new Test);
+ } else {
+ error("invalid [] directive on line %u: %s", lineno, line.c_str());
+ }
+ } break;
+ default:
+ if (_currentTest)
+ _currentTest->parseLine(line, lineno);
+ else
+ error("invalid directive on line %u: %s\n", lineno, line.c_str());
+ }
+}
+
+Script::~Script() {
+}
+
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index cf5619b8c15..91c3c7642d8 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -22,14 +22,42 @@
#ifndef PHOENIXVR_SCRIPT_H
#define PHOENIXVR_SCRIPT_H
+#include "common/hash-str.h"
+#include "common/hashmap.h"
+#include "common/ptr.h"
+#include "common/str.h"
+
namespace Common {
class SeekableReadStream;
}
namespace PhoenixVR {
class Script {
+ struct Test {
+ void parseLine(const Common::String &line, uint lineno);
+ };
+ using TestPtr = Common::SharedPtr<Test>;
+
+ struct Warp {
+ Common::String vrFile;
+ Common::String testFile;
+ Common::Array<TestPtr> tests;
+
+ void parseLine(const Common::String &line, uint lineno);
+ };
+
+ using WarpPtr = Common::SharedPtr<Warp>;
+ Common::HashMap<Common::String, WarpPtr> _warps;
+ WarpPtr _currentWarp;
+ TestPtr _currentTest;
+
+private:
+ static Common::String strip(const Common::String &str);
+ void parseLine(const Common::String &line, uint lineno);
+
public:
Script(Common::SeekableReadStream &s);
+ ~Script();
};
} // namespace PhoenixVR
Commit: f59be037b01f060d03f55190647a2ce5e8b8b37c
https://github.com/scummvm/scummvm/commit/f59be037b01f060d03f55190647a2ce5e8b8b37c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:21+01:00
Commit Message:
PHOENIXVR: add warp/text hierarchy
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 311b8e3c85e..3c27094f0f8 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -57,8 +57,13 @@ Common::String PhoenixVREngine::getGameId() const {
void PhoenixVREngine::load(const Common::Path &scriptFile) {
Common::File file;
- if (!file.open(scriptFile))
- error("can't open script file");
+ if (!file.open(scriptFile)) {
+ auto pakFile = scriptFile;
+ pakFile = pakFile.removeExtension().append(".pak");
+ file.open(pakFile);
+ }
+ if (!file.isOpen())
+ error("can't open script file %s", scriptFile.toString().c_str());
Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
Common::ScopedPtr<Script> script(new Script(*scriptStream));
}
@@ -66,7 +71,7 @@ void PhoenixVREngine::load(const Common::Path &scriptFile) {
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
- load("script.pak");
+ load("script.lst");
// Set the engine's debugger console
setDebugger(new Console());
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index a3fc48f3bbe..50f26fd4a27 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -12,7 +12,9 @@ class Parser {
uint _pos;
public:
- Parser(const Common::String &line, uint lineno) : _line(line), _lineno(lineno), _pos(0) {}
+ Parser(const Common::String &line, uint lineno) : _line(line), _lineno(lineno), _pos(0) {
+ skip();
+ }
void skip() {
while (_pos < _line.size() && Common::isSpace(_line[_pos]))
@@ -27,7 +29,7 @@ public:
void expect(int ch) {
skip();
if (_pos >= _line.size() || _line[_pos] != ch)
- error("expected '%c'", ch);
+ error("expected '%c' at line %u", ch, _lineno);
++_pos;
skip();
}
@@ -45,7 +47,7 @@ public:
Common::String nextWord() {
skip();
auto begin = _pos;
- while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && !Common::isPunct(_line[_pos]))
+ while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && _line[_pos] != ',')
++_pos;
auto end = _pos;
skip();
@@ -54,11 +56,16 @@ public:
};
} // namespace
-void Script::Test::parseLine(const Common::String &line, uint lineno) {
- debug("test parser: %u: %s\n", lineno, line.c_str());
+void Script::Warp::setText(int idx, const TestPtr &text) {
+ if (idx < -1)
+ error("test id must be >= -1");
+ uint realIdx = idx + 1;
+ if (realIdx + 1 > tests.size())
+ tests.resize(realIdx + 1);
+ tests[realIdx] = text;
}
-Script::Script(Common::SeekableReadStream &s) {
+Script::Script(Common::SeekableReadStream &s) : _pluginContext(false) {
uint lineno = 1;
while (!s.eos()) {
auto line = s.readLine();
@@ -71,7 +78,6 @@ void Script::parseLine(const Common::String &line, uint lineno) {
return;
Parser p(line, lineno);
- p.skip();
if (p.atEnd())
return;
@@ -80,25 +86,44 @@ void Script::parseLine(const Common::String &line, uint lineno) {
p.next();
if (p.maybe("bool]=")) {
auto name = p.nextWord();
- debug("getting bool flag %s\n", name.c_str());
+ debug("bool flag %s", name.c_str());
} else if (p.maybe("warp]=")) {
auto vr = p.nextWord();
auto test = p.nextWord();
debug("got warp %s %s", vr.c_str(), test.c_str());
_currentWarp.reset(new Warp{vr, test, {}});
- _warps[vr] = _currentWarp;
+ _warpsIndex[vr] = _warps.size();
+ _warps.push_back(_currentWarp);
} else if (p.maybe("test]=")) {
if (!_currentWarp)
error("test without warp");
- _currentTest.reset(new Test);
+ auto word = p.nextWord();
+ debug("test %s\n", word.c_str());
+ auto idx = std::atoi(word.c_str());
+ if (!_currentWarp)
+ error("text must have parent wrap section");
+ _currentTest.reset(new Test{idx});
+ _currentWarp->setText(idx, _currentTest);
} else {
error("invalid [] directive on line %u: %s", lineno, line.c_str());
}
- } break;
+ break;
+ }
default:
- if (_currentTest)
- _currentTest->parseLine(line, lineno);
- else
+ if (_currentTest) {
+ if (p.maybe("plugin")) {
+ if (_pluginContext)
+ error("nested plugin context is not allowed");
+ _pluginContext = true;
+ } else if (p.maybe("endplugin")) {
+ if (!_pluginContext)
+ error("endplugin without plugin");
+ _pluginContext = false;
+ } else {
+ error("unhandled script command %s, at line %u", line.c_str(), lineno);
+ }
+
+ } else
error("invalid directive on line %u: %s\n", lineno, line.c_str());
}
}
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 91c3c7642d8..67c25ca0769 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -33,8 +33,14 @@ class SeekableReadStream;
namespace PhoenixVR {
class Script {
+ struct Command {
+ virtual ~Command() = default;
+ virtual void exec() = 0;
+ };
+ using CommandPtr = Common::SharedPtr<Command>;
+
struct Test {
- void parseLine(const Common::String &line, uint lineno);
+ int idx;
};
using TestPtr = Common::SharedPtr<Test>;
@@ -44,12 +50,15 @@ class Script {
Common::Array<TestPtr> tests;
void parseLine(const Common::String &line, uint lineno);
+ void setText(int idx, const TestPtr &text);
};
using WarpPtr = Common::SharedPtr<Warp>;
- Common::HashMap<Common::String, WarpPtr> _warps;
+ Common::HashMap<Common::String, uint> _warpsIndex;
+ Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
TestPtr _currentTest;
+ bool _pluginContext;
private:
static Common::String strip(const Common::String &str);
Commit: e48bb28c5414210ced33655164b2c494da7d183e
https://github.com/scummvm/scummvm/commit/e48bb28c5414210ced33655164b2c494da7d183e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:21+01:00
Commit Message:
PHOENIXVR: add command list, fix warp parser so tst regions are read
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 3c27094f0f8..cd993897225 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -55,7 +55,7 @@ Common::String PhoenixVREngine::getGameId() const {
return _gameDescription->gameId;
}
-void PhoenixVREngine::load(const Common::Path &scriptFile) {
+void PhoenixVREngine::loadScript(const Common::Path &scriptFile) {
Common::File file;
if (!file.open(scriptFile)) {
auto pakFile = scriptFile;
@@ -71,7 +71,7 @@ void PhoenixVREngine::load(const Common::Path &scriptFile) {
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
- load("script.lst");
+ loadScript("script.lst");
// Set the engine's debugger console
setDebugger(new Console());
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 64938ec00ba..82c5d31abfc 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -100,7 +100,7 @@ public:
}
private:
- void load(const Common::Path &scriptFile);
+ void loadScript(const Common::Path &scriptFile);
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 50f26fd4a27..836c6f4f9c2 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -24,13 +24,15 @@ public:
}
bool atEnd() const { return _pos >= _line.size(); }
+
int peek() const { return _pos < _line.size() ? _line[_pos] : 0; }
int next() { return _pos < _line.size() ? _line[_pos++] : 0; }
- void expect(int ch) {
+
+ void expect(int expected) {
skip();
- if (_pos >= _line.size() || _line[_pos] != ch)
- error("expected '%c' at line %u", ch, _lineno);
- ++_pos;
+ auto ch = next();
+ if (ch != expected)
+ error("expected '%c' at line %u, got %c", expected, _lineno, ch);
skip();
}
@@ -47,13 +49,64 @@ public:
Common::String nextWord() {
skip();
auto begin = _pos;
- while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && _line[_pos] != ',')
+ while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && _line[_pos] != ',' && _line[_pos] != '(')
++_pos;
auto end = _pos;
skip();
return _line.substr(begin, end - begin);
}
+
+ Common::String readString() {
+ skip();
+ expect('"');
+ Common::String str;
+ while (_pos < _line.size()) {
+ auto ch = _line[_pos++];
+ if (ch == '"')
+ break;
+ str += ch;
+ }
+ skip();
+ return str;
+ }
+
+ Common::Array<Common::String> readStringList() {
+ Common::Array<Common::String> list;
+ do {
+ list.push_back(readString());
+ } while (peek() == ',');
+ return list;
+ }
+};
+
+struct MultiCD_Set_Transition_Script : public Script::Command {
+ Common::String path;
+
+ MultiCD_Set_Transition_Script(const Common::Array<Common::String> &args) : path(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("MultiCD_Set_Transition_Script %s", path.c_str());
+ }
+};
+
+struct MultiCD_Set_Next_Script : public Script::Command {
+ Common::String filename;
+
+ MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("MultiCD_Set_Next_Script %s", filename.c_str());
+ }
};
+
+struct End : public Script::Command {
+ End() {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ ctx.running = false;
+ }
+};
+
+#define PLUGIN_LIST(E) \
+ E(MultiCD_Set_Transition_Script) \
+ E(MultiCD_Set_Next_Script)
} // namespace
void Script::Warp::setText(int idx, const TestPtr &text) {
@@ -89,6 +142,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
debug("bool flag %s", name.c_str());
} else if (p.maybe("warp]=")) {
auto vr = p.nextWord();
+ p.expect(',');
auto test = p.nextWord();
debug("got warp %s %s", vr.c_str(), test.c_str());
_currentWarp.reset(new Warp{vr, test, {}});
@@ -98,11 +152,11 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (!_currentWarp)
error("test without warp");
auto word = p.nextWord();
- debug("test %s\n", word.c_str());
+ debug("test %s", word.c_str());
auto idx = std::atoi(word.c_str());
if (!_currentWarp)
error("text must have parent wrap section");
- _currentTest.reset(new Test{idx});
+ _currentTest.reset(new Test{idx, {}});
_currentWarp->setText(idx, _currentTest);
} else {
error("invalid [] directive on line %u: %s", lineno, line.c_str());
@@ -111,7 +165,10 @@ void Script::parseLine(const Common::String &line, uint lineno) {
}
default:
if (_currentTest) {
- if (p.maybe("plugin")) {
+ auto &commands = _currentTest->commands;
+ if (p.maybe("end")) {
+ commands.emplace_back(new End());
+ } else if (p.maybe("plugin")) {
if (_pluginContext)
error("nested plugin context is not allowed");
_pluginContext = true;
@@ -120,7 +177,20 @@ void Script::parseLine(const Common::String &line, uint lineno) {
error("endplugin without plugin");
_pluginContext = false;
} else {
- error("unhandled script command %s, at line %u", line.c_str(), lineno);
+ if (_pluginContext) {
+ auto cmd = p.nextWord();
+ p.expect('(');
+ auto args = p.readStringList();
+ p.expect(')');
+#define ADD_PLUGIN(NAME) \
+ if (cmd.equalsIgnoreCase(#NAME)) { \
+ commands.emplace_back(new NAME(args)); \
+ } else
+
+ PLUGIN_LIST(ADD_PLUGIN)
+ error("unimplemented plugin command %s", cmd.c_str());
+ } else
+ error("unhandled script command %s, at line %u", line.c_str(), lineno);
}
} else
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 67c25ca0769..689c5344350 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -32,15 +32,24 @@ class SeekableReadStream;
}
namespace PhoenixVR {
+
+class PhoenixVREngine;
+
class Script {
+public:
+ struct ExecutionContext {
+ PhoenixVREngine *engine;
+ bool running = true;
+ };
struct Command {
virtual ~Command() = default;
- virtual void exec() = 0;
+ virtual void exec(ExecutionContext &ctx) const = 0;
};
using CommandPtr = Common::SharedPtr<Command>;
struct Test {
int idx;
+ Common::Array<CommandPtr> commands;
};
using TestPtr = Common::SharedPtr<Test>;
@@ -54,6 +63,8 @@ class Script {
};
using WarpPtr = Common::SharedPtr<Warp>;
+
+private:
Common::HashMap<Common::String, uint> _warpsIndex;
Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
Commit: baaab2270729a1ff00e7d0e87c46f26278ccc4a0
https://github.com/scummvm/scummvm/commit/baaab2270729a1ff00e7d0e87c46f26278ccc4a0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:21+01:00
Commit Message:
PHOENIXVR: more parse stubs
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index cd993897225..f54d554d555 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -55,17 +55,55 @@ Common::String PhoenixVREngine::getGameId() const {
return _gameDescription->gameId;
}
+Common::String PhoenixVREngine::resolvePath(const Common::String &path) {
+ auto resolved = removeDrive(path);
+ resolved.replace('\\', '/');
+ return resolved;
+}
+
+Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
+ if (path.size() < 2 || path[1] != ':')
+ return path;
+ else
+ return path.substr(2);
+}
+
+void PhoenixVREngine::setNextScript(const Common::String &path) {
+ _nextScript = resolvePath(path);
+ debug("setNextScript %s", _nextScript.c_str());
+}
+
+void PhoenixVREngine::setCursorDefault(uint idx, const Common::String &path) {
+ debug("setCursorDefault %u: %s", idx, path.c_str());
+}
+
+void PhoenixVREngine::runScript(Common::SeekableReadStream &scriptSource) {
+ Script script(scriptSource);
+ Script::ExecutionContext ctx{this, true};
+ script.exec(ctx);
+}
+
void PhoenixVREngine::loadScript(const Common::Path &scriptFile) {
- Common::File file;
- if (!file.open(scriptFile)) {
- auto pakFile = scriptFile;
- pakFile = pakFile.removeExtension().append(".pak");
- file.open(pakFile);
+ Common::String nextScript = scriptFile.toString();
+ while (!nextScript.empty()) {
+ Common::File file;
+ Common::Path nextPath(nextScript);
+ if (file.open(nextPath)) {
+ runScript(file);
+ } else {
+ auto pakFile = nextPath;
+ pakFile = pakFile.removeExtension().append(".pak");
+ file.open(pakFile);
+ }
+ if (!file.isOpen())
+ error("can't open script file %s", scriptFile.toString().c_str());
+ Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
+ runScript(*scriptStream);
+ if (!_nextScript.empty()) {
+ nextScript = removeDrive(_nextScript);
+ } else
+ nextScript.clear();
}
- if (!file.isOpen())
- error("can't open script file %s", scriptFile.toString().c_str());
- Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
- Common::ScopedPtr<Script> script(new Script(*scriptStream));
}
Common::Error PhoenixVREngine::run() {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 82c5d31abfc..1dab480658d 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -99,8 +99,18 @@ public:
return syncGame(s);
}
+ // Script API
+ void setNextScript(const Common::String &path);
+ void setCursorDefault(uint idx, const Common::String &path);
+
private:
void loadScript(const Common::Path &scriptFile);
+ void runScript(Common::SeekableReadStream &script);
+ static Common::String removeDrive(const Common::String &path);
+ static Common::String resolvePath(const Common::String &path);
+
+private:
+ Common::String _nextScript;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 836c6f4f9c2..a1b10698a43 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -2,6 +2,7 @@
#include "common/debug.h"
#include "common/stream.h"
#include "common/textconsole.h"
+#include "phoenixvr/phoenixvr.h"
namespace PhoenixVR {
@@ -32,7 +33,7 @@ public:
skip();
auto ch = next();
if (ch != expected)
- error("expected '%c' at line %u, got %c", expected, _lineno, ch);
+ error("expected '%c' at line %u, got %c in %s", expected, _lineno, ch, _line.c_str());
skip();
}
@@ -46,17 +47,27 @@ public:
return yes;
}
+ Common::String nextArg() {
+ skip();
+ auto begin = _pos;
+ while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && _line[_pos] != ',' && _line[_pos] != ')')
+ ++_pos;
+ auto end = _pos;
+ skip();
+ return _line.substr(begin, end - begin);
+ }
+
Common::String nextWord() {
skip();
auto begin = _pos;
- while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && _line[_pos] != ',' && _line[_pos] != '(')
+ while (_pos < _line.size() && !Common::isSpace(_line[_pos]) && _line[_pos] != ',' && _line[_pos] != '(' && _line[_pos] != '=' && _line[_pos] != ')')
++_pos;
auto end = _pos;
skip();
return _line.substr(begin, end - begin);
}
- Common::String readString() {
+ Common::String nextString() {
skip();
expect('"');
Common::String str;
@@ -73,8 +84,14 @@ public:
Common::Array<Common::String> readStringList() {
Common::Array<Common::String> list;
do {
- list.push_back(readString());
- } while (peek() == ',');
+ if (peek() == '"')
+ list.push_back(nextString());
+ else {
+ list.push_back(nextArg());
+ }
+ if (peek() == ',')
+ next();
+ } while (peek() != ')');
return list;
}
};
@@ -94,6 +111,49 @@ struct MultiCD_Set_Next_Script : public Script::Command {
MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("MultiCD_Set_Next_Script %s", filename.c_str());
+ ctx.engine->setNextScript(filename);
+ }
+};
+
+struct LoadSave_Enter_Script : public Script::Command {
+ Common::String reloading, notReloading;
+
+ LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
+ }
+};
+
+struct Play_Movie : public Script::Command {
+ Common::String filename;
+
+ Play_Movie(const Common::Array<Common::String> &args) : filename(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_Movie %s", filename.c_str());
+ }
+};
+
+struct While : public Script::Command {
+ double seconds;
+
+ While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("while %g", seconds);
+ }
+};
+
+struct Cmp : public Script::Command {
+ Common::String var;
+ Common::String negativeVar;
+ Common::String arg0;
+ Common::String op;
+ Common::String arg1;
+
+ Cmp(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]),
+ arg0(args[2]), op(args[3]), arg1(args[4]) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("cmp");
}
};
@@ -105,10 +165,32 @@ struct End : public Script::Command {
};
#define PLUGIN_LIST(E) \
+ E(Cmp) \
+ E(LoadSave_Enter_Script) \
E(MultiCD_Set_Transition_Script) \
- E(MultiCD_Set_Next_Script)
+ E(MultiCD_Set_Next_Script) \
+ E(Play_Movie) \
+ E(While) \
+ /* */
+
+#define ADD_PLUGIN(NAME) \
+ if (cmd.equalsIgnoreCase(#NAME)) \
+ return Script::CommandPtr(new NAME(args));
+
+Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args) {
+ PLUGIN_LIST(ADD_PLUGIN)
+ error("unhandled plugin command %s", cmd.c_str());
+}
} // namespace
+void Script::Test::exec(ExecutionContext &ctx) const {
+ for (auto &cmd : commands) {
+ if (!ctx.running)
+ break;
+ cmd->exec(ctx);
+ }
+}
+
void Script::Warp::setText(int idx, const TestPtr &text) {
if (idx < -1)
error("test id must be >= -1");
@@ -118,6 +200,10 @@ void Script::Warp::setText(int idx, const TestPtr &text) {
tests[realIdx] = text;
}
+const Script::TestPtr &Script::Warp::getTest(int idx) const {
+ return tests[idx + 1];
+}
+
Script::Script(Common::SeekableReadStream &s) : _pluginContext(false) {
uint lineno = 1;
while (!s.eos()) {
@@ -134,6 +220,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (p.atEnd())
return;
+ debug("line %s at %u", line.c_str(), lineno);
switch (p.peek()) {
case '[': {
p.next();
@@ -166,33 +253,72 @@ void Script::parseLine(const Common::String &line, uint lineno) {
default:
if (_currentTest) {
auto &commands = _currentTest->commands;
- if (p.maybe("end")) {
- commands.emplace_back(new End());
- } else if (p.maybe("plugin")) {
+ if (p.maybe("plugin")) {
if (_pluginContext)
- error("nested plugin context is not allowed");
+ error("nested plugin context is not allowed, line: %u", lineno);
_pluginContext = true;
} else if (p.maybe("endplugin")) {
if (!_pluginContext)
error("endplugin without plugin");
_pluginContext = false;
+ } else if (p.maybe("end")) {
+ commands.emplace_back(new End());
+ } else if (p.maybe("setcursordefault")) {
+ auto idx = atoi(p.nextWord().c_str());
+ p.expect(',');
+ auto fname = p.nextWord();
+ debug("setcursordefault %d: %s", idx, fname.c_str());
+ } else if (p.maybe("lockkey")) {
+ auto idx = atoi(p.nextWord().c_str());
+ p.expect(',');
+ auto fname = p.nextWord();
+ debug("lockkey %d: %s", idx, fname.c_str());
+ } else if (p.maybe("resetlockkey")) {
+ debug("resetlockkey");
+ } else if (p.maybe("setzoom=")) {
+ auto zoom = atoi(p.nextWord().c_str());
+ debug("setzoom %d\n", zoom);
+ } else if (p.maybe("anglexmax=")) {
+ auto xmax = atoi(p.nextWord().c_str());
+ debug("anglexmax %d", xmax);
+ } else if (p.maybe("ifand=")) {
+ auto var = p.nextWord();
+ debug("ifand %s\n", var.c_str());
+ } else if (p.maybe("gotowarp")) {
+ auto id = p.nextWord();
+ debug("gotowarp %s", id.c_str());
+ } else if (p.maybe("playsound")) {
+ auto sound = p.nextWord();
+ p.expect(',');
+ auto arg0 = p.nextWord();
+ p.expect(',');
+ auto arg1 = p.nextWord();
+ debug("playsound %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str());
+ } else if (p.maybe("stopsound")) {
+ auto sound = p.nextWord();
+ debug("stopsound %s", sound.c_str());
+ } else if (p.maybe("setcursor")) {
+ auto image = p.nextWord();
+ p.expect(',');
+ auto warp = p.nextWord();
+ p.expect(',');
+ auto idx = p.nextWord();
+ debug("setcursor %s %s %s", image.c_str(), warp.c_str(), idx.c_str());
+ } else if (p.maybe("set")) {
+ auto var = p.nextWord();
+ p.expect('=');
+ auto value = p.nextWord();
+ debug("set %s = %s", var.c_str(), value.c_str());
} else {
if (_pluginContext) {
auto cmd = p.nextWord();
p.expect('(');
auto args = p.readStringList();
p.expect(')');
-#define ADD_PLUGIN(NAME) \
- if (cmd.equalsIgnoreCase(#NAME)) { \
- commands.emplace_back(new NAME(args)); \
- } else
-
- PLUGIN_LIST(ADD_PLUGIN)
- error("unimplemented plugin command %s", cmd.c_str());
+ commands.push_back(createCommand(cmd, args));
} else
error("unhandled script command %s, at line %u", line.c_str(), lineno);
}
-
} else
error("invalid directive on line %u: %s\n", lineno, line.c_str());
}
@@ -201,4 +327,14 @@ void Script::parseLine(const Common::String &line, uint lineno) {
Script::~Script() {
}
+void Script::exec(ExecutionContext &ctx) const {
+ for (auto &warp : _warps) {
+ if (!ctx.running)
+ break;
+ debug("warp %s", warp->vrFile.c_str());
+ auto &test = warp->getDefaultTest();
+ test->exec(ctx);
+ }
+}
+
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 689c5344350..6273c9ba014 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -39,7 +39,7 @@ class Script {
public:
struct ExecutionContext {
PhoenixVREngine *engine;
- bool running = true;
+ bool running;
};
struct Command {
virtual ~Command() = default;
@@ -50,6 +50,7 @@ public:
struct Test {
int idx;
Common::Array<CommandPtr> commands;
+ void exec(ExecutionContext &ctx) const;
};
using TestPtr = Common::SharedPtr<Test>;
@@ -60,6 +61,10 @@ public:
void parseLine(const Common::String &line, uint lineno);
void setText(int idx, const TestPtr &text);
+ const TestPtr &getTest(int idx) const;
+ const TestPtr &getDefaultTest() const {
+ return getTest(-1);
+ }
};
using WarpPtr = Common::SharedPtr<Warp>;
@@ -78,6 +83,8 @@ private:
public:
Script(Common::SeekableReadStream &s);
~Script();
+
+ void exec(ExecutionContext &ctx) const;
};
} // namespace PhoenixVR
Commit: c8d58900e23f4eb9416fac2e0043e1fadd2cebba
https://github.com/scummvm/scummvm/commit/c8d58900e23f4eb9416fac2e0043e1fadd2cebba
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:22+01:00
Commit Message:
PHOENIXVR: move parseCommand to the parser
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index a1b10698a43..63347b8f570 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -7,13 +7,100 @@
namespace PhoenixVR {
namespace {
+struct MultiCD_Set_Transition_Script : public Script::Command {
+ Common::String path;
+
+ MultiCD_Set_Transition_Script(const Common::Array<Common::String> &args) : path(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("MultiCD_Set_Transition_Script %s", path.c_str());
+ }
+};
+
+struct MultiCD_Set_Next_Script : public Script::Command {
+ Common::String filename;
+
+ MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("MultiCD_Set_Next_Script %s", filename.c_str());
+ ctx.engine->setNextScript(filename);
+ }
+};
+
+struct LoadSave_Enter_Script : public Script::Command {
+ Common::String reloading, notReloading;
+
+ LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
+ }
+};
+
+struct Play_Movie : public Script::Command {
+ Common::String filename;
+
+ Play_Movie(const Common::Array<Common::String> &args) : filename(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_Movie %s", filename.c_str());
+ }
+};
+
+struct While : public Script::Command {
+ double seconds;
+
+ While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("while %g", seconds);
+ }
+};
+
+struct Cmp : public Script::Command {
+ Common::String var;
+ Common::String negativeVar;
+ Common::String arg0;
+ Common::String op;
+ Common::String arg1;
+
+ Cmp(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]),
+ arg0(args[2]), op(args[3]), arg1(args[4]) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("cmp");
+ }
+};
+
+struct End : public Script::Command {
+ End() {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ ctx.running = false;
+ }
+};
+
+#define PLUGIN_LIST(E) \
+ E(Cmp) \
+ E(LoadSave_Enter_Script) \
+ E(MultiCD_Set_Transition_Script) \
+ E(MultiCD_Set_Next_Script) \
+ E(Play_Movie) \
+ E(While) \
+ /* */
+
+#define ADD_PLUGIN(NAME) \
+ if (cmd.equalsIgnoreCase(#NAME)) \
+ return Script::CommandPtr(new NAME(args));
+
+Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args) {
+ PLUGIN_LIST(ADD_PLUGIN)
+ error("unhandled plugin command %s", cmd.c_str());
+}
+
class Parser {
const Common::String &_line;
uint _lineno;
uint _pos;
+ bool &_pluginContext;
public:
- Parser(const Common::String &line, uint lineno) : _line(line), _lineno(lineno), _pos(0) {
+ Parser(const Common::String &line, uint lineno, bool &pluginContext) : _line(line), _lineno(lineno), _pos(0), _pluginContext(pluginContext) {
skip();
}
@@ -94,93 +181,79 @@ public:
} while (peek() != ')');
return list;
}
-};
-
-struct MultiCD_Set_Transition_Script : public Script::Command {
- Common::String path;
-
- MultiCD_Set_Transition_Script(const Common::Array<Common::String> &args) : path(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("MultiCD_Set_Transition_Script %s", path.c_str());
- }
-};
-
-struct MultiCD_Set_Next_Script : public Script::Command {
- Common::String filename;
-
- MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("MultiCD_Set_Next_Script %s", filename.c_str());
- ctx.engine->setNextScript(filename);
- }
-};
-
-struct LoadSave_Enter_Script : public Script::Command {
- Common::String reloading, notReloading;
-
- LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
- }
-};
-
-struct Play_Movie : public Script::Command {
- Common::String filename;
-
- Play_Movie(const Common::Array<Common::String> &args) : filename(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_Movie %s", filename.c_str());
- }
-};
-struct While : public Script::Command {
- double seconds;
-
- While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("while %g", seconds);
- }
-};
-
-struct Cmp : public Script::Command {
- Common::String var;
- Common::String negativeVar;
- Common::String arg0;
- Common::String op;
- Common::String arg1;
-
- Cmp(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]),
- arg0(args[2]), op(args[3]), arg1(args[4]) {}
-
- void exec(Script::ExecutionContext &ctx) const override {
- debug("cmp");
- }
-};
-
-struct End : public Script::Command {
- End() {}
- void exec(Script::ExecutionContext &ctx) const override {
- ctx.running = false;
- }
+ Script::CommandPtr parseCommand() {
+ using CommandPtr = Script::CommandPtr;
+ if (maybe("plugin")) {
+ if (_pluginContext)
+ error("nested plugin context is not allowed, line: %u", _lineno);
+ _pluginContext = true;
+ } else if (maybe("endplugin")) {
+ if (!_pluginContext)
+ error("endplugin without plugin");
+ _pluginContext = false;
+ } else if (maybe("end")) {
+ return CommandPtr{new End()};
+ } else if (maybe("setcursordefault")) {
+ auto idx = atoi(nextWord().c_str());
+ expect(',');
+ auto fname = nextWord();
+ debug("setcursordefault %d: %s", idx, fname.c_str());
+ } else if (maybe("lockkey")) {
+ auto idx = atoi(nextWord().c_str());
+ expect(',');
+ auto fname = nextWord();
+ debug("lockkey %d: %s", idx, fname.c_str());
+ } else if (maybe("resetlockkey")) {
+ debug("resetlockkey");
+ } else if (maybe("setzoom=")) {
+ auto zoom = atoi(nextWord().c_str());
+ debug("setzoom %d\n", zoom);
+ } else if (maybe("anglexmax=")) {
+ auto xmax = atoi(nextWord().c_str());
+ debug("anglexmax %d", xmax);
+ } else if (maybe("ifand=")) {
+ auto var = nextWord();
+ debug("ifand %s\n", var.c_str());
+ } else if (maybe("gotowarp")) {
+ auto id = nextWord();
+ debug("gotowarp %s", id.c_str());
+ } else if (maybe("playsound")) {
+ auto sound = nextWord();
+ expect(',');
+ auto arg0 = nextWord();
+ expect(',');
+ auto arg1 = nextWord();
+ debug("playsound %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str());
+ } else if (maybe("stopsound")) {
+ auto sound = nextWord();
+ debug("stopsound %s", sound.c_str());
+ } else if (maybe("setcursor")) {
+ auto image = nextWord();
+ expect(',');
+ auto warp = nextWord();
+ expect(',');
+ auto idx = nextWord();
+ debug("setcursor %s %s %s", image.c_str(), warp.c_str(), idx.c_str());
+ } else if (maybe("set")) {
+ auto var = nextWord();
+ expect('=');
+ auto value = nextWord();
+ debug("set %s = %s", var.c_str(), value.c_str());
+ } else {
+ if (_pluginContext) {
+ auto cmd = nextWord();
+ expect('(');
+ auto args = readStringList();
+ expect(')');
+ return createCommand(cmd, args);
+ } else
+ error("unhandled script command %s, at line %u", _line.c_str(), _lineno);
+ }
+ return {};
+ };
};
-#define PLUGIN_LIST(E) \
- E(Cmp) \
- E(LoadSave_Enter_Script) \
- E(MultiCD_Set_Transition_Script) \
- E(MultiCD_Set_Next_Script) \
- E(Play_Movie) \
- E(While) \
- /* */
-
-#define ADD_PLUGIN(NAME) \
- if (cmd.equalsIgnoreCase(#NAME)) \
- return Script::CommandPtr(new NAME(args));
-
-Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args) {
- PLUGIN_LIST(ADD_PLUGIN)
- error("unhandled plugin command %s", cmd.c_str());
-}
} // namespace
void Script::Test::exec(ExecutionContext &ctx) const {
@@ -216,11 +289,12 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (line.empty())
return;
- Parser p(line, lineno);
+ Parser p(line, lineno, _pluginContext);
if (p.atEnd())
return;
debug("line %s at %u", line.c_str(), lineno);
+
switch (p.peek()) {
case '[': {
p.next();
@@ -253,72 +327,9 @@ void Script::parseLine(const Common::String &line, uint lineno) {
default:
if (_currentTest) {
auto &commands = _currentTest->commands;
- if (p.maybe("plugin")) {
- if (_pluginContext)
- error("nested plugin context is not allowed, line: %u", lineno);
- _pluginContext = true;
- } else if (p.maybe("endplugin")) {
- if (!_pluginContext)
- error("endplugin without plugin");
- _pluginContext = false;
- } else if (p.maybe("end")) {
- commands.emplace_back(new End());
- } else if (p.maybe("setcursordefault")) {
- auto idx = atoi(p.nextWord().c_str());
- p.expect(',');
- auto fname = p.nextWord();
- debug("setcursordefault %d: %s", idx, fname.c_str());
- } else if (p.maybe("lockkey")) {
- auto idx = atoi(p.nextWord().c_str());
- p.expect(',');
- auto fname = p.nextWord();
- debug("lockkey %d: %s", idx, fname.c_str());
- } else if (p.maybe("resetlockkey")) {
- debug("resetlockkey");
- } else if (p.maybe("setzoom=")) {
- auto zoom = atoi(p.nextWord().c_str());
- debug("setzoom %d\n", zoom);
- } else if (p.maybe("anglexmax=")) {
- auto xmax = atoi(p.nextWord().c_str());
- debug("anglexmax %d", xmax);
- } else if (p.maybe("ifand=")) {
- auto var = p.nextWord();
- debug("ifand %s\n", var.c_str());
- } else if (p.maybe("gotowarp")) {
- auto id = p.nextWord();
- debug("gotowarp %s", id.c_str());
- } else if (p.maybe("playsound")) {
- auto sound = p.nextWord();
- p.expect(',');
- auto arg0 = p.nextWord();
- p.expect(',');
- auto arg1 = p.nextWord();
- debug("playsound %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str());
- } else if (p.maybe("stopsound")) {
- auto sound = p.nextWord();
- debug("stopsound %s", sound.c_str());
- } else if (p.maybe("setcursor")) {
- auto image = p.nextWord();
- p.expect(',');
- auto warp = p.nextWord();
- p.expect(',');
- auto idx = p.nextWord();
- debug("setcursor %s %s %s", image.c_str(), warp.c_str(), idx.c_str());
- } else if (p.maybe("set")) {
- auto var = p.nextWord();
- p.expect('=');
- auto value = p.nextWord();
- debug("set %s = %s", var.c_str(), value.c_str());
- } else {
- if (_pluginContext) {
- auto cmd = p.nextWord();
- p.expect('(');
- auto args = p.readStringList();
- p.expect(')');
- commands.push_back(createCommand(cmd, args));
- } else
- error("unhandled script command %s, at line %u", line.c_str(), lineno);
- }
+ auto cmd = p.parseCommand();
+ if (cmd)
+ commands.push_back(Common::move(cmd));
} else
error("invalid directive on line %u: %s\n", lineno, line.c_str());
}
Commit: 20952937777c579e2d2fe20a26d14a366ff12652
https://github.com/scummvm/scummvm/commit/20952937777c579e2d2fe20a26d14a366ff12652
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:22+01:00
Commit Message:
PHOENIXVR: add Scope
Changed paths:
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 63347b8f570..e714f040083 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -68,6 +68,12 @@ struct Cmp : public Script::Command {
}
};
+struct Branch : public Script::Command {
+ Common::Array<Common::String> vars;
+ Script::CommandPtr target;
+ Branch(const Common::Array<Common::String> &args) : vars(args) {}
+};
+
struct End : public Script::Command {
End() {}
void exec(Script::ExecutionContext &ctx) const override {
@@ -256,7 +262,7 @@ public:
} // namespace
-void Script::Test::exec(ExecutionContext &ctx) const {
+void Script::Scope::exec(ExecutionContext &ctx) const {
for (auto &cmd : commands) {
if (!ctx.running)
break;
@@ -326,7 +332,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
}
default:
if (_currentTest) {
- auto &commands = _currentTest->commands;
+ auto &commands = _currentTest->scope.commands;
auto cmd = p.parseCommand();
if (cmd)
commands.push_back(Common::move(cmd));
@@ -344,7 +350,7 @@ void Script::exec(ExecutionContext &ctx) const {
break;
debug("warp %s", warp->vrFile.c_str());
auto &test = warp->getDefaultTest();
- test->exec(ctx);
+ test->scope.exec(ctx);
}
}
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 6273c9ba014..759c62ae48b 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -47,11 +47,16 @@ public:
};
using CommandPtr = Common::SharedPtr<Command>;
- struct Test {
- int idx;
+ struct Scope : public Script::Command {
Common::Array<CommandPtr> commands;
void exec(ExecutionContext &ctx) const;
};
+ using ScopePtr = Common::SharedPtr<Scope>;
+
+ struct Test {
+ int idx;
+ Scope scope;
+ };
using TestPtr = Common::SharedPtr<Test>;
struct Warp {
Commit: 3738b605f47930254bb6b35994b393886bf3b2fa
https://github.com/scummvm/scummvm/commit/3738b605f47930254bb6b35994b393886bf3b2fa
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:22+01:00
Commit Message:
PHOENIXVR: enought stubs to parse script1
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index e714f040083..26782a734fa 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -44,6 +44,41 @@ struct Play_Movie : public Script::Command {
}
};
+struct Play_AnimBloc : public Script::Command {
+ Common::String name;
+ Common::String block;
+ int start;
+ int stop;
+
+ Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), block(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_AnimBloc %s %s %d-%d", name.c_str(), block.c_str(), start, stop);
+ }
+};
+
+struct Play_AnimBloc_Number : public Script::Command {
+ Common::String name1, name2;
+ Common::String block;
+ int start;
+ int stop;
+
+ Play_AnimBloc_Number(const Common::Array<Common::String> &args) : name1(args[0]), name2(args[1]),
+ block(args[2]), start(atoi(args[3].c_str())), stop(atoi(args[4].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_AnimBloc_Number %s %s %s %d-%d", name1.c_str(), name2.c_str(), block.c_str(), start, stop);
+ }
+};
+
+struct Until : public Script::Command {
+ Common::String block;
+ int frame;
+
+ Until(const Common::Array<Common::String> &args) : block(args[0]), frame(atoi(args[1].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("until %s %d", block.c_str(), frame);
+ }
+};
+
struct While : public Script::Command {
double seconds;
@@ -53,6 +88,63 @@ struct While : public Script::Command {
}
};
+struct StartTimer : public Script::Command {
+ double seconds;
+
+ StartTimer(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("starttimer %g", seconds);
+ }
+};
+
+struct PauseTimer : public Script::Command {
+ int arg1, arg2;
+
+ PauseTimer(const Common::Array<Common::String> &args) : arg1(atoi(args[0].c_str())), arg2(atoi(args[1].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("pause_timer %d %d", arg1, arg2);
+ }
+};
+
+struct KillTimer : public Script::Command {
+ KillTimer(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("killtimer");
+ }
+};
+
+struct ChangeCurseur : public Script::Command {
+ int cursor;
+ ChangeCurseur(const Common::Array<Common::String> &args) : cursor(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("changecurseur %d", cursor);
+ }
+};
+
+struct Add : public Script::Command {
+ Common::String dstVar;
+ Common::String srcVar;
+ int addend;
+
+ Add(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("add %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
+ }
+};
+
+struct Sub : public Script::Command {
+ Common::String dstVar;
+ Common::String srcVar;
+ int addend;
+
+ Sub(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("sub %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
+ }
+};
+
struct Cmp : public Script::Command {
Common::String var;
Common::String negativeVar;
@@ -68,12 +160,109 @@ struct Cmp : public Script::Command {
}
};
+struct LoadSave_Init_Slots : public Script::Command {
+ int slots;
+
+ LoadSave_Init_Slots(const Common::Array<Common::String> &args) : slots(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Init_Slots %d", slots);
+ }
+};
+
+struct LoadSave_Draw_Slot : public Script::Command {
+ int slot;
+ int arg0;
+ int arg1;
+ int arg2;
+
+ LoadSave_Draw_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())),
+ arg0(atoi(args[1].c_str())),
+ arg1(atoi(args[2].c_str())),
+ arg2(atoi(args[3].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Draw_Slot %d %d %d %d", slot, arg0, arg1, arg2);
+ }
+};
+
+struct LoadSave_Test_Slot : public Script::Command {
+ int slot;
+ Common::String show;
+ Common::String hide;
+
+ LoadSave_Test_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())), show(args[1]), hide(args[2]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
+ }
+};
+
+struct LoadSave_Capture_Context : public Script::Command {
+ LoadSave_Capture_Context(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Capture_Context");
+ }
+};
+
+struct LoadSave_Context_Restored : public Script::Command {
+ Common::String progress;
+ Common::String done;
+
+ LoadSave_Context_Restored(const Common::Array<Common::String> &args) : progress(args[0]), done(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Context_Restored %s %s", progress.c_str(), done.c_str());
+ }
+};
+
+struct LoadSave_Load : public Script::Command {
+ int slot;
+
+ LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Load %d", slot);
+ }
+};
+
+struct LoadSave_Save : public Script::Command {
+ int slot;
+
+ LoadSave_Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Save %d", slot);
+ }
+};
+
+struct LoadSave_Set_Context_Label : public Script::Command {
+ Common::String label;
+
+ LoadSave_Set_Context_Label(const Common::Array<Common::String> &args) : label(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Set_Context_Label %s", label.c_str());
+ }
+};
+
struct Branch : public Script::Command {
Common::Array<Common::String> vars;
Script::CommandPtr target;
Branch(const Common::Array<Common::String> &args) : vars(args) {}
};
+struct RolloverMalette : public Script::Command {
+ int arg;
+
+ RolloverMalette(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("RolloverMalette %d", arg);
+ }
+};
+
+struct RolloverSecretaire : public Script::Command {
+ int arg;
+
+ RolloverSecretaire(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("RolloverSecretaire %d", arg);
+ }
+};
+
struct End : public Script::Command {
End() {}
void exec(Script::ExecutionContext &ctx) const override {
@@ -82,11 +271,30 @@ struct End : public Script::Command {
};
#define PLUGIN_LIST(E) \
+ E(Add) \
+ E(ChangeCurseur) \
E(Cmp) \
+ E(KillTimer) \
+ E(LoadSave_Capture_Context) \
+ E(LoadSave_Context_Restored) \
E(LoadSave_Enter_Script) \
+ E(LoadSave_Init_Slots) \
+ E(LoadSave_Load) \
+ E(LoadSave_Save) \
+ E(LoadSave_Set_Context_Label) \
+ E(LoadSave_Draw_Slot) \
+ E(LoadSave_Test_Slot) \
E(MultiCD_Set_Transition_Script) \
E(MultiCD_Set_Next_Script) \
+ E(PauseTimer) \
+ E(Play_AnimBloc) \
+ E(Play_AnimBloc_Number) \
E(Play_Movie) \
+ E(RolloverMalette) \
+ E(RolloverSecretaire) \
+ E(StartTimer) \
+ E(Sub) \
+ E(Until) \
E(While) \
/* */
@@ -103,10 +311,9 @@ class Parser {
const Common::String &_line;
uint _lineno;
uint _pos;
- bool &_pluginContext;
public:
- Parser(const Common::String &line, uint lineno, bool &pluginContext) : _line(line), _lineno(lineno), _pos(0), _pluginContext(pluginContext) {
+ Parser(const Common::String &line, uint lineno, bool &pluginContext) : _line(line), _lineno(lineno), _pos(0) {
skip();
}
@@ -190,15 +397,7 @@ public:
Script::CommandPtr parseCommand() {
using CommandPtr = Script::CommandPtr;
- if (maybe("plugin")) {
- if (_pluginContext)
- error("nested plugin context is not allowed, line: %u", _lineno);
- _pluginContext = true;
- } else if (maybe("endplugin")) {
- if (!_pluginContext)
- error("endplugin without plugin");
- _pluginContext = false;
- } else if (maybe("end")) {
+ if (maybe("end")) {
return CommandPtr{new End()};
} else if (maybe("setcursordefault")) {
auto idx = atoi(nextWord().c_str());
@@ -214,16 +413,28 @@ public:
debug("resetlockkey");
} else if (maybe("setzoom=")) {
auto zoom = atoi(nextWord().c_str());
- debug("setzoom %d\n", zoom);
+ debug("setzoom %d", zoom);
} else if (maybe("anglexmax=")) {
auto xmax = atoi(nextWord().c_str());
debug("anglexmax %d", xmax);
} else if (maybe("ifand=")) {
auto var = nextWord();
- debug("ifand %s\n", var.c_str());
+ debug("ifand %s", var.c_str());
+ } else if (maybe("ifor=")) {
+ auto var = nextWord();
+ debug("ifor %s", var.c_str());
} else if (maybe("gotowarp")) {
auto id = nextWord();
debug("gotowarp %s", id.c_str());
+ } else if (maybe("playsound3d")) {
+ auto sound = nextWord();
+ expect(',');
+ auto arg0 = nextWord();
+ expect(',');
+ auto arg1 = nextWord();
+ expect(',');
+ auto arg2 = nextWord();
+ debug("playsound3d %s %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str(), arg2.c_str());
} else if (maybe("playsound")) {
auto sound = nextWord();
expect(',');
@@ -231,6 +442,9 @@ public:
expect(',');
auto arg1 = nextWord();
debug("playsound %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str());
+ } else if (maybe("stopsound3d")) {
+ auto sound = nextWord();
+ debug("stopsound3d %s", sound.c_str());
} else if (maybe("stopsound")) {
auto sound = nextWord();
debug("stopsound %s", sound.c_str());
@@ -246,15 +460,6 @@ public:
expect('=');
auto value = nextWord();
debug("set %s = %s", var.c_str(), value.c_str());
- } else {
- if (_pluginContext) {
- auto cmd = nextWord();
- expect('(');
- auto args = readStringList();
- expect(')');
- return createCommand(cmd, args);
- } else
- error("unhandled script command %s, at line %u", _line.c_str(), _lineno);
}
return {};
};
@@ -299,7 +504,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (p.atEnd())
return;
- debug("line %s at %u", line.c_str(), lineno);
+ // debug("line %u: %s", lineno, line.c_str());
switch (p.peek()) {
case '[': {
@@ -332,12 +537,32 @@ void Script::parseLine(const Common::String &line, uint lineno) {
}
default:
if (_currentTest) {
- auto &commands = _currentTest->scope.commands;
- auto cmd = p.parseCommand();
- if (cmd)
- commands.push_back(Common::move(cmd));
+ if (p.maybe("plugin")) {
+ if (_pluginContext)
+ error("nested plugin context is not allowed, line: %u", lineno);
+ _pluginContext = true;
+ } else if (p.maybe("endplugin")) {
+ if (!_pluginContext)
+ error("endplugin without plugin");
+ _pluginContext = false;
+ } else {
+ auto &commands = _currentTest->scope.commands;
+ if (_pluginContext) {
+ auto name = p.nextWord();
+ p.expect('(');
+ auto args = p.readStringList();
+ p.expect(')');
+ auto cmd = createCommand(name, args);
+ if (cmd)
+ commands.push_back(Common::move(cmd));
+ } else {
+ auto cmd = p.parseCommand();
+ if (cmd)
+ commands.push_back(Common::move(cmd));
+ }
+ }
} else
- error("invalid directive on line %u: %s\n", lineno, line.c_str());
+ error("invalid directive on line %u: %s", lineno, line.c_str());
}
}
@@ -345,13 +570,10 @@ Script::~Script() {
}
void Script::exec(ExecutionContext &ctx) const {
- for (auto &warp : _warps) {
- if (!ctx.running)
- break;
- debug("warp %s", warp->vrFile.c_str());
- auto &test = warp->getDefaultTest();
- test->scope.exec(ctx);
- }
+ auto &warp = _warps.front();
+ debug("warp %s", warp->vrFile.c_str());
+ auto &test = warp->getDefaultTest();
+ test->scope.exec(ctx);
}
} // namespace PhoenixVR
Commit: f9f6593c8bd916a7bd52a6b203b2481122cd084b
https://github.com/scummvm/scummvm/commit/f9f6593c8bd916a7bd52a6b203b2481122cd084b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:23+01:00
Commit Message:
PHOENIXVR: add plugin scope
Changed paths:
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 26782a734fa..46cde69d57d 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -313,7 +313,7 @@ class Parser {
uint _pos;
public:
- Parser(const Common::String &line, uint lineno, bool &pluginContext) : _line(line), _lineno(lineno), _pos(0) {
+ Parser(const Common::String &line, uint lineno) : _line(line), _lineno(lineno), _pos(0) {
skip();
}
@@ -488,7 +488,7 @@ const Script::TestPtr &Script::Warp::getTest(int idx) const {
return tests[idx + 1];
}
-Script::Script(Common::SeekableReadStream &s) : _pluginContext(false) {
+Script::Script(Common::SeekableReadStream &s) {
uint lineno = 1;
while (!s.eos()) {
auto line = s.readLine();
@@ -500,7 +500,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (line.empty())
return;
- Parser p(line, lineno, _pluginContext);
+ Parser p(line, lineno);
if (p.atEnd())
return;
@@ -537,24 +537,25 @@ void Script::parseLine(const Common::String &line, uint lineno) {
}
default:
if (_currentTest) {
+ auto &commands = _currentTest->scope.commands;
if (p.maybe("plugin")) {
- if (_pluginContext)
+ if (_pluginScope)
error("nested plugin context is not allowed, line: %u", lineno);
- _pluginContext = true;
+ _pluginScope.reset(new Script::Scope);
} else if (p.maybe("endplugin")) {
- if (!_pluginContext)
+ if (!_pluginScope)
error("endplugin without plugin");
- _pluginContext = false;
+ commands.push_back(Common::move(_pluginScope));
+ _pluginScope.reset();
} else {
- auto &commands = _currentTest->scope.commands;
- if (_pluginContext) {
+ if (_pluginScope) {
auto name = p.nextWord();
p.expect('(');
auto args = p.readStringList();
p.expect(')');
auto cmd = createCommand(name, args);
if (cmd)
- commands.push_back(Common::move(cmd));
+ _pluginScope->commands.push_back(Common::move(cmd));
} else {
auto cmd = p.parseCommand();
if (cmd)
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 759c62ae48b..fbdadfbeafb 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -79,7 +79,7 @@ private:
Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
TestPtr _currentTest;
- bool _pluginContext;
+ ScopePtr _pluginScope;
private:
static Common::String strip(const Common::String &str);
Commit: 10bcf0e6091811b09cb4eccfe1ca64482ec3db06
https://github.com/scummvm/scummvm/commit/10bcf0e6091811b09cb4eccfe1ca64482ec3db06
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:23+01:00
Commit Message:
PHOENIXVR: implement conditional
Changed paths:
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 46cde69d57d..92724701368 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -239,12 +239,6 @@ struct LoadSave_Set_Context_Label : public Script::Command {
}
};
-struct Branch : public Script::Command {
- Common::Array<Common::String> vars;
- Script::CommandPtr target;
- Branch(const Common::Array<Common::String> &args) : vars(args) {}
-};
-
struct RolloverMalette : public Script::Command {
int arg;
@@ -263,6 +257,20 @@ struct RolloverSecretaire : public Script::Command {
}
};
+struct IfAnd : public Script::Conditional {
+ using Script::Conditional::Conditional;
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("ifand");
+ }
+};
+
+struct IfOr : public Script::Conditional {
+ using Script::Conditional::Conditional;
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("ifor");
+ }
+};
+
struct End : public Script::Command {
End() {}
void exec(Script::ExecutionContext &ctx) const override {
@@ -417,12 +425,6 @@ public:
} else if (maybe("anglexmax=")) {
auto xmax = atoi(nextWord().c_str());
debug("anglexmax %d", xmax);
- } else if (maybe("ifand=")) {
- auto var = nextWord();
- debug("ifand %s", var.c_str());
- } else if (maybe("ifor=")) {
- auto var = nextWord();
- debug("ifor %s", var.c_str());
} else if (maybe("gotowarp")) {
auto id = nextWord();
debug("gotowarp %s", id.c_str());
@@ -538,15 +540,26 @@ void Script::parseLine(const Common::String &line, uint lineno) {
default:
if (_currentTest) {
auto &commands = _currentTest->scope.commands;
- if (p.maybe("plugin")) {
+ if (p.maybe("ifand=")) {
+ _conditional.reset(new IfAnd(p.readStringList()));
+ } else if (p.maybe("ifor=")) {
+ _conditional.reset(new IfOr(p.readStringList()));
+ } else if (p.maybe("plugin")) {
if (_pluginScope)
error("nested plugin context is not allowed, line: %u", lineno);
_pluginScope.reset(new Script::Scope);
} else if (p.maybe("endplugin")) {
if (!_pluginScope)
error("endplugin without plugin");
- commands.push_back(Common::move(_pluginScope));
- _pluginScope.reset();
+ if (_conditional) {
+ _conditional->target = Common::move(_pluginScope);
+ _pluginScope.reset();
+ commands.push_back(Common::move(_conditional));
+ _conditional.reset();
+ } else {
+ commands.push_back(Common::move(_pluginScope));
+ _pluginScope.reset();
+ }
} else {
if (_pluginScope) {
auto name = p.nextWord();
@@ -554,12 +567,20 @@ void Script::parseLine(const Common::String &line, uint lineno) {
auto args = p.readStringList();
p.expect(')');
auto cmd = createCommand(name, args);
- if (cmd)
- _pluginScope->commands.push_back(Common::move(cmd));
+ if (cmd) {
+ if (_conditional) {
+ _conditional->target = Common::move(cmd);
+ commands.push_back(Common::move(_conditional));
+ _conditional.reset();
+ } else
+ _pluginScope->commands.push_back(Common::move(cmd));
+ }
} else {
auto cmd = p.parseCommand();
if (cmd)
commands.push_back(Common::move(cmd));
+ else
+ warning("unhandled script command %s", line.c_str());
}
}
} else
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index fbdadfbeafb..ad55bc66bd9 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -53,6 +53,13 @@ public:
};
using ScopePtr = Common::SharedPtr<Scope>;
+ struct Conditional : public Script::Command {
+ Common::Array<Common::String> vars;
+ Script::CommandPtr target;
+ Conditional(Common::Array<Common::String> args) : vars(Common::move(args)) {}
+ };
+ using ConditionalPtr = Common::SharedPtr<Conditional>;
+
struct Test {
int idx;
Scope scope;
@@ -80,6 +87,7 @@ private:
WarpPtr _currentWarp;
TestPtr _currentTest;
ScopePtr _pluginScope;
+ ConditionalPtr _conditional;
private:
static Common::String strip(const Common::String &str);
Commit: 5723dc7a6bf58f5e8a5b1988b9efeaa7c498be45
https://github.com/scummvm/scummvm/commit/5723dc7a6bf58f5e8a5b1988b9efeaa7c498be45
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:23+01:00
Commit Message:
PHOENIXVR: implement all script stubs enough to compile script1
Changed paths:
A engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
new file mode 100644
index 00000000000..229cefa3c32
--- /dev/null
+++ b/engines/phoenixvr/commands.h
@@ -0,0 +1,478 @@
+#ifndef PHOENIXVR_COMMANDS_H
+#define PHOENIXVR_COMMANDS_H
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "phoenixvr/phoenixvr.h"
+#include "phoenixvr/script.h"
+
+namespace PhoenixVR {
+
+namespace {
+struct MultiCD_Set_Transition_Script : public Script::Command {
+ Common::String path;
+
+ MultiCD_Set_Transition_Script(const Common::Array<Common::String> &args) : path(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("MultiCD_Set_Transition_Script %s", path.c_str());
+ }
+};
+
+struct MultiCD_Set_Next_Script : public Script::Command {
+ Common::String filename;
+
+ MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("MultiCD_Set_Next_Script %s", filename.c_str());
+ ctx.engine->setNextScript(filename);
+ }
+};
+
+struct LoadSave_Enter_Script : public Script::Command {
+ Common::String reloading, notReloading;
+
+ LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
+ }
+};
+
+struct Play_Movie : public Script::Command {
+ Common::String filename;
+
+ Play_Movie(const Common::Array<Common::String> &args) : filename(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_Movie %s", filename.c_str());
+ }
+};
+
+struct Play_AnimBloc : public Script::Command {
+ Common::String name;
+ Common::String block;
+ int start;
+ int stop;
+
+ Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), block(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_AnimBloc %s %s %d-%d", name.c_str(), block.c_str(), start, stop);
+ }
+};
+
+struct Play_AnimBloc_Number : public Script::Command {
+ Common::String name1, name2;
+ Common::String block;
+ int start;
+ int stop;
+
+ Play_AnimBloc_Number(const Common::Array<Common::String> &args) : name1(args[0]), name2(args[1]),
+ block(args[2]), start(atoi(args[3].c_str())), stop(atoi(args[4].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("Play_AnimBloc_Number %s %s %s %d-%d", name1.c_str(), name2.c_str(), block.c_str(), start, stop);
+ }
+};
+
+struct Until : public Script::Command {
+ Common::String block;
+ int frame;
+
+ Until(const Common::Array<Common::String> &args) : block(args[0]), frame(atoi(args[1].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("until %s %d", block.c_str(), frame);
+ }
+};
+
+struct While : public Script::Command {
+ float seconds;
+
+ While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("while %g", seconds);
+ }
+};
+
+struct StartTimer : public Script::Command {
+ float seconds;
+
+ StartTimer(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("starttimer %g", seconds);
+ }
+};
+
+struct PauseTimer : public Script::Command {
+ int arg1, arg2;
+
+ PauseTimer(const Common::Array<Common::String> &args) : arg1(atoi(args[0].c_str())), arg2(atoi(args[1].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("pause_timer %d %d", arg1, arg2);
+ }
+};
+
+struct KillTimer : public Script::Command {
+ KillTimer(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("killtimer");
+ }
+};
+
+struct ChangeCurseur : public Script::Command {
+ int cursor;
+ ChangeCurseur(const Common::Array<Common::String> &args) : cursor(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("changecurseur %d", cursor);
+ }
+};
+
+struct Add : public Script::Command {
+ Common::String dstVar;
+ Common::String srcVar;
+ int addend;
+
+ Add(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("add %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
+ }
+};
+
+struct Sub : public Script::Command {
+ Common::String dstVar;
+ Common::String srcVar;
+ int addend;
+
+ Sub(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("sub %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
+ }
+};
+
+struct Cmp : public Script::Command {
+ Common::String var;
+ Common::String negativeVar;
+ Common::String arg0;
+ Common::String op;
+ Common::String arg1;
+
+ Cmp(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]),
+ arg0(args[2]), op(args[3]), arg1(args[4]) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("cmp");
+ }
+};
+
+struct LoadSave_Init_Slots : public Script::Command {
+ int slots;
+
+ LoadSave_Init_Slots(const Common::Array<Common::String> &args) : slots(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Init_Slots %d", slots);
+ }
+};
+
+struct LoadSave_Draw_Slot : public Script::Command {
+ int slot;
+ int arg0;
+ int arg1;
+ int arg2;
+
+ LoadSave_Draw_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())),
+ arg0(atoi(args[1].c_str())),
+ arg1(atoi(args[2].c_str())),
+ arg2(atoi(args[3].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Draw_Slot %d %d %d %d", slot, arg0, arg1, arg2);
+ }
+};
+
+struct LoadSave_Test_Slot : public Script::Command {
+ int slot;
+ Common::String show;
+ Common::String hide;
+
+ LoadSave_Test_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())), show(args[1]), hide(args[2]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
+ }
+};
+
+struct LoadSave_Capture_Context : public Script::Command {
+ LoadSave_Capture_Context(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Capture_Context");
+ }
+};
+
+struct LoadSave_Context_Restored : public Script::Command {
+ Common::String progress;
+ Common::String done;
+
+ LoadSave_Context_Restored(const Common::Array<Common::String> &args) : progress(args[0]), done(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Context_Restored %s %s", progress.c_str(), done.c_str());
+ }
+};
+
+struct LoadSave_Load : public Script::Command {
+ int slot;
+
+ LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Load %d", slot);
+ }
+};
+
+struct LoadSave_Save : public Script::Command {
+ int slot;
+
+ LoadSave_Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Save %d", slot);
+ }
+};
+
+struct LoadSave_Set_Context_Label : public Script::Command {
+ Common::String label;
+
+ LoadSave_Set_Context_Label(const Common::Array<Common::String> &args) : label(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Set_Context_Label %s", label.c_str());
+ }
+};
+
+struct RolloverMalette : public Script::Command {
+ int arg;
+
+ RolloverMalette(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("RolloverMalette %d", arg);
+ }
+};
+
+struct RolloverSecretaire : public Script::Command {
+ int arg;
+
+ RolloverSecretaire(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("RolloverSecretaire %d", arg);
+ }
+};
+
+#define PLUGIN_LIST(E) \
+ E(Add) \
+ E(ChangeCurseur) \
+ E(Cmp) \
+ E(KillTimer) \
+ E(LoadSave_Capture_Context) \
+ E(LoadSave_Context_Restored) \
+ E(LoadSave_Enter_Script) \
+ E(LoadSave_Init_Slots) \
+ E(LoadSave_Load) \
+ E(LoadSave_Save) \
+ E(LoadSave_Set_Context_Label) \
+ E(LoadSave_Draw_Slot) \
+ E(LoadSave_Test_Slot) \
+ E(MultiCD_Set_Transition_Script) \
+ E(MultiCD_Set_Next_Script) \
+ E(PauseTimer) \
+ E(Play_AnimBloc) \
+ E(Play_AnimBloc_Number) \
+ E(Play_Movie) \
+ E(RolloverMalette) \
+ E(RolloverSecretaire) \
+ E(StartTimer) \
+ E(Sub) \
+ E(Until) \
+ E(While) \
+ /* */
+
+#define ADD_PLUGIN(NAME) \
+ if (cmd.equalsIgnoreCase(#NAME)) \
+ return Script::CommandPtr(new NAME(args));
+
+Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args) {
+ PLUGIN_LIST(ADD_PLUGIN)
+ error("unhandled plugin command %s", cmd.c_str());
+}
+
+struct IfAnd : public Script::Conditional {
+ using Script::Conditional::Conditional;
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("ifand");
+ }
+};
+
+struct IfOr : public Script::Conditional {
+ using Script::Conditional::Conditional;
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("ifor");
+ }
+};
+
+struct Set : public Script::Command {
+ Common::String name;
+ int value;
+
+ Set(Common::String n, int v) : name(Common::move(n)), value(v) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ ctx.engine->setVariable(name, value);
+ }
+};
+
+struct End : public Script::Command {
+ End() {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("end");
+ ctx.running = false;
+ }
+};
+
+struct Return : public Script::Command {
+ Return() {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("return");
+ ctx.running = false;
+ }
+};
+
+struct SetCursorDefault : public Script::Command {
+ int idx;
+ Common::String fname;
+ SetCursorDefault(int i, Common::String f) : idx(i), fname(Common::move(f)) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("setcursordefault %d, %s\n", idx, fname.c_str());
+ }
+};
+
+struct SetCursor : public Script::Command {
+ Common::String fname;
+ Common::String warp;
+ int idx;
+ SetCursor(Common::String f, Common::String w, int i) : fname(Common::move(f)), warp(Common::move(w)), idx(i) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("setcursor %s %s:%d\n", fname.c_str(), warp.c_str(), idx);
+ }
+};
+
+struct HideCursor : public Script::Command {
+ Common::String warp;
+ int idx;
+ HideCursor(Common::String w, int i) : warp(Common::move(w)), idx(i) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("setcursor %s:%d\n", warp.c_str(), idx);
+ }
+};
+
+struct ResetLockKey : public Script::Command {
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("resetlockkey");
+ }
+};
+
+struct LockKey : public Script::Command {
+ int idx;
+ Common::String warp;
+
+ LockKey(int i, Common::String w) : idx(i), warp(Common::move(w)) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("lock key F%d: %s", idx, warp.c_str());
+ }
+};
+
+struct Zoom : public Script::Command {
+ int zoom;
+ Zoom(int z) : zoom(z) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("zoom %d", zoom);
+ }
+};
+
+struct AngleXMax : public Script::Command {
+ float xMax;
+ AngleXMax(float x) : xMax(x) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("angle x max %g", xMax);
+ }
+};
+
+struct AngleYMax : public Script::Command {
+ float yMax0, yMax1;
+ AngleYMax(float y0, float y1) : yMax0(y0), yMax1(y1) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("angle y max %g %g", yMax0, yMax1);
+ }
+};
+
+struct GoToWarp : public Script::Command {
+ Common::String warp;
+ GoToWarp(Common::String w) : warp(Common::move(w)) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("goto warp %s", warp.c_str());
+ }
+};
+
+struct PlaySound : public Script::Command {
+ Common::String sound;
+ int volume;
+ int unk;
+
+ PlaySound(Common::String s, int v, int u) : sound(Common::move(s)), volume(v), unk(u) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("play sound %s %d %d", sound.c_str(), volume, unk);
+ }
+};
+
+struct StopSound : public Script::Command {
+ Common::String sound;
+
+ StopSound(Common::String s) : sound(Common::move(s)) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("stop sound %s", sound.c_str());
+ }
+};
+
+struct PlaySound3D : public Script::Command {
+ Common::String sound;
+ int volume;
+ float angle;
+ int unk;
+
+ PlaySound3D(Common::String s, int v, float a, int u) : sound(Common::move(s)), volume(v), angle(a), unk(u) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("play sound %s %d %g %d", sound.c_str(), volume, angle, unk);
+ }
+};
+
+struct StopSound3D : public Script::Command {
+ Common::String sound;
+
+ StopSound3D(Common::String s) : sound(Common::move(s)) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("stop sound 3d %s", sound.c_str());
+ }
+};
+
+struct Fade : public Script::Command {
+ int arg0, arg1, arg2;
+
+ Fade(int a0, int a1, int a2) : arg0(a0), arg1(a1), arg2(a2) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("fade %d %d %d", arg0, arg1, arg2);
+ }
+};
+
+} // namespace
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index f54d554d555..fdee8161f9a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -77,6 +77,19 @@ void PhoenixVREngine::setCursorDefault(uint idx, const Common::String &path) {
debug("setCursorDefault %u: %s", idx, path.c_str());
}
+void PhoenixVREngine::declareVariable(const Common::String &name) {
+ _variables.setVal(name, 0);
+}
+
+void PhoenixVREngine::setVariable(const Common::String &name, int value) {
+ debug("set %s %d", name.c_str(), value);
+ _variables.setVal(name, value);
+}
+
+int PhoenixVREngine::getVariable(const Common::String &name) const {
+ return _variables.getVal(name);
+}
+
void PhoenixVREngine::runScript(Common::SeekableReadStream &scriptSource) {
Script script(scriptSource);
Script::ExecutionContext ctx{this, true};
@@ -101,6 +114,7 @@ void PhoenixVREngine::loadScript(const Common::Path &scriptFile) {
runScript(*scriptStream);
if (!_nextScript.empty()) {
nextScript = removeDrive(_nextScript);
+ _nextScript.clear();
} else
nextScript.clear();
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 1dab480658d..a1acbc8af26 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -103,6 +103,10 @@ public:
void setNextScript(const Common::String &path);
void setCursorDefault(uint idx, const Common::String &path);
+ void declareVariable(const Common::String &name);
+ void setVariable(const Common::String &name, int value);
+ int getVariable(const Common::String &name) const;
+
private:
void loadScript(const Common::Path &scriptFile);
void runScript(Common::SeekableReadStream &script);
@@ -111,6 +115,7 @@ private:
private:
Common::String _nextScript;
+ Common::HashMap<Common::String, int> _variables;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 92724701368..cb88b83b133 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -2,318 +2,12 @@
#include "common/debug.h"
#include "common/stream.h"
#include "common/textconsole.h"
+#include "phoenixvr/commands.h"
#include "phoenixvr/phoenixvr.h"
namespace PhoenixVR {
namespace {
-struct MultiCD_Set_Transition_Script : public Script::Command {
- Common::String path;
-
- MultiCD_Set_Transition_Script(const Common::Array<Common::String> &args) : path(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("MultiCD_Set_Transition_Script %s", path.c_str());
- }
-};
-
-struct MultiCD_Set_Next_Script : public Script::Command {
- Common::String filename;
-
- MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("MultiCD_Set_Next_Script %s", filename.c_str());
- ctx.engine->setNextScript(filename);
- }
-};
-
-struct LoadSave_Enter_Script : public Script::Command {
- Common::String reloading, notReloading;
-
- LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
- }
-};
-
-struct Play_Movie : public Script::Command {
- Common::String filename;
-
- Play_Movie(const Common::Array<Common::String> &args) : filename(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_Movie %s", filename.c_str());
- }
-};
-
-struct Play_AnimBloc : public Script::Command {
- Common::String name;
- Common::String block;
- int start;
- int stop;
-
- Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), block(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc %s %s %d-%d", name.c_str(), block.c_str(), start, stop);
- }
-};
-
-struct Play_AnimBloc_Number : public Script::Command {
- Common::String name1, name2;
- Common::String block;
- int start;
- int stop;
-
- Play_AnimBloc_Number(const Common::Array<Common::String> &args) : name1(args[0]), name2(args[1]),
- block(args[2]), start(atoi(args[3].c_str())), stop(atoi(args[4].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc_Number %s %s %s %d-%d", name1.c_str(), name2.c_str(), block.c_str(), start, stop);
- }
-};
-
-struct Until : public Script::Command {
- Common::String block;
- int frame;
-
- Until(const Common::Array<Common::String> &args) : block(args[0]), frame(atoi(args[1].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("until %s %d", block.c_str(), frame);
- }
-};
-
-struct While : public Script::Command {
- double seconds;
-
- While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("while %g", seconds);
- }
-};
-
-struct StartTimer : public Script::Command {
- double seconds;
-
- StartTimer(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("starttimer %g", seconds);
- }
-};
-
-struct PauseTimer : public Script::Command {
- int arg1, arg2;
-
- PauseTimer(const Common::Array<Common::String> &args) : arg1(atoi(args[0].c_str())), arg2(atoi(args[1].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("pause_timer %d %d", arg1, arg2);
- }
-};
-
-struct KillTimer : public Script::Command {
- KillTimer(const Common::Array<Common::String> &args) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("killtimer");
- }
-};
-
-struct ChangeCurseur : public Script::Command {
- int cursor;
- ChangeCurseur(const Common::Array<Common::String> &args) : cursor(atoi(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("changecurseur %d", cursor);
- }
-};
-
-struct Add : public Script::Command {
- Common::String dstVar;
- Common::String srcVar;
- int addend;
-
- Add(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
-
- void exec(Script::ExecutionContext &ctx) const override {
- debug("add %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
- }
-};
-
-struct Sub : public Script::Command {
- Common::String dstVar;
- Common::String srcVar;
- int addend;
-
- Sub(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
-
- void exec(Script::ExecutionContext &ctx) const override {
- debug("sub %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
- }
-};
-
-struct Cmp : public Script::Command {
- Common::String var;
- Common::String negativeVar;
- Common::String arg0;
- Common::String op;
- Common::String arg1;
-
- Cmp(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]),
- arg0(args[2]), op(args[3]), arg1(args[4]) {}
-
- void exec(Script::ExecutionContext &ctx) const override {
- debug("cmp");
- }
-};
-
-struct LoadSave_Init_Slots : public Script::Command {
- int slots;
-
- LoadSave_Init_Slots(const Common::Array<Common::String> &args) : slots(atoi(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Init_Slots %d", slots);
- }
-};
-
-struct LoadSave_Draw_Slot : public Script::Command {
- int slot;
- int arg0;
- int arg1;
- int arg2;
-
- LoadSave_Draw_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())),
- arg0(atoi(args[1].c_str())),
- arg1(atoi(args[2].c_str())),
- arg2(atoi(args[3].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Draw_Slot %d %d %d %d", slot, arg0, arg1, arg2);
- }
-};
-
-struct LoadSave_Test_Slot : public Script::Command {
- int slot;
- Common::String show;
- Common::String hide;
-
- LoadSave_Test_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())), show(args[1]), hide(args[2]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
- }
-};
-
-struct LoadSave_Capture_Context : public Script::Command {
- LoadSave_Capture_Context(const Common::Array<Common::String> &args) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Capture_Context");
- }
-};
-
-struct LoadSave_Context_Restored : public Script::Command {
- Common::String progress;
- Common::String done;
-
- LoadSave_Context_Restored(const Common::Array<Common::String> &args) : progress(args[0]), done(args[1]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Context_Restored %s %s", progress.c_str(), done.c_str());
- }
-};
-
-struct LoadSave_Load : public Script::Command {
- int slot;
-
- LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Load %d", slot);
- }
-};
-
-struct LoadSave_Save : public Script::Command {
- int slot;
-
- LoadSave_Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Save %d", slot);
- }
-};
-
-struct LoadSave_Set_Context_Label : public Script::Command {
- Common::String label;
-
- LoadSave_Set_Context_Label(const Common::Array<Common::String> &args) : label(args[0]) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Set_Context_Label %s", label.c_str());
- }
-};
-
-struct RolloverMalette : public Script::Command {
- int arg;
-
- RolloverMalette(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("RolloverMalette %d", arg);
- }
-};
-
-struct RolloverSecretaire : public Script::Command {
- int arg;
-
- RolloverSecretaire(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("RolloverSecretaire %d", arg);
- }
-};
-
-struct IfAnd : public Script::Conditional {
- using Script::Conditional::Conditional;
- void exec(Script::ExecutionContext &ctx) const override {
- debug("ifand");
- }
-};
-
-struct IfOr : public Script::Conditional {
- using Script::Conditional::Conditional;
- void exec(Script::ExecutionContext &ctx) const override {
- debug("ifor");
- }
-};
-
-struct End : public Script::Command {
- End() {}
- void exec(Script::ExecutionContext &ctx) const override {
- ctx.running = false;
- }
-};
-
-#define PLUGIN_LIST(E) \
- E(Add) \
- E(ChangeCurseur) \
- E(Cmp) \
- E(KillTimer) \
- E(LoadSave_Capture_Context) \
- E(LoadSave_Context_Restored) \
- E(LoadSave_Enter_Script) \
- E(LoadSave_Init_Slots) \
- E(LoadSave_Load) \
- E(LoadSave_Save) \
- E(LoadSave_Set_Context_Label) \
- E(LoadSave_Draw_Slot) \
- E(LoadSave_Test_Slot) \
- E(MultiCD_Set_Transition_Script) \
- E(MultiCD_Set_Next_Script) \
- E(PauseTimer) \
- E(Play_AnimBloc) \
- E(Play_AnimBloc_Number) \
- E(Play_Movie) \
- E(RolloverMalette) \
- E(RolloverSecretaire) \
- E(StartTimer) \
- E(Sub) \
- E(Until) \
- E(While) \
- /* */
-
-#define ADD_PLUGIN(NAME) \
- if (cmd.equalsIgnoreCase(#NAME)) \
- return Script::CommandPtr(new NAME(args));
-
-Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args) {
- PLUGIN_LIST(ADD_PLUGIN)
- error("unhandled plugin command %s", cmd.c_str());
-}
class Parser {
const Common::String &_line;
@@ -345,6 +39,15 @@ public:
skip();
}
+ bool maybe(char ch) {
+ skip();
+ if (peek() == ch) {
+ next();
+ return true;
+ } else
+ return false;
+ }
+
bool maybe(const Common::String &prefix) {
skip();
bool yes = scumm_strnicmp(_line.c_str() + _pos, prefix.c_str(), prefix.size()) == 0;
@@ -365,6 +68,20 @@ public:
return _line.substr(begin, end - begin);
}
+ int nextInt() {
+ bool negative = false;
+ int value = 0;
+ if (maybe('-'))
+ negative = true;
+ do {
+ auto ch = next();
+ if (ch < '0' || ch > '9')
+ error("expected digit at %d, line: %s", _pos, _line.c_str());
+ value = value * 10 + (ch - '0');
+ } while (Common::isDigit(peek()));
+ return negative ? -value : value;
+ }
+
Common::String nextWord() {
skip();
auto begin = _pos;
@@ -391,7 +108,7 @@ public:
Common::Array<Common::String> readStringList() {
Common::Array<Common::String> list;
- do {
+ while (!atEnd() && peek() != ')') {
if (peek() == '"')
list.push_back(nextString());
else {
@@ -399,69 +116,82 @@ public:
}
if (peek() == ',')
next();
- } while (peek() != ')');
+ }
return list;
}
Script::CommandPtr parseCommand() {
using CommandPtr = Script::CommandPtr;
- if (maybe("end")) {
- return CommandPtr{new End()};
- } else if (maybe("setcursordefault")) {
- auto idx = atoi(nextWord().c_str());
+ if (maybe("setcursordefault")) {
+ auto idx = nextInt();
expect(',');
- auto fname = nextWord();
- debug("setcursordefault %d: %s", idx, fname.c_str());
+ return CommandPtr(new SetCursorDefault(idx, nextWord()));
} else if (maybe("lockkey")) {
- auto idx = atoi(nextWord().c_str());
+ auto idx = nextInt();
expect(',');
auto fname = nextWord();
- debug("lockkey %d: %s", idx, fname.c_str());
+ return CommandPtr(new LockKey(idx, Common::move(fname)));
} else if (maybe("resetlockkey")) {
- debug("resetlockkey");
+ return CommandPtr(new ResetLockKey());
+ } else if (maybe("fade=")) {
+ auto arg0 = nextInt();
+ expect(',');
+ auto arg1 = nextInt();
+ expect(',');
+ auto arg2 = nextInt();
+ return CommandPtr(new Fade(arg0, arg1, arg2));
} else if (maybe("setzoom=")) {
- auto zoom = atoi(nextWord().c_str());
- debug("setzoom %d", zoom);
+ return CommandPtr(new Zoom(nextInt()));
} else if (maybe("anglexmax=")) {
- auto xmax = atoi(nextWord().c_str());
- debug("anglexmax %d", xmax);
+ return CommandPtr(new AngleXMax(nextInt() / 1024.0f));
+ } else if (maybe("angleymax=")) {
+ auto y0 = nextInt() / 1024.0f;
+ expect(',');
+ auto y1 = nextInt() / 1024.0f;
+ return CommandPtr(new AngleYMax(y0, y1));
} else if (maybe("gotowarp")) {
- auto id = nextWord();
- debug("gotowarp %s", id.c_str());
+ return CommandPtr(new GoToWarp(nextWord()));
} else if (maybe("playsound3d")) {
auto sound = nextWord();
expect(',');
- auto arg0 = nextWord();
+ auto arg0 = nextInt();
expect(',');
- auto arg1 = nextWord();
+ auto arg1 = nextInt();
expect(',');
- auto arg2 = nextWord();
- debug("playsound3d %s %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str(), arg2.c_str());
+ auto arg2 = nextInt();
+ return CommandPtr(new PlaySound3D(Common::move(sound), arg0, arg1 / 1024.0f, arg2));
} else if (maybe("playsound")) {
auto sound = nextWord();
expect(',');
- auto arg0 = nextWord();
+ auto arg0 = nextInt();
expect(',');
- auto arg1 = nextWord();
- debug("playsound %s %s %s", sound.c_str(), arg0.c_str(), arg1.c_str());
+ auto arg1 = nextInt();
+ return CommandPtr(new PlaySound(Common::move(sound), arg0, arg1));
} else if (maybe("stopsound3d")) {
- auto sound = nextWord();
- debug("stopsound3d %s", sound.c_str());
+ return CommandPtr(new StopSound3D(nextWord()));
} else if (maybe("stopsound")) {
- auto sound = nextWord();
- debug("stopsound %s", sound.c_str());
+ return CommandPtr(new StopSound(nextWord()));
} else if (maybe("setcursor")) {
auto image = nextWord();
expect(',');
auto warp = nextWord();
expect(',');
- auto idx = nextWord();
- debug("setcursor %s %s %s", image.c_str(), warp.c_str(), idx.c_str());
+ auto idx = nextInt();
+ return CommandPtr(new SetCursor(Common::move(image), Common::move(warp), idx));
+ } else if (maybe("hidecursor")) {
+ auto warp = nextWord();
+ expect(',');
+ auto idx = nextInt();
+ return CommandPtr(new HideCursor(Common::move(warp), idx));
} else if (maybe("set")) {
auto var = nextWord();
expect('=');
- auto value = nextWord();
- debug("set %s = %s", var.c_str(), value.c_str());
+ auto value = nextInt();
+ return CommandPtr(new Set(Common::move(var), value));
+ } else if (maybe("return")) {
+ return CommandPtr{new Return()};
+ } else if (maybe("end")) {
+ return CommandPtr{new End()};
}
return {};
};
@@ -506,28 +236,24 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (p.atEnd())
return;
- // debug("line %u: %s", lineno, line.c_str());
-
switch (p.peek()) {
case '[': {
p.next();
if (p.maybe("bool]=")) {
auto name = p.nextWord();
- debug("bool flag %s", name.c_str());
+ // FIXME: pass engine here?
+ g_engine->declareVariable(name);
} else if (p.maybe("warp]=")) {
auto vr = p.nextWord();
p.expect(',');
auto test = p.nextWord();
- debug("got warp %s %s", vr.c_str(), test.c_str());
_currentWarp.reset(new Warp{vr, test, {}});
_warpsIndex[vr] = _warps.size();
_warps.push_back(_currentWarp);
} else if (p.maybe("test]=")) {
if (!_currentWarp)
error("test without warp");
- auto word = p.nextWord();
- debug("test %s", word.c_str());
- auto idx = std::atoi(word.c_str());
+ auto idx = p.nextInt();
if (!_currentWarp)
error("text must have parent wrap section");
_currentTest.reset(new Test{idx, {}});
@@ -580,7 +306,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (cmd)
commands.push_back(Common::move(cmd));
else
- warning("unhandled script command %s", line.c_str());
+ error("unhandled script command %s", line.c_str());
}
}
} else
@@ -593,7 +319,7 @@ Script::~Script() {
void Script::exec(ExecutionContext &ctx) const {
auto &warp = _warps.front();
- debug("warp %s", warp->vrFile.c_str());
+ debug("execute warp script %s", warp->vrFile.c_str());
auto &test = warp->getDefaultTest();
test->scope.exec(ctx);
}
Commit: e3c7a3f95f08d9dabc21a12d80d9f38ddd17fe49
https://github.com/scummvm/scummvm/commit/e3c7a3f95f08d9dabc21a12d80d9f38ddd17fe49
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:23+01:00
Commit Message:
PHOENIXVR: add region set stub, rework script loading, unified init/goto
Changed paths:
A engines/phoenixvr/region_set.cpp
A engines/phoenixvr/region_set.h
engines/phoenixvr/commands.h
engines/phoenixvr/module.mk
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 229cefa3c32..c9103c3ed30 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -342,7 +342,7 @@ struct SetCursorDefault : public Script::Command {
Common::String fname;
SetCursorDefault(int i, Common::String f) : idx(i), fname(Common::move(f)) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursordefault %d, %s\n", idx, fname.c_str());
+ debug("setcursordefault %d, %s", idx, fname.c_str());
}
};
@@ -352,7 +352,7 @@ struct SetCursor : public Script::Command {
int idx;
SetCursor(Common::String f, Common::String w, int i) : fname(Common::move(f)), warp(Common::move(w)), idx(i) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursor %s %s:%d\n", fname.c_str(), warp.c_str(), idx);
+ debug("setcursor %s %s:%d", fname.c_str(), warp.c_str(), idx);
}
};
@@ -361,7 +361,7 @@ struct HideCursor : public Script::Command {
int idx;
HideCursor(Common::String w, int i) : warp(Common::move(w)), idx(i) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursor %s:%d\n", warp.c_str(), idx);
+ debug("setcursor %s:%d", warp.c_str(), idx);
}
};
@@ -414,7 +414,7 @@ struct GoToWarp : public Script::Command {
GoToWarp(Common::String w) : warp(Common::move(w)) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("goto warp %s", warp.c_str());
+ ctx.engine->goToWarp(warp);
}
};
diff --git a/engines/phoenixvr/module.mk b/engines/phoenixvr/module.mk
index 1b3168766b2..1f862bbd514 100644
--- a/engines/phoenixvr/module.mk
+++ b/engines/phoenixvr/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS = \
phoenixvr.o \
console.o \
metaengine.o \
+ region_set.o \
script.o
# This module can be built as a plugin
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index fdee8161f9a..eb62f600db3 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -32,6 +32,7 @@
#include "phoenixvr/console.h"
#include "phoenixvr/detection.h"
#include "phoenixvr/pakf.h"
+#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
namespace PhoenixVR {
@@ -73,6 +74,11 @@ void PhoenixVREngine::setNextScript(const Common::String &path) {
debug("setNextScript %s", _nextScript.c_str());
}
+void PhoenixVREngine::goToWarp(const Common::String &warp) {
+ debug("gotowarp %s", warp.c_str());
+ _nextWarp = warp;
+}
+
void PhoenixVREngine::setCursorDefault(uint idx, const Common::String &path) {
debug("setCursorDefault %u: %s", idx, path.c_str());
}
@@ -90,40 +96,10 @@ int PhoenixVREngine::getVariable(const Common::String &name) const {
return _variables.getVal(name);
}
-void PhoenixVREngine::runScript(Common::SeekableReadStream &scriptSource) {
- Script script(scriptSource);
- Script::ExecutionContext ctx{this, true};
- script.exec(ctx);
-}
-
-void PhoenixVREngine::loadScript(const Common::Path &scriptFile) {
- Common::String nextScript = scriptFile.toString();
- while (!nextScript.empty()) {
- Common::File file;
- Common::Path nextPath(nextScript);
- if (file.open(nextPath)) {
- runScript(file);
- } else {
- auto pakFile = nextPath;
- pakFile = pakFile.removeExtension().append(".pak");
- file.open(pakFile);
- }
- if (!file.isOpen())
- error("can't open script file %s", scriptFile.toString().c_str());
- Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
- runScript(*scriptStream);
- if (!_nextScript.empty()) {
- nextScript = removeDrive(_nextScript);
- _nextScript.clear();
- } else
- nextScript.clear();
- }
-}
-
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
- loadScript("script.lst");
+ setNextScript("script.lst");
// Set the engine's debugger console
setDebugger(new Console());
@@ -139,6 +115,39 @@ Common::Error PhoenixVREngine::run() {
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(e)) {
}
+ if (!_nextScript.empty()) {
+ debug("loading script from %s", _nextScript.c_str());
+ auto nextScript = Common::move(_nextScript);
+ _nextScript.clear();
+
+ nextScript = removeDrive(nextScript);
+
+ Common::File file;
+ Common::Path nextPath(nextScript);
+ if (file.open(nextPath)) {
+ _script.reset(new Script(file));
+ } else {
+ auto pakFile = nextPath;
+ pakFile = pakFile.removeExtension().append(".pak");
+ file.open(pakFile);
+ if (!file.isOpen())
+ error("can't open script file %s", nextScript.c_str());
+ Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
+ _script.reset(new Script(*scriptStream));
+ }
+ goToWarp(_script->getInitScript()->vrFile);
+ }
+ if (!_nextWarp.empty()) {
+ _warp = _script->getWarp(_nextWarp);
+ _nextWarp.clear();
+ debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
+ _regSet.reset(new RegionSet(_warp->testFile));
+
+ Script::ExecutionContext ctx{this, true};
+ debug("execute warp script %s", _warp->vrFile.c_str());
+ auto &test = _warp->getDefaultTest();
+ test->scope.exec(ctx);
+ }
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index a1acbc8af26..c04ef833008 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -35,6 +35,8 @@
#include "graphics/screen.h"
#include "phoenixvr/detection.h"
+#include "phoenixvr/region_set.h"
+#include "phoenixvr/script.h"
namespace PhoenixVR {
@@ -101,6 +103,7 @@ public:
// Script API
void setNextScript(const Common::String &path);
+ void goToWarp(const Common::String &warp);
void setCursorDefault(uint idx, const Common::String &path);
void declareVariable(const Common::String &name);
@@ -108,14 +111,17 @@ public:
int getVariable(const Common::String &name) const;
private:
- void loadScript(const Common::Path &scriptFile);
- void runScript(Common::SeekableReadStream &script);
static Common::String removeDrive(const Common::String &path);
static Common::String resolvePath(const Common::String &path);
private:
Common::String _nextScript;
+ Common::String _nextWarp;
Common::HashMap<Common::String, int> _variables;
+ Common::ScopedPtr<Script> _script;
+
+ Script::ConstWarpPtr _warp;
+ Common::ScopedPtr<RegionSet> _regSet;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
new file mode 100644
index 00000000000..747a74d09a2
--- /dev/null
+++ b/engines/phoenixvr/region_set.cpp
@@ -0,0 +1,37 @@
+/* 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 "phoenixvr/region_set.h"
+#include "common/debug.h"
+#include "common/file.h"
+
+namespace PhoenixVR {
+RegionSet::RegionSet(const Common::String &fname) {
+ Common::File file;
+ if (!file.open(Common::Path(fname))) {
+ debug("can't find region %s", fname.c_str());
+ return;
+ }
+ auto n = file.readUint32LE();
+ debug("regions: %u", n);
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
new file mode 100644
index 00000000000..36e5f0a314f
--- /dev/null
+++ b/engines/phoenixvr/region_set.h
@@ -0,0 +1,44 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_REGIONS_H
+#define PHOENIXVR_REGIONS_H
+
+#include "common/array.h"
+
+namespace Common {
+class String;
+};
+
+namespace PhoenixVR {
+struct Region {
+ float a, b, c, d;
+};
+
+class RegionSet {
+ Common::Array<Region> _regions;
+
+public:
+ RegionSet(const Common::String &fname);
+};
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index cb88b83b133..f9799e3930c 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -317,11 +317,13 @@ void Script::parseLine(const Common::String &line, uint lineno) {
Script::~Script() {
}
-void Script::exec(ExecutionContext &ctx) const {
- auto &warp = _warps.front();
- debug("execute warp script %s", warp->vrFile.c_str());
- auto &test = warp->getDefaultTest();
- test->scope.exec(ctx);
+Script::ConstWarpPtr Script::getWarp(const Common::String &name) const {
+ auto idx = _warpsIndex.getVal(name);
+ return _warps[idx];
+}
+
+Script::ConstWarpPtr Script::getInitScript() const {
+ return _warps.front();
}
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index ad55bc66bd9..bf45737f16b 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -80,6 +80,7 @@ public:
};
using WarpPtr = Common::SharedPtr<Warp>;
+ using ConstWarpPtr = Common::SharedPtr<const Warp>;
private:
Common::HashMap<Common::String, uint> _warpsIndex;
@@ -97,7 +98,8 @@ public:
Script(Common::SeekableReadStream &s);
~Script();
- void exec(ExecutionContext &ctx) const;
+ ConstWarpPtr getWarp(const Common::String &name) const;
+ ConstWarpPtr getInitScript() const;
};
} // namespace PhoenixVR
Commit: 649faa8506c12672b3eb67efb1b9a81fad967e06
https://github.com/scummvm/scummvm/commit/649faa8506c12672b3eb67efb1b9a81fad967e06
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:24+01:00
Commit Message:
PHOENIXVR: add region set reader
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/region_set.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index eb62f600db3..545827740e1 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -40,8 +40,13 @@ namespace PhoenixVR {
PhoenixVREngine *g_engine;
PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
- _gameDescription(gameDesc), _randomSource("PhoenixVR"), _pixelFormat(Graphics::PixelFormat::createFormatRGB24()) {
+ _gameDescription(gameDesc),
+ _randomSource("PhoenixVR"),
+ _pixelFormat(Graphics::PixelFormat::createFormatRGB24()) {
g_engine = this;
+ auto path = Common::FSNode(ConfMan.getPath("path"));
+ SearchMan.addSubDirectoryMatching(path, "NecroES/Data", 1, 1, true);
+ SearchMan.addSubDirectoryMatching(path, "NecroES/Data2", 2, 1, true);
}
PhoenixVREngine::~PhoenixVREngine() {
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index 747a74d09a2..288ec20b490 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -31,7 +31,14 @@ RegionSet::RegionSet(const Common::String &fname) {
return;
}
auto n = file.readUint32LE();
- debug("regions: %u", n);
+ while (n--) {
+ auto a = file.readFloatLE();
+ auto b = file.readFloatLE();
+ auto c = file.readFloatLE();
+ auto d = file.readFloatLE();
+ debug("region %g %g %g %g", a, b, c, d);
+ _regions.push_back(Region{a, b, c, d});
+ }
}
} // namespace PhoenixVR
Commit: 390e17908316e791a5b56f299d53b42a30bd6196
https://github.com/scummvm/scummvm/commit/390e17908316e791a5b56f299d53b42a30bd6196
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:24+01:00
Commit Message:
PHOENIXVR: read variable.txt and implement ifand/ifor/cmp
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index c9103c3ed30..737aab57078 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -153,13 +153,21 @@ struct Cmp : public Script::Command {
Common::String negativeVar;
Common::String arg0;
Common::String op;
- Common::String arg1;
+ int arg1;
Cmp(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]),
- arg0(args[2]), op(args[3]), arg1(args[4]) {}
+ arg0(args[2]), op(args[3]), arg1(atoi(args[4].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("cmp");
+ debug("cmp %s %s %s %s %d", var.c_str(), negativeVar.c_str(), arg0.c_str(), op.c_str(), arg1);
+ if (op == "==") {
+ auto value0 = ctx.engine->getVariable(arg0);
+ bool r = value0 == arg1;
+ ctx.engine->setVariable(var, r);
+ ctx.engine->setVariable(negativeVar, !r);
+ } else {
+ error("invalid cmp op %s", op.c_str());
+ }
}
};
@@ -300,14 +308,36 @@ Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<
struct IfAnd : public Script::Conditional {
using Script::Conditional::Conditional;
void exec(Script::ExecutionContext &ctx) const override {
- debug("ifand");
+ auto *engine = ctx.engine;
+ bool result = true;
+ for (auto &var : vars) {
+ auto value = engine->getVariable(var);
+ debug("ifand, %s: %d", var.c_str(), value);
+ if (!value)
+ result = false;
+ }
+ if (!result)
+ return;
+ debug("ifand: executing conditional block");
+ target->exec(ctx);
}
};
struct IfOr : public Script::Conditional {
using Script::Conditional::Conditional;
void exec(Script::ExecutionContext &ctx) const override {
- debug("ifor");
+ auto *engine = ctx.engine;
+ bool result = false;
+ for (auto &var : vars) {
+ auto value = engine->getVariable(var);
+ debug("ifor, %s: %d", var.c_str(), value);
+ if (value)
+ result = true;
+ }
+ if (!result)
+ return;
+ debug("ifor: executing conditional block");
+ target->exec(ctx);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 545827740e1..76503c57159 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -104,6 +104,15 @@ int PhoenixVREngine::getVariable(const Common::String &name) const {
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
+ {
+ Common::File vars;
+ if (vars.open(Common::Path("variable.txt"))) {
+ while (!vars.eos()) {
+ auto var = vars.readLine();
+ declareVariable(var);
+ }
+ }
+ }
setNextScript("script.lst");
// Set the engine's debugger console
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index c04ef833008..31976b02636 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -117,7 +117,7 @@ private:
private:
Common::String _nextScript;
Common::String _nextWarp;
- Common::HashMap<Common::String, int> _variables;
+ Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
Common::ScopedPtr<Script> _script;
Script::ConstWarpPtr _warp;
Commit: 16d127786b0c2bcbd75568e2719f876ae83f481b
https://github.com/scummvm/scummvm/commit/16d127786b0c2bcbd75568e2719f876ae83f481b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:24+01:00
Commit Message:
PHOENIXVR: retrieve region for cursor command
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 737aab57078..bb0a678521f 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -378,11 +378,16 @@ struct SetCursorDefault : public Script::Command {
struct SetCursor : public Script::Command {
Common::String fname;
- Common::String warp;
+ Common::String wname;
int idx;
- SetCursor(Common::String f, Common::String w, int i) : fname(Common::move(f)), warp(Common::move(w)), idx(i) {}
- void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursor %s %s:%d", fname.c_str(), warp.c_str(), idx);
+ SetCursor(Common::String f, Common::String w, int i) : fname(Common::move(f)), wname(Common::move(w)), idx(i) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("setcursor %s %s:%d", fname.c_str(), wname.c_str(), idx);
+ auto warp = ctx.engine->getCurrentWarp();
+ if (!warp || !warp->vrFile.equalsIgnoreCase(wname))
+ error("setting cursor for different warp, active: %s, required: %s", warp ? warp->vrFile.c_str() : "null", wname.c_str());
+ auto reg = g_engine->getRegion(idx);
+ debug("region: %g %g %g %g", reg.a, reg.b, reg.c, reg.d);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 76503c57159..681af9214ce 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -84,6 +84,16 @@ void PhoenixVREngine::goToWarp(const Common::String &warp) {
_nextWarp = warp;
}
+Script::ConstWarpPtr PhoenixVREngine::getWarp(const Common::String &name) {
+ return _script->getWarp(name);
+}
+
+Region PhoenixVREngine::getRegion(int idx) const {
+ if (!_regSet)
+ error("no region set");
+ return _regSet->getRegion(idx);
+}
+
void PhoenixVREngine::setCursorDefault(uint idx, const Common::String &path) {
debug("setCursorDefault %u: %s", idx, path.c_str());
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 31976b02636..88860d8722c 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -110,6 +110,10 @@ public:
void setVariable(const Common::String &name, int value);
int getVariable(const Common::String &name) const;
+ Script::ConstWarpPtr getWarp(const Common::String &name);
+ Script::ConstWarpPtr getCurrentWarp() { return _warp; }
+ Region getRegion(int idx) const;
+
private:
static Common::String removeDrive(const Common::String &path);
static Common::String resolvePath(const Common::String &path);
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index 36e5f0a314f..89680aa5df1 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -38,6 +38,9 @@ class RegionSet {
public:
RegionSet(const Common::String &fname);
+ const Region &getRegion(int idx) const {
+ return _regions[idx];
+ }
};
} // namespace PhoenixVR
Commit: 578a5b080ea159fbe569e8b13b461a34714f84f9
https://github.com/scummvm/scummvm/commit/578a5b080ea159fbe569e8b13b461a34714f84f9
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:25+01:00
Commit Message:
PHOENIXVR: use g_engine
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index bb0a678521f..7cf1efab3cb 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -25,7 +25,7 @@ struct MultiCD_Set_Next_Script : public Script::Command {
MultiCD_Set_Next_Script(const Common::Array<Common::String> &args) : filename(args[0]) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("MultiCD_Set_Next_Script %s", filename.c_str());
- ctx.engine->setNextScript(filename);
+ g_engine->setNextScript(filename);
}
};
@@ -161,10 +161,10 @@ struct Cmp : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
debug("cmp %s %s %s %s %d", var.c_str(), negativeVar.c_str(), arg0.c_str(), op.c_str(), arg1);
if (op == "==") {
- auto value0 = ctx.engine->getVariable(arg0);
+ auto value0 = g_engine->getVariable(arg0);
bool r = value0 == arg1;
- ctx.engine->setVariable(var, r);
- ctx.engine->setVariable(negativeVar, !r);
+ g_engine->setVariable(var, r);
+ g_engine->setVariable(negativeVar, !r);
} else {
error("invalid cmp op %s", op.c_str());
}
@@ -308,10 +308,9 @@ Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<
struct IfAnd : public Script::Conditional {
using Script::Conditional::Conditional;
void exec(Script::ExecutionContext &ctx) const override {
- auto *engine = ctx.engine;
bool result = true;
for (auto &var : vars) {
- auto value = engine->getVariable(var);
+ auto value = g_engine->getVariable(var);
debug("ifand, %s: %d", var.c_str(), value);
if (!value)
result = false;
@@ -326,10 +325,9 @@ struct IfAnd : public Script::Conditional {
struct IfOr : public Script::Conditional {
using Script::Conditional::Conditional;
void exec(Script::ExecutionContext &ctx) const override {
- auto *engine = ctx.engine;
bool result = false;
for (auto &var : vars) {
- auto value = engine->getVariable(var);
+ auto value = g_engine->getVariable(var);
debug("ifor, %s: %d", var.c_str(), value);
if (value)
result = true;
@@ -347,7 +345,7 @@ struct Set : public Script::Command {
Set(Common::String n, int v) : name(Common::move(n)), value(v) {}
void exec(Script::ExecutionContext &ctx) const override {
- ctx.engine->setVariable(name, value);
+ g_engine->setVariable(name, value);
}
};
@@ -383,7 +381,7 @@ struct SetCursor : public Script::Command {
SetCursor(Common::String f, Common::String w, int i) : fname(Common::move(f)), wname(Common::move(w)), idx(i) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("setcursor %s %s:%d", fname.c_str(), wname.c_str(), idx);
- auto warp = ctx.engine->getCurrentWarp();
+ auto warp = g_engine->getCurrentWarp();
if (!warp || !warp->vrFile.equalsIgnoreCase(wname))
error("setting cursor for different warp, active: %s, required: %s", warp ? warp->vrFile.c_str() : "null", wname.c_str());
auto reg = g_engine->getRegion(idx);
@@ -449,7 +447,7 @@ struct GoToWarp : public Script::Command {
GoToWarp(Common::String w) : warp(Common::move(w)) {}
void exec(Script::ExecutionContext &ctx) const override {
- ctx.engine->goToWarp(warp);
+ g_engine->goToWarp(warp);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 681af9214ce..133e9a6c090 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -167,7 +167,7 @@ Common::Error PhoenixVREngine::run() {
debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
_regSet.reset(new RegionSet(_warp->testFile));
- Script::ExecutionContext ctx{this, true};
+ Script::ExecutionContext ctx;
debug("execute warp script %s", _warp->vrFile.c_str());
auto &test = _warp->getDefaultTest();
test->scope.exec(ctx);
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index bf45737f16b..f707c5646c6 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -33,13 +33,10 @@ class SeekableReadStream;
namespace PhoenixVR {
-class PhoenixVREngine;
-
class Script {
public:
struct ExecutionContext {
- PhoenixVREngine *engine;
- bool running;
+ bool running = true;
};
struct Command {
virtual ~Command() = default;
Commit: db8106f1c934c8e7983137f377c72034c2c92107
https://github.com/scummvm/scummvm/commit/db8106f1c934c8e7983137f377c72034c2c92107
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:25+01:00
Commit Message:
PHOENIXVR: implement mouse cursors
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/region_set.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 7cf1efab3cb..0770462bef7 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -370,7 +370,7 @@ struct SetCursorDefault : public Script::Command {
Common::String fname;
SetCursorDefault(int i, Common::String f) : idx(i), fname(Common::move(f)) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursordefault %d, %s", idx, fname.c_str());
+ g_engine->setCursorDefault(idx, fname);
}
};
@@ -380,12 +380,7 @@ struct SetCursor : public Script::Command {
int idx;
SetCursor(Common::String f, Common::String w, int i) : fname(Common::move(f)), wname(Common::move(w)), idx(i) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursor %s %s:%d", fname.c_str(), wname.c_str(), idx);
- auto warp = g_engine->getCurrentWarp();
- if (!warp || !warp->vrFile.equalsIgnoreCase(wname))
- error("setting cursor for different warp, active: %s, required: %s", warp ? warp->vrFile.c_str() : "null", wname.c_str());
- auto reg = g_engine->getRegion(idx);
- debug("region: %g %g %g %g", reg.a, reg.b, reg.c, reg.d);
+ g_engine->setCursor(fname, wname, idx);
}
};
@@ -394,7 +389,7 @@ struct HideCursor : public Script::Command {
int idx;
HideCursor(Common::String w, int i) : warp(Common::move(w)), idx(i) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("setcursor %s:%d", warp.c_str(), idx);
+ g_engine->hideCursor(warp, idx);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 133e9a6c090..1714006d322 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -29,6 +29,7 @@
#include "engines/util.h"
#include "graphics/framelimiter.h"
#include "graphics/paletteman.h"
+#include "image/pcx.h"
#include "phoenixvr/console.h"
#include "phoenixvr/detection.h"
#include "phoenixvr/pakf.h"
@@ -42,7 +43,7 @@ PhoenixVREngine *g_engine;
PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
- _pixelFormat(Graphics::PixelFormat::createFormatRGB24()) {
+ _pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()) {
g_engine = this;
auto path = Common::FSNode(ConfMan.getPath("path"));
SearchMan.addSubDirectoryMatching(path, "NecroES/Data", 1, 1, true);
@@ -94,8 +95,29 @@ Region PhoenixVREngine::getRegion(int idx) const {
return _regSet->getRegion(idx);
}
-void PhoenixVREngine::setCursorDefault(uint idx, const Common::String &path) {
- debug("setCursorDefault %u: %s", idx, path.c_str());
+void PhoenixVREngine::setCursorDefault(int idx, const Common::String &path) {
+ debug("setCursorDefault %d: %s", idx, path.c_str());
+ if (idx == 0) {
+ _defaultCursor.free();
+ _defaultCursor.surface = loadSurface(path);
+ }
+}
+
+void PhoenixVREngine::setCursor(const Common::String &path, const Common::String &wname, int idx) {
+ auto reg = g_engine->getRegion(idx);
+ auto &cursor = _cursors[idx];
+ auto rect = reg.toRect();
+ debug("cursor region %s:%d: %s, %s", wname.c_str(), idx, rect.toString().c_str(), path.c_str());
+ if (!_warp || !_warp->vrFile.equalsIgnoreCase(wname))
+ error("setting cursor for different warp, active: %s, required: %s", _warp ? _warp->vrFile.c_str() : "null", wname.c_str());
+ cursor.free();
+ cursor.surface = loadSurface(path);
+ cursor.rect = rect;
+}
+
+void PhoenixVREngine::hideCursor(const Common::String &warp, int idx) {
+ debug("hide cursor %s:%d", warp.c_str(), idx);
+ _cursors[idx].free();
}
void PhoenixVREngine::declareVariable(const Common::String &name) {
@@ -111,6 +133,32 @@ int PhoenixVREngine::getVariable(const Common::String &name) const {
return _variables.getVal(name);
}
+Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
+ Common::File file;
+ if (!file.open(Common::Path(path))) {
+ warning("can't find %s", path.c_str());
+ return nullptr;
+ }
+ if (path.hasSuffix(".pcx")) {
+ Image::PCXDecoder pcx;
+ if (pcx.loadStream(file))
+ return pcx.getSurface()->convertTo(_pixelFormat, pcx.hasPalette() ? pcx.getPalette().data() : nullptr);
+ warning("pcx decode failed on %s", path.c_str());
+ return nullptr;
+ }
+ warning("can't find decoder for %s", path.c_str());
+ return nullptr;
+}
+
+void PhoenixVREngine::Cursor::free() {
+ if (surface) {
+ surface->free();
+ delete surface;
+ surface = nullptr;
+ }
+ rect.setEmpty();
+}
+
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
@@ -133,11 +181,31 @@ Common::Error PhoenixVREngine::run() {
if (saveSlot != -1)
(void)loadGameState(saveSlot);
- Common::Event e;
+ Common::Event event;
Graphics::FrameLimiter limiter(g_system, 60);
while (!shouldQuit()) {
- while (g_system->getEventManager()->pollEvent(e)) {
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+ case Common::EVENT_LBUTTONUP:
+ for (uint i = 0; i != _cursors.size(); ++i) {
+ auto &c = _cursors[i];
+ if (c.rect.contains(event.mouse)) {
+ debug("click region %u", i);
+ auto test = _warp->getTest(i);
+ if (test) {
+ Script::ExecutionContext ctx;
+ test->scope.exec(ctx);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
}
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
@@ -167,12 +235,31 @@ Common::Error PhoenixVREngine::run() {
debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
_regSet.reset(new RegionSet(_warp->testFile));
+ for (auto &c : _cursors)
+ c.free();
+ _cursors.resize(_regSet->size());
+
Script::ExecutionContext ctx;
debug("execute warp script %s", _warp->vrFile.c_str());
auto &test = _warp->getDefaultTest();
test->scope.exec(ctx);
}
+ _screen->clear();
+ Graphics::Surface *cursor = nullptr;
+ for (auto &c : _cursors) {
+ if (c.rect.contains(_mousePos)) {
+ cursor = c.surface;
+ if (cursor)
+ break;
+ }
+ }
+ if (!cursor)
+ cursor = _defaultCursor.surface;
+ if (cursor) {
+ paint(*cursor, _mousePos);
+ }
+
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
limiter.delayBeforeSwap();
@@ -183,6 +270,13 @@ Common::Error PhoenixVREngine::run() {
return Common::kNoError;
}
+void PhoenixVREngine::paint(Graphics::Surface &src, Common::Point dst) {
+ Common::Rect srcRect = src.getRect();
+ Common::Rect clip(0, 0, _screen->w, _screen->h);
+ if (Common::Rect::getBlitRect(dst, srcRect, clip))
+ _screen->copyRectToSurface(src, dst.x, dst.y, srcRect);
+}
+
Common::Error PhoenixVREngine::syncGame(Common::Serializer &s) {
// The Serializer has methods isLoading() and isSaving()
// if you need to specific steps; for example setting
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 88860d8722c..aeb94cb2987 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -104,7 +104,9 @@ public:
// Script API
void setNextScript(const Common::String &path);
void goToWarp(const Common::String &warp);
- void setCursorDefault(uint idx, const Common::String &path);
+ void setCursorDefault(int idx, const Common::String &path);
+ void setCursor(const Common::String &path, const Common::String &warp, int idx);
+ void hideCursor(const Common::String &warp, int idx);
void declareVariable(const Common::String &name);
void setVariable(const Common::String &name, int value);
@@ -117,8 +119,11 @@ public:
private:
static Common::String removeDrive(const Common::String &path);
static Common::String resolvePath(const Common::String &path);
+ Graphics::Surface *loadSurface(const Common::String &path);
+ void paint(Graphics::Surface &src, Common::Point dst);
private:
+ Common::Point _mousePos;
Common::String _nextScript;
Common::String _nextWarp;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
@@ -126,6 +131,14 @@ private:
Script::ConstWarpPtr _warp;
Common::ScopedPtr<RegionSet> _regSet;
+
+ struct Cursor {
+ Common::Rect rect;
+ Graphics::Surface *surface = nullptr;
+ void free();
+ };
+ Common::Array<Cursor> _cursors;
+ Cursor _defaultCursor;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index 288ec20b490..be292e658d0 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -41,4 +41,13 @@ RegionSet::RegionSet(const Common::String &fname) {
}
}
+Common::Rect Region::toRect() const {
+ Common::Rect rect;
+ rect.left = static_cast<short>(MIN(a, b));
+ rect.right = static_cast<short>(MAX(a, b));
+ rect.top = static_cast<short>(MIN(c, d));
+ rect.bottom = static_cast<short>(MAX(c, d));
+ return rect;
+}
+
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index 89680aa5df1..365337a9b3b 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -23,6 +23,7 @@
#define PHOENIXVR_REGIONS_H
#include "common/array.h"
+#include "common/rect.h"
namespace Common {
class String;
@@ -31,6 +32,7 @@ class String;
namespace PhoenixVR {
struct Region {
float a, b, c, d;
+ Common::Rect toRect() const;
};
class RegionSet {
@@ -38,6 +40,7 @@ class RegionSet {
public:
RegionSet(const Common::String &fname);
+ uint size() const { return _regions.size(); }
const Region &getRegion(int idx) const {
return _regions[idx];
}
Commit: 24e84b670c3b057b0461d773408e21421aab4dfb
https://github.com/scummvm/scummvm/commit/24e84b670c3b057b0461d773408e21421aab4dfb
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:25+01:00
Commit Message:
PHOENIXVR: add test slot stub
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 0770462bef7..e3f470ad190 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -203,6 +203,8 @@ struct LoadSave_Test_Slot : public Script::Command {
LoadSave_Test_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())), show(args[1]), hide(args[2]) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
+ g_engine->setVariable(show, 0);
+ g_engine->setVariable(hide, 1);
}
};
Commit: bc75ca086b25045bcb02c013920cecd987f8d205
https://github.com/scummvm/scummvm/commit/bc75ca086b25045bcb02c013920cecd987f8d205
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:26+01:00
Commit Message:
PHOENIXVR: ignore cursor for different vr
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1714006d322..c90a6dbd319 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -108,8 +108,10 @@ void PhoenixVREngine::setCursor(const Common::String &path, const Common::String
auto &cursor = _cursors[idx];
auto rect = reg.toRect();
debug("cursor region %s:%d: %s, %s", wname.c_str(), idx, rect.toString().c_str(), path.c_str());
- if (!_warp || !_warp->vrFile.equalsIgnoreCase(wname))
- error("setting cursor for different warp, active: %s, required: %s", _warp ? _warp->vrFile.c_str() : "null", wname.c_str());
+ if (!_warp || !_warp->vrFile.equalsIgnoreCase(wname)) {
+ warning("setting cursor for different warp, active: %s, required: %s", _warp ? _warp->vrFile.c_str() : "null", wname.c_str());
+ return;
+ }
cursor.free();
cursor.surface = loadSurface(path);
cursor.rect = rect;
Commit: bac5e4d0b905d4900c13c7b51998a90d95b075f1
https://github.com/scummvm/scummvm/commit/bac5e4d0b905d4900c13c7b51998a90d95b075f1
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:26+01:00
Commit Message:
PHOENIXVR: implement VR unpacker
Changed paths:
A engines/phoenixvr/vr.cpp
A engines/phoenixvr/vr.h
engines/phoenixvr/commands.h
engines/phoenixvr/module.mk
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index e3f470ad190..151741b655c 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -1,3 +1,24 @@
+/* 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/>.
+ *
+ */
+
#ifndef PHOENIXVR_COMMANDS_H
#define PHOENIXVR_COMMANDS_H
diff --git a/engines/phoenixvr/module.mk b/engines/phoenixvr/module.mk
index 1f862bbd514..b841e207140 100644
--- a/engines/phoenixvr/module.mk
+++ b/engines/phoenixvr/module.mk
@@ -6,7 +6,8 @@ MODULE_OBJS = \
console.o \
metaengine.o \
region_set.o \
- script.o
+ script.o \
+ vr.o
# This module can be built as a plugin
ifeq ($(ENABLE_PHOENIXVR), DYNAMIC_PLUGIN)
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index c90a6dbd319..9698960eedb 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -35,6 +35,7 @@
#include "phoenixvr/pakf.h"
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
+#include "phoenixvr/vr.h"
namespace PhoenixVR {
@@ -234,7 +235,20 @@ Common::Error PhoenixVREngine::run() {
if (!_nextWarp.empty()) {
_warp = _script->getWarp(_nextWarp);
_nextWarp.clear();
+
debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
+
+ if (_static640x480) {
+ _static640x480->free();
+ delete _static640x480;
+ _static640x480 = nullptr;
+ }
+
+ Common::File vr;
+ if (vr.open(Common::Path(_warp->vrFile))) {
+ _static640x480 = VR::load640x480(_pixelFormat, vr);
+ }
+
_regSet.reset(new RegionSet(_warp->testFile));
for (auto &c : _cursors)
@@ -247,7 +261,11 @@ Common::Error PhoenixVREngine::run() {
test->scope.exec(ctx);
}
- _screen->clear();
+ if (_static640x480)
+ _screen->copyFrom(*_static640x480);
+ else
+ _screen->clear();
+
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
if (c.rect.contains(_mousePos)) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index aeb94cb2987..9b5ddd7182d 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -139,6 +139,7 @@ private:
};
Common::Array<Cursor> _cursors;
Cursor _defaultCursor;
+ Graphics::Surface *_static640x480 = nullptr;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index f9799e3930c..951000941fb 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -240,9 +240,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
case '[': {
p.next();
if (p.maybe("bool]=")) {
- auto name = p.nextWord();
- // FIXME: pass engine here?
- g_engine->declareVariable(name);
+ g_engine->declareVariable(p.nextWord());
} else if (p.maybe("warp]=")) {
auto vr = p.nextWord();
p.expect(',');
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
new file mode 100644
index 00000000000..a063d9c048e
--- /dev/null
+++ b/engines/phoenixvr/vr.cpp
@@ -0,0 +1,200 @@
+#include "phoenixvr/vr.h"
+#include "common/array.h"
+#include "common/debug.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+
+namespace PhoenixVR {
+
+#define CHUNK_VR (0x12fa84ab)
+#define CHUNK_STATIC (0xa0b1c400)
+
+namespace {
+const byte ZIGZAG[] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4,
+ 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7,
+ 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22,
+ 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39,
+ 46, 53, 60, 61, 54, 47, 55, 62, 63};
+
+struct HuffChar {
+ short next;
+ short falseIdx;
+ short trueIdx;
+};
+
+class BitStream {
+ const byte *_data;
+ uint _bytePos;
+ byte _bitMask;
+
+public:
+ BitStream(const byte *data, uint bytePos) : _data(data), _bytePos(bytePos), _bitMask(0x80) {}
+
+ bool readBit() {
+ bool bit = _data[_bytePos] & _bitMask;
+ _bitMask >>= 1;
+ if (_bitMask == 0) {
+ _bitMask = 128;
+ ++_bytePos;
+ }
+ return bit;
+ }
+
+ int readUInt(byte n) {
+ int value = 0;
+ for (int i = 0; i != n; ++i) {
+ if (readBit())
+ value |= 1 << i;
+ }
+ return value;
+ }
+
+ int readInt(byte n) {
+ int value = readUInt(n);
+ if ((value & (1 << (n - 1))) == 0)
+ value += 1 - (1 << n);
+ return value;
+ }
+
+ void alignToByte() {
+ if (_bitMask != 0x80) {
+ _bitMask = 128;
+ ++_bytePos;
+ }
+ }
+};
+
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
+ HuffChar table[514] = {};
+ uint offset = 0;
+ uint8 codebyte = huff[offset++];
+ do {
+ uint8 next = huff[offset++];
+ if (codebyte <= next) {
+ for (auto idx = codebyte; idx <= next; ++idx) {
+ table[idx].next = huff[offset++];
+ }
+ }
+ codebyte = huff[offset++];
+ } while (codebyte != 0);
+ table[256].next = 1;
+ table[513].next = 0x7FFF;
+
+ short startEntry;
+ short codeIdx = 257, nIdx = 257;
+ while (true) {
+ short idx = 0, dstIdx = 0;
+ short trueIdx = 513, falseIdx = 513;
+ short nextLo = 513, nextHi = 513;
+ while (idx < nIdx) {
+ auto next = table[dstIdx].next;
+ if (next != 0) {
+ if (next >= table[nextLo].next) {
+ if (next < table[nextHi].next) {
+ trueIdx = idx;
+ nextHi = dstIdx;
+ }
+ } else {
+ trueIdx = falseIdx;
+ nextHi = nextLo;
+ falseIdx = idx;
+ nextLo = dstIdx;
+ }
+ }
+ ++idx;
+ ++dstIdx;
+ }
+ if (trueIdx == 513) {
+ startEntry = nIdx - 1;
+ break;
+ }
+ table[codeIdx].next = table[falseIdx].next + table[trueIdx].next;
+ table[falseIdx].next = table[trueIdx].next = 0;
+ table[codeIdx].falseIdx = falseIdx;
+ table[codeIdx].trueIdx = trueIdx;
+ ++codeIdx;
+ ++nIdx;
+ }
+ Common::Array<byte> decoded;
+ decoded.reserve(huffSize);
+ {
+ BitStream bs(huff, offset);
+ while (true) {
+ short value = startEntry;
+ while (value > 256) {
+ auto bit = bs.readBit();
+ if (bit)
+ value = table[value].trueIdx;
+ else
+ value = table[value].falseIdx;
+ }
+ if (value == 256)
+ break;
+ decoded.push_back(static_cast<byte>(value));
+ }
+ }
+ debug("decoded %u bytes", decoded.size());
+ return decoded;
+}
+
+void unpack640x480(Graphics::Surface &s, const byte *huff, uint huffSize, const byte *coeff, uint dataSize) {
+ auto decoded = unpackHuffman(huff, huffSize);
+ uint decodedOffset = 0;
+ BitStream bs(coeff, 0);
+ while (decodedOffset < decoded.size()) {
+ short ac[64] = {};
+ ac[0] = bs.readUInt(8);
+ for (uint idx = 1; idx < 64;) {
+ auto b = decoded[decodedOffset++];
+ if (b == 0x00) {
+ break;
+ } else if (b == 0xf0) {
+ idx += 16;
+ } else {
+ auto h = b >> 4;
+ auto l = b & 0x0f;
+ idx += h;
+ if (l && idx < 64) {
+ ac[ZIGZAG[idx++]] = bs.readInt(l);
+ }
+ }
+ }
+ Common::String str;
+ for (uint i = 0; i != 64; ++i)
+ str += Common::String::format("%d ", ac[i]);
+ debug("decoded block %s", str.c_str());
+ }
+}
+} // namespace
+
+Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
+ auto magic = s.readUint32LE();
+ if (magic != CHUNK_VR) {
+ error("wrong VR magic");
+ }
+ auto fsize = s.readUint32LE();
+ debug("file size = %08x", fsize);
+ while (s.pos() < fsize) {
+ auto chunkId = s.readUint32LE();
+ auto chunkSize = s.readUint32LE();
+ debug("chunk %08x %u", chunkId, chunkSize);
+ if (chunkId == CHUNK_STATIC) {
+ auto unk0 = s.readUint32LE();
+ auto dataSize = s.readUint32LE();
+ auto coefOffset = s.readUint32LE();
+ auto unpHuffSize = s.readUint32LE();
+ debug("static picture header %u packed data size: %u ac/dc offset: %08x unpacked huff size: %u", unk0, dataSize, coefOffset, unpHuffSize);
+ Common::Array<byte> staticData(chunkSize - 16 - 8);
+ s.read(staticData.data(), staticData.size());
+ Graphics::Surface *pic = new Graphics::Surface();
+ pic->create(640, 480, format);
+ unpack640x480(*pic, staticData.data(), unpHuffSize, staticData.data() + coefOffset - 4, staticData.size());
+ return pic;
+ }
+ s.skip(chunkSize - 8);
+ }
+ return nullptr;
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
new file mode 100644
index 00000000000..e5fd337814d
--- /dev/null
+++ b/engines/phoenixvr/vr.h
@@ -0,0 +1,39 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_COMMANDS_H
+#define PHOENIXVR_COMMANDS_H
+
+#include "common/stream.h"
+#include "graphics/pixelformat.h"
+
+namespace Graphics {
+class Surface;
+}
+
+namespace PhoenixVR {
+class VR {
+public:
+ static Graphics::Surface *load640x480(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
+};
+} // namespace PhoenixVR
+
+#endif
Commit: 431fca932afd21d75d06b446ecdd3d03751b156c
https://github.com/scummvm/scummvm/commit/431fca932afd21d75d06b446ecdd3d03751b156c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:26+01:00
Commit Message:
PHOENIXVR: decode vr and convert yuv to rgb
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9698960eedb..5b0ff56de1a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -277,7 +277,7 @@ Common::Error PhoenixVREngine::run() {
if (!cursor)
cursor = _defaultCursor.surface;
if (cursor) {
- paint(*cursor, _mousePos);
+ paint(*cursor, _mousePos - Common::Point(cursor->w / 2, cursor->h / 2));
}
// Delay for a bit. All events loops should have a delay
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index a063d9c048e..967aacb755c 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -3,6 +3,8 @@
#include "common/debug.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
+#include "graphics/yuv_to_rgb.h"
+#include "math/dct.h"
namespace PhoenixVR {
@@ -31,6 +33,10 @@ class BitStream {
public:
BitStream(const byte *data, uint bytePos) : _data(data), _bytePos(bytePos), _bitMask(0x80) {}
+ uint getBytePos() const {
+ return _bytePos;
+ }
+
bool readBit() {
bool bit = _data[_bytePos] & _bitMask;
_bitMask >>= 1;
@@ -133,18 +139,28 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
break;
decoded.push_back(static_cast<byte>(value));
}
+ bs.alignToByte();
+ offset = bs.getBytePos();
}
- debug("decoded %u bytes", decoded.size());
+ debug("decoded %u bytes at %08x", decoded.size(), offset);
return decoded;
}
-void unpack640x480(Graphics::Surface &s, const byte *huff, uint huffSize, const byte *coeff, uint dataSize) {
+void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *coeff, uint dataSize) {
auto decoded = unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
+ static const Math::DCT dct(6, Math::DCT::DCT_III);
+
+ static constexpr uint planePitch = 640;
+ static constexpr uint planeSize = planePitch * 480;
+ Common::Array<byte> planes(planeSize * 3, 0);
+
BitStream bs(coeff, 0);
+ uint channel = 0;
+ uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
- short ac[64] = {};
- ac[0] = bs.readUInt(8);
+ float ac[64] = {};
+ ac[0] = 1.0f * bs.readUInt(8);
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
@@ -156,15 +172,46 @@ void unpack640x480(Graphics::Surface &s, const byte *huff, uint huffSize, const
auto l = b & 0x0f;
idx += h;
if (l && idx < 64) {
- ac[ZIGZAG[idx++]] = bs.readInt(l);
+ ac[ZIGZAG[idx]] = 1024 * bs.readInt(l);
+ ++idx;
}
}
}
+ dct.calc(ac);
Common::String str;
for (uint i = 0; i != 64; ++i)
- str += Common::String::format("%d ", ac[i]);
+ str += Common::String::format("%g ", ac[i]);
+ debug("decoded block %s", str.c_str());
+
+ debug("block at %d,%d %d", x0, y0, channel);
+ auto *dst = planes.data() + channel++ * planeSize + y0 * planePitch + x0;
+ if (channel == 3) {
+ channel = 0;
+ x0 += 8;
+ if (x0 >= 640) {
+ x0 = 0;
+ y0 += 8;
+ }
+ }
+ const auto *src = ac;
+ str.clear();
+ for (unsigned h = 8; h--; dst += planePitch - 8) {
+ for (unsigned w = 8; w--;) {
+ int v = *src++;
+ if (v < 0)
+ v = 0;
+ if (v > 255)
+ v = 255;
+ str += Common::String::format("%d ", v);
+ *dst++ = v;
+ }
+ }
debug("decoded block %s", str.c_str());
}
+ auto *y = planes.data();
+ auto *u = y + planeSize;
+ auto *v = u + planeSize;
+ YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull, y, u, v, 640, 480, planePitch, planePitch);
}
} // namespace
@@ -189,7 +236,7 @@ Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::
s.read(staticData.data(), staticData.size());
Graphics::Surface *pic = new Graphics::Surface();
pic->create(640, 480, format);
- unpack640x480(*pic, staticData.data(), unpHuffSize, staticData.data() + coefOffset - 4, staticData.size());
+ unpack640x480(*pic, staticData.data(), unpHuffSize, staticData.data() + coefOffset, staticData.size());
return pic;
}
s.skip(chunkSize - 8);
Commit: ee328d3ed7c21fb719747fbfe03b013275c99dba
https://github.com/scummvm/scummvm/commit/ee328d3ed7c21fb719747fbfe03b013275c99dba
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:26+01:00
Commit Message:
PHOENIXVR: more RE on picture compression
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 967aacb755c..473501cb481 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -19,6 +19,208 @@ const byte ZIGZAG[] = {
15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39,
46, 53, 60, 61, 54, 47, 55, 62, 63};
+// looks like standard JPEG quantisation matrix
+const char QY[] = {
+ 16,
+ 11,
+ 10,
+ 16,
+ 24,
+ 40,
+ 51,
+ 61,
+ 12,
+ 12,
+ 14,
+ 19,
+ 26,
+ 58,
+ 60,
+ 55,
+ 14,
+ 13,
+ 16,
+ 24,
+ 40,
+ 57,
+ 69,
+ 56,
+ 14,
+ 17,
+ 22,
+ 29,
+ 51,
+ 87,
+ 80,
+ 62,
+ 18,
+ 22,
+ 37,
+ 56,
+ 68,
+ 109,
+ 103,
+ 77,
+ 24,
+ 35,
+ 55,
+ 64,
+ 81,
+ 104,
+ 113,
+ 92,
+ 49,
+ 64,
+ 78,
+ 87,
+ 103,
+ 121,
+ 120,
+ 101,
+ 72,
+ 92,
+ 95,
+ 98,
+ 112,
+ 100,
+ 103,
+ 99,
+};
+
+const char QUV[] = {
+ 17,
+ 18,
+ 24,
+ 47,
+ 99,
+ 99,
+ 99,
+ 99,
+ 18,
+ 21,
+ 26,
+ 66,
+ 99,
+ 99,
+ 99,
+ 99,
+ 24,
+ 26,
+ 56,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 47,
+ 66,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+};
+
+const uint16 Q[] = {
+ 0x4000,
+ 0x58C5,
+ 0x539F,
+ 0x4B42,
+ 0x4000,
+ 0x3249,
+ 0x22A3,
+ 0x11A8,
+ 0x58C5,
+ 0x7B21,
+ 0x73FC,
+ 0x6862,
+ 0x58C5,
+ 0x45BF,
+ 0x300B,
+ 0x187E,
+ 0x539F,
+ 0x73FC,
+ 0x6D41,
+ 0x6254,
+ 0x539F,
+ 0x41B3,
+ 0x2D41,
+ 0x1712,
+ 0x4B42,
+ 0x6862,
+ 0x6254,
+ 0x587E,
+ 0x4B42,
+ 0x3B21,
+ 0x28BA,
+ 0x14C3,
+ 0x4000,
+ 0x58C5,
+ 0x539F,
+ 0x4B42,
+ 0x4000,
+ 0x3249,
+ 0x22A3,
+ 0x11A8,
+ 0x3249,
+ 0x45BF,
+ 0x41B3,
+ 0x3B21,
+ 0x3249,
+ 0x2782,
+ 0x1B37,
+ 0x0DE0,
+ 0x22A3,
+ 0x300B,
+ 0x2D41,
+ 0x28BA,
+ 0x22A3,
+ 0x1B37,
+ 0x12BF,
+ 0x98E,
+ 0x11A8,
+ 0x187E,
+ 0x1712,
+ 0x14C3,
+ 0x11A8,
+ 0x0DE0,
+ 0x98E,
+ 0x4DF,
+};
+
struct HuffChar {
short next;
short falseIdx;
@@ -146,7 +348,7 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
return decoded;
}
-void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *coeff, uint dataSize) {
+void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *coeff, uint coefSize, int quality) {
auto decoded = unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
static const Math::DCT dct(6, Math::DCT::DCT_III);
@@ -155,12 +357,25 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
static constexpr uint planeSize = planePitch * 480;
Common::Array<byte> planes(planeSize * 3, 0);
+ auto iquant = [&](uint channel, int idx) {
+ int v = ((channel == 0 ? QY : QUV)[idx] * quality + 50) / 100;
+ if (v > 255)
+ v = 255;
+ else if (v < 8) {
+ v = 8;
+ }
+
+ v *= Q[idx];
+ v >>= 13;
+ return v;
+ };
+
BitStream bs(coeff, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
float ac[64] = {};
- ac[0] = 1.0f * bs.readUInt(8);
+ ac[0] = 1.0f * iquant(channel, 0) * bs.readUInt(8);
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
@@ -172,13 +387,18 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
auto l = b & 0x0f;
idx += h;
if (l && idx < 64) {
- ac[ZIGZAG[idx]] = 1024 * bs.readInt(l);
+ auto ac_idx = ZIGZAG[idx];
+ ac[ac_idx] = 1.0f * iquant(channel, ac_idx) * bs.readInt(l);
++idx;
}
}
}
- dct.calc(ac);
Common::String str;
+ for (uint i = 0; i != 64; ++i)
+ str += Common::String::format("%g ", ac[i]);
+ debug("input block %s", str.c_str());
+ str.clear();
+ dct.calc(ac);
for (uint i = 0; i != 64; ++i)
str += Common::String::format("%g ", ac[i]);
debug("decoded block %s", str.c_str());
@@ -194,19 +414,19 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
}
}
const auto *src = ac;
- str.clear();
+ // str.clear();
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++;
+ int v = (channel == 0 ? 128 : 0) + *src++;
if (v < 0)
v = 0;
if (v > 255)
v = 255;
- str += Common::String::format("%d ", v);
+ // str += Common::String::format("%d ", v);
*dst++ = v;
}
}
- debug("decoded block %s", str.c_str());
+ // debug("decoded block %s", str.c_str());
}
auto *y = planes.data();
auto *u = y + planeSize;
@@ -236,7 +456,8 @@ Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::
s.read(staticData.data(), staticData.size());
Graphics::Surface *pic = new Graphics::Surface();
pic->create(640, 480, format);
- unpack640x480(*pic, staticData.data(), unpHuffSize, staticData.data() + coefOffset, staticData.size());
+ coefOffset += 8;
+ unpack640x480(*pic, staticData.data(), unpHuffSize, staticData.data() + coefOffset, staticData.size() - coefOffset, unk0);
return pic;
}
s.skip(chunkSize - 8);
Commit: 97679de76aaf07ea2e5c93ee5d5eb7ae6c30062e
https://github.com/scummvm/scummvm/commit/97679de76aaf07ea2e5c93ee5d5eb7ae6c30062e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:27+01:00
Commit Message:
PHOENIXVR: add colorkey for cursor bitmaps
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 5b0ff56de1a..e5a008b558b 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -144,8 +144,16 @@ Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
}
if (path.hasSuffix(".pcx")) {
Image::PCXDecoder pcx;
- if (pcx.loadStream(file))
- return pcx.getSurface()->convertTo(_pixelFormat, pcx.hasPalette() ? pcx.getPalette().data() : nullptr);
+ if (pcx.loadStream(file)) {
+ auto *s = pcx.getSurface()->convertTo(_pixelFormat, pcx.hasPalette() ? pcx.getPalette().data() : nullptr);
+ if (s) {
+ byte r = 0, g = 0, b = 0;
+ if (pcx.hasPalette())
+ pcx.getPalette().get(0, r, g, b);
+ s->applyColorKey(r, g, b);
+ }
+ return s;
+ }
warning("pcx decode failed on %s", path.c_str());
return nullptr;
}
@@ -293,8 +301,10 @@ Common::Error PhoenixVREngine::run() {
void PhoenixVREngine::paint(Graphics::Surface &src, Common::Point dst) {
Common::Rect srcRect = src.getRect();
Common::Rect clip(0, 0, _screen->w, _screen->h);
- if (Common::Rect::getBlitRect(dst, srcRect, clip))
- _screen->copyRectToSurface(src, dst.x, dst.y, srcRect);
+ if (Common::Rect::getBlitRect(dst, srcRect, clip)) {
+ Common::Rect dstRect(dst.x, dst.y, dst.x + srcRect.width(), dst.y + srcRect.height());
+ _screen->blendBlitFrom(src, srcRect, dstRect);
+ }
}
Common::Error PhoenixVREngine::syncGame(Common::Serializer &s) {
Commit: ccf6d6961d136b49f30184c8ac2a1ddec8c5ba9b
https://github.com/scummvm/scummvm/commit/ccf6d6961d136b49f30184c8ac2a1ddec8c5ba9b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:27+01:00
Commit Message:
PHOENIXVR: fix dc coefficient pointer
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 473501cb481..795e7169d04 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -348,7 +348,7 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
return decoded;
}
-void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *coeff, uint coefSize, int quality) {
+void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
auto decoded = unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
static const Math::DCT dct(6, Math::DCT::DCT_III);
@@ -370,12 +370,12 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
return v;
};
- BitStream bs(coeff, 0);
+ BitStream acBs(acPtr, 0), dcBs(dcPtr, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
float ac[64] = {};
- ac[0] = 1.0f * iquant(channel, 0) * bs.readUInt(8);
+ ac[0] = 1.0f * iquant(channel, 0) * dcBs.readUInt(8);
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
@@ -388,7 +388,7 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
idx += h;
if (l && idx < 64) {
auto ac_idx = ZIGZAG[idx];
- ac[ac_idx] = 1.0f * iquant(channel, ac_idx) * bs.readInt(l);
+ ac[ac_idx] = 1.0f * iquant(channel, ac_idx) * acBs.readInt(l);
++idx;
}
}
@@ -417,7 +417,7 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
// str.clear();
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = (channel == 0 ? 128 : 0) + *src++;
+ int v = *src++;
if (v < 0)
v = 0;
if (v > 255)
@@ -456,8 +456,11 @@ Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::
s.read(staticData.data(), staticData.size());
Graphics::Surface *pic = new Graphics::Surface();
pic->create(640, 480, format);
+ auto dcOffset = READ_LE_UINT32(staticData.data() + coefOffset);
coefOffset += 8;
- unpack640x480(*pic, staticData.data(), unpHuffSize, staticData.data() + coefOffset, staticData.size() - coefOffset, unk0);
+ auto *acPtr = staticData.data() + coefOffset;
+ auto *dcPtr = acPtr + dcOffset;
+ unpack640x480(*pic, staticData.data(), unpHuffSize, acPtr, dcPtr, unk0);
return pic;
}
s.skip(chunkSize - 8);
Commit: 48b439d68b12153792a1f1c7ae318f1f01047767
https://github.com/scummvm/scummvm/commit/48b439d68b12153792a1f1c7ae318f1f01047767
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:27+01:00
Commit Message:
PHOENIXVR: model reader after original code
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 795e7169d04..3c007620d2e 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -447,20 +447,22 @@ Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::
auto chunkSize = s.readUint32LE();
debug("chunk %08x %u", chunkId, chunkSize);
if (chunkId == CHUNK_STATIC) {
- auto unk0 = s.readUint32LE();
+ auto quality = s.readUint32LE();
auto dataSize = s.readUint32LE();
- auto coefOffset = s.readUint32LE();
- auto unpHuffSize = s.readUint32LE();
- debug("static picture header %u packed data size: %u ac/dc offset: %08x unpacked huff size: %u", unk0, dataSize, coefOffset, unpHuffSize);
- Common::Array<byte> staticData(chunkSize - 16 - 8);
- s.read(staticData.data(), staticData.size());
+
+ Common::Array<byte> vrData(dataSize);
+ s.read(vrData.data(), vrData.size());
+
+ auto huffSize = READ_LE_UINT32(vrData.data());
+ auto unpHuffSize = READ_LE_UINT32(vrData.data() + 4);
+ debug("static picture header, quality: %u, packed data size: %u, huff size: %08x, unpacked huff size: %u", quality, dataSize, huffSize, unpHuffSize);
Graphics::Surface *pic = new Graphics::Surface();
pic->create(640, 480, format);
- auto dcOffset = READ_LE_UINT32(staticData.data() + coefOffset);
- coefOffset += 8;
- auto *acPtr = staticData.data() + coefOffset;
- auto *dcPtr = acPtr + dcOffset;
- unpack640x480(*pic, staticData.data(), unpHuffSize, acPtr, dcPtr, unk0);
+ auto *huff = vrData.data() + 8;
+ auto *acPtr = vrData.data() + huffSize + 12;
+ auto dcOffset = READ_LE_UINT32(vrData.data() + huffSize + 8);
+ auto *dcPtr = vrData.data() + huffSize + 16 + dcOffset;
+ unpack640x480(*pic, huff, unpHuffSize, acPtr, dcPtr, quality);
return pic;
}
s.skip(chunkSize - 8);
Commit: 0d926b57af12827c515e69821ac8495cedccc91b
https://github.com/scummvm/scummvm/commit/0d926b57af12827c515e69821ac8495cedccc91b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:28+01:00
Commit Message:
PHOENIXVR: use region set to process click
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index e5a008b558b..9e2194b38d2 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -202,9 +202,12 @@ Common::Error PhoenixVREngine::run() {
_mousePos = event.mouse;
break;
case Common::EVENT_LBUTTONUP:
- for (uint i = 0; i != _cursors.size(); ++i) {
- auto &c = _cursors[i];
- if (c.rect.contains(event.mouse)) {
+ debug("click %s", _mousePos.toString().c_str());
+ if (!_regSet)
+ break;
+ for (uint i = 0; i != _regSet->size(); ++i) {
+ auto rect = _regSet->getRegion(i).toRect();
+ if (rect.contains(event.mouse)) {
debug("click region %u", i);
auto test = _warp->getTest(i);
if (test) {
Commit: 31149492fdbe540ea5d80a1d612503f6cbfb3f2c
https://github.com/scummvm/scummvm/commit/31149492fdbe540ea5d80a1d612503f6cbfb3f2c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:28+01:00
Commit Message:
PHOENIXVR: add DCT2DIII class
Changed paths:
A engines/phoenixvr/dct.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/dct.h b/engines/phoenixvr/dct.h
new file mode 100644
index 00000000000..0a239e8a51a
--- /dev/null
+++ b/engines/phoenixvr/dct.h
@@ -0,0 +1,77 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_DCT_H
+#define PHOENIXVR_DCT_H
+
+#include "common/scummsys.h"
+#include <cmath>
+
+namespace PhoenixVR {
+
+template<uint Bits>
+class DCT2DIII {
+ static constexpr uint N = 1 << Bits;
+ static constexpr uint N2 = 1 << (Bits - 1);
+ static constexpr uint N4 = 1 << (Bits - 2);
+
+ float _cos[N2];
+
+ float dctCos(uint idx) const {
+ return _cos[idx % N2];
+ }
+ float dctCos(uint i, uint j) const {
+ return dctCos(((i << 1) + 1) * j);
+ }
+
+public:
+ DCT2DIII() {
+ for (uint i = 0; i != N2; ++i) {
+ _cos[i] = std::cos(i * M_PI / N4);
+ }
+ }
+
+ void calc(const float *src, float *dst) const {
+ const float sqrt12 = M_SQRT1_2;
+ auto *dstPtr = dst;
+ for (byte y = 0; y < 8; ++y) {
+ for (byte x = 0; x < 8; ++x) {
+ float row = 0;
+ float cosX0 = dctCos(x, 0);
+ for (byte v = 0; v < 8; ++v) {
+ auto *src_col = src + (v << 3);
+ float col = *src_col++ * cosX0 * sqrt12;
+ for (byte u = 1; u < 8; ++u) {
+ col += *src_col++ * dctCos(x, u);
+ }
+ row += col * dctCos(y, v) * (v != 0 ? 1 : sqrt12);
+ }
+ *dstPtr++ = row / 4;
+ }
+ }
+ for (uint i = 0; i != 64; ++i)
+ dst[i] /= 4;
+ }
+};
+
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 3c007620d2e..45543fe8e64 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -4,7 +4,7 @@
#include "common/textconsole.h"
#include "graphics/surface.h"
#include "graphics/yuv_to_rgb.h"
-#include "math/dct.h"
+#include "phoenixvr/dct.h"
namespace PhoenixVR {
@@ -12,6 +12,12 @@ namespace PhoenixVR {
#define CHUNK_STATIC (0xa0b1c400)
namespace {
+
+template<typename T>
+T clip(T a) {
+ return a >= 0 ? a <= 255 ? a : 255 : 0;
+}
+
const byte ZIGZAG[] = {
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7,
@@ -351,7 +357,7 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
auto decoded = unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
- static const Math::DCT dct(6, Math::DCT::DCT_III);
+ static const DCT2DIII<6> dct;
static constexpr uint planePitch = 640;
static constexpr uint planeSize = planePitch * 480;
@@ -398,22 +404,15 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
str += Common::String::format("%g ", ac[i]);
debug("input block %s", str.c_str());
str.clear();
- dct.calc(ac);
+ float out[64];
+ dct.calc(ac, out);
for (uint i = 0; i != 64; ++i)
- str += Common::String::format("%g ", ac[i]);
+ str += Common::String::format("%g ", out[i]);
debug("decoded block %s", str.c_str());
debug("block at %d,%d %d", x0, y0, channel);
- auto *dst = planes.data() + channel++ * planeSize + y0 * planePitch + x0;
- if (channel == 3) {
- channel = 0;
- x0 += 8;
- if (x0 >= 640) {
- x0 = 0;
- y0 += 8;
- }
- }
- const auto *src = ac;
+ auto *dst = planes.data() + channel * planeSize + y0 * planePitch + x0;
+ const auto *src = out;
// str.clear();
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
@@ -426,12 +425,40 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
*dst++ = v;
}
}
+ ++channel;
+ if (channel == 3) {
+ channel = 0;
+ x0 += 8;
+ if (x0 >= 640) {
+ x0 = 0;
+ y0 += 8;
+ }
+ }
// debug("decoded block %s", str.c_str());
}
- auto *y = planes.data();
- auto *u = y + planeSize;
- auto *v = u + planeSize;
- YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull, y, u, v, 640, 480, planePitch, planePitch);
+ auto *yPtr = planes.data();
+ auto *crPtr = yPtr + planeSize;
+ auto *cbPtr = crPtr + planeSize;
+#if 0
+ auto &format = pic.format;
+ for(int yy = 0; yy < 480; ++yy) {
+ auto *rows = static_cast<uint32*>(pic.getBasePtr(0, yy));
+ for(int xx = 0; xx < 640; ++xx) {
+ int16 y = 128 + *yPtr++;
+ int16 cr = (int16)*crPtr++;
+ int16 cb = (int16)*cbPtr++;
+
+ int r = clip(y + ((cr * 91881 + 32768) >> 16));
+ int g = clip(y - ((cb * 22553 + cr * 46801 + 32768) >> 16));
+ int b = clip(y + ((cb * 116129 + 32768) >> 16));
+
+ *rows++ = format.RGBToColor(r, g, b);
+ }
+ }
+#else
+ YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull,
+ yPtr, crPtr, cbPtr, 640, 480, planePitch, planePitch);
+#endif
}
} // namespace
Commit: 709668630f53c92219f7297c6c7ed7597e2351ba
https://github.com/scummvm/scummvm/commit/709668630f53c92219f7297c6c7ed7597e2351ba
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:28+01:00
Commit Message:
PHOENIXVR: make warps case insensitive
Changed paths:
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index f707c5646c6..329f04e330b 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -80,7 +80,7 @@ public:
using ConstWarpPtr = Common::SharedPtr<const Warp>;
private:
- Common::HashMap<Common::String, uint> _warpsIndex;
+ Common::HashMap<Common::String, uint, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _warpsIndex;
Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
TestPtr _currentTest;
Commit: 8885fa52dfd8c2c5eb08f568067a11f64a4363ad
https://github.com/scummvm/scummvm/commit/8885fa52dfd8c2c5eb08f568067a11f64a4363ad
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:29+01:00
Commit Message:
PHOENIXVR: inverse dc coefficients, found where normalisation happens
Changed paths:
engines/phoenixvr/dct.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/dct.h b/engines/phoenixvr/dct.h
index 0a239e8a51a..69a616b03d4 100644
--- a/engines/phoenixvr/dct.h
+++ b/engines/phoenixvr/dct.h
@@ -67,8 +67,6 @@ public:
*dstPtr++ = row / 4;
}
}
- for (uint i = 0; i != 64; ++i)
- dst[i] /= 4;
}
};
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 45543fe8e64..50d64bd4aad 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -416,13 +416,9 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
// str.clear();
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++;
- if (v < 0)
- v = 0;
- if (v > 255)
- v = 255;
+ int v = clip<int>(*src++ / 16 + 128);
// str += Common::String::format("%d ", v);
- *dst++ = v;
+ *dst++ = v - 128;
}
}
++channel;
Commit: 5c9aef64cc9921c4aa5c21c095957413707769a7
https://github.com/scummvm/scummvm/commit/5c9aef64cc9921c4aa5c21c095957413707769a7
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:29+01:00
Commit Message:
PHOENIXVR: remove dct coefficients logging
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 50d64bd4aad..1ec4e355550 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -399,18 +399,8 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
}
}
}
- Common::String str;
- for (uint i = 0; i != 64; ++i)
- str += Common::String::format("%g ", ac[i]);
- debug("input block %s", str.c_str());
- str.clear();
float out[64];
dct.calc(ac, out);
- for (uint i = 0; i != 64; ++i)
- str += Common::String::format("%g ", out[i]);
- debug("decoded block %s", str.c_str());
-
- debug("block at %d,%d %d", x0, y0, channel);
auto *dst = planes.data() + channel * planeSize + y0 * planePitch + x0;
const auto *src = out;
// str.clear();
Commit: 2e8a8e7392dc0eedcc874ed9b6d3c7e2905858b0
https://github.com/scummvm/scummvm/commit/2e8a8e7392dc0eedcc874ed9b6d3c7e2905858b0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:29+01:00
Commit Message:
PHOENIXVR: support 256x6144 pictures
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9e2194b38d2..a90288b23a4 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -249,15 +249,15 @@ Common::Error PhoenixVREngine::run() {
debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
- if (_static640x480) {
- _static640x480->free();
- delete _static640x480;
- _static640x480 = nullptr;
+ if (_static) {
+ _static->free();
+ delete _static;
+ _static = nullptr;
}
Common::File vr;
if (vr.open(Common::Path(_warp->vrFile))) {
- _static640x480 = VR::load640x480(_pixelFormat, vr);
+ _static = VR::loadStatic(_pixelFormat, vr);
}
_regSet.reset(new RegionSet(_warp->testFile));
@@ -272,9 +272,12 @@ Common::Error PhoenixVREngine::run() {
test->scope.exec(ctx);
}
- if (_static640x480)
- _screen->copyFrom(*_static640x480);
- else
+ if (_static) {
+ Common::Point dst(0, 0);
+ Common::Rect src(_static->getRect());
+ Common::Rect::getBlitRect(dst, src, _screen->getBounds());
+ _screen->copyRectToSurface(*_static, dst.x, dst.y, src);
+ } else
_screen->clear();
Graphics::Surface *cursor = nullptr;
@@ -303,7 +306,7 @@ Common::Error PhoenixVREngine::run() {
void PhoenixVREngine::paint(Graphics::Surface &src, Common::Point dst) {
Common::Rect srcRect = src.getRect();
- Common::Rect clip(0, 0, _screen->w, _screen->h);
+ Common::Rect clip = _screen->getBounds();
if (Common::Rect::getBlitRect(dst, srcRect, clip)) {
Common::Rect dstRect(dst.x, dst.y, dst.x + srcRect.width(), dst.y + srcRect.height());
_screen->blendBlitFrom(src, srcRect, dstRect);
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 9b5ddd7182d..3b301aeab14 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -139,7 +139,7 @@ private:
};
Common::Array<Cursor> _cursors;
Cursor _defaultCursor;
- Graphics::Surface *_static640x480 = nullptr;
+ Graphics::Surface *_static = nullptr;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 1ec4e355550..8c975725dd7 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -9,7 +9,8 @@
namespace PhoenixVR {
#define CHUNK_VR (0x12fa84ab)
-#define CHUNK_STATIC (0xa0b1c400)
+#define CHUNK_STATIC_2D (0xa0b1c400)
+#define CHUNK_STATIC_3D (0xa0b1c200)
namespace {
@@ -354,13 +355,13 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
return decoded;
}
-void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
+void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
auto decoded = unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
- static constexpr uint planePitch = 640;
- static constexpr uint planeSize = planePitch * 480;
+ const uint planePitch = pic.w;
+ const uint planeSize = planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
auto iquant = [&](uint channel, int idx) {
@@ -403,11 +404,9 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
dct.calc(ac, out);
auto *dst = planes.data() + channel * planeSize + y0 * planePitch + x0;
const auto *src = out;
- // str.clear();
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
int v = clip<int>(*src++ / 16 + 128);
- // str += Common::String::format("%d ", v);
*dst++ = v - 128;
}
}
@@ -415,40 +414,39 @@ void unpack640x480(Graphics::Surface &pic, const byte *huff, uint huffSize, cons
if (channel == 3) {
channel = 0;
x0 += 8;
- if (x0 >= 640) {
+ if (static_cast<int16>(x0) >= pic.w) {
x0 = 0;
y0 += 8;
}
}
- // debug("decoded block %s", str.c_str());
}
auto *yPtr = planes.data();
auto *crPtr = yPtr + planeSize;
auto *cbPtr = crPtr + planeSize;
#if 0
auto &format = pic.format;
- for(int yy = 0; yy < 480; ++yy) {
+ for(int yy = 0; yy < pic.h; ++yy) {
auto *rows = static_cast<uint32*>(pic.getBasePtr(0, yy));
- for(int xx = 0; xx < 640; ++xx) {
- int16 y = 128 + *yPtr++;
+ for(int xx = 0; xx < pic.w; ++xx) {
+ int16 y = *yPtr++;
int16 cr = (int16)*crPtr++;
int16 cb = (int16)*cbPtr++;
- int r = clip(y + ((cr * 91881 + 32768) >> 16));
- int g = clip(y - ((cb * 22553 + cr * 46801 + 32768) >> 16));
- int b = clip(y + ((cb * 116129 + 32768) >> 16));
+ int r = y;
+ int g = y;
+ int b = y;
*rows++ = format.RGBToColor(r, g, b);
}
}
#else
YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull,
- yPtr, crPtr, cbPtr, 640, 480, planePitch, planePitch);
+ yPtr, crPtr, cbPtr, pic.w, pic.h, planePitch, planePitch);
#endif
}
} // namespace
-Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
+Graphics::Surface *VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
auto magic = s.readUint32LE();
if (magic != CHUNK_VR) {
error("wrong VR magic");
@@ -459,7 +457,9 @@ Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::
auto chunkId = s.readUint32LE();
auto chunkSize = s.readUint32LE();
debug("chunk %08x %u", chunkId, chunkSize);
- if (chunkId == CHUNK_STATIC) {
+ bool pic2d = chunkId == CHUNK_STATIC_2D;
+ bool pic3d = chunkId == CHUNK_STATIC_3D;
+ if (pic2d || pic3d) {
auto quality = s.readUint32LE();
auto dataSize = s.readUint32LE();
@@ -470,12 +470,15 @@ Graphics::Surface *VR::load640x480(const Graphics::PixelFormat &format, Common::
auto unpHuffSize = READ_LE_UINT32(vrData.data() + 4);
debug("static picture header, quality: %u, packed data size: %u, huff size: %08x, unpacked huff size: %u", quality, dataSize, huffSize, unpHuffSize);
Graphics::Surface *pic = new Graphics::Surface();
- pic->create(640, 480, format);
+ if (pic3d)
+ pic->create(256, 6144, format);
+ else
+ pic->create(640, 480, format);
auto *huff = vrData.data() + 8;
auto *acPtr = vrData.data() + huffSize + 12;
auto dcOffset = READ_LE_UINT32(vrData.data() + huffSize + 8);
auto *dcPtr = vrData.data() + huffSize + 16 + dcOffset;
- unpack640x480(*pic, huff, unpHuffSize, acPtr, dcPtr, quality);
+ unpack(*pic, huff, unpHuffSize, acPtr, dcPtr, quality);
return pic;
}
s.skip(chunkSize - 8);
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index e5fd337814d..f2ce74b3940 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -32,7 +32,7 @@ class Surface;
namespace PhoenixVR {
class VR {
public:
- static Graphics::Surface *load640x480(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
+ static Graphics::Surface *loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
};
} // namespace PhoenixVR
Commit: 58cefb16b078eece6fa8191f6f00a11bc2e9f5a5
https://github.com/scummvm/scummvm/commit/58cefb16b078eece6fa8191f6f00a11bc2e9f5a5
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:29+01:00
Commit Message:
PHOENIXVR: add quantisation structure to premultiply quantisation coefficients
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8c975725dd7..3420b1297c9 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -228,6 +228,36 @@ const uint16 Q[] = {
0x4DF,
};
+struct Quantisation {
+ int quantY[64];
+ int quantCbCr[64];
+
+ Quantisation(int quality) {
+ for (uint i = 0; i != 64; ++i) {
+ auto v = (QY[i] * quality + 50) / 100;
+ if (v > 255)
+ v = 255;
+ else if (v < 8) {
+ v = 8;
+ }
+ v *= Q[i];
+ v >>= 13;
+ quantY[i] = v;
+ }
+ for (uint i = 0; i != 64; ++i) {
+ auto v = (QUV[i] * quality + 50) / 100;
+ if (v > 255)
+ v = 255;
+ else if (v < 8) {
+ v = 8;
+ }
+ v *= Q[i];
+ v >>= 13;
+ quantCbCr[i] = v;
+ }
+ }
+};
+
struct HuffChar {
short next;
short falseIdx;
@@ -356,6 +386,7 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
}
void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
+ Quantisation quant(quality);
auto decoded = unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
@@ -364,25 +395,13 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const uint planeSize = planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
- auto iquant = [&](uint channel, int idx) {
- int v = ((channel == 0 ? QY : QUV)[idx] * quality + 50) / 100;
- if (v > 255)
- v = 255;
- else if (v < 8) {
- v = 8;
- }
-
- v *= Q[idx];
- v >>= 13;
- return v;
- };
-
BitStream acBs(acPtr, 0), dcBs(dcPtr, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
float ac[64] = {};
- ac[0] = 1.0f * iquant(channel, 0) * dcBs.readUInt(8);
+ auto *iquant = channel ? quant.quantCbCr : quant.quantY;
+ ac[0] = 1.0f * iquant[0] * dcBs.readUInt(8);
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
@@ -395,7 +414,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
idx += h;
if (l && idx < 64) {
auto ac_idx = ZIGZAG[idx];
- ac[ac_idx] = 1.0f * iquant(channel, ac_idx) * acBs.readInt(l);
+ ac[ac_idx] = 1.0f * iquant[ac_idx] * acBs.readInt(l);
++idx;
}
}
Commit: 425311071875ffb4955b932349be364185433364
https://github.com/scummvm/scummvm/commit/425311071875ffb4955b932349be364185433364
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:30+01:00
Commit Message:
PHOENIXVR: fix DC zero point
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 3420b1297c9..eca8b64da0b 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -400,8 +400,9 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
float ac[64] = {};
+ byte dc8 = dcBs.readUInt(8) - 128;
auto *iquant = channel ? quant.quantCbCr : quant.quantY;
- ac[0] = 1.0f * iquant[0] * dcBs.readUInt(8);
+ ac[0] = 1.0f * iquant[0] * (int)dc8;
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
Commit: fe0853880597e8d1536efa959b956bf1c2872696
https://github.com/scummvm/scummvm/commit/fe0853880597e8d1536efa959b956bf1c2872696
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:30+01:00
Commit Message:
PHOENIXVR: relatively working dynamic range
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index eca8b64da0b..8bdd40f8d56 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -426,8 +426,10 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const auto *src = out;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = clip<int>(*src++ / 16 + 128);
- *dst++ = v - 128;
+ int v = *src++;
+ v -= 510;
+ v = v / 8 + 128;
+ *dst++ = clip(v);
}
}
++channel;
Commit: db5308062d110462fd8d32c1aeae252d469f87f6
https://github.com/scummvm/scummvm/commit/db5308062d110462fd8d32c1aeae252d469f87f6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:30+01:00
Commit Message:
PHOENIXVR: add executeTest() api and call it from changecurseur
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 151741b655c..017405653d5 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -142,6 +142,7 @@ struct ChangeCurseur : public Script::Command {
ChangeCurseur(const Common::Array<Common::String> &args) : cursor(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("changecurseur %d", cursor);
+ g_engine->executeTest(cursor);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index a90288b23a4..b8b6aabfba0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -170,6 +170,16 @@ void PhoenixVREngine::Cursor::free() {
rect.setEmpty();
}
+void PhoenixVREngine::executeTest(int idx) {
+ debug("execute test %d", idx);
+ auto test = _warp->getTest(idx);
+ if (test) {
+ Script::ExecutionContext ctx;
+ test->scope.exec(ctx);
+ } else
+ warning("invalid test id %d", idx);
+}
+
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
@@ -209,11 +219,7 @@ Common::Error PhoenixVREngine::run() {
auto rect = _regSet->getRegion(i).toRect();
if (rect.contains(event.mouse)) {
debug("click region %u", i);
- auto test = _warp->getTest(i);
- if (test) {
- Script::ExecutionContext ctx;
- test->scope.exec(ctx);
- }
+ executeTest(i);
}
}
break;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 3b301aeab14..79293a2e937 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -112,6 +112,8 @@ public:
void setVariable(const Common::String &name, int value);
int getVariable(const Common::String &name) const;
+ void executeTest(int idx);
+
Script::ConstWarpPtr getWarp(const Common::String &name);
Script::ConstWarpPtr getCurrentWarp() { return _warp; }
Region getRegion(int idx) const;
Commit: 1da8b39a2a507c032f3e49a28e8d36e6295a19ba
https://github.com/scummvm/scummvm/commit/1da8b39a2a507c032f3e49a28e8d36e6295a19ba
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:31+01:00
Commit Message:
PHOENIXVR: remove dc coefficient modification, it's signed int8
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8bdd40f8d56..d83b12d6503 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -400,7 +400,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
float ac[64] = {};
- byte dc8 = dcBs.readUInt(8) - 128;
+ int8 dc8 = dcBs.readUInt(8);
auto *iquant = channel ? quant.quantCbCr : quant.quantY;
ac[0] = 1.0f * iquant[0] * (int)dc8;
for (uint idx = 1; idx < 64;) {
@@ -426,10 +426,9 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const auto *src = out;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++;
- v -= 510;
- v = v / 8 + 128;
- *dst++ = clip(v);
+ int v = *src++ / 4 + 128;
+ v = clip(v);
+ *dst++ = v;
}
}
++channel;
Commit: d3df6fede6ab9e905c8c0d0b0a241a1e959aeecb
https://github.com/scummvm/scummvm/commit/d3df6fede6ab9e905c8c0d0b0a241a1e959aeecb
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:31+01:00
Commit Message:
PHOENIXVR: move picture management code to VR
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index b8b6aabfba0..df1c9dc3414 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -255,15 +255,9 @@ Common::Error PhoenixVREngine::run() {
debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
- if (_static) {
- _static->free();
- delete _static;
- _static = nullptr;
- }
-
Common::File vr;
if (vr.open(Common::Path(_warp->vrFile))) {
- _static = VR::loadStatic(_pixelFormat, vr);
+ _vr = VR::loadStatic(_pixelFormat, vr);
}
_regSet.reset(new RegionSet(_warp->testFile));
@@ -278,13 +272,7 @@ Common::Error PhoenixVREngine::run() {
test->scope.exec(ctx);
}
- if (_static) {
- Common::Point dst(0, 0);
- Common::Rect src(_static->getRect());
- Common::Rect::getBlitRect(dst, src, _screen->getBounds());
- _screen->copyRectToSurface(*_static, dst.x, dst.y, src);
- } else
- _screen->clear();
+ _vr.render(_screen);
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 79293a2e937..401c19092ea 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -37,6 +37,7 @@
#include "phoenixvr/detection.h"
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
+#include "phoenixvr/vr.h"
namespace PhoenixVR {
@@ -141,7 +142,7 @@ private:
};
Common::Array<Cursor> _cursors;
Cursor _defaultCursor;
- Graphics::Surface *_static = nullptr;
+ VR _vr;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index d83b12d6503..99ee8152e0b 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -1,9 +1,12 @@
#include "phoenixvr/vr.h"
#include "common/array.h"
#include "common/debug.h"
+#include "common/file.h"
#include "common/textconsole.h"
+#include "graphics/screen.h"
#include "graphics/surface.h"
#include "graphics/yuv_to_rgb.h"
+#include "image/bmp.h"
#include "phoenixvr/dct.h"
namespace PhoenixVR {
@@ -467,7 +470,15 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
} // namespace
-Graphics::Surface *VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
+VR::~VR() {
+ if (_pic) {
+ _pic->free();
+ _pic.reset();
+ }
+}
+
+VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
+ VR vr;
auto magic = s.readUint32LE();
if (magic != CHUNK_VR) {
error("wrong VR magic");
@@ -490,21 +501,43 @@ Graphics::Surface *VR::loadStatic(const Graphics::PixelFormat &format, Common::S
auto huffSize = READ_LE_UINT32(vrData.data());
auto unpHuffSize = READ_LE_UINT32(vrData.data() + 4);
debug("static picture header, quality: %u, packed data size: %u, huff size: %08x, unpacked huff size: %u", quality, dataSize, huffSize, unpHuffSize);
- Graphics::Surface *pic = new Graphics::Surface();
- if (pic3d)
+ if (vr._pic)
+ error("2d/3d picture in the same file");
+ vr._pic.reset(new Graphics::Surface());
+ auto *pic = vr._pic.get();
+ if (pic3d) {
+ vr._vr = true;
pic->create(256, 6144, format);
- else
+ } else
pic->create(640, 480, format);
auto *huff = vrData.data() + 8;
auto *acPtr = vrData.data() + huffSize + 12;
auto dcOffset = READ_LE_UINT32(vrData.data() + huffSize + 8);
auto *dcPtr = vrData.data() + huffSize + 16 + dcOffset;
unpack(*pic, huff, unpHuffSize, acPtr, dcPtr, quality);
- return pic;
}
s.skip(chunkSize - 8);
}
- return nullptr;
+ if (vr._pic) {
+ Common::DumpFile out;
+ if (out.open("static.bmp"))
+ Image::writeBMP(out, *vr._pic);
+ }
+ return vr;
+}
+
+void VR::render(Graphics::Screen *screen) {
+ if (!_pic) {
+ screen->clear();
+ return;
+ }
+
+ if (!_vr) {
+ Common::Point dst(0, 0);
+ Common::Rect src(_pic->getRect());
+ Common::Rect::getBlitRect(dst, src, screen->getBounds());
+ screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
+ }
}
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index f2ce74b3940..7b2152d4995 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -19,20 +19,30 @@
*
*/
-#ifndef PHOENIXVR_COMMANDS_H
-#define PHOENIXVR_COMMANDS_H
+#ifndef PHOENIXVR_VR_H
+#define PHOENIXVR_VR_H
#include "common/stream.h"
#include "graphics/pixelformat.h"
namespace Graphics {
class Surface;
-}
+class Screen;
+} // namespace Graphics
namespace PhoenixVR {
class VR {
+ Common::ScopedPtr<Graphics::Surface> _pic;
+ bool _vr = false;
+
public:
- static Graphics::Surface *loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
+ ~VR();
+ VR() = default;
+ VR(VR &&) = default;
+ VR &operator=(VR &&) = default;
+
+ static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
+ void render(Graphics::Screen *screen);
};
} // namespace PhoenixVR
Commit: e1336510926bb31d2fe60dac2ec7e0fffc740fd1
https://github.com/scummvm/scummvm/commit/e1336510926bb31d2fe60dac2ec7e0fffc740fd1
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:31+01:00
Commit Message:
PHOENIXVR: implement add/sub
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 017405653d5..917f83e44f7 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -149,24 +149,26 @@ struct ChangeCurseur : public Script::Command {
struct Add : public Script::Command {
Common::String dstVar;
Common::String srcVar;
- int addend;
+ int imm;
- Add(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
+ Add(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), imm(atoi(args[2].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("add %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
+ debug("add %s %s %d", dstVar.c_str(), srcVar.c_str(), imm);
+ g_engine->setVariable(dstVar, g_engine->getVariable(srcVar) + imm);
}
};
struct Sub : public Script::Command {
Common::String dstVar;
Common::String srcVar;
- int addend;
+ int imm;
- Sub(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), addend(atoi(args[2].c_str())) {}
+ Sub(const Common::Array<Common::String> &args) : dstVar(args[0]), srcVar(args[1]), imm(atoi(args[2].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("sub %s %s %d", dstVar.c_str(), srcVar.c_str(), addend);
+ debug("sub %s %s %d", dstVar.c_str(), srcVar.c_str(), imm);
+ g_engine->setVariable(dstVar, g_engine->getVariable(srcVar) - imm);
}
};
Commit: 2064d00a1985154b6ed527eb404a3fd9e89e8c9f
https://github.com/scummvm/scummvm/commit/2064d00a1985154b6ed527eb404a3fd9e89e8c9f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:31+01:00
Commit Message:
PHOENIXVR: add end which should quit if no next script loaded
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 917f83e44f7..7f2ea90172b 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -378,8 +378,8 @@ struct Set : public Script::Command {
struct End : public Script::Command {
End() {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("end");
ctx.running = false;
+ g_engine->end();
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index df1c9dc3414..3e0582f9636 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -81,6 +81,12 @@ void PhoenixVREngine::setNextScript(const Common::String &path) {
debug("setNextScript %s", _nextScript.c_str());
}
+void PhoenixVREngine::end() {
+ debug("end");
+ if (_nextScript.empty())
+ quitGame();
+}
+
void PhoenixVREngine::goToWarp(const Common::String &warp) {
debug("gotowarp %s", warp.c_str());
_nextWarp = warp;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 401c19092ea..a5d3169ef88 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -114,6 +114,7 @@ public:
int getVariable(const Common::String &name) const;
void executeTest(int idx);
+ void end();
Script::ConstWarpPtr getWarp(const Common::String &name);
Script::ConstWarpPtr getCurrentWarp() { return _warp; }
Commit: cc4a6632f13a1a075e2bcd8aeabc8a43acb55cb4
https://github.com/scummvm/scummvm/commit/cc4a6632f13a1a075e2bcd8aeabc8a43acb55cb4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:32+01:00
Commit Message:
PHOENIXVR: add simple mouse handling and arrange faces
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 3e0582f9636..a3692d35fe9 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -189,6 +189,7 @@ void PhoenixVREngine::executeTest(int idx) {
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
+ _screenCenter = _screen->getBounds().center();
{
Common::File vars;
if (vars.open(Common::Path("variable.txt"))) {
@@ -214,6 +215,10 @@ Common::Error PhoenixVREngine::run() {
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ if (event.kbd.ascii == ' ')
+ goToWarp("N1M01L03W02E0.vr");
+ break;
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
break;
@@ -233,6 +238,16 @@ Common::Error PhoenixVREngine::run() {
break;
}
}
+ if (_vr.isVR()) {
+ auto da = _mousePos - _screenCenter;
+ _system->warpMouse(_screenCenter.x, _screenCenter.y);
+ static const float kSpeedX = 0.01f;
+ static const float kSpeedY = 0.01f;
+ _angleX -= float(da.x) * kSpeedX;
+ _angleX = fmodf(_angleX, M_PI * 2);
+ _angleY += -float(da.y) * kSpeedY;
+ _angleY = fmodf(_angleY, M_PI * 2);
+ }
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
auto nextScript = Common::move(_nextScript);
@@ -278,7 +293,7 @@ Common::Error PhoenixVREngine::run() {
test->scope.exec(ctx);
}
- _vr.render(_screen);
+ _vr.render(_screen, _angleX, _angleY);
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index a5d3169ef88..bd04807cee7 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -45,17 +45,15 @@ struct PhoenixVRGameDescription;
class PhoenixVREngine : public Engine {
private:
+ Graphics::Screen *_screen = nullptr;
+ Common::Point _screenCenter;
const ADGameDescription *_gameDescription;
Common::RandomSource _randomSource;
Graphics::PixelFormat _pixelFormat;
-protected:
// Engine APIs
Common::Error run() override;
-public:
- Graphics::Screen *_screen = nullptr;
-
public:
PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc);
~PhoenixVREngine() override;
@@ -144,6 +142,8 @@ private:
Common::Array<Cursor> _cursors;
Cursor _defaultCursor;
VR _vr;
+ float _angleX = 0;
+ float _angleY = 0;
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 99ee8152e0b..6c12b5670db 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -2,6 +2,7 @@
#include "common/array.h"
#include "common/debug.h"
#include "common/file.h"
+#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/screen.h"
#include "graphics/surface.h"
@@ -526,7 +527,7 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
return vr;
}
-void VR::render(Graphics::Screen *screen) {
+void VR::render(Graphics::Screen *screen, float ax, float ay) {
if (!_pic) {
screen->clear();
return;
@@ -537,6 +538,42 @@ void VR::render(Graphics::Screen *screen) {
Common::Rect src(_pic->getRect());
Common::Rect::getBlitRect(dst, src, screen->getBounds());
screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
+ } else {
+ screen->clear();
+ auto w = g_system->getWidth();
+ auto h = g_system->getHeight();
+
+ static const float kFOV = (90 / 180.0f) * M_PI;
+ static const float kdA = kFOV / w;
+ struct Column {
+ float angle;
+ int faceIdx;
+ float tan;
+ };
+ Common::Array<Column> columns(w);
+ float a = ax - kFOV / 2;
+ for (int dstX = 0; dstX != w; ++dstX) {
+ int faceIdx = static_cast<int>((a + M_PI_4) / M_PI_2) % 4;
+ if (faceIdx < 0)
+ faceIdx += 4;
+ static int faceIdxH[] = {1, 4, 3, 5};
+ faceIdx = faceIdxH[faceIdx];
+ columns[dstX] = {a, faceIdx, tan(a)};
+ a += kdA;
+ }
+ for (int dstY = 0; dstY != h; ++dstY) {
+ for (int dstX = 0; dstX != w; ++dstX) {
+ auto &col = columns[dstX];
+ int srcX = (dstX << 9) / w;
+ int srcY = (dstY << 9) / h;
+ int tileId = col.faceIdx * 4;
+ tileId += (srcY < 256) ? (srcX < 256 ? 0 : 1) : (srcX < 256 ? 3 : 2);
+ srcX &= 0xff;
+ srcY &= 0xff;
+ srcY += (tileId << 8);
+ screen->setPixel(dstX, dstY, _pic->getPixel(srcX, srcY));
+ }
+ }
}
}
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 7b2152d4995..f0d5359d87e 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -42,7 +42,8 @@ public:
VR &operator=(VR &&) = default;
static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
- void render(Graphics::Screen *screen);
+ void render(Graphics::Screen *screen, float ax, float ay);
+ bool isVR() const { return _vr; }
};
} // namespace PhoenixVR
Commit: f9743d00192990c39ba84f9550e65c10ce0b3e0d
https://github.com/scummvm/scummvm/commit/f9743d00192990c39ba84f9550e65c10ce0b3e0d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:32+01:00
Commit Message:
PHOENIXVR: add real scrolling and calculate angles per columns
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index a3692d35fe9..1b7abb05b63 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -212,6 +212,7 @@ Common::Error PhoenixVREngine::run() {
Common::Event event;
Graphics::FrameLimiter limiter(g_system, 60);
+ uint frameDuration = 0;
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
@@ -241,12 +242,12 @@ Common::Error PhoenixVREngine::run() {
if (_vr.isVR()) {
auto da = _mousePos - _screenCenter;
_system->warpMouse(_screenCenter.x, _screenCenter.y);
- static const float kSpeedX = 0.01f;
- static const float kSpeedY = 0.01f;
- _angleX -= float(da.x) * kSpeedX;
- _angleX = fmodf(_angleX, M_PI * 2);
- _angleY += -float(da.y) * kSpeedY;
- _angleY = fmodf(_angleY, M_PI * 2);
+ _mousePos = _screenCenter;
+ static const float kSpeedX = 0.2f;
+ static const float kSpeedY = 0.2f;
+ const auto dt = float(frameDuration) / 1000.0f;
+ _angleX += float(da.x) * kSpeedX * dt;
+ _angleY += float(da.y) * kSpeedY * dt;
}
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
@@ -313,7 +314,7 @@ Common::Error PhoenixVREngine::run() {
// to prevent the system being unduly loaded
limiter.delayBeforeSwap();
_screen->update();
- limiter.startFrame();
+ frameDuration = limiter.startFrame();
}
return Common::kNoError;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 6c12b5670db..1f90a884372 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -548,24 +548,31 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
struct Column {
float angle;
int faceIdx;
- float tan;
+ int srcX;
};
Common::Array<Column> columns(w);
float a = ax - kFOV / 2;
for (int dstX = 0; dstX != w; ++dstX) {
- int faceIdx = static_cast<int>((a + M_PI_4) / M_PI_2) % 4;
+ auto aPi4 = a + float(M_PI_4);
+ float quadrantA = fmodf(aPi4, M_PI_2);
+ if (quadrantA < 0)
+ quadrantA += M_PI_2;
+ quadrantA -= M_PI_4;
+ int faceIdx = static_cast<int>(aPi4 / M_PI_2) % 4;
if (faceIdx < 0)
faceIdx += 4;
static int faceIdxH[] = {1, 4, 3, 5};
faceIdx = faceIdxH[faceIdx];
- columns[dstX] = {a, faceIdx, tan(a)};
+ auto srcX = static_cast<int>(tan(quadrantA) * 256) + 256;
+ columns[dstX] = {quadrantA, faceIdx, srcX};
a += kdA;
}
for (int dstY = 0; dstY != h; ++dstY) {
+ const int srcY0 = (dstY << 9) / h;
for (int dstX = 0; dstX != w; ++dstX) {
auto &col = columns[dstX];
- int srcX = (dstX << 9) / w;
- int srcY = (dstY << 9) / h;
+ int srcX = col.srcX;
+ int srcY = srcY0;
int tileId = col.faceIdx * 4;
tileId += (srcY < 256) ? (srcX < 256 ? 0 : 1) : (srcX < 256 ? 3 : 2);
srcX &= 0xff;
Commit: 940ad58a5ce2588d54fd4135fe06d76ff94534f2
https://github.com/scummvm/scummvm/commit/940ad58a5ce2588d54fd4135fe06d76ff94534f2
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:32+01:00
Commit Message:
PHOENIXVR: add limit for frame duration
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1b7abb05b63..d4418cb2598 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -245,6 +245,8 @@ Common::Error PhoenixVREngine::run() {
_mousePos = _screenCenter;
static const float kSpeedX = 0.2f;
static const float kSpeedY = 0.2f;
+ if (frameDuration > 100)
+ frameDuration = 100;
const auto dt = float(frameDuration) / 1000.0f;
_angleX += float(da.x) * kSpeedX * dt;
_angleY += float(da.y) * kSpeedY * dt;
Commit: 7b648c8ae1936dfafbb70fd210b403eaafac4b15
https://github.com/scummvm/scummvm/commit/7b648c8ae1936dfafbb70fd210b403eaafac4b15
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:33+01:00
Commit Message:
PHOENIXVR: add separate projections for x/y
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 1f90a884372..916e791eeb5 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -527,6 +527,37 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
return vr;
}
+namespace {
+
+struct Projection {
+ struct Point {
+ float angle;
+ int faceIdx;
+ int texPos;
+ };
+ Common::Array<Point> points;
+
+ Projection(uint num, float start, float fov) : points(num) {
+ const auto da = fov / float(num);
+ float a = start - fov / 2;
+ for (uint i = 0; i != num; ++i) {
+ auto aPi4 = a + float(M_PI_4);
+ float quadrantA = fmodf(aPi4, M_PI_2);
+ if (quadrantA < 0)
+ quadrantA += M_PI_2;
+ quadrantA -= M_PI_4;
+ int faceIdx = static_cast<int>(aPi4 / M_PI_2) % 4;
+ if (faceIdx < 0)
+ faceIdx += 4;
+ debug("%u/%u: qA %g", i, num, quadrantA);
+ auto texPos = static_cast<int>(tan(quadrantA) * 256) + 256;
+ points[i] = {quadrantA, faceIdx, texPos};
+ a += da;
+ }
+ }
+};
+} // namespace
+
void VR::render(Graphics::Screen *screen, float ax, float ay) {
if (!_pic) {
screen->clear();
@@ -544,36 +575,26 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
auto h = g_system->getHeight();
static const float kFOV = (90 / 180.0f) * M_PI;
- static const float kdA = kFOV / w;
- struct Column {
- float angle;
- int faceIdx;
- int srcX;
- };
- Common::Array<Column> columns(w);
- float a = ax - kFOV / 2;
- for (int dstX = 0; dstX != w; ++dstX) {
- auto aPi4 = a + float(M_PI_4);
- float quadrantA = fmodf(aPi4, M_PI_2);
- if (quadrantA < 0)
- quadrantA += M_PI_2;
- quadrantA -= M_PI_4;
- int faceIdx = static_cast<int>(aPi4 / M_PI_2) % 4;
- if (faceIdx < 0)
- faceIdx += 4;
- static int faceIdxH[] = {1, 4, 3, 5};
- faceIdx = faceIdxH[faceIdx];
- auto srcX = static_cast<int>(tan(quadrantA) * 256) + 256;
- columns[dstX] = {quadrantA, faceIdx, srcX};
- a += kdA;
- }
+ Projection projH(w, ax, kFOV);
+ Projection projV(h, ay, kFOV);
+ debug("angle %g %g", ax, ay);
for (int dstY = 0; dstY != h; ++dstY) {
- const int srcY0 = (dstY << 9) / h;
+ auto &pv = projV.points[dstY];
+ if (dstY == 0)
+ debug("v faceidx %d", pv.faceIdx);
for (int dstX = 0; dstX != w; ++dstX) {
- auto &col = columns[dstX];
- int srcX = col.srcX;
- int srcY = srcY0;
- int tileId = col.faceIdx * 4;
+ auto &ph = projH.points[dstX];
+ int srcX = ph.texPos;
+ int srcY = pv.texPos;
+ static int faceIdxHMap[] = {1, 4, 3, 5};
+ int faceIdx;
+ if (pv.faceIdx == 1)
+ faceIdx = 2;
+ else if (pv.faceIdx == 3)
+ faceIdx = 0;
+ else
+ faceIdx = faceIdxHMap[ph.faceIdx];
+ int tileId = faceIdx * 4;
tileId += (srcY < 256) ? (srcX < 256 ? 0 : 1) : (srcX < 256 ? 3 : 2);
srcX &= 0xff;
srcY &= 0xff;
Commit: 0e9737e78f63d9a74c12ff8750fc8fce2ba3af8b
https://github.com/scummvm/scummvm/commit/0e9737e78f63d9a74c12ff8750fc8fce2ba3af8b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:33+01:00
Commit Message:
PHOENIXVR: map vector to cube face
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index d4418cb2598..d0e79f8ec97 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -245,8 +245,6 @@ Common::Error PhoenixVREngine::run() {
_mousePos = _screenCenter;
static const float kSpeedX = 0.2f;
static const float kSpeedY = 0.2f;
- if (frameDuration > 100)
- frameDuration = 100;
const auto dt = float(frameDuration) / 1000.0f;
_angleX += float(da.x) * kSpeedX * dt;
_angleY += float(da.y) * kSpeedY * dt;
@@ -282,6 +280,10 @@ Common::Error PhoenixVREngine::run() {
Common::File vr;
if (vr.open(Common::Path(_warp->vrFile))) {
_vr = VR::loadStatic(_pixelFormat, vr);
+ if (_vr.isVR()) {
+ _mousePos = _screenCenter;
+ _system->warpMouse(_screenCenter.x, _screenCenter.y);
+ }
}
_regSet.reset(new RegionSet(_warp->testFile));
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 916e791eeb5..d0a1de66212 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -529,11 +529,72 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
namespace {
+struct Cube {
+ float x;
+ float y;
+ int faceIdx;
+};
+
+Cube toCube(float x, float y, float z) {
+ Cube cube;
+
+ float absX = fabs(x);
+ float absY = fabs(y);
+ float absZ = fabs(z);
+
+ bool isXPositive = x > 0;
+ bool isYPositive = y > 0;
+ bool isZPositive = z > 0;
+
+ float maxAxis, cy, cx;
+
+ if (isXPositive && absX >= absY && absX >= absZ) {
+ maxAxis = absX;
+ cx = y;
+ cy = z;
+ cube.faceIdx = 1;
+ }
+ if (!isXPositive && absX >= absY && absX >= absZ) {
+ maxAxis = absX;
+ cx = -y;
+ cy = z;
+ cube.faceIdx = 3;
+ }
+ if (isYPositive && absY >= absX && absY >= absZ) {
+ maxAxis = absY;
+ cx = -x;
+ cy = z;
+ cube.faceIdx = 4;
+ }
+ if (!isYPositive && absY >= absX && absY >= absZ) {
+ maxAxis = absY;
+ cx = x;
+ cy = z;
+ cube.faceIdx = 5;
+ }
+ if (isZPositive && absZ >= absX && absZ >= absY) {
+ maxAxis = absZ;
+ cx = -x;
+ cy = -y;
+ cube.faceIdx = 0;
+ }
+ if (!isZPositive && absZ >= absX && absZ >= absY) {
+ maxAxis = absZ;
+ cx = -x;
+ cy = y;
+ cube.faceIdx = 2;
+ }
+
+ // Convert range from â1 to 1 to 0 to 1
+ cube.y = 0.5f * (cy / maxAxis + 1.0f);
+ cube.x = 0.5f * (cx / maxAxis + 1.0f);
+ return cube;
+}
+
struct Projection {
struct Point {
float angle;
- int faceIdx;
- int texPos;
+ float sine, cosine;
};
Common::Array<Point> points;
@@ -541,17 +602,7 @@ struct Projection {
const auto da = fov / float(num);
float a = start - fov / 2;
for (uint i = 0; i != num; ++i) {
- auto aPi4 = a + float(M_PI_4);
- float quadrantA = fmodf(aPi4, M_PI_2);
- if (quadrantA < 0)
- quadrantA += M_PI_2;
- quadrantA -= M_PI_4;
- int faceIdx = static_cast<int>(aPi4 / M_PI_2) % 4;
- if (faceIdx < 0)
- faceIdx += 4;
- debug("%u/%u: qA %g", i, num, quadrantA);
- auto texPos = static_cast<int>(tan(quadrantA) * 256) + 256;
- points[i] = {quadrantA, faceIdx, texPos};
+ points[i] = {a, sin(a), cos(a)};
a += da;
}
}
@@ -576,25 +627,19 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
static const float kFOV = (90 / 180.0f) * M_PI;
Projection projH(w, ax, kFOV);
- Projection projV(h, ay, kFOV);
+ Projection projV(h, ay - M_PI_2, kFOV);
debug("angle %g %g", ax, ay);
for (int dstY = 0; dstY != h; ++dstY) {
auto &pv = projV.points[dstY];
- if (dstY == 0)
- debug("v faceidx %d", pv.faceIdx);
for (int dstX = 0; dstX != w; ++dstX) {
auto &ph = projH.points[dstX];
- int srcX = ph.texPos;
- int srcY = pv.texPos;
- static int faceIdxHMap[] = {1, 4, 3, 5};
- int faceIdx;
- if (pv.faceIdx == 1)
- faceIdx = 2;
- else if (pv.faceIdx == 3)
- faceIdx = 0;
- else
- faceIdx = faceIdxHMap[ph.faceIdx];
- int tileId = faceIdx * 4;
+ float x = ph.cosine * pv.sine;
+ float y = ph.sine * pv.sine;
+ float z = pv.cosine;
+ auto cube = toCube(x, y, z);
+ int srcX = static_cast<int>(512 * cube.x);
+ int srcY = static_cast<int>(512 * cube.y);
+ int tileId = cube.faceIdx * 4;
tileId += (srcY < 256) ? (srcX < 256 ? 0 : 1) : (srcX < 256 ? 3 : 2);
srcX &= 0xff;
srcY &= 0xff;
Commit: 99209f8d8f45b6efcfec6c8d59398c52a8e3fee1
https://github.com/scummvm/scummvm/commit/99209f8d8f45b6efcfec6c8d59398c52a8e3fee1
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:33+01:00
Commit Message:
PHOENIXVR: add RectF
Changed paths:
A engines/phoenixvr/rectf.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/region_set.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index d0e79f8ec97..17124282737 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -229,7 +229,7 @@ Common::Error PhoenixVREngine::run() {
break;
for (uint i = 0; i != _regSet->size(); ++i) {
auto rect = _regSet->getRegion(i).toRect();
- if (rect.contains(event.mouse)) {
+ if (rect.contains(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
}
@@ -302,7 +302,7 @@ Common::Error PhoenixVREngine::run() {
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
- if (c.rect.contains(_mousePos)) {
+ if (c.rect.contains(_mousePos.x, _mousePos.y)) {
cursor = c.surface;
if (cursor)
break;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index bd04807cee7..e5140883ce5 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -135,7 +135,7 @@ private:
Common::ScopedPtr<RegionSet> _regSet;
struct Cursor {
- Common::Rect rect;
+ RectF rect;
Graphics::Surface *surface = nullptr;
void free();
};
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
new file mode 100644
index 00000000000..e1f826ecd45
--- /dev/null
+++ b/engines/phoenixvr/rectf.h
@@ -0,0 +1,40 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_RECTF_H
+#define PHOENIXVR_RECTF_H
+
+#include "common/rect.h"
+
+namespace PhoenixVR {
+
+BEGIN_POINT_TYPE(float, PointF)
+END_POINT_TYPE(float, PointF)
+
+BEGIN_RECT_TYPE(float, RectF, PointF);
+Common::String toString() const {
+ return Common::String::format("%g, %g, %g, %g", left, top, right, bottom);
+}
+END_RECT_TYPE(float, RectF, PointF);
+
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index be292e658d0..9ef0c389a70 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -41,12 +41,12 @@ RegionSet::RegionSet(const Common::String &fname) {
}
}
-Common::Rect Region::toRect() const {
- Common::Rect rect;
- rect.left = static_cast<short>(MIN(a, b));
- rect.right = static_cast<short>(MAX(a, b));
- rect.top = static_cast<short>(MIN(c, d));
- rect.bottom = static_cast<short>(MAX(c, d));
+RectF Region::toRect() const {
+ RectF rect;
+ rect.left = MIN(a, b);
+ rect.right = MAX(a, b);
+ rect.top = MIN(c, d);
+ rect.bottom = MAX(c, d);
return rect;
}
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index 365337a9b3b..d028e298927 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -23,7 +23,7 @@
#define PHOENIXVR_REGIONS_H
#include "common/array.h"
-#include "common/rect.h"
+#include "phoenixvr/rectf.h"
namespace Common {
class String;
@@ -32,7 +32,7 @@ class String;
namespace PhoenixVR {
struct Region {
float a, b, c, d;
- Common::Rect toRect() const;
+ RectF toRect() const;
};
class RegionSet {
Commit: 5c32c05f941497476b7744caa7ddffbecc5d5f48
https://github.com/scummvm/scummvm/commit/5c32c05f941497476b7744caa7ddffbecc5d5f48
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:34+01:00
Commit Message:
PHOENIXVR: add fmod to angles
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 17124282737..46eef83193f 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -245,9 +245,16 @@ Common::Error PhoenixVREngine::run() {
_mousePos = _screenCenter;
static const float kSpeedX = 0.2f;
static const float kSpeedY = 0.2f;
+ static const float PIx2 = M_PI * 2;
const auto dt = float(frameDuration) / 1000.0f;
_angleX += float(da.x) * kSpeedX * dt;
+ _angleX = fmodf(_angleX, PIx2);
+ if (_angleX < 0)
+ _angleX += PIx2;
_angleY += float(da.y) * kSpeedY * dt;
+ _angleY = fmodf(_angleY, M_PI * 2);
+ if (_angleY < 0)
+ _angleY += PIx2;
}
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
Commit: 034c3fcbf0fb3c97c605ee9a9ed5f11d48021d0b
https://github.com/scummvm/scummvm/commit/034c3fcbf0fb3c97c605ee9a9ed5f11d48021d0b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:34+01:00
Commit Message:
PHOENIXVR: lookup vr angles in vr mode
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 46eef83193f..31fbf548eb0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -309,7 +309,16 @@ Common::Error PhoenixVREngine::run() {
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
- if (c.rect.contains(_mousePos.x, _mousePos.y)) {
+ float x, y;
+ if (_vr.isVR()) {
+ x = _angleX / M_PI_4;
+ y = (2 * M_PI - _angleY) / M_PI_4;
+ debug("vr %g %g", x, y);
+ } else {
+ x = _mousePos.x;
+ y = _mousePos.y;
+ }
+ if (c.rect.contains(x, y)) {
cursor = c.surface;
if (cursor)
break;
Commit: d9f34e8e552195ea7da25f6f65644f8647b8377c
https://github.com/scummvm/scummvm/commit/d9f34e8e552195ea7da25f6f65644f8647b8377c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:34+01:00
Commit Message:
PHOENIXVR: add mixer stub
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 7f2ea90172b..bfb2427ef50 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -475,12 +475,12 @@ struct GoToWarp : public Script::Command {
struct PlaySound : public Script::Command {
Common::String sound;
int volume;
- int unk;
+ int loops;
- PlaySound(Common::String s, int v, int u) : sound(Common::move(s)), volume(v), unk(u) {}
+ PlaySound(Common::String s, int v, int l) : sound(Common::move(s)), volume(v), loops(l) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("play sound %s %d %d", sound.c_str(), volume, unk);
+ g_engine->playSound(sound, volume, loops);
}
};
@@ -490,7 +490,7 @@ struct StopSound : public Script::Command {
StopSound(Common::String s) : sound(Common::move(s)) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("stop sound %s", sound.c_str());
+ g_engine->stopSound(sound);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 31fbf548eb0..28aa9c45fe4 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -20,18 +20,18 @@
*/
#include "phoenixvr/phoenixvr.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+#include "audio/mixer.h"
#include "common/config-manager.h"
-#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "engines/util.h"
#include "graphics/framelimiter.h"
-#include "graphics/paletteman.h"
#include "image/pcx.h"
#include "phoenixvr/console.h"
-#include "phoenixvr/detection.h"
#include "phoenixvr/pakf.h"
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
@@ -44,7 +44,8 @@ PhoenixVREngine *g_engine;
PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
- _pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()) {
+ _pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()),
+ _mixer(syst->getMixer()) {
g_engine = this;
auto path = Common::FSNode(ConfMan.getPath("path"));
SearchMan.addSubDirectoryMatching(path, "NecroES/Data", 1, 1, true);
@@ -142,6 +143,30 @@ int PhoenixVREngine::getVariable(const Common::String &name) const {
return _variables.getVal(name);
}
+void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int loops) {
+ debug("play sound %s %d %d", sound.c_str(), volume, loops);
+ Audio::SoundHandle h;
+ Common::ScopedPtr<Common::File> f(new Common::File());
+ if (!f->open(Common::Path(sound))) {
+ warning("sound %s couldn't be found", sound.c_str());
+ return;
+ }
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &h, Audio::makeWAVStream(f.release(), DisposeAfterUse::YES), -1, volume);
+ if (loops < 0)
+ _mixer->loopChannel(h);
+ _sounds[sound] = h;
+}
+
+void PhoenixVREngine::stopSound(const Common::String &sound) {
+ debug("stop sound %s", sound.c_str());
+ auto it = _sounds.find(sound);
+ if (it != _sounds.end()) {
+ _mixer->stopHandle(it->_value);
+ _sounds.erase(it);
+ }
+}
+
Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
Common::File file;
if (!file.open(Common::Path(path))) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index e5140883ce5..56fd4b9ae5b 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -22,6 +22,7 @@
#ifndef PHOENIXVR_H
#define PHOENIXVR_H
+#include "audio/mixer.h"
#include "common/error.h"
#include "common/fs.h"
#include "common/hash-str.h"
@@ -107,6 +108,9 @@ public:
void setCursor(const Common::String &path, const Common::String &warp, int idx);
void hideCursor(const Common::String &warp, int idx);
+ void playSound(const Common::String &sound, uint8 volume, int loops);
+ void stopSound(const Common::String &sound);
+
void declareVariable(const Common::String &name);
void setVariable(const Common::String &name, int value);
int getVariable(const Common::String &name) const;
@@ -129,6 +133,7 @@ private:
Common::String _nextScript;
Common::String _nextWarp;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
+ Common::HashMap<Common::String, Audio::SoundHandle, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _sounds;
Common::ScopedPtr<Script> _script;
Script::ConstWarpPtr _warp;
@@ -144,6 +149,7 @@ private:
VR _vr;
float _angleX = 0;
float _angleY = 0;
+ Audio::Mixer *_mixer;
};
extern PhoenixVREngine *g_engine;
Commit: e5a283d6c5994a009b39880ba06ab595a9342cfc
https://github.com/scummvm/scummvm/commit/e5a283d6c5994a009b39880ba06ab595a9342cfc
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:34+01:00
Commit Message:
PHOENIXVR: calculate position once
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 28aa9c45fe4..7fad4ec9d1b 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -333,7 +333,7 @@ Common::Error PhoenixVREngine::run() {
_vr.render(_screen, _angleX, _angleY);
Graphics::Surface *cursor = nullptr;
- for (auto &c : _cursors) {
+ {
float x, y;
if (_vr.isVR()) {
x = _angleX / M_PI_4;
@@ -343,10 +343,12 @@ Common::Error PhoenixVREngine::run() {
x = _mousePos.x;
y = _mousePos.y;
}
- if (c.rect.contains(x, y)) {
- cursor = c.surface;
- if (cursor)
- break;
+ for (auto &c : _cursors) {
+ if (c.rect.contains(x, y)) {
+ cursor = c.surface;
+ if (cursor)
+ break;
+ }
}
}
if (!cursor)
Commit: f9485b3add32f188beba052ed64273c902242876
https://github.com/scummvm/scummvm/commit/f9485b3add32f188beba052ed64273c902242876
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:35+01:00
Commit Message:
PHOENIXVR: populate cursor array from region set
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 7fad4ec9d1b..d363a332412 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -250,10 +250,8 @@ Common::Error PhoenixVREngine::run() {
break;
case Common::EVENT_LBUTTONUP:
debug("click %s", _mousePos.toString().c_str());
- if (!_regSet)
- break;
- for (uint i = 0; i != _regSet->size(); ++i) {
- auto rect = _regSet->getRegion(i).toRect();
+ for (uint i = 0, n = _cursors.size(); i != n; ++i) {
+ auto &rect = _cursors[i].rect;
if (rect.contains(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
@@ -322,7 +320,11 @@ Common::Error PhoenixVREngine::run() {
for (auto &c : _cursors)
c.free();
+
_cursors.resize(_regSet->size());
+ for (uint i = 0; i != _regSet->size(); ++i) {
+ _cursors[i].rect = _regSet->getRegion(i).toRect();
+ }
Script::ExecutionContext ctx;
debug("execute warp script %s", _warp->vrFile.c_str());
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index d028e298927..b7db03d8f44 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -41,7 +41,7 @@ class RegionSet {
public:
RegionSet(const Common::String &fname);
uint size() const { return _regions.size(); }
- const Region &getRegion(int idx) const {
+ const Region &getRegion(uint idx) const {
return _regions[idx];
}
};
Commit: f8b84254a58ad829db894482368292305fcaaa09
https://github.com/scummvm/scummvm/commit/f8b84254a58ad829db894482368292305fcaaa09
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:35+01:00
Commit Message:
PHOENIXVR: add containsVR() with required transformations
Changed paths:
A engines/phoenixvr/rectf.cpp
engines/phoenixvr/module.mk
engines/phoenixvr/rectf.h
diff --git a/engines/phoenixvr/module.mk b/engines/phoenixvr/module.mk
index b841e207140..22c89ac1631 100644
--- a/engines/phoenixvr/module.mk
+++ b/engines/phoenixvr/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS = \
phoenixvr.o \
console.o \
metaengine.o \
+ rectf.o \
region_set.o \
script.o \
vr.o
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
new file mode 100644
index 00000000000..e6208d56ae2
--- /dev/null
+++ b/engines/phoenixvr/rectf.cpp
@@ -0,0 +1,33 @@
+/* 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 "phoenixvr/rectf.h"
+#include "phoenixvr/angle.h"
+
+namespace PhoenixVR {
+bool RectF::containsVR(float ax, float ay) const {
+ Angle x(ax);
+ x.add(+M_PI_4);
+ float y = ay;
+ return contains(x.angle(), y);
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index e1f826ecd45..de654ea577e 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -33,6 +33,7 @@ BEGIN_RECT_TYPE(float, RectF, PointF);
Common::String toString() const {
return Common::String::format("%g, %g, %g, %g", left, top, right, bottom);
}
+bool containsVR(float ax, float ay) const;
END_RECT_TYPE(float, RectF, PointF);
} // namespace PhoenixVR
Commit: a80c3d8730edfe4dec00fe4200691636ff58a7b0
https://github.com/scummvm/scummvm/commit/a80c3d8730edfe4dec00fe4200691636ff58a7b0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:35+01:00
Commit Message:
PHOENIXVR: add Angle structure to handle wrapping
Changed paths:
A engines/phoenixvr/angle.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/angle.h b/engines/phoenixvr/angle.h
new file mode 100644
index 00000000000..11ec56b8469
--- /dev/null
+++ b/engines/phoenixvr/angle.h
@@ -0,0 +1,49 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_ANGLE_H
+#define PHOENIXVR_ANGLE_H
+
+#include <math.h>
+
+namespace Common {
+class String;
+};
+
+namespace PhoenixVR {
+class Angle {
+ float _angle;
+
+public:
+ Angle(float angle = 0) : _angle(angle) {}
+
+ float angle() const { return _angle; }
+
+ void add(float v) {
+ static const float kPi2 = M_PI * 2;
+ _angle = fmod(_angle + v, kPi2);
+ if (_angle < 0)
+ _angle += kPi2;
+ }
+};
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index d363a332412..a24e5914d23 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -268,16 +268,9 @@ Common::Error PhoenixVREngine::run() {
_mousePos = _screenCenter;
static const float kSpeedX = 0.2f;
static const float kSpeedY = 0.2f;
- static const float PIx2 = M_PI * 2;
const auto dt = float(frameDuration) / 1000.0f;
- _angleX += float(da.x) * kSpeedX * dt;
- _angleX = fmodf(_angleX, PIx2);
- if (_angleX < 0)
- _angleX += PIx2;
- _angleY += float(da.y) * kSpeedY * dt;
- _angleY = fmodf(_angleY, M_PI * 2);
- if (_angleY < 0)
- _angleY += PIx2;
+ _angleX.add(float(da.x) * kSpeedX * dt);
+ _angleY.add(float(da.y) * kSpeedY * dt);
}
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
@@ -332,25 +325,14 @@ Common::Error PhoenixVREngine::run() {
test->scope.exec(ctx);
}
- _vr.render(_screen, _angleX, _angleY);
+ _vr.render(_screen, _angleX.angle(), _angleY.angle());
Graphics::Surface *cursor = nullptr;
- {
- float x, y;
- if (_vr.isVR()) {
- x = _angleX / M_PI_4;
- y = (2 * M_PI - _angleY) / M_PI_4;
- debug("vr %g %g", x, y);
- } else {
- x = _mousePos.x;
- y = _mousePos.y;
- }
- for (auto &c : _cursors) {
- if (c.rect.contains(x, y)) {
- cursor = c.surface;
- if (cursor)
- break;
- }
+ for (auto &c : _cursors) {
+ if (_vr.isVR() ? c.rect.containsVR(_angleX.angle(), _angleX.angle()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
+ cursor = c.surface;
+ if (cursor)
+ break;
}
}
if (!cursor)
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 56fd4b9ae5b..228b258e969 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -35,6 +35,7 @@
#include "engines/savestate.h"
#include "graphics/screen.h"
+#include "phoenixvr/angle.h"
#include "phoenixvr/detection.h"
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
@@ -122,6 +123,13 @@ public:
Script::ConstWarpPtr getCurrentWarp() { return _warp; }
Region getRegion(int idx) const;
+ uint numCursors() const {
+ return _cursors.size();
+ }
+ const RectF *getCursorRect(uint idx) const {
+ return idx < _cursors.size() ? &_cursors[idx].rect : nullptr;
+ }
+
private:
static Common::String removeDrive(const Common::String &path);
static Common::String resolvePath(const Common::String &path);
@@ -147,8 +155,8 @@ private:
Common::Array<Cursor> _cursors;
Cursor _defaultCursor;
VR _vr;
- float _angleX = 0;
- float _angleY = 0;
+ Angle _angleX;
+ Angle _angleY;
Audio::Mixer *_mixer;
};
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index d0a1de66212..f10e26de146 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -9,6 +9,7 @@
#include "graphics/yuv_to_rgb.h"
#include "image/bmp.h"
#include "phoenixvr/dct.h"
+#include "phoenixvr/phoenixvr.h"
namespace PhoenixVR {
@@ -600,10 +601,14 @@ struct Projection {
Projection(uint num, float start, float fov) : points(num) {
const auto da = fov / float(num);
- float a = start - fov / 2;
+ float a = fmodf(start - fov / 2 + 2 * M_PI, 2 * M_PI);
+ float a2 = fmodf(start + 2 * M_PI, 2 * M_PI);
for (uint i = 0; i != num; ++i) {
- points[i] = {a, sin(a), cos(a)};
+ points[i] = {a2, sin(a), cos(a)};
a += da;
+ a2 += da;
+ a = fmodf(a, M_PI * 2);
+ a2 = fmodf(a2, M_PI * 2);
}
}
};
@@ -644,7 +649,23 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
srcX &= 0xff;
srcY &= 0xff;
srcY += (tileId << 8);
- screen->setPixel(dstX, dstY, _pic->getPixel(srcX, srcY));
+ auto color = _pic->getPixel(srcX, srcY);
+#if 0
+ auto n = g_engine->numCursors();
+ for(uint i = 0; i != n; ++i) {
+ auto *src = g_engine->getCursorRect(i);
+ if (src && src->containsVR(ph.angle, pv.angle)) {
+ uint8 r, g, b;
+ _pic->format.colorToRGB(color, r, g, b);
+ r += 32;
+ g += 32;
+ b += 32;
+ color = _pic->format.RGBToColor(r, g, b);
+ break;
+ }
+ }
+#endif
+ screen->setPixel(dstX, dstY, color);
}
}
}
Commit: 0e838ea5592ad8e904203c579114b5709db80e87
https://github.com/scummvm/scummvm/commit/0e838ea5592ad8e904203c579114b5709db80e87
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:36+01:00
Commit Message:
PHOENIXVR: vr fix up
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index a24e5914d23..2307dc4a4a8 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -252,7 +252,7 @@ Common::Error PhoenixVREngine::run() {
debug("click %s", _mousePos.toString().c_str());
for (uint i = 0, n = _cursors.size(); i != n; ++i) {
auto &rect = _cursors[i].rect;
- if (rect.contains(event.mouse.x, event.mouse.y)) {
+ if (_vr.isVR() ? rect.containsVR(_angleX.angle(), _angleY.angle()) : rect.contains(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
}
@@ -329,7 +329,7 @@ Common::Error PhoenixVREngine::run() {
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
- if (_vr.isVR() ? c.rect.containsVR(_angleX.angle(), _angleX.angle()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
+ if (_vr.isVR() ? c.rect.containsVR(_angleX.angle(), _angleY.angle()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
cursor = c.surface;
if (cursor)
break;
Commit: 1b0c6366d6e07c25cae19ac75dac5f7c81836d64
https://github.com/scummvm/scummvm/commit/1b0c6366d6e07c25cae19ac75dac5f7c81836d64
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:36+01:00
Commit Message:
PHOENIXVR: shut noisy log up
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 2307dc4a4a8..4537a2d54f7 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -262,7 +262,7 @@ Common::Error PhoenixVREngine::run() {
break;
}
}
- if (_vr.isVR()) {
+ if (_vr.isVR() && _mousePos != _screenCenter) {
auto da = _mousePos - _screenCenter;
_system->warpMouse(_screenCenter.x, _screenCenter.y);
_mousePos = _screenCenter;
@@ -271,6 +271,7 @@ Common::Error PhoenixVREngine::run() {
const auto dt = float(frameDuration) / 1000.0f;
_angleX.add(float(da.x) * kSpeedX * dt);
_angleY.add(float(da.y) * kSpeedY * dt);
+ debug("angle %g %g", _angleX.angle(), _angleY.angle());
}
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index f10e26de146..7dc1e963c68 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -633,7 +633,6 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
static const float kFOV = (90 / 180.0f) * M_PI;
Projection projH(w, ax, kFOV);
Projection projV(h, ay - M_PI_2, kFOV);
- debug("angle %g %g", ax, ay);
for (int dstY = 0; dstY != h; ++dstY) {
auto &pv = projV.points[dstY];
for (int dstX = 0; dstX != w; ++dstX) {
Commit: 1b3948ac15fa64feb9dc4b55c8d33b4fcab4ef82
https://github.com/scummvm/scummvm/commit/1b3948ac15fa64feb9dc4b55c8d33b4fcab4ef82
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:36+01:00
Commit Message:
PHOENIXVR: expose transformation for vr, remove containVR
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/rectf.cpp
engines/phoenixvr/rectf.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 4537a2d54f7..5c53900c195 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -248,16 +248,21 @@ Common::Error PhoenixVREngine::run() {
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
break;
- case Common::EVENT_LBUTTONUP:
- debug("click %s", _mousePos.toString().c_str());
+ case Common::EVENT_LBUTTONUP: {
+ auto vrPos = currentVRPos();
+ if (_vr.isVR()) {
+ debug("click ax: %g, ay: %g", vrPos.x, vrPos.y);
+ } else
+ debug("click %s", _mousePos.toString().c_str());
+
for (uint i = 0, n = _cursors.size(); i != n; ++i) {
auto &rect = _cursors[i].rect;
- if (_vr.isVR() ? rect.containsVR(_angleX.angle(), _angleY.angle()) : rect.contains(event.mouse.x, event.mouse.y)) {
+ if (_vr.isVR() ? rect.contains(vrPos) : rect.contains(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
}
}
- break;
+ } break;
default:
break;
}
@@ -330,7 +335,7 @@ Common::Error PhoenixVREngine::run() {
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
- if (_vr.isVR() ? c.rect.containsVR(_angleX.angle(), _angleY.angle()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
+ if (_vr.isVR() ? c.rect.contains(currentVRPos()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
cursor = c.surface;
if (cursor)
break;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 228b258e969..54b6a0dd2bb 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -135,6 +135,9 @@ private:
static Common::String resolvePath(const Common::String &path);
Graphics::Surface *loadSurface(const Common::String &path);
void paint(Graphics::Surface &src, Common::Point dst);
+ PointF currentVRPos() const {
+ return RectF::transform(_angleX.angle(), _angleY.angle());
+ }
private:
Common::Point _mousePos;
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index e6208d56ae2..e5d8f67f5ef 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -23,11 +23,12 @@
#include "phoenixvr/angle.h"
namespace PhoenixVR {
-bool RectF::containsVR(float ax, float ay) const {
+
+PointF RectF::transform(float ax, float ay) {
Angle x(ax);
x.add(+M_PI_4);
float y = ay;
- return contains(x.angle(), y);
+ return {x.angle(), y};
}
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index de654ea577e..0c51da3f553 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -33,7 +33,7 @@ BEGIN_RECT_TYPE(float, RectF, PointF);
Common::String toString() const {
return Common::String::format("%g, %g, %g, %g", left, top, right, bottom);
}
-bool containsVR(float ax, float ay) const;
+static PointF transform(float ax, float ay);
END_RECT_TYPE(float, RectF, PointF);
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 7dc1e963c68..9d5c8a6c68b 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -653,7 +653,7 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
auto n = g_engine->numCursors();
for(uint i = 0; i != n; ++i) {
auto *src = g_engine->getCursorRect(i);
- if (src && src->containsVR(ph.angle, pv.angle)) {
+ if (src && src->contains(RectF::transform(ph.angle, pv.angle))) {
uint8 r, g, b;
_pic->format.colorToRGB(color, r, g, b);
r += 32;
Commit: df282ca3e61a0866d37f9d5b50f6ff5c0d6c5e50
https://github.com/scummvm/scummvm/commit/df282ca3e61a0866d37f9d5b50f6ff5c0d6c5e50
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:37+01:00
Commit Message:
PHOENIXVR: add keyword() to parser to distinguish between prefix and keyword, implement setAngle
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index bfb2427ef50..4a771af6b3c 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -463,6 +463,15 @@ struct AngleYMax : public Script::Command {
}
};
+struct SetAngle : public Script::Command {
+ float a0, a1;
+ SetAngle(float a0_, float a1_) : a0(a0_), a1(a1_) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("set angle %g %g", a0, a1);
+ }
+};
+
struct GoToWarp : public Script::Command {
Common::String warp;
GoToWarp(Common::String w) : warp(Common::move(w)) {}
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 951000941fb..822b6675fcf 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -48,14 +48,33 @@ public:
return false;
}
- bool maybe(const Common::String &prefix) {
+ bool peek(const Common::String &prefix) {
skip();
- bool yes = scumm_strnicmp(_line.c_str() + _pos, prefix.c_str(), prefix.size()) == 0;
- if (yes) {
+ return scumm_strnicmp(_line.c_str() + _pos, prefix.c_str(), prefix.size()) == 0;
+ }
+
+ bool maybe(const Common::String &prefix) {
+ if (peek(prefix)) {
_pos += prefix.size();
skip();
+ return true;
}
- return yes;
+ return false;
+ }
+
+ bool keyword(const Common::String &prefix) {
+ skip();
+ bool yes = peek(prefix);
+ // keywords ends either on non-alpha or eof
+ if (yes) {
+ auto end = _pos + prefix.size();
+ if (end >= _line.size() || !Common::isAlpha(_line[end])) {
+ _pos += prefix.size();
+ skip();
+ return true;
+ }
+ }
+ return false;
}
Common::String nextArg() {
@@ -122,16 +141,16 @@ public:
Script::CommandPtr parseCommand() {
using CommandPtr = Script::CommandPtr;
- if (maybe("setcursordefault")) {
+ if (keyword("setcursordefault")) {
auto idx = nextInt();
expect(',');
return CommandPtr(new SetCursorDefault(idx, nextWord()));
- } else if (maybe("lockkey")) {
+ } else if (keyword("lockkey")) {
auto idx = nextInt();
expect(',');
auto fname = nextWord();
return CommandPtr(new LockKey(idx, Common::move(fname)));
- } else if (maybe("resetlockkey")) {
+ } else if (keyword("resetlockkey")) {
return CommandPtr(new ResetLockKey());
} else if (maybe("fade=")) {
auto arg0 = nextInt();
@@ -142,6 +161,14 @@ public:
return CommandPtr(new Fade(arg0, arg1, arg2));
} else if (maybe("setzoom=")) {
return CommandPtr(new Zoom(nextInt()));
+ } else if (maybe("setangle=")) {
+ auto i0 = nextInt();
+ if (i0 > 4095)
+ i0 -= 8192;
+ auto a0 = i0 / 1024.0f;
+ expect(',');
+ auto a1 = nextInt() / 1024.0f;
+ return CommandPtr(new SetAngle(a0, a1));
} else if (maybe("anglexmax=")) {
return CommandPtr(new AngleXMax(nextInt() / 1024.0f));
} else if (maybe("angleymax=")) {
@@ -149,9 +176,9 @@ public:
expect(',');
auto y1 = nextInt() / 1024.0f;
return CommandPtr(new AngleYMax(y0, y1));
- } else if (maybe("gotowarp")) {
+ } else if (keyword("gotowarp")) {
return CommandPtr(new GoToWarp(nextWord()));
- } else if (maybe("playsound3d")) {
+ } else if (keyword("playsound3d")) {
auto sound = nextWord();
expect(',');
auto arg0 = nextInt();
@@ -160,37 +187,37 @@ public:
expect(',');
auto arg2 = nextInt();
return CommandPtr(new PlaySound3D(Common::move(sound), arg0, arg1 / 1024.0f, arg2));
- } else if (maybe("playsound")) {
+ } else if (keyword("playsound")) {
auto sound = nextWord();
expect(',');
auto arg0 = nextInt();
expect(',');
auto arg1 = nextInt();
return CommandPtr(new PlaySound(Common::move(sound), arg0, arg1));
- } else if (maybe("stopsound3d")) {
+ } else if (keyword("stopsound3d")) {
return CommandPtr(new StopSound3D(nextWord()));
- } else if (maybe("stopsound")) {
+ } else if (keyword("stopsound")) {
return CommandPtr(new StopSound(nextWord()));
- } else if (maybe("setcursor")) {
+ } else if (keyword("setcursor")) {
auto image = nextWord();
expect(',');
auto warp = nextWord();
expect(',');
auto idx = nextInt();
return CommandPtr(new SetCursor(Common::move(image), Common::move(warp), idx));
- } else if (maybe("hidecursor")) {
+ } else if (keyword("hidecursor")) {
auto warp = nextWord();
expect(',');
auto idx = nextInt();
return CommandPtr(new HideCursor(Common::move(warp), idx));
- } else if (maybe("set")) {
+ } else if (keyword("set")) {
auto var = nextWord();
expect('=');
auto value = nextInt();
return CommandPtr(new Set(Common::move(var), value));
- } else if (maybe("return")) {
+ } else if (keyword("return")) {
return CommandPtr{new Return()};
- } else if (maybe("end")) {
+ } else if (keyword("end")) {
return CommandPtr{new End()};
}
return {};
Commit: cf096169549781cb0b0779662f0f3e9ec87ee317
https://github.com/scummvm/scummvm/commit/cf096169549781cb0b0779662f0f3e9ec87ee317
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:37+01:00
Commit Message:
PHOENIXVR: scripting using 8192 fixed point ints for angles
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 822b6675fcf..cf7a5927f62 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -139,6 +139,10 @@ public:
return list;
}
+ static float toAngle(int a) {
+ return (float(a) / 8192.0f) * float(M_PI * 2);
+ }
+
Script::CommandPtr parseCommand() {
using CommandPtr = Script::CommandPtr;
if (keyword("setcursordefault")) {
@@ -165,16 +169,16 @@ public:
auto i0 = nextInt();
if (i0 > 4095)
i0 -= 8192;
- auto a0 = i0 / 1024.0f;
+ auto a0 = toAngle(i0);
expect(',');
- auto a1 = nextInt() / 1024.0f;
+ auto a1 = toAngle(nextInt());
return CommandPtr(new SetAngle(a0, a1));
} else if (maybe("anglexmax=")) {
- return CommandPtr(new AngleXMax(nextInt() / 1024.0f));
+ return CommandPtr(new AngleXMax(toAngle(nextInt())));
} else if (maybe("angleymax=")) {
- auto y0 = nextInt() / 1024.0f;
+ auto y0 = toAngle(nextInt());
expect(',');
- auto y1 = nextInt() / 1024.0f;
+ auto y1 = toAngle(nextInt());
return CommandPtr(new AngleYMax(y0, y1));
} else if (keyword("gotowarp")) {
return CommandPtr(new GoToWarp(nextWord()));
@@ -186,7 +190,7 @@ public:
auto arg1 = nextInt();
expect(',');
auto arg2 = nextInt();
- return CommandPtr(new PlaySound3D(Common::move(sound), arg0, arg1 / 1024.0f, arg2));
+ return CommandPtr(new PlaySound3D(Common::move(sound), arg0, toAngle(arg1), arg2));
} else if (keyword("playsound")) {
auto sound = nextWord();
expect(',');
Commit: b51137429deeac6dbfaf13a673f4b1b4b14c60db
https://github.com/scummvm/scummvm/commit/b51137429deeac6dbfaf13a673f4b1b4b14c60db
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:37+01:00
Commit Message:
PHOENIXVR: correct angley for pi/2
Changed paths:
engines/phoenixvr/rectf.cpp
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index e5d8f67f5ef..14372696051 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -27,7 +27,7 @@ namespace PhoenixVR {
PointF RectF::transform(float ax, float ay) {
Angle x(ax);
x.add(+M_PI_4);
- float y = ay;
+ float y = ay + float(M_PI_2);
return {x.angle(), y};
}
Commit: 38f0aedde9c0f2200486792c9b0fdfddb25644a8
https://github.com/scummvm/scummvm/commit/38f0aedde9c0f2200486792c9b0fdfddb25644a8
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:38+01:00
Commit Message:
PHOENIXVR: support 2 default cursors
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 5c53900c195..dc71e11a2af 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -105,10 +105,11 @@ Region PhoenixVREngine::getRegion(int idx) const {
void PhoenixVREngine::setCursorDefault(int idx, const Common::String &path) {
debug("setCursorDefault %d: %s", idx, path.c_str());
- if (idx == 0) {
- _defaultCursor.free();
- _defaultCursor.surface = loadSurface(path);
- }
+ if (idx == 0 || idx == 1) {
+ _defaultCursor[idx].free();
+ _defaultCursor[idx].surface = loadSurface(path);
+ } else
+ warning("only 2 default cursors supported, got %d", idx);
}
void PhoenixVREngine::setCursor(const Common::String &path, const Common::String &wname, int idx) {
@@ -337,12 +338,13 @@ Common::Error PhoenixVREngine::run() {
for (auto &c : _cursors) {
if (_vr.isVR() ? c.rect.contains(currentVRPos()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
cursor = c.surface;
- if (cursor)
- break;
+ if (!cursor)
+ cursor = _defaultCursor[1].surface;
+ break;
}
}
if (!cursor)
- cursor = _defaultCursor.surface;
+ cursor = _defaultCursor[0].surface;
if (cursor) {
paint(*cursor, _mousePos - Common::Point(cursor->w / 2, cursor->h / 2));
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 54b6a0dd2bb..c0f98254192 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -156,7 +156,7 @@ private:
void free();
};
Common::Array<Cursor> _cursors;
- Cursor _defaultCursor;
+ Cursor _defaultCursor[2];
VR _vr;
Angle _angleX;
Angle _angleY;
Commit: de885c71435aac294fa975327084f75cafc162f6
https://github.com/scummvm/scummvm/commit/de885c71435aac294fa975327084f75cafc162f6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:38+01:00
Commit Message:
PHOENIXVR: move dct tables to separate file
Changed paths:
A engines/phoenixvr/dct_tables.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/dct_tables.h b/engines/phoenixvr/dct_tables.h
new file mode 100644
index 00000000000..51fb749678f
--- /dev/null
+++ b/engines/phoenixvr/dct_tables.h
@@ -0,0 +1,237 @@
+/* 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/scummsys.h"
+
+namespace PhoenixVR {
+namespace {
+
+const byte ZIGZAG[] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4,
+ 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7,
+ 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22,
+ 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39,
+ 46, 53, 60, 61, 54, 47, 55, 62, 63};
+
+// looks like standard JPEG quantisation matrix
+const char QY[] = {
+ 16,
+ 11,
+ 10,
+ 16,
+ 24,
+ 40,
+ 51,
+ 61,
+ 12,
+ 12,
+ 14,
+ 19,
+ 26,
+ 58,
+ 60,
+ 55,
+ 14,
+ 13,
+ 16,
+ 24,
+ 40,
+ 57,
+ 69,
+ 56,
+ 14,
+ 17,
+ 22,
+ 29,
+ 51,
+ 87,
+ 80,
+ 62,
+ 18,
+ 22,
+ 37,
+ 56,
+ 68,
+ 109,
+ 103,
+ 77,
+ 24,
+ 35,
+ 55,
+ 64,
+ 81,
+ 104,
+ 113,
+ 92,
+ 49,
+ 64,
+ 78,
+ 87,
+ 103,
+ 121,
+ 120,
+ 101,
+ 72,
+ 92,
+ 95,
+ 98,
+ 112,
+ 100,
+ 103,
+ 99,
+};
+
+const char QUV[] = {
+ 17,
+ 18,
+ 24,
+ 47,
+ 99,
+ 99,
+ 99,
+ 99,
+ 18,
+ 21,
+ 26,
+ 66,
+ 99,
+ 99,
+ 99,
+ 99,
+ 24,
+ 26,
+ 56,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 47,
+ 66,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+ 99,
+};
+
+const uint16 Q[] = {
+ 0x4000,
+ 0x58C5,
+ 0x539F,
+ 0x4B42,
+ 0x4000,
+ 0x3249,
+ 0x22A3,
+ 0x11A8,
+ 0x58C5,
+ 0x7B21,
+ 0x73FC,
+ 0x6862,
+ 0x58C5,
+ 0x45BF,
+ 0x300B,
+ 0x187E,
+ 0x539F,
+ 0x73FC,
+ 0x6D41,
+ 0x6254,
+ 0x539F,
+ 0x41B3,
+ 0x2D41,
+ 0x1712,
+ 0x4B42,
+ 0x6862,
+ 0x6254,
+ 0x587E,
+ 0x4B42,
+ 0x3B21,
+ 0x28BA,
+ 0x14C3,
+ 0x4000,
+ 0x58C5,
+ 0x539F,
+ 0x4B42,
+ 0x4000,
+ 0x3249,
+ 0x22A3,
+ 0x11A8,
+ 0x3249,
+ 0x45BF,
+ 0x41B3,
+ 0x3B21,
+ 0x3249,
+ 0x2782,
+ 0x1B37,
+ 0x0DE0,
+ 0x22A3,
+ 0x300B,
+ 0x2D41,
+ 0x28BA,
+ 0x22A3,
+ 0x1B37,
+ 0x12BF,
+ 0x98E,
+ 0x11A8,
+ 0x187E,
+ 0x1712,
+ 0x14C3,
+ 0x11A8,
+ 0x0DE0,
+ 0x98E,
+ 0x4DF,
+};
+
+} // namespace
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 9d5c8a6c68b..d1fd2c2ca0e 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -1,3 +1,24 @@
+/* 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 "phoenixvr/vr.h"
#include "common/array.h"
#include "common/debug.h"
@@ -9,6 +30,7 @@
#include "graphics/yuv_to_rgb.h"
#include "image/bmp.h"
#include "phoenixvr/dct.h"
+#include "phoenixvr/dct_tables.h"
#include "phoenixvr/phoenixvr.h"
namespace PhoenixVR {
@@ -24,215 +46,6 @@ T clip(T a) {
return a >= 0 ? a <= 255 ? a : 255 : 0;
}
-const byte ZIGZAG[] = {
- 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4,
- 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7,
- 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22,
- 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39,
- 46, 53, 60, 61, 54, 47, 55, 62, 63};
-
-// looks like standard JPEG quantisation matrix
-const char QY[] = {
- 16,
- 11,
- 10,
- 16,
- 24,
- 40,
- 51,
- 61,
- 12,
- 12,
- 14,
- 19,
- 26,
- 58,
- 60,
- 55,
- 14,
- 13,
- 16,
- 24,
- 40,
- 57,
- 69,
- 56,
- 14,
- 17,
- 22,
- 29,
- 51,
- 87,
- 80,
- 62,
- 18,
- 22,
- 37,
- 56,
- 68,
- 109,
- 103,
- 77,
- 24,
- 35,
- 55,
- 64,
- 81,
- 104,
- 113,
- 92,
- 49,
- 64,
- 78,
- 87,
- 103,
- 121,
- 120,
- 101,
- 72,
- 92,
- 95,
- 98,
- 112,
- 100,
- 103,
- 99,
-};
-
-const char QUV[] = {
- 17,
- 18,
- 24,
- 47,
- 99,
- 99,
- 99,
- 99,
- 18,
- 21,
- 26,
- 66,
- 99,
- 99,
- 99,
- 99,
- 24,
- 26,
- 56,
- 99,
- 99,
- 99,
- 99,
- 99,
- 47,
- 66,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
- 99,
-};
-
-const uint16 Q[] = {
- 0x4000,
- 0x58C5,
- 0x539F,
- 0x4B42,
- 0x4000,
- 0x3249,
- 0x22A3,
- 0x11A8,
- 0x58C5,
- 0x7B21,
- 0x73FC,
- 0x6862,
- 0x58C5,
- 0x45BF,
- 0x300B,
- 0x187E,
- 0x539F,
- 0x73FC,
- 0x6D41,
- 0x6254,
- 0x539F,
- 0x41B3,
- 0x2D41,
- 0x1712,
- 0x4B42,
- 0x6862,
- 0x6254,
- 0x587E,
- 0x4B42,
- 0x3B21,
- 0x28BA,
- 0x14C3,
- 0x4000,
- 0x58C5,
- 0x539F,
- 0x4B42,
- 0x4000,
- 0x3249,
- 0x22A3,
- 0x11A8,
- 0x3249,
- 0x45BF,
- 0x41B3,
- 0x3B21,
- 0x3249,
- 0x2782,
- 0x1B37,
- 0x0DE0,
- 0x22A3,
- 0x300B,
- 0x2D41,
- 0x28BA,
- 0x22A3,
- 0x1B37,
- 0x12BF,
- 0x98E,
- 0x11A8,
- 0x187E,
- 0x1712,
- 0x14C3,
- 0x11A8,
- 0x0DE0,
- 0x98E,
- 0x4DF,
-};
-
struct Quantisation {
int quantY[64];
int quantCbCr[64];
Commit: 042ce50a0eaff029923e0697f14f378bf875c265
https://github.com/scummvm/scummvm/commit/042ce50a0eaff029923e0697f14f378bf875c265
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:38+01:00
Commit Message:
PHOENIXVR: pass fov from engine
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index dc71e11a2af..541a410e8ab 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -45,6 +45,7 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
_pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()),
+ _fov(M_PI_2),
_mixer(syst->getMixer()) {
g_engine = this;
auto path = Common::FSNode(ConfMan.getPath("path"));
@@ -332,7 +333,7 @@ Common::Error PhoenixVREngine::run() {
test->scope.exec(ctx);
}
- _vr.render(_screen, _angleX.angle(), _angleY.angle());
+ _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index c0f98254192..8cf47045a12 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -158,6 +158,7 @@ private:
Common::Array<Cursor> _cursors;
Cursor _defaultCursor[2];
VR _vr;
+ float _fov;
Angle _angleX;
Angle _angleY;
Audio::Mixer *_mixer;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index d1fd2c2ca0e..8019a309588 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -427,7 +427,7 @@ struct Projection {
};
} // namespace
-void VR::render(Graphics::Screen *screen, float ax, float ay) {
+void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
if (!_pic) {
screen->clear();
return;
@@ -443,9 +443,8 @@ void VR::render(Graphics::Screen *screen, float ax, float ay) {
auto w = g_system->getWidth();
auto h = g_system->getHeight();
- static const float kFOV = (90 / 180.0f) * M_PI;
- Projection projH(w, ax, kFOV);
- Projection projV(h, ay - M_PI_2, kFOV);
+ Projection projH(w, ax, fov);
+ Projection projV(h, ay - M_PI_2, fov);
for (int dstY = 0; dstY != h; ++dstY) {
auto &pv = projV.points[dstY];
for (int dstX = 0; dstX != w; ++dstX) {
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index f0d5359d87e..2a5b022d94b 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -42,7 +42,7 @@ public:
VR &operator=(VR &&) = default;
static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
- void render(Graphics::Screen *screen, float ax, float ay);
+ void render(Graphics::Screen *screen, float ax, float ay, float fov);
bool isVR() const { return _vr; }
};
} // namespace PhoenixVR
Commit: b178268c7b03f2ca5cecb68a49bbdff9b719150b
https://github.com/scummvm/scummvm/commit/b178268c7b03f2ca5cecb68a49bbdff9b719150b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:38+01:00
Commit Message:
PHOENIXVR: remove vr x-angle hack, moved to RectF::translate
Changed paths:
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/rectf.cpp
engines/phoenixvr/rectf.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 8cf47045a12..8400624130f 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -136,7 +136,7 @@ private:
Graphics::Surface *loadSurface(const Common::String &path);
void paint(Graphics::Surface &src, Common::Point dst);
PointF currentVRPos() const {
- return RectF::transform(_angleX.angle(), _angleY.angle());
+ return RectF::transform(_angleX.angle(), _angleY.angle(), _fov);
}
private:
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index 14372696051..a1d79373da2 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -24,9 +24,9 @@
namespace PhoenixVR {
-PointF RectF::transform(float ax, float ay) {
+PointF RectF::transform(float ax, float ay, float fov) {
Angle x(ax);
- x.add(+M_PI_4);
+ x.add(M_PI_2);
float y = ay + float(M_PI_2);
return {x.angle(), y};
}
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index 0c51da3f553..447ccdc3336 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -33,7 +33,7 @@ BEGIN_RECT_TYPE(float, RectF, PointF);
Common::String toString() const {
return Common::String::format("%g, %g, %g, %g", left, top, right, bottom);
}
-static PointF transform(float ax, float ay);
+static PointF transform(float ax, float ay, float fov);
END_RECT_TYPE(float, RectF, PointF);
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8019a309588..5b51295654c 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -414,14 +414,14 @@ struct Projection {
Projection(uint num, float start, float fov) : points(num) {
const auto da = fov / float(num);
- float a = fmodf(start - fov / 2 + 2 * M_PI, 2 * M_PI);
- float a2 = fmodf(start + 2 * M_PI, 2 * M_PI);
+ static const float kPi2 = 2 * M_PI;
+ float a = fmodf(start - fov / 2, kPi2);
+ if (a < 0)
+ a += kPi2;
for (uint i = 0; i != num; ++i) {
- points[i] = {a2, sin(a), cos(a)};
+ points[i] = {a, sin(a), cos(a)};
a += da;
- a2 += da;
a = fmodf(a, M_PI * 2);
- a2 = fmodf(a2, M_PI * 2);
}
}
};
@@ -465,7 +465,7 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
auto n = g_engine->numCursors();
for(uint i = 0; i != n; ++i) {
auto *src = g_engine->getCursorRect(i);
- if (src && src->contains(RectF::transform(ph.angle, pv.angle))) {
+ if (src && src->contains(RectF::transform(ph.angle, pv.angle, fov))) {
uint8 r, g, b;
_pic->format.colorToRGB(color, r, g, b);
r += 32;
Commit: 0e0a48eb08ebb19b7cd9fcb334b8bdaa8499975f
https://github.com/scummvm/scummvm/commit/0e0a48eb08ebb19b7cd9fcb334b8bdaa8499975f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:39+01:00
Commit Message:
PHOENIXVR: break after first region
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 541a410e8ab..6e21e43c9aa 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -262,6 +262,7 @@ Common::Error PhoenixVREngine::run() {
if (_vr.isVR() ? rect.contains(vrPos) : rect.contains(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
+ break;
}
}
} break;
Commit: abb811b995934cdfd89770b6cfab5f6443949095
https://github.com/scummvm/scummvm/commit/abb811b995934cdfd89770b6cfab5f6443949095
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:39+01:00
Commit Message:
VIDEO: add 4xm decoder stub
Changed paths:
A video/4xm_decoder.cpp
A video/4xm_decoder.h
video/module.mk
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
new file mode 100644
index 00000000000..0b9614f8b1f
--- /dev/null
+++ b/video/4xm_decoder.cpp
@@ -0,0 +1,204 @@
+/* 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 "video/4xm_decoder.h"
+#include "audio/audiostream.h"
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+
+namespace Video {
+
+Common::Rational floatToRational(float value) {
+ int num = static_cast<int>(1000 * value);
+ int denom = 1000;
+ return {num, denom};
+}
+
+namespace {
+Common::String tagName(uint32 tag) {
+ char name[5] = {char(tag >> 24), char(tag >> 16), char(tag >> 8), char(tag), 0};
+ return {name};
+}
+} // namespace
+
+const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
+ if (!_frame) {
+ _frame = new Graphics::Surface();
+ _frame->create(_w, _h, getPixelFormat());
+ }
+ debug("decode next video frame");
+ return _frame;
+}
+
+int FourXMDecoder::FourXMVideoTrack::getFrameCount() const {
+ return _dec->_frames.size();
+}
+
+FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
+ if (_frame) {
+ _frame->free();
+ delete _frame;
+ }
+}
+
+class FourXMDecoder::FourXMAudioTrack::Stream : public Audio::SeekableAudioStream {
+ const FourXMAudioTrack *_track;
+ uint _frameIndex = 0;
+
+public:
+ Stream(const FourXMAudioTrack *track) : _track(track) {}
+ int readBuffer(int16 *buffer, const int numSamples) override {
+ debug("decode next audio frame");
+ ++_frameIndex;
+ return 0;
+ }
+
+ bool isStereo() const override {
+ return _track->_audioChannels > 1;
+ }
+
+ /** Sample rate of the stream. */
+ virtual int getRate() const override {
+ return _track->_sampleRate;
+ }
+
+ virtual bool endOfData() const override {
+ return _frameIndex >= _track->_dec->_frames.size();
+ }
+
+ bool seek(const Audio::Timestamp &ts) override {
+ return true;
+ }
+ Audio::Timestamp getLength() const override {
+ return {};
+ }
+};
+
+Audio::SeekableAudioStream *FourXMDecoder::FourXMAudioTrack::getSeekableAudioStream() const {
+ return new Stream(this);
+}
+
+void FourXMDecoder::readList(uint32 listEnd) {
+ assert(_stream);
+ uint32 listType = _stream->readUint32BE();
+ if (listType == MKTAG('F', 'R', 'A', 'M')) {
+ _frames.push_back({_stream->pos() - 4, listEnd});
+ return;
+ }
+ debug("%08lx: list type %s", _stream->pos() - 4, tagName(listType).c_str());
+ while (_stream->pos() < listEnd) {
+ uint32 tag = _stream->readUint32BE();
+ uint32 size = _stream->readUint32LE();
+ if (listType != MKTAG('F', 'R', 'A', 'M'))
+ debug("%08lx: tag %s, size %u/0x%x", _stream->pos() - 8, tagName(tag).c_str(), size, size);
+ auto pos = _stream->pos();
+ if (tag == MKTAG('L', 'I', 'S', 'T')) {
+ readList(pos + size);
+ }
+ switch (listType) {
+ case MKTAG('H', 'N', 'F', 'O'):
+ switch (tag) {
+ case MKTAG('s', 't', 'd', '_'):
+ _dataRate = _stream->readUint32LE();
+ _frameRate = floatToRational(_stream->readFloatLE());
+ debug("data rate: %u, frame rate: %d/%d", _dataRate, _frameRate.getNumerator(), _frameRate.getDenominator());
+ break;
+ default:
+ break;
+ }
+ break;
+ case MKTAG('V', 'T', 'R', 'K'):
+ switch (tag) {
+ case MKTAG('v', 't', 'r', 'k'): {
+ _stream->skip(28);
+ auto w = _stream->readUint32LE();
+ auto h = _stream->readUint32LE();
+ debug("video %ux%u", w, h);
+ addTrack(_video = new FourXMVideoTrack(this, _frameRate, w, h));
+ } break;
+ default:
+ break;
+ }
+ break;
+ case MKTAG('S', 'T', 'R', 'K'):
+ switch (tag) {
+ case MKTAG('s', 't', 'r', 'k'): {
+ auto trackIdx = _stream->readUint32LE();
+ auto audioType = _stream->readUint32LE();
+ _stream->skip(20);
+ auto audioChannels = _stream->readUint32LE();
+ auto sampleRate = _stream->readUint32LE();
+ auto sampleResolution = _stream->readUint32LE();
+ debug("audio track %u %u %u %u %u", trackIdx, audioType, audioChannels, sampleRate, sampleResolution);
+ addTrack(_audio = new FourXMAudioTrack(this, trackIdx, audioType, audioChannels, sampleRate, sampleResolution));
+ } break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ _stream->seek(pos + size + (size & 1));
+ }
+}
+
+bool FourXMDecoder::loadStream(Common::SeekableReadStream *stream) {
+ if (!stream->size()) {
+ return false;
+ }
+
+ uint32 riffTag = stream->readUint32BE();
+ if (riffTag != MKTAG('R', 'I', 'F', 'F')) {
+ warning("Failed to find RIFF header");
+ return false;
+ }
+
+ uint32 fileSize = stream->readUint32LE();
+ uint32 riffType = stream->readUint32BE();
+
+ if (riffType != MKTAG('4', 'X', 'M', 'V')) {
+ warning("RIFF not an 4XM file, got: %08x", riffType);
+ return false;
+ }
+
+ _stream = stream;
+
+ debug("file size %u", fileSize);
+ while (stream->pos() < fileSize) {
+ uint32 tag = stream->readUint32BE();
+ uint32 size = stream->readUint32LE();
+ debug("%08lx: tag %s, size %u/0x%x", stream->pos() - 8, tagName(tag).c_str(), size, size);
+ auto pos = stream->pos();
+ if (tag == MKTAG('L', 'I', 'S', 'T')) {
+ readList(pos + size);
+ }
+ stream->seek(pos + size + (size & 1));
+ }
+
+ debug("loaded %u frames", _frames.size());
+ return getNumTracks() != 0;
+}
+
+} // namespace Video
diff --git a/video/4xm_decoder.h b/video/4xm_decoder.h
new file mode 100644
index 00000000000..f000fc24395
--- /dev/null
+++ b/video/4xm_decoder.h
@@ -0,0 +1,96 @@
+/* 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 "video/video_decoder.h"
+
+namespace Video {
+
+/**
+ * Decoder for 4XM videos.
+ *
+ * Video decoder used in engines:
+ * - phoenixvr
+ */
+class FourXMDecoder : public Video::VideoDecoder {
+public:
+ bool loadStream(Common::SeekableReadStream *stream) override;
+
+private:
+ struct Frame {
+ int64 offset;
+ int64 end;
+ };
+
+ class FourXMVideoTrack : public FixedRateVideoTrack {
+ const FourXMDecoder *_dec;
+ Common::Rational _frameRate;
+ uint _w, _h;
+ int _curFrame;
+ Graphics::Surface *_frame;
+
+ public:
+ FourXMVideoTrack(const FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _curFrame(0), _frame(nullptr) {}
+ ~FourXMVideoTrack();
+
+ uint16 getWidth() const override { return _w; }
+ uint16 getHeight() const override { return _h; }
+
+ Graphics::PixelFormat getPixelFormat() const override {
+ return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); // RGB565
+ }
+ int getCurFrame() const override {
+ return _curFrame;
+ }
+ int getFrameCount() const override;
+ const Graphics::Surface *decodeNextFrame() override;
+
+ private:
+ Common::Rational getFrameRate() const override { return _frameRate; }
+ };
+
+ class FourXMAudioTrack : public SeekableAudioTrack {
+ class Stream;
+ const FourXMDecoder *_dec;
+ uint _trackIdx;
+ uint _audioType;
+ uint _audioChannels;
+ uint _sampleRate;
+ uint _sampleResolution;
+
+ public:
+ FourXMAudioTrack(const FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate, uint sampleResolution) : SeekableAudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate), _sampleResolution(sampleResolution) {
+ }
+
+ private:
+ Audio::SeekableAudioStream *getSeekableAudioStream() const override;
+ };
+
+ void readList(uint32 size);
+
+ uint32 _dataRate = 0;
+ Common::Rational _frameRate;
+ Common::SeekableReadStream *_stream;
+ Common::Array<Frame> _frames;
+ FourXMVideoTrack *_video = nullptr;
+ FourXMAudioTrack *_audio = nullptr;
+};
+
+} // namespace Video
diff --git a/video/module.mk b/video/module.mk
index 356bc94d951..3a8ae32cc3e 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -2,6 +2,7 @@ MODULE := video
MODULE_OBJS := \
3do_decoder.o \
+ 4xm_decoder.o \
avi_decoder.o \
coktel_decoder.o \
dxa_decoder.o \
Commit: d9251402c20733c59cb8b24fd532e59943b7af09
https://github.com/scummvm/scummvm/commit/d9251402c20733c59cb8b24fd532e59943b7af09
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:39+01:00
Commit Message:
PHOENIXVR: move rendering logic to tick(), add playMovie stub
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 4a771af6b3c..6a07a71cb5d 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -64,7 +64,7 @@ struct Play_Movie : public Script::Command {
Play_Movie(const Common::Array<Common::String> &args) : filename(args[0]) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_Movie %s", filename.c_str());
+ g_engine->playMovie(filename);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 6e21e43c9aa..8287502df3a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -36,6 +36,7 @@
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
#include "phoenixvr/vr.h"
+#include "video/4xm_decoder.h"
namespace PhoenixVR {
@@ -169,6 +170,19 @@ void PhoenixVREngine::stopSound(const Common::String &sound) {
}
}
+void PhoenixVREngine::playMovie(const Common::String &movie) {
+ debug("playMovie %s", movie.c_str());
+#if 0
+ _movie.reset(new Video::FourXMDecoder());
+ if (_movie->loadFile(Common::Path{movie})) {
+ _movie->start();
+ } else {
+ _movie.reset();
+ warning("playMovie %s failed", movie.c_str());
+ }
+#endif
+}
+
Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
Common::File file;
if (!file.open(Common::Path(path))) {
@@ -213,6 +227,88 @@ void PhoenixVREngine::executeTest(int idx) {
warning("invalid test id %d", idx);
}
+void PhoenixVREngine::tick(float dt) {
+ if (_vr.isVR() && _mousePos != _screenCenter) {
+ auto da = _mousePos - _screenCenter;
+ _system->warpMouse(_screenCenter.x, _screenCenter.y);
+ _mousePos = _screenCenter;
+ static const float kSpeedX = 0.2f;
+ static const float kSpeedY = 0.2f;
+ _angleX.add(float(da.x) * kSpeedX * dt);
+ _angleY.add(float(da.y) * kSpeedY * dt);
+ debug("angle %g %g", _angleX.angle(), _angleY.angle());
+ }
+ if (!_nextScript.empty()) {
+ debug("loading script from %s", _nextScript.c_str());
+ auto nextScript = Common::move(_nextScript);
+ _nextScript.clear();
+
+ nextScript = removeDrive(nextScript);
+
+ Common::File file;
+ Common::Path nextPath(nextScript);
+ if (file.open(nextPath)) {
+ _script.reset(new Script(file));
+ } else {
+ auto pakFile = nextPath;
+ pakFile = pakFile.removeExtension().append(".pak");
+ file.open(pakFile);
+ if (!file.isOpen())
+ error("can't open script file %s", nextScript.c_str());
+ Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
+ _script.reset(new Script(*scriptStream));
+ }
+ goToWarp(_script->getInitScript()->vrFile);
+ }
+ if (!_nextWarp.empty()) {
+ _warp = _script->getWarp(_nextWarp);
+ _nextWarp.clear();
+
+ debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
+
+ Common::File vr;
+ if (vr.open(Common::Path(_warp->vrFile))) {
+ _vr = VR::loadStatic(_pixelFormat, vr);
+ if (_vr.isVR()) {
+ _mousePos = _screenCenter;
+ _system->warpMouse(_screenCenter.x, _screenCenter.y);
+ }
+ }
+
+ _regSet.reset(new RegionSet(_warp->testFile));
+
+ for (auto &c : _cursors)
+ c.free();
+
+ _cursors.resize(_regSet->size());
+ for (uint i = 0; i != _regSet->size(); ++i) {
+ _cursors[i].rect = _regSet->getRegion(i).toRect();
+ }
+
+ Script::ExecutionContext ctx;
+ debug("execute warp script %s", _warp->vrFile.c_str());
+ auto &test = _warp->getDefaultTest();
+ test->scope.exec(ctx);
+ }
+
+ _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
+
+ Graphics::Surface *cursor = nullptr;
+ for (auto &c : _cursors) {
+ if (_vr.isVR() ? c.rect.contains(currentVRPos()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
+ cursor = c.surface;
+ if (!cursor)
+ cursor = _defaultCursor[1].surface;
+ break;
+ }
+ }
+ if (!cursor)
+ cursor = _defaultCursor[0].surface;
+ if (cursor) {
+ paint(*cursor, _mousePos - Common::Point(cursor->w / 2, cursor->h / 2));
+ }
+}
+
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
_screen = new Graphics::Screen();
@@ -244,8 +340,13 @@ Common::Error PhoenixVREngine::run() {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN:
- if (event.kbd.ascii == ' ')
- goToWarp("N1M01L03W02E0.vr");
+ if (event.kbd.ascii == ' ') {
+ if (_movie) {
+ _movie->stop();
+ _movie.reset();
+ } else
+ goToWarp("N1M01L03W02E0.vr");
+ }
break;
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
@@ -270,86 +371,16 @@ Common::Error PhoenixVREngine::run() {
break;
}
}
- if (_vr.isVR() && _mousePos != _screenCenter) {
- auto da = _mousePos - _screenCenter;
- _system->warpMouse(_screenCenter.x, _screenCenter.y);
- _mousePos = _screenCenter;
- static const float kSpeedX = 0.2f;
- static const float kSpeedY = 0.2f;
- const auto dt = float(frameDuration) / 1000.0f;
- _angleX.add(float(da.x) * kSpeedX * dt);
- _angleY.add(float(da.y) * kSpeedY * dt);
- debug("angle %g %g", _angleX.angle(), _angleY.angle());
- }
- if (!_nextScript.empty()) {
- debug("loading script from %s", _nextScript.c_str());
- auto nextScript = Common::move(_nextScript);
- _nextScript.clear();
-
- nextScript = removeDrive(nextScript);
-
- Common::File file;
- Common::Path nextPath(nextScript);
- if (file.open(nextPath)) {
- _script.reset(new Script(file));
- } else {
- auto pakFile = nextPath;
- pakFile = pakFile.removeExtension().append(".pak");
- file.open(pakFile);
- if (!file.isOpen())
- error("can't open script file %s", nextScript.c_str());
- Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
- _script.reset(new Script(*scriptStream));
- }
- goToWarp(_script->getInitScript()->vrFile);
- }
- if (!_nextWarp.empty()) {
- _warp = _script->getWarp(_nextWarp);
- _nextWarp.clear();
-
- debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
-
- Common::File vr;
- if (vr.open(Common::Path(_warp->vrFile))) {
- _vr = VR::loadStatic(_pixelFormat, vr);
- if (_vr.isVR()) {
- _mousePos = _screenCenter;
- _system->warpMouse(_screenCenter.x, _screenCenter.y);
- }
- }
-
- _regSet.reset(new RegionSet(_warp->testFile));
-
- for (auto &c : _cursors)
- c.free();
-
- _cursors.resize(_regSet->size());
- for (uint i = 0; i != _regSet->size(); ++i) {
- _cursors[i].rect = _regSet->getRegion(i).toRect();
- }
-
- Script::ExecutionContext ctx;
- debug("execute warp script %s", _warp->vrFile.c_str());
- auto &test = _warp->getDefaultTest();
- test->scope.exec(ctx);
- }
-
- _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
-
- Graphics::Surface *cursor = nullptr;
- for (auto &c : _cursors) {
- if (_vr.isVR() ? c.rect.contains(currentVRPos()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
- cursor = c.surface;
- if (!cursor)
- cursor = _defaultCursor[1].surface;
- break;
- }
- }
- if (!cursor)
- cursor = _defaultCursor[0].surface;
- if (cursor) {
- paint(*cursor, _mousePos - Common::Point(cursor->w / 2, cursor->h / 2));
- }
+ if (_movie) {
+ if (_movie->isPlaying()) {
+ debug("playing movie frame: %d", _movie->getCurFrame());
+ auto *s = _movie->decodeNextFrame();
+ if (s)
+ _screen->copyFrom(*s);
+ } else
+ _movie.reset();
+ } else
+ tick(float(frameDuration) / 1000.0f);
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 8400624130f..04d0a5a9d11 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -34,6 +34,7 @@
#include "engines/engine.h"
#include "engines/savestate.h"
#include "graphics/screen.h"
+#include "video/video_decoder.h"
#include "phoenixvr/angle.h"
#include "phoenixvr/detection.h"
@@ -111,6 +112,7 @@ public:
void playSound(const Common::String &sound, uint8 volume, int loops);
void stopSound(const Common::String &sound);
+ void playMovie(const Common::String &movie);
void declareVariable(const Common::String &name);
void setVariable(const Common::String &name, int value);
@@ -138,6 +140,7 @@ private:
PointF currentVRPos() const {
return RectF::transform(_angleX.angle(), _angleY.angle(), _fov);
}
+ void tick(float dt);
private:
Common::Point _mousePos;
@@ -150,6 +153,8 @@ private:
Script::ConstWarpPtr _warp;
Common::ScopedPtr<RegionSet> _regSet;
+ Common::ScopedPtr<Video::VideoDecoder> _movie;
+
struct Cursor {
RectF rect;
Graphics::Surface *surface = nullptr;
Commit: 0826a8a615ba5cb5e807360930fb934b8d68d15a
https://github.com/scummvm/scummvm/commit/0826a8a615ba5cb5e807360930fb934b8d68d15a
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:40+01:00
Commit Message:
PHOENIXVR: add perspective projection
Changed paths:
engines/phoenixvr/rectf.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index a1d79373da2..4a29be5fbd8 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -26,9 +26,10 @@ namespace PhoenixVR {
PointF RectF::transform(float ax, float ay, float fov) {
Angle x(ax);
- x.add(M_PI_2);
- float y = ay + float(M_PI_2);
- return {x.angle(), y};
+ Angle y(ay);
+ x.add(-M_PI_2);
+ y.add(M_PI_2);
+ return {x.angle(), y.angle()};
}
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 5b51295654c..494afe74fd6 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -29,6 +29,7 @@
#include "graphics/surface.h"
#include "graphics/yuv_to_rgb.h"
#include "image/bmp.h"
+#include "math/vector3d.h"
#include "phoenixvr/dct.h"
#include "phoenixvr/dct_tables.h"
#include "phoenixvr/phoenixvr.h"
@@ -405,26 +406,6 @@ Cube toCube(float x, float y, float z) {
return cube;
}
-struct Projection {
- struct Point {
- float angle;
- float sine, cosine;
- };
- Common::Array<Point> points;
-
- Projection(uint num, float start, float fov) : points(num) {
- const auto da = fov / float(num);
- static const float kPi2 = 2 * M_PI;
- float a = fmodf(start - fov / 2, kPi2);
- if (a < 0)
- a += kPi2;
- for (uint i = 0; i != num; ++i) {
- points[i] = {a, sin(a), cos(a)};
- a += da;
- a = fmodf(a, M_PI * 2);
- }
- }
-};
} // namespace
void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
@@ -443,16 +424,27 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
auto w = g_system->getWidth();
auto h = g_system->getHeight();
- Projection projH(w, ax, fov);
- Projection projV(h, ay - M_PI_2, fov);
- for (int dstY = 0; dstY != h; ++dstY) {
- auto &pv = projV.points[dstY];
- for (int dstX = 0; dstX != w; ++dstX) {
- auto &ph = projH.points[dstX];
- float x = ph.cosine * pv.sine;
- float y = ph.sine * pv.sine;
- float z = pv.cosine;
- auto cube = toCube(x, y, z);
+ // camera pose
+ using namespace Math;
+ Vector3d forward = Vector3d(
+ cosf(ax) * sinf(-ay),
+ sinf(ax) * sinf(-ay),
+ cosf(-ay))
+ .getNormalized();
+ Vector3d right = Vector3d::crossProduct(forward, Vector3d(0, 0, -1)).getNormalized();
+ Vector3d up = Vector3d::crossProduct(forward, right); // already normalized
+
+ // camera projection
+ float gx = tanf(fov / 2.0f), gy = gx * h / w;
+ Vector3d incrementX = right * (2 * gx / w);
+ Vector3d incrementY = up * (2 * gy / h);
+ Vector3d start = forward - right * gx - up * gy;
+ Vector3d line = start;
+ for (int dstY = 0; dstY != h; ++dstY, line += incrementY) {
+ Vector3d pixel = line;
+ for (int dstX = 0; dstX != w; ++dstX, pixel += incrementX) {
+ Vector3d ray = pixel.getNormalized();
+ auto cube = toCube(ray.x(), ray.y(), ray.z());
int srcX = static_cast<int>(512 * cube.x);
int srcY = static_cast<int>(512 * cube.y);
int tileId = cube.faceIdx * 4;
@@ -461,21 +453,6 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
srcY &= 0xff;
srcY += (tileId << 8);
auto color = _pic->getPixel(srcX, srcY);
-#if 0
- auto n = g_engine->numCursors();
- for(uint i = 0; i != n; ++i) {
- auto *src = g_engine->getCursorRect(i);
- if (src && src->contains(RectF::transform(ph.angle, pv.angle, fov))) {
- uint8 r, g, b;
- _pic->format.colorToRGB(color, r, g, b);
- r += 32;
- g += 32;
- b += 32;
- color = _pic->format.RGBToColor(r, g, b);
- break;
- }
- }
-#endif
screen->setPixel(dstX, dstY, color);
}
}
Commit: 08950506ec8bc81925a666216d2bdfa8fed91c51
https://github.com/scummvm/scummvm/commit/08950506ec8bc81925a666216d2bdfa8fed91c51
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:40+01:00
Commit Message:
PHOENIXVR: add AngleX/AngleY, proper clipping and fix rectangle matching
Changed paths:
engines/phoenixvr/angle.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/rectf.cpp
diff --git a/engines/phoenixvr/angle.h b/engines/phoenixvr/angle.h
index 11ec56b8469..6da7d040ea3 100644
--- a/engines/phoenixvr/angle.h
+++ b/engines/phoenixvr/angle.h
@@ -22,6 +22,7 @@
#ifndef PHOENIXVR_ANGLE_H
#define PHOENIXVR_ANGLE_H
+#include "math/utils.h"
#include <math.h>
namespace Common {
@@ -30,18 +31,48 @@ class String;
namespace PhoenixVR {
class Angle {
+protected:
float _angle;
+ float _min;
+ float _max;
public:
- Angle(float angle = 0) : _angle(angle) {}
+ Angle(float angle, float min, float max) : _min(min), _max(max) {
+ set(angle);
+ }
+
+ Angle &operator=(float angle) {
+ set(angle);
+ return *this;
+ }
float angle() const { return _angle; }
+ void set(float v) {
+ auto range = _max - _min;
+ auto a = fmod(v - _min, range);
+ if (a < 0)
+ a += range;
+ _angle = a + _min;
+ }
+
+ void add(float v) {
+ set(_angle + v);
+ }
+};
+struct AngleX : Angle {
+ AngleX(float angle) : Angle(angle, 0, 2 * M_PI) {}
+};
+
+struct AngleY : Angle {
+ AngleY(float angle) : Angle(angle, -M_PI, -Math::epsilon) {}
void add(float v) {
- static const float kPi2 = M_PI * 2;
- _angle = fmod(_angle + v, kPi2);
- if (_angle < 0)
- _angle += kPi2;
+ v += angle();
+ if (v <= _min)
+ v = _min + Math::epsilon;
+ if (v >= _max)
+ v = _max - Math::epsilon;
+ set(v);
}
};
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 8287502df3a..4821d15cf45 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -31,6 +31,7 @@
#include "engines/util.h"
#include "graphics/framelimiter.h"
#include "image/pcx.h"
+#include "math/utils.h"
#include "phoenixvr/console.h"
#include "phoenixvr/pakf.h"
#include "phoenixvr/region_set.h"
@@ -47,6 +48,8 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_randomSource("PhoenixVR"),
_pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()),
_fov(M_PI_2),
+ _angleX(M_PI),
+ _angleY(-M_PI_2),
_mixer(syst->getMixer()) {
g_engine = this;
auto path = Common::FSNode(ConfMan.getPath("path"));
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 04d0a5a9d11..0df2e28b1e7 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -164,8 +164,8 @@ private:
Cursor _defaultCursor[2];
VR _vr;
float _fov;
- Angle _angleX;
- Angle _angleY;
+ AngleX _angleX;
+ AngleY _angleY;
Audio::Mixer *_mixer;
};
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index 4a29be5fbd8..53a0bf89f2d 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -25,10 +25,10 @@
namespace PhoenixVR {
PointF RectF::transform(float ax, float ay, float fov) {
- Angle x(ax);
- Angle y(ay);
+ AngleX x(ax);
+ AngleX y(ay); // not a typo, we need 0;pi*2 range
x.add(-M_PI_2);
- y.add(M_PI_2);
+ y.add(M_PI_4);
return {x.angle(), y.angle()};
}
Commit: fe56b075212a71aa08045a8df8a0bc0fccc0e408
https://github.com/scummvm/scummvm/commit/fe56b075212a71aa08045a8df8a0bc0fccc0e408
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:40+01:00
Commit Message:
PHOENIXVR: add 3d sound stubs
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 6a07a71cb5d..17e5d8accce 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -507,12 +507,12 @@ struct PlaySound3D : public Script::Command {
Common::String sound;
int volume;
float angle;
- int unk;
+ int loops;
- PlaySound3D(Common::String s, int v, float a, int u) : sound(Common::move(s)), volume(v), angle(a), unk(u) {}
+ PlaySound3D(Common::String s, int v, float a, int l) : sound(Common::move(s)), volume(v), angle(a), loops(l) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("play sound %s %d %g %d", sound.c_str(), volume, angle, unk);
+ g_engine->playSound(sound, volume, loops);
}
};
@@ -522,7 +522,7 @@ struct StopSound3D : public Script::Command {
StopSound3D(Common::String s) : sound(Common::move(s)) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("stop sound 3d %s", sound.c_str());
+ g_engine->stopSound(sound);
}
};
Commit: a054e025ccaa963462a8ea885801295ebdaad58b
https://github.com/scummvm/scummvm/commit/a054e025ccaa963462a8ea885801295ebdaad58b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:41+01:00
Commit Message:
PHOENIXVR: add 3d sound support
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 17e5d8accce..e5b6704d388 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -512,7 +512,7 @@ struct PlaySound3D : public Script::Command {
PlaySound3D(Common::String s, int v, float a, int l) : sound(Common::move(s)), volume(v), angle(a), loops(l) {}
void exec(Script::ExecutionContext &ctx) const override {
- g_engine->playSound(sound, volume, loops);
+ g_engine->playSound(sound, volume, loops, true, angle);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 4821d15cf45..c8e032f55e4 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -149,8 +149,8 @@ int PhoenixVREngine::getVariable(const Common::String &name) const {
return _variables.getVal(name);
}
-void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int loops) {
- debug("play sound %s %d %d", sound.c_str(), volume, loops);
+void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int loops, bool spatial, float angle) {
+ debug("play sound %s %d %d 3d: %d, angle: %g", sound.c_str(), volume, loops, spatial, angle);
Audio::SoundHandle h;
Common::ScopedPtr<Common::File> f(new Common::File());
if (!f->open(Common::Path(sound))) {
@@ -161,14 +161,14 @@ void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int l
_mixer->playStream(Audio::Mixer::kPlainSoundType, &h, Audio::makeWAVStream(f.release(), DisposeAfterUse::YES), -1, volume);
if (loops < 0)
_mixer->loopChannel(h);
- _sounds[sound] = h;
+ _sounds[sound] = Sound{h, spatial, angle};
}
void PhoenixVREngine::stopSound(const Common::String &sound) {
debug("stop sound %s", sound.c_str());
auto it = _sounds.find(sound);
if (it != _sounds.end()) {
- _mixer->stopHandle(it->_value);
+ _mixer->stopHandle(it->_value.handle);
_sounds.erase(it);
}
}
@@ -241,6 +241,14 @@ void PhoenixVREngine::tick(float dt) {
_angleY.add(float(da.y) * kSpeedY * dt);
debug("angle %g %g", _angleX.angle(), _angleY.angle());
}
+ for (auto &kv : _sounds) {
+ auto &sound = kv._value;
+ if (!sound.spatial)
+ continue;
+
+ int8 balance = -127 * sinf(_angleX.angle() - (sound.angle + M_PI_2));
+ _mixer->setChannelBalance(sound.handle, balance);
+ }
if (!_nextScript.empty()) {
debug("loading script from %s", _nextScript.c_str());
auto nextScript = Common::move(_nextScript);
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 0df2e28b1e7..b16959a77b3 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -110,7 +110,7 @@ public:
void setCursor(const Common::String &path, const Common::String &warp, int idx);
void hideCursor(const Common::String &warp, int idx);
- void playSound(const Common::String &sound, uint8 volume, int loops);
+ void playSound(const Common::String &sound, uint8 volume, int loops, bool spatial = false, float angle = 0);
void stopSound(const Common::String &sound);
void playMovie(const Common::String &movie);
@@ -147,7 +147,12 @@ private:
Common::String _nextScript;
Common::String _nextWarp;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
- Common::HashMap<Common::String, Audio::SoundHandle, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _sounds;
+ struct Sound {
+ Audio::SoundHandle handle;
+ bool spatial;
+ float angle;
+ };
+ Common::HashMap<Common::String, Sound, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _sounds;
Common::ScopedPtr<Script> _script;
Script::ConstWarpPtr _warp;
Commit: fc91b33f04191309124ea7107cca63d1907e84ad
https://github.com/scummvm/scummvm/commit/fc91b33f04191309124ea7107cca63d1907e84ad
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:41+01:00
Commit Message:
PHOENIXVR: turn the scene for 90° so no correction needed
rectangles and sounds match the horizontal angle now
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/rectf.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index c8e032f55e4..e0a14f97f25 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -246,7 +246,7 @@ void PhoenixVREngine::tick(float dt) {
if (!sound.spatial)
continue;
- int8 balance = -127 * sinf(_angleX.angle() - (sound.angle + M_PI_2));
+ int8 balance = 127 * sinf(sound.angle - _angleX.angle());
_mixer->setChannelBalance(sound.handle, balance);
}
if (!_nextScript.empty()) {
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index 53a0bf89f2d..b591c7070c6 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -27,7 +27,6 @@ namespace PhoenixVR {
PointF RectF::transform(float ax, float ay, float fov) {
AngleX x(ax);
AngleX y(ay); // not a typo, we need 0;pi*2 range
- x.add(-M_PI_2);
y.add(M_PI_4);
return {x.angle(), y.angle()};
}
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 494afe74fd6..6196f89e72f 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -367,36 +367,36 @@ Cube toCube(float x, float y, float z) {
maxAxis = absX;
cx = y;
cy = z;
- cube.faceIdx = 1;
+ cube.faceIdx = 4;
}
if (!isXPositive && absX >= absY && absX >= absZ) {
maxAxis = absX;
cx = -y;
cy = z;
- cube.faceIdx = 3;
+ cube.faceIdx = 5;
}
if (isYPositive && absY >= absX && absY >= absZ) {
maxAxis = absY;
cx = -x;
cy = z;
- cube.faceIdx = 4;
+ cube.faceIdx = 3;
}
if (!isYPositive && absY >= absX && absY >= absZ) {
maxAxis = absY;
cx = x;
cy = z;
- cube.faceIdx = 5;
+ cube.faceIdx = 1;
}
if (isZPositive && absZ >= absX && absZ >= absY) {
maxAxis = absZ;
- cx = -x;
- cy = -y;
+ cx = y;
+ cy = -x;
cube.faceIdx = 0;
}
if (!isZPositive && absZ >= absX && absZ >= absY) {
maxAxis = absZ;
- cx = -x;
- cy = y;
+ cx = y;
+ cy = x;
cube.faceIdx = 2;
}
Commit: 056b09347d27c6d2da4e23807c8cd4c55306319f
https://github.com/scummvm/scummvm/commit/056b09347d27c6d2da4e23807c8cd4c55306319f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:41+01:00
Commit Message:
PHOENIXVR: add lockkey support
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index e5b6704d388..12d817b28b7 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -422,6 +422,7 @@ struct HideCursor : public Script::Command {
struct ResetLockKey : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
debug("resetlockkey");
+ // FIXME: scripts don't restore lockkey after reset
}
};
@@ -433,6 +434,52 @@ struct LockKey : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
debug("lock key F%d: %s", idx, warp.c_str());
+ Common::KeyCode code;
+ switch (idx) {
+ case 0:
+ code = Common::KeyCode::KEYCODE_ESCAPE;
+ break;
+ case 1:
+ code = Common::KeyCode::KEYCODE_F1;
+ break;
+ case 2:
+ code = Common::KeyCode::KEYCODE_F2;
+ break;
+ case 3:
+ code = Common::KeyCode::KEYCODE_F3;
+ break;
+ case 4:
+ code = Common::KeyCode::KEYCODE_F4;
+ break;
+ case 5:
+ code = Common::KeyCode::KEYCODE_F5;
+ break;
+ case 6:
+ code = Common::KeyCode::KEYCODE_F6;
+ break;
+ case 7:
+ code = Common::KeyCode::KEYCODE_F7;
+ break;
+ case 8:
+ code = Common::KeyCode::KEYCODE_F8;
+ break;
+ case 9:
+ code = Common::KeyCode::KEYCODE_F9;
+ break;
+ case 10:
+ code = Common::KeyCode::KEYCODE_F10;
+ break;
+ case 11:
+ code = Common::KeyCode::KEYCODE_F11;
+ break;
+ case 12:
+ code = Common::KeyCode::KEYCODE_F12;
+ break;
+ default:
+ warning("unknown lock key %d", idx);
+ return;
+ }
+ g_engine->lockKey(code, warp);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index e0a14f97f25..70dde48061f 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -186,6 +186,14 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
#endif
}
+void PhoenixVREngine::resetLockKey() {
+ _keys.clear();
+}
+
+void PhoenixVREngine::lockKey(Common::KeyCode code, const Common::String &warp) {
+ _keys[code] = warp;
+}
+
Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
Common::File file;
if (!file.open(Common::Path(path))) {
@@ -350,15 +358,19 @@ Common::Error PhoenixVREngine::run() {
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
- case Common::EVENT_KEYDOWN:
- if (event.kbd.ascii == ' ') {
+ case Common::EVENT_KEYDOWN: {
+ auto it = _keys.find(event.kbd.keycode);
+ if (it != _keys.end()) {
+ debug("matched code %d", static_cast<int>(event.kbd.keycode));
+ goToWarp(it->_value);
+ } else if (event.kbd.ascii == ' ') {
if (_movie) {
_movie->stop();
_movie.reset();
} else
goToWarp("N1M01L03W02E0.vr");
}
- break;
+ } break;
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
break;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index b16959a77b3..6d63a711bb5 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -26,6 +26,7 @@
#include "common/error.h"
#include "common/fs.h"
#include "common/hash-str.h"
+#include "common/keyboard.h"
#include "common/random.h"
#include "common/scummsys.h"
#include "common/serializer.h"
@@ -132,6 +133,9 @@ public:
return idx < _cursors.size() ? &_cursors[idx].rect : nullptr;
}
+ void resetLockKey();
+ void lockKey(Common::KeyCode code, const Common::String &warp);
+
private:
static Common::String removeDrive(const Common::String &path);
static Common::String resolvePath(const Common::String &path);
@@ -146,6 +150,12 @@ private:
Common::Point _mousePos;
Common::String _nextScript;
Common::String _nextWarp;
+
+ struct KeyCodeHash : public Common::UnaryFunction<Common::KeyCode, uint> {
+ uint operator()(Common::KeyCode val) const { return static_cast<uint>(val); }
+ };
+
+ Common::HashMap<Common::KeyCode, Common::String, KeyCodeHash> _keys;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
struct Sound {
Audio::SoundHandle handle;
Commit: f9a6e875761a07c0e692300c245e475b37363fbf
https://github.com/scummvm/scummvm/commit/f9a6e875761a07c0e692300c245e475b37363fbf
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:41+01:00
Commit Message:
PHOENIXVR: fix conditional prefix for command
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index cf7a5927f62..0570a58e95b 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -296,8 +296,12 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (_currentTest) {
auto &commands = _currentTest->scope.commands;
if (p.maybe("ifand=")) {
+ if (_pluginScope)
+ error("ifand in plugin scope");
_conditional.reset(new IfAnd(p.readStringList()));
} else if (p.maybe("ifor=")) {
+ if (_pluginScope)
+ error("ifor in plugin scope");
_conditional.reset(new IfOr(p.readStringList()));
} else if (p.maybe("plugin")) {
if (_pluginScope)
@@ -332,9 +336,14 @@ void Script::parseLine(const Common::String &line, uint lineno) {
}
} else {
auto cmd = p.parseCommand();
- if (cmd)
- commands.push_back(Common::move(cmd));
- else
+ if (cmd) {
+ if (_conditional) {
+ _conditional->target = Common::move(cmd);
+ commands.push_back(Common::move(_conditional));
+ _conditional.reset();
+ } else
+ commands.push_back(Common::move(cmd));
+ } else
error("unhandled script command %s", line.c_str());
}
}
Commit: 4cd8b084cf078addce206d17bd2d894fb9c854f2
https://github.com/scummvm/scummvm/commit/4cd8b084cf078addce206d17bd2d894fb9c854f2
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:42+01:00
Commit Message:
PHOENIXVR: convert decoded picture to the current pixel format
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 70dde48061f..c2d3858b6c2 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -398,8 +398,15 @@ Common::Error PhoenixVREngine::run() {
if (_movie->isPlaying()) {
debug("playing movie frame: %d", _movie->getCurFrame());
auto *s = _movie->decodeNextFrame();
- if (s)
- _screen->copyFrom(*s);
+ if (s) {
+ Common::ScopedPtr<Graphics::Surface> converted;
+ if (s->format != _pixelFormat) {
+ converted.reset(s->convertTo(_pixelFormat));
+ _screen->copyFrom(*converted);
+ converted->free();
+ } else
+ _screen->copyFrom(*s);
+ }
} else
_movie.reset();
} else
Commit: 57843fe940e9fb283e55e4bf3863c418888c1ee6
https://github.com/scummvm/scummvm/commit/57843fe940e9fb283e55e4bf3863c418888c1ee6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:42+01:00
Commit Message:
VIDEO: 4XM: move frame related code back to decoder
Changed paths:
video/4xm_decoder.cpp
video/4xm_decoder.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 0b9614f8b1f..eca7a71a0db 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -48,9 +48,14 @@ const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
_frame->create(_w, _h, getPixelFormat());
}
debug("decode next video frame");
+ _dec->decodeNextFrameImpl();
return _frame;
}
+int FourXMDecoder::FourXMVideoTrack::getCurFrame() const {
+ return _dec->_curFrame;
+}
+
int FourXMDecoder::FourXMVideoTrack::getFrameCount() const {
return _dec->_frames.size();
}
@@ -62,15 +67,13 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
}
}
-class FourXMDecoder::FourXMAudioTrack::Stream : public Audio::SeekableAudioStream {
+class FourXMDecoder::FourXMAudioTrack::Stream : public Audio::AudioStream {
const FourXMAudioTrack *_track;
- uint _frameIndex = 0;
public:
Stream(const FourXMAudioTrack *track) : _track(track) {}
int readBuffer(int16 *buffer, const int numSamples) override {
debug("decode next audio frame");
- ++_frameIndex;
return 0;
}
@@ -84,21 +87,57 @@ public:
}
virtual bool endOfData() const override {
- return _frameIndex >= _track->_dec->_frames.size();
- }
-
- bool seek(const Audio::Timestamp &ts) override {
- return true;
- }
- Audio::Timestamp getLength() const override {
- return {};
+ auto *decoder = _track->_dec;
+ return decoder->_curFrame >= decoder->_frames.size();
}
};
-Audio::SeekableAudioStream *FourXMDecoder::FourXMAudioTrack::getSeekableAudioStream() const {
+Audio::AudioStream *FourXMDecoder::FourXMAudioTrack::getAudioStream() const {
return new Stream(this);
}
+void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, const byte *data, uint size) {
+}
+
+void FourXMDecoder::FourXMAudioTrack::decode(uint32 tag, const byte *data, uint size) {
+}
+
+void FourXMDecoder::decodeNextFrameImpl() {
+ if (_curFrame >= _frames.size())
+ return;
+ auto &frame = _frames[_curFrame];
+ _stream->seek(frame.offset);
+ uint32 listType = _stream->readUint32BE();
+ if (listType != MKTAG('F', 'R', 'A', 'M')) {
+ error("invalid FRAM offset for frame %u", _curFrame);
+ }
+
+ Common::Array<byte> packet;
+ while (_stream->pos() < frame.end) {
+ uint32 tag = _stream->readUint32BE();
+ uint32 size = _stream->readUint32LE();
+ debug("%u: sub frame %s, %u bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos() - 4);
+ auto pos = _stream->pos();
+ if (size > packet.size())
+ packet.resize(size);
+ _stream->read(packet.data(), size);
+ switch (tag) {
+ case MKTAG('s', 'n', 'd', '_'):
+ _audio->decode(tag, packet.data(), size);
+ break;
+ case MKTAG('i', 'f', 'r', 'm'):
+ case MKTAG('p', 'f', 'r', 'm'):
+ case MKTAG('c', 'f', 'r', 'm'):
+ _video->decode(tag, packet.data(), size);
+ break;
+ default:
+ warning("unknown frame type %s", tagName(tag).c_str());
+ }
+ _stream->seek(pos + size + (size & 1));
+ }
+ ++_curFrame;
+}
+
void FourXMDecoder::readList(uint32 listEnd) {
assert(_stream);
uint32 listType = _stream->readUint32BE();
diff --git a/video/4xm_decoder.h b/video/4xm_decoder.h
index f000fc24395..f7e7e9383a7 100644
--- a/video/4xm_decoder.h
+++ b/video/4xm_decoder.h
@@ -40,14 +40,13 @@ private:
};
class FourXMVideoTrack : public FixedRateVideoTrack {
- const FourXMDecoder *_dec;
+ FourXMDecoder *_dec;
Common::Rational _frameRate;
uint _w, _h;
- int _curFrame;
Graphics::Surface *_frame;
public:
- FourXMVideoTrack(const FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _curFrame(0), _frame(nullptr) {}
+ FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _frame(nullptr) {}
~FourXMVideoTrack();
uint16 getWidth() const override { return _w; }
@@ -56,19 +55,19 @@ private:
Graphics::PixelFormat getPixelFormat() const override {
return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); // RGB565
}
- int getCurFrame() const override {
- return _curFrame;
- }
+ int getCurFrame() const override;
int getFrameCount() const override;
const Graphics::Surface *decodeNextFrame() override;
+ void decode(uint32 tag, const byte *data, uint size);
+
private:
Common::Rational getFrameRate() const override { return _frameRate; }
};
- class FourXMAudioTrack : public SeekableAudioTrack {
+ class FourXMAudioTrack : public AudioTrack {
class Stream;
- const FourXMDecoder *_dec;
+ FourXMDecoder *_dec;
uint _trackIdx;
uint _audioType;
uint _audioChannels;
@@ -76,19 +75,23 @@ private:
uint _sampleResolution;
public:
- FourXMAudioTrack(const FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate, uint sampleResolution) : SeekableAudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate), _sampleResolution(sampleResolution) {
+ FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate, uint sampleResolution) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate), _sampleResolution(sampleResolution) {
}
+ void decode(uint32 tag, const byte *data, uint size);
+
private:
- Audio::SeekableAudioStream *getSeekableAudioStream() const override;
+ Audio::AudioStream *getAudioStream() const override;
};
void readList(uint32 size);
+ void decodeNextFrameImpl();
uint32 _dataRate = 0;
Common::Rational _frameRate;
Common::SeekableReadStream *_stream;
Common::Array<Frame> _frames;
+ uint _curFrame = 0;
FourXMVideoTrack *_video = nullptr;
FourXMAudioTrack *_audio = nullptr;
};
Commit: 3bbd158e61f3a151b17dffe42cc598cda2921e57
https://github.com/scummvm/scummvm/commit/3bbd158e61f3a151b17dffe42cc598cda2921e57
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:42+01:00
Commit Message:
AUDIO: 4XM: add adpcm codec implementation
Changed paths:
audio/decoders/adpcm.cpp
audio/decoders/adpcm.h
audio/decoders/adpcm_intern.h
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
index 6febb83073c..d3e8d3735ec 100644
--- a/audio/decoders/adpcm.cpp
+++ b/audio/decoders/adpcm.cpp
@@ -559,6 +559,32 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
return samp;
}
+int FOURXM_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ assert(_stream->pos() == 0);
+ assert(_channels == 1 || _channels == 2);
+ for (int i = 0; i < _channels; i++)
+ _status.ima_ch[i].last = _stream->readSint16LE();
+ for (int i = 0; i < _channels; i++)
+ _status.ima_ch[i].stepIndex = _stream->readSint16LE();
+
+ assert(_stream->pos() == _channels * 4);
+ auto samplesToDecode = (_endpos - _stream->pos()) * 2;
+ assert(numSamples >= samplesToDecode);
+
+ int samples = 0;
+ auto encodedPerChannel = (_endpos - _stream->pos()) / _channels;
+ for (int i = 0; i < _channels; i++) {
+ for(auto s = encodedPerChannel; s--; ) {
+ byte data = _stream->readByte();
+ buffer[samples++] = decodeIMA(data & 0x0f, i);
+ buffer[samples++] = decodeIMA((data >> 4) & 0x0f, i);
+ }
+ }
+ assert(_stream->pos() == _endpos);
+ return samples;
+}
+
+
SeekableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, ADPCMType type, int rate, int channels, uint32 blockAlign) {
// If size is 0, report the entire size of the stream
if (!size)
@@ -579,6 +605,8 @@ SeekableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, Dispose
return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
case kADPCMXA:
return new XA_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCM4XM:
+ return new FOURXM_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
default:
error("Unsupported ADPCM encoding");
break;
diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h
index bef9fdfad7e..ca270a074e7 100644
--- a/audio/decoders/adpcm.h
+++ b/audio/decoders/adpcm.h
@@ -59,7 +59,8 @@ enum ADPCMType {
kADPCMDVI, // Intel DVI IMA ADPCM
kADPCMApple, // Apple QuickTime IMA ADPCM
kADPCMDK3, // Duck DK3 IMA ADPCM
- kADPCMXA // XA ADPCM
+ kADPCMXA, // XA ADPCM
+ kADPCM4XM // 4XM ADPCM
};
/**
diff --git a/audio/decoders/adpcm_intern.h b/audio/decoders/adpcm_intern.h
index b848782242c..f1eeedb9d82 100644
--- a/audio/decoders/adpcm_intern.h
+++ b/audio/decoders/adpcm_intern.h
@@ -262,6 +262,22 @@ private:
bool _topNibble;
};
+class FOURXM_ADPCMStream : public Ima_ADPCMStream {
+public:
+ FOURXM_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+ void reset() {
+ Ima_ADPCMStream::reset();
+ }
+
+private:
+};
+
+
} // End of namespace Audio
#endif
Commit: 29a4920d4f8082529572c76966cfd484857e4917
https://github.com/scummvm/scummvm/commit/29a4920d4f8082529572c76966cfd484857e4917
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:43+01:00
Commit Message:
VIDEO: 4XM: decode ADPCM audio data
Changed paths:
video/4xm_decoder.cpp
video/4xm_decoder.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index eca7a71a0db..949e3557232 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -21,8 +21,10 @@
#include "video/4xm_decoder.h"
#include "audio/audiostream.h"
+#include "audio/decoders/adpcm.h"
#include "common/debug.h"
#include "common/endian.h"
+#include "common/memstream.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
@@ -67,14 +69,28 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
}
}
-class FourXMDecoder::FourXMAudioTrack::Stream : public Audio::AudioStream {
+class FourXMDecoder::FourXMAudioStream : public Audio::AudioStream {
const FourXMAudioTrack *_track;
+ Common::List<Common::Array<int16>> _buffers;
public:
- Stream(const FourXMAudioTrack *track) : _track(track) {}
+ FourXMAudioStream(const FourXMAudioTrack *track) : _track(track) {}
+
int readBuffer(int16 *buffer, const int numSamples) override {
- debug("decode next audio frame");
- return 0;
+ int offset = 0;
+ while (!_buffers.empty() && offset < numSamples) {
+ auto &next = _buffers.front();
+ auto read = MIN<uint>(numSamples - offset, next.size());
+ for (uint i = 0; i != read; ++i) {
+ buffer[offset++] = next[i];
+ }
+ if (read == next.size()) {
+ _buffers.pop_front();
+ } else {
+ next.erase(next.begin(), next.begin() + read);
+ }
+ }
+ return offset;
}
bool isStereo() const override {
@@ -90,18 +106,31 @@ public:
auto *decoder = _track->_dec;
return decoder->_curFrame >= decoder->_frames.size();
}
+
+ void decode(uint32 tag, const byte *data, uint size) {
+ Common::MemoryReadStream ms(data, size);
+ Common::ScopedPtr<Audio::SeekableAudioStream> x(Audio::makeADPCMStream(&ms, DisposeAfterUse::NO, size, Audio::kADPCM4XM, _track->_sampleRate, _track->_audioChannels));
+ auto numChannels = _track->_audioChannels;
+ int numSamples = (size - 4 * numChannels) * 2;
+ Common::Array<int16> buf(numSamples);
+ int r = x->readBuffer(buf.data(), numSamples);
+ if (r < 0) {
+ warning("ADPCMStream::readBuffer failed");
+ return;
+ }
+ assert(r == numSamples);
+ if (!buf.empty())
+ _buffers.push_back(Common::move(buf));
+ }
};
Audio::AudioStream *FourXMDecoder::FourXMAudioTrack::getAudioStream() const {
- return new Stream(this);
+ return _dec->_audioStream = new FourXMAudioStream(this);
}
void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, const byte *data, uint size) {
}
-void FourXMDecoder::FourXMAudioTrack::decode(uint32 tag, const byte *data, uint size) {
-}
-
void FourXMDecoder::decodeNextFrameImpl() {
if (_curFrame >= _frames.size())
return;
@@ -123,7 +152,10 @@ void FourXMDecoder::decodeNextFrameImpl() {
_stream->read(packet.data(), size);
switch (tag) {
case MKTAG('s', 'n', 'd', '_'):
- _audio->decode(tag, packet.data(), size);
+ if (_audioStream)
+ _audioStream->decode(tag, packet.data(), size);
+ else
+ warning("no audio stream to decode sample to");
break;
case MKTAG('i', 'f', 'r', 'm'):
case MKTAG('p', 'f', 'r', 'm'):
diff --git a/video/4xm_decoder.h b/video/4xm_decoder.h
index f7e7e9383a7..35efcad9c22 100644
--- a/video/4xm_decoder.h
+++ b/video/4xm_decoder.h
@@ -65,8 +65,11 @@ private:
Common::Rational getFrameRate() const override { return _frameRate; }
};
+ class FourXMAudioStream;
+
class FourXMAudioTrack : public AudioTrack {
- class Stream;
+ friend class FourXMAudioStream;
+
FourXMDecoder *_dec;
uint _trackIdx;
uint _audioType;
@@ -78,8 +81,6 @@ private:
FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate, uint sampleResolution) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate), _sampleResolution(sampleResolution) {
}
- void decode(uint32 tag, const byte *data, uint size);
-
private:
Audio::AudioStream *getAudioStream() const override;
};
@@ -94,6 +95,7 @@ private:
uint _curFrame = 0;
FourXMVideoTrack *_video = nullptr;
FourXMAudioTrack *_audio = nullptr;
+ FourXMAudioStream *_audioStream = nullptr;
};
} // namespace Video
Commit: 43fd11a7c43189773b32dfae356101d180e9d9f1
https://github.com/scummvm/scummvm/commit/43fd11a7c43189773b32dfae356101d180e9d9f1
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:43+01:00
Commit Message:
PHOENIXVR: wait until next frame is available
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index c2d3858b6c2..b8148dc802b 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -396,16 +396,18 @@ Common::Error PhoenixVREngine::run() {
}
if (_movie) {
if (_movie->isPlaying()) {
- debug("playing movie frame: %d", _movie->getCurFrame());
- auto *s = _movie->decodeNextFrame();
- if (s) {
- Common::ScopedPtr<Graphics::Surface> converted;
- if (s->format != _pixelFormat) {
- converted.reset(s->convertTo(_pixelFormat));
- _screen->copyFrom(*converted);
- converted->free();
- } else
- _screen->copyFrom(*s);
+ if (_movie->getTimeToNextFrame() <= 0) {
+ debug("playing movie frame: %d", _movie->getCurFrame());
+ auto *s = _movie->decodeNextFrame();
+ if (s) {
+ Common::ScopedPtr<Graphics::Surface> converted;
+ if (s->format != _pixelFormat) {
+ converted.reset(s->convertTo(_pixelFormat));
+ _screen->copyFrom(*converted);
+ converted->free();
+ } else
+ _screen->copyFrom(*s);
+ }
}
} else
_movie.reset();
Commit: dfdaafbd8813f89db705e7004b8a69a83484ad2d
https://github.com/scummvm/scummvm/commit/dfdaafbd8813f89db705e7004b8a69a83484ad2d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:43+01:00
Commit Message:
AUDIO: 4XM: produce interlaced samples
Changed paths:
audio/decoders/adpcm.cpp
video/4xm_decoder.cpp
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
index d3e8d3735ec..9d330a904b9 100644
--- a/audio/decoders/adpcm.cpp
+++ b/audio/decoders/adpcm.cpp
@@ -568,20 +568,22 @@ int FOURXM_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
_status.ima_ch[i].stepIndex = _stream->readSint16LE();
assert(_stream->pos() == _channels * 4);
- auto samplesToDecode = (_endpos - _stream->pos()) * 2;
+ int samplesToDecode = (_endpos - _stream->pos()) * 2;
assert(numSamples >= samplesToDecode);
- int samples = 0;
auto encodedPerChannel = (_endpos - _stream->pos()) / _channels;
for (int i = 0; i < _channels; i++) {
+ int samples = i;
for(auto s = encodedPerChannel; s--; ) {
byte data = _stream->readByte();
- buffer[samples++] = decodeIMA(data & 0x0f, i);
- buffer[samples++] = decodeIMA((data >> 4) & 0x0f, i);
+ buffer[samples] = decodeIMA(data & 0x0f, i);
+ samples += _channels;
+ buffer[samples] = decodeIMA((data >> 4) & 0x0f, i);
+ samples += _channels;
}
}
assert(_stream->pos() == _endpos);
- return samples;
+ return samplesToDecode;
}
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 949e3557232..a28f362581b 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -125,7 +125,9 @@ public:
};
Audio::AudioStream *FourXMDecoder::FourXMAudioTrack::getAudioStream() const {
- return _dec->_audioStream = new FourXMAudioStream(this);
+ if (!_dec->_audioStream)
+ _dec->_audioStream = new FourXMAudioStream(this);
+ return _dec->_audioStream;
}
void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, const byte *data, uint size) {
Commit: a8078c023149089858fc619a0f9ebbb05ec3d5da
https://github.com/scummvm/scummvm/commit/a8078c023149089858fc619a0f9ebbb05ec3d5da
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:44+01:00
Commit Message:
PHOENIXVR: use endOfVideo as end of video marker
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index b8148dc802b..cd6564bd91d 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -395,7 +395,7 @@ Common::Error PhoenixVREngine::run() {
}
}
if (_movie) {
- if (_movie->isPlaying()) {
+ if (!_movie->endOfVideo()) {
if (_movie->getTimeToNextFrame() <= 0) {
debug("playing movie frame: %d", _movie->getCurFrame());
auto *s = _movie->decodeNextFrame();
Commit: cad08976e796fdad69cba924d5834e7d1485eda3
https://github.com/scummvm/scummvm/commit/cad08976e796fdad69cba924d5834e7d1485eda3
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:44+01:00
Commit Message:
VIDEO: 4XM: do not use audio for sync (it's in frame after video), parse audio packet
Changed paths:
video/4xm_decoder.cpp
video/4xm_decoder.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index a28f362581b..d1f2c646076 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -147,18 +147,22 @@ void FourXMDecoder::decodeNextFrameImpl() {
while (_stream->pos() < frame.end) {
uint32 tag = _stream->readUint32BE();
uint32 size = _stream->readUint32LE();
- debug("%u: sub frame %s, %u bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos() - 4);
+ debug("%u: sub frame %s, %u bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos());
auto pos = _stream->pos();
if (size > packet.size())
packet.resize(size);
_stream->read(packet.data(), size);
switch (tag) {
- case MKTAG('s', 'n', 'd', '_'):
- if (_audioStream)
- _audioStream->decode(tag, packet.data(), size);
- else
- warning("no audio stream to decode sample to");
- break;
+ case MKTAG('s', 'n', 'd', '_'): {
+ auto trackIdx = _stream->readUint32LE();
+ _stream->skip(4);
+ if (trackIdx == 0) {
+ if (_audioStream)
+ _audioStream->decode(tag, packet.data() + 8, size - 8);
+ else
+ warning("no audio stream to decode sample to");
+ }
+ } break;
case MKTAG('i', 'f', 'r', 'm'):
case MKTAG('p', 'f', 'r', 'm'):
case MKTAG('c', 'f', 'r', 'm'):
@@ -223,8 +227,12 @@ void FourXMDecoder::readList(uint32 listEnd) {
auto audioChannels = _stream->readUint32LE();
auto sampleRate = _stream->readUint32LE();
auto sampleResolution = _stream->readUint32LE();
- debug("audio track %u %u %u %u %u", trackIdx, audioType, audioChannels, sampleRate, sampleResolution);
- addTrack(_audio = new FourXMAudioTrack(this, trackIdx, audioType, audioChannels, sampleRate, sampleResolution));
+ debug("audio track idx: %u type: %u channels: %u sample rate: %u bits: %u", trackIdx, audioType, audioChannels, sampleRate, sampleResolution);
+ if (sampleResolution != 16)
+ error("only 16 bit audio is supported");
+ addTrack(_audio = new FourXMAudioTrack(this, trackIdx, audioType, audioChannels, sampleRate));
+ if (trackIdx == 0) // only support single track for now
+ _audioStream = new FourXMAudioStream(_audio);
} break;
default:
break;
diff --git a/video/4xm_decoder.h b/video/4xm_decoder.h
index 35efcad9c22..d9781b0b111 100644
--- a/video/4xm_decoder.h
+++ b/video/4xm_decoder.h
@@ -32,6 +32,7 @@ namespace Video {
class FourXMDecoder : public Video::VideoDecoder {
public:
bool loadStream(Common::SeekableReadStream *stream) override;
+ bool useAudioSync() const override { return false; }
private:
struct Frame {
@@ -75,10 +76,9 @@ private:
uint _audioType;
uint _audioChannels;
uint _sampleRate;
- uint _sampleResolution;
public:
- FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate, uint sampleResolution) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate), _sampleResolution(sampleResolution) {
+ FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate) {
}
private:
Commit: 215d188e3a4dc5fa245e95dad0dd9b61703884a8
https://github.com/scummvm/scummvm/commit/215d188e3a4dc5fa245e95dad0dd9b61703884a8
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:44+01:00
Commit Message:
VIDEO: 4XM: use queueing audio stream and simplify audio track
Changed paths:
video/4xm_decoder.cpp
video/4xm_decoder.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index d1f2c646076..c4cc3a81063 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -22,6 +22,7 @@
#include "video/4xm_decoder.h"
#include "audio/audiostream.h"
#include "audio/decoders/adpcm.h"
+#include "audio/decoders/raw.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/memstream.h"
@@ -69,66 +70,52 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
}
}
-class FourXMDecoder::FourXMAudioStream : public Audio::AudioStream {
- const FourXMAudioTrack *_track;
- Common::List<Common::Array<int16>> _buffers;
+class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
+ FourXMDecoder *_dec;
+ uint _trackIdx;
+ uint _audioType;
+ uint _audioChannels;
+ uint _sampleRate;
+ Common::MemoryReadWriteStream _bitStream;
+ Common::ScopedPtr<Audio::AudioStream> _adpcm;
+ Common::ScopedPtr<Audio::QueuingAudioStream> _output;
public:
- FourXMAudioStream(const FourXMAudioTrack *track) : _track(track) {}
-
- int readBuffer(int16 *buffer, const int numSamples) override {
- int offset = 0;
- while (!_buffers.empty() && offset < numSamples) {
- auto &next = _buffers.front();
- auto read = MIN<uint>(numSamples - offset, next.size());
- for (uint i = 0; i != read; ++i) {
- buffer[offset++] = next[i];
- }
- if (read == next.size()) {
- _buffers.pop_front();
- } else {
- next.erase(next.begin(), next.begin() + read);
- }
- }
- return offset;
- }
-
- bool isStereo() const override {
- return _track->_audioChannels > 1;
- }
-
- /** Sample rate of the stream. */
- virtual int getRate() const override {
- return _track->_sampleRate;
- }
-
- virtual bool endOfData() const override {
- auto *decoder = _track->_dec;
- return decoder->_curFrame >= decoder->_frames.size();
+ FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
+ _bitStream(DisposeAfterUse::YES), _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
}
void decode(uint32 tag, const byte *data, uint size) {
- Common::MemoryReadStream ms(data, size);
- Common::ScopedPtr<Audio::SeekableAudioStream> x(Audio::makeADPCMStream(&ms, DisposeAfterUse::NO, size, Audio::kADPCM4XM, _track->_sampleRate, _track->_audioChannels));
- auto numChannels = _track->_audioChannels;
- int numSamples = (size - 4 * numChannels) * 2;
- Common::Array<int16> buf(numSamples);
- int r = x->readBuffer(buf.data(), numSamples);
+ debug("got %u of audio data", size);
+ _bitStream.write(data, size);
+ if (!_adpcm) {
+ assert(_bitStream.pos() == 0);
+ _adpcm.reset(Audio::makeADPCMStream(&_bitStream, DisposeAfterUse::NO, size, Audio::kADPCM4XM, _sampleRate, _audioChannels));
+ // this is first bitstream write, getting preamble size from stream pos.
+ size -= _bitStream.pos();
+ }
+ int numSamples = size * 2;
+
+ auto bufSize = numSamples * sizeof(int16);
+ int16 *buf = static_cast<int16 *>(malloc(bufSize));
+ int r = _adpcm->readBuffer(buf, numSamples);
if (r < 0) {
+ free(buf);
warning("ADPCMStream::readBuffer failed");
return;
}
+ assert(_bitStream.pos() == _bitStream.size());
assert(r == numSamples);
- if (!buf.empty())
- _buffers.push_back(Common::move(buf));
+ debug("decoded %d samples", r);
+ byte flags = Audio::FLAG_16BITS;
+ if (_audioChannels > 1)
+ flags |= Audio::FLAG_STEREO;
+ _output->queueBuffer(reinterpret_cast<byte *>(buf), bufSize, DisposeAfterUse::YES, flags);
}
-};
-Audio::AudioStream *FourXMDecoder::FourXMAudioTrack::getAudioStream() const {
- if (!_dec->_audioStream)
- _dec->_audioStream = new FourXMAudioStream(this);
- return _dec->_audioStream;
-}
+private:
+ Audio::AudioStream *getAudioStream() const override { return _output.get(); }
+};
void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, const byte *data, uint size) {
}
@@ -154,13 +141,17 @@ void FourXMDecoder::decodeNextFrameImpl() {
_stream->read(packet.data(), size);
switch (tag) {
case MKTAG('s', 'n', 'd', '_'): {
- auto trackIdx = _stream->readUint32LE();
- _stream->skip(4);
- if (trackIdx == 0) {
- if (_audioStream)
- _audioStream->decode(tag, packet.data() + 8, size - 8);
- else
- warning("no audio stream to decode sample to");
+ uint offset = 0;
+ while (offset < size) {
+ auto trackIdx = READ_UINT32(packet.data() + offset);
+ auto packetSize = READ_UINT32(packet.data() + offset + 4);
+ if (trackIdx == 0) {
+ if (_audio)
+ _audio->decode(tag, packet.data() + offset + 8, packetSize);
+ else
+ warning("no audio stream to decode sample to");
+ }
+ offset += packetSize + 8;
}
} break;
case MKTAG('i', 'f', 'r', 'm'):
@@ -231,8 +222,6 @@ void FourXMDecoder::readList(uint32 listEnd) {
if (sampleResolution != 16)
error("only 16 bit audio is supported");
addTrack(_audio = new FourXMAudioTrack(this, trackIdx, audioType, audioChannels, sampleRate));
- if (trackIdx == 0) // only support single track for now
- _audioStream = new FourXMAudioStream(_audio);
} break;
default:
break;
diff --git a/video/4xm_decoder.h b/video/4xm_decoder.h
index d9781b0b111..31a6e9205f4 100644
--- a/video/4xm_decoder.h
+++ b/video/4xm_decoder.h
@@ -66,24 +66,7 @@ private:
Common::Rational getFrameRate() const override { return _frameRate; }
};
- class FourXMAudioStream;
-
- class FourXMAudioTrack : public AudioTrack {
- friend class FourXMAudioStream;
-
- FourXMDecoder *_dec;
- uint _trackIdx;
- uint _audioType;
- uint _audioChannels;
- uint _sampleRate;
-
- public:
- FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate) {
- }
-
- private:
- Audio::AudioStream *getAudioStream() const override;
- };
+ class FourXMAudioTrack;
void readList(uint32 size);
void decodeNextFrameImpl();
@@ -95,7 +78,6 @@ private:
uint _curFrame = 0;
FourXMVideoTrack *_video = nullptr;
FourXMAudioTrack *_audio = nullptr;
- FourXMAudioStream *_audioStream = nullptr;
};
} // namespace Video
Commit: 6904853041270d1fac0e0ebce78bc2f4c95bbb71
https://github.com/scummvm/scummvm/commit/6904853041270d1fac0e0ebce78bc2f4c95bbb71
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:44+01:00
Commit Message:
PHOENIXVR: use needsUpdate()
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index cd6564bd91d..8d525adb519 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -396,7 +396,7 @@ Common::Error PhoenixVREngine::run() {
}
if (_movie) {
if (!_movie->endOfVideo()) {
- if (_movie->getTimeToNextFrame() <= 0) {
+ if (_movie->needsUpdate()) {
debug("playing movie frame: %d", _movie->getCurFrame());
auto *s = _movie->decodeNextFrame();
if (s) {
Commit: 5102e01b023929e8e73544baf80e2087898654a0
https://github.com/scummvm/scummvm/commit/5102e01b023929e8e73544baf80e2087898654a0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:45+01:00
Commit Message:
VIDEO: 4XM: implement PCM audio tracks
Changed paths:
video/4xm_decoder.cpp
video/4xm_decoder.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index c4cc3a81063..cb6a3120448 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -45,6 +45,62 @@ Common::String tagName(uint32 tag) {
}
} // namespace
+class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
+ FourXMDecoder *_dec;
+ uint _trackIdx;
+ uint _audioType;
+ uint _audioChannels;
+ uint _sampleRate;
+ Common::ScopedPtr<Audio::QueuingAudioStream> _output;
+
+public:
+ FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
+ _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
+ }
+
+ void decode(uint32 tag, byte *buf, uint size) {
+ if (_audioType == 0) {
+ // Raw PCM data
+ byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
+ if (_audioChannels > 1)
+ flags |= Audio::FLAG_STEREO;
+ _output->queueBuffer(buf, size, DisposeAfterUse::YES, flags);
+ } else {
+ free(buf);
+ warning("unsupported audio type %u", _audioType);
+ }
+ }
+
+private:
+ Audio::AudioStream *getAudioStream() const override { return _output.get(); }
+};
+
+class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
+ FourXMDecoder *_dec;
+ Common::Rational _frameRate;
+ uint _w, _h;
+ Graphics::Surface *_frame;
+
+public:
+ FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _frame(nullptr) {}
+ ~FourXMVideoTrack();
+
+ uint16 getWidth() const override { return _w; }
+ uint16 getHeight() const override { return _h; }
+
+ Graphics::PixelFormat getPixelFormat() const override {
+ return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); // RGB565
+ }
+ int getCurFrame() const override;
+ int getFrameCount() const override;
+ const Graphics::Surface *decodeNextFrame() override;
+
+ void decode(uint32 tag, byte *buf, uint size);
+
+private:
+ Common::Rational getFrameRate() const override { return _frameRate; }
+};
+
const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
if (!_frame) {
_frame = new Graphics::Surface();
@@ -70,54 +126,8 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
}
}
-class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
- FourXMDecoder *_dec;
- uint _trackIdx;
- uint _audioType;
- uint _audioChannels;
- uint _sampleRate;
- Common::MemoryReadWriteStream _bitStream;
- Common::ScopedPtr<Audio::AudioStream> _adpcm;
- Common::ScopedPtr<Audio::QueuingAudioStream> _output;
-
-public:
- FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
- _bitStream(DisposeAfterUse::YES), _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
- }
-
- void decode(uint32 tag, const byte *data, uint size) {
- debug("got %u of audio data", size);
- _bitStream.write(data, size);
- if (!_adpcm) {
- assert(_bitStream.pos() == 0);
- _adpcm.reset(Audio::makeADPCMStream(&_bitStream, DisposeAfterUse::NO, size, Audio::kADPCM4XM, _sampleRate, _audioChannels));
- // this is first bitstream write, getting preamble size from stream pos.
- size -= _bitStream.pos();
- }
- int numSamples = size * 2;
-
- auto bufSize = numSamples * sizeof(int16);
- int16 *buf = static_cast<int16 *>(malloc(bufSize));
- int r = _adpcm->readBuffer(buf, numSamples);
- if (r < 0) {
- free(buf);
- warning("ADPCMStream::readBuffer failed");
- return;
- }
- assert(_bitStream.pos() == _bitStream.size());
- assert(r == numSamples);
- debug("decoded %d samples", r);
- byte flags = Audio::FLAG_16BITS;
- if (_audioChannels > 1)
- flags |= Audio::FLAG_STEREO;
- _output->queueBuffer(reinterpret_cast<byte *>(buf), bufSize, DisposeAfterUse::YES, flags);
- }
-
-private:
- Audio::AudioStream *getAudioStream() const override { return _output.get(); }
-};
-
-void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, const byte *data, uint size) {
+void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, byte *buf, uint size) {
+ free(buf);
}
void FourXMDecoder::decodeNextFrameImpl() {
@@ -130,37 +140,39 @@ void FourXMDecoder::decodeNextFrameImpl() {
error("invalid FRAM offset for frame %u", _curFrame);
}
- Common::Array<byte> packet;
while (_stream->pos() < frame.end) {
uint32 tag = _stream->readUint32BE();
uint32 size = _stream->readUint32LE();
debug("%u: sub frame %s, %u bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos());
auto pos = _stream->pos();
- if (size > packet.size())
- packet.resize(size);
- _stream->read(packet.data(), size);
+
+ auto loadBuf = [this](uint bufSize) {
+ byte *buf = static_cast<byte *>(malloc(bufSize));
+ if (!buf)
+ error("failed to allocate %u bytes", bufSize);
+ _stream->read(buf, bufSize);
+ return buf;
+ };
switch (tag) {
case MKTAG('s', 'n', 'd', '_'): {
uint offset = 0;
while (offset < size) {
- auto trackIdx = READ_UINT32(packet.data() + offset);
- auto packetSize = READ_UINT32(packet.data() + offset + 4);
- if (trackIdx == 0) {
- if (_audio)
- _audio->decode(tag, packet.data() + offset + 8, packetSize);
- else
- warning("no audio stream to decode sample to");
- }
- offset += packetSize + 8;
+ auto trackIdx = _stream->readUint32LE();
+ auto packetSize = _stream->readUint32LE();
+ if (trackIdx == 0 && _audio) {
+ _audio->decode(tag, loadBuf(packetSize), packetSize);
+ } else
+ offset += packetSize + 8;
}
} break;
case MKTAG('i', 'f', 'r', 'm'):
case MKTAG('p', 'f', 'r', 'm'):
case MKTAG('c', 'f', 'r', 'm'):
- _video->decode(tag, packet.data(), size);
+ _video->decode(tag, loadBuf(size), size);
break;
default:
warning("unknown frame type %s", tagName(tag).c_str());
+ _stream->skip(size);
}
_stream->seek(pos + size + (size & 1));
}
diff --git a/video/4xm_decoder.h b/video/4xm_decoder.h
index 31a6e9205f4..d1a9e026cae 100644
--- a/video/4xm_decoder.h
+++ b/video/4xm_decoder.h
@@ -40,32 +40,7 @@ private:
int64 end;
};
- class FourXMVideoTrack : public FixedRateVideoTrack {
- FourXMDecoder *_dec;
- Common::Rational _frameRate;
- uint _w, _h;
- Graphics::Surface *_frame;
-
- public:
- FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _frame(nullptr) {}
- ~FourXMVideoTrack();
-
- uint16 getWidth() const override { return _w; }
- uint16 getHeight() const override { return _h; }
-
- Graphics::PixelFormat getPixelFormat() const override {
- return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); // RGB565
- }
- int getCurFrame() const override;
- int getFrameCount() const override;
- const Graphics::Surface *decodeNextFrame() override;
-
- void decode(uint32 tag, const byte *data, uint size);
-
- private:
- Common::Rational getFrameRate() const override { return _frameRate; }
- };
-
+ class FourXMVideoTrack;
class FourXMAudioTrack;
void readList(uint32 size);
Commit: dae7769aa8110c47c3d07391daec169c50bdbbde
https://github.com/scummvm/scummvm/commit/dae7769aa8110c47c3d07391daec169c50bdbbde
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:45+01:00
Commit Message:
AUDIO: ADPCM: store predictors in ctor
Changed paths:
audio/decoders/adpcm.cpp
audio/decoders/adpcm_intern.h
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
index 9d330a904b9..97c6385a097 100644
--- a/audio/decoders/adpcm.cpp
+++ b/audio/decoders/adpcm.cpp
@@ -559,20 +559,12 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
return samp;
}
+// format is planar, we expect numSamples of L channel + numSamples of R channel to follow.
int FOURXM_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
- assert(_stream->pos() == 0);
- assert(_channels == 1 || _channels == 2);
- for (int i = 0; i < _channels; i++)
- _status.ima_ch[i].last = _stream->readSint16LE();
- for (int i = 0; i < _channels; i++)
- _status.ima_ch[i].stepIndex = _stream->readSint16LE();
-
- assert(_stream->pos() == _channels * 4);
- int samplesToDecode = (_endpos - _stream->pos()) * 2;
- assert(numSamples >= samplesToDecode);
-
- auto encodedPerChannel = (_endpos - _stream->pos()) / _channels;
- for (int i = 0; i < _channels; i++) {
+ int requiredSamplesPerChannel = numSamples / _channels; // each byte encode 2 samples.
+
+ auto encodedPerChannel = requiredSamplesPerChannel / 2;
+ for (int i = 0; i < _channels; ++i) {
int samples = i;
for(auto s = encodedPerChannel; s--; ) {
byte data = _stream->readByte();
@@ -582,8 +574,8 @@ int FOURXM_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
samples += _channels;
}
}
- assert(_stream->pos() == _endpos);
- return samplesToDecode;
+ assert(_stream->pos() == _stream->size());
+ return numSamples;
}
@@ -608,7 +600,7 @@ SeekableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, Dispose
case kADPCMXA:
return new XA_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
case kADPCM4XM:
- return new FOURXM_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ return new FOURXM_ADPCMStream(stream, disposeAfterUse, size, rate, channels);
default:
error("Unsupported ADPCM encoding");
break;
diff --git a/audio/decoders/adpcm_intern.h b/audio/decoders/adpcm_intern.h
index f1eeedb9d82..029ceb6f791 100644
--- a/audio/decoders/adpcm_intern.h
+++ b/audio/decoders/adpcm_intern.h
@@ -168,7 +168,6 @@ public:
}
virtual int readBuffer(int16 *buffer, const int numSamples);
-
};
class MSIma_ADPCMStream : public Ima_ADPCMStream {
@@ -264,8 +263,17 @@ private:
class FOURXM_ADPCMStream : public Ima_ADPCMStream {
public:
- FOURXM_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
- : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ FOURXM_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, 0) {
+ assert(_channels == 1 || _channels == 2);
+ for (int i = 0; i < _channels; i++)
+ _status.ima_ch[i].last = _stream->readSint16LE();
+ for (int i = 0; i < _channels; i++) {
+ auto stepIndex = _stream->readSint16LE();
+ _status.ima_ch[i].stepIndex = stepIndex;
+ if (stepIndex > 88)
+ error("invalid step index");
+ }
}
virtual int readBuffer(int16 *buffer, const int numSamples);
@@ -273,11 +281,8 @@ public:
void reset() {
Ima_ADPCMStream::reset();
}
-
-private:
};
-
} // End of namespace Audio
#endif
Commit: 364dda63d38d250e9069d7454ce7ab2dd20b3759
https://github.com/scummvm/scummvm/commit/364dda63d38d250e9069d7454ce7ab2dd20b3759
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:45+01:00
Commit Message:
VIDEO: 4XM: add ADPCM support
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index cb6a3120448..318e63c896e 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -52,12 +52,16 @@ class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
uint _audioChannels;
uint _sampleRate;
Common::ScopedPtr<Audio::QueuingAudioStream> _output;
+ Common::ScopedPtr<Audio::AudioStream> _adpcm;
+ Common::MemoryReadWriteStream _bitStream;
public:
FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
- _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
+ _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)), _bitStream(DisposeAfterUse::YES) {
}
+ byte getAudioType() const { return _audioType; }
+
void decode(uint32 tag, byte *buf, uint size) {
if (_audioType == 0) {
// Raw PCM data
@@ -65,6 +69,22 @@ public:
if (_audioChannels > 1)
flags |= Audio::FLAG_STEREO;
_output->queueBuffer(buf, size, DisposeAfterUse::YES, flags);
+ } else if (_audioType == 1) {
+ _bitStream.write(buf, size);
+ auto header = 0;
+ if (!_adpcm) {
+ _adpcm.reset(Audio::makeADPCMStream(&_bitStream, DisposeAfterUse::NO, 0, Audio::ADPCMType::kADPCM4XM, _sampleRate, _audioChannels));
+ header += _audioChannels * 4;
+ }
+ size -= header;
+ auto numSamples = size * 2;
+ void *samples = malloc(numSamples * sizeof(int16));
+ _adpcm->readBuffer(static_cast<int16 *>(samples), numSamples);
+ byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
+ if (_audioChannels > 1)
+ flags |= Audio::FLAG_STEREO;
+ _output->queueBuffer(static_cast<byte *>(samples), numSamples * 2, DisposeAfterUse::YES, flags);
+ free(buf);
} else {
free(buf);
warning("unsupported audio type %u", _audioType);
@@ -155,14 +175,33 @@ void FourXMDecoder::decodeNextFrameImpl() {
};
switch (tag) {
case MKTAG('s', 'n', 'd', '_'): {
- uint offset = 0;
- while (offset < size) {
+ if (!_audio)
+ error("no audio track but got sound frame");
+ switch (_audio->getAudioType()) {
+ case 0: {
+ uint offset = 0;
+ while (offset < size) {
+ auto trackIdx = _stream->readUint32LE();
+ auto packetSize = _stream->readUint32LE();
+ if (trackIdx == 0 && _audio) {
+ _audio->decode(tag, loadBuf(packetSize), packetSize);
+ } else {
+ _stream->skip(packetSize);
+ offset += packetSize + 8;
+ }
+ }
+ } break;
+ case 1: {
auto trackIdx = _stream->readUint32LE();
- auto packetSize = _stream->readUint32LE();
+ _stream->skip(4);
if (trackIdx == 0 && _audio) {
- _audio->decode(tag, loadBuf(packetSize), packetSize);
- } else
- offset += packetSize + 8;
+ _audio->decode(tag, loadBuf(size - 8), size - 8);
+ } else {
+ _stream->skip(size - 8);
+ }
+ } break;
+ default:
+ warning("unknown audio type");
}
} break;
case MKTAG('i', 'f', 'r', 'm'):
Commit: 638af51c12f204980cae6aa00c1c0423282c3b67
https://github.com/scummvm/scummvm/commit/638af51c12f204980cae6aa00c1c0423282c3b67
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:46+01:00
Commit Message:
AUDIO: 4XM: store planar data and read it from readBuffer
Changed paths:
audio/decoders/adpcm.cpp
audio/decoders/adpcm_intern.h
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
index 97c6385a097..55208ae6248 100644
--- a/audio/decoders/adpcm.cpp
+++ b/audio/decoders/adpcm.cpp
@@ -559,23 +559,46 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
return samp;
}
-// format is planar, we expect numSamples of L channel + numSamples of R channel to follow.
-int FOURXM_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
- int requiredSamplesPerChannel = numSamples / _channels; // each byte encode 2 samples.
-
- auto encodedPerChannel = requiredSamplesPerChannel / 2;
+void FOURXM_ADPCMStream::decode() {
+ assert(_channels == 1 || _channels == 2);
+ for (int i = 0; i < _channels; i++)
+ _status.ima_ch[i].last = _stream->readSint16LE();
+ for (int i = 0; i < _channels; i++) {
+ auto stepIndex = _stream->readSint16LE();
+ _status.ima_ch[i].stepIndex = stepIndex;
+ if (stepIndex > 88)
+ error("invalid step index %d", stepIndex);
+ }
+ auto size = _endpos - _stream->pos();
+ _planes.resize(_channels);
+ auto encodedPerChannel = size / _channels;
for (int i = 0; i < _channels; ++i) {
- int samples = i;
+ auto &plane = _planes[i];
+ plane.resize(encodedPerChannel * 2);
+ auto sample = 0;
for(auto s = encodedPerChannel; s--; ) {
byte data = _stream->readByte();
- buffer[samples] = decodeIMA(data & 0x0f, i);
- samples += _channels;
- buffer[samples] = decodeIMA((data >> 4) & 0x0f, i);
- samples += _channels;
+ plane[sample++] = decodeIMA(data & 0x0f, i);
+ plane[sample++] = decodeIMA((data >> 4) & 0x0f, i);
+ }
+ }
+}
+
+
+// format is planar, we expect numSamples of L channel + numSamples of R channel to follow.
+int FOURXM_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ assert(numSamples % _channels == 0);
+ int samples = 0;
+ while(samples < numSamples) {
+ for(int c = 0; c != _channels; ++c) {
+ auto &plane = _planes[c];
+ if (_samplePos >= plane.size())
+ return samples;
+ buffer[samples++] = plane[_samplePos];
}
+ ++_samplePos;
}
- assert(_stream->pos() == _stream->size());
- return numSamples;
+ return samples;
}
diff --git a/audio/decoders/adpcm_intern.h b/audio/decoders/adpcm_intern.h
index 029ceb6f791..599a1964d73 100644
--- a/audio/decoders/adpcm_intern.h
+++ b/audio/decoders/adpcm_intern.h
@@ -33,6 +33,7 @@
#include "audio/audiostream.h"
#include "common/endian.h"
#include "common/ptr.h"
+#include "common/array.h"
#include "common/stream.h"
#include "common/textconsole.h"
@@ -265,22 +266,24 @@ class FOURXM_ADPCMStream : public Ima_ADPCMStream {
public:
FOURXM_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels)
: Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, 0) {
- assert(_channels == 1 || _channels == 2);
- for (int i = 0; i < _channels; i++)
- _status.ima_ch[i].last = _stream->readSint16LE();
- for (int i = 0; i < _channels; i++) {
- auto stepIndex = _stream->readSint16LE();
- _status.ima_ch[i].stepIndex = stepIndex;
- if (stepIndex > 88)
- error("invalid step index");
- }
+ decode();
}
- virtual int readBuffer(int16 *buffer, const int numSamples);
+ int readBuffer(int16 *buffer, const int numSamples) override;
+ bool endOfData() const override {
+ return !_planes.empty() && _samplePos >= _planes[0].size();
+ }
void reset() {
Ima_ADPCMStream::reset();
+ _samplePos = 0;
+ _planes.clear();
}
+private:
+ void decode();
+
+ uint _samplePos = 0;
+ Common::Array<Common::Array<int16>> _planes;
};
} // End of namespace Audio
Commit: 418e2ba497ec0bfe45b1d72ba7962343f6b81e49
https://github.com/scummvm/scummvm/commit/418e2ba497ec0bfe45b1d72ba7962343f6b81e49
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:46+01:00
Commit Message:
VIDEO: 4XM: add ADPCM support
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 318e63c896e..2bac06f2285 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -52,12 +52,10 @@ class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
uint _audioChannels;
uint _sampleRate;
Common::ScopedPtr<Audio::QueuingAudioStream> _output;
- Common::ScopedPtr<Audio::AudioStream> _adpcm;
- Common::MemoryReadWriteStream _bitStream;
public:
FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
- _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)), _bitStream(DisposeAfterUse::YES) {
+ _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
}
byte getAudioType() const { return _audioType; }
@@ -70,21 +68,8 @@ public:
flags |= Audio::FLAG_STEREO;
_output->queueBuffer(buf, size, DisposeAfterUse::YES, flags);
} else if (_audioType == 1) {
- _bitStream.write(buf, size);
- auto header = 0;
- if (!_adpcm) {
- _adpcm.reset(Audio::makeADPCMStream(&_bitStream, DisposeAfterUse::NO, 0, Audio::ADPCMType::kADPCM4XM, _sampleRate, _audioChannels));
- header += _audioChannels * 4;
- }
- size -= header;
- auto numSamples = size * 2;
- void *samples = malloc(numSamples * sizeof(int16));
- _adpcm->readBuffer(static_cast<int16 *>(samples), numSamples);
- byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
- if (_audioChannels > 1)
- flags |= Audio::FLAG_STEREO;
- _output->queueBuffer(static_cast<byte *>(samples), numSamples * 2, DisposeAfterUse::YES, flags);
- free(buf);
+ auto *input = new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
+ _output->queueAudioStream(Audio::makeADPCMStream(input, DisposeAfterUse::YES, size, Audio::ADPCMType::kADPCM4XM, _sampleRate, _audioChannels));
} else {
free(buf);
warning("unsupported audio type %u", _audioType);
Commit: 7ca5e834d48cd69e7e52eee5218cabf1e51b2291
https://github.com/scummvm/scummvm/commit/7ca5e834d48cd69e7e52eee5218cabf1e51b2291
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:46+01:00
Commit Message:
PHOENIXVR: add i/p/c frame headers parsing
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 2bac06f2285..7786df92d08 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -101,6 +101,9 @@ public:
const Graphics::Surface *decodeNextFrame() override;
void decode(uint32 tag, byte *buf, uint size);
+ void decode_ifrm(Common::SeekableReadStream *stream);
+ void decode_pfrm(Common::SeekableReadStream *stream);
+ void decode_cfrm(Common::SeekableReadStream *stream);
private:
Common::Rational getFrameRate() const override { return _frameRate; }
@@ -131,8 +134,47 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
}
}
+void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *stream) {
+ stream->skip(4);
+ auto bitstreamSize = stream->readUint32LE();
+ stream->skip(bitstreamSize);
+ auto prefixSize = stream->readUint32LE();
+ auto tokenCount = stream->readUint32LE();
+ debug("i-frame, bitstream: %u, prefix stream: %u, tokens: %u", bitstreamSize, prefixSize, tokenCount);
+ stream->skip(prefixSize * 4);
+}
+
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
+ stream->skip(12);
+ auto bitStreamSize = stream->readUint32LE();
+ auto wordStreamSize = stream->readUint32LE();
+ auto byteStreamSize = stream->readUint32LE();
+ debug("p-frame, bitstream: %u, wordstream: %u, bytestream: %u", bitStreamSize, wordStreamSize, byteStreamSize);
+}
+
+void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *stream) {
+ stream->skip(4);
+ auto frameIdx = stream->readUint32LE();
+ auto frameSize = stream->readUint32LE();
+ debug("c-frame, frame id: %u, size: %u", frameIdx, frameSize);
+}
+
void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, byte *buf, uint size) {
- free(buf);
+ Common::MemoryReadStream ms(buf, size, DisposeAfterUse::YES);
+ switch (tag) {
+ case MKTAG('i', 'f', 'r', 'm'):
+ decode_ifrm(&ms);
+ break;
+ case MKTAG('p', 'f', 'r', 'm'):
+ decode_pfrm(&ms);
+ break;
+ case MKTAG('c', 'f', 'r', 'm'):
+ decode_cfrm(&ms);
+ break;
+ default:
+ warning("uknown video frame %s", tagName(tag).c_str());
+ break;
+ }
}
void FourXMDecoder::decodeNextFrameImpl() {
Commit: 2287e2c4c6ae8daed743f7aea5d969406da4cfff
https://github.com/scummvm/scummvm/commit/2287e2c4c6ae8daed743f7aea5d969406da4cfff
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:46+01:00
Commit Message:
PHOENIXVR: enabled play movie, it's necessary for game scripts to work
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 8d525adb519..226a6fa37e9 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -175,7 +175,6 @@ void PhoenixVREngine::stopSound(const Common::String &sound) {
void PhoenixVREngine::playMovie(const Common::String &movie) {
debug("playMovie %s", movie.c_str());
-#if 0
_movie.reset(new Video::FourXMDecoder());
if (_movie->loadFile(Common::Path{movie})) {
_movie->start();
@@ -183,7 +182,6 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
_movie.reset();
warning("playMovie %s failed", movie.c_str());
}
-#endif
}
void PhoenixVREngine::resetLockKey() {
Commit: 09d56114eacd35fece3fbd63f77a2b3c725c572a
https://github.com/scummvm/scummvm/commit/09d56114eacd35fece3fbd63f77a2b3c725c572a
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:47+01:00
Commit Message:
PHOENIXVR: implement timer logic
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 12d817b28b7..124cfa84260 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -118,15 +118,17 @@ struct StartTimer : public Script::Command {
StartTimer(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("starttimer %g", seconds);
+ g_engine->startTimer(seconds);
}
};
struct PauseTimer : public Script::Command {
- int arg1, arg2;
+ int paused, deactivate;
- PauseTimer(const Common::Array<Common::String> &args) : arg1(atoi(args[0].c_str())), arg2(atoi(args[1].c_str())) {}
+ PauseTimer(const Common::Array<Common::String> &args) : paused(atoi(args[0].c_str())), deactivate(atoi(args[1].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("pause_timer %d %d", arg1, arg2);
+ debug("pausetimer %d %d", paused, deactivate);
+ g_engine->pauseTimer(paused, deactivate);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 226a6fa37e9..aff5cf52ca6 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -236,7 +236,49 @@ void PhoenixVREngine::executeTest(int idx) {
warning("invalid test id %d", idx);
}
+void PhoenixVREngine::startTimer(float seconds) {
+ _timer = seconds;
+ _timerFlags = 5;
+}
+
+void PhoenixVREngine::pauseTimer(bool pause, bool deactivate) {
+ if (pause)
+ _timerFlags |= 2;
+ else
+ _timerFlags &= ~2;
+ if (deactivate)
+ _timerFlags &= ~4;
+ else
+ _timerFlags |= 4;
+}
+
+void PhoenixVREngine::killTimer() {
+ _timerFlags = 0;
+}
+
+void PhoenixVREngine::tickTimer(float dt) {
+ if (_timerFlags) {
+ if ((_timerFlags & 2) == 0) {
+ if (_timer > dt) {
+ _timer -= dt;
+ } else {
+ _timer = 0;
+ }
+ debug("timer tick %g", _timer);
+ }
+ if (_timerFlags & 4) {
+ if (_timer <= 0) {
+ debug("timer trigger");
+ killTimer();
+ executeTest(99);
+ }
+ }
+ }
+}
+
void PhoenixVREngine::tick(float dt) {
+ tickTimer(dt);
+
if (_vr.isVR() && _mousePos != _screenCenter) {
auto da = _mousePos - _screenCenter;
_system->warpMouse(_screenCenter.x, _screenCenter.y);
@@ -392,6 +434,7 @@ Common::Error PhoenixVREngine::run() {
break;
}
}
+ float dt = float(frameDuration) / 1000.0f;
if (_movie) {
if (!_movie->endOfVideo()) {
if (_movie->needsUpdate()) {
@@ -410,7 +453,7 @@ Common::Error PhoenixVREngine::run() {
} else
_movie.reset();
} else
- tick(float(frameDuration) / 1000.0f);
+ tick(dt);
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 6d63a711bb5..caf111554d9 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -135,6 +135,9 @@ public:
void resetLockKey();
void lockKey(Common::KeyCode code, const Common::String &warp);
+ void startTimer(float seconds);
+ void pauseTimer(bool pause, bool deactivate);
+ void killTimer();
private:
static Common::String removeDrive(const Common::String &path);
@@ -145,6 +148,7 @@ private:
return RectF::transform(_angleX.angle(), _angleY.angle(), _fov);
}
void tick(float dt);
+ void tickTimer(float dt);
private:
Common::Point _mousePos;
@@ -182,6 +186,11 @@ private:
AngleX _angleX;
AngleY _angleY;
Audio::Mixer *_mixer;
+
+ static constexpr byte kPaused = 2;
+ static constexpr byte kActive = 4;
+ byte _timerFlags = 0;
+ float _timer = 0;
};
extern PhoenixVREngine *g_engine;
Commit: 61595fdf37c8fa6d5a19cb1f8d41faca0d8f1507
https://github.com/scummvm/scummvm/commit/61595fdf37c8fa6d5a19cb1f8d41faca0d8f1507
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:47+01:00
Commit Message:
VIDEO: move huffman/bitstream code to 4xm_utils
Changed paths:
A video/4xm_utils.cpp
A video/4xm_utils.h
engines/phoenixvr/vr.cpp
video/4xm_decoder.cpp
video/module.mk
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 6196f89e72f..2d1efa0dcc9 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -33,6 +33,7 @@
#include "phoenixvr/dct.h"
#include "phoenixvr/dct_tables.h"
#include "phoenixvr/phoenixvr.h"
+#include "video/4xm_utils.h"
namespace PhoenixVR {
@@ -77,136 +78,9 @@ struct Quantisation {
}
};
-struct HuffChar {
- short next;
- short falseIdx;
- short trueIdx;
-};
-
-class BitStream {
- const byte *_data;
- uint _bytePos;
- byte _bitMask;
-
-public:
- BitStream(const byte *data, uint bytePos) : _data(data), _bytePos(bytePos), _bitMask(0x80) {}
-
- uint getBytePos() const {
- return _bytePos;
- }
-
- bool readBit() {
- bool bit = _data[_bytePos] & _bitMask;
- _bitMask >>= 1;
- if (_bitMask == 0) {
- _bitMask = 128;
- ++_bytePos;
- }
- return bit;
- }
-
- int readUInt(byte n) {
- int value = 0;
- for (int i = 0; i != n; ++i) {
- if (readBit())
- value |= 1 << i;
- }
- return value;
- }
-
- int readInt(byte n) {
- int value = readUInt(n);
- if ((value & (1 << (n - 1))) == 0)
- value += 1 - (1 << n);
- return value;
- }
-
- void alignToByte() {
- if (_bitMask != 0x80) {
- _bitMask = 128;
- ++_bytePos;
- }
- }
-};
-
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
- HuffChar table[514] = {};
- uint offset = 0;
- uint8 codebyte = huff[offset++];
- do {
- uint8 next = huff[offset++];
- if (codebyte <= next) {
- for (auto idx = codebyte; idx <= next; ++idx) {
- table[idx].next = huff[offset++];
- }
- }
- codebyte = huff[offset++];
- } while (codebyte != 0);
- table[256].next = 1;
- table[513].next = 0x7FFF;
-
- short startEntry;
- short codeIdx = 257, nIdx = 257;
- while (true) {
- short idx = 0, dstIdx = 0;
- short trueIdx = 513, falseIdx = 513;
- short nextLo = 513, nextHi = 513;
- while (idx < nIdx) {
- auto next = table[dstIdx].next;
- if (next != 0) {
- if (next >= table[nextLo].next) {
- if (next < table[nextHi].next) {
- trueIdx = idx;
- nextHi = dstIdx;
- }
- } else {
- trueIdx = falseIdx;
- nextHi = nextLo;
- falseIdx = idx;
- nextLo = dstIdx;
- }
- }
- ++idx;
- ++dstIdx;
- }
- if (trueIdx == 513) {
- startEntry = nIdx - 1;
- break;
- }
- table[codeIdx].next = table[falseIdx].next + table[trueIdx].next;
- table[falseIdx].next = table[trueIdx].next = 0;
- table[codeIdx].falseIdx = falseIdx;
- table[codeIdx].trueIdx = trueIdx;
- ++codeIdx;
- ++nIdx;
- }
- Common::Array<byte> decoded;
- decoded.reserve(huffSize);
- {
- BitStream bs(huff, offset);
- while (true) {
- short value = startEntry;
- while (value > 256) {
- auto bit = bs.readBit();
- if (bit)
- value = table[value].trueIdx;
- else
- value = table[value].falseIdx;
- }
- if (value == 256)
- break;
- decoded.push_back(static_cast<byte>(value));
- }
- bs.alignToByte();
- offset = bs.getBytePos();
- }
- debug("decoded %u bytes at %08x", decoded.size(), offset);
- return decoded;
-}
-
void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
Quantisation quant(quality);
- auto decoded = unpackHuffman(huff, huffSize);
+ auto decoded = Video::FourXM::unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
@@ -214,7 +88,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const uint planeSize = planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
- BitStream acBs(acPtr, 0), dcBs(dcPtr, 0);
+ Video::FourXM::BitStream acBs(acPtr, 0), dcBs(dcPtr, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
while (decodedOffset < decoded.size()) {
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 7786df92d08..bb086657018 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -29,6 +29,7 @@
#include "common/stream.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
+#include "video/4xm_utils.h"
namespace Video {
@@ -137,11 +138,20 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *stream) {
stream->skip(4);
auto bitstreamSize = stream->readUint32LE();
+ auto bitstreamPos = stream->pos();
stream->skip(bitstreamSize);
auto prefixSize = stream->readUint32LE();
auto tokenCount = stream->readUint32LE();
debug("i-frame, bitstream: %u, prefix stream: %u, tokens: %u", bitstreamSize, prefixSize, tokenCount);
- stream->skip(prefixSize * 4);
+
+ Common::Array<byte> prefixStream(prefixSize * 4);
+ stream->read(prefixStream.data(), prefixStream.size());
+ assert(stream->pos() == stream->size());
+
+ Common::hexdump(prefixStream.data(), prefixStream.size());
+ auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size());
+ debug("decoded %u bytes", prefixData.size());
+ Common::hexdump(prefixData.data(), prefixData.size());
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
new file mode 100644
index 00000000000..17840b6258e
--- /dev/null
+++ b/video/4xm_utils.cpp
@@ -0,0 +1,110 @@
+/* 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 "video/4xm_utils.h"
+#include "common/debug.h"
+
+namespace Video {
+namespace FourXM {
+
+struct HuffChar {
+ short next;
+ short falseIdx;
+ short trueIdx;
+};
+
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
+ HuffChar table[514] = {};
+ uint offset = 0;
+ uint8 codebyte = huff[offset++];
+ do {
+ uint8 next = huff[offset++];
+ if (codebyte <= next) {
+ for (auto idx = codebyte; idx <= next; ++idx) {
+ table[idx].next = huff[offset++];
+ }
+ }
+ codebyte = huff[offset++];
+ } while (codebyte != 0);
+ table[256].next = 1;
+ table[513].next = 0x7FFF;
+
+ short startEntry;
+ short codeIdx = 257, nIdx = 257;
+ while (true) {
+ short idx = 0, dstIdx = 0;
+ short trueIdx = 513, falseIdx = 513;
+ short nextLo = 513, nextHi = 513;
+ while (idx < nIdx) {
+ auto next = table[dstIdx].next;
+ if (next != 0) {
+ if (next >= table[nextLo].next) {
+ if (next < table[nextHi].next) {
+ trueIdx = idx;
+ nextHi = dstIdx;
+ }
+ } else {
+ trueIdx = falseIdx;
+ nextHi = nextLo;
+ falseIdx = idx;
+ nextLo = dstIdx;
+ }
+ }
+ ++idx;
+ ++dstIdx;
+ }
+ if (trueIdx == 513) {
+ startEntry = nIdx - 1;
+ break;
+ }
+ table[codeIdx].next = table[falseIdx].next + table[trueIdx].next;
+ table[falseIdx].next = table[trueIdx].next = 0;
+ table[codeIdx].falseIdx = falseIdx;
+ table[codeIdx].trueIdx = trueIdx;
+ ++codeIdx;
+ ++nIdx;
+ }
+ Common::Array<byte> decoded;
+ decoded.reserve(huffSize);
+ {
+ BitStream bs(huff, offset);
+ while (true) {
+ short value = startEntry;
+ while (value > 256) {
+ auto bit = bs.readBit();
+ if (bit)
+ value = table[value].trueIdx;
+ else
+ value = table[value].falseIdx;
+ }
+ if (value == 256)
+ break;
+ decoded.push_back(static_cast<byte>(value));
+ }
+ bs.alignToByte();
+ offset = bs.getBytePos();
+ }
+ debug("decoded %u bytes at %08x", decoded.size(), offset);
+ return decoded;
+}
+
+} // namespace FourXM
+} // namespace Video
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
new file mode 100644
index 00000000000..54e9d4f53f9
--- /dev/null
+++ b/video/4xm_utils.h
@@ -0,0 +1,75 @@
+/* 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/array.h"
+
+namespace Video {
+namespace FourXM {
+
+class BitStream {
+ const byte *_data;
+ uint _bytePos;
+ byte _bitMask;
+
+public:
+ BitStream(const byte *data, uint bytePos) : _data(data), _bytePos(bytePos), _bitMask(0x80) {}
+
+ uint getBytePos() const {
+ return _bytePos;
+ }
+
+ bool readBit() {
+ bool bit = _data[_bytePos] & _bitMask;
+ _bitMask >>= 1;
+ if (_bitMask == 0) {
+ _bitMask = 128;
+ ++_bytePos;
+ }
+ return bit;
+ }
+
+ int readUInt(byte n) {
+ int value = 0;
+ for (int i = 0; i != n; ++i) {
+ if (readBit())
+ value |= 1 << i;
+ }
+ return value;
+ }
+
+ int readInt(byte n) {
+ int value = readUInt(n);
+ if ((value & (1 << (n - 1))) == 0)
+ value += 1 - (1 << n);
+ return value;
+ }
+
+ void alignToByte() {
+ if (_bitMask != 0x80) {
+ _bitMask = 128;
+ ++_bytePos;
+ }
+ }
+};
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize);
+
+} // namespace FourXM
+} // namespace Video
diff --git a/video/module.mk b/video/module.mk
index 3a8ae32cc3e..7e17e2ff21d 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -3,6 +3,7 @@ MODULE := video
MODULE_OBJS := \
3do_decoder.o \
4xm_decoder.o \
+ 4xm_utils.o \
avi_decoder.o \
coktel_decoder.o \
dxa_decoder.o \
Commit: 3b781fbd25d025a074a8295a21e2654dd2fa7364
https://github.com/scummvm/scummvm/commit/3b781fbd25d025a074a8295a21e2654dd2fa7364
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:47+01:00
Commit Message:
PHOENIXVR: implement contains with wrapping (still somewhat wrong)
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/region_set.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index aff5cf52ca6..efa4531c9c0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -128,7 +128,7 @@ void PhoenixVREngine::setCursor(const Common::String &path, const Common::String
}
cursor.free();
cursor.surface = loadSurface(path);
- cursor.rect = rect;
+ cursor.region = reg;
}
void PhoenixVREngine::hideCursor(const Common::String &warp, int idx) {
@@ -223,7 +223,7 @@ void PhoenixVREngine::Cursor::free() {
delete surface;
surface = nullptr;
}
- rect.setEmpty();
+ region.setEmpty();
}
void PhoenixVREngine::executeTest(int idx) {
@@ -341,7 +341,7 @@ void PhoenixVREngine::tick(float dt) {
_cursors.resize(_regSet->size());
for (uint i = 0; i != _regSet->size(); ++i) {
- _cursors[i].rect = _regSet->getRegion(i).toRect();
+ _cursors[i].region = _regSet->getRegion(i);
}
Script::ExecutionContext ctx;
@@ -354,7 +354,7 @@ void PhoenixVREngine::tick(float dt) {
Graphics::Surface *cursor = nullptr;
for (auto &c : _cursors) {
- if (_vr.isVR() ? c.rect.contains(currentVRPos()) : c.rect.contains(_mousePos.x, _mousePos.y)) {
+ if (_vr.isVR() ? c.region.contains(currentVRPos()) : c.region.contains(_mousePos.x, _mousePos.y)) {
cursor = c.surface;
if (!cursor)
cursor = _defaultCursor[1].surface;
@@ -422,8 +422,8 @@ Common::Error PhoenixVREngine::run() {
debug("click %s", _mousePos.toString().c_str());
for (uint i = 0, n = _cursors.size(); i != n; ++i) {
- auto &rect = _cursors[i].rect;
- if (_vr.isVR() ? rect.contains(vrPos) : rect.contains(event.mouse.x, event.mouse.y)) {
+ auto &cur = _cursors[i];
+ if (_vr.isVR() ? cur.region.contains(vrPos) : cur.region.contains(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
break;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index caf111554d9..74b7c988faa 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -129,8 +129,8 @@ public:
uint numCursors() const {
return _cursors.size();
}
- const RectF *getCursorRect(uint idx) const {
- return idx < _cursors.size() ? &_cursors[idx].rect : nullptr;
+ const Region *getCursorRegion(uint idx) const {
+ return idx < _cursors.size() ? &_cursors[idx].region : nullptr;
}
void resetLockKey();
@@ -175,7 +175,7 @@ private:
Common::ScopedPtr<Video::VideoDecoder> _movie;
struct Cursor {
- RectF rect;
+ Region region;
Graphics::Surface *surface = nullptr;
void free();
};
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index 9ef0c389a70..e94c3e3cddb 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -50,4 +50,12 @@ RectF Region::toRect() const {
return rect;
}
+bool Region::contains(float angleX, float angleY) const {
+ bool containsX = (a < b) ? angleX >= a && angleX < b : angleX < a || angleX >= b;
+ if (!containsX)
+ return false;
+ bool containsY = (c < d) ? angleY >= c && angleY < d : angleY < c || angleY >= d;
+ return containsY;
+}
+
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index b7db03d8f44..557e968ae4e 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -32,7 +32,14 @@ class String;
namespace PhoenixVR {
struct Region {
float a, b, c, d;
+ void setEmpty() {
+ a = b = c = d = 0;
+ }
RectF toRect() const;
+ bool contains(float angleX, float angleY) const;
+ bool contains(const PointF &p) const {
+ return contains(p.x, p.y);
+ }
};
class RegionSet {
Commit: 723ad4353772cbfccfe374860ff815df7459aaac
https://github.com/scummvm/scummvm/commit/723ad4353772cbfccfe374860ff815df7459aaac
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:48+01:00
Commit Message:
PHOENIXVR: remove wrap to the new location by space key
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index efa4531c9c0..4271b8f3548 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -407,8 +407,7 @@ Common::Error PhoenixVREngine::run() {
if (_movie) {
_movie->stop();
_movie.reset();
- } else
- goToWarp("N1M01L03W02E0.vr");
+ }
}
} break;
case Common::EVENT_MOUSEMOVE:
Commit: 9b024d5afbff5f561a733a2f064ba6a423be86bc
https://github.com/scummvm/scummvm/commit/9b024d5afbff5f561a733a2f064ba6a423be86bc
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:48+01:00
Commit Message:
PHOENIXVR: add rectf/pointf toString
Changed paths:
engines/phoenixvr/rectf.h
engines/phoenixvr/region_set.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index 447ccdc3336..3ce8e22e6fb 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -27,6 +27,9 @@
namespace PhoenixVR {
BEGIN_POINT_TYPE(float, PointF)
+Common::String toString() const {
+ return Common::String::format("%g, %g", x, y);
+}
END_POINT_TYPE(float, PointF)
BEGIN_RECT_TYPE(float, RectF, PointF);
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index e94c3e3cddb..3e05ea99609 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -41,6 +41,10 @@ RegionSet::RegionSet(const Common::String &fname) {
}
}
+Common::String Region::toString() const {
+ return Common::String::format("%g-%g, %g-%g", a, b, c, d);
+}
+
RectF Region::toRect() const {
RectF rect;
rect.left = MIN(a, b);
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index 557e968ae4e..28d0fa87ad1 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -36,6 +36,7 @@ struct Region {
a = b = c = d = 0;
}
RectF toRect() const;
+ Common::String toString() const;
bool contains(float angleX, float angleY) const;
bool contains(const PointF &p) const {
return contains(p.x, p.y);
Commit: 422b5ee30b1d8163a7c5a3c535d6bd186900fbcc
https://github.com/scummvm/scummvm/commit/422b5ee30b1d8163a7c5a3c535d6bd186900fbcc
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:48+01:00
Commit Message:
PHOENIXVR: add warp console command
Changed paths:
engines/phoenixvr/console.cpp
engines/phoenixvr/console.h
diff --git a/engines/phoenixvr/console.cpp b/engines/phoenixvr/console.cpp
index 8d9c238c2bf..b63f2fbf552 100644
--- a/engines/phoenixvr/console.cpp
+++ b/engines/phoenixvr/console.cpp
@@ -20,18 +20,23 @@
*/
#include "phoenixvr/console.h"
+#include "phoenixvr/phoenixvr.h"
namespace PhoenixVR {
Console::Console() : GUI::Debugger() {
- registerCmd("test", WRAP_METHOD(Console, Cmd_test));
+ registerCmd("warp", WRAP_METHOD(Console, cmdWarp));
}
Console::~Console() {
}
-bool Console::Cmd_test(int argc, const char **argv) {
- debugPrintf("Test\n");
+bool Console::cmdWarp(int argc, const char **argv) {
+ if (argc < 2) {
+ debugPrintf("warp <location.vr>");
+ return true;
+ }
+ g_engine->goToWarp(argv[1]);
return true;
}
diff --git a/engines/phoenixvr/console.h b/engines/phoenixvr/console.h
index 4be62344b7a..82355d74257 100644
--- a/engines/phoenixvr/console.h
+++ b/engines/phoenixvr/console.h
@@ -29,7 +29,7 @@ namespace PhoenixVR {
class Console : public GUI::Debugger {
private:
- bool Cmd_test(int argc, const char **argv);
+ bool cmdWarp(int argc, const char **argv);
public:
Console();
Commit: 3274daf1e4051ad54c265f790b77beb992e35db9
https://github.com/scummvm/scummvm/commit/3274daf1e4051ad54c265f790b77beb992e35db9
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:49+01:00
Commit Message:
PHOENIXVR: always sort region coordinates (original engine do this for both 2d/3d)
Changed paths:
engines/phoenixvr/region_set.cpp
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index 3e05ea99609..cf56e585340 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -36,13 +36,13 @@ RegionSet::RegionSet(const Common::String &fname) {
auto b = file.readFloatLE();
auto c = file.readFloatLE();
auto d = file.readFloatLE();
- debug("region %g %g %g %g", a, b, c, d);
- _regions.push_back(Region{a, b, c, d});
+ _regions.push_back(Region{MIN(a, b), MAX(a, b), MIN(c, d), MAX(c, d)});
+ debug("region %s", _regions.back().toString().c_str());
}
}
Common::String Region::toString() const {
- return Common::String::format("%g-%g, %g-%g", a, b, c, d);
+ return Common::String::format("{ x: %g,%g, y:%g,%g }", a, b, c, d);
}
RectF Region::toRect() const {
Commit: a220f5b2062cba0899dbb4ca9c98530915eadab6
https://github.com/scummvm/scummvm/commit/a220f5b2062cba0899dbb4ca9c98530915eadab6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:49+01:00
Commit Message:
PHOENIXVR: add REed method of checking region, fix y angle ranges
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/rectf.cpp
engines/phoenixvr/region_set.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 4271b8f3548..f3c23506e37 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -287,7 +287,7 @@ void PhoenixVREngine::tick(float dt) {
static const float kSpeedY = 0.2f;
_angleX.add(float(da.x) * kSpeedX * dt);
_angleY.add(float(da.y) * kSpeedY * dt);
- debug("angle %g %g", _angleX.angle(), _angleY.angle());
+ debug("angle %g %g -> %s", _angleX.angle(), _angleY.angle(), currentVRPos().toString().c_str());
}
for (auto &kv : _sounds) {
auto &sound = kv._value;
@@ -353,8 +353,9 @@ void PhoenixVREngine::tick(float dt) {
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
Graphics::Surface *cursor = nullptr;
- for (auto &c : _cursors) {
- if (_vr.isVR() ? c.region.contains(currentVRPos()) : c.region.contains(_mousePos.x, _mousePos.y)) {
+ for (uint i = 0; i != _cursors.size(); ++i) {
+ auto &c = _cursors[i];
+ if (_vr.isVR() ? c.region.contains3D(currentVRPos()) : c.region.contains2D(_mousePos.x, _mousePos.y)) {
cursor = c.surface;
if (!cursor)
cursor = _defaultCursor[1].surface;
@@ -422,7 +423,7 @@ Common::Error PhoenixVREngine::run() {
for (uint i = 0, n = _cursors.size(); i != n; ++i) {
auto &cur = _cursors[i];
- if (_vr.isVR() ? cur.region.contains(vrPos) : cur.region.contains(event.mouse.x, event.mouse.y)) {
+ if (_vr.isVR() ? cur.region.contains3D(vrPos) : cur.region.contains2D(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
break;
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index b591c7070c6..0dd7d7cf356 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -27,7 +27,7 @@ namespace PhoenixVR {
PointF RectF::transform(float ax, float ay, float fov) {
AngleX x(ax);
AngleX y(ay); // not a typo, we need 0;pi*2 range
- y.add(M_PI_4);
+ y.add(M_PI_4 * 3);
return {x.angle(), y.angle()};
}
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index cf56e585340..0dc1de0b1cd 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -54,12 +54,32 @@ RectF Region::toRect() const {
return rect;
}
-bool Region::contains(float angleX, float angleY) const {
- bool containsX = (a < b) ? angleX >= a && angleX < b : angleX < a || angleX >= b;
- if (!containsX)
- return false;
- bool containsY = (c < d) ? angleY >= c && angleY < d : angleY < c || angleY >= d;
- return containsY;
+bool Region::contains3D(float angleX, float angleY) const {
+ static const float kPI2 = 2 * M_PI;
+ float x0 = a, x1 = b;
+ float y0 = c, y1 = d;
+ if (x1 - x0 > M_PI) {
+ float t = x0 + kPI2;
+ x0 = x1;
+ x1 = t;
+ }
+ if (y1 - y0 > M_PI) {
+ float t = y0 + kPI2;
+ y0 = y1;
+ y1 = t;
+ }
+ float ax_pi2 = angleX + kPI2;
+ if ((angleX >= x0 && angleX <= x1) || (ax_pi2 >= x0 && ax_pi2 <= x1)) {
+ if (angleY >= y0 && angleY <= y1)
+ return true;
+
+ float ay_pi2 = angleY + kPI2;
+ if (ay_pi2 < y0)
+ return false;
+ if (ay_pi2 <= y1)
+ return true;
+ }
+ return false;
}
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index 28d0fa87ad1..6ca6c133f16 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -37,9 +37,10 @@ struct Region {
}
RectF toRect() const;
Common::String toString() const;
- bool contains(float angleX, float angleY) const;
- bool contains(const PointF &p) const {
- return contains(p.x, p.y);
+ bool contains3D(float angleX, float angleY) const;
+ bool contains3D(PointF p) const { return contains3D(p.x, p.y); }
+ bool contains2D(float x, float y) const {
+ return toRect().contains(x, y);
}
};
Commit: c0f2959ba36fcbc07a2d371cf5b58a7f8ffa54ea
https://github.com/scummvm/scummvm/commit/c0f2959ba36fcbc07a2d371cf5b58a7f8ffa54ea
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:49+01:00
Commit Message:
PHOENIXVR: fix color keying
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index f3c23506e37..8a2d42049b0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -204,8 +204,6 @@ Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
auto *s = pcx.getSurface()->convertTo(_pixelFormat, pcx.hasPalette() ? pcx.getPalette().data() : nullptr);
if (s) {
byte r = 0, g = 0, b = 0;
- if (pcx.hasPalette())
- pcx.getPalette().get(0, r, g, b);
s->applyColorKey(r, g, b);
}
return s;
Commit: a1982e42f1c3e38027b554a3786841a42fe9d961
https://github.com/scummvm/scummvm/commit/a1982e42f1c3e38027b554a3786841a42fe9d961
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:49+01:00
Commit Message:
PHOENIXVR: allow setCursor out of bounds
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 8a2d42049b0..9435e61dd34 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -102,10 +102,10 @@ Script::ConstWarpPtr PhoenixVREngine::getWarp(const Common::String &name) {
return _script->getWarp(name);
}
-Region PhoenixVREngine::getRegion(int idx) const {
+const Region *PhoenixVREngine::getRegion(int idx) const {
if (!_regSet)
error("no region set");
- return _regSet->getRegion(idx);
+ return (idx < static_cast<int>(_regSet->size())) ? &_regSet->getRegion(idx) : nullptr;
}
void PhoenixVREngine::setCursorDefault(int idx, const Common::String &path) {
@@ -118,17 +118,20 @@ void PhoenixVREngine::setCursorDefault(int idx, const Common::String &path) {
}
void PhoenixVREngine::setCursor(const Common::String &path, const Common::String &wname, int idx) {
- auto reg = g_engine->getRegion(idx);
- auto &cursor = _cursors[idx];
- auto rect = reg.toRect();
- debug("cursor region %s:%d: %s, %s", wname.c_str(), idx, rect.toString().c_str(), path.c_str());
+ debug("setCursor %s %s:%d", path.c_str(), wname.c_str(), idx);
if (!_warp || !_warp->vrFile.equalsIgnoreCase(wname)) {
warning("setting cursor for different warp, active: %s, required: %s", _warp ? _warp->vrFile.c_str() : "null", wname.c_str());
return;
}
+ auto *reg = g_engine->getRegion(idx);
+ if (idx >= static_cast<int>(_cursors.size()))
+ _cursors.resize(idx + 1);
+ auto &cursor = _cursors[idx];
+ debug("cursor region %s:%d: %s, %s", wname.c_str(), idx, reg ? reg->toString().c_str() : "no region", path.c_str());
cursor.free();
cursor.surface = loadSurface(path);
- cursor.region = reg;
+ if (reg)
+ cursor.region = *reg;
}
void PhoenixVREngine::hideCursor(const Common::String &warp, int idx) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 74b7c988faa..944f1709d22 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -124,7 +124,7 @@ public:
Script::ConstWarpPtr getWarp(const Common::String &name);
Script::ConstWarpPtr getCurrentWarp() { return _warp; }
- Region getRegion(int idx) const;
+ const Region *getRegion(int idx) const;
uint numCursors() const {
return _cursors.size();
Commit: 8a480c6ec92f4526a701f1f9b8a48bc054de5dc4
https://github.com/scummvm/scummvm/commit/8a480c6ec92f4526a701f1f9b8a48bc054de5dc4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:50+01:00
Commit Message:
PHOENIXVR: parse animation
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 2d1efa0dcc9..8e62cdb29fd 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -40,6 +40,8 @@ namespace PhoenixVR {
#define CHUNK_VR (0x12fa84ab)
#define CHUNK_STATIC_2D (0xa0b1c400)
#define CHUNK_STATIC_3D (0xa0b1c200)
+#define CHUNK_ANIMATION (0xa0b1c201)
+#define CHUNK_ANIMATION_BLOCK (0xa0b1c211)
namespace {
@@ -176,9 +178,10 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto fsize = s.readUint32LE();
debug("file size = %08x", fsize);
while (s.pos() < fsize) {
+ auto chunkPos = s.pos();
auto chunkId = s.readUint32LE();
auto chunkSize = s.readUint32LE();
- debug("chunk %08x %u", chunkId, chunkSize);
+ debug("chunk %08x %u at %08lx", chunkId, chunkSize, chunkPos);
bool pic2d = chunkId == CHUNK_STATIC_2D;
bool pic3d = chunkId == CHUNK_STATIC_3D;
if (pic2d || pic3d) {
@@ -205,8 +208,37 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto dcOffset = READ_LE_UINT32(vrData.data() + huffSize + 8);
auto *dcPtr = vrData.data() + huffSize + 16 + dcOffset;
unpack(*pic, huff, unpHuffSize, acPtr, dcPtr, quality);
+ } else if (chunkId == CHUNK_ANIMATION) {
+ auto name = s.readString(0, 32);
+ s.skip(4);
+ debug("animation %s", name.c_str());
+ while (s.pos() < chunkPos + chunkSize) {
+ auto animChunkPos = s.pos();
+ auto animChunkId = s.readUint32LE();
+ auto animChunkSize = s.readUint32LE();
+ debug("animation chunk %08x %u at %08lx", animChunkId, animChunkSize, animChunkPos);
+ if (animChunkId == CHUNK_ANIMATION_BLOCK) {
+ auto prefixSize = s.readUint32LE();
+ debug("prefixes: %u", prefixSize);
+ Common::Array<byte> prefixData(prefixSize * 4);
+ s.read(prefixData.data(), prefixData.size());
+ Common::hexdump(prefixData.data(), prefixData.size());
+ auto quality = s.readUint32LE();
+ auto size = s.readUint32LE();
+ Common::Array<byte> blockData(size);
+ s.read(blockData.data(), blockData.size());
+ Common::hexdump(blockData.data(), blockData.size());
+ auto huffSize = READ_LE_UINT32(blockData.data());
+ auto unpHuffSize = READ_LE_UINT32(blockData.data() + 4);
+ auto decoded = Video::FourXM::unpackHuffman(blockData.data() + 8, huffSize);
+ assert(decoded.size() == unpHuffSize);
+ debug("quality: %u, size: %u", quality, size);
+ break;
+ }
+ s.seek(animChunkPos + animChunkSize);
+ }
}
- s.skip(chunkSize - 8);
+ s.seek(chunkPos + chunkSize);
}
if (vr._pic) {
Common::DumpFile out;
Commit: dc33b0c0a07526a277ce73efbe30086d83e2af13
https://github.com/scummvm/scummvm/commit/dc33b0c0a07526a277ce73efbe30086d83e2af13
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:50+01:00
Commit Message:
PHOENIXVR: implement animation unpacking
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 124cfa84260..a1e59e38e08 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -70,13 +70,14 @@ struct Play_Movie : public Script::Command {
struct Play_AnimBloc : public Script::Command {
Common::String name;
- Common::String block;
+ Common::String var;
int start;
int stop;
- Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), block(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
+ Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), var(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc %s %s %d-%d", name.c_str(), block.c_str(), start, stop);
+ debug("Play_AnimBloc %s %s %d-%d", name.c_str(), var.c_str(), start, stop);
+ g_engine->playAnimation(name, var);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9435e61dd34..b4ac0dfb91c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -186,6 +186,9 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
warning("playMovie %s failed", movie.c_str());
}
}
+void PhoenixVREngine::playAnimation(const Common::String &name, const Common::String &var) {
+ _vr.playAnimation(name);
+}
void PhoenixVREngine::resetLockKey() {
_keys.clear();
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 944f1709d22..d2fc0ae5463 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -138,6 +138,7 @@ public:
void startTimer(float seconds);
void pauseTimer(bool pause, bool deactivate);
void killTimer();
+ void playAnimation(const Common::String &name, const Common::String &var);
private:
static Common::String removeDrive(const Common::String &path);
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8e62cdb29fd..0f87d126fed 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -50,6 +50,17 @@ T clip(T a) {
return a >= 0 ? a <= 255 ? a : 255 : 0;
}
+uint32 YCbCr2RGB(const Graphics::PixelFormat &format, int16 y, int16 cb, int16 cr) {
+ cr -= 128;
+ cb -= 128;
+
+ int r = clip(y + ((cr * 91881 + 32768) >> 16));
+ int g = clip(y - ((cb * 22553 + cr * 46801 + 32768) >> 16));
+ int b = clip(y + ((cb * 116129 + 32768) >> 16));
+
+ return format.RGBToColor(r, g, b);
+}
+
struct Quantisation {
int quantY[64];
int quantCbCr[64];
@@ -80,19 +91,20 @@ struct Quantisation {
}
};
-void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality) {
+void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality, const Common::Array<uint> *prefix = nullptr) {
Quantisation quant(quality);
auto decoded = Video::FourXM::unpackHuffman(huff, huffSize);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
- const uint planePitch = pic.w;
- const uint planeSize = planePitch * pic.h;
+ const uint planePitch = prefix ? 8 : pic.w;
+ const uint planeSize = prefix ? prefix->size() * 64 : planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
Video::FourXM::BitStream acBs(acPtr, 0), dcBs(dcPtr, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
+ uint blockIdx = 0;
while (decodedOffset < decoded.size()) {
float ac[64] = {};
int8 dc8 = dcBs.readUInt(8);
@@ -117,7 +129,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
float out[64];
dct.calc(ac, out);
- auto *dst = planes.data() + channel * planeSize + y0 * planePitch + x0;
+ auto *dst = prefix ? planes.data() + channel * planeSize + blockIdx * 64 : planes.data() + channel * planeSize + y0 * planePitch + x0;
const auto *src = out;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
@@ -128,6 +140,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
++channel;
if (channel == 3) {
+ ++blockIdx;
channel = 0;
x0 += 8;
if (static_cast<int16>(x0) >= pic.w) {
@@ -137,28 +150,42 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
}
auto *yPtr = planes.data();
- auto *crPtr = yPtr + planeSize;
- auto *cbPtr = crPtr + planeSize;
+ auto *cbPtr = yPtr + planeSize;
+ auto *crPtr = cbPtr + planeSize;
+
+ if (prefix) {
+ for (auto dstOffset : *prefix) {
+ int dstY = dstOffset / pic.w;
+ int dstX = dstOffset % pic.w;
+ for (uint by = 0; by < 8; ++by) {
+ auto *dstPixel = static_cast<uint32 *>(pic.getBasePtr(dstX, dstY++));
+ for (uint bx = 0; bx < 8; ++bx) {
+ int16 y = *yPtr++;
+ int16 cr = *crPtr++;
+ int16 cb = *cbPtr++;
+
+ *dstPixel++ = YCbCr2RGB(pic.format, y, cb, cr);
+ }
+ }
+ }
+ } else {
#if 0
- auto &format = pic.format;
- for(int yy = 0; yy < pic.h; ++yy) {
- auto *rows = static_cast<uint32*>(pic.getBasePtr(0, yy));
- for(int xx = 0; xx < pic.w; ++xx) {
- int16 y = *yPtr++;
- int16 cr = (int16)*crPtr++;
- int16 cb = (int16)*cbPtr++;
-
- int r = y;
- int g = y;
- int b = y;
-
- *rows++ = format.RGBToColor(r, g, b);
+ auto &format = pic.format;
+ for(int yy = 0; yy < pic.h; ++yy) {
+ auto *rows = static_cast<uint32*>(pic.getBasePtr(0, yy));
+ for(int xx = 0; xx < pic.w; ++xx) {
+ int16 y = *yPtr++;
+ int16 cr = *crPtr++;
+ int16 cb = *cbPtr++;
+
+ *rows++ = YCbCr2RGB(format, y, cb, cr);
+ }
}
- }
#else
- YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull,
- yPtr, crPtr, cbPtr, pic.w, pic.h, planePitch, planePitch);
+ YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull,
+ yPtr, cbPtr, crPtr, pic.w, pic.h, planePitch, planePitch);
#endif
+ }
}
} // namespace
@@ -218,22 +245,9 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto animChunkSize = s.readUint32LE();
debug("animation chunk %08x %u at %08lx", animChunkId, animChunkSize, animChunkPos);
if (animChunkId == CHUNK_ANIMATION_BLOCK) {
- auto prefixSize = s.readUint32LE();
- debug("prefixes: %u", prefixSize);
- Common::Array<byte> prefixData(prefixSize * 4);
- s.read(prefixData.data(), prefixData.size());
- Common::hexdump(prefixData.data(), prefixData.size());
- auto quality = s.readUint32LE();
- auto size = s.readUint32LE();
- Common::Array<byte> blockData(size);
+ auto &blockData = vr._animations[name];
+ blockData.resize(animChunkSize);
s.read(blockData.data(), blockData.size());
- Common::hexdump(blockData.data(), blockData.size());
- auto huffSize = READ_LE_UINT32(blockData.data());
- auto unpHuffSize = READ_LE_UINT32(blockData.data() + 4);
- auto decoded = Video::FourXM::unpackHuffman(blockData.data() + 8, huffSize);
- assert(decoded.size() == unpHuffSize);
- debug("quality: %u, size: %u", quality, size);
- break;
}
s.seek(animChunkPos + animChunkSize);
}
@@ -314,6 +328,35 @@ Cube toCube(float x, float y, float z) {
} // namespace
+void VR::playAnimation(const Common::String &name) {
+ auto it = _animations.find(name);
+ if (it == _animations.end()) {
+ warning("no animation %s", name.c_str());
+ return;
+ }
+ const auto *data = it->_value.data();
+ auto prefixSize = READ_LE_UINT32(data);
+ debug("prefixes: %u", prefixSize);
+ Common::Array<uint32> prefixData(prefixSize);
+ uint offset = 4;
+ for (uint i = 0; i != prefixSize; ++i) {
+ prefixData[i] = READ_LE_UINT32(data + offset);
+ offset += 4;
+ }
+ auto quality = READ_LE_UINT32(data + offset);
+ auto size = READ_LE_UINT32(data + offset + 4);
+ debug("quality: %u, size: %u", quality, size);
+ offset += 8;
+ auto huffSize = READ_LE_UINT32(data + offset);
+ auto unpHuffSize = READ_LE_UINT32(data + offset + 4);
+ offset += 8;
+ debug("huffman size: %u, unpacked size: %u", huffSize, unpHuffSize);
+ auto *acPtr = data + offset + huffSize + 4;
+ auto dcOffset = READ_LE_UINT32(data + offset + huffSize);
+ auto *dcPtr = data + offset + huffSize + 8 + dcOffset;
+ unpack(*_pic, data + offset, huffSize, acPtr, dcPtr, quality, &prefixData);
+}
+
void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
if (!_pic) {
screen->clear();
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 2a5b022d94b..772e3ad3605 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -22,6 +22,8 @@
#ifndef PHOENIXVR_VR_H
#define PHOENIXVR_VR_H
+#include "common/hash-str.h"
+#include "common/hashmap.h"
#include "common/stream.h"
#include "graphics/pixelformat.h"
@@ -34,16 +36,18 @@ namespace PhoenixVR {
class VR {
Common::ScopedPtr<Graphics::Surface> _pic;
bool _vr = false;
+ Common::HashMap<Common::String, Common::Array<byte>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _animations;
public:
~VR();
VR() = default;
VR(VR &&) = default;
- VR &operator=(VR &&) = default;
+ VR &operator=(VR &&) noexcept = default;
static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
void render(Graphics::Screen *screen, float ax, float ay, float fov);
bool isVR() const { return _vr; }
+ void playAnimation(const Common::String &name);
};
} // namespace PhoenixVR
Commit: 76136ea4977c2bd26ae62a43c479765af025b8e3
https://github.com/scummvm/scummvm/commit/76136ea4977c2bd26ae62a43c479765af025b8e3
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:50+01:00
Commit Message:
PHOENIXVR: add warnings for non-existent commands
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index a1e59e38e08..a342a42b257 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -55,7 +55,7 @@ struct LoadSave_Enter_Script : public Script::Command {
LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
+ warning("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
}
};
@@ -90,7 +90,7 @@ struct Play_AnimBloc_Number : public Script::Command {
Play_AnimBloc_Number(const Common::Array<Common::String> &args) : name1(args[0]), name2(args[1]),
block(args[2]), start(atoi(args[3].c_str())), stop(atoi(args[4].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc_Number %s %s %s %d-%d", name1.c_str(), name2.c_str(), block.c_str(), start, stop);
+ warning("Play_AnimBloc_Number %s %s %s %d-%d", name1.c_str(), name2.c_str(), block.c_str(), start, stop);
}
};
@@ -100,7 +100,7 @@ struct Until : public Script::Command {
Until(const Common::Array<Common::String> &args) : block(args[0]), frame(atoi(args[1].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("until %s %d", block.c_str(), frame);
+ warning("until %s %d", block.c_str(), frame);
}
};
@@ -109,7 +109,7 @@ struct While : public Script::Command {
While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("while %g", seconds);
+ warning("while %g", seconds);
}
};
@@ -137,6 +137,7 @@ struct KillTimer : public Script::Command {
KillTimer(const Common::Array<Common::String> &args) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("killtimer");
+ g_engine->killTimer();
}
};
@@ -203,7 +204,7 @@ struct LoadSave_Init_Slots : public Script::Command {
LoadSave_Init_Slots(const Common::Array<Common::String> &args) : slots(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Init_Slots %d", slots);
+ warning("LoadSave_Init_Slots %d", slots);
}
};
@@ -218,7 +219,7 @@ struct LoadSave_Draw_Slot : public Script::Command {
arg1(atoi(args[2].c_str())),
arg2(atoi(args[3].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Draw_Slot %d %d %d %d", slot, arg0, arg1, arg2);
+ warning("LoadSave_Draw_Slot %d %d %d %d", slot, arg0, arg1, arg2);
}
};
@@ -229,7 +230,7 @@ struct LoadSave_Test_Slot : public Script::Command {
LoadSave_Test_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())), show(args[1]), hide(args[2]) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
+ warning("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
g_engine->setVariable(show, 0);
g_engine->setVariable(hide, 1);
}
@@ -238,7 +239,7 @@ struct LoadSave_Test_Slot : public Script::Command {
struct LoadSave_Capture_Context : public Script::Command {
LoadSave_Capture_Context(const Common::Array<Common::String> &args) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Capture_Context");
+ warning("LoadSave_Capture_Context");
}
};
@@ -248,7 +249,7 @@ struct LoadSave_Context_Restored : public Script::Command {
LoadSave_Context_Restored(const Common::Array<Common::String> &args) : progress(args[0]), done(args[1]) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Context_Restored %s %s", progress.c_str(), done.c_str());
+ warning("LoadSave_Context_Restored %s %s", progress.c_str(), done.c_str());
}
};
@@ -257,7 +258,7 @@ struct LoadSave_Load : public Script::Command {
LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Load %d", slot);
+ warning("LoadSave_Load %d", slot);
}
};
@@ -266,7 +267,7 @@ struct LoadSave_Save : public Script::Command {
LoadSave_Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Save %d", slot);
+ warning("LoadSave_Save %d", slot);
}
};
@@ -275,7 +276,7 @@ struct LoadSave_Set_Context_Label : public Script::Command {
LoadSave_Set_Context_Label(const Common::Array<Common::String> &args) : label(args[0]) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("LoadSave_Set_Context_Label %s", label.c_str());
+ warning("LoadSave_Set_Context_Label %s", label.c_str());
}
};
@@ -284,7 +285,7 @@ struct RolloverMalette : public Script::Command {
RolloverMalette(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("RolloverMalette %d", arg);
+ warning("RolloverMalette %d", arg);
}
};
@@ -293,7 +294,7 @@ struct RolloverSecretaire : public Script::Command {
RolloverSecretaire(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("RolloverSecretaire %d", arg);
+ warning("RolloverSecretaire %d", arg);
}
};
@@ -486,12 +487,12 @@ struct LockKey : public Script::Command {
}
};
-struct Zoom : public Script::Command {
- int zoom;
- Zoom(int z) : zoom(z) {}
+struct SetZoom : public Script::Command {
+ int fov;
+ SetZoom(int f) : fov(f) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("zoom %d", zoom);
+ g_engine->setZoom(fov);
}
};
@@ -500,7 +501,7 @@ struct AngleXMax : public Script::Command {
AngleXMax(float x) : xMax(x) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("angle x max %g", xMax);
+ warning("angle x max %g", xMax);
}
};
@@ -509,7 +510,7 @@ struct AngleYMax : public Script::Command {
AngleYMax(float y0, float y1) : yMax0(y0), yMax1(y1) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("angle y max %g %g", yMax0, yMax1);
+ warning("angle y max %g %g", yMax0, yMax1);
}
};
@@ -518,7 +519,7 @@ struct SetAngle : public Script::Command {
SetAngle(float a0_, float a1_) : a0(a0_), a1(a1_) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("set angle %g %g", a0, a1);
+ warning("set angle %g %g", a0, a1);
}
};
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index d2fc0ae5463..8c38bc3719b 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -139,6 +139,9 @@ public:
void pauseTimer(bool pause, bool deactivate);
void killTimer();
void playAnimation(const Common::String &name, const Common::String &var);
+ void setZoom(int fov) {
+ _fov = M_PI * fov / 180;
+ }
private:
static Common::String removeDrive(const Common::String &path);
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 0570a58e95b..cf9ab400c9d 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -164,7 +164,7 @@ public:
auto arg2 = nextInt();
return CommandPtr(new Fade(arg0, arg1, arg2));
} else if (maybe("setzoom=")) {
- return CommandPtr(new Zoom(nextInt()));
+ return CommandPtr(new SetZoom(nextInt()));
} else if (maybe("setangle=")) {
auto i0 = nextInt();
if (i0 > 4095)
Commit: 4d6265c08d8c058ab9d1f245a826c253f602f684
https://github.com/scummvm/scummvm/commit/4d6265c08d8c058ab9d1f245a826c253f602f684
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:51+01:00
Commit Message:
PHOENIXVR: add angle to radians coefficient
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index cf9ab400c9d..0f2a2610e20 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -140,7 +140,8 @@ public:
}
static float toAngle(int a) {
- return (float(a) / 8192.0f) * float(M_PI * 2);
+ static const float angleToFloat = M_PI / 4096.0f;
+ return angleToFloat * a;
}
Script::CommandPtr parseCommand() {
Commit: da84ee45a98e4a632f13cdb0f32b80f441bf1f70
https://github.com/scummvm/scummvm/commit/da84ee45a98e4a632f13cdb0f32b80f441bf1f70
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:51+01:00
Commit Message:
PHOENIXVR: implement Play_AnimBloc_Number
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index a342a42b257..c850830e5ce 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -70,27 +70,30 @@ struct Play_Movie : public Script::Command {
struct Play_AnimBloc : public Script::Command {
Common::String name;
- Common::String var;
+ Common::String block;
int start;
int stop;
- Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), var(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
+ Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), block(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc %s %s %d-%d", name.c_str(), var.c_str(), start, stop);
- g_engine->playAnimation(name, var);
+ debug("Play_AnimBloc %s %s %d-%d", name.c_str(), block.c_str(), start, stop);
+ g_engine->playAnimation(name, block);
}
};
struct Play_AnimBloc_Number : public Script::Command {
- Common::String name1, name2;
+ Common::String prefix, var;
Common::String block;
int start;
int stop;
- Play_AnimBloc_Number(const Common::Array<Common::String> &args) : name1(args[0]), name2(args[1]),
+ Play_AnimBloc_Number(const Common::Array<Common::String> &args) : prefix(args[0]), var(args[1]),
block(args[2]), start(atoi(args[3].c_str())), stop(atoi(args[4].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("Play_AnimBloc_Number %s %s %s %d-%d", name1.c_str(), name2.c_str(), block.c_str(), start, stop);
+ debug("Play_AnimBloc_Number %s %s %s %d-%d", prefix.c_str(), var.c_str(), block.c_str(), start, stop);
+ int value = g_engine->getVariable(var);
+ auto name = Common::String::format("%s%04d", prefix.c_str(), value);
+ g_engine->playAnimation(name, block);
}
};
Commit: 13b798d1ed9c196ca92a7f235fb85587f445ebfd
https://github.com/scummvm/scummvm/commit/13b798d1ed9c196ca92a7f235fb85587f445ebfd
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:51+01:00
Commit Message:
PHOENIXVR: implement test save slot function
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index c850830e5ce..8c7b8d0a5dc 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -233,9 +233,10 @@ struct LoadSave_Test_Slot : public Script::Command {
LoadSave_Test_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())), show(args[1]), hide(args[2]) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Test_Slot %d %s %s", slot, show.c_str(), hide.c_str());
- g_engine->setVariable(show, 0);
- g_engine->setVariable(hide, 1);
+ bool exists = g_engine->testSaveSlot(slot);
+ debug("LoadSave_Test_Slot %d %s %s -> %d", slot, show.c_str(), hide.c_str(), exists);
+ g_engine->setVariable(show, exists);
+ g_engine->setVariable(hide, !exists);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index b4ac0dfb91c..84dba978bbc 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -26,6 +26,7 @@
#include "common/config-manager.h"
#include "common/events.h"
#include "common/file.h"
+#include "common/savefile.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "engines/util.h"
@@ -478,6 +479,10 @@ void PhoenixVREngine::paint(Graphics::Surface &src, Common::Point dst) {
}
}
+bool PhoenixVREngine::testSaveSlot(int idx) const {
+ return _saveFileMan->exists(getSaveStateName(idx));
+}
+
Common::Error PhoenixVREngine::syncGame(Common::Serializer &s) {
// The Serializer has methods isLoading() and isSaving()
// if you need to specific steps; for example setting
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 8c38bc3719b..9566dba87cb 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -143,6 +143,8 @@ public:
_fov = M_PI * fov / 180;
}
+ bool testSaveSlot(int idx) const;
+
private:
static Common::String removeDrive(const Common::String &path);
static Common::String resolvePath(const Common::String &path);
Commit: 4a8292b488dc0c604812832306e8cc09e513b9bd
https://github.com/scummvm/scummvm/commit/4a8292b488dc0c604812832306e8cc09e513b9bd
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:52+01:00
Commit Message:
PHOENIXVR: initial save state implementation
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 8c7b8d0a5dc..532803997a4 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -213,16 +213,17 @@ struct LoadSave_Init_Slots : public Script::Command {
struct LoadSave_Draw_Slot : public Script::Command {
int slot;
- int arg0;
- int arg1;
- int arg2;
+ int face;
+ int x;
+ int y;
LoadSave_Draw_Slot(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())),
- arg0(atoi(args[1].c_str())),
- arg1(atoi(args[2].c_str())),
- arg2(atoi(args[3].c_str())) {}
+ face(atoi(args[1].c_str())),
+ x(atoi(args[2].c_str())),
+ y(atoi(args[3].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Draw_Slot %d %d %d %d", slot, arg0, arg1, arg2);
+ debug("LoadSave_Draw_Slot %d %d %d %d", slot, face, x, y);
+ g_engine->drawSlot(slot, face, x, y);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 84dba978bbc..652d60e3162 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -31,6 +31,7 @@
#include "common/system.h"
#include "engines/util.h"
#include "graphics/framelimiter.h"
+#include "graphics/surface.h"
#include "image/pcx.h"
#include "math/utils.h"
#include "phoenixvr/console.h"
@@ -483,13 +484,66 @@ bool PhoenixVREngine::testSaveSlot(int idx) const {
return _saveFileMan->exists(getSaveStateName(idx));
}
-Common::Error PhoenixVREngine::syncGame(Common::Serializer &s) {
- // The Serializer has methods isLoading() and isSaving()
- // if you need to specific steps; for example setting
- // an array size after reading it's length, whereas
- // for saving it would write the existing array's length
- int dummy = 0;
- s.syncAsUint32LE(dummy);
+void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
+ Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
+ if (!slot)
+ return;
+ auto state = loadGameStateObject(slot.get());
+
+ Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
+ Graphics::Surface thumbnail;
+ thumbnail.init(state.thumbWidth, state.thumbHeight, state.thumbnail.size() / state.thumbHeight, state.thumbnail.data(), rgb565);
+ auto &dst = _vr.getSurface();
+ Graphics::Surface *src = thumbnail.convertTo(dst.format);
+ src->flipVertical(src->getRect());
+ static const uint8 mapFaceId[] = {0, 3, 2, 1, 5, 4};
+ y += mapFaceId[face] * 4 * 256;
+ if (x > 256) {
+ x -= 256;
+ y += 256;
+ }
+ // FIXME: clip vertically here.
+ dst.copyRectToSurface(*src, x, y, src->getRect());
+ src->free();
+ delete src;
+}
+
+Common::Error PhoenixVREngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
+ return Common::kNoError;
+}
+
+PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::SeekableReadStream *stream) {
+ GameState state;
+
+ auto readString = [&]() {
+ auto size = stream->readUint32LE();
+ return stream->readString(0, size);
+ };
+
+ state.script = readString();
+ debug("save.script: %s", state.script.c_str());
+ state.game = readString();
+ debug("save.game: %s", state.game.c_str());
+ state.info = readString();
+ debug("save.datetime: %s", state.info.c_str());
+ uint dibHeaderSize = stream->readUint32LE();
+ stream->seek(-4, SEEK_CUR);
+ state.dibHeader.resize(dibHeaderSize + 3 * 4); // rmask/gmask/bmask
+ stream->read(state.dibHeader.data(), state.dibHeader.size());
+ state.thumbWidth = READ_LE_UINT32(state.dibHeader.data() + 0x04);
+ state.thumbHeight = READ_LE_UINT32(state.dibHeader.data() + 0x08);
+ auto imageSize = READ_LE_UINT32(state.dibHeader.data() + 0x14);
+ debug("save.image %dx%d, %u", state.thumbWidth, state.thumbHeight, imageSize);
+ state.thumbnail.resize(imageSize);
+ stream->read(state.thumbnail.data(), state.thumbnail.size());
+ auto gameStateSize = stream->readUint32LE();
+ debug("save.state %u bytes", gameStateSize);
+ state.state.resize(gameStateSize);
+ stream->read(state.state.data(), state.state.size());
+ return state;
+}
+
+Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *stream) {
return Common::kNoError;
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 9566dba87cb..5c84b711da0 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -89,20 +89,8 @@ public:
return true;
}
- /**
- * Uses a serializer to allow implementing savegame
- * loading and saving using a single method
- */
- Common::Error syncGame(Common::Serializer &s);
-
- Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
- Common::Serializer s(nullptr, stream);
- return syncGame(s);
- }
- Common::Error loadGameStream(Common::SeekableReadStream *stream) override {
- Common::Serializer s(stream, nullptr);
- return syncGame(s);
- }
+ Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
+ Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
// Script API
void setNextScript(const Common::String &path);
@@ -144,6 +132,7 @@ public:
}
bool testSaveSlot(int idx) const;
+ void drawSlot(int idx, int face, int x, int y);
private:
static Common::String removeDrive(const Common::String &path);
@@ -197,6 +186,19 @@ private:
static constexpr byte kActive = 4;
byte _timerFlags = 0;
float _timer = 0;
+
+ struct GameState {
+ Common::String script;
+ Common::String game;
+ Common::String info;
+ Common::Array<byte> dibHeader;
+ int16 thumbWidth;
+ int16 thumbHeight;
+ Common::Array<byte> thumbnail;
+ Common::Array<byte> state;
+ };
+
+ GameState loadGameStateObject(Common::SeekableReadStream *stream);
};
extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 772e3ad3605..83d3246964c 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -48,6 +48,7 @@ public:
void render(Graphics::Screen *screen, float ax, float ay, float fov);
bool isVR() const { return _vr; }
void playAnimation(const Common::String &name);
+ Graphics::Surface &getSurface() { return *_pic; }
};
} // namespace PhoenixVR
Commit: 79480dbbfa8f1ecc43b7cb6d89a28fb2476b455c
https://github.com/scummvm/scummvm/commit/79480dbbfa8f1ecc43b7cb6d89a28fb2476b455c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:52+01:00
Commit Message:
PHOENIXVR: fix y direction
Changed paths:
engines/phoenixvr/rectf.cpp
diff --git a/engines/phoenixvr/rectf.cpp b/engines/phoenixvr/rectf.cpp
index 0dd7d7cf356..6febe031b77 100644
--- a/engines/phoenixvr/rectf.cpp
+++ b/engines/phoenixvr/rectf.cpp
@@ -26,8 +26,7 @@ namespace PhoenixVR {
PointF RectF::transform(float ax, float ay, float fov) {
AngleX x(ax);
- AngleX y(ay); // not a typo, we need 0;pi*2 range
- y.add(M_PI_4 * 3);
+ AngleX y(-ay); // not a typo, we need 0;pi*2 range
return {x.angle(), y.angle()};
}
Commit: 3459ddb9cbe1c72eb5cb3b0e4bd12c045c8e725d
https://github.com/scummvm/scummvm/commit/3459ddb9cbe1c72eb5cb3b0e4bd12c045c8e725d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:52+01:00
Commit Message:
PHOENIXVR: add bitstream size and unpack dc prefix stream
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 0f87d126fed..cbfc1215fe8 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -91,9 +91,9 @@ struct Quantisation {
}
};
-void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, const byte *dcPtr, int quality, const Common::Array<uint> *prefix = nullptr) {
+void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, uint acSize, const byte *dcPtr, uint dcSize, int quality, const Common::Array<uint> *prefix = nullptr) {
Quantisation quant(quality);
- auto decoded = Video::FourXM::unpackHuffman(huff, huffSize);
+ auto decoded = Video::FourXM::unpackHuffman(huff, huffSize, false);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
@@ -101,7 +101,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const uint planeSize = prefix ? prefix->size() * 64 : planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
- Video::FourXM::BitStream acBs(acPtr, 0), dcBs(dcPtr, 0);
+ Video::FourXM::BitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
uint blockIdx = 0;
@@ -231,10 +231,12 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
} else
pic->create(640, 480, format);
auto *huff = vrData.data() + 8;
- auto *acPtr = vrData.data() + huffSize + 12;
- auto dcOffset = READ_LE_UINT32(vrData.data() + huffSize + 8);
- auto *dcPtr = vrData.data() + huffSize + 16 + dcOffset;
- unpack(*pic, huff, unpHuffSize, acPtr, dcPtr, quality);
+ uint acOffset = huffSize + 12;
+ auto *acPtr = vrData.data() + acOffset;
+ auto dcOffset = READ_LE_UINT32(huff + huffSize);
+ auto *dcPtr = acPtr + 4 + dcOffset;
+ auto *dcEnd = vrData.data() + vrData.size();
+ unpack(*pic, huff, unpHuffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
} else if (chunkId == CHUNK_ANIMATION) {
auto name = s.readString(0, 32);
s.skip(4);
@@ -354,7 +356,8 @@ void VR::playAnimation(const Common::String &name) {
auto *acPtr = data + offset + huffSize + 4;
auto dcOffset = READ_LE_UINT32(data + offset + huffSize);
auto *dcPtr = data + offset + huffSize + 8 + dcOffset;
- unpack(*_pic, data + offset, huffSize, acPtr, dcPtr, quality, &prefixData);
+ auto *dcEnd = data + it->_value.size();
+ unpack(*_pic, data + offset, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality, &prefixData);
}
void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index bb086657018..f33e7c7a105 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -138,8 +138,10 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *stream) {
stream->skip(4);
auto bitstreamSize = stream->readUint32LE();
- auto bitstreamPos = stream->pos();
- stream->skip(bitstreamSize);
+
+ Common::Array<byte> bitstreamData(bitstreamSize);
+ stream->read(bitstreamData.data(), bitstreamData.size());
+
auto prefixSize = stream->readUint32LE();
auto tokenCount = stream->readUint32LE();
debug("i-frame, bitstream: %u, prefix stream: %u, tokens: %u", bitstreamSize, prefixSize, tokenCount);
@@ -148,10 +150,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
stream->read(prefixStream.data(), prefixStream.size());
assert(stream->pos() == stream->size());
- Common::hexdump(prefixStream.data(), prefixStream.size());
- auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size());
- debug("decoded %u bytes", prefixData.size());
- Common::hexdump(prefixData.data(), prefixData.size());
+ auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size(), true);
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 17840b6258e..6c647ceadf8 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -31,7 +31,7 @@ struct HuffChar {
short trueIdx;
};
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedStart) {
HuffChar table[514] = {};
uint offset = 0;
uint8 codebyte = huff[offset++];
@@ -44,6 +44,9 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
}
codebyte = huff[offset++];
} while (codebyte != 0);
+ if (alignedStart && (offset % 4) != 0) {
+ offset += 4 - (offset % 4);
+ }
table[256].next = 1;
table[513].next = 0x7FFF;
@@ -83,9 +86,9 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize) {
++nIdx;
}
Common::Array<byte> decoded;
- decoded.reserve(huffSize);
+ decoded.reserve(huffSize * 2);
{
- BitStream bs(huff, offset);
+ BitStream bs(huff, huffSize, offset);
while (true) {
short value = startEntry;
while (value > 256) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 54e9d4f53f9..9a9e54ecce6 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -26,17 +26,19 @@ namespace FourXM {
class BitStream {
const byte *_data;
+ uint _size;
uint _bytePos;
byte _bitMask;
public:
- BitStream(const byte *data, uint bytePos) : _data(data), _bytePos(bytePos), _bitMask(0x80) {}
+ BitStream(const byte *data, uint size, uint bytePos) : _data(data), _size(size), _bytePos(bytePos), _bitMask(0x80) {}
uint getBytePos() const {
return _bytePos;
}
bool readBit() {
+ assert(_bytePos < _size);
bool bit = _data[_bytePos] & _bitMask;
_bitMask >>= 1;
if (_bitMask == 0) {
@@ -69,7 +71,7 @@ public:
}
}
};
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize);
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedStart);
} // namespace FourXM
} // namespace Video
Commit: 17791a0ef6e6dcc358a3996bad372ffe0dcafae7
https://github.com/scummvm/scummvm/commit/17791a0ef6e6dcc358a3996bad372ffe0dcafae7
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:53+01:00
Commit Message:
PHOENIXVR: pass compressed huff size for static picture
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index cbfc1215fe8..998ede11382 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -236,7 +236,7 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto dcOffset = READ_LE_UINT32(huff + huffSize);
auto *dcPtr = acPtr + 4 + dcOffset;
auto *dcEnd = vrData.data() + vrData.size();
- unpack(*pic, huff, unpHuffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
+ unpack(*pic, huff, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
} else if (chunkId == CHUNK_ANIMATION) {
auto name = s.readString(0, 32);
s.skip(4);
Commit: e5f562cf1e378a486b2cd2e7db3d796e312706f7
https://github.com/scummvm/scummvm/commit/e5f562cf1e378a486b2cd2e7db3d796e312706f7
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:53+01:00
Commit Message:
PHOENIXVR: rename next to freq
Changed paths:
video/4xm_utils.cpp
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 6c647ceadf8..f04e6a04d8b 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -26,7 +26,7 @@ namespace Video {
namespace FourXM {
struct HuffChar {
- short next;
+ short freq;
short falseIdx;
short trueIdx;
};
@@ -36,10 +36,10 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
uint offset = 0;
uint8 codebyte = huff[offset++];
do {
- uint8 next = huff[offset++];
- if (codebyte <= next) {
- for (auto idx = codebyte; idx <= next; ++idx) {
- table[idx].next = huff[offset++];
+ uint8 freq = huff[offset++];
+ if (codebyte <= freq) {
+ for (auto idx = codebyte; idx <= freq; ++idx) {
+ table[idx].freq = huff[offset++];
}
}
codebyte = huff[offset++];
@@ -47,8 +47,8 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
if (alignedStart && (offset % 4) != 0) {
offset += 4 - (offset % 4);
}
- table[256].next = 1;
- table[513].next = 0x7FFF;
+ table[256].freq = 1;
+ table[513].freq = 0x7FFF;
short startEntry;
short codeIdx = 257, nIdx = 257;
@@ -57,10 +57,10 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
short trueIdx = 513, falseIdx = 513;
short nextLo = 513, nextHi = 513;
while (idx < nIdx) {
- auto next = table[dstIdx].next;
- if (next != 0) {
- if (next >= table[nextLo].next) {
- if (next < table[nextHi].next) {
+ auto freq = table[dstIdx].freq;
+ if (freq != 0) {
+ if (freq >= table[nextLo].freq) {
+ if (freq < table[nextHi].freq) {
trueIdx = idx;
nextHi = dstIdx;
}
@@ -78,8 +78,8 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
startEntry = nIdx - 1;
break;
}
- table[codeIdx].next = table[falseIdx].next + table[trueIdx].next;
- table[falseIdx].next = table[trueIdx].next = 0;
+ table[codeIdx].freq = table[falseIdx].freq + table[trueIdx].freq;
+ table[falseIdx].freq = table[trueIdx].freq = 0;
table[codeIdx].falseIdx = falseIdx;
table[codeIdx].trueIdx = trueIdx;
++codeIdx;
@@ -106,6 +106,7 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
offset = bs.getBytePos();
}
debug("decoded %u bytes at %08x", decoded.size(), offset);
+ assert(offset == huffSize); // must decode to the end
return decoded;
}
Commit: 19e517e153a155ca95bf7dd40c06e9f2a3b4318e
https://github.com/scummvm/scummvm/commit/19e517e153a155ca95bf7dd40c06e9f2a3b4318e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:53+01:00
Commit Message:
PHOENIXVR: simplify huffman
Changed paths:
video/4xm_utils.cpp
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index f04e6a04d8b..e59464c6928 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -26,7 +26,7 @@ namespace Video {
namespace FourXM {
struct HuffChar {
- short freq;
+ int freq;
short falseIdx;
short trueIdx;
};
@@ -34,63 +34,58 @@ struct HuffChar {
Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedStart) {
HuffChar table[514] = {};
uint offset = 0;
- uint8 codebyte = huff[offset++];
+ uint8 freq_first = huff[offset++];
do {
- uint8 freq = huff[offset++];
- if (codebyte <= freq) {
- for (auto idx = codebyte; idx <= freq; ++idx) {
+ uint8 freq_last = huff[offset++];
+ if (freq_first <= freq_last) {
+ for (auto idx = freq_first; idx <= freq_last; ++idx) {
table[idx].freq = huff[offset++];
}
}
- codebyte = huff[offset++];
- } while (codebyte != 0);
+ freq_first = huff[offset++];
+ } while (freq_first != 0);
if (alignedStart && (offset % 4) != 0) {
offset += 4 - (offset % 4);
}
table[256].freq = 1;
table[513].freq = 0x7FFF;
- short startEntry;
- short codeIdx = 257, nIdx = 257;
+ int startEntry;
+ short codeIdx = 257;
while (true) {
- short idx = 0, dstIdx = 0;
- short trueIdx = 513, falseIdx = 513;
- short nextLo = 513, nextHi = 513;
- while (idx < nIdx) {
- auto freq = table[dstIdx].freq;
+ short idx = 0;
+ short smallest2 = 513, smallest1 = 513;
+ while (idx < codeIdx) {
+ auto freq = table[idx].freq;
if (freq != 0) {
- if (freq >= table[nextLo].freq) {
- if (freq < table[nextHi].freq) {
- trueIdx = idx;
- nextHi = dstIdx;
+ if (freq >= table[smallest1].freq) {
+ if (freq < table[smallest2].freq) {
+ smallest2 = idx;
}
} else {
- trueIdx = falseIdx;
- nextHi = nextLo;
- falseIdx = idx;
- nextLo = dstIdx;
+ smallest2 = smallest1;
+ smallest1 = idx;
}
}
++idx;
- ++dstIdx;
}
- if (trueIdx == 513) {
- startEntry = nIdx - 1;
+ if (smallest2 == 513) {
+ startEntry = codeIdx - 1;
break;
}
- table[codeIdx].freq = table[falseIdx].freq + table[trueIdx].freq;
- table[falseIdx].freq = table[trueIdx].freq = 0;
- table[codeIdx].falseIdx = falseIdx;
- table[codeIdx].trueIdx = trueIdx;
+ table[codeIdx].freq = table[smallest1].freq + table[smallest2].freq;
+ table[smallest1].freq = table[smallest2].freq = 0;
+ table[codeIdx].falseIdx = smallest1;
+ table[codeIdx].trueIdx = smallest2;
++codeIdx;
- ++nIdx;
+ assert(codeIdx < 513);
}
Common::Array<byte> decoded;
decoded.reserve(huffSize * 2);
{
BitStream bs(huff, huffSize, offset);
while (true) {
- short value = startEntry;
+ int value = startEntry;
while (value > 256) {
auto bit = bs.readBit();
if (bit)
Commit: 9dbf22f80fc4cd640b424c042066f3f070922cb9
https://github.com/scummvm/scummvm/commit/9dbf22f80fc4cd640b424c042066f3f070922cb9
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:54+01:00
Commit Message:
PHOENIXVR: make bitstream generic and unpack 4xm bitstream
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 998ede11382..f6382310fb1 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -93,7 +93,7 @@ struct Quantisation {
void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, uint acSize, const byte *dcPtr, uint dcSize, int quality, const Common::Array<uint> *prefix = nullptr) {
Quantisation quant(quality);
- auto decoded = Video::FourXM::unpackHuffman(huff, huffSize, false);
+ auto decoded = Video::FourXM::unpackHuffman(huff, huffSize, 1);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
@@ -101,7 +101,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const uint planeSize = prefix ? prefix->size() * 64 : planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
- Video::FourXM::BitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
+ Video::FourXM::ByteBitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
uint blockIdx = 0;
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index e59464c6928..51badc2c69a 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -31,7 +31,35 @@ struct HuffChar {
short trueIdx;
};
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedStart) {
+namespace {
+
+template<typename Word>
+Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset, const HuffChar *table, int startEntry) {
+ Common::Array<byte> decoded;
+ decoded.reserve(huffSize * 2);
+ assert((offset % sizeof(Word)) == 0);
+ BitStream<Word> bs(reinterpret_cast<const Word *>(huff), huffSize / sizeof(Word), offset / sizeof(Word));
+ while (true) {
+ int value = startEntry;
+ while (value > 256) {
+ auto bit = bs.readBit();
+ if (bit)
+ value = table[value].trueIdx;
+ else
+ value = table[value].falseIdx;
+ }
+ if (value == 256)
+ break;
+ decoded.push_back(static_cast<byte>(value));
+ }
+ bs.alignToWord();
+ offset = bs.getWordPos() * sizeof(Word);
+ return decoded;
+}
+
+} // namespace
+
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize) {
HuffChar table[514] = {};
uint offset = 0;
uint8 freq_first = huff[offset++];
@@ -44,8 +72,8 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
}
freq_first = huff[offset++];
} while (freq_first != 0);
- if (alignedStart && (offset % 4) != 0) {
- offset += 4 - (offset % 4);
+ if (wordSize > 1 && (offset % wordSize) != 0) {
+ offset += wordSize - (offset % wordSize);
}
table[256].freq = 1;
table[513].freq = 0x7FFF;
@@ -78,30 +106,23 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedS
table[codeIdx].falseIdx = smallest1;
table[codeIdx].trueIdx = smallest2;
++codeIdx;
- assert(codeIdx < 513);
}
+ assert(codeIdx < 513);
Common::Array<byte> decoded;
- decoded.reserve(huffSize * 2);
- {
- BitStream bs(huff, huffSize, offset);
- while (true) {
- int value = startEntry;
- while (value > 256) {
- auto bit = bs.readBit();
- if (bit)
- value = table[value].trueIdx;
- else
- value = table[value].falseIdx;
- }
- if (value == 256)
- break;
- decoded.push_back(static_cast<byte>(value));
- }
- bs.alignToByte();
- offset = bs.getBytePos();
+ switch (wordSize) {
+ case 1:
+ decoded = unpackStream<byte>(huff, huffSize, offset, table, startEntry);
+ break;
+ case 4:
+ decoded = unpackStream<uint32>(huff, huffSize, offset, table, startEntry);
+ break;
+ default:
+ error("invalid word size");
}
debug("decoded %u bytes at %08x", decoded.size(), offset);
- assert(offset == huffSize); // must decode to the end
+ if (wordSize == 1) {
+ assert(offset == huffSize); // must decode to the end
+ }
return decoded;
}
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 9a9e54ecce6..ec53b789e30 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -24,26 +24,29 @@
namespace Video {
namespace FourXM {
+template<typename Type>
class BitStream {
- const byte *_data;
+ const Type *_data;
uint _size;
- uint _bytePos;
- byte _bitMask;
+ uint _wordPos;
+ Type _bitMask;
+ static constexpr Type InitialMask = 1u << (sizeof(Type) * 8 - 1);
+ static constexpr uint WordSize = sizeof(Type);
public:
- BitStream(const byte *data, uint size, uint bytePos) : _data(data), _size(size), _bytePos(bytePos), _bitMask(0x80) {}
+ BitStream(const Type *data, uint size, uint wordPos) : _data(data), _size(size), _wordPos(wordPos), _bitMask(InitialMask) {}
- uint getBytePos() const {
- return _bytePos;
+ uint getWordPos() const {
+ return _wordPos;
}
bool readBit() {
- assert(_bytePos < _size);
- bool bit = _data[_bytePos] & _bitMask;
+ assert(_wordPos < _size);
+ bool bit = _data[_wordPos] & _bitMask;
_bitMask >>= 1;
if (_bitMask == 0) {
- _bitMask = 128;
- ++_bytePos;
+ _bitMask = InitialMask;
+ ++_wordPos;
}
return bit;
}
@@ -64,14 +67,15 @@ public:
return value;
}
- void alignToByte() {
- if (_bitMask != 0x80) {
- _bitMask = 128;
- ++_bytePos;
+ void alignToWord() {
+ if (_bitMask != InitialMask) {
+ _bitMask = InitialMask;
+ ++_wordPos;
}
}
};
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, bool alignedStart);
+using ByteBitStream = BitStream<byte>;
+Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize);
} // namespace FourXM
} // namespace Video
Commit: c556eca84f56e58c64224b15bb3e09119059135d
https://github.com/scummvm/scummvm/commit/c556eca84f56e58c64224b15bb3e09119059135d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:54+01:00
Commit Message:
PHOENIXVR: add fast idct
Changed paths:
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 51badc2c69a..f942941e60d 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -126,5 +126,96 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize
return decoded;
}
+#define FIX_1_082392200 70936
+#define FIX_1_414213562 92682
+#define FIX_1_847759065 121095
+#define FIX_2_613125930 171254
+
+#define MULTIPLY(var, const) ((int)((var) * (unsigned)(const)) >> 16)
+
+void idct(int16_t block[64]) {
+ int tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
+ int tmp10, tmp11, tmp12, tmp13;
+ int z5, z10, z11, z12, z13;
+ int i;
+ int temp[64];
+
+ for (i = 0; i < 8; i++) {
+ tmp10 = block[8 * 0 + i] + block[8 * 4 + i];
+ tmp11 = block[8 * 0 + i] - block[8 * 4 + i];
+
+ tmp13 = block[8 * 2 + i] + block[8 * 6 + i];
+ tmp12 = MULTIPLY(block[8 * 2 + i] - block[8 * 6 + i], FIX_1_414213562) - tmp13;
+
+ tmp0 = tmp10 + tmp13;
+ tmp3 = tmp10 - tmp13;
+ tmp1 = tmp11 + tmp12;
+ tmp2 = tmp11 - tmp12;
+
+ z13 = block[8 * 5 + i] + block[8 * 3 + i];
+ z10 = block[8 * 5 + i] - block[8 * 3 + i];
+ z11 = block[8 * 1 + i] + block[8 * 7 + i];
+ z12 = block[8 * 1 + i] - block[8 * 7 + i];
+
+ tmp7 = z11 + z13;
+ tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562);
+
+ z5 = MULTIPLY(z10 + z12, FIX_1_847759065);
+ tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5;
+ tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5;
+
+ tmp6 = tmp12 - tmp7;
+ tmp5 = tmp11 - tmp6;
+ tmp4 = tmp10 + tmp5;
+
+ temp[8 * 0 + i] = tmp0 + tmp7;
+ temp[8 * 7 + i] = tmp0 - tmp7;
+ temp[8 * 1 + i] = tmp1 + tmp6;
+ temp[8 * 6 + i] = tmp1 - tmp6;
+ temp[8 * 2 + i] = tmp2 + tmp5;
+ temp[8 * 5 + i] = tmp2 - tmp5;
+ temp[8 * 4 + i] = tmp3 + tmp4;
+ temp[8 * 3 + i] = tmp3 - tmp4;
+ }
+
+ for (i = 0; i < 8 * 8; i += 8) {
+ tmp10 = temp[0 + i] + temp[4 + i];
+ tmp11 = temp[0 + i] - temp[4 + i];
+
+ tmp13 = temp[2 + i] + temp[6 + i];
+ tmp12 = MULTIPLY(temp[2 + i] - temp[6 + i], FIX_1_414213562) - tmp13;
+
+ tmp0 = tmp10 + tmp13;
+ tmp3 = tmp10 - tmp13;
+ tmp1 = tmp11 + tmp12;
+ tmp2 = tmp11 - tmp12;
+
+ z13 = temp[5 + i] + temp[3 + i];
+ z10 = temp[5 + i] - temp[3 + i];
+ z11 = temp[1 + i] + temp[7 + i];
+ z12 = temp[1 + i] - temp[7 + i];
+
+ tmp7 = z11 + z13;
+ tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562);
+
+ z5 = MULTIPLY(z10 + z12, FIX_1_847759065);
+ tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5;
+ tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5;
+
+ tmp6 = tmp12 - tmp7;
+ tmp5 = tmp11 - tmp6;
+ tmp4 = tmp10 + tmp5;
+
+ block[0 + i] = (tmp0 + tmp7) >> 6;
+ block[7 + i] = (tmp0 - tmp7) >> 6;
+ block[1 + i] = (tmp1 + tmp6) >> 6;
+ block[6 + i] = (tmp1 - tmp6) >> 6;
+ block[2 + i] = (tmp2 + tmp5) >> 6;
+ block[5 + i] = (tmp2 - tmp5) >> 6;
+ block[4 + i] = (tmp3 + tmp4) >> 6;
+ block[3 + i] = (tmp3 - tmp4) >> 6;
+ }
+}
+
} // namespace FourXM
} // namespace Video
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index ec53b789e30..ddf0f6f5922 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -77,5 +77,7 @@ public:
using ByteBitStream = BitStream<byte>;
Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize);
+void idct(int16_t block[64]);
+
} // namespace FourXM
} // namespace Video
Commit: edd3b385e201b2d4e426fa41b2425dde60487678
https://github.com/scummvm/scummvm/commit/edd3b385e201b2d4e426fa41b2425dde60487678
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:54+01:00
Commit Message:
VIDEO: 4XM: decode DC/AC coefficients
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index f33e7c7a105..8a199afdf27 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -135,6 +135,83 @@ FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
}
}
+namespace {
+static const uint8_t iquant[64] = {
+ 16,
+ 15,
+ 13,
+ 19,
+ 24,
+ 31,
+ 28,
+ 17,
+ 17,
+ 23,
+ 25,
+ 31,
+ 36,
+ 63,
+ 45,
+ 21,
+ 18,
+ 24,
+ 27,
+ 37,
+ 52,
+ 59,
+ 49,
+ 20,
+ 16,
+ 28,
+ 34,
+ 40,
+ 60,
+ 80,
+ 51,
+ 20,
+ 18,
+ 31,
+ 48,
+ 66,
+ 68,
+ 86,
+ 56,
+ 21,
+ 19,
+ 38,
+ 56,
+ 59,
+ 64,
+ 64,
+ 48,
+ 20,
+ 27,
+ 48,
+ 55,
+ 55,
+ 56,
+ 51,
+ 35,
+ 15,
+ 20,
+ 35,
+ 34,
+ 32,
+ 31,
+ 22,
+ 15,
+ 8,
+};
+
+const byte zigzag[] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4,
+ 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7,
+ 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22,
+ 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39,
+ 46, 53, 60, 61, 54, 47, 55, 62, 63};
+
+} // namespace
+
void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *stream) {
stream->skip(4);
auto bitstreamSize = stream->readUint32LE();
@@ -150,7 +227,50 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
stream->read(prefixStream.data(), prefixStream.size());
assert(stream->pos() == stream->size());
- auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size(), true);
+ auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size(), 4);
+ FourXM::ByteBitStream bitstream(bitstreamData.data(), bitstreamData.size(), 0);
+ uint prefixOffset = 0;
+ auto mbW = (_frame->w + 15) / 16;
+ auto mbH = (_frame->h + 15) / 16;
+ int lastDC = 0;
+ for (int y = 0; y != mbH; ++y) {
+ for (int x = 0; x != mbW; ++x) {
+ debug("decoding macroblock %d,%d", y, x);
+ int16_t block[6][64] = {};
+ auto readBlock = [&](byte blockIdx, int16_t *ac) {
+ int dc = prefixData[prefixOffset++];
+ if (dc >> 4)
+ error("dc run code");
+ dc = bitstream.readInt(dc);
+ dc = lastDC + dc * iquant[0];
+ debug("DC = %d", dc);
+ lastDC = dc;
+ ac[0] = dc;
+ if (blockIdx <= 4)
+ ac[0] *= 128 * 64;
+ for (uint idx = 1; idx < 64;) {
+ auto b = prefixData[prefixOffset++];
+ if (b == 0x00) {
+ break;
+ } else if (b == 0xf0) {
+ idx += 16;
+ } else {
+ auto h = b >> 4;
+ auto l = b & 0x0f;
+ idx += h;
+ if (l && idx < 64) {
+ auto ac_idx = zigzag[idx];
+ ac[ac_idx] = iquant[ac_idx] * bitstream.readInt(l);
+ ++idx;
+ }
+ }
+ }
+ FourXM::idct(ac);
+ };
+ for (int b = 0; b != 6; ++b)
+ readBlock(b, block[b]);
+ }
+ }
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
Commit: 8c931e5b15a121a37a1c043c56807db57e01b947
https://github.com/scummvm/scummvm/commit/8c931e5b15a121a37a1c043c56807db57e01b947
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:54+01:00
Commit Message:
VIDEO: 4XM: fix iframe unpacking
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index f6382310fb1..0a9138dd445 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -101,7 +101,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const uint planeSize = prefix ? prefix->size() * 64 : planePitch * pic.h;
Common::Array<byte> planes(planeSize * 3, 0);
- Video::FourXM::ByteBitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
+ Video::FourXM::LEByteBitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
uint channel = 0;
uint x0 = 0, y0 = 0;
uint blockIdx = 0;
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 8a199afdf27..950565e18be 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -228,14 +228,11 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
assert(stream->pos() == stream->size());
auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size(), 4);
- FourXM::ByteBitStream bitstream(bitstreamData.data(), bitstreamData.size(), 0);
+ FourXM::BEByteBitStream bitstream(bitstreamData.data(), bitstreamData.size(), 0);
uint prefixOffset = 0;
- auto mbW = (_frame->w + 15) / 16;
- auto mbH = (_frame->h + 15) / 16;
int lastDC = 0;
- for (int y = 0; y != mbH; ++y) {
- for (int x = 0; x != mbW; ++x) {
- debug("decoding macroblock %d,%d", y, x);
+ for (int mbY = 0; mbY < _frame->h; mbY += 16) {
+ for (int mbX = 0; mbX < _frame->w; mbX += 16) {
int16_t block[6][64] = {};
auto readBlock = [&](byte blockIdx, int16_t *ac) {
int dc = prefixData[prefixOffset++];
@@ -243,11 +240,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
error("dc run code");
dc = bitstream.readInt(dc);
dc = lastDC + dc * iquant[0];
- debug("DC = %d", dc);
lastDC = dc;
ac[0] = dc;
- if (blockIdx <= 4)
- ac[0] *= 128 * 64;
for (uint idx = 1; idx < 64;) {
auto b = prefixData[prefixOffset++];
if (b == 0x00) {
@@ -265,12 +259,39 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
}
}
}
+ if (blockIdx < 4)
+ ac[0] += 0x80 * 8 * 8;
+
FourXM::idct(ac);
};
+
for (int b = 0; b != 6; ++b)
readBlock(b, block[b]);
+
+ auto acIdx = [](byte y, byte x) {
+ assert(y < 8 && x < 8);
+ return y << 3 | x;
+ };
+ auto blockCB = block[4];
+ auto blockCR = block[5];
+ for (byte y = 0; y != 16; ++y) {
+ for (byte x = 0; x != 16; ++x) {
+ auto yb = y & 7;
+ auto xb = x & 7;
+ auto mbBlockIdx = ((y >> 3) << 1) | (x >> 3);
+ auto Y = block[mbBlockIdx][acIdx(yb, xb)];
+ auto cblockIdx = acIdx(y >> 1, x >> 1);
+ auto CB = blockCB[cblockIdx];
+ auto CR = blockCR[cblockIdx];
+ int CG = (CB + CR) >> 1;
+ CB += CB;
+ auto color = _frame->format.RGBToColor(Y + CR, Y - CG, Y + CB);
+ _frame->setPixel(mbX | x, mbY | y, color);
+ }
+ }
}
}
+ assert(prefixOffset == prefixData.size());
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index f942941e60d..286931f3b0d 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -38,7 +38,7 @@ Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset,
Common::Array<byte> decoded;
decoded.reserve(huffSize * 2);
assert((offset % sizeof(Word)) == 0);
- BitStream<Word> bs(reinterpret_cast<const Word *>(huff), huffSize / sizeof(Word), offset / sizeof(Word));
+ BitStream<Word, false> bs(reinterpret_cast<const Word *>(huff), huffSize / sizeof(Word), offset / sizeof(Word));
while (true) {
int value = startEntry;
while (value > 256) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index ddf0f6f5922..8d1f178b610 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -24,7 +24,7 @@
namespace Video {
namespace FourXM {
-template<typename Type>
+template<typename Type, bool BigEndian>
class BitStream {
const Type *_data;
uint _size;
@@ -53,9 +53,17 @@ public:
int readUInt(byte n) {
int value = 0;
- for (int i = 0; i != n; ++i) {
- if (readBit())
- value |= 1 << i;
+ if (BigEndian) {
+ for (int i = 0; i != n; ++i) {
+ value <<= 1;
+ if (readBit())
+ value |= 1;
+ }
+ } else {
+ for (int i = 0; i != n; ++i) {
+ if (readBit())
+ value |= 1 << i;
+ }
}
return value;
}
@@ -74,7 +82,8 @@ public:
}
}
};
-using ByteBitStream = BitStream<byte>;
+using LEByteBitStream = BitStream<byte, false>;
+using BEByteBitStream = BitStream<byte, true>;
Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize);
void idct(int16_t block[64]);
Commit: 66b76eb5a95a98a2f181ee63a93de3faf4259f47
https://github.com/scummvm/scummvm/commit/66b76eb5a95a98a2f181ee63a93de3faf4259f47
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:55+01:00
Commit Message:
VIDEO: 4XM: add scaffolding for p frames
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 950565e18be..6b78680317c 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -107,6 +107,7 @@ public:
void decode_cfrm(Common::SeekableReadStream *stream);
private:
+ void decode_pfrm_block(int x, int y, int log2w, int log2h, const Common::MemoryReadStream &bitStream, const Common::MemoryReadStream &wordStream, const Common::MemoryReadStream &byteStream);
Common::Rational getFrameRate() const override { return _frameRate; }
};
@@ -210,6 +211,13 @@ const byte zigzag[] = {
15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39,
46, 53, 60, 61, 54, 47, 55, 62, 63};
+static const int8_t size2index[4][4] = {
+ {-1, 3, 1, 1},
+ {3, 0, 0, 0},
+ {2, 0, 0, 0},
+ {2, 0, 0, 0},
+};
+
} // namespace
void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *stream) {
@@ -294,12 +302,35 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
assert(prefixOffset == prefixData.size());
}
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(int x, int y, int log2w, int log2h, const Common::MemoryReadStream &bitStream, const Common::MemoryReadStream &wordStream, const Common::MemoryReadStream &byteStream) {
+ assert(log2w >= 0 && log2h >= 0);
+ auto index = size2index[log2h][log2w];
+ assert(index >= 0);
+}
+
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
stream->skip(12);
auto bitStreamSize = stream->readUint32LE();
auto wordStreamSize = stream->readUint32LE();
auto byteStreamSize = stream->readUint32LE();
debug("p-frame, bitstream: %u, wordstream: %u, bytestream: %u", bitStreamSize, wordStreamSize, byteStreamSize);
+
+ Common::Array<byte> bitStreamData(bitStreamSize);
+ stream->read(bitStreamData.data(), bitStreamData.size());
+ Common::Array<byte> wordStreamData(wordStreamSize);
+ stream->read(wordStreamData.data(), wordStreamData.size());
+ Common::Array<byte> byteStreamData(byteStreamSize);
+ stream->read(byteStreamData.data(), byteStreamData.size());
+
+ Common::MemoryReadStream bitStream(bitStreamData.data(), bitStreamData.size());
+ Common::MemoryReadStream wordStream(wordStreamData.data(), wordStreamData.size());
+ Common::MemoryReadStream byteStream(byteStreamData.data(), byteStreamData.size());
+
+ for (int y = 0, h = _frame->h; y < h; y += 8) {
+ for (int x = 0, w = _frame->w; x < w; x += 8) {
+ decode_pfrm_block(x, y, 3, 3, bitStream, wordStream, byteStream);
+ }
+ }
}
void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *stream) {
Commit: 97d16ebe7d9784579263e99cd37e489996fe6391
https://github.com/scummvm/scummvm/commit/97d16ebe7d9784579263e99cd37e489996fe6391
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:55+01:00
Commit Message:
VIDEO: 4XM: factor huffman decoder out
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 0a9138dd445..11937652224 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -93,7 +93,7 @@ struct Quantisation {
void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, uint acSize, const byte *dcPtr, uint dcSize, int quality, const Common::Array<uint> *prefix = nullptr) {
Quantisation quant(quality);
- auto decoded = Video::FourXM::unpackHuffman(huff, huffSize, 1);
+ auto decoded = Video::FourXM::HuffmanDecoder::unpack(huff, huffSize, 1);
uint decodedOffset = 0;
static const DCT2DIII<6> dct;
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 6b78680317c..7087f59873e 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -235,7 +235,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
stream->read(prefixStream.data(), prefixStream.size());
assert(stream->pos() == stream->size());
- auto prefixData = FourXM::unpackHuffman(prefixStream.data(), prefixStream.size(), 4);
+ auto prefixData = FourXM::HuffmanDecoder::unpack(prefixStream.data(), prefixStream.size(), 4);
FourXM::BEByteBitStream bitstream(bitstreamData.data(), bitstreamData.size(), 0);
uint prefixOffset = 0;
int lastDC = 0;
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 286931f3b0d..90986f68bb4 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -25,29 +25,27 @@
namespace Video {
namespace FourXM {
-struct HuffChar {
- int freq;
- short falseIdx;
- short trueIdx;
-};
-
-namespace {
+template<typename BitStreamType>
+uint HuffmanDecoder::next(BitStreamType &bs) {
+ uint value = _startEntry;
+ while (value > 256) {
+ auto bit = bs.readBit();
+ if (bit)
+ value = _table[value].trueIdx;
+ else
+ value = _table[value].falseIdx;
+ }
+ return value;
+}
template<typename Word>
-Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset, const HuffChar *table, int startEntry) {
+Common::Array<byte> HuffmanDecoder::unpackStream(const byte *huff, uint huffSize, uint &offset) {
Common::Array<byte> decoded;
decoded.reserve(huffSize * 2);
assert((offset % sizeof(Word)) == 0);
BitStream<Word, false> bs(reinterpret_cast<const Word *>(huff), huffSize / sizeof(Word), offset / sizeof(Word));
while (true) {
- int value = startEntry;
- while (value > 256) {
- auto bit = bs.readBit();
- if (bit)
- value = table[value].trueIdx;
- else
- value = table[value].falseIdx;
- }
+ auto value = next(bs);
if (value == 256)
break;
decoded.push_back(static_cast<byte>(value));
@@ -57,37 +55,37 @@ Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset,
return decoded;
}
-} // namespace
-
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize) {
- HuffChar table[514] = {};
+uint HuffmanDecoder::loadStatistics(const byte *huff, uint huffSize) {
uint offset = 0;
uint8 freq_first = huff[offset++];
do {
uint8 freq_last = huff[offset++];
if (freq_first <= freq_last) {
for (auto idx = freq_first; idx <= freq_last; ++idx) {
- table[idx].freq = huff[offset++];
+ _table[idx].freq = huff[offset++];
}
}
freq_first = huff[offset++];
} while (freq_first != 0);
- if (wordSize > 1 && (offset % wordSize) != 0) {
- offset += wordSize - (offset % wordSize);
+ if (_wordSize > 1 && (offset % _wordSize) != 0) {
+ offset += _wordSize - (offset % _wordSize);
}
- table[256].freq = 1;
- table[513].freq = 0x7FFF;
+ _table[256].freq = 1;
+ _table[513].freq = 0x7FFF;
- int startEntry;
- short codeIdx = 257;
+ buildTable(257);
+ return offset;
+}
+
+void HuffmanDecoder::buildTable(uint codeIdx) {
while (true) {
- short idx = 0;
- short smallest2 = 513, smallest1 = 513;
+ ushort idx = 0;
+ ushort smallest2 = 513, smallest1 = 513;
while (idx < codeIdx) {
- auto freq = table[idx].freq;
+ auto freq = _table[idx].freq;
if (freq != 0) {
- if (freq >= table[smallest1].freq) {
- if (freq < table[smallest2].freq) {
+ if (freq >= _table[smallest1].freq) {
+ if (freq < _table[smallest2].freq) {
smallest2 = idx;
}
} else {
@@ -98,23 +96,28 @@ Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize
++idx;
}
if (smallest2 == 513) {
- startEntry = codeIdx - 1;
+ _startEntry = codeIdx - 1;
break;
}
- table[codeIdx].freq = table[smallest1].freq + table[smallest2].freq;
- table[smallest1].freq = table[smallest2].freq = 0;
- table[codeIdx].falseIdx = smallest1;
- table[codeIdx].trueIdx = smallest2;
+ _table[codeIdx].freq = _table[smallest1].freq + _table[smallest2].freq;
+ _table[smallest1].freq = _table[smallest2].freq = 0;
+ _table[codeIdx].falseIdx = smallest1;
+ _table[codeIdx].trueIdx = smallest2;
++codeIdx;
}
assert(codeIdx < 513);
+}
+
+Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte wordSize) {
+ HuffmanDecoder dec(wordSize);
+ auto offset = dec.loadStatistics(huff, huffSize);
Common::Array<byte> decoded;
switch (wordSize) {
case 1:
- decoded = unpackStream<byte>(huff, huffSize, offset, table, startEntry);
+ decoded = dec.unpackStream<byte>(huff, huffSize, offset);
break;
case 4:
- decoded = unpackStream<uint32>(huff, huffSize, offset, table, startEntry);
+ decoded = dec.unpackStream<uint32>(huff, huffSize, offset);
break;
default:
error("invalid word size");
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 8d1f178b610..72ae32699c6 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -84,7 +84,32 @@ public:
};
using LEByteBitStream = BitStream<byte, false>;
using BEByteBitStream = BitStream<byte, true>;
-Common::Array<byte> unpackHuffman(const byte *huff, uint huffSize, byte wordSize);
+
+class HuffmanDecoder {
+ struct HuffChar {
+ uint freq;
+ ushort falseIdx;
+ ushort trueIdx;
+ };
+ HuffChar _table[514] = {};
+ byte _wordSize;
+ uint _startEntry = 0;
+
+public:
+ HuffmanDecoder(byte wordSize) : _wordSize(wordSize) {}
+ uint loadStatistics(const byte *huff, uint huffSize);
+
+ static Common::Array<byte> unpack(const byte *huff, uint huffSize, byte wordSize);
+
+private:
+ template<typename BitStreamType>
+ uint next(BitStreamType &bs);
+
+ template<typename Word>
+ Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset);
+
+ void buildTable(uint max);
+};
void idct(int16_t block[64]);
Commit: 458adff2cc3736714137e91da0e40b87ab3e6a5d
https://github.com/scummvm/scummvm/commit/458adff2cc3736714137e91da0e40b87ab3e6a5d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:55+01:00
Commit Message:
PHOENIXVR: factor unpack() out
Changed paths:
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 90986f68bb4..72b9d2f8ec0 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -108,27 +108,31 @@ void HuffmanDecoder::buildTable(uint codeIdx) {
assert(codeIdx < 513);
}
-Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte wordSize) {
- HuffmanDecoder dec(wordSize);
- auto offset = dec.loadStatistics(huff, huffSize);
+Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint &offset) {
Common::Array<byte> decoded;
- switch (wordSize) {
+ switch (_wordSize) {
case 1:
- decoded = dec.unpackStream<byte>(huff, huffSize, offset);
+ decoded = unpackStream<byte>(huff, huffSize, offset);
break;
case 4:
- decoded = dec.unpackStream<uint32>(huff, huffSize, offset);
+ decoded = unpackStream<uint32>(huff, huffSize, offset);
break;
default:
error("invalid word size");
}
debug("decoded %u bytes at %08x", decoded.size(), offset);
- if (wordSize == 1) {
+ if (_wordSize == 1) {
assert(offset == huffSize); // must decode to the end
}
return decoded;
}
+Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte wordSize) {
+ HuffmanDecoder dec(wordSize);
+ auto offset = dec.loadStatistics(huff, huffSize);
+ return dec.unpack(huff, huffSize, offset);
+}
+
#define FIX_1_082392200 70936
#define FIX_1_414213562 92682
#define FIX_1_847759065 121095
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 72ae32699c6..6ff77b7d8dd 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -99,6 +99,8 @@ public:
HuffmanDecoder(byte wordSize) : _wordSize(wordSize) {}
uint loadStatistics(const byte *huff, uint huffSize);
+ Common::Array<byte> unpack(const byte *huff, uint huffSize, uint &offset);
+
static Common::Array<byte> unpack(const byte *huff, uint huffSize, byte wordSize);
private:
Commit: b1755314644cc56515401fcd78bd0c682243ca46
https://github.com/scummvm/scummvm/commit/b1755314644cc56515401fcd78bd0c682243ca46
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:56+01:00
Commit Message:
PHOENIXVR: remove wordSize
Changed paths:
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 72b9d2f8ec0..92ca4b79237 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -67,9 +67,6 @@ uint HuffmanDecoder::loadStatistics(const byte *huff, uint huffSize) {
}
freq_first = huff[offset++];
} while (freq_first != 0);
- if (_wordSize > 1 && (offset % _wordSize) != 0) {
- offset += _wordSize - (offset % _wordSize);
- }
_table[256].freq = 1;
_table[513].freq = 0x7FFF;
@@ -108,9 +105,9 @@ void HuffmanDecoder::buildTable(uint codeIdx) {
assert(codeIdx < 513);
}
-Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint &offset) {
+Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize) {
Common::Array<byte> decoded;
- switch (_wordSize) {
+ switch (wordSize) {
case 1:
decoded = unpackStream<byte>(huff, huffSize, offset);
break;
@@ -121,16 +118,19 @@ Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint
error("invalid word size");
}
debug("decoded %u bytes at %08x", decoded.size(), offset);
- if (_wordSize == 1) {
+ if (wordSize == 1) {
assert(offset == huffSize); // must decode to the end
}
return decoded;
}
Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte wordSize) {
- HuffmanDecoder dec(wordSize);
+ HuffmanDecoder dec;
auto offset = dec.loadStatistics(huff, huffSize);
- return dec.unpack(huff, huffSize, offset);
+ if (wordSize > 1 && (offset % wordSize) != 0) {
+ offset += wordSize - (offset % wordSize);
+ }
+ return dec.unpack(huff, huffSize, offset, wordSize);
}
#define FIX_1_082392200 70936
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 6ff77b7d8dd..a903c37b5e3 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -92,14 +92,12 @@ class HuffmanDecoder {
ushort trueIdx;
};
HuffChar _table[514] = {};
- byte _wordSize;
uint _startEntry = 0;
public:
- HuffmanDecoder(byte wordSize) : _wordSize(wordSize) {}
uint loadStatistics(const byte *huff, uint huffSize);
- Common::Array<byte> unpack(const byte *huff, uint huffSize, uint &offset);
+ Common::Array<byte> unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize);
static Common::Array<byte> unpack(const byte *huff, uint huffSize, byte wordSize);
Commit: a3b50c021554ebd7057f7db354d84aaa40909cd4
https://github.com/scummvm/scummvm/commit/a3b50c021554ebd7057f7db354d84aaa40909cd4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:56+01:00
Commit Message:
PHOENIXVR: attempt to reconstruct frequencies
Changed paths:
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 7087f59873e..130e014ec3a 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -86,9 +86,20 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
Common::Rational _frameRate;
uint _w, _h;
Graphics::Surface *_frame;
+ FourXM::HuffmanDecoder _blockType[2][4] = {};
public:
- FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _frame(nullptr) {}
+ FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _frame(nullptr) {
+ _blockType[0][0].initStatistics({16, 8, 4, 2, 1, 1});
+ _blockType[0][1].initStatistics({8, 0, 4, 2, 1, 1});
+ _blockType[0][2].initStatistics({8, 4, 0, 2, 1, 1});
+ _blockType[0][3].initStatistics({8, 0, 0, 4, 2, 1, 1});
+
+ _blockType[1][0].initStatistics({2, 1, 1, 2, 1, 1});
+ _blockType[1][1].initStatistics({2, 0, 2, 2, 1, 1});
+ _blockType[1][2].initStatistics({2, 2, 0, 2, 1, 1});
+ _blockType[1][3].initStatistics({2, 0, 0, 2, 2, 1, 1});
+ }
~FourXMVideoTrack();
uint16 getWidth() const override { return _w; }
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 92ca4b79237..b68ff4f429a 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -55,6 +55,14 @@ Common::Array<byte> HuffmanDecoder::unpackStream(const byte *huff, uint huffSize
return decoded;
}
+void HuffmanDecoder::initStatistics(const std::initializer_list<uint> &freqs) {
+ auto freqBegin = freqs.begin();
+ for (size_t i = 0; i != freqs.size(); ++i) {
+ _table[i].freq = *freqBegin++;
+ }
+ buildTable(freqs.size());
+}
+
uint HuffmanDecoder::loadStatistics(const byte *huff, uint huffSize) {
uint offset = 0;
uint8 freq_first = huff[offset++];
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index a903c37b5e3..61e7ce2f440 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -96,6 +96,7 @@ class HuffmanDecoder {
public:
uint loadStatistics(const byte *huff, uint huffSize);
+ void initStatistics(const std::initializer_list<uint> &freqs);
Common::Array<byte> unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize);
Commit: ac600ee492b70840cc20231b956293a99727ff94
https://github.com/scummvm/scummvm/commit/ac600ee492b70840cc20231b956293a99727ff94
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:56+01:00
Commit Message:
PHOENIXVR: add mcdc
Changed paths:
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 130e014ec3a..d8790ad569d 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -85,20 +85,21 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
FourXMDecoder *_dec;
Common::Rational _frameRate;
uint _w, _h;
+ uint16 _version = 0;
Graphics::Surface *_frame;
FourXM::HuffmanDecoder _blockType[2][4] = {};
public:
- FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _frame(nullptr) {
- _blockType[0][0].initStatistics({16, 8, 4, 2, 1, 1});
- _blockType[0][1].initStatistics({8, 0, 4, 2, 1, 1});
- _blockType[0][2].initStatistics({8, 4, 0, 2, 1, 1});
- _blockType[0][3].initStatistics({8, 0, 0, 4, 2, 1, 1});
-
- _blockType[1][0].initStatistics({2, 1, 1, 2, 1, 1});
- _blockType[1][1].initStatistics({2, 0, 2, 2, 1, 1});
- _blockType[1][2].initStatistics({2, 2, 0, 2, 1, 1});
- _blockType[1][3].initStatistics({2, 0, 0, 2, 2, 1, 1});
+ FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version), _frame(nullptr) {
+ _blockType[0][0].initStatistics({2, 1, 1, 2, 1, 1});
+ _blockType[0][1].initStatistics({2, 0, 2, 2, 1, 1});
+ _blockType[0][2].initStatistics({2, 2, 0, 2, 1, 1});
+ _blockType[0][3].initStatistics({2, 0, 0, 2, 2, 1, 1});
+
+ _blockType[1][0].initStatistics({16, 8, 4, 2, 1, 1});
+ _blockType[1][1].initStatistics({8, 0, 4, 2, 1, 1});
+ _blockType[1][2].initStatistics({8, 4, 0, 2, 1, 1});
+ _blockType[1][3].initStatistics({8, 0, 0, 4, 2, 1, 1});
}
~FourXMVideoTrack();
@@ -118,7 +119,7 @@ public:
void decode_cfrm(Common::SeekableReadStream *stream);
private:
- void decode_pfrm_block(int x, int y, int log2w, int log2h, const Common::MemoryReadStream &bitStream, const Common::MemoryReadStream &wordStream, const Common::MemoryReadStream &byteStream);
+ void decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEByteBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
Common::Rational getFrameRate() const override { return _frameRate; }
};
@@ -313,10 +314,77 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
assert(prefixOffset == prefixData.size());
}
-void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(int x, int y, int log2w, int log2h, const Common::MemoryReadStream &bitStream, const Common::MemoryReadStream &wordStream, const Common::MemoryReadStream &byteStream) {
+namespace {
+#define LE_CENTRIC_MUL(dst, src, scale, dc) \
+ { \
+ unsigned tmpval = READ_LE_UINT32(src) * (scale) + (dc); \
+ *(dst) = tmpval; \
+ }
+
+void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
+ int h, int stride, int scale, unsigned dc) {
+ int i;
+ dc *= 0x10001;
+
+ switch (log2w) {
+ case 0:
+ for (i = 0; i < h; i++) {
+ dst[0] = scale * src[0] + dc;
+ if (scale)
+ src += stride;
+ dst += stride;
+ }
+ break;
+ case 1:
+ for (i = 0; i < h; i++) {
+ LE_CENTRIC_MUL(dst, src, scale, dc);
+ if (scale)
+ src += stride;
+ dst += stride;
+ }
+ break;
+ case 2:
+ for (i = 0; i < h; i++) {
+ LE_CENTRIC_MUL(dst, src, scale, dc);
+ LE_CENTRIC_MUL(dst + 2, src + 2, scale, dc);
+ if (scale)
+ src += stride;
+ dst += stride;
+ }
+ break;
+ case 3:
+ for (i = 0; i < h; i++) {
+ LE_CENTRIC_MUL(dst, src, scale, dc);
+ LE_CENTRIC_MUL(dst + 2, src + 2, scale, dc);
+ LE_CENTRIC_MUL(dst + 4, src + 4, scale, dc);
+ LE_CENTRIC_MUL(dst + 6, src + 6, scale, dc);
+ if (scale)
+ src += stride;
+ dst += stride;
+ }
+ break;
+ default:
+ error("invalid log2w");
+ }
+}
+} // namespace
+
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEByteBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
+ auto h = 1 << log2h, w = 1 << log2w;
+ auto &huff = _blockType[_version > 1][index];
+ auto code = huff.next(bs);
+ int scale = 0;
+ int dc = 0;
+ if (code == 5) {
+ dc = wordStream.readSint16LE();
+ } else {
+ error("invalid code %d", code);
+ }
+
+ mcdc(static_cast<uint16 *>(frame->getBasePtr(x, y)), static_cast<const uint16 *>(_frame->getBasePtr(x, y)), log2w, h, frame->pitch / frame->format.bytesPerPixel, scale, dc);
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
@@ -333,15 +401,18 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::Array<byte> byteStreamData(byteStreamSize);
stream->read(byteStreamData.data(), byteStreamData.size());
- Common::MemoryReadStream bitStream(bitStreamData.data(), bitStreamData.size());
+ FourXM::BEByteBitStream bitStream(bitStreamData.data(), bitStreamData.size(), 0);
Common::MemoryReadStream wordStream(wordStreamData.data(), wordStreamData.size());
Common::MemoryReadStream byteStream(byteStreamData.data(), byteStreamData.size());
+ Common::ScopedPtr<Graphics::Surface> frame(new Graphics::Surface());
+ frame->copyFrom(*_frame);
for (int y = 0, h = _frame->h; y < h; y += 8) {
for (int x = 0, w = _frame->w; x < w; x += 8) {
- decode_pfrm_block(x, y, 3, 3, bitStream, wordStream, byteStream);
+ decode_pfrm_block(frame.get(), x, y, 3, 3, bitStream, wordStream, byteStream);
}
}
+ _frame = frame.release();
}
void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *stream) {
@@ -469,11 +540,13 @@ void FourXMDecoder::readList(uint32 listEnd) {
case MKTAG('V', 'T', 'R', 'K'):
switch (tag) {
case MKTAG('v', 't', 'r', 'k'): {
- _stream->skip(28);
+ _stream->skip(4);
+ auto extra = _stream->readUint32LE();
+ _stream->skip(20);
auto w = _stream->readUint32LE();
auto h = _stream->readUint32LE();
- debug("video %ux%u", w, h);
- addTrack(_video = new FourXMVideoTrack(this, _frameRate, w, h));
+ debug("video %ux%u, version: %u", w, h, extra >> 16);
+ addTrack(_video = new FourXMVideoTrack(this, _frameRate, w, h, extra >> 16));
} break;
default:
break;
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index b68ff4f429a..123d9083445 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -37,6 +37,8 @@ uint HuffmanDecoder::next(BitStreamType &bs) {
}
return value;
}
+template uint HuffmanDecoder::next(LEByteBitStream &bs);
+template uint HuffmanDecoder::next(BEByteBitStream &bs);
template<typename Word>
Common::Array<byte> HuffmanDecoder::unpackStream(const byte *huff, uint huffSize, uint &offset) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 61e7ce2f440..1e5c7c753ee 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -102,10 +102,10 @@ public:
static Common::Array<byte> unpack(const byte *huff, uint huffSize, byte wordSize);
-private:
template<typename BitStreamType>
uint next(BitStreamType &bs);
+private:
template<typename Word>
Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset);
Commit: fe98af38516f6c434854e76aa46569e225e2ec58
https://github.com/scummvm/scummvm/commit/fe98af38516f6c434854e76aa46569e225e2ec58
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:56+01:00
Commit Message:
VIDEO: 4XM: implement p frame scaffolding
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index d8790ad569d..0cd6c9ba7a3 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -383,7 +383,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
} else {
error("invalid code %d", code);
}
-
mcdc(static_cast<uint16 *>(frame->getBasePtr(x, y)), static_cast<const uint16 *>(_frame->getBasePtr(x, y)), log2w, h, frame->pitch / frame->format.bytesPerPixel, scale, dc);
}
Commit: 97a447c4fa3a16ea7d9533860b47bd00734aa1a4
https://github.com/scummvm/scummvm/commit/97a447c4fa3a16ea7d9533860b47bd00734aa1a4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:57+01:00
Commit Message:
VIDEO: 4XM: fix version
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 0cd6c9ba7a3..b9eaf3bcfdb 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -539,9 +539,9 @@ void FourXMDecoder::readList(uint32 listEnd) {
case MKTAG('V', 'T', 'R', 'K'):
switch (tag) {
case MKTAG('v', 't', 'r', 'k'): {
- _stream->skip(4);
+ _stream->skip(8);
auto extra = _stream->readUint32LE();
- _stream->skip(20);
+ _stream->skip(16);
auto w = _stream->readUint32LE();
auto h = _stream->readUint32LE();
debug("video %ux%u, version: %u", w, h, extra >> 16);
Commit: 8cda95f3701203e092a200019591381c9cc9bb2c
https://github.com/scummvm/scummvm/commit/8cda95f3701203e092a200019591381c9cc9bb2c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:57+01:00
Commit Message:
VIDEO: 4XM: fix running condition for huffman tree
Changed paths:
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 123d9083445..ff5ae5714e8 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -28,7 +28,7 @@ namespace FourXM {
template<typename BitStreamType>
uint HuffmanDecoder::next(BitStreamType &bs) {
uint value = _startEntry;
- while (value > 256) {
+ while (value >= _numCodes) {
auto bit = bs.readBit();
if (bit)
value = _table[value].trueIdx;
@@ -78,13 +78,15 @@ uint HuffmanDecoder::loadStatistics(const byte *huff, uint huffSize) {
freq_first = huff[offset++];
} while (freq_first != 0);
_table[256].freq = 1;
- _table[513].freq = 0x7FFF;
buildTable(257);
return offset;
}
-void HuffmanDecoder::buildTable(uint codeIdx) {
+void HuffmanDecoder::buildTable(uint numCodes) {
+ _numCodes = numCodes;
+ _table[513].freq = 0x7FFF;
+ auto codeIdx = numCodes;
while (true) {
ushort idx = 0;
ushort smallest2 = 513, smallest1 = 513;
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 1e5c7c753ee..0d644fe1421 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -30,7 +30,7 @@ class BitStream {
uint _size;
uint _wordPos;
Type _bitMask;
- static constexpr Type InitialMask = 1u << (sizeof(Type) * 8 - 1);
+ static constexpr Type InitialMask = Type(1) << (sizeof(Type) * 8 - 1);
static constexpr uint WordSize = sizeof(Type);
public:
@@ -93,6 +93,7 @@ class HuffmanDecoder {
};
HuffChar _table[514] = {};
uint _startEntry = 0;
+ uint _numCodes = 0;
public:
uint loadStatistics(const byte *huff, uint huffSize);
@@ -109,7 +110,7 @@ private:
template<typename Word>
Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset);
- void buildTable(uint max);
+ void buildTable(uint numCodes);
};
void idct(int16_t block[64]);
Commit: 1f48d797611fac9e3f88892cc51d76af9507391e
https://github.com/scummvm/scummvm/commit/1f48d797611fac9e3f88892cc51d76af9507391e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:57+01:00
Commit Message:
VIDEO: 4XM: add code decoder
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index b9eaf3bcfdb..ab3ad99c4f4 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -44,6 +44,10 @@ Common::String tagName(uint32 tag) {
char name[5] = {char(tag >> 24), char(tag >> 16), char(tag >> 8), char(tag), 0};
return {name};
}
+
+static const int8_t mv[256][2] = {
+ {0, 0}, {0, -1}, {-1, 0}, {1, 0}, {0, 1}, {-1, -1}, {1, -1}, {-1, 1}, {1, 1}, {0, -2}, {-2, 0}, {2, 0}, {0, 2}, {-1, -2}, {1, -2}, {-2, -1}, {2, -1}, {-2, 1}, {2, 1}, {-1, 2}, {1, 2}, {-2, -2}, {2, -2}, {-2, 2}, {2, 2}, {0, -3}, {-3, 0}, {3, 0}, {0, 3}, {-1, -3}, {1, -3}, {-3, -1}, {3, -1}, {-3, 1}, {3, 1}, {-1, 3}, {1, 3}, {-2, -3}, {2, -3}, {-3, -2}, {3, -2}, {-3, 2}, {3, 2}, {-2, 3}, {2, 3}, {0, -4}, {-4, 0}, {4, 0}, {0, 4}, {-1, -4}, {1, -4}, {-4, -1}, {4, -1}, {4, 1}, {-1, 4}, {1, 4}, {-3, -3}, {-3, 3}, {3, 3}, {-2, -4}, {-4, -2}, {4, -2}, {-4, 2}, {-2, 4}, {2, 4}, {-3, -4}, {3, -4}, {4, -3}, {-5, 0}, {-4, 3}, {-3, 4}, {3, 4}, {-1, -5}, {-5, -1}, {-5, 1}, {-1, 5}, {-2, -5}, {2, -5}, {5, -2}, {5, 2}, {-4, -4}, {-4, 4}, {-3, -5}, {-5, -3}, {-5, 3}, {3, 5}, {-6, 0}, {0, 6}, {-6, -1}, {-6, 1}, {1, 6}, {2, -6}, {-6, 2}, {2, 6}, {-5, -4}, {5, 4}, {4, 5}, {-6, -3}, {6, 3}, {-7, 0}, {-1, -7}, {5, -5}, {-7, 1}, {-1, 7}, {4, -6}, {6, 4}, {-2, -7}, {-7, 2}, {-3, -7}, {7, -3}, {3, 7}, {6, -5}, {0, -8}, {-1, -8}, {-7, -4}, {-8, 1}, {4, 7}, {2, -8}, {-2, 8}, {6, 6}, {-8, 3}, {5, -7}, {-5, 7}, {8, -4}, {0, -9}, {-9, -1}, {1, 9}, {7, -6}, {-7, 6}, {-5, -8}, {-5, 8}, {-9, 3}, {9, -4}, {7, -7}, {8, -6}, {6, 8}, {10, 1}, {-10, 2}, {9, -5}, {10, -3}, {-8, -7}, {-10, -4}, {6, -9}, {-11, 0}, {11, 1}, {-11, -2}, {-2, 11}, {7, -9}, {-7, 9}, {10, 6}, {-4, 11}, {8, -9}, {8, 9}, {5, 11}, {7, -10}, {12, -3}, {11, 6}, {-9, -9}, {8, 10}, {5, 12}, {-11, 7}, {13, 2}, {6, -12}, {10, 9}, {-11, 8}, {-7, 12}, {0, 14}, {14, -2}, {-9, 11}, {-6, 13}, {-14, -4}, {-5, -14}, {5, 14}, {-15, -1}, {-14, -6}, {3, -15}, {11, -11}, {-7, 14}, {-5, 15}, {8, -14}, {15, 6}, {3, 16}, {7, -15}, {-16, 5}, {0, 17}, {-16, -6}, {-10, 14}, {-16, 7}, {12, 13}, {-16, 8}, {-17, 6}, {-18, 3}, {-7, 17}, {15, 11}, {16, 10}, {2, -19}, {3, -19}, {-11, -16}, {-18, 8}, {-19, -6}, {2, -20}, {-17, -11}, {-10, -18}, {8, 19}, {-21, -1}, {-20, 7}, {-4, 21}, {21, 5}, {15, 16}, {2, -22}, {-10, -20}, {-22, 5}, {20, -11}, {-7, -22}, {-12, 20}, {23, -5}, {13, -20}, {24, -2}, {-15, 19}, {-11, 22}, {16, 19}, {23, -10}, {-18, -18}, {-9, -24}, {24, -10}, {-3, 26}, {-23, 13}, {-18, -20}, {17, 21}, {-4, 27}, {27, 6}, {1, -28}, {-11, 26}, {-17, -23}, {7, 28}, {11, -27}, {29, 5}, {-23, -19}, {-28, -11}, {-21, 22}, {-30, 7}, {-17, 26}, {-27, 16}, {13, 29}, {19, -26}, {10, -31}, {-14, -30}, {20, -27}, {-29, 18}, {-16, -31}, {-28, -22}, {21, -30}, {-25, 28}, {26, -29}, {25, -32}, {-32, -32}};
+
} // namespace
class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
@@ -88,6 +92,7 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
uint16 _version = 0;
Graphics::Surface *_frame;
FourXM::HuffmanDecoder _blockType[2][4] = {};
+ byte _mv[256];
public:
FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version), _frame(nullptr) {
@@ -127,6 +132,13 @@ const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
if (!_frame) {
_frame = new Graphics::Surface();
_frame->create(_w, _h, getPixelFormat());
+
+ for (uint i = 0; i < 256; i++) {
+ if (_version > 1)
+ _mv[i] = mv[i][0] + mv[i][1] * _frame->pitch / 2;
+ else
+ _mv[i] = (i & 15) - 8 + ((i >> 4) - 8) * _frame->pitch / 2;
+ }
}
debug("decode next video frame");
_dec->decodeNextFrameImpl();
@@ -315,11 +327,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
}
namespace {
-#define LE_CENTRIC_MUL(dst, src, scale, dc) \
- { \
- unsigned tmpval = READ_LE_UINT32(src) * (scale) + (dc); \
- *(dst) = tmpval; \
- }
void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
int h, int stride, int scale, unsigned dc) {
@@ -329,7 +336,8 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
switch (log2w) {
case 0:
for (i = 0; i < h; i++) {
- dst[0] = scale * src[0] + dc;
+ for (int j = 0; j != 1; ++j)
+ dst[j] = scale * src[j] + dc;
if (scale)
src += stride;
dst += stride;
@@ -337,7 +345,8 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
break;
case 1:
for (i = 0; i < h; i++) {
- LE_CENTRIC_MUL(dst, src, scale, dc);
+ for (int j = 0; j != 2; ++j)
+ dst[j] = scale * src[j] + dc;
if (scale)
src += stride;
dst += stride;
@@ -345,8 +354,8 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
break;
case 2:
for (i = 0; i < h; i++) {
- LE_CENTRIC_MUL(dst, src, scale, dc);
- LE_CENTRIC_MUL(dst + 2, src + 2, scale, dc);
+ for (int j = 0; j != 4; ++j)
+ dst[j] = scale * src[j] + dc;
if (scale)
src += stride;
dst += stride;
@@ -354,10 +363,8 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
break;
case 3:
for (i = 0; i < h; i++) {
- LE_CENTRIC_MUL(dst, src, scale, dc);
- LE_CENTRIC_MUL(dst + 2, src + 2, scale, dc);
- LE_CENTRIC_MUL(dst + 4, src + 4, scale, dc);
- LE_CENTRIC_MUL(dst + 6, src + 6, scale, dc);
+ for (int j = 0; j != 8; ++j)
+ dst[j] = scale * src[j] + dc;
if (scale)
src += stride;
dst += stride;
@@ -373,17 +380,50 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
- auto h = 1 << log2h, w = 1 << log2w;
auto &huff = _blockType[_version > 1][index];
auto code = huff.next(bs);
- int scale = 0;
+ int scale = 1;
int dc = 0;
- if (code == 5) {
+
+ auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
+ auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
+ auto pitch = frame->pitch / frame->format.bytesPerPixel;
+ if (code == 1) {
+ --log2h;
+ decode_pfrm_block(frame, x, y, log2w, log2h, bs, wordStream, byteStream);
+ int dy = 1 << log2h;
+ decode_pfrm_block(frame, x, y + dy, log2w, log2h, bs, wordStream, byteStream);
+ return;
+ } else if (code == 2) {
+ --log2w;
+ decode_pfrm_block(frame, x, y, log2w, log2h, bs, wordStream, byteStream);
+ int dx = 1 << log2w;
+ decode_pfrm_block(frame, x + dx, y, log2w, log2h, bs, wordStream, byteStream);
+ return;
+ } else if (code == 6) {
+ if (log2w) {
+ dst[0] = wordStream.readUint16LE();
+ dst[1] = wordStream.readUint16LE();
+ } else {
+ dst[0] = wordStream.readUint16LE();
+ dst[pitch] = wordStream.readUint16LE();
+ }
+ return;
+ }
+ if (code == 0) {
+ byte b = byteStream.readByte();
+ src += _mv[b];
+ } else if (code == 3 && _version >= 2) {
+ } else if (code == 4) {
+ src += _mv[byteStream.readByte()];
+ dc = wordStream.readSint16LE();
+ } else if (code == 5) {
+ scale = 0;
dc = wordStream.readSint16LE();
} else {
- error("invalid code %d", code);
+ error("invalid code %d (steps %u,%u)", code, log2w, log2h);
}
- mcdc(static_cast<uint16 *>(frame->getBasePtr(x, y)), static_cast<const uint16 *>(_frame->getBasePtr(x, y)), log2w, h, frame->pitch / frame->format.bytesPerPixel, scale, dc);
+ mcdc(dst, src, log2w, 1 << log2h, pitch, scale, dc);
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
@@ -406,11 +446,14 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::ScopedPtr<Graphics::Surface> frame(new Graphics::Surface());
frame->copyFrom(*_frame);
- for (int y = 0, h = _frame->h; y < h; y += 8) {
- for (int x = 0, w = _frame->w; x < w; x += 8) {
+ for (int y = 0, h = frame->h; y < h; y += 8) {
+ for (int x = 0, w = frame->w; x < w; x += 8) {
decode_pfrm_block(frame.get(), x, y, 3, 3, bitStream, wordStream, byteStream);
}
}
+ debug("bitStream: %u/%u", bitStream.getWordPos(), bitStreamData.size());
+ debug("wordStream: %lu/%lu", wordStream.pos(), wordStream.size());
+ debug("byteStream: %lu/%lu", byteStream.pos(), byteStream.size());
_frame = frame.release();
}
Commit: 9873f191188ce44ab917bcfedf318194af276d91
https://github.com/scummvm/scummvm/commit/9873f191188ce44ab917bcfedf318194af276d91
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:58+01:00
Commit Message:
VIDEO: 4MX: dump huffman tables
Changed paths:
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index ab3ad99c4f4..2467c69754d 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -105,6 +105,11 @@ public:
_blockType[1][1].initStatistics({8, 0, 4, 2, 1, 1});
_blockType[1][2].initStatistics({8, 4, 0, 2, 1, 1});
_blockType[1][3].initStatistics({8, 0, 0, 4, 2, 1, 1});
+ for (int i = 0; i != 2; ++i)
+ for (int j = 0; j != 4; ++j) {
+ debug("blockType[%d][%d]:", i, j);
+ _blockType[i][j].dump();
+ }
}
~FourXMVideoTrack();
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index ff5ae5714e8..e98465294a0 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -83,13 +83,40 @@ uint HuffmanDecoder::loadStatistics(const byte *huff, uint huffSize) {
return offset;
}
+void HuffmanDecoder::dumpImpl(uint code, uint size, uint index, uint ch) const {
+ if (index == _startEntry) {
+ debug("%u: code %u, size: %u", ch, code, size);
+ return;
+ }
+ for (uint i = 0; i != kLastEntry; ++i) {
+ auto &e = _table[i];
+ if (e.falseIdx == kMaxTableSize)
+ continue;
+
+ if (e.falseIdx == index) {
+ dumpImpl(code, size + 1, i, ch);
+ return;
+ }
+ if (e.trueIdx == index) {
+ dumpImpl(code | (1 << size), size + 1, i, ch);
+ return;
+ }
+ }
+}
+
+void HuffmanDecoder::dump() const {
+ for (uint ch = 0; ch < _numCodes; ++ch) {
+ dumpImpl(0, 0, ch, ch);
+ }
+}
+
void HuffmanDecoder::buildTable(uint numCodes) {
_numCodes = numCodes;
- _table[513].freq = 0x7FFF;
+ _table[kLastEntry].freq = 0x7FFF;
auto codeIdx = numCodes;
while (true) {
ushort idx = 0;
- ushort smallest2 = 513, smallest1 = 513;
+ ushort smallest2 = kLastEntry, smallest1 = kLastEntry;
while (idx < codeIdx) {
auto freq = _table[idx].freq;
if (freq != 0) {
@@ -104,7 +131,7 @@ void HuffmanDecoder::buildTable(uint numCodes) {
}
++idx;
}
- if (smallest2 == 513) {
+ if (smallest2 == kLastEntry) {
_startEntry = codeIdx - 1;
break;
}
@@ -114,7 +141,7 @@ void HuffmanDecoder::buildTable(uint numCodes) {
_table[codeIdx].trueIdx = smallest2;
++codeIdx;
}
- assert(codeIdx < 513);
+ assert(codeIdx < kLastEntry);
}
Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 0d644fe1421..8a070cd5d96 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -86,12 +86,14 @@ using LEByteBitStream = BitStream<byte, false>;
using BEByteBitStream = BitStream<byte, true>;
class HuffmanDecoder {
+ static constexpr uint kMaxTableSize = 514;
+ static constexpr uint kLastEntry = kMaxTableSize - 1;
struct HuffChar {
- uint freq;
- ushort falseIdx;
- ushort trueIdx;
+ uint freq = 0;
+ ushort falseIdx = kMaxTableSize;
+ ushort trueIdx = kMaxTableSize;
};
- HuffChar _table[514] = {};
+ HuffChar _table[kMaxTableSize] = {};
uint _startEntry = 0;
uint _numCodes = 0;
@@ -106,11 +108,14 @@ public:
template<typename BitStreamType>
uint next(BitStreamType &bs);
+ void dump() const;
+
private:
template<typename Word>
Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset);
void buildTable(uint numCodes);
+ void dumpImpl(uint code, uint size, uint index, uint ch) const;
};
void idct(int16_t block[64]);
Commit: 3f9718a541232a8db808786ab1bd131e5525987c
https://github.com/scummvm/scummvm/commit/3f9718a541232a8db808786ab1bd131e5525987c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:58+01:00
Commit Message:
VIDEO: 4XM: check streams overrun
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 2467c69754d..ae1f2c64da6 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -96,10 +96,12 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
public:
FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version), _frame(nullptr) {
+ /* FIXME: double check codes, some bits are swapped
_blockType[0][0].initStatistics({2, 1, 1, 2, 1, 1});
_blockType[0][1].initStatistics({2, 0, 2, 2, 1, 1});
_blockType[0][2].initStatistics({2, 2, 0, 2, 1, 1});
_blockType[0][3].initStatistics({2, 0, 0, 2, 2, 1, 1});
+ */
_blockType[1][0].initStatistics({16, 8, 4, 2, 1, 1});
_blockType[1][1].initStatistics({8, 0, 4, 2, 1, 1});
@@ -385,7 +387,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
- auto &huff = _blockType[_version > 1][index];
+ auto &huff = _blockType[_version > 1 ? 1 : 0][index];
auto code = huff.next(bs);
int scale = 1;
int dc = 0;
@@ -406,6 +408,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
decode_pfrm_block(frame, x + dx, y, log2w, log2h, bs, wordStream, byteStream);
return;
} else if (code == 6) {
+ assert(wordStream.pos() + 4 <= wordStream.size());
if (log2w) {
dst[0] = wordStream.readUint16LE();
dst[1] = wordStream.readUint16LE();
@@ -416,13 +419,18 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
return;
}
if (code == 0) {
+ assert(byteStream.pos() < byteStream.size());
byte b = byteStream.readByte();
src += _mv[b];
} else if (code == 3 && _version >= 2) {
+ return;
} else if (code == 4) {
+ assert(byteStream.pos() < byteStream.size());
+ assert(wordStream.pos() + 2 <= wordStream.size());
src += _mv[byteStream.readByte()];
dc = wordStream.readSint16LE();
} else if (code == 5) {
+ assert(wordStream.pos() + 2 <= wordStream.size());
scale = 0;
dc = wordStream.readSint16LE();
} else {
Commit: 514b71b76f2edb213f05dc85c10cbad8aff63e32
https://github.com/scummvm/scummvm/commit/514b71b76f2edb213f05dc85c10cbad8aff63e32
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:58+01:00
Commit Message:
VIDEO: 4XM: fix order/unit of bitstream for pframe
Changed paths:
video/4xm_decoder.cpp
video/4xm_utils.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index ae1f2c64da6..90f4032da56 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -131,7 +131,7 @@ public:
void decode_cfrm(Common::SeekableReadStream *stream);
private:
- void decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEByteBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
+ void decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::LEWordBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
Common::Rational getFrameRate() const override { return _frameRate; }
};
@@ -383,7 +383,7 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
}
} // namespace
-void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEByteBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::LEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
@@ -395,6 +395,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
auto pitch = frame->pitch / frame->format.bytesPerPixel;
+
if (code == 1) {
--log2h;
decode_pfrm_block(frame, x, y, log2w, log2h, bs, wordStream, byteStream);
@@ -453,7 +454,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::Array<byte> byteStreamData(byteStreamSize);
stream->read(byteStreamData.data(), byteStreamData.size());
- FourXM::BEByteBitStream bitStream(bitStreamData.data(), bitStreamData.size(), 0);
+ FourXM::LEWordBitStream bitStream(reinterpret_cast<const uint32 *>(bitStreamData.data()), bitStreamData.size() / 4, 0);
Common::MemoryReadStream wordStream(wordStreamData.data(), wordStreamData.size());
Common::MemoryReadStream byteStream(byteStreamData.data(), byteStreamData.size());
@@ -464,7 +465,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
decode_pfrm_block(frame.get(), x, y, 3, 3, bitStream, wordStream, byteStream);
}
}
- debug("bitStream: %u/%u", bitStream.getWordPos(), bitStreamData.size());
+ debug("bitStream: %u/%u", bitStream.getWordPos(), bitStreamData.size() / 4);
debug("wordStream: %lu/%lu", wordStream.pos(), wordStream.size());
debug("byteStream: %lu/%lu", byteStream.pos(), byteStream.size());
_frame = frame.release();
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 8a070cd5d96..d052b25f0a1 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -84,6 +84,7 @@ public:
};
using LEByteBitStream = BitStream<byte, false>;
using BEByteBitStream = BitStream<byte, true>;
+using LEWordBitStream = BitStream<uint32, false>;
class HuffmanDecoder {
static constexpr uint kMaxTableSize = 514;
Commit: fb9531ebc26f694d2614e37f7094964255eb3e94
https://github.com/scummvm/scummvm/commit/fb9531ebc26f694d2614e37f7094964255eb3e94
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:59+01:00
Commit Message:
VIDEO: 4XM: collect cframe
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 90f4032da56..6c8717b14c6 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -93,6 +93,7 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
Graphics::Surface *_frame;
FourXM::HuffmanDecoder _blockType[2][4] = {};
byte _mv[256];
+ Common::HashMap<byte, Common::Array<byte>> _cframes;
public:
FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version), _frame(nullptr) {
@@ -475,7 +476,20 @@ void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *st
stream->skip(4);
auto frameIdx = stream->readUint32LE();
auto frameSize = stream->readUint32LE();
+ auto streamSize = stream->size() - stream->pos();
debug("c-frame, frame id: %u, size: %u", frameIdx, frameSize);
+ auto &cframe = _cframes[frameIdx];
+ auto dstOffset = cframe.size();
+ cframe.resize(cframe.size() + streamSize);
+ stream->read(cframe.data() + dstOffset, streamSize);
+ if (cframe.size() >= frameSize) {
+ assert(_dec->_curFrame == frameIdx);
+ Common::MemoryReadStream ms(cframe.data(), cframe.size());
+ debug("PFRAME");
+ Common::hexdump(cframe.data(), cframe.size());
+ // decode_pfrm(&ms);
+ _cframes.erase(frameIdx);
+ }
}
void FourXMDecoder::FourXMVideoTrack::decode(uint32 tag, byte *buf, uint size) {
Commit: c3321f16718862ec8862e60660d36aa72629f465
https://github.com/scummvm/scummvm/commit/c3321f16718862ec8862e60660d36aa72629f465
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:59+01:00
Commit Message:
VIDEO: 4XM: read video trackIdx
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 6c8717b14c6..740ea2a0f74 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -253,7 +253,6 @@ static const int8_t size2index[4][4] = {
} // namespace
void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *stream) {
- stream->skip(4);
auto bitstreamSize = stream->readUint32LE();
Common::Array<byte> bitstreamData(bitstreamSize);
@@ -442,7 +441,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
- stream->skip(12);
+ stream->skip(8);
auto bitStreamSize = stream->readUint32LE();
auto wordStreamSize = stream->readUint32LE();
auto byteStreamSize = stream->readUint32LE();
@@ -473,7 +472,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
}
void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *stream) {
- stream->skip(4);
auto frameIdx = stream->readUint32LE();
auto frameSize = stream->readUint32LE();
auto streamSize = stream->size() - stream->pos();
@@ -523,7 +521,7 @@ void FourXMDecoder::decodeNextFrameImpl() {
while (_stream->pos() < frame.end) {
uint32 tag = _stream->readUint32BE();
uint32 size = _stream->readUint32LE();
- debug("%u: sub frame %s, %u bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos());
+ debug("%u: sub frame %s, 0x%x bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos());
auto pos = _stream->pos();
auto loadBuf = [this](uint bufSize) {
@@ -566,9 +564,11 @@ void FourXMDecoder::decodeNextFrameImpl() {
} break;
case MKTAG('i', 'f', 'r', 'm'):
case MKTAG('p', 'f', 'r', 'm'):
- case MKTAG('c', 'f', 'r', 'm'):
- _video->decode(tag, loadBuf(size), size);
- break;
+ case MKTAG('c', 'f', 'r', 'm'): {
+ auto trackIdx = _stream->readUint32LE();
+ if (trackIdx == 0)
+ _video->decode(tag, loadBuf(size - 4), size - 4);
+ } break;
default:
warning("unknown frame type %s", tagName(tag).c_str());
_stream->skip(size);
Commit: 82abc38095f8e67eff7d42fd9a7a554e28c2bbbf
https://github.com/scummvm/scummvm/commit/82abc38095f8e67eff7d42fd9a7a554e28c2bbbf
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:59+01:00
Commit Message:
VIDEO: 4XM: swap bitstream LE to BE
Changed paths:
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 740ea2a0f74..bade2e23639 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -132,7 +132,7 @@ public:
void decode_cfrm(Common::SeekableReadStream *stream);
private:
- void decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::LEWordBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
+ void decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEWordBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
Common::Rational getFrameRate() const override { return _frameRate; }
};
@@ -383,7 +383,7 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
}
} // namespace
-void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::LEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
@@ -454,7 +454,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::Array<byte> byteStreamData(byteStreamSize);
stream->read(byteStreamData.data(), byteStreamData.size());
- FourXM::LEWordBitStream bitStream(reinterpret_cast<const uint32 *>(bitStreamData.data()), bitStreamData.size() / 4, 0);
+ FourXM::BEWordBitStream bitStream(reinterpret_cast<const uint32 *>(bitStreamData.data()), bitStreamData.size() / 4, 0);
Common::MemoryReadStream wordStream(wordStreamData.data(), wordStreamData.size());
Common::MemoryReadStream byteStream(byteStreamData.data(), byteStreamData.size());
@@ -483,8 +483,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *st
if (cframe.size() >= frameSize) {
assert(_dec->_curFrame == frameIdx);
Common::MemoryReadStream ms(cframe.data(), cframe.size());
- debug("PFRAME");
- Common::hexdump(cframe.data(), cframe.size());
+ // debug("PFRAME");
+ // Common::hexdump(cframe.data(), cframe.size());
// decode_pfrm(&ms);
_cframes.erase(frameIdx);
}
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index e98465294a0..bba7772458e 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -39,6 +39,8 @@ uint HuffmanDecoder::next(BitStreamType &bs) {
}
template uint HuffmanDecoder::next(LEByteBitStream &bs);
template uint HuffmanDecoder::next(BEByteBitStream &bs);
+template uint HuffmanDecoder::next(LEWordBitStream &bs);
+template uint HuffmanDecoder::next(BEWordBitStream &bs);
template<typename Word>
Common::Array<byte> HuffmanDecoder::unpackStream(const byte *huff, uint huffSize, uint &offset) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index d052b25f0a1..4049672d2d0 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -85,6 +85,7 @@ public:
using LEByteBitStream = BitStream<byte, false>;
using BEByteBitStream = BitStream<byte, true>;
using LEWordBitStream = BitStream<uint32, false>;
+using BEWordBitStream = BitStream<uint32, true>;
class HuffmanDecoder {
static constexpr uint kMaxTableSize = 514;
Commit: 101c95e30bbc8d86ac5920207fbac57b6a111813
https://github.com/scummvm/scummvm/commit/101c95e30bbc8d86ac5920207fbac57b6a111813
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:29:59+01:00
Commit Message:
VIDEO: 4XM: drop version <= 1
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index bade2e23639..ffbce4d5949 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -91,28 +91,22 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
uint _w, _h;
uint16 _version = 0;
Graphics::Surface *_frame;
- FourXM::HuffmanDecoder _blockType[2][4] = {};
+ FourXM::HuffmanDecoder _blockType[4] = {};
byte _mv[256];
Common::HashMap<byte, Common::Array<byte>> _cframes;
public:
FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version), _frame(nullptr) {
- /* FIXME: double check codes, some bits are swapped
- _blockType[0][0].initStatistics({2, 1, 1, 2, 1, 1});
- _blockType[0][1].initStatistics({2, 0, 2, 2, 1, 1});
- _blockType[0][2].initStatistics({2, 2, 0, 2, 1, 1});
- _blockType[0][3].initStatistics({2, 0, 0, 2, 2, 1, 1});
- */
-
- _blockType[1][0].initStatistics({16, 8, 4, 2, 1, 1});
- _blockType[1][1].initStatistics({8, 0, 4, 2, 1, 1});
- _blockType[1][2].initStatistics({8, 4, 0, 2, 1, 1});
- _blockType[1][3].initStatistics({8, 0, 0, 4, 2, 1, 1});
- for (int i = 0; i != 2; ++i)
- for (int j = 0; j != 4; ++j) {
- debug("blockType[%d][%d]:", i, j);
- _blockType[i][j].dump();
- }
+ if (_version <= 1)
+ error("versions 0 and 1 are not supported");
+ _blockType[0].initStatistics({16, 8, 4, 2, 1, 1});
+ _blockType[1].initStatistics({8, 0, 4, 2, 1, 1});
+ _blockType[2].initStatistics({8, 4, 0, 2, 1, 1});
+ _blockType[3].initStatistics({8, 0, 0, 4, 2, 1, 1});
+ for (int i = 0; i != 4; ++i) {
+ debug("blockType[%d]:", i);
+ _blockType[i].dump();
+ }
}
~FourXMVideoTrack();
@@ -140,13 +134,10 @@ const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
if (!_frame) {
_frame = new Graphics::Surface();
_frame->create(_w, _h, getPixelFormat());
+ auto pitch = _frame->pitch / _frame->format.bytesPerPixel;
- for (uint i = 0; i < 256; i++) {
- if (_version > 1)
- _mv[i] = mv[i][0] + mv[i][1] * _frame->pitch / 2;
- else
- _mv[i] = (i & 15) - 8 + ((i >> 4) - 8) * _frame->pitch / 2;
- }
+ for (uint i = 0; i < 256; i++)
+ _mv[i] = mv[i][0] + mv[i][1] * pitch;
}
debug("decode next video frame");
_dec->decodeNextFrameImpl();
@@ -387,7 +378,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
- auto &huff = _blockType[_version > 1 ? 1 : 0][index];
+ auto &huff = _blockType[index];
auto code = huff.next(bs);
int scale = 1;
int dc = 0;
Commit: f36dd2988bb8646b85ad99afae59c6fcd0e81d8b
https://github.com/scummvm/scummvm/commit/f36dd2988bb8646b85ad99afae59c6fcd0e81d8b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:00+01:00
Commit Message:
VIDEO: 4XM: re-enable cfrm
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index ffbce4d5949..e1036ef606e 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -474,9 +474,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *st
if (cframe.size() >= frameSize) {
assert(_dec->_curFrame == frameIdx);
Common::MemoryReadStream ms(cframe.data(), cframe.size());
- // debug("PFRAME");
- // Common::hexdump(cframe.data(), cframe.size());
- // decode_pfrm(&ms);
+ decode_pfrm(&ms);
_cframes.erase(frameIdx);
}
}
Commit: bbef0a200844e8e2ec19a6b2146ddcc40fd10887
https://github.com/scummvm/scummvm/commit/bbef0a200844e8e2ec19a6b2146ddcc40fd10887
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:00+01:00
Commit Message:
VIDEO: 4XM: rewrite mcdc a bit, use template scaling parameter
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index e1036ef606e..f32a9d7adb0 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -326,50 +326,17 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
namespace {
-void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
- int h, int stride, int scale, unsigned dc) {
- int i;
- dc *= 0x10001;
-
- switch (log2w) {
- case 0:
- for (i = 0; i < h; i++) {
- for (int j = 0; j != 1; ++j)
- dst[j] = scale * src[j] + dc;
- if (scale)
- src += stride;
- dst += stride;
- }
- break;
- case 1:
- for (i = 0; i < h; i++) {
- for (int j = 0; j != 2; ++j)
- dst[j] = scale * src[j] + dc;
- if (scale)
- src += stride;
- dst += stride;
- }
- break;
- case 2:
- for (i = 0; i < h; i++) {
- for (int j = 0; j != 4; ++j)
- dst[j] = scale * src[j] + dc;
- if (scale)
- src += stride;
- dst += stride;
- }
- break;
- case 3:
- for (i = 0; i < h; i++) {
- for (int j = 0; j != 8; ++j)
- dst[j] = scale * src[j] + dc;
- if (scale)
- src += stride;
- dst += stride;
- }
- break;
- default:
- error("invalid log2w");
+template<bool Scale>
+void mcdc(uint16_t *__restrict__ dst, const uint16_t *__restrict__ src, int log2w,
+ int log2h, int stride, int dc) {
+ int h = 1 << log2h;
+ int w = 1 << log2w;
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; ++j)
+ dst[j] = Scale ? src[j] + dc : dc;
+ if (Scale)
+ src += stride;
+ dst += stride;
}
}
} // namespace
@@ -380,12 +347,13 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
assert(index >= 0);
auto &huff = _blockType[index];
auto code = huff.next(bs);
- int scale = 1;
+ bool scale = true;
int dc = 0;
auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
auto pitch = frame->pitch / frame->format.bytesPerPixel;
+ assert(_frame->pitch == frame->pitch);
if (code == 1) {
--log2h;
@@ -412,8 +380,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
}
if (code == 0) {
assert(byteStream.pos() < byteStream.size());
- byte b = byteStream.readByte();
- src += _mv[b];
+ src += _mv[byteStream.readByte()];
} else if (code == 3 && _version >= 2) {
return;
} else if (code == 4) {
@@ -423,12 +390,16 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
dc = wordStream.readSint16LE();
} else if (code == 5) {
assert(wordStream.pos() + 2 <= wordStream.size());
- scale = 0;
+ scale = false;
dc = wordStream.readSint16LE();
} else {
error("invalid code %d (steps %u,%u)", code, log2w, log2h);
}
- mcdc(dst, src, log2w, 1 << log2h, pitch, scale, dc);
+
+ if (scale)
+ mcdc<true>(dst, src, log2w, log2h, pitch, dc);
+ else
+ mcdc<false>(dst, src, log2w, log2h, pitch, dc);
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
Commit: f6b147d42473457adc86dc0cb8ffbf3629a56f3a
https://github.com/scummvm/scummvm/commit/f6b147d42473457adc86dc0cb8ffbf3629a56f3a
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:00+01:00
Commit Message:
VIDEO: 4XM: fix motion vector sign
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index f32a9d7adb0..b6f54e991fd 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -92,7 +92,7 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
uint16 _version = 0;
Graphics::Surface *_frame;
FourXM::HuffmanDecoder _blockType[4] = {};
- byte _mv[256];
+ int _mv[256];
Common::HashMap<byte, Common::Array<byte>> _cframes;
public:
Commit: eae51786edf3e6a285c5e24dc52d02a16efef1a8
https://github.com/scummvm/scummvm/commit/eae51786edf3e6a285c5e24dc52d02a16efef1a8
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:01+01:00
Commit Message:
PHOENIXVR: remove custom dct, use 4xm
Changed paths:
R engines/phoenixvr/dct.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/dct.h b/engines/phoenixvr/dct.h
deleted file mode 100644
index 69a616b03d4..00000000000
--- a/engines/phoenixvr/dct.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* 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/>.
- *
- */
-
-#ifndef PHOENIXVR_DCT_H
-#define PHOENIXVR_DCT_H
-
-#include "common/scummsys.h"
-#include <cmath>
-
-namespace PhoenixVR {
-
-template<uint Bits>
-class DCT2DIII {
- static constexpr uint N = 1 << Bits;
- static constexpr uint N2 = 1 << (Bits - 1);
- static constexpr uint N4 = 1 << (Bits - 2);
-
- float _cos[N2];
-
- float dctCos(uint idx) const {
- return _cos[idx % N2];
- }
- float dctCos(uint i, uint j) const {
- return dctCos(((i << 1) + 1) * j);
- }
-
-public:
- DCT2DIII() {
- for (uint i = 0; i != N2; ++i) {
- _cos[i] = std::cos(i * M_PI / N4);
- }
- }
-
- void calc(const float *src, float *dst) const {
- const float sqrt12 = M_SQRT1_2;
- auto *dstPtr = dst;
- for (byte y = 0; y < 8; ++y) {
- for (byte x = 0; x < 8; ++x) {
- float row = 0;
- float cosX0 = dctCos(x, 0);
- for (byte v = 0; v < 8; ++v) {
- auto *src_col = src + (v << 3);
- float col = *src_col++ * cosX0 * sqrt12;
- for (byte u = 1; u < 8; ++u) {
- col += *src_col++ * dctCos(x, u);
- }
- row += col * dctCos(y, v) * (v != 0 ? 1 : sqrt12);
- }
- *dstPtr++ = row / 4;
- }
- }
- }
-};
-
-} // namespace PhoenixVR
-
-#endif
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 11937652224..5c7fb257882 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -30,9 +30,7 @@
#include "graphics/yuv_to_rgb.h"
#include "image/bmp.h"
#include "math/vector3d.h"
-#include "phoenixvr/dct.h"
#include "phoenixvr/dct_tables.h"
-#include "phoenixvr/phoenixvr.h"
#include "video/4xm_utils.h"
namespace PhoenixVR {
@@ -74,7 +72,7 @@ struct Quantisation {
v = 8;
}
v *= Q[i];
- v >>= 13;
+ v >>= 12;
quantY[i] = v;
}
for (uint i = 0; i != 64; ++i) {
@@ -85,7 +83,7 @@ struct Quantisation {
v = 8;
}
v *= Q[i];
- v >>= 13;
+ v >>= 12;
quantCbCr[i] = v;
}
}
@@ -95,7 +93,6 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
Quantisation quant(quality);
auto decoded = Video::FourXM::HuffmanDecoder::unpack(huff, huffSize, 1);
uint decodedOffset = 0;
- static const DCT2DIII<6> dct;
const uint planePitch = prefix ? 8 : pic.w;
const uint planeSize = prefix ? prefix->size() * 64 : planePitch * pic.h;
@@ -106,10 +103,10 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
uint x0 = 0, y0 = 0;
uint blockIdx = 0;
while (decodedOffset < decoded.size()) {
- float ac[64] = {};
+ int16 ac[64] = {};
int8 dc8 = dcBs.readUInt(8);
auto *iquant = channel ? quant.quantCbCr : quant.quantY;
- ac[0] = 1.0f * iquant[0] * (int)dc8;
+ ac[0] = iquant[0] * dc8 + 0x80 * 8 * 8;
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
@@ -122,18 +119,18 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
idx += h;
if (l && idx < 64) {
auto ac_idx = ZIGZAG[idx];
- ac[ac_idx] = 1.0f * iquant[ac_idx] * acBs.readInt(l);
+ ac[ac_idx] = iquant[ac_idx] * acBs.readInt(l);
++idx;
}
}
}
- float out[64];
- dct.calc(ac, out);
+
+ Video::FourXM::idct(ac);
auto *dst = prefix ? planes.data() + channel * planeSize + blockIdx * 64 : planes.data() + channel * planeSize + y0 * planePitch + x0;
- const auto *src = out;
+ const auto *src = ac;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++ / 4 + 128;
+ int v = *src++;
v = clip(v);
*dst++ = v;
}
Commit: 027e2a087b1f90349162c8b52479a583fbe5761b
https://github.com/scummvm/scummvm/commit/027e2a087b1f90349162c8b52479a583fbe5761b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:01+01:00
Commit Message:
PHOENIXVR: fix inventory and right click
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 532803997a4..815686fed1e 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -479,10 +479,10 @@ struct LockKey : public Script::Command {
code = Common::KeyCode::KEYCODE_F10;
break;
case 11:
- code = Common::KeyCode::KEYCODE_F11;
+ code = Common::KeyCode::KEYCODE_RETURN;
break;
case 12:
- code = Common::KeyCode::KEYCODE_F12;
+ code = Common::KeyCode::KEYCODE_TAB;
break;
default:
warning("unknown lock key %d", idx);
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 652d60e3162..3b790f4f441 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -436,6 +436,12 @@ Common::Error PhoenixVREngine::run() {
}
}
} break;
+ case Common::EVENT_RBUTTONUP: {
+ debug("right click");
+ auto it = _keys.find(Common::KeyCode::KEYCODE_TAB);
+ if (it != _keys.end())
+ goToWarp(it->_value);
+ }
default:
break;
}
Commit: d4c71c988df871d31823f157f2239c8a4a70d01f
https://github.com/scummvm/scummvm/commit/d4c71c988df871d31823f157f2239c8a4a70d01f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:01+01:00
Commit Message:
PHOENIXVR: return non-existing tests
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 3b790f4f441..544e25f98b0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -352,8 +352,11 @@ void PhoenixVREngine::tick(float dt) {
Script::ExecutionContext ctx;
debug("execute warp script %s", _warp->vrFile.c_str());
- auto &test = _warp->getDefaultTest();
- test->scope.exec(ctx);
+ auto test = _warp->getDefaultTest();
+ if (test)
+ test->scope.exec(ctx);
+ else
+ warning("no default script!");
}
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 0f2a2610e20..ee062c11436 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -243,13 +243,14 @@ void Script::Warp::setText(int idx, const TestPtr &text) {
if (idx < -1)
error("test id must be >= -1");
uint realIdx = idx + 1;
- if (realIdx + 1 > tests.size())
+ if (realIdx >= tests.size())
tests.resize(realIdx + 1);
tests[realIdx] = text;
}
-const Script::TestPtr &Script::Warp::getTest(int idx) const {
- return tests[idx + 1];
+Script::TestPtr Script::Warp::getTest(int idx) const {
+ idx += 1;
+ return idx < (int)tests.size() ? tests[idx] : Script::TestPtr{};
}
Script::Script(Common::SeekableReadStream &s) {
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 329f04e330b..676c701002d 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -70,8 +70,8 @@ public:
void parseLine(const Common::String &line, uint lineno);
void setText(int idx, const TestPtr &text);
- const TestPtr &getTest(int idx) const;
- const TestPtr &getDefaultTest() const {
+ TestPtr getTest(int idx) const;
+ TestPtr getDefaultTest() const {
return getTest(-1);
}
};
Commit: abc03a48025add74b42a205cef942c7536b7450e
https://github.com/scummvm/scummvm/commit/abc03a48025add74b42a205cef942c7536b7450e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:01+01:00
Commit Message:
PHOENIXVR: implement return/resetLockKey
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 815686fed1e..ccd87092318 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -395,8 +395,8 @@ struct End : public Script::Command {
struct Return : public Script::Command {
Return() {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("return");
ctx.running = false;
+ g_engine->returnToWarp();
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 544e25f98b0..46adfdd3af3 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -95,9 +95,21 @@ void PhoenixVREngine::end() {
quitGame();
}
-void PhoenixVREngine::goToWarp(const Common::String &warp) {
+void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
debug("gotowarp %s", warp.c_str());
_nextWarp = warp;
+ if (savePrev) {
+ assert(_warp);
+ _prevWarp = _warp->vrFile;
+ }
+}
+
+void PhoenixVREngine::returnToWarp() {
+ if (_prevWarp.empty()) {
+ warning("return: no previous warp");
+ }
+ _nextWarp = _prevWarp;
+ _prevWarp.clear();
}
Script::ConstWarpPtr PhoenixVREngine::getWarp(const Common::String &name) {
@@ -193,7 +205,7 @@ void PhoenixVREngine::playAnimation(const Common::String &name, const Common::St
}
void PhoenixVREngine::resetLockKey() {
- _keys.clear();
+ _prevWarp.clear(); // original game does only this o_O
}
void PhoenixVREngine::lockKey(Common::KeyCode code, const Common::String &warp) {
@@ -412,7 +424,7 @@ Common::Error PhoenixVREngine::run() {
auto it = _keys.find(event.kbd.keycode);
if (it != _keys.end()) {
debug("matched code %d", static_cast<int>(event.kbd.keycode));
- goToWarp(it->_value);
+ goToWarp(it->_value, true);
} else if (event.kbd.ascii == ' ') {
if (_movie) {
_movie->stop();
@@ -443,7 +455,7 @@ Common::Error PhoenixVREngine::run() {
debug("right click");
auto it = _keys.find(Common::KeyCode::KEYCODE_TAB);
if (it != _keys.end())
- goToWarp(it->_value);
+ goToWarp(it->_value, true);
}
default:
break;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 5c84b711da0..59dc340cef5 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -94,7 +94,8 @@ public:
// Script API
void setNextScript(const Common::String &path);
- void goToWarp(const Common::String &warp);
+ void goToWarp(const Common::String &warp, bool savePrev = false);
+ void returnToWarp();
void setCursorDefault(int idx, const Common::String &path);
void setCursor(const Common::String &path, const Common::String &warp, int idx);
void hideCursor(const Common::String &warp, int idx);
@@ -149,6 +150,7 @@ private:
Common::Point _mousePos;
Common::String _nextScript;
Common::String _nextWarp;
+ Common::String _prevWarp;
struct KeyCodeHash : public Common::UnaryFunction<Common::KeyCode, uint> {
uint operator()(Common::KeyCode val) const { return static_cast<uint>(val); }
Commit: 67c109468129f48c2db7930a957f3581d439e777
https://github.com/scummvm/scummvm/commit/67c109468129f48c2db7930a957f3581d439e777
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:02+01:00
Commit Message:
PHOENIXVR: reduce log noise from vr code
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_utils.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 5c7fb257882..f3453609bfc 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -200,12 +200,10 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
error("wrong VR magic");
}
auto fsize = s.readUint32LE();
- debug("file size = %08x", fsize);
while (s.pos() < fsize) {
auto chunkPos = s.pos();
auto chunkId = s.readUint32LE();
auto chunkSize = s.readUint32LE();
- debug("chunk %08x %u at %08lx", chunkId, chunkSize, chunkPos);
bool pic2d = chunkId == CHUNK_STATIC_2D;
bool pic3d = chunkId == CHUNK_STATIC_3D;
if (pic2d || pic3d) {
@@ -216,8 +214,6 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
s.read(vrData.data(), vrData.size());
auto huffSize = READ_LE_UINT32(vrData.data());
- auto unpHuffSize = READ_LE_UINT32(vrData.data() + 4);
- debug("static picture header, quality: %u, packed data size: %u, huff size: %08x, unpacked huff size: %u", quality, dataSize, huffSize, unpHuffSize);
if (vr._pic)
error("2d/3d picture in the same file");
vr._pic.reset(new Graphics::Surface());
@@ -335,7 +331,6 @@ void VR::playAnimation(const Common::String &name) {
}
const auto *data = it->_value.data();
auto prefixSize = READ_LE_UINT32(data);
- debug("prefixes: %u", prefixSize);
Common::Array<uint32> prefixData(prefixSize);
uint offset = 4;
for (uint i = 0; i != prefixSize; ++i) {
@@ -343,13 +338,9 @@ void VR::playAnimation(const Common::String &name) {
offset += 4;
}
auto quality = READ_LE_UINT32(data + offset);
- auto size = READ_LE_UINT32(data + offset + 4);
- debug("quality: %u, size: %u", quality, size);
offset += 8;
auto huffSize = READ_LE_UINT32(data + offset);
- auto unpHuffSize = READ_LE_UINT32(data + offset + 4);
offset += 8;
- debug("huffman size: %u, unpacked size: %u", huffSize, unpHuffSize);
auto *acPtr = data + offset + huffSize + 4;
auto dcOffset = READ_LE_UINT32(data + offset + huffSize);
auto *dcPtr = data + offset + huffSize + 8 + dcOffset;
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index bba7772458e..84ebe3bb309 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -158,7 +158,6 @@ Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint
default:
error("invalid word size");
}
- debug("decoded %u bytes at %08x", decoded.size(), offset);
if (wordSize == 1) {
assert(offset == huffSize); // must decode to the end
}
Commit: a17647055ba28770f3e4930b78117c3395813e4e
https://github.com/scummvm/scummvm/commit/a17647055ba28770f3e4930b78117c3395813e4e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:02+01:00
Commit Message:
PHOENIXVR: add CD directory to SearchMan
(there's duplicate region files there, e.g. table in study
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 46adfdd3af3..801bd53ab3c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -54,9 +54,6 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_angleY(-M_PI_2),
_mixer(syst->getMixer()) {
g_engine = this;
- auto path = Common::FSNode(ConfMan.getPath("path"));
- SearchMan.addSubDirectoryMatching(path, "NecroES/Data", 1, 1, true);
- SearchMan.addSubDirectoryMatching(path, "NecroES/Data2", 2, 1, true);
}
PhoenixVREngine::~PhoenixVREngine() {
@@ -71,12 +68,6 @@ Common::String PhoenixVREngine::getGameId() const {
return _gameDescription->gameId;
}
-Common::String PhoenixVREngine::resolvePath(const Common::String &path) {
- auto resolved = removeDrive(path);
- resolved.replace('\\', '/');
- return resolved;
-}
-
Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
if (path.size() < 2 || path[1] != ':')
return path;
@@ -84,9 +75,19 @@ Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
return path.substr(2);
}
-void PhoenixVREngine::setNextScript(const Common::String &path) {
- _nextScript = resolvePath(path);
- debug("setNextScript %s", _nextScript.c_str());
+void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
+ debug("setNextScript %s", nextScript.c_str());
+ auto nextPath = Common::Path(removeDrive(nextScript), '\\');
+ auto parentDir = nextPath.getParent();
+ _nextScript = nextPath.getLastComponent();
+ auto path = ConfMan.getPath("path");
+ auto dataDir = path;
+ dataDir.appendInPlace(Common::Path("/"));
+ dataDir.appendInPlace(parentDir);
+
+ SearchMan.clear();
+ debug("adding %s to search man", dataDir.toString().c_str());
+ SearchMan.addDirectory(dataDir, 0, 1, true);
}
void PhoenixVREngine::end() {
@@ -316,12 +317,10 @@ void PhoenixVREngine::tick(float dt) {
_mixer->setChannelBalance(sound.handle, balance);
}
if (!_nextScript.empty()) {
- debug("loading script from %s", _nextScript.c_str());
+ debug("loading script from %s", _nextScript.toString().c_str());
auto nextScript = Common::move(_nextScript);
_nextScript.clear();
- nextScript = removeDrive(nextScript);
-
Common::File file;
Common::Path nextPath(nextScript);
if (file.open(nextPath)) {
@@ -331,7 +330,7 @@ void PhoenixVREngine::tick(float dt) {
pakFile = pakFile.removeExtension().append(".pak");
file.open(pakFile);
if (!file.isOpen())
- error("can't open script file %s", nextScript.c_str());
+ error("can't open script file %s", nextScript.toString().c_str());
Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
_script.reset(new Script(*scriptStream));
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 59dc340cef5..0b1e334f5fa 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -137,7 +137,6 @@ public:
private:
static Common::String removeDrive(const Common::String &path);
- static Common::String resolvePath(const Common::String &path);
Graphics::Surface *loadSurface(const Common::String &path);
void paint(Graphics::Surface &src, Common::Point dst);
PointF currentVRPos() const {
@@ -148,7 +147,7 @@ private:
private:
Common::Point _mousePos;
- Common::String _nextScript;
+ Common::Path _nextScript;
Common::String _nextWarp;
Common::String _prevWarp;
Commit: 9e954b4b238da09b66b277945bb5be2c5c544f88
https://github.com/scummvm/scummvm/commit/9e954b4b238da09b66b277945bb5be2c5c544f88
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:02+01:00
Commit Message:
PHOENIXVR: store original var order
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 801bd53ab3c..15406917c85 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -395,11 +395,13 @@ Common::Error PhoenixVREngine::run() {
_screenCenter = _screen->getBounds().center();
{
Common::File vars;
- if (vars.open(Common::Path("variable.txt"))) {
- while (!vars.eos()) {
- auto var = vars.readLine();
- declareVariable(var);
- }
+ if (!vars.open(Common::Path("variable.txt")))
+ error("can't read variable.txt");
+
+ while (!vars.eos()) {
+ auto var = vars.readLine();
+ declareVariable(var);
+ _variableOrder.push_back(Common::move(var));
}
}
setNextScript("script.lst");
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 0b1e334f5fa..0245f7ccbf8 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -156,6 +156,7 @@ private:
};
Common::HashMap<Common::KeyCode, Common::String, KeyCodeHash> _keys;
+ Common::Array<Common::String> _variableOrder;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
struct Sound {
Audio::SoundHandle handle;
Commit: 481f6fadc2c15f386eccf4e96b24e7f5a9c16822
https://github.com/scummvm/scummvm/commit/481f6fadc2c15f386eccf4e96b24e7f5a9c16822
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:03+01:00
Commit Message:
PHOENIXVR: loading original save (semi stub)
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index ccd87092318..a07100a9fc3 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -263,7 +263,7 @@ struct LoadSave_Load : public Script::Command {
LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Load %d", slot);
+ g_engine->loadSaveSlot(slot);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 15406917c85..2f80221bb3c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -26,6 +26,7 @@
#include "common/config-manager.h"
#include "common/events.h"
#include "common/file.h"
+#include "common/memstream.h"
#include "common/savefile.h"
#include "common/scummsys.h"
#include "common/system.h"
@@ -33,7 +34,6 @@
#include "graphics/framelimiter.h"
#include "graphics/surface.h"
#include "image/pcx.h"
-#include "math/utils.h"
#include "phoenixvr/console.h"
#include "phoenixvr/pakf.h"
#include "phoenixvr/region_set.h"
@@ -506,6 +506,46 @@ bool PhoenixVREngine::testSaveSlot(int idx) const {
return _saveFileMan->exists(getSaveStateName(idx));
}
+void PhoenixVREngine::loadSaveSlot(int idx) {
+ Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
+ if (!slot) {
+ warning("loadSaveSlot: invalid save slot %d", idx);
+ return;
+ }
+ auto state = loadGameStateObject(slot.get());
+ Common::MemoryReadStream ms(state.state.data(), state.state.size());
+ Common::hexdump(state.state.data(), state.state.size());
+
+ auto angleX = ms.readSint32LE();
+ auto angleY = ms.readSint32LE();
+ auto soundVolumnPanY = ms.readSint32LE();
+ auto soundVolumePanX = ms.readSint32LE();
+ auto angleXMax = ms.readSint32LE();
+ auto angleYMin = ms.readSint32LE();
+ auto angleYMax = ms.readSint32LE();
+ auto currentWarpIdx = ms.readUint32LE();
+ auto currentWarpTests = ms.readUint32LE();
+ auto printText = ms.readString(0, 257);
+ auto text = ms.readString(0, 257);
+ debug("angle: %d %d, sound pan: %d %d, angle X max %d, angle Y range %d %d, warp: %u, tests: %u",
+ angleX, angleY, soundVolumePanX, soundVolumnPanY,
+ angleXMax, angleYMin, angleYMax,
+ currentWarpIdx, currentWarpTests);
+ for (auto &vr : _script->getWarpNames()) {
+ auto warp = getWarp(vr);
+ auto &tests = warp->tests;
+ for (uint testIdx = 1; testIdx < tests.size(); ++testIdx) {
+ auto cursor = ms.readString(0, 257);
+ debug("warp %s.%d: %s", vr.c_str(), testIdx, cursor.c_str());
+ }
+ }
+ debug("vars at %08lx", ms.pos());
+ for (auto &name : _variableOrder) {
+ auto value = ms.readUint32LE();
+ debug("var %s: %u", name.c_str(), value);
+ }
+}
+
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
if (!slot)
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 0245f7ccbf8..d71ffd595b1 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -133,6 +133,7 @@ public:
}
bool testSaveSlot(int idx) const;
+ void loadSaveSlot(int idx);
void drawSlot(int idx, int face, int x, int y);
private:
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index ee062c11436..d903027382a 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -239,18 +239,9 @@ void Script::Scope::exec(ExecutionContext &ctx) const {
}
}
-void Script::Warp::setText(int idx, const TestPtr &text) {
- if (idx < -1)
- error("test id must be >= -1");
- uint realIdx = idx + 1;
- if (realIdx >= tests.size())
- tests.resize(realIdx + 1);
- tests[realIdx] = text;
-}
-
Script::TestPtr Script::Warp::getTest(int idx) const {
- idx += 1;
- return idx < (int)tests.size() ? tests[idx] : Script::TestPtr{};
+ auto it = Common::find_if(tests.begin(), tests.end(), [&](const TestPtr &test) { return test->idx == idx; });
+ return it != tests.end() ? *it : Script::TestPtr{};
}
Script::Script(Common::SeekableReadStream &s) {
@@ -281,6 +272,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
_currentWarp.reset(new Warp{vr, test, {}});
_warpsIndex[vr] = _warps.size();
_warps.push_back(_currentWarp);
+ _warpNames.push_back(vr);
} else if (p.maybe("test]=")) {
if (!_currentWarp)
error("test without warp");
@@ -288,7 +280,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (!_currentWarp)
error("text must have parent wrap section");
_currentTest.reset(new Test{idx, {}});
- _currentWarp->setText(idx, _currentTest);
+ _currentWarp->tests.push_back(_currentTest);
} else {
error("invalid [] directive on line %u: %s", lineno, line.c_str());
}
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 676c701002d..da25635a3c4 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -69,7 +69,6 @@ public:
Common::Array<TestPtr> tests;
void parseLine(const Common::String &line, uint lineno);
- void setText(int idx, const TestPtr &text);
TestPtr getTest(int idx) const;
TestPtr getDefaultTest() const {
return getTest(-1);
@@ -81,6 +80,7 @@ public:
private:
Common::HashMap<Common::String, uint, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _warpsIndex;
+ Common::Array<Common::String> _warpNames;
Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
TestPtr _currentTest;
@@ -97,6 +97,10 @@ public:
ConstWarpPtr getWarp(const Common::String &name) const;
ConstWarpPtr getInitScript() const;
+
+ const Common::Array<Common::String> &getWarpNames() const {
+ return _warpNames;
+ }
};
} // namespace PhoenixVR
Commit: c32324a4079db671d061a28233c2dac09043e7e7
https://github.com/scummvm/scummvm/commit/c32324a4079db671d061a28233c2dac09043e7e7
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:03+01:00
Commit Message:
PHOENIXVR: set next script while loading
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 2f80221bb3c..1348f3bb0a5 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -90,6 +90,26 @@ void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
SearchMan.addDirectory(dataDir, 0, 1, true);
}
+void PhoenixVREngine::loadNextScript() {
+ debug("loading script from %s", _nextScript.toString().c_str());
+ auto nextScript = Common::move(_nextScript);
+ _nextScript.clear();
+
+ Common::File file;
+ const Common::Path &nextPath(nextScript);
+ if (file.open(nextPath)) {
+ _script.reset(new Script(file));
+ } else {
+ auto pakFile = nextPath;
+ pakFile = pakFile.removeExtension().append(".pak");
+ file.open(pakFile);
+ if (!file.isOpen())
+ error("can't open script file %s", nextScript.toString().c_str());
+ Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
+ _script.reset(new Script(*scriptStream));
+ }
+}
+
void PhoenixVREngine::end() {
debug("end");
if (_nextScript.empty())
@@ -317,23 +337,7 @@ void PhoenixVREngine::tick(float dt) {
_mixer->setChannelBalance(sound.handle, balance);
}
if (!_nextScript.empty()) {
- debug("loading script from %s", _nextScript.toString().c_str());
- auto nextScript = Common::move(_nextScript);
- _nextScript.clear();
-
- Common::File file;
- Common::Path nextPath(nextScript);
- if (file.open(nextPath)) {
- _script.reset(new Script(file));
- } else {
- auto pakFile = nextPath;
- pakFile = pakFile.removeExtension().append(".pak");
- file.open(pakFile);
- if (!file.isOpen())
- error("can't open script file %s", nextScript.toString().c_str());
- Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
- _script.reset(new Script(*scriptStream));
- }
+ loadNextScript();
goToWarp(_script->getInitScript()->vrFile);
}
if (!_nextWarp.empty()) {
@@ -513,6 +517,13 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
return;
}
auto state = loadGameStateObject(slot.get());
+
+ setNextScript(state.script);
+ // keep it alive until loading finishes.
+ auto currentScript = Common::move(_script);
+ assert(!_nextScript.empty());
+ loadNextScript();
+
Common::MemoryReadStream ms(state.state.data(), state.state.size());
Common::hexdump(state.state.data(), state.state.size());
@@ -531,6 +542,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
angleX, angleY, soundVolumePanX, soundVolumnPanY,
angleXMax, angleYMin, angleYMax,
currentWarpIdx, currentWarpTests);
+
for (auto &vr : _script->getWarpNames()) {
auto warp = getWarp(vr);
auto &tests = warp->tests;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index d71ffd595b1..3e48438970a 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -145,6 +145,7 @@ private:
}
void tick(float dt);
void tickTimer(float dt);
+ void loadNextScript();
private:
Common::Point _mousePos;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index d903027382a..4dd82fbb789 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -354,6 +354,10 @@ Script::ConstWarpPtr Script::getWarp(const Common::String &name) const {
return _warps[idx];
}
+Script::ConstWarpPtr Script::getWarp(uint idx) const {
+ return idx < _warps.size() ? Script::ConstWarpPtr{_warps[idx]} : Script::ConstWarpPtr{};
+}
+
Script::ConstWarpPtr Script::getInitScript() const {
return _warps.front();
}
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index da25635a3c4..74b6b4fdf97 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -96,6 +96,7 @@ public:
~Script();
ConstWarpPtr getWarp(const Common::String &name) const;
+ ConstWarpPtr getWarp(uint index) const;
ConstWarpPtr getInitScript() const;
const Common::Array<Common::String> &getWarpNames() const {
Commit: 3bd88d48a2585e81347f0fa939aa0ef26345d617
https://github.com/scummvm/scummvm/commit/3bd88d48a2585e81347f0fa939aa0ef26345d617
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:03+01:00
Commit Message:
PHOENIXVR: implement loading, change next/prev to indices
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index a07100a9fc3..41a4d35a9cc 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -254,7 +254,9 @@ struct LoadSave_Context_Restored : public Script::Command {
LoadSave_Context_Restored(const Common::Array<Common::String> &args) : progress(args[0]), done(args[1]) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Context_Restored %s %s", progress.c_str(), done.c_str());
+ debug("LoadSave_Context_Restored: semi-stub: can be used to report that loading is in a progress");
+ g_engine->setVariable(progress, 0);
+ g_engine->setVariable(done, 1);
}
};
@@ -263,6 +265,7 @@ struct LoadSave_Load : public Script::Command {
LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Load %d", slot);
g_engine->loadSaveSlot(slot);
}
};
@@ -387,6 +390,7 @@ struct Set : public Script::Command {
struct End : public Script::Command {
End() {}
void exec(Script::ExecutionContext &ctx) const override {
+ debug("end");
ctx.running = false;
g_engine->end();
}
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1348f3bb0a5..c387d09c5af 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -118,23 +118,21 @@ void PhoenixVREngine::end() {
void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
debug("gotowarp %s", warp.c_str());
- _nextWarp = warp;
+ _nextWarp = _script->getWarp(warp);
if (savePrev) {
assert(_warp);
- _prevWarp = _warp->vrFile;
+ _prevWarp = _script->getWarp(_warp->vrFile);
+ assert(_prevWarp >= 0);
}
}
void PhoenixVREngine::returnToWarp() {
- if (_prevWarp.empty()) {
+ if (_prevWarp < 0) {
warning("return: no previous warp");
}
+ debug("returning to previous warp: %d", _prevWarp);
_nextWarp = _prevWarp;
- _prevWarp.clear();
-}
-
-Script::ConstWarpPtr PhoenixVREngine::getWarp(const Common::String &name) {
- return _script->getWarp(name);
+ _prevWarp = -1;
}
const Region *PhoenixVREngine::getRegion(int idx) const {
@@ -226,7 +224,7 @@ void PhoenixVREngine::playAnimation(const Common::String &name, const Common::St
}
void PhoenixVREngine::resetLockKey() {
- _prevWarp.clear(); // original game does only this o_O
+ _prevWarp = -1; // original game does only this o_O
}
void PhoenixVREngine::lockKey(Common::KeyCode code, const Common::String &warp) {
@@ -340,9 +338,9 @@ void PhoenixVREngine::tick(float dt) {
loadNextScript();
goToWarp(_script->getInitScript()->vrFile);
}
- if (!_nextWarp.empty()) {
+ if (_nextWarp >= 0) {
_warp = _script->getWarp(_nextWarp);
- _nextWarp.clear();
+ _nextWarp = -1;
debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
@@ -525,7 +523,6 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
loadNextScript();
Common::MemoryReadStream ms(state.state.data(), state.state.size());
- Common::hexdump(state.state.data(), state.state.size());
auto angleX = ms.readSint32LE();
auto angleY = ms.readSint32LE();
@@ -534,7 +531,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto angleXMax = ms.readSint32LE();
auto angleYMin = ms.readSint32LE();
auto angleYMax = ms.readSint32LE();
- auto currentWarpIdx = ms.readUint32LE();
+ auto currentWarpIdx = ms.readSint32LE();
auto currentWarpTests = ms.readUint32LE();
auto printText = ms.readString(0, 257);
auto text = ms.readString(0, 257);
@@ -543,12 +540,17 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
angleXMax, angleYMin, angleYMax,
currentWarpIdx, currentWarpTests);
+ _nextWarp = currentWarpIdx;
+
for (auto &vr : _script->getWarpNames()) {
- auto warp = getWarp(vr);
+ auto warpIdx = _script->getWarp(vr);
+ assert(warpIdx >= 0);
+ auto warp = _script->getWarp(warpIdx);
+ assert(warp);
auto &tests = warp->tests;
- for (uint testIdx = 1; testIdx < tests.size(); ++testIdx) {
+ for (uint testIdx = 0; testIdx < tests.size(); ++testIdx) {
auto cursor = ms.readString(0, 257);
- debug("warp %s.%d: %s", vr.c_str(), testIdx, cursor.c_str());
+ // debug("warp %s.%d: %s", vr.c_str(), testIdx, cursor.c_str());
}
}
debug("vars at %08lx", ms.pos());
@@ -556,6 +558,17 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto value = ms.readUint32LE();
debug("var %s: %u", name.c_str(), value);
}
+ debug("vars end at %08lx", ms.pos());
+ auto currentSubroutine = ms.readSint32LE();
+ _prevWarp = ms.readSint32LE();
+ debug("currentSubroutine %d, prev warp %d", currentSubroutine, _prevWarp);
+ for (uint i = 0; i != 12; ++i) {
+ auto lockKey = ms.readString(0, 257);
+ debug("lockKey %d %s", i, lockKey.c_str());
+ }
+ auto music = ms.readString(0, 257);
+ auto musicVolume = ms.readUint32LE();
+ debug("current music %s, volume: %u", music.c_str(), musicVolume);
}
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 3e48438970a..c51791618a2 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -111,8 +111,6 @@ public:
void executeTest(int idx);
void end();
- Script::ConstWarpPtr getWarp(const Common::String &name);
- Script::ConstWarpPtr getCurrentWarp() { return _warp; }
const Region *getRegion(int idx) const;
uint numCursors() const {
@@ -150,8 +148,9 @@ private:
private:
Common::Point _mousePos;
Common::Path _nextScript;
- Common::String _nextWarp;
- Common::String _prevWarp;
+ Script::ConstWarpPtr _warp;
+ int _nextWarp = -1;
+ int _prevWarp = -1;
struct KeyCodeHash : public Common::UnaryFunction<Common::KeyCode, uint> {
uint operator()(Common::KeyCode val) const { return static_cast<uint>(val); }
@@ -168,7 +167,6 @@ private:
Common::HashMap<Common::String, Sound, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _sounds;
Common::ScopedPtr<Script> _script;
- Script::ConstWarpPtr _warp;
Common::ScopedPtr<RegionSet> _regSet;
Common::ScopedPtr<Video::VideoDecoder> _movie;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 4dd82fbb789..35430476c9e 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -1,5 +1,4 @@
#include "phoenixvr/script.h"
-#include "common/debug.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "phoenixvr/commands.h"
@@ -349,13 +348,12 @@ void Script::parseLine(const Common::String &line, uint lineno) {
Script::~Script() {
}
-Script::ConstWarpPtr Script::getWarp(const Common::String &name) const {
- auto idx = _warpsIndex.getVal(name);
- return _warps[idx];
+int Script::getWarp(const Common::String &name) const {
+ return _warpsIndex.getVal(name);
}
-Script::ConstWarpPtr Script::getWarp(uint idx) const {
- return idx < _warps.size() ? Script::ConstWarpPtr{_warps[idx]} : Script::ConstWarpPtr{};
+Script::ConstWarpPtr Script::getWarp(int idx) const {
+ return idx >= 0 && idx < static_cast<int>(_warps.size()) ? Script::ConstWarpPtr{_warps[idx]} : Script::ConstWarpPtr{};
}
Script::ConstWarpPtr Script::getInitScript() const {
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 74b6b4fdf97..aeeacc98081 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -79,7 +79,7 @@ public:
using ConstWarpPtr = Common::SharedPtr<const Warp>;
private:
- Common::HashMap<Common::String, uint, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _warpsIndex;
+ Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _warpsIndex;
Common::Array<Common::String> _warpNames;
Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
@@ -95,8 +95,8 @@ public:
Script(Common::SeekableReadStream &s);
~Script();
- ConstWarpPtr getWarp(const Common::String &name) const;
- ConstWarpPtr getWarp(uint index) const;
+ int getWarp(const Common::String &name) const;
+ ConstWarpPtr getWarp(int index) const;
ConstWarpPtr getInitScript() const;
const Common::Array<Common::String> &getWarpNames() const {
Commit: e2bc292d9ef88431996dd7fdbcaa89c9f07f3fa6
https://github.com/scummvm/scummvm/commit/e2bc292d9ef88431996dd7fdbcaa89c9f07f3fa6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:04+01:00
Commit Message:
PHOENIXVR: fix loading code enough to load original saves
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 41a4d35a9cc..b6ea356bc3f 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -55,7 +55,10 @@ struct LoadSave_Enter_Script : public Script::Command {
LoadSave_Enter_Script(const Common::Array<Common::String> &args) : reloading(args[0]), notReloading(args[1]) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
+ debug("LoadSave_Enter_Script %s, %s", reloading.c_str(), notReloading.c_str());
+ auto loading = g_engine->isLoading();
+ g_engine->setVariable(reloading, loading);
+ g_engine->setVariable(notReloading, !loading);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index c387d09c5af..3b485f13586 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -108,12 +108,16 @@ void PhoenixVREngine::loadNextScript() {
Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
_script.reset(new Script(*scriptStream));
}
+ for (auto &var : _script->getVarNames())
+ declareVariable(var);
}
void PhoenixVREngine::end() {
debug("end");
- if (_nextScript.empty())
+ if (_nextScript.empty() && _nextWarp < 0 && _nextScript < 0) {
+ debug("quit game");
quitGame();
+ }
}
void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
@@ -173,7 +177,8 @@ void PhoenixVREngine::hideCursor(const Common::String &warp, int idx) {
}
void PhoenixVREngine::declareVariable(const Common::String &name) {
- _variables.setVal(name, 0);
+ if (!_variables.contains(name))
+ _variables.setVal(name, 0);
}
void PhoenixVREngine::setVariable(const Common::String &name, int value) {
@@ -340,10 +345,9 @@ void PhoenixVREngine::tick(float dt) {
}
if (_nextWarp >= 0) {
_warp = _script->getWarp(_nextWarp);
+ debug("warp %d -> %s %s", _nextWarp, _warp->vrFile.c_str(), _warp->testFile.c_str());
_nextWarp = -1;
- debug("warp %s %s", _warp->vrFile.c_str(), _warp->testFile.c_str());
-
Common::File vr;
if (vr.open(Common::Path(_warp->vrFile))) {
_vr = VR::loadStatic(_pixelFormat, vr);
@@ -402,6 +406,8 @@ Common::Error PhoenixVREngine::run() {
while (!vars.eos()) {
auto var = vars.readLine();
+ if (var == "*")
+ break;
declareVariable(var);
_variableOrder.push_back(Common::move(var));
}
@@ -554,7 +560,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
}
}
debug("vars at %08lx", ms.pos());
- for (auto &name : _variableOrder) {
+ for (auto &name : _script->getVarNames()) {
auto value = ms.readUint32LE();
debug("var %s: %u", name.c_str(), value);
}
@@ -569,6 +575,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto music = ms.readString(0, 257);
auto musicVolume = ms.readUint32LE();
debug("current music %s, volume: %u", music.c_str(), musicVolume);
+ _loading = true;
}
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index c51791618a2..65688a4f1e0 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -134,6 +134,12 @@ public:
void loadSaveSlot(int idx);
void drawSlot(int idx, int face, int x, int y);
+ bool isLoading() {
+ bool loading = _loading;
+ _loading = false;
+ return loading;
+ }
+
private:
static Common::String removeDrive(const Common::String &path);
Graphics::Surface *loadSurface(const Common::String &path);
@@ -183,6 +189,7 @@ private:
AngleX _angleX;
AngleY _angleY;
Audio::Mixer *_mixer;
+ bool _loading = false;
static constexpr byte kPaused = 2;
static constexpr byte kActive = 4;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 35430476c9e..673f131b580 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -263,7 +263,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
case '[': {
p.next();
if (p.maybe("bool]=")) {
- g_engine->declareVariable(p.nextWord());
+ _vars.push_back(p.nextWord());
} else if (p.maybe("warp]=")) {
auto vr = p.nextWord();
p.expect(',');
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index aeeacc98081..962158347bd 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -81,6 +81,7 @@ public:
private:
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _warpsIndex;
Common::Array<Common::String> _warpNames;
+ Common::Array<Common::String> _vars;
Common::Array<WarpPtr> _warps;
WarpPtr _currentWarp;
TestPtr _currentTest;
@@ -102,6 +103,9 @@ public:
const Common::Array<Common::String> &getWarpNames() const {
return _warpNames;
}
+ const Common::Array<Common::String> &getVarNames() const {
+ return _vars;
+ }
};
} // namespace PhoenixVR
Commit: a862ebf8613dba3ce7f12dc5b4090ff6e2ca9e4f
https://github.com/scummvm/scummvm/commit/a862ebf8613dba3ce7f12dc5b4090ff6e2ca9e4f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:04+01:00
Commit Message:
PHOENIXVR: fix empty animation chunks
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index f3453609bfc..8d751db7c32 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -238,10 +238,11 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto animChunkPos = s.pos();
auto animChunkId = s.readUint32LE();
auto animChunkSize = s.readUint32LE();
+ assert(animChunkSize >= 8);
debug("animation chunk %08x %u at %08lx", animChunkId, animChunkSize, animChunkPos);
if (animChunkId == CHUNK_ANIMATION_BLOCK) {
auto &blockData = vr._animations[name];
- blockData.resize(animChunkSize);
+ blockData.resize(animChunkSize - 8);
s.read(blockData.data(), blockData.size());
}
s.seek(animChunkPos + animChunkSize);
@@ -329,6 +330,8 @@ void VR::playAnimation(const Common::String &name) {
warning("no animation %s", name.c_str());
return;
}
+ if (it->_value.size() == 0)
+ return;
const auto *data = it->_value.data();
auto prefixSize = READ_LE_UINT32(data);
Common::Array<uint32> prefixData(prefixSize);
Commit: a8b6167fd4462eff6f41646e38039a7059637107
https://github.com/scummvm/scummvm/commit/a8b6167fd4462eff6f41646e38039a7059637107
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:04+01:00
Commit Message:
PHOENIXVR: actually set variable from state loading code
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 3b485f13586..1e5a830a29a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -563,6 +563,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
for (auto &name : _script->getVarNames()) {
auto value = ms.readUint32LE();
debug("var %s: %u", name.c_str(), value);
+ g_engine->setVariable(name, value);
}
debug("vars end at %08lx", ms.pos());
auto currentSubroutine = ms.readSint32LE();
Commit: b8d823198720e7858f71fdbfb60982483f504175
https://github.com/scummvm/scummvm/commit/b8d823198720e7858f71fdbfb60982483f504175
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:04+01:00
Commit Message:
PHOENIXVR: implement cmp ops
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index b6ea356bc3f..0b6636e5007 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -194,14 +194,23 @@ struct Cmp : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
debug("cmp %s %s %s %s %d", var.c_str(), negativeVar.c_str(), arg0.c_str(), op.c_str(), arg1);
+ bool r;
+ auto value0 = g_engine->getVariable(arg0);
if (op == "==") {
- auto value0 = g_engine->getVariable(arg0);
- bool r = value0 == arg1;
- g_engine->setVariable(var, r);
- g_engine->setVariable(negativeVar, !r);
+ r = value0 == arg1;
+ } else if (op == "<") {
+ r = value0 < arg1;
+ } else if (op == "<=") {
+ r = value0 <= arg1;
+ } else if (op == ">") {
+ r = value0 > arg1;
+ } else if (op == ">=") {
+ r = value0 >= arg1;
} else {
error("invalid cmp op %s", op.c_str());
}
+ g_engine->setVariable(var, r);
+ g_engine->setVariable(negativeVar, !r);
}
};
Commit: 1e4b3c851d65da29ad5f7992b97b440cc46973e6
https://github.com/scummvm/scummvm/commit/1e4b3c851d65da29ad5f7992b97b440cc46973e6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:05+01:00
Commit Message:
PHOENIXVR: added guard against overwriting previous warp
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1e5a830a29a..ef41dbaeb16 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -125,8 +125,11 @@ void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
_nextWarp = _script->getWarp(warp);
if (savePrev) {
assert(_warp);
- _prevWarp = _script->getWarp(_warp->vrFile);
- assert(_prevWarp >= 0);
+ // Pretty much a hack to prevent user stuck in inventory
+ if (_prevWarp < 0) {
+ _prevWarp = _script->getWarp(_warp->vrFile);
+ assert(_prevWarp >= 0);
+ }
}
}
Commit: bac1f1c2c801beec0a62d2cfa01a3701f4fb04fa
https://github.com/scummvm/scummvm/commit/bac1f1c2c801beec0a62d2cfa01a3701f4fb04fa
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:05+01:00
Commit Message:
PHOENIXVR: implement while(wait)
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 0b6636e5007..81321dc570c 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -115,7 +115,7 @@ struct While : public Script::Command {
While(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("while %g", seconds);
+ g_engine->wait(seconds);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index ef41dbaeb16..052d7b79f61 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -120,6 +120,41 @@ void PhoenixVREngine::end() {
}
}
+void PhoenixVREngine::wait(float seconds) {
+ debug("wait %gs", seconds);
+ auto begin = g_system->getMillis();
+ unsigned millis = seconds * 1000;
+ Graphics::FrameLimiter limiter(g_system, 60);
+ while (!shouldQuit() && g_system->getMillis() - begin < millis) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN: {
+ auto it = _keys.find(event.kbd.keycode);
+ if (it != _keys.end()) {
+ debug("matched code %d", static_cast<int>(event.kbd.keycode));
+ goToWarp(it->_value, true);
+ } else if (event.kbd.ascii == ' ') {
+ if (_movie) {
+ _movie->stop();
+ _movie.reset();
+ }
+ }
+ } break;
+
+ default:
+ break;
+ }
+ }
+
+ // Delay for a bit. All events loops should have a delay
+ // to prevent the system being unduly loaded
+ limiter.delayBeforeSwap();
+ _screen->update();
+ limiter.startFrame();
+ }
+}
+
void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
debug("gotowarp %s", warp.c_str());
_nextWarp = _script->getWarp(warp);
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 65688a4f1e0..a2ba2c7bb13 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -110,6 +110,7 @@ public:
void executeTest(int idx);
void end();
+ void wait(float seconds);
const Region *getRegion(int idx) const;
Commit: f11956ce30e166c3b5d17cc1f69c979cc068dfde
https://github.com/scummvm/scummvm/commit/f11956ce30e166c3b5d17cc1f69c979cc068dfde
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:05+01:00
Commit Message:
PHOENIXVR: change warning to debug
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8d751db7c32..0e5c54157c6 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -327,7 +327,7 @@ Cube toCube(float x, float y, float z) {
void VR::playAnimation(const Common::String &name) {
auto it = _animations.find(name);
if (it == _animations.end()) {
- warning("no animation %s", name.c_str());
+ debug("no animation %s", name.c_str());
return;
}
if (it->_value.size() == 0)
Commit: aec46a1b6614ff7fb6123379269df8b81d261ae9
https://github.com/scummvm/scummvm/commit/aec46a1b6614ff7fb6123379269df8b81d261ae9
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:06+01:00
Commit Message:
VIDEO: 4XM: remove noisy debug logs
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index b6f54e991fd..cffbcdc7cb1 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -103,10 +103,6 @@ public:
_blockType[1].initStatistics({8, 0, 4, 2, 1, 1});
_blockType[2].initStatistics({8, 4, 0, 2, 1, 1});
_blockType[3].initStatistics({8, 0, 0, 4, 2, 1, 1});
- for (int i = 0; i != 4; ++i) {
- debug("blockType[%d]:", i);
- _blockType[i].dump();
- }
}
~FourXMVideoTrack();
@@ -139,7 +135,6 @@ const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
for (uint i = 0; i < 256; i++)
_mv[i] = mv[i][0] + mv[i][1] * pitch;
}
- debug("decode next video frame");
_dec->decodeNextFrameImpl();
return _frame;
}
@@ -250,8 +245,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
stream->read(bitstreamData.data(), bitstreamData.size());
auto prefixSize = stream->readUint32LE();
- auto tokenCount = stream->readUint32LE();
- debug("i-frame, bitstream: %u, prefix stream: %u, tokens: %u", bitstreamSize, prefixSize, tokenCount);
+ /* auto tokenCount = */ stream->readUint32LE();
Common::Array<byte> prefixStream(prefixSize * 4);
stream->read(prefixStream.data(), prefixStream.size());
@@ -407,7 +401,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
auto bitStreamSize = stream->readUint32LE();
auto wordStreamSize = stream->readUint32LE();
auto byteStreamSize = stream->readUint32LE();
- debug("p-frame, bitstream: %u, wordstream: %u, bytestream: %u", bitStreamSize, wordStreamSize, byteStreamSize);
Common::Array<byte> bitStreamData(bitStreamSize);
stream->read(bitStreamData.data(), bitStreamData.size());
@@ -427,9 +420,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
decode_pfrm_block(frame.get(), x, y, 3, 3, bitStream, wordStream, byteStream);
}
}
- debug("bitStream: %u/%u", bitStream.getWordPos(), bitStreamData.size() / 4);
- debug("wordStream: %lu/%lu", wordStream.pos(), wordStream.size());
- debug("byteStream: %lu/%lu", byteStream.pos(), byteStream.size());
_frame = frame.release();
}
@@ -437,7 +427,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *st
auto frameIdx = stream->readUint32LE();
auto frameSize = stream->readUint32LE();
auto streamSize = stream->size() - stream->pos();
- debug("c-frame, frame id: %u, size: %u", frameIdx, frameSize);
auto &cframe = _cframes[frameIdx];
auto dstOffset = cframe.size();
cframe.resize(cframe.size() + streamSize);
@@ -481,7 +470,6 @@ void FourXMDecoder::decodeNextFrameImpl() {
while (_stream->pos() < frame.end) {
uint32 tag = _stream->readUint32BE();
uint32 size = _stream->readUint32LE();
- debug("%u: sub frame %s, 0x%x bytes at %08lx", _curFrame, tagName(tag).c_str(), size, _stream->pos());
auto pos = _stream->pos();
auto loadBuf = [this](uint bufSize) {
@@ -545,12 +533,9 @@ void FourXMDecoder::readList(uint32 listEnd) {
_frames.push_back({_stream->pos() - 4, listEnd});
return;
}
- debug("%08lx: list type %s", _stream->pos() - 4, tagName(listType).c_str());
while (_stream->pos() < listEnd) {
uint32 tag = _stream->readUint32BE();
uint32 size = _stream->readUint32LE();
- if (listType != MKTAG('F', 'R', 'A', 'M'))
- debug("%08lx: tag %s, size %u/0x%x", _stream->pos() - 8, tagName(tag).c_str(), size, size);
auto pos = _stream->pos();
if (tag == MKTAG('L', 'I', 'S', 'T')) {
readList(pos + size);
@@ -628,11 +613,9 @@ bool FourXMDecoder::loadStream(Common::SeekableReadStream *stream) {
_stream = stream;
- debug("file size %u", fileSize);
while (stream->pos() < fileSize) {
uint32 tag = stream->readUint32BE();
uint32 size = stream->readUint32LE();
- debug("%08lx: tag %s, size %u/0x%x", stream->pos() - 8, tagName(tag).c_str(), size, size);
auto pos = stream->pos();
if (tag == MKTAG('L', 'I', 'S', 'T')) {
readList(pos + size);
@@ -640,7 +623,6 @@ bool FourXMDecoder::loadStream(Common::SeekableReadStream *stream) {
stream->seek(pos + size + (size & 1));
}
- debug("loaded %u frames", _frames.size());
return getNumTracks() != 0;
}
Commit: 4f428466f9976d71778435ea9f673b9ff6839a7d
https://github.com/scummvm/scummvm/commit/4f428466f9976d71778435ea9f673b9ff6839a7d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:06+01:00
Commit Message:
PHOENIXVR: remove log about video frame/animation
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 052d7b79f61..f75e295a3b2 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -512,7 +512,6 @@ Common::Error PhoenixVREngine::run() {
if (_movie) {
if (!_movie->endOfVideo()) {
if (_movie->needsUpdate()) {
- debug("playing movie frame: %d", _movie->getCurFrame());
auto *s = _movie->decodeNextFrame();
if (s) {
Common::ScopedPtr<Graphics::Surface> converted;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 0e5c54157c6..03a48dd10e9 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -239,7 +239,6 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto animChunkId = s.readUint32LE();
auto animChunkSize = s.readUint32LE();
assert(animChunkSize >= 8);
- debug("animation chunk %08x %u at %08lx", animChunkId, animChunkSize, animChunkPos);
if (animChunkId == CHUNK_ANIMATION_BLOCK) {
auto &blockData = vr._animations[name];
blockData.resize(animChunkSize - 8);
Commit: b0e48f18c4da4df512579bfd451a76cde1c6721c
https://github.com/scummvm/scummvm/commit/b0e48f18c4da4df512579bfd451a76cde1c6721c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:06+01:00
Commit Message:
PHOENIXVR: log about not taking ifand branch
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 81321dc570c..68fbb0c5ffb 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -365,8 +365,10 @@ struct IfAnd : public Script::Conditional {
if (!value)
result = false;
}
- if (!result)
+ if (!result) {
+ debug("ifand: not executing conditional block");
return;
+ }
debug("ifand: executing conditional block");
target->exec(ctx);
}
Commit: 6fbd74aed6bd01a299745fc5427240d03cdc5ecd
https://github.com/scummvm/scummvm/commit/6fbd74aed6bd01a299745fc5427240d03cdc5ecd
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:06+01:00
Commit Message:
PHOENIXVR: store cursors in cache
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index f75e295a3b2..30bfb1369eb 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -57,6 +57,11 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
}
PhoenixVREngine::~PhoenixVREngine() {
+ for (auto it = _cursorCache.begin(); it != _cursorCache.end(); ++it) {
+ auto *s = it->_value;
+ s->free();
+ delete s;
+ }
delete _screen;
}
@@ -186,8 +191,7 @@ const Region *PhoenixVREngine::getRegion(int idx) const {
void PhoenixVREngine::setCursorDefault(int idx, const Common::String &path) {
debug("setCursorDefault %d: %s", idx, path.c_str());
if (idx == 0 || idx == 1) {
- _defaultCursor[idx].free();
- _defaultCursor[idx].surface = loadSurface(path);
+ _defaultCursor[idx].surface = loadCursor(path);
} else
warning("only 2 default cursors supported, got %d", idx);
}
@@ -203,15 +207,14 @@ void PhoenixVREngine::setCursor(const Common::String &path, const Common::String
_cursors.resize(idx + 1);
auto &cursor = _cursors[idx];
debug("cursor region %s:%d: %s, %s", wname.c_str(), idx, reg ? reg->toString().c_str() : "no region", path.c_str());
- cursor.free();
- cursor.surface = loadSurface(path);
+ cursor.surface = loadCursor(path);
if (reg)
cursor.region = *reg;
}
void PhoenixVREngine::hideCursor(const Common::String &warp, int idx) {
debug("hide cursor %s:%d", warp.c_str(), idx);
- _cursors[idx].free();
+ _cursors[idx].surface = nullptr;
}
void PhoenixVREngine::declareVariable(const Common::String &name) {
@@ -297,13 +300,15 @@ Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
return nullptr;
}
-void PhoenixVREngine::Cursor::free() {
- if (surface) {
- surface->free();
- delete surface;
- surface = nullptr;
- }
- region.setEmpty();
+Graphics::Surface *PhoenixVREngine::loadCursor(const Common::String &path) {
+ auto it = _cursorCache.find(path);
+ if (it != _cursorCache.end())
+ return it->_value;
+ auto s = loadSurface(path);
+ if (!s)
+ error("can't load cursor from %s", path.c_str());
+ _cursorCache[path] = s;
+ return s;
}
void PhoenixVREngine::executeTest(int idx) {
@@ -398,7 +403,7 @@ void PhoenixVREngine::tick(float dt) {
_regSet.reset(new RegionSet(_warp->testFile));
for (auto &c : _cursors)
- c.free();
+ c.surface = nullptr;
_cursors.resize(_regSet->size());
for (uint i = 0; i != _regSet->size(); ++i) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index a2ba2c7bb13..ff1aaf98a9a 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -144,6 +144,7 @@ public:
private:
static Common::String removeDrive(const Common::String &path);
Graphics::Surface *loadSurface(const Common::String &path);
+ Graphics::Surface *loadCursor(const Common::String &path);
void paint(Graphics::Surface &src, Common::Point dst);
PointF currentVRPos() const {
return RectF::transform(_angleX.angle(), _angleY.angle(), _fov);
@@ -177,11 +178,11 @@ private:
Common::ScopedPtr<RegionSet> _regSet;
Common::ScopedPtr<Video::VideoDecoder> _movie;
+ Common::HashMap<Common::String, Graphics::Surface *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _cursorCache;
struct Cursor {
Region region;
Graphics::Surface *surface = nullptr;
- void free();
};
Common::Array<Cursor> _cursors;
Cursor _defaultCursor[2];
Commit: b3d8e4aa6719a10a53b5c2deaf49be6cfd61ab3b
https://github.com/scummvm/scummvm/commit/b3d8e4aa6719a10a53b5c2deaf49be6cfd61ab3b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:07+01:00
Commit Message:
PHOENIXVR: do not reset conditional on every plugin command, plugin-endplugin is a single scope
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 673f131b580..3f07fbccfc1 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -319,14 +319,10 @@ void Script::parseLine(const Common::String &line, uint lineno) {
auto args = p.readStringList();
p.expect(')');
auto cmd = createCommand(name, args);
- if (cmd) {
- if (_conditional) {
- _conditional->target = Common::move(cmd);
- commands.push_back(Common::move(_conditional));
- _conditional.reset();
- } else
- _pluginScope->commands.push_back(Common::move(cmd));
- }
+ if (cmd)
+ _pluginScope->commands.push_back(Common::move(cmd));
+ else
+ error("unhandled plugin command %s", line.c_str());
} else {
auto cmd = p.parseCommand();
if (cmd) {
Commit: 52f7bc8f74b4f2ea09efd61b90a6dfb9b445e469
https://github.com/scummvm/scummvm/commit/52f7bc8f74b4f2ea09efd61b90a6dfb9b445e469
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:07+01:00
Commit Message:
PHOENIXVR: made isLoading immutable, temp fix for save.vr
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 68fbb0c5ffb..3dc5709d408 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -268,7 +268,7 @@ struct LoadSave_Context_Restored : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
debug("LoadSave_Context_Restored: semi-stub: can be used to report that loading is in a progress");
g_engine->setVariable(progress, 0);
- g_engine->setVariable(done, 1);
+ g_engine->setVariable(done, g_engine->isLoading());
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 30bfb1369eb..d6210cd3f50 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -417,6 +417,7 @@ void PhoenixVREngine::tick(float dt) {
test->scope.exec(ctx);
else
warning("no default script!");
+ _loading = false;
}
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index ff1aaf98a9a..4c6c5d074c9 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -135,10 +135,8 @@ public:
void loadSaveSlot(int idx);
void drawSlot(int idx, int face, int x, int y);
- bool isLoading() {
- bool loading = _loading;
- _loading = false;
- return loading;
+ bool isLoading() const {
+ return _loading;
}
private:
Commit: f18ec84dfe6c957e1bc4cdecf66fe8d0e0c449c4
https://github.com/scummvm/scummvm/commit/f18ec84dfe6c957e1bc4cdecf66fe8d0e0c449c4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:07+01:00
Commit Message:
PHOENIXVR: remove InitSlots warning
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 3dc5709d408..bd21df6b401 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -219,7 +219,6 @@ struct LoadSave_Init_Slots : public Script::Command {
LoadSave_Init_Slots(const Common::Array<Common::String> &args) : slots(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Init_Slots %d", slots);
}
};
Commit: f4d777e7e36f33cf2376a405e7887354636ec710
https://github.com/scummvm/scummvm/commit/f4d777e7e36f33cf2376a405e7887354636ec710
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:08+01:00
Commit Message:
PHOENIXVR: make playMovie synchronous
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index d6210cd3f50..64ed650ad76 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -129,8 +129,9 @@ void PhoenixVREngine::wait(float seconds) {
debug("wait %gs", seconds);
auto begin = g_system->getMillis();
unsigned millis = seconds * 1000;
- Graphics::FrameLimiter limiter(g_system, 60);
- while (!shouldQuit() && g_system->getMillis() - begin < millis) {
+ Graphics::FrameLimiter limiter(g_system, kFPSLimit);
+ bool waiting = true;
+ while (!shouldQuit() && waiting && g_system->getMillis() - begin < millis) {
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
@@ -140,12 +141,10 @@ void PhoenixVREngine::wait(float seconds) {
debug("matched code %d", static_cast<int>(event.kbd.keycode));
goToWarp(it->_value, true);
} else if (event.kbd.ascii == ' ') {
- if (_movie) {
- _movie->stop();
- _movie.reset();
- }
+ waiting = false;
}
- } break;
+ break;
+ }
default:
break;
@@ -257,11 +256,48 @@ void PhoenixVREngine::stopSound(const Common::String &sound) {
void PhoenixVREngine::playMovie(const Common::String &movie) {
debug("playMovie %s", movie.c_str());
- _movie.reset(new Video::FourXMDecoder());
- if (_movie->loadFile(Common::Path{movie})) {
- _movie->start();
+ Video::FourXMDecoder dec;
+
+ if (dec.loadFile(Common::Path{movie})) {
+ dec.start();
+
+ bool playing = true;
+ Graphics::FrameLimiter limiter(g_system, kFPSLimit);
+ while (!shouldQuit() && playing && !dec.endOfVideo()) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN: {
+ if (event.kbd.ascii == ' ') {
+ playing = false;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ if (dec.needsUpdate()) {
+ auto *s = dec.decodeNextFrame();
+ if (s) {
+ Common::ScopedPtr<Graphics::Surface> converted;
+ if (s->format != _pixelFormat) {
+ converted.reset(s->convertTo(_pixelFormat));
+ _screen->copyFrom(*converted);
+ converted->free();
+ } else
+ _screen->copyFrom(*s);
+ }
+ }
+
+ // Delay for a bit. All events loops should have a delay
+ // to prevent the system being unduly loaded
+ limiter.delayBeforeSwap();
+ _screen->update();
+ limiter.startFrame();
+ }
} else {
- _movie.reset();
warning("playMovie %s failed", movie.c_str());
}
}
@@ -468,7 +504,7 @@ Common::Error PhoenixVREngine::run() {
Common::Event event;
- Graphics::FrameLimiter limiter(g_system, 60);
+ Graphics::FrameLimiter limiter(g_system, kFPSLimit);
uint frameDuration = 0;
while (!shouldQuit()) {
while (g_system->getEventManager()->pollEvent(event)) {
@@ -478,11 +514,6 @@ Common::Error PhoenixVREngine::run() {
if (it != _keys.end()) {
debug("matched code %d", static_cast<int>(event.kbd.keycode));
goToWarp(it->_value, true);
- } else if (event.kbd.ascii == ' ') {
- if (_movie) {
- _movie->stop();
- _movie.reset();
- }
}
} break;
case Common::EVENT_MOUSEMOVE:
@@ -515,24 +546,7 @@ Common::Error PhoenixVREngine::run() {
}
}
float dt = float(frameDuration) / 1000.0f;
- if (_movie) {
- if (!_movie->endOfVideo()) {
- if (_movie->needsUpdate()) {
- auto *s = _movie->decodeNextFrame();
- if (s) {
- Common::ScopedPtr<Graphics::Surface> converted;
- if (s->format != _pixelFormat) {
- converted.reset(s->convertTo(_pixelFormat));
- _screen->copyFrom(*converted);
- converted->free();
- } else
- _screen->copyFrom(*s);
- }
- }
- } else
- _movie.reset();
- } else
- tick(dt);
+ tick(dt);
// Delay for a bit. All events loops should have a delay
// to prevent the system being unduly loaded
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 4c6c5d074c9..852ff6e08bf 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -49,6 +49,8 @@ struct PhoenixVRGameDescription;
class PhoenixVREngine : public Engine {
private:
+ static constexpr uint kFPSLimit = 60;
+
Graphics::Screen *_screen = nullptr;
Common::Point _screenCenter;
const ADGameDescription *_gameDescription;
@@ -175,7 +177,6 @@ private:
Common::ScopedPtr<RegionSet> _regSet;
- Common::ScopedPtr<Video::VideoDecoder> _movie;
Common::HashMap<Common::String, Graphics::Surface *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _cursorCache;
struct Cursor {
Commit: 99966914f955ca22ec9960b0bf7dfaf558269995
https://github.com/scummvm/scummvm/commit/99966914f955ca22ec9960b0bf7dfaf558269995
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:08+01:00
Commit Message:
PHOENIXVR: load samples/samples3d
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 64ed650ad76..642effd965b 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -633,6 +633,28 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto music = ms.readString(0, 257);
auto musicVolume = ms.readUint32LE();
debug("current music %s, volume: %u", music.c_str(), musicVolume);
+
+ // sound samples
+ for (uint i = 0; i != 8; ++i) {
+ auto name = ms.readString(0, 257);
+ auto vol = ms.readUint32LE();
+ auto loop = ms.readUint32LE();
+ debug("sound: %s vol: %u loop: %u", name.c_str(), vol, loop);
+ if (!name.empty())
+ playSound(name, vol, -1);
+ }
+
+ // sound samples 3D
+ for (uint i = 0; i != 8; ++i) {
+ auto name = ms.readString(0, 257);
+ auto vol = ms.readUint32LE();
+ auto loop = ms.readUint32LE();
+ auto angle = ms.readUint32LE();
+ debug("3d sound: %s %u %u %u", name.c_str(), vol, loop, angle);
+ if (!name.empty())
+ playSound(name, vol, -1, true, float(angle * M_PI));
+ }
+
_loading = true;
}
Commit: 3b02d320c03c3602503a585851fb7946fc7e54ea
https://github.com/scummvm/scummvm/commit/3b02d320c03c3602503a585851fb7946fc7e54ea
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:08+01:00
Commit Message:
PHOENIXVR: implement SetContextLabel
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index bd21df6b401..ce2d90a3777 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -295,7 +295,7 @@ struct LoadSave_Set_Context_Label : public Script::Command {
LoadSave_Set_Context_Label(const Common::Array<Common::String> &args) : label(args[0]) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Set_Context_Label %s", label.c_str());
+ g_engine->setContextLabel(label);
}
};
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 852ff6e08bf..30e7a28fc96 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -137,6 +137,10 @@ public:
void loadSaveSlot(int idx);
void drawSlot(int idx, int face, int x, int y);
+ void setContextLabel(const Common::String &contextLabel) {
+ _contextLabel = contextLabel;
+ }
+
bool isLoading() const {
return _loading;
}
@@ -207,6 +211,7 @@ private:
Common::Array<byte> thumbnail;
Common::Array<byte> state;
};
+ Common::String _contextLabel;
GameState loadGameStateObject(Common::SeekableReadStream *stream);
};
Commit: 53cec7d67fdc6d9c7a1959a4bdfb6cacc127f866
https://github.com/scummvm/scummvm/commit/53cec7d67fdc6d9c7a1959a4bdfb6cacc127f866
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:08+01:00
Commit Message:
PHOENIXVR: add saveSaveSlot stub
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index ce2d90a3777..524c40796a3 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -286,7 +286,8 @@ struct LoadSave_Save : public Script::Command {
LoadSave_Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Save %d", slot);
+ debug("LoadSave_Save %d", slot);
+ g_engine->saveSaveSlot(slot);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 642effd965b..4aea921a7e8 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -658,6 +658,14 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
_loading = true;
}
+void PhoenixVREngine::saveSaveSlot(int idx) {
+ Common::ScopedPtr<Common::OutSaveFile> slot(_saveFileMan->openForSaving(getSaveStateName(idx)));
+ if (!slot) {
+ warning("saveSaveSlot: invalid save slot %d", idx);
+ return;
+ }
+}
+
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
if (!slot)
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 30e7a28fc96..4cb6196a96c 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -135,6 +135,7 @@ public:
bool testSaveSlot(int idx) const;
void loadSaveSlot(int idx);
+ void saveSaveSlot(int idx);
void drawSlot(int idx, int face, int x, int y);
void setContextLabel(const Common::String &contextLabel) {
Commit: a2c1ee0d3edaa4be80d8ea09d9edbd3b1af26c8c
https://github.com/scummvm/scummvm/commit/a2c1ee0d3edaa4be80d8ea09d9edbd3b1af26c8c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:09+01:00
Commit Message:
PHOENIXVR: reimplement cursor table - fixes all known cursor issues so far
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 4aea921a7e8..573f1cbe9b5 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -115,6 +115,13 @@ void PhoenixVREngine::loadNextScript() {
}
for (auto &var : _script->getVarNames())
declareVariable(var);
+
+ int numWarps = _script->numWarps();
+ _cursors.resize(numWarps);
+ for (int i = 0; i != numWarps; ++i) {
+ auto warp = _script->getWarp(i);
+ _cursors[i].resize(warp->tests.size());
+ }
}
void PhoenixVREngine::end() {
@@ -190,30 +197,19 @@ const Region *PhoenixVREngine::getRegion(int idx) const {
void PhoenixVREngine::setCursorDefault(int idx, const Common::String &path) {
debug("setCursorDefault %d: %s", idx, path.c_str());
if (idx == 0 || idx == 1) {
- _defaultCursor[idx].surface = loadCursor(path);
+ _defaultCursor[idx] = path;
} else
warning("only 2 default cursors supported, got %d", idx);
}
void PhoenixVREngine::setCursor(const Common::String &path, const Common::String &wname, int idx) {
debug("setCursor %s %s:%d", path.c_str(), wname.c_str(), idx);
- if (!_warp || !_warp->vrFile.equalsIgnoreCase(wname)) {
- warning("setting cursor for different warp, active: %s, required: %s", _warp ? _warp->vrFile.c_str() : "null", wname.c_str());
- return;
- }
- auto *reg = g_engine->getRegion(idx);
- if (idx >= static_cast<int>(_cursors.size()))
- _cursors.resize(idx + 1);
- auto &cursor = _cursors[idx];
- debug("cursor region %s:%d: %s, %s", wname.c_str(), idx, reg ? reg->toString().c_str() : "no region", path.c_str());
- cursor.surface = loadCursor(path);
- if (reg)
- cursor.region = *reg;
+ _cursors[_script->getWarp(wname)][idx] = path;
}
void PhoenixVREngine::hideCursor(const Common::String &warp, int idx) {
debug("hide cursor %s:%d", warp.c_str(), idx);
- _cursors[idx].surface = nullptr;
+ _cursors[_script->getWarp(warp)][idx].clear();
}
void PhoenixVREngine::declareVariable(const Common::String &name) {
@@ -337,6 +333,8 @@ Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
}
Graphics::Surface *PhoenixVREngine::loadCursor(const Common::String &path) {
+ if (path.empty())
+ return nullptr;
auto it = _cursorCache.find(path);
if (it != _cursorCache.end())
return it->_value;
@@ -423,6 +421,7 @@ void PhoenixVREngine::tick(float dt) {
goToWarp(_script->getInitScript()->vrFile);
}
if (_nextWarp >= 0) {
+ _warpIdx = _nextWarp;
_warp = _script->getWarp(_nextWarp);
debug("warp %d -> %s %s", _nextWarp, _warp->vrFile.c_str(), _warp->testFile.c_str());
_nextWarp = -1;
@@ -438,14 +437,6 @@ void PhoenixVREngine::tick(float dt) {
_regSet.reset(new RegionSet(_warp->testFile));
- for (auto &c : _cursors)
- c.surface = nullptr;
-
- _cursors.resize(_regSet->size());
- for (uint i = 0; i != _regSet->size(); ++i) {
- _cursors[i].region = _regSet->getRegion(i);
- }
-
Script::ExecutionContext ctx;
debug("execute warp script %s", _warp->vrFile.c_str());
auto test = _warp->getDefaultTest();
@@ -459,17 +450,22 @@ void PhoenixVREngine::tick(float dt) {
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
Graphics::Surface *cursor = nullptr;
- for (uint i = 0; i != _cursors.size(); ++i) {
- auto &c = _cursors[i];
- if (_vr.isVR() ? c.region.contains3D(currentVRPos()) : c.region.contains2D(_mousePos.x, _mousePos.y)) {
- cursor = c.surface;
+ auto &cursors = _cursors[_warpIdx];
+ for (uint i = 0; i != cursors.size(); ++i) {
+ auto *region = getRegion(i);
+ if (!region)
+ continue;
+
+ if (_vr.isVR() ? region->contains3D(currentVRPos()) : region->contains2D(_mousePos.x, _mousePos.y)) {
+ auto &name = cursors[i];
+ cursor = loadCursor(name);
if (!cursor)
- cursor = _defaultCursor[1].surface;
+ cursor = loadCursor(_defaultCursor[1]);
break;
}
}
if (!cursor)
- cursor = _defaultCursor[0].surface;
+ cursor = loadCursor(_defaultCursor[0]);
if (cursor) {
paint(*cursor, _mousePos - Common::Point(cursor->w / 2, cursor->h / 2));
}
@@ -526,9 +522,13 @@ Common::Error PhoenixVREngine::run() {
} else
debug("click %s", _mousePos.toString().c_str());
- for (uint i = 0, n = _cursors.size(); i != n; ++i) {
- auto &cur = _cursors[i];
- if (_vr.isVR() ? cur.region.contains3D(vrPos) : cur.region.contains2D(event.mouse.x, event.mouse.y)) {
+ auto &cursors = _cursors[_warpIdx];
+ for (uint i = 0, n = cursors.size(); i != n; ++i) {
+ auto *region = getRegion(i);
+ if (!region)
+ continue;
+
+ if (_vr.isVR() ? region->contains3D(vrPos) : region->contains2D(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
break;
@@ -605,15 +605,15 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
_nextWarp = currentWarpIdx;
- for (auto &vr : _script->getWarpNames()) {
- auto warpIdx = _script->getWarp(vr);
- assert(warpIdx >= 0);
+ for (uint warpIdx = 0; warpIdx != _script->numWarps(); ++warpIdx) {
auto warp = _script->getWarp(warpIdx);
assert(warp);
auto &tests = warp->tests;
+ auto &warpCursors = _cursors[warpIdx];
for (uint testIdx = 0; testIdx < tests.size(); ++testIdx) {
auto cursor = ms.readString(0, 257);
- // debug("warp %s.%d: %s", vr.c_str(), testIdx, cursor.c_str());
+ // debug("warp %s.%d: %s", warp->vrFile.c_str(), testIdx, cursor.c_str());
+ warpCursors[testIdx] = cursor;
}
}
debug("vars at %08lx", ms.pos());
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 4cb6196a96c..e4ea0801186 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -116,13 +116,6 @@ public:
const Region *getRegion(int idx) const;
- uint numCursors() const {
- return _cursors.size();
- }
- const Region *getCursorRegion(uint idx) const {
- return idx < _cursors.size() ? &_cursors[idx].region : nullptr;
- }
-
void resetLockKey();
void lockKey(Common::KeyCode code, const Common::String &warp);
void startTimer(float seconds);
@@ -161,6 +154,7 @@ private:
private:
Common::Point _mousePos;
Common::Path _nextScript;
+ int _warpIdx = -1;
Script::ConstWarpPtr _warp;
int _nextWarp = -1;
int _prevWarp = -1;
@@ -184,12 +178,9 @@ private:
Common::HashMap<Common::String, Graphics::Surface *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _cursorCache;
- struct Cursor {
- Region region;
- Graphics::Surface *surface = nullptr;
- };
- Common::Array<Cursor> _cursors;
- Cursor _defaultCursor[2];
+ Common::Array<Common::Array<Common::String>> _cursors;
+ Common::String _defaultCursor[2];
+
VR _vr;
float _fov;
AngleX _angleX;
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 962158347bd..b4127a10474 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -100,6 +100,10 @@ public:
ConstWarpPtr getWarp(int index) const;
ConstWarpPtr getInitScript() const;
+ uint numWarps() const {
+ return _warps.size();
+ }
+
const Common::Array<Common::String> &getWarpNames() const {
return _warpNames;
}
Commit: aa64bc004c8774c30558fa193370b7f5ec89d411
https://github.com/scummvm/scummvm/commit/aa64bc004c8774c30558fa193370b7f5ec89d411
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:09+01:00
Commit Message:
PHOENIXVR: do not write static.bmp
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 03a48dd10e9..3a3d04a40b8 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -249,11 +249,6 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
}
s.seek(chunkPos + chunkSize);
}
- if (vr._pic) {
- Common::DumpFile out;
- if (out.open("static.bmp"))
- Image::writeBMP(out, *vr._pic);
- }
return vr;
}
Commit: 64a1e40689e3da139b9b4a32cd0976dbd24168d0
https://github.com/scummvm/scummvm/commit/64a1e40689e3da139b9b4a32cd0976dbd24168d0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:09+01:00
Commit Message:
PHOENIXVR: implement angle range limits
Changed paths:
engines/phoenixvr/angle.h
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/angle.h b/engines/phoenixvr/angle.h
index 6da7d040ea3..5b48e83d0cf 100644
--- a/engines/phoenixvr/angle.h
+++ b/engines/phoenixvr/angle.h
@@ -36,8 +36,12 @@ protected:
float _min;
float _max;
+ float _rangeMin;
+ float _rangeMax;
+
public:
Angle(float angle, float min, float max) : _min(min), _max(max) {
+ resetRange();
set(angle);
}
@@ -49,6 +53,10 @@ public:
float angle() const { return _angle; }
void set(float v) {
+ if (v > _rangeMax)
+ v = _rangeMax;
+ else if (v < _rangeMin)
+ v = _rangeMin;
auto range = _max - _min;
auto a = fmod(v - _min, range);
if (a < 0)
@@ -59,6 +67,15 @@ public:
void add(float v) {
set(_angle + v);
}
+
+ void setRange(float min, float max) {
+ _rangeMin = min;
+ _rangeMax = max;
+ }
+
+ void resetRange() {
+ setRange(-INFINITY, INFINITY);
+ }
};
struct AngleX : Angle {
AngleX(float angle) : Angle(angle, 0, 2 * M_PI) {}
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 524c40796a3..88cc17e9b88 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -524,25 +524,28 @@ struct AngleXMax : public Script::Command {
AngleXMax(float x) : xMax(x) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("angle x max %g", xMax);
+ debug("anglexmax %g", xMax);
+ g_engine->setXMax(xMax);
}
};
struct AngleYMax : public Script::Command {
- float yMax0, yMax1;
- AngleYMax(float y0, float y1) : yMax0(y0), yMax1(y1) {}
+ float yMin, yMax;
+ AngleYMax(float min, float max) : yMin(min), yMax(max) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("angle y max %g %g", yMax0, yMax1);
+ debug("angleymax %g %g", yMin, yMax);
+ g_engine->setYMax(yMin, yMax);
}
};
struct SetAngle : public Script::Command {
- float a0, a1;
- SetAngle(float a0_, float a1_) : a0(a0_), a1(a1_) {}
+ float x, y;
+ SetAngle(float x_, float y_) : x(x_), y(y_) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("set angle %g %g", a0, a1);
+ debug("setangle %g %g", x, y);
+ g_engine->setAngle(x, y);
}
};
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index e4ea0801186..20beee308c8 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -126,6 +126,19 @@ public:
_fov = M_PI * fov / 180;
}
+ void setXMax(float max) {
+ static const float baseX = -M_PI_2;
+ _angleY.setRange(baseX - max, baseX + max);
+ }
+
+ // this is set to large values and effectively useless
+ void setYMax(float min, float max) {}
+ void setAngle(float x, float y) {
+ _angleX.set(y);
+ static const float baseX = -M_PI_2;
+ _angleY.set(baseX + x);
+ }
+
bool testSaveSlot(int idx) const;
void loadSaveSlot(int idx);
void saveSaveSlot(int idx);
Commit: 795283644a9876f27e0a1e9f7b06c4788a36b095
https://github.com/scummvm/scummvm/commit/795283644a9876f27e0a1e9f7b06c4788a36b095
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:10+01:00
Commit Message:
PHOENIXVR: partially implemented context capture
Changed paths:
engines/phoenixvr/angle.h
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/angle.h b/engines/phoenixvr/angle.h
index 5b48e83d0cf..10078d1ecc1 100644
--- a/engines/phoenixvr/angle.h
+++ b/engines/phoenixvr/angle.h
@@ -68,6 +68,14 @@ public:
set(_angle + v);
}
+ float rangeMin() const {
+ return _rangeMin;
+ }
+
+ float rangeMax() const {
+ return _rangeMax;
+ }
+
void setRange(float min, float max) {
_rangeMin = min;
_rangeMax = max;
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 88cc17e9b88..2497bb2ec55 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -255,7 +255,8 @@ struct LoadSave_Test_Slot : public Script::Command {
struct LoadSave_Capture_Context : public Script::Command {
LoadSave_Capture_Context(const Common::Array<Common::String> &args) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("LoadSave_Capture_Context");
+ debug("LoadSave_Capture_Context");
+ g_engine->captureContext();
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 573f1cbe9b5..db6e827c56d 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -571,6 +571,41 @@ bool PhoenixVREngine::testSaveSlot(int idx) const {
return _saveFileMan->exists(getSaveStateName(idx));
}
+void PhoenixVREngine::captureContext() {
+ Common::MemoryWriteStreamDynamic ms(DisposeAfterUse::YES);
+
+ auto writeString = [&ms](const Common::String &str) {
+ assert(str.size() <= 256);
+ ms.writeString(str);
+ uint tail = 257 - str.size();
+ while (tail--)
+ ms.writeByte(0);
+ };
+
+ ms.writeSint32LE(fromAngle(_angleY.angle() + M_PI_2));
+ ms.writeSint32LE(fromAngle(_angleX.angle()));
+ ms.writeSint32LE(0);
+ ms.writeSint32LE(0);
+ ms.writeSint32LE(fromAngle(_angleY.rangeMax() + M_PI_2));
+ ms.writeSint32LE(fromAngle(_angleX.rangeMin()));
+ ms.writeSint32LE(fromAngle(_angleX.rangeMax()));
+ ms.writeSint32LE(_warpIdx);
+ ms.writeUint32LE(_warp->tests.size());
+ writeString({});
+ writeString({});
+ for (auto &warpCursors : _cursors)
+ for (auto &cursor : warpCursors)
+ writeString(cursor);
+
+ for (auto &name : _script->getVarNames()) {
+ auto value = g_engine->getVariable(name);
+ ms.writeUint32LE(value);
+ }
+
+ ms.writeUint32LE(0); // current subroutine
+ ms.writeSint32LE(_prevWarp);
+}
+
void PhoenixVREngine::loadSaveSlot(int idx) {
Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
if (!slot) {
@@ -603,6 +638,10 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
angleXMax, angleYMin, angleYMax,
currentWarpIdx, currentWarpTests);
+ setAngle(toAngle(angleX), toAngle(angleY));
+ setXMax(toAngle(angleXMax));
+ setYMax(toAngle(angleYMin), toAngle(angleYMax));
+
_nextWarp = currentWarpIdx;
for (uint warpIdx = 0; warpIdx != _script->numWarps(); ++warpIdx) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 20beee308c8..e996d4106c3 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -132,7 +132,10 @@ public:
}
// this is set to large values and effectively useless
- void setYMax(float min, float max) {}
+ void setYMax(float min, float max) {
+ _angleX.setRange(min, max);
+ }
+
void setAngle(float x, float y) {
_angleX.set(y);
static const float baseX = -M_PI_2;
@@ -143,6 +146,7 @@ public:
void loadSaveSlot(int idx);
void saveSaveSlot(int idx);
void drawSlot(int idx, int face, int x, int y);
+ void captureContext();
void setContextLabel(const Common::String &contextLabel) {
_contextLabel = contextLabel;
@@ -217,6 +221,7 @@ private:
Common::Array<byte> state;
};
Common::String _contextLabel;
+ Common::Array<byte> _capturedState;
GameState loadGameStateObject(Common::SeekableReadStream *stream);
};
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 3f07fbccfc1..9561aeaa797 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -7,7 +7,6 @@
namespace PhoenixVR {
namespace {
-
class Parser {
const Common::String &_line;
uint _lineno;
@@ -138,11 +137,6 @@ public:
return list;
}
- static float toAngle(int a) {
- static const float angleToFloat = M_PI / 4096.0f;
- return angleToFloat * a;
- }
-
Script::CommandPtr parseCommand() {
using CommandPtr = Script::CommandPtr;
if (keyword("setcursordefault")) {
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index b4127a10474..3c6f20698f4 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -32,7 +32,17 @@ class SeekableReadStream;
}
namespace PhoenixVR {
+namespace {
+inline float toAngle(int a) {
+ static const float angleToFloat = M_PI / 4096.0f;
+ return angleToFloat * a;
+}
+inline int fromAngle(float a) {
+ static const float floatToAngle = 4096.0f / M_PI;
+ return floatToAngle * a;
+}
+} // namespace
class Script {
public:
struct ExecutionContext {
Commit: 9fc57807dc7e8dc0aa7293ef54f97795942ca0c5
https://github.com/scummvm/scummvm/commit/9fc57807dc7e8dc0aa7293ef54f97795942ca0c5
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:10+01:00
Commit Message:
PHOENIXVR: store _lockKey slots as is (also load them from save)
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 2497bb2ec55..12e278c5169 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -461,53 +461,8 @@ struct LockKey : public Script::Command {
LockKey(int i, Common::String w) : idx(i), warp(Common::move(w)) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("lock key F%d: %s", idx, warp.c_str());
- Common::KeyCode code;
- switch (idx) {
- case 0:
- code = Common::KeyCode::KEYCODE_ESCAPE;
- break;
- case 1:
- code = Common::KeyCode::KEYCODE_F1;
- break;
- case 2:
- code = Common::KeyCode::KEYCODE_F2;
- break;
- case 3:
- code = Common::KeyCode::KEYCODE_F3;
- break;
- case 4:
- code = Common::KeyCode::KEYCODE_F4;
- break;
- case 5:
- code = Common::KeyCode::KEYCODE_F5;
- break;
- case 6:
- code = Common::KeyCode::KEYCODE_F6;
- break;
- case 7:
- code = Common::KeyCode::KEYCODE_F7;
- break;
- case 8:
- code = Common::KeyCode::KEYCODE_F8;
- break;
- case 9:
- code = Common::KeyCode::KEYCODE_F9;
- break;
- case 10:
- code = Common::KeyCode::KEYCODE_F10;
- break;
- case 11:
- code = Common::KeyCode::KEYCODE_RETURN;
- break;
- case 12:
- code = Common::KeyCode::KEYCODE_TAB;
- break;
- default:
- warning("unknown lock key %d", idx);
- return;
- }
- g_engine->lockKey(code, warp);
+ debug("lock key %d: %s", idx, warp.c_str());
+ g_engine->lockKey(idx, warp);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index db6e827c56d..411299e40d3 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -49,6 +49,7 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
_pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()),
+ _lockKey(13),
_fov(M_PI_2),
_angleX(M_PI),
_angleY(-M_PI_2),
@@ -143,11 +144,7 @@ void PhoenixVREngine::wait(float seconds) {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN: {
- auto it = _keys.find(event.kbd.keycode);
- if (it != _keys.end()) {
- debug("matched code %d", static_cast<int>(event.kbd.keycode));
- goToWarp(it->_value, true);
- } else if (event.kbd.ascii == ' ') {
+ if (event.kbd.ascii == ' ') {
waiting = false;
}
break;
@@ -305,8 +302,8 @@ void PhoenixVREngine::resetLockKey() {
_prevWarp = -1; // original game does only this o_O
}
-void PhoenixVREngine::lockKey(Common::KeyCode code, const Common::String &warp) {
- _keys[code] = warp;
+void PhoenixVREngine::lockKey(int idx, const Common::String &warp) {
+ _lockKey[idx] = warp;
}
Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
@@ -506,10 +503,54 @@ Common::Error PhoenixVREngine::run() {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN: {
- auto it = _keys.find(event.kbd.keycode);
- if (it != _keys.end()) {
+ int code = -1;
+ switch (event.kbd.keycode) {
+ case Common::KeyCode::KEYCODE_ESCAPE:
+ code = 0;
+ break;
+ case Common::KeyCode::KEYCODE_F1:
+ code = 1;
+ break;
+ case Common::KeyCode::KEYCODE_F2:
+ code = 2;
+ break;
+ case Common::KeyCode::KEYCODE_F3:
+ code = 3;
+ break;
+ case Common::KeyCode::KEYCODE_F4:
+ code = 4;
+ break;
+ case Common::KeyCode::KEYCODE_F5:
+ code = 5;
+ break;
+ case Common::KeyCode::KEYCODE_F6:
+ code = 6;
+ break;
+ case Common::KeyCode::KEYCODE_F7:
+ code = 7;
+ break;
+ case Common::KeyCode::KEYCODE_F8:
+ code = 8;
+ break;
+ case Common::KeyCode::KEYCODE_F9:
+ code = 9;
+ break;
+ case Common::KeyCode::KEYCODE_F10:
+ code = 10;
+ break;
+ case Common::KeyCode::KEYCODE_RETURN:
+ code = 11;
+ break;
+ case Common::KeyCode::KEYCODE_TAB:
+ code = 12;
+ break;
+ default:
+ break;
+ }
+
+ if (code >= 0) {
debug("matched code %d", static_cast<int>(event.kbd.keycode));
- goToWarp(it->_value, true);
+ goToWarp(_lockKey[code], true);
}
} break;
case Common::EVENT_MOUSEMOVE:
@@ -537,9 +578,9 @@ Common::Error PhoenixVREngine::run() {
} break;
case Common::EVENT_RBUTTONUP: {
debug("right click");
- auto it = _keys.find(Common::KeyCode::KEYCODE_TAB);
- if (it != _keys.end())
- goToWarp(it->_value, true);
+ auto &rclick = _lockKey[12];
+ if (!rclick.empty())
+ goToWarp(rclick, true);
}
default:
break;
@@ -668,6 +709,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
for (uint i = 0; i != 12; ++i) {
auto lockKey = ms.readString(0, 257);
debug("lockKey %d %s", i, lockKey.c_str());
+ _lockKey[i] = lockKey;
}
auto music = ms.readString(0, 257);
auto musicVolume = ms.readUint32LE();
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index e996d4106c3..200706803e8 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -117,7 +117,7 @@ public:
const Region *getRegion(int idx) const;
void resetLockKey();
- void lockKey(Common::KeyCode code, const Common::String &warp);
+ void lockKey(int idx, const Common::String &warp);
void startTimer(float seconds);
void pauseTimer(bool pause, bool deactivate);
void killTimer();
@@ -180,7 +180,7 @@ private:
uint operator()(Common::KeyCode val) const { return static_cast<uint>(val); }
};
- Common::HashMap<Common::KeyCode, Common::String, KeyCodeHash> _keys;
+ Common::Array<Common::String> _lockKey;
Common::Array<Common::String> _variableOrder;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
struct Sound {
Commit: 425aacf0a3dd524cf655ef14f762dfc544e4f7bd
https://github.com/scummvm/scummvm/commit/425aacf0a3dd524cf655ef14f762dfc544e4f7bd
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:10+01:00
Commit Message:
PHOENIXVR: implement state capture, fix 3d sound entries order
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 411299e40d3..fabcf8d5dc8 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -645,6 +645,32 @@ void PhoenixVREngine::captureContext() {
ms.writeUint32LE(0); // current subroutine
ms.writeSint32LE(_prevWarp);
+
+ for (uint i = 0; i != 12; ++i) {
+ writeString(_lockKey[i]);
+ }
+ writeString({}); // music
+ ms.writeUint32LE(0); // musicVolume
+
+ // FIXME: serialize sounds here
+ // sound samples
+ for (uint i = 0; i != 8; ++i) {
+ writeString({});
+ ms.writeUint32LE(255);
+ ms.writeUint32LE(0); // volume
+ }
+
+ // sound samples 3D
+ for (uint i = 0; i != 8; ++i) {
+ writeString({});
+ ms.writeUint32LE(2000); // angle
+ ms.writeUint32LE(255);
+ ms.writeUint32LE(0); // volume
+ }
+
+ auto *state = ms.getData();
+ _capturedState.assign(state, state + ms.size());
+ debug("captured %u bytes of state", _capturedState.size());
}
void PhoenixVREngine::loadSaveSlot(int idx) {
@@ -728,9 +754,9 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
// sound samples 3D
for (uint i = 0; i != 8; ++i) {
auto name = ms.readString(0, 257);
+ auto angle = ms.readUint32LE();
auto vol = ms.readUint32LE();
auto loop = ms.readUint32LE();
- auto angle = ms.readUint32LE();
debug("3d sound: %s %u %u %u", name.c_str(), vol, loop, angle);
if (!name.empty())
playSound(name, vol, -1, true, float(angle * M_PI));
Commit: 8c1b49962f14e4e2bf3dea06d5ac8130b6d6e950
https://github.com/scummvm/scummvm/commit/8c1b49962f14e4e2bf3dea06d5ac8130b6d6e950
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:11+01:00
Commit Message:
PHOENIXVR: stop all sounds on load
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index fabcf8d5dc8..8bc4bdf5e07 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -741,6 +741,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto musicVolume = ms.readUint32LE();
debug("current music %s, volume: %u", music.c_str(), musicVolume);
+ _mixer->stopAll();
// sound samples
for (uint i = 0; i != 8; ++i) {
auto name = ms.readString(0, 257);
Commit: fe11e4170732b95aef8a91546aa0c9132da85122
https://github.com/scummvm/scummvm/commit/fe11e4170732b95aef8a91546aa0c9132da85122
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:11+01:00
Commit Message:
PHOENIXVR: remove inactive sounds, store sounds in captured state
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 8bc4bdf5e07..18a7849c4f9 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -235,7 +235,7 @@ void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int l
_mixer->playStream(Audio::Mixer::kPlainSoundType, &h, Audio::makeWAVStream(f.release(), DisposeAfterUse::YES), -1, volume);
if (loops < 0)
_mixer->loopChannel(h);
- _sounds[sound] = Sound{h, spatial, angle};
+ _sounds[sound] = Sound{h, spatial, angle, volume};
}
void PhoenixVREngine::stopSound(const Common::String &sound) {
@@ -405,14 +405,23 @@ void PhoenixVREngine::tick(float dt) {
_angleY.add(float(da.y) * kSpeedY * dt);
debug("angle %g %g -> %s", _angleX.angle(), _angleY.angle(), currentVRPos().toString().c_str());
}
+ Common::Array<Common::String> finishedSounds;
for (auto &kv : _sounds) {
auto &sound = kv._value;
+ if (!_mixer->isSoundHandleActive(sound.handle)) {
+ finishedSounds.push_back(kv._key);
+ }
if (!sound.spatial)
continue;
int8 balance = 127 * sinf(sound.angle - _angleX.angle());
_mixer->setChannelBalance(sound.handle, balance);
}
+ for (auto &sound : finishedSounds) {
+ debug("sound %s stopped", sound.c_str());
+ _sounds.erase(sound);
+ }
+
if (!_nextScript.empty()) {
loadNextScript();
goToWarp(_script->getInitScript()->vrFile);
@@ -652,20 +661,36 @@ void PhoenixVREngine::captureContext() {
writeString({}); // music
ms.writeUint32LE(0); // musicVolume
- // FIXME: serialize sounds here
+ struct SoundState {
+ Common::String name;
+ uint8 volume;
+ int angle;
+ };
+ Common::Array<SoundState> sounds, sounds3d;
+ for (auto &kv : _sounds) {
+ auto &sound = kv._value;
+ if (sound.spatial)
+ sounds3d.push_back({kv._key, sound.volume, fromAngle(sound.angle)});
+ else
+ sounds.push_back({kv._key, sound.volume, 0});
+ }
+
// sound samples
+ SoundState def{{}, 255, 0};
for (uint i = 0; i != 8; ++i) {
- writeString({});
- ms.writeUint32LE(255);
- ms.writeUint32LE(0); // volume
+ auto *soundState = i < sounds.size() ? &sounds[i] : &def;
+ writeString(soundState->name);
+ ms.writeUint32LE(soundState->volume);
+ ms.writeUint32LE(0); // flags?
}
// sound samples 3D
for (uint i = 0; i != 8; ++i) {
- writeString({});
- ms.writeUint32LE(2000); // angle
- ms.writeUint32LE(255);
- ms.writeUint32LE(0); // volume
+ auto *soundState = i < sounds3d.size() ? &sounds3d[i] : &def;
+ writeString(soundState->name);
+ ms.writeUint32LE(soundState->angle);
+ ms.writeUint32LE(soundState->volume);
+ ms.writeUint32LE(0); // flags?
}
auto *state = ms.getData();
@@ -746,8 +771,8 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
for (uint i = 0; i != 8; ++i) {
auto name = ms.readString(0, 257);
auto vol = ms.readUint32LE();
- auto loop = ms.readUint32LE();
- debug("sound: %s vol: %u loop: %u", name.c_str(), vol, loop);
+ auto flags = ms.readUint32LE();
+ debug("sound: %s vol: %u flags: %u", name.c_str(), vol, flags);
if (!name.empty())
playSound(name, vol, -1);
}
@@ -757,8 +782,8 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto name = ms.readString(0, 257);
auto angle = ms.readUint32LE();
auto vol = ms.readUint32LE();
- auto loop = ms.readUint32LE();
- debug("3d sound: %s %u %u %u", name.c_str(), vol, loop, angle);
+ auto flags = ms.readUint32LE();
+ debug("3d sound: %s vol: %u flags: %u angle: %u", name.c_str(), vol, flags, angle);
if (!name.empty())
playSound(name, vol, -1, true, float(angle * M_PI));
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 200706803e8..76cf7f26faf 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -187,6 +187,7 @@ private:
Audio::SoundHandle handle;
bool spatial;
float angle;
+ uint8 volume;
};
Common::HashMap<Common::String, Sound, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _sounds;
Common::ScopedPtr<Script> _script;
Commit: 417b54e18d038356683ce7a6b630776b41cdaf83
https://github.com/scummvm/scummvm/commit/417b54e18d038356683ce7a6b630776b41cdaf83
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:11+01:00
Commit Message:
PHOENIXVR: remove stream load/save method
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 18a7849c4f9..94cfd73a4a0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -823,10 +823,6 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
delete src;
}
-Common::Error PhoenixVREngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
- return Common::kNoError;
-}
-
PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::SeekableReadStream *stream) {
GameState state;
@@ -858,9 +854,4 @@ PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::Seekable
return state;
}
-Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *stream) {
-
- return Common::kNoError;
-}
-
} // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 76cf7f26faf..c87d5d23743 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -91,9 +91,6 @@ public:
return true;
}
- Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
- Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
-
// Script API
void setNextScript(const Common::String &path);
void goToWarp(const Common::String &warp, bool savePrev = false);
Commit: f7db662abaf0b2f29d225a0bcd332753812bd487
https://github.com/scummvm/scummvm/commit/f7db662abaf0b2f29d225a0bcd332753812bd487
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:11+01:00
Commit Message:
PHOENIXVR: implement save
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 94cfd73a4a0..6b88f24c012 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -49,6 +49,8 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
_pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()),
+ _rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0),
+ _thumbnail(139, 103, _rgb565),
_lockKey(13),
_fov(M_PI_2),
_angleX(M_PI),
@@ -83,6 +85,7 @@ Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
debug("setNextScript %s", nextScript.c_str());
+ _contextScript = nextScript;
auto nextPath = Common::Path(removeDrive(nextScript), '\\');
auto parentDir = nextPath.getParent();
_nextScript = nextPath.getLastComponent();
@@ -172,6 +175,8 @@ void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
if (_prevWarp < 0) {
_prevWarp = _script->getWarp(_warp->vrFile);
assert(_prevWarp >= 0);
+ // saving thumbnail
+ _thumbnail.simpleBlitFrom(*_screen, Graphics::FLIP_V);
}
}
}
@@ -704,7 +709,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
warning("loadSaveSlot: invalid save slot %d", idx);
return;
}
- auto state = loadGameStateObject(slot.get());
+ auto state = loadGameStateObject(*slot);
setNextScript(state.script);
// keep it alive until loading finishes.
@@ -792,18 +797,61 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
}
void PhoenixVREngine::saveSaveSlot(int idx) {
- Common::ScopedPtr<Common::OutSaveFile> slot(_saveFileMan->openForSaving(getSaveStateName(idx)));
+ Common::ScopedPtr<Common::OutSaveFile> slot(_saveFileMan->openForSaving(getSaveStateName(idx), false));
if (!slot) {
warning("saveSaveSlot: invalid save slot %d", idx);
return;
}
+ GameState state;
+ state.script = _contextScript;
+ state.game = _contextLabel;
+
+ static const char *wday[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
+
+ TimeDate td = {};
+ g_system->getTimeAndDate(td);
+ // Saturday 03 01 2026[\x00]23 h 17
+ state.info = Common::String::format("%s %02d %02d %04d%c%02d h %02d", wday[td.tm_wday], td.tm_mday, td.tm_mon + 1, td.tm_year, 0, td.tm_hour, td.tm_min);
+
+ state.thumbWidth = _thumbnail.w;
+ state.thumbHeight = _thumbnail.h;
+ auto *thumbnailPixels = static_cast<byte *>(_thumbnail.getPixels());
+ auto thumbnailSize = _thumbnail.pitch * _thumbnail.h;
+
+ Common::MemoryWriteStreamDynamic dib(DisposeAfterUse::YES);
+ dib.writeUint32LE(0x28);
+ dib.writeUint32LE(_thumbnail.w);
+ dib.writeUint32LE(_thumbnail.h);
+ dib.writeUint16LE(1); // planes
+ dib.writeUint16LE(16);
+ dib.writeUint32LE(3); // compression
+ dib.writeUint32LE(thumbnailSize);
+ dib.writeUint32LE(0);
+ dib.writeUint32LE(0);
+ dib.writeUint32LE(3);
+ dib.writeUint32LE(0);
+
+ // RGB masks
+ dib.writeUint32LE(0xf800);
+ dib.writeUint32LE(0x07e0);
+ dib.writeUint32LE(0x001f);
+
+ assert(dib.size() == 0x28 + 3 * 4);
+ state.dibHeader.assign(dib.getData(), dib.getData() + dib.size());
+
+ state.thumbnail.assign(thumbnailPixels, thumbnailPixels + thumbnailSize);
+ state.state = Common::move(_capturedState);
+ _capturedState.clear();
+
+ saveGameStateObject(*slot, state);
}
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
if (!slot)
return;
- auto state = loadGameStateObject(slot.get());
+ auto state = loadGameStateObject(*slot);
Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
Graphics::Surface thumbnail;
@@ -823,12 +871,12 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
delete src;
}
-PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::SeekableReadStream *stream) {
+PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::SeekableReadStream &stream) {
GameState state;
auto readString = [&]() {
- auto size = stream->readUint32LE();
- return stream->readString(0, size);
+ auto size = stream.readUint32LE();
+ return stream.readString(0, size);
};
state.script = readString();
@@ -837,21 +885,43 @@ PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::Seekable
debug("save.game: %s", state.game.c_str());
state.info = readString();
debug("save.datetime: %s", state.info.c_str());
- uint dibHeaderSize = stream->readUint32LE();
- stream->seek(-4, SEEK_CUR);
+ uint dibHeaderSize = stream.readUint32LE();
+ stream.seek(-4, SEEK_CUR);
state.dibHeader.resize(dibHeaderSize + 3 * 4); // rmask/gmask/bmask
- stream->read(state.dibHeader.data(), state.dibHeader.size());
+ stream.read(state.dibHeader.data(), state.dibHeader.size());
+ debug("DIB header");
+ Common::hexdump(state.dibHeader.data(), state.dibHeader.size());
state.thumbWidth = READ_LE_UINT32(state.dibHeader.data() + 0x04);
state.thumbHeight = READ_LE_UINT32(state.dibHeader.data() + 0x08);
auto imageSize = READ_LE_UINT32(state.dibHeader.data() + 0x14);
debug("save.image %dx%d, %u", state.thumbWidth, state.thumbHeight, imageSize);
state.thumbnail.resize(imageSize);
- stream->read(state.thumbnail.data(), state.thumbnail.size());
- auto gameStateSize = stream->readUint32LE();
+ stream.read(state.thumbnail.data(), state.thumbnail.size());
+ auto gameStateSize = stream.readUint32LE();
debug("save.state %u bytes", gameStateSize);
state.state.resize(gameStateSize);
- stream->read(state.state.data(), state.state.size());
+ stream.read(state.state.data(), state.state.size());
return state;
}
+void PhoenixVREngine::saveGameStateObject(Common::SeekableWriteStream &stream, const GameState &state) {
+ auto writeString = [&](const Common::String &str) {
+ stream.writeUint32LE(str.size() + 1);
+ stream.writeString(str);
+ stream.writeByte(0);
+ };
+
+ writeString(state.script);
+ writeString(state.game);
+ writeString(state.info);
+
+ assert(state.dibHeader.size() == 0x28 + 3 * 4);
+ stream.write(state.dibHeader.data(), state.dibHeader.size());
+
+ stream.write(state.thumbnail.data(), state.thumbnail.size());
+
+ stream.writeUint32LE(state.state.size());
+ stream.write(state.state.data(), state.state.size());
+}
+
} // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index c87d5d23743..1398c3cba92 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -56,6 +56,8 @@ private:
const ADGameDescription *_gameDescription;
Common::RandomSource _randomSource;
Graphics::PixelFormat _pixelFormat;
+ Graphics::PixelFormat _rgb565;
+ Graphics::ManagedSurface _thumbnail;
// Engine APIs
Common::Error run() override;
@@ -218,10 +220,12 @@ private:
Common::Array<byte> thumbnail;
Common::Array<byte> state;
};
+ Common::String _contextScript;
Common::String _contextLabel;
Common::Array<byte> _capturedState;
- GameState loadGameStateObject(Common::SeekableReadStream *stream);
+ GameState loadGameStateObject(Common::SeekableReadStream &stream);
+ void saveGameStateObject(Common::SeekableWriteStream &stream, const GameState &state);
};
extern PhoenixVREngine *g_engine;
Commit: beb835cafa62ec6c8167ac5c9a1cc0163a21f392
https://github.com/scummvm/scummvm/commit/beb835cafa62ec6c8167ac5c9a1cc0163a21f392
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:12+01:00
Commit Message:
PHOENIXVR: do not trigger empty warps for non-assigned key codes
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 6b88f24c012..7b82d41bee3 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -564,7 +564,8 @@ Common::Error PhoenixVREngine::run() {
if (code >= 0) {
debug("matched code %d", static_cast<int>(event.kbd.keycode));
- goToWarp(_lockKey[code], true);
+ if (!_lockKey[code].empty())
+ goToWarp(_lockKey[code], true);
}
} break;
case Common::EVENT_MOUSEMOVE:
Commit: 26167dd22cb8b19075d946c739624e48ba0b2b4f
https://github.com/scummvm/scummvm/commit/26167dd22cb8b19075d946c739624e48ba0b2b4f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:12+01:00
Commit Message:
PHOENIXVR: handle relative mouse position, lock mouse and remove wrapMouse
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 7b82d41bee3..c733f88cf02 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -400,9 +400,9 @@ void PhoenixVREngine::tickTimer(float dt) {
void PhoenixVREngine::tick(float dt) {
tickTimer(dt);
- if (_vr.isVR() && _mousePos != _screenCenter) {
- auto da = _mousePos - _screenCenter;
- _system->warpMouse(_screenCenter.x, _screenCenter.y);
+ if (_vr.isVR() && (_mouseRel.x || _mouseRel.y)) {
+ auto da = _mouseRel;
+ _mouseRel = {};
_mousePos = _screenCenter;
static const float kSpeedX = 0.2f;
static const float kSpeedY = 0.2f;
@@ -442,8 +442,9 @@ void PhoenixVREngine::tick(float dt) {
_vr = VR::loadStatic(_pixelFormat, vr);
if (_vr.isVR()) {
_mousePos = _screenCenter;
- _system->warpMouse(_screenCenter.x, _screenCenter.y);
+ _mouseRel = {};
}
+ _system->lockMouse(_vr.isVR());
}
_regSet.reset(new RegionSet(_warp->testFile));
@@ -570,6 +571,7 @@ Common::Error PhoenixVREngine::run() {
} break;
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
+ _mouseRel += event.relMouse;
break;
case Common::EVENT_LBUTTONUP: {
auto vrPos = currentVRPos();
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 1398c3cba92..76efca2b34e 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -168,7 +168,7 @@ private:
void loadNextScript();
private:
- Common::Point _mousePos;
+ Common::Point _mousePos, _mouseRel;
Common::Path _nextScript;
int _warpIdx = -1;
Script::ConstWarpPtr _warp;
Commit: 305237e8f23c6ec20db9b893c3ee750855b806b9
https://github.com/scummvm/scummvm/commit/305237e8f23c6ec20db9b893c3ee750855b806b9
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:12+01:00
Commit Message:
PHOENIXVR: remove DIB hex dump
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index c733f88cf02..54fee8957d0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -892,8 +892,6 @@ PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::Seekable
stream.seek(-4, SEEK_CUR);
state.dibHeader.resize(dibHeaderSize + 3 * 4); // rmask/gmask/bmask
stream.read(state.dibHeader.data(), state.dibHeader.size());
- debug("DIB header");
- Common::hexdump(state.dibHeader.data(), state.dibHeader.size());
state.thumbWidth = READ_LE_UINT32(state.dibHeader.data() + 0x04);
state.thumbHeight = READ_LE_UINT32(state.dibHeader.data() + 0x08);
auto imageSize = READ_LE_UINT32(state.dibHeader.data() + 0x14);
Commit: 1aed7caa52802750c015e283a176ac3687cda9c6
https://github.com/scummvm/scummvm/commit/1aed7caa52802750c015e283a176ac3687cda9c6
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:13+01:00
Commit Message:
PHOENIXVR: remove face id mapping (save/load slot does not match loading screen index)
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 54fee8957d0..5f0b3d2e3c7 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -862,8 +862,7 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
auto &dst = _vr.getSurface();
Graphics::Surface *src = thumbnail.convertTo(dst.format);
src->flipVertical(src->getRect());
- static const uint8 mapFaceId[] = {0, 3, 2, 1, 5, 4};
- y += mapFaceId[face] * 4 * 256;
+ y += face * 4 * 256;
if (x > 256) {
x -= 256;
y += 256;
Commit: 86967a5ab876636c288e5f8472bcc9bfcbad9544
https://github.com/scummvm/scummvm/commit/86967a5ab876636c288e5f8472bcc9bfcbad9544
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:13+01:00
Commit Message:
PHOENIXVR: allow compression, it compresses save files > 60x roughly
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 5f0b3d2e3c7..873f52cf925 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -800,7 +800,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
}
void PhoenixVREngine::saveSaveSlot(int idx) {
- Common::ScopedPtr<Common::OutSaveFile> slot(_saveFileMan->openForSaving(getSaveStateName(idx), false));
+ Common::ScopedPtr<Common::OutSaveFile> slot(_saveFileMan->openForSaving(getSaveStateName(idx)));
if (!slot) {
warning("saveSaveSlot: invalid save slot %d", idx);
return;
Commit: c0f5a15061642db8ef89851231615ff5100fbc96
https://github.com/scummvm/scummvm/commit/c0f5a15061642db8ef89851231615ff5100fbc96
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:13+01:00
Commit Message:
PHOENIXVR: fix thumbnail scaling/conversion
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 873f52cf925..35ff82d2650 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -176,7 +176,9 @@ void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
_prevWarp = _script->getWarp(_warp->vrFile);
assert(_prevWarp >= 0);
// saving thumbnail
- _thumbnail.simpleBlitFrom(*_screen, Graphics::FLIP_V);
+ Common::ScopedPtr<Graphics::ManagedSurface> screenshot(_screen->scale(_thumbnail.w, _thumbnail.h, true));
+ screenshot->convertToInPlace(_rgb565);
+ _thumbnail.simpleBlitFrom(*screenshot, Graphics::FLIP_V);
}
}
}
Commit: e2a184b6c4215187e863cc0fea9f74c29c89b6b2
https://github.com/scummvm/scummvm/commit/e2a184b6c4215187e863cc0fea9f74c29c89b6b2
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:14+01:00
Commit Message:
PHOENIXVR: do not save non-looped sounds
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 35ff82d2650..cc1985c06ae 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -242,7 +242,7 @@ void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int l
_mixer->playStream(Audio::Mixer::kPlainSoundType, &h, Audio::makeWAVStream(f.release(), DisposeAfterUse::YES), -1, volume);
if (loops < 0)
_mixer->loopChannel(h);
- _sounds[sound] = Sound{h, spatial, angle, volume};
+ _sounds[sound] = Sound{h, spatial, angle, volume, loops};
}
void PhoenixVREngine::stopSound(const Common::String &sound) {
@@ -679,6 +679,8 @@ void PhoenixVREngine::captureContext() {
Common::Array<SoundState> sounds, sounds3d;
for (auto &kv : _sounds) {
auto &sound = kv._value;
+ if (sound.loops >= 0)
+ continue;
if (sound.spatial)
sounds3d.push_back({kv._key, sound.volume, fromAngle(sound.angle)});
else
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 76efca2b34e..7676af6625a 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -187,6 +187,7 @@ private:
bool spatial;
float angle;
uint8 volume;
+ int loops;
};
Common::HashMap<Common::String, Sound, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _sounds;
Common::ScopedPtr<Script> _script;
Commit: ec96e62b293c0966461041cc4e4647b5e72da4c7
https://github.com/scummvm/scummvm/commit/ec96e62b293c0966461041cc4e4647b5e72da4c7
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:14+01:00
Commit Message:
PHOENIXVR: simplify cursor loading
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index cc1985c06ae..9b60abee2df 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -748,15 +748,11 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
_nextWarp = currentWarpIdx;
- for (uint warpIdx = 0; warpIdx != _script->numWarps(); ++warpIdx) {
- auto warp = _script->getWarp(warpIdx);
- assert(warp);
- auto &tests = warp->tests;
- auto &warpCursors = _cursors[warpIdx];
- for (uint testIdx = 0; testIdx < tests.size(); ++testIdx) {
+ for (auto &warpCursors : _cursors) {
+ for (auto &warpCursor : warpCursors) {
auto cursor = ms.readString(0, 257);
- // debug("warp %s.%d: %s", warp->vrFile.c_str(), testIdx, cursor.c_str());
- warpCursors[testIdx] = cursor;
+ debug("cursor %s", cursor.c_str());
+ warpCursor = cursor;
}
}
debug("vars at %08lx", ms.pos());
Commit: ca5cef7a106d888c8bf4624896892be4f1e9a9b1
https://github.com/scummvm/scummvm/commit/ca5cef7a106d888c8bf4624896892be4f1e9a9b1
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:14+01:00
Commit Message:
PHOENIXVR: ignore .VR cursors (original saves have them, but there's no RIFF chunks apart from PIC3D in there)
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9b60abee2df..450269f3b70 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -752,6 +752,10 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
for (auto &warpCursor : warpCursors) {
auto cursor = ms.readString(0, 257);
debug("cursor %s", cursor.c_str());
+ if (cursor.hasSuffix(".VR") || cursor.hasSuffix(".vr")) {
+ debug("ignoring VR cursor, original engine saves `LOAD.VR` as a cursor name at loading screen");
+ cursor.clear();
+ }
warpCursor = cursor;
}
}
Commit: bbf563aa1ac13d7bdceea3fe768a29cc7101bb47
https://github.com/scummvm/scummvm/commit/bbf563aa1ac13d7bdceea3fe768a29cc7101bb47
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:14+01:00
Commit Message:
PHOENIXVR: clean up prev warp
only user interaction update previous warp id and
it's expected that you can't use "lock keys" (shortcuts) while any dialog is active,
or it would just stuck there forever.
any shortcut/rightclick updates thumbnail
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 450269f3b70..e9d52e4da4a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -167,19 +167,15 @@ void PhoenixVREngine::wait(float seconds) {
}
void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
- debug("gotowarp %s", warp.c_str());
+ debug("gotowarp %s, save prev: %d", warp.c_str(), savePrev);
_nextWarp = _script->getWarp(warp);
if (savePrev) {
- assert(_warp);
- // Pretty much a hack to prevent user stuck in inventory
- if (_prevWarp < 0) {
- _prevWarp = _script->getWarp(_warp->vrFile);
- assert(_prevWarp >= 0);
- // saving thumbnail
- Common::ScopedPtr<Graphics::ManagedSurface> screenshot(_screen->scale(_thumbnail.w, _thumbnail.h, true));
- screenshot->convertToInPlace(_rgb565);
- _thumbnail.simpleBlitFrom(*screenshot, Graphics::FLIP_V);
- }
+ assert(_warpIdx >= 0);
+ _prevWarp = _warpIdx;
+ // saving thumbnail
+ Common::ScopedPtr<Graphics::ManagedSurface> screenshot(_screen->scale(_thumbnail.w, _thumbnail.h, true));
+ screenshot->convertToInPlace(_rgb565);
+ _thumbnail.simpleBlitFrom(*screenshot, Graphics::FLIP_V);
}
}
@@ -520,6 +516,8 @@ Common::Error PhoenixVREngine::run() {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN: {
+ if (_prevWarp != -1)
+ break;
int code = -1;
switch (event.kbd.keycode) {
case Common::KeyCode::KEYCODE_ESCAPE:
@@ -571,6 +569,14 @@ Common::Error PhoenixVREngine::run() {
goToWarp(_lockKey[code], true);
}
} break;
+ case Common::EVENT_RBUTTONUP: {
+ if (_prevWarp != -1)
+ break;
+ debug("right click");
+ auto &rclick = _lockKey[12];
+ if (!rclick.empty())
+ goToWarp(rclick, true);
+ } break;
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
_mouseRel += event.relMouse;
@@ -595,12 +601,6 @@ Common::Error PhoenixVREngine::run() {
}
}
} break;
- case Common::EVENT_RBUTTONUP: {
- debug("right click");
- auto &rclick = _lockKey[12];
- if (!rclick.empty())
- goToWarp(rclick, true);
- }
default:
break;
}
Commit: ff2b940974bd327cc8b5f16322453ef7bc412df0
https://github.com/scummvm/scummvm/commit/ff2b940974bd327cc8b5f16322453ef7bc412df0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:15+01:00
Commit Message:
VIDEO: 4XM: use unsigned for dc
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index cffbcdc7cb1..99809cf53fc 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -322,7 +322,7 @@ namespace {
template<bool Scale>
void mcdc(uint16_t *__restrict__ dst, const uint16_t *__restrict__ src, int log2w,
- int log2h, int stride, int dc) {
+ int log2h, int stride, uint dc) {
int h = 1 << log2h;
int w = 1 << log2w;
for (int i = 0; i < h; i++) {
@@ -372,6 +372,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
}
return;
}
+
if (code == 0) {
assert(byteStream.pos() < byteStream.size());
src += _mv[byteStream.readByte()];
@@ -381,11 +382,11 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
assert(byteStream.pos() < byteStream.size());
assert(wordStream.pos() + 2 <= wordStream.size());
src += _mv[byteStream.readByte()];
- dc = wordStream.readSint16LE();
+ dc = wordStream.readUint16LE();
} else if (code == 5) {
assert(wordStream.pos() + 2 <= wordStream.size());
scale = false;
- dc = wordStream.readSint16LE();
+ dc = wordStream.readUint16LE();
} else {
error("invalid code %d (steps %u,%u)", code, log2w, log2h);
}
Commit: f2b20ff19e2a2d5c26ebf0eabf66c0ba21d28290
https://github.com/scummvm/scummvm/commit/f2b20ff19e2a2d5c26ebf0eabf66c0ba21d28290
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:15+01:00
Commit Message:
PHOENIXVR: fix tile split for save slots thumbnails
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index e9d52e4da4a..9cae03e279e 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -867,12 +867,24 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
Graphics::Surface *src = thumbnail.convertTo(dst.format);
src->flipVertical(src->getRect());
y += face * 4 * 256;
+ bool splitV = true;
if (x > 256) {
x -= 256;
y += 256;
+ splitV = false;
+ }
+
+ int tileY = y / 256;
+ auto srcRect = src->getRect();
+ short srcSplitY = MIN(y + thumbnail.h, (tileY + 1) * 256) - y;
+ if (splitV)
+ srcRect.bottom = srcSplitY;
+ dst.copyRectToSurface(*src, x, y, srcRect);
+ if (splitV) {
+ srcRect.top = srcSplitY;
+ srcRect.bottom = src->h;
+ dst.copyRectToSurface(*src, x, (tileY + 3) * 256, srcRect);
}
- // FIXME: clip vertically here.
- dst.copyRectToSurface(*src, x, y, src->getRect());
src->free();
delete src;
}
Commit: ed1520c4f98c8769627752b064db910a36fc7c5d
https://github.com/scummvm/scummvm/commit/ed1520c4f98c8769627752b064db910a36fc7c5d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:15+01:00
Commit Message:
PHOENIXVR: call resetLockKey
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 12e278c5169..0ab3268e101 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -449,8 +449,7 @@ struct HideCursor : public Script::Command {
struct ResetLockKey : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
- debug("resetlockkey");
- // FIXME: scripts don't restore lockkey after reset
+ g_engine->resetLockKey();
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9cae03e279e..f3c4c7b198c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -302,6 +302,7 @@ void PhoenixVREngine::playAnimation(const Common::String &name, const Common::St
}
void PhoenixVREngine::resetLockKey() {
+ debug("resetlockkey");
_prevWarp = -1; // original game does only this o_O
}
Commit: 7da3a859bba891fbeeca62d232d3092aa4ed976a
https://github.com/scummvm/scummvm/commit/7da3a859bba891fbeeca62d232d3092aa4ed976a
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:16+01:00
Commit Message:
PHOENIXVR: add list save/query save skeleton
Changed paths:
engines/phoenixvr/metaengine.cpp
engines/phoenixvr/metaengine.h
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 4dba22b041e..4e8d4caaeaa 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/savefile.h"
#include "common/translation.h"
#include "phoenixvr/detection.h"
@@ -52,9 +53,40 @@ Common::Error PhoenixVRMetaEngine::createInstance(OSystem *syst, Engine **engine
return Common::kNoError;
}
+SaveStateList PhoenixVRMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ Common::String pattern(getSavegameFilePattern(target));
+
+ filenames = saveFileMan->listSavefiles(pattern);
+
+ SaveStateList saveList;
+ for (const auto &file : filenames) {
+ auto dotPos = file.rfind('.');
+ if (dotPos == file.npos)
+ continue;
+ int slotNum = atoi(file.c_str() + dotPos + 1);
+
+ if (slotNum >= 0 && slotNum <= getMaximumSaveSlot()) {
+ SaveStateDescriptor desc = querySaveMetaInfos(target, slotNum);
+ desc.setSaveSlot(slotNum);
+ desc.setDeletableFlag(true);
+ saveList.push_back(desc);
+ }
+ }
+
+ // Sort saves based on slot number.
+ Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
+ return saveList;
+}
+
+SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ SaveStateDescriptor desc;
+ return desc;
+}
+
bool PhoenixVRMetaEngine::hasFeature(MetaEngineFeature f) const {
- return checkExtendedSaves(f) ||
- (f == kSupportsLoadingDuringStartup);
+ return f == kSimpleSavesNames || f == kSupportsListSaves || f == kSupportsLoadingDuringStartup || f == kSavesSupportThumbnail;
}
#if PLUGIN_ENABLED_DYNAMIC(PHOENIXVR)
diff --git a/engines/phoenixvr/metaengine.h b/engines/phoenixvr/metaengine.h
index c621ee6c1b5..7c16eeb6a8d 100644
--- a/engines/phoenixvr/metaengine.h
+++ b/engines/phoenixvr/metaengine.h
@@ -37,6 +37,14 @@ public:
*/
bool hasFeature(MetaEngineFeature f) const override;
+ int getMaximumSaveSlot() const override {
+ return 10;
+ }
+
+ SaveStateList listSaves(const char *target) const override;
+
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
+
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
};
Commit: 6c871179a5a86b18c9fde981d4036ee8c2e3e76b
https://github.com/scummvm/scummvm/commit/6c871179a5a86b18c9fde981d4036ee8c2e3e76b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:16+01:00
Commit Message:
PHOENIXVR: add game state object
Changed paths:
A engines/phoenixvr/game_state.cpp
A engines/phoenixvr/game_state.h
engines/phoenixvr/metaengine.cpp
engines/phoenixvr/module.mk
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/game_state.cpp b/engines/phoenixvr/game_state.cpp
new file mode 100644
index 00000000000..dab7ff86fa4
--- /dev/null
+++ b/engines/phoenixvr/game_state.cpp
@@ -0,0 +1,66 @@
+#include "phoenixvr/game_state.h"
+#include "common/debug.h"
+#include "graphics/surface.h"
+
+namespace PhoenixVR {
+GameState GameState::load(Common::SeekableReadStream &stream) {
+ GameState state;
+
+ auto readString = [&]() {
+ auto size = stream.readUint32LE();
+ return stream.readString(0, size);
+ };
+
+ state.script = readString();
+ debug("save.script: %s", state.script.c_str());
+ state.game = readString();
+ debug("save.game: %s", state.game.c_str());
+ state.info = readString();
+ debug("save.datetime: %s", state.info.c_str());
+ uint dibHeaderSize = stream.readUint32LE();
+ stream.seek(-4, SEEK_CUR);
+ state.dibHeader.resize(dibHeaderSize + 3 * 4); // rmask/gmask/bmask
+ stream.read(state.dibHeader.data(), state.dibHeader.size());
+ state.thumbWidth = READ_LE_UINT32(state.dibHeader.data() + 0x04);
+ state.thumbHeight = READ_LE_UINT32(state.dibHeader.data() + 0x08);
+ auto imageSize = READ_LE_UINT32(state.dibHeader.data() + 0x14);
+ debug("save.image %dx%d, %u", state.thumbWidth, state.thumbHeight, imageSize);
+ state.thumbnail.resize(imageSize);
+ stream.read(state.thumbnail.data(), state.thumbnail.size());
+ auto gameStateSize = stream.readUint32LE();
+ debug("save.state %u bytes", gameStateSize);
+ state.state.resize(gameStateSize);
+ stream.read(state.state.data(), state.state.size());
+ return state;
+}
+
+void GameState::save(Common::SeekableWriteStream &stream) const {
+ auto writeString = [&](const Common::String &str) {
+ stream.writeUint32LE(str.size() + 1);
+ stream.writeString(str);
+ stream.writeByte(0);
+ };
+
+ writeString(script);
+ writeString(game);
+ writeString(info);
+
+ assert(dibHeader.size() == 0x28 + 3 * 4);
+ stream.write(dibHeader.data(), dibHeader.size());
+
+ stream.write(thumbnail.data(), thumbnail.size());
+
+ stream.writeUint32LE(state.size());
+ stream.write(state.data(), state.size());
+}
+
+Graphics::Surface *GameState::getThumbnail(const Graphics::PixelFormat &fmt) {
+ Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
+ Graphics::Surface th;
+ th.init(thumbWidth, thumbHeight, thumbnail.size() / thumbHeight, thumbnail.data(), rgb565);
+ Graphics::Surface *src = th.convertTo(fmt);
+ src->flipVertical(src->getRect());
+ return src;
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/game_state.h b/engines/phoenixvr/game_state.h
new file mode 100644
index 00000000000..8e8f26f02fe
--- /dev/null
+++ b/engines/phoenixvr/game_state.h
@@ -0,0 +1,54 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_GAME_STATE_H
+#define PHOENIXVR_GAME_STATE_H
+
+#include "common/array.h"
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "common/stream.h"
+
+namespace Graphics {
+struct Surface;
+struct PixelFormat;
+} // namespace Graphics
+
+namespace PhoenixVR {
+
+struct GameState {
+ Common::String script;
+ Common::String game;
+ Common::String info;
+ Common::Array<byte> dibHeader;
+ int16 thumbWidth;
+ int16 thumbHeight;
+ Common::Array<byte> thumbnail;
+ Common::Array<byte> state;
+
+ static GameState load(Common::SeekableReadStream &stream);
+ void save(Common::SeekableWriteStream &stream) const;
+
+ Graphics::Surface *getThumbnail(const Graphics::PixelFormat &fmt);
+};
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 4e8d4caaeaa..8e113dc87e8 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -23,6 +23,7 @@
#include "common/translation.h"
#include "phoenixvr/detection.h"
+#include "phoenixvr/game_state.h"
#include "phoenixvr/metaengine.h"
#include "phoenixvr/phoenixvr.h"
@@ -69,8 +70,6 @@ SaveStateList PhoenixVRMetaEngine::listSaves(const char *target) const {
if (slotNum >= 0 && slotNum <= getMaximumSaveSlot()) {
SaveStateDescriptor desc = querySaveMetaInfos(target, slotNum);
- desc.setSaveSlot(slotNum);
- desc.setDeletableFlag(true);
saveList.push_back(desc);
}
}
@@ -80,13 +79,35 @@ SaveStateList PhoenixVRMetaEngine::listSaves(const char *target) const {
return saveList;
}
-SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target, int slotIdx) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+
+ auto fname = getSavegameFile(slotIdx, target);
+ Common::ScopedPtr<Common::InSaveFile> slot(saveFileMan->openForLoading(fname));
+ if (!slot)
+ return {};
+
+ auto state = PhoenixVR::GameState::load(*slot);
+
SaveStateDescriptor desc;
+ desc.setSaveSlot(slotIdx);
+ desc.setDeletableFlag(true);
+ desc.setDescription(state.game + " " + state.info);
+ desc.setThumbnail(state.getThumbnail(Graphics::BlendBlit::getSupportedPixelFormat()));
return desc;
}
bool PhoenixVRMetaEngine::hasFeature(MetaEngineFeature f) const {
- return f == kSimpleSavesNames || f == kSupportsListSaves || f == kSupportsLoadingDuringStartup || f == kSavesSupportThumbnail;
+ switch (f) {
+ case kSimpleSavesNames:
+ case kSupportsListSaves:
+ case kSupportsLoadingDuringStartup:
+ case kSavesSupportThumbnail:
+ case kSavesSupportMetaInfo:
+ return true;
+ default:
+ return false;
+ }
}
#if PLUGIN_ENABLED_DYNAMIC(PHOENIXVR)
diff --git a/engines/phoenixvr/module.mk b/engines/phoenixvr/module.mk
index 22c89ac1631..bb2fa944861 100644
--- a/engines/phoenixvr/module.mk
+++ b/engines/phoenixvr/module.mk
@@ -1,10 +1,11 @@
MODULE := engines/phoenixvr
MODULE_OBJS = \
- pakf.o \
- phoenixvr.o \
+ game_state.o \
console.o \
metaengine.o \
+ pakf.o \
+ phoenixvr.o \
rectf.o \
region_set.o \
script.o \
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index f3c4c7b198c..3c531bdd090 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -35,6 +35,7 @@
#include "graphics/surface.h"
#include "image/pcx.h"
#include "phoenixvr/console.h"
+#include "phoenixvr/game_state.h"
#include "phoenixvr/pakf.h"
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
@@ -504,10 +505,12 @@ Common::Error PhoenixVREngine::run() {
// Set the engine's debugger console
setDebugger(new Console());
+#if 0
// If a savegame was selected from the launcher, load it
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot != -1)
(void)loadGameState(saveSlot);
+#endif
Common::Event event;
@@ -717,7 +720,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
warning("loadSaveSlot: invalid save slot %d", idx);
return;
}
- auto state = loadGameStateObject(*slot);
+ auto state = GameState::load(*slot);
setNextScript(state.script);
// keep it alive until loading finishes.
@@ -852,21 +855,15 @@ void PhoenixVREngine::saveSaveSlot(int idx) {
state.state = Common::move(_capturedState);
_capturedState.clear();
- saveGameStateObject(*slot, state);
+ state.save(*slot);
}
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
if (!slot)
return;
- auto state = loadGameStateObject(*slot);
+ auto state = GameState::load(*slot);
- Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
- Graphics::Surface thumbnail;
- thumbnail.init(state.thumbWidth, state.thumbHeight, state.thumbnail.size() / state.thumbHeight, state.thumbnail.data(), rgb565);
- auto &dst = _vr.getSurface();
- Graphics::Surface *src = thumbnail.convertTo(dst.format);
- src->flipVertical(src->getRect());
y += face * 4 * 256;
bool splitV = true;
if (x > 256) {
@@ -875,9 +872,11 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
splitV = false;
}
+ auto &dst = _vr.getSurface();
+ auto *src = state.getThumbnail(dst.format);
int tileY = y / 256;
auto srcRect = src->getRect();
- short srcSplitY = MIN(y + thumbnail.h, (tileY + 1) * 256) - y;
+ short srcSplitY = MIN(y + src->h, (tileY + 1) * 256) - y;
if (splitV)
srcRect.bottom = srcSplitY;
dst.copyRectToSurface(*src, x, y, srcRect);
@@ -890,55 +889,4 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
delete src;
}
-PhoenixVREngine::GameState PhoenixVREngine::loadGameStateObject(Common::SeekableReadStream &stream) {
- GameState state;
-
- auto readString = [&]() {
- auto size = stream.readUint32LE();
- return stream.readString(0, size);
- };
-
- state.script = readString();
- debug("save.script: %s", state.script.c_str());
- state.game = readString();
- debug("save.game: %s", state.game.c_str());
- state.info = readString();
- debug("save.datetime: %s", state.info.c_str());
- uint dibHeaderSize = stream.readUint32LE();
- stream.seek(-4, SEEK_CUR);
- state.dibHeader.resize(dibHeaderSize + 3 * 4); // rmask/gmask/bmask
- stream.read(state.dibHeader.data(), state.dibHeader.size());
- state.thumbWidth = READ_LE_UINT32(state.dibHeader.data() + 0x04);
- state.thumbHeight = READ_LE_UINT32(state.dibHeader.data() + 0x08);
- auto imageSize = READ_LE_UINT32(state.dibHeader.data() + 0x14);
- debug("save.image %dx%d, %u", state.thumbWidth, state.thumbHeight, imageSize);
- state.thumbnail.resize(imageSize);
- stream.read(state.thumbnail.data(), state.thumbnail.size());
- auto gameStateSize = stream.readUint32LE();
- debug("save.state %u bytes", gameStateSize);
- state.state.resize(gameStateSize);
- stream.read(state.state.data(), state.state.size());
- return state;
-}
-
-void PhoenixVREngine::saveGameStateObject(Common::SeekableWriteStream &stream, const GameState &state) {
- auto writeString = [&](const Common::String &str) {
- stream.writeUint32LE(str.size() + 1);
- stream.writeString(str);
- stream.writeByte(0);
- };
-
- writeString(state.script);
- writeString(state.game);
- writeString(state.info);
-
- assert(state.dibHeader.size() == 0x28 + 3 * 4);
- stream.write(state.dibHeader.data(), state.dibHeader.size());
-
- stream.write(state.thumbnail.data(), state.thumbnail.size());
-
- stream.writeUint32LE(state.state.size());
- stream.write(state.state.data(), state.state.size());
-}
-
} // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 7676af6625a..2a78d9da13b 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -46,6 +46,7 @@
namespace PhoenixVR {
struct PhoenixVRGameDescription;
+struct GameState;
class PhoenixVREngine : public Engine {
private:
@@ -90,7 +91,7 @@ public:
return true;
}
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
- return true;
+ return _prevWarp == -1;
}
// Script API
@@ -211,22 +212,9 @@ private:
byte _timerFlags = 0;
float _timer = 0;
- struct GameState {
- Common::String script;
- Common::String game;
- Common::String info;
- Common::Array<byte> dibHeader;
- int16 thumbWidth;
- int16 thumbHeight;
- Common::Array<byte> thumbnail;
- Common::Array<byte> state;
- };
Common::String _contextScript;
Common::String _contextLabel;
Common::Array<byte> _capturedState;
-
- GameState loadGameStateObject(Common::SeekableReadStream &stream);
- void saveGameStateObject(Common::SeekableWriteStream &stream, const GameState &state);
};
extern PhoenixVREngine *g_engine;
Commit: 1e75108802ab0de28c0c267b78feee805b3ab987
https://github.com/scummvm/scummvm/commit/1e75108802ab0de28c0c267b78feee805b3ab987
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:16+01:00
Commit Message:
PHOENIXVR: disable save from launcher, saving requires CaptureState called from script
Changed paths:
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 2a78d9da13b..c68eec429f3 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -91,7 +91,7 @@ public:
return true;
}
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
- return _prevWarp == -1;
+ return false;
}
// Script API
Commit: 459c720e3346af67a6a96cf8742374bc2e6a2b62
https://github.com/scummvm/scummvm/commit/459c720e3346af67a6a96cf8742374bc2e6a2b62
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:16+01:00
Commit Message:
VIDEO: 4XM: do not calculate dst/src if we don't need them
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 99809cf53fc..f33ea34291d 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -344,8 +344,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
bool scale = true;
int dc = 0;
- auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
- auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
auto pitch = frame->pitch / frame->format.bytesPerPixel;
assert(_frame->pitch == frame->pitch);
@@ -362,6 +360,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
decode_pfrm_block(frame, x + dx, y, log2w, log2h, bs, wordStream, byteStream);
return;
} else if (code == 6) {
+ auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
assert(wordStream.pos() + 4 <= wordStream.size());
if (log2w) {
dst[0] = wordStream.readUint16LE();
@@ -373,6 +372,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
return;
}
+ auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
+ auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
if (code == 0) {
assert(byteStream.pos() < byteStream.size());
src += _mv[byteStream.readByte()];
Commit: 89af1aa20babf9c4de94b1ed696303f5b407399b
https://github.com/scummvm/scummvm/commit/89af1aa20babf9c4de94b1ed696303f5b407399b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:17+01:00
Commit Message:
VIDEO: 4XM: remove restrict, dst/src can overlap
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index f33ea34291d..416905844d8 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -321,7 +321,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
namespace {
template<bool Scale>
-void mcdc(uint16_t *__restrict__ dst, const uint16_t *__restrict__ src, int log2w,
+void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
int log2h, int stride, uint dc) {
int h = 1 << log2h;
int w = 1 << log2w;
Commit: eb7ea22f4307e4f8f6f0df8c52f0636ddfcf7239
https://github.com/scummvm/scummvm/commit/eb7ea22f4307e4f8f6f0df8c52f0636ddfcf7239
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:17+01:00
Commit Message:
VIDEO: 4XM: use word size for mcdc access, separate cases
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 416905844d8..9c9dfd4ea32 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -323,14 +323,48 @@ namespace {
template<bool Scale>
void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
int log2h, int stride, uint dc) {
+ dc |= dc << 16;
int h = 1 << log2h;
- int w = 1 << log2w;
- for (int i = 0; i < h; i++) {
- for (int j = 0; j < w; ++j)
- dst[j] = Scale ? src[j] + dc : dc;
- if (Scale)
+ if (Scale) {
+ for (int i = 0; i < h; ++i) {
+ auto *dst32 = reinterpret_cast<uint32_t *>(dst);
+ auto *src32 = reinterpret_cast<const uint32_t *>(src);
+ switch (log2w) {
+ case 3:
+ dst32[2] = src32[2] + dc;
+ dst32[3] = src32[3] + dc;
+ // fall through
+ case 2:
+ dst32[1] = src32[1] + dc;
+ // fall through
+ case 1:
+ dst32[0] = src32[0] + dc;
+ break;
+ case 0:
+ *dst = *src + dc;
+ }
src += stride;
- dst += stride;
+ dst += stride;
+ }
+ } else {
+ for (int i = 0; i < h; ++i) {
+ auto *dst32 = reinterpret_cast<uint32_t *>(dst);
+ switch (log2w) {
+ case 3:
+ dst32[2] = dc;
+ dst32[3] = dc;
+ // fall through
+ case 2:
+ dst32[1] = dc;
+ // fall through
+ case 1:
+ dst32[0] = dc;
+ break;
+ case 0:
+ *dst = dc;
+ }
+ dst += stride;
+ }
}
}
} // namespace
@@ -341,8 +375,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
assert(index >= 0);
auto &huff = _blockType[index];
auto code = huff.next(bs);
- bool scale = true;
- int dc = 0;
auto pitch = frame->pitch / frame->format.bytesPerPixel;
assert(_frame->pitch == frame->pitch);
@@ -371,31 +403,28 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame
}
return;
}
+ if (code == 3 && _version >= 2)
+ return;
auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
if (code == 0) {
assert(byteStream.pos() < byteStream.size());
src += _mv[byteStream.readByte()];
- } else if (code == 3 && _version >= 2) {
- return;
+ mcdc<true>(dst, src, log2w, log2h, pitch, 0);
} else if (code == 4) {
assert(byteStream.pos() < byteStream.size());
assert(wordStream.pos() + 2 <= wordStream.size());
src += _mv[byteStream.readByte()];
- dc = wordStream.readUint16LE();
+ auto dc = wordStream.readUint16LE();
+ mcdc<true>(dst, src, log2w, log2h, pitch, dc);
} else if (code == 5) {
assert(wordStream.pos() + 2 <= wordStream.size());
- scale = false;
- dc = wordStream.readUint16LE();
+ auto dc = wordStream.readUint16LE();
+ mcdc<false>(dst, src, log2w, log2h, pitch, dc);
} else {
error("invalid code %d (steps %u,%u)", code, log2w, log2h);
}
-
- if (scale)
- mcdc<true>(dst, src, log2w, log2h, pitch, dc);
- else
- mcdc<false>(dst, src, log2w, log2h, pitch, dc);
}
void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *stream) {
Commit: 966bd667a98aa2196d0248d5ad1a15eb5e6bee70
https://github.com/scummvm/scummvm/commit/966bd667a98aa2196d0248d5ad1a15eb5e6bee70
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:17+01:00
Commit Message:
PHOENIXVR: stop sound handle before erasing
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 3c531bdd090..a51695a9a7c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -414,6 +414,7 @@ void PhoenixVREngine::tick(float dt) {
for (auto &kv : _sounds) {
auto &sound = kv._value;
if (!_mixer->isSoundHandleActive(sound.handle)) {
+ _mixer->stopHandle(sound.handle);
finishedSounds.push_back(kv._key);
}
if (!sound.spatial)
Commit: eac9ab5b1b22db540618e0cc94ee872caa269200
https://github.com/scummvm/scummvm/commit/eac9ab5b1b22db540618e0cc94ee872caa269200
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:18+01:00
Commit Message:
PHOENIXVR: add hover argument to test
Changed paths:
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 9561aeaa797..4953ee2fab8 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -270,9 +270,13 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (!_currentWarp)
error("test without warp");
auto idx = p.nextInt();
+ auto hover = 0;
if (!_currentWarp)
error("text must have parent wrap section");
- _currentTest.reset(new Test{idx, {}});
+ if (p.maybe(',')) {
+ hover = p.nextInt();
+ }
+ _currentTest.reset(new Test{idx, hover, {}});
_currentWarp->tests.push_back(_currentTest);
} else {
error("invalid [] directive on line %u: %s", lineno, line.c_str());
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 3c6f20698f4..ba3ee0383ce 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -69,6 +69,7 @@ public:
struct Test {
int idx;
+ int hover;
Scope scope;
};
using TestPtr = Common::SharedPtr<Test>;
Commit: 987bcfaf9f1e88380ea77dadda7e8382777cd8f0
https://github.com/scummvm/scummvm/commit/987bcfaf9f1e88380ea77dadda7e8382777cd8f0
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:18+01:00
Commit Message:
PHOENIXVR: implement mouse hover leave
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index a51695a9a7c..7dbb3d443b9 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -170,6 +170,7 @@ void PhoenixVREngine::wait(float seconds) {
void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
debug("gotowarp %s, save prev: %d", warp.c_str(), savePrev);
_nextWarp = _script->getWarp(warp);
+ _hoverIndex = -1;
if (savePrev) {
assert(_warpIdx >= 0);
_prevWarp = _warpIdx;
@@ -464,17 +465,30 @@ void PhoenixVREngine::tick(float dt) {
Graphics::Surface *cursor = nullptr;
auto &cursors = _cursors[_warpIdx];
- for (uint i = 0; i != cursors.size(); ++i) {
+ for (int i = 0, n = cursors.size(); i != n; ++i) {
auto *region = getRegion(i);
if (!region)
continue;
if (_vr.isVR() ? region->contains3D(currentVRPos()) : region->contains2D(_mousePos.x, _mousePos.y)) {
+ auto test = _warp->getTest(i);
+ if (test && test->hover == 1 && _hoverIndex < 0) {
+ debug("executing hover test %d", i);
+ _hoverIndex = i;
+ executeTest(i);
+ }
+
auto &name = cursors[i];
cursor = loadCursor(name);
if (!cursor)
cursor = loadCursor(_defaultCursor[1]);
- break;
+ } else if (i == _hoverIndex) {
+ debug("leaving hover region");
+ auto leave = _warp->getTest(i + 1);
+ if (leave && leave->hover == 2) {
+ executeTest(i + 1);
+ }
+ _hoverIndex = -1;
}
}
if (!cursor)
@@ -599,6 +613,10 @@ Common::Error PhoenixVREngine::run() {
if (!region)
continue;
+ auto test = _warp->getTest(i);
+ if (test && test->hover != 0)
+ continue;
+
if (_vr.isVR() ? region->contains3D(vrPos) : region->contains2D(event.mouse.x, event.mouse.y)) {
debug("click region %u", i);
executeTest(i);
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index c68eec429f3..6967e50a423 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -175,6 +175,7 @@ private:
Script::ConstWarpPtr _warp;
int _nextWarp = -1;
int _prevWarp = -1;
+ int _hoverIndex = -1;
struct KeyCodeHash : public Common::UnaryFunction<Common::KeyCode, uint> {
uint operator()(Common::KeyCode val) const { return static_cast<uint>(val); }
Commit: ad15fcacf1a9092511b2cf29aa1c071e5e17bd52
https://github.com/scummvm/scummvm/commit/ad15fcacf1a9092511b2cf29aa1c071e5e17bd52
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:18+01:00
Commit Message:
PHOENIXVR: output regions - helps navigate dark maze
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 7dbb3d443b9..7165a2533e7 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -410,6 +410,12 @@ void PhoenixVREngine::tick(float dt) {
_angleX.add(float(da.x) * kSpeedX * dt);
_angleY.add(float(da.y) * kSpeedY * dt);
debug("angle %g %g -> %s", _angleX.angle(), _angleY.angle(), currentVRPos().toString().c_str());
+ if (_regSet) {
+ for (uint i = 0, n = _regSet->size(); i != n; ++i) {
+ auto ® = _regSet->getRegion(i);
+ debug("region %d: %s, in: %d", i, reg.toString().c_str(), reg.contains3D(currentVRPos()));
+ }
+ }
}
Common::Array<Common::String> finishedSounds;
for (auto &kv : _sounds) {
Commit: 0e2da70dc6b552600a958d0425242be20b7cc396
https://github.com/scummvm/scummvm/commit/0e2da70dc6b552600a958d0425242be20b7cc396
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:19+01:00
Commit Message:
PHOENIXVR: use case insensitive subdirectory search (fixes end of CD1)
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 7165a2533e7..1b54a555b5e 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -87,17 +87,20 @@ Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
debug("setNextScript %s", nextScript.c_str());
_contextScript = nextScript;
+ if (nextScript.find('\\') == nextScript.npos) {
+ // simple filename, e.g. "script.lst"
+ _nextScript = nextScript;
+ return;
+ }
+
auto nextPath = Common::Path(removeDrive(nextScript), '\\');
auto parentDir = nextPath.getParent();
_nextScript = nextPath.getLastComponent();
auto path = ConfMan.getPath("path");
- auto dataDir = path;
- dataDir.appendInPlace(Common::Path("/"));
- dataDir.appendInPlace(parentDir);
SearchMan.clear();
- debug("adding %s to search man", dataDir.toString().c_str());
- SearchMan.addDirectory(dataDir, 0, 1, true);
+ debug("adding %s ~ %s to search man", path.toString().c_str(), parentDir.toString().c_str());
+ SearchMan.addSubDirectoryMatching(Common::FSNode{path}, parentDir.toString(), true, 2, false);
}
void PhoenixVREngine::loadNextScript() {
Commit: 9871c6dc6b2f193e9b0d34c75f84c6a29b4dde36
https://github.com/scummvm/scummvm/commit/9871c6dc6b2f193e9b0d34c75f84c6a29b4dde36
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:19+01:00
Commit Message:
PHOENIXVR: add SaveVariable plugin
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 0ab3268e101..d2621e23a6a 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -319,6 +319,13 @@ struct RolloverSecretaire : public Script::Command {
}
};
+struct SaveVariable : public Script::Command {
+ SaveVariable(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("SaveVariable()");
+ }
+};
+
#define PLUGIN_LIST(E) \
E(Add) \
E(ChangeCurseur) \
@@ -341,6 +348,7 @@ struct RolloverSecretaire : public Script::Command {
E(Play_Movie) \
E(RolloverMalette) \
E(RolloverSecretaire) \
+ E(SaveVariable) \
E(StartTimer) \
E(Sub) \
E(Until) \
Commit: 8f5391f1cc4e2bc6a9053bbde0751459a745c897
https://github.com/scummvm/scummvm/commit/8f5391f1cc4e2bc6a9053bbde0751459a745c897
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:19+01:00
Commit Message:
PHOENIXVR: call init script before returning to loaded warp
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1b54a555b5e..df94c4b6290 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -755,6 +755,11 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
auto currentScript = Common::move(_script);
assert(!_nextScript.empty());
loadNextScript();
+ {
+ auto test = _script->getWarp(0)->getDefaultTest();
+ Script::ExecutionContext ctx;
+ test->scope.exec(ctx);
+ }
Common::MemoryReadStream ms(state.state.data(), state.state.size());
Commit: 5cf48dc93c773642f170cdcd1ff3d45534bbd37f
https://github.com/scummvm/scummvm/commit/5cf48dc93c773642f170cdcd1ff3d45534bbd37f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:19+01:00
Commit Message:
PHOENIXVR: save clear call
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 3a3d04a40b8..5426fefae50 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -357,7 +357,6 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
Common::Rect::getBlitRect(dst, src, screen->getBounds());
screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
} else {
- screen->clear();
auto w = g_system->getWidth();
auto h = g_system->getHeight();
@@ -393,6 +392,7 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
screen->setPixel(dstX, dstY, color);
}
}
+ screen->addDirtyRect(screen->getBounds());
}
}
Commit: ffd31bbf8d254b0effdb3eb5f7397257de1d6cd5
https://github.com/scummvm/scummvm/commit/ffd31bbf8d254b0effdb3eb5f7397257de1d6cd5
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:20+01:00
Commit Message:
PHOENIXVR: add 'r' hotkey to show 2d/3d regions
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/rectf.h
engines/phoenixvr/region_set.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index df94c4b6290..14362b46166 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -470,7 +470,7 @@ void PhoenixVREngine::tick(float dt) {
_loading = false;
}
- _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov);
+ _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
Graphics::Surface *cursor = nullptr;
auto &cursors = _cursors[_warpIdx];
@@ -544,6 +544,8 @@ Common::Error PhoenixVREngine::run() {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN: {
+ if (event.kbd.keycode == Common::KeyCode::KEYCODE_r)
+ _showRegions = !_showRegions;
if (_prevWarp != -1)
break;
int code = -1;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 6967e50a423..24fd6aeb1bc 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -207,6 +207,7 @@ private:
AngleY _angleY;
Audio::Mixer *_mixer;
bool _loading = false;
+ bool _showRegions = false;
static constexpr byte kPaused = 2;
static constexpr byte kActive = 4;
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index 3ce8e22e6fb..f5740a52061 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -33,6 +33,9 @@ Common::String toString() const {
END_POINT_TYPE(float, PointF)
BEGIN_RECT_TYPE(float, RectF, PointF);
+Common::Rect toRect() const {
+ return Common::Rect(left, top, right, bottom);
+}
Common::String toString() const {
return Common::String::format("%g, %g, %g, %g", left, top, right, bottom);
}
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index 6ca6c133f16..dbadb78fe71 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -50,6 +50,7 @@ class RegionSet {
public:
RegionSet(const Common::String &fname);
uint size() const { return _regions.size(); }
+ const Common::Array<Region> &getRegions() const { return _regions; }
const Region &getRegion(uint idx) const {
return _regions[idx];
}
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 5426fefae50..b57b35a9af9 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -30,7 +30,9 @@
#include "graphics/yuv_to_rgb.h"
#include "image/bmp.h"
#include "math/vector3d.h"
+#include "phoenixvr/angle.h"
#include "phoenixvr/dct_tables.h"
+#include "phoenixvr/region_set.h"
#include "video/4xm_utils.h"
namespace PhoenixVR {
@@ -345,7 +347,7 @@ void VR::playAnimation(const Common::String &name) {
unpack(*_pic, data + offset, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality, &prefixData);
}
-void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
+void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, RegionSet *regSet) {
if (!_pic) {
screen->clear();
return;
@@ -356,6 +358,10 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
Common::Rect src(_pic->getRect());
Common::Rect::getBlitRect(dst, src, screen->getBounds());
screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
+ if (regSet) {
+ for (auto &rect : regSet->getRegions())
+ screen->drawRoundRect(rect.toRect().toRect(), 4, _pic->format.RGBToColor(255, 255, 255), false);
+ }
} else {
auto w = g_system->getWidth();
auto h = g_system->getHeight();
@@ -371,12 +377,28 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
Vector3d up = Vector3d::crossProduct(forward, right); // already normalized
// camera projection
+ static constexpr float kPi2 = M_PI * 2;
float gx = tanf(fov / 2.0f), gy = gx * h / w;
Vector3d incrementX = right * (2 * gx / w);
Vector3d incrementY = up * (2 * gy / h);
Vector3d start = forward - right * gx - up * gy;
Vector3d line = start;
+ float regX, regY, regDX = 0, regDY = 0;
+ if (regSet) {
+ regY = ay - fov / 2;
+ regDX = fov / w;
+ regDY = fov / h;
+ if (regY < 0)
+ regY += M_PI * 2;
+ }
for (int dstY = 0; dstY != h; ++dstY, line += incrementY) {
+ if (regSet) {
+ regX = ax - fov / 2;
+ if (regX < 0)
+ regX += kPi2;
+ if (regX >= kPi2)
+ regX -= kPi2;
+ }
Vector3d pixel = line;
for (int dstX = 0; dstX != w; ++dstX, pixel += incrementX) {
Vector3d ray = pixel.getNormalized();
@@ -389,8 +411,29 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov) {
srcY &= 0xff;
srcY += (tileId << 8);
auto color = _pic->getPixel(srcX, srcY);
+ if (regSet) {
+ regX += regDX;
+ if (regX >= kPi2)
+ regX -= kPi2;
+ for (auto ® : regSet->getRegions()) {
+ if (reg.contains3D(regX, M_PI * 2 - regY)) {
+ byte r, g, b;
+ _pic->format.colorToRGB(color, r, g, b);
+ static constexpr int kGlow = 15;
+ auto dr = MIN(255 - r, kGlow), db = MIN(255 - b, kGlow);
+ r += dr;
+ b += db;
+ color = screen->format.RGBToColor(r, g, b);
+ }
+ }
+ }
screen->setPixel(dstX, dstY, color);
}
+ if (regSet) {
+ regY += regDY;
+ if (regY >= kPi2)
+ regY -= kPi2;
+ }
}
screen->addDirtyRect(screen->getBounds());
}
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 83d3246964c..c884e8c7bdd 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -33,6 +33,7 @@ class Screen;
} // namespace Graphics
namespace PhoenixVR {
+class RegionSet;
class VR {
Common::ScopedPtr<Graphics::Surface> _pic;
bool _vr = false;
@@ -45,7 +46,7 @@ public:
VR &operator=(VR &&) noexcept = default;
static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
- void render(Graphics::Screen *screen, float ax, float ay, float fov);
+ void render(Graphics::Screen *screen, float ax, float ay, float fov, RegionSet *regSet);
bool isVR() const { return _vr; }
void playAnimation(const Common::String &name);
Graphics::Surface &getSurface() { return *_pic; }
Commit: 4fb6cfb8a020fa938733056215ecc98c36889590
https://github.com/scummvm/scummvm/commit/4fb6cfb8a020fa938733056215ecc98c36889590
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:20+01:00
Commit Message:
PHOENIXVR: add BIGF compiler.dat parser
Changed paths:
A engines/phoenixvr/bigf.cpp
A engines/phoenixvr/bigf.h
engines/phoenixvr/module.mk
diff --git a/engines/phoenixvr/bigf.cpp b/engines/phoenixvr/bigf.cpp
new file mode 100644
index 00000000000..1be5b82b63f
--- /dev/null
+++ b/engines/phoenixvr/bigf.cpp
@@ -0,0 +1,49 @@
+/* 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 "phoenixvr/bigf.h"
+#include "common/debug.h"
+#include "common/file.h"
+
+namespace PhoenixVR {
+BIGF::BIGF(const Common::String &path) : _path(path) {
+ Common::File file;
+ if (!file.open(Common::Path{path})) {
+ error("can't open %s", path.c_str());
+ }
+ auto magic = file.readString(0, 0x50);
+ if (magic != "BIGF\r\n") {
+ error("invalid BIGF magic");
+ }
+ while (true) {
+ auto name = file.readString();
+ if (name.empty())
+ break;
+ debug("name %s", name.c_str());
+ auto size = file.readUint32LE();
+ auto offset = file.readUint32LE();
+ debug("size: %08x, offset: %08x", size, offset);
+ }
+ _dataPos = file.pos();
+ debug("data at %08zx", _dataPos);
+}
+
+} // namespace PhoenixVR
diff --git a/engines/phoenixvr/bigf.h b/engines/phoenixvr/bigf.h
new file mode 100644
index 00000000000..18ed1f7ec8e
--- /dev/null
+++ b/engines/phoenixvr/bigf.h
@@ -0,0 +1,38 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_BIGF_H
+#define PHOENIXVR_BIGF_H
+
+#include "common/str.h"
+
+namespace PhoenixVR {
+
+class BIGF {
+ Common::String _path;
+ size_t _dataPos;
+
+public:
+ BIGF(const Common::String &path);
+};
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/module.mk b/engines/phoenixvr/module.mk
index bb2fa944861..64ef19ebe03 100644
--- a/engines/phoenixvr/module.mk
+++ b/engines/phoenixvr/module.mk
@@ -1,6 +1,7 @@
MODULE := engines/phoenixvr
MODULE_OBJS = \
+ bigf.o \
game_state.o \
console.o \
metaengine.o \
Commit: 9792a1846aa4b6c215694dc3b7315872ea89199d
https://github.com/scummvm/scummvm/commit/9792a1846aa4b6c215694dc3b7315872ea89199d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:20+01:00
Commit Message:
PHOENIXVR: terminate script after gotowarp
Changed paths:
engines/phoenixvr/commands.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index d2621e23a6a..bf5de343a54 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -518,6 +518,7 @@ struct GoToWarp : public Script::Command {
void exec(Script::ExecutionContext &ctx) const override {
g_engine->goToWarp(warp);
+ ctx.running = false; // terminate script after warp
}
};
Commit: 23f567d98d3d58c89def6b9cd3140d33a08121d2
https://github.com/scummvm/scummvm/commit/23f567d98d3d58c89def6b9cd3140d33a08121d2
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:21+01:00
Commit Message:
PHOENIXVR: schedule next test from ChangeCurseur and timer - fixes disc puzzles in shed and lab
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index bf5de343a54..7b08416fa0a 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -152,7 +152,7 @@ struct ChangeCurseur : public Script::Command {
ChangeCurseur(const Common::Array<Common::String> &args) : cursor(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("changecurseur %d", cursor);
- g_engine->executeTest(cursor);
+ g_engine->scheduleTest(cursor);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 14362b46166..559f86f7ed6 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -351,6 +351,11 @@ Graphics::Surface *PhoenixVREngine::loadCursor(const Common::String &path) {
return s;
}
+void PhoenixVREngine::scheduleTest(int idx) {
+ debug("schedule test %d for execution", idx);
+ _nextTest = idx;
+}
+
void PhoenixVREngine::executeTest(int idx) {
debug("execute test %d", idx);
auto test = _warp->getTest(idx);
@@ -395,7 +400,7 @@ void PhoenixVREngine::tickTimer(float dt) {
if (_timer <= 0) {
debug("timer trigger");
killTimer();
- executeTest(99);
+ scheduleTest(99);
}
}
}
@@ -470,6 +475,12 @@ void PhoenixVREngine::tick(float dt) {
_loading = false;
}
+ if (_nextTest >= 0) {
+ auto nextTest = _nextTest;
+ _nextTest = -1;
+ executeTest(nextTest);
+ }
+
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
Graphics::Surface *cursor = nullptr;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 24fd6aeb1bc..2ead106c7ed 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -111,6 +111,7 @@ public:
int getVariable(const Common::String &name) const;
void executeTest(int idx);
+ void scheduleTest(int idx);
void end();
void wait(float seconds);
@@ -176,6 +177,7 @@ private:
int _nextWarp = -1;
int _prevWarp = -1;
int _hoverIndex = -1;
+ int _nextTest = -1;
struct KeyCodeHash : public Common::UnaryFunction<Common::KeyCode, uint> {
uint operator()(Common::KeyCode val) const { return static_cast<uint>(val); }
Commit: 4ac7067f9d1ef15998562bc5ad0ea259b43d14f5
https://github.com/scummvm/scummvm/commit/4ac7067f9d1ef15998562bc5ad0ea259b43d14f5
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:21+01:00
Commit Message:
PHOENIXVR: render vr before wait - fixes some animation-wait sequences (missing light animation on brain puzzle)
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 559f86f7ed6..28f61e68d07 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -142,6 +142,7 @@ void PhoenixVREngine::end() {
void PhoenixVREngine::wait(float seconds) {
debug("wait %gs", seconds);
+ renderVR();
auto begin = g_system->getMillis();
unsigned millis = seconds * 1000;
Graphics::FrameLimiter limiter(g_system, kFPSLimit);
@@ -406,6 +407,10 @@ void PhoenixVREngine::tickTimer(float dt) {
}
}
+void PhoenixVREngine::renderVR() {
+ _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
+}
+
void PhoenixVREngine::tick(float dt) {
tickTimer(dt);
@@ -481,7 +486,7 @@ void PhoenixVREngine::tick(float dt) {
executeTest(nextTest);
}
- _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
+ renderVR();
Graphics::Surface *cursor = nullptr;
auto &cursors = _cursors[_warpIdx];
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 2ead106c7ed..3a602a96fd4 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -168,6 +168,7 @@ private:
void tick(float dt);
void tickTimer(float dt);
void loadNextScript();
+ void renderVR();
private:
Common::Point _mousePos, _mouseRel;
Commit: cae1c6bce79c6dd2ca294e53569c5e10267a62d1
https://github.com/scummvm/scummvm/commit/cae1c6bce79c6dd2ca294e53569c5e10267a62d1
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:21+01:00
Commit Message:
PHOENIXVR: implement save/load variable state
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 7b08416fa0a..159173c9c80 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -322,7 +322,14 @@ struct RolloverSecretaire : public Script::Command {
struct SaveVariable : public Script::Command {
SaveVariable(const Common::Array<Common::String> &args) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("SaveVariable()");
+ g_engine->saveVariables();
+ }
+};
+
+struct LoadVariable : public Script::Command {
+ LoadVariable(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ g_engine->loadVariables();
}
};
@@ -340,6 +347,7 @@ struct SaveVariable : public Script::Command {
E(LoadSave_Set_Context_Label) \
E(LoadSave_Draw_Slot) \
E(LoadSave_Test_Slot) \
+ E(LoadVariable) \
E(MultiCD_Set_Transition_Script) \
E(MultiCD_Set_Next_Script) \
E(PauseTimer) \
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 28f61e68d07..193ee6b419a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -411,6 +411,23 @@ void PhoenixVREngine::renderVR() {
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
}
+void PhoenixVREngine::saveVariables() {
+ debug("SaveVariable() - saving variable state");
+ _variableSnapshot.resize(_variableOrder.size());
+ for (uint i = 0, n = _variableOrder.size(); i != n; ++i) {
+ _variableSnapshot[i] = _variables.getVal(_variableOrder[i]);
+ }
+}
+
+void PhoenixVREngine::loadVariables() {
+ debug("LoadVariable() - loading variable state");
+ assert(_variableSnapshot.size() == _variableOrder.size());
+ for (uint i = 0, n = _variableOrder.size(); i != n; ++i) {
+ _variables.setVal(_variableOrder[i], _variableSnapshot[i]);
+ }
+ _variableSnapshot.clear();
+}
+
void PhoenixVREngine::tick(float dt) {
tickTimer(dt);
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 3a602a96fd4..075917b4370 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -157,6 +157,9 @@ public:
return _loading;
}
+ void saveVariables();
+ void loadVariables();
+
private:
static Common::String removeDrive(const Common::String &path);
Graphics::Surface *loadSurface(const Common::String &path);
@@ -186,6 +189,7 @@ private:
Common::Array<Common::String> _lockKey;
Common::Array<Common::String> _variableOrder;
+ Common::Array<int> _variableSnapshot;
Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
struct Sound {
Audio::SoundHandle handle;
Commit: 2e7bbb890ac2beb116fa08763bc86037b3dc724b
https://github.com/scummvm/scummvm/commit/2e7bbb890ac2beb116fa08763bc86037b3dc724b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:21+01:00
Commit Message:
PHOENIXVR: implement rollover (text labels)
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 159173c9c80..5950acbcb2b 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -301,12 +301,21 @@ struct LoadSave_Set_Context_Label : public Script::Command {
}
};
+struct Rollover : public Script::Command {
+ int arg;
+
+ Rollover(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ g_engine->rollover({57, 427, 409, 480}, arg, 14, 1, 0);
+ }
+};
+
struct RolloverMalette : public Script::Command {
int arg;
RolloverMalette(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("RolloverMalette %d", arg);
+ g_engine->rollover({251, 346, 522, 394}, arg, 18, 1, 0xD698);
}
};
@@ -315,7 +324,7 @@ struct RolloverSecretaire : public Script::Command {
RolloverSecretaire(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("RolloverSecretaire %d", arg);
+ g_engine->rollover({216, 367, 536, 430}, arg, 12, 1, 0xFFFF);
}
};
@@ -354,6 +363,7 @@ struct LoadVariable : public Script::Command {
E(Play_AnimBloc) \
E(Play_AnimBloc_Number) \
E(Play_Movie) \
+ E(Rollover) \
E(RolloverMalette) \
E(RolloverSecretaire) \
E(SaveVariable) \
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 193ee6b419a..207cbc382bc 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -31,6 +31,8 @@
#include "common/scummsys.h"
#include "common/system.h"
#include "engines/util.h"
+#include "graphics/font.h"
+#include "graphics/fonts/ttf.h"
#include "graphics/framelimiter.h"
#include "graphics/surface.h"
#include "image/pcx.h"
@@ -175,6 +177,7 @@ void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
debug("gotowarp %s, save prev: %d", warp.c_str(), savePrev);
_nextWarp = _script->getWarp(warp);
_hoverIndex = -1;
+ _text.reset();
if (savePrev) {
assert(_warpIdx >= 0);
_prevWarp = _warpIdx;
@@ -409,6 +412,11 @@ void PhoenixVREngine::tickTimer(float dt) {
void PhoenixVREngine::renderVR() {
_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
+ if (_text) {
+ int16 x = _textRect.left + (_textRect.width() - _text->w) / 2;
+ int16 y = _textRect.top + (_textRect.height() - _text->h) / 2;
+ _screen->blitFrom(*_text, {x, y});
+ }
}
void PhoenixVREngine::saveVariables() {
@@ -428,6 +436,38 @@ void PhoenixVREngine::loadVariables() {
_variableSnapshot.clear();
}
+void PhoenixVREngine::rollover(Common::Rect dstRect, int textId, int size, bool bold, uint16_t color) {
+ Graphics::Font *font = nullptr;
+ if (size < 14)
+ font = _font12.get();
+ else if (size < 18)
+ font = _font14.get();
+ else
+ font = _font18.get();
+
+ if (!font)
+ return;
+
+ if (!_textes.contains(textId)) {
+ debug("rollover reset");
+ _text.reset();
+ return;
+ }
+ auto &text = _textes.getVal(textId);
+ debug("rollover %s, %s font size: %d, bold: %d, color: %02x", dstRect.toString().c_str(), text.c_str(), size, bold, color);
+
+ auto textH = font->getFontHeight();
+ auto textW = font->getStringWidth(text);
+ debug("text %dx%d", textW, textH);
+ _text.reset(new Graphics::ManagedSurface(textW, textH, _screen->format));
+ _text->clear();
+ byte r, g, b;
+ _rgb565.colorToRGB(color, r, g, b);
+ auto textColor = _text->format.RGBToColor(r, g, b);
+ font->drawAlphaString(_text.get(), text, 0, 0, textW, textColor, Graphics::kTextAlignLeft);
+ _textRect = dstRect;
+}
+
void PhoenixVREngine::tick(float dt) {
tickTimer(dt);
@@ -542,6 +582,13 @@ void PhoenixVREngine::tick(float dt) {
Common::Error PhoenixVREngine::run() {
initGraphics(640, 480, &_pixelFormat);
+#ifdef USE_FREETYPE2
+ static const Common::String family("NotoSerif-Bold.ttf");
+ _font12.reset(Graphics::loadTTFFontFromArchive(family, 12));
+ _font14.reset(Graphics::loadTTFFontFromArchive(family, 14));
+ _font18.reset(Graphics::loadTTFFontFromArchive(family, 18));
+#endif
+
_screen = new Graphics::Screen();
_screenCenter = _screen->getBounds().center();
{
@@ -557,6 +604,26 @@ Common::Error PhoenixVREngine::run() {
_variableOrder.push_back(Common::move(var));
}
}
+ {
+ Common::File textes;
+ if (!textes.open(Common::Path("textes.txt")))
+ error("can't read textes.txt");
+ while (!textes.eos()) {
+ auto text = textes.readLine();
+ if (text.empty() || text[0] != '*')
+ continue;
+ uint pos = 1;
+ while (pos < text.size() && Common::isSpace(text[pos]))
+ ++pos;
+ int textId = atoi(text.c_str() + pos);
+ while (pos < text.size() && Common::isDigit(text[pos]))
+ ++pos;
+ while (pos < text.size() && Common::isSpace(text[pos]))
+ ++pos;
+ _textes.setVal(textId, text.substr(pos));
+ }
+ debug("loaded %u textes", _textes.size());
+ }
setNextScript("script.lst");
// Set the engine's debugger console
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 075917b4370..ac25d20757e 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -43,6 +43,10 @@
#include "phoenixvr/script.h"
#include "phoenixvr/vr.h"
+namespace Graphics {
+class Font;
+}
+
namespace PhoenixVR {
struct PhoenixVRGameDescription;
@@ -160,6 +164,8 @@ public:
void saveVariables();
void loadVariables();
+ void rollover(Common::Rect dstRect, int textId, int size, bool bold, uint16_t color);
+
private:
static Common::String removeDrive(const Common::String &path);
Graphics::Surface *loadSurface(const Common::String &path);
@@ -224,6 +230,15 @@ private:
Common::String _contextScript;
Common::String _contextLabel;
Common::Array<byte> _capturedState;
+
+ Common::HashMap<int, Common::String> _textes;
+
+ Common::ScopedPtr<Graphics::Font> _font12;
+ Common::ScopedPtr<Graphics::Font> _font14;
+ Common::ScopedPtr<Graphics::Font> _font18;
+
+ Common::ScopedPtr<Graphics::ManagedSurface> _text;
+ Common::Rect _textRect;
};
extern PhoenixVREngine *g_engine;
Commit: a72e0bbe0e028a026109e0179efa3f593b934b3b
https://github.com/scummvm/scummvm/commit/a72e0bbe0e028a026109e0179efa3f593b934b3b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:22+01:00
Commit Message:
PHOENIXVR: implement LABEL/GOSUB
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/script.cpp
engines/phoenixvr/script.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 5950acbcb2b..be10f4ee88e 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -428,6 +428,20 @@ struct Set : public Script::Command {
}
};
+struct GoSub : public Script::Command {
+ Common::String label;
+ GoSub(const Common::String &l) : label(l) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("gosub %s", label.c_str());
+ assert(ctx.scope);
+ auto *labelPtr = ctx.scope->findLabel(label);
+ assert(labelPtr);
+ Script::ExecutionContext sub = {};
+ sub.subroutine = true;
+ ctx.scope->exec(sub, labelPtr->offset);
+ }
+};
+
struct End : public Script::Command {
End() {}
void exec(Script::ExecutionContext &ctx) const override {
@@ -441,7 +455,12 @@ struct Return : public Script::Command {
Return() {}
void exec(Script::ExecutionContext &ctx) const override {
ctx.running = false;
- g_engine->returnToWarp();
+ if (ctx.subroutine) {
+ debug("return to caller");
+ } else {
+ debug("return to previous warp");
+ g_engine->returnToWarp();
+ }
}
};
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 4953ee2fab8..d256febb658 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -213,6 +213,8 @@ public:
expect('=');
auto value = nextInt();
return CommandPtr(new Set(Common::move(var), value));
+ } else if (keyword("gosub")) {
+ return CommandPtr(new GoSub(nextWord()));
} else if (keyword("return")) {
return CommandPtr{new Return()};
} else if (keyword("end")) {
@@ -225,11 +227,18 @@ public:
} // namespace
void Script::Scope::exec(ExecutionContext &ctx) const {
- for (auto &cmd : commands) {
+ exec(ctx, 0);
+}
+
+void Script::Scope::exec(ExecutionContext &ctx, uint offset) const {
+ auto oldScope = ctx.scope;
+ ctx.scope = this;
+ for (uint i = offset, n = commands.size(); i < n; ++i) {
if (!ctx.running)
break;
- cmd->exec(ctx);
+ commands[i]->exec(ctx);
}
+ ctx.scope = oldScope;
}
Script::TestPtr Script::Warp::getTest(int idx) const {
@@ -310,6 +319,12 @@ void Script::parseLine(const Common::String &line, uint lineno) {
commands.push_back(Common::move(_pluginScope));
_pluginScope.reset();
}
+ } else if (p.maybe("label")) {
+ if (_pluginScope)
+ error("no labels in plugin scope allowed");
+ auto name = p.nextWord();
+ auto offset = _currentTest->scope.commands.size();
+ _currentTest->scope.labels.push_back({Common::move(name), offset});
} else {
if (_pluginScope) {
auto name = p.nextWord();
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index ba3ee0383ce..90377729604 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -45,8 +45,11 @@ inline int fromAngle(float a) {
} // namespace
class Script {
public:
+ struct Scope;
struct ExecutionContext {
bool running = true;
+ bool subroutine = false;
+ const Scope *scope = nullptr;
};
struct Command {
virtual ~Command() = default;
@@ -56,7 +59,20 @@ public:
struct Scope : public Script::Command {
Common::Array<CommandPtr> commands;
- void exec(ExecutionContext &ctx) const;
+
+ struct Label {
+ Common::String name;
+ uint offset;
+ };
+ Common::Array<Label> labels;
+
+ const Label *findLabel(const Common::String &name) const {
+ auto it = Common::find_if(labels.begin(), labels.end(), [&](const Label &label) { return label.name.equalsIgnoreCase(name); });
+ return it != labels.end() ? &*it : nullptr;
+ }
+
+ void exec(ExecutionContext &ctx) const override;
+ void exec(ExecutionContext &ctx, uint offset) const;
};
using ScopePtr = Common::SharedPtr<Scope>;
Commit: 1017542462c0dfa9e86fb3ddcf9b66bbef3ccc43
https://github.com/scummvm/scummvm/commit/1017542462c0dfa9e86fb3ddcf9b66bbef3ccc43
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:22+01:00
Commit Message:
PHOENIXVR: skip LoadVariable if there's no snapshot
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 207cbc382bc..86907a470b8 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -429,6 +429,10 @@ void PhoenixVREngine::saveVariables() {
void PhoenixVREngine::loadVariables() {
debug("LoadVariable() - loading variable state");
+ if (_variableSnapshot.empty()) {
+ debug("skipping, no snapshot");
+ return;
+ }
assert(_variableSnapshot.size() == _variableOrder.size());
for (uint i = 0, n = _variableOrder.size(); i != n; ++i) {
_variables.setVal(_variableOrder[i], _variableSnapshot[i]);
Commit: 76eede809af44cd007481a8a159f83ef1ebd18fb
https://github.com/scummvm/scummvm/commit/76eede809af44cd007481a8a159f83ef1ebd18fb
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:22+01:00
Commit Message:
PHOENIXVR: do not override cursors if any was found, use default cursors later
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 86907a470b8..9a3975d028c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -551,12 +551,14 @@ void PhoenixVREngine::tick(float dt) {
Graphics::Surface *cursor = nullptr;
auto &cursors = _cursors[_warpIdx];
+ bool anyMatched = false;
for (int i = 0, n = cursors.size(); i != n; ++i) {
auto *region = getRegion(i);
if (!region)
continue;
if (_vr.isVR() ? region->contains3D(currentVRPos()) : region->contains2D(_mousePos.x, _mousePos.y)) {
+ anyMatched = true;
auto test = _warp->getTest(i);
if (test && test->hover == 1 && _hoverIndex < 0) {
debug("executing hover test %d", i);
@@ -565,9 +567,9 @@ void PhoenixVREngine::tick(float dt) {
}
auto &name = cursors[i];
- cursor = loadCursor(name);
- if (!cursor)
- cursor = loadCursor(_defaultCursor[1]);
+ if (!cursor) {
+ cursor = loadCursor(name);
+ }
} else if (i == _hoverIndex) {
debug("leaving hover region");
auto leave = _warp->getTest(i + 1);
@@ -578,7 +580,7 @@ void PhoenixVREngine::tick(float dt) {
}
}
if (!cursor)
- cursor = loadCursor(_defaultCursor[0]);
+ cursor = loadCursor(anyMatched ? _defaultCursor[1] : _defaultCursor[0]);
if (cursor) {
paint(*cursor, _mousePos - Common::Point(cursor->w / 2, cursor->h / 2));
}
Commit: 2787806b92d597ceffe8e06a6039c5824a104e35
https://github.com/scummvm/scummvm/commit/2787806b92d597ceffe8e06a6039c5824a104e35
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:23+01:00
Commit Message:
PHOENIXVR: allow multiline text
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9a3975d028c..8d5ee2bc2db 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -460,15 +460,30 @@ void PhoenixVREngine::rollover(Common::Rect dstRect, int textId, int size, bool
auto &text = _textes.getVal(textId);
debug("rollover %s, %s font size: %d, bold: %d, color: %02x", dstRect.toString().c_str(), text.c_str(), size, bold, color);
- auto textH = font->getFontHeight();
- auto textW = font->getStringWidth(text);
+ Common::Array<Common::String> lines;
+ font->wordWrapText(text, dstRect.width(), lines, Graphics::kWordWrapDefault);
+
+ auto fontH = font->getFontHeight();
+ int textW = 0;
+ Common::Array<int> widths(lines.size());
+ for (uint i = 0, n = lines.size(); i != n; ++i) {
+ auto w = font->getStringWidth(lines[i]);
+ widths[i] = w;
+ textW = MAX(textW, w);
+ }
+
+ auto numLines = static_cast<int>(lines.size());
+ auto textH = fontH * numLines;
debug("text %dx%d", textW, textH);
_text.reset(new Graphics::ManagedSurface(textW, textH, _screen->format));
_text->clear();
byte r, g, b;
_rgb565.colorToRGB(color, r, g, b);
auto textColor = _text->format.RGBToColor(r, g, b);
- font->drawAlphaString(_text.get(), text, 0, 0, textW, textColor, Graphics::kTextAlignLeft);
+ for (int i = 0; i != numLines; ++i) {
+ int dw = (textW - widths[i]) / 2;
+ font->drawAlphaString(_text.get(), lines[i], dw, i * fontH, textW, textColor, Graphics::kTextAlignLeft);
+ }
_textRect = dstRect;
}
Commit: 2d1485b7fb4d8195add13c881966a97012830ac8
https://github.com/scummvm/scummvm/commit/2d1485b7fb4d8195add13c881966a97012830ac8
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:23+01:00
Commit Message:
PHOENIXVR: fix invalid location
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 8d5ee2bc2db..edbaeee34f7 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -175,7 +175,10 @@ void PhoenixVREngine::wait(float seconds) {
void PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
debug("gotowarp %s, save prev: %d", warp.c_str(), savePrev);
- _nextWarp = _script->getWarp(warp);
+ if (warp != "N3M09L03W515E1.vr") // typo in Script4.lst
+ _nextWarp = _script->getWarp(warp);
+ else
+ _nextWarp = _script->getWarp("N3M09L03W51E1.vr");
_hoverIndex = -1;
_text.reset();
if (savePrev) {
Commit: 3eae2d0dc35bea21e8caf4f5fc3e308a32096ddf
https://github.com/scummvm/scummvm/commit/3eae2d0dc35bea21e8caf4f5fc3e308a32096ddf
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:23+01:00
Commit Message:
PHOENIXVR: kill timer when loading the game
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index edbaeee34f7..6d58220214a 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -876,6 +876,7 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
}
auto state = GameState::load(*slot);
+ killTimer();
setNextScript(state.script);
// keep it alive until loading finishes.
auto currentScript = Common::move(_script);
Commit: a9fc04c9488cc830cd864f7233f8c4a0e88c9a76
https://github.com/scummvm/scummvm/commit/a9fc04c9488cc830cd864f7233f8c4a0e88c9a76
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:24+01:00
Commit Message:
PHOENIXVR: scale thumbnails to 160x~120
Changed paths:
engines/phoenixvr/game_state.cpp
engines/phoenixvr/game_state.h
engines/phoenixvr/metaengine.cpp
diff --git a/engines/phoenixvr/game_state.cpp b/engines/phoenixvr/game_state.cpp
index dab7ff86fa4..8c953994b97 100644
--- a/engines/phoenixvr/game_state.cpp
+++ b/engines/phoenixvr/game_state.cpp
@@ -54,12 +54,19 @@ void GameState::save(Common::SeekableWriteStream &stream) const {
stream.write(state.data(), state.size());
}
-Graphics::Surface *GameState::getThumbnail(const Graphics::PixelFormat &fmt) {
+Graphics::Surface *GameState::getThumbnail(const Graphics::PixelFormat &fmt, int newWidth) {
Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
Graphics::Surface th;
th.init(thumbWidth, thumbHeight, thumbnail.size() / thumbHeight, thumbnail.data(), rgb565);
Graphics::Surface *src = th.convertTo(fmt);
src->flipVertical(src->getRect());
+ if (newWidth > 0) {
+ int newHeight = newWidth * src->h / src->w;
+ auto *scaled = src->scale(newWidth, newHeight, true);
+ src->free();
+ delete src;
+ return scaled;
+ }
return src;
}
diff --git a/engines/phoenixvr/game_state.h b/engines/phoenixvr/game_state.h
index 8e8f26f02fe..e92227e5e63 100644
--- a/engines/phoenixvr/game_state.h
+++ b/engines/phoenixvr/game_state.h
@@ -47,7 +47,7 @@ struct GameState {
static GameState load(Common::SeekableReadStream &stream);
void save(Common::SeekableWriteStream &stream) const;
- Graphics::Surface *getThumbnail(const Graphics::PixelFormat &fmt);
+ Graphics::Surface *getThumbnail(const Graphics::PixelFormat &fmt, int newWidth = 0);
};
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 8e113dc87e8..08f053a08af 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -93,7 +93,7 @@ SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target,
desc.setSaveSlot(slotIdx);
desc.setDeletableFlag(true);
desc.setDescription(state.game + " " + state.info);
- desc.setThumbnail(state.getThumbnail(Graphics::BlendBlit::getSupportedPixelFormat()));
+ desc.setThumbnail(state.getThumbnail(g_system->getOverlayFormat(), 160));
return desc;
}
Commit: 8e02ec8e7e9175472def3177ea3e83a527003623
https://github.com/scummvm/scummvm/commit/8e02ec8e7e9175472def3177ea3e83a527003623
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:24+01:00
Commit Message:
PHOENIXVR: do not use overlay format - it breaks cli --list-saves
Changed paths:
engines/phoenixvr/metaengine.cpp
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 08f053a08af..1380e717b60 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -93,7 +93,7 @@ SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target,
desc.setSaveSlot(slotIdx);
desc.setDeletableFlag(true);
desc.setDescription(state.game + " " + state.info);
- desc.setThumbnail(state.getThumbnail(g_system->getOverlayFormat(), 160));
+ desc.setThumbnail(state.getThumbnail(Graphics::BlendBlit::getSupportedPixelFormat(), 160));
return desc;
}
Commit: 49ab208a5602696c9037d7fac39f660b784b0831
https://github.com/scummvm/scummvm/commit/49ab208a5602696c9037d7fac39f660b784b0831
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:24+01:00
Commit Message:
PHOENIXVR: allow loading from launcher
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/game_state.cpp
engines/phoenixvr/game_state.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index be10f4ee88e..1e04bab7967 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -278,7 +278,9 @@ struct LoadSave_Load : public Script::Command {
LoadSave_Load(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("LoadSave_Load %d", slot);
- g_engine->loadSaveSlot(slot);
+ auto err = g_engine->loadGameState(slot);
+ if (err.getCode() != Common::ErrorCode::kNoError)
+ error("loading state failed %d", slot);
}
};
@@ -288,7 +290,9 @@ struct LoadSave_Save : public Script::Command {
LoadSave_Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("LoadSave_Save %d", slot);
- g_engine->saveSaveSlot(slot);
+ auto err = g_engine->saveGameState(slot, {});
+ if (err.getCode() != Common::ErrorCode::kNoError)
+ error("saving state failed %d", slot);
}
};
diff --git a/engines/phoenixvr/game_state.cpp b/engines/phoenixvr/game_state.cpp
index 8c953994b97..b24e1a8abd4 100644
--- a/engines/phoenixvr/game_state.cpp
+++ b/engines/phoenixvr/game_state.cpp
@@ -34,7 +34,7 @@ GameState GameState::load(Common::SeekableReadStream &stream) {
return state;
}
-void GameState::save(Common::SeekableWriteStream &stream) const {
+void GameState::save(Common::WriteStream &stream) const {
auto writeString = [&](const Common::String &str) {
stream.writeUint32LE(str.size() + 1);
stream.writeString(str);
diff --git a/engines/phoenixvr/game_state.h b/engines/phoenixvr/game_state.h
index e92227e5e63..ccfeb4401ee 100644
--- a/engines/phoenixvr/game_state.h
+++ b/engines/phoenixvr/game_state.h
@@ -45,7 +45,7 @@ struct GameState {
Common::Array<byte> state;
static GameState load(Common::SeekableReadStream &stream);
- void save(Common::SeekableWriteStream &stream) const;
+ void save(Common::WriteStream &stream) const;
Graphics::Surface *getThumbnail(const Graphics::PixelFormat &fmt, int newWidth = 0);
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 6d58220214a..1d9f0a72711 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -653,12 +653,13 @@ Common::Error PhoenixVREngine::run() {
// Set the engine's debugger console
setDebugger(new Console());
-#if 0
// If a savegame was selected from the launcher, load it
int saveSlot = ConfMan.getInt("save_slot");
- if (saveSlot != -1)
- (void)loadGameState(saveSlot);
-#endif
+ if (saveSlot != -1) {
+ auto r = loadGameState(saveSlot);
+ if (r.getCode() != Common::ErrorCode::kNoError)
+ return r;
+ }
Common::Event event;
@@ -868,12 +869,7 @@ void PhoenixVREngine::captureContext() {
debug("captured %u bytes of state", _capturedState.size());
}
-void PhoenixVREngine::loadSaveSlot(int idx) {
- Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
- if (!slot) {
- warning("loadSaveSlot: invalid save slot %d", idx);
- return;
- }
+Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *slot) {
auto state = GameState::load(*slot);
killTimer();
@@ -965,14 +961,10 @@ void PhoenixVREngine::loadSaveSlot(int idx) {
}
_loading = true;
+ return Common::kNoError;
}
-void PhoenixVREngine::saveSaveSlot(int idx) {
- Common::ScopedPtr<Common::OutSaveFile> slot(_saveFileMan->openForSaving(getSaveStateName(idx)));
- if (!slot) {
- warning("saveSaveSlot: invalid save slot %d", idx);
- return;
- }
+Common::Error PhoenixVREngine::saveGameStream(Common::WriteStream *slot, bool isAutosave) {
GameState state;
state.script = _contextScript;
state.game = _contextLabel;
@@ -1016,6 +1008,7 @@ void PhoenixVREngine::saveSaveSlot(int idx) {
_capturedState.clear();
state.save(*slot);
+ return Common::kNoError;
}
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index ac25d20757e..fe813269f10 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -148,8 +148,8 @@ public:
}
bool testSaveSlot(int idx) const;
- void loadSaveSlot(int idx);
- void saveSaveSlot(int idx);
+ Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
+ Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
void drawSlot(int idx, int face, int x, int y);
void captureContext();
Commit: 1ffb2d661c3c22763cddd97b446fb01356a169a4
https://github.com/scummvm/scummvm/commit/1ffb2d661c3c22763cddd97b446fb01356a169a4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:24+01:00
Commit Message:
PHOENIXVR: implement can{Save|Load}GameStateCurrently
Changed paths:
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index fe813269f10..b17e434d096 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -92,10 +92,10 @@ public:
};
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
- return true;
+ return getVariable("E_Canload") != 0;
}
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
- return false;
+ return getVariable("E_Cansave") != 0;
}
// Script API
Commit: 1e4317641d00f41f0c81fd93921cae3d395bf128
https://github.com/scummvm/scummvm/commit/1e4317641d00f41f0c81fd93921cae3d395bf128
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:25+01:00
Commit Message:
PHOENIXVR: clear cursors before loading next script
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1d9f0a72711..6ab37bb5866 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -127,6 +127,7 @@ void PhoenixVREngine::loadNextScript() {
declareVariable(var);
int numWarps = _script->numWarps();
+ _cursors.clear();
_cursors.resize(numWarps);
for (int i = 0; i != numWarps; ++i) {
auto warp = _script->getWarp(i);
Commit: 840f1f6b2206ac33a27dc57a2143d6670a0bfa95
https://github.com/scummvm/scummvm/commit/840f1f6b2206ac33a27dc57a2143d6670a0bfa95
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:25+01:00
Commit Message:
PHOENIXVR: change mdcit ac[0] to match original
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index b57b35a9af9..9df00de03f4 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -108,7 +108,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
int16 ac[64] = {};
int8 dc8 = dcBs.readUInt(8);
auto *iquant = channel ? quant.quantCbCr : quant.quantY;
- ac[0] = iquant[0] * dc8 + 0x80 * 8 * 8;
+ ac[0] = iquant[0] * dc8;
for (uint idx = 1; idx < 64;) {
auto b = decoded[decodedOffset++];
if (b == 0x00) {
@@ -132,7 +132,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const auto *src = ac;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++;
+ int v = *src++ + 128;
v = clip(v);
*dst++ = v;
}
Commit: 3e0322b7e91bbcf4a9993d43f8f514a7082598fa
https://github.com/scummvm/scummvm/commit/3e0322b7e91bbcf4a9993d43f8f514a7082598fa
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:25+01:00
Commit Message:
PHOENIXVR: cleaning up animations, figured out arguments meaning
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 1e04bab7967..1367ef4a468 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -73,30 +73,31 @@ struct Play_Movie : public Script::Command {
struct Play_AnimBloc : public Script::Command {
Common::String name;
- Common::String block;
- int start;
- int stop;
+ Common::String dstVar;
+ int dstVarValue;
+ float speed; // ticks per second
- Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), block(args[1]), start(atoi(args[2].c_str())), stop(atoi(args[3].c_str())) {}
+ Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), dstVar(args[1]), dstVarValue(atoi(args[2].c_str())), speed(atof(args[3].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc %s %s %d-%d", name.c_str(), block.c_str(), start, stop);
- g_engine->playAnimation(name, block);
+ debug("Play_AnimBloc %s %s %d, %g", name.c_str(), dstVar.c_str(), dstVarValue, speed);
+ g_engine->playAnimation(name, dstVar, dstVarValue);
}
};
struct Play_AnimBloc_Number : public Script::Command {
Common::String prefix, var;
- Common::String block;
- int start;
- int stop;
+ Common::String dstVar;
+ int dstVarValue;
+ float speed;
Play_AnimBloc_Number(const Common::Array<Common::String> &args) : prefix(args[0]), var(args[1]),
- block(args[2]), start(atoi(args[3].c_str())), stop(atoi(args[4].c_str())) {}
+ dstVar(args[2]), dstVarValue(atoi(args[3].c_str())),
+ speed(atof(args[4].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- debug("Play_AnimBloc_Number %s %s %s %d-%d", prefix.c_str(), var.c_str(), block.c_str(), start, stop);
+ debug("Play_AnimBloc_Number %s %s %s %d, %g", prefix.c_str(), var.c_str(), dstVar.c_str(), dstVarValue, speed);
int value = g_engine->getVariable(var);
auto name = Common::String::format("%s%04d", prefix.c_str(), value);
- g_engine->playAnimation(name, block);
+ g_engine->playAnimation(name, dstVar, dstVarValue);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 6ab37bb5866..a8ea071e87b 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -310,8 +310,9 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
warning("playMovie %s failed", movie.c_str());
}
}
-void PhoenixVREngine::playAnimation(const Common::String &name, const Common::String &var) {
+void PhoenixVREngine::playAnimation(const Common::String &name, const Common::String &var, int varValue) {
_vr.playAnimation(name);
+ setVariable(var, varValue);
}
void PhoenixVREngine::resetLockKey() {
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index b17e434d096..2195e254c70 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -126,7 +126,7 @@ public:
void startTimer(float seconds);
void pauseTimer(bool pause, bool deactivate);
void killTimer();
- void playAnimation(const Common::String &name, const Common::String &var);
+ void playAnimation(const Common::String &name, const Common::String &var, int varValue);
void setZoom(int fov) {
_fov = M_PI * fov / 180;
}
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 9df00de03f4..e2f89e276b5 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -234,8 +234,8 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
unpack(*pic, huff, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
} else if (chunkId == CHUNK_ANIMATION) {
auto name = s.readString(0, 32);
- s.skip(4);
- debug("animation %s", name.c_str());
+ auto numFrames = s.readUint32LE();
+ debug("animation %s, frames: %u", name.c_str(), numFrames);
while (s.pos() < chunkPos + chunkSize) {
auto animChunkPos = s.pos();
auto animChunkId = s.readUint32LE();
Commit: 53ea5bc171687017e56ff23070b7f57e2bf9c978
https://github.com/scummvm/scummvm/commit/53ea5bc171687017e56ff23070b7f57e2bf9c978
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:26+01:00
Commit Message:
PHOENIXVR: support animation with multiple frames and speed
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 1367ef4a468..5a2b29f5410 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -80,7 +80,7 @@ struct Play_AnimBloc : public Script::Command {
Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), dstVar(args[1]), dstVarValue(atoi(args[2].c_str())), speed(atof(args[3].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
debug("Play_AnimBloc %s %s %d, %g", name.c_str(), dstVar.c_str(), dstVarValue, speed);
- g_engine->playAnimation(name, dstVar, dstVarValue);
+ g_engine->playAnimation(name, dstVar, dstVarValue, speed);
}
};
@@ -97,7 +97,7 @@ struct Play_AnimBloc_Number : public Script::Command {
debug("Play_AnimBloc_Number %s %s %s %d, %g", prefix.c_str(), var.c_str(), dstVar.c_str(), dstVarValue, speed);
int value = g_engine->getVariable(var);
auto name = Common::String::format("%s%04d", prefix.c_str(), value);
- g_engine->playAnimation(name, dstVar, dstVarValue);
+ g_engine->playAnimation(name, dstVar, dstVarValue, speed);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index a8ea071e87b..4eb6d11fc74 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -145,13 +145,14 @@ void PhoenixVREngine::end() {
void PhoenixVREngine::wait(float seconds) {
debug("wait %gs", seconds);
- renderVR();
auto begin = g_system->getMillis();
unsigned millis = seconds * 1000;
Graphics::FrameLimiter limiter(g_system, kFPSLimit);
bool waiting = true;
+ unsigned frameDuration = 0;
while (!shouldQuit() && waiting && g_system->getMillis() - begin < millis) {
Common::Event event;
+ renderVR(frameDuration / 1000.0f);
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN: {
@@ -170,7 +171,7 @@ void PhoenixVREngine::wait(float seconds) {
// to prevent the system being unduly loaded
limiter.delayBeforeSwap();
_screen->update();
- limiter.startFrame();
+ frameDuration = limiter.startFrame();
}
}
@@ -310,9 +311,8 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
warning("playMovie %s failed", movie.c_str());
}
}
-void PhoenixVREngine::playAnimation(const Common::String &name, const Common::String &var, int varValue) {
- _vr.playAnimation(name);
- setVariable(var, varValue);
+void PhoenixVREngine::playAnimation(const Common::String &name, const Common::String &var, int varValue, float speed) {
+ _vr.playAnimation(name, var, varValue, speed);
}
void PhoenixVREngine::resetLockKey() {
@@ -415,8 +415,8 @@ void PhoenixVREngine::tickTimer(float dt) {
}
}
-void PhoenixVREngine::renderVR() {
- _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, _showRegions ? _regSet.get() : nullptr);
+void PhoenixVREngine::renderVR(float dt) {
+ _vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, dt, _showRegions ? _regSet.get() : nullptr);
if (_text) {
int16 x = _textRect.left + (_textRect.width() - _text->w) / 2;
int16 y = _textRect.top + (_textRect.height() - _text->h) / 2;
@@ -567,7 +567,7 @@ void PhoenixVREngine::tick(float dt) {
executeTest(nextTest);
}
- renderVR();
+ renderVR(dt);
Graphics::Surface *cursor = nullptr;
auto &cursors = _cursors[_warpIdx];
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 2195e254c70..fd98d196c1a 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -126,7 +126,7 @@ public:
void startTimer(float seconds);
void pauseTimer(bool pause, bool deactivate);
void killTimer();
- void playAnimation(const Common::String &name, const Common::String &var, int varValue);
+ void playAnimation(const Common::String &name, const Common::String &var, int varValue, float speed);
void setZoom(int fov) {
_fov = M_PI * fov / 180;
}
@@ -177,7 +177,7 @@ private:
void tick(float dt);
void tickTimer(float dt);
void loadNextScript();
- void renderVR();
+ void renderVR(float dt);
private:
Common::Point _mousePos, _mouseRel;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index e2f89e276b5..cd757ee009a 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -32,6 +32,7 @@
#include "math/vector3d.h"
#include "phoenixvr/angle.h"
#include "phoenixvr/dct_tables.h"
+#include "phoenixvr/phoenixvr.h"
#include "phoenixvr/region_set.h"
#include "video/4xm_utils.h"
@@ -233,21 +234,26 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto *dcEnd = vrData.data() + vrData.size();
unpack(*pic, huff, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
} else if (chunkId == CHUNK_ANIMATION) {
- auto name = s.readString(0, 32);
+ Animation animation;
+ animation.name = s.readString(0, 32);
auto numFrames = s.readUint32LE();
- debug("animation %s, frames: %u", name.c_str(), numFrames);
+ animation.frames.reserve(numFrames);
+ debug("animation %s, frames: %u", animation.name.c_str(), numFrames);
while (s.pos() < chunkPos + chunkSize) {
auto animChunkPos = s.pos();
auto animChunkId = s.readUint32LE();
auto animChunkSize = s.readUint32LE();
+ debug("animation frame at %08zx: %08x %u", animChunkPos, animChunkId, animChunkSize);
assert(animChunkSize >= 8);
+ Animation::Frame frame;
if (animChunkId == CHUNK_ANIMATION_BLOCK) {
- auto &blockData = vr._animations[name];
- blockData.resize(animChunkSize - 8);
- s.read(blockData.data(), blockData.size());
+ frame.blockData.resize(animChunkSize - 8);
+ s.read(frame.blockData.data(), frame.blockData.size());
}
+ animation.frames.push_back(Common::move(frame));
s.seek(animChunkPos + animChunkSize);
}
+ vr._animations.push_back(Common::move(animation));
}
s.seek(chunkPos + chunkSize);
}
@@ -320,16 +326,13 @@ Cube toCube(float x, float y, float z) {
} // namespace
-void VR::playAnimation(const Common::String &name) {
- auto it = _animations.find(name);
- if (it == _animations.end()) {
- debug("no animation %s", name.c_str());
+void VR::Animation::Frame::render(Graphics::Surface &pic) const {
+ if (blockData.empty())
return;
- }
- if (it->_value.size() == 0)
- return;
- const auto *data = it->_value.data();
+ const auto *data = blockData.data();
auto prefixSize = READ_LE_UINT32(data);
+ if (prefixSize == 0)
+ return;
Common::Array<uint32> prefixData(prefixSize);
uint offset = 4;
for (uint i = 0; i != prefixSize; ++i) {
@@ -343,11 +346,49 @@ void VR::playAnimation(const Common::String &name) {
auto *acPtr = data + offset + huffSize + 4;
auto dcOffset = READ_LE_UINT32(data + offset + huffSize);
auto *dcPtr = data + offset + huffSize + 8 + dcOffset;
- auto *dcEnd = data + it->_value.size();
- unpack(*_pic, data + offset, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality, &prefixData);
+ auto *dcEnd = data + blockData.size();
+ unpack(pic, data + offset, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality, &prefixData);
+}
+
+void VR::playAnimation(const Common::String &name, const Common::String &variable, int value, float speed) {
+ auto it = Common::find_if(_animations.begin(), _animations.end(), [&](const Animation &a) { return a.name.compareToIgnoreCase(name) == 0; });
+ if (it == _animations.end()) {
+ debug("no animation %s", name.c_str());
+ return;
+ }
+ auto &animation = *it;
+ animation.active = true;
+ animation.frameIndex = 0;
+ animation.t = 0;
+ animation.speed = speed;
+ animation.variable = variable;
+ animation.variableValue = value;
+ animation.renderNextFrame(*_pic);
+}
+
+void VR::Animation::renderNextFrame(Graphics::Surface &pic) {
+ assert(active);
+ if (frameIndex >= frames.size()) {
+ active = false;
+ g_engine->setVariable(variable, variableValue);
+ } else {
+ auto &frame = frames[frameIndex++];
+ frame.render(pic);
+ }
+}
+
+void VR::Animation::render(Graphics::Surface &pic, float dt) {
+ if (!active)
+ return;
+
+ t += speed * dt;
+ if (t > 1) {
+ renderNextFrame(pic);
+ t = fmodf(t, 1);
+ }
}
-void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, RegionSet *regSet) {
+void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float dt, RegionSet *regSet) {
if (!_pic) {
screen->clear();
return;
@@ -358,6 +399,8 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, RegionS
Common::Rect src(_pic->getRect());
Common::Rect::getBlitRect(dst, src, screen->getBounds());
screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
+ for (auto &animation : _animations)
+ animation.render(*_pic, dt);
if (regSet) {
for (auto &rect : regSet->getRegions())
screen->drawRoundRect(rect.toRect().toRect(), 4, _pic->format.RGBToColor(255, 255, 255), false);
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index c884e8c7bdd..760555e4ced 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -22,8 +22,7 @@
#ifndef PHOENIXVR_VR_H
#define PHOENIXVR_VR_H
-#include "common/hash-str.h"
-#include "common/hashmap.h"
+#include "common/array.h"
#include "common/stream.h"
#include "graphics/pixelformat.h"
@@ -37,18 +36,38 @@ class RegionSet;
class VR {
Common::ScopedPtr<Graphics::Surface> _pic;
bool _vr = false;
- Common::HashMap<Common::String, Common::Array<byte>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _animations;
+ struct Animation {
+ struct Frame {
+ Common::Array<byte> blockData;
+ void render(Graphics::Surface &pic) const;
+ };
+
+ Common::String name;
+ Common::Array<Frame> frames;
+
+ bool active = false;
+ float t = 0;
+ float speed = 25.0f;
+
+ unsigned frameIndex = 0;
+
+ Common::String variable;
+ int variableValue = 0;
+ void renderNextFrame(Graphics::Surface &pic);
+ void render(Graphics::Surface &pic, float dt);
+ };
+ Common::Array<Animation> _animations;
public:
~VR();
VR() = default;
- VR(VR &&) = default;
+ VR(VR &&) noexcept = default;
VR &operator=(VR &&) noexcept = default;
static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
- void render(Graphics::Screen *screen, float ax, float ay, float fov, RegionSet *regSet);
+ void render(Graphics::Screen *screen, float ax, float ay, float fov, float dt, RegionSet *regSet);
bool isVR() const { return _vr; }
- void playAnimation(const Common::String &name);
+ void playAnimation(const Common::String &name, const Common::String &variable, int value, float speed);
Graphics::Surface &getSurface() { return *_pic; }
};
} // namespace PhoenixVR
Commit: 21c345d83adec579b9ef2acfa67bc5ddad405762
https://github.com/scummvm/scummvm/commit/21c345d83adec579b9ef2acfa67bc5ddad405762
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:26+01:00
Commit Message:
PHOENIXVR: add until() support for 2d/3d
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 5a2b29f5410..582576ce19e 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -102,12 +102,12 @@ struct Play_AnimBloc_Number : public Script::Command {
};
struct Until : public Script::Command {
- Common::String block;
- int frame;
+ Common::String var;
+ int value;
- Until(const Common::Array<Common::String> &args) : block(args[0]), frame(atoi(args[1].c_str())) {}
+ Until(const Common::Array<Common::String> &args) : var(args[0]), value(atoi(args[1].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
- warning("until %s %d", block.c_str(), frame);
+ g_engine->until(var, value);
}
};
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 4eb6d11fc74..ea2cf57013f 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -143,6 +143,28 @@ void PhoenixVREngine::end() {
}
}
+void PhoenixVREngine::until(const Common::String &var, int value) {
+ debug("until %s %d", var.c_str(), value);
+ Graphics::FrameLimiter limiter(g_system, kFPSLimit);
+ unsigned frameDuration = 0;
+ while (!shouldQuit() && getVariable(var) != value) {
+ Common::Event event;
+ renderVR(frameDuration / 1000.0f);
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ default:
+ break;
+ }
+ }
+
+ // Delay for a bit. All events loops should have a delay
+ // to prevent the system being unduly loaded
+ limiter.delayBeforeSwap();
+ _screen->update();
+ frameDuration = limiter.startFrame();
+ }
+}
+
void PhoenixVREngine::wait(float seconds) {
debug("wait %gs", seconds);
auto begin = g_system->getMillis();
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index fd98d196c1a..cac6da7320a 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -118,6 +118,7 @@ public:
void scheduleTest(int idx);
void end();
void wait(float seconds);
+ void until(const Common::String &var, int value);
const Region *getRegion(int idx) const;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index cd757ee009a..bff25eab98f 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -394,13 +394,14 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
return;
}
+ for (auto &animation : _animations)
+ animation.render(*_pic, dt);
+
if (!_vr) {
Common::Point dst(0, 0);
Common::Rect src(_pic->getRect());
Common::Rect::getBlitRect(dst, src, screen->getBounds());
screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
- for (auto &animation : _animations)
- animation.render(*_pic, dt);
if (regSet) {
for (auto &rect : regSet->getRegions())
screen->drawRoundRect(rect.toRect().toRect(), 4, _pic->format.RGBToColor(255, 255, 255), false);
Commit: 06c90092ea4761ce5b7dd4a7484561668fd63a32
https://github.com/scummvm/scummvm/commit/06c90092ea4761ce5b7dd4a7484561668fd63a32
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:26+01:00
Commit Message:
PHOENIXVR: original engine does sar 13 when apply quantisation
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index bff25eab98f..b3b29c39ec6 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -75,7 +75,7 @@ struct Quantisation {
v = 8;
}
v *= Q[i];
- v >>= 12;
+ v >>= 13;
quantY[i] = v;
}
for (uint i = 0; i != 64; ++i) {
@@ -86,7 +86,7 @@ struct Quantisation {
v = 8;
}
v *= Q[i];
- v >>= 12;
+ v >>= 13;
quantCbCr[i] = v;
}
}
Commit: ced855013a9efbe65feaa40d147f9283fe1fcad5
https://github.com/scummvm/scummvm/commit/ced855013a9efbe65feaa40d147f9283fe1fcad5
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:26+01:00
Commit Message:
PHOENIXVR: compensate 13 bit shift with multiplication (hack)
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index b3b29c39ec6..fd1604d4da8 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -133,7 +133,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
const auto *src = ac;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++ + 128;
+ int v = *src++ * 2 + 128; // FIXME: just compensating 13 bit shift
v = clip(v);
*dst++ = v;
}
Commit: 0e7a875384d9bcd238dda5366299e416181c3be5
https://github.com/scummvm/scummvm/commit/0e7a875384d9bcd238dda5366299e416181c3be5
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:27+01:00
Commit Message:
PHOENIXVR: fix quit()
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index ea2cf57013f..9c80a7f8f9f 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -137,7 +137,7 @@ void PhoenixVREngine::loadNextScript() {
void PhoenixVREngine::end() {
debug("end");
- if (_nextScript.empty() && _nextWarp < 0 && _nextScript < 0) {
+ if (_nextScript.empty() && _nextWarp < 0) {
debug("quit game");
quitGame();
}
Commit: 5dd3d3ece859ea9900e9485e554a2c69792358bf
https://github.com/scummvm/scummvm/commit/5dd3d3ece859ea9900e9485e554a2c69792358bf
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:27+01:00
Commit Message:
PHOENIXVR: fix crash after autosave conflict
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 9c80a7f8f9f..1e4366855f1 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -133,6 +133,7 @@ void PhoenixVREngine::loadNextScript() {
auto warp = _script->getWarp(i);
_cursors[i].resize(warp->tests.size());
}
+ _warpIdx = 0;
}
void PhoenixVREngine::end() {
Commit: 0fd60c17647c06b3a725300d441f878bcacc24ed
https://github.com/scummvm/scummvm/commit/0fd60c17647c06b3a725300d441f878bcacc24ed
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:27+01:00
Commit Message:
VIDEO: 4XM: remove unused private members
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 9c9dfd4ea32..50c0c7dea61 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -51,15 +51,13 @@ static const int8_t mv[256][2] = {
} // namespace
class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
- FourXMDecoder *_dec;
- uint _trackIdx;
uint _audioType;
uint _audioChannels;
uint _sampleRate;
Common::ScopedPtr<Audio::QueuingAudioStream> _output;
public:
- FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _dec(dec), _trackIdx(trackIdx), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
+ FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
_output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
}
Commit: 21267f98bb91ae6c729302ef7e9355cf6f5a673b
https://github.com/scummvm/scummvm/commit/21267f98bb91ae6c729302ef7e9355cf6f5a673b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:28+01:00
Commit Message:
VIDEO: 4XM: use sequential pointer access for video decoding
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 50c0c7dea61..eaf637a94f3 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -120,7 +120,7 @@ public:
void decode_cfrm(Common::SeekableReadStream *stream);
private:
- void decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEWordBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
+ void decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, FourXM::BEWordBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
Common::Rational getFrameRate() const override { return _frameRate; }
};
@@ -321,45 +321,20 @@ namespace {
template<bool Scale>
void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
int log2h, int stride, uint dc) {
- dc |= dc << 16;
int h = 1 << log2h;
+ int w = 1 << log2w;
if (Scale) {
- for (int i = 0; i < h; ++i) {
- auto *dst32 = reinterpret_cast<uint32_t *>(dst);
- auto *src32 = reinterpret_cast<const uint32_t *>(src);
- switch (log2w) {
- case 3:
- dst32[2] = src32[2] + dc;
- dst32[3] = src32[3] + dc;
- // fall through
- case 2:
- dst32[1] = src32[1] + dc;
- // fall through
- case 1:
- dst32[0] = src32[0] + dc;
- break;
- case 0:
- *dst = *src + dc;
+ for (int i = 0; i != h; ++i) {
+ for (int j = 0; j != w; ++j) {
+ dst[j] = src[j] + dc;
}
src += stride;
dst += stride;
}
} else {
- for (int i = 0; i < h; ++i) {
- auto *dst32 = reinterpret_cast<uint32_t *>(dst);
- switch (log2w) {
- case 3:
- dst32[2] = dc;
- dst32[3] = dc;
- // fall through
- case 2:
- dst32[1] = dc;
- // fall through
- case 1:
- dst32[0] = dc;
- break;
- case 0:
- *dst = dc;
+ for (int i = 0; i != h; ++i) {
+ for (int j = 0; j != w; ++j) {
+ dst[j] = dc;
}
dst += stride;
}
@@ -367,59 +342,53 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
}
} // namespace
-void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(Graphics::Surface *frame, int x, int y, int log2w, int log2h, FourXM::BEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, FourXM::BEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
auto &huff = _blockType[index];
auto code = huff.next(bs);
- auto pitch = frame->pitch / frame->format.bytesPerPixel;
- assert(_frame->pitch == frame->pitch);
-
if (code == 1) {
--log2h;
- decode_pfrm_block(frame, x, y, log2w, log2h, bs, wordStream, byteStream);
- int dy = 1 << log2h;
- decode_pfrm_block(frame, x, y + dy, log2w, log2h, bs, wordStream, byteStream);
+ decode_pfrm_block(dst, src, stride, log2w, log2h, bs, wordStream, byteStream);
+ auto offset = stride << log2h;
+ decode_pfrm_block(dst + offset, src + offset, stride, log2w, log2h, bs, wordStream, byteStream);
return;
} else if (code == 2) {
--log2w;
- decode_pfrm_block(frame, x, y, log2w, log2h, bs, wordStream, byteStream);
- int dx = 1 << log2w;
- decode_pfrm_block(frame, x + dx, y, log2w, log2h, bs, wordStream, byteStream);
+ decode_pfrm_block(dst, src, stride, log2w, log2h, bs, wordStream, byteStream);
+ auto offset = 1 << log2w;
+ decode_pfrm_block(dst + offset, src + offset, stride, log2w, log2h, bs, wordStream, byteStream);
return;
} else if (code == 6) {
- auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
assert(wordStream.pos() + 4 <= wordStream.size());
if (log2w) {
dst[0] = wordStream.readUint16LE();
dst[1] = wordStream.readUint16LE();
} else {
dst[0] = wordStream.readUint16LE();
- dst[pitch] = wordStream.readUint16LE();
+ dst[stride] = wordStream.readUint16LE();
}
return;
}
if (code == 3 && _version >= 2)
return;
- auto dst = static_cast<uint16 *>(frame->getBasePtr(x, y));
- auto src = static_cast<const uint16 *>(_frame->getBasePtr(x, y));
if (code == 0) {
assert(byteStream.pos() < byteStream.size());
src += _mv[byteStream.readByte()];
- mcdc<true>(dst, src, log2w, log2h, pitch, 0);
+ mcdc<true>(dst, src, log2w, log2h, stride, 0);
} else if (code == 4) {
assert(byteStream.pos() < byteStream.size());
assert(wordStream.pos() + 2 <= wordStream.size());
src += _mv[byteStream.readByte()];
auto dc = wordStream.readUint16LE();
- mcdc<true>(dst, src, log2w, log2h, pitch, dc);
+ mcdc<true>(dst, src, log2w, log2h, stride, dc);
} else if (code == 5) {
assert(wordStream.pos() + 2 <= wordStream.size());
auto dc = wordStream.readUint16LE();
- mcdc<false>(dst, src, log2w, log2h, pitch, dc);
+ mcdc<false>(dst, src, log2w, log2h, stride, dc);
} else {
error("invalid code %d (steps %u,%u)", code, log2w, log2h);
}
@@ -444,10 +413,17 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::ScopedPtr<Graphics::Surface> frame(new Graphics::Surface());
frame->copyFrom(*_frame);
+ uint16 *dst = static_cast<uint16 *>(frame->getPixels());
+ const uint16 *src = static_cast<const uint16 *>(_frame->getPixels());
+ assert(frame->format.bytesPerPixel == 2);
+ auto stride = frame->pitch / frame->format.bytesPerPixel;
+ assert(stride == _frame->pitch / _frame->format.bytesPerPixel);
for (int y = 0, h = frame->h; y < h; y += 8) {
for (int x = 0, w = frame->w; x < w; x += 8) {
- decode_pfrm_block(frame.get(), x, y, 3, 3, bitStream, wordStream, byteStream);
+ decode_pfrm_block(dst + x, src + x, stride, 3, 3, bitStream, wordStream, byteStream);
}
+ dst += stride << 3;
+ src += stride << 3;
}
_frame = frame.release();
}
Commit: 42c9f5824b6eb8b3c569c50567133ddfe9e9e86f
https://github.com/scummvm/scummvm/commit/42c9f5824b6eb8b3c569c50567133ddfe9e9e86f
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-02-09T23:30:28+01:00
Commit Message:
VIDEO: 4XM: Fix compliation
Changed paths:
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index 84ebe3bb309..e139ff4f127 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -117,8 +117,8 @@ void HuffmanDecoder::buildTable(uint numCodes) {
_table[kLastEntry].freq = 0x7FFF;
auto codeIdx = numCodes;
while (true) {
- ushort idx = 0;
- ushort smallest2 = kLastEntry, smallest1 = kLastEntry;
+ uint16 idx = 0;
+ uint16 smallest2 = kLastEntry, smallest1 = kLastEntry;
while (idx < codeIdx) {
auto freq = _table[idx].freq;
if (freq != 0) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 4049672d2d0..9f1977e74ba 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -92,8 +92,8 @@ class HuffmanDecoder {
static constexpr uint kLastEntry = kMaxTableSize - 1;
struct HuffChar {
uint freq = 0;
- ushort falseIdx = kMaxTableSize;
- ushort trueIdx = kMaxTableSize;
+ uint16 falseIdx = kMaxTableSize;
+ uint16 trueIdx = kMaxTableSize;
};
HuffChar _table[kMaxTableSize] = {};
uint _startEntry = 0;
Commit: b3adb579c180443af7f3bf1575643b3d12679c07
https://github.com/scummvm/scummvm/commit/b3adb579c180443af7f3bf1575643b3d12679c07
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-02-09T23:30:28+01:00
Commit Message:
PHOENIXVR: Fix compilation
Changed paths:
engines/phoenixvr/rectf.h
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index f5740a52061..1565af78d30 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -32,7 +32,7 @@ Common::String toString() const {
}
END_POINT_TYPE(float, PointF)
-BEGIN_RECT_TYPE(float, RectF, PointF);
+BEGIN_RECT_TYPE(float, RectF, PointF)
Common::Rect toRect() const {
return Common::Rect(left, top, right, bottom);
}
Commit: 403845aa68af4e6e482f5a1c12b538298675ab35
https://github.com/scummvm/scummvm/commit/403845aa68af4e6e482f5a1c12b538298675ab35
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-02-09T23:30:29+01:00
Commit Message:
PHONEXIVR: Fix warning with struct/class mismatch
Changed paths:
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 760555e4ced..5f53696cce5 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -27,7 +27,7 @@
#include "graphics/pixelformat.h"
namespace Graphics {
-class Surface;
+struct Surface;
class Screen;
} // namespace Graphics
Commit: 5888d3cd038b3ddf6945b535199bca7f11fa9de3
https://github.com/scummvm/scummvm/commit/5888d3cd038b3ddf6945b535199bca7f11fa9de3
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-02-09T23:30:29+01:00
Commit Message:
PHOENIXVR: Fix portability issue with file offset printing
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 1e4366855f1..2c4203b8f7f 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -944,13 +944,13 @@ Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *slot)
warpCursor = cursor;
}
}
- debug("vars at %08lx", ms.pos());
+ debug("vars at %08x", (uint32)ms.pos());
for (auto &name : _script->getVarNames()) {
auto value = ms.readUint32LE();
debug("var %s: %u", name.c_str(), value);
g_engine->setVariable(name, value);
}
- debug("vars end at %08lx", ms.pos());
+ debug("vars end at %08x", (uint32)ms.pos());
auto currentSubroutine = ms.readSint32LE();
_prevWarp = ms.readSint32LE();
debug("currentSubroutine %d, prev warp %d", currentSubroutine, _prevWarp);
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index fd1604d4da8..72db9790256 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -243,7 +243,7 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto animChunkPos = s.pos();
auto animChunkId = s.readUint32LE();
auto animChunkSize = s.readUint32LE();
- debug("animation frame at %08zx: %08x %u", animChunkPos, animChunkId, animChunkSize);
+ debug("animation frame at %08x: %08x %u", (uint32)animChunkPos, animChunkId, animChunkSize);
assert(animChunkSize >= 8);
Animation::Frame frame;
if (animChunkId == CHUNK_ANIMATION_BLOCK) {
Commit: 041a150f7b14f8d4c29a85934d4d32d24e90b93a
https://github.com/scummvm/scummvm/commit/041a150f7b14f8d4c29a85934d4d32d24e90b93a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-02-09T23:30:29+01:00
Commit Message:
TESTBED: Added 4xm to the list of supported videos in the video playback test
Changed paths:
engines/testbed/video.cpp
diff --git a/engines/testbed/video.cpp b/engines/testbed/video.cpp
index b71b174703a..a348f1da114 100644
--- a/engines/testbed/video.cpp
+++ b/engines/testbed/video.cpp
@@ -29,6 +29,7 @@
#include "video/qt_decoder.h"
#include "video/qt_data.h"
#include "video/smk_decoder.h"
+#include "video/4xm_decoder.h"
#include "testbed/testbed.h"
#include "testbed/video.h"
@@ -74,6 +75,8 @@ Common::Error Videotests::videoTest(Common::SeekableReadStream *stream, const Co
video = new Video::MveDecoder();
} else if (name.hasSuffixIgnoreCase(".smk")) {
video = new Video::SmackerDecoder();
+ } else if (name.hasSuffixIgnoreCase(".4xm")) {
+ video = new Video::FourXMDecoder();
} else {
qtVideo = new Video::QuickTimeDecoder();
video = qtVideo;
Commit: 154b6c523ccfd23adf8158e743d08cb1c34a2c2a
https://github.com/scummvm/scummvm/commit/154b6c523ccfd23adf8158e743d08cb1c34a2c2a
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:29+01:00
Commit Message:
VIDEO: 4XM: remove setPixel, use sequential macroblock access in i frame decoding code
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index eaf637a94f3..7ca12368f3c 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -253,6 +253,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
FourXM::BEByteBitStream bitstream(bitstreamData.data(), bitstreamData.size(), 0);
uint prefixOffset = 0;
int lastDC = 0;
+ auto &format = _frame->format;
+ const auto dstPitch = _frame->pitch / format.bytesPerPixel - 16;
for (int mbY = 0; mbY < _frame->h; mbY += 16) {
for (int mbX = 0; mbX < _frame->w; mbX += 16) {
int16_t block[6][64] = {};
@@ -296,7 +298,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
};
auto blockCB = block[4];
auto blockCR = block[5];
- for (byte y = 0; y != 16; ++y) {
+ auto *dst = static_cast<uint16 *>(_frame->getBasePtr(mbX, mbY));
+ for (byte y = 0; y != 16; ++y, dst += dstPitch) {
for (byte x = 0; x != 16; ++x) {
auto yb = y & 7;
auto xb = x & 7;
@@ -306,9 +309,9 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
auto CB = blockCB[cblockIdx];
auto CR = blockCR[cblockIdx];
int CG = (CB + CR) >> 1;
- CB += CB;
- auto color = _frame->format.RGBToColor(Y + CR, Y - CG, Y + CB);
- _frame->setPixel(mbX | x, mbY | y, color);
+ CB *= 2;
+ auto color = format.RGBToColor(Y + CR, Y - CG, Y + CB);
+ *dst++ = color;
}
}
}
Commit: 0935e05bc2c19f9c6119033fc2671e1eb61300bf
https://github.com/scummvm/scummvm/commit/0935e05bc2c19f9c6119033fc2671e1eb61300bf
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:30+01:00
Commit Message:
VIDEO: 4XM: use double buffering and p-frame re-use the frame before previous, this seems to reduce dot artefacts a lot
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 7ca12368f3c..08d8767bc68 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -28,7 +28,7 @@
#include "common/memstream.h"
#include "common/stream.h"
#include "common/textconsole.h"
-#include "graphics/surface.h"
+#include "graphics/managed_surface.h"
#include "video/4xm_utils.h"
namespace Video {
@@ -88,13 +88,14 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
Common::Rational _frameRate;
uint _w, _h;
uint16 _version = 0;
- Graphics::Surface *_frame;
+ Common::ScopedPtr<Graphics::ManagedSurface> _framePtr, _lastFramePtr;
+ Graphics::Surface *_frame = nullptr, *_lastFrame = nullptr;
FourXM::HuffmanDecoder _blockType[4] = {};
int _mv[256];
Common::HashMap<byte, Common::Array<byte>> _cframes;
public:
- FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version), _frame(nullptr) {
+ FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version) {
if (_version <= 1)
error("versions 0 and 1 are not supported");
_blockType[0].initStatistics({16, 8, 4, 2, 1, 1});
@@ -125,16 +126,21 @@ private:
};
const Graphics::Surface *FourXMDecoder::FourXMVideoTrack::decodeNextFrame() {
- if (!_frame) {
- _frame = new Graphics::Surface();
- _frame->create(_w, _h, getPixelFormat());
- auto pitch = _frame->pitch / _frame->format.bytesPerPixel;
+ if (!_framePtr) {
+ _framePtr.reset(new Graphics::ManagedSurface());
+ _framePtr->create(_w, _h, getPixelFormat());
+ _frame = _framePtr->surfacePtr();
+ _lastFramePtr.reset(new Graphics::ManagedSurface());
+ _lastFramePtr->create(_w, _h, getPixelFormat());
+ _lastFrame = _lastFramePtr->surfacePtr();
+ assert(_framePtr->pitch == _lastFramePtr->pitch);
+ auto pitch = _framePtr->pitch / _framePtr->format.bytesPerPixel;
for (uint i = 0; i < 256; i++)
_mv[i] = mv[i][0] + mv[i][1] * pitch;
}
_dec->decodeNextFrameImpl();
- return _frame;
+ return _lastFrame;
}
int FourXMDecoder::FourXMVideoTrack::getCurFrame() const {
@@ -145,12 +151,7 @@ int FourXMDecoder::FourXMVideoTrack::getFrameCount() const {
return _dec->_frames.size();
}
-FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() {
- if (_frame) {
- _frame->free();
- delete _frame;
- }
-}
+FourXMDecoder::FourXMVideoTrack::~FourXMVideoTrack() = default;
namespace {
static const uint8_t iquant[64] = {
@@ -317,6 +318,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
}
}
assert(prefixOffset == prefixData.size());
+ SWAP(_frame, _lastFrame);
}
namespace {
@@ -414,21 +416,19 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::MemoryReadStream wordStream(wordStreamData.data(), wordStreamData.size());
Common::MemoryReadStream byteStream(byteStreamData.data(), byteStreamData.size());
- Common::ScopedPtr<Graphics::Surface> frame(new Graphics::Surface());
- frame->copyFrom(*_frame);
- uint16 *dst = static_cast<uint16 *>(frame->getPixels());
- const uint16 *src = static_cast<const uint16 *>(_frame->getPixels());
- assert(frame->format.bytesPerPixel == 2);
- auto stride = frame->pitch / frame->format.bytesPerPixel;
+ uint16 *dst = static_cast<uint16 *>(_frame->getPixels());
+ const uint16 *src = static_cast<const uint16 *>(_lastFrame->getPixels());
+ assert(_frame->format.bytesPerPixel == 2);
+ auto stride = _frame->pitch / _frame->format.bytesPerPixel;
assert(stride == _frame->pitch / _frame->format.bytesPerPixel);
- for (int y = 0, h = frame->h; y < h; y += 8) {
- for (int x = 0, w = frame->w; x < w; x += 8) {
+ for (int y = 0, h = _frame->h; y < h; y += 8) {
+ for (int x = 0, w = _frame->w; x < w; x += 8) {
decode_pfrm_block(dst + x, src + x, stride, 3, 3, bitStream, wordStream, byteStream);
}
dst += stride << 3;
src += stride << 3;
}
- _frame = frame.release();
+ SWAP(_frame, _lastFrame);
}
void FourXMDecoder::FourXMVideoTrack::decode_cfrm(Common::SeekableReadStream *stream) {
Commit: 3ab928b79daf05b379589d243a70d278a2e9035e
https://github.com/scummvm/scummvm/commit/3ab928b79daf05b379589d243a70d278a2e9035e
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:30+01:00
Commit Message:
VIDEO: 4XM: use LE audio flag only on LE platforms
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 08d8767bc68..5d7bf337502 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -66,7 +66,10 @@ public:
void decode(uint32 tag, byte *buf, uint size) {
if (_audioType == 0) {
// Raw PCM data
- byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
+ byte flags = Audio::FLAG_16BITS;
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
if (_audioChannels > 1)
flags |= Audio::FLAG_STEREO;
_output->queueBuffer(buf, size, DisposeAfterUse::YES, flags);
Commit: 378e94d6b46a8ec3fda91fa87127656187f09305
https://github.com/scummvm/scummvm/commit/378e94d6b46a8ec3fda91fa87127656187f09305
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:30+01:00
Commit Message:
AUDIO: ADPCM: 4XM: allow custom shift, pass 4 for 4XM
Changed paths:
audio/decoders/adpcm.cpp
audio/decoders/adpcm_intern.h
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
index 55208ae6248..94dcf309d2b 100644
--- a/audio/decoders/adpcm.cpp
+++ b/audio/decoders/adpcm.cpp
@@ -547,8 +547,8 @@ const int16 Ima_ADPCMStream::_imaTable[89] = {
32767
};
-int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
- int32 E = (2 * (code & 0x7) + 1) * _imaTable[_status.ima_ch[channel].stepIndex] / 8;
+int16 Ima_ADPCMStream::decodeIMA(byte code, int channel, int shift) {
+ int32 E = ((2 * (code & 0x7) + 1) * _imaTable[_status.ima_ch[channel].stepIndex]) >> shift;
int32 diff = (code & 0x08) ? -E : E;
int32 samp = CLIP<int32>(_status.ima_ch[channel].last + diff, -32768, 32767);
@@ -578,8 +578,8 @@ void FOURXM_ADPCMStream::decode() {
auto sample = 0;
for(auto s = encodedPerChannel; s--; ) {
byte data = _stream->readByte();
- plane[sample++] = decodeIMA(data & 0x0f, i);
- plane[sample++] = decodeIMA((data >> 4) & 0x0f, i);
+ plane[sample++] = decodeIMA(data & 0x0f, i, 4);
+ plane[sample++] = decodeIMA((data >> 4) & 0x0f, i, 4);
}
}
}
diff --git a/audio/decoders/adpcm_intern.h b/audio/decoders/adpcm_intern.h
index 599a1964d73..f3033918593 100644
--- a/audio/decoders/adpcm_intern.h
+++ b/audio/decoders/adpcm_intern.h
@@ -118,7 +118,7 @@ private:
class Ima_ADPCMStream : public ADPCMStream {
protected:
- int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel
+ int16 decodeIMA(byte code, int channel = 0, int shift = 3); // Default to using the left channel/using one channel
public:
Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
@@ -274,11 +274,12 @@ public:
return !_planes.empty() && _samplePos >= _planes[0].size();
}
- void reset() {
+ void reset() override {
Ima_ADPCMStream::reset();
_samplePos = 0;
_planes.clear();
}
+
private:
void decode();
Commit: bfcbb663741ea7aabb0bcaf1d9b38967b285cd63
https://github.com/scummvm/scummvm/commit/bfcbb663741ea7aabb0bcaf1d9b38967b285cd63
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:31+01:00
Commit Message:
VIDEO: 4XM: fix ADPCM clicks
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 5d7bf337502..79b619d6cb1 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -501,7 +501,7 @@ void FourXMDecoder::decodeNextFrameImpl() {
auto trackIdx = _stream->readUint32LE();
auto packetSize = _stream->readUint32LE();
if (trackIdx == 0 && _audio) {
- _audio->decode(tag, loadBuf(packetSize), packetSize);
+ _audio->decode(tag, loadBuf(packetSize - 8), packetSize - 8);
} else {
_stream->skip(packetSize);
offset += packetSize + 8;
Commit: 6ba1ba9d22467bf4bd9af2880949d56efe03a202
https://github.com/scummvm/scummvm/commit/6ba1ba9d22467bf4bd9af2880949d56efe03a202
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:31+01:00
Commit Message:
VIDEO: 4XM: use 32 bit multiplication - fix the remaining visual artefacts of decoding
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 79b619d6cb1..dfb4ae58a3b 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -329,25 +329,51 @@ namespace {
template<bool Scale>
void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
int log2h, int stride, uint dc) {
+ dc |= dc << 16;
int h = 1 << log2h;
- int w = 1 << log2w;
if (Scale) {
- for (int i = 0; i != h; ++i) {
- for (int j = 0; j != w; ++j) {
- dst[j] = src[j] + dc;
+ for (int i = 0; i < h; ++i) {
+ auto *dst32 = reinterpret_cast<uint32_t *>(dst);
+ auto *src32 = reinterpret_cast<const uint32_t *>(src);
+ switch (log2w) {
+ case 3:
+ dst32[2] = src32[2] + dc;
+ dst32[3] = src32[3] + dc;
+ // fall through
+ case 2:
+ dst32[1] = src32[1] + dc;
+ // fall through
+ case 1:
+ dst32[0] = src32[0] + dc;
+ break;
+ case 0:
+ *dst = *src + dc;
}
src += stride;
dst += stride;
}
} else {
- for (int i = 0; i != h; ++i) {
- for (int j = 0; j != w; ++j) {
- dst[j] = dc;
+ for (int i = 0; i < h; ++i) {
+ auto *dst32 = reinterpret_cast<uint32_t *>(dst);
+ switch (log2w) {
+ case 3:
+ dst32[2] = dc;
+ dst32[3] = dc;
+ // fall through
+ case 2:
+ dst32[1] = dc;
+ // fall through
+ case 1:
+ dst32[0] = dc;
+ break;
+ case 0:
+ *dst = dc;
}
dst += stride;
}
}
}
+
} // namespace
void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, FourXM::BEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
Commit: 9d6491ea6aaeee10039e080b1eae6ee5f61729d4
https://github.com/scummvm/scummvm/commit/9d6491ea6aaeee10039e080b1eae6ee5f61729d4
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:31+01:00
Commit Message:
VIDEO: 4XM: revisited binary format and fix all audio issues
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index dfb4ae58a3b..525076f13a7 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -63,7 +63,7 @@ public:
byte getAudioType() const { return _audioType; }
- void decode(uint32 tag, byte *buf, uint size) {
+ void decode(byte *buf, uint size) {
if (_audioType == 0) {
// Raw PCM data
byte flags = Audio::FLAG_16BITS;
@@ -513,7 +513,8 @@ void FourXMDecoder::decodeNextFrameImpl() {
byte *buf = static_cast<byte *>(malloc(bufSize));
if (!buf)
error("failed to allocate %u bytes", bufSize);
- _stream->read(buf, bufSize);
+ if (_stream->read(buf, bufSize) != bufSize)
+ error("loadBuf: short read");
return buf;
};
switch (tag) {
@@ -527,20 +528,19 @@ void FourXMDecoder::decodeNextFrameImpl() {
auto trackIdx = _stream->readUint32LE();
auto packetSize = _stream->readUint32LE();
if (trackIdx == 0 && _audio) {
- _audio->decode(tag, loadBuf(packetSize - 8), packetSize - 8);
+ debug("audio data %u %u", trackIdx, packetSize);
+ _audio->decode(loadBuf(packetSize), packetSize);
} else {
_stream->skip(packetSize);
- offset += packetSize + 8;
}
+ offset += packetSize + 8;
}
} break;
case 1: {
auto trackIdx = _stream->readUint32LE();
_stream->skip(4);
if (trackIdx == 0 && _audio) {
- _audio->decode(tag, loadBuf(size - 8), size - 8);
- } else {
- _stream->skip(size - 8);
+ _audio->decode(loadBuf(size - 8), size - 8);
}
} break;
default:
Commit: efe1d38c283792031d0a9c2b1e9b7b0c8d39448b
https://github.com/scummvm/scummvm/commit/efe1d38c283792031d0a9c2b1e9b7b0c8d39448b
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:32+01:00
Commit Message:
VIDEO: 4XM: add shift argument to idct
Changed paths:
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index e139ff4f127..ba52ee2bb13 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -180,7 +180,7 @@ Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte
#define MULTIPLY(var, const) ((int)((var) * (unsigned)(const)) >> 16)
-void idct(int16_t block[64]) {
+void idct(int16_t block[64], int shift) {
int tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
int tmp10, tmp11, tmp12, tmp13;
int z5, z10, z11, z12, z13;
@@ -253,14 +253,14 @@ void idct(int16_t block[64]) {
tmp5 = tmp11 - tmp6;
tmp4 = tmp10 + tmp5;
- block[0 + i] = (tmp0 + tmp7) >> 6;
- block[7 + i] = (tmp0 - tmp7) >> 6;
- block[1 + i] = (tmp1 + tmp6) >> 6;
- block[6 + i] = (tmp1 - tmp6) >> 6;
- block[2 + i] = (tmp2 + tmp5) >> 6;
- block[5 + i] = (tmp2 - tmp5) >> 6;
- block[4 + i] = (tmp3 + tmp4) >> 6;
- block[3 + i] = (tmp3 - tmp4) >> 6;
+ block[0 + i] = (tmp0 + tmp7) >> shift;
+ block[7 + i] = (tmp0 - tmp7) >> shift;
+ block[1 + i] = (tmp1 + tmp6) >> shift;
+ block[6 + i] = (tmp1 - tmp6) >> shift;
+ block[2 + i] = (tmp2 + tmp5) >> shift;
+ block[5 + i] = (tmp2 - tmp5) >> shift;
+ block[4 + i] = (tmp3 + tmp4) >> shift;
+ block[3 + i] = (tmp3 - tmp4) >> shift;
}
}
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 9f1977e74ba..0e72b285e8d 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -120,7 +120,7 @@ private:
void dumpImpl(uint code, uint size, uint index, uint ch) const;
};
-void idct(int16_t block[64]);
+void idct(int16_t block[64], int shift = 6);
} // namespace FourXM
} // namespace Video
Commit: 224bcb3f813d26499a223d7173d16b3d88f36173
https://github.com/scummvm/scummvm/commit/224bcb3f813d26499a223d7173d16b3d88f36173
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:32+01:00
Commit Message:
PHOENIXVR: improve idct accuracy by 1 bit
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 72db9790256..722eda032d3 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -27,7 +27,6 @@
#include "common/textconsole.h"
#include "graphics/screen.h"
#include "graphics/surface.h"
-#include "graphics/yuv_to_rgb.h"
#include "image/bmp.h"
#include "math/vector3d.h"
#include "phoenixvr/angle.h"
@@ -128,12 +127,12 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
}
- Video::FourXM::idct(ac);
+ Video::FourXM::idct(ac, 5);
auto *dst = prefix ? planes.data() + channel * planeSize + blockIdx * 64 : planes.data() + channel * planeSize + y0 * planePitch + x0;
const auto *src = ac;
for (unsigned h = 8; h--; dst += planePitch - 8) {
for (unsigned w = 8; w--;) {
- int v = *src++ * 2 + 128; // FIXME: just compensating 13 bit shift
+ int v = *src++ + 128;
v = clip(v);
*dst++ = v;
}
@@ -169,11 +168,10 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
}
} else {
-#if 0
auto &format = pic.format;
- for(int yy = 0; yy < pic.h; ++yy) {
- auto *rows = static_cast<uint32*>(pic.getBasePtr(0, yy));
- for(int xx = 0; xx < pic.w; ++xx) {
+ for (int yy = 0; yy < pic.h; ++yy) {
+ auto *rows = static_cast<uint32 *>(pic.getBasePtr(0, yy));
+ for (int xx = 0; xx < pic.w; ++xx) {
int16 y = *yPtr++;
int16 cr = *crPtr++;
int16 cb = *cbPtr++;
@@ -181,10 +179,6 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
*rows++ = YCbCr2RGB(format, y, cb, cr);
}
}
-#else
- YUVToRGBMan.convert444(&pic, Graphics::YUVToRGBManager::kScaleFull,
- yPtr, cbPtr, crPtr, pic.w, pic.h, planePitch, planePitch);
-#endif
}
}
} // namespace
Commit: e1f355abfe63738c179d8f8eb5b73996d4743b6f
https://github.com/scummvm/scummvm/commit/e1f355abfe63738c179d8f8eb5b73996d4743b6f
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:32+01:00
Commit Message:
VIDEO: 4XM: remove noisy log
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 525076f13a7..7de5fa52215 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -528,7 +528,6 @@ void FourXMDecoder::decodeNextFrameImpl() {
auto trackIdx = _stream->readUint32LE();
auto packetSize = _stream->readUint32LE();
if (trackIdx == 0 && _audio) {
- debug("audio data %u %u", trackIdx, packetSize);
_audio->decode(loadBuf(packetSize), packetSize);
} else {
_stream->skip(packetSize);
Commit: 0d768f9fab6f0c146852546968bd6b022e4a1953
https://github.com/scummvm/scummvm/commit/0d768f9fab6f0c146852546968bd6b022e4a1953
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:32+01:00
Commit Message:
PHOENIXVR: remove planar YUV from VR decoder, simplify code
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 722eda032d3..8765385087a 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -51,9 +51,6 @@ T clip(T a) {
}
uint32 YCbCr2RGB(const Graphics::PixelFormat &format, int16 y, int16 cb, int16 cr) {
- cr -= 128;
- cb -= 128;
-
int r = clip(y + ((cr * 91881 + 32768) >> 16));
int g = clip(y - ((cb * 22553 + cr * 46801 + 32768) >> 16));
int b = clip(y + ((cb * 116129 + 32768) >> 16));
@@ -93,90 +90,61 @@ struct Quantisation {
void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, uint acSize, const byte *dcPtr, uint dcSize, int quality, const Common::Array<uint> *prefix = nullptr) {
Quantisation quant(quality);
+
auto decoded = Video::FourXM::HuffmanDecoder::unpack(huff, huffSize, 1);
uint decodedOffset = 0;
- const uint planePitch = prefix ? 8 : pic.w;
- const uint planeSize = prefix ? prefix->size() * 64 : planePitch * pic.h;
- Common::Array<byte> planes(planeSize * 3, 0);
-
Video::FourXM::LEByteBitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
- uint channel = 0;
- uint x0 = 0, y0 = 0;
- uint blockIdx = 0;
- while (decodedOffset < decoded.size()) {
- int16 ac[64] = {};
- int8 dc8 = dcBs.readUInt(8);
- auto *iquant = channel ? quant.quantCbCr : quant.quantY;
- ac[0] = iquant[0] * dc8;
- for (uint idx = 1; idx < 64;) {
- auto b = decoded[decodedOffset++];
- if (b == 0x00) {
- break;
- } else if (b == 0xf0) {
- idx += 16;
- } else {
- auto h = b >> 4;
- auto l = b & 0x0f;
- idx += h;
- if (l && idx < 64) {
- auto ac_idx = ZIGZAG[idx];
- ac[ac_idx] = iquant[ac_idx] * acBs.readInt(l);
- ++idx;
+
+ const auto dstPitch = pic.pitch / pic.format.bytesPerPixel - 8;
+ for (uint blockIdx = 0; decodedOffset < decoded.size(); ++blockIdx) {
+ int16 block[3][64] = {};
+ for (unsigned channel = 0; channel != 3; ++channel) {
+ int16 *ac = block[channel];
+ int8 dc8 = dcBs.readUInt(8);
+ auto *iquant = channel ? quant.quantCbCr : quant.quantY;
+ ac[0] = iquant[0] * dc8;
+ for (uint idx = 1; idx < 64;) {
+ auto b = decoded[decodedOffset++];
+ if (b == 0x00) {
+ break;
+ } else if (b == 0xf0) {
+ idx += 16;
+ } else {
+ auto h = b >> 4;
+ auto l = b & 0x0f;
+ idx += h;
+ if (l && idx < 64) {
+ auto ac_idx = ZIGZAG[idx];
+ ac[ac_idx] = iquant[ac_idx] * acBs.readInt(l);
+ ++idx;
+ }
}
}
- }
- Video::FourXM::idct(ac, 5);
- auto *dst = prefix ? planes.data() + channel * planeSize + blockIdx * 64 : planes.data() + channel * planeSize + y0 * planePitch + x0;
- const auto *src = ac;
- for (unsigned h = 8; h--; dst += planePitch - 8) {
- for (unsigned w = 8; w--;) {
- int v = *src++ + 128;
- v = clip(v);
- *dst++ = v;
- }
+ Video::FourXM::idct(ac, 5);
}
- ++channel;
- if (channel == 3) {
- ++blockIdx;
- channel = 0;
- x0 += 8;
- if (static_cast<int16>(x0) >= pic.w) {
- x0 = 0;
- y0 += 8;
- }
+ int dstY;
+ int dstX;
+ if (prefix) {
+ auto dstOffset = (*prefix)[blockIdx];
+ dstY = dstOffset / pic.w;
+ dstX = dstOffset % pic.w;
+ } else {
+ auto dstOffset = blockIdx << 3;
+ dstX = dstOffset % pic.w;
+ dstY = (dstOffset / pic.w) << 3;
}
- }
- auto *yPtr = planes.data();
- auto *cbPtr = yPtr + planeSize;
- auto *crPtr = cbPtr + planeSize;
-
- if (prefix) {
- for (auto dstOffset : *prefix) {
- int dstY = dstOffset / pic.w;
- int dstX = dstOffset % pic.w;
- for (uint by = 0; by < 8; ++by) {
- auto *dstPixel = static_cast<uint32 *>(pic.getBasePtr(dstX, dstY++));
- for (uint bx = 0; bx < 8; ++bx) {
- int16 y = *yPtr++;
- int16 cr = *crPtr++;
- int16 cb = *cbPtr++;
-
- *dstPixel++ = YCbCr2RGB(pic.format, y, cb, cr);
- }
- }
- }
- } else {
- auto &format = pic.format;
- for (int yy = 0; yy < pic.h; ++yy) {
- auto *rows = static_cast<uint32 *>(pic.getBasePtr(0, yy));
- for (int xx = 0; xx < pic.w; ++xx) {
- int16 y = *yPtr++;
- int16 cr = *crPtr++;
- int16 cb = *cbPtr++;
-
- *rows++ = YCbCr2RGB(format, y, cb, cr);
+
+ auto *dstPixel = static_cast<uint32 *>(pic.getBasePtr(dstX, dstY));
+ uint srcIdx = 0;
+ for (uint by = 0; by < 8; ++by, dstPixel += dstPitch) {
+ for (uint bx = 0; bx < 8; ++bx, ++srcIdx) {
+ int16 y = block[0][srcIdx];
+ int16 cb = block[1][srcIdx];
+ int16 cr = block[2][srcIdx];
+
+ *dstPixel++ = YCbCr2RGB(pic.format, y + 128, cb, cr);
}
}
}
Commit: 504c06e83310c6a4b8d5eb5cba509e4df262eb09
https://github.com/scummvm/scummvm/commit/504c06e83310c6a4b8d5eb5cba509e4df262eb09
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:33+01:00
Commit Message:
PHOENIXVR: use managed surfac where possible
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 2c4203b8f7f..e93c1a601ba 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -34,7 +34,7 @@
#include "graphics/font.h"
#include "graphics/fonts/ttf.h"
#include "graphics/framelimiter.h"
-#include "graphics/surface.h"
+#include "graphics/managed_surface.h"
#include "image/pcx.h"
#include "phoenixvr/console.h"
#include "phoenixvr/game_state.h"
@@ -314,11 +314,10 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
if (dec.needsUpdate()) {
auto *s = dec.decodeNextFrame();
if (s) {
- Common::ScopedPtr<Graphics::Surface> converted;
if (s->format != _pixelFormat) {
- converted.reset(s->convertTo(_pixelFormat));
- _screen->copyFrom(*converted);
- converted->free();
+ Graphics::ManagedSurface converted;
+ converted.convertFrom(*s, _pixelFormat);
+ _screen->copyFrom(*converted.surfacePtr());
} else
_screen->copyFrom(*s);
}
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8765385087a..0b6e66810d8 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -151,12 +151,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
} // namespace
-VR::~VR() {
- if (_pic) {
- _pic->free();
- _pic.reset();
- }
-}
+VR::~VR() = default;
VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
VR vr;
@@ -181,7 +176,7 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto huffSize = READ_LE_UINT32(vrData.data());
if (vr._pic)
error("2d/3d picture in the same file");
- vr._pic.reset(new Graphics::Surface());
+ vr._pic.reset(new Graphics::ManagedSurface());
auto *pic = vr._pic.get();
if (pic3d) {
vr._vr = true;
@@ -194,7 +189,7 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
auto dcOffset = READ_LE_UINT32(huff + huffSize);
auto *dcPtr = acPtr + 4 + dcOffset;
auto *dcEnd = vrData.data() + vrData.size();
- unpack(*pic, huff, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
+ unpack(*pic->surfacePtr(), huff, huffSize, acPtr, dcPtr - acPtr, dcPtr, dcEnd - dcPtr, quality);
} else if (chunkId == CHUNK_ANIMATION) {
Animation animation;
animation.name = s.readString(0, 32);
@@ -325,7 +320,7 @@ void VR::playAnimation(const Common::String &name, const Common::String &variabl
animation.speed = speed;
animation.variable = variable;
animation.variableValue = value;
- animation.renderNextFrame(*_pic);
+ animation.renderNextFrame(*_pic->surfacePtr());
}
void VR::Animation::renderNextFrame(Graphics::Surface &pic) {
@@ -357,11 +352,11 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
}
for (auto &animation : _animations)
- animation.render(*_pic, dt);
+ animation.render(*_pic->surfacePtr(), dt);
if (!_vr) {
Common::Point dst(0, 0);
- Common::Rect src(_pic->getRect());
+ Common::Rect src(_pic->getBounds());
Common::Rect::getBlitRect(dst, src, screen->getBounds());
screen->copyRectToSurface(*_pic, dst.x, dst.y, src);
if (regSet) {
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 5f53696cce5..513ad8a7b26 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -24,6 +24,7 @@
#include "common/array.h"
#include "common/stream.h"
+#include "graphics/managed_surface.h"
#include "graphics/pixelformat.h"
namespace Graphics {
@@ -34,7 +35,7 @@ class Screen;
namespace PhoenixVR {
class RegionSet;
class VR {
- Common::ScopedPtr<Graphics::Surface> _pic;
+ Common::ScopedPtr<Graphics::ManagedSurface> _pic;
bool _vr = false;
struct Animation {
struct Frame {
@@ -68,7 +69,7 @@ public:
void render(Graphics::Screen *screen, float ax, float ay, float fov, float dt, RegionSet *regSet);
bool isVR() const { return _vr; }
void playAnimation(const Common::String &name, const Common::String &variable, int value, float speed);
- Graphics::Surface &getSurface() { return *_pic; }
+ Graphics::Surface &getSurface() { return *_pic->surfacePtr(); }
};
} // namespace PhoenixVR
Commit: 9512d73ca3d264db53e229c83eb09111c9bb5699
https://github.com/scummvm/scummvm/commit/9512d73ca3d264db53e229c83eb09111c9bb5699
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:33+01:00
Commit Message:
PHOENIXVR: add variable.txt to detection
Changed paths:
engines/phoenixvr/detection_tables.h
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index 85bd8af1631..a267f661cbe 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -19,6 +19,7 @@
*
*/
+#include "advancedDetector.h"
namespace PhoenixVR {
const PlainGameDescriptor phoenixvrGames[] = {
@@ -28,7 +29,9 @@ const PlainGameDescriptor phoenixvrGames[] = {
const ADGameDescription gameDescriptions[] = {
{"necrono",
nullptr,
- AD_ENTRY1s("script.pak", "86294b9c445c3e06e24269c84036a207", 223),
+ AD_ENTRY2s(
+ "script.pak", "86294b9c445c3e06e24269c84036a207", 223,
+ "variable.txt", "32de208759345075112f3c48b548853d", 3468),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DROPPLATFORM,
Commit: 45da78d1b1e7e0ed9efbdedbd82a3a0c4249b39c
https://github.com/scummvm/scummvm/commit/45da78d1b1e7e0ed9efbdedbd82a3a0c4249b39c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:34+01:00
Commit Message:
PHOENIXVR: add credits
Changed paths:
engines/phoenixvr/credits.pl
diff --git a/engines/phoenixvr/credits.pl b/engines/phoenixvr/credits.pl
index 151c9a5835d..5544437a5ee 100644
--- a/engines/phoenixvr/credits.pl
+++ b/engines/phoenixvr/credits.pl
@@ -1,3 +1,4 @@
begin_section("PhoenixVR");
- add_person("Name 1", "Handle 1", "");
+ add_person("Vladimir Menshakov", "whoozle", "");
+ add_person("Hermann Noll", "Helco", "Help with 3D VR projections");
end_section();
Commit: 0beab84bd322cea4fcac69c60f1f20771b5b8b1c
https://github.com/scummvm/scummvm/commit/0beab84bd322cea4fcac69c60f1f20771b5b8b1c
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:34+01:00
Commit Message:
PHOENIXVR: switch detection to textes.txt
add Cameron Files: The Secret at Loch Ness
Changed paths:
engines/phoenixvr/detection_tables.h
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index a267f661cbe..b7da018ac3e 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -24,6 +24,7 @@ namespace PhoenixVR {
const PlainGameDescriptor phoenixvrGames[] = {
{"necrono", "Necronomicon: The Dawning of Darkness"},
+ {"cameronlochness", "Cameron Files: The Secret at Loch Ness"},
{0, 0}};
const ADGameDescription gameDescriptions[] = {
@@ -31,12 +32,22 @@ const ADGameDescription gameDescriptions[] = {
nullptr,
AD_ENTRY2s(
"script.pak", "86294b9c445c3e06e24269c84036a207", 223,
- "variable.txt", "32de208759345075112f3c48b548853d", 3468),
+ "textes.txt", "f795f35b079cb8ef599724a2a7336c7e", 5319),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DROPPLATFORM,
GUIO1(GUIO_NONE)},
+ {"cameronlochness",
+ nullptr,
+ AD_ENTRY2s(
+ "script.pak", "a7ee3aae653658f93bba7f237bcf06f3", 1904,
+ "textes.txt", "294efb30581661615359ce234e2e85fb", 1596),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DROPPLATFORM | ADGF_UNSUPPORTED,
+ GUIO1(GUIO_NONE)},
+
AD_TABLE_END_MARKER};
} // End of namespace PhoenixVR
Commit: 0b0cb1f9b73fcb1487314228dcbbd84e9e35bcca
https://github.com/scummvm/scummvm/commit/0b0cb1f9b73fcb1487314228dcbbd84e9e35bcca
Author: Vlad the Lad (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:34+01:00
Commit Message:
Add highres 16bit flags to configure
Co-authored-by: Cameron Cawley <ccawley2011 at gmail.com>
Changed paths:
engines/phoenixvr/configure.engine
diff --git a/engines/phoenixvr/configure.engine b/engines/phoenixvr/configure.engine
index 16eaf0d97a1..e77a35e64c2 100644
--- a/engines/phoenixvr/configure.engine
+++ b/engines/phoenixvr/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
-add_engine phoenixvr "PhoenixVR" no "" "" "" ""
+add_engine phoenixvr "PhoenixVR" no "" "" "highres 16bit" ""
Commit: 0cf0adb65984ffaeabc77496bce391e073dbb4ea
https://github.com/scummvm/scummvm/commit/0cf0adb65984ffaeabc77496bce391e073dbb4ea
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:34+01:00
Commit Message:
PHOENIXVR: change quantisation tables types to byte
Changed paths:
engines/phoenixvr/dct_tables.h
diff --git a/engines/phoenixvr/dct_tables.h b/engines/phoenixvr/dct_tables.h
index 51fb749678f..381ea546874 100644
--- a/engines/phoenixvr/dct_tables.h
+++ b/engines/phoenixvr/dct_tables.h
@@ -32,7 +32,7 @@ const byte ZIGZAG[] = {
46, 53, 60, 61, 54, 47, 55, 62, 63};
// looks like standard JPEG quantisation matrix
-const char QY[] = {
+const byte QY[] = {
16,
11,
10,
@@ -99,7 +99,7 @@ const char QY[] = {
99,
};
-const char QUV[] = {
+const byte QUV[] = {
17,
18,
24,
Commit: f3aedf0b97e6302aa2ad47ab6589fd13ff4d23d7
https://github.com/scummvm/scummvm/commit/f3aedf0b97e6302aa2ad47ab6589fd13ff4d23d7
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:35+01:00
Commit Message:
VIDEO: 4XM: use packetized audio stream
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 7de5fa52215..61560ccb0e3 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -54,17 +54,12 @@ class FourXMDecoder::FourXMAudioTrack : public AudioTrack {
uint _audioType;
uint _audioChannels;
uint _sampleRate;
- Common::ScopedPtr<Audio::QueuingAudioStream> _output;
+ Common::ScopedPtr<Audio::PacketizedAudioStream> _output;
public:
- FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate),
- _output(Audio::makeQueuingAudioStream(sampleRate, audioChannels > 1)) {
- }
-
- byte getAudioType() const { return _audioType; }
-
- void decode(byte *buf, uint size) {
- if (_audioType == 0) {
+ FourXMAudioTrack(FourXMDecoder *dec, uint trackIdx, uint audioType, uint audioChannels, uint sampleRate) : AudioTrack(Audio::Mixer::SoundType::kPlainSoundType), _audioType(audioType), _audioChannels(audioChannels), _sampleRate(sampleRate) {
+ switch (_audioType) {
+ case 0: {
// Raw PCM data
byte flags = Audio::FLAG_16BITS;
#ifdef SCUMM_LITTLE_ENDIAN
@@ -72,14 +67,22 @@ public:
#endif
if (_audioChannels > 1)
flags |= Audio::FLAG_STEREO;
- _output->queueBuffer(buf, size, DisposeAfterUse::YES, flags);
- } else if (_audioType == 1) {
- auto *input = new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
- _output->queueAudioStream(Audio::makeADPCMStream(input, DisposeAfterUse::YES, size, Audio::ADPCMType::kADPCM4XM, _sampleRate, _audioChannels));
- } else {
- free(buf);
- warning("unsupported audio type %u", _audioType);
+ _output.reset(Audio::makePacketizedRawStream(sampleRate, flags));
+ break;
}
+ case 1:
+ _output.reset(Audio::makePacketizedADPCMStream(Audio::ADPCMType::kADPCM4XM, sampleRate, audioChannels));
+ break;
+ default:
+ error("FourXMAudioTrack: unknown audio type: %d", _audioType);
+ }
+ }
+
+ byte getAudioType() const { return _audioType; }
+
+ void decode(byte *buf, uint size) {
+ auto *input = new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
+ _output->queuePacket(input);
}
private:
Commit: b971082cee1aa3fa0a9990fcb015a0e05e38dc89
https://github.com/scummvm/scummvm/commit/b971082cee1aa3fa0a9990fcb015a0e05e38dc89
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:35+01:00
Commit Message:
PHOENIXVR: remove intermediate conversion for video, use simpleBlitFrom
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index e93c1a601ba..34f9787b8d0 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -313,14 +313,8 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
}
if (dec.needsUpdate()) {
auto *s = dec.decodeNextFrame();
- if (s) {
- if (s->format != _pixelFormat) {
- Graphics::ManagedSurface converted;
- converted.convertFrom(*s, _pixelFormat);
- _screen->copyFrom(*converted.surfacePtr());
- } else
- _screen->copyFrom(*s);
- }
+ if (s)
+ _screen->simpleBlitFrom(*s);
}
// Delay for a bit. All events loops should have a delay
Commit: 74d40a60295bf028aca3a5b678d5d2e3721d7a15
https://github.com/scummvm/scummvm/commit/74d40a60295bf028aca3a5b678d5d2e3721d7a15
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:35+01:00
Commit Message:
PHOENIXVR: add fallback font if freetype is not available
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 34f9787b8d0..96506f085ca 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -32,6 +32,7 @@
#include "common/system.h"
#include "engines/util.h"
#include "graphics/font.h"
+#include "graphics/fontman.h"
#include "graphics/fonts/ttf.h"
#include "graphics/framelimiter.h"
#include "graphics/managed_surface.h"
@@ -462,13 +463,17 @@ void PhoenixVREngine::loadVariables() {
}
void PhoenixVREngine::rollover(Common::Rect dstRect, int textId, int size, bool bold, uint16_t color) {
- Graphics::Font *font = nullptr;
+ const Graphics::Font *font = nullptr;
+#ifdef USE_FREETYPE2
if (size < 14)
font = _font12.get();
else if (size < 18)
font = _font14.get();
else
font = _font18.get();
+#else
+ font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
+#endif
if (!font)
return;
Commit: d7dbc21bbaa4d7a4006afaa09ec3bb1153c9214a
https://github.com/scummvm/scummvm/commit/d7dbc21bbaa4d7a4006afaa09ec3bb1153c9214a
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:36+01:00
Commit Message:
PHOENIXVR: support 16 bit pixel formats, use preferred hw format for the engine
Changed paths:
engines/phoenixvr/metaengine.cpp
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 1380e717b60..18e76d245a7 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -93,7 +93,8 @@ SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target,
desc.setSaveSlot(slotIdx);
desc.setDeletableFlag(true);
desc.setDescription(state.game + " " + state.info);
- desc.setThumbnail(state.getThumbnail(Graphics::BlendBlit::getSupportedPixelFormat(), 160));
+ Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
+ desc.setThumbnail(state.getThumbnail(rgb565, 160));
return desc;
}
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 96506f085ca..d219ee0d9fe 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -52,7 +52,6 @@ PhoenixVREngine *g_engine;
PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
- _pixelFormat(Graphics::BlendBlit::getSupportedPixelFormat()),
_rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0),
_thumbnail(139, 103, _rgb565),
_lockKey(13),
@@ -61,6 +60,13 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_angleY(-M_PI_2),
_mixer(syst->getMixer()) {
g_engine = this;
+ for (auto format : g_system->getSupportedFormats()) {
+ debug("preferred format: %s", format.toString().c_str());
+ _pixelFormat = format;
+ break;
+ }
+ if (_pixelFormat.bytesPerPixel < 2)
+ error("Expected at least 16 bit format");
}
PhoenixVREngine::~PhoenixVREngine() {
@@ -350,7 +356,7 @@ Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
if (path.hasSuffix(".pcx")) {
Image::PCXDecoder pcx;
if (pcx.loadStream(file)) {
- auto *s = pcx.getSurface()->convertTo(_pixelFormat, pcx.hasPalette() ? pcx.getPalette().data() : nullptr);
+ auto *s = pcx.getSurface()->convertTo(Graphics::BlendBlit::getSupportedPixelFormat(), pcx.hasPalette() ? pcx.getPalette().data() : nullptr);
if (s) {
byte r = 0, g = 0, b = 0;
s->applyColorKey(r, g, b);
@@ -805,7 +811,7 @@ void PhoenixVREngine::paint(Graphics::Surface &src, Common::Point dst) {
Common::Rect clip = _screen->getBounds();
if (Common::Rect::getBlitRect(dst, srcRect, clip)) {
Common::Rect dstRect(dst.x, dst.y, dst.x + srcRect.width(), dst.y + srcRect.height());
- _screen->blendBlitFrom(src, srcRect, dstRect);
+ _screen->blitFrom(src, srcRect, dstRect);
}
}
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 0b6e66810d8..bf4b6a7c8b4 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -136,16 +136,33 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
dstY = (dstOffset / pic.w) << 3;
}
- auto *dstPixel = static_cast<uint32 *>(pic.getBasePtr(dstX, dstY));
- uint srcIdx = 0;
- for (uint by = 0; by < 8; ++by, dstPixel += dstPitch) {
- for (uint bx = 0; bx < 8; ++bx, ++srcIdx) {
- int16 y = block[0][srcIdx];
- int16 cb = block[1][srcIdx];
- int16 cr = block[2][srcIdx];
-
- *dstPixel++ = YCbCr2RGB(pic.format, y + 128, cb, cr);
+ switch (pic.format.bytesPerPixel) {
+ case 4: {
+ auto *dstPixel = static_cast<uint32 *>(pic.getBasePtr(dstX, dstY));
+ uint srcIdx = 0;
+ for (uint by = 0; by < 8; ++by, dstPixel += dstPitch) {
+ for (uint bx = 0; bx < 8; ++bx, ++srcIdx) {
+ int16 y = block[0][srcIdx];
+ int16 cb = block[1][srcIdx];
+ int16 cr = block[2][srcIdx];
+
+ *dstPixel++ = YCbCr2RGB(pic.format, y + 128, cb, cr);
+ }
+ }
+ } break;
+ case 2: {
+ auto *dstPixel = static_cast<uint16 *>(pic.getBasePtr(dstX, dstY));
+ uint srcIdx = 0;
+ for (uint by = 0; by < 8; ++by, dstPixel += dstPitch) {
+ for (uint bx = 0; bx < 8; ++bx, ++srcIdx) {
+ int16 y = block[0][srcIdx];
+ int16 cb = block[1][srcIdx];
+ int16 cr = block[2][srcIdx];
+
+ *dstPixel++ = YCbCr2RGB(pic.format, y + 128, cb, cr);
+ }
}
+ } break;
}
}
}
Commit: 2d687c415ba70bdd4c2afa5ae9acf30e8cbbfb03
https://github.com/scummvm/scummvm/commit/2d687c415ba70bdd4c2afa5ae9acf30e8cbbfb03
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:36+01:00
Commit Message:
PHOENIXVR: remove custom bitstream
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index bf4b6a7c8b4..8effdc28b6f 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -21,8 +21,10 @@
#include "phoenixvr/vr.h"
#include "common/array.h"
+#include "common/bitstream.h"
#include "common/debug.h"
#include "common/file.h"
+#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/screen.h"
@@ -50,6 +52,17 @@ T clip(T a) {
return a >= 0 ? a <= 255 ? a : 255 : 0;
}
+unsigned reverseBits(unsigned value, unsigned n) {
+ unsigned r = 0;
+ while (n--) {
+ r <<= 1;
+ if (value & 1)
+ r |= 1;
+ value >>= 1;
+ }
+ return r;
+}
+
uint32 YCbCr2RGB(const Graphics::PixelFormat &format, int16 y, int16 cb, int16 cr) {
int r = clip(y + ((cr * 91881 + 32768) >> 16));
int g = clip(y - ((cb * 22553 + cr * 46801 + 32768) >> 16));
@@ -94,14 +107,16 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
auto decoded = Video::FourXM::HuffmanDecoder::unpack(huff, huffSize, 1);
uint decodedOffset = 0;
- Video::FourXM::LEByteBitStream acBs(acPtr, acSize, 0), dcBs(dcPtr, dcSize, 0);
+ Common::BitStreamMemoryStream acMs(acPtr, acSize);
+ Common::BitStreamMemoryStream dcMs(dcPtr, dcSize);
+ Common::BitStreamMemory8MSB acBs(&acMs), dcBs(&dcMs);
const auto dstPitch = pic.pitch / pic.format.bytesPerPixel - 8;
for (uint blockIdx = 0; decodedOffset < decoded.size(); ++blockIdx) {
int16 block[3][64] = {};
for (unsigned channel = 0; channel != 3; ++channel) {
int16 *ac = block[channel];
- int8 dc8 = dcBs.readUInt(8);
+ int8 dc8 = static_cast<int8>(reverseBits(dcBs.getBits<8>(), 8));
auto *iquant = channel ? quant.quantCbCr : quant.quantY;
ac[0] = iquant[0] * dc8;
for (uint idx = 1; idx < 64;) {
@@ -116,7 +131,8 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
idx += h;
if (l && idx < 64) {
auto ac_idx = ZIGZAG[idx];
- ac[ac_idx] = iquant[ac_idx] * acBs.readInt(l);
+ auto k = Video::FourXM::readInt(reverseBits(acBs.getBits(l), l), l);
+ ac[ac_idx] = iquant[ac_idx] * k;
++idx;
}
}
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 61560ccb0e3..5941fe134ea 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -23,6 +23,7 @@
#include "audio/audiostream.h"
#include "audio/decoders/adpcm.h"
#include "audio/decoders/raw.h"
+#include "common/bitstream.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/memstream.h"
@@ -127,7 +128,7 @@ public:
void decode_cfrm(Common::SeekableReadStream *stream);
private:
- void decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, FourXM::BEWordBitStream &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
+ void decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, Common::BitStreamMemory32LEMSB &bitStream, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream);
Common::Rational getFrameRate() const override { return _frameRate; }
};
@@ -257,7 +258,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
assert(stream->pos() == stream->size());
auto prefixData = FourXM::HuffmanDecoder::unpack(prefixStream.data(), prefixStream.size(), 4);
- FourXM::BEByteBitStream bitstream(bitstreamData.data(), bitstreamData.size(), 0);
+ Common::BitStreamMemoryStream bitstreamInput(bitstreamData.data(), bitstreamData.size());
+ Common::BitStreamMemory8MSB bitstream(&bitstreamInput);
uint prefixOffset = 0;
int lastDC = 0;
auto &format = _frame->format;
@@ -269,7 +271,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
int dc = prefixData[prefixOffset++];
if (dc >> 4)
error("dc run code");
- dc = bitstream.readInt(dc);
+ dc = FourXM::readInt(bitstream, dc);
dc = lastDC + dc * iquant[0];
lastDC = dc;
ac[0] = dc;
@@ -285,7 +287,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
idx += h;
if (l && idx < 64) {
auto ac_idx = zigzag[idx];
- ac[ac_idx] = iquant[ac_idx] * bitstream.readInt(l);
+ ac[ac_idx] = iquant[ac_idx] * FourXM::readInt(bitstream, l);
++idx;
}
}
@@ -379,7 +381,7 @@ void mcdc(uint16_t *dst, const uint16_t *src, int log2w,
} // namespace
-void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, FourXM::BEWordBitStream &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
+void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(uint16 *dst, const uint16 *src, int stride, int log2w, int log2h, Common::BitStreamMemory32LEMSB &bs, Common::MemoryReadStream &wordStream, Common::MemoryReadStream &byteStream) {
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
@@ -444,7 +446,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm(Common::SeekableReadStream *st
Common::Array<byte> byteStreamData(byteStreamSize);
stream->read(byteStreamData.data(), byteStreamData.size());
- FourXM::BEWordBitStream bitStream(reinterpret_cast<const uint32 *>(bitStreamData.data()), bitStreamData.size() / 4, 0);
+ Common::BitStreamMemoryStream bitStreamInput(bitStreamData.data(), bitStreamData.size());
+ Common::BitStreamMemory32LEMSB bitStream(&bitStreamInput);
Common::MemoryReadStream wordStream(wordStreamData.data(), wordStreamData.size());
Common::MemoryReadStream byteStream(byteStreamData.data(), byteStreamData.size());
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index ba52ee2bb13..ce22f05d0e3 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -20,7 +20,9 @@
*/
#include "video/4xm_utils.h"
+#include "common/bitstream.h"
#include "common/debug.h"
+#include "common/memstream.h"
namespace Video {
namespace FourXM {
@@ -29,7 +31,7 @@ template<typename BitStreamType>
uint HuffmanDecoder::next(BitStreamType &bs) {
uint value = _startEntry;
while (value >= _numCodes) {
- auto bit = bs.readBit();
+ auto bit = bs.template getBits<1>();
if (bit)
value = _table[value].trueIdx;
else
@@ -37,25 +39,24 @@ uint HuffmanDecoder::next(BitStreamType &bs) {
}
return value;
}
-template uint HuffmanDecoder::next(LEByteBitStream &bs);
-template uint HuffmanDecoder::next(BEByteBitStream &bs);
-template uint HuffmanDecoder::next(LEWordBitStream &bs);
-template uint HuffmanDecoder::next(BEWordBitStream &bs);
+template uint HuffmanDecoder::next(Common::BitStreamMemory8MSB &bs);
+template uint HuffmanDecoder::next(Common::BitStreamMemory32LEMSB &bs);
+template uint HuffmanDecoder::next(Common::BitStreamMemory32BEMSB &bs);
-template<typename Word>
+template<typename BitStream>
Common::Array<byte> HuffmanDecoder::unpackStream(const byte *huff, uint huffSize, uint &offset) {
Common::Array<byte> decoded;
decoded.reserve(huffSize * 2);
- assert((offset % sizeof(Word)) == 0);
- BitStream<Word, false> bs(reinterpret_cast<const Word *>(huff), huffSize / sizeof(Word), offset / sizeof(Word));
+ Common::BitStreamMemoryStream ms{huff, huffSize};
+ BitStream bs(&ms);
+ bs.skip(offset * 8);
while (true) {
auto value = next(bs);
if (value == 256)
break;
decoded.push_back(static_cast<byte>(value));
}
- bs.alignToWord();
- offset = bs.getWordPos() * sizeof(Word);
+ offset = bs.pos();
return decoded;
}
@@ -148,28 +149,23 @@ void HuffmanDecoder::buildTable(uint numCodes) {
Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize) {
Common::Array<byte> decoded;
+ offset = (offset + wordSize - 1) / wordSize * wordSize;
switch (wordSize) {
case 1:
- decoded = unpackStream<byte>(huff, huffSize, offset);
+ decoded = unpackStream<Common::BitStreamMemory8MSB>(huff, huffSize, offset);
break;
case 4:
- decoded = unpackStream<uint32>(huff, huffSize, offset);
+ decoded = unpackStream<Common::BitStreamMemory32LEMSB>(huff, huffSize, offset);
break;
default:
error("invalid word size");
}
- if (wordSize == 1) {
- assert(offset == huffSize); // must decode to the end
- }
return decoded;
}
Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte wordSize) {
HuffmanDecoder dec;
auto offset = dec.loadStatistics(huff, huffSize);
- if (wordSize > 1 && (offset % wordSize) != 0) {
- offset += wordSize - (offset % wordSize);
- }
return dec.unpack(huff, huffSize, offset, wordSize);
}
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 0e72b285e8d..b202694fafa 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -24,69 +24,6 @@
namespace Video {
namespace FourXM {
-template<typename Type, bool BigEndian>
-class BitStream {
- const Type *_data;
- uint _size;
- uint _wordPos;
- Type _bitMask;
- static constexpr Type InitialMask = Type(1) << (sizeof(Type) * 8 - 1);
- static constexpr uint WordSize = sizeof(Type);
-
-public:
- BitStream(const Type *data, uint size, uint wordPos) : _data(data), _size(size), _wordPos(wordPos), _bitMask(InitialMask) {}
-
- uint getWordPos() const {
- return _wordPos;
- }
-
- bool readBit() {
- assert(_wordPos < _size);
- bool bit = _data[_wordPos] & _bitMask;
- _bitMask >>= 1;
- if (_bitMask == 0) {
- _bitMask = InitialMask;
- ++_wordPos;
- }
- return bit;
- }
-
- int readUInt(byte n) {
- int value = 0;
- if (BigEndian) {
- for (int i = 0; i != n; ++i) {
- value <<= 1;
- if (readBit())
- value |= 1;
- }
- } else {
- for (int i = 0; i != n; ++i) {
- if (readBit())
- value |= 1 << i;
- }
- }
- return value;
- }
-
- int readInt(byte n) {
- int value = readUInt(n);
- if ((value & (1 << (n - 1))) == 0)
- value += 1 - (1 << n);
- return value;
- }
-
- void alignToWord() {
- if (_bitMask != InitialMask) {
- _bitMask = InitialMask;
- ++_wordPos;
- }
- }
-};
-using LEByteBitStream = BitStream<byte, false>;
-using BEByteBitStream = BitStream<byte, true>;
-using LEWordBitStream = BitStream<uint32, false>;
-using BEWordBitStream = BitStream<uint32, true>;
-
class HuffmanDecoder {
static constexpr uint kMaxTableSize = 514;
static constexpr uint kLastEntry = kMaxTableSize - 1;
@@ -122,5 +59,16 @@ private:
void idct(int16_t block[64], int shift = 6);
+inline int readInt(int value, unsigned n) {
+ if ((value & (1 << (n - 1))) == 0)
+ value += 1 - (1 << n);
+ return value;
+}
+
+template<typename BitStream>
+inline int readInt(BitStream &bs, size_t n) {
+ return readInt(bs.getBits(n), n);
+}
+
} // namespace FourXM
} // namespace Video
Commit: 749e902598ee3e4fef3101bc578d898118fce1aa
https://github.com/scummvm/scummvm/commit/749e902598ee3e4fef3101bc578d898118fce1aa
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:36+01:00
Commit Message:
PHOENIXVR: detect messenger (2000) game
Changed paths:
engines/phoenixvr/detection_tables.h
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index b7da018ac3e..475c37ebd1c 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -25,6 +25,7 @@ namespace PhoenixVR {
const PlainGameDescriptor phoenixvrGames[] = {
{"necrono", "Necronomicon: The Dawning of Darkness"},
{"cameronlochness", "Cameron Files: The Secret at Loch Ness"},
+ {"messenger", "The Messenger/Louvre: The Final Curse"},
{0, 0}};
const ADGameDescription gameDescriptions[] = {
@@ -48,6 +49,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_DROPPLATFORM | ADGF_UNSUPPORTED,
GUIO1(GUIO_NONE)},
+ {"messenger",
+ nullptr,
+ AD_ENTRY2s(
+ "script.pak", "1e0f9cb47bc203e9e2983b03ffa85174", 185,
+ "textes.txt", "23f577d1201bc3024ca49cb11f9f7347", 5261),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_DROPPLATFORM | ADGF_UNSUPPORTED,
+ GUIO1(GUIO_NONE)},
AD_TABLE_END_MARKER};
} // End of namespace PhoenixVR
Commit: c8b842d63e38b96abf03cbe8f6313511e4545982
https://github.com/scummvm/scummvm/commit/c8b842d63e38b96abf03cbe8f6313511e4545982
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:37+01:00
Commit Message:
PHOENIXVR: make variables.txt optional
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index d219ee0d9fe..aed5de46cdb 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -646,16 +646,16 @@ Common::Error PhoenixVREngine::run() {
_screenCenter = _screen->getBounds().center();
{
Common::File vars;
- if (!vars.open(Common::Path("variable.txt")))
- error("can't read variable.txt");
-
- while (!vars.eos()) {
- auto var = vars.readLine();
- if (var == "*")
- break;
- declareVariable(var);
- _variableOrder.push_back(Common::move(var));
- }
+ if (vars.open(Common::Path("variable.txt"))) {
+ while (!vars.eos()) {
+ auto var = vars.readLine();
+ if (var == "*")
+ break;
+ declareVariable(var);
+ _variableOrder.push_back(Common::move(var));
+ }
+ } else
+ debug("no variables.txt");
}
{
Common::File textes;
Commit: 61e438cb95aecd3b896bfced58bb262a67646c18
https://github.com/scummvm/scummvm/commit/61e438cb95aecd3b896bfced58bb262a67646c18
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:37+01:00
Commit Message:
PHOENIXVR: add enough plugin stubs to run messenger/cameron files
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 582576ce19e..0c4e045948c 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -84,6 +84,15 @@ struct Play_AnimBloc : public Script::Command {
}
};
+struct Stop_AnimBloc : public Script::Command {
+ Common::String name;
+
+ Stop_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("Stop_AnimBloc %s", name.c_str());
+ }
+};
+
struct Play_AnimBloc_Number : public Script::Command {
Common::String prefix, var;
Common::String dstVar;
@@ -183,6 +192,78 @@ struct Sub : public Script::Command {
}
};
+struct Not : public Script::Command {
+ Common::String var;
+
+ Not(Common::String v) : var(Common::move(v)) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ debug("not %s", var.c_str());
+ g_engine->setVariable(var, g_engine->getVariable(var) ? 0 : 1);
+ }
+};
+
+struct GetMonde4 : public Script::Command {
+ Common::String var;
+ Common::String negativeVar;
+
+ GetMonde4(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("GetMonde4 %s %s", var.c_str(), negativeVar.c_str());
+ g_engine->setVariable(var, 0);
+ g_engine->setVariable(negativeVar, 1);
+ }
+};
+
+struct SetMonde4 : public Script::Command {
+ int value;
+
+ SetMonde4(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("SetMonde4 %d", value);
+ }
+};
+
+struct AddObject : public Script::Command {
+ int object;
+ Common::String var;
+ Common::String negativeVar;
+
+ AddObject(const Common::Array<Common::String> &args) : object(atoi(args[0].c_str())), var(args[1]), negativeVar(args[2]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("AddObject %d %s %s", object, var.c_str(), negativeVar.c_str());
+ }
+};
+
+struct AddCoffreObject : public Script::Command {
+ int object;
+
+ AddCoffreObject(const Common::Array<Common::String> &args) : object(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("AddCoffreObject %d", object);
+ }
+};
+
+struct IsPresent : public Script::Command {
+ int object;
+ Common::String var;
+ Common::String negativeVar;
+
+ IsPresent(const Common::Array<Common::String> &args) : object(atoi(args[0].c_str())), var(args[1]), negativeVar(args[2]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("IsPresent %d %s %s", object, var.c_str(), negativeVar.c_str());
+ }
+};
+
+struct RemoveObject : public Script::Command {
+ int object;
+
+ RemoveObject(const Common::Array<Common::String> &args) : object(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("RemoveObject %d", object);
+ }
+};
+
struct Cmp : public Script::Command {
Common::String var;
Common::String negativeVar;
@@ -215,11 +296,103 @@ struct Cmp : public Script::Command {
}
};
+struct Select : public Script::Command {
+ int value;
+ Common::String arg0;
+ Common::String arg1;
+
+ Select(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())), arg0(args[1]), arg1(args[2]) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("Select %d %s %s", value, arg0.c_str(), arg1.c_str());
+ }
+};
+
+struct DoAction : public Script::Command {
+ int value;
+ Common::String arg;
+
+ DoAction(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())), arg(args[1]) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("DoAction %d %s", value, arg.c_str());
+ }
+};
+
+struct IsHere : public Script::Command {
+ IsHere(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("IsHere");
+ }
+};
+
+struct InitCoffre : public Script::Command {
+ InitCoffre(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("InitCoffre");
+ }
+};
+
+struct LoadCoffre : public Script::Command {
+ LoadCoffre(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("LoadCoffre");
+ }
+};
+
+struct SaveCoffre : public Script::Command {
+ SaveCoffre(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("SaveCoffre");
+ }
+};
+
+struct AfficheCoffre : public Script::Command {
+ AfficheCoffre(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("AfficheCoffre");
+ }
+};
+
+struct AfficheSelection : public Script::Command {
+ AfficheSelection(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("AfficheSelection");
+ }
+};
+
+struct AffichePorteF : public Script::Command {
+ int value;
+ AffichePorteF(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("AffichePorteF %d", value);
+ }
+};
+
+struct SelectPorteF : public Script::Command {
+ int value;
+ Common::String var;
+ SelectPorteF(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())), var(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("SelectPorteF %d %s", value, var.c_str());
+ }
+};
+
+struct SelectCoffre : public Script::Command {
+ int value;
+ Common::String var;
+ SelectCoffre(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())), var(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("SelectCoffre %d %s", value, var.c_str());
+ }
+};
+
struct LoadSave_Init_Slots : public Script::Command {
int slots;
LoadSave_Init_Slots(const Common::Array<Common::String> &args) : slots(atoi(args[0].c_str())) {}
void exec(Script::ExecutionContext &ctx) const override {
+ debug("LoadSave_Init_Slots %d", slots);
}
};
@@ -306,6 +479,51 @@ struct LoadSave_Set_Context_Label : public Script::Command {
}
};
+struct Discocier : public Script::Command {
+ Discocier(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("Discocier");
+ }
+};
+
+struct Reset : public Script::Command {
+ Reset(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("Reset");
+ }
+};
+
+struct MemoryRelease : public Script::Command {
+ MemoryRelease(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("MemoryRelease");
+ }
+};
+
+struct DrawTextSelection : public Script::Command {
+ DrawTextSelection(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("DrawTextSelection");
+ }
+};
+
+struct CarteDestination : public Script::Command {
+ Common::String varX;
+ Common::String varY;
+ CarteDestination(const Common::Array<Common::String> &args) : varX(args[0]), varY(args[1]) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("CarteDestination %s %s", varX.c_str(), varY.c_str());
+ }
+};
+
+struct Scroll : public Script::Command {
+ int value;
+ Scroll(const Common::Array<Common::String> &args) : value(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("Scroll");
+ }
+};
+
struct Rollover : public Script::Command {
int arg;
@@ -333,6 +551,15 @@ struct RolloverSecretaire : public Script::Command {
}
};
+struct PorteFRollover : public Script::Command {
+ int arg;
+
+ PorteFRollover(const Common::Array<Common::String> &args) : arg(atoi(args[0].c_str())) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("PorteFRollover %d", arg);
+ }
+};
+
struct SaveVariable : public Script::Command {
SaveVariable(const Common::Array<Common::String> &args) {}
void exec(Script::ExecutionContext &ctx) const override {
@@ -347,11 +574,36 @@ struct LoadVariable : public Script::Command {
}
};
+struct End : public Script::Command {
+ End(const Common::Array<Common::String> &args) {}
+ void exec(Script::ExecutionContext &ctx) const override {
+ g_engine->quitGame();
+ }
+};
+
#define PLUGIN_LIST(E) \
E(Add) \
+ E(AddCoffreObject) \
+ E(AddObject) \
+ E(AfficheCoffre) \
+ E(AffichePorteF) \
+ E(AfficheSelection) \
+ E(CarteDestination) \
E(ChangeCurseur) \
E(Cmp) \
+ E(Discocier) \
+ E(DrawTextSelection) \
+ E(End) \
+ E(GetMonde4) \
+ E(SetMonde4) \
+ E(IsPresent) \
E(KillTimer) \
+ E(InitCoffre) \
+ E(IsHere) \
+ E(LoadCoffre) \
+ E(SaveCoffre) \
+ E(SelectPorteF) \
+ E(SelectCoffre) \
E(LoadSave_Capture_Context) \
E(LoadSave_Context_Restored) \
E(LoadSave_Enter_Script) \
@@ -362,16 +614,24 @@ struct LoadVariable : public Script::Command {
E(LoadSave_Draw_Slot) \
E(LoadSave_Test_Slot) \
E(LoadVariable) \
+ E(MemoryRelease) \
E(MultiCD_Set_Transition_Script) \
E(MultiCD_Set_Next_Script) \
E(PauseTimer) \
E(Play_AnimBloc) \
E(Play_AnimBloc_Number) \
E(Play_Movie) \
+ E(Reset) \
+ E(RemoveObject) \
E(Rollover) \
E(RolloverMalette) \
E(RolloverSecretaire) \
+ E(PorteFRollover) \
E(SaveVariable) \
+ E(Select) \
+ E(Scroll) \
+ E(Stop_AnimBloc) \
+ E(DoAction) \
E(StartTimer) \
E(Sub) \
E(Until) \
@@ -382,9 +642,9 @@ struct LoadVariable : public Script::Command {
if (cmd.equalsIgnoreCase(#NAME)) \
return Script::CommandPtr(new NAME(args));
-Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args) {
+Script::CommandPtr createCommand(const Common::String &cmd, const Common::Array<Common::String> &args, int lineno) {
PLUGIN_LIST(ADD_PLUGIN)
- error("unhandled plugin command %s", cmd.c_str());
+ error("unhandled plugin command %s at line %d", cmd.c_str(), lineno);
}
struct IfAnd : public Script::Conditional {
@@ -447,8 +707,8 @@ struct GoSub : public Script::Command {
}
};
-struct End : public Script::Command {
- End() {}
+struct EndScript : public Script::Command {
+ EndScript() {}
void exec(Script::ExecutionContext &ctx) const override {
debug("end");
ctx.running = false;
@@ -554,6 +814,16 @@ struct SetAngle : public Script::Command {
}
};
+struct InterpolAngle : public Script::Command {
+ float x, y;
+ int unk;
+ InterpolAngle(float x_, float y_, int u) : x(x_), y(y_), unk(u) {}
+
+ void exec(Script::ExecutionContext &ctx) const override {
+ warning("interpolangle %g,%g %d", x, y, unk);
+ }
+};
+
struct GoToWarp : public Script::Command {
Common::String warp;
GoToWarp(Common::String w) : warp(Common::move(w)) {}
@@ -576,6 +846,10 @@ struct PlaySound : public Script::Command {
}
};
+struct PlayMusique : public PlaySound {
+ PlayMusique(Common::String s, int v) : PlaySound(Common::move(s), v, -1) {}
+};
+
struct StopSound : public Script::Command {
Common::String sound;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index d256febb658..20f5d87234a 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -159,7 +159,7 @@ public:
return CommandPtr(new Fade(arg0, arg1, arg2));
} else if (maybe("setzoom=")) {
return CommandPtr(new SetZoom(nextInt()));
- } else if (maybe("setangle=")) {
+ } else if (maybe("setangle=") || keyword("setangle")) {
auto i0 = nextInt();
if (i0 > 4095)
i0 -= 8192;
@@ -167,6 +167,16 @@ public:
expect(',');
auto a1 = toAngle(nextInt());
return CommandPtr(new SetAngle(a0, a1));
+ } else if (keyword("interpolangle")) {
+ auto i0 = nextInt();
+ if (i0 > 4095)
+ i0 -= 8192;
+ auto a0 = toAngle(i0);
+ expect(',');
+ auto a1 = toAngle(nextInt());
+ expect(',');
+ int unk = nextInt();
+ return CommandPtr(new InterpolAngle(a0, a1, unk));
} else if (maybe("anglexmax=")) {
return CommandPtr(new AngleXMax(toAngle(nextInt())));
} else if (maybe("angleymax=")) {
@@ -185,6 +195,11 @@ public:
expect(',');
auto arg2 = nextInt();
return CommandPtr(new PlaySound3D(Common::move(sound), arg0, toAngle(arg1), arg2));
+ } else if (keyword("playmusique")) {
+ auto sound = nextWord();
+ expect(',');
+ auto arg0 = nextInt();
+ return CommandPtr(new PlayMusique(Common::move(sound), arg0));
} else if (keyword("playsound")) {
auto sound = nextWord();
expect(',');
@@ -194,7 +209,7 @@ public:
return CommandPtr(new PlaySound(Common::move(sound), arg0, arg1));
} else if (keyword("stopsound3d")) {
return CommandPtr(new StopSound3D(nextWord()));
- } else if (keyword("stopsound")) {
+ } else if (keyword("stopsound") || keyword("stopmusique")) {
return CommandPtr(new StopSound(nextWord()));
} else if (keyword("setcursor")) {
auto image = nextWord();
@@ -213,12 +228,15 @@ public:
expect('=');
auto value = nextInt();
return CommandPtr(new Set(Common::move(var), value));
+ } else if (keyword("not")) {
+ auto var = nextWord();
+ return CommandPtr(new Not(Common::move(var)));
} else if (keyword("gosub")) {
return CommandPtr(new GoSub(nextWord()));
} else if (keyword("return")) {
return CommandPtr{new Return()};
} else if (keyword("end")) {
- return CommandPtr{new End()};
+ return CommandPtr{new EndScript()};
}
return {};
};
@@ -331,11 +349,11 @@ void Script::parseLine(const Common::String &line, uint lineno) {
p.expect('(');
auto args = p.readStringList();
p.expect(')');
- auto cmd = createCommand(name, args);
+ auto cmd = createCommand(name, args, lineno);
if (cmd)
_pluginScope->commands.push_back(Common::move(cmd));
else
- error("unhandled plugin command %s", line.c_str());
+ error("unhandled plugin command %s at line %d", line.c_str(), lineno);
} else {
auto cmd = p.parseCommand();
if (cmd) {
@@ -346,11 +364,11 @@ void Script::parseLine(const Common::String &line, uint lineno) {
} else
commands.push_back(Common::move(cmd));
} else
- error("unhandled script command %s", line.c_str());
+ error("unhandled script command %s at line %d", line.c_str(), lineno);
}
}
} else
- error("invalid directive on line %u: %s", lineno, line.c_str());
+ error("invalid directive at line %u: %s", lineno, line.c_str());
}
}
Commit: da1d5591315fda8cd5ebd366bca3ba6231c58805
https://github.com/scummvm/scummvm/commit/da1d5591315fda8cd5ebd366bca3ba6231c58805
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:37+01:00
Commit Message:
PHOENIXVR: rename cameronlochness to lochness
Changed paths:
engines/phoenixvr/detection_tables.h
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index 475c37ebd1c..1f591498a02 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -39,7 +39,7 @@ const ADGameDescription gameDescriptions[] = {
ADGF_DROPPLATFORM,
GUIO1(GUIO_NONE)},
- {"cameronlochness",
+ {"lochness",
nullptr,
AD_ENTRY2s(
"script.pak", "a7ee3aae653658f93bba7f237bcf06f3", 1904,
Commit: e29b9f8f4968be2baa09c9a51f849f10462a69aa
https://github.com/scummvm/scummvm/commit/e29b9f8f4968be2baa09c9a51f849f10462a69aa
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:37+01:00
Commit Message:
PHOENIXVR: remove searchman hack, add ::resolve to handle paths
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/region_set.cpp
engines/phoenixvr/region_set.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index aed5de46cdb..b6194f2e46e 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -93,6 +93,12 @@ Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
return path.substr(2);
}
+Common::Path PhoenixVREngine::resolve(const Common::String &name) {
+ auto p = _currentScriptPath.append(name, '\\').normalize();
+ debug("resolved %s to %s", name.c_str(), p.toString().c_str());
+ return p;
+}
+
void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
debug("setNextScript %s", nextScript.c_str());
_contextScript = nextScript;
@@ -103,22 +109,18 @@ void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
}
auto nextPath = Common::Path(removeDrive(nextScript), '\\');
- auto parentDir = nextPath.getParent();
- _nextScript = nextPath.getLastComponent();
- auto path = ConfMan.getPath("path");
-
- SearchMan.clear();
- debug("adding %s ~ %s to search man", path.toString().c_str(), parentDir.toString().c_str());
- SearchMan.addSubDirectoryMatching(Common::FSNode{path}, parentDir.toString(), true, 2, false);
+ _currentScriptPath = nextPath.getParent();
+ debug("changed script directory to %s", _currentScriptPath.toString().c_str());
+ _nextScript = nextPath.getLastComponent().toString();
}
void PhoenixVREngine::loadNextScript() {
- debug("loading script from %s", _nextScript.toString().c_str());
+ debug("loading script from %s", _nextScript.c_str());
auto nextScript = Common::move(_nextScript);
_nextScript.clear();
Common::File file;
- const Common::Path &nextPath(nextScript);
+ auto nextPath = resolve(nextScript);
if (file.open(nextPath)) {
_script.reset(new Script(file));
} else {
@@ -126,7 +128,7 @@ void PhoenixVREngine::loadNextScript() {
pakFile = pakFile.removeExtension().append(".pak");
file.open(pakFile);
if (!file.isOpen())
- error("can't open script file %s", nextScript.toString().c_str());
+ error("can't open script file %s", nextScript.c_str());
Common::ScopedPtr<Common::SeekableReadStream> scriptStream(unpack(file));
_script.reset(new Script(*scriptStream));
}
@@ -274,7 +276,7 @@ void PhoenixVREngine::playSound(const Common::String &sound, uint8 volume, int l
debug("play sound %s %d %d 3d: %d, angle: %g", sound.c_str(), volume, loops, spatial, angle);
Audio::SoundHandle h;
Common::ScopedPtr<Common::File> f(new Common::File());
- if (!f->open(Common::Path(sound))) {
+ if (!f->open(resolve(sound))) {
warning("sound %s couldn't be found", sound.c_str());
return;
}
@@ -298,7 +300,7 @@ void PhoenixVREngine::playMovie(const Common::String &movie) {
debug("playMovie %s", movie.c_str());
Video::FourXMDecoder dec;
- if (dec.loadFile(Common::Path{movie})) {
+ if (dec.loadFile(resolve(movie))) {
dec.start();
bool playing = true;
@@ -349,7 +351,7 @@ void PhoenixVREngine::lockKey(int idx, const Common::String &warp) {
Graphics::Surface *PhoenixVREngine::loadSurface(const Common::String &path) {
Common::File file;
- if (!file.open(Common::Path(path))) {
+ if (!file.open(resolve(path))) {
warning("can't find %s", path.c_str());
return nullptr;
}
@@ -567,7 +569,7 @@ void PhoenixVREngine::tick(float dt) {
_nextWarp = -1;
Common::File vr;
- if (vr.open(Common::Path(_warp->vrFile))) {
+ if (vr.open(resolve(_warp->vrFile))) {
_vr = VR::loadStatic(_pixelFormat, vr);
if (_vr.isVR()) {
_mousePos = _screenCenter;
@@ -576,7 +578,7 @@ void PhoenixVREngine::tick(float dt) {
_system->lockMouse(_vr.isVR());
}
- _regSet.reset(new RegionSet(_warp->testFile));
+ _regSet.reset(new RegionSet(resolve(_warp->testFile)));
Script::ExecutionContext ctx;
debug("execute warp script %s", _warp->vrFile.c_str());
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index cac6da7320a..6fb9234f368 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -169,6 +169,8 @@ public:
private:
static Common::String removeDrive(const Common::String &path);
+ Common::Path resolve(const Common::String &name);
+
Graphics::Surface *loadSurface(const Common::String &path);
Graphics::Surface *loadCursor(const Common::String &path);
void paint(Graphics::Surface &src, Common::Point dst);
@@ -182,7 +184,8 @@ private:
private:
Common::Point _mousePos, _mouseRel;
- Common::Path _nextScript;
+ Common::String _nextScript;
+ Common::Path _currentScriptPath;
int _warpIdx = -1;
Script::ConstWarpPtr _warp;
int _nextWarp = -1;
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index 0dc1de0b1cd..f104024041f 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -24,10 +24,10 @@
#include "common/file.h"
namespace PhoenixVR {
-RegionSet::RegionSet(const Common::String &fname) {
+RegionSet::RegionSet(const Common::Path &fname) {
Common::File file;
- if (!file.open(Common::Path(fname))) {
- debug("can't find region %s", fname.c_str());
+ if (!file.open(fname)) {
+ debug("can't find region %s", fname.toString().c_str());
return;
}
auto n = file.readUint32LE();
diff --git a/engines/phoenixvr/region_set.h b/engines/phoenixvr/region_set.h
index dbadb78fe71..f0a61134396 100644
--- a/engines/phoenixvr/region_set.h
+++ b/engines/phoenixvr/region_set.h
@@ -23,6 +23,7 @@
#define PHOENIXVR_REGIONS_H
#include "common/array.h"
+#include "common/path.h"
#include "phoenixvr/rectf.h"
namespace Common {
@@ -48,7 +49,7 @@ class RegionSet {
Common::Array<Region> _regions;
public:
- RegionSet(const Common::String &fname);
+ RegionSet(const Common::Path &fname);
uint size() const { return _regions.size(); }
const Common::Array<Region> &getRegions() const { return _regions; }
const Region &getRegion(uint idx) const {
Commit: aae2671a9bc25cf539b61caf7b7ff43006010e8d
https://github.com/scummvm/scummvm/commit/aae2671a9bc25cf539b61caf7b7ff43006010e8d
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:38+01:00
Commit Message:
COMMON: HUFFMAN: add fromFrequencies()
Changed paths:
common/compression/huffman.h
test/common/compression/huffman.h
diff --git a/common/compression/huffman.h b/common/compression/huffman.h
index da38d0b49bf..d0548d2a95e 100644
--- a/common/compression/huffman.h
+++ b/common/compression/huffman.h
@@ -26,6 +26,7 @@
#include "common/array.h"
#include "common/list.h"
+#include "common/queue.h"
#include "common/types.h"
namespace Common {
@@ -48,7 +49,7 @@ inline uint32 REVERSEBITS(uint32 x) {
x = (((x & ~0x0F0F0F0F) >> 4) | ((x & 0x0F0F0F0F) << 4));
x = (((x & ~0x00FF00FF) >> 8) | ((x & 0x00FF00FF) << 8));
- return((x >> 16) | (x << 16));
+ return ((x >> 16) | (x << 16));
}
/**
@@ -68,6 +69,21 @@ public:
*/
Huffman(uint8 maxLength, uint32 codeCount, const uint32 *codes, const uint8 *lengths, const uint32 *symbols = nullptr);
+ /** Construct Huffman decoder from the symbol frequencies using canonical huffman algorithm.
+ *
+ * @param freqCount Number of frequencies.
+ * @param freq Frequencies.
+ * @param symbols Symbols.
+ */
+ static Huffman fromFrequencies(uint32 freqCount, const uint32 *freq, const uint32 *symbols);
+
+ /** Construct Huffman decoder from the symbol frequencies using canonical huffman algorithm.
+ * Symbols added for each non-zero frequency in the list
+ *
+ * @param freqs Frequencies,
+ */
+ static Huffman fromFrequencies(std::initializer_list<uint32> freqs);
+
/** Return the next symbol in the bit stream. */
uint32 getSymbol(BITSTREAM &bits) const;
@@ -88,7 +104,7 @@ private:
/** Prefix lookup table used to speed up the decoding of short codes. */
struct PrefixEntry {
uint32 symbol;
- uint8 length;
+ uint8 length;
PrefixEntry() : length(0xFF) {}
};
@@ -97,7 +113,92 @@ private:
PrefixEntry _prefixTable[1 << _prefixTableBits];
};
-template <class BITSTREAM>
+template<class BITSTREAM>
+Huffman<BITSTREAM> Huffman<BITSTREAM>::fromFrequencies(std::initializer_list<uint32> init) {
+ Common::Array<uint32> freqs;
+ Common::Array<uint32> symbols;
+ uint32 sym = 0;
+ for (auto freq : init) {
+ if (freq != 0) {
+ freqs.push_back(freq);
+ symbols.push_back(sym);
+ }
+ ++sym;
+ }
+ return fromFrequencies(freqs.size(), freqs.data(), symbols.data());
+}
+
+template<class BITSTREAM>
+Huffman<BITSTREAM> Huffman<BITSTREAM>::fromFrequencies(uint32 freqCount, const uint32 *freq, const uint32 *symbols) {
+ assert(freqCount > 0);
+ assert(freq);
+ assert(symbols);
+
+ Common::Array<uint32> codes(freqCount, 0);
+ Common::Array<uint8> lengths(freqCount, 0);
+
+ static constexpr uint32 End = ~uint32(0);
+ struct Symbol {
+ uint32 zero, one;
+ uint32 freq;
+ };
+
+ Common::Array<Symbol> syms;
+ for (uint32_t i = 0; i != freqCount; ++i)
+ syms.push_back(Symbol{End, End, freq[i]});
+
+ auto appendBit = [&](uint32 top, bool bit) {
+ Common::Queue<uint32> queue;
+ queue.push(top);
+ while (!queue.empty()) {
+ auto idx = queue.front();
+ queue.pop();
+ if (idx < freqCount) {
+ auto &len = lengths[idx];
+ if (bit)
+ codes[idx] |= (1 << len);
+ ++len;
+ } else {
+ assert(syms[idx].zero != End);
+ queue.push(syms[idx].zero);
+ assert(syms[idx].one != End);
+ queue.push(syms[idx].one);
+ }
+ }
+ };
+
+ while (true) {
+ uint32 smallest1 = End, smallest2 = End;
+ for (uint32 idx = 0; idx != syms.size(); ++idx) {
+ auto &sym = syms[idx];
+ if (sym.freq != 0) {
+ if (smallest1 != End && sym.freq >= syms[smallest1].freq) {
+ if (smallest2 == End || sym.freq < syms[smallest2].freq) {
+ smallest2 = idx;
+ }
+ } else {
+ smallest2 = smallest1;
+ smallest1 = idx;
+ }
+ }
+ }
+ if (smallest2 == End)
+ break;
+
+ auto &zero = syms[smallest1];
+ auto &one = syms[smallest2];
+ auto sum = zero.freq + one.freq;
+ zero.freq = 0;
+ one.freq = 0;
+ syms.push_back(Symbol{smallest1, smallest2, sum});
+ appendBit(smallest1, false);
+ appendBit(smallest2, true);
+ }
+
+ return Huffman<BITSTREAM>{0, freqCount, codes.data(), lengths.data(), symbols};
+}
+
+template<class BITSTREAM>
Huffman<BITSTREAM>::Huffman(uint8 maxLength, uint32 codeCount, const uint32 *codes, const uint8 *lengths, const uint32 *symbols) {
assert(codeCount > 0);
@@ -144,7 +245,7 @@ Huffman<BITSTREAM>::Huffman(uint8 maxLength, uint32 codeCount, const uint32 *cod
}
}
-template <class BITSTREAM>
+template<class BITSTREAM>
uint32 Huffman<BITSTREAM>::getSymbol(BITSTREAM &bits) const {
uint32 code = bits.peekBits(_prefixTableBits);
diff --git a/test/common/compression/huffman.h b/test/common/compression/huffman.h
index 1ebaa1f44fd..2faeede114e 100644
--- a/test/common/compression/huffman.h
+++ b/test/common/compression/huffman.h
@@ -1,16 +1,16 @@
-#include <cxxtest/TestSuite.h>
-#include "common/compression/huffman.h"
#include "common/bitstream.h"
+#include "common/compression/huffman.h"
#include "common/memstream.h"
+#include <cxxtest/TestSuite.h>
/**
-* A test suite for the Huffman decoder in common/compression/huffman.h
-* The encoding used comes from the example on the Wikipedia page
-* for Huffman.
-* TODO: It could be improved by generating one at runtime.
-*/
+ * A test suite for the Huffman decoder in common/compression/huffman.h
+ * The encoding used comes from the example on the Wikipedia page
+ * for Huffman.
+ * TODO: It could be improved by generating one at runtime.
+ */
class HuffmanTestSuite : public CxxTest::TestSuite {
- public:
+public:
void test_get_with_full_symbols() {
/*
@@ -28,9 +28,9 @@ class HuffmanTestSuite : public CxxTest::TestSuite {
uint32 codeCount = 5;
uint8 maxLength = 3;
- const uint8 lengths[] = {3,3,2,2,2};
- const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
- const uint32 symbols[] = {0xA, 0xB, 0xC, 0xD, 0xE};
+ const uint8 lengths[] = {3, 3, 2, 2, 2};
+ const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
+ const uint32 symbols[] = {0xA, 0xB, 0xC, 0xD, 0xE};
Common::Huffman<Common::BitStream8MSB> h(maxLength, codeCount, codes, lengths, symbols);
@@ -75,13 +75,13 @@ class HuffmanTestSuite : public CxxTest::TestSuite {
*/
uint32 codeCount = 5;
- const uint8 lengths[] = {3,3,2,2,2};
- const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
+ const uint8 lengths[] = {3, 3, 2, 2, 2};
+ const uint32 codes[] = {0x2, 0x3, 0x3, 0x0, 0x2};
Common::Huffman<Common::BitStream8MSB> h(0, codeCount, codes, lengths, 0);
byte input[] = {0x4F, 0x20};
- uint32 expected[] = {0, 1, 2, 3, 4, 3 ,3};
+ uint32 expected[] = {0, 1, 2, 3, 4, 3, 3};
Common::MemoryReadStream ms(input, sizeof(input));
Common::BitStream8MSB bs(ms);
@@ -94,4 +94,21 @@ class HuffmanTestSuite : public CxxTest::TestSuite {
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[5]);
TS_ASSERT_EQUALS(h.getSymbol(bs), expected[6]);
}
+
+ void test_get_with_freqs() {
+ const uint32 freqs[] = {8, 4, 2, 1, 1};
+ const uint32 symbols[] = {0, 2, 3, 4, 5};
+ auto h = Common::Huffman<Common::BitStream8MSB>::fromFrequencies(5, freqs, symbols);
+
+ byte input[] = {0x5b, 0xbc};
+ Common::MemoryReadStream ms(input, sizeof(input));
+ Common::BitStream8MSB bs(ms);
+
+ uint32 expected[] = {0, 2, 3, 4, 5};
+ TS_ASSERT_EQUALS(h.getSymbol(bs), expected[0]);
+ TS_ASSERT_EQUALS(h.getSymbol(bs), expected[1]);
+ TS_ASSERT_EQUALS(h.getSymbol(bs), expected[2]);
+ TS_ASSERT_EQUALS(h.getSymbol(bs), expected[3]);
+ TS_ASSERT_EQUALS(h.getSymbol(bs), expected[4]);
+ }
};
Commit: d81ac175f72a35c26b9df295f93851b785b6f0d3
https://github.com/scummvm/scummvm/commit/d81ac175f72a35c26b9df295f93851b785b6f0d3
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:38+01:00
Commit Message:
PHOENIXVR: replace M_PI with float constants
Changed paths:
A engines/phoenixvr/math.h
engines/phoenixvr/angle.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
engines/phoenixvr/region_set.cpp
engines/phoenixvr/script.h
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/angle.h b/engines/phoenixvr/angle.h
index 10078d1ecc1..b29ff8e237f 100644
--- a/engines/phoenixvr/angle.h
+++ b/engines/phoenixvr/angle.h
@@ -23,7 +23,7 @@
#define PHOENIXVR_ANGLE_H
#include "math/utils.h"
-#include <math.h>
+#include "phoenixvr/math.h"
namespace Common {
class String;
@@ -86,11 +86,11 @@ public:
}
};
struct AngleX : Angle {
- AngleX(float angle) : Angle(angle, 0, 2 * M_PI) {}
+ AngleX(float angle) : Angle(angle, 0, kTau) {}
};
struct AngleY : Angle {
- AngleY(float angle) : Angle(angle, -M_PI, -Math::epsilon) {}
+ AngleY(float angle) : Angle(angle, -kPi, -Math::epsilon) {}
void add(float v) {
v += angle();
if (v <= _min)
diff --git a/engines/phoenixvr/math.h b/engines/phoenixvr/math.h
new file mode 100644
index 00000000000..4b0347e0627
--- /dev/null
+++ b/engines/phoenixvr/math.h
@@ -0,0 +1,33 @@
+/* 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/>.
+ *
+ */
+
+#ifndef PHOENIXVR_MATH_H
+#define PHOENIXVR_MATH_H
+
+#include <math.h>
+
+namespace PhoenixVR {
+static constexpr auto kTau = static_cast<float>(M_PI * 2);
+static constexpr auto kPi = static_cast<float>(M_PI);
+static constexpr auto kPi2 = static_cast<float>(M_PI_2);
+} // namespace PhoenixVR
+
+#endif
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index b6194f2e46e..d50c7745e17 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -39,6 +39,7 @@
#include "image/pcx.h"
#include "phoenixvr/console.h"
#include "phoenixvr/game_state.h"
+#include "phoenixvr/math.h"
#include "phoenixvr/pakf.h"
#include "phoenixvr/region_set.h"
#include "phoenixvr/script.h"
@@ -55,9 +56,9 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
_rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0),
_thumbnail(139, 103, _rgb565),
_lockKey(13),
- _fov(M_PI_2),
- _angleX(M_PI),
- _angleY(-M_PI_2),
+ _fov(kPi2),
+ _angleX(kPi),
+ _angleY(-kPi2),
_mixer(syst->getMixer()) {
g_engine = this;
for (auto format : g_system->getSupportedFormats()) {
@@ -832,11 +833,11 @@ void PhoenixVREngine::captureContext() {
ms.writeByte(0);
};
- ms.writeSint32LE(fromAngle(_angleY.angle() + M_PI_2));
+ ms.writeSint32LE(fromAngle(_angleY.angle() + kPi2));
ms.writeSint32LE(fromAngle(_angleX.angle()));
ms.writeSint32LE(0);
ms.writeSint32LE(0);
- ms.writeSint32LE(fromAngle(_angleY.rangeMax() + M_PI_2));
+ ms.writeSint32LE(fromAngle(_angleY.rangeMax() + kPi2));
ms.writeSint32LE(fromAngle(_angleX.rangeMin()));
ms.writeSint32LE(fromAngle(_angleX.rangeMax()));
ms.writeSint32LE(_warpIdx);
@@ -988,7 +989,7 @@ Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *slot)
auto flags = ms.readUint32LE();
debug("3d sound: %s vol: %u flags: %u angle: %u", name.c_str(), vol, flags, angle);
if (!name.empty())
- playSound(name, vol, -1, true, float(angle * M_PI));
+ playSound(name, vol, -1, true, static_cast<float>(angle) * kPi);
}
_loading = true;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 6fb9234f368..c598b281966 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -129,11 +129,11 @@ public:
void killTimer();
void playAnimation(const Common::String &name, const Common::String &var, int varValue, float speed);
void setZoom(int fov) {
- _fov = M_PI * fov / 180;
+ _fov = kPi * fov / 180;
}
void setXMax(float max) {
- static const float baseX = -M_PI_2;
+ static const float baseX = -kPi2;
_angleY.setRange(baseX - max, baseX + max);
}
@@ -144,7 +144,7 @@ public:
void setAngle(float x, float y) {
_angleX.set(y);
- static const float baseX = -M_PI_2;
+ static const float baseX = -kPi2;
_angleY.set(baseX + x);
}
diff --git a/engines/phoenixvr/region_set.cpp b/engines/phoenixvr/region_set.cpp
index f104024041f..5210cf229b3 100644
--- a/engines/phoenixvr/region_set.cpp
+++ b/engines/phoenixvr/region_set.cpp
@@ -22,6 +22,7 @@
#include "phoenixvr/region_set.h"
#include "common/debug.h"
#include "common/file.h"
+#include "phoenixvr/math.h"
namespace PhoenixVR {
RegionSet::RegionSet(const Common::Path &fname) {
@@ -55,25 +56,24 @@ RectF Region::toRect() const {
}
bool Region::contains3D(float angleX, float angleY) const {
- static const float kPI2 = 2 * M_PI;
float x0 = a, x1 = b;
float y0 = c, y1 = d;
- if (x1 - x0 > M_PI) {
- float t = x0 + kPI2;
+ if (x1 - x0 > kPi) {
+ float t = x0 + kTau;
x0 = x1;
x1 = t;
}
- if (y1 - y0 > M_PI) {
- float t = y0 + kPI2;
+ if (y1 - y0 > kPi) {
+ float t = y0 + kTau;
y0 = y1;
y1 = t;
}
- float ax_pi2 = angleX + kPI2;
+ float ax_pi2 = angleX + kTau;
if ((angleX >= x0 && angleX <= x1) || (ax_pi2 >= x0 && ax_pi2 <= x1)) {
if (angleY >= y0 && angleY <= y1)
return true;
- float ay_pi2 = angleY + kPI2;
+ float ay_pi2 = angleY + kTau;
if (ay_pi2 < y0)
return false;
if (ay_pi2 <= y1)
diff --git a/engines/phoenixvr/script.h b/engines/phoenixvr/script.h
index 90377729604..7c23791316a 100644
--- a/engines/phoenixvr/script.h
+++ b/engines/phoenixvr/script.h
@@ -26,6 +26,7 @@
#include "common/hashmap.h"
#include "common/ptr.h"
#include "common/str.h"
+#include "phoenixvr/math.h"
namespace Common {
class SeekableReadStream;
@@ -34,13 +35,13 @@ class SeekableReadStream;
namespace PhoenixVR {
namespace {
inline float toAngle(int a) {
- static const float angleToFloat = M_PI / 4096.0f;
- return angleToFloat * a;
+ static const float angleToFloat = kPi / 4096.0f;
+ return angleToFloat * static_cast<float>(a);
}
inline int fromAngle(float a) {
- static const float floatToAngle = 4096.0f / M_PI;
- return floatToAngle * a;
+ static const float floatToAngle = 4096.0f / kPi;
+ return static_cast<int>(floatToAngle * a);
}
} // namespace
class Script {
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 8effdc28b6f..621f47fddff 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -411,7 +411,6 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
Vector3d up = Vector3d::crossProduct(forward, right); // already normalized
// camera projection
- static constexpr float kPi2 = M_PI * 2;
float gx = tanf(fov / 2.0f), gy = gx * h / w;
Vector3d incrementX = right * (2 * gx / w);
Vector3d incrementY = up * (2 * gy / h);
@@ -423,7 +422,7 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
regDX = fov / w;
regDY = fov / h;
if (regY < 0)
- regY += M_PI * 2;
+ regY += kTau;
}
for (int dstY = 0; dstY != h; ++dstY, line += incrementY) {
if (regSet) {
@@ -450,7 +449,7 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
if (regX >= kPi2)
regX -= kPi2;
for (auto ® : regSet->getRegions()) {
- if (reg.contains3D(regX, M_PI * 2 - regY)) {
+ if (reg.contains3D(regX, kTau - regY)) {
byte r, g, b;
_pic->format.colorToRGB(color, r, g, b);
static constexpr int kGlow = 15;
Commit: f244bae56047cb5e3bb321c0671d1e4d7218c0ff
https://github.com/scummvm/scummvm/commit/f244bae56047cb5e3bb321c0671d1e4d7218c0ff
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:38+01:00
Commit Message:
PHOENIXVR: use Common::Huffman
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_decoder.cpp
video/4xm_utils.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 621f47fddff..dcb438178e2 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -104,7 +104,11 @@ struct Quantisation {
void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte *acPtr, uint acSize, const byte *dcPtr, uint dcSize, int quality, const Common::Array<uint> *prefix = nullptr) {
Quantisation quant(quality);
- auto decoded = Video::FourXM::HuffmanDecoder::unpack(huff, huffSize, 1);
+ uint huffOffset = 0;
+ auto huffDecoder = Video::FourXM::loadStatistics<Common::Huffman<Common::BitStreamMemory8MSB>>(huff, huffOffset);
+ Common::BitStreamMemoryStream huffMs(huff + huffOffset, huffSize - huffOffset);
+ Common::BitStreamMemory8MSB huffBs(&huffMs);
+ auto decoded = Video::FourXM::unpackHuffman(huffDecoder, huffBs);
uint decodedOffset = 0;
Common::BitStreamMemoryStream acMs(acPtr, acSize);
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 5941fe134ea..712bddc11d4 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -97,7 +97,9 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
uint16 _version = 0;
Common::ScopedPtr<Graphics::ManagedSurface> _framePtr, _lastFramePtr;
Graphics::Surface *_frame = nullptr, *_lastFrame = nullptr;
- FourXM::HuffmanDecoder _blockType[4] = {};
+ using HuffmanBitStream = Common::BitStreamMemory32LEMSB;
+ using HuffmanType = Common::Huffman<HuffmanBitStream>;
+ Common::ScopedPtr<HuffmanType> _blockType[4];
int _mv[256];
Common::HashMap<byte, Common::Array<byte>> _cframes;
@@ -105,10 +107,10 @@ public:
FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version) {
if (_version <= 1)
error("versions 0 and 1 are not supported");
- _blockType[0].initStatistics({16, 8, 4, 2, 1, 1});
- _blockType[1].initStatistics({8, 0, 4, 2, 1, 1});
- _blockType[2].initStatistics({8, 4, 0, 2, 1, 1});
- _blockType[3].initStatistics({8, 0, 0, 4, 2, 1, 1});
+ _blockType[0].reset(new HuffmanType(HuffmanType::fromFrequencies({16, 8, 4, 2, 1, 1})));
+ _blockType[1].reset(new HuffmanType(HuffmanType::fromFrequencies({8, 0, 4, 2, 1, 1})));
+ _blockType[2].reset(new HuffmanType(HuffmanType::fromFrequencies({8, 4, 0, 2, 1, 1})));
+ _blockType[3].reset(new HuffmanType(HuffmanType::fromFrequencies({8, 0, 0, 4, 2, 1, 1})));
}
~FourXMVideoTrack();
@@ -257,10 +259,16 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
stream->read(prefixStream.data(), prefixStream.size());
assert(stream->pos() == stream->size());
- auto prefixData = FourXM::HuffmanDecoder::unpack(prefixStream.data(), prefixStream.size(), 4);
+ const auto *huffPtr = prefixStream.data();
+ uint huffOffset = 0;
+ auto prefixDecoder = FourXM::loadStatistics<HuffmanType>(huffPtr, huffOffset);
+ huffOffset = (huffOffset + 3) & ~3u;
+
+ Common::BitStreamMemoryStream huffMs(huffPtr + huffOffset, prefixStream.size() - huffOffset);
+ Common::BitStreamMemory32LEMSB huffBs{&huffMs};
+
Common::BitStreamMemoryStream bitstreamInput(bitstreamData.data(), bitstreamData.size());
Common::BitStreamMemory8MSB bitstream(&bitstreamInput);
- uint prefixOffset = 0;
int lastDC = 0;
auto &format = _frame->format;
const auto dstPitch = _frame->pitch / format.bytesPerPixel - 16;
@@ -268,7 +276,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
for (int mbX = 0; mbX < _frame->w; mbX += 16) {
int16_t block[6][64] = {};
auto readBlock = [&](byte blockIdx, int16_t *ac) {
- int dc = prefixData[prefixOffset++];
+ int dc = prefixDecoder.getSymbol(huffBs);
if (dc >> 4)
error("dc run code");
dc = FourXM::readInt(bitstream, dc);
@@ -276,7 +284,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
lastDC = dc;
ac[0] = dc;
for (uint idx = 1; idx < 64;) {
- auto b = prefixData[prefixOffset++];
+ auto b = prefixDecoder.getSymbol(huffBs);
if (b == 0x00) {
break;
} else if (b == 0xf0) {
@@ -325,7 +333,6 @@ void FourXMDecoder::FourXMVideoTrack::decode_ifrm(Common::SeekableReadStream *st
}
}
}
- assert(prefixOffset == prefixData.size());
SWAP(_frame, _lastFrame);
}
@@ -385,8 +392,8 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(uint16 *dst, const uint1
assert(log2w >= 0 && log2h >= 0);
auto index = size2index[log2h][log2w];
assert(index >= 0);
- auto &huff = _blockType[index];
- auto code = huff.next(bs);
+ auto &huff = *_blockType[index];
+ auto code = huff.getSymbol(bs);
if (code == 1) {
--log2h;
diff --git a/video/4xm_utils.cpp b/video/4xm_utils.cpp
index ce22f05d0e3..a6e75361fb0 100644
--- a/video/4xm_utils.cpp
+++ b/video/4xm_utils.cpp
@@ -27,148 +27,6 @@
namespace Video {
namespace FourXM {
-template<typename BitStreamType>
-uint HuffmanDecoder::next(BitStreamType &bs) {
- uint value = _startEntry;
- while (value >= _numCodes) {
- auto bit = bs.template getBits<1>();
- if (bit)
- value = _table[value].trueIdx;
- else
- value = _table[value].falseIdx;
- }
- return value;
-}
-template uint HuffmanDecoder::next(Common::BitStreamMemory8MSB &bs);
-template uint HuffmanDecoder::next(Common::BitStreamMemory32LEMSB &bs);
-template uint HuffmanDecoder::next(Common::BitStreamMemory32BEMSB &bs);
-
-template<typename BitStream>
-Common::Array<byte> HuffmanDecoder::unpackStream(const byte *huff, uint huffSize, uint &offset) {
- Common::Array<byte> decoded;
- decoded.reserve(huffSize * 2);
- Common::BitStreamMemoryStream ms{huff, huffSize};
- BitStream bs(&ms);
- bs.skip(offset * 8);
- while (true) {
- auto value = next(bs);
- if (value == 256)
- break;
- decoded.push_back(static_cast<byte>(value));
- }
- offset = bs.pos();
- return decoded;
-}
-
-void HuffmanDecoder::initStatistics(const std::initializer_list<uint> &freqs) {
- auto freqBegin = freqs.begin();
- for (size_t i = 0; i != freqs.size(); ++i) {
- _table[i].freq = *freqBegin++;
- }
- buildTable(freqs.size());
-}
-
-uint HuffmanDecoder::loadStatistics(const byte *huff, uint huffSize) {
- uint offset = 0;
- uint8 freq_first = huff[offset++];
- do {
- uint8 freq_last = huff[offset++];
- if (freq_first <= freq_last) {
- for (auto idx = freq_first; idx <= freq_last; ++idx) {
- _table[idx].freq = huff[offset++];
- }
- }
- freq_first = huff[offset++];
- } while (freq_first != 0);
- _table[256].freq = 1;
-
- buildTable(257);
- return offset;
-}
-
-void HuffmanDecoder::dumpImpl(uint code, uint size, uint index, uint ch) const {
- if (index == _startEntry) {
- debug("%u: code %u, size: %u", ch, code, size);
- return;
- }
- for (uint i = 0; i != kLastEntry; ++i) {
- auto &e = _table[i];
- if (e.falseIdx == kMaxTableSize)
- continue;
-
- if (e.falseIdx == index) {
- dumpImpl(code, size + 1, i, ch);
- return;
- }
- if (e.trueIdx == index) {
- dumpImpl(code | (1 << size), size + 1, i, ch);
- return;
- }
- }
-}
-
-void HuffmanDecoder::dump() const {
- for (uint ch = 0; ch < _numCodes; ++ch) {
- dumpImpl(0, 0, ch, ch);
- }
-}
-
-void HuffmanDecoder::buildTable(uint numCodes) {
- _numCodes = numCodes;
- _table[kLastEntry].freq = 0x7FFF;
- auto codeIdx = numCodes;
- while (true) {
- uint16 idx = 0;
- uint16 smallest2 = kLastEntry, smallest1 = kLastEntry;
- while (idx < codeIdx) {
- auto freq = _table[idx].freq;
- if (freq != 0) {
- if (freq >= _table[smallest1].freq) {
- if (freq < _table[smallest2].freq) {
- smallest2 = idx;
- }
- } else {
- smallest2 = smallest1;
- smallest1 = idx;
- }
- }
- ++idx;
- }
- if (smallest2 == kLastEntry) {
- _startEntry = codeIdx - 1;
- break;
- }
- _table[codeIdx].freq = _table[smallest1].freq + _table[smallest2].freq;
- _table[smallest1].freq = _table[smallest2].freq = 0;
- _table[codeIdx].falseIdx = smallest1;
- _table[codeIdx].trueIdx = smallest2;
- ++codeIdx;
- }
- assert(codeIdx < kLastEntry);
-}
-
-Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize) {
- Common::Array<byte> decoded;
- offset = (offset + wordSize - 1) / wordSize * wordSize;
- switch (wordSize) {
- case 1:
- decoded = unpackStream<Common::BitStreamMemory8MSB>(huff, huffSize, offset);
- break;
- case 4:
- decoded = unpackStream<Common::BitStreamMemory32LEMSB>(huff, huffSize, offset);
- break;
- default:
- error("invalid word size");
- }
- return decoded;
-}
-
-Common::Array<byte> HuffmanDecoder::unpack(const byte *huff, uint huffSize, byte wordSize) {
- HuffmanDecoder dec;
- auto offset = dec.loadStatistics(huff, huffSize);
- return dec.unpack(huff, huffSize, offset, wordSize);
-}
-
#define FIX_1_082392200 70936
#define FIX_1_414213562 92682
#define FIX_1_847759065 121095
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index b202694fafa..133cd5d04f2 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -20,42 +20,48 @@
*/
#include "common/array.h"
+#include "common/compression/huffman.h"
+#include "common/debug.h"
namespace Video {
namespace FourXM {
-class HuffmanDecoder {
- static constexpr uint kMaxTableSize = 514;
- static constexpr uint kLastEntry = kMaxTableSize - 1;
- struct HuffChar {
- uint freq = 0;
- uint16 falseIdx = kMaxTableSize;
- uint16 trueIdx = kMaxTableSize;
- };
- HuffChar _table[kMaxTableSize] = {};
- uint _startEntry = 0;
- uint _numCodes = 0;
+template<typename HuffmanType>
+HuffmanType loadStatistics(const byte *&huff, uint &offset) {
+ Common::Array<uint32> freqs, symbols;
-public:
- uint loadStatistics(const byte *huff, uint huffSize);
- void initStatistics(const std::initializer_list<uint> &freqs);
+ uint8 freq_first = huff[offset++];
+ do {
+ uint8 freq_last = huff[offset++];
+ if (freq_first <= freq_last) {
+ for (auto idx = freq_first; idx <= freq_last; ++idx) {
+ auto freq = huff[offset++];
+ if (freq != 0) {
+ freqs.push_back(freq);
+ symbols.push_back(idx);
+ }
+ }
+ }
+ freq_first = huff[offset++];
+ } while (freq_first != 0);
- Common::Array<byte> unpack(const byte *huff, uint huffSize, uint &offset, byte wordSize);
+ freqs.push_back(1);
+ symbols.push_back(256);
- static Common::Array<byte> unpack(const byte *huff, uint huffSize, byte wordSize);
-
- template<typename BitStreamType>
- uint next(BitStreamType &bs);
-
- void dump() const;
-
-private:
- template<typename Word>
- Common::Array<byte> unpackStream(const byte *huff, uint huffSize, uint &offset);
+ return HuffmanType::fromFrequencies(freqs.size(), freqs.data(), symbols.data());
+}
- void buildTable(uint numCodes);
- void dumpImpl(uint code, uint size, uint index, uint ch) const;
-};
+template<typename HuffmanType, typename Bitstream>
+inline Common::Array<byte> unpackHuffman(HuffmanType &h, Bitstream &bs) {
+ Common::Array<byte> unpacked;
+ while (true) {
+ uint32 code = h.getSymbol(bs);
+ if (code > 255)
+ break;
+ unpacked.push_back(code);
+ }
+ return unpacked;
+}
void idct(int16_t block[64], int shift = 6);
Commit: 919a04fc148f25f105aff25de6f477e62fb047c8
https://github.com/scummvm/scummvm/commit/919a04fc148f25f105aff25de6f477e62fb047c8
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:39+01:00
Commit Message:
PHOENIXVR: use key mapper
Changed paths:
engines/phoenixvr/metaengine.cpp
engines/phoenixvr/metaengine.h
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 18e76d245a7..64180f3c38b 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -22,6 +22,10 @@
#include "common/savefile.h"
#include "common/translation.h"
+#include "backends/keymapper/action.h"
+#include "backends/keymapper/keymap.h"
+#include "backends/keymapper/standard-actions.h"
+
#include "phoenixvr/detection.h"
#include "phoenixvr/game_state.h"
#include "phoenixvr/metaengine.h"
@@ -54,6 +58,55 @@ Common::Error PhoenixVRMetaEngine::createInstance(OSystem *syst, Engine **engine
return Common::kNoError;
}
+Common::KeymapArray PhoenixVRMetaEngine::initKeymaps(const char *target) const {
+ auto *keyMap = new Common::Keymap(Common::Keymap::kKeymapTypeGame, "phoenixvr", "Default keymap for PhoenixVR");
+
+ Common::Action *act;
+
+ act = new Common::Action(Common::kStandardActionLeftClick, _("Action"));
+ act->addDefaultInputMapping("MOUSE_LEFT");
+ act->addDefaultInputMapping("JOY_A");
+ act->addDefaultInputMapping("RETURN");
+ act->setLeftClickEvent();
+ keyMap->addAction(act);
+
+ act = new Common::Action(Common::kStandardActionRightClick, _("Inventory"));
+ act->addDefaultInputMapping("MOUSE_RIGHT");
+ act->addDefaultInputMapping("TAB");
+ act->addDefaultInputMapping("JOY_B");
+ act->setRightClickEvent();
+ keyMap->addAction(act);
+
+ act = new Common::Action("QUIT", _("Quit game"));
+ act->addDefaultInputMapping("C+q");
+ act->setEvent(Common::EventType::EVENT_QUIT);
+ keyMap->addAction(act);
+
+ act = new Common::Action(Common::kStandardActionSkip, _("Skip cutscene"));
+ act->addDefaultInputMapping("SPACE");
+ act->addDefaultInputMapping("JOY_Y");
+ act->setKeyEvent(Common::KeyState{Common::KEYCODE_SPACE, ' '});
+ keyMap->addAction(act);
+
+ act = new Common::Action("QUICKSAVE", _("Quick save"));
+ act->addDefaultInputMapping("F5");
+ act->setKeyEvent(Common::KeyState{Common::KEYCODE_F5});
+ keyMap->addAction(act);
+
+ act = new Common::Action("QUICKLOAD", _("Quick load"));
+ act->addDefaultInputMapping("F8");
+ act->setKeyEvent(Common::KeyState{Common::KEYCODE_F8});
+ keyMap->addAction(act);
+
+ act = new Common::Action(Common::kStandardActionOpenMainMenu, _("Menu"));
+ act->addDefaultInputMapping("ESCAPE");
+ act->addDefaultInputMapping("JOY_START");
+ act->setKeyEvent(Common::KeyState{Common::KEYCODE_ESCAPE});
+ keyMap->addAction(act);
+
+ return Common::Keymap::arrayOf(keyMap);
+}
+
SaveStateList PhoenixVRMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
diff --git a/engines/phoenixvr/metaengine.h b/engines/phoenixvr/metaengine.h
index 7c16eeb6a8d..a2702e51b86 100644
--- a/engines/phoenixvr/metaengine.h
+++ b/engines/phoenixvr/metaengine.h
@@ -46,6 +46,7 @@ public:
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override;
+ Common::KeymapArray initKeymaps(const char *target) const override;
};
#endif // PHOENIXVR_METAENGINE_H
Commit: 577a8bf8d38e565b27ffab06bb45386beebe5689
https://github.com/scummvm/scummvm/commit/577a8bf8d38e565b27ffab06bb45386beebe5689
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:39+01:00
Commit Message:
PHOENIXVR: clear mouseRel in 2d mode
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index d50c7745e17..8f269d69eac 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -540,7 +540,9 @@ void PhoenixVREngine::tick(float dt) {
debug("region %d: %s, in: %d", i, reg.toString().c_str(), reg.contains3D(currentVRPos()));
}
}
- }
+ } else
+ _mouseRel = {};
+
Common::Array<Common::String> finishedSounds;
for (auto &kv : _sounds) {
auto &sound = kv._value;
Commit: f10733f7e462b7c0ff2e938a9588ab0e7777f4ce
https://github.com/scummvm/scummvm/commit/f10733f7e462b7c0ff2e938a9588ab0e7777f4ce
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:39+01:00
Commit Message:
PHOENIXVR: JANITORIAL: fix compilation warnings/errors
Changed paths:
engines/phoenixvr/angle.h
engines/phoenixvr/rectf.h
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/angle.h b/engines/phoenixvr/angle.h
index b29ff8e237f..3271f89f8cc 100644
--- a/engines/phoenixvr/angle.h
+++ b/engines/phoenixvr/angle.h
@@ -27,7 +27,7 @@
namespace Common {
class String;
-};
+}
namespace PhoenixVR {
class Angle {
diff --git a/engines/phoenixvr/rectf.h b/engines/phoenixvr/rectf.h
index 1565af78d30..f6fb52d15fb 100644
--- a/engines/phoenixvr/rectf.h
+++ b/engines/phoenixvr/rectf.h
@@ -40,7 +40,7 @@ Common::String toString() const {
return Common::String::format("%g, %g, %g, %g", left, top, right, bottom);
}
static PointF transform(float ax, float ay, float fov);
-END_RECT_TYPE(float, RectF, PointF);
+END_RECT_TYPE(float, RectF, PointF)
} // namespace PhoenixVR
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index dcb438178e2..13e78556020 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -188,7 +188,10 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
}
} // namespace
-VR::~VR() = default;
+VR::~VR() noexcept = default;
+VR::VR() noexcept = default;
+VR::VR(VR &&) noexcept = default;
+VR &VR::operator=(VR &&) noexcept = default;
VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s) {
VR vr;
@@ -273,7 +276,7 @@ Cube toCube(float x, float y, float z) {
bool isYPositive = y > 0;
bool isZPositive = z > 0;
- float maxAxis, cy, cx;
+ float maxAxis = 0, cy = 0, cx = 0;
if (isXPositive && absX >= absY && absX >= absZ) {
maxAxis = absX;
@@ -420,7 +423,7 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
Vector3d incrementY = up * (2 * gy / h);
Vector3d start = forward - right * gx - up * gy;
Vector3d line = start;
- float regX, regY, regDX = 0, regDY = 0;
+ float regX = 0, regY = 0, regDX = 0, regDY = 0;
if (regSet) {
regY = ay - fov / 2;
regDX = fov / w;
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 513ad8a7b26..3cc6ea553d5 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -60,10 +60,10 @@ class VR {
Common::Array<Animation> _animations;
public:
- ~VR();
- VR() = default;
- VR(VR &&) noexcept = default;
- VR &operator=(VR &&) noexcept = default;
+ ~VR() noexcept;
+ VR() noexcept;
+ VR(VR &&) noexcept;
+ VR &operator=(VR &&) noexcept;
static VR loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStream &s);
void render(Graphics::Screen *screen, float ax, float ay, float fov, float dt, RegionSet *regSet);
Commit: 012c9b3256aafc8fe7ca873375c8fe08c2549eaf
https://github.com/scummvm/scummvm/commit/012c9b3256aafc8fe7ca873375c8fe08c2549eaf
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:39+01:00
Commit Message:
PHOENIXVR: fix assertion when you press RETURN very early in the game
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 8f269d69eac..3ad6befad20 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -777,6 +777,8 @@ Common::Error PhoenixVREngine::run() {
} else
debug("click %s", _mousePos.toString().c_str());
+ if (_warpIdx < 0)
+ break;
auto &cursors = _cursors[_warpIdx];
for (uint i = 0, n = cursors.size(); i != n; ++i) {
auto *region = getRegion(i);
Commit: cdaa5455c55978be8938a49358f9625e590fb3f2
https://github.com/scummvm/scummvm/commit/cdaa5455c55978be8938a49358f9625e590fb3f2
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:40+01:00
Commit Message:
PHOENIXVR: remove unpackHuffman, use huffman decoder directly
Changed paths:
engines/phoenixvr/vr.cpp
video/4xm_utils.h
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 13e78556020..944ccebe405 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -108,15 +108,14 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
auto huffDecoder = Video::FourXM::loadStatistics<Common::Huffman<Common::BitStreamMemory8MSB>>(huff, huffOffset);
Common::BitStreamMemoryStream huffMs(huff + huffOffset, huffSize - huffOffset);
Common::BitStreamMemory8MSB huffBs(&huffMs);
- auto decoded = Video::FourXM::unpackHuffman(huffDecoder, huffBs);
- uint decodedOffset = 0;
Common::BitStreamMemoryStream acMs(acPtr, acSize);
Common::BitStreamMemoryStream dcMs(dcPtr, dcSize);
Common::BitStreamMemory8MSB acBs(&acMs), dcBs(&dcMs);
const auto dstPitch = pic.pitch / pic.format.bytesPerPixel - 8;
- for (uint blockIdx = 0; decodedOffset < decoded.size(); ++blockIdx) {
+ unsigned numBlocks = prefix ? prefix->size() : ((pic.w + 7) / 8) * ((pic.h + 7) / 8);
+ for (uint blockIdx = 0; blockIdx < numBlocks; ++blockIdx) {
int16 block[3][64] = {};
for (unsigned channel = 0; channel != 3; ++channel) {
int16 *ac = block[channel];
@@ -124,7 +123,8 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
auto *iquant = channel ? quant.quantCbCr : quant.quantY;
ac[0] = iquant[0] * dc8;
for (uint idx = 1; idx < 64;) {
- auto b = decoded[decodedOffset++];
+ auto b = huffDecoder.getSymbol(huffBs);
+ assert(b < 0x100);
if (b == 0x00) {
break;
} else if (b == 0xf0) {
diff --git a/video/4xm_utils.h b/video/4xm_utils.h
index 133cd5d04f2..5c6e459fe52 100644
--- a/video/4xm_utils.h
+++ b/video/4xm_utils.h
@@ -51,18 +51,6 @@ HuffmanType loadStatistics(const byte *&huff, uint &offset) {
return HuffmanType::fromFrequencies(freqs.size(), freqs.data(), symbols.data());
}
-template<typename HuffmanType, typename Bitstream>
-inline Common::Array<byte> unpackHuffman(HuffmanType &h, Bitstream &bs) {
- Common::Array<byte> unpacked;
- while (true) {
- uint32 code = h.getSymbol(bs);
- if (code > 255)
- break;
- unpacked.push_back(code);
- }
- return unpacked;
-}
-
void idct(int16_t block[64], int shift = 6);
inline int readInt(int value, unsigned n) {
Commit: a8b8f36224425b8fce831dba61c19cbb7977c841
https://github.com/scummvm/scummvm/commit/a8b8f36224425b8fce831dba61c19cbb7977c841
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:40+01:00
Commit Message:
PHOENIXVR: if animation speed is 0, set dst var to value instantly
Changed paths:
engines/phoenixvr/vr.cpp
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 944ccebe405..113e4cddc7e 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -361,6 +361,8 @@ void VR::playAnimation(const Common::String &name, const Common::String &variabl
animation.variable = variable;
animation.variableValue = value;
animation.renderNextFrame(*_pic->surfacePtr());
+ if (animation.speed == 0)
+ g_engine->setVariable(variable, value);
}
void VR::Animation::renderNextFrame(Graphics::Surface &pic) {
Commit: 4f1406eabc77e7f9101fc320ce36459e1fd1a098
https://github.com/scummvm/scummvm/commit/4f1406eabc77e7f9101fc320ce36459e1fd1a098
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:40+01:00
Commit Message:
PHOENIXVR: initialize default cursors
Changed paths:
engines/phoenixvr/phoenixvr.cpp
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 3ad6befad20..10ec3839949 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -647,6 +647,9 @@ Common::Error PhoenixVREngine::run() {
_font18.reset(Graphics::loadTTFFontFromArchive(family, 18));
#endif
+ setCursorDefault(0, "Cursor1.pcx");
+ setCursorDefault(1, "Cursor2.pcx");
+
_screen = new Graphics::Screen();
_screenCenter = _screen->getBounds().center();
{
Commit: b25153a87c2f2ec54139aa87ee051307bc0c0de8
https://github.com/scummvm/scummvm/commit/b25153a87c2f2ec54139aa87ee051307bc0c0de8
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:41+01:00
Commit Message:
PHOENIXVR: remove switch with one case in parser
Changed paths:
engines/phoenixvr/script.cpp
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 20f5d87234a..2a0d4e7f9a8 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -280,9 +280,7 @@ void Script::parseLine(const Common::String &line, uint lineno) {
if (p.atEnd())
return;
- switch (p.peek()) {
- case '[': {
- p.next();
+ if (p.maybe('[')) {
if (p.maybe("bool]=")) {
_vars.push_back(p.nextWord());
} else if (p.maybe("warp]=")) {
@@ -308,68 +306,64 @@ void Script::parseLine(const Common::String &line, uint lineno) {
} else {
error("invalid [] directive on line %u: %s", lineno, line.c_str());
}
- break;
- }
- default:
- if (_currentTest) {
- auto &commands = _currentTest->scope.commands;
- if (p.maybe("ifand=")) {
- if (_pluginScope)
- error("ifand in plugin scope");
- _conditional.reset(new IfAnd(p.readStringList()));
- } else if (p.maybe("ifor=")) {
- if (_pluginScope)
- error("ifor in plugin scope");
- _conditional.reset(new IfOr(p.readStringList()));
- } else if (p.maybe("plugin")) {
- if (_pluginScope)
- error("nested plugin context is not allowed, line: %u", lineno);
- _pluginScope.reset(new Script::Scope);
- } else if (p.maybe("endplugin")) {
- if (!_pluginScope)
- error("endplugin without plugin");
- if (_conditional) {
- _conditional->target = Common::move(_pluginScope);
- _pluginScope.reset();
- commands.push_back(Common::move(_conditional));
- _conditional.reset();
- } else {
- commands.push_back(Common::move(_pluginScope));
- _pluginScope.reset();
- }
- } else if (p.maybe("label")) {
- if (_pluginScope)
- error("no labels in plugin scope allowed");
+ } else if (_currentTest) {
+ auto &commands = _currentTest->scope.commands;
+ if (p.maybe("ifand=")) {
+ if (_pluginScope)
+ error("ifand in plugin scope");
+ _conditional.reset(new IfAnd(p.readStringList()));
+ } else if (p.maybe("ifor=")) {
+ if (_pluginScope)
+ error("ifor in plugin scope");
+ _conditional.reset(new IfOr(p.readStringList()));
+ } else if (p.maybe("plugin")) {
+ if (_pluginScope)
+ error("nested plugin context is not allowed, line: %u", lineno);
+ _pluginScope.reset(new Script::Scope);
+ } else if (p.maybe("endplugin")) {
+ if (!_pluginScope)
+ error("endplugin without plugin");
+ if (_conditional) {
+ _conditional->target = Common::move(_pluginScope);
+ _pluginScope.reset();
+ commands.push_back(Common::move(_conditional));
+ _conditional.reset();
+ } else {
+ commands.push_back(Common::move(_pluginScope));
+ _pluginScope.reset();
+ }
+ } else if (p.maybe("label")) {
+ if (_pluginScope)
+ error("no labels in plugin scope allowed");
+ auto name = p.nextWord();
+ auto offset = _currentTest->scope.commands.size();
+ _currentTest->scope.labels.push_back({Common::move(name), offset});
+ } else {
+ if (_pluginScope) {
auto name = p.nextWord();
- auto offset = _currentTest->scope.commands.size();
- _currentTest->scope.labels.push_back({Common::move(name), offset});
+ p.expect('(');
+ auto args = p.readStringList();
+ p.expect(')');
+ auto cmd = createCommand(name, args, lineno);
+ if (cmd)
+ _pluginScope->commands.push_back(Common::move(cmd));
+ else
+ error("unhandled plugin command %s at line %d", line.c_str(), lineno);
} else {
- if (_pluginScope) {
- auto name = p.nextWord();
- p.expect('(');
- auto args = p.readStringList();
- p.expect(')');
- auto cmd = createCommand(name, args, lineno);
- if (cmd)
- _pluginScope->commands.push_back(Common::move(cmd));
- else
- error("unhandled plugin command %s at line %d", line.c_str(), lineno);
- } else {
- auto cmd = p.parseCommand();
- if (cmd) {
- if (_conditional) {
- _conditional->target = Common::move(cmd);
- commands.push_back(Common::move(_conditional));
- _conditional.reset();
- } else
- commands.push_back(Common::move(cmd));
+ auto cmd = p.parseCommand();
+ if (cmd) {
+ if (_conditional) {
+ _conditional->target = Common::move(cmd);
+ commands.push_back(Common::move(_conditional));
+ _conditional.reset();
} else
- error("unhandled script command %s at line %d", line.c_str(), lineno);
- }
+ commands.push_back(Common::move(cmd));
+ } else
+ error("unhandled script command %s at line %d", line.c_str(), lineno);
}
- } else
- error("invalid directive at line %u: %s", lineno, line.c_str());
- }
+ }
+ } else
+ error("invalid directive at line %u: %s", lineno, line.c_str());
}
Script::~Script() {
Commit: a35083986619f8a9435e26cf2658ada2ade34378
https://github.com/scummvm/scummvm/commit/a35083986619f8a9435e26cf2658ada2ade34378
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:41+01:00
Commit Message:
PHOENIXVR: disable autosave and save
Changed paths:
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index c598b281966..72db5ff03b8 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -91,11 +91,14 @@ public:
(f == kSupportsReturnToLauncher);
};
+ // disable autosave
+ int getAutosaveSlot() const override { return -1; }
+
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
- return getVariable("E_Canload") != 0;
+ return true;
}
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
- return getVariable("E_Cansave") != 0;
+ return false;
}
// Script API
Commit: 038b33c3fea1427337aa775720b0ae91a6b212e9
https://github.com/scummvm/scummvm/commit/038b33c3fea1427337aa775720b0ae91a6b212e9
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-02-09T23:30:41+01:00
Commit Message:
PHOENIXVR: fix hints (press H), add noise effect
Changed paths:
engines/phoenixvr/metaengine.cpp
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/vr.cpp
engines/phoenixvr/vr.h
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index 64180f3c38b..ae4dd2e88aa 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -104,6 +104,11 @@ Common::KeymapArray PhoenixVRMetaEngine::initKeymaps(const char *target) const {
act->setKeyEvent(Common::KeyState{Common::KEYCODE_ESCAPE});
keyMap->addAction(act);
+ act = new Common::Action("HINT", _("Show hints"));
+ act->addDefaultInputMapping("h");
+ act->setKeyEvent(Common::KeyState{Common::KEYCODE_h});
+ keyMap->addAction(act);
+
return Common::Keymap::arrayOf(keyMap);
}
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 10ec3839949..90d2aa69571 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -706,7 +706,7 @@ Common::Error PhoenixVREngine::run() {
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN: {
- if (event.kbd.keycode == Common::KeyCode::KEYCODE_r)
+ if (event.kbd.keycode == Common::KeyCode::KEYCODE_h)
_showRegions = !_showRegions;
if (_prevWarp != -1)
break;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 113e4cddc7e..979db3bd30d 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -437,9 +437,9 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
if (regSet) {
regX = ax - fov / 2;
if (regX < 0)
- regX += kPi2;
- if (regX >= kPi2)
- regX -= kPi2;
+ regX += kTau;
+ if (regX >= kTau)
+ regX -= kTau;
}
Vector3d pixel = line;
for (int dstX = 0; dstX != w; ++dstX, pixel += incrementX) {
@@ -455,16 +455,15 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
auto color = _pic->getPixel(srcX, srcY);
if (regSet) {
regX += regDX;
- if (regX >= kPi2)
- regX -= kPi2;
+ if (regX >= kTau)
+ regX -= kTau;
for (auto ® : regSet->getRegions()) {
if (reg.contains3D(regX, kTau - regY)) {
byte r, g, b;
_pic->format.colorToRGB(color, r, g, b);
- static constexpr int kGlow = 15;
- auto dr = MIN(255 - r, kGlow), db = MIN(255 - b, kGlow);
- r += dr;
- b += db;
+ r ^= _rnd.getRandomNumber(31);
+ g ^= _rnd.getRandomNumber(31);
+ b ^= _rnd.getRandomNumber(31);
color = screen->format.RGBToColor(r, g, b);
}
}
@@ -473,8 +472,8 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
}
if (regSet) {
regY += regDY;
- if (regY >= kPi2)
- regY -= kPi2;
+ if (regY >= kTau)
+ regY -= kTau;
}
}
screen->addDirtyRect(screen->getBounds());
diff --git a/engines/phoenixvr/vr.h b/engines/phoenixvr/vr.h
index 3cc6ea553d5..e6527881244 100644
--- a/engines/phoenixvr/vr.h
+++ b/engines/phoenixvr/vr.h
@@ -23,6 +23,7 @@
#define PHOENIXVR_VR_H
#include "common/array.h"
+#include "common/random.h"
#include "common/stream.h"
#include "graphics/managed_surface.h"
#include "graphics/pixelformat.h"
@@ -58,6 +59,7 @@ class VR {
void render(Graphics::Surface &pic, float dt);
};
Common::Array<Animation> _animations;
+ Common::RandomSource _rnd = {"vr"};
public:
~VR() noexcept;
More information about the Scummvm-git-logs
mailing list