Files
2026-02-02 04:50:13 +01:00

476 lines
12 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/files/nuvie_io_file.h"
#include "ultima/nuvie/files/u6_lzw.h"
#include "ultima/nuvie/files/u6_lib_n.h"
namespace Ultima {
namespace Nuvie {
U6Lib_n::U6Lib_n() : num_offsets(0), items(nullptr), data(nullptr),
del_data(false), filesize(0), game_type(NUVIE_GAME_U6), lib_size(0) {
}
U6Lib_n::~U6Lib_n(void) {
close();
}
// load u6lib from `filename'
bool U6Lib_n::open(const Common::Path &filename, uint8 size, uint8 type) {
NuvieIOFileRead *file;
file = new NuvieIOFileRead();
if (file->open(filename) == false) {
delete file;
return false;
}
del_data = true;
return open((NuvieIO *)file, size, type);
}
// load u6lib from opened stream
bool U6Lib_n::open(NuvieIO *new_data, uint8 size, uint8 type) {
game_type = type;
data = new_data;
lib_size = size;
this->parse_lib();
return true;
}
void U6Lib_n::close() {
if (items) {
for (uint32 i = 0; i < num_offsets; i++)
delete items[i].name;
free(items);
}
items = nullptr;
if (del_data) {
if (data != nullptr)
data->close();
delete data;
}
data = nullptr;
del_data = false;
num_offsets = 0;
return;
}
/* Open a ^new^ file for writing, with lib_size and type.
*/
bool U6Lib_n::create(const Common::Path &filename, uint8 size, uint8 type) {
NuvieIOFileWrite *file = new NuvieIOFileWrite();
if (!file->open(filename)) {
DEBUG(0, LEVEL_ERROR, "U6Lib: Error creating %s\n", filename.toString().c_str());
delete file;
return false;
}
game_type = type;
lib_size = size;
data = (NuvieIO *)file;
return true;
}
uint32 U6Lib_n::get_num_items(void) {
return num_offsets;
}
/* Returns the location of `item_number' in the library file.
*/
uint32 U6Lib_n::get_item_offset(uint32 item_number) {
if (item_number >= num_offsets)
return 0;
return (items[item_number].offset);
}
uint32 U6Lib_n::get_item_size(uint32 item_number) {
if (item_number >= num_offsets)
return 0;
return (items[item_number].uncomp_size);
}
// read and return item data
unsigned char *U6Lib_n::get_item(uint32 item_number, unsigned char *ret_buf) {
U6LibItem *item;
unsigned char *buf, *lzw_buf;
if (item_number >= num_offsets)
return nullptr;
item = &items[item_number];
if (item->size == 0 || item->offset == 0)
return nullptr;
if (ret_buf == nullptr)
buf = (unsigned char *)malloc(item->uncomp_size);
else
buf = ret_buf;
data->seek(item->offset);
if (is_compressed(item_number)) {
U6Lzw lzw;
lzw_buf = (unsigned char *)malloc(item->size);
data->readToBuf(lzw_buf, item->size);
lzw.decompress_buffer(lzw_buf, item->size, buf, item->uncomp_size);
} else {
data->readToBuf(buf, item->size);
}
return buf;
}
bool U6Lib_n::is_compressed(uint32 item_number) {
uint32 i;
switch (items[item_number].flag) {
case 0x1 :
case 0x20 :
return true;
case 0xff :
for (i = item_number; i < num_offsets; i++) {
if (items[i].flag != 0xff)
break;
}
if (i < num_offsets)
return is_compressed(i);
break;
}
return false;
}
void U6Lib_n::parse_lib() {
uint32 i;
bool skip4 = false;
if (lib_size != 2 && lib_size != 4)
return;
data->seekStart();
if (game_type != NUVIE_GAME_U6) { //U6 doesn't have a 4 byte filesize header.
skip4 = true;
filesize = data->read4();
} else
filesize = data->get_size();
num_offsets = calculate_num_offsets(skip4);
items = (U6LibItem *)malloc(sizeof(U6LibItem) * (num_offsets + 1));
memset(items, 0, sizeof(U6LibItem) * (num_offsets + 1));
data->seekStart();
if (skip4)
data->seek(0x4);
for (i = 0; i < num_offsets && !data->is_end(); i++) {
if (lib_size == 2)
items[i].offset = data->read2();
else {
items[i].offset = data->read4();
// U6 converse files dont have flag?
items[i].flag = (items[i].offset & 0xff000000) >> 24; //extract flag byte
items[i].offset &= 0xffffff;
}
}
items[num_offsets].offset = filesize; //this is used to calculate the size of the last item in the lib.
calculate_item_sizes();
return;
}
// for reading, calculate item sizes based on offsets
void U6Lib_n::calculate_item_sizes() {
uint32 i, next_offset = 0;
for (i = 0; i < num_offsets; i++) {
items[i].size = 0;
// get next non-zero offset, including the filesize at items[num_offsets]
for (uint32 o = (i + 1); o <= num_offsets; o++)
if (items[o].offset) {
next_offset = items[o].offset;
break;
}
if (items[i].offset && (next_offset > items[i].offset))
items[i].size = next_offset - items[i].offset;
items[i].uncomp_size = calculate_item_uncomp_size(&items[i]);
}
return;
}
// for reading, calculate uncompressed item size based on item flag
uint32 U6Lib_n::calculate_item_uncomp_size(U6LibItem *item) {
uint32 uncomp_size = 0;
switch (item->flag) {
case 0x01 : //compressed
case 0x20 : //MD fonts.lzc, MDD_MUS.LZC use this tag among others
data->seek(item->offset);
uncomp_size = data->read4();
break;
//FIX check this. uncompressed 4 byte item size header
case 0xc1 :
uncomp_size = item->size; // - 4;
break;
// uncompressed
case 0x0 :
case 0x2 :
case 0xe0 :
default :
uncomp_size = item->size;
break;
}
return uncomp_size;
}
// we need to handle nullptr offsets at the start of the offset table in the converse.a file
uint32 U6Lib_n::calculate_num_offsets(bool skip4) { //skip4 bytes of header.
uint32 i;
uint32 offset = 0;
if (skip4)
data->seek(0x4);
// We assume the first data in the file is directly behind the offset table,
// so we continue scanning until we hit a data block.
uint32 max_count = 0xffffffff;
for (i = 0; !data->is_end(); i++) {
if (i == max_count)
return i;
if (lib_size == 2)
offset = data->read2();
else {
offset = data->read4();
offset &= 0xffffff; // clear flag byte.
}
if (offset != 0) {
if (skip4)
offset -= 4;
if (offset / lib_size < max_count)
max_count = offset / lib_size;
}
}
return 0;
}
/* For writing multiple files to a lib, read in source filenames and offsets
* from an opened index file. Offsets may be ignored when writing.
*/
void U6Lib_n::load_index(Common::ReadStream *index_f) {
char input[256] = "", // input line
offset_str[9] = "", // listed offset
name[256] = ""; // source file name
int in_len = 0, oc = 0; // length of input line, character in copy string
int c = 0, entry_count = 0; // character in input line, number of entries
if (!index_f)
return;
while (strgets(input, 256, index_f)) {
in_len = strlen(input);
// skip spaces, read offset, break on #
for (c = 0; c < in_len && Common::isSpace(input[c]) && input[c] != '#'; c++);
for (oc = 0; c < in_len && !Common::isSpace(input[c]) && input[c] != '#'; c++)
offset_str[oc++] = input[c];
offset_str[oc] = '\0';
// skip spaces, read name, break on # or \n or \r
for (; c < in_len && Common::isSpace(input[c]) && input[c] != '#'; c++);
for (oc = 0; c < in_len && input[c] != '\n' && input[c] != '\r' && input[c] != '#'; c++)
name[oc++] = input[c];
name[oc] = '\0';
if (strlen(offset_str)) { // if line is not empty (!= zero entry)
uint32 offset32 = strtol(offset_str, nullptr, 16);
add_item(offset32, name);
++entry_count;
}
offset_str[0] = '\0';
oc = 0;
}
(void)entry_count; // Fix "unused varable" warning
}
/* Append an offset and a name to the library. The other fields are initialized.
*/
void U6Lib_n::add_item(uint32 offset32, const char *name) {
if (!num_offsets)
items = (U6LibItem *)malloc(sizeof(U6LibItem));
else
items = (U6LibItem *)nuvie_realloc(items, sizeof(U6LibItem) * (num_offsets + 1));
U6LibItem *item = &items[num_offsets];
item->offset = offset32;
item->name = new string(name);
item->size = 0;
item->uncomp_size = 0;
item->flag = 0; // uncompressed
item->data = nullptr;
++num_offsets;
}
/* Returns the name of (filename associated with) `item_number'.
*/
const char *U6Lib_n::get_item_name(uint32 item_number) {
if (item_number >= num_offsets)
return nullptr;
return (items[item_number].name ? items[item_number].name->c_str() : nullptr);
}
/* Set data for an item, in preparation of writing or to cache the library.
* Size & uncompressed size is set to source length.
*/
void U6Lib_n::set_item_data(uint32 item_number, unsigned char *src, uint32 src_len) {
if (item_number >= num_offsets)
return;
// FIXME: need a way to set an item as compressed or uncompressed so we know
// which size to set
items[item_number].size = src_len;
items[item_number].uncomp_size = src_len;
if (src_len) {
unsigned char *dcopy = (unsigned char *)malloc(src_len);
memcpy(dcopy, src, src_len);
items[item_number].data = dcopy;
} else
items[item_number].data = 0;
}
/* For writing, (re)calculate item offsets from item sizes.
*/
void U6Lib_n::calc_item_offsets() {
if (num_offsets == 0)
return;
if (items[0].size) // first offset is past library index
items[0].offset = (num_offsets * lib_size);
else
items[0].offset = 0; // 0 = no data, no affect on other items
// DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: sizes[0] == %d\n", sizes[0]);
// DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: offsets[0] == %d\n", offsets[0]);
for (uint32 i = 1; i < num_offsets; i++) {
if (items[i].size) {
// find previous item with non-zero offset
uint32 prev_i = 0;
for (uint32 i_sub = 1; i_sub <= i; i_sub++) {
prev_i = i - i_sub;
if (items[prev_i].offset != 0)
break;
}
items[i].offset = (items[prev_i].offset + items[prev_i].size);
if (items[i].offset == 0) // last item had no data; skip index here
items[i].offset = (num_offsets * lib_size);
} else
items[i].offset = 0; // 0 = no data, no affect on other items
// DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: sizes[%d] == %d\n", i, sizes[i]);
// DEBUG(0,LEVEL_DEBUGGING,"calc_item_offsets: offsets[%d] == %d\n", i, offsets[i]);
}
}
void U6Lib_n::write_header() {
data->seekStart();
if (game_type == NUVIE_GAME_U6)
return;
uint32 totalSize = 4 + num_offsets * lib_size;
for (uint i = 0; i < num_offsets; i++) {
totalSize += items[i].size;
}
data->write4(totalSize);
}
/* Write the library index. (the 2 or 4 byte offsets before the data)
*/
void U6Lib_n::write_index() {
data->seekStart();
if (game_type != NUVIE_GAME_U6) {
data->seek(4);
}
for (uint32 o = 0; o < num_offsets; o++) {
uint32 offset = items[o].offset;
if (game_type != NUVIE_GAME_U6 && offset != 0) {
offset += 4;
}
if (lib_size == 2)
data->write2((uint16)offset);
else if (lib_size == 4)
data->write4(offset);
}
}
/* Write all item data to the library file at their respective offsets.
*/
void U6Lib_n::write_items() {
for (uint32 i = 0; i < num_offsets; i++)
write_item(i);
}
/* Write item data to the library file at the indicated offset, unless the
* offset is 0 (then the data is considered empty).
*/
void U6Lib_n::write_item(uint32 item_number) {
if (item_number >= num_offsets
|| items[item_number].offset == 0 || items[item_number].size == 0)
return;
if (game_type == NUVIE_GAME_U6)
data->seek(items[item_number].offset);
else
data->seek(items[item_number].offset + 4);
((NuvieIOFileWrite *)data)->writeBuf(items[item_number].data, items[item_number].size);
}
} // End of namespace Nuvie
} // End of namespace Ultima