Files
scummvm-cursorfix/engines/kyra/graphics/screen_eob_amiga.cpp
2026-02-02 04:50:13 +01:00

412 lines
12 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/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/resource/resource.h"
#include "common/memstream.h"
#include "common/translation.h"
#include "gui/error.h"
namespace Kyra {
static uint32 _decodeFrameAmiga_x = 0;
bool decodeFrameAmiga_readNextBit(const uint8 *&data, uint32 &code, uint32 &chk) {
_decodeFrameAmiga_x = code & 1;
code >>= 1;
if (code)
return _decodeFrameAmiga_x;
data -= 4;
code = READ_BE_UINT32(data);
chk ^= code;
_decodeFrameAmiga_x = code & 1;
code = (code >> 1) | (1 << 31);
return _decodeFrameAmiga_x;
}
uint32 decodeFrameAmiga_readBits(const uint8 *&data, uint32 &code, uint32 &chk, int count) {
uint32 res = 0;
while (count--) {
decodeFrameAmiga_readNextBit(data, code, chk);
uint32 bt1 = _decodeFrameAmiga_x;
_decodeFrameAmiga_x = res >> 31;
res = (res << 1) | bt1;
}
return res;
}
void Screen_EoB::loadSpecialAmigaCPS(const char *fileName, int destPage, bool isGraphics) {
uint32 fileSize = 0;
const uint8 *file = _vm->resource()->fileData(fileName, &fileSize);
if (!file)
error("Screen_EoB::loadSpecialAmigaCPS(): Failed to load file '%s'", fileName);
uint32 inSize = READ_BE_UINT32(file);
const uint8 *pos = file;
// Check whether the file starts with the actual compression header.
// If this is not the case, there should a palette before the header.
// Unlike normal CPS files these files never have more than one palette.
if (((inSize + 15) & ~3) != ((fileSize + 3) & ~3)) {
Common::MemoryReadStream in(pos, 64);
_palettes[0]->loadAmigaPalette(in, 0, 32);
pos += 64;
}
inSize = READ_BE_UINT32(pos);
uint32 outSize = READ_BE_UINT32(pos + 4);
uint32 chk = READ_BE_UINT32(pos + 8);
pos = pos + 8 + inSize;
uint8 *dstStart = _pagePtrs[destPage];
uint8 *dst = dstStart + outSize;
uint32 val = READ_BE_UINT32(pos);
_decodeFrameAmiga_x = 0;
chk ^= val;
while (dst > dstStart) {
int para = -1;
int para2 = 0;
if (decodeFrameAmiga_readNextBit(pos, val, chk)) {
uint32 code = decodeFrameAmiga_readBits(pos, val, chk, 2);
if (code == 3) {
para = para2 = 8;
} else {
int cnt = 0;
if (code < 2) {
cnt = 3 + code;
para2 = 9 + code;
} else {
cnt = decodeFrameAmiga_readBits(pos, val, chk, 8) + 1;
para2 = 12;
}
code = decodeFrameAmiga_readBits(pos, val, chk, para2);
while (cnt--) {
dst--;
*dst = dst[code & 0xFFFF];
}
}
} else {
if (decodeFrameAmiga_readNextBit(pos, val, chk)) {
uint32 code = decodeFrameAmiga_readBits(pos, val, chk, 8);
dst--;
*dst = dst[code & 0xFFFF];
dst--;
*dst = dst[code & 0xFFFF];
} else {
para = 3;
}
}
if (para > 0) {
uint32 code = decodeFrameAmiga_readBits(pos, val, chk, para);
uint32 cnt = (code & 0xFFFF) + para2 + 1;
while (cnt--) {
for (int i = 0; i < 8; ++i) {
decodeFrameAmiga_readNextBit(pos, val, chk);
uint32 bt1 = _decodeFrameAmiga_x;
_decodeFrameAmiga_x = code >> 31;
code = (code << 1) | bt1;
}
*(--dst) = code & 0xFF;
}
}
}
delete[] file;
if (chk)
error("Screen_EoB::loadSpecialAmigaCPS(): Checksum error");
if (isGraphics)
convertAmigaGfx(_pagePtrs[destPage], 320, 200);
}
void Screen_EoB::setDualPalettes(Palette &top, Palette &bottom) {
// The original supports simultaneous fading of both palettes, but doesn't make any use of that
// feature. The fade rate is always set to 0. So I see no need to implement that.
_palettes[0]->copy(top, 0, 32, 0);
_palettes[0]->copy(bottom, 0, 32, 32);
setScreenPalette(*_palettes[0]);
enableDualPaletteMode(120);
}
AmigaDOSFont::AmigaDOSFont(Resource *res, bool needsLocalizedFont) : _res(res), _needsLocalizedFont(needsLocalizedFont), _width(0), _height(0), _first(0), _last(0), _content(0), _numElements(0), _selectedElement(0), _maxPathLen(256), _colorMap(nullptr) {
assert(_res);
}
bool AmigaDOSFont::load(Common::SeekableReadStream &file) {
unload();
uint16 id = file.readUint16BE();
// We only support type 0x0f00, since this is the only type used for EOB
if (id != 0x0f00)
return false;
_numElements = file.readUint16BE();
_content = new FontContent[_numElements];
char *cfile = new char[_maxPathLen];
for (int i = 0; i < _numElements; ++i) {
file.read(cfile, _maxPathLen);
_content[i].height = file.readUint16BE();
_content[i].style = file.readByte();
_content[i].flags = file.readByte();
_content[i].contentFile = cfile;
for (int ii = 0; ii < i; ++ii) {
if (_content[ii].contentFile == _content[i].contentFile && _content[ii].data.get())
_content[i].data = _content[ii].data;
}
if (!_content[i].data.get()) {
TextFont *contentData = loadContentFile(cfile);
if (contentData) {
_content[i].data = Common::SharedPtr<TextFont>(contentData);
} else {
unload();
return false;
}
}
if (!(_content[i].flags & 0x40) && (_content[i].height != _content[i].data->height)) {
warning("Amiga DOS Font construction / scaling not implemented.");
}
}
delete[] cfile;
selectMode(0);
return true;
}
int AmigaDOSFont::getCharWidth(uint16 c) const {
if (c < _first || c > _last)
return 0;
c -= _first;
int width = _content[_selectedElement].data->spacing ? _content[_selectedElement].data->spacing[c] : _content[_selectedElement].data->width;
/*if (_content[_selectedElement].data->kerning)
width += _content[_selectedElement].data->kerning[c];*/
return width;
}
void AmigaDOSFont::drawChar(uint16 c, byte *dst, int pitch, int) const {
if (c < _first || c > _last || !dst)
return;
static const uint16 table[] = {
0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00, 0xff00,
0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc, 0xfffe, 0xffff
};
c -= _first;
int w = _content[_selectedElement].data->spacing ? _content[_selectedElement].data->spacing[c] : _content[_selectedElement].data->width;
int xbits = _content[_selectedElement].data->location[c * 2 + 1];
int h = _content[_selectedElement].data->height;
uint16 bitPos = _content[_selectedElement].data->location[c * 2] & 0x0F;
uint16 mod = _content[_selectedElement].data->modulo;
const uint8 *data = _content[_selectedElement].data->bitmap + ((_content[_selectedElement].data->location[c * 2] >> 3) & ~1);
uint32 xbt_mask = xbits ? table[(xbits - 1) & 0x0F] << 16 : 0;
for (int y = 0; y < h; ++y) {
uint32 mask = 0x80000000;
uint32 bits = (READ_BE_UINT32(data) << bitPos) & xbt_mask;
data += mod;
for (int x = 0; x < w; ++x) {
if (bits & mask) {
if (_colorMap[1])
*dst = _colorMap[1];
} else {
if (_colorMap[0])
*dst = _colorMap[0];
}
mask >>= 1;
dst++;
}
dst += (pitch - w);
}
}
uint8 AmigaDOSFont::_errorDialogDisplayed = 0;
void AmigaDOSFont::errorDialog(int index) {
if (_errorDialogDisplayed & (1 << index))
return;
_errorDialogDisplayed |= (1 << index);
// I've made rather elaborate dialogs here, since the Amiga font file handling is quite prone to cause problems for users.
// This will hopefully prevent unnecessary forum posts and bug reports.
if (index == 0) {
::GUI::displayErrorDialog(_(
"This AMIGA version requires the following font files:\n\nEOBF6.FONT\nEOBF6/6\nEOBF8.FONT\nEOBF8/8\n\n"
"If you used the original installer for the installation these files\nshould be located in the AmigaDOS system 'Fonts/' folder.\n"
"Please copy them into the EOB game data directory.\n"
));
error("Failed to load font files.");
} else if (index == 1) {
::GUI::displayErrorDialog(_(
"This AMIGA version requires the following font files:\n\nEOBF6.FONT\nEOBF6/6\nEOBF8.FONT\nEOBF8/8\n\n"
"This is a localized (non-English) version of EOB II which uses language specific characters\n"
"contained only in the specific font files that came with your game. You cannot use the font\n"
"files from the English version or from any EOB I game which seems to be what you are doing.\n\n"
"The game will continue, but the language specific characters will not be displayed.\n"
"Please copy the correct font files into your EOB II game data directory.\n\n"
));
}
}
void AmigaDOSFont::unload() {
delete[] _content;
}
AmigaDOSFont::TextFont *AmigaDOSFont::loadContentFile(const Common::Path &fileName) {
Common::SeekableReadStreamEndian *str = _res->createEndianAwareReadStream(fileName);
if (!str && !fileName.getParent().empty()) {
// These content files are usually located in sub directories (i. e. the eobf8.font
// has a sub dir named 'eobf8' with a file '8' in it). In case someone put the content
// files directly in the game directory we still try to open it.
Common::Path fileNameAlt(fileName.baseName(), Common::Path::kNoSeparator);
str = _res->createEndianAwareReadStream(fileNameAlt);
if (!str) {
// Someone might even have copied the floppy disks to the game directory with the
// full sub directory structure. So we also try that...
fileNameAlt = Common::Path("fonts");
fileNameAlt.joinInPlace(fileName);
str = _res->createEndianAwareReadStream(fileNameAlt);
}
if (!str)
errorDialog(0);
}
assert(str);
uint32 hunkId = str->readUint32();
// Except for some sanity checks we skip all of the Amiga hunk file magic
if (hunkId != 0x03f3)
return 0;
str->seek(20, SEEK_CUR);
uint32 hunkType = str->readUint32();
if (hunkType != 0x3E9)
return 0;
uint32 dataSize = str->readUint32() * 4;
int32 hunkStartPos = str->pos();
str->seek(34, SEEK_CUR);
TextFont *fnt = new TextFont();
int32 fntStartPos = str->pos();
str->seek(44, SEEK_CUR);
fnt->height = str->readUint16();
str->seek(2, SEEK_CUR);
fnt->width = str->readUint16();
fnt->baseLine = str->readUint16();
str->seek(4, SEEK_CUR);
fnt->firstChar = str->readByte();
fnt->lastChar = str->readByte();
if (_needsLocalizedFont && fnt->lastChar <= 127)
errorDialog(1);
str->seek(18, SEEK_CUR);
int32 curPos = str->pos();
uint32 bufferSize = dataSize - (curPos - fntStartPos);
uint8 *buffer = new uint8[bufferSize];
str->read(buffer, bufferSize);
str->seek(curPos - 18, SEEK_SET);
uint32 offset = str->readUint32();
fnt->bitmap = offset ? buffer + offset - (curPos - hunkStartPos) : nullptr;
assert(fnt->bitmap);
fnt->modulo = str->readUint16();
offset = str->readUint32();
uint16 *loc = (uint16*)(offset ? buffer + offset - (curPos - hunkStartPos) : nullptr);
assert(loc);
for (int i = 0; i <= (fnt->lastChar - fnt->firstChar) * 2 + 1; ++i)
loc[i] = READ_BE_UINT16(&loc[i]);
fnt->location = loc;
offset = str->readUint32();
int16 *idat = offset ? (int16*)(buffer + offset - (curPos - hunkStartPos)) : nullptr;
if (idat) {
for (int i = 0; i <= (fnt->lastChar - fnt->firstChar) * 2 + 1; ++i)
idat[i] = (int16)READ_BE_UINT16(&idat[i]);
}
fnt->spacing = idat;
offset = str->readUint32();
// This warning will only show up if someone tries to use this code elsewhere. It cannot happen with EOB fonts.
if (offset)
warning("Trying to load an AmigaDOS font with kerning data. This is not implemented. Font Rendering will not be accurate.");
idat = offset ? (int16*)(buffer + offset - (curPos - hunkStartPos)) : nullptr;
if (idat) {
for (int i = 0; i <= (fnt->lastChar - fnt->firstChar) * 2 + 1; ++i)
idat[i] = (int16)READ_BE_UINT16(&idat[i]);
}
fnt->kerning = idat;
fnt->data = buffer;
delete str;
return fnt;
}
void AmigaDOSFont::selectMode(int mode) {
if (mode < 0 || mode > _numElements - 1)
return;
_selectedElement = mode;
_width = _content[mode].data->width;
_height = _content[mode].data->height;
_first = _content[mode].data->firstChar;
_last = _content[mode].data->lastChar;
}
} // End of namespace Kyra
#endif // ENABLE_EOB