diff options
Diffstat (limited to 'apps/plugins/xworld/resource.c')
-rw-r--r-- | apps/plugins/xworld/resource.c | 443 |
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 | |||
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 | } | ||