/* 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 . * */ #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(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(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(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(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(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(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(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(_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