587 lines
13 KiB
C++
587 lines
13 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 "common/fs.h"
|
|
#include "awe/pak.h"
|
|
#include "awe/resource_nth.h"
|
|
#include "awe/script.h"
|
|
#include "awe/awe.h"
|
|
|
|
namespace Awe {
|
|
|
|
static char *loadTextFile(Common::File &f, const int size) {
|
|
char *buf = (char *)malloc(size + 1);
|
|
if (buf) {
|
|
const int count = f.read(buf, size);
|
|
if (count != size) {
|
|
warning("Failed to read %d bytes (%d expected)", count, size);
|
|
free(buf);
|
|
return nullptr;
|
|
}
|
|
buf[count] = 0;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
struct Resource15th : ResourceNth {
|
|
Pak _pak;
|
|
const char *_dataPath = "Data";
|
|
const char *_menuPath = "Menu";
|
|
char *_textBuf;
|
|
const char *_stringsTable[157];
|
|
bool _hasRemasteredMusic;
|
|
|
|
Resource15th() {
|
|
_hasRemasteredMusic = Common::FSNode("Music/AW/RmSnd").isDirectory();
|
|
_textBuf = nullptr;
|
|
memset(_stringsTable, 0, sizeof(_stringsTable));
|
|
}
|
|
|
|
virtual ~Resource15th() {
|
|
free(_textBuf);
|
|
}
|
|
|
|
virtual bool init() {
|
|
_pak.open(_dataPath);
|
|
_pak.readEntries();
|
|
return _pak._entriesCount != 0;
|
|
}
|
|
|
|
virtual uint8 *load(const char *name) {
|
|
uint8 *buf = nullptr;
|
|
const PakEntry *e = _pak.find(name);
|
|
if (e) {
|
|
buf = (uint8 *)malloc(e->size);
|
|
if (buf) {
|
|
uint32 size;
|
|
_pak.loadData(e, buf, &size);
|
|
}
|
|
} else {
|
|
warning("Unable to load '%s'", name);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
virtual uint8 *loadBmp(int num) {
|
|
char name[32];
|
|
if (num >= 3000) {
|
|
snprintf(name, sizeof(name), "e%04d.bmp", num);
|
|
} else {
|
|
snprintf(name, sizeof(name), "file%03d.bmp", num);
|
|
}
|
|
return load(name);
|
|
}
|
|
|
|
virtual uint8 *loadDat(int num, uint8 *dst, uint32 *size) {
|
|
char name[32];
|
|
snprintf(name, sizeof(name), "file%03d.dat", num);
|
|
const PakEntry *e = _pak.find(name);
|
|
if (e) {
|
|
_pak.loadData(e, dst, size);
|
|
return dst;
|
|
} else {
|
|
warning("Unable to load '%s'", name);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
virtual uint8 *loadWav(int num, uint8 *dst, uint32 *size) {
|
|
char name[32];
|
|
const PakEntry *e = nullptr;
|
|
if (Script::_useRemasteredAudio) {
|
|
snprintf(name, sizeof(name), "rmsnd/file%03d.wav", num);
|
|
e = _pak.find(name);
|
|
}
|
|
if (!e) {
|
|
snprintf(name, sizeof(name), "file%03db.wav", num);
|
|
e = _pak.find(name);
|
|
if (!e) {
|
|
snprintf(name, sizeof(name), "file%03d.wav", num);
|
|
e = _pak.find(name);
|
|
}
|
|
}
|
|
if (e) {
|
|
uint8 *p = (uint8 *)malloc(e->size);
|
|
if (p) {
|
|
_pak.loadData(e, p, size);
|
|
*size = 0;
|
|
return p;
|
|
}
|
|
warning("Failed to allocate %d bytes", e->size);
|
|
} else {
|
|
warning("Unable to load '%s'", name);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void loadStrings(Language lang) {
|
|
if (!_textBuf) {
|
|
const char *name = nullptr;
|
|
switch (lang) {
|
|
case Common::FR_FRA:
|
|
name = "Francais.Txt";
|
|
break;
|
|
case Common::EN_ANY:
|
|
name = "English.Txt";
|
|
break;
|
|
case Common::DE_DEU:
|
|
name = "German.Txt";
|
|
break;
|
|
case Common::ES_ESP:
|
|
name = "Espanol.txt";
|
|
break;
|
|
case Common::IT_ITA:
|
|
name = "Italian.Txt";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
char path[MAXPATHLEN];
|
|
snprintf(path, sizeof(path), "%s/lang_%s", _menuPath, name);
|
|
Common::File f;
|
|
if (f.open(path)) {
|
|
const int size = f.size();
|
|
_textBuf = loadTextFile(f, size);
|
|
if (_textBuf) {
|
|
char *p = _textBuf;
|
|
while (true) {
|
|
char *end = strchr(p, '\r');
|
|
if (!end) {
|
|
break;
|
|
}
|
|
*end++ = 0;
|
|
if (*end == '\n') {
|
|
*end++ = 0;
|
|
}
|
|
const int len = end - p;
|
|
int strNum = -1;
|
|
if (len > 3 && sscanf(p, "%03d", &strNum) == 1) {
|
|
p += 3;
|
|
while (*p == ' ' || *p == '\t') {
|
|
++p;
|
|
}
|
|
if (strNum > 0 && (uint32)strNum < ARRAYSIZE(_stringsTable)) {
|
|
_stringsTable[strNum] = p;
|
|
}
|
|
}
|
|
p = end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual const char *getString(Language lang, int num) {
|
|
loadStrings(lang);
|
|
if ((uint32)num < ARRAYSIZE(_stringsTable)) {
|
|
return _stringsTable[num];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
virtual const char *getMusicName(int num) {
|
|
const char *path = nullptr;
|
|
switch (num) {
|
|
case 7:
|
|
if (_hasRemasteredMusic && Script::_useRemasteredAudio) {
|
|
path = "Music/AW/RmSnd/Intro2004.wav";
|
|
} else {
|
|
path = "Music/AW/Intro2004.wav";
|
|
}
|
|
break;
|
|
case 138:
|
|
if (_hasRemasteredMusic && Script::_useRemasteredAudio) {
|
|
path = "Music/AW/RmSnd/End2004.wav";
|
|
} else {
|
|
path = "Music/AW/End2004.wav";
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
virtual void getBitmapSize(int *w, int *h) {
|
|
*w = 1280;
|
|
*h = 800;
|
|
}
|
|
};
|
|
|
|
static uint8 *inflateGzip(const char *filepath) {
|
|
Common::File f;
|
|
if (!f.open(filepath)) {
|
|
warning("Unable to open '%s'", filepath);
|
|
return nullptr;
|
|
}
|
|
const uint16 sig = f.readUint16LE();
|
|
if (sig != 0x8B1F) {
|
|
warning("Unexpected file signature 0x%x for '%s'", sig, filepath);
|
|
return nullptr;
|
|
}
|
|
f.seek(-4, SEEK_END);
|
|
const uint32 dataSize = f.readUint32LE();
|
|
uint8 *out = (uint8 *)malloc(dataSize);
|
|
if (!out) {
|
|
warning("Failed to allocate %d bytes", dataSize);
|
|
return nullptr;
|
|
}
|
|
f.seek(0);
|
|
|
|
#ifdef TODO
|
|
z_stream str;
|
|
memset(&str, 0, sizeof(str));
|
|
int err = inflateInit2(&str, MAX_WBITS + 16);
|
|
if (err == Z_OK) {
|
|
Bytef buf[1 << MAX_WBITS];
|
|
str.next_in = buf;
|
|
str.avail_in = 0;
|
|
str.next_out = out;
|
|
str.avail_out = dataSize;
|
|
while (err == Z_OK && str.avail_out != 0) {
|
|
if (str.avail_in == 0 && !f.err()) {
|
|
str.next_in = buf;
|
|
str.avail_in = f.read(buf, sizeof(buf));
|
|
}
|
|
err = inflate(&str, Z_NO_FLUSH);
|
|
}
|
|
inflateEnd(&str);
|
|
if (err == Z_STREAM_END) {
|
|
return out;
|
|
}
|
|
}
|
|
free(out);
|
|
#else
|
|
error("TODO: inflateGzip");
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
struct Resource20th : ResourceNth {
|
|
char *_textBuf;
|
|
const char *_stringsTable[192];
|
|
char _musicName[64];
|
|
uint8 _musicType;
|
|
char _datName[32];
|
|
const char *_bitmapSize;
|
|
|
|
Resource20th() : _textBuf(nullptr), _bitmapSize(nullptr) {
|
|
memset(_stringsTable, 0, sizeof(_stringsTable));
|
|
_musicType = 0;
|
|
_datName[0] = 0;
|
|
_musicName[0] = 0;
|
|
}
|
|
|
|
virtual ~Resource20th() {
|
|
free(_textBuf);
|
|
}
|
|
|
|
virtual bool init() {
|
|
static const char *dirs[] = { "BGZ", "DAT", "WGZ", nullptr };
|
|
for (int i = 0; dirs[i]; ++i) {
|
|
char path[MAXPATHLEN];
|
|
snprintf(path, sizeof(path), "game/%s", dirs[i]);
|
|
Common::FSNode f(path);
|
|
|
|
if (f.exists() && !f.isDirectory()) {
|
|
warning("'%s' is not a directory", path);
|
|
return false;
|
|
}
|
|
}
|
|
static const char *bmps[] = {
|
|
"1728x1080",
|
|
"1280x800",
|
|
"1152x720",
|
|
"960x600",
|
|
"864x540",
|
|
"768x480",
|
|
"480x300",
|
|
"320x200",
|
|
nullptr
|
|
};
|
|
_bitmapSize = nullptr;
|
|
for (int i = 0; bmps[i]; ++i) {
|
|
char path[MAXPATHLEN];
|
|
snprintf(path, sizeof(path), "game/BGZ/data%s", bmps[i]);
|
|
Common::FSNode f(path);
|
|
|
|
if (f.isDirectory()) {
|
|
_bitmapSize = bmps[i];
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
virtual uint8 *load(const char *name) {
|
|
if (strcmp(name, "font.bmp") == 0) {
|
|
return inflateGzip("game/BGZ/Font.bgz");
|
|
} else if (strcmp(name, "heads.bmp") == 0) {
|
|
return inflateGzip("game/BGZ/Heads.bgz");
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
virtual uint8 *loadBmp(int num) {
|
|
char path[MAXPATHLEN];
|
|
if (num >= 3000 && _bitmapSize) {
|
|
snprintf(path, sizeof(path), "game/BGZ/data%s/%s_e%04d.bgz", _bitmapSize, _bitmapSize, num);
|
|
} else {
|
|
snprintf(path, sizeof(path), "game/BGZ/file%03d.bgz", num);
|
|
}
|
|
|
|
return inflateGzip(path);
|
|
}
|
|
|
|
void preloadDat(int part, int type, int num) {
|
|
static const char *names[] = {
|
|
"INTRO", "EAU", "PRI", "CITE", "arene", "LUXE", "FINAL", nullptr
|
|
};
|
|
static const char *exts[] = {
|
|
"pal", "mac", "mat", nullptr
|
|
};
|
|
if (part > 0 && part < 8) {
|
|
if (type == 3) {
|
|
assert(num == 0x11);
|
|
Common::strcpy_s(_datName, "BANK2.MAT");
|
|
} else {
|
|
snprintf(_datName, sizeof(_datName), "%s2011.%s", names[part - 1], exts[type]);
|
|
}
|
|
debugC(kDebugResource, "Loading '%s'", _datName);
|
|
} else {
|
|
_datName[0] = 0;
|
|
}
|
|
}
|
|
|
|
virtual uint8 *loadDat(int num, uint8 *dst, uint32 *size) {
|
|
bool datOpen = false;
|
|
char path[MAXPATHLEN];
|
|
Common::strcpy_s(path, "game/DAT");
|
|
|
|
Common::File f;
|
|
if (_datName[0]) {
|
|
datOpen = f.open(_datName);
|
|
}
|
|
if (!datOpen) {
|
|
snprintf(_datName, sizeof(_datName), "FILE%03d.DAT", num);
|
|
datOpen = f.open(_datName);
|
|
}
|
|
if (datOpen) {
|
|
const uint32 dataSize = f.size();
|
|
const uint32 count = f.read(dst, dataSize);
|
|
if (count != dataSize) {
|
|
warning("Failed to read %d bytes (expected %d)", dataSize, count);
|
|
}
|
|
*size = dataSize;
|
|
} else {
|
|
warning("Unable to open '%s/%s'", path, _datName);
|
|
dst = nullptr;
|
|
}
|
|
_datName[0] = 0;
|
|
return dst;
|
|
}
|
|
|
|
virtual uint8 *loadWav(int num, uint8 *dst, uint32 *size) {
|
|
char path[MAXPATHLEN];
|
|
if (!Script::_useRemasteredAudio) {
|
|
snprintf(path, sizeof(path), "game/WGZ/original/file%03d.wgz", num);
|
|
|
|
if (!Common::File::exists(path)) {
|
|
snprintf(path, sizeof(path), "game/WGZ/original/file%03dB.wgz", num);
|
|
}
|
|
|
|
*size = 0;
|
|
return inflateGzip(path);
|
|
}
|
|
|
|
switch (num) {
|
|
case 81:
|
|
{
|
|
const int r = g_engine->getRandomNumber(1, 3);
|
|
snprintf(path, sizeof(path), "game/WGZ/file081-EX-%d.wgz", r);
|
|
}
|
|
break;
|
|
|
|
case 85:
|
|
{
|
|
const int r = g_engine->getRandomNumber(1, 2);
|
|
const char *snd = "IN";
|
|
if (_musicType == 1) {
|
|
snd = "EX";
|
|
}
|
|
snprintf(path, sizeof(path), "game/WGZ/file085-%s-%d.wgz", snd, r);
|
|
}
|
|
break;
|
|
case 96:
|
|
{
|
|
const int r = g_engine->getRandomNumber(1, 3);
|
|
const char *snd = "GR";
|
|
if (_musicType == 1) {
|
|
snd = "EX";
|
|
} else if (_musicType == 2) {
|
|
snd = "IN";
|
|
}
|
|
snprintf(path, sizeof(path), "game/WGZ/file096-%s-%d.wgz", snd, r);
|
|
}
|
|
break;
|
|
case 163:
|
|
{
|
|
const char *snd = "GR";
|
|
if (_musicType == 1) {
|
|
snd = "EX";
|
|
} else if (_musicType == 2) {
|
|
snd = "IN";
|
|
}
|
|
snprintf(path, sizeof(path), "game/WGZ/file163-%s-1.wgz", snd);
|
|
}
|
|
break;
|
|
default:
|
|
snprintf(path, sizeof(path), "game/WGZ/file%03d.wgz", num);
|
|
if (!Common::File::exists(path)) {
|
|
snprintf(path, sizeof(path), "game/WGZ/file%03dB.wgz", num);
|
|
}
|
|
break;
|
|
}
|
|
*size = 0;
|
|
return inflateGzip(path);
|
|
}
|
|
|
|
void loadStrings(Language lang) {
|
|
if (!_textBuf) {
|
|
const char *name = nullptr;
|
|
switch (lang) {
|
|
case Common::FR_FRA:
|
|
name = "FR";
|
|
break;
|
|
case Common::EN_ANY:
|
|
name = "EN";
|
|
break;
|
|
case Common::DE_DEU:
|
|
name = "DE";
|
|
break;
|
|
case Common::ES_ESP:
|
|
name = "ES";
|
|
break;
|
|
case Common::IT_ITA:
|
|
name = "IT";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
char path[MAXPATHLEN];
|
|
static const char *fmt[] = {
|
|
"game/TXT/%s.txt",
|
|
"game/TXT/Linux/%s.txt",
|
|
nullptr
|
|
};
|
|
bool isOpen = false;
|
|
Common::File f;
|
|
for (int i = 0; fmt[i] && !isOpen; ++i) {
|
|
snprintf(path, sizeof(path), fmt[i], name);
|
|
isOpen = f.open(path);
|
|
}
|
|
if (isOpen) {
|
|
const int size = f.size();
|
|
_textBuf = loadTextFile(f, size);
|
|
if (_textBuf) {
|
|
int count = 0;
|
|
for (char *p = _textBuf; count < (int)ARRAYSIZE(_stringsTable); ) {
|
|
_stringsTable[count++] = p;
|
|
char *end = strchr(p, '\n');
|
|
if (!end) {
|
|
break;
|
|
}
|
|
*end++ = 0;
|
|
p = end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
virtual const char *getString(Language lang, int num) {
|
|
loadStrings(lang);
|
|
if ((uint32)num < ARRAYSIZE(_stringsTable)) {
|
|
return _stringsTable[num];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
virtual const char *getMusicName(int num) {
|
|
if (num >= 5000 && Script::_useRemasteredAudio) {
|
|
snprintf(_musicName, sizeof(_musicName), "game/OGG/amb%d.ogg", num);
|
|
switch (num) {
|
|
case 5005:
|
|
_musicType = 1;
|
|
break;
|
|
case 5006:
|
|
_musicType = 3;
|
|
break;
|
|
default:
|
|
_musicType = 2;
|
|
break;
|
|
}
|
|
} else {
|
|
switch (num) {
|
|
case 7:
|
|
if (Script::_useRemasteredAudio) {
|
|
Common::strcpy_s(_musicName, "game/OGG/Intro_20th.ogg");
|
|
} else {
|
|
Common::strcpy_s(_musicName, "game/OGG/original/intro.ogg");
|
|
}
|
|
break;
|
|
case 138:
|
|
if (!Script::_useRemasteredAudio) {
|
|
Common::strcpy_s(_musicName, "game/OGG/original/ending.ogg");
|
|
break;
|
|
}
|
|
/* fall-through */
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
return _musicName;
|
|
}
|
|
|
|
virtual void getBitmapSize(int *w, int *h) {
|
|
if (_bitmapSize && sscanf(_bitmapSize, "%dx%d", w, h) == 2) {
|
|
return;
|
|
}
|
|
*w = 0;
|
|
*h = 0;
|
|
}
|
|
};
|
|
|
|
ResourceNth *ResourceNth::create(int edition) {
|
|
switch (edition) {
|
|
case 15:
|
|
return new Resource15th();
|
|
case 20:
|
|
return new Resource20th();
|
|
default:
|
|
break;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace Awe
|