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 "bladerunner/archive.h"
24
25 #include "common/debug.h"
26
27 namespace BladeRunner {
28
MIXArchive()29 MIXArchive::MIXArchive() {
30 _isTLK = false;
31 _entryCount = 0;
32 _size = 0;
33 }
34
~MIXArchive()35 MIXArchive::~MIXArchive() {
36 if (_fd.isOpen()) {
37 warning("~MIXArchive: File not closed: %s", _fd.getName());
38 }
39 }
40
exists(const Common::String & filename)41 bool MIXArchive::exists(const Common::String &filename) {
42 return Common::File::exists(filename);
43 }
44
open(const Common::String & filename)45 bool MIXArchive::open(const Common::String &filename) {
46 if (!_fd.open(filename)) {
47 error("MIXArchive::open(): Can not open %s", filename.c_str());
48 return false;
49 }
50
51 _isTLK = filename.hasSuffix(".TLK");
52
53 _entryCount = _fd.readUint16LE();
54 _size = _fd.readUint32LE();
55
56
57 _entries.resize(_entryCount);
58 for (uint16 i = 0; i != _entryCount; ++i) {
59 _entries[i].hash = _fd.readUint32LE();
60 _entries[i].offset = _fd.readUint32LE();
61 _entries[i].length = _fd.readUint32LE();
62
63 // Verify that the entries are sorted by id. Note that id is signed.
64 if (i > 0) {
65 assert(_entries[i].hash > _entries[i - 1].hash);
66 }
67 }
68
69 if (_fd.err()) {
70 error("MIXArchive::open(): Error reading entries in %s", filename.c_str());
71 _fd.close();
72 return false;
73 }
74
75 // debug("MIXArchive::open: Opened archive %s", filename.c_str());
76
77 return true;
78 }
79
close()80 void MIXArchive::close() {
81 return _fd.close();
82 }
83
isOpen() const84 bool MIXArchive::isOpen() const {
85 return _fd.isOpen();
86 }
87
88 #define ROL(n) ((n << 1) | ((n >> 31) & 1))
89
getHash(const Common::String & name)90 int32 MIXArchive::getHash(const Common::String &name) {
91 char buffer[12] = { 0 };
92
93 for (uint i = 0; i != name.size() && i < 12u; ++i) {
94 buffer[i] = (char)toupper(name[i]);
95 }
96
97 uint32 id = 0;
98 for (int i = 0; i < 12 && buffer[i]; i += 4) {
99 uint32 t = (uint32)buffer[i + 3] << 24
100 | (uint32)buffer[i + 2] << 16
101 | (uint32)buffer[i + 1] << 8
102 | (uint32)buffer[i + 0];
103
104 id = ROL(id) + t;
105 }
106
107 return id;
108 }
109
tlk_id(const Common::String & name)110 static uint32 tlk_id(const Common::String &name) {
111 char buffer[12] = { 0 };
112
113 for (uint i = 0; i != name.size() && i < 12u; ++i)
114 buffer[i] = (char)toupper(name[i]);
115
116 int actor_id = 10 * (buffer[0] - '0') +
117 (buffer[1] - '0');
118
119 int speech_id = 1000 * (buffer[3] - '0') +
120 100 * (buffer[4] - '0') +
121 10 * (buffer[5] - '0') +
122 (buffer[6] - '0');
123
124 return 10000 * actor_id + speech_id;
125 }
126
indexForHash(int32 hash) const127 uint32 MIXArchive::indexForHash(int32 hash) const {
128 uint32 lo = 0, hi = _entryCount;
129
130 while (lo < hi) {
131 uint32 mid = lo + (hi - lo) / 2;
132
133 if (hash > _entries[mid].hash) {
134 lo = mid + 1;
135 } else if (hash < _entries[mid].hash) {
136 hi = mid;
137 } else {
138 return mid;
139 }
140 }
141 return _entryCount;
142 }
143
createReadStreamForMember(const Common::String & name)144 Common::SeekableReadStream *MIXArchive::createReadStreamForMember(const Common::String &name) {
145 int32 hash;
146
147 if (_isTLK) {
148 hash = tlk_id(name);
149 } else {
150 hash = MIXArchive::getHash(name);
151 }
152
153 uint32 i = indexForHash(hash);
154
155 if (i == _entryCount) {
156 return nullptr;
157 }
158
159 uint32 start = _entries[i].offset + 6 + 12 * _entryCount;
160 uint32 end = _entries[i].length + start;
161
162 return new Common::SafeSeekableSubReadStream(&_fd, start, end, DisposeAfterUse::NO);
163 }
164
165 } // End of namespace BladeRunner
166