Files
scummvm-cursorfix/engines/awe/resource_nth.cpp
2026-02-02 04:50:13 +01:00

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