summaryrefslogtreecommitdiff
path: root/apps/plugins/xworld/resource.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/xworld/resource.c')
-rw-r--r--apps/plugins/xworld/resource.c443
1 files changed, 443 insertions, 0 deletions
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
34void 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
43void 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
55static 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
75int resourceSizeStats[7][2];
76#define STATS_TOTAL_SIZE 6
77int 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*/
83void 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*/
183void 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
238void 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
250void 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. */
267void 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). */
289void 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
352void 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
379void 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
385void 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
440const char* res_getDataDir(struct Resource* res)
441{
442 return res->_dataDir;
443}