TINSEL: Initial work on the Saturn version of DW1

This version is using the same graphics format as the PSX version, with
differences in resource endianess, but features digital music with a
different track structure and has a different sound sample format

Pending issues:
- Font rendering is wrong - this is evident in the text and menus in
  the game title screen
- Cursor sprite and trails are wrong - can be seen in the menu of the
  game title screen
- Digital music is not supported yet - seems that a different track
  structure is used
- The sound sample format is different than the PC version - looks to
   be raw, but isn't
- The game crashes at the first room after watching Rincewind's waking
  up cutscene
This commit is contained in:
Filippos Karapetis 2021-09-04 12:12:56 +03:00
parent 11dbe3ca3d
commit 4f1676e38b
9 changed files with 59 additions and 44 deletions

View File

@ -157,7 +157,11 @@ bool GotoCD() {
bool TinselFile::_warningShown = false;
TinselFile::TinselFile() : ReadStreamEndian(TinselV1Mac) {
TinselFile::TinselFile() : ReadStreamEndian(TinselV1Saturn) {
_stream = nullptr;
}
TinselFile::TinselFile(bool bigEndian) : ReadStreamEndian(bigEndian) {
_stream = nullptr;
}

View File

@ -62,7 +62,9 @@ private:
Common::SeekableReadStream *_stream;
bool openInternal(const Common::String &filename);
public:
// This constructor is only used for _sampleStream inside sound.h
TinselFile();
TinselFile(bool bigEndian);
~TinselFile() override;
bool open(const Common::String &filename);
void close();

View File

@ -56,11 +56,11 @@ static inline uint16 t3getColor(uint8 r, uint8 g, uint8 b) {
}
/**
* PSX Block list unwinder.
* PSX/Saturn Block list unwinder.
* Chunk type 0x0003 (CHUNK_CHARPTR) in PSX version of DW 1 & 2 is compressed (original code
* calls the compression PJCRLE), thus we need to decompress it before passing data to drawing functions
*/
uint8* psxPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
uint8* psxSaturnPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
uint32 remainingBlocks = 0;
uint16 compressionType = 0; // Kind of decompression to apply
@ -84,7 +84,7 @@ uint8* psxPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
while (remainingBlocks) { // Repeat until all blocks are decompressed
if (!controlBits) {
controlData = READ_16(srcIdx);
controlData = READ_LE_UINT16(srcIdx);
srcIdx += 2;
// If bit 15 of controlData is enabled, compression data is type 1.
@ -103,7 +103,7 @@ uint8* psxPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
// If there is compression, we need to fetch an index
// to be treated as "base" for compression.
if (compressionType != 0) {
controlData = READ_16(srcIdx);
controlData = READ_LE_UINT16(srcIdx);
srcIdx += 2;
baseIndex = controlData;
}
@ -125,7 +125,7 @@ uint8* psxPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
switch (compressionType) {
case 0: // No compression, plain copy of indexes
while (decremTiles) {
WRITE_LE_UINT16(dstIdx, READ_16(srcIdx));
WRITE_LE_UINT16(dstIdx, READ_LE_UINT16(srcIdx));
srcIdx += 2;
dstIdx += 2;
decremTiles--;
@ -301,9 +301,9 @@ static void MacDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool apply
/**
* Straight rendering with transparency support, PSX variant supporting also 4-BIT clut data
* Straight rendering with transparency support, PSX/Saturn variant supporting also 4-BIT clut data for PSX
*/
static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool fourBitClut, uint32 psxSkipBytes, byte *psxMapperTable, bool transparency) {
static void psxSaturnDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool fourBitClut, uint32 psxSkipBytes, byte *psxMapperTable, bool transparency) {
// Set up the offset between destination blocks
int rightClip = applyClipping ? pObj->rightClip : 0;
Common::Rect boxBounds;
@ -1066,7 +1066,7 @@ void DrawObject(DRAWOBJECT *pObj) {
byte psxMapperTable[16];
bool psxFourBitClut = false; // Used by Tinsel PSX, true if an image using a 4bit CLUT is rendered
bool psxRLEindex = false; // Used by Tinsel PSX, true if an image is using PJCRLE compressed indexes
bool psxSaturnRLEindex = false; // Used by Tinsel PSX/Saturn, true if an image is using PJCRLE compressed indexes
uint32 psxSkipBytes = 0; // Used by Tinsel PSX, number of bytes to skip before counting indexes for image tiles
if ((pObj->width <= 0) || (pObj->height <= 0))
@ -1083,29 +1083,30 @@ void DrawObject(DRAWOBJECT *pObj) {
byte *p = (byte *)_vm->_handle->LockMem(pObj->hBits & HANDLEMASK);
srcPtr = p + (pObj->hBits & OFFSETMASK);
pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
pObj->transOffset = READ_LE_UINT32(p + 0x14);
pObj->charBase = (char *)p + READ_32(p + 0x10);
pObj->transOffset = READ_32(p + 0x14);
// Decompress block indexes for Discworld PSX
if (TinselV1PSX) {
uint8 paletteType = *(srcPtr); // if 0x88 we are using an 8bit palette type, if 0x44 we are using a 4 bit PSX CLUT
uint8 indexType = *(srcPtr + 1); // if 0xCC indexes for this image are compressed with PCJRLE, if 0xDD indexes are not compressed
// Decompress block indexes for Discworld PSX/Saturn
if (TinselV1PSX || TinselV1Saturn) {
uint8 paletteType = TinselV1PSX ? *(srcPtr) : *(srcPtr + 1); // if 0x88 we are using an 8bit palette type, if 0x44 we are using a 4 bit PSX CLUT
uint8 indexType = TinselV1PSX ? *(srcPtr + 1) : *(srcPtr); // if 0xCC indexes for this image are compressed with PCJRLE, if 0xDD indexes are not compressed
switch (paletteType) {
case 0x88: // Normal 8-bit palette
case 0x00: // Normal 8-bit palette (Saturn)
case 0x88: // Normal 8-bit palette (PSX)
psxFourBitClut = false;
psxSkipBytes = 0;
switch (indexType) {
case 0xDD: // Normal uncompressed indexes
psxRLEindex = false;
psxSaturnRLEindex = false;
srcPtr += sizeof(uint16); // Get to the beginning of index data
break;
case 0xCC: // PJCRLE compressed indexes
psxRLEindex = true;
srcPtr = psxPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16));
psxSaturnRLEindex = true;
srcPtr = psxSaturnPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16));
break;
default:
error("Unknown PSX index type 0x%.2X", indexType);
error("Unknown PSX/Saturn index type 0x%.2X", indexType);
break;
}
break;
@ -1113,15 +1114,15 @@ void DrawObject(DRAWOBJECT *pObj) {
psxPaletteMapper(pObj->pPal, srcPtr + sizeof(uint16), psxMapperTable);
psxFourBitClut = true;
psxSkipBytes = READ_LE_UINT32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip
psxSkipBytes = READ_32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip
switch (indexType) {
case 0xDD: // Normal uncompressed indexes
psxRLEindex = false;
psxSaturnRLEindex = false;
srcPtr += sizeof(uint16) * 17; // Skip image type and clut, and get to beginning of index data
break;
case 0xCC: // PJCRLE compressed indexes
psxRLEindex = true;
srcPtr = psxPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16) * 17);
psxSaturnRLEindex = true;
srcPtr = psxSaturnPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16) * 17);
break;
default:
error("Unknown PSX index type 0x%.2X", indexType);
@ -1129,7 +1130,7 @@ void DrawObject(DRAWOBJECT *pObj) {
}
break;
default:
error("Unknown PSX palette type 0x%.2X", paletteType);
error("Unknown PSX/Saturn palette type 0x%.2X", paletteType);
break;
}
}
@ -1169,8 +1170,8 @@ void DrawObject(DRAWOBJECT *pObj) {
t3WrtNonZero(pObj, srcPtr, destPtr);
else if (TinselV2)
t2WrtNonZero(pObj, srcPtr, destPtr, (typeId & DMA_CLIP) != 0, (typeId & DMA_FLIPH) != 0);
else if (TinselV1PSX)
PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
else if (TinselV1PSX || TinselV1Saturn)
psxSaturnDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
else if (TinselV1Mac)
MacDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41);
else if (TinselV1)
@ -1184,8 +1185,8 @@ void DrawObject(DRAWOBJECT *pObj) {
t3WrtAll(pObj, srcPtr, destPtr);
else if (TinselV2 || TinselV1Mac || TinselV0)
WrtAll(pObj, srcPtr, destPtr, typeId == 0x48);
else if (TinselV1PSX)
PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x48, psxFourBitClut, psxSkipBytes, psxMapperTable, false);
else if (TinselV1PSX || TinselV1Saturn)
psxSaturnDrawTiles(pObj, srcPtr, destPtr, typeId == 0x48, psxFourBitClut, psxSkipBytes, psxMapperTable, false);
else if (TinselV1)
WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x48);
break;
@ -1214,9 +1215,9 @@ void DrawObject(DRAWOBJECT *pObj) {
}
}
// If we were using Discworld PSX, free the memory allocated
// If we were using Discworld PSX/Saturn, free the memory allocated
// for decompressed block indexes.
if (TinselV1PSX && psxRLEindex)
if ((TinselV1PSX || TinselV1Saturn) && psxSaturnRLEindex)
free(srcPtr);
}

View File

@ -83,7 +83,7 @@ void Handle::SetupHandleTable() {
int len;
uint i;
MEMHANDLE *pH;
TinselFile f;
TinselFile f(TinselV1Mac || TinselV1Saturn);
const char *indexFileName = TinselV1PSX ? PSX_INDEX_FILENAME : INDEX_FILENAME;

View File

@ -176,6 +176,8 @@ bool Music::PlayMidiSequence(uint32 dwFileOffset, bool bLoop) {
// The Macintosh version of DW1 uses raw PCM for music
dwSeqLen = midiStream.readUint32BE();
_vm->_sound->playDW1MacMusic(midiStream, dwSeqLen);
} else if (TinselV1Saturn) {
// TODO: Music format for the Saturn version
} else {
dwSeqLen = midiStream.readUint32LE();
@ -298,6 +300,10 @@ void Music::OpenMidiFiles() {
}
midiStream.close();
} else if (TinselV1Saturn) {
// The Saturn version has digital music with a different track structure
// TODO
warning("Music support for Discworld 1 Saturn");
} else {
if (_midiBuffer.pDat)
// already allocated

View File

@ -192,9 +192,8 @@ static void SceneTinselProcess(CORO_PARAM, const void *param) {
// The following myEscape value setting is used for enabling title screen skipping in DW1
if (TinselV1 && (g_sceneCtr == 1)) g_initialMyEscape = GetEscEvents();
// DW1 PSX has its own scene skipping script code for scenes 2 and 3 (bug #6094).
// Same goes for DW1 Mac.
_ctx->myEscape = (TinselV1 && (g_sceneCtr < ((TinselV1PSX || TinselV1Mac) ? 2 : 4))) ? g_initialMyEscape : 0;
// DW1 PSX, Saturn and Mac has its own scene skipping script code for scenes 2 and 3 (bug #6094).
_ctx->myEscape = (TinselV1 && (g_sceneCtr < ((TinselV1PSX || TinselV1Saturn || TinselV1Mac) ? 2 : 4))) ? g_initialMyEscape : 0;
// get the stuff copied to process when it was created
_ctx->pInit = (const TP_INIT *)param;

View File

@ -100,7 +100,7 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound
error(FILE_IS_CORRUPT, _vm->getSampleFile(g_sampleLanguage));
// read the length of the sample
uint32 sampleLen = _sampleStream.readUint32LE();
uint32 sampleLen = _sampleStream.readUint32();
if (_sampleStream.eos() || _sampleStream.err())
error(FILE_IS_CORRUPT, _vm->getSampleFile(g_sampleLanguage));
@ -115,6 +115,8 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound
// Play the audio stream
_vm->_mixer->playStream(type, &curChan.handle, xaStream);
} else if (TinselV1Saturn) {
// TODO: Sound format for the Saturn version - looks to be raw, but isn't
} else {
// allocate a buffer
byte *sampleBuf = (byte *)malloc(sampleLen);
@ -499,7 +501,7 @@ void SoundManager::openSampleFiles() {
if (TinselV0 || (TinselV1 && !_vm->isV1CD()))
return;
TinselFile f;
TinselFile f(TinselV1Saturn);
if (_sampleIndex)
// already allocated
@ -518,7 +520,7 @@ void SoundManager::openSampleFiles() {
// Load data
for (int i = 0; i < _sampleIndexLen; ++i) {
_sampleIndex[i] = f.readUint32LE();
_sampleIndex[i] = f.readUint32();
if (f.err()) {
showSoundError(FILE_READ_ERROR, _vm->getSampleIndex(g_sampleLanguage));
}

View File

@ -88,7 +88,7 @@ void ResetVarsStrRes() {
* @param newLang The new language
*/
void ChangeLanguage(LANGUAGE newLang) {
TinselFile f;
TinselFile f(TinselV1Mac || TinselV1Saturn);
uint32 textLen = 0; // length of buffer
g_textLanguage = newLang;

View File

@ -107,12 +107,13 @@ typedef bool (*KEYFPTR)(const Common::KeyState &);
#define TinselV2Demo (TinselVersion == TINSEL_V2 && _vm->getIsADGFDemo())
#define TinselV1PSX (TinselVersion == TINSEL_V1 && _vm->getPlatform() == Common::kPlatformPSX)
#define TinselV1Mac (TinselVersion == TINSEL_V1 && _vm->getPlatform() == Common::kPlatformMacintosh)
#define TinselV1Saturn (TinselVersion == TINSEL_V1 && _vm->getPlatform() == Common::kPlatformSaturn)
#define READ_16(v) (TinselV1Mac ? READ_BE_UINT16(v) : READ_LE_UINT16(v))
#define READ_32(v) (TinselV1Mac ? READ_BE_UINT32(v) : READ_LE_UINT32(v))
#define FROM_16(v) (TinselV1Mac ? FROM_BE_16(v) : FROM_LE_16(v))
#define FROM_32(v) (TinselV1Mac ? FROM_BE_32(v) : FROM_LE_32(v))
#define TO_32(v) (TinselV1Mac ? TO_BE_32(v) : TO_LE_32(v))
#define READ_16(v) (TinselV1Mac || TinselV1Saturn ? READ_BE_UINT16(v) : READ_LE_UINT16(v))
#define READ_32(v) (TinselV1Mac || TinselV1Saturn ? READ_BE_UINT32(v) : READ_LE_UINT32(v))
#define FROM_16(v) (TinselV1Mac || TinselV1Saturn ? FROM_BE_16(v) : FROM_LE_16(v))
#define FROM_32(v) (TinselV1Mac || TinselV1Saturn ? FROM_BE_32(v) : FROM_LE_32(v))
#define TO_32(v) (TinselV1Mac || TinselV1Saturn ? TO_BE_32(v) : TO_LE_32(v))
// Global reference to the TinselEngine object
extern TinselEngine *_vm;