669 lines
16 KiB
C++
669 lines
16 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 "m4/fileio/sys_file.h"
|
|
#include "m4/fileio/extensions.h"
|
|
#include "m4/fileio/fileio.h"
|
|
#include "m4/adv_r/db_env.h"
|
|
#include "m4/mem/memman.h"
|
|
#include "m4/vars.h"
|
|
#include "m4/m4.h"
|
|
|
|
namespace M4 {
|
|
|
|
// Hash entry format:
|
|
// Filename 33 bytes, Hagfile 1 byte, Disks 1 byte, Offset 4 bytes,
|
|
// Size 4 bytes Next_record 4 Bytes, Total is 47 bytes.
|
|
#define HASH_RECORD_LENGTH 47
|
|
|
|
SysFile::SysFile(const Common::String &fname, FileMode myfmode) :
|
|
filename(fname), fmode(myfmode) {
|
|
}
|
|
|
|
bool SysFile::exists() {
|
|
show_error_flag = false;
|
|
open_read_low_level();
|
|
show_error_flag = true;
|
|
|
|
if (!_G(hag).hag_flag)
|
|
return _fp != nullptr;
|
|
|
|
return (hag_success);
|
|
}
|
|
|
|
uint32 SysFile::size() {
|
|
uint32 fsize;
|
|
|
|
open_read();
|
|
|
|
if (!_G(hag).hag_flag) {
|
|
fsize = rs()->size();
|
|
|
|
} else {
|
|
if (hag_success)
|
|
fsize = curr_hash_record.size;
|
|
else
|
|
fsize = 0;
|
|
}
|
|
|
|
return fsize;
|
|
}
|
|
|
|
uint32 SysFile::get_pos() {
|
|
if (!_G(hag).hag_flag) {
|
|
if (!_fp)
|
|
return 0;
|
|
|
|
return rs()->pos();
|
|
|
|
}
|
|
|
|
if (hag_success)
|
|
return (uint32)(curr_hag_record->hag_pos - curr_hash_record.offset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SysFile::open_read_low_level() {
|
|
Common::File temp_fp;
|
|
char hag_name[33];
|
|
char *temp_name;
|
|
Common::String resource_hag;
|
|
Common::File hagfile_fp;
|
|
|
|
if (filename.empty()) {
|
|
_fp = nullptr;
|
|
return;
|
|
}
|
|
|
|
if (!_G(hag).first_read_flag) {
|
|
// First time to read
|
|
if (_G(hag).hag_flag) {
|
|
// Use hag file
|
|
// Get hagfile table list here
|
|
if (!temp_fp.open(_G(hag).hash_file))
|
|
error("Hash file not found: %s", _G(hag).hash_file.toString().c_str());
|
|
|
|
const uint32 hash_table_size = temp_fp.readUint32LE();
|
|
if (!temp_fp.seek(hash_table_size * HASH_RECORD_LENGTH, SEEK_CUR))
|
|
error("fail to seek");
|
|
|
|
_G(hag).hag_name_list = nullptr;
|
|
while (!temp_fp.eos()) {
|
|
if (temp_fp.read(hag_name, 33) != 33)
|
|
break;
|
|
const byte hagfile = temp_fp.readByte();
|
|
|
|
Hag_Name_Record *temp_ptr = (Hag_Name_Record *)mem_alloc(sizeof(Hag_Name_Record), "hag_name_list");
|
|
assert(temp_ptr);
|
|
|
|
// Check hag file exists or not
|
|
Common::Path local_name(f_extension_new(hag_name, "HAG"));
|
|
|
|
if (!Common::File::exists(local_name))
|
|
error("couldn't find hag file: %s", local_name.toString().c_str());
|
|
|
|
// put into hag_name_list //
|
|
Common::strcpy_s(temp_ptr->filename, hag_name);
|
|
temp_ptr->hagfile = hagfile;
|
|
temp_ptr->next = _G(hag).hag_name_list;
|
|
_G(hag).hag_name_list = temp_ptr;
|
|
}
|
|
|
|
temp_fp.close();
|
|
} else {
|
|
_G(hag).hag_flag = false;
|
|
}
|
|
|
|
_G(hag).first_read_flag = true;
|
|
}
|
|
|
|
switch (mode) {
|
|
case UNOPENED:
|
|
switch (fmode) {
|
|
case TEXT:
|
|
temp_name = env_find(filename);
|
|
|
|
if (!_G(hag).hag_flag) {
|
|
if (temp_name) {
|
|
filename = temp_name;
|
|
_fp = f_io_open(Common::Path(filename), "rb");
|
|
|
|
if (!_fp && show_error_flag)
|
|
error("Failed opening - %s", filename.c_str());
|
|
} else {
|
|
_fp = open_by_first_char();
|
|
}
|
|
} else {
|
|
// Hag mode
|
|
filename = get_last_string(filename);
|
|
|
|
if (!open_hash_file() && show_error_flag)
|
|
error("not in hag file: %s", filename.c_str());
|
|
}
|
|
break;
|
|
|
|
case BINARY:
|
|
// is it a font file? They're stored in a *special* place oo-la-la
|
|
temp_name = env_find(filename);
|
|
|
|
if (!_G(hag).hag_flag) {
|
|
if (temp_name)
|
|
filename = temp_name;
|
|
open_by_first_char();
|
|
|
|
} else {
|
|
Common::String last_string = get_last_string(filename);
|
|
|
|
if (!open_hash_file()) {
|
|
if (show_error_flag)
|
|
error("not in hag file: %s", filename.c_str());
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case READ:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mode = READ;
|
|
}
|
|
|
|
void SysFile::open_read() {
|
|
open_read_low_level();
|
|
|
|
if (_G(hag).hag_flag && curr_hag_record) {
|
|
if (!curr_hag_record->hag_fp)
|
|
error("hag file not open for: %s", filename.c_str());
|
|
}
|
|
|
|
if (!_G(hag).hag_flag && !_fp) {
|
|
error("Error opening - %s", filename.c_str());
|
|
}
|
|
|
|
if (_G(hag).hag_flag && !hag_success) {
|
|
error("Error opening - %s", filename.c_str());
|
|
}
|
|
}
|
|
|
|
void SysFile::open_write() {
|
|
error("open_write is not implemented in ScummVM");
|
|
}
|
|
|
|
Common::String SysFile::get_last_string(const Common::String &src) {
|
|
int len = src.size();
|
|
Common::String result;
|
|
|
|
int j;
|
|
for (j = len - 1; j >= 0; j--) {
|
|
if (src[j] == '\\' || src[j] == ':')
|
|
break;
|
|
}
|
|
|
|
if (j >= 0) {
|
|
for (int k = j + 1; k < len; k++)
|
|
result += src[k];
|
|
return result;
|
|
|
|
}
|
|
|
|
return src;
|
|
}
|
|
|
|
bool SysFile::open_hash_file() {
|
|
Common::SeekableReadStream *hashfp = dynamic_cast<Common::SeekableReadStream *>(f_io_open(_G(hag).hash_file, "rb"));
|
|
if (!hashfp) {
|
|
warning("open_hash_file: %s", _G(hag).hash_file.toString().c_str());
|
|
hag_success = false;
|
|
return false;
|
|
}
|
|
|
|
uint32 hash_table_size = hashfp->readUint32LE();
|
|
uint32 hash_address = key_to_hash_address(filename, hash_table_size);
|
|
|
|
if (!hash_search(filename, &curr_hash_record, curr_hag_record, hash_address, hashfp, hash_table_size, show_error_flag)) {
|
|
hag_success = 0;
|
|
return false;
|
|
}
|
|
|
|
// How to open hag file
|
|
// Calculate Hagfile name - depends on hagfile field in curr_hash_record
|
|
Common::String local_name;
|
|
|
|
if (!get_local_name_from_hagfile(local_name, curr_hash_record.hagfile)) {
|
|
hag_success = 0;
|
|
return false;
|
|
}
|
|
|
|
// Check if this Hag file already open or not
|
|
local_name = f_extension_new(local_name, "HAG");
|
|
Common::String temp_name = local_name;
|
|
Common::String hag_name = local_name; // Original used in cd_resource + name
|
|
|
|
bool found = false;
|
|
Hag_Record *temp_ptr = _G(hag).hag_file_list;
|
|
|
|
// Search local open files for hag file...
|
|
while (temp_ptr) {
|
|
if (hag_name.equalsIgnoreCase(temp_ptr->hag_name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
temp_ptr = temp_ptr->next;
|
|
}
|
|
|
|
// Search resource directory open files for hag file
|
|
if (!found) {
|
|
temp_ptr = _G(hag).hag_file_list;
|
|
found = false;
|
|
while (temp_ptr) {
|
|
if (temp_name.equalsIgnoreCase(temp_ptr->hag_name)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
temp_ptr = temp_ptr->next;
|
|
}
|
|
if (!found) {
|
|
// hag file is not open, try the current directory first, then RESOURCE_PATH
|
|
Common::Stream *temp_fp = f_io_open(Common::Path(hag_name), "rb");
|
|
if (!temp_fp) {
|
|
// hag_file is not in current directory, search for RESOURCE_PATH
|
|
temp_fp = f_io_open(Common::Path(temp_name), "rb");
|
|
if (!temp_fp) {
|
|
error("hag file not found: %s", hag_name.c_str());
|
|
hag_success = 0;
|
|
return 0;
|
|
}
|
|
|
|
// Add this new open hag file in resource dir into open hag file list
|
|
temp_ptr = (Hag_Record *)mem_alloc(sizeof(Hag_Record), "Hag_File_List");
|
|
if (!temp_ptr) {
|
|
f_io_close(temp_fp);
|
|
error("creating Hag_record");
|
|
hag_success = 0;
|
|
return 0;
|
|
}
|
|
|
|
Common::strcpy_s(temp_ptr->hag_name, temp_name.c_str());
|
|
temp_ptr->hag_fp = temp_fp;
|
|
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(temp_fp);
|
|
assert(rs);
|
|
|
|
if (!rs->seek(curr_hash_record.offset))
|
|
term_message("fail to fseek");
|
|
last_head_pos = rs->pos();
|
|
|
|
temp_ptr->hag_pos = curr_hash_record.offset;
|
|
temp_ptr->hagfile = curr_hash_record.hagfile;
|
|
|
|
// insert the element into list
|
|
temp_ptr->next = _G(hag).hag_file_list;
|
|
_G(hag).hag_file_list = temp_ptr;
|
|
}
|
|
// we just opened a previously unopened hag file
|
|
else {
|
|
// add this new open hag file in exec dir into its list
|
|
temp_ptr = (Hag_Record *)mem_alloc(sizeof(Hag_Record), "Hag_File_List");
|
|
if (!temp_ptr) {
|
|
f_io_close(temp_fp);
|
|
error("creating hag_record");
|
|
hag_success = 0;
|
|
return 0;
|
|
}
|
|
|
|
Common::strcpy_s(temp_ptr->hag_name, hag_name.c_str());
|
|
temp_ptr->hag_fp = temp_fp;
|
|
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(temp_fp);
|
|
assert(rs);
|
|
|
|
if (!rs->seek(curr_hash_record.offset))
|
|
term_message("fail to fseek");
|
|
last_head_pos = rs->pos();
|
|
|
|
temp_ptr->hag_pos = curr_hash_record.offset;
|
|
temp_ptr->hagfile = curr_hash_record.hagfile;
|
|
|
|
// Insert the element into list
|
|
temp_ptr->next = _G(hag).hag_file_list;
|
|
_G(hag).hag_file_list = temp_ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(temp_ptr->hag_fp);
|
|
assert(rs);
|
|
|
|
// set hag file pointer to current file position //
|
|
if (!rs->seek(curr_hash_record.offset))
|
|
term_message("fail to fseek");
|
|
last_head_pos = rs->pos();
|
|
|
|
temp_ptr->hag_pos = curr_hash_record.offset;
|
|
curr_hag_record = temp_ptr;
|
|
|
|
hag_success = true;
|
|
return true;
|
|
}
|
|
|
|
uint32 SysFile::key_to_hash_address(const Common::String &src, uint32 hash_table_size) {
|
|
Common::String key = src;
|
|
key.toUppercase();
|
|
|
|
if (key.empty())
|
|
return 0;
|
|
|
|
uint32 h = key[0];
|
|
const int len = key.size();
|
|
for (int i = 1; i < len; i++)
|
|
h = ((h * 256) + key[i]) % hash_table_size; // * 256 is because one char is 8 bits
|
|
|
|
return h;
|
|
}
|
|
|
|
int SysFile::hash_search(const Common::String &fname, Hash_Record *current_hash_record_ptr, Hag_Record *current_hag_record, uint32 hash_address,
|
|
Common::SeekableReadStream *hashfp, uint32 hash_table_size, bool show_errors) {
|
|
bool found = false;
|
|
char myfilename[33];
|
|
Common::String local_name;
|
|
|
|
|
|
uint32 next_entry = hash_address;
|
|
// 4 bytes is header of hash file, store hash_table_size
|
|
uint32 offset = HASH_RECORD_LENGTH * next_entry + 4;
|
|
uint32 best_dist = 0x7fffffff;
|
|
uint32 find_offset = offset;
|
|
myfilename[0] = '\0';
|
|
|
|
while (!found) {
|
|
if (!hashfp->seek(offset))
|
|
term_message("fail to fseek");
|
|
hashfp->read(myfilename, 33);
|
|
|
|
if (myfilename[0] == '\0') {
|
|
// this hash table is empty, insert new record here
|
|
f_io_close(hashfp);
|
|
|
|
if (show_errors)
|
|
error("not found in hag file: %s", fname.c_str());
|
|
else
|
|
term_message("fclass: file not found '%s', in hag file", fname.c_str());
|
|
return 0;
|
|
}
|
|
|
|
if (fname.equalsIgnoreCase(myfilename)) {
|
|
// The new record already in hash table, do nothing
|
|
auto &r = *current_hash_record_ptr;
|
|
r.hagfile = hashfp->readByte();
|
|
r.disks = hashfp->readByte();
|
|
r.offset = hashfp->readUint32LE();
|
|
r.size = hashfp->readUint32LE();
|
|
uint32 next_record = hashfp->readUint32LE();
|
|
r.filename = myfilename;
|
|
|
|
// As long as we find a hag file, use it immedeiately
|
|
get_local_name_from_hagfile(local_name, current_hash_record_ptr->hagfile);
|
|
Common::String local_hag_name = f_extension_new(local_name, "HAG");
|
|
local_name = local_hag_name;
|
|
|
|
if (!Common::File::exists(Common::Path(local_name))) {
|
|
found = true;
|
|
find_offset = offset;
|
|
break;
|
|
}
|
|
if (current_hag_record && current_hag_record->hagfile == current_hash_record_ptr->hagfile) {
|
|
// in same hag file
|
|
if (best_dist > (uint32)ABS((int32)current_hag_record->hag_pos - (int32)current_hash_record_ptr->offset)) {
|
|
best_dist = ABS((int32)current_hag_record->hag_pos - (int32)current_hash_record_ptr->offset);
|
|
find_offset = offset;
|
|
}
|
|
} else {
|
|
find_offset = offset;
|
|
}
|
|
if (next_record == offset) {
|
|
// only one record of fname in hash table
|
|
found = true;
|
|
} else {
|
|
offset = next_record;
|
|
}
|
|
} else { // collision here, search the next entry to see if it is empty until find a empty one
|
|
next_entry = (next_entry + 1) % hash_table_size; // search the hash table a round way
|
|
offset = HASH_RECORD_LENGTH * next_entry + 4; // 4 bytes is header of hash file, store hash_table_size
|
|
}
|
|
}
|
|
|
|
|
|
// get the best close one of hag file for multiple same fname
|
|
if (find_offset != offset) {
|
|
if (!hashfp->seek(find_offset))
|
|
term_message("fail to fseek");
|
|
|
|
auto &r = *current_hash_record_ptr;
|
|
hashfp->read(myfilename, 33);
|
|
r.filename = myfilename;
|
|
r.hagfile = hashfp->readByte();
|
|
r.disks = hashfp->readByte();
|
|
r.offset = hashfp->readUint32LE();
|
|
r.size = hashfp->readUint32LE();
|
|
}
|
|
|
|
f_io_close(hashfp);
|
|
if (!found) {
|
|
error("not in hag file: %s", fname.c_str());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Common::Stream *SysFile::open_by_first_char() {
|
|
if (filename.hasPrefix("*")) {
|
|
// MADS folder file in original
|
|
_fp = f_io_open(Common::Path(filename.c_str() + 1, '/'), "rb");
|
|
} else {
|
|
_fp = f_io_open(Common::Path(filename, '/'), "rb");
|
|
}
|
|
|
|
if (!_fp) {
|
|
if (show_error_flag)
|
|
error("fclass: file not found '%s'", filename.c_str());
|
|
else
|
|
term_message("fclass: file not found '%s'", filename.c_str());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool SysFile::get_local_name_from_hagfile(Common::String &local_name, byte hagfile) {
|
|
bool found = false;
|
|
Hag_Name_Record *temp_ptr = _G(hag).hag_name_list;
|
|
|
|
while (temp_ptr && !found) {
|
|
if (temp_ptr->hagfile == hagfile) {
|
|
found = true;
|
|
local_name = temp_ptr->filename;
|
|
} else {
|
|
temp_ptr = temp_ptr->next;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool SysFile::seek(uint32 pos) {
|
|
if (!_G(hag).hag_flag) {
|
|
return rs()->seek(pos);
|
|
}
|
|
|
|
if (hag_success) {
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(curr_hag_record->hag_fp);
|
|
assert(rs);
|
|
|
|
if (!rs->seek(curr_hash_record.offset + pos - curr_hag_record->hag_pos, SEEK_CUR))
|
|
term_message("fail to fseek");
|
|
last_head_pos = rs->pos();
|
|
|
|
curr_hag_record->hag_pos = curr_hash_record.offset + pos; // Change file position
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SysFile::seek_ahead(int32 amount) {
|
|
if (!_G(hag).hag_flag) {
|
|
return rs()->seek(amount, SEEK_CUR);
|
|
|
|
}
|
|
|
|
if (hag_success) {
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(curr_hag_record->hag_fp);
|
|
assert(rs);
|
|
|
|
if (!rs->seek(amount, SEEK_CUR))
|
|
term_message("fail to fseek");
|
|
|
|
last_head_pos = rs->pos();
|
|
curr_hag_record->hag_pos += amount; // Change file position
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
uint32 SysFile::read(MemHandle bufferHandle) {
|
|
const int32 bytesToRead = size() - get_pos();
|
|
if (bytesToRead < 0)
|
|
error("SysFile::read - %s", filename.c_str());
|
|
|
|
return read(bufferHandle, bytesToRead);
|
|
}
|
|
|
|
int32 SysFile::read(byte *bufferHandle, int32 n) {
|
|
void *h = bufferHandle;
|
|
return read((MemHandle)&h, n);
|
|
}
|
|
|
|
int32 SysFile::read(MemHandle bufferHandle, int32 n) {
|
|
if (!bufferHandle)
|
|
error("reading %s", filename.c_str());
|
|
|
|
open_read();
|
|
|
|
if (!*bufferHandle)
|
|
mem_ReallocateHandle(bufferHandle, n, "SysFile");
|
|
if (!*bufferHandle)
|
|
error("Needed %d to read info", n);
|
|
|
|
if (!_G(hag).hag_flag) {
|
|
return (uint32)rs()->read(*bufferHandle, n);
|
|
}
|
|
|
|
// Hag mode
|
|
if (hag_success) {
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(curr_hag_record->hag_fp);
|
|
assert(rs);
|
|
rs->seek(last_head_pos);
|
|
uint32 temp_myfpos = rs->pos();
|
|
|
|
uint32 temp_size = (uint32)rs->read(*bufferHandle, n);
|
|
curr_hag_record->hag_pos = temp_myfpos + temp_size; // Change file position
|
|
|
|
last_head_pos = rs->pos();
|
|
return temp_size;
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
byte SysFile::readByte() {
|
|
byte buf[1];
|
|
void *ptr = (void *)buf;
|
|
read(&ptr, 1);
|
|
|
|
return buf[0];
|
|
}
|
|
|
|
uint16 SysFile::readUint16LE() {
|
|
byte buf[2];
|
|
void *ptr = (void *)buf;
|
|
read(&ptr, 2);
|
|
|
|
return READ_LE_UINT16(buf);
|
|
}
|
|
|
|
uint32 SysFile::readUint32LE() {
|
|
byte buf[4];
|
|
void *ptr = (void *)buf;
|
|
read(&ptr, 4);
|
|
|
|
return READ_LE_UINT32(buf);
|
|
}
|
|
|
|
Common::SeekableReadStream *SysFile::rs() const {
|
|
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(_fp);
|
|
assert(rs);
|
|
return rs;
|
|
}
|
|
|
|
void SysFile::close() {
|
|
delete _fp;
|
|
_fp = nullptr;
|
|
}
|
|
|
|
|
|
void sysfile_init(bool in_hag_mode) {
|
|
_G(hag).hag_flag = in_hag_mode;
|
|
|
|
if (in_hag_mode) {
|
|
const char *name = "burger";
|
|
if (g_engine->getGameType() == GType_Riddle)
|
|
name = "ripley";
|
|
else if (g_engine->isDemo() == GStyle_NonInteractiveDemo)
|
|
name = "overview";
|
|
|
|
_G(hag).hash_file = Common::Path(Common::String::format("%s.has", name));
|
|
term_message("Initialized in hag mode");
|
|
} else {
|
|
term_message("Initialized in file mode");
|
|
}
|
|
}
|
|
|
|
void sysfile_shutdown() {
|
|
Hag_Record *temp_ptr = _G(hag).hag_file_list;
|
|
while (temp_ptr) {
|
|
_G(hag).hag_file_list = _G(hag).hag_file_list->next;
|
|
f_io_close(temp_ptr->hag_fp);
|
|
mem_free(temp_ptr);
|
|
temp_ptr = _G(hag).hag_file_list;
|
|
}
|
|
}
|
|
|
|
} // namespace M4
|