/* 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 "common/file.h"
#include "common/memstream.h"
#include "common/endian.h"
#include "freescape/freescape.h"
#include "freescape/games/driller/driller.h"
#include "freescape/language/8bitDetokeniser.h"
namespace Freescape {
namespace {
// A simple implementation of memmem, which is a non-standard GNU extension.
const void *local_memmem(const void *haystack, size_t haystack_len, const void *needle, size_t needle_len) {
if (needle_len == 0) {
return haystack;
}
if (haystack_len < needle_len) {
return nullptr;
}
const char *h = (const char *)haystack;
for (size_t i = 0; i <= haystack_len - needle_len; ++i) {
if (memcmp(h + i, needle, needle_len) == 0) {
return h + i;
}
}
return nullptr;
}
} // namespace
Common::SeekableReadStream *DrillerEngine::decryptFileAtariVirtualWorlds(const Common::Path &filename) {
Common::File file;
if (!file.open(filename)) {
error("Failed to open %s", filename.toString().c_str());
}
const int size = file.size();
byte *data = (byte *)malloc(size);
file.read(data, size);
int start = 0;
int valid_offset = -1;
int chunk_size = 0;
while (true) {
const byte *found = (const byte *)local_memmem(data + start, size - start, "CBCP", 4);
if (!found) break;
int idx = found - data;
if (idx + 8 <= size) {
int sz = READ_BE_UINT32(data + idx + 4);
if (sz > 0 && sz < size + 0x20000) {
valid_offset = idx;
chunk_size = sz;
}
}
start = idx + 1;
}
if (valid_offset == -1) {
error("No valid CBCP chunk found in %s", filename.toString().c_str());
}
const byte *payload = data + valid_offset + 8;
const int payload_size = chunk_size;
if (payload_size < 12) {
error("Payload too short in %s", filename.toString().c_str());
}
uint32 bit_buf_init = READ_BE_UINT32(payload + payload_size - 12);
uint32 checksum_init = READ_BE_UINT32(payload + payload_size - 8);
uint32 decoded_size = READ_BE_UINT32(payload + payload_size - 4);
byte *out_buffer = (byte *)malloc(decoded_size);
int dst_idx = decoded_size;
struct BitStream {
const byte *_src_data;
int _src_idx;
uint32 _bit_buffer;
uint32 _checksum;
int _refill_carry;
BitStream(const byte *src_data, int start_idx, uint32 bit_buffer, uint32 checksum) :
_src_data(src_data), _src_idx(start_idx), _bit_buffer(bit_buffer), _checksum(checksum), _refill_carry(0) {}
void refill() {
if (_src_idx < 0) {
_refill_carry = 0;
_bit_buffer = 0x80000000;
return;
}
uint32 val = READ_BE_UINT32(_src_data + _src_idx);
_src_idx -= 4;
_checksum ^= val;
_refill_carry = val & 1;
_bit_buffer = (val >> 1) | 0x80000000;
}
int getBits(int count) {
uint32 result = 0;
for (int i = 0; i < count; ++i) {
int carry = _bit_buffer & 1;
_bit_buffer >>= 1;
if (_bit_buffer == 0) {
refill();
carry = _refill_carry;
}
result = (result << 1) | carry;
}
return result;
}
};
int src_idx = payload_size - 16;
uint32 checksum = checksum_init ^ bit_buf_init;
BitStream bs(payload, src_idx, bit_buf_init, checksum);
while (dst_idx > 0) {
if (bs.getBits(1) == 0) {
if (bs.getBits(1) == 1) {
int offset = bs.getBits(8);
for (int i = 0; i < 2; ++i) {
dst_idx--;
if (dst_idx >= 0) {
out_buffer[dst_idx] = out_buffer[dst_idx + offset];
}
}
} else {
int count = bs.getBits(3) + 1;
for (int i = 0; i < count; ++i) {
dst_idx--;
if (dst_idx >= 0) {
out_buffer[dst_idx] = bs.getBits(8);
}
}
}
} else {
int tag = bs.getBits(2);
if (tag == 3) {
int count = bs.getBits(8) + 9;
for (int i = 0; i < count; ++i) {
dst_idx--;
if (dst_idx >= 0) {
out_buffer[dst_idx] = bs.getBits(8);
}
}
} else if (tag == 2) {
int length = bs.getBits(8) + 1;
int offset = bs.getBits(12);
for (int i = 0; i < length; ++i) {
dst_idx--;
if (dst_idx >= 0) {
out_buffer[dst_idx] = out_buffer[dst_idx + offset];
}
}
} else {
int bits_offset = 9 + tag;
int length = 3 + tag;
int offset = bs.getBits(bits_offset);
for (int i = 0; i < length; ++i) {
dst_idx--;
if (dst_idx >= 0) {
out_buffer[dst_idx] = out_buffer[dst_idx + offset];
}
}
}
}
}
free(data);
return new Common::MemoryReadStream(out_buffer, decoded_size);
}
Common::SeekableReadStream *DrillerEngine::decryptFileAtari(const Common::Path &filename) {
Common::File file;
file.open(filename);
if (!file.isOpen())
error("Failed to open %s", filename.toString().c_str());
int size = file.size();
byte *encryptedBuffer = (byte *)malloc(size);
file.read(encryptedBuffer, size);
file.close();
byte *a6 = encryptedBuffer + 0x118;
byte *a5 = encryptedBuffer + size - 4;
uint64 key = 0xb9f11bce;
while (a6 <= a5) {
uint64 d0 = (a6[0] << 24) | (a6[1] << 16) | (a6[2] << 8) | a6[3];
d0 += key;
d0 = uint32(d0);
a6[0] = byte((d0 >> 24) & 0xFF);
a6[1] = byte((d0 >> 16) & 0xFF);
a6[2] = byte((d0 >> 8) & 0xFF);
a6[3] = byte(d0 & 0xFF);
key += 0x51684624;
key = uint32(key);
a6 += 4;
}
return (new Common::MemoryReadStream(encryptedBuffer, size));
}
void DrillerEngine::loadAssetsAtariFullGame() {
Common::SeekableReadStream *stream = nullptr;
if (_variant & GF_ATARI_RETAIL) {
stream = decryptFileAtari("x.prg");
_border = loadAndConvertNeoImage(stream, 0x14b96);
_borderExtra = loadAndConvertNeoImage(stream, 0x1c916);
_title = loadAndConvertNeoImage(stream, 0x3f6);
loadFonts(stream, 0x8a92);
Common::Array chars;
chars = getCharsAmigaAtariInternal(8, 8, -3, 33, 32, stream, 0x8a92 + 112 * 33 + 1, 100);
_fontSmall = Font(chars);
_fontSmall.setCharWidth(5);
loadMessagesFixedSize(stream, 0xda22, 14, 20);
loadGlobalObjects(stream, 0xd116, 8);
load8bitBinary(stream, 0x2afb8, 16);
loadPalettes(stream, 0x2ab76);
loadSoundsFx(stream, 0x30da6 + 0x147c, 25);
} else if (_variant & GF_ATARI_BUDGET) {
Common::File file;
file.open("x.prg");
if (!file.isOpen()) {
stream = decryptFileAtariVirtualWorlds("dril.all");
} else
stream = &file;
if (isSpaceStationOblivion()) {
_border = loadAndConvertNeoImage(&file, 0x13544);
byte palette[16 * 3];
for (int i = 0; i < 16; i++) { // gray scale palette
palette[i * 3 + 0] = i * (255 / 16);
palette[i * 3 + 1] = i * (255 / 16);
palette[i * 3 + 2] = i * (255 / 16);
}
_title = loadAndConvertNeoImage(&file, 0x10, palette);
loadFonts(&file, 0x8a32 - 0x1d6);
loadMessagesFixedSize(&file, 0xc5d8 - 0x1da, 14, 20);
loadGlobalObjects(&file, 0xbccc - 0x1da, 8);
load8bitBinary(&file, 0x29b3c - 0x1d6, 16);
loadPalettes(&file, 0x296fa - 0x1d6);
loadSoundsFx(&file, 0x30da6 - 0x1d6, 25);
} else {
_border = loadAndConvertNeoImage(stream, 0x1371a);
_title = loadAndConvertNeoImage(stream, 0x396);
loadFonts(stream, 0x8a32);
Common::Array chars;
chars = getCharsAmigaAtariInternal(8, 8, -3, 33, 32, stream, 0x8a32 + 112 * 33 + 1, 100);
_fontSmall = Font(chars);
_fontSmall.setCharWidth(5);
loadMessagesFixedSize(stream, 0xc5d8, 14, 20);
loadGlobalObjects(stream, 0xbccc, 8);
load8bitBinary(stream, 0x29b3c, 16);
loadPalettes(stream, 0x296fa);
loadSoundsFx(stream, 0x30da6, 25);
}
} else
error("Unknown Atari ST Driller variant");
for (auto &area : _areaMap) {
// Center and pad each area name so we do not have to do it at each frame
area._value->_name = centerAndPadString(area._value->_name, 14);
}
_indicators.push_back(loadBundledImage("driller_tank_indicator_0_Amiga", false));
_indicators.push_back(loadBundledImage("driller_tank_indicator_1_Amiga", false));
_indicators.push_back(loadBundledImage("driller_tank_indicator_2_Amiga", false));
_indicators.push_back(loadBundledImage("driller_tank_indicator_3_Amiga", false));
_indicators.push_back(loadBundledImage("driller_ship_indicator_Amiga", false));
for (auto &it : _indicators)
it->convertToInPlace(_gfx->_texturePixelFormat);
}
void DrillerEngine::loadAssetsAtariDemo() {
Common::File file;
file.open("lift.neo");
if (!file.isOpen())
error("Failed to open 'lift.neo' file");
_title = loadAndConvertNeoImage(&file, 0);
file.close();
file.open("console.neo");
if (!file.isOpen())
error("Failed to open 'console.neo' file");
_border = loadAndConvertNeoImage(&file, 0);
file.close();
file.open("demo.cmd");
if (!file.isOpen())
error("Failed to open 'demo.cmd' file");
loadDemoData(&file, 0, 0x1000);
file.close();
if (_variant & GF_ATARI_MAGAZINE_DEMO) {
file.open("auto_x.prg");
if (!file.isOpen())
error("Failed to open 'auto_x.prg' file");
_demoMode = false;
} else {
file.open("x.prg");
if (!file.isOpen())
error("Failed to open 'x.prg' file");
}
if (_variant & GF_ATARI_MAGAZINE_DEMO) {
loadMessagesFixedSize(&file, 0x40d2, 14, 20);
loadGlobalObjects(&file, 0x3e88, 8);
loadFonts(&file, 0x7ee);
Common::Array chars;
chars = getCharsAmigaAtariInternal(8, 8, -3, 33, 32, &file, 0x7ee + 112 * 33 + 1, 100);
_fontSmall = Font(chars);
_fontSmall.setCharWidth(5);
} else {
loadFonts(&file, 0x7bc);
loadMessagesFixedSize(&file, 0x3b90, 14, 20);
loadGlobalObjects(&file, 0x3946, 8);
}
file.close();
file.open("data");
if (!file.isOpen())
error("Failed to open 'data' file");
load8bitBinary(&file, 0x442, 16);
loadPalettes(&file, 0x0);
file.close();
file.open("soundfx");
if (!file.isOpen())
error("Failed to open 'soundfx' executable for AtariST demo");
loadSoundsFx(&file, 0, 25);
for (auto &area : _areaMap) {
// Center and pad each area name so we do not have to do it at each frame
area._value->_name = centerAndPadString(area._value->_name, 14);
}
_indicators.push_back(loadBundledImage("driller_tank_indicator_0_Amiga", false));
_indicators.push_back(loadBundledImage("driller_tank_indicator_1_Amiga", false));
_indicators.push_back(loadBundledImage("driller_tank_indicator_2_Amiga", false));
_indicators.push_back(loadBundledImage("driller_tank_indicator_3_Amiga", false));
_indicators.push_back(loadBundledImage("driller_ship_indicator_Amiga", false));
for (auto &it : _indicators)
it->convertToInPlace(_gfx->_texturePixelFormat);
}
} // End of namespace Freescape