diff options
41 files changed, 6964 insertions, 0 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 609f359e7d..fd7a49af8f 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES | |||
@@ -134,4 +134,5 @@ wavrecord,apps | |||
134 | wavview,viewers | 134 | wavview,viewers |
135 | wormlet,games | 135 | wormlet,games |
136 | xobox,games | 136 | xobox,games |
137 | xworld,games | ||
137 | zxbox,viewers | 138 | zxbox,viewers |
diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 8e653983b7..4b4015be08 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS | |||
@@ -12,6 +12,12 @@ clock | |||
12 | /* For all targets with a bitmap display */ | 12 | /* For all targets with a bitmap display */ |
13 | #ifdef HAVE_LCD_BITMAP | 13 | #ifdef HAVE_LCD_BITMAP |
14 | 14 | ||
15 | /* XWorld only supports color horizontal stride LCDs /for now/ ;) */ | ||
16 | #if (defined(HAVE_LCD_COLOR) && \ | ||
17 | (!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE)) | ||
18 | xworld | ||
19 | #endif | ||
20 | |||
15 | #if (CONFIG_KEYPAD != ONDIO_PAD) /* not enough buttons */ \ | 21 | #if (CONFIG_KEYPAD != ONDIO_PAD) /* not enough buttons */ \ |
16 | && (CONFIG_KEYPAD != SANSA_M200_PAD) /* not enough buttons */ \ | 22 | && (CONFIG_KEYPAD != SANSA_M200_PAD) /* not enough buttons */ \ |
17 | && (CONFIG_KEYPAD != HM60X_PAD) /* not enough buttons */ \ | 23 | && (CONFIG_KEYPAD != HM60X_PAD) /* not enough buttons */ \ |
diff --git a/apps/plugins/xworld/README b/apps/plugins/xworld/README new file mode 100644 index 0000000000..3a07b1ba94 --- /dev/null +++ b/apps/plugins/xworld/README | |||
@@ -0,0 +1,86 @@ | |||
1 | This is the original readme from the "Fabother World" sources; the Rockbox port's | ||
2 | readme is in README.rockbox. | ||
3 | |||
4 | Franklin Wei | ||
5 | |||
6 | ================================================================================= | ||
7 | |||
8 | This is "Fabother World": an Another World (Out Of This World in North America) interpreter codebase. This work is based on: | ||
9 | |||
10 | - Piotr Padkowski's newRaw interpreter which was based on | ||
11 | - Gregory Montoir's reverse engineering of | ||
12 | - Eric Chahi's assembly code. | ||
13 | |||
14 | I cleaned up a lot of the code, removing cryptic hexadecimal notation | ||
15 | with meaningful macros name. I also cleanup a lot of the code so it has a | ||
16 | C/C++ philosophy instead of an assembly structure. | ||
17 | |||
18 | I also created a Visual Studio 2010 project. | ||
19 | |||
20 | TODO: | ||
21 | |||
22 | Create a MacOS X project. | ||
23 | Add a different rendering path OpenGL support. | ||
24 | |||
25 | Fabien Sanglard | ||
26 | |||
27 | |||
28 | raw README | ||
29 | Release version: 0.1.1 (May 15 2004) | ||
30 | ------------------------------------------------------------------------------- | ||
31 | |||
32 | About: | ||
33 | ------ | ||
34 | |||
35 | raw is a re-implementation of the engine used in the game Another World. This | ||
36 | game, released under the name Out Of This World in non-European countries, was | ||
37 | written by Eric Chahi at the beginning of the '90s. More information can be | ||
38 | found here : http://www.mobygames.com/game/sheet/p,2/gameId,564/. | ||
39 | |||
40 | Please be aware that, currently, this implementation may contains bugs and | ||
41 | non-implemented features that make it impossible to finish the game. | ||
42 | |||
43 | Supported Versions: | ||
44 | ------------------- | ||
45 | |||
46 | Currently, only the english PC DOS version is supported ("Out of this World"). | ||
47 | |||
48 | Compiling: | ||
49 | ---------- | ||
50 | |||
51 | Tweak the Makefile if needed and type make (only gcc3 has been tested so far). | ||
52 | The SDL and zlib libraries are required. | ||
53 | |||
54 | Running: | ||
55 | -------- | ||
56 | |||
57 | You will need the original files, here is the required list : | ||
58 | BANK* | ||
59 | MEMLIST.BIN | ||
60 | |||
61 | To start the game, you can either : | ||
62 | - put the game's datafiles in the same directory as the executable | ||
63 | - use the --datapath command line option to specify the datafiles directory | ||
64 | |||
65 | Here are the various in game hotkeys : | ||
66 | Arrow Keys allow you to move Lester | ||
67 | Enter/Space allow you run/shoot with your gun | ||
68 | C allow to enter a code to jump at a specific level | ||
69 | P pause the game | ||
70 | Alt X exit the game | ||
71 | Ctrl S save game state | ||
72 | Ctrl L load game state | ||
73 | Ctrl + and - change game state slot | ||
74 | Ctrl F toggle fast mode | ||
75 | Alt Enter toggle windowed/fullscreen mode | ||
76 | Alt + and - change scaler factor | ||
77 | |||
78 | Credits: | ||
79 | -------- | ||
80 | |||
81 | Eric Chahi, obviously, for making this great game. | ||
82 | |||
83 | Contact: | ||
84 | -------- | ||
85 | |||
86 | Gregory Montoir, cyx@users.sourceforge.net | ||
diff --git a/apps/plugins/xworld/README.newraw b/apps/plugins/xworld/README.newraw new file mode 100644 index 0000000000..f5163c978c --- /dev/null +++ b/apps/plugins/xworld/README.newraw | |||
@@ -0,0 +1,4 @@ | |||
1 | |||
2 | Changes: | ||
3 | Added 2x and 3x high quality scalers (ripped from Reminescence) | ||
4 | |||
diff --git a/apps/plugins/xworld/README.rockbox b/apps/plugins/xworld/README.rockbox new file mode 100644 index 0000000000..2ea63faa17 --- /dev/null +++ b/apps/plugins/xworld/README.rockbox | |||
@@ -0,0 +1,51 @@ | |||
1 | This is the Rockbox port of Fabien Sanglard's "Fabother World", an Another World | ||
2 | interpreter. | ||
3 | |||
4 | Porting process: | ||
5 | ---------------- | ||
6 | |||
7 | The original code abstracted most of the platform-specific tasks, such as file I/O, | ||
8 | sound, input, and video. However, the original code was in C++, so it was converted | ||
9 | to C class-by-class. The conversion was attempted to be as conservative as possible, | ||
10 | so little code was rewritten during the conversion process. | ||
11 | |||
12 | Notes: | ||
13 | ------ | ||
14 | |||
15 | - Optimization is badly needed. | ||
16 | - Vertical stride support is almost there. | ||
17 | - The game looks terrible in B+W/grayscale. This was the primary reason no attempt | ||
18 | was made to support these targets. | ||
19 | - The game does not run well on devices that have an LCD with a vertical stride. | ||
20 | - The M:Robe 500 is the only color device that meets this criterion, so it is | ||
21 | disabled by default. | ||
22 | - Sound doesn't sound 100% like the PC version. Perhaps the frequency reported to | ||
23 | the mixer is incorrect, or the buffer size is too big so that short sounds are | ||
24 | being missed. | ||
25 | |||
26 | To do (in no particular order): | ||
27 | ------------------------------- | ||
28 | |||
29 | - Support vertical stride LCD's | ||
30 | - Support grayscale/monochrome LCD's | ||
31 | - Optimize | ||
32 | |||
33 | Credits: | ||
34 | -------- | ||
35 | |||
36 | ************************************** | ||
37 | ************************************** | ||
38 | ********** !!!ERIC CHAHI!!! ********** | ||
39 | ************************************** | ||
40 | ************************************** | ||
41 | <the original author of Another World> | ||
42 | |||
43 | Gregory Montoir | ||
44 | Piotr Padkowski | ||
45 | Fabien Sanglard | ||
46 | |||
47 | Rockbox porters: | ||
48 | ---------------- | ||
49 | |||
50 | Franklin Wei | ||
51 | Benjamin Brown | ||
diff --git a/apps/plugins/xworld/SOURCES b/apps/plugins/xworld/SOURCES new file mode 100644 index 0000000000..98b5f14fb8 --- /dev/null +++ b/apps/plugins/xworld/SOURCES | |||
@@ -0,0 +1,15 @@ | |||
1 | bank.c | ||
2 | engine.c | ||
3 | file.c | ||
4 | intern.c | ||
5 | mixer.c | ||
6 | parts.c | ||
7 | resource.c | ||
8 | serializer.c | ||
9 | sfxplayer.c | ||
10 | sys.c | ||
11 | util.c | ||
12 | video.c | ||
13 | video_data.c | ||
14 | vm.c | ||
15 | xworld.c | ||
diff --git a/apps/plugins/xworld/awendian.h b/apps/plugins/xworld/awendian.h new file mode 100644 index 0000000000..863b19bb47 --- /dev/null +++ b/apps/plugins/xworld/awendian.h | |||
@@ -0,0 +1,51 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ***************************************************************************/ | ||
21 | |||
22 | #ifndef __SYS_H__ | ||
23 | #define __SYS_H__ | ||
24 | |||
25 | #include "rbendian.h" | ||
26 | #include "stdint.h" | ||
27 | |||
28 | |||
29 | #ifdef ROCKBOX_LITTLE_ENDIAN | ||
30 | #define SYS_LITTLE_ENDIAN | ||
31 | #else | ||
32 | #define SYS_BIG_ENDIAN | ||
33 | #endif | ||
34 | |||
35 | |||
36 | #if defined SYS_LITTLE_ENDIAN | ||
37 | #define READ_BE_UINT16(p) ((((const uint8_t*)p)[0] << 8) | ((const uint8_t*)p)[1]) | ||
38 | #define READ_BE_UINT32(p) ((((const uint8_t*)p)[0] << 24) | (((const uint8_t*)p)[1] << 16) | (((const uint8_t*)p)[2] << 8) | ((const uint8_t*)p)[3]) | ||
39 | |||
40 | #elif defined SYS_BIG_ENDIAN | ||
41 | |||
42 | #define READ_BE_UINT16(p) (*(const uint16_t*)p) | ||
43 | #define READ_BE_UINT32(p) (*(const uint32_t*)p) | ||
44 | |||
45 | #else | ||
46 | |||
47 | #error No endianness defined | ||
48 | |||
49 | #endif | ||
50 | |||
51 | #endif | ||
diff --git a/apps/plugins/xworld/bank.c b/apps/plugins/xworld/bank.c new file mode 100644 index 0000000000..e2cfbd4680 --- /dev/null +++ b/apps/plugins/xworld/bank.c | |||
@@ -0,0 +1,153 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "bank.h" | ||
25 | #include "file.h" | ||
26 | #include "resource.h" | ||
27 | |||
28 | void bank_create(struct Bank* b, const char *dataDir) | ||
29 | { | ||
30 | b->_dataDir = dataDir; | ||
31 | } | ||
32 | |||
33 | bool bank_read(struct Bank* b, const struct MemEntry *me, uint8_t *buf) { | ||
34 | |||
35 | bool ret = false; | ||
36 | char bankName[10]; | ||
37 | rb->snprintf(bankName, 10, "bank%02x", me->bankId); | ||
38 | File f; | ||
39 | file_create(&f, false); | ||
40 | if (!file_open(&f, bankName, b->_dataDir, "rb")) | ||
41 | error("bank_read() unable to open '%s' in dir '%s'", bankName, b->_dataDir); | ||
42 | |||
43 | file_seek(&f, me->bankOffset); | ||
44 | |||
45 | /* Depending if the resource is packed or not we */ | ||
46 | /* can read directly or unpack it. */ | ||
47 | if (me->packedSize == me->size) { | ||
48 | file_read(&f, buf, me->packedSize); | ||
49 | ret = true; | ||
50 | } else { | ||
51 | file_read(&f, buf, me->packedSize); | ||
52 | b->_startBuf = buf; | ||
53 | b->_iBuf = buf + me->packedSize - 4; | ||
54 | ret = bank_unpack(b); | ||
55 | } | ||
56 | file_close(&f); | ||
57 | return ret; | ||
58 | } | ||
59 | |||
60 | void bank_decUnk1(struct Bank* b, uint8_t numChunks, uint8_t addCount) { | ||
61 | uint16_t count = bank_getCode(b, numChunks) + addCount + 1; | ||
62 | debug(DBG_BANK, "bank_decUnk1(%d, %d) count=%d", numChunks, addCount, count); | ||
63 | b->_unpCtx.datasize -= count; | ||
64 | while (count--) { | ||
65 | assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf); | ||
66 | *b->_oBuf = (uint8_t)bank_getCode(b, 8); | ||
67 | --b->_oBuf; | ||
68 | } | ||
69 | } | ||
70 | |||
71 | /* | ||
72 | Note from fab: This look like run-length encoding. | ||
73 | */ | ||
74 | void bank_decUnk2(struct Bank* b, uint8_t numChunks) { | ||
75 | uint16_t i = bank_getCode(b, numChunks); | ||
76 | uint16_t count = b->_unpCtx.size + 1; | ||
77 | debug(DBG_BANK, "bank_decUnk2(%d) i=%d count=%d", numChunks, i, count); | ||
78 | b->_unpCtx.datasize -= count; | ||
79 | while (count--) { | ||
80 | assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf); | ||
81 | *b->_oBuf = *(b->_oBuf + i); | ||
82 | --b->_oBuf; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | /* | ||
87 | Most resource in the banks are compacted. | ||
88 | */ | ||
89 | bool bank_unpack(struct Bank* b) { | ||
90 | b->_unpCtx.size = 0; | ||
91 | b->_unpCtx.datasize = READ_BE_UINT32(b->_iBuf); | ||
92 | b->_iBuf -= 4; | ||
93 | b->_oBuf = b->_startBuf + b->_unpCtx.datasize - 1; | ||
94 | b->_unpCtx.crc = READ_BE_UINT32(b->_iBuf); | ||
95 | b->_iBuf -= 4; | ||
96 | b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf); | ||
97 | b->_iBuf -= 4; | ||
98 | b->_unpCtx.crc ^= b->_unpCtx.chk; | ||
99 | do { | ||
100 | if (!bank_nextChunk(b)) { | ||
101 | b->_unpCtx.size = 1; | ||
102 | if (!bank_nextChunk(b)) { | ||
103 | bank_decUnk1(b, 3, 0); | ||
104 | } else { | ||
105 | bank_decUnk2(b, 8); | ||
106 | } | ||
107 | } else { | ||
108 | uint16_t c = bank_getCode(b, 2); | ||
109 | if (c == 3) { | ||
110 | bank_decUnk1(b, 8, 8); | ||
111 | } else { | ||
112 | if (c < 2) { | ||
113 | b->_unpCtx.size = c + 2; | ||
114 | bank_decUnk2(b, c + 9); | ||
115 | } else { | ||
116 | b->_unpCtx.size = bank_getCode(b, 8); | ||
117 | bank_decUnk2(b, 12); | ||
118 | } | ||
119 | } | ||
120 | } | ||
121 | } while (b->_unpCtx.datasize > 0); | ||
122 | return (b->_unpCtx.crc == 0); | ||
123 | } | ||
124 | |||
125 | uint16_t bank_getCode(struct Bank* b, uint8_t numChunks) { | ||
126 | uint16_t c = 0; | ||
127 | while (numChunks--) { | ||
128 | c <<= 1; | ||
129 | if (bank_nextChunk(b)) { | ||
130 | c |= 1; | ||
131 | } | ||
132 | } | ||
133 | return c; | ||
134 | } | ||
135 | |||
136 | bool bank_nextChunk(struct Bank* b) { | ||
137 | bool CF = bank_rcr(b, false); | ||
138 | if (b->_unpCtx.chk == 0) { | ||
139 | assert(b->_iBuf >= b->_startBuf); | ||
140 | b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf); | ||
141 | b->_iBuf -= 4; | ||
142 | b->_unpCtx.crc ^= b->_unpCtx.chk; | ||
143 | CF = bank_rcr(b, true); | ||
144 | } | ||
145 | return CF; | ||
146 | } | ||
147 | |||
148 | bool bank_rcr(struct Bank* b, bool CF) { | ||
149 | bool rCF = (b->_unpCtx.chk & 1); | ||
150 | b->_unpCtx.chk >>= 1; | ||
151 | if (CF) b->_unpCtx.chk |= 0x80000000; | ||
152 | return rCF; | ||
153 | } | ||
diff --git a/apps/plugins/xworld/bank.h b/apps/plugins/xworld/bank.h new file mode 100644 index 0000000000..6f280c5271 --- /dev/null +++ b/apps/plugins/xworld/bank.h | |||
@@ -0,0 +1,55 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __BANK_H__ | ||
24 | #define __BANK_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | struct MemEntry; | ||
29 | |||
30 | struct UnpackContext { | ||
31 | uint16_t size; | ||
32 | uint32_t crc; | ||
33 | uint32_t chk; | ||
34 | int32_t datasize; | ||
35 | }; | ||
36 | |||
37 | struct Bank | ||
38 | { | ||
39 | struct UnpackContext _unpCtx; | ||
40 | const char *_dataDir; | ||
41 | uint8_t *_iBuf, *_oBuf, *_startBuf; | ||
42 | }; | ||
43 | |||
44 | /* needs allocated memory */ | ||
45 | void bank_create(struct Bank*, const char *dataDir); | ||
46 | |||
47 | bool bank_read(struct Bank*, const struct MemEntry *me, uint8_t *buf); | ||
48 | void bank_decUnk1(struct Bank*, uint8_t numChunks, uint8_t addCount); | ||
49 | void bank_decUnk2(struct Bank*, uint8_t numChunks); | ||
50 | bool bank_unpack(struct Bank*); | ||
51 | uint16_t bank_getCode(struct Bank*, uint8_t numChunks); | ||
52 | bool bank_nextChunk(struct Bank*); | ||
53 | bool bank_rcr(struct Bank*, bool CF); | ||
54 | |||
55 | #endif | ||
diff --git a/apps/plugins/xworld/engine.c b/apps/plugins/xworld/engine.c new file mode 100644 index 0000000000..0d1c1bfa61 --- /dev/null +++ b/apps/plugins/xworld/engine.c | |||
@@ -0,0 +1,397 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "engine.h" | ||
25 | #include "file.h" | ||
26 | #include "serializer.h" | ||
27 | #include "sys.h" | ||
28 | #include "parts.h" | ||
29 | #include "video_data.h" | ||
30 | #include "video.h" | ||
31 | |||
32 | void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir) | ||
33 | { | ||
34 | e->sys = stub; | ||
35 | e->sys->e = e; | ||
36 | e->_dataDir = dataDir; | ||
37 | e->_saveDir = saveDir; | ||
38 | |||
39 | mixer_create(&e->mixer, e->sys); | ||
40 | |||
41 | /* this needs to be here and not engine_init() to ensure that it is not called on a reset */ | ||
42 | res_create(&e->res, &e->video, e->sys, dataDir); | ||
43 | |||
44 | res_allocMemBlock(&e->res); | ||
45 | |||
46 | video_create(&e->video, &e->res, e->sys); | ||
47 | |||
48 | player_create(&e->player, &e->mixer, &e->res, e->sys); | ||
49 | |||
50 | vm_create(&e->vm, &e->mixer, &e->res, &e->player, &e->video, e->sys); | ||
51 | } | ||
52 | |||
53 | void engine_run(struct Engine* e) { | ||
54 | |||
55 | while (!e->sys->input.quit) { | ||
56 | |||
57 | vm_checkThreadRequests(&e->vm); | ||
58 | |||
59 | vm_inp_updatePlayer(&e->vm); | ||
60 | |||
61 | engine_processInput(e); | ||
62 | |||
63 | vm_hostFrame(&e->vm); | ||
64 | } | ||
65 | |||
66 | } | ||
67 | |||
68 | /* | ||
69 | * this function loads the font in XWORLD_FONT_FILE into video_font | ||
70 | */ | ||
71 | |||
72 | /* | ||
73 | * the file format for the font file is like this: | ||
74 | * "XFNT" magic | ||
75 | * 8-bit version number | ||
76 | * <768 bytes data> | ||
77 | * sum of data, XOR'ed by version number repeated 4 times (32-bit) | ||
78 | */ | ||
79 | bool engine_loadFontFile(struct Engine* e) | ||
80 | { | ||
81 | uint8_t *old_font = sys_get_buffer(e->sys, sizeof(video_font)); | ||
82 | rb->memcpy(old_font, video_font, sizeof(video_font)); | ||
83 | |||
84 | File f; | ||
85 | file_create(&f, false); | ||
86 | if(!file_open(&f, XWORLD_FONT_FILE, e->_dataDir, "rb")) | ||
87 | { | ||
88 | goto fail; | ||
89 | } | ||
90 | |||
91 | /* read header */ | ||
92 | char header[5]; | ||
93 | int ret = file_read(&f, header, sizeof(header)); | ||
94 | if(ret != sizeof(header) || | ||
95 | header[0] != 'X' || | ||
96 | header[1] != 'F' || | ||
97 | header[2] != 'N' || | ||
98 | header[3] != 'T') | ||
99 | { | ||
100 | warning("Invalid font file signature, falling back to alternate font"); | ||
101 | goto fail; | ||
102 | } | ||
103 | |||
104 | if(header[4] != XWORLD_FONT_VERSION) | ||
105 | { | ||
106 | warning("Font file version mismatch (have=%d, need=%d), falling back to alternate font", header[4], XWORLD_FONT_VERSION); | ||
107 | goto fail; | ||
108 | } | ||
109 | |||
110 | uint32_t sum = 0; | ||
111 | for(unsigned int i = 0;i<sizeof(video_font);++i) | ||
112 | { | ||
113 | sum += video_font[i] = file_readByte(&f); | ||
114 | } | ||
115 | |||
116 | uint32_t mask = (header[4] << 24) | | ||
117 | (header[4] << 16) | | ||
118 | (header[4] << 8 ) | | ||
119 | (header[4] << 0 ); | ||
120 | sum ^= mask; | ||
121 | uint32_t check = file_readUint32BE(&f); | ||
122 | |||
123 | if(check != sum) | ||
124 | { | ||
125 | warning("Bad font checksum, falling back to alternate font"); | ||
126 | goto fail; | ||
127 | } | ||
128 | |||
129 | file_close(&f); | ||
130 | return true; | ||
131 | |||
132 | fail: | ||
133 | file_close(&f); | ||
134 | |||
135 | memcpy(video_font, old_font, sizeof(video_font)); | ||
136 | return false; | ||
137 | } | ||
138 | |||
139 | /* | ||
140 | * this function loads the string table in STRING_TABLE_FILE into | ||
141 | * video_stringsTableEng | ||
142 | */ | ||
143 | |||
144 | /* | ||
145 | * the file format for the string table is like this: | ||
146 | * "XWST" magic | ||
147 | * 8-bit version number | ||
148 | * 8-bit title length | ||
149 | * <title data (0-255 bytes, _NO NULL_) | ||
150 | * 16-bit number of string entries (currently limited to 255) | ||
151 | * entry format: | ||
152 | struct file_entry_t | ||
153 | { | ||
154 | uint16_t id; | ||
155 | uint16_t len; - length of str | ||
156 | char* str; - NO NULL | ||
157 | } | ||
158 | */ | ||
159 | bool engine_loadStringTable(struct Engine* e) | ||
160 | { | ||
161 | File f; | ||
162 | file_create(&f, false); | ||
163 | if(!file_open(&f, STRING_TABLE_FILE, e->_dataDir, "rb")) | ||
164 | { | ||
165 | /* | ||
166 | * this gives verbose warnings while loadFontFile doesn't because the font looks similar | ||
167 | * enough to pass for the "original", but the strings don't | ||
168 | */ | ||
169 | warning("Unable to find string table, falling back to alternate strings"); | ||
170 | goto fail; | ||
171 | } | ||
172 | |||
173 | /* read header */ | ||
174 | |||
175 | char header[5]; | ||
176 | int ret = file_read(&f, header, sizeof(header)); | ||
177 | if(ret != sizeof(header) || | ||
178 | header[0] != 'X' || | ||
179 | header[1] != 'W' || | ||
180 | header[2] != 'S' || | ||
181 | header[3] != 'T') | ||
182 | { | ||
183 | warning("Invalid string table signature, falling back to alternate strings"); | ||
184 | goto fail; | ||
185 | } | ||
186 | |||
187 | if(header[4] != STRING_TABLE_VERSION) | ||
188 | { | ||
189 | warning("String table version mismatch (have=%d, need=%d), falling back to alternate strings", header[4], STRING_TABLE_VERSION); | ||
190 | goto fail; | ||
191 | } | ||
192 | |||
193 | /* read title */ | ||
194 | |||
195 | uint8_t title_length = file_readByte(&f); | ||
196 | char *title_buf; | ||
197 | if(title_length) | ||
198 | { | ||
199 | title_buf = sys_get_buffer(e->sys, (int32_t)title_length + 1); /* make room for the NULL */ | ||
200 | ret = file_read(&f, title_buf, title_length); | ||
201 | if(ret != title_length) | ||
202 | { | ||
203 | warning("Title shorter than expected, falling back to alternate strings"); | ||
204 | goto fail; | ||
205 | } | ||
206 | } | ||
207 | else | ||
208 | { | ||
209 | title_buf = "UNKNOWN"; | ||
210 | } | ||
211 | |||
212 | /* read entries */ | ||
213 | |||
214 | uint16_t num_entries = file_readUint16BE(&f); | ||
215 | for(unsigned int i = 0; i < num_entries && i < ARRAYLEN(video_stringsTableEng); ++i) | ||
216 | { | ||
217 | video_stringsTableEng[i].id = file_readUint16BE(&f); | ||
218 | uint16_t len = file_readUint16BE(&f); | ||
219 | |||
220 | if(file_ioErr(&f)) | ||
221 | { | ||
222 | warning("Unexpected EOF in while parsing entry %d, falling back to alternate strings", i); | ||
223 | goto fail; | ||
224 | } | ||
225 | |||
226 | video_stringsTableEng[i].str = sys_get_buffer(e->sys, (int32_t)len + 1); | ||
227 | |||
228 | ret = file_read(&f, video_stringsTableEng[i].str, len); | ||
229 | if(ret != len) | ||
230 | { | ||
231 | warning("Entry %d too short, falling back to alternate strings", i); | ||
232 | goto fail; | ||
233 | } | ||
234 | } | ||
235 | |||
236 | file_close(&f); | ||
237 | rb->splashf(HZ, "String table '%s' loaded", title_buf); | ||
238 | return true; | ||
239 | fail: | ||
240 | file_close(&f); | ||
241 | return false; | ||
242 | } | ||
243 | |||
244 | void engine_init(struct Engine* e) { | ||
245 | sys_init(e->sys, "Out Of This World"); | ||
246 | |||
247 | res_readEntries(&e->res); | ||
248 | |||
249 | engine_loadStringTable(e); | ||
250 | |||
251 | engine_loadFontFile(e); | ||
252 | |||
253 | video_init(&e->video); | ||
254 | |||
255 | vm_init(&e->vm); | ||
256 | |||
257 | mixer_init(&e->mixer); | ||
258 | |||
259 | player_init(&e->player); | ||
260 | |||
261 | /* Init virtual machine, legacy way */ | ||
262 | /* vm_initForPart(&e->vm, GAME_PART_FIRST); // This game part is the protection screen */ | ||
263 | |||
264 | /* Try to cheat here. You can jump anywhere but the VM crashes afterward. */ | ||
265 | /* Starting somewhere is probably not enough, the variables and calls return are probably missing. */ | ||
266 | /* vm_initForPart(&e->vm, GAME_PART2); Skip protection screen and go directly to intro */ | ||
267 | /* vm_initForPart(&e->vm, GAME_PART3); CRASH */ | ||
268 | /* vm_initForPart(&e->vm, GAME_PART4); Start directly in jail but then crash */ | ||
269 | /* vm->initForPart(&e->vm, GAME_PART5); CRASH */ | ||
270 | /* vm->initForPart(GAME_PART6); Start in the battlechar but CRASH afteward */ | ||
271 | /* vm->initForPart(GAME_PART7); CRASH */ | ||
272 | /* vm->initForPart(GAME_PART8); CRASH */ | ||
273 | /* vm->initForPart(GAME_PART9); Green screen not doing anything */ | ||
274 | } | ||
275 | |||
276 | void engine_finish(struct Engine* e) { | ||
277 | player_free(&e->player); | ||
278 | mixer_free(&e->mixer); | ||
279 | res_freeMemBlock(&e->res); | ||
280 | } | ||
281 | |||
282 | void engine_processInput(struct Engine* e) { | ||
283 | if (e->sys->input.load) { | ||
284 | engine_loadGameState(e, e->_stateSlot); | ||
285 | e->sys->input.load = false; | ||
286 | } | ||
287 | if (e->sys->input.save) { | ||
288 | engine_saveGameState(e, e->_stateSlot, "quicksave"); | ||
289 | e->sys->input.save = false; | ||
290 | } | ||
291 | if (e->sys->input.fastMode) { | ||
292 | e->vm._fastMode = !&e->vm._fastMode; | ||
293 | e->sys->input.fastMode = false; | ||
294 | } | ||
295 | if (e->sys->input.stateSlot != 0) { | ||
296 | int8_t slot = e->_stateSlot + e->sys->input.stateSlot; | ||
297 | if (slot >= 0 && slot < MAX_SAVE_SLOTS) { | ||
298 | e->_stateSlot = slot; | ||
299 | debug(DBG_INFO, "Current game state slot is %d", e->_stateSlot); | ||
300 | } | ||
301 | e->sys->input.stateSlot = 0; | ||
302 | } | ||
303 | } | ||
304 | |||
305 | void engine_makeGameStateName(struct Engine* e, uint8_t slot, char *buf, int sz) { | ||
306 | (void) e; | ||
307 | rb->snprintf(buf, sz, "xworld_save.s%02d", slot); | ||
308 | } | ||
309 | |||
310 | void engine_saveGameState(struct Engine* e, uint8_t slot, const char *desc) { | ||
311 | char stateFile[20]; | ||
312 | /* sizeof(char) is guaranteed to be 1 */ | ||
313 | engine_makeGameStateName(e, slot, stateFile, sizeof(stateFile)); | ||
314 | File f; | ||
315 | file_create(&f, false); | ||
316 | if (!file_open(&f, stateFile, e->_saveDir, "wb")) { | ||
317 | warning("Unable to save state file '%s'", stateFile); | ||
318 | } else { | ||
319 | /* header */ | ||
320 | file_writeUint32BE(&f, SAVE_MAGIC); | ||
321 | file_writeUint16BE(&f, CUR_VER); | ||
322 | file_writeUint16BE(&f, 0); | ||
323 | char hdrdesc[32]; | ||
324 | strncpy(hdrdesc, desc, sizeof(hdrdesc) - 1); | ||
325 | file_write(&f, hdrdesc, sizeof(hdrdesc)); | ||
326 | /* contents */ | ||
327 | struct Serializer s; | ||
328 | ser_create(&s, &f, SM_SAVE, e->res._memPtrStart, CUR_VER); | ||
329 | vm_saveOrLoad(&e->vm, &s); | ||
330 | res_saveOrLoad(&e->res, &s); | ||
331 | video_saveOrLoad(&e->video, &s); | ||
332 | player_saveOrLoad(&e->player, &s); | ||
333 | mixer_saveOrLoad(&e->mixer, &s); | ||
334 | if (file_ioErr(&f)) { | ||
335 | warning("I/O error when saving game state"); | ||
336 | } else { | ||
337 | debug(DBG_INFO, "Saved state to slot %d", e->_stateSlot); | ||
338 | } | ||
339 | } | ||
340 | file_close(&f); | ||
341 | } | ||
342 | |||
343 | bool engine_loadGameState(struct Engine* e, uint8_t slot) { | ||
344 | char stateFile[20]; | ||
345 | engine_makeGameStateName(e, slot, stateFile, 20); | ||
346 | File f; | ||
347 | file_create(&f, false); | ||
348 | if (!file_open(&f, stateFile, e->_saveDir, "rb")) { | ||
349 | debug(DBG_ENG, "Unable to open state file '%s'", stateFile); | ||
350 | goto fail; | ||
351 | } else { | ||
352 | uint32_t id = file_readUint32BE(&f); | ||
353 | if (id != SAVE_MAGIC) { | ||
354 | debug(DBG_ENG, "Bad savegame format"); | ||
355 | goto fail; | ||
356 | } else { | ||
357 | /* mute */ | ||
358 | player_stop(&e->player); | ||
359 | mixer_stopAll(&e->mixer); | ||
360 | /* header */ | ||
361 | uint16_t ver = file_readUint16BE(&f); | ||
362 | file_readUint16BE(&f); | ||
363 | char hdrdesc[32]; | ||
364 | file_read(&f, hdrdesc, sizeof(hdrdesc)); | ||
365 | /* contents */ | ||
366 | struct Serializer s; | ||
367 | ser_create(&s, &f, SM_LOAD, e->res._memPtrStart, ver); | ||
368 | vm_saveOrLoad(&e->vm, &s); | ||
369 | res_saveOrLoad(&e->res, &s); | ||
370 | video_saveOrLoad(&e->video, &s); | ||
371 | player_saveOrLoad(&e->player, &s); | ||
372 | mixer_saveOrLoad(&e->mixer, &s); | ||
373 | } | ||
374 | if (file_ioErr(&f)) { | ||
375 | debug(DBG_ENG, "I/O error when loading game state"); | ||
376 | goto fail; | ||
377 | } else { | ||
378 | debug(DBG_INFO, "Loaded state from slot %d", e->_stateSlot); | ||
379 | } | ||
380 | } | ||
381 | file_close(&f); | ||
382 | return true; | ||
383 | fail: | ||
384 | file_close(&f); | ||
385 | return false; | ||
386 | } | ||
387 | |||
388 | void engine_deleteGameState(struct Engine* e, uint8_t slot) { | ||
389 | char stateFile[20]; | ||
390 | engine_makeGameStateName(e, slot, stateFile, 20); | ||
391 | file_remove(stateFile, e->_saveDir); | ||
392 | } | ||
393 | |||
394 | const char* engine_getDataDir(struct Engine* e) | ||
395 | { | ||
396 | return e->_dataDir; | ||
397 | } | ||
diff --git a/apps/plugins/xworld/engine.h b/apps/plugins/xworld/engine.h new file mode 100644 index 0000000000..a0ad2b469e --- /dev/null +++ b/apps/plugins/xworld/engine.h | |||
@@ -0,0 +1,70 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __ENGINE_H__ | ||
24 | #define __ENGINE_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | #include "vm.h" | ||
28 | #include "mixer.h" | ||
29 | #include "sfxplayer.h" | ||
30 | #include "resource.h" | ||
31 | #include "video.h" | ||
32 | #include "sys.h" | ||
33 | |||
34 | #define STRING_TABLE_FILE "xworld.strings" /* this is relative to dataDir */ | ||
35 | #define STRING_TABLE_VERSION 0x03 | ||
36 | |||
37 | #define XWORLD_FONT_FILE "xworld.font" /* relative to dataDir */ | ||
38 | #define XWORLD_FONT_VERSION 0x01 | ||
39 | |||
40 | struct System; | ||
41 | |||
42 | #define MAX_SAVE_SLOTS 1 | ||
43 | #define SAVE_MAGIC 0x42424657 | ||
44 | struct Engine { | ||
45 | struct System *sys; | ||
46 | struct VirtualMachine vm; | ||
47 | struct Mixer mixer; | ||
48 | struct Resource res; | ||
49 | struct SfxPlayer player; | ||
50 | struct Video video; | ||
51 | const char *_dataDir, *_saveDir; | ||
52 | uint8_t _stateSlot; | ||
53 | }; | ||
54 | |||
55 | void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir); | ||
56 | |||
57 | void engine_run(struct Engine*); | ||
58 | void engine_init(struct Engine*); | ||
59 | void engine_finish(struct Engine*); | ||
60 | void engine_processInput(struct Engine*); | ||
61 | |||
62 | bool engine_loadFontFile(struct Engine*); | ||
63 | bool engine_loadStringTable(struct Engine*); | ||
64 | |||
65 | void engine_makeGameStateName(struct Engine*, uint8_t slot, char *buf, int sz); | ||
66 | void engine_saveGameState(struct Engine*, uint8_t slot, const char *desc); | ||
67 | bool engine_loadGameState(struct Engine*, uint8_t slot); | ||
68 | void engine_deleteGameState(struct Engine*, uint8_t slot); | ||
69 | const char* engine_getDataDir(struct Engine*); | ||
70 | #endif | ||
diff --git a/apps/plugins/xworld/file.c b/apps/plugins/xworld/file.c new file mode 100644 index 0000000000..8353544ef5 --- /dev/null +++ b/apps/plugins/xworld/file.c | |||
@@ -0,0 +1,172 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "file.h" | ||
25 | |||
26 | void file_create(struct File* f, bool gzipped) { | ||
27 | f->gzipped = gzipped; | ||
28 | f->fd = -1; | ||
29 | f->ioErr = false; | ||
30 | } | ||
31 | |||
32 | bool file_open(struct File* f, const char *filename, const char *directory, const char *mode) { | ||
33 | char buf[512]; | ||
34 | rb->snprintf(buf, 512, "%s/%s", directory, filename); | ||
35 | char *p = buf + rb->strlen(directory) + 1; | ||
36 | string_lower(p); | ||
37 | |||
38 | int flags = 0; | ||
39 | for(int i = 0; mode[i]; ++i) | ||
40 | { | ||
41 | switch(mode[i]) | ||
42 | { | ||
43 | case 'w': | ||
44 | flags |= O_WRONLY | O_CREAT | O_TRUNC; | ||
45 | break; | ||
46 | case 'r': | ||
47 | flags |= O_RDONLY; | ||
48 | break; | ||
49 | default: | ||
50 | break; | ||
51 | } | ||
52 | } | ||
53 | f->fd = -1; | ||
54 | debug(DBG_FILE, "trying %s first", buf); | ||
55 | f->fd = rb->open(buf, flags, 0666); | ||
56 | if (f->fd < 0) { // let's try uppercase | ||
57 | string_upper(p); | ||
58 | debug(DBG_FILE, "now trying %s uppercase", buf); | ||
59 | f->fd = rb->open(buf, flags, 0666); | ||
60 | } | ||
61 | if(f->fd > 0) | ||
62 | return true; | ||
63 | else | ||
64 | return false; | ||
65 | } | ||
66 | |||
67 | void file_close(struct File* f) { | ||
68 | if(f->gzipped) | ||
69 | { | ||
70 | } | ||
71 | else | ||
72 | { | ||
73 | rb->close(f->fd); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | bool file_ioErr(struct File* f) { | ||
78 | return f->ioErr; | ||
79 | } | ||
80 | |||
81 | void file_seek(struct File* f, int32_t off) { | ||
82 | if(f->gzipped) | ||
83 | { | ||
84 | } | ||
85 | else | ||
86 | { | ||
87 | rb->lseek(f->fd, off, SEEK_SET); | ||
88 | } | ||
89 | } | ||
90 | int file_read(struct File* f, void *ptr, uint32_t size) { | ||
91 | if(f->gzipped) | ||
92 | { | ||
93 | return -1; | ||
94 | } | ||
95 | else | ||
96 | { | ||
97 | unsigned int rc = rb->read(f->fd, ptr, size); | ||
98 | if(rc != size) | ||
99 | f->ioErr = true; | ||
100 | return rc; | ||
101 | } | ||
102 | } | ||
103 | uint8_t file_readByte(struct File* f) { | ||
104 | uint8_t b; | ||
105 | if(f->gzipped) | ||
106 | { | ||
107 | b = 0xff; | ||
108 | } | ||
109 | else | ||
110 | { | ||
111 | if(rb->read(f->fd, &b, 1) != 1) | ||
112 | { | ||
113 | f->ioErr = true; | ||
114 | debug(DBG_FILE, "file read failed"); | ||
115 | } | ||
116 | } | ||
117 | return b; | ||
118 | } | ||
119 | |||
120 | uint16_t file_readUint16BE(struct File* f) { | ||
121 | uint8_t hi = file_readByte(f); | ||
122 | uint8_t lo = file_readByte(f); | ||
123 | return (hi << 8) | lo; | ||
124 | } | ||
125 | |||
126 | uint32_t file_readUint32BE(struct File* f) { | ||
127 | uint16_t hi = file_readUint16BE(f); | ||
128 | uint16_t lo = file_readUint16BE(f); | ||
129 | return (hi << 16) | lo; | ||
130 | } | ||
131 | |||
132 | int file_write(struct File* f, void *ptr, uint32_t size) { | ||
133 | if(f->gzipped) | ||
134 | { | ||
135 | return 0; | ||
136 | } | ||
137 | else | ||
138 | { | ||
139 | return rb->write(f->fd, ptr, size); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | void file_writeByte(struct File* f, uint8_t b) { | ||
144 | file_write(f, &b, 1); | ||
145 | } | ||
146 | |||
147 | void file_writeUint16BE(struct File* f, uint16_t n) { | ||
148 | file_writeByte(f, n >> 8); | ||
149 | file_writeByte(f, n & 0xFF); | ||
150 | } | ||
151 | |||
152 | void file_writeUint32BE(struct File* f, uint32_t n) { | ||
153 | file_writeUint16BE(f, n >> 16); | ||
154 | file_writeUint16BE(f, n & 0xFFFF); | ||
155 | } | ||
156 | |||
157 | void file_remove(const char* filename, const char* directory) | ||
158 | { | ||
159 | char buf[512]; | ||
160 | rb->snprintf(buf, 512, "%s/%s", directory, filename); | ||
161 | char *p = buf + rb->strlen(directory) + 1; | ||
162 | string_lower(p); | ||
163 | if(rb->file_exists(buf)) | ||
164 | { | ||
165 | rb->remove(buf); | ||
166 | } | ||
167 | else | ||
168 | { | ||
169 | string_upper(p); | ||
170 | rb->remove(buf); | ||
171 | } | ||
172 | } | ||
diff --git a/apps/plugins/xworld/file.h b/apps/plugins/xworld/file.h new file mode 100644 index 0000000000..b24cdaf19e --- /dev/null +++ b/apps/plugins/xworld/file.h | |||
@@ -0,0 +1,51 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __FILE_H__ | ||
24 | #define __FILE_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | typedef struct File { | ||
29 | int fd; | ||
30 | bool gzipped; | ||
31 | bool ioErr; | ||
32 | } File; | ||
33 | |||
34 | void file_create(struct File*, bool gzipped); | ||
35 | |||
36 | bool file_open(struct File*, const char *filename, const char *directory, const char *mode); | ||
37 | void file_close(struct File*); | ||
38 | bool file_ioErr(struct File*); | ||
39 | void file_seek(struct File*, int32_t off); | ||
40 | int file_read(struct File*, void *ptr, uint32_t size); | ||
41 | uint8_t file_readByte(struct File*); | ||
42 | uint16_t file_readUint16BE(struct File*); | ||
43 | uint32_t file_readUint32BE(struct File*); | ||
44 | int file_write(struct File*, void *ptr, uint32_t size); | ||
45 | void file_writeByte(struct File*, uint8_t b); | ||
46 | void file_writeUint16BE(struct File*, uint16_t n); | ||
47 | void file_writeUint32BE(struct File*, uint32_t n); | ||
48 | |||
49 | void file_remove(const char* filename, const char* directory); | ||
50 | |||
51 | #endif | ||
diff --git a/apps/plugins/xworld/intern.c b/apps/plugins/xworld/intern.c new file mode 100644 index 0000000000..0d58100f37 --- /dev/null +++ b/apps/plugins/xworld/intern.c | |||
@@ -0,0 +1,34 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "intern.h" | ||
24 | #include "awendian.h" | ||
25 | |||
26 | uint8_t ICODE_ATTR scriptPtr_fetchByte(struct Ptr* p) { | ||
27 | return *p->pc++; | ||
28 | } | ||
29 | |||
30 | uint16_t ICODE_ATTR scriptPtr_fetchWord(struct Ptr* p) { | ||
31 | uint16_t i = READ_BE_UINT16(p->pc); | ||
32 | p->pc += 2; | ||
33 | return i; | ||
34 | } | ||
diff --git a/apps/plugins/xworld/intern.h b/apps/plugins/xworld/intern.h new file mode 100644 index 0000000000..3355f28eb1 --- /dev/null +++ b/apps/plugins/xworld/intern.h | |||
@@ -0,0 +1,44 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __INTERN_H__ | ||
24 | #define __INTERN_H__ | ||
25 | |||
26 | #include "plugin.h" | ||
27 | #include "string.h" | ||
28 | #include "awendian.h" | ||
29 | #include "util.h" | ||
30 | |||
31 | #define assert(c) (c?(void)0:error("Assertion failed line %d, file %s", __LINE__, __FILE__)) | ||
32 | |||
33 | struct Ptr { | ||
34 | uint8_t* pc; | ||
35 | }; | ||
36 | |||
37 | uint8_t scriptPtr_fetchByte(struct Ptr* p) ICODE_ATTR; | ||
38 | uint16_t scriptPtr_fetchWord(struct Ptr* p) ICODE_ATTR; | ||
39 | |||
40 | struct Point { | ||
41 | int16_t x, y; | ||
42 | }; | ||
43 | |||
44 | #endif | ||
diff --git a/apps/plugins/xworld/keymaps.h b/apps/plugins/xworld/keymaps.h new file mode 100644 index 0000000000..edba05b9d2 --- /dev/null +++ b/apps/plugins/xworld/keymaps.h | |||
@@ -0,0 +1,183 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ***************************************************************************/ | ||
21 | |||
22 | #ifndef _XWORLD_KEYMAPS_H | ||
23 | #define _XWORLD_KEYMAPS_H | ||
24 | #endif | ||
25 | |||
26 | #if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) || \ | ||
27 | (CONFIG_KEYPAD == PHILIPS_HDD6330_PAD) || \ | ||
28 | (CONFIG_KEYPAD == PHILIPS_SA9200_PAD) || \ | ||
29 | (CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \ | ||
30 | (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \ | ||
31 | (CONFIG_KEYPAD == SANSA_CONNECT_PAD) || \ | ||
32 | (CONFIG_KEYPAD == SANSA_C200_PAD) || \ | ||
33 | (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \ | ||
34 | (CONFIG_KEYPAD == SANSA_E200_PAD) || \ | ||
35 | (CONFIG_KEYPAD == SANSA_FUZE_PAD) || \ | ||
36 | (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || \ | ||
37 | (CONFIG_KEYPAD == GIGABEAT_PAD) || \ | ||
38 | (CONFIG_KEYPAD == GIGABEAT_S_PAD) || \ | ||
39 | (CONFIG_KEYPAD == SAMSUNG_YH920_PAD) || \ | ||
40 | (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ | ||
41 | (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) || \ | ||
42 | (CONFIG_KEYPAD == CREATIVE_ZEN_PAD) || \ | ||
43 | (CONFIG_KEYPAD == SONY_NWZ_PAD) || \ | ||
44 | (CONFIG_KEYPAD == CREATIVEZVM_PAD) || \ | ||
45 | (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) || \ | ||
46 | (CONFIG_KEYPAD == IRIVER_H300_PAD) || \ | ||
47 | (CONFIG_KEYPAD == HM801_PAD) | ||
48 | #define BTN_UP BUTTON_UP | ||
49 | #define BTN_DOWN BUTTON_DOWN | ||
50 | #define BTN_LEFT BUTTON_LEFT | ||
51 | #define BTN_RIGHT BUTTON_RIGHT | ||
52 | |||
53 | #if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) | ||
54 | #define BTN_UP_LEFT BUTTON_BACK | ||
55 | #define BTN_UP_RIGHT BUTTON_PLAYPAUSE | ||
56 | #define BTN_DOWN_LEFT BUTTON_BOTTOMLEFT | ||
57 | #define BTN_DOWN_RIGHT BUTTON_BOTTOMRIGHT | ||
58 | #endif | ||
59 | |||
60 | #if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) || \ | ||
61 | (CONFIG_KEYPAD == PHILIPS_HDD6330_PAD) || \ | ||
62 | (CONFIG_KEYPAD == PHILIPS_SA9200_PAD) || \ | ||
63 | (CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \ | ||
64 | (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \ | ||
65 | (CONFIG_KEYPAD == SANSA_CONNECT_PAD) || \ | ||
66 | (CONFIG_KEYPAD == SANSA_C200_PAD) || \ | ||
67 | (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || \ | ||
68 | (CONFIG_KEYPAD == DX50_PAD) || \ | ||
69 | (CONFIG_KEYPAD == ONDAVX747_PAD) | ||
70 | #define BTN_FIRE BUTTON_VOL_UP | ||
71 | #define BTN_PAUSE BUTTON_VOL_DOWN | ||
72 | |||
73 | #elif (CONFIG_KEYPAD == SANSA_FUZE_PAD) | ||
74 | #define BTN_FIRE BUTTON_HOME | ||
75 | #define BTN_PAUSE BUTTON_SELECT | ||
76 | |||
77 | #elif (CONFIG_KEYPAD == SAMSUNG_YH920_PAD) | ||
78 | #define BTN_FIRE BUTTON_FFWD | ||
79 | #define BTN_PAUSE BUTTON_REW | ||
80 | |||
81 | #elif (CONFIG_KEYPAD == SANSA_E200_PAD) | ||
82 | #define BTN_FIRE BUTTON_REC | ||
83 | #define BTN_PAUSE BUTTON_POWER | ||
84 | |||
85 | #elif (CONFIG_KEYPAD == SANSA_CLIP_PAD) | ||
86 | #define BTN_FIRE BUTTON_SELECT | ||
87 | #define BTN_PAUSE BUTTON_POWER | ||
88 | |||
89 | #elif (CONFIG_KEYPAD == CREATIVE_ZEN_PAD) | ||
90 | #define BTN_FIRE BUTTON_SELECT | ||
91 | #define BTN_PAUSE BUTTON_BACK | ||
92 | |||
93 | #elif (CONFIG_KEYPAD == CREATIVEZVM_PAD) | ||
94 | #define BTN_FIRE BUTTON_PLAY | ||
95 | #define BTN_PAUSE BUTTON_MENU | ||
96 | |||
97 | #elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) | ||
98 | #define BTN_FIRE BUTTON_USER | ||
99 | #define BTN_PAUSE BUTTON_MENU | ||
100 | |||
101 | #elif (CONFIG_KEYPAD == SONY_NWZ_PAD) | ||
102 | #define BTN_FIRE BUTTON_PLAY | ||
103 | #define BTN_PAUSE BUTTON_BACK | ||
104 | |||
105 | #elif (CONFIG_KEYPAD == IRIVER_H300_PAD) | ||
106 | #define BTN_FIRE BUTTON_REC | ||
107 | #define BTN_PAUSE BUTTON_MODE | ||
108 | |||
109 | #elif (CONFIG_KEYPAD == HM801_PAD) | ||
110 | #define BTN_FIRE BUTTON_PREV | ||
111 | #define BTN_PAUSE BUTTON_NEXT | ||
112 | |||
113 | #elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ | ||
114 | (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) | ||
115 | #define BTN_FIRE BUTTON_REC | ||
116 | #define BTN_PAUSE BUTTON_PLAY | ||
117 | |||
118 | #elif (CONFIG_KEYPAD == GIGABEAT_PAD) || \ | ||
119 | (CONFIG_KEYPAD == GIGABEAT_S_PAD) | ||
120 | #define BTN_FIRE BUTTON_VOL_UP | ||
121 | #define BTN_PAUSE BUTTON_MENU | ||
122 | #endif | ||
123 | |||
124 | #elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD) | ||
125 | #define BTN_UP BUTTON_OK | ||
126 | #define BTN_DOWN BUTTON_CANCEL | ||
127 | #define BTN_LEFT BUTTON_MENU | ||
128 | #define BTN_RIGHT BUTTON_PLAY | ||
129 | #define BTN_FIRE BUTTON_POWER | ||
130 | #define BTN_PAUSE BUTTON_REC | ||
131 | |||
132 | #elif (CONFIG_KEYPAD == IRIVER_H10_PAD) | ||
133 | #define BTN_UP BUTTON_SCROLL_UP | ||
134 | #define BTN_DOWN BUTTON_SCROLL_DOWN | ||
135 | #define BTN_LEFT BUTTON_LEFT | ||
136 | #define BTN_RIGHT BUTTON_RIGHT | ||
137 | #define BTN_FIRE BUTTON_REW | ||
138 | #define BTN_PAUSE BUTTON_PLAY | ||
139 | |||
140 | #elif (CONFIG_KEYPAD == MROBE500_PAD) | ||
141 | #define BTN_FIRE BUTTON_POWER | ||
142 | |||
143 | #elif (CONFIG_KEYPAD == MROBE_REMOTE) | ||
144 | #define BTN_UP BUTTON_RC_PLAY | ||
145 | #define BTN_DOWN BUTTON_RC_DOWN | ||
146 | #define BTN_LEFT BUTTON_RC_REW | ||
147 | #define BTN_RIGHT BUTTON_RC_FF | ||
148 | |||
149 | #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ | ||
150 | (CONFIG_KEYPAD == IPOD_3G_PAD) || \ | ||
151 | (CONFIG_KEYPAD == IPOD_1G2G_PAD) | ||
152 | #define BTN_UP BUTTON_MENU | ||
153 | #define BTN_DOWN BUTTON_PLAY | ||
154 | #define BTN_LEFT BUTTON_LEFT | ||
155 | #define BTN_RIGHT BUTTON_RIGHT | ||
156 | #define BTN_FIRE BUTTON_SELECT | ||
157 | #define BTN_PAUSE (BUTTON_MENU | BUTTON_SELECT) | ||
158 | |||
159 | #elif (CONFIG_KEYPAD == ONDAVX777_PAD) | ||
160 | #define BTN_FIRE BUTTON_POWER | ||
161 | |||
162 | #elif (CONFIG_KEYPAD == DX50_PAD) | ||
163 | #define BTN_FIRE BUTTON_PLUS | ||
164 | #define BTN_PAUSE BUTTON_MENU | ||
165 | |||
166 | #else | ||
167 | #error Unsupported keypad | ||
168 | #endif | ||
169 | |||
170 | #ifdef HAVE_TOUCHSCREEN | ||
171 | #define BTN_UP BUTTON_TOPMIDDLE | ||
172 | #define BTN_DOWN BUTTON_BOTTOMMIDDLE | ||
173 | #define BTN_LEFT BUTTON_LEFT | ||
174 | #define BTN_RIGHT BUTTON_RIGHT | ||
175 | |||
176 | #if (CONFIG_KEYPAD == MROBE500_PAD) | ||
177 | #define BTN_PAUSE BUTTON_BOTTOMLEFT | ||
178 | |||
179 | #elif (CONFIG_KEYPAD != COWON_D2_PAD) || (CONFIG_KEYPAD != DX50_PAD) | ||
180 | #define BTN_FIRE BUTTON_BOTTOMLEFT | ||
181 | #define BTN_PAUSE BUTTON_TOPLEFT | ||
182 | #endif | ||
183 | #endif | ||
diff --git a/apps/plugins/xworld/mixer.c b/apps/plugins/xworld/mixer.c new file mode 100644 index 0000000000..de7536cd36 --- /dev/null +++ b/apps/plugins/xworld/mixer.c | |||
@@ -0,0 +1,199 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "mixer.h" | ||
24 | #include "serializer.h" | ||
25 | #include "sys.h" | ||
26 | |||
27 | static int8_t ICODE_ATTR addclamp(int a, int b) { | ||
28 | int add = a + b; | ||
29 | if (add < -128) { | ||
30 | add = -128; | ||
31 | } | ||
32 | else if (add > 127) { | ||
33 | add = 127; | ||
34 | } | ||
35 | return (int8_t)add; | ||
36 | } | ||
37 | |||
38 | void mixer_create(struct Mixer* mx, struct System *stub) | ||
39 | { | ||
40 | mx->sys = stub; | ||
41 | } | ||
42 | |||
43 | static void mixer_mixCallback(void *param, uint8_t *buf, int len); | ||
44 | |||
45 | void mixer_init(struct Mixer* mx) { | ||
46 | rb->memset(mx->_channels, 0, sizeof(mx->_channels)); | ||
47 | if(!mx->sys) | ||
48 | { | ||
49 | error("in mixer sys is NULL"); | ||
50 | } | ||
51 | mx->_mutex = sys_createMutex(mx->sys); | ||
52 | sys_startAudio(mx->sys, mixer_mixCallback, mx); | ||
53 | } | ||
54 | |||
55 | void mixer_free(struct Mixer* mx) { | ||
56 | mixer_stopAll(mx); | ||
57 | sys_stopAudio(mx->sys); | ||
58 | sys_destroyMutex(mx->sys, mx->_mutex); | ||
59 | } | ||
60 | |||
61 | void mixer_playChannel(struct Mixer* mx, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume) { | ||
62 | debug(DBG_SND, "mixer_playChannel(%d, %d, %d)", channel, freq, volume); | ||
63 | assert(channel < AUDIO_NUM_CHANNELS); | ||
64 | |||
65 | /* FW: the mutex code was converted 1:1 from C++ to C, leading to the ugly calls */ | ||
66 | /* to constructors/destructors as seen here */ | ||
67 | |||
68 | struct MutexStack_t ms; | ||
69 | MutexStack(&ms, mx->sys, mx->_mutex); | ||
70 | |||
71 | struct MixerChannel *ch = &mx->_channels[channel]; | ||
72 | ch->active = true; | ||
73 | ch->volume = volume; | ||
74 | ch->chunk = *mc; | ||
75 | ch->chunkPos = 0; | ||
76 | ch->chunkInc = (freq << 8) / sys_getOutputSampleRate(mx->sys); | ||
77 | |||
78 | MutexStack_destroy(&ms); | ||
79 | } | ||
80 | |||
81 | void mixer_stopChannel(struct Mixer* mx, uint8_t channel) { | ||
82 | debug(DBG_SND, "mixer_stopChannel(%d)", channel); | ||
83 | assert(channel < AUDIO_NUM_CHANNELS); | ||
84 | |||
85 | struct MutexStack_t ms; | ||
86 | MutexStack(&ms, mx->sys, mx->_mutex); | ||
87 | |||
88 | mx->_channels[channel].active = false; | ||
89 | |||
90 | MutexStack_destroy(&ms); | ||
91 | } | ||
92 | |||
93 | void mixer_setChannelVolume(struct Mixer* mx, uint8_t channel, uint8_t volume) { | ||
94 | debug(DBG_SND, "mixer_setChannelVolume(%d, %d)", channel, volume); | ||
95 | assert(channel < AUDIO_NUM_CHANNELS); | ||
96 | |||
97 | struct MutexStack_t ms; | ||
98 | MutexStack(&ms, mx->sys, mx->_mutex); | ||
99 | |||
100 | mx->_channels[channel].volume = volume; | ||
101 | |||
102 | MutexStack_destroy(&ms); | ||
103 | } | ||
104 | |||
105 | void mixer_stopAll(struct Mixer* mx) { | ||
106 | debug(DBG_SND, "mixer_stopAll()"); | ||
107 | |||
108 | struct MutexStack_t ms; | ||
109 | MutexStack(&ms, mx->sys, mx->_mutex); | ||
110 | |||
111 | for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) { | ||
112 | mx->_channels[i].active = false; | ||
113 | } | ||
114 | |||
115 | MutexStack_destroy(&ms); | ||
116 | } | ||
117 | |||
118 | /* Mx is SDL callback. Called in order to populate the buf with len bytes. */ | ||
119 | /* The mixer iterates through all active channels and combine all sounds. */ | ||
120 | |||
121 | /* Since there is no way to know when SDL will ask for a buffer fill, we need */ | ||
122 | /* to synchronize with a mutex so the channels remain stable during the execution */ | ||
123 | /* of this method. */ | ||
124 | static void ICODE_ATTR mixer_mix(struct Mixer* mx, int8_t *buf, int len) { | ||
125 | int8_t *pBuf; | ||
126 | |||
127 | struct MutexStack_t ms; | ||
128 | MutexStack(&ms, mx->sys, mx->_mutex); | ||
129 | |||
130 | /* Clear the buffer since nothing guarantees we are receiving clean memory. */ | ||
131 | rb->memset(buf, 0, len); | ||
132 | |||
133 | for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) { | ||
134 | struct MixerChannel *ch = &mx->_channels[i]; | ||
135 | if (!ch->active) | ||
136 | continue; | ||
137 | |||
138 | pBuf = buf; | ||
139 | for (int j = 0; j < len; ++j, ++pBuf) { | ||
140 | |||
141 | uint16_t p1, p2; | ||
142 | uint16_t ilc = (ch->chunkPos & 0xFF); | ||
143 | p1 = ch->chunkPos >> 8; | ||
144 | ch->chunkPos += ch->chunkInc; | ||
145 | |||
146 | if (ch->chunk.loopLen != 0) { | ||
147 | if (p1 == ch->chunk.loopPos + ch->chunk.loopLen - 1) { | ||
148 | debug(DBG_SND, "Looping sample on channel %d", i); | ||
149 | ch->chunkPos = p2 = ch->chunk.loopPos; | ||
150 | } else { | ||
151 | p2 = p1 + 1; | ||
152 | } | ||
153 | } else { | ||
154 | if (p1 == ch->chunk.len - 1) { | ||
155 | debug(DBG_SND, "Stopping sample on channel %d", i); | ||
156 | ch->active = false; | ||
157 | break; | ||
158 | } else { | ||
159 | p2 = p1 + 1; | ||
160 | } | ||
161 | } | ||
162 | /* interpolate */ | ||
163 | int8_t b1 = *(int8_t *)(ch->chunk.data + p1); | ||
164 | int8_t b2 = *(int8_t *)(ch->chunk.data + p2); | ||
165 | int8_t b = (int8_t)((b1 * (0xFF - ilc) + b2 * ilc) >> 8); | ||
166 | |||
167 | /* set volume and clamp */ | ||
168 | *pBuf = addclamp(*pBuf, (int)b * ch->volume / 0x40); /* 0x40=64 */ | ||
169 | } | ||
170 | |||
171 | } | ||
172 | |||
173 | MutexStack_destroy(&ms); | ||
174 | } | ||
175 | |||
176 | static void ICODE_ATTR mixer_mixCallback(void *param, uint8_t *buf, int len) { | ||
177 | debug(DBG_SND, "mixer_mixCallback"); | ||
178 | mixer_mix((struct Mixer*)param, (int8_t *)buf, len); | ||
179 | } | ||
180 | |||
181 | void mixer_saveOrLoad(struct Mixer* mx, struct Serializer *ser) { | ||
182 | sys_lockMutex(mx->sys, mx->_mutex); | ||
183 | for (int i = 0; i < AUDIO_NUM_CHANNELS; ++i) { | ||
184 | struct MixerChannel *ch = &mx->_channels[i]; | ||
185 | struct Entry entries[] = { | ||
186 | SE_INT(&ch->active, SES_BOOL, VER(2)), | ||
187 | SE_INT(&ch->volume, SES_INT8, VER(2)), | ||
188 | SE_INT(&ch->chunkPos, SES_INT32, VER(2)), | ||
189 | SE_INT(&ch->chunkInc, SES_INT32, VER(2)), | ||
190 | SE_PTR(&ch->chunk.data, VER(2)), | ||
191 | SE_INT(&ch->chunk.len, SES_INT16, VER(2)), | ||
192 | SE_INT(&ch->chunk.loopPos, SES_INT16, VER(2)), | ||
193 | SE_INT(&ch->chunk.loopLen, SES_INT16, VER(2)), | ||
194 | SE_END() | ||
195 | }; | ||
196 | ser_saveOrLoadEntries(ser, entries); | ||
197 | } | ||
198 | sys_unlockMutex(mx->sys, mx->_mutex); | ||
199 | }; | ||
diff --git a/apps/plugins/xworld/mixer.h b/apps/plugins/xworld/mixer.h new file mode 100644 index 0000000000..9447bcfb81 --- /dev/null +++ b/apps/plugins/xworld/mixer.h | |||
@@ -0,0 +1,69 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __MIXER_H__ | ||
24 | #define __MIXER_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | struct MixerChunk { | ||
29 | const uint8_t *data; | ||
30 | uint16_t len; | ||
31 | uint16_t loopPos; | ||
32 | uint16_t loopLen; | ||
33 | }; | ||
34 | |||
35 | struct MixerChannel { | ||
36 | uint8_t active; | ||
37 | uint8_t volume; | ||
38 | struct MixerChunk chunk; | ||
39 | uint32_t chunkPos; | ||
40 | uint32_t chunkInc; | ||
41 | }; | ||
42 | |||
43 | struct Serializer; | ||
44 | struct System; | ||
45 | |||
46 | #define AUDIO_NUM_CHANNELS 4 | ||
47 | |||
48 | struct Mixer { | ||
49 | void *_mutex; | ||
50 | struct System *sys; | ||
51 | |||
52 | /* Since the virtual machine and SDL are running simultaneously in two different threads */ | ||
53 | /* any read or write to an elements of the sound channels MUST be synchronized with a */ | ||
54 | /* mutex. */ | ||
55 | struct MixerChannel _channels[AUDIO_NUM_CHANNELS]; | ||
56 | }; | ||
57 | |||
58 | void mixer_create(struct Mixer*, struct System *stub); | ||
59 | void mixer_init(struct Mixer*); | ||
60 | void mixer_free(struct Mixer*); | ||
61 | |||
62 | void mixer_playChannel(struct Mixer*, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume); | ||
63 | void mixer_stopChannel(struct Mixer*, uint8_t channel); | ||
64 | void mixer_setChannelVolume(struct Mixer*, uint8_t channel, uint8_t volume); | ||
65 | void mixer_stopAll(struct Mixer*); | ||
66 | |||
67 | void mixer_saveOrLoad(struct Mixer*, struct Serializer *ser); | ||
68 | |||
69 | #endif | ||
diff --git a/apps/plugins/xworld/parts.c b/apps/plugins/xworld/parts.c new file mode 100644 index 0000000000..12b0b3b7b6 --- /dev/null +++ b/apps/plugins/xworld/parts.c | |||
@@ -0,0 +1,56 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "parts.h" | ||
24 | |||
25 | |||
26 | /* | ||
27 | #define MEMLIST_PART_PALETTE 0 | ||
28 | #define MEMLIST_PART_CODE 1 | ||
29 | #define MEMLIST_PART_VIDEO1 2 | ||
30 | #define MEMLIST_PART_VIDEO2 3 | ||
31 | */ | ||
32 | |||
33 | /* | ||
34 | MEMLIST_PART_VIDEO1 and MEMLIST_PART_VIDEO2 are used to store polygons. | ||
35 | |||
36 | It seems that: | ||
37 | - MEMLIST_PART_VIDEO1 contains the cinematic polygons. | ||
38 | - MEMLIST_PART_VIDEO2 contains the polygons for player and enemies animations. | ||
39 | |||
40 | That would make sense since protection screen and cinematic game parts do not load MEMLIST_PART_VIDEO2. | ||
41 | |||
42 | */ | ||
43 | const uint16_t memListParts[GAME_NUM_PARTS][4] = { | ||
44 | |||
45 | /* MEMLIST_PART_PALETTE MEMLIST_PART_CODE MEMLIST_PART_VIDEO1 MEMLIST_PART_VIDEO2 */ | ||
46 | { 0x14, 0x15, 0x16, 0x00 }, /* protection screens */ | ||
47 | { 0x17, 0x18, 0x19, 0x00 }, /* introduction cinematic */ | ||
48 | { 0x1A, 0x1B, 0x1C, 0x11 }, | ||
49 | { 0x1D, 0x1E, 0x1F, 0x11 }, | ||
50 | { 0x20, 0x21, 0x22, 0x11 }, | ||
51 | { 0x23, 0x24, 0x25, 0x00 }, /* battlechar cinematic */ | ||
52 | { 0x26, 0x27, 0x28, 0x11 }, | ||
53 | { 0x29, 0x2A, 0x2B, 0x11 }, | ||
54 | { 0x7D, 0x7E, 0x7F, 0x00 }, | ||
55 | { 0x7D, 0x7E, 0x7F, 0x00 } /* password screen */ | ||
56 | }; | ||
diff --git a/apps/plugins/xworld/parts.h b/apps/plugins/xworld/parts.h new file mode 100644 index 0000000000..684306cc6d --- /dev/null +++ b/apps/plugins/xworld/parts.h | |||
@@ -0,0 +1,57 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __AW_PARTS_ | ||
24 | #define __AW_PARTS_ | ||
25 | |||
26 | #include "intern.h" | ||
27 | #include "awendian.h" | ||
28 | |||
29 | /* The game is divided in 10 parts. */ | ||
30 | #define GAME_NUM_PARTS 10 | ||
31 | |||
32 | #define GAME_PART_FIRST 0x3E80 | ||
33 | #define GAME_PART1 0x3E80 | ||
34 | #define GAME_PART2 0x3E81 /* Introduction */ | ||
35 | #define GAME_PART3 0x3E82 | ||
36 | #define GAME_PART4 0x3E83 /* Wake up in the suspended jail */ | ||
37 | #define GAME_PART5 0x3E84 | ||
38 | #define GAME_PART6 0x3E85 /* BattleChar sequence */ | ||
39 | #define GAME_PART7 0x3E86 | ||
40 | #define GAME_PART8 0x3E87 | ||
41 | #define GAME_PART9 0x3E88 | ||
42 | #define GAME_PART10 0x3E89 | ||
43 | #define GAME_PART_LAST 0x3E89 | ||
44 | |||
45 | extern const uint16_t memListParts[GAME_NUM_PARTS][4]; | ||
46 | |||
47 | /* For each part of the game, four resources are referenced. */ | ||
48 | #define MEMLIST_PART_PALETTE 0 | ||
49 | #define MEMLIST_PART_CODE 1 | ||
50 | #define MEMLIST_PART_POLY_CINEMATIC 2 | ||
51 | #define MEMLIST_PART_VIDEO2 3 | ||
52 | |||
53 | |||
54 | #define MEMLIST_PART_NONE 0x00 | ||
55 | |||
56 | |||
57 | #endif | ||
diff --git a/apps/plugins/xworld/resource.c b/apps/plugins/xworld/resource.c new file mode 100644 index 0000000000..2820dcb998 --- /dev/null +++ b/apps/plugins/xworld/resource.c | |||
@@ -0,0 +1,443 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "resource.h" | ||
25 | #include "bank.h" | ||
26 | #include "file.h" | ||
27 | #include "serializer.h" | ||
28 | #include "video.h" | ||
29 | #include "util.h" | ||
30 | #include "parts.h" | ||
31 | #include "vm.h" | ||
32 | #include "sys.h" | ||
33 | |||
34 | void res_create(struct Resource* res, struct Video* vid, struct System* sys, const char* dataDir) | ||
35 | { | ||
36 | res->video = vid; | ||
37 | res->sys = sys; | ||
38 | res->_dataDir = dataDir; | ||
39 | res->currentPartId = 0; | ||
40 | res->requestedNextPart = 0; | ||
41 | } | ||
42 | |||
43 | void res_readBank(struct Resource* res, const MemEntry *me, uint8_t *dstBuf) { | ||
44 | uint16_t n = me - res->_memList; | ||
45 | debug(DBG_BANK, "res_readBank(%d)", n); | ||
46 | |||
47 | struct Bank bk; | ||
48 | bank_create(&bk, res->_dataDir); | ||
49 | if (!bank_read(&bk, me, dstBuf)) { | ||
50 | error("res_readBank() unable to unpack entry %d\n", n); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | #ifdef XWORLD_DEBUG | ||
55 | static const char *resTypeToString(struct Resource* res, unsigned int type) | ||
56 | { | ||
57 | (void) res; | ||
58 | static const char* resTypes[] = | ||
59 | { | ||
60 | "RT_SOUND", | ||
61 | "RT_MUSIC", | ||
62 | "RT_POLY_ANIM", | ||
63 | "RT_PALETTE", | ||
64 | "RT_BYTECODE", | ||
65 | "RT_POLY_CINEMATIC" | ||
66 | }; | ||
67 | if (type >= (sizeof(resTypes) / sizeof(const char *))) | ||
68 | return "RT_UNKNOWN"; | ||
69 | return resTypes[type]; | ||
70 | } | ||
71 | #endif | ||
72 | |||
73 | #define RES_SIZE 0 | ||
74 | #define RES_COMPRESSED 1 | ||
75 | int resourceSizeStats[7][2]; | ||
76 | #define STATS_TOTAL_SIZE 6 | ||
77 | int resourceUnitStats[7][2]; | ||
78 | |||
79 | /* | ||
80 | Read all entries from memlist.bin. Do not load anything in memory, | ||
81 | this is just a fast way to access the data later based on their id. | ||
82 | */ | ||
83 | void res_readEntries(struct Resource* res) { | ||
84 | File f; | ||
85 | file_create(&f, false); | ||
86 | |||
87 | int resourceCounter = 0; | ||
88 | |||
89 | if (!file_open(&f, "memlist.bin", res->_dataDir, "rb")) { | ||
90 | error("Could not open 'MEMLIST.BIN', data files missing"); | ||
91 | /* error() will exit() no need to return or do anything else. */ | ||
92 | } | ||
93 | |||
94 | /* Prepare stats array */ | ||
95 | rb->memset(resourceSizeStats, 0, sizeof(resourceSizeStats)); | ||
96 | rb->memset(resourceUnitStats, 0, sizeof(resourceUnitStats)); | ||
97 | |||
98 | res->_numMemList = 0; | ||
99 | struct MemEntry *memEntry = res->_memList; | ||
100 | while (1) { | ||
101 | assert(res->_numMemList < ARRAYLEN(res->_memList)); | ||
102 | memEntry->state = file_readByte(&f); | ||
103 | memEntry->type = file_readByte(&f); | ||
104 | memEntry->bufPtr = 0; | ||
105 | file_readUint16BE(&f); | ||
106 | memEntry->unk4 = file_readUint16BE(&f); | ||
107 | memEntry->rankNum = file_readByte(&f); | ||
108 | memEntry->bankId = file_readByte(&f); | ||
109 | memEntry->bankOffset = file_readUint32BE(&f); | ||
110 | memEntry->unkC = file_readUint16BE(&f); | ||
111 | memEntry->packedSize = file_readUint16BE(&f); | ||
112 | memEntry->unk10 = file_readUint16BE(&f); | ||
113 | memEntry->size = file_readUint16BE(&f); | ||
114 | |||
115 | debug(DBG_RES, "mementry state is %d", memEntry->state); | ||
116 | if (memEntry->state == MEMENTRY_STATE_END_OF_MEMLIST) { | ||
117 | break; | ||
118 | } | ||
119 | |||
120 | /* Memory tracking */ | ||
121 | if (memEntry->packedSize == memEntry->size) | ||
122 | { | ||
123 | resourceUnitStats[memEntry->type][RES_SIZE] ++; | ||
124 | resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] ++; | ||
125 | } | ||
126 | else | ||
127 | { | ||
128 | resourceUnitStats[memEntry->type][RES_COMPRESSED] ++; | ||
129 | resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] ++; | ||
130 | } | ||
131 | |||
132 | resourceSizeStats[memEntry->type][RES_SIZE] += memEntry->size; | ||
133 | resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] += memEntry->size; | ||
134 | resourceSizeStats[memEntry->type][RES_COMPRESSED] += memEntry->packedSize; | ||
135 | resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] += memEntry->packedSize; | ||
136 | |||
137 | debug(DBG_RES, "R:0x%02X, %-17s size=%5d (compacted gain=%2.0f%%)", | ||
138 | resourceCounter, | ||
139 | resTypeToString(res, memEntry->type), | ||
140 | memEntry->size, | ||
141 | memEntry->size ? (memEntry->size - memEntry->packedSize) / (float)memEntry->size * 100.0f : 0.0f); | ||
142 | |||
143 | resourceCounter++; | ||
144 | |||
145 | res->_numMemList++; | ||
146 | memEntry++; | ||
147 | } | ||
148 | |||
149 | debug(DBG_RES, "\n"); | ||
150 | debug(DBG_RES, "Total # resources: %d", resourceCounter); | ||
151 | debug(DBG_RES, "Compressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); | ||
152 | debug(DBG_RES, "Uncompressed : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE]); | ||
153 | debug(DBG_RES, "Note: %2.0f%% of resources are compressed.", 100 * resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] / (float)resourceCounter); | ||
154 | debug(DBG_RES, "\n"); | ||
155 | debug(DBG_RES, "Total size (uncompressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE]); | ||
156 | debug(DBG_RES, "Total size (compressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); | ||
157 | debug(DBG_RES, "Note: Overall compression gain is : %2.0f%%.", | ||
158 | (resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] - resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]) / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100); | ||
159 | |||
160 | debug(DBG_RES, "\n"); | ||
161 | for(int i = 0 ; i < 6 ; i++) | ||
162 | debug(DBG_RES, "Total %-17s unpacked size: %7d (%2.0f%% of total unpacked size) packedSize %7d (%2.0f%% of floppy space) gain:(%2.0f%%)", | ||
163 | resTypeToString(res, i), | ||
164 | resourceSizeStats[i][RES_SIZE], | ||
165 | resourceSizeStats[i][RES_SIZE] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100.0f, | ||
166 | resourceSizeStats[i][RES_COMPRESSED], | ||
167 | resourceSizeStats[i][RES_COMPRESSED] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] * 100.0f, | ||
168 | (resourceSizeStats[i][RES_SIZE] - resourceSizeStats[i][RES_COMPRESSED]) / (float)resourceSizeStats[i][RES_SIZE] * 100.0f); | ||
169 | |||
170 | debug(DBG_RES, "Note: Damn you sound compression rate!"); | ||
171 | |||
172 | debug(DBG_RES, "\nTotal bank files: %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] + resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]); | ||
173 | for(int i = 0 ; i < 6 ; i++) | ||
174 | debug(DBG_RES, "Total %-17s files: %3d", resTypeToString(res, i), resourceUnitStats[i][RES_SIZE] + resourceUnitStats[i][RES_COMPRESSED]); | ||
175 | |||
176 | file_close(&f); | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | Go over every resource and check if they are marked at "MEMENTRY_STATE_LOAD_ME". | ||
181 | Load them in memory and mark them are MEMENTRY_STATE_LOADED | ||
182 | */ | ||
183 | void res_loadMarkedAsNeeded(struct Resource* res) { | ||
184 | |||
185 | while (1) { | ||
186 | struct MemEntry *me = NULL; | ||
187 | |||
188 | /* get resource with max rankNum */ | ||
189 | uint8_t maxNum = 0; | ||
190 | uint16_t i = res->_numMemList; | ||
191 | struct MemEntry *it = res->_memList; | ||
192 | while (i--) { | ||
193 | if (it->state == MEMENTRY_STATE_LOAD_ME && maxNum <= it->rankNum) { | ||
194 | maxNum = it->rankNum; | ||
195 | me = it; | ||
196 | } | ||
197 | it++; | ||
198 | } | ||
199 | |||
200 | if (me == NULL) { | ||
201 | break; // no entry found | ||
202 | } | ||
203 | |||
204 | |||
205 | /* At this point the resource descriptor should be pointed to "me" */ | ||
206 | |||
207 | uint8_t *loadDestination = NULL; | ||
208 | if (me->type == RT_POLY_ANIM) { | ||
209 | loadDestination = res->_vidCurPtr; | ||
210 | } else { | ||
211 | loadDestination = res->_scriptCurPtr; | ||
212 | if (me->size > res->_vidBakPtr - res->_scriptCurPtr) { | ||
213 | warning("res_load() not enough memory"); | ||
214 | me->state = MEMENTRY_STATE_NOT_NEEDED; | ||
215 | continue; | ||
216 | } | ||
217 | } | ||
218 | |||
219 | |||
220 | if (me->bankId == 0) { | ||
221 | warning("res_load() ec=0x%X (me->bankId == 0)", 0xF00); | ||
222 | me->state = MEMENTRY_STATE_NOT_NEEDED; | ||
223 | } else { | ||
224 | debug(DBG_BANK, "res_load() bufPos=%X size=%X type=%X pos=%X bankId=%X", loadDestination - res->_memPtrStart, me->packedSize, me->type, me->bankOffset, me->bankId); | ||
225 | res_readBank(res, me, loadDestination); | ||
226 | if(me->type == RT_POLY_ANIM) { | ||
227 | video_copyPagePtr(res->video, res->_vidCurPtr); | ||
228 | me->state = MEMENTRY_STATE_NOT_NEEDED; | ||
229 | } else { | ||
230 | me->bufPtr = loadDestination; | ||
231 | me->state = MEMENTRY_STATE_LOADED; | ||
232 | res->_scriptCurPtr += me->size; | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | } | ||
237 | |||
238 | void res_invalidateRes(struct Resource* res) { | ||
239 | struct MemEntry *me = res->_memList; | ||
240 | uint16_t i = res->_numMemList; | ||
241 | while (i--) { | ||
242 | if (me->type <= RT_POLY_ANIM || me->type > 6) { /* 6 WTF ?!?! ResType goes up to 5 !! */ | ||
243 | me->state = MEMENTRY_STATE_NOT_NEEDED; | ||
244 | } | ||
245 | ++me; | ||
246 | } | ||
247 | res->_scriptCurPtr = res->_scriptBakPtr; | ||
248 | } | ||
249 | |||
250 | void res_invalidateAll(struct Resource* res) { | ||
251 | struct MemEntry *me = res->_memList; | ||
252 | uint16_t i = res->_numMemList; | ||
253 | while (i--) { | ||
254 | me->state = MEMENTRY_STATE_NOT_NEEDED; | ||
255 | ++me; | ||
256 | } | ||
257 | res->_scriptCurPtr = res->_memPtrStart; | ||
258 | } | ||
259 | |||
260 | /* This method serves two purpose: | ||
261 | - Load parts in memory segments (palette,code,video1,video2) | ||
262 | or | ||
263 | - Load a resource in memory | ||
264 | |||
265 | This is decided based on the resourceId. If it does not match a mementry id it is supposed to | ||
266 | be a part id. */ | ||
267 | void res_loadPartsOrMemoryEntry(struct Resource* res, uint16_t resourceId) { | ||
268 | |||
269 | if (resourceId > res->_numMemList) { | ||
270 | |||
271 | res->requestedNextPart = resourceId; | ||
272 | |||
273 | } else { | ||
274 | |||
275 | struct MemEntry *me = &res->_memList[resourceId]; | ||
276 | |||
277 | if (me->state == MEMENTRY_STATE_NOT_NEEDED) { | ||
278 | me->state = MEMENTRY_STATE_LOAD_ME; | ||
279 | res_loadMarkedAsNeeded(res); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | } | ||
284 | |||
285 | /* Protection screen and cinematic don't need the player and enemies polygon data | ||
286 | so _memList[video2Index] is never loaded for those parts of the game. When | ||
287 | needed (for action phrases) _memList[video2Index] is always loaded with 0x11 | ||
288 | (as seen in memListParts). */ | ||
289 | void res_setupPart(struct Resource* res, uint16_t partId) { | ||
290 | |||
291 | if (partId == res->currentPartId) | ||
292 | return; | ||
293 | |||
294 | if (partId < GAME_PART_FIRST || partId > GAME_PART_LAST) | ||
295 | error("res_setupPart() ec=0x%X invalid partId", partId); | ||
296 | |||
297 | uint16_t memListPartIndex = partId - GAME_PART_FIRST; | ||
298 | |||
299 | uint8_t paletteIndex = memListParts[memListPartIndex][MEMLIST_PART_PALETTE]; | ||
300 | uint8_t codeIndex = memListParts[memListPartIndex][MEMLIST_PART_CODE]; | ||
301 | uint8_t videoCinematicIndex = memListParts[memListPartIndex][MEMLIST_PART_POLY_CINEMATIC]; | ||
302 | uint8_t video2Index = memListParts[memListPartIndex][MEMLIST_PART_VIDEO2]; | ||
303 | |||
304 | /* Mark all resources as located on harddrive. */ | ||
305 | res_invalidateAll(res); | ||
306 | |||
307 | res->_memList[paletteIndex].state = MEMENTRY_STATE_LOAD_ME; | ||
308 | res->_memList[codeIndex].state = MEMENTRY_STATE_LOAD_ME; | ||
309 | res->_memList[videoCinematicIndex].state = MEMENTRY_STATE_LOAD_ME; | ||
310 | |||
311 | /* This is probably a cinematic or a non interactive part of the game. */ | ||
312 | /* Player and enemy polygons are not needed. */ | ||
313 | if (video2Index != MEMLIST_PART_NONE) | ||
314 | res->_memList[video2Index].state = MEMENTRY_STATE_LOAD_ME; | ||
315 | |||
316 | |||
317 | res_loadMarkedAsNeeded(res); | ||
318 | |||
319 | res->segPalettes = res->_memList[paletteIndex].bufPtr; | ||
320 | debug(DBG_RES, "paletteIndex is 0x%08x", res->segPalettes); | ||
321 | res->segBytecode = res->_memList[codeIndex].bufPtr; | ||
322 | res->segCinematic = res->_memList[videoCinematicIndex].bufPtr; | ||
323 | |||
324 | |||
325 | |||
326 | /* This is probably a cinematic or a non interactive part of the game. */ | ||
327 | /* Player and enemy polygons are not needed. */ | ||
328 | if (video2Index != MEMLIST_PART_NONE) | ||
329 | res->_segVideo2 = res->_memList[video2Index].bufPtr; | ||
330 | |||
331 | debug(DBG_RES, ""); | ||
332 | debug(DBG_RES, "setupPart(%d)", partId - GAME_PART_FIRST); | ||
333 | debug(DBG_RES, "Loaded resource %d (%s) in segPalettes.", paletteIndex, resTypeToString(res, res->_memList[paletteIndex].type)); | ||
334 | debug(DBG_RES, "Loaded resource %d (%s) in segBytecode.", codeIndex, resTypeToString(res, res->_memList[codeIndex].type)); | ||
335 | debug(DBG_RES, "Loaded resource %d (%s) in segCinematic.", videoCinematicIndex, resTypeToString(res, res->_memList[videoCinematicIndex].type)); | ||
336 | |||
337 | |||
338 | /* prevent warnings: */ | ||
339 | #ifdef XWORLD_DEBUG | ||
340 | if (video2Index != MEMLIST_PART_NONE) | ||
341 | debug(DBG_RES, "Loaded resource %d (%s) in _segVideo2.", video2Index, resTypeToString(res, res->_memList[video2Index].type)); | ||
342 | #endif | ||
343 | |||
344 | |||
345 | res->currentPartId = partId; | ||
346 | |||
347 | |||
348 | /* _scriptCurPtr is changed in res->load(); */ | ||
349 | res->_scriptBakPtr = res->_scriptCurPtr; | ||
350 | } | ||
351 | |||
352 | void res_allocMemBlock(struct Resource* res) { | ||
353 | if(rb->audio_status()) | ||
354 | rb->audio_stop(); | ||
355 | /* steal the audio buffer */ | ||
356 | size_t sz; | ||
357 | /* memory usage is as follows: | ||
358 | [VM memory - 600K] | ||
359 | [Framebuffers - 128K] | ||
360 | [Temporary framebuffer - 192K] | ||
361 | [String table buffer] | ||
362 | */ | ||
363 | res->_memPtrStart = rb->plugin_get_audio_buffer(&sz); | ||
364 | if(sz < MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)) | ||
365 | { | ||
366 | warning("res_allocMemBlock: can't allocate enough memory!"); | ||
367 | } | ||
368 | |||
369 | res->sys->membuf = res->_memPtrStart + ( MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)); | ||
370 | res->sys->bytes_left = sz - (MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data)); | ||
371 | |||
372 | debug(DBG_RES, "audiobuf is %d bytes in size", sz); | ||
373 | |||
374 | res->_scriptBakPtr = res->_scriptCurPtr = res->_memPtrStart; | ||
375 | res->_vidBakPtr = res->_vidCurPtr = res->_memPtrStart + MEM_BLOCK_SIZE - 0x800 * 16; //0x800 = 2048, so we have 32KB free for vidBack and vidCur | ||
376 | res->_useSegVideo2 = false; | ||
377 | } | ||
378 | |||
379 | void res_freeMemBlock(struct Resource* res) { | ||
380 | (void) res; | ||
381 | /* there's no need to do anything to free the audio buffer */ | ||
382 | return; | ||
383 | } | ||
384 | |||
385 | void res_saveOrLoad(struct Resource* res, struct Serializer *ser) { | ||
386 | uint8_t loadedList[64]; | ||
387 | if (ser->_mode == SM_SAVE) { | ||
388 | rb->memset(loadedList, 0, sizeof(loadedList)); | ||
389 | uint8_t *p = loadedList; | ||
390 | uint8_t *q = res->_memPtrStart; | ||
391 | while (1) { | ||
392 | struct MemEntry *it = res->_memList; | ||
393 | struct MemEntry *me = 0; | ||
394 | uint16_t num = res->_numMemList; | ||
395 | while (num--) { | ||
396 | if (it->state == MEMENTRY_STATE_LOADED && it->bufPtr == q) { | ||
397 | me = it; | ||
398 | } | ||
399 | ++it; | ||
400 | } | ||
401 | if (me == 0) { | ||
402 | break; | ||
403 | } else { | ||
404 | assert(p < loadedList + 64); | ||
405 | *p++ = me - res->_memList; | ||
406 | q += me->size; | ||
407 | } | ||
408 | } | ||
409 | } | ||
410 | |||
411 | struct Entry entries[] = { | ||
412 | SE_ARRAY(loadedList, 64, SES_INT8, VER(1)), | ||
413 | SE_INT(&res->currentPartId, SES_INT16, VER(1)), | ||
414 | SE_PTR(&res->_scriptBakPtr, VER(1)), | ||
415 | SE_PTR(&res->_scriptCurPtr, VER(1)), | ||
416 | SE_PTR(&res->_vidBakPtr, VER(1)), | ||
417 | SE_PTR(&res->_vidCurPtr, VER(1)), | ||
418 | SE_INT(&res->_useSegVideo2, SES_BOOL, VER(1)), | ||
419 | SE_PTR(&res->segPalettes, VER(1)), | ||
420 | SE_PTR(&res->segBytecode, VER(1)), | ||
421 | SE_PTR(&res->segCinematic, VER(1)), | ||
422 | SE_PTR(&res->_segVideo2, VER(1)), | ||
423 | SE_END() | ||
424 | }; | ||
425 | |||
426 | ser_saveOrLoadEntries(ser, entries); | ||
427 | if (ser->_mode == SM_LOAD) { | ||
428 | uint8_t *p = loadedList; | ||
429 | uint8_t *q = res->_memPtrStart; | ||
430 | while (*p) { | ||
431 | struct MemEntry *me = &res->_memList[*p++]; | ||
432 | res_readBank(res, me, q); | ||
433 | me->bufPtr = q; | ||
434 | me->state = MEMENTRY_STATE_LOADED; | ||
435 | q += me->size; | ||
436 | } | ||
437 | } | ||
438 | } | ||
439 | |||
440 | const char* res_getDataDir(struct Resource* res) | ||
441 | { | ||
442 | return res->_dataDir; | ||
443 | } | ||
diff --git a/apps/plugins/xworld/resource.h b/apps/plugins/xworld/resource.h new file mode 100644 index 0000000000..3242777bc2 --- /dev/null +++ b/apps/plugins/xworld/resource.h | |||
@@ -0,0 +1,107 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __RESOURCE_H__ | ||
24 | #define __RESOURCE_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | |||
29 | #define MEMENTRY_STATE_END_OF_MEMLIST 0xFF | ||
30 | #define MEMENTRY_STATE_NOT_NEEDED 0 | ||
31 | #define MEMENTRY_STATE_LOADED 1 | ||
32 | #define MEMENTRY_STATE_LOAD_ME 2 | ||
33 | |||
34 | /* | ||
35 | This is a directory entry. When the game starts, it loads memlist.bin and | ||
36 | populate and array of MemEntry | ||
37 | */ | ||
38 | typedef struct MemEntry { | ||
39 | uint8_t state; /* 0x0 */ | ||
40 | uint8_t type; /* 0x1, Resource::ResType */ | ||
41 | uint8_t *bufPtr; /* 0x2 */ | ||
42 | uint16_t unk4; /* 0x4, unused */ | ||
43 | uint8_t rankNum; /* 0x6 */ | ||
44 | uint8_t bankId; /* 0x7 */ | ||
45 | uint32_t bankOffset; /* 0x8 0xA */ | ||
46 | uint16_t unkC; /* 0xC, unused */ | ||
47 | uint16_t packedSize; /* 0xE */ | ||
48 | /* All ressources are packed (for a gain of 28% according to Chahi) */ | ||
49 | |||
50 | uint16_t unk10; /* 0x10, unused */ | ||
51 | uint16_t size; /* 0x12 */ | ||
52 | } __attribute__((packed)) MemEntry; | ||
53 | |||
54 | /* | ||
55 | Note: state is not a boolean, it can have value 0, 1, 2 or 255, respectively meaning: | ||
56 | 0:NOT_NEEDED | ||
57 | 1:LOADED | ||
58 | 2:LOAD_ME | ||
59 | 255:END_OF_MEMLIST | ||
60 | |||
61 | See MEMENTRY_STATE_* #defines above. | ||
62 | */ | ||
63 | |||
64 | struct Serializer; | ||
65 | struct Video; | ||
66 | |||
67 | #define MEM_BLOCK_SIZE (600 * 1024) | ||
68 | #define RT_SOUND 0 | ||
69 | #define RT_MUSIC 1 | ||
70 | #define RT_POLY_ANIM 2 | ||
71 | #define RT_PALETTE 3 | ||
72 | #define RT_BYTECODE 4 | ||
73 | #define RT_POLY_CINEMATIC 5 | ||
74 | |||
75 | struct Resource { | ||
76 | struct Video *video; | ||
77 | struct System *sys; | ||
78 | const char *_dataDir; | ||
79 | struct MemEntry _memList[150]; | ||
80 | uint16_t _numMemList; | ||
81 | uint16_t currentPartId, requestedNextPart; | ||
82 | uint8_t *_memPtrStart, *_scriptBakPtr, *_scriptCurPtr, *_vidBakPtr, *_vidCurPtr; | ||
83 | bool _useSegVideo2; | ||
84 | |||
85 | uint8_t *segPalettes; | ||
86 | uint8_t *segBytecode; | ||
87 | uint8_t *segCinematic; | ||
88 | uint8_t *_segVideo2; | ||
89 | }; | ||
90 | |||
91 | |||
92 | void res_create(struct Resource*, struct Video*, struct System*, const char* dataDir); | ||
93 | |||
94 | void res_readBank(struct Resource*, const MemEntry *me, uint8_t *dstBuf); | ||
95 | void res_readEntries(struct Resource*); | ||
96 | void res_loadMarkedAsNeeded(struct Resource*); | ||
97 | void res_invalidateAll(struct Resource*); | ||
98 | void res_invalidateRes(struct Resource*); | ||
99 | void res_loadPartsOrMemoryEntry(struct Resource*, uint16_t num); | ||
100 | void res_setupPart(struct Resource*, uint16_t ptrId); | ||
101 | void res_allocMemBlock(struct Resource*); | ||
102 | void res_freeMemBlock(struct Resource*); | ||
103 | |||
104 | void res_saveOrLoad(struct Resource*, struct Serializer *ser); | ||
105 | |||
106 | const char* res_getDataDir(struct Resource*); | ||
107 | #endif | ||
diff --git a/apps/plugins/xworld/serializer.c b/apps/plugins/xworld/serializer.c new file mode 100644 index 0000000000..5268c2784c --- /dev/null +++ b/apps/plugins/xworld/serializer.c | |||
@@ -0,0 +1,141 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "serializer.h" | ||
24 | #include "file.h" | ||
25 | |||
26 | |||
27 | void ser_create(struct Serializer* c, File *stream, enum Mode mode, uint8_t *ptrBlock, uint16_t saveVer) | ||
28 | { | ||
29 | c->_stream = stream; | ||
30 | c->_mode = mode; | ||
31 | c->_ptrBlock = ptrBlock; | ||
32 | c->_saveVer = saveVer; | ||
33 | } | ||
34 | |||
35 | void ser_saveOrLoadEntries(struct Serializer* c, struct Entry *entry) { | ||
36 | debug(DBG_SER, "ser_saveOrLoadEntries() _mode=%d", c->_mode); | ||
37 | c->_bytesCount = 0; | ||
38 | switch (c->_mode) { | ||
39 | case SM_SAVE: | ||
40 | ser_saveEntries(c, entry); | ||
41 | break; | ||
42 | case SM_LOAD: | ||
43 | ser_loadEntries(c, entry); | ||
44 | break; | ||
45 | } | ||
46 | debug(DBG_SER, "ser_saveOrLoadEntries() _bytesCount=%d", c->_bytesCount); | ||
47 | } | ||
48 | |||
49 | void ser_saveEntries(struct Serializer* c, struct Entry *entry) { | ||
50 | debug(DBG_SER, "ser_saveEntries()"); | ||
51 | for (; entry->type != SET_END; ++entry) { | ||
52 | if (entry->maxVer == CUR_VER) { | ||
53 | switch (entry->type) { | ||
54 | case SET_INT: | ||
55 | ser_saveInt(c, entry->size, entry->data); | ||
56 | c->_bytesCount += entry->size; | ||
57 | break; | ||
58 | case SET_ARRAY: | ||
59 | if (entry->size == SES_INT8) { | ||
60 | file_write(c->_stream, entry->data, entry->n); | ||
61 | c->_bytesCount += entry->n; | ||
62 | } else { | ||
63 | uint8_t *p = (uint8_t *)entry->data; | ||
64 | for (int i = 0; i < entry->n; ++i) { | ||
65 | ser_saveInt(c, entry->size, p); | ||
66 | p += entry->size; | ||
67 | c->_bytesCount += entry->size; | ||
68 | } | ||
69 | } | ||
70 | break; | ||
71 | case SET_PTR: | ||
72 | file_writeUint32BE(c->_stream, *(uint8_t **)(entry->data) - c->_ptrBlock); | ||
73 | c->_bytesCount += 4; | ||
74 | break; | ||
75 | case SET_END: | ||
76 | break; | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | |||
82 | void ser_loadEntries(struct Serializer* c, struct Entry *entry) { | ||
83 | debug(DBG_SER, "ser_loadEntries()"); | ||
84 | for (; entry->type != SET_END; ++entry) { | ||
85 | if (c->_saveVer >= entry->minVer && c->_saveVer <= entry->maxVer) { | ||
86 | switch (entry->type) { | ||
87 | case SET_INT: | ||
88 | ser_loadInt(c, entry->size, entry->data); | ||
89 | c->_bytesCount += entry->size; | ||
90 | break; | ||
91 | case SET_ARRAY: | ||
92 | if (entry->size == SES_INT8) { | ||
93 | file_read(c->_stream, entry->data, entry->n); | ||
94 | c->_bytesCount += entry->n; | ||
95 | } else { | ||
96 | uint8_t *p = (uint8_t *)entry->data; | ||
97 | for (int i = 0; i < entry->n; ++i) { | ||
98 | ser_loadInt(c, entry->size, p); | ||
99 | p += entry->size; | ||
100 | c->_bytesCount += entry->size; | ||
101 | } | ||
102 | } | ||
103 | break; | ||
104 | case SET_PTR: | ||
105 | *(uint8_t **)(entry->data) = c->_ptrBlock + file_readUint32BE(c->_stream); | ||
106 | c->_bytesCount += 4; | ||
107 | break; | ||
108 | case SET_END: | ||
109 | break; | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | void ser_saveInt(struct Serializer* c, uint8_t es, void *p) { | ||
116 | switch (es) { | ||
117 | case 1: | ||
118 | file_writeByte(c->_stream, *(uint8_t *)p); | ||
119 | break; | ||
120 | case 2: | ||
121 | file_writeUint16BE(c->_stream, *(uint16_t *)p); | ||
122 | break; | ||
123 | case 4: | ||
124 | file_writeUint32BE(c->_stream, *(uint32_t *)p); | ||
125 | break; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | void ser_loadInt(struct Serializer* c, uint8_t es, void *p) { | ||
130 | switch (es) { | ||
131 | case 1: | ||
132 | *(uint8_t *)p = file_readByte(c->_stream); | ||
133 | break; | ||
134 | case 2: | ||
135 | *(uint16_t *)p = file_readUint16BE(c->_stream); | ||
136 | break; | ||
137 | case 4: | ||
138 | *(uint32_t *)p = file_readUint32BE(c->_stream); | ||
139 | break; | ||
140 | } | ||
141 | } | ||
diff --git a/apps/plugins/xworld/serializer.h b/apps/plugins/xworld/serializer.h new file mode 100644 index 0000000000..fde8f6d076 --- /dev/null +++ b/apps/plugins/xworld/serializer.h | |||
@@ -0,0 +1,84 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __SERIALIZER_H__ | ||
24 | #define __SERIALIZER_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | #define CUR_VER 2 | ||
29 | |||
30 | #define VER(x) x | ||
31 | |||
32 | enum EntryType { | ||
33 | SET_INT, | ||
34 | SET_ARRAY, | ||
35 | SET_PTR, | ||
36 | SET_END | ||
37 | }; | ||
38 | |||
39 | #define SE_INT(i,sz,ver) { SET_INT, sz, 1, i, ver, CUR_VER } | ||
40 | #define SE_ARRAY(a,n,sz,ver) { SET_ARRAY, sz, n, a, ver, CUR_VER } | ||
41 | #define SE_PTR(p,ver) { SET_PTR, 0, 0, p, ver, CUR_VER } | ||
42 | #define SE_END() { SET_END, 0, 0, 0, 0, 0 } | ||
43 | |||
44 | struct File; | ||
45 | |||
46 | enum { | ||
47 | SES_BOOL = 1, | ||
48 | SES_INT8 = 1, | ||
49 | SES_INT16 = 2, | ||
50 | SES_INT32 = 4 | ||
51 | }; | ||
52 | |||
53 | enum Mode { | ||
54 | SM_SAVE, | ||
55 | SM_LOAD | ||
56 | }; | ||
57 | |||
58 | struct Entry { | ||
59 | enum EntryType type; | ||
60 | uint8_t size; | ||
61 | uint16_t n; | ||
62 | void *data; | ||
63 | uint16_t minVer; | ||
64 | uint16_t maxVer; | ||
65 | }; | ||
66 | |||
67 | struct Serializer { | ||
68 | File *_stream; | ||
69 | enum Mode _mode; | ||
70 | uint8_t *_ptrBlock; | ||
71 | uint16_t _saveVer; | ||
72 | uint32_t _bytesCount; | ||
73 | }; | ||
74 | |||
75 | void ser_create(struct Serializer*, File *stream, enum Mode mode, uint8_t *ptrBlock, uint16_t saveVer); | ||
76 | |||
77 | void ser_saveOrLoadEntries(struct Serializer*, struct Entry *entry); | ||
78 | |||
79 | void ser_saveEntries(struct Serializer*, struct Entry *entry); | ||
80 | void ser_loadEntries(struct Serializer*, struct Entry *entry); | ||
81 | |||
82 | void ser_saveInt(struct Serializer*, uint8_t es, void *p); | ||
83 | void ser_loadInt(struct Serializer*, uint8_t es, void *p); | ||
84 | #endif | ||
diff --git a/apps/plugins/xworld/sfxplayer.c b/apps/plugins/xworld/sfxplayer.c new file mode 100644 index 0000000000..9bdc143d02 --- /dev/null +++ b/apps/plugins/xworld/sfxplayer.c | |||
@@ -0,0 +1,247 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "sfxplayer.h" | ||
24 | #include "mixer.h" | ||
25 | #include "resource.h" | ||
26 | #include "serializer.h" | ||
27 | #include "sys.h" | ||
28 | |||
29 | void player_create(struct SfxPlayer* sfx, struct Mixer *mix, struct Resource *res, struct System *stub) | ||
30 | { | ||
31 | sfx->mixer = mix; | ||
32 | sfx->res = res; | ||
33 | sfx->sys = stub; | ||
34 | sfx->_delay = 0; | ||
35 | sfx->_resNum = 0; | ||
36 | } | ||
37 | |||
38 | void player_init(struct SfxPlayer* sfx) { | ||
39 | debug(DBG_SND, "sys is 0x%08x", sfx->sys); | ||
40 | sfx->_mutex = sys_createMutex(sfx->sys); | ||
41 | } | ||
42 | |||
43 | void player_free(struct SfxPlayer* sfx) { | ||
44 | player_stop(sfx); | ||
45 | sys_destroyMutex(sfx->sys, sfx->_mutex); | ||
46 | } | ||
47 | |||
48 | void player_setEventsDelay(struct SfxPlayer* sfx, uint16_t delay) { | ||
49 | debug(DBG_SND, "player_setEventsDelay(%d)", delay); | ||
50 | struct MutexStack_t ms; | ||
51 | MutexStack(&ms, sfx->sys, sfx->_mutex); | ||
52 | sfx->_delay = delay * 60 / 7050; | ||
53 | MutexStack_destroy(&ms); | ||
54 | } | ||
55 | |||
56 | void player_loadSfxModule(struct SfxPlayer* sfx, uint16_t resNum, uint16_t delay, uint8_t pos) { | ||
57 | |||
58 | debug(DBG_SND, "player_loadSfxModule(0x%X, %d, %d)", resNum, delay, pos); | ||
59 | struct MutexStack_t ms; | ||
60 | MutexStack(&ms, sfx->sys, sfx->_mutex); | ||
61 | |||
62 | |||
63 | struct MemEntry *me = &sfx->res->_memList[resNum]; | ||
64 | |||
65 | if (me->state == MEMENTRY_STATE_LOADED && me->type == RT_MUSIC) { | ||
66 | sfx->_resNum = resNum; | ||
67 | rb->memset(&sfx->_sfxMod, 0, sizeof(struct SfxModule)); | ||
68 | sfx->_sfxMod.curOrder = pos; | ||
69 | sfx->_sfxMod.numOrder = READ_BE_UINT16(me->bufPtr + 0x3E); | ||
70 | debug(DBG_SND, "player_loadSfxModule() curOrder = 0x%X numOrder = 0x%X", sfx->_sfxMod.curOrder, sfx->_sfxMod.numOrder); | ||
71 | for (int i = 0; i < 0x80; ++i) { | ||
72 | sfx->_sfxMod.orderTable[i] = *(me->bufPtr + 0x40 + i); | ||
73 | } | ||
74 | if (delay == 0) { | ||
75 | sfx->_delay = READ_BE_UINT16(me->bufPtr); | ||
76 | } else { | ||
77 | sfx->_delay = delay; | ||
78 | } | ||
79 | sfx->_delay = sfx->_delay * 60 / 7050; | ||
80 | sfx->_sfxMod.data = me->bufPtr + 0xC0; | ||
81 | debug(DBG_SND, "player_loadSfxModule() eventDelay = %d ms", sfx->_delay); | ||
82 | player_prepareInstruments(sfx, me->bufPtr + 2); | ||
83 | } else { | ||
84 | warning("player_loadSfxModule() ec=0x%X", 0xF8); | ||
85 | } | ||
86 | MutexStack_destroy(&ms); | ||
87 | } | ||
88 | |||
89 | void player_prepareInstruments(struct SfxPlayer* sfx, const uint8_t *p) { | ||
90 | |||
91 | rb->memset(sfx->_sfxMod.samples, 0, sizeof(sfx->_sfxMod.samples)); | ||
92 | |||
93 | for (int i = 0; i < 15; ++i) { | ||
94 | struct SfxInstrument *ins = &sfx->_sfxMod.samples[i]; | ||
95 | uint16_t resNum = READ_BE_UINT16(p); | ||
96 | p += 2; | ||
97 | if (resNum != 0) { | ||
98 | ins->volume = READ_BE_UINT16(p); | ||
99 | struct MemEntry *me = &sfx->res->_memList[resNum]; | ||
100 | if (me->state == MEMENTRY_STATE_LOADED && me->type == RT_SOUND) { | ||
101 | ins->data = me->bufPtr; | ||
102 | rb->memset(ins->data + 8, 0, 4); | ||
103 | debug(DBG_SND, "Loaded instrument 0x%X n=%d volume=%d", resNum, i, ins->volume); | ||
104 | } else { | ||
105 | error("Error loading instrument 0x%X", resNum); | ||
106 | } | ||
107 | } | ||
108 | p += 2; /* skip volume */ | ||
109 | } | ||
110 | } | ||
111 | |||
112 | void player_start(struct SfxPlayer* sfx) { | ||
113 | debug(DBG_SND, "player_start()"); | ||
114 | struct MutexStack_t ms; | ||
115 | MutexStack(&ms, sfx->sys, sfx->_mutex); | ||
116 | sfx->_sfxMod.curPos = 0; | ||
117 | sfx->_timerId = sys_addTimer(sfx->sys, sfx->_delay, player_eventsCallback, sfx); | ||
118 | MutexStack_destroy(&ms); | ||
119 | } | ||
120 | |||
121 | void player_stop(struct SfxPlayer* sfx) { | ||
122 | debug(DBG_SND, "player_stop()"); | ||
123 | struct MutexStack_t ms; | ||
124 | MutexStack(&ms, sfx->sys, sfx->_mutex); | ||
125 | if (sfx->_resNum != 0) { | ||
126 | sfx->_resNum = 0; | ||
127 | sys_removeTimer(sfx->sys, sfx->_timerId); | ||
128 | } | ||
129 | MutexStack_destroy(&ms); | ||
130 | } | ||
131 | |||
132 | void player_handleEvents(struct SfxPlayer* sfx) { | ||
133 | struct MutexStack_t ms; | ||
134 | MutexStack(&ms, sfx->sys, sfx->_mutex); | ||
135 | uint8_t order = sfx->_sfxMod.orderTable[sfx->_sfxMod.curOrder]; | ||
136 | const uint8_t *patternData = sfx->_sfxMod.data + sfx->_sfxMod.curPos + order * 1024; | ||
137 | for (uint8_t ch = 0; ch < 4; ++ch) { | ||
138 | player_handlePattern(sfx, ch, patternData); | ||
139 | patternData += 4; | ||
140 | } | ||
141 | sfx->_sfxMod.curPos += 4 * 4; | ||
142 | debug(DBG_SND, "player_handleEvents() order = 0x%X curPos = 0x%X", order, sfx->_sfxMod.curPos); | ||
143 | if (sfx->_sfxMod.curPos >= 1024) { | ||
144 | sfx->_sfxMod.curPos = 0; | ||
145 | order = sfx->_sfxMod.curOrder + 1; | ||
146 | if (order == sfx->_sfxMod.numOrder) { | ||
147 | sfx->_resNum = 0; | ||
148 | sys_removeTimer(sfx->sys, sfx->_timerId); | ||
149 | mixer_stopAll(sfx->mixer); | ||
150 | } | ||
151 | sfx->_sfxMod.curOrder = order; | ||
152 | } | ||
153 | MutexStack_destroy(&ms); | ||
154 | } | ||
155 | |||
156 | void player_handlePattern(struct SfxPlayer* sfx, uint8_t channel, const uint8_t *data) { | ||
157 | struct SfxPattern pat; | ||
158 | rb->memset(&pat, 0, sizeof(struct SfxPattern)); | ||
159 | pat.note_1 = READ_BE_UINT16(data + 0); | ||
160 | pat.note_2 = READ_BE_UINT16(data + 2); | ||
161 | if (pat.note_1 != 0xFFFD) { | ||
162 | uint16_t sample = (pat.note_2 & 0xF000) >> 12; | ||
163 | if (sample != 0) { | ||
164 | uint8_t *ptr = sfx->_sfxMod.samples[sample - 1].data; | ||
165 | if (ptr != 0) { | ||
166 | debug(DBG_SND, "player_handlePattern() preparing sample %d", sample); | ||
167 | pat.sampleVolume = sfx->_sfxMod.samples[sample - 1].volume; | ||
168 | pat.sampleStart = 8; | ||
169 | pat.sampleBuffer = ptr; | ||
170 | pat.sampleLen = READ_BE_UINT16(ptr) * 2; | ||
171 | uint16_t loopLen = READ_BE_UINT16(ptr + 2) * 2; | ||
172 | if (loopLen != 0) { | ||
173 | pat.loopPos = pat.sampleLen; | ||
174 | pat.loopData = ptr; | ||
175 | pat.loopLen = loopLen; | ||
176 | } else { | ||
177 | pat.loopPos = 0; | ||
178 | pat.loopData = 0; | ||
179 | pat.loopLen = 0; | ||
180 | } | ||
181 | int16_t m = pat.sampleVolume; | ||
182 | uint8_t effect = (pat.note_2 & 0x0F00) >> 8; | ||
183 | if (effect == 5) { /* volume up */ | ||
184 | uint8_t volume = (pat.note_2 & 0xFF); | ||
185 | m += volume; | ||
186 | if (m > 0x3F) { | ||
187 | m = 0x3F; | ||
188 | } | ||
189 | } else if (effect == 6) { /* volume down */ | ||
190 | uint8_t volume = (pat.note_2 & 0xFF); | ||
191 | m -= volume; | ||
192 | if (m < 0) { | ||
193 | m = 0; | ||
194 | } | ||
195 | } | ||
196 | mixer_setChannelVolume(sfx->mixer, channel, m); | ||
197 | pat.sampleVolume = m; | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | if (pat.note_1 == 0xFFFD) { | ||
202 | debug(DBG_SND, "player_handlePattern() _scriptVars[0xF4] = 0x%X", pat.note_2); | ||
203 | *sfx->_markVar = pat.note_2; | ||
204 | } else if (pat.note_1 != 0) { | ||
205 | if (pat.note_1 == 0xFFFE) { | ||
206 | mixer_stopChannel(sfx->mixer, channel); | ||
207 | } else if (pat.sampleBuffer != 0) { | ||
208 | struct MixerChunk mc; | ||
209 | rb->memset(&mc, 0, sizeof(mc)); | ||
210 | mc.data = pat.sampleBuffer + pat.sampleStart; | ||
211 | mc.len = pat.sampleLen; | ||
212 | mc.loopPos = pat.loopPos; | ||
213 | mc.loopLen = pat.loopLen; | ||
214 | /* convert amiga period value to hz */ | ||
215 | uint16_t freq = 7159092 / (pat.note_1 * 2); | ||
216 | debug(DBG_SND, "player_handlePattern() adding sample freq = 0x%X", freq); | ||
217 | mixer_playChannel(sfx->mixer, channel, &mc, freq, pat.sampleVolume); | ||
218 | } | ||
219 | } | ||
220 | } | ||
221 | |||
222 | uint32_t player_eventsCallback(uint32_t interval, void *param) { | ||
223 | (void) interval; | ||
224 | debug(DBG_SND, "player_eventsCallback with interval %d ms and param 0x%08x", interval, param); | ||
225 | struct SfxPlayer *p = (struct SfxPlayer *)param; | ||
226 | player_handleEvents(p); | ||
227 | return p->_delay; | ||
228 | } | ||
229 | |||
230 | void player_saveOrLoad(struct SfxPlayer* sfx, struct Serializer *ser) { | ||
231 | sys_lockMutex(sfx->sys, sfx->_mutex); | ||
232 | struct Entry entries[] = { | ||
233 | SE_INT(&sfx->_delay, SES_INT8, VER(2)), | ||
234 | SE_INT(&sfx->_resNum, SES_INT16, VER(2)), | ||
235 | SE_INT(&sfx->_sfxMod.curPos, SES_INT16, VER(2)), | ||
236 | SE_INT(&sfx->_sfxMod.curOrder, SES_INT8, VER(2)), | ||
237 | SE_END() | ||
238 | }; | ||
239 | ser_saveOrLoadEntries(ser, entries); | ||
240 | sys_unlockMutex(sfx->sys, sfx->_mutex); | ||
241 | if (ser->_mode == SM_LOAD && sfx->_resNum != 0) { | ||
242 | uint16_t delay = sfx->_delay; | ||
243 | player_loadSfxModule(sfx, sfx->_resNum, 0, sfx->_sfxMod.curOrder); | ||
244 | sfx->_delay = delay; | ||
245 | sfx->_timerId = sys_addTimer(sfx->sys, sfx->_delay, player_eventsCallback, sfx); | ||
246 | } | ||
247 | } | ||
diff --git a/apps/plugins/xworld/sfxplayer.h b/apps/plugins/xworld/sfxplayer.h new file mode 100644 index 0000000000..0bf92b93d0 --- /dev/null +++ b/apps/plugins/xworld/sfxplayer.h | |||
@@ -0,0 +1,88 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __SFXPLAYER_H__ | ||
24 | #define __SFXPLAYER_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | struct SfxInstrument { | ||
29 | uint8_t *data; | ||
30 | uint16_t volume; | ||
31 | }; | ||
32 | |||
33 | struct SfxModule { | ||
34 | const uint8_t *data; | ||
35 | uint16_t curPos; | ||
36 | uint8_t curOrder; | ||
37 | uint8_t numOrder; | ||
38 | uint8_t orderTable[0x80]; | ||
39 | struct SfxInstrument samples[15]; | ||
40 | }; | ||
41 | |||
42 | struct SfxPattern { | ||
43 | uint16_t note_1; | ||
44 | uint16_t note_2; | ||
45 | uint16_t sampleStart; | ||
46 | uint8_t *sampleBuffer; | ||
47 | uint16_t sampleLen; | ||
48 | uint16_t loopPos; | ||
49 | uint8_t *loopData; | ||
50 | uint16_t loopLen; | ||
51 | uint16_t sampleVolume; | ||
52 | }; | ||
53 | |||
54 | struct Mixer; | ||
55 | struct Resource; | ||
56 | struct Serializer; | ||
57 | struct System; | ||
58 | |||
59 | struct SfxPlayer { | ||
60 | struct Mixer *mixer; | ||
61 | struct Resource *res; | ||
62 | struct System *sys; | ||
63 | |||
64 | void *_mutex; | ||
65 | void *_timerId; | ||
66 | uint16_t _delay; | ||
67 | uint16_t _resNum; | ||
68 | struct SfxModule _sfxMod; | ||
69 | int16_t *_markVar; | ||
70 | }; | ||
71 | |||
72 | void player_create(struct SfxPlayer*, struct Mixer *mix, struct Resource *res, struct System *stub); | ||
73 | void player_init(struct SfxPlayer*); | ||
74 | void player_free(struct SfxPlayer*); | ||
75 | |||
76 | void player_setEventsDelay(struct SfxPlayer*, uint16_t delay); | ||
77 | void player_loadSfxModule(struct SfxPlayer*, uint16_t resNum, uint16_t delay, uint8_t pos); | ||
78 | void player_prepareInstruments(struct SfxPlayer*, const uint8_t *p); | ||
79 | void player_start(struct SfxPlayer*); | ||
80 | void player_stop(struct SfxPlayer*); | ||
81 | void player_handleEvents(struct SfxPlayer*); | ||
82 | void player_handlePattern(struct SfxPlayer*, uint8_t channel, const uint8_t *patternData); | ||
83 | |||
84 | uint32_t player_eventsCallback(uint32_t interval, void *param); | ||
85 | |||
86 | void player_saveOrLoad(struct SfxPlayer*, struct Serializer *ser); | ||
87 | |||
88 | #endif | ||
diff --git a/apps/plugins/xworld/sys.c b/apps/plugins/xworld/sys.c new file mode 100644 index 0000000000..4bcdfafebd --- /dev/null +++ b/apps/plugins/xworld/sys.c | |||
@@ -0,0 +1,942 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ***************************************************************************/ | ||
21 | |||
22 | /* TODO: */ | ||
23 | /* vertical stride support (as of Dec. 2014, only the M:Robe 500 has a color, | ||
24 | vertical stride LCD) */ | ||
25 | |||
26 | /* monochrome/grayscale support (many of these targets have vertical strides, | ||
27 | so get that working first!) */ | ||
28 | |||
29 | #include "plugin.h" | ||
30 | #include "lib/display_text.h" | ||
31 | #include "lib/helper.h" | ||
32 | #include "lib/playback_control.h" | ||
33 | #include "lib/pluginlib_actions.h" | ||
34 | #include "lib/pluginlib_bmp.h" | ||
35 | #include "lib/pluginlib_exit.h" | ||
36 | #include "sys.h" | ||
37 | #include "parts.h" | ||
38 | #include "engine.h" | ||
39 | #include "keymaps.h" | ||
40 | |||
41 | static struct System* save_sys; | ||
42 | |||
43 | static bool sys_save_settings(struct System* sys) | ||
44 | { | ||
45 | File f; | ||
46 | file_create(&f, false); | ||
47 | if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "wb")) | ||
48 | { | ||
49 | return false; | ||
50 | } | ||
51 | file_write(&f, &sys->settings, sizeof(sys->settings)); | ||
52 | file_close(&f); | ||
53 | return true; | ||
54 | } | ||
55 | |||
56 | static void sys_rotate_keymap(struct System* sys) | ||
57 | { | ||
58 | switch(sys->settings.rotation_option) | ||
59 | { | ||
60 | case 0: | ||
61 | sys->keymap.up = BTN_UP; | ||
62 | sys->keymap.down = BTN_DOWN; | ||
63 | sys->keymap.left = BTN_LEFT; | ||
64 | sys->keymap.right = BTN_RIGHT; | ||
65 | #if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) | ||
66 | sys->keymap.upleft = BTN_UP_LEFT; | ||
67 | sys->keymap.upright = BTN_UP_RIGHT; | ||
68 | sys->keymap.downleft = BTN_DOWN_RIGHT; | ||
69 | sys->keymap.downright = BTN_DOWN_LEFT; | ||
70 | #endif | ||
71 | break; | ||
72 | case 1: | ||
73 | sys->keymap.up = BTN_RIGHT; | ||
74 | sys->keymap.down = BTN_LEFT; | ||
75 | sys->keymap.left = BTN_UP; | ||
76 | sys->keymap.right = BTN_DOWN; | ||
77 | #if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) | ||
78 | sys->keymap.upleft = BTN_UP_RIGHT; | ||
79 | sys->keymap.upright = BTN_DOWN_RIGHT; | ||
80 | sys->keymap.downleft = BTN_UP_LEFT; | ||
81 | sys->keymap.downright = BTN_DOWN_LEFT; | ||
82 | #endif | ||
83 | break; | ||
84 | case 2: | ||
85 | sys->keymap.up = BTN_LEFT; | ||
86 | sys->keymap.down = BTN_RIGHT; | ||
87 | sys->keymap.left = BTN_DOWN; | ||
88 | sys->keymap.right = BTN_UP; | ||
89 | #if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) | ||
90 | sys->keymap.upleft = BTN_DOWN_LEFT; | ||
91 | sys->keymap.upright = BTN_UP_LEFT; | ||
92 | sys->keymap.downleft = BTN_DOWN_RIGHT; | ||
93 | sys->keymap.downright = BTN_DOWN_LEFT; | ||
94 | #endif | ||
95 | break; | ||
96 | default: | ||
97 | error("sys_rotate_keymap: fall-through!"); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | static bool sys_load_settings(struct System* sys) | ||
102 | { | ||
103 | File f; | ||
104 | file_create(&f, false); | ||
105 | if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "rb")) | ||
106 | { | ||
107 | return false; | ||
108 | } | ||
109 | file_read(&f, &sys->settings, sizeof(sys->settings)); | ||
110 | file_close(&f); | ||
111 | sys_rotate_keymap(sys); | ||
112 | return true; | ||
113 | } | ||
114 | |||
115 | void exit_handler(void) | ||
116 | { | ||
117 | sys_save_settings(save_sys); | ||
118 | sys_stopAudio(save_sys); | ||
119 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
120 | rb->cpu_boost(false); | ||
121 | #endif | ||
122 | backlight_use_settings(); | ||
123 | } | ||
124 | |||
125 | static bool sys_do_help(void) | ||
126 | { | ||
127 | #ifdef HAVE_LCD_COLOR | ||
128 | rb->lcd_set_foreground(LCD_WHITE); | ||
129 | rb->lcd_set_background(LCD_BLACK); | ||
130 | #endif | ||
131 | rb->lcd_setfont(FONT_UI); | ||
132 | char* help_text[] = {"XWorld", "", | ||
133 | "XWorld", "is", "an", "interpreter", "for", "Another", "World,", "a", "fantastic", "game", "by", "Eric", "Chahi." | ||
134 | }; | ||
135 | struct style_text style[] = { | ||
136 | {0, TEXT_CENTER | TEXT_UNDERLINE}, | ||
137 | LAST_STYLE_ITEM | ||
138 | }; | ||
139 | return display_text(ARRAYLEN(help_text), help_text, style, NULL, true); | ||
140 | } | ||
141 | |||
142 | static const struct opt_items scaling_settings[3] = { | ||
143 | { "Disabled", -1 }, | ||
144 | { "Fast" , -1 }, | ||
145 | #ifdef HAVE_LCD_COLOR | ||
146 | { "Good" , -1 } | ||
147 | #endif | ||
148 | }; | ||
149 | |||
150 | static const struct opt_items rotation_settings[3] = { | ||
151 | { "Disabled" , -1 }, | ||
152 | { "Clockwise" , -1 }, | ||
153 | { "Anticlockwise", -1 } | ||
154 | }; | ||
155 | |||
156 | static void do_video_settings(struct System* sys) | ||
157 | { | ||
158 | MENUITEM_STRINGLIST(menu, "Video Settings", NULL, | ||
159 | "Negative", | ||
160 | "Scaling", | ||
161 | "Rotation", | ||
162 | "Show FPS", | ||
163 | "Zoom on code", | ||
164 | "Back"); | ||
165 | int sel = 0; | ||
166 | while(1) | ||
167 | { | ||
168 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
169 | { | ||
170 | case 0: | ||
171 | rb->set_bool("Negative", &sys->settings.negative_enabled); | ||
172 | break; | ||
173 | case 1: | ||
174 | rb->set_option("Scaling", &sys->settings.scaling_quality, INT, scaling_settings, | ||
175 | #ifdef HAVE_LCD_COLOR | ||
176 | 3 | ||
177 | #else | ||
178 | 2 | ||
179 | #endif | ||
180 | , NULL); | ||
181 | if(sys->settings.scaling_quality && | ||
182 | sys->settings.zoom) | ||
183 | { | ||
184 | rb->splash(HZ*2, "Zoom automatically disabled."); | ||
185 | sys->settings.zoom = false; | ||
186 | } | ||
187 | break; | ||
188 | case 2: | ||
189 | rb->set_option("Rotation", &sys->settings.rotation_option, INT, rotation_settings, 3, NULL); | ||
190 | if(sys->settings.rotation_option && | ||
191 | sys->settings.zoom) | ||
192 | { | ||
193 | rb->splash(HZ*2, "Zoom automatically disabled."); | ||
194 | sys->settings.zoom = false; | ||
195 | } | ||
196 | sys_rotate_keymap(sys); | ||
197 | break; | ||
198 | case 3: | ||
199 | rb->set_bool("Show FPS", &sys->settings.showfps); | ||
200 | break; | ||
201 | case 4: | ||
202 | rb->set_bool("Zoom on code", &sys->settings.zoom); | ||
203 | /* zoom only works with scaling and rotation disabled */ | ||
204 | if(sys->settings.zoom && | ||
205 | ( sys->settings.scaling_quality | | ||
206 | sys->settings.rotation_option)) | ||
207 | { | ||
208 | rb->splash(HZ*2, "Scaling and rotation automatically disabled."); | ||
209 | sys->settings.scaling_quality = 0; | ||
210 | sys->settings.rotation_option = 0; | ||
211 | } | ||
212 | break; | ||
213 | case 5: | ||
214 | rb->lcd_clear_display(); | ||
215 | sys_save_settings(sys); | ||
216 | return; | ||
217 | } | ||
218 | } | ||
219 | } | ||
220 | |||
221 | #define MAX_SOUNDBUF_SIZE 512 | ||
222 | const struct opt_items sound_bufsize_options[] = { | ||
223 | {"8 samples" , 8}, | ||
224 | {"16 samples" , 16}, | ||
225 | {"32 samples" , 32}, | ||
226 | {"64 samples" , 64}, | ||
227 | {"128 samples", 128}, | ||
228 | {"256 samples", 256}, | ||
229 | {"512 samples", 512}, | ||
230 | }; | ||
231 | |||
232 | static void do_sound_settings(struct System* sys) | ||
233 | { | ||
234 | MENUITEM_STRINGLIST(menu, "Sound Settings", NULL, | ||
235 | "Enabled", | ||
236 | "Buffer Level", | ||
237 | "Back", | ||
238 | ); | ||
239 | int sel = 0; | ||
240 | while(1) | ||
241 | { | ||
242 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
243 | { | ||
244 | case 0: | ||
245 | rb->set_bool("Enabled", &sys->settings.sound_enabled); | ||
246 | break; | ||
247 | case 1: | ||
248 | rb->set_option("Buffer Level", &sys->settings.sound_bufsize, INT, | ||
249 | sound_bufsize_options, ARRAYLEN(sound_bufsize_options), NULL); | ||
250 | break; | ||
251 | case 2: | ||
252 | sys_save_settings(sys); | ||
253 | return; | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | |||
258 | static void sys_reset_settings(struct System* sys) | ||
259 | { | ||
260 | sys->settings.negative_enabled = false; | ||
261 | sys->settings.rotation_option = 0; | ||
262 | sys->settings.scaling_quality = 1; | ||
263 | sys->settings.sound_enabled = true; | ||
264 | sys->settings.sound_bufsize = 64; | ||
265 | sys->settings.showfps = true; | ||
266 | sys->settings.zoom = false; | ||
267 | sys_rotate_keymap(sys); | ||
268 | } | ||
269 | |||
270 | static struct System* mainmenu_sysptr; | ||
271 | |||
272 | static int mainmenu_cb(int action, const struct menu_item_ex *this_item) | ||
273 | { | ||
274 | int idx = ((intptr_t)this_item); | ||
275 | if(action == ACTION_REQUEST_MENUITEM && !mainmenu_sysptr->loaded && (idx == 0 || idx == 8 || idx == 10)) | ||
276 | return ACTION_EXIT_MENUITEM; | ||
277 | return action; | ||
278 | } | ||
279 | |||
280 | static AudioCallback audio_callback; | ||
281 | static void* audio_param; | ||
282 | static struct System* audio_sys; | ||
283 | |||
284 | /************************************** MAIN MENU ***************************************/ | ||
285 | |||
286 | void sys_menu(struct System* sys) | ||
287 | { | ||
288 | sys_stopAudio(sys); | ||
289 | rb->splash(0, "Loading..."); | ||
290 | sys->loaded = engine_loadGameState(sys->e, 0); | ||
291 | rb->lcd_update(); | ||
292 | mainmenu_sysptr = sys; | ||
293 | int sel = 0; | ||
294 | MENUITEM_STRINGLIST(menu, "XWorld Menu", mainmenu_cb, | ||
295 | "Resume Game", /* 0 */ | ||
296 | "Start New Game", /* 1 */ | ||
297 | "Playback Control", /* 2 */ | ||
298 | "Video Settings", /* 3 */ | ||
299 | "Sound Settings", /* 4 */ | ||
300 | "Fast Mode", /* 5 */ | ||
301 | "Help", /* 6 */ | ||
302 | "Reset Settings", /* 7 */ | ||
303 | "Load", /* 8 */ | ||
304 | "Save", /* 9 */ | ||
305 | "Quit without Saving", /* 10 */ | ||
306 | "Save and Quit"); /* 11 */ | ||
307 | bool quit = false; | ||
308 | while(!quit) | ||
309 | { | ||
310 | int item; | ||
311 | switch(item = rb->do_menu(&menu, &sel, NULL, false)) | ||
312 | { | ||
313 | case 0: | ||
314 | quit = true; | ||
315 | break; | ||
316 | case 1: | ||
317 | vm_initForPart(&sys->e->vm, GAME_PART_FIRST); // This game part is the protection screen | ||
318 | quit = true; | ||
319 | break; | ||
320 | case 2: | ||
321 | playback_control(NULL); | ||
322 | break; | ||
323 | case 3: | ||
324 | do_video_settings(sys); | ||
325 | break; | ||
326 | case 4: | ||
327 | do_sound_settings(sys); | ||
328 | break; | ||
329 | case 5: | ||
330 | rb->set_bool("Fast Mode", &sys->e->vm._fastMode); | ||
331 | sys_save_settings(sys); | ||
332 | break; | ||
333 | case 6: | ||
334 | sys_do_help(); | ||
335 | break; | ||
336 | case 7: | ||
337 | sys_reset_settings(sys); | ||
338 | sys_save_settings(sys); | ||
339 | break; | ||
340 | case 8: | ||
341 | rb->splash(0, "Loading..."); | ||
342 | sys->loaded = engine_loadGameState(sys->e, 0); | ||
343 | rb->lcd_update(); | ||
344 | break; | ||
345 | case 9: | ||
346 | sys->e->_stateSlot = 0; | ||
347 | rb->splash(0, "Saving..."); | ||
348 | engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave"); | ||
349 | rb->lcd_update(); | ||
350 | break; | ||
351 | case 10: | ||
352 | engine_deleteGameState(sys->e, 0); | ||
353 | exit(PLUGIN_OK); | ||
354 | break; | ||
355 | case 11: | ||
356 | /* saves are NOT deleted on loading */ | ||
357 | exit(PLUGIN_OK); | ||
358 | break; | ||
359 | default: | ||
360 | error("sys_menu: fall-through!"); | ||
361 | } | ||
362 | } | ||
363 | rb->lcd_clear_display(); | ||
364 | sys_startAudio(sys, audio_callback, audio_param); | ||
365 | } | ||
366 | |||
367 | void sys_init(struct System* sys, const char* title) | ||
368 | { | ||
369 | (void) title; | ||
370 | #ifdef HAVE_ADJUSTABLE_CPU_FREQ | ||
371 | rb->cpu_boost(true); | ||
372 | #endif | ||
373 | backlight_ignore_timeout(); | ||
374 | rb_atexit(exit_handler); | ||
375 | save_sys = sys; | ||
376 | rb->memset(&sys->input, 0, sizeof(sys->input)); | ||
377 | sys->mutex_bitfield = ~0; | ||
378 | if(!sys_load_settings(sys)) | ||
379 | { | ||
380 | sys_reset_settings(sys); | ||
381 | } | ||
382 | } | ||
383 | |||
384 | void sys_destroy(struct System* sys) | ||
385 | { | ||
386 | (void) sys; | ||
387 | rb->splash(HZ, "Exiting..."); | ||
388 | } | ||
389 | |||
390 | void sys_setPalette(struct System* sys, uint8_t start, uint8_t n, const uint8_t *buf) | ||
391 | { | ||
392 | for(int i = start; i < start + n; ++i) | ||
393 | { | ||
394 | uint8_t c[3]; | ||
395 | for (int j = 0; j < 3; j++) { | ||
396 | uint8_t col = buf[i * 3 + j]; | ||
397 | c[j] = (col << 2) | (col & 3); | ||
398 | } | ||
399 | #if (LCD_DEPTH > 16) && (LCD_DEPTH <= 24) | ||
400 | sys->palette[i] = (fb_data) { | ||
401 | c[2], c[1], c[0] | ||
402 | }; | ||
403 | #elif defined(HAVE_LCD_COLOR) | ||
404 | sys->palette[i] = FB_RGBPACK(c[0], c[1], c[2]); | ||
405 | #elif LCD_DEPTH > 1 | ||
406 | sys->palette[i] = LCD_BRIGHTNESS((c[0] + c[1] + c[2]) / 3); | ||
407 | #endif | ||
408 | } | ||
409 | } | ||
410 | |||
411 | /****************************** THE MAIN DRAWING METHOD ********************************/ | ||
412 | |||
413 | void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch) | ||
414 | { | ||
415 | static int last_timestamp = 1; | ||
416 | |||
417 | /* get the address of the temporary framebuffer that has been allocated in the audiobuf */ | ||
418 | fb_data* framebuffer = (fb_data*) sys->e->res._memPtrStart + MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE); | ||
419 | buf += y * pitch + x; | ||
420 | |||
421 | /************************ BLIT THE TEMPORARY FRAMEBUFFER ***********************/ | ||
422 | |||
423 | if(sys->settings.rotation_option) | ||
424 | { | ||
425 | /* clockwise */ | ||
426 | if(sys->settings.rotation_option == 1) | ||
427 | { | ||
428 | while (h--) { | ||
429 | /* one byte gives two pixels */ | ||
430 | for (int i = 0; i < w / 2; ++i) { | ||
431 | uint8_t pix1 = *(buf + i) >> 4; | ||
432 | uint8_t pix2 = *(buf + i) & 0xF; | ||
433 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
434 | framebuffer[( (h * 2) ) * 320 + i] = sys->palette[pix1]; | ||
435 | framebuffer[( (h * 2) + 1) * 320 + i] = sys->palette[pix2]; | ||
436 | #else | ||
437 | framebuffer[( (i * 2) ) * 200 + h] = sys->palette[pix1]; | ||
438 | framebuffer[( (i * 2) + 1) * 200 + h] = sys->palette[pix2]; | ||
439 | #endif | ||
440 | } | ||
441 | buf += pitch; | ||
442 | } | ||
443 | } | ||
444 | /* counterclockwise */ | ||
445 | else | ||
446 | { | ||
447 | while (h--) { | ||
448 | /* one byte gives two pixels */ | ||
449 | for (int i = 0; i < w / 2; ++i) { | ||
450 | uint8_t pix1 = *(buf + i) >> 4; | ||
451 | uint8_t pix2 = *(buf + i) & 0xF; | ||
452 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
453 | framebuffer[(200 - h * 2 ) * 320 + ( 320 - i )] = sys->palette[pix1]; | ||
454 | framebuffer[(200 - h * 2 - 1) * 320 + ( 320 - i )] = sys->palette[pix2]; | ||
455 | #else | ||
456 | framebuffer[(320 - i * 2 ) * 200 + ( 200 - h )] = sys->palette[pix1]; | ||
457 | framebuffer[(320 - i * 2 - 1) * 200 + ( 200 - h )] = sys->palette[pix2]; | ||
458 | #endif | ||
459 | } | ||
460 | buf += pitch; | ||
461 | } | ||
462 | } | ||
463 | } | ||
464 | /* no rotation */ | ||
465 | else | ||
466 | { | ||
467 | int next = 0; | ||
468 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
469 | for(int x = 0; x < w / 2; ++x) | ||
470 | { | ||
471 | for(int y = 0; y < h; ++y) | ||
472 | { | ||
473 | uint8_t pix1 = buf[ y * w + x ] >> 4; | ||
474 | uint8_t pix2 = buf[ y * w + x ] & 0xF; | ||
475 | framebuffer[(x + 0)*h + y] = sys->palette[pix1]; | ||
476 | framebuffer[(x + 1)*h + y] = sys->palette[pix2]; | ||
477 | } | ||
478 | } | ||
479 | #else | ||
480 | while (h--) { | ||
481 | /* one byte gives two pixels */ | ||
482 | for (int i = 0; i < w / 2; ++i) { | ||
483 | uint8_t pix1 = *(buf + i) >> 4; | ||
484 | uint8_t pix2 = *(buf + i) & 0xF; | ||
485 | framebuffer[next] = sys->palette[pix1]; | ||
486 | ++next; | ||
487 | framebuffer[next] = sys->palette[pix2]; | ||
488 | ++next; | ||
489 | } | ||
490 | buf += pitch; | ||
491 | } | ||
492 | #endif | ||
493 | } | ||
494 | |||
495 | /*************************** NOW SCALE IT! ***************************/ | ||
496 | |||
497 | if(sys->settings.scaling_quality) | ||
498 | { | ||
499 | struct bitmap in_bmp; | ||
500 | if(sys->settings.rotation_option) | ||
501 | { | ||
502 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
503 | in_bmp.width = 320; | ||
504 | in_bmp.height = 200; | ||
505 | #else | ||
506 | in_bmp.width = 200; | ||
507 | in_bmp.height = 320; | ||
508 | #endif | ||
509 | } | ||
510 | else | ||
511 | { | ||
512 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
513 | in_bmp.width = 200; | ||
514 | in_bmp.height = 320; | ||
515 | #else | ||
516 | in_bmp.width = 320; | ||
517 | in_bmp.height = 200; | ||
518 | #endif | ||
519 | } | ||
520 | in_bmp.data = (unsigned char*) framebuffer; | ||
521 | struct bitmap out_bmp; | ||
522 | out_bmp.width = LCD_WIDTH; | ||
523 | out_bmp.height = LCD_HEIGHT; | ||
524 | out_bmp.data = (unsigned char*) rb->lcd_framebuffer; | ||
525 | |||
526 | #ifdef HAVE_LCD_COLOR | ||
527 | if(sys->settings.scaling_quality == 1) | ||
528 | #endif | ||
529 | simple_resize_bitmap(&in_bmp, &out_bmp); | ||
530 | #ifdef HAVE_LCD_COLOR | ||
531 | else | ||
532 | smooth_resize_bitmap(&in_bmp, &out_bmp); | ||
533 | #endif | ||
534 | } | ||
535 | else | ||
536 | { | ||
537 | #if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE) | ||
538 | for(int x = 0; x < 320; ++x) | ||
539 | { | ||
540 | for(int y = 0; y < 200; ++y) | ||
541 | { | ||
542 | rb->lcd_set_foreground(framebuffer[x * 200 + y]); | ||
543 | rb->lcd_drawpixel(x, y); | ||
544 | } | ||
545 | } | ||
546 | #else | ||
547 | if(sys->settings.zoom) | ||
548 | { | ||
549 | rb->lcd_bitmap_part(framebuffer, CODE_X, CODE_Y, STRIDE(SCREEN_MAIN, 320, 200), 0, 0, 320 - CODE_X, 200 - CODE_Y); | ||
550 | } | ||
551 | else | ||
552 | { | ||
553 | if(sys->settings.rotation_option) | ||
554 | rb->lcd_bitmap(framebuffer, 0, 0, 200, 320); | ||
555 | else | ||
556 | rb->lcd_bitmap(framebuffer, 0, 0, 320, 200); | ||
557 | } | ||
558 | #endif | ||
559 | } | ||
560 | |||
561 | /************************************* APPLY FILTERS ******************************************/ | ||
562 | |||
563 | if(sys->settings.negative_enabled) | ||
564 | { | ||
565 | for(int y = 0; y < LCD_HEIGHT; ++y) | ||
566 | { | ||
567 | for(int x = 0; x < LCD_WIDTH; ++x) | ||
568 | { | ||
569 | #ifdef HAVE_LCD_COLOR | ||
570 | int r, g, b; | ||
571 | fb_data pix = rb->lcd_framebuffer[y * LCD_WIDTH + x]; | ||
572 | r = RGB_UNPACK_RED (pix); | ||
573 | g = RGB_UNPACK_GREEN(pix); | ||
574 | b = RGB_UNPACK_BLUE (pix); | ||
575 | rb->lcd_framebuffer[y * LCD_WIDTH + x] = LCD_RGBPACK(0xff - r, 0xff - g, 0xff - b); | ||
576 | #else | ||
577 | rb->lcd_framebuffer[y * LCD_WIDTH + x] = LCD_BRIGHTNESS(0xff - rb->lcd_framebuffer[y * LCD_WIDTH + x]); | ||
578 | #endif | ||
579 | } | ||
580 | } | ||
581 | } | ||
582 | |||
583 | /*********************** SHOW FPS *************************/ | ||
584 | |||
585 | int current_time = sys_getTimeStamp(sys); | ||
586 | if(sys->settings.showfps) | ||
587 | { | ||
588 | rb->lcd_set_foreground(LCD_BLACK); | ||
589 | rb->lcd_set_background(LCD_WHITE); | ||
590 | /* use 1000 and not HZ here because getTimeStamp is in milliseconds */ | ||
591 | rb->lcd_putsf(0, 0, "FPS: %d", 1000 / ((current_time - last_timestamp == 0) ? 1 : current_time - last_timestamp)); | ||
592 | } | ||
593 | rb->lcd_update(); | ||
594 | last_timestamp = sys_getTimeStamp(sys); | ||
595 | } | ||
596 | |||
597 | static void do_pause_menu(struct System* sys) | ||
598 | { | ||
599 | sys_stopAudio(sys); | ||
600 | int sel = 0; | ||
601 | MENUITEM_STRINGLIST(menu, "XWorld Menu", NULL, | ||
602 | "Resume Game", /* 0 */ | ||
603 | "Start New Game", /* 1 */ | ||
604 | "Playback Control", /* 2 */ | ||
605 | "Video Settings", /* 3 */ | ||
606 | "Sound Settings", /* 4 */ | ||
607 | "Fast Mode", /* 5 */ | ||
608 | "Enter Code", /* 6 */ | ||
609 | "Help", /* 7 */ | ||
610 | "Reset Settings", /* 8 */ | ||
611 | "Load", /* 9 */ | ||
612 | "Save", /* 10 */ | ||
613 | "Quit"); /* 11 */ | ||
614 | |||
615 | bool quit = false; | ||
616 | while(!quit) | ||
617 | { | ||
618 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
619 | { | ||
620 | case 0: | ||
621 | quit = true; | ||
622 | break; | ||
623 | case 1: | ||
624 | vm_initForPart(&sys->e->vm, GAME_PART_FIRST); | ||
625 | quit = true; | ||
626 | break; | ||
627 | case 2: | ||
628 | playback_control(NULL); | ||
629 | break; | ||
630 | case 3: | ||
631 | do_video_settings(sys); | ||
632 | break; | ||
633 | case 4: | ||
634 | do_sound_settings(sys); | ||
635 | break; | ||
636 | case 5: | ||
637 | rb->set_bool("Fast Mode", &sys->e->vm._fastMode); | ||
638 | sys_save_settings(sys); | ||
639 | break; | ||
640 | case 6: | ||
641 | sys->input.code = true; | ||
642 | quit = true; | ||
643 | break; | ||
644 | case 7: | ||
645 | sys_do_help(); | ||
646 | break; | ||
647 | case 8: | ||
648 | sys_reset_settings(sys); | ||
649 | sys_save_settings(sys); | ||
650 | break; | ||
651 | case 9: | ||
652 | rb->splash(0, "Loading..."); | ||
653 | sys->loaded = engine_loadGameState(sys->e, 0); | ||
654 | rb->lcd_update(); | ||
655 | quit = true; | ||
656 | break; | ||
657 | case 10: | ||
658 | sys->e->_stateSlot = 0; | ||
659 | rb->splash(0, "Saving..."); | ||
660 | engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave"); | ||
661 | rb->lcd_update(); | ||
662 | break; | ||
663 | case 11: | ||
664 | exit(PLUGIN_OK); | ||
665 | break; | ||
666 | } | ||
667 | } | ||
668 | rb->lcd_clear_display(); | ||
669 | sys_startAudio(sys, audio_callback, audio_param); | ||
670 | } | ||
671 | |||
672 | void sys_processEvents(struct System* sys) | ||
673 | { | ||
674 | int btn = rb->button_get(false); | ||
675 | btn &= ~BUTTON_REDRAW; | ||
676 | debug(DBG_SYS, "button is 0x%08x", btn); | ||
677 | |||
678 | /* exit early if we can */ | ||
679 | if(btn == BUTTON_NONE) | ||
680 | { | ||
681 | return; | ||
682 | } | ||
683 | |||
684 | /* Ignore some buttons that cause errant input */ | ||
685 | #if (CONFIG_KEYPAD == IPOD_4G_PAD) || \ | ||
686 | (CONFIG_KEYPAD == IPOD_3G_PAD) || \ | ||
687 | (CONFIG_KEYPAD == IPOD_1G2G_PAD) | ||
688 | if(btn & 0x80000000) | ||
689 | return; | ||
690 | #endif | ||
691 | #if (CONFIG_KEYPAD == SANSA_E200_PAD) | ||
692 | if(btn == (BUTTON_SCROLL_FWD || BUTTON_SCROLL_BACK)) | ||
693 | return; | ||
694 | #endif | ||
695 | #if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) | ||
696 | if(btn == (BUTTON_SELECT)) | ||
697 | return; | ||
698 | #endif | ||
699 | |||
700 | /* handle special keys first */ | ||
701 | switch(btn) | ||
702 | { | ||
703 | case BTN_PAUSE: | ||
704 | do_pause_menu(sys); | ||
705 | sys->input.dirMask = 0; | ||
706 | sys->input.button = false; | ||
707 | /* exit early to avoid unwanted button presses being detected */ | ||
708 | return; | ||
709 | default: | ||
710 | exit_on_usb(btn); | ||
711 | break; | ||
712 | } | ||
713 | |||
714 | /* handle releases */ | ||
715 | if(btn & BUTTON_REL) | ||
716 | { | ||
717 | debug(DBG_SYS, "button_rel"); | ||
718 | btn &= ~BUTTON_REL; | ||
719 | |||
720 | if(btn & BTN_FIRE) | ||
721 | sys->input.button = false; | ||
722 | if(btn & sys->keymap.up) | ||
723 | sys->input.dirMask &= ~DIR_UP; | ||
724 | if(btn & sys->keymap.down) | ||
725 | sys->input.dirMask &= ~DIR_DOWN; | ||
726 | if(btn & sys->keymap.left) | ||
727 | sys->input.dirMask &= ~DIR_LEFT; | ||
728 | if(btn & sys->keymap.right) | ||
729 | sys->input.dirMask &= ~DIR_RIGHT; | ||
730 | #ifdef BTN_DOWN_LEFT | ||
731 | if(btn & sys->keymap.downleft) | ||
732 | sys->input.dirMask &= ~(DIR_DOWN | DIR_LEFT); | ||
733 | #endif | ||
734 | #ifdef BTN_DOWN_RIGHT | ||
735 | if(btn & sys->keymap.downright) | ||
736 | sys->input.dirMask &= ~(DIR_DOWN | DIR_RIGHT); | ||
737 | #endif | ||
738 | #ifdef BTN_UP_LEFT | ||
739 | if(btn & sys->keymap.upleft) | ||
740 | sys->input.dirMask &= ~(DIR_UP | DIR_LEFT); | ||
741 | #endif | ||
742 | #ifdef BTN_UP_RIGHT | ||
743 | if(btn & sys->keymap.upright) | ||
744 | sys->input.dirMask &= ~(DIR_UP | DIR_RIGHT); | ||
745 | #endif | ||
746 | } | ||
747 | /* then handle presses */ | ||
748 | else | ||
749 | { | ||
750 | if(btn & BTN_FIRE) | ||
751 | sys->input.button = true; | ||
752 | if(btn & sys->keymap.up) | ||
753 | sys->input.dirMask |= DIR_UP; | ||
754 | if(btn & sys->keymap.down) | ||
755 | sys->input.dirMask |= DIR_DOWN; | ||
756 | if(btn & sys->keymap.left) | ||
757 | sys->input.dirMask |= DIR_LEFT; | ||
758 | if(btn & sys->keymap.right) | ||
759 | sys->input.dirMask |= DIR_RIGHT; | ||
760 | #ifdef BTN_DOWN_LEFT | ||
761 | if(btn & sys->keymap.downleft) | ||
762 | sys->input.dirMask |= (DIR_DOWN | DIR_LEFT); | ||
763 | #endif | ||
764 | #ifdef BTN_DOWN_RIGHT | ||
765 | if(btn & sys->keymap.downright) | ||
766 | sys->input.dirMask |= (DIR_DOWN | DIR_RIGHT); | ||
767 | #endif | ||
768 | #ifdef BTN_UP_LEFT | ||
769 | if(btn & sys->keymap.upleft) | ||
770 | sys->input.dirMask |= (DIR_UP | DIR_LEFT); | ||
771 | #endif | ||
772 | #ifdef BTN_UP_RIGHT | ||
773 | if(btn & sys->keymap.upright) | ||
774 | sys->input.dirMask |= (DIR_UP | DIR_RIGHT); | ||
775 | #endif | ||
776 | } | ||
777 | debug(DBG_SYS, "dirMask is 0x%02x", sys->input.dirMask); | ||
778 | debug(DBG_SYS, "button is %s", sys->input.button == true ? "true" : "false"); | ||
779 | } | ||
780 | |||
781 | void sys_sleep(struct System* sys, uint32_t duration) | ||
782 | { | ||
783 | (void) sys; | ||
784 | /* duration is in ms */ | ||
785 | rb->sleep(duration / 10); | ||
786 | } | ||
787 | |||
788 | uint32_t sys_getTimeStamp(struct System* sys) | ||
789 | { | ||
790 | (void) sys; | ||
791 | return (uint32_t) (*rb->current_tick) * 10; | ||
792 | } | ||
793 | |||
794 | static int16_t rb_soundbuf [MAX_SOUNDBUF_SIZE] IBSS_ATTR; | ||
795 | static int8_t temp_soundbuf[MAX_SOUNDBUF_SIZE] IBSS_ATTR; | ||
796 | static void ICODE_ATTR get_more(const void** start, size_t* size) | ||
797 | { | ||
798 | if(audio_sys->settings.sound_enabled) | ||
799 | { | ||
800 | audio_callback(audio_param, temp_soundbuf, audio_sys->settings.sound_bufsize); | ||
801 | /* convert xworld format (signed 8-bit) to rockbox format (signed 16-bit) */ | ||
802 | for(int i = 0; i < audio_sys->settings.sound_bufsize; ++i) | ||
803 | { | ||
804 | rb_soundbuf[i] = temp_soundbuf[i] * 0x100; | ||
805 | } | ||
806 | *start = rb_soundbuf; | ||
807 | *size = audio_sys->settings.sound_bufsize; | ||
808 | } | ||
809 | else | ||
810 | { | ||
811 | *start = NULL; | ||
812 | *size = 0; | ||
813 | } | ||
814 | } | ||
815 | |||
816 | void sys_startAudio(struct System* sys, AudioCallback callback, void *param) | ||
817 | { | ||
818 | (void) sys; | ||
819 | audio_callback = callback; | ||
820 | audio_param = param; | ||
821 | audio_sys = sys; | ||
822 | rb->pcm_play_data(get_more, NULL, NULL, 0); | ||
823 | } | ||
824 | |||
825 | void sys_stopAudio(struct System* sys) | ||
826 | { | ||
827 | (void) sys; | ||
828 | rb->pcm_play_stop(); | ||
829 | } | ||
830 | |||
831 | uint32_t sys_getOutputSampleRate(struct System* sys) | ||
832 | { | ||
833 | (void) sys; | ||
834 | return rb->mixer_get_frequency(); | ||
835 | } | ||
836 | |||
837 | /* pretty non-reentrant here, but who cares? it's not like someone | ||
838 | would want to run two instances of the game on Rockbox! :D */ | ||
839 | |||
840 | static uint32_t cur_delay; | ||
841 | static void* cur_param; | ||
842 | static TimerCallback user_callback; | ||
843 | static void timer_callback(void) | ||
844 | { | ||
845 | debug(DBG_SYS, "timer callback with delay of %d ms callback 0x%08x param 0x%08x", cur_delay, timer_callback, cur_param); | ||
846 | uint32_t ret = user_callback(cur_delay, cur_param); | ||
847 | if(ret != cur_delay) | ||
848 | { | ||
849 | cur_delay = ret; | ||
850 | rb->timer_register(1, NULL, TIMER_FREQ / 1000 * ret, timer_callback IF_COP(, CPU)); | ||
851 | } | ||
852 | } | ||
853 | |||
854 | void *sys_addTimer(struct System* sys, uint32_t delay, TimerCallback callback, void *param) | ||
855 | { | ||
856 | (void) sys; | ||
857 | debug(DBG_SYS, "registering timer with delay of %d ms callback 0x%08x param 0x%08x", delay, callback, param); | ||
858 | user_callback = callback; | ||
859 | cur_delay = delay; | ||
860 | cur_param = param; | ||
861 | rb->timer_register(1, NULL, TIMER_FREQ / 1000 * delay, timer_callback IF_COP(, CPU)); | ||
862 | return NULL; | ||
863 | } | ||
864 | |||
865 | void sys_removeTimer(struct System* sys, void *timerId) | ||
866 | { | ||
867 | (void) sys; | ||
868 | (void) timerId; | ||
869 | /* there's only one timer per game instance, so ignore both parameters */ | ||
870 | rb->timer_unregister(); | ||
871 | } | ||
872 | |||
873 | void *sys_createMutex(struct System* sys) | ||
874 | { | ||
875 | if(!sys) | ||
876 | error("sys is NULL!"); | ||
877 | for(int i = 0; i < MAX_MUTEXES; ++i) | ||
878 | { | ||
879 | if(sys->mutex_bitfield & (1 << i)) | ||
880 | { | ||
881 | rb->mutex_init(&sys->mutex_memory[i]); | ||
882 | sys->mutex_bitfield |= (1 << i); | ||
883 | return &sys->mutex_memory[i]; | ||
884 | } | ||
885 | } | ||
886 | warning("Out of mutexes!"); | ||
887 | return NULL; | ||
888 | } | ||
889 | |||
890 | void sys_destroyMutex(struct System* sys, void *mutex) | ||
891 | { | ||
892 | int mutex_number = ((char*)mutex - (char*)sys->mutex_memory) / sizeof(struct mutex); /* pointer arithmetic! check for bugs! */ | ||
893 | sys->mutex_bitfield &= ~(1 << mutex_number); | ||
894 | } | ||
895 | |||
896 | void sys_lockMutex(struct System* sys, void *mutex) | ||
897 | { | ||
898 | (void) sys; | ||
899 | debug(DBG_SYS, "calling mutex_lock"); | ||
900 | rb->mutex_lock((struct mutex*) mutex); | ||
901 | } | ||
902 | |||
903 | void sys_unlockMutex(struct System* sys, void *mutex) | ||
904 | { | ||
905 | (void) sys; | ||
906 | debug(DBG_SYS, "calling mutex_unlock"); | ||
907 | rb->mutex_unlock((struct mutex*) mutex); | ||
908 | } | ||
909 | |||
910 | uint8_t* getOffScreenFramebuffer(struct System* sys) | ||
911 | { | ||
912 | (void) sys; | ||
913 | return NULL; | ||
914 | } | ||
915 | |||
916 | void *sys_get_buffer(struct System* sys, size_t sz) | ||
917 | { | ||
918 | if((signed int)sys->bytes_left - (signed int)sz >= 0) | ||
919 | { | ||
920 | void* ret = sys->membuf; | ||
921 | rb->memset(ret, 0, sz); | ||
922 | sys->membuf += sz; | ||
923 | return ret; | ||
924 | } | ||
925 | else | ||
926 | { | ||
927 | error("sys_get_buffer: out of memory!"); | ||
928 | return NULL; | ||
929 | } | ||
930 | } | ||
931 | |||
932 | void MutexStack(struct MutexStack_t* s, struct System *stub, void *mutex) | ||
933 | { | ||
934 | s->sys = stub; | ||
935 | s->_mutex = mutex; | ||
936 | sys_lockMutex(s->sys, s->_mutex); | ||
937 | } | ||
938 | |||
939 | void MutexStack_destroy(struct MutexStack_t* s) | ||
940 | { | ||
941 | sys_unlockMutex(s->sys, s->_mutex); | ||
942 | } | ||
diff --git a/apps/plugins/xworld/sys.h b/apps/plugins/xworld/sys.h new file mode 100644 index 0000000000..f1920acf37 --- /dev/null +++ b/apps/plugins/xworld/sys.h | |||
@@ -0,0 +1,146 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __XWORLD_SYS_H__ | ||
24 | #define __XWORLD_SYS_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | #define SYS_NEGATIVE_COLOR | ||
29 | #define NUM_COLORS 16 | ||
30 | #define MAX_MUTEXES 16 | ||
31 | #define SETTINGS_FILE "settings.xfg" | ||
32 | #define CODE_X 80 | ||
33 | #define CODE_Y 36 | ||
34 | |||
35 | enum { | ||
36 | DIR_LEFT = 1 << 0, | ||
37 | DIR_RIGHT = 1 << 1, | ||
38 | DIR_UP = 1 << 2, | ||
39 | DIR_DOWN = 1 << 3 | ||
40 | }; | ||
41 | |||
42 | struct PlayerInput { | ||
43 | |||
44 | uint8_t dirMask; | ||
45 | bool button; | ||
46 | bool code; | ||
47 | bool pause; | ||
48 | bool quit; | ||
49 | char lastChar; | ||
50 | bool save, load; | ||
51 | bool fastMode; | ||
52 | int8_t stateSlot; | ||
53 | }; | ||
54 | |||
55 | |||
56 | struct keymapping_t { | ||
57 | int up; | ||
58 | int down; | ||
59 | int left; | ||
60 | int right; | ||
61 | #if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) | ||
62 | int upleft; | ||
63 | int upright; | ||
64 | int downleft; | ||
65 | int downright; | ||
66 | #endif | ||
67 | }; | ||
68 | |||
69 | typedef void (*AudioCallback)(void *param, uint8_t *stream, int len); | ||
70 | typedef uint32_t (*TimerCallback)(uint32_t delay, void *param); | ||
71 | |||
72 | struct System { | ||
73 | struct mutex mutex_memory[MAX_MUTEXES]; | ||
74 | uint16_t mutex_bitfield; | ||
75 | struct PlayerInput input; | ||
76 | fb_data palette[NUM_COLORS]; | ||
77 | struct Engine* e; | ||
78 | struct keymapping_t keymap; | ||
79 | |||
80 | void *membuf; | ||
81 | size_t bytes_left; | ||
82 | |||
83 | bool loaded; | ||
84 | |||
85 | struct { | ||
86 | bool negative_enabled; | ||
87 | /* | ||
88 | scaling quality: | ||
89 | 0: off | ||
90 | 1: fast | ||
91 | 2: good (color only) | ||
92 | */ | ||
93 | int scaling_quality; | ||
94 | /* | ||
95 | rotation: | ||
96 | 0: off | ||
97 | 1: cw | ||
98 | 2: ccw | ||
99 | */ | ||
100 | int rotation_option; | ||
101 | |||
102 | bool showfps; | ||
103 | bool sound_enabled; | ||
104 | int sound_bufsize; | ||
105 | bool zoom; | ||
106 | } settings; | ||
107 | }; | ||
108 | |||
109 | void sys_init(struct System*, const char *title); | ||
110 | void sys_menu(struct System*); | ||
111 | void sys_destroy(struct System*); | ||
112 | |||
113 | void sys_setPalette(struct System*, uint8_t s, uint8_t n, const uint8_t *buf); | ||
114 | void sys_copyRect(struct System*, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch); | ||
115 | |||
116 | void sys_processEvents(struct System*); | ||
117 | void sys_sleep(struct System*, uint32_t duration); | ||
118 | uint32_t sys_getTimeStamp(struct System*); | ||
119 | |||
120 | void sys_startAudio(struct System*, AudioCallback callback, void *param); | ||
121 | void sys_stopAudio(struct System*); | ||
122 | uint32_t sys_getOutputSampleRate(struct System*); | ||
123 | |||
124 | void *sys_addTimer(struct System*, uint32_t delay, TimerCallback callback, void *param); | ||
125 | void sys_removeTimer(struct System*, void *timerId); | ||
126 | |||
127 | void *sys_createMutex(struct System*); | ||
128 | void sys_destroyMutex(struct System*, void *mutex); | ||
129 | void sys_lockMutex(struct System*, void *mutex); | ||
130 | void sys_unlockMutex(struct System*, void *mutex); | ||
131 | |||
132 | /* a quick 'n dirty function to get some bytes in the audio buffer after zeroing them */ | ||
133 | /* pretty much does the same thing as calloc, though much uglier and there's no free() */ | ||
134 | void *sys_get_buffer(struct System*, size_t); | ||
135 | |||
136 | uint8_t* getOffScreenFramebuffer(struct System*); | ||
137 | |||
138 | struct MutexStack_t { | ||
139 | struct System *sys; | ||
140 | void *_mutex; | ||
141 | }; | ||
142 | |||
143 | void MutexStack(struct MutexStack_t*, struct System*, void*); | ||
144 | void MutexStack_destroy(struct MutexStack_t*); | ||
145 | |||
146 | #endif | ||
diff --git a/apps/plugins/xworld/util.c b/apps/plugins/xworld/util.c new file mode 100644 index 0000000000..b82691ad29 --- /dev/null +++ b/apps/plugins/xworld/util.c | |||
@@ -0,0 +1,82 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "lib/pluginlib_exit.h" | ||
25 | #include <stdarg.h> | ||
26 | #include "util.h" | ||
27 | uint16_t g_debugMask; | ||
28 | |||
29 | #ifdef XWORLD_DEBUG | ||
30 | void debug_real(uint16_t cm, const char *msg, ...) { | ||
31 | #ifdef ROCKBOX_HAS_LOGF | ||
32 | char buf[1024]; | ||
33 | if (cm & g_debugMask) { | ||
34 | va_list va; | ||
35 | va_start(va, msg); | ||
36 | rb->vsnprintf(buf, 1024, msg, va); | ||
37 | va_end(va); | ||
38 | LOGF("%s", buf); | ||
39 | } | ||
40 | #else | ||
41 | (void) cm; | ||
42 | (void) msg; | ||
43 | #endif | ||
44 | } | ||
45 | #endif | ||
46 | |||
47 | void error(const char *msg, ...) { | ||
48 | char buf[1024]; | ||
49 | va_list va; | ||
50 | va_start(va, msg); | ||
51 | rb->vsnprintf(buf, 1024, msg, va); | ||
52 | va_end(va); | ||
53 | rb->splashf(HZ * 2, "ERROR: %s!", buf); | ||
54 | LOGF("ERROR: %s", buf); | ||
55 | exit(-1); | ||
56 | } | ||
57 | |||
58 | void warning(const char *msg, ...) { | ||
59 | char buf[1024]; | ||
60 | va_list va; | ||
61 | va_start(va, msg); | ||
62 | rb->vsnprintf(buf, 1024, msg, va); | ||
63 | va_end(va); | ||
64 | rb->splashf(HZ * 2, "WARNING: %s!", buf); | ||
65 | LOGF("WARNING: %s", buf); | ||
66 | } | ||
67 | |||
68 | void string_lower(char *p) { | ||
69 | for (; *p; ++p) { | ||
70 | if (*p >= 'A' && *p <= 'Z') { | ||
71 | *p += 'a' - 'A'; | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | void string_upper(char *p) { | ||
77 | for (; *p; ++p) { | ||
78 | if (*p >= 'a' && *p <= 'z') { | ||
79 | *p += 'A' - 'a'; | ||
80 | } | ||
81 | } | ||
82 | } | ||
diff --git a/apps/plugins/xworld/util.h b/apps/plugins/xworld/util.h new file mode 100644 index 0000000000..8852335661 --- /dev/null +++ b/apps/plugins/xworld/util.h | |||
@@ -0,0 +1,59 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __UTIL_H__ | ||
24 | #define __UTIL_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | /* #define XWORLD_DEBUG */ | ||
29 | |||
30 | #ifdef XWORLD_DEBUG | ||
31 | #define debug(m,f,...) debug_real(m, f, ##__VA_ARGS__) | ||
32 | #else | ||
33 | #define debug(m,f,...) | ||
34 | #endif | ||
35 | |||
36 | enum { | ||
37 | DBG_VM = 1 << 0, | ||
38 | DBG_BANK = 1 << 1, | ||
39 | DBG_VIDEO = 1 << 2, | ||
40 | DBG_SND = 1 << 3, | ||
41 | DBG_SER = 1 << 4, | ||
42 | DBG_INFO = 1 << 5, | ||
43 | DBG_RES = 1 << 6, | ||
44 | DBG_FILE = 1 << 7, | ||
45 | DBG_SYS = 1 << 8, | ||
46 | DBG_ENG = 1 << 9 | ||
47 | }; | ||
48 | |||
49 | extern uint16_t g_debugMask; | ||
50 | #ifdef XWORLD_DEBUG | ||
51 | extern void debug_real(uint16_t cm, const char *msg, ...); | ||
52 | #endif | ||
53 | extern void error(const char *msg, ...); | ||
54 | extern void warning(const char *msg, ...); | ||
55 | |||
56 | extern void string_lower(char *p); | ||
57 | extern void string_upper(char *p); | ||
58 | |||
59 | #endif | ||
diff --git a/apps/plugins/xworld/video.c b/apps/plugins/xworld/video.c new file mode 100644 index 0000000000..b4182df7a7 --- /dev/null +++ b/apps/plugins/xworld/video.c | |||
@@ -0,0 +1,1141 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "video.h" | ||
24 | #include "video_data.h" | ||
25 | #include "resource.h" | ||
26 | #include "serializer.h" | ||
27 | #include "sys.h" | ||
28 | #include "file.h" | ||
29 | |||
30 | void polygon_readVertices(struct Polygon* g, const uint8_t *p, uint16_t zoom) { | ||
31 | g->bbw = (*p++) * zoom / 64; | ||
32 | g->bbh = (*p++) * zoom / 64; | ||
33 | g->numPoints = *p++; | ||
34 | assert((g->numPoints & 1) == 0 && g->numPoints < MAX_POINTS); | ||
35 | |||
36 | //Read all points, directly from bytecode segment | ||
37 | for (int i = 0; i < g->numPoints; ++i) { | ||
38 | struct Point *pt = &g->points[i]; | ||
39 | pt->x = (*p++) * zoom / 64; | ||
40 | pt->y = (*p++) * zoom / 64; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | void video_create(struct Video* v, struct Resource* res, struct System* sys) | ||
45 | { | ||
46 | v->res = res; | ||
47 | v->sys = sys; | ||
48 | } | ||
49 | |||
50 | void video_init(struct Video* v) { | ||
51 | v->paletteIdRequested = NO_PALETTE_CHANGE_REQUESTED; | ||
52 | v->page_data = v->res->_memPtrStart + MEM_BLOCK_SIZE; | ||
53 | |||
54 | rb->memset(v->page_data, 0, 4 * VID_PAGE_SIZE); | ||
55 | |||
56 | for (int i = 0; i < 4; ++i) { | ||
57 | v->_pagePtrs[i] = v->page_data + i * VID_PAGE_SIZE; | ||
58 | } | ||
59 | |||
60 | v->_curPagePtr3 = video_getPagePtr(v, 1); | ||
61 | v->_curPagePtr2 = video_getPagePtr(v, 2); | ||
62 | |||
63 | video_changePagePtr1(v, 0xFE); | ||
64 | |||
65 | v->_interpTable[0] = 0x4000; | ||
66 | |||
67 | for (int i = 1; i < 0x400; ++i) { | ||
68 | v->_interpTable[i] = 0x4000 / i; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | void video_setDataBuffer(struct Video* v, uint8_t *dataBuf, uint16_t offset) { | ||
73 | |||
74 | v->_dataBuf = dataBuf; | ||
75 | v->_pData.pc = dataBuf + offset; | ||
76 | } | ||
77 | |||
78 | |||
79 | /* A shape can be given in two different ways: | ||
80 | |||
81 | - A list of screenspace vertices. | ||
82 | - A list of objectspace vertices, based on a delta from the first vertex. | ||
83 | |||
84 | This is a recursive function. */ | ||
85 | void video_readAndDrawPolygon(struct Video* v, uint8_t color, uint16_t zoom, const struct Point *pt) { | ||
86 | |||
87 | uint8_t i = scriptPtr_fetchByte(&v->_pData); | ||
88 | |||
89 | //This is | ||
90 | if (i >= 0xC0) { // 0xc0 = 192 | ||
91 | |||
92 | // WTF ? | ||
93 | if (color & 0x80) { //0x80 = 128 (1000 0000) | ||
94 | color = i & 0x3F; //0x3F = 63 (0011 1111) | ||
95 | } | ||
96 | |||
97 | // pc is misleading here since we are not reading bytecode but only | ||
98 | // vertices informations. | ||
99 | polygon_readVertices(&v->polygon, v->_pData.pc, zoom); | ||
100 | |||
101 | video_fillPolygon(v, color, zoom, pt); | ||
102 | |||
103 | |||
104 | |||
105 | } else { | ||
106 | i &= 0x3F; //0x3F = 63 | ||
107 | if (i == 1) { | ||
108 | warning("video_readAndDrawPolygon() ec=0x%X (i != 2)", 0xF80); | ||
109 | } else if (i == 2) { | ||
110 | video_readAndDrawPolygonHierarchy(v, zoom, pt); | ||
111 | |||
112 | } else { | ||
113 | warning("video_readAndDrawPolygon() ec=0x%X (i != 2)", 0xFBB); | ||
114 | } | ||
115 | } | ||
116 | |||
117 | |||
118 | |||
119 | } | ||
120 | |||
121 | void video_fillPolygon(struct Video* v, uint16_t color, uint16_t zoom, const struct Point *pt) { | ||
122 | |||
123 | (void) zoom; | ||
124 | |||
125 | if (v->polygon.bbw == 0 && v->polygon.bbh == 1 && v->polygon.numPoints == 4) { | ||
126 | video_drawPoint(v, color, pt->x, pt->y); | ||
127 | |||
128 | return; | ||
129 | } | ||
130 | |||
131 | int16_t x1 = pt->x - v->polygon.bbw / 2; | ||
132 | int16_t x2 = pt->x + v->polygon.bbw / 2; | ||
133 | int16_t y1 = pt->y - v->polygon.bbh / 2; | ||
134 | int16_t y2 = pt->y + v->polygon.bbh / 2; | ||
135 | |||
136 | if (x1 > 319 || x2 < 0 || y1 > 199 || y2 < 0) | ||
137 | return; | ||
138 | |||
139 | v->_hliney = y1; | ||
140 | |||
141 | uint16_t i, j; | ||
142 | i = 0; | ||
143 | j = v->polygon.numPoints - 1; | ||
144 | |||
145 | x2 = v->polygon.points[i].x + x1; | ||
146 | x1 = v->polygon.points[j].x + x1; | ||
147 | |||
148 | ++i; | ||
149 | --j; | ||
150 | |||
151 | drawLine drawFct; | ||
152 | if (color < 0x10) { | ||
153 | drawFct = &video_drawLineN; | ||
154 | } else if (color > 0x10) { | ||
155 | drawFct = &video_drawLineP; | ||
156 | } else { | ||
157 | drawFct = &video_drawLineBlend; | ||
158 | } | ||
159 | |||
160 | uint32_t cpt1 = x1 << 16; | ||
161 | uint32_t cpt2 = x2 << 16; | ||
162 | |||
163 | while (1) { | ||
164 | v->polygon.numPoints -= 2; | ||
165 | if (v->polygon.numPoints == 0) { | ||
166 | #if TRACE_FRAMEBUFFER | ||
167 | video_dumpFrameBuffers(v, "fillPolygonEnd"); | ||
168 | #endif | ||
169 | #if TRACE_BG_BUFFER | ||
170 | video_dumpBackGroundBuffer(v); | ||
171 | #endif | ||
172 | break; | ||
173 | } | ||
174 | uint16_t h; | ||
175 | int32_t step1 = video_calcStep(v, &v->polygon.points[j + 1], &v->polygon.points[j], &h); | ||
176 | int32_t step2 = video_calcStep(v, &v->polygon.points[i - 1], &v->polygon.points[i], &h); | ||
177 | |||
178 | ++i; | ||
179 | --j; | ||
180 | |||
181 | cpt1 = (cpt1 & 0xFFFF0000) | 0x7FFF; | ||
182 | cpt2 = (cpt2 & 0xFFFF0000) | 0x8000; | ||
183 | |||
184 | if (h == 0) { | ||
185 | cpt1 += step1; | ||
186 | cpt2 += step2; | ||
187 | } else { | ||
188 | for (; h != 0; --h) { | ||
189 | if (v->_hliney >= 0) { | ||
190 | x1 = cpt1 >> 16; | ||
191 | x2 = cpt2 >> 16; | ||
192 | if (x1 <= 319 && x2 >= 0) { | ||
193 | if (x1 < 0) x1 = 0; | ||
194 | if (x2 > 319) x2 = 319; | ||
195 | (*drawFct)(v, x1, x2, color); | ||
196 | } | ||
197 | } | ||
198 | cpt1 += step1; | ||
199 | cpt2 += step2; | ||
200 | ++v->_hliney; | ||
201 | if (v->_hliney > 199) return; | ||
202 | } | ||
203 | } | ||
204 | |||
205 | #if TRACE_FRAMEBUFFER | ||
206 | video_dumpFrameBuffers(v, "fillPolygonChild"); | ||
207 | #endif | ||
208 | #if TRACE_BG_BUFFER | ||
209 | |||
210 | video_dumpBackGroundBuffer(v); | ||
211 | #endif | ||
212 | } | ||
213 | } | ||
214 | |||
215 | /* | ||
216 | What is read from the bytecode is not a pure screnspace polygon but a polygonspace polygon. | ||
217 | |||
218 | */ | ||
219 | void video_readAndDrawPolygonHierarchy(struct Video* v, uint16_t zoom, const struct Point *pgc) { | ||
220 | |||
221 | struct Point pt = *pgc; | ||
222 | pt.x -= scriptPtr_fetchByte(&v->_pData) * zoom / 64; | ||
223 | pt.y -= scriptPtr_fetchByte(&v->_pData) * zoom / 64; | ||
224 | |||
225 | int16_t childs = scriptPtr_fetchByte(&v->_pData); | ||
226 | debug(DBG_VIDEO, "video_readAndDrawPolygonHierarchy childs=%d", childs); | ||
227 | |||
228 | for ( ; childs >= 0; --childs) { | ||
229 | |||
230 | uint16_t off = scriptPtr_fetchWord(&v->_pData); | ||
231 | |||
232 | struct Point po; | ||
233 | po = pt; | ||
234 | po.x += scriptPtr_fetchByte(&v->_pData) * zoom / 64; | ||
235 | po.y += scriptPtr_fetchByte(&v->_pData) * zoom / 64; | ||
236 | |||
237 | uint16_t color = 0xFF; | ||
238 | uint16_t _bp = off; | ||
239 | off &= 0x7FFF; | ||
240 | |||
241 | if (_bp & 0x8000) { | ||
242 | color = *v->_pData.pc & 0x7F; | ||
243 | v->_pData.pc += 2; | ||
244 | } | ||
245 | |||
246 | uint8_t *bak = v->_pData.pc; | ||
247 | v->_pData.pc = v->_dataBuf + off * 2; | ||
248 | |||
249 | |||
250 | video_readAndDrawPolygon(v, color, zoom, &po); | ||
251 | |||
252 | |||
253 | v->_pData.pc = bak; | ||
254 | } | ||
255 | |||
256 | |||
257 | } | ||
258 | |||
259 | int32_t video_calcStep(struct Video* v, const struct Point *p1, const struct Point *p2, uint16_t *dy) { | ||
260 | (void) v; | ||
261 | *dy = p2->y - p1->y; | ||
262 | return (p2->x - p1->x) * v->_interpTable[*dy] * 4; | ||
263 | } | ||
264 | |||
265 | void video_drawString(struct Video* v, uint8_t color, uint16_t x, uint16_t y, uint16_t stringId) { | ||
266 | |||
267 | const struct StrEntry *se = video_stringsTableEng; | ||
268 | |||
269 | //Search for the location where the string is located. | ||
270 | while (se->id != END_OF_STRING_DICTIONARY && se->id != stringId) | ||
271 | ++se; | ||
272 | |||
273 | debug(DBG_VIDEO, "video_drawString(%d, %d, %d, '%s')", color, x, y, se->str); | ||
274 | |||
275 | //Not found | ||
276 | if (se->id == END_OF_STRING_DICTIONARY) | ||
277 | return; | ||
278 | |||
279 | |||
280 | //Used if the string contains a return carriage. | ||
281 | uint16_t xOrigin = x; | ||
282 | int len = rb->strlen(se->str); | ||
283 | for (int i = 0; i < len; ++i) { | ||
284 | |||
285 | if (se->str[i] == '\n') { | ||
286 | y += 8; | ||
287 | x = xOrigin; | ||
288 | continue; | ||
289 | } | ||
290 | |||
291 | video_drawChar(v, se->str[i], x, y, color, v->_curPagePtr1); | ||
292 | x++; | ||
293 | |||
294 | } | ||
295 | } | ||
296 | |||
297 | void video_drawChar(struct Video* v, uint8_t character, uint16_t x, uint16_t y, uint8_t color, uint8_t *buf) { | ||
298 | (void) v; | ||
299 | if (x <= 39 && y <= 192) { | ||
300 | |||
301 | /* each character is 8x8 */ | ||
302 | const uint8_t *ft = video_font + (character - ' ') * 8; | ||
303 | |||
304 | /* x is multiplied by 4 and not 8 because there are two pixels per byte */ | ||
305 | uint8_t *p = buf + x * 4 + y * 160; | ||
306 | |||
307 | for (int j = 0; j < 8; ++j) { | ||
308 | uint8_t ch = *(ft + j); | ||
309 | for (int i = 0; i < 4; ++i) { | ||
310 | uint8_t b = *(p + i); | ||
311 | uint8_t cmask = 0xFF; | ||
312 | uint8_t colb = 0; | ||
313 | if (ch & 0x80) { | ||
314 | colb |= color << 4; | ||
315 | cmask &= 0x0F; | ||
316 | } | ||
317 | ch <<= 1; | ||
318 | if (ch & 0x80) { | ||
319 | colb |= color; | ||
320 | cmask &= 0xF0; | ||
321 | } | ||
322 | ch <<= 1; | ||
323 | *(p + i) = (b & cmask) | colb; | ||
324 | } | ||
325 | /* skip to the next line (320 pixels = 160 bytes) */ | ||
326 | p += 160; | ||
327 | } | ||
328 | } | ||
329 | } | ||
330 | |||
331 | void video_drawPoint(struct Video* v, uint8_t color, int16_t x, int16_t y) { | ||
332 | debug(DBG_VIDEO, "drawPoint(%d, %d, %d)", color, x, y); | ||
333 | if (x >= 0 && x <= 319 && y >= 0 && y <= 199) { | ||
334 | uint16_t off = y * 160 + x / 2; | ||
335 | |||
336 | uint8_t cmasko, cmaskn; | ||
337 | if (x & 1) { | ||
338 | cmaskn = 0x0F; | ||
339 | cmasko = 0xF0; | ||
340 | } else { | ||
341 | cmaskn = 0xF0; | ||
342 | cmasko = 0x0F; | ||
343 | } | ||
344 | |||
345 | uint8_t colb = (color << 4) | color; | ||
346 | if (color == 0x10) { | ||
347 | cmaskn &= 0x88; | ||
348 | cmasko = ~cmaskn; | ||
349 | colb = 0x88; | ||
350 | } else if (color == 0x11) { | ||
351 | colb = *(v->_pagePtrs[0] + off); | ||
352 | } | ||
353 | uint8_t b = *(v->_curPagePtr1 + off); | ||
354 | *(v->_curPagePtr1 + off) = (b & cmasko) | (colb & cmaskn); | ||
355 | } | ||
356 | } | ||
357 | |||
358 | /* Blend a line in the current framebuffer (v->_curPagePtr1) | ||
359 | */ | ||
360 | void video_drawLineBlend(struct Video* v, int16_t x1, int16_t x2, uint8_t color) { | ||
361 | /* silence warnings without XWORLD_DEBUG */ | ||
362 | (void) color; | ||
363 | debug(DBG_VIDEO, "drawLineBlend(%d, %d, %d)", x1, x2, color); | ||
364 | int16_t xmax = MAX(x1, x2); | ||
365 | int16_t xmin = MIN(x1, x2); | ||
366 | uint8_t *p = v->_curPagePtr1 + v->_hliney * 160 + xmin / 2; | ||
367 | |||
368 | uint16_t w = xmax / 2 - xmin / 2 + 1; | ||
369 | uint8_t cmaske = 0; | ||
370 | uint8_t cmasks = 0; | ||
371 | if (xmin & 1) { | ||
372 | --w; | ||
373 | cmasks = 0xF7; | ||
374 | } | ||
375 | if (!(xmax & 1)) { | ||
376 | --w; | ||
377 | cmaske = 0x7F; | ||
378 | } | ||
379 | |||
380 | if (cmasks != 0) { | ||
381 | *p = (*p & cmasks) | 0x08; | ||
382 | ++p; | ||
383 | } | ||
384 | while (w--) { | ||
385 | *p = (*p & 0x77) | 0x88; | ||
386 | ++p; | ||
387 | } | ||
388 | if (cmaske != 0) { | ||
389 | *p = (*p & cmaske) | 0x80; | ||
390 | ++p; | ||
391 | } | ||
392 | |||
393 | |||
394 | } | ||
395 | |||
396 | void video_drawLineN(struct Video* v, int16_t x1, int16_t x2, uint8_t color) { | ||
397 | debug(DBG_VIDEO, "drawLineN(%d, %d, %d)", x1, x2, color); | ||
398 | int16_t xmax = MAX(x1, x2); | ||
399 | int16_t xmin = MIN(x1, x2); | ||
400 | uint8_t *p = v->_curPagePtr1 + v->_hliney * 160 + xmin / 2; | ||
401 | |||
402 | uint16_t w = xmax / 2 - xmin / 2 + 1; | ||
403 | uint8_t cmaske = 0; | ||
404 | uint8_t cmasks = 0; | ||
405 | if (xmin & 1) { | ||
406 | --w; | ||
407 | cmasks = 0xF0; | ||
408 | } | ||
409 | if (!(xmax & 1)) { | ||
410 | --w; | ||
411 | cmaske = 0x0F; | ||
412 | } | ||
413 | |||
414 | uint8_t colb = ((color & 0xF) << 4) | (color & 0xF); | ||
415 | if (cmasks != 0) { | ||
416 | *p = (*p & cmasks) | (colb & 0x0F); | ||
417 | ++p; | ||
418 | } | ||
419 | while (w--) { | ||
420 | *p++ = colb; | ||
421 | } | ||
422 | if (cmaske != 0) { | ||
423 | *p = (*p & cmaske) | (colb & 0xF0); | ||
424 | ++p; | ||
425 | } | ||
426 | |||
427 | |||
428 | } | ||
429 | |||
430 | void video_drawLineP(struct Video* v, int16_t x1, int16_t x2, uint8_t color) { | ||
431 | /* silence warnings without XWORLD_DEBUG */ | ||
432 | (void) color; | ||
433 | debug(DBG_VIDEO, "drawLineP(%d, %d, %d)", x1, x2, color); | ||
434 | int16_t xmax = MAX(x1, x2); | ||
435 | int16_t xmin = MIN(x1, x2); | ||
436 | uint16_t off = v->_hliney * 160 + xmin / 2; | ||
437 | uint8_t *p = v->_curPagePtr1 + off; | ||
438 | uint8_t *q = v->_pagePtrs[0] + off; | ||
439 | |||
440 | uint8_t w = xmax / 2 - xmin / 2 + 1; | ||
441 | uint8_t cmaske = 0; | ||
442 | uint8_t cmasks = 0; | ||
443 | if (xmin & 1) { | ||
444 | --w; | ||
445 | cmasks = 0xF0; | ||
446 | } | ||
447 | if (!(xmax & 1)) { | ||
448 | --w; | ||
449 | cmaske = 0x0F; | ||
450 | } | ||
451 | |||
452 | if (cmasks != 0) { | ||
453 | *p = (*p & cmasks) | (*q & 0x0F); | ||
454 | ++p; | ||
455 | ++q; | ||
456 | } | ||
457 | while (w--) { | ||
458 | *p++ = *q++; | ||
459 | } | ||
460 | if (cmaske != 0) { | ||
461 | *p = (*p & cmaske) | (*q & 0xF0); | ||
462 | ++p; | ||
463 | ++q; | ||
464 | } | ||
465 | |||
466 | } | ||
467 | |||
468 | uint8_t *video_getPagePtr(struct Video* v, uint8_t page) { | ||
469 | uint8_t *p; | ||
470 | if (page <= 3) { | ||
471 | p = v->_pagePtrs[page]; | ||
472 | } else { | ||
473 | switch (page) { | ||
474 | case 0xFF: | ||
475 | p = v->_curPagePtr3; | ||
476 | break; | ||
477 | case 0xFE: | ||
478 | p = v->_curPagePtr2; | ||
479 | break; | ||
480 | default: | ||
481 | p = v->_pagePtrs[0]; // XXX check | ||
482 | warning("video_getPagePtr() p != [0,1,2,3,0xFF,0xFE] == 0x%X", page); | ||
483 | break; | ||
484 | } | ||
485 | } | ||
486 | return p; | ||
487 | } | ||
488 | |||
489 | |||
490 | |||
491 | void video_changePagePtr1(struct Video* v, uint8_t page) { | ||
492 | debug(DBG_VIDEO, "video_changePagePtr1(%d)", page); | ||
493 | v->_curPagePtr1 = video_getPagePtr(v, page); | ||
494 | } | ||
495 | |||
496 | |||
497 | |||
498 | void video_fillPage(struct Video* v, uint8_t pageId, uint8_t color) { | ||
499 | debug(DBG_VIDEO, "video_fillPage(%d, %d)", pageId, color); | ||
500 | uint8_t *p = video_getPagePtr(v, pageId); | ||
501 | |||
502 | // Since a palette indice is coded on 4 bits, we need to duplicate the | ||
503 | // clearing color to the upper part of the byte. | ||
504 | uint8_t c = (color << 4) | color; | ||
505 | |||
506 | rb->memset(p, c, VID_PAGE_SIZE); | ||
507 | |||
508 | #if TRACE_FRAMEBUFFER | ||
509 | video_dumpFrameBuffers(v, "-fillPage"); | ||
510 | #endif | ||
511 | #if TRACE_BG_BUFFER | ||
512 | |||
513 | video_dumpBackGroundBuffer(v); | ||
514 | #endif | ||
515 | } | ||
516 | |||
517 | |||
518 | |||
519 | |||
520 | |||
521 | #if TRACE_FRAMEBUFFER | ||
522 | #define SCREENSHOT_BPP 3 | ||
523 | int traceFrameBufferCounter = 0; | ||
524 | uint8_t allFrameBuffers[640 * 400 * SCREENSHOT_BPP]; | ||
525 | |||
526 | #endif | ||
527 | |||
528 | |||
529 | |||
530 | |||
531 | |||
532 | |||
533 | /* This opcode is used once the background of a scene has been drawn in one of the framebuffer: | ||
534 | it is copied in the current framebuffer at the start of a new frame in order to improve performances. */ | ||
535 | void video_copyPage(struct Video* v, uint8_t srcPageId, uint8_t dstPageId, int16_t vscroll) { | ||
536 | |||
537 | debug(DBG_VIDEO, "video_copyPage(%d, %d)", srcPageId, dstPageId); | ||
538 | |||
539 | if (srcPageId == dstPageId) | ||
540 | return; | ||
541 | |||
542 | uint8_t *p; | ||
543 | uint8_t *q; | ||
544 | |||
545 | if (srcPageId >= 0xFE || !((srcPageId &= 0xBF) & 0x80)) { | ||
546 | p = video_getPagePtr(v, srcPageId); | ||
547 | q = video_getPagePtr(v, dstPageId); | ||
548 | memcpy(q, p, VID_PAGE_SIZE); | ||
549 | |||
550 | } else { | ||
551 | p = video_getPagePtr(v, srcPageId & 3); | ||
552 | q = video_getPagePtr(v, dstPageId); | ||
553 | if (vscroll >= -199 && vscroll <= 199) { | ||
554 | uint16_t h = 200; | ||
555 | if (vscroll < 0) { | ||
556 | h += vscroll; | ||
557 | p += -vscroll * 160; | ||
558 | } else { | ||
559 | h -= vscroll; | ||
560 | q += vscroll * 160; | ||
561 | } | ||
562 | memcpy(q, p, h * 160); | ||
563 | } | ||
564 | } | ||
565 | |||
566 | |||
567 | #if TRACE_FRAMEBUFFER | ||
568 | char name[256]; | ||
569 | rb->memset(name, 0, sizeof(name)); | ||
570 | sprintf(name, "copyPage_0x%X_to_0x%X", (p - v->_pagePtrs[0]) / VID_PAGE_SIZE, (q - v->_pagePtrs[0]) / VID_PAGE_SIZE); | ||
571 | dumpFrameBuffers(name); | ||
572 | #endif | ||
573 | } | ||
574 | |||
575 | |||
576 | |||
577 | |||
578 | void video_copyPagePtr(struct Video* v, const uint8_t *src) { | ||
579 | debug(DBG_VIDEO, "video_copyPagePtr()"); | ||
580 | uint8_t *dst = v->_pagePtrs[0]; | ||
581 | int h = 200; | ||
582 | while (h--) { | ||
583 | int w = 40; | ||
584 | while (w--) { | ||
585 | uint8_t p[] = { | ||
586 | *(src + 8000 * 3), | ||
587 | *(src + 8000 * 2), | ||
588 | *(src + 8000 * 1), | ||
589 | *(src + 8000 * 0) | ||
590 | }; | ||
591 | for(int j = 0; j < 4; ++j) { | ||
592 | uint8_t acc = 0; | ||
593 | for (int i = 0; i < 8; ++i) { | ||
594 | acc <<= 1; | ||
595 | acc |= (p[i & 3] & 0x80) ? 1 : 0; | ||
596 | p[i & 3] <<= 1; | ||
597 | } | ||
598 | *dst++ = acc; | ||
599 | } | ||
600 | ++src; | ||
601 | } | ||
602 | } | ||
603 | |||
604 | |||
605 | } | ||
606 | |||
607 | /* | ||
608 | uint8_t *video_allocPage() { | ||
609 | uint8_t *buf = (uint8_t *)malloc(VID_PAGE_SIZE); | ||
610 | rb->memset(buf, 0, VID_PAGE_SIZE); | ||
611 | return buf; | ||
612 | } | ||
613 | */ | ||
614 | |||
615 | |||
616 | |||
617 | #if TRACE_FRAMEBUFFER | ||
618 | int dumpPaletteCursor = 0; | ||
619 | #endif | ||
620 | |||
621 | /* | ||
622 | Note: The palettes set used to be allocated on the stack but I moved it to | ||
623 | the heap so I could dump the four framebuffer and follow how | ||
624 | frames are generated. | ||
625 | */ | ||
626 | uint8_t pal[NUM_COLORS * 3]; //3 = BYTES_PER_PIXEL | ||
627 | void video_changePal(struct Video* v, uint8_t palNum) { | ||
628 | debug(DBG_VIDEO, "video_changePal(v=0x%08x, palNum=%d", v, palNum); | ||
629 | if (palNum >= 32) | ||
630 | return; | ||
631 | |||
632 | uint8_t *p = v->res->segPalettes + palNum * 32; //colors are coded on 2bytes (565) for 16 colors = 32 | ||
633 | debug(DBG_VIDEO, "segPalettes: 0x%08x", v->res->segPalettes); | ||
634 | // Moved to the heap, legacy code used to allocate the palette | ||
635 | // on the stack. | ||
636 | //uint8_t pal[NUM_COLORS * 3]; //3 = BYTES_PER_PIXEL | ||
637 | |||
638 | for (int i = 0; i < NUM_COLORS; ++i) | ||
639 | { | ||
640 | debug(DBG_VIDEO, "i: %d", i); | ||
641 | debug(DBG_VIDEO, "p: 0x%08x", p); | ||
642 | uint8_t c1 = *(p + 0); | ||
643 | uint8_t c2 = *(p + 1); | ||
644 | p += 2; | ||
645 | pal[i * 3 + 0] = ((c1 & 0x0F) << 2) | ((c1 & 0x0F) >> 2); // r | ||
646 | pal[i * 3 + 1] = ((c2 & 0xF0) >> 2) | ((c2 & 0xF0) >> 6); // g | ||
647 | pal[i * 3 + 2] = ((c2 & 0x0F) >> 2) | ((c2 & 0x0F) << 2); // b | ||
648 | } | ||
649 | |||
650 | sys_setPalette(v->sys, 0, NUM_COLORS, pal); | ||
651 | v->currentPaletteId = palNum; | ||
652 | |||
653 | |||
654 | #if TRACE_PALETTE | ||
655 | printf("\nuint8_t dumpPalette[48] = {\n"); | ||
656 | for (int i = 0; i < NUM_COLORS; ++i) | ||
657 | { | ||
658 | printf("0x%X,0x%X,0x%X,", pal[i * 3 + 0], pal[i * 3 + 1], pal[i * 3 + 2]); | ||
659 | } | ||
660 | printf("\n};\n"); | ||
661 | #endif | ||
662 | |||
663 | |||
664 | #if TRACE_FRAMEBUFFER | ||
665 | dumpPaletteCursor++; | ||
666 | #endif | ||
667 | } | ||
668 | |||
669 | void video_updateDisplay(struct Video* v, uint8_t pageId) { | ||
670 | |||
671 | debug(DBG_VIDEO, "video_updateDisplay(%d)", pageId); | ||
672 | |||
673 | if (pageId != 0xFE) { | ||
674 | if (pageId == 0xFF) { | ||
675 | /* swap ptrs 2 and 3 */ | ||
676 | uint8_t* temp = v->_curPagePtr3; | ||
677 | v->_curPagePtr3 = v->_curPagePtr2; | ||
678 | v->_curPagePtr2 = temp; | ||
679 | } else { | ||
680 | v->_curPagePtr2 = video_getPagePtr(v, pageId); | ||
681 | } | ||
682 | } | ||
683 | |||
684 | //Check if we need to change the palette | ||
685 | if (v->paletteIdRequested != NO_PALETTE_CHANGE_REQUESTED) { | ||
686 | video_changePal(v, v->paletteIdRequested); | ||
687 | v->paletteIdRequested = NO_PALETTE_CHANGE_REQUESTED; | ||
688 | } | ||
689 | |||
690 | //Q: Why 160 ? | ||
691 | //A: Because one byte gives two palette indices so | ||
692 | // we only need to move 320/2 per line. | ||
693 | sys_copyRect(v->sys, 0, 0, 320, 200, v->_curPagePtr2, 160); | ||
694 | |||
695 | #if TRACE_FRAMEBUFFER | ||
696 | dumpFrameBuffer(v->_curPagePtr2, allFrameBuffers, 320, 200); | ||
697 | #endif | ||
698 | } | ||
699 | |||
700 | void video_saveOrLoad(struct Video* v, struct Serializer *ser) { | ||
701 | uint8_t mask = 0; | ||
702 | if (ser->_mode == SM_SAVE) { | ||
703 | for (int i = 0; i < 4; ++i) { | ||
704 | if (v->_pagePtrs[i] == v->_curPagePtr1) | ||
705 | mask |= i << 4; | ||
706 | if (v->_pagePtrs[i] == v->_curPagePtr2) | ||
707 | mask |= i << 2; | ||
708 | if (v->_pagePtrs[i] == v->_curPagePtr3) | ||
709 | mask |= i << 0; | ||
710 | } | ||
711 | } | ||
712 | struct Entry entries[] = { | ||
713 | SE_INT(&v->currentPaletteId, SES_INT8, VER(1)), | ||
714 | SE_INT(&v->paletteIdRequested, SES_INT8, VER(1)), | ||
715 | SE_INT(&mask, SES_INT8, VER(1)), | ||
716 | SE_ARRAY(v->_pagePtrs[0], VID_PAGE_SIZE, SES_INT8, VER(1)), | ||
717 | SE_ARRAY(v->_pagePtrs[1], VID_PAGE_SIZE, SES_INT8, VER(1)), | ||
718 | SE_ARRAY(v->_pagePtrs[2], VID_PAGE_SIZE, SES_INT8, VER(1)), | ||
719 | SE_ARRAY(v->_pagePtrs[3], VID_PAGE_SIZE, SES_INT8, VER(1)), | ||
720 | SE_END() | ||
721 | }; | ||
722 | ser_saveOrLoadEntries(ser, entries); | ||
723 | |||
724 | if (ser->_mode == SM_LOAD) { | ||
725 | v->_curPagePtr1 = v->_pagePtrs[(mask >> 4) & 0x3]; | ||
726 | v->_curPagePtr2 = v->_pagePtrs[(mask >> 2) & 0x3]; | ||
727 | v->_curPagePtr3 = v->_pagePtrs[(mask >> 0) & 0x3]; | ||
728 | video_changePal(v, v->currentPaletteId); | ||
729 | } | ||
730 | } | ||
731 | |||
732 | |||
733 | |||
734 | #if TRACE_FRAMEBUFFER | ||
735 | |||
736 | |||
737 | uint8_t allPalettesDump[][48] = { | ||
738 | |||
739 | |||
740 | { | ||
741 | 0x4, 0x4, 0x4, 0x22, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x2E, 0x22, 0x0, 0x3F, 0x0, 0x0, 0x33, 0x26, 0x0, 0x37, 0x2A, 0x0, 0x3B, 0x33, 0x0, 0x3F, 0x3B, 0x0, 0x3F, 0x3F, 0x1D, 0x3F, 0x3F, 0x2A, | ||
742 | }, | ||
743 | |||
744 | { | ||
745 | 0x4, 0x4, 0x4, 0xC, 0xC, 0x11, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x2E, 0x22, 0x0, 0x15, 0x11, 0x11, 0x33, 0x26, 0x0, 0x37, 0x2A, 0x0, 0x3B, 0x33, 0x0, 0x3F, 0x3B, 0x0, 0x3F, 0x3F, 0x1D, 0x3F, 0x3F, 0x2A, | ||
746 | } | ||
747 | |||
748 | , { | ||
749 | 0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x1D, 0x1D, 0x1D, 0x15, 0x15, 0x15, 0xC, 0x8, 0xC, 0x11, 0x11, 0x15, 0x1D, 0x15, 0x15, 0x15, 0x0, 0x0, 0x0, 0x4, 0xC, 0x3F, 0x3F, 0x2A, | ||
750 | } | ||
751 | |||
752 | , { | ||
753 | 0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x1D, 0x1D, 0x1D, 0x15, 0x15, 0x15, 0xC, 0x8, 0xC, 0x11, 0x11, 0x15, 0x1D, 0x15, 0x15, 0x15, 0x0, 0x0, 0x0, 0x4, 0xC, 0x3F, 0x3F, 0x2A, | ||
754 | } | ||
755 | |||
756 | , { | ||
757 | 0x0, 0x0, 0x0, 0x1D, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x1D, 0x1D, 0x1D, 0x15, 0x15, 0x15, 0xC, 0x8, 0xC, 0x15, 0x11, 0x19, 0x1D, 0x15, 0x15, 0x15, 0x0, 0x0, 0x0, 0x4, 0xC, 0x3F, 0x3F, 0x2A, | ||
758 | } | ||
759 | |||
760 | , { | ||
761 | 0x0, 0x4, 0x8, 0x15, 0x1D, 0x1D, 0x0, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0xC, 0x19, 0x22, 0x11, 0x1D, 0x26, 0x8, 0x8, 0x8, 0x0, 0x0, 0x0, 0x2E, 0x2E, 0x2E, 0xC, 0xC, 0xC, 0x15, 0xC, 0x15, 0xC, 0x15, 0x15, 0x11, 0x19, 0x19, 0x1D, 0x26, 0x26, | ||
762 | } | ||
763 | |||
764 | , { | ||
765 | 0x0, 0x0, 0x0, 0x0, 0x4, 0xC, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x15, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x11, 0x11, 0x11, 0x11, 0x15, 0x26, 0x0, 0x0, 0x33, 0x26, 0x0, 0x3B, 0x33, 0x11, | ||
766 | } | ||
767 | |||
768 | , { | ||
769 | 0x0, 0x0, 0x0, 0x0, 0x4, 0xC, 0x0, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x15, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x11, 0x11, 0x11, 0x11, 0x15, 0x26, 0x0, 0x0, 0x26, 0x15, 0x0, 0x26, 0x1D, 0x0, | ||
770 | } | ||
771 | |||
772 | , { | ||
773 | 0x0, 0x0, 0x0, 0x0, 0x4, 0xC, 0x0, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x15, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x11, 0x11, 0x11, 0x11, 0x15, 0x26, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x26, 0x1D, 0x0, | ||
774 | } | ||
775 | |||
776 | , { | ||
777 | 0x0, 0x0, 0x0, 0x8, 0x4, 0xC, 0x15, 0xC, 0x11, 0x1D, 0x11, 0x0, 0xC, 0x8, 0x11, 0x2E, 0x1D, 0x0, 0x37, 0x26, 0x8, 0x3F, 0x2E, 0x0, 0x0, 0x0, 0x0, 0x11, 0xC, 0x15, 0x26, 0x15, 0x0, 0x15, 0x11, 0x19, 0x1D, 0x15, 0x1D, 0x26, 0x19, 0x19, 0x0, 0x0, 0x0, 0x3F, 0x3F, 0x3F, | ||
778 | } | ||
779 | |||
780 | , { | ||
781 | 0x0, 0x0, 0x0, 0x8, 0x4, 0xC, 0x37, 0x1D, 0x1D, 0x3B, 0x2A, 0x22, 0x11, 0xC, 0x15, 0x2A, 0x0, 0x0, 0x33, 0x11, 0x0, 0x3F, 0x33, 0x1D, 0x3B, 0x19, 0x0, 0x11, 0x11, 0x19, 0x19, 0x15, 0x1D, 0x22, 0x19, 0x22, 0x2A, 0x1D, 0x26, 0x33, 0x22, 0x26, 0x37, 0x26, 0x22, 0x1D, 0x37, 0x3F, | ||
782 | } | ||
783 | |||
784 | , { | ||
785 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x1D, 0x4, 0x8, 0xC, 0x2A, 0x1D, 0xC, 0x3F, 0x3B, 0x26, 0x3B, 0x2A, 0x11, 0x2A, 0x0, 0x0, 0x0, 0x11, 0x15, 0x2A, 0x1D, 0x26, 0xC, 0x8, 0xC, 0x8, 0x15, 0x1D, 0x37, 0x26, 0x22, 0x33, 0x11, 0x0, 0x2E, 0x1D, 0x19, 0x22, 0x0, 0x0, 0x3F, 0x33, 0x1D, | ||
786 | } | ||
787 | |||
788 | , { | ||
789 | 0x0, 0x0, 0x0, 0x0, 0x15, 0x0, 0x4, 0x8, 0xC, 0x0, 0xC, 0x11, 0x8, 0x11, 0x19, 0x0, 0x19, 0x22, 0x2A, 0x0, 0x0, 0x19, 0x15, 0x22, 0x2E, 0x26, 0x2E, 0xC, 0x8, 0xC, 0x0, 0x2E, 0x0, 0x37, 0x26, 0x22, 0x33, 0x11, 0x0, 0x2E, 0x1D, 0x19, 0x1D, 0x0, 0x0, 0x3F, 0x3F, 0x19, | ||
790 | } | ||
791 | |||
792 | , { | ||
793 | 0x0, 0x0, 0x0, 0x8, 0xC, 0x11, 0x11, 0x11, 0x15, 0x19, 0x15, 0x22, 0x26, 0x19, 0x2A, 0x2E, 0x26, 0x2E, 0x4, 0x4, 0xC, 0x0, 0xC, 0x15, 0x2A, 0x1D, 0x26, 0x0, 0x19, 0x0, 0x0, 0x2A, 0x0, 0x37, 0x26, 0x22, 0x0, 0x15, 0x1D, 0x37, 0x2E, 0x1D, 0x3F, 0x3F, 0x2E, 0x37, 0x37, 0x26, | ||
794 | } | ||
795 | |||
796 | , { | ||
797 | 0x0, 0x0, 0x0, 0x0, 0x15, 0x0, 0x4, 0x8, 0xC, 0x0, 0xC, 0x11, 0x8, 0x11, 0x19, 0x0, 0x19, 0x22, 0x2A, 0x0, 0x0, 0x19, 0x15, 0x22, 0x2E, 0x26, 0x2E, 0xC, 0x8, 0xC, 0x0, 0x2E, 0x0, 0x37, 0x26, 0x22, 0x33, 0x11, 0x0, 0x2E, 0x1D, 0x19, 0x1D, 0x0, 0x0, 0x3F, 0x3F, 0x19, | ||
798 | } | ||
799 | |||
800 | , { | ||
801 | 0x0, 0x0, 0x0, 0x0, 0xC, 0x0, 0x8, 0x0, 0x4, 0xC, 0x8, 0xC, 0x19, 0x11, 0x11, 0x3F, 0x3F, 0x19, 0x0, 0x1D, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x4, 0x8, 0x11, 0x19, 0x11, 0x19, 0x0, 0x0, 0x2E, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x37, 0x1D, 0x15, 0x0, 0x11, 0x11, 0x0, 0x3F, 0x2A, | ||
802 | } | ||
803 | |||
804 | , { | ||
805 | 0x0, 0x0, 0x0, 0x0, 0xC, 0x0, 0x0, 0x11, 0x0, 0x0, 0x19, 0x0, 0xC, 0x26, 0x0, 0x15, 0x2E, 0x0, 0x4, 0x8, 0x0, 0x26, 0x11, 0x0, 0x2E, 0x3F, 0x0, 0x0, 0x15, 0x0, 0xC, 0x1D, 0x0, 0x15, 0x2E, 0x0, 0x15, 0x37, 0x0, 0x1D, 0x3F, 0x0, 0xC, 0x1D, 0xC, 0x\ | ||
806 | 1D, 0x26, 0x15, | ||
807 | } | ||
808 | }; | ||
809 | |||
810 | |||
811 | |||
812 | #include "png.h" | ||
813 | int GL_FCS_SaveAsSpecifiedPNG(char* path, uint8_t* pixels, int depth = 8, int format = PNG_COLOR_TYPE_RGB) | ||
814 | { | ||
815 | png_structp png_ptr = NULL; | ||
816 | png_infop info_ptr = NULL; | ||
817 | png_byte ** row_pointers = NULL; | ||
818 | int status = -1; | ||
819 | int bytePerPixel = 0; | ||
820 | int y; | ||
821 | |||
822 | int fd = open (path, "wb"); | ||
823 | if (fd < 0) { | ||
824 | goto fopen_failed; | ||
825 | } | ||
826 | |||
827 | png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | ||
828 | if (png_ptr == NULL) { | ||
829 | goto png_create_write_struct_failed; | ||
830 | } | ||
831 | |||
832 | info_ptr = png_create_info_struct (png_ptr); | ||
833 | if (info_ptr == NULL) { | ||
834 | goto png_create_info_struct_failed; | ||
835 | } | ||
836 | |||
837 | if (setjmp (png_jmpbuf (png_ptr))) { | ||
838 | goto png_failure; | ||
839 | } | ||
840 | |||
841 | /* Set image attributes. */ | ||
842 | |||
843 | png_set_IHDR (png_ptr, | ||
844 | info_ptr, | ||
845 | 640, | ||
846 | 400, | ||
847 | depth, | ||
848 | format, | ||
849 | PNG_INTERLACE_NONE, | ||
850 | PNG_COMPRESSION_TYPE_DEFAULT, | ||
851 | PNG_FILTER_TYPE_DEFAULT); | ||
852 | |||
853 | if (format == PNG_COLOR_TYPE_GRAY ) | ||
854 | bytePerPixel = depth / 8 * 1; | ||
855 | else | ||
856 | bytePerPixel = depth / 8 * 3; | ||
857 | |||
858 | row_pointers = (png_byte **)png_malloc (png_ptr, 400 * sizeof (png_byte *)); | ||
859 | //for (y = vid.height-1; y >=0; --y) | ||
860 | for (y = 0; y < 400; y++) | ||
861 | { | ||
862 | row_pointers[y] = (png_byte*)&pixels[640 * (400 - y) * bytePerPixel]; | ||
863 | } | ||
864 | |||
865 | png_init_io (png_ptr, fp); | ||
866 | png_set_rows (png_ptr, info_ptr, row_pointers); | ||
867 | png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); | ||
868 | //png_read_image (png_ptr, info_ptr);// | ||
869 | |||
870 | status = 0; | ||
871 | |||
872 | png_free (png_ptr, row_pointers); | ||
873 | |||
874 | |||
875 | png_failure: | ||
876 | png_create_info_struct_failed: | ||
877 | png_destroy_write_struct (&png_ptr, &info_ptr); | ||
878 | |||
879 | png_create_write_struct_failed: | ||
880 | fclose (fp); | ||
881 | fopen_failed: | ||
882 | return status; | ||
883 | } | ||
884 | |||
885 | void writeLine(uint8_t *dst, uint8_t *src, int size) | ||
886 | { | ||
887 | uint8_t* dumpPalette; | ||
888 | |||
889 | if (!dumpPaletteCursor) | ||
890 | dumpPalette = allPalettesDump[dumpPaletteCursor]; | ||
891 | else | ||
892 | dumpPalette = pal; | ||
893 | |||
894 | for( uint8_t twoPixels = 0 ; twoPixels < size ; twoPixels++) | ||
895 | { | ||
896 | int pixelIndex0 = (*src & 0xF0) >> 4; | ||
897 | pixelIndex0 &= 0x10 - 1; | ||
898 | |||
899 | int pixelIndex1 = (*src & 0xF); | ||
900 | pixelIndex1 &= 0x10 - 1; | ||
901 | |||
902 | //We need to write those two pixels | ||
903 | dst[0] = dumpPalette[pixelIndex0 * 3] << 2 | dumpPalette[pixelIndex0 * 3]; | ||
904 | dst[1] = dumpPalette[pixelIndex0 * 3 + 1] << 2 | dumpPalette[pixelIndex0 * 3 + 1]; | ||
905 | dst[2] = dumpPalette[pixelIndex0 * 3 + 2] << 2 | dumpPalette[pixelIndex0 * 3 + 2]; | ||
906 | //dst[3] = 0xFF; | ||
907 | dst += SCREENSHOT_BPP; | ||
908 | |||
909 | dst[0] = dumpPalette[pixelIndex1 * 3] << 2 | dumpPalette[pixelIndex1 * 3]; | ||
910 | dst[1] = dumpPalette[pixelIndex1 * 3 + 1] << 2 | dumpPalette[pixelIndex1 * 3 + 1]; | ||
911 | dst[2] = dumpPalette[pixelIndex1 * 3 + 2] << 2 | dumpPalette[pixelIndex1 * 3 + 2]; | ||
912 | //dst[3] = 0xFF; | ||
913 | dst += SCREENSHOT_BPP; | ||
914 | |||
915 | src++; | ||
916 | } | ||
917 | } | ||
918 | |||
919 | void video_dumpFrameBuffer(uint8_t *src, uint8_t *dst, int x, int y) | ||
920 | { | ||
921 | |||
922 | for (int line = 199 ; line >= 0 ; line--) | ||
923 | { | ||
924 | writeLine(dst + x * SCREENSHOT_BPP + y * 640 * SCREENSHOT_BPP , src + line * 160, 160); | ||
925 | dst += 640 * SCREENSHOT_BPP; | ||
926 | } | ||
927 | } | ||
928 | |||
929 | void video_dumpFrameBuffers(char* comment) | ||
930 | { | ||
931 | |||
932 | if (!traceFrameBufferCounter) | ||
933 | { | ||
934 | rb->memset(allFrameBuffers, 0, sizeof(allFrameBuffers)); | ||
935 | } | ||
936 | |||
937 | |||
938 | dumpFrameBuffer(v->_pagePtrs[1], allFrameBuffers, 0, 0); | ||
939 | dumpFrameBuffer(v->_pagePtrs[0], allFrameBuffers, 0, 200); | ||
940 | dumpFrameBuffer(v->_pagePtrs[2], allFrameBuffers, 320, 0); | ||
941 | //dumpFrameBuffer(v->_pagePtrs[3],allFrameBuffers,320,200); | ||
942 | |||
943 | |||
944 | //if (v->_curPagePtr1 == v->_pagePtrs[3]) | ||
945 | // | ||
946 | |||
947 | /* | ||
948 | uint8_t* offScreen = sys->getOffScreenFramebuffer(); | ||
949 | for(int i=0 ; i < 200 ; i++) | ||
950 | writeLine(allFrameBuffers+320*3+640*i*3 + 200*640*3, offScreen+320*i/2 , 160); | ||
951 | */ | ||
952 | |||
953 | |||
954 | int frameId = traceFrameBufferCounter++; | ||
955 | //Write bitmap to disk. | ||
956 | |||
957 | |||
958 | |||
959 | // Filling TGA header information | ||
960 | /* | ||
961 | char path[256]; | ||
962 | sprintf(path,"test%d.tga",traceFrameBufferCounter); | ||
963 | |||
964 | #define IMAGE_WIDTH 640 | ||
965 | #define IMAGE_HEIGHT 400 | ||
966 | |||
967 | uint8_t tga_header[18]; | ||
968 | rb->memset(tga_header, 0, 18); | ||
969 | tga_header[2] = 2; | ||
970 | tga_header[12] = (IMAGE_WIDTH & 0x00FF); | ||
971 | tga_header[13] = (IMAGE_WIDTH & 0xFF00) / 256; | ||
972 | tga_header[14] = (IMAGE_HEIGHT & 0x00FF) ; | ||
973 | tga_header[15] =(IMAGE_HEIGHT & 0xFF00) / 256; | ||
974 | tga_header[16] = 32 ; | ||
975 | |||
976 | |||
977 | |||
978 | // Open the file, write both header and payload, close, done. | ||
979 | char path[256]; | ||
980 | sprintf(path,"test%d.tga",traceFrameBufferCounter); | ||
981 | FILE* pScreenshot = fopen(path, "wb"); | ||
982 | fwrite(&tga_header, 18, sizeof(uint8_t), pScreenshot); | ||
983 | fwrite(allFrameBuffers, IMAGE_WIDTH * IMAGE_HEIGHT,SCREENSHOT_BPP * sizeof(uint8_t),pScreenshot); | ||
984 | fclose(pScreenshot); | ||
985 | */ | ||
986 | |||
987 | |||
988 | char path[256]; | ||
989 | //sprintf(path,"%4d%s.png",traceFrameBufferCounter,comment); | ||
990 | sprintf(path, "%4d.png", traceFrameBufferCounter); | ||
991 | |||
992 | GL_FCS_SaveAsSpecifiedPNG(path, allFrameBuffers); | ||
993 | } | ||
994 | #endif | ||
995 | |||
996 | #if TRACE_BG_BUFFER | ||
997 | |||
998 | |||
999 | |||
1000 | uint8_t bgPalette[48] = { | ||
1001 | 0x8, 0x8, 0xC, 0xC, 0xC, 0x15, 0xC, 0x11, 0x1D, 0x15, 0x2A, 0x3F, 0x1D, 0x19, 0x19, 0x37, 0x2E, 0x2A, 0x26, 0x1D, 0x1D, 0x37, 0x26, 0x22, 0x22, 0xC, 0x0, 0x26, 0x33, 0x3F, 0x11, 0x11, 0x15, 0x11, 0x15, 0x1D, 0x15, 0x19, 0x26, 0x15, 0x1D, 0x37, 0x0, 0x26, 0x3F, 0x2E, 0x15, 0x0, | ||
1002 | }; | ||
1003 | void bgWriteLine(uint8_t *dst, uint8_t *src, int size) | ||
1004 | { | ||
1005 | uint8_t* dumpPalette; | ||
1006 | |||
1007 | // if (!dumpPaletteCursor) | ||
1008 | // dumpPalette = allPalettesDump[dumpPaletteCursor]; | ||
1009 | // else | ||
1010 | dumpPalette = bgPalette; | ||
1011 | |||
1012 | for( uint8_t twoPixels = 0 ; twoPixels < size ; twoPixels++) | ||
1013 | { | ||
1014 | int pixelIndex0 = (*src & 0xF0) >> 4; | ||
1015 | pixelIndex0 &= 0x10 - 1; | ||
1016 | |||
1017 | int pixelIndex1 = (*src & 0xF); | ||
1018 | pixelIndex1 &= 0x10 - 1; | ||
1019 | |||
1020 | //We need to write those two pixels | ||
1021 | dst[0] = dumpPalette[pixelIndex0 * 3] << 2 | dumpPalette[pixelIndex0 * 3]; | ||
1022 | dst[1] = dumpPalette[pixelIndex0 * 3 + 1] << 2 | dumpPalette[pixelIndex0 * 3 + 1]; | ||
1023 | dst[2] = dumpPalette[pixelIndex0 * 3 + 2] << 2 | dumpPalette[pixelIndex0 * 3 + 2]; | ||
1024 | //dst[3] = 0xFF; | ||
1025 | dst += 3; | ||
1026 | |||
1027 | dst[0] = dumpPalette[pixelIndex1 * 3] << 2 | dumpPalette[pixelIndex1 * 3]; | ||
1028 | dst[1] = dumpPalette[pixelIndex1 * 3 + 1] << 2 | dumpPalette[pixelIndex1 * 3 + 1]; | ||
1029 | dst[2] = dumpPalette[pixelIndex1 * 3 + 2] << 2 | dumpPalette[pixelIndex1 * 3 + 2]; | ||
1030 | //dst[3] = 0xFF; | ||
1031 | dst += 3; | ||
1032 | |||
1033 | src++; | ||
1034 | } | ||
1035 | } | ||
1036 | |||
1037 | void bgDumpFrameBuffer(uint8_t *src, uint8_t *dst, int x, int y) | ||
1038 | { | ||
1039 | |||
1040 | for (int line = 199 ; line >= 0 ; line--) | ||
1041 | { | ||
1042 | bgWriteLine(dst + x * 3 + y * 320 * 3 , src + line * 160, 160); | ||
1043 | dst += 320 * 3; | ||
1044 | } | ||
1045 | } | ||
1046 | |||
1047 | #include "png.h" | ||
1048 | int bgSaveAsSpecifiedPNG(char* path, uint8_t* pixels, int depth = 8, int format = PNG_COLOR_TYPE_RGB) | ||
1049 | { | ||
1050 | #if 0 | ||
1051 | FILE * fp; | ||
1052 | png_structp png_ptr = NULL; | ||
1053 | png_infop info_ptr = NULL; | ||
1054 | png_byte ** row_pointers = NULL; | ||
1055 | int status = -1; | ||
1056 | int bytePerPixel = 0; | ||
1057 | int y; | ||
1058 | |||
1059 | fp = fopen (path, "wb"); | ||
1060 | if (! fp) { | ||
1061 | goto fopen_failed; | ||
1062 | } | ||
1063 | |||
1064 | png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | ||
1065 | if (png_ptr == NULL) { | ||
1066 | goto png_create_write_struct_failed; | ||
1067 | } | ||
1068 | |||
1069 | info_ptr = png_create_info_struct (png_ptr); | ||
1070 | if (info_ptr == NULL) { | ||
1071 | goto png_create_info_struct_failed; | ||
1072 | } | ||
1073 | |||
1074 | if (setjmp (png_jmpbuf (png_ptr))) { | ||
1075 | goto png_failure; | ||
1076 | } | ||
1077 | |||
1078 | /* Set image attributes. */ | ||
1079 | |||
1080 | png_set_IHDR (png_ptr, | ||
1081 | info_ptr, | ||
1082 | 320, | ||
1083 | 200, | ||
1084 | depth, | ||
1085 | format, | ||
1086 | PNG_INTERLACE_NONE, | ||
1087 | PNG_COMPRESSION_TYPE_DEFAULT, | ||
1088 | PNG_FILTER_TYPE_DEFAULT); | ||
1089 | |||
1090 | if (format == PNG_COLOR_TYPE_GRAY ) | ||
1091 | bytePerPixel = depth / 8 * 1; | ||
1092 | else | ||
1093 | bytePerPixel = depth / 8 * 3; | ||
1094 | |||
1095 | row_pointers = (png_byte **)png_malloc (png_ptr, 200 * sizeof (png_byte *)); | ||
1096 | //for (y = vid.height-1; y >=0; --y) | ||
1097 | for (y = 0; y < 200; y++) | ||
1098 | { | ||
1099 | row_pointers[y] = (png_byte*)&pixels[320 * (200 - y) * bytePerPixel]; | ||
1100 | } | ||
1101 | |||
1102 | png_init_io (png_ptr, fp); | ||
1103 | png_set_rows (png_ptr, info_ptr, row_pointers); | ||
1104 | png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); | ||
1105 | //png_read_image (png_ptr, info_ptr);// | ||
1106 | |||
1107 | status = 0; | ||
1108 | |||
1109 | png_free (png_ptr, row_pointers); | ||
1110 | |||
1111 | |||
1112 | png_failure: | ||
1113 | png_create_info_struct_failed: | ||
1114 | png_destroy_write_struct (&png_ptr, &info_ptr); | ||
1115 | |||
1116 | png_create_write_struct_failed: | ||
1117 | fclose (fp); | ||
1118 | fopen_failed: | ||
1119 | return status; | ||
1120 | #endif | ||
1121 | } | ||
1122 | |||
1123 | int bgFrameBufferCounter = 0; | ||
1124 | |||
1125 | void video_dumpBackGroundBuffer() | ||
1126 | { | ||
1127 | if (v->_curPagePtr1 != v->_pagePtrs[0]) | ||
1128 | return; | ||
1129 | |||
1130 | uint8_t bgBuffer[320 * 200 * 3]; | ||
1131 | bgDumpFrameBuffer(v->_curPagePtr1, bgBuffer, 0, 0); | ||
1132 | |||
1133 | |||
1134 | char path[256]; | ||
1135 | //sprintf(path,"%4d%s.png",traceFrameBufferCounter,comment); | ||
1136 | sprintf(path, "bg%4d.png", bgFrameBufferCounter++); | ||
1137 | |||
1138 | bgSaveAsSpecifiedPNG(path, bgBuffer); | ||
1139 | } | ||
1140 | |||
1141 | #endif | ||
diff --git a/apps/plugins/xworld/video.h b/apps/plugins/xworld/video.h new file mode 100644 index 0000000000..d4ff64f5b5 --- /dev/null +++ b/apps/plugins/xworld/video.h | |||
@@ -0,0 +1,127 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __VIDEO_H__ | ||
24 | #define __VIDEO_H__ | ||
25 | |||
26 | #include "intern.h" | ||
27 | |||
28 | struct StrEntry { | ||
29 | uint16_t id; | ||
30 | char *str; | ||
31 | }; | ||
32 | #define MAX_POINTS 50 | ||
33 | struct Polygon { | ||
34 | |||
35 | uint16_t bbw, bbh; | ||
36 | uint8_t numPoints; | ||
37 | struct Point points[MAX_POINTS]; | ||
38 | |||
39 | }; | ||
40 | void polygon_readVertices(struct Polygon*, const uint8_t *p, uint16_t zoom); | ||
41 | |||
42 | struct Resource; | ||
43 | struct Serializer; | ||
44 | struct System; | ||
45 | |||
46 | // This is used to detect the end of _stringsTableEng and _stringsTableDemo | ||
47 | #define END_OF_STRING_DICTIONARY 0xFFFF | ||
48 | |||
49 | // Special value when no palette change is necessary | ||
50 | #define NO_PALETTE_CHANGE_REQUESTED 0xFF | ||
51 | |||
52 | /* 320x200 pixels, with 2 pixels/byte */ | ||
53 | #define VID_PAGE_SIZE ( 320 * 200 / 2 ) | ||
54 | |||
55 | struct Video { | ||
56 | |||
57 | /* FW: static const uint8_t _font[];*/ | ||
58 | /* FW: moved to video_data.c */ | ||
59 | struct Resource *res; | ||
60 | struct System *sys; | ||
61 | |||
62 | |||
63 | |||
64 | uint8_t paletteIdRequested, currentPaletteId; | ||
65 | uint8_t *_pagePtrs[4]; | ||
66 | uint8_t *page_data; | ||
67 | // I am almost sure that: | ||
68 | // _curPagePtr1 is the backbuffer | ||
69 | // _curPagePtr2 is the frontbuffer | ||
70 | // _curPagePtr3 is the background builder. | ||
71 | uint8_t *_curPagePtr1, *_curPagePtr2, *_curPagePtr3; | ||
72 | |||
73 | struct Polygon polygon; | ||
74 | int16_t _hliney; | ||
75 | |||
76 | //Precomputer division lookup table | ||
77 | uint16_t _interpTable[0x400]; | ||
78 | |||
79 | struct Ptr _pData; | ||
80 | uint8_t *_dataBuf; | ||
81 | |||
82 | |||
83 | }; | ||
84 | |||
85 | typedef void (*drawLine)(struct Video*, int16_t x1, int16_t x2, uint8_t col); | ||
86 | |||
87 | //Video(Resource *res, System *stub); | ||
88 | void video_create(struct Video*, struct Resource*, struct System*); | ||
89 | void video_init(struct Video* v); | ||
90 | |||
91 | void video_setDataBuffer(struct Video* v, uint8_t *dataBuf, uint16_t offset); | ||
92 | void video_readAndDrawPolygon(struct Video* v, uint8_t color, uint16_t zoom, const struct Point *pt); | ||
93 | void video_fillPolygon(struct Video* v, uint16_t color, uint16_t zoom, const struct Point *pt); | ||
94 | void video_readAndDrawPolygonHierarchy(struct Video* v, uint16_t zoom, const struct Point *pt); | ||
95 | int32_t video_calcStep(struct Video* v, const struct Point *p1, const struct Point *p2, uint16_t *dy); | ||
96 | |||
97 | void video_drawString(struct Video* v, uint8_t color, uint16_t x, uint16_t y, uint16_t strId); | ||
98 | void video_drawChar(struct Video* v, uint8_t c, uint16_t x, uint16_t y, uint8_t color, uint8_t *buf); | ||
99 | void video_drawPoint(struct Video* v, uint8_t color, int16_t x, int16_t y); | ||
100 | void video_drawLineBlend(struct Video* v, int16_t x1, int16_t x2, uint8_t color); | ||
101 | void video_drawLineN(struct Video* v, int16_t x1, int16_t x2, uint8_t color); | ||
102 | void video_drawLineP(struct Video* v, int16_t x1, int16_t x2, uint8_t color); | ||
103 | uint8_t *video_getPagePtr(struct Video* v, uint8_t page); | ||
104 | void video_changePagePtr1(struct Video* v, uint8_t page); | ||
105 | void video_fillPage(struct Video* v, uint8_t page, uint8_t color); | ||
106 | void video_copyPage(struct Video* v, uint8_t src, uint8_t dst, int16_t vscroll); | ||
107 | void video_copyPagePtr(struct Video* v, const uint8_t *src); | ||
108 | uint8_t *video_allocPage(struct Video* v); | ||
109 | void video_changePal(struct Video* v, uint8_t pal); | ||
110 | void video_updateDisplay(struct Video* v, uint8_t page); | ||
111 | |||
112 | void video_saveOrLoad(struct Video* v, struct Serializer *ser); | ||
113 | |||
114 | #define TRACE_PALETTE 0 | ||
115 | #define TRACE_FRAMEBUFFER 0 | ||
116 | #if TRACE_FRAMEBUFFER | ||
117 | void video_dumpFrameBuffer(struct Video* v, uint8_t *src, uint8_t *dst, int x, int y); | ||
118 | void video_dumpFrameBuffers(struct Video* v, char* comment); | ||
119 | |||
120 | #endif | ||
121 | |||
122 | #define TRACE_BG_BUFFER 0 | ||
123 | #if TRACE_BG_BUFFER | ||
124 | void video_dumpBackGroundBuffer(struct Video* v); | ||
125 | #endif | ||
126 | |||
127 | #endif | ||
diff --git a/apps/plugins/xworld/video_data.c b/apps/plugins/xworld/video_data.c new file mode 100644 index 0000000000..e658c175d9 --- /dev/null +++ b/apps/plugins/xworld/video_data.c | |||
@@ -0,0 +1,271 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "video.h" | ||
24 | #include "video_data.h" | ||
25 | #include "stdint.h" | ||
26 | |||
27 | /* this font is based off 10-Fixed.bdf with lowercase characters | ||
28 | from 09-Fixed.bdf and a handcrafted copyright symbol */ | ||
29 | |||
30 | uint8_t video_font[FONT_SIZE] = { | ||
31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ' ' */ | ||
32 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20, 0x00, /* '!' */ | ||
33 | 0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, /* '"' */ | ||
34 | 0x50, 0x50, 0xF8, 0x50, 0xF8, 0x50, 0x50, 0x00, /* '#' */ | ||
35 | 0x20, 0x70, 0xA0, 0x70, 0x28, 0x70, 0x20, 0x00, /* '$' */ | ||
36 | 0x48, 0xA8, 0x50, 0x20, 0x50, 0xA8, 0x90, 0x00, /* '%' */ | ||
37 | 0x40, 0xA0, 0xA0, 0x40, 0xA8, 0x90, 0x68, 0x00, /* '&' */ | ||
38 | 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, /* ''' */ | ||
39 | 0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00, /* '(' */ | ||
40 | 0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, /* ')' */ | ||
41 | 0x00, 0x88, 0x50, 0xF8, 0x50, 0x88, 0x00, 0x00, /* '*' */ | ||
42 | 0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00, 0x00, /* '+' */ | ||
43 | 0x00, 0x00, 0x00, 0x00, 0x30, 0x20, 0x40, 0x00, /* ',' */ | ||
44 | 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, /* '-' */ | ||
45 | 0x00, 0x00, 0x00, 0x00, 0x20, 0x50, 0x20, 0x00, /* '.' */ | ||
46 | 0x08, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x00, /* '/' */ | ||
47 | 0x20, 0x50, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, /* '0' */ | ||
48 | 0x20, 0x60, 0xA0, 0x20, 0x20, 0x20, 0xF8, 0x00, /* '1' */ | ||
49 | 0x70, 0x88, 0x08, 0x30, 0x40, 0x80, 0xF8, 0x00, /* '2' */ | ||
50 | 0xF8, 0x08, 0x10, 0x30, 0x08, 0x88, 0x70, 0x00, /* '3' */ | ||
51 | 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10, 0x00, /* '4' */ | ||
52 | 0xF8, 0x80, 0xB0, 0xC8, 0x08, 0x88, 0x70, 0x00, /* '5' */ | ||
53 | 0x30, 0x40, 0x80, 0xB0, 0xC8, 0x88, 0x70, 0x00, /* '6' */ | ||
54 | 0xF8, 0x08, 0x10, 0x10, 0x20, 0x40, 0x40, 0x00, /* '7' */ | ||
55 | 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00, /* '8' */ | ||
56 | 0x70, 0x88, 0x98, 0x68, 0x08, 0x10, 0x60, 0x00, /* '9' */ | ||
57 | 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00, 0x00, /* ':' */ | ||
58 | 0x00, 0x30, 0x30, 0x00, 0x30, 0x20, 0x40, 0x00, /* ';' */ | ||
59 | 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00, /* '<' */ | ||
60 | 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x00, /* '=' */ | ||
61 | 0x40, 0x20, 0x10, 0x08, 0x10, 0x20, 0x40, 0x00, /* '>' */ | ||
62 | 0x70, 0x88, 0x10, 0x20, 0x20, 0x00, 0x20, 0x00, /* '?' */ | ||
63 | 0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, /* ')' */ | ||
64 | 0x20, 0x50, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x00, /* 'A' */ | ||
65 | 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0, 0x00, /* 'B' */ | ||
66 | 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70, 0x00, /* 'C' */ | ||
67 | 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0, 0x00, /* 'D' */ | ||
68 | 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8, 0x00, /* 'E' */ | ||
69 | 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00, /* 'F' */ | ||
70 | 0x70, 0x88, 0x80, 0x80, 0x98, 0x88, 0x70, 0x00, /* 'G' */ | ||
71 | 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88, 0x00, /* 'H' */ | ||
72 | 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'I' */ | ||
73 | 0x38, 0x10, 0x10, 0x10, 0x10, 0x90, 0x60, 0x00, /* 'J' */ | ||
74 | 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88, 0x00, /* 'K' */ | ||
75 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0x00, /* 'L' */ | ||
76 | 0x88, 0x88, 0xD8, 0xA8, 0x88, 0x88, 0x88, 0x00, /* 'M' */ | ||
77 | 0x88, 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x00, /* 'N' */ | ||
78 | 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'O' */ | ||
79 | 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80, 0x00, /* 'P' */ | ||
80 | 0x70, 0x88, 0x88, 0x88, 0x88, 0xA8, 0x70, 0x00, /* 'Q' */ | ||
81 | 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88, 0x00, /* 'R' */ | ||
82 | 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70, 0x00, /* 'S' */ | ||
83 | 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, /* 'T' */ | ||
84 | 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'U' */ | ||
85 | 0x88, 0x88, 0x88, 0x50, 0x50, 0x50, 0x20, 0x00, /* 'V' */ | ||
86 | 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88, 0x00, /* 'W' */ | ||
87 | 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00, /* 'X' */ | ||
88 | 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20, 0x00, /* 'Y' */ | ||
89 | 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8, 0x00, /* 'Z' */ | ||
90 | 0x70, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, /* '[' */ | ||
91 | 0x80, 0x80, 0x40, 0x20, 0x10, 0x10, 0x00, 0x00, /* '\' */ | ||
92 | 0x70, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00, 0x00, /* ']' */ | ||
93 | 0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* '^' */ | ||
94 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, /* '_' */ | ||
95 | 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* '`' */ | ||
96 | 0x00, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00, /* 'a' */ | ||
97 | 0x80, 0x80, 0xB0, 0xC8, 0x88, 0xC8, 0xB0, 0x00, /* 'b' */ | ||
98 | 0x00, 0x00, 0x70, 0x88, 0x80, 0x88, 0x70, 0x00, /* 'c' */ | ||
99 | 0x08, 0x08, 0x68, 0x98, 0x88, 0x98, 0x68, 0x00, /* 'd' */ | ||
100 | 0x00, 0x00, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00, /* 'e' */ | ||
101 | 0x30, 0x48, 0x40, 0xF0, 0x40, 0x40, 0x40, 0x00, /* 'f' */ | ||
102 | 0x00, 0x00, 0x60, 0x90, 0x90, 0x70, 0x10, 0x60, /* 'g' */ | ||
103 | 0x80, 0x80, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00, /* 'h' */ | ||
104 | 0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'i' */ | ||
105 | 0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0xA0, 0x40, /* 'j' */ | ||
106 | 0x80, 0x80, 0x88, 0x90, 0xE0, 0x90, 0x88, 0x00, /* 'k' */ | ||
107 | 0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'l' */ | ||
108 | 0x00, 0x00, 0xD0, 0xA8, 0xA8, 0xA8, 0x88, 0x00, /* 'm' */ | ||
109 | 0x00, 0x00, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00, /* 'n' */ | ||
110 | 0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'o' */ | ||
111 | 0x00, 0x00, 0xE0, 0x90, 0x90, 0xE0, 0x80, 0x80, /* 'p' */ | ||
112 | 0x00, 0x00, 0x70, 0x90, 0x90, 0x70, 0x10, 0x10, /* 'q' */ | ||
113 | 0x00, 0x00, 0xB0, 0xC8, 0x80, 0x80, 0x80, 0x00, /* 'r' */ | ||
114 | 0x00, 0x00, 0x70, 0x80, 0x70, 0x08, 0xF0, 0x00, /* 's' */ | ||
115 | 0x40, 0x40, 0xF0, 0x40, 0x40, 0x48, 0x30, 0x00, /* 't' */ | ||
116 | 0x00, 0x00, 0x88, 0x88, 0x88, 0x98, 0x68, 0x00, /* 'u' */ | ||
117 | 0x00, 0x00, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00, /* 'v' */ | ||
118 | 0x00, 0x00, 0x88, 0x88, 0xA8, 0xA8, 0x50, 0x00, /* 'w' */ | ||
119 | 0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00, /* 'x' */ | ||
120 | 0x00, 0x00, 0x90, 0x90, 0x90, 0x70, 0x90, 0x60, /* 'y' */ | ||
121 | 0x00, 0x00, 0xF8, 0x10, 0x20, 0x40, 0xF8, 0x00, /* 'z' */ | ||
122 | 0x18, 0x20, 0x10, 0x60, 0x10, 0x20, 0x18, 0x00, /* '{' */ | ||
123 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x00, /* cursor */ | ||
124 | 0x38, 0x54, 0xAA, 0xA2, 0xAA, 0x54, 0x38, 0x00, /* copyright symbol */ | ||
125 | 0x70, 0x88, 0x88, 0x88, 0x88, 0x50, 0xD8, 0x00, /* omega */ | ||
126 | 0x00, 0xA0, 0x10, 0x80, 0x10, 0x80, 0x50, 0x00, /* DEL */ | ||
127 | }; | ||
128 | |||
129 | struct StrEntry video_stringsTableEng[MAX_STRING_TABLE_SIZE] = { | ||
130 | { 0x001, "B A N A N A 2000" }, | ||
131 | { 0x002, "Copyright } 2014 Banana Corporation \nGPLv2\n\nBUNIX Revision 3.14" }, | ||
132 | { 0x003, "1" }, | ||
133 | { 0x004, "3" }, | ||
134 | { 0x005, "." }, | ||
135 | { 0x006, "a" }, | ||
136 | { 0x007, "@" }, | ||
137 | { 0x008, "BANANA 2000" }, | ||
138 | { 0x00A, "R" }, | ||
139 | { 0x00B, "U" }, | ||
140 | { 0x00C, "N" }, | ||
141 | { 0x00D, "P" }, | ||
142 | { 0x00E, "R" }, | ||
143 | { 0x00F, "O" }, | ||
144 | { 0x010, "J" }, | ||
145 | { 0x011, "E" }, | ||
146 | { 0x012, "C" }, | ||
147 | { 0x013, "T" }, | ||
148 | { 0x014, "Fields 100.05Mf OK" }, | ||
149 | { 0x015, "Lines of Flux % 14.077 OK" }, | ||
150 | { 0x016, "IONS OK" }, | ||
151 | { 0x017, " %%%ddd OK" }, | ||
152 | { 0x018, "TEMP ok" }, | ||
153 | { 0x019, "EXECUTE" }, | ||
154 | { 0x01A, "V= 24%\nG: 1.05\n\nMG: 177.2l\n\nOPT: G>\n\n Field:\nI: OFF\nII: ON\nIII: ON\n\np~: I\n" }, | ||
155 | { 0x01B, "on" }, | ||
156 | { 0x01C, "-" }, | ||
157 | { 0x021, "|" }, | ||
158 | { 0x022, "--- Simulation ---" }, | ||
159 | { 0x023, " TEST WILL START IN SECONDS" }, | ||
160 | { 0x024, " 20" }, | ||
161 | { 0x025, " 19" }, | ||
162 | { 0x026, " 18" }, | ||
163 | { 0x027, " 4" }, | ||
164 | { 0x028, " 3" }, | ||
165 | { 0x029, " 2" }, | ||
166 | { 0x02A, " 1" }, | ||
167 | { 0x02B, " 0" }, | ||
168 | { 0x02C, "C A U T I O N" }, | ||
169 | { 0x031, "- Test 0:\nGenerate electron beam\n" }, | ||
170 | { 0x032, "- Test 1:\nCalculating flux coefficient\n" }, | ||
171 | { 0x033, "- Test 2:\nIncrease magnetic field\n" }, | ||
172 | { 0x034, "R E S U L T S" }, | ||
173 | { 0x035, "- NOTE:\nChances of producing:\n Anti-matter: 34 %\n Neutrino 71: 4 %\n Positron 34: 99 %\n" }, | ||
174 | { 0x036, " Continue Test y/n ?" }, | ||
175 | { 0x037, "Are You Sure?" }, | ||
176 | { 0x038, "Setting Configuration\n of accelerator\n'Verified'" }, | ||
177 | { 0x039, " Continue ?" }, | ||
178 | { 0x03C, "T___T" }, | ||
179 | { 0x03D, "OOO ~" }, | ||
180 | { 0x03E, ".40X13DD" }, | ||
181 | { 0x03F, "ferfxwre" }, | ||
182 | { 0x040, "Trfor 25%" }, | ||
183 | { 0x041, "32% 56% GOOD" }, | ||
184 | { 0x042, "E=2.7182818289" }, | ||
185 | { 0x043, "G=330.01" }, | ||
186 | { 0x044, "+" }, | ||
187 | { 0x045, "*" }, | ||
188 | { 0x046, "% 234" }, | ||
189 | { 0x047, "Gorwle 12" }, | ||
190 | { 0x048, "[[[[" }, | ||
191 | { 0x049, "Elephine Soft" }, | ||
192 | { 0x04A, "By Many talented People" }, | ||
193 | { 0x04B, " 4" }, | ||
194 | { 0x04C, " 16" }, | ||
195 | { 0x12C, "0" }, | ||
196 | { 0x12D, "1" }, | ||
197 | { 0x12E, "2" }, | ||
198 | { 0x12F, "3" }, | ||
199 | { 0x130, "4" }, | ||
200 | { 0x131, "5" }, | ||
201 | { 0x132, "6" }, | ||
202 | { 0x133, "7" }, | ||
203 | { 0x134, "8" }, | ||
204 | { 0x135, "9" }, | ||
205 | { 0x136, "A" }, | ||
206 | { 0x137, "B" }, | ||
207 | { 0x138, "C" }, | ||
208 | { 0x139, "D" }, | ||
209 | { 0x13A, "E" }, | ||
210 | { 0x13B, "F" }, | ||
211 | { 0x13C, " LEVEL CODE:" }, | ||
212 | { 0x13D, " PRESS ANY KEY TO CONTINUE" }, | ||
213 | { 0x13E, " ENTER CODE" }, | ||
214 | { 0x13F, " CODE NOT VALID!!" }, | ||
215 | { 0x140, "AN NULER" }, | ||
216 | { 0x141, " ??????\n\n\n\n\n\n\n\n\nANY KEY TO CONTINUE" }, | ||
217 | { 0x142, " ENTER THE CODE CORRELATING TO\n POSITION\n ON THE DECODER WHEEL" }, | ||
218 | { 0x143, " LOAD..." }, | ||
219 | { 0x144, " ERROR" }, | ||
220 | { 0x15E, "LDKD" }, | ||
221 | { 0x15F, "HTDC" }, | ||
222 | { 0x160, "CLLD" }, | ||
223 | { 0x161, "FXLC" }, | ||
224 | { 0x162, "KRFK" }, | ||
225 | { 0x163, "XDDJ" }, | ||
226 | { 0x164, "LBKG" }, | ||
227 | { 0x165, "KLFB" }, | ||
228 | { 0x166, "TTCT" }, | ||
229 | { 0x167, "DDRX" }, | ||
230 | { 0x168, "TBHK" }, | ||
231 | { 0x169, "BRTD" }, | ||
232 | { 0x16A, "CKJL" }, | ||
233 | { 0x16B, "LFCK" }, | ||
234 | { 0x16C, "BFLX" }, | ||
235 | { 0x16D, "XJRT" }, | ||
236 | { 0x16E, "HRTB" }, | ||
237 | { 0x16F, "HBHK" }, | ||
238 | { 0x170, "JCGB" }, | ||
239 | { 0x171, "HHFL" }, | ||
240 | { 0x172, "TFBB" }, | ||
241 | { 0x173, "TXHF" }, | ||
242 | { 0x174, "JHJL" }, | ||
243 | { 0x181, " " }, | ||
244 | { 0x182, " " }, | ||
245 | { 0x183, " " }, | ||
246 | { 0x184, " " }, | ||
247 | { 0x185, " " }, | ||
248 | { 0x186, " " }, | ||
249 | { 0x187, " " }, | ||
250 | { 0x188, " " }, | ||
251 | { 0x18B, " " }, | ||
252 | { 0x18C, " " }, | ||
253 | { 0x18D, " " }, | ||
254 | { 0x18E, " " }, | ||
255 | { 0x258, " " }, | ||
256 | { 0x259, " " }, | ||
257 | { 0x25A, " " }, | ||
258 | { 0x25B, " " }, | ||
259 | { 0x25C, " " }, | ||
260 | { 0x25D, " " }, | ||
261 | { 0x263, " " }, | ||
262 | { 0x264, " " }, | ||
263 | { 0x265, " " }, | ||
264 | { 0x190, "Hello Master." }, | ||
265 | { 0x191, "Identifiy confirmed.\nAccess granted." }, | ||
266 | { 0x192, " ACCESSING" }, | ||
267 | { 0x193, " " }, | ||
268 | { 0x194, "y\n" }, | ||
269 | { 0x193, "!!!\n" }, | ||
270 | { END_OF_STRING_DICTIONARY, "" } | ||
271 | }; | ||
diff --git a/apps/plugins/xworld/video_data.h b/apps/plugins/xworld/video_data.h new file mode 100644 index 0000000000..cd53205744 --- /dev/null +++ b/apps/plugins/xworld/video_data.h | |||
@@ -0,0 +1,29 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ***************************************************************************/ | ||
21 | |||
22 | #include "video.h" | ||
23 | #include "stdint.h" | ||
24 | #define FONT_SIZE (8 * (0x80 - ' ')) | ||
25 | #define MAX_STRING_TABLE_SIZE 255 | ||
26 | |||
27 | extern uint8_t video_font[FONT_SIZE]; | ||
28 | |||
29 | extern struct StrEntry video_stringsTableEng[MAX_STRING_TABLE_SIZE]; | ||
diff --git a/apps/plugins/xworld/vm.c b/apps/plugins/xworld/vm.c new file mode 100644 index 0000000000..de632d710d --- /dev/null +++ b/apps/plugins/xworld/vm.c | |||
@@ -0,0 +1,763 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "vm.h" | ||
25 | #include "mixer.h" | ||
26 | #include "resource.h" | ||
27 | #include "video.h" | ||
28 | #include "serializer.h" | ||
29 | #include "sfxplayer.h" | ||
30 | #include "sys.h" | ||
31 | #include "parts.h" | ||
32 | #include "file.h" | ||
33 | |||
34 | static const uint16_t vm_frequenceTable[] = { | ||
35 | 0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C, | ||
36 | 0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE, | ||
37 | 0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110, | ||
38 | 0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE, | ||
39 | 0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD | ||
40 | }; | ||
41 | |||
42 | void vm_create(struct VirtualMachine* m, struct Mixer *mix, struct Resource* res, struct SfxPlayer *ply, struct Video *vid, struct System *stub) | ||
43 | { | ||
44 | m->res = res; | ||
45 | m->video = vid; | ||
46 | m->sys = stub; | ||
47 | m->mixer = mix; | ||
48 | m->player = ply; | ||
49 | } | ||
50 | |||
51 | void vm_init(struct VirtualMachine* m) { | ||
52 | |||
53 | rb->memset(m->vmVariables, 0, sizeof(m->vmVariables)); | ||
54 | m->vmVariables[0x54] = 0x81; | ||
55 | m->vmVariables[VM_VARIABLE_RANDOM_SEED] = *rb->current_tick; | ||
56 | |||
57 | m->_fastMode = false; | ||
58 | m->player->_markVar = &m->vmVariables[VM_VARIABLE_MUS_MARK]; | ||
59 | } | ||
60 | |||
61 | void vm_op_movConst(struct VirtualMachine* m) { | ||
62 | uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
63 | int16_t value = scriptPtr_fetchWord(&m->_scriptPtr); | ||
64 | debug(DBG_VM, "vm_op_movConst(0x%02X, %d)", variableId, value); | ||
65 | m->vmVariables[variableId] = value; | ||
66 | } | ||
67 | |||
68 | void vm_op_mov(struct VirtualMachine* m) { | ||
69 | uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
70 | uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
71 | debug(DBG_VM, "vm_op_mov(0x%02X, 0x%02X)", dstVariableId, srcVariableId); | ||
72 | m->vmVariables[dstVariableId] = m->vmVariables[srcVariableId]; | ||
73 | } | ||
74 | |||
75 | void vm_op_add(struct VirtualMachine* m) { | ||
76 | uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
77 | uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
78 | debug(DBG_VM, "vm_op_add(0x%02X, 0x%02X)", dstVariableId, srcVariableId); | ||
79 | m->vmVariables[dstVariableId] += m->vmVariables[srcVariableId]; | ||
80 | } | ||
81 | |||
82 | void vm_op_addConst(struct VirtualMachine* m) { | ||
83 | if (m->res->currentPartId == 0x3E86 && m->_scriptPtr.pc == m->res->segBytecode + 0x6D48) { | ||
84 | warning("vm_op_addConst() hack for non-stop looping gun sound bug"); | ||
85 | // the script 0x27 slot 0x17 doesn't stop the gun sound from looping, I | ||
86 | // don't really know why ; for now, let's play the 'stopping sound' like | ||
87 | // the other scripts do | ||
88 | // (0x6D43) jmp(0x6CE5) | ||
89 | // (0x6D46) break | ||
90 | // (0x6D47) VAR(6) += -50 | ||
91 | vm_snd_playSound(m, 0x5B, 1, 64, 1); | ||
92 | } | ||
93 | uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
94 | int16_t value = scriptPtr_fetchWord(&m->_scriptPtr); | ||
95 | debug(DBG_VM, "vm_op_addConst(0x%02X, %d)", variableId, value); | ||
96 | m->vmVariables[variableId] += value; | ||
97 | } | ||
98 | |||
99 | void vm_op_call(struct VirtualMachine* m) { | ||
100 | |||
101 | uint16_t offset = scriptPtr_fetchWord(&m->_scriptPtr); | ||
102 | uint8_t sp = m->_stackPtr; | ||
103 | |||
104 | debug(DBG_VM, "vm_op_call(0x%X)", offset); | ||
105 | m->_scriptStackCalls[sp] = m->_scriptPtr.pc - m->res->segBytecode; | ||
106 | if (m->_stackPtr == 0xFF) { | ||
107 | error("vm_op_call() ec=0x%X stack overflow", 0x8F); | ||
108 | } | ||
109 | ++m->_stackPtr; | ||
110 | m->_scriptPtr.pc = m->res->segBytecode + offset ; | ||
111 | } | ||
112 | |||
113 | void vm_op_ret(struct VirtualMachine* m) { | ||
114 | debug(DBG_VM, "vm_op_ret()"); | ||
115 | if (m->_stackPtr == 0) { | ||
116 | error("vm_op_ret() ec=0x%X stack underflow", 0x8F); | ||
117 | } | ||
118 | --m->_stackPtr; | ||
119 | uint8_t sp = m->_stackPtr; | ||
120 | m->_scriptPtr.pc = m->res->segBytecode + m->_scriptStackCalls[sp]; | ||
121 | } | ||
122 | |||
123 | void vm_op_pauseThread(struct VirtualMachine* m) { | ||
124 | debug(DBG_VM, "vm_op_pauseThread()"); | ||
125 | m->gotoNextThread = true; | ||
126 | } | ||
127 | |||
128 | void vm_op_jmp(struct VirtualMachine* m) { | ||
129 | uint16_t pcOffset = scriptPtr_fetchWord(&m->_scriptPtr); | ||
130 | debug(DBG_VM, "vm_op_jmp(0x%02X)", pcOffset); | ||
131 | m->_scriptPtr.pc = m->res->segBytecode + pcOffset; | ||
132 | } | ||
133 | |||
134 | void vm_op_setSetVect(struct VirtualMachine* m) { | ||
135 | uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
136 | uint16_t pcOffsetRequested = scriptPtr_fetchWord(&m->_scriptPtr); | ||
137 | debug(DBG_VM, "vm_op_setSetVect(0x%X, 0x%X)", threadId, pcOffsetRequested); | ||
138 | m->threadsData[REQUESTED_PC_OFFSET][threadId] = pcOffsetRequested; | ||
139 | } | ||
140 | |||
141 | void vm_op_jnz(struct VirtualMachine* m) { | ||
142 | uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr); | ||
143 | debug(DBG_VM, "vm_op_jnz(0x%02X)", i); | ||
144 | --m->vmVariables[i]; | ||
145 | if (m->vmVariables[i] != 0) { | ||
146 | vm_op_jmp(m); | ||
147 | } else { | ||
148 | scriptPtr_fetchWord(&m->_scriptPtr); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | #define BYPASS_PROTECTION | ||
153 | void vm_op_condJmp(struct VirtualMachine* m) { | ||
154 | |||
155 | //printf("Jump : %X \n",m->_scriptPtr.pc-m->res->segBytecode); | ||
156 | //FCS Whoever wrote this is patching the bytecode on the fly. This is ballzy !! | ||
157 | #ifdef BYPASS_PROTECTION | ||
158 | |||
159 | if (m->res->currentPartId == GAME_PART_FIRST && m->_scriptPtr.pc == m->res->segBytecode + 0xCB9) { | ||
160 | |||
161 | // (0x0CB8) condJmp(0x80, VAR(41), VAR(30), 0xCD3) | ||
162 | *(m->_scriptPtr.pc + 0x00) = 0x81; | ||
163 | *(m->_scriptPtr.pc + 0x03) = 0x0D; | ||
164 | *(m->_scriptPtr.pc + 0x04) = 0x24; | ||
165 | // (0x0D4E) condJmp(0x4, VAR(50), 6, 0xDBC) | ||
166 | *(m->_scriptPtr.pc + 0x99) = 0x0D; | ||
167 | *(m->_scriptPtr.pc + 0x9A) = 0x5A; | ||
168 | debug(DBG_VM, "vm_op_condJmp() bypassing protection"); | ||
169 | debug(DBG_VM, "bytecode has been patched"); | ||
170 | |||
171 | //vm_bypassProtection(m); | ||
172 | } | ||
173 | |||
174 | |||
175 | #endif | ||
176 | |||
177 | uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr); | ||
178 | int16_t b = m->vmVariables[scriptPtr_fetchByte(&m->_scriptPtr)]; | ||
179 | uint8_t c = scriptPtr_fetchByte(&m->_scriptPtr); | ||
180 | int16_t a; | ||
181 | |||
182 | if (opcode & 0x80) { | ||
183 | a = m->vmVariables[c]; | ||
184 | } else if (opcode & 0x40) { | ||
185 | a = c * 256 + scriptPtr_fetchByte(&m->_scriptPtr); | ||
186 | } else { | ||
187 | a = c; | ||
188 | } | ||
189 | debug(DBG_VM, "vm_op_condJmp(%d, 0x%02X, 0x%02X)", opcode, b, a); | ||
190 | |||
191 | // Check if the conditional value is met. | ||
192 | bool expr = false; | ||
193 | switch (opcode & 7) { | ||
194 | case 0: // jz | ||
195 | expr = (b == a); | ||
196 | break; | ||
197 | case 1: // jnz | ||
198 | expr = (b != a); | ||
199 | break; | ||
200 | case 2: // jg | ||
201 | expr = (b > a); | ||
202 | break; | ||
203 | case 3: // jge | ||
204 | expr = (b >= a); | ||
205 | break; | ||
206 | case 4: // jl | ||
207 | expr = (b < a); | ||
208 | break; | ||
209 | case 5: // jle | ||
210 | expr = (b <= a); | ||
211 | break; | ||
212 | default: | ||
213 | warning("vm_op_condJmp() invalid condition %d", (opcode & 7)); | ||
214 | break; | ||
215 | } | ||
216 | |||
217 | if (expr) { | ||
218 | vm_op_jmp(m); | ||
219 | } else { | ||
220 | scriptPtr_fetchWord(&m->_scriptPtr); | ||
221 | } | ||
222 | |||
223 | } | ||
224 | |||
225 | void vm_op_setPalette(struct VirtualMachine* m) { | ||
226 | uint16_t paletteId = scriptPtr_fetchWord(&m->_scriptPtr); | ||
227 | debug(DBG_VM, "vm_op_changePalette(%d)", paletteId); | ||
228 | m->video->paletteIdRequested = paletteId >> 8; | ||
229 | } | ||
230 | |||
231 | void vm_op_resetThread(struct VirtualMachine* m) { | ||
232 | |||
233 | uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
234 | uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr); | ||
235 | |||
236 | // FCS: WTF, this is cryptic as hell !! | ||
237 | // int8_t n = (i & 0x3F) - threadId; //0x3F = 0011 1111 | ||
238 | // The following is so much clearer | ||
239 | |||
240 | //Make sure i is within [0-VM_NUM_THREADS-1] | ||
241 | i = i & (VM_NUM_THREADS - 1) ; | ||
242 | int8_t n = i - threadId; | ||
243 | |||
244 | if (n < 0) { | ||
245 | warning("vm_op_m->resetThread() ec=0x%X (n < 0)", 0x880); | ||
246 | return; | ||
247 | } | ||
248 | ++n; | ||
249 | uint8_t a = scriptPtr_fetchByte(&m->_scriptPtr); | ||
250 | |||
251 | debug(DBG_VM, "vm_op_m->resetThread(%d, %d, %d)", threadId, i, a); | ||
252 | |||
253 | if (a == 2) { | ||
254 | uint16_t *p = &m->threadsData[REQUESTED_PC_OFFSET][threadId]; | ||
255 | while (n--) { | ||
256 | *p++ = 0xFFFE; | ||
257 | } | ||
258 | } else if (a < 2) { | ||
259 | uint8_t *p = &m->vmIsChannelActive[REQUESTED_STATE][threadId]; | ||
260 | while (n--) { | ||
261 | *p++ = a; | ||
262 | } | ||
263 | } | ||
264 | } | ||
265 | |||
266 | void vm_op_selectVideoPage(struct VirtualMachine* m) { | ||
267 | uint8_t frameBufferId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
268 | debug(DBG_VM, "vm_op_selectVideoPage(%d)", frameBufferId); | ||
269 | video_changePagePtr1(m->video, frameBufferId); | ||
270 | } | ||
271 | |||
272 | void vm_op_fillVideoPage(struct VirtualMachine* m) { | ||
273 | uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
274 | uint8_t color = scriptPtr_fetchByte(&m->_scriptPtr); | ||
275 | debug(DBG_VM, "vm_op_fillVideoPage(%d, %d)", pageId, color); | ||
276 | video_fillPage(m->video, pageId, color); | ||
277 | } | ||
278 | |||
279 | void vm_op_copyVideoPage(struct VirtualMachine* m) { | ||
280 | uint8_t srcPageId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
281 | uint8_t dstPageId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
282 | debug(DBG_VM, "vm_op_copyVideoPage(%d, %d)", srcPageId, dstPageId); | ||
283 | video_copyPage(m->video, srcPageId, dstPageId, m->vmVariables[VM_VARIABLE_SCROLL_Y]); | ||
284 | } | ||
285 | |||
286 | |||
287 | static uint32_t lastTimeStamp = 0; | ||
288 | void vm_op_blitFramebuffer(struct VirtualMachine* m) { | ||
289 | |||
290 | uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
291 | debug(DBG_VM, "vm_op_blitFramebuffer(%d)", pageId); | ||
292 | vm_inp_handleSpecialKeys(m); | ||
293 | |||
294 | /* Nasty hack....was this present in the original assembly ??!! */ | ||
295 | if (m->res->currentPartId == GAME_PART_FIRST && m->vmVariables[0x67] == 1) | ||
296 | m->vmVariables[0xDC] = 0x21; | ||
297 | |||
298 | if (!m->_fastMode) { | ||
299 | |||
300 | int32_t delay = sys_getTimeStamp(m->sys) - lastTimeStamp; | ||
301 | int32_t timeToSleep = m->vmVariables[VM_VARIABLE_PAUSE_SLICES] * 20 - delay; | ||
302 | |||
303 | /* The bytecode will set m->vmVariables[VM_VARIABLE_PAUSE_SLICES] from 1 to 5 */ | ||
304 | /* The virtual machine hence indicates how long the image should be displayed. */ | ||
305 | |||
306 | if (timeToSleep > 0) | ||
307 | { | ||
308 | sys_sleep(m->sys, timeToSleep); | ||
309 | } | ||
310 | |||
311 | lastTimeStamp = sys_getTimeStamp(m->sys); | ||
312 | } | ||
313 | |||
314 | /* WTF ? */ | ||
315 | m->vmVariables[0xF7] = 0; | ||
316 | |||
317 | video_updateDisplay(m->video, pageId); | ||
318 | } | ||
319 | |||
320 | void vm_op_killThread(struct VirtualMachine* m) { | ||
321 | debug(DBG_VM, "vm_op_killThread()"); | ||
322 | m->_scriptPtr.pc = m->res->segBytecode + 0xFFFF; | ||
323 | m->gotoNextThread = true; | ||
324 | } | ||
325 | |||
326 | void vm_op_drawString(struct VirtualMachine* m) { | ||
327 | uint16_t stringId = scriptPtr_fetchWord(&m->_scriptPtr); | ||
328 | uint16_t x = scriptPtr_fetchByte(&m->_scriptPtr); | ||
329 | uint16_t y = scriptPtr_fetchByte(&m->_scriptPtr); | ||
330 | uint16_t color = scriptPtr_fetchByte(&m->_scriptPtr); | ||
331 | |||
332 | debug(DBG_VM, "vm_op_drawString(0x%03X, %d, %d, %d)", stringId, x, y, color); | ||
333 | |||
334 | video_drawString(m->video, color, x, y, stringId); | ||
335 | } | ||
336 | |||
337 | void vm_op_sub(struct VirtualMachine* m) { | ||
338 | uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr); | ||
339 | uint8_t j = scriptPtr_fetchByte(&m->_scriptPtr); | ||
340 | debug(DBG_VM, "vm_op_sub(0x%02X, 0x%02X)", i, j); | ||
341 | m->vmVariables[i] -= m->vmVariables[j]; | ||
342 | } | ||
343 | |||
344 | void vm_op_and(struct VirtualMachine* m) { | ||
345 | uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
346 | uint16_t n = scriptPtr_fetchWord(&m->_scriptPtr); | ||
347 | debug(DBG_VM, "vm_op_and(0x%02X, %d)", variableId, n); | ||
348 | m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] & n; | ||
349 | } | ||
350 | |||
351 | void vm_op_or(struct VirtualMachine* m) { | ||
352 | uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
353 | uint16_t value = scriptPtr_fetchWord(&m->_scriptPtr); | ||
354 | debug(DBG_VM, "vm_op_or(0x%02X, %d)", variableId, value); | ||
355 | m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] | value; | ||
356 | } | ||
357 | |||
358 | void vm_op_shl(struct VirtualMachine* m) { | ||
359 | uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
360 | uint16_t leftShiftValue = scriptPtr_fetchWord(&m->_scriptPtr); | ||
361 | debug(DBG_VM, "vm_op_shl(0x%02X, %d)", variableId, leftShiftValue); | ||
362 | m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] << leftShiftValue; | ||
363 | } | ||
364 | |||
365 | void vm_op_shr(struct VirtualMachine* m) { | ||
366 | uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr); | ||
367 | uint16_t rightShiftValue = scriptPtr_fetchWord(&m->_scriptPtr); | ||
368 | debug(DBG_VM, "vm_op_shr(0x%02X, %d)", variableId, rightShiftValue); | ||
369 | m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] >> rightShiftValue; | ||
370 | } | ||
371 | |||
372 | void vm_op_playSound(struct VirtualMachine* m) { | ||
373 | uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr); | ||
374 | uint8_t freq = scriptPtr_fetchByte(&m->_scriptPtr); | ||
375 | uint8_t vol = scriptPtr_fetchByte(&m->_scriptPtr); | ||
376 | uint8_t channel = scriptPtr_fetchByte(&m->_scriptPtr); | ||
377 | debug(DBG_VM, "vm_op_playSound(0x%X, %d, %d, %d)", resourceId, freq, vol, channel); | ||
378 | vm_snd_playSound(m, resourceId, freq, vol, channel); | ||
379 | } | ||
380 | |||
381 | void vm_op_updateMemList(struct VirtualMachine* m) { | ||
382 | |||
383 | uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr); | ||
384 | debug(DBG_VM, "vm_op_updateMemList(%d)", resourceId); | ||
385 | |||
386 | if (resourceId == 0) { | ||
387 | player_stop(m->player); | ||
388 | mixer_stopAll(m->mixer); | ||
389 | res_invalidateRes(m->res); | ||
390 | } else { | ||
391 | res_loadPartsOrMemoryEntry(m->res, resourceId); | ||
392 | } | ||
393 | } | ||
394 | |||
395 | void vm_op_playMusic(struct VirtualMachine* m) { | ||
396 | uint16_t resNum = scriptPtr_fetchWord(&m->_scriptPtr); | ||
397 | uint16_t delay = scriptPtr_fetchWord(&m->_scriptPtr); | ||
398 | uint8_t pos = scriptPtr_fetchByte(&m->_scriptPtr); | ||
399 | debug(DBG_VM, "vm_op_playMusic(0x%X, %d, %d)", resNum, delay, pos); | ||
400 | vm_snd_playMusic(m, resNum, delay, pos); | ||
401 | } | ||
402 | |||
403 | void vm_initForPart(struct VirtualMachine* m, uint16_t partId) { | ||
404 | |||
405 | player_stop(m->player); | ||
406 | mixer_stopAll(m->mixer); | ||
407 | |||
408 | /* WTF is that ? */ | ||
409 | m->vmVariables[0xE4] = 0x14; | ||
410 | |||
411 | res_setupPart(m->res, partId); | ||
412 | |||
413 | /* Set all thread to inactive (pc at 0xFFFF or 0xFFFE ) */ | ||
414 | rb->memset((uint8_t *)m->threadsData, 0xFF, sizeof(m->threadsData)); | ||
415 | |||
416 | rb->memset((uint8_t *)m->vmIsChannelActive, 0, sizeof(m->vmIsChannelActive)); | ||
417 | |||
418 | int firstThreadId = 0; | ||
419 | m->threadsData[PC_OFFSET][firstThreadId] = 0; | ||
420 | } | ||
421 | |||
422 | /* | ||
423 | This is called every frames in the infinite loop. | ||
424 | */ | ||
425 | void vm_checkThreadRequests(struct VirtualMachine* m) { | ||
426 | |||
427 | /* Check if a part switch has been requested. */ | ||
428 | if (m->res->requestedNextPart != 0) { | ||
429 | vm_initForPart(m, m->res->requestedNextPart); | ||
430 | m->res->requestedNextPart = 0; | ||
431 | } | ||
432 | |||
433 | |||
434 | /* Check if a state update has been requested for any thread during the previous VM execution: */ | ||
435 | /* - Pause */ | ||
436 | /* - Jump */ | ||
437 | |||
438 | /* JUMP: */ | ||
439 | /* Note: If a jump has been requested, the jump destination is stored */ | ||
440 | /* in m->threadsData[REQUESTED_PC_OFFSET]. Otherwise m->threadsData[REQUESTED_PC_OFFSET] == 0xFFFF */ | ||
441 | |||
442 | /* PAUSE: */ | ||
443 | /* Note: If a pause has been requested it is stored in m->vmIsChannelActive[REQUESTED_STATE][i] */ | ||
444 | |||
445 | for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) { | ||
446 | |||
447 | m->vmIsChannelActive[CURR_STATE][threadId] = m->vmIsChannelActive[REQUESTED_STATE][threadId]; | ||
448 | |||
449 | uint16_t n = m->threadsData[REQUESTED_PC_OFFSET][threadId]; | ||
450 | |||
451 | if (n != VM_NO_SETVEC_REQUESTED) { | ||
452 | |||
453 | m->threadsData[PC_OFFSET][threadId] = (n == 0xFFFE) ? VM_INACTIVE_THREAD : n; | ||
454 | m->threadsData[REQUESTED_PC_OFFSET][threadId] = VM_NO_SETVEC_REQUESTED; | ||
455 | } | ||
456 | } | ||
457 | } | ||
458 | |||
459 | void vm_hostFrame(struct VirtualMachine* m) { | ||
460 | |||
461 | /* Run the Virtual Machine for every active threads (one vm frame). */ | ||
462 | /* Inactive threads are marked with a thread instruction pointer set to 0xFFFF (VM_INACTIVE_THREAD). */ | ||
463 | /* A thread must feature a break opcode so the interpreter can move to the next thread. */ | ||
464 | |||
465 | for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) { | ||
466 | |||
467 | if (m->vmIsChannelActive[CURR_STATE][threadId]) | ||
468 | continue; | ||
469 | |||
470 | uint16_t n = m->threadsData[PC_OFFSET][threadId]; | ||
471 | |||
472 | if (n != VM_INACTIVE_THREAD) { | ||
473 | |||
474 | /* Set the script pointer to the right location. */ | ||
475 | /* script pc is used in executeThread in order */ | ||
476 | /* to get the next opcode. */ | ||
477 | m->_scriptPtr.pc = m->res->segBytecode + n; | ||
478 | m->_stackPtr = 0; | ||
479 | |||
480 | m->gotoNextThread = false; | ||
481 | debug(DBG_VM, "vm_hostFrame() i=0x%02X n=0x%02X *p=0x%02X", threadId, n, *m->_scriptPtr.pc); | ||
482 | vm_executeThread(m); | ||
483 | |||
484 | /* Since .pc is going to be modified by this next loop iteration, we need to save it. */ | ||
485 | m->threadsData[PC_OFFSET][threadId] = m->_scriptPtr.pc - m->res->segBytecode; | ||
486 | |||
487 | |||
488 | debug(DBG_VM, "vm_hostFrame() i=0x%02X pos=0x%X", threadId, m->threadsData[PC_OFFSET][threadId]); | ||
489 | if (m->sys->input.quit) { | ||
490 | break; | ||
491 | } | ||
492 | } | ||
493 | } | ||
494 | } | ||
495 | |||
496 | #define COLOR_BLACK 0xFF | ||
497 | #define DEFAULT_ZOOM 0x40 | ||
498 | |||
499 | |||
500 | void vm_executeThread(struct VirtualMachine* m) { | ||
501 | |||
502 | while (!m->gotoNextThread) { | ||
503 | uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr); | ||
504 | |||
505 | /* 1000 0000 is set */ | ||
506 | if (opcode & 0x80) | ||
507 | { | ||
508 | uint16_t off = ((opcode << 8) | scriptPtr_fetchByte(&m->_scriptPtr)) * 2; | ||
509 | m->res->_useSegVideo2 = false; | ||
510 | int16_t x = scriptPtr_fetchByte(&m->_scriptPtr); | ||
511 | int16_t y = scriptPtr_fetchByte(&m->_scriptPtr); | ||
512 | int16_t h = y - 199; | ||
513 | if (h > 0) { | ||
514 | y = 199; | ||
515 | x += h; | ||
516 | } | ||
517 | debug(DBG_VIDEO, "vid_opcd_0x80 : opcode=0x%X off=0x%X x=%d y=%d", opcode, off, x, y); | ||
518 | |||
519 | /* This switch the polygon database to "cinematic" and probably draws a black polygon */ | ||
520 | /* over all the screen. */ | ||
521 | video_setDataBuffer(m->video, m->res->segCinematic, off); | ||
522 | struct Point temp; | ||
523 | temp.x = x; | ||
524 | temp.y = y; | ||
525 | video_readAndDrawPolygon(m->video, COLOR_BLACK, DEFAULT_ZOOM, &temp); | ||
526 | |||
527 | continue; | ||
528 | } | ||
529 | |||
530 | /* 0100 0000 is set */ | ||
531 | if (opcode & 0x40) | ||
532 | { | ||
533 | int16_t x, y; | ||
534 | uint16_t off = scriptPtr_fetchWord(&m->_scriptPtr) * 2; | ||
535 | x = scriptPtr_fetchByte(&m->_scriptPtr); | ||
536 | |||
537 | m->res->_useSegVideo2 = false; | ||
538 | |||
539 | if (!(opcode & 0x20)) | ||
540 | { | ||
541 | if (!(opcode & 0x10)) /* 0001 0000 is set */ | ||
542 | { | ||
543 | x = (x << 8) | scriptPtr_fetchByte(&m->_scriptPtr); | ||
544 | } else { | ||
545 | x = m->vmVariables[x]; | ||
546 | } | ||
547 | } | ||
548 | else | ||
549 | { | ||
550 | if (opcode & 0x10) { /* 0001 0000 is set */ | ||
551 | x += 0x100; | ||
552 | } | ||
553 | } | ||
554 | |||
555 | y = scriptPtr_fetchByte(&m->_scriptPtr); | ||
556 | |||
557 | if (!(opcode & 8)) /* 0000 1000 is set */ | ||
558 | { | ||
559 | if (!(opcode & 4)) { /* 0000 0100 is set */ | ||
560 | y = (y << 8) | scriptPtr_fetchByte(&m->_scriptPtr); | ||
561 | } else { | ||
562 | y = m->vmVariables[y]; | ||
563 | } | ||
564 | } | ||
565 | |||
566 | uint16_t zoom = scriptPtr_fetchByte(&m->_scriptPtr); | ||
567 | |||
568 | if (!(opcode & 2)) /* 0000 0010 is set */ | ||
569 | { | ||
570 | if (!(opcode & 1)) /* 0000 0001 is set */ | ||
571 | { | ||
572 | --m->_scriptPtr.pc; | ||
573 | zoom = 0x40; | ||
574 | } | ||
575 | else | ||
576 | { | ||
577 | zoom = m->vmVariables[zoom]; | ||
578 | } | ||
579 | } | ||
580 | else | ||
581 | { | ||
582 | |||
583 | if (opcode & 1) { /* 0000 0001 is set */ | ||
584 | m->res->_useSegVideo2 = true; | ||
585 | --m->_scriptPtr.pc; | ||
586 | zoom = 0x40; | ||
587 | } | ||
588 | } | ||
589 | debug(DBG_VIDEO, "vid_opcd_0x40 : off=0x%X x=%d y=%d", off, x, y); | ||
590 | video_setDataBuffer(m->video, m->res->_useSegVideo2 ? m->res->_segVideo2 : m->res->segCinematic, off); | ||
591 | struct Point temp; | ||
592 | temp.x = x; | ||
593 | temp.y = y; | ||
594 | video_readAndDrawPolygon(m->video, 0xFF, zoom, &temp); | ||
595 | |||
596 | continue; | ||
597 | } | ||
598 | |||
599 | |||
600 | if (opcode > 0x1A) | ||
601 | { | ||
602 | error("vm_executeThread() ec=0x%X invalid opcode=0x%X", 0xFFF, opcode); | ||
603 | } | ||
604 | else | ||
605 | { | ||
606 | (vm_opcodeTable[opcode])(m); | ||
607 | } | ||
608 | |||
609 | rb->yield(); | ||
610 | } | ||
611 | } | ||
612 | |||
613 | void vm_inp_updatePlayer(struct VirtualMachine* m) { | ||
614 | |||
615 | sys_processEvents(m->sys); | ||
616 | |||
617 | if (m->res->currentPartId == 0x3E89) { | ||
618 | char c = m->sys->input.lastChar; | ||
619 | if (c == 8 || /*c == 0xD |*/ c == 0 || (c >= 'a' && c <= 'z')) { | ||
620 | m->vmVariables[VM_VARIABLE_LAST_KEYCHAR] = c & ~0x20; | ||
621 | m->sys->input.lastChar = 0; | ||
622 | } | ||
623 | } | ||
624 | |||
625 | int16_t lr = 0; | ||
626 | int16_t mask = 0; | ||
627 | int16_t ud = 0; | ||
628 | |||
629 | if (m->sys->input.dirMask & DIR_RIGHT) { | ||
630 | lr = 1; | ||
631 | mask |= 1; | ||
632 | } | ||
633 | if (m->sys->input.dirMask & DIR_LEFT) { | ||
634 | lr = -1; | ||
635 | mask |= 2; | ||
636 | } | ||
637 | if (m->sys->input.dirMask & DIR_DOWN) { | ||
638 | ud = 1; | ||
639 | mask |= 4; | ||
640 | } | ||
641 | |||
642 | m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = ud; | ||
643 | |||
644 | if (m->sys->input.dirMask & DIR_UP) { | ||
645 | m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = -1; | ||
646 | } | ||
647 | |||
648 | if (m->sys->input.dirMask & DIR_UP) { /* inpJump */ | ||
649 | ud = -1; | ||
650 | mask |= 8; | ||
651 | } | ||
652 | |||
653 | m->vmVariables[VM_VARIABLE_HERO_POS_JUMP_DOWN] = ud; | ||
654 | m->vmVariables[VM_VARIABLE_HERO_POS_LEFT_RIGHT] = lr; | ||
655 | m->vmVariables[VM_VARIABLE_HERO_POS_MASK] = mask; | ||
656 | int16_t button = 0; | ||
657 | |||
658 | if (m->sys->input.button) { | ||
659 | button = 1; | ||
660 | mask |= 0x80; | ||
661 | } | ||
662 | |||
663 | m->vmVariables[VM_VARIABLE_HERO_ACTION] = button; | ||
664 | m->vmVariables[VM_VARIABLE_HERO_ACTION_POS_MASK] = mask; | ||
665 | } | ||
666 | |||
667 | void vm_inp_handleSpecialKeys(struct VirtualMachine* m) { | ||
668 | |||
669 | if (m->sys->input.pause) { | ||
670 | |||
671 | if (m->res->currentPartId != GAME_PART1 && m->res->currentPartId != GAME_PART2) { | ||
672 | m->sys->input.pause = false; | ||
673 | while (!m->sys->input.pause) { | ||
674 | sys_processEvents(m->sys); | ||
675 | sys_sleep(m->sys, 200); | ||
676 | } | ||
677 | } | ||
678 | m->sys->input.pause = false; | ||
679 | } | ||
680 | |||
681 | if (m->sys->input.code) { | ||
682 | m->sys->input.code = false; | ||
683 | if (m->res->currentPartId != GAME_PART_LAST && m->res->currentPartId != GAME_PART_FIRST) { | ||
684 | m->res->requestedNextPart = GAME_PART_LAST; | ||
685 | } | ||
686 | } | ||
687 | |||
688 | /* User has inputted a bad code, the "ERROR" screen is showing */ | ||
689 | if (m->vmVariables[0xC9] == 1) { | ||
690 | debug(DBG_VM, "vm_inp_handleSpecialKeys() unhandled case (m->vmVariables[0xC9] == 1)"); | ||
691 | } | ||
692 | |||
693 | } | ||
694 | |||
695 | void vm_snd_playSound(struct VirtualMachine* m, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel) { | ||
696 | |||
697 | debug(DBG_SND, "snd_playSound(0x%X, %d, %d, %d)", resNum, freq, vol, channel); | ||
698 | |||
699 | struct MemEntry *me = &m->res->_memList[resNum]; | ||
700 | |||
701 | if (me->state != MEMENTRY_STATE_LOADED) | ||
702 | return; | ||
703 | |||
704 | |||
705 | if (vol == 0) { | ||
706 | mixer_stopChannel(m->mixer, channel); | ||
707 | } else { | ||
708 | struct MixerChunk mc; | ||
709 | rb->memset(&mc, 0, sizeof(mc)); | ||
710 | mc.data = me->bufPtr + 8; /* skip header */ | ||
711 | mc.len = READ_BE_UINT16(me->bufPtr) * 2; | ||
712 | mc.loopLen = READ_BE_UINT16(me->bufPtr + 2) * 2; | ||
713 | if (mc.loopLen != 0) { | ||
714 | mc.loopPos = mc.len; | ||
715 | } | ||
716 | assert(freq < 40); | ||
717 | mixer_playChannel(m->mixer, channel & 3, &mc, vm_frequenceTable[freq], MIN(vol, 0x3F)); | ||
718 | } | ||
719 | |||
720 | } | ||
721 | |||
722 | void vm_snd_playMusic(struct VirtualMachine* m, uint16_t resNum, uint16_t delay, uint8_t pos) { | ||
723 | |||
724 | debug(DBG_SND, "snd_playMusic(0x%X, %d, %d)", resNum, delay, pos); | ||
725 | |||
726 | if (resNum != 0) { | ||
727 | player_loadSfxModule(m->player, resNum, delay, pos); | ||
728 | player_start(m->player); | ||
729 | } else if (delay != 0) { | ||
730 | player_setEventsDelay(m->player, delay); | ||
731 | } else { | ||
732 | player_stop(m->player); | ||
733 | } | ||
734 | } | ||
735 | |||
736 | void vm_saveOrLoad(struct VirtualMachine* m, struct Serializer *ser) { | ||
737 | struct Entry entries[] = { | ||
738 | SE_ARRAY(m->vmVariables, 0x100, SES_INT16, VER(1)), | ||
739 | SE_ARRAY(m->_scriptStackCalls, 0x100, SES_INT16, VER(1)), | ||
740 | SE_ARRAY(m->threadsData, 0x40 * 2, SES_INT16, VER(1)), | ||
741 | SE_ARRAY(m->vmIsChannelActive, 0x40 * 2, SES_INT8, VER(1)), | ||
742 | SE_END() | ||
743 | }; | ||
744 | ser_saveOrLoadEntries(ser, entries); | ||
745 | } | ||
746 | |||
747 | void vm_bypassProtection(struct VirtualMachine* m) | ||
748 | { | ||
749 | File f; | ||
750 | file_create(&f, true); | ||
751 | if (!file_open(&f, "bank0e", res_getDataDir(m->res), "rb")) { | ||
752 | warning("Unable to bypass protection: add bank0e file to datadir"); | ||
753 | } else { | ||
754 | struct Serializer s; | ||
755 | ser_create(&s, &f, SM_LOAD, m->res->_memPtrStart, 2); | ||
756 | vm_saveOrLoad(m, &s); | ||
757 | res_saveOrLoad(m->res, &s); | ||
758 | video_saveOrLoad(m->video, &s); | ||
759 | player_saveOrLoad(m->player, &s); | ||
760 | mixer_saveOrLoad(m->mixer, &s); | ||
761 | } | ||
762 | file_close(&f); | ||
763 | } | ||
diff --git a/apps/plugins/xworld/vm.h b/apps/plugins/xworld/vm.h new file mode 100644 index 0000000000..1409dd47e3 --- /dev/null +++ b/apps/plugins/xworld/vm.h | |||
@@ -0,0 +1,184 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #ifndef __LOGIC_H__ | ||
24 | #define __LOGIC_H__ | ||
25 | |||
26 | |||
27 | |||
28 | #include "intern.h" | ||
29 | |||
30 | #define VM_NUM_THREADS 64 | ||
31 | #define VM_NUM_VARIABLES 256 | ||
32 | #define VM_NO_SETVEC_REQUESTED 0xFFFF | ||
33 | #define VM_INACTIVE_THREAD 0xFFFF | ||
34 | |||
35 | |||
36 | #define VM_VARIABLE_RANDOM_SEED 0x3C | ||
37 | |||
38 | #define VM_VARIABLE_LAST_KEYCHAR 0xDA | ||
39 | |||
40 | #define VM_VARIABLE_HERO_POS_UP_DOWN 0xE5 | ||
41 | #define VM_VARIABLE_MUS_MARK 0xF4 | ||
42 | |||
43 | #define VM_VARIABLE_SCROLL_Y 0xF9 | ||
44 | #define VM_VARIABLE_HERO_ACTION 0xFA | ||
45 | #define VM_VARIABLE_HERO_POS_JUMP_DOWN 0xFB | ||
46 | #define VM_VARIABLE_HERO_POS_LEFT_RIGHT 0xFC | ||
47 | #define VM_VARIABLE_HERO_POS_MASK 0xFD | ||
48 | #define VM_VARIABLE_HERO_ACTION_POS_MASK 0xFE | ||
49 | #define VM_VARIABLE_PAUSE_SLICES 0xFF | ||
50 | |||
51 | struct Mixer; | ||
52 | struct Resource; | ||
53 | struct Serializer; | ||
54 | struct SfxPlayer; | ||
55 | struct System; | ||
56 | struct Video; | ||
57 | |||
58 | //For threadsData navigation | ||
59 | #define PC_OFFSET 0 | ||
60 | #define REQUESTED_PC_OFFSET 1 | ||
61 | #define NUM_DATA_FIELDS 2 | ||
62 | |||
63 | //For vmIsChannelActive navigation | ||
64 | #define CURR_STATE 0 | ||
65 | #define REQUESTED_STATE 1 | ||
66 | #define NUM_THREAD_FIELDS 2 | ||
67 | |||
68 | struct VirtualMachine; | ||
69 | |||
70 | void vm_create(struct VirtualMachine*, struct Mixer *mix, struct Resource *res, struct SfxPlayer *ply, struct Video *vid, struct System *stub); | ||
71 | void vm_init(struct VirtualMachine*); | ||
72 | |||
73 | void vm_op_movConst(struct VirtualMachine*); | ||
74 | void vm_op_mov(struct VirtualMachine*); | ||
75 | void vm_op_add(struct VirtualMachine*); | ||
76 | void vm_op_addConst(struct VirtualMachine*); | ||
77 | void vm_op_call(struct VirtualMachine*); | ||
78 | void vm_op_ret(struct VirtualMachine*); | ||
79 | void vm_op_pauseThread(struct VirtualMachine*); | ||
80 | void vm_op_jmp(struct VirtualMachine*); | ||
81 | void vm_op_setSetVect(struct VirtualMachine*); | ||
82 | void vm_op_jnz(struct VirtualMachine*); | ||
83 | void vm_op_condJmp(struct VirtualMachine*); | ||
84 | void vm_op_setPalette(struct VirtualMachine*); | ||
85 | void vm_op_resetThread(struct VirtualMachine*); | ||
86 | void vm_op_selectVideoPage(struct VirtualMachine*); | ||
87 | void vm_op_fillVideoPage(struct VirtualMachine*); | ||
88 | void vm_op_copyVideoPage(struct VirtualMachine*); | ||
89 | void vm_op_blitFramebuffer(struct VirtualMachine*); | ||
90 | void vm_op_killThread(struct VirtualMachine*); | ||
91 | void vm_op_drawString(struct VirtualMachine*); | ||
92 | void vm_op_sub(struct VirtualMachine*); | ||
93 | void vm_op_and(struct VirtualMachine*); | ||
94 | void vm_op_or(struct VirtualMachine*); | ||
95 | void vm_op_shl(struct VirtualMachine*); | ||
96 | void vm_op_shr(struct VirtualMachine*); | ||
97 | void vm_op_playSound(struct VirtualMachine*); | ||
98 | void vm_op_updateMemList(struct VirtualMachine*); | ||
99 | void vm_op_playMusic(struct VirtualMachine*); | ||
100 | |||
101 | void vm_initForPart(struct VirtualMachine*, uint16_t partId); | ||
102 | void vm_setupPart(struct VirtualMachine*, uint16_t partId); | ||
103 | void vm_checkThreadRequests(struct VirtualMachine*); | ||
104 | void vm_hostFrame(struct VirtualMachine*); | ||
105 | void vm_executeThread(struct VirtualMachine*); | ||
106 | |||
107 | void vm_inp_updatePlayer(struct VirtualMachine*); | ||
108 | void vm_inp_handleSpecialKeys(struct VirtualMachine*); | ||
109 | |||
110 | void vm_snd_playSound(struct VirtualMachine*, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel); | ||
111 | void vm_snd_playMusic(struct VirtualMachine*, uint16_t resNum, uint16_t delay, uint8_t pos); | ||
112 | |||
113 | void vm_saveOrLoad(struct VirtualMachine*, struct Serializer *ser); | ||
114 | void vm_bypassProtection(struct VirtualMachine*); | ||
115 | |||
116 | typedef void (*OpcodeStub)(struct VirtualMachine*); | ||
117 | |||
118 | // The type of entries in opcodeTable. This allows "fast" branching | ||
119 | static const OpcodeStub vm_opcodeTable[] = { | ||
120 | /* 0x00 */ | ||
121 | &vm_op_movConst, | ||
122 | &vm_op_mov, | ||
123 | &vm_op_add, | ||
124 | &vm_op_addConst, | ||
125 | /* 0x04 */ | ||
126 | &vm_op_call, | ||
127 | &vm_op_ret, | ||
128 | &vm_op_pauseThread, | ||
129 | &vm_op_jmp, | ||
130 | /* 0x08 */ | ||
131 | &vm_op_setSetVect, | ||
132 | &vm_op_jnz, | ||
133 | &vm_op_condJmp, | ||
134 | &vm_op_setPalette, | ||
135 | /* 0x0C */ | ||
136 | &vm_op_resetThread, | ||
137 | &vm_op_selectVideoPage, | ||
138 | &vm_op_fillVideoPage, | ||
139 | &vm_op_copyVideoPage, | ||
140 | /* 0x10 */ | ||
141 | &vm_op_blitFramebuffer, | ||
142 | &vm_op_killThread, | ||
143 | &vm_op_drawString, | ||
144 | &vm_op_sub, | ||
145 | /* 0x14 */ | ||
146 | &vm_op_and, | ||
147 | &vm_op_or, | ||
148 | &vm_op_shl, | ||
149 | &vm_op_shr, | ||
150 | /* 0x18 */ | ||
151 | &vm_op_playSound, | ||
152 | &vm_op_updateMemList, | ||
153 | &vm_op_playMusic | ||
154 | }; | ||
155 | |||
156 | struct VirtualMachine { | ||
157 | //This table is used to play a sound | ||
158 | //static const uint16_t frequenceTable[]; | ||
159 | /* FW: moved from staticres.c to vm.c */ | ||
160 | |||
161 | struct Mixer *mixer; | ||
162 | struct Resource *res; | ||
163 | struct SfxPlayer *player; | ||
164 | struct Video *video; | ||
165 | struct System *sys; | ||
166 | |||
167 | int16_t vmVariables[VM_NUM_VARIABLES]; | ||
168 | uint16_t _scriptStackCalls[VM_NUM_THREADS]; | ||
169 | |||
170 | uint16_t threadsData[NUM_DATA_FIELDS][VM_NUM_THREADS]; | ||
171 | // This array is used: | ||
172 | // 0 to save the channel's instruction pointer | ||
173 | // when the channel release control (this happens on a break). | ||
174 | |||
175 | // 1 When a setVec is requested for the next vm frame. | ||
176 | |||
177 | uint8_t vmIsChannelActive[NUM_THREAD_FIELDS][VM_NUM_THREADS]; | ||
178 | |||
179 | struct Ptr _scriptPtr; | ||
180 | uint8_t _stackPtr; | ||
181 | bool gotoNextThread; | ||
182 | bool _fastMode; | ||
183 | }; | ||
184 | #endif | ||
diff --git a/apps/plugins/xworld/xworld.c b/apps/plugins/xworld/xworld.c new file mode 100644 index 0000000000..932ff1c3e1 --- /dev/null +++ b/apps/plugins/xworld/xworld.c | |||
@@ -0,0 +1,244 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2014 Franklin Wei, Benjamin Brown | ||
11 | * Copyright (C) 2004 Gregory Montoir | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ***************************************************************************/ | ||
22 | |||
23 | #include "plugin.h" | ||
24 | #include "engine.h" | ||
25 | #include "sys.h" | ||
26 | #include "util.h" | ||
27 | |||
28 | /* we don't want these on the stack, they're big and could cause a stack overflow */ | ||
29 | static struct Engine e; | ||
30 | static struct System sys; | ||
31 | |||
32 | enum plugin_status plugin_start(const void* parameter) | ||
33 | { | ||
34 | (void) parameter; | ||
35 | |||
36 | /* no trailing slashes */ | ||
37 | const char *dataPath = "/.rockbox/xworld"; | ||
38 | const char *savePath = "/.rockbox/xworld"; | ||
39 | g_debugMask = 0; | ||
40 | |||
41 | engine_create(&e, &sys, dataPath, savePath); | ||
42 | engine_init(&e); | ||
43 | sys_menu(&sys); | ||
44 | |||
45 | engine_run(&e); | ||
46 | |||
47 | engine_finish(&e); | ||
48 | return PLUGIN_OK; | ||
49 | } | ||
50 | |||
51 | |||
52 | |||
53 | /* | ||
54 | Game was originally made with 16. SIXTEEN colors. Running on 320x200 (64,000 pixels.) | ||
55 | |||
56 | Great fan site here: https://sites.google.com/site/interlinkknight/anotherworld | ||
57 | Contains the wheelcode :P ! | ||
58 | |||
59 | A lot of details can be found regarding the game and engine architecture at: | ||
60 | http://www.anotherworld.fr/anotherworld_uk/another_world.htm | ||
61 | |||
62 | The chronology of the game implementation can retraced via the ordering of the opcodes: | ||
63 | The sound and music opcode are at the end: Music and sound was done at the end. | ||
64 | |||
65 | Call tree: | ||
66 | ========= | ||
67 | |||
68 | SDLSystem systemImplementaion ; | ||
69 | System *sys = & systemImplementaion ; | ||
70 | |||
71 | main | ||
72 | { | ||
73 | |||
74 | Engine *e = new Engine(); | ||
75 | e->run() | ||
76 | { | ||
77 | sys->init("Out Of This World"); | ||
78 | setup(); | ||
79 | vm.restartAt(0x3E80); // demo starts at 0x3E81 | ||
80 | |||
81 | while (!_stub->_pi.quit) | ||
82 | { | ||
83 | vm.setupScripts(); | ||
84 | vm.inp_updatePlayer(); | ||
85 | processInput(); | ||
86 | vm.runScripts(); | ||
87 | } | ||
88 | |||
89 | finish(); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | |||
94 | Virtual Machine: | ||
95 | ================ | ||
96 | |||
97 | Seems the threading model is collaborative multi-tasking (as opposed to preemptive multitasking): | ||
98 | A thread (called a Channel on Eric Chahi's website) will release the hand to the next one via the | ||
99 | break opcode. | ||
100 | |||
101 | It seems that even when a setvec is requested by a thread, we cannot set the instruction pointer | ||
102 | yet. The thread is allowed to keep on executing its code for the remaining of the vm frame. | ||
103 | |||
104 | A virtual machine frame has a variable duration. The unit of time is 20ms and the frame can be set | ||
105 | to live for 1 (20ms ; 50Hz) up to 5 (100ms ; 10Hz). | ||
106 | |||
107 | |||
108 | There are 30 something opcodes. The graphic opcode are more complex, not only the declare the operation to perform | ||
109 | they also define where to find the vertices (segVideo1 or segVideo2). | ||
110 | |||
111 | No stack available but a thread can save its pc (Program Counter) once: One method call and return is possible. | ||
112 | |||
113 | |||
114 | Video : | ||
115 | ======= | ||
116 | Double buffer architecture. AW opcodes even has a special instruction for blitting from one | ||
117 | frame buffer to an other. | ||
118 | |||
119 | Double buffering is implemented in software | ||
120 | |||
121 | According to Eric Chahi's webpage there are 4 framebuffer. Since on full screenbuffer is 320x200/2 = 32kB | ||
122 | that would mean the total size consumed is 128KB ? | ||
123 | |||
124 | Sound : | ||
125 | ======= | ||
126 | Mixing is done on software. | ||
127 | |||
128 | Since the virtual machine and SDL are running simultaneously in two different threads: | ||
129 | Any read or write to an elements of the sound channels MUST be synchronized with a | ||
130 | mutex. | ||
131 | |||
132 | FastMode : | ||
133 | ========== | ||
134 | |||
135 | The game engine features a "fast-mode"...what it to be able to respond to the now defunct | ||
136 | TURBO button commonly found on 386/486 era PC ?! | ||
137 | |||
138 | |||
139 | Endianess: | ||
140 | ========== | ||
141 | |||
142 | Atari and Amiga used bigEndian CPUs. Data are hence stored within BANK in big endian format. | ||
143 | On an Intel or ARM CPU data will have to be transformed when read. | ||
144 | |||
145 | |||
146 | |||
147 | The original codebase contained a looooot of cryptic hexa values. | ||
148 | 0x100 (for 256 variables) | ||
149 | 0x400 (for one kilobyte) | ||
150 | 0x40 (for num threads) | ||
151 | 0x3F (num thread mask) | ||
152 | I cleaned that up. | ||
153 | |||
154 | Questions & Answers : | ||
155 | ===================== | ||
156 | |||
157 | Q: How does the interpreter deals with the CPU speed ?! A pentium is a tad faster than a Motorola 68000 | ||
158 | after all. | ||
159 | A: See vm frame time: The vm frame duration is variable. The vm actually write for how long a video frame | ||
160 | should be displayed in variable 0xFF. The value is the number of 20ms slice | ||
161 | |||
162 | Q: Why is a palette 2048 bytes if there are only 16 colors ? I would have expected 48 bytes... | ||
163 | A: ??? | ||
164 | |||
165 | Q: Why does Resource::load() search for ressource to load from higher to lower....since it will load stuff | ||
166 | until no more ressources are marked as "Need to be loaded". | ||
167 | A: ??? | ||
168 | |||
169 | Original DOS version : | ||
170 | ====================== | ||
171 | |||
172 | Banks: 1,236,519 B | ||
173 | exe : 20,293 B | ||
174 | |||
175 | |||
176 | Total bank size: 1236519 (100%) | ||
177 | --------------------------------- | ||
178 | Total RT_SOUND size: 585052 ( 47%) | ||
179 | Total RT_MUSIC size: 3540 ( 0%) | ||
180 | Total RT_POLY_ANIM size: 106676 ( 9%) | ||
181 | Total RT_PALETTE size: 11032 ( 1%) | ||
182 | Total RT_BYTECODE size: 135948 ( 11%) | ||
183 | Total RT_POLY_CINEMATIC size: 291008 ( 24%) | ||
184 | |||
185 | As usual sounds are the most consuming assets (Quake1,Quake2 etc.....) | ||
186 | |||
187 | |||
188 | memlist.bin features 146 entries : | ||
189 | ================================== | ||
190 | |||
191 | Most important part in an entry are: | ||
192 | |||
193 | bankId : - Give the file were the resource is. | ||
194 | offset : - How much to skip in the file before hiting the resource. | ||
195 | size,packetSize : - How much to read, should we unpack what we read. | ||
196 | |||
197 | |||
198 | |||
199 | Polygons drawing : | ||
200 | ================= | ||
201 | |||
202 | Polygons can be given as: | ||
203 | - a pure screenspace sequence of points: I call those screenspace polygons. | ||
204 | - a list of delta to add or substract to the first vertex. I call those: objectspace polygons. | ||
205 | |||
206 | Video : | ||
207 | ======= | ||
208 | |||
209 | Q: Why 4 framebuffer ? | ||
210 | A: It seems the background is generated once (like in the introduction) and stored in a framebuffer. | ||
211 | Every frame the saved background is copied and new elements are drawn on top. | ||
212 | |||
213 | |||
214 | Trivia : | ||
215 | ======== | ||
216 | |||
217 | If you are used to RGBA 32bits per pixel framebuffer you are in for a shock: | ||
218 | Another world is 16 colors palette based, making it 4bits per pixel !! | ||
219 | |||
220 | Video generation : | ||
221 | ================== | ||
222 | |||
223 | Thank god the engine sets the palette before starting to drawing instead of after bliting. | ||
224 | I would have been unable to generate screenshots otherwise. | ||
225 | |||
226 | Memory managment : | ||
227 | ================= | ||
228 | |||
229 | There is 0 malloc during the game. All resources are loaded in one big buffer (Resource::load). | ||
230 | The entire buffer is "freed" at the end of a game part. | ||
231 | |||
232 | |||
233 | The renderer is actually capable of Blending a new poly in the framebuffer (Video::drawLineT) | ||
234 | |||
235 | |||
236 | I am almost sure that: | ||
237 | _curPagePtr1 is the backbuffer | ||
238 | _curPagePtr2 is the frontbuffer | ||
239 | _curPagePtr3 is the background builder. | ||
240 | |||
241 | |||
242 | * Why does memlist.bin uses a special state field 0xFF in order to mark the end of resources ??! | ||
243 | It would have been so much easier to write the number of resources at the beginning of the code. | ||
244 | */ | ||
diff --git a/apps/plugins/xworld/xworld.make b/apps/plugins/xworld/xworld.make new file mode 100644 index 0000000000..7d6966842b --- /dev/null +++ b/apps/plugins/xworld/xworld.make | |||
@@ -0,0 +1,27 @@ | |||
1 | # __________ __ ___. | ||
2 | # Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
3 | # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
4 | # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
5 | # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
6 | # \/ \/ \/ \/ \/ | ||
7 | # $Id$ | ||
8 | # | ||
9 | |||
10 | XWORLDSRCDIR := $(APPSDIR)/plugins/xworld | ||
11 | XWORLDBUILDDIR := $(BUILDDIR)/apps/plugins/xworld | ||
12 | |||
13 | ROCKS += $(XWORLDBUILDDIR)/xworld.rock | ||
14 | |||
15 | XWORLD_SRC := $(call preprocess, $(XWORLDSRCDIR)/SOURCES) | ||
16 | XWORLD_OBJ := $(call c2obj, $(XWORLD_SRC)) | ||
17 | |||
18 | # add source files to OTHER_SRC to get automatic dependencies | ||
19 | OTHER_SRC += $(XWORLD_SRC) | ||
20 | |||
21 | XWORLDFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O2 | ||
22 | |||
23 | $(XWORLDBUILDDIR)/xworld.rock: $(XWORLD_OBJ) | ||
24 | |||
25 | $(XWORLDBUILDDIR)/%.o: $(XWORLDSRCDIR)/%.c $(XWORLDSRCDIR)/xworld.make | ||
26 | $(SILENT)mkdir -p $(dir $@) | ||
27 | $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(XWORLDFLAGS) -c $< -o $@ | ||
diff --git a/docs/CREDITS b/docs/CREDITS index 96a5b784bd..0f548e2810 100644 --- a/docs/CREDITS +++ b/docs/CREDITS | |||
@@ -672,3 +672,5 @@ The Pure Data team (Miller Puckette and others) | |||
672 | The MikMod team | 672 | The MikMod team |
673 | Michael McTernan (The ARM unwinder author) | 673 | Michael McTernan (The ARM unwinder author) |
674 | Albert Song | 674 | Albert Song |
675 | The New RAW team (Piotr Padkowski and others) | ||
676 | The Fabother World team (Fabien Sanglard and others) | ||
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index a49cfacbef..4143fd6b63 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex | |||
@@ -94,6 +94,8 @@ text files% | |||
94 | 94 | ||
95 | \opt{lcd_bitmap}{\input{plugins/xobox.tex}} | 95 | \opt{lcd_bitmap}{\input{plugins/xobox.tex}} |
96 | 96 | ||
97 | \opt{lcd_bitmap}{\input{plugins/xworld.tex}} | ||
98 | |||
97 | \section{Demos} | 99 | \section{Demos} |
98 | 100 | ||
99 | \opt{lcd_bitmap}{\input{plugins/bounce.tex}} | 101 | \opt{lcd_bitmap}{\input{plugins/bounce.tex}} |
diff --git a/manual/plugins/xworld.tex b/manual/plugins/xworld.tex new file mode 100644 index 0000000000..32e3ecbf78 --- /dev/null +++ b/manual/plugins/xworld.tex | |||
@@ -0,0 +1,81 @@ | |||
1 | \subsection{XWorld} | ||
2 | |||
3 | In this cinematic, award winning platform game by Éric Chahi, you must evade capture | ||
4 | and do your best to escape an alien planet. After an experiment goes awry the hero | ||
5 | must team up with an unlikely ally, when they both become fugitives on another world. | ||
6 | XWorld requires the data files, bank* and memlist.bin, from the original "Another World" | ||
7 | PC game to be copied into the .rockbox/xworld/ directory before the game can be played. | ||
8 | |||
9 | \begin{btnmap} | ||
10 | % | ||
11 | \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% | ||
12 | ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD% | ||
13 | ,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD% | ||
14 | ,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD} | ||
15 | {\ButtonUp} | ||
16 | \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonMenu} | ||
17 | \opt{IRIVER_H10_PAD}{\ButtonScrollUp} | ||
18 | \opt{HAVE_TOUCHSCREEN}{\TouchTopMiddle} | ||
19 | \opt{PBELL_VIBE500_PAD}{\ButtonOk} | ||
20 | \opt{HAVEREMOTEKEYMAP}{& } | ||
21 | & Up and Jump \\ | ||
22 | % | ||
23 | \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% | ||
24 | ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD% | ||
25 | ,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD% | ||
26 | ,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD} | ||
27 | {\ButtonDown} | ||
28 | \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonPlay} | ||
29 | \opt{IRIVER_H10_PAD}{\ButtonScrollDown} | ||
30 | \opt{HAVE_TOUCHSCREEN}{\TouchBottomMiddle} | ||
31 | \opt{PBELL_VIBE500_PAD}{\ButtonCancel} | ||
32 | \opt{HAVEREMOTEKEYMAP}{& } | ||
33 | & Down and Crouch\\ | ||
34 | % | ||
35 | \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD% | ||
36 | ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD% | ||
37 | ,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD% | ||
38 | ,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD% | ||
39 | ,IRIVER_H10_PAD} | ||
40 | {\ButtonLeft / \ButtonRight} | ||
41 | \opt{HAVE_TOUCHSCREEN}{\TouchMidLeft / \TouchMidRight} | ||
42 | \opt{PBELL_VIBE500_PAD}{\ButtonMenu / \ButtonPlay} | ||
43 | \opt{HAVEREMOTEKEYMAP}{& } | ||
44 | & Move Left and Right\\ | ||
45 | % | ||
46 | \opt{SANSA_FUZE_PAD}{\ButtonHome} | ||
47 | \opt{SAMSUNG_YH920_PAD}{\ButtonFFWD} | ||
48 | \opt{IRIVER_H300_PAD,SANSA_E200_PAD,SAMSUNG_YH820_PAD,IAUDIO_X5M5_PAD}{\ButtonRec} | ||
49 | \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD,CREATIVE_ZEN_PAD,SANSA_CLIP_PAD}{\ButtonSelect} | ||
50 | \opt{SONY_NWZ_PAD,CREATIVEZVM_PAD}{\ButtonPlay} | ||
51 | \opt{ONDAVX777_PAD,MROBE500_PAD,PBELL_VIBE500_PAD}{\ButtonPower} | ||
52 | \opt{SAMSUNG_YPR0_PAD}{\ButtonUser} | ||
53 | \opt{IRIVER_H10_PAD}{\ButtonRew} | ||
54 | \opt{HM801_PAD}{\ButtonPrev} | ||
55 | \opt{SONY_NWZ_PAD,CREATIVEZVM_PAD}{\ButtonPlay} | ||
56 | \opt{MROBE500_PAD}{\ButtonPower} | ||
57 | \opt{DX50_PAD,ONDAVX747_PAD,PHILIPS_HDD1630_PAD,PHILIPS_HDD6330_PAD,PHILIPS_SA9200_PAD% | ||
58 | ,CREATIVE_ZENXFI2_PAD,CREATIVE_ZENXFI3_PAD,SANSA_CONNECT_PAD,SANSA_C200_PAD% | ||
59 | ,SANSA_FUZEPLUS_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp} | ||
60 | \opt{HAVE_TOUCHSCREEN}{\ButtonBottomLeft} | ||
61 | \opt{HAVEREMOTEKEYMAP}{& } | ||
62 | & Action and Fire\\ | ||
63 | % | ||
64 | \opt{DX50_PAD,ONDAVX747_PAD,PHILIPS_HDD1630_PAD,PHILIPS_HDD6330_PAD,PHILIPS_SA9200_PAD% | ||
65 | ,CREATIVE_ZENXFI2_PAD,CREATIVE_ZENXFI3_PAD,SANSA_CONNECT_PAD,SANSA_C200_PAD% | ||
66 | ,SANSA_FUZEPLUS_PAD}{\ButtonVolDown} | ||
67 | \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonMenu} | ||
68 | \opt{SANSA_FUZE_PAD}{\ButtonSelect} | ||
69 | \opt{SAMSUNG_YH920_PAD}{\ButtonRew} | ||
70 | \opt{SAMSUNG_YH820_PAD,IAUDIO_X5M5_PAD}{\ButtonPlay} | ||
71 | \opt{SANSA_E200_PAD,SANSA_CLIP_PAD}{\ButtonPower} | ||
72 | \opt{CREATIVE_ZEN_PAD,SONY_NWZ_PAD}{\ButtonBack} | ||
73 | \opt{CREATIVEZVM_PAD,SAMSUNG_YPR0_PAD}{\ButtonMenu} | ||
74 | \opt{IRIVER_H300_PAD}{\ButtonMode} | ||
75 | \opt{HM801_PAD}{\ButtonNext} | ||
76 | \opt{PBELL_VIBE500_PAD}{\ButtonRec} | ||
77 | \opt{IRIVER_H10_PAD}{\ButtonPlay} | ||
78 | \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonMenu / \ButtonSelect} | ||
79 | \opt{HAVEREMOTEKEYMAP}{& } | ||
80 | & Menu\\ | ||
81 | \end{btnmap} | ||