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