1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "ultima/nuvie/core/nuvie_defs.h"
24 #include "ultima/nuvie/misc/u6_misc.h"
25 #include "ultima/nuvie/files/nuvie_io_file.h"
26 #include "ultima/nuvie/files/u6_lzw.h"
27 #include "ultima/nuvie/files/u6_lib_n.h"
28
29 namespace Ultima {
30 namespace Nuvie {
31
U6Lib_n()32 U6Lib_n::U6Lib_n() : num_offsets(0), items(NULL), data(NULL),
33 del_data(false), filesize(0), game_type(NUVIE_GAME_U6), lib_size(0) {
34 }
35
36
~U6Lib_n(void)37 U6Lib_n::~U6Lib_n(void) {
38 close();
39 }
40
41 // load u6lib from `filename'
open(Std::string & filename,uint8 size,uint8 type)42 bool U6Lib_n::open(Std::string &filename, uint8 size, uint8 type) {
43 NuvieIOFileRead *file;
44
45 file = new NuvieIOFileRead();
46
47 if (file->open(filename) == false) {
48 delete file;
49 return false;
50 }
51
52 del_data = true;
53
54 return open((NuvieIO *)file, size, type);
55 }
56
57
58 // load u6lib from opened stream
open(NuvieIO * new_data,uint8 size,uint8 type)59 bool U6Lib_n::open(NuvieIO *new_data, uint8 size, uint8 type) {
60 game_type = type;
61 data = new_data;
62
63 lib_size = size;
64 this->parse_lib();
65
66 return true;
67 }
68
close()69 void U6Lib_n::close() {
70 if (items) {
71 for (uint32 i = 0; i < num_offsets; i++)
72 delete items[i].name;
73 free(items);
74 }
75 items = NULL;
76
77 if (data != NULL)
78 data->close();
79
80 if (del_data)
81 delete data;
82
83 data = NULL;
84 del_data = false;
85
86 num_offsets = 0;
87
88 return;
89 }
90
91 /* Open a ^new^ file for writing, with lib_size and type.
92 */
create(Std::string & filename,uint8 size,uint8 type)93 bool U6Lib_n::create(Std::string &filename, uint8 size, uint8 type) {
94 NuvieIOFileWrite *file = new NuvieIOFileWrite();
95 if (!file->open(filename)) {
96 DEBUG(0, LEVEL_ERROR, "U6Lib: Error creating %s\n", filename.c_str());
97 delete file;
98 return (false);
99 }
100 game_type = type;
101 lib_size = size;
102 data = (NuvieIO *)file;
103 return (true);
104 }
105
106
get_num_items(void)107 uint32 U6Lib_n::get_num_items(void) {
108 return num_offsets;
109 }
110
111
112 /* Returns the location of `item_number' in the library file.
113 */
get_item_offset(uint32 item_number)114 uint32 U6Lib_n::get_item_offset(uint32 item_number) {
115 if (item_number >= num_offsets)
116 return (0);
117 return (items[item_number].offset);
118 }
119
get_item_size(uint32 item_number)120 uint32 U6Lib_n::get_item_size(uint32 item_number) {
121 if (item_number >= num_offsets)
122 return (0);
123
124 return (items[item_number].uncomp_size);
125 }
126
127
128 // read and return item data
get_item(uint32 item_number,unsigned char * ret_buf)129 unsigned char *U6Lib_n::get_item(uint32 item_number, unsigned char *ret_buf) {
130 U6LibItem *item;
131 unsigned char *buf, *lzw_buf;
132
133 if (item_number >= num_offsets)
134 return NULL;
135
136 item = &items[item_number];
137
138 if (item->size == 0 || item->offset == 0)
139 return NULL;
140
141 if (ret_buf == NULL)
142 buf = (unsigned char *)malloc(item->uncomp_size);
143 else
144 buf = ret_buf;
145
146 data->seek(item->offset);
147
148 if (is_compressed(item_number)) {
149 U6Lzw lzw;
150 lzw_buf = (unsigned char *)malloc(item->size);
151 data->readToBuf(lzw_buf, item->size);
152 lzw.decompress_buffer(lzw_buf, item->size, buf, item->uncomp_size);
153 } else {
154 data->readToBuf(buf, item->size);
155 }
156 return buf;
157 }
158
is_compressed(uint32 item_number)159 bool U6Lib_n::is_compressed(uint32 item_number) {
160 uint32 i;
161
162 switch (items[item_number].flag) {
163 case 0x1 :
164 case 0x20 :
165 return true;
166 case 0xff :
167 for (i = item_number; i < num_offsets; i++) {
168 if (items[i].flag != 0xff)
169 break;
170 }
171 if (i < num_offsets)
172 return is_compressed(i);
173 break;
174 }
175
176 return false;
177 }
178
parse_lib()179 void U6Lib_n::parse_lib() {
180 uint32 i;
181 bool skip4 = false;
182
183 if (lib_size != 2 && lib_size != 4)
184 return;
185
186 data->seekStart();
187
188 if (game_type != NUVIE_GAME_U6) { //U6 doesn't have a 4 byte filesize header.
189 skip4 = true;
190 filesize = data->read4();
191 } else
192 filesize = data->get_size();
193
194 num_offsets = calculate_num_offsets(skip4);
195
196 items = (U6LibItem *)malloc(sizeof(U6LibItem) * (num_offsets + 1));
197 memset(items, 0, sizeof(U6LibItem) * (num_offsets + 1));
198
199 data->seekStart();
200 if (skip4)
201 data->seek(0x4);
202 for (i = 0; i < num_offsets && !data->is_end(); i++) {
203 if (lib_size == 2)
204 items[i].offset = data->read2();
205 else {
206 items[i].offset = data->read4();
207 // U6 converse files dont have flag?
208 items[i].flag = (items[i].offset & 0xff000000) >> 24; //extract flag byte
209 items[i].offset &= 0xffffff;
210 }
211 }
212
213 items[num_offsets].offset = filesize; //this is used to calculate the size of the last item in the lib.
214
215 calculate_item_sizes();
216
217 return;
218 }
219
220
221 // for reading, calculate item sizes based on offsets
calculate_item_sizes()222 void U6Lib_n::calculate_item_sizes() {
223 uint32 i, next_offset = 0;
224
225 for (i = 0; i < num_offsets; i++) {
226 items[i].size = 0;
227 // get next non-zero offset, including the filesize at items[num_offsets]
228 for (uint32 o = (i + 1); o <= num_offsets; o++)
229 if (items[o].offset) {
230 next_offset = items[o].offset;
231 break;
232 }
233
234 if (items[i].offset && (next_offset > items[i].offset))
235 items[i].size = next_offset - items[i].offset;
236
237 items[i].uncomp_size = calculate_item_uncomp_size(&items[i]);
238 }
239
240 return;
241 }
242
243 // for reading, calculate uncompressed item size based on item flag
calculate_item_uncomp_size(U6LibItem * item)244 uint32 U6Lib_n::calculate_item_uncomp_size(U6LibItem *item) {
245 uint32 uncomp_size = 0;
246
247 switch (item->flag) {
248 case 0x01 : //compressed
249 case 0x20 : //MD fonts.lzc, MDD_MUS.LZC use this tag among others
250 data->seek(item->offset);
251 uncomp_size = data->read4();
252 break;
253
254 //FIX check this. uncompressed 4 byte item size header
255 case 0xc1 :
256 uncomp_size = item->size; // - 4;
257 break;
258
259 // uncompressed
260 case 0x0 :
261 case 0x2 :
262 case 0xe0 :
263 default :
264 uncomp_size = item->size;
265 break;
266 }
267
268 return uncomp_size;
269 }
270
271 // we need to handle NULL offsets at the start of the offset table in the converse.a file
calculate_num_offsets(bool skip4)272 uint32 U6Lib_n::calculate_num_offsets(bool skip4) { //skip4 bytes of header.
273 uint32 i;
274 uint32 offset = 0;
275
276 if (skip4)
277 data->seek(0x4);
278
279
280 // We assume the first data in the file is directly behind the offset table,
281 // so we continue scanning until we hit a data block.
282 uint32 max_count = 0xffffffff;
283 for (i = 0; !data->is_end(); i++) {
284 if (i == max_count)
285 return i;
286
287 if (lib_size == 2)
288 offset = data->read2();
289 else {
290 offset = data->read4();
291 offset &= 0xffffff; // clear flag byte.
292 }
293 if (offset != 0) {
294 if (skip4)
295 offset -= 4;
296
297 if (offset / lib_size < max_count)
298 max_count = offset / lib_size;
299 }
300 }
301
302 return 0;
303 }
304
305
306 /* For writing multiple files to a lib, read in source filenames and offsets
307 * from an opened index file. Offsets may be ignored when writing.
308 */
load_index(Common::ReadStream * index_f)309 void U6Lib_n::load_index(Common::ReadStream *index_f) {
310 char input[256] = "", // input line
311 offset_str[9] = "", // listed offset
312 name[256] = ""; // source file name
313 int in_len = 0, oc = 0; // length of input line, character in copy string
314 int c = 0, entry_count = 0; // character in input line, number of entries
315
316 if (!index_f)
317 return;
318 while (strgets(input, 256, index_f)) {
319 in_len = strlen(input);
320 // skip spaces, read offset, break on #
321 for (c = 0; c < in_len && Common::isSpace(input[c]) && input[c] != '#'; c++);
322 for (oc = 0; c < in_len && !Common::isSpace(input[c]) && input[c] != '#'; c++)
323 offset_str[oc++] = input[c];
324 offset_str[oc] = '\0';
325 // skip spaces, read name, break on # or \n or \r
326 for (; c < in_len && Common::isSpace(input[c]) && input[c] != '#'; c++);
327 for (oc = 0; c < in_len && input[c] != '\n' && input[c] != '\r' && input[c] != '#'; c++)
328 name[oc++] = input[c];
329 name[oc] = '\0';
330 if (strlen(offset_str)) { // if line is not empty (!= zero entry)
331 uint32 offset32 = strtol(offset_str, NULL, 16);
332 add_item(offset32, name);
333 ++entry_count;
334 }
335 offset_str[0] = '\0';
336 oc = 0;
337 }
338 }
339
340
341 /* Append an offset and a name to the library. The other fields are initialized.
342 */
add_item(uint32 offset32,const char * name)343 void U6Lib_n::add_item(uint32 offset32, const char *name) {
344 if (!num_offsets)
345 items = (U6LibItem *)malloc(sizeof(U6LibItem));
346 else
347 items = (U6LibItem *)nuvie_realloc(items, sizeof(U6LibItem) * (num_offsets + 1));
348 U6LibItem *item = &items[num_offsets];
349 item->offset = offset32;
350 item->name = new string(name);
351 item->size = 0;
352 item->uncomp_size = 0;
353 item->flag = 0; // uncompressed
354 item->data = NULL;
355 ++num_offsets;
356 }
357
358
359 /* Returns the name of (filename associated with) `item_number'.
360 */
get_item_name(uint32 item_number)361 const char *U6Lib_n::get_item_name(uint32 item_number) {
362 if (item_number >= num_offsets)
363 return (NULL);
364 return (items[item_number].name ? items[item_number].name->c_str() : NULL);
365 }
366
367
368 /* Set data for an item, in preparation of writing or to cache the library.
369 * Size & uncompressed size is set to source length.
370 */
set_item_data(uint32 item_number,unsigned char * src,uint32 src_len)371 void U6Lib_n::set_item_data(uint32 item_number, unsigned char *src, uint32 src_len) {
372 unsigned char *dcopy = 0;
373 if (item_number >= num_offsets)
374 return;
375 // FIXME: need a way to set an item as compressed or uncompressed so we know
376 // which size to set
377 items[item_number].size = src_len;
378 items[item_number].uncomp_size = src_len;
379 if (src_len) {
380 dcopy = (unsigned char *)malloc(src_len);
381 memcpy(dcopy, src, src_len);
382 items[item_number].data = dcopy;
383 } else
384 items[item_number].data = 0;
385 }
386
387
388 /* For writing, (re)calculate item offsets from item sizes.
389 */
calc_item_offsets()390 void U6Lib_n::calc_item_offsets() {
391 if (num_offsets == 0)
392 return;
393 if (items[0].size) // first offset is past library index
394 items[0].offset = (num_offsets * lib_size);
395 else
396 items[0].offset = 0; // 0 = no data, no affect on other items
397 // DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: sizes[0] == %d\n", sizes[0]);
398 // DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: offsets[0] == %d\n", offsets[0]);
399 for (uint32 i = 1; i < num_offsets; i++) {
400 if (items[i].size) {
401 // find previous item with non-zero offset
402 uint32 prev_i = 0;
403 for (uint32 i_sub = 1; i_sub <= i; i_sub++) {
404 prev_i = i - i_sub;
405 if (items[prev_i].offset != 0)
406 break;
407 }
408 items[i].offset = (items[prev_i].offset + items[prev_i].size);
409 if (items[i].offset == 0) // last item had no data; skip index here
410 items[i].offset = (num_offsets * lib_size);
411 } else
412 items[i].offset = 0; // 0 = no data, no affect on other items
413 // DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: sizes[%d] == %d\n", i, sizes[i]);
414 // DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: offsets[%d] == %d\n", i, offsets[i]);
415 }
416 }
417
write_header()418 void U6Lib_n::write_header() {
419 data->seekStart();
420 if (game_type == NUVIE_GAME_U6)
421 return;
422
423 uint32 totalSize = 4 + num_offsets * lib_size;
424
425 for (uint i = 0; i < num_offsets; i++) {
426 totalSize += items[i].size;
427 }
428
429 data->write4(totalSize);
430 }
431
432 /* Write the library index. (the 2 or 4 byte offsets before the data)
433 */
write_index()434 void U6Lib_n::write_index() {
435 data->seekStart();
436 if (game_type != NUVIE_GAME_U6) {
437 data->seek(4);
438 }
439
440 for (uint32 o = 0; o < num_offsets; o++) {
441 uint32 offset = items[o].offset;
442 if (game_type != NUVIE_GAME_U6 && offset != 0) {
443 offset += 4;
444 }
445 if (lib_size == 2)
446 data->write2((uint16)offset);
447 else if (lib_size == 4)
448 data->write4(offset);
449 }
450 }
451
452
453 /* Write all item data to the library file at their respective offsets.
454 */
write_items()455 void U6Lib_n::write_items() {
456 for (uint32 i = 0; i < num_offsets; i++)
457 write_item(i);
458 }
459
460
461 /* Write item data to the library file at the indicated offset, unless the
462 * offset is 0 (then the data is considered empty).
463 */
write_item(uint32 item_number)464 void U6Lib_n::write_item(uint32 item_number) {
465 if (item_number >= num_offsets
466 || items[item_number].offset == 0 || items[item_number].size == 0)
467 return;
468 if (game_type == NUVIE_GAME_U6)
469 data->seek(items[item_number].offset);
470 else
471 data->seek(items[item_number].offset + 4);
472 ((NuvieIOFileWrite *)data)->writeBuf(items[item_number].data, items[item_number].size);
473 }
474
475 } // End of namespace Nuvie
476 } // End of namespace Ultima
477