/* * InterplayDecoder * Copyright (C) 2020 sirlemonhead * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* This code is based on interplayvideo.c, dpcm.c and ipmovie.c from the FFmpeg project which can be obtained * from http://www.ffmpeg.org/. Below is the license from interplayvideo.c * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * Interplay MVE Video Decoder * Copyright (C) 2003 The FFmpeg project * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "playmve.h" #include "printf.h" #include "v_draw.h" #include "s_music.h" #include "cmdlib.h" static const int16_t delta_table[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 47, 51, 56, 61, 66, 72, 79, 86, 94, 102, 112, 122, 133, 145, 158, 173, 189, 206, 225, 245, 267, 292, 318, 348, 379, 414, 452, 493, 538, 587, 640, 699, 763, 832, 908, 991, 1081, 1180, 1288, 1405, 1534, 1673, 1826, 1993, 2175, 2373, 2590, 2826, 3084, 3365, 3672, 4008, 4373, 4772, 5208, 5683, 6202, 6767, 7385, 8059, 8794, 9597, 10472, 11428, 12471, 13609, 14851, 16206, 17685, 19298, 21060, 22981, 25078, 27367, 29864, 32589, -29973, -26728, -23186, -19322, -15105, -10503, -5481, -1, 1, 1, 5481, 10503, 15105, 19322, 23186, 26728, 29973, -32589, -29864, -27367, -25078, -22981, -21060, -19298, -17685, -16206, -14851, -13609, -12471, -11428, -10472, -9597, -8794, -8059, -7385, -6767, -6202, -5683, -5208, -4772, -4373, -4008, -3672, -3365, -3084, -2826, -2590, -2373, -2175, -1993, -1826, -1673, -1534, -1405, -1288, -1180, -1081, -991, -908, -832, -763, -699, -640, -587, -538, -493, -452, -414, -379, -348, -318, -292, -267, -245, -225, -206, -189, -173, -158, -145, -133, -122, -112, -102, -94, -86, -79, -72, -66, -61, -56, -51, -47, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1 }; // macro to fetch 16-bit little-endian words from a bytestream #define LE_16(x) ((*x) | ((*(x+1)) << 8)) static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata) { InterplayDecoder* pId = (InterplayDecoder*)userdata; memcpy(buff, &pId->audio.samples[pId->audio.nRead], len); pId->audio.nRead += len / 2; if (pId->audio.nRead >= (int)countof(pId->audio.samples)) pId->audio.nRead = 0; return true; } InterplayDecoder::InterplayDecoder(bool soundenabled) { bIsPlaying = false; bAudioStarted = !soundenabled; // This prevents the stream from getting created nWidth = 0; nHeight = 0; nFrame = 0; memset(palette, 0, sizeof(palette)); memset(&audio, 0, sizeof(audio)); audio.nRead = 18000; // skip the initial silence. This is needed to sync audio and video because OpenAL's lag is a bit on the high side. nFps = 0.0; nFrameDuration = 0; nTimerRate = 0; nTimerDiv = 0; pVideoBuffers[0] = nullptr; pVideoBuffers[1] = nullptr; decodeMap.pData = nullptr; decodeMap.nSize = 0; nCurrentVideoBuffer = 0; nPreviousVideoBuffer = 1; videoStride = 0; } InterplayDecoder::~InterplayDecoder() { Close(); } void InterplayDecoder::SwapFrames() { int t = nPreviousVideoBuffer; nPreviousVideoBuffer = nCurrentVideoBuffer; nCurrentVideoBuffer = t; } void InterplayDecoder::Close() { fr.Close(); bIsPlaying = false; if (stream) S_StopCustomStream(stream); stream = nullptr; if (decodeMap.pData) { delete[] decodeMap.pData; decodeMap.pData = nullptr; } if (pVideoBuffers[0]) { delete[] pVideoBuffers[0]; pVideoBuffers[0] = nullptr; } if (pVideoBuffers[1]) { delete[] pVideoBuffers[1]; pVideoBuffers[1] = nullptr; } } bool InterplayDecoder::Open(FileReader &fr_) { // open the file (read only) char lsig[20]; // check the file signature fr_.Read((uint8_t*)lsig, sizeof(lsig)); if (memcmp(lsig, "Interplay MVE File\x1A\0", sizeof(lsig)) != 0) { Printf(TEXTCOLOR_RED "InterplayDecoder: Unknown MVE signature\n "); return false; } // skip the next 6 bytes fr_.Seek(6, FileReader::SeekCur); fr = std::move(fr_); //Run(); return true; } bool InterplayDecoder::RunFrame(uint64_t clock) { uint8_t chunkPreamble[CHUNK_PREAMBLE_SIZE]; uint8_t opcodePreamble[OPCODE_PREAMBLE_SIZE]; uint8_t opcodeType; uint8_t opcodeVersion; int opcodeSize, chunkSize; int chunkType = 0; // iterate through the chunks in the file do { // handle timing - wait until we're ready to process the next frame. if (nNextFrameTime > clock) { return true; } else { nNextFrameTime += nFrameDuration; } if (fr.Read(chunkPreamble, CHUNK_PREAMBLE_SIZE) != CHUNK_PREAMBLE_SIZE) { Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); return false; } chunkSize = LE_16(&chunkPreamble[0]); chunkType = LE_16(&chunkPreamble[2]); // iterate through individual opcodes while (chunkSize > 0) { if (fr.Read(opcodePreamble, OPCODE_PREAMBLE_SIZE) != OPCODE_PREAMBLE_SIZE) { Printf(TEXTCOLOR_RED "InterplayDecoder: could not read from file (EOF?)\n"); return false; } opcodeSize = LE_16(&opcodePreamble[0]); opcodeType = opcodePreamble[2]; opcodeVersion = opcodePreamble[3]; chunkSize -= OPCODE_PREAMBLE_SIZE; chunkSize -= opcodeSize; switch (opcodeType) { case OPCODE_END_OF_STREAM: { fr.Seek(opcodeSize, FileReader::SeekCur); break; } case OPCODE_END_OF_CHUNK: { fr.Seek(opcodeSize, FileReader::SeekCur); break; } case OPCODE_CREATE_TIMER: { nTimerRate = fr.ReadUInt32(); nTimerDiv = fr.ReadUInt16(); nFrameDuration = ((uint64_t)nTimerRate * nTimerDiv) * 1000; break; } case OPCODE_INIT_AUDIO_BUFFERS: { fr.Seek(2, FileReader::SeekCur); uint16_t flags = fr.ReadUInt16(); audio.nSampleRate = fr.ReadUInt16(); uint32_t nBufferBytes; if (opcodeVersion == 0) { nBufferBytes = fr.ReadUInt16(); } else { nBufferBytes = fr.ReadUInt32(); } if (flags & 0x1) { audio.nChannels = 2; } else { audio.nChannels = 1; } if (flags & 0x2) { audio.nBitDepth = 16; } else { audio.nBitDepth = 8; } break; } case OPCODE_START_STOP_AUDIO: { if (!bAudioStarted) { // start audio playback stream = S_CreateCustomStream(6000, audio.nSampleRate, audio.nChannels, StreamCallbackFunc, this); bAudioStarted = true; } fr.Seek(opcodeSize, FileReader::SeekCur); break; } case OPCODE_INIT_VIDEO_BUFFERS: { assert(opcodeSize == 8); nWidth = fr.ReadUInt16() * 8; nHeight = fr.ReadUInt16() * 8; int count = fr.ReadUInt16(); int truecolour = fr.ReadUInt16(); assert(truecolour == 0); pVideoBuffers[0] = new uint8_t[nWidth * nHeight]; pVideoBuffers[1] = new uint8_t[nWidth * nHeight]; videoStride = nWidth; animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight); break; } case OPCODE_UNKNOWN_06: case OPCODE_UNKNOWN_0E: case OPCODE_UNKNOWN_10: case OPCODE_UNKNOWN_12: case OPCODE_UNKNOWN_13: case OPCODE_UNKNOWN_14: case OPCODE_UNKNOWN_15: { fr.Seek(opcodeSize, FileReader::SeekCur); break; } case OPCODE_SEND_BUFFER: { int nPalStart = fr.ReadUInt16(); int nPalCount = fr.ReadUInt16(); animtex.SetFrame(&palette[0].r , GetCurrentFrame()); nFrame++; SwapFrames(); fr.Seek(opcodeSize-4, FileReader::SeekCur); break; } case OPCODE_AUDIO_FRAME: { int nStart = (int)fr.Tell(); uint16_t seqIndex = fr.ReadUInt16(); uint16_t streamMask = fr.ReadUInt16(); uint16_t nSamples = fr.ReadUInt16(); // number of samples this chunk int predictor[2]; int i = 0; for (int ch = 0; ch < audio.nChannels; ch++) { predictor[ch] = fr.ReadUInt16(); i++; if (predictor[ch] & 0x8000) { predictor[ch] |= 0xFFFF0000; // sign extend } audio.samples[audio.nWrite++] = predictor[ch]; if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; } int ch = 0; for (; i < (nSamples / 2); i++) { predictor[ch] += delta_table[fr.ReadUInt8()]; predictor[ch] = clamp(predictor[ch], -32768, 32768); audio.samples[audio.nWrite++] = predictor[ch]; if (audio.nWrite >= (int)countof(audio.samples)) audio.nWrite = 0; // toggle channel ch ^= audio.nChannels - 1; } int nEnd = (int)fr.Tell(); int nRead = nEnd - nStart; assert(opcodeSize == nRead); break; } case OPCODE_SILENCE_FRAME: { uint16_t seqIndex = fr.ReadUInt16(); uint16_t streamMask = fr.ReadUInt16(); uint16_t nStreamLen = fr.ReadUInt16(); break; } case OPCODE_INIT_VIDEO_MODE: { fr.Seek(opcodeSize, FileReader::SeekCur); break; } case OPCODE_CREATE_GRADIENT: { fr.Seek(opcodeSize, FileReader::SeekCur); Printf("InterplayDecoder: Create gradient not supported.\n"); break; } case OPCODE_SET_PALETTE: { if (opcodeSize > 0x304 || opcodeSize < 4) { Printf("set_palette opcode with invalid size\n"); chunkType = CHUNK_BAD; break; } int nPalStart = fr.ReadUInt16(); int nPalCount = fr.ReadUInt16(); for (int i = nPalStart; i <= nPalCount; i++) { palette[i].r = fr.ReadUInt8() << 2; palette[i].g = fr.ReadUInt8() << 2; palette[i].b = fr.ReadUInt8() << 2; } break; } case OPCODE_SET_PALETTE_COMPRESSED: { fr.Seek(opcodeSize, FileReader::SeekCur); Printf("InterplayDecoder: Set palette compressed not supported.\n"); break; } case OPCODE_SET_DECODING_MAP: { if (!decodeMap.pData) { decodeMap.pData = new uint8_t[opcodeSize]; decodeMap.nSize = opcodeSize; } else { if (opcodeSize != (int)decodeMap.nSize) { delete[] decodeMap.pData; decodeMap.pData = new uint8_t[opcodeSize]; decodeMap.nSize = opcodeSize; } } int nRead = (int)fr.Read(decodeMap.pData, opcodeSize); assert(nRead == opcodeSize); break; } case OPCODE_VIDEO_DATA: { int nStart = (int)fr.Tell(); // need to skip 14 bytes fr.Seek(14, FileReader::SeekCur); if (decodeMap.nSize) { int i = 0; for (uint32_t y = 0; y < nHeight; y += 8) { for (uint32_t x = 0; x < nWidth; x += 8) { uint32_t opcode; // alternate between getting low and high 4 bits if (i & 1) { opcode = decodeMap.pData[i >> 1] >> 4; } else { opcode = decodeMap.pData[i >> 1] & 0x0F; } i++; int32_t offset = x + (y * videoStride); switch (opcode) { default: break; case 0: DecodeBlock0(offset); break; case 1: DecodeBlock1(offset); break; case 2: DecodeBlock2(offset); break; case 3: DecodeBlock3(offset); break; case 4: DecodeBlock4(offset); break; case 5: DecodeBlock5(offset); break; case 7: DecodeBlock7(offset); break; case 8: DecodeBlock8(offset); break; case 9: DecodeBlock9(offset); break; case 10: DecodeBlock10(offset); break; case 11: DecodeBlock11(offset); break; case 12: DecodeBlock12(offset); break; case 13: DecodeBlock13(offset); break; case 14: DecodeBlock14(offset); break; case 15: DecodeBlock15(offset); break; } } } } int nEnd = (int)fr.Tell(); int nSkipBytes = opcodeSize - (nEnd - nStart); // we can end up with 1 byte left we need to skip assert(nSkipBytes <= 1); fr.Seek(nSkipBytes, FileReader::SeekCur); break; } default: break; } } } while (chunkType < CHUNK_VIDEO && bIsPlaying); return chunkType != CHUNK_END; } void InterplayDecoder::CopyBlock(uint8_t* pDest, uint8_t* pSrc) { for (int y = 0; y < 8; y++) { memcpy(pDest, pSrc, 8); pSrc += (intptr_t)videoStride; pDest += (intptr_t)videoStride; } } void InterplayDecoder::DecodeBlock0(int32_t offset) { // copy from the same offset but from the previous frame uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset; uint8_t* pSrc = GetPreviousFrame() + (intptr_t)offset; CopyBlock(pDest, pSrc); } void InterplayDecoder::DecodeBlock1(int32_t offset) { // nothing to do for this. } void InterplayDecoder::DecodeBlock2(int32_t offset) { // copy block from 2 frames ago using a motion vector; need 1 more byte uint8_t B = fr.ReadUInt8(); int x, y; if (B < 56) { x = 8 + (B % 7); y = B / 7; } else { x = -14 + ((B - 56) % 29); y = 8 + ((B - 56) / 29); } uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset; uint8_t* pSrc = GetCurrentFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride); CopyBlock(pDest, pSrc); } void InterplayDecoder::DecodeBlock3(int32_t offset) { // copy 8x8 block from current frame from an up/left block uint8_t B = fr.ReadUInt8(); int x, y; // need 1 more byte for motion if (B < 56) { x = -(8 + (B % 7)); y = -(B / 7); } else { x = -(-14 + ((B - 56) % 29)); y = -(8 + ((B - 56) / 29)); } uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset; uint8_t* pSrc = GetCurrentFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride); CopyBlock(pDest, pSrc); } void InterplayDecoder::DecodeBlock4(int32_t offset) { // copy a block from the previous frame; need 1 more byte int x, y; uint8_t B, BL, BH; B = fr.ReadUInt8(); BL = B & 0x0F; BH = (B >> 4) & 0x0F; x = -8 + BL; y = -8 + BH; uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset; uint8_t* pSrc = GetPreviousFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride); CopyBlock(pDest, pSrc); } void InterplayDecoder::DecodeBlock5(int32_t offset) { // copy a block from the previous frame using an expanded range; need 2 more bytes int8_t x = fr.ReadUInt8(); int8_t y = fr.ReadUInt8(); uint8_t* pDest = GetCurrentFrame() + (intptr_t)offset; uint8_t* pSrc = GetPreviousFrame() + (intptr_t)(int64_t)offset + (int64_t)x + (int64_t(y) * (int64_t)videoStride); CopyBlock(pDest, pSrc); } // Block6 is unknown and skipped void InterplayDecoder::DecodeBlock7(int32_t offset) { uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint32_t flags = 0; uint8_t P[2]; P[0] = fr.ReadUInt8(); P[1] = fr.ReadUInt8(); // 2-color encoding if (P[0] <= P[1]) { // need 8 more bytes from the stream for (int y = 0; y < 8; y++) { flags = fr.ReadUInt8() | 0x100; for (; flags != 1; flags >>= 1) { *pBuffer++ = P[flags & 1]; } pBuffer += (videoStride - 8); } } else { // need 2 more bytes from the stream flags = fr.ReadUInt16(); for (int y = 0; y < 8; y += 2) { for (int x = 0; x < 8; x += 2, flags >>= 1) { pBuffer[x] = pBuffer[x + 1] = pBuffer[x + videoStride] = pBuffer[x + 1 + videoStride] = P[flags & 1]; } pBuffer += videoStride * 2; } } } void InterplayDecoder::DecodeBlock8(int32_t offset) { uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint32_t flags = 0; uint8_t P[4]; // 2-color encoding for each 4x4 quadrant, or 2-color encoding on either top and bottom or left and right halves P[0] = fr.ReadUInt8(); P[1] = fr.ReadUInt8(); if (P[0] <= P[1]) { for (int y = 0; y < 16; y++) { // new values for each 4x4 block if (!(y & 3)) { if (y) { P[0] = fr.ReadUInt8(); P[1] = fr.ReadUInt8(); } flags = fr.ReadUInt16(); } for (int x = 0; x < 4; x++, flags >>= 1) { *pBuffer++ = P[flags & 1]; } pBuffer += videoStride - 4; // switch to right half if (y == 7) pBuffer -= 8 * videoStride - 4; } } else { flags = fr.ReadUInt32(); P[2] = fr.ReadUInt8(); P[3] = fr.ReadUInt8(); if (P[2] <= P[3]) { // vertical split; left & right halves are 2-color encoded for (int y = 0; y < 16; y++) { for (int x = 0; x < 4; x++, flags >>= 1) { *pBuffer++ = P[flags & 1]; } pBuffer += videoStride - 4; // switch to right half if (y == 7) { pBuffer -= 8 * videoStride - 4; P[0] = P[2]; P[1] = P[3]; flags = fr.ReadUInt32(); } } } else { // horizontal split; top & bottom halves are 2-color encoded for (int y = 0; y < 8; y++) { if (y == 4) { P[0] = P[2]; P[1] = P[3]; flags = fr.ReadUInt32(); } for (int x = 0; x < 8; x++, flags >>= 1) *pBuffer++ = P[flags & 1]; pBuffer += (videoStride - 8); } } } } void InterplayDecoder::DecodeBlock9(int32_t offset) { uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[4]; fr.Read(P, 4); // 4-color encoding if (P[0] <= P[1]) { if (P[2] <= P[3]) { // 1 of 4 colors for each pixel, need 16 more bytes for (int y = 0; y < 8; y++) { // get the next set of 8 2-bit flags int flags = fr.ReadUInt16(); for (int x = 0; x < 8; x++, flags >>= 2) { *pBuffer++ = P[flags & 0x03]; } pBuffer += (videoStride - 8); } } else { // 1 of 4 colors for each 2x2 block, need 4 more bytes uint32_t flags = fr.ReadUInt32(); for (int y = 0; y < 8; y += 2) { for (int x = 0; x < 8; x += 2, flags >>= 2) { pBuffer[x] = pBuffer[x + 1] = pBuffer[x + videoStride] = pBuffer[x + 1 + videoStride] = P[flags & 0x03]; } pBuffer += videoStride * 2; } } } else { // 1 of 4 colors for each 2x1 or 1x2 block, need 8 more bytes uint64_t flags = fr.ReadUInt64(); if (P[2] <= P[3]) { for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x += 2, flags >>= 2) { pBuffer[x] = pBuffer[x + 1] = P[flags & 0x03]; } pBuffer += videoStride; } } else { for (int y = 0; y < 8; y += 2) { for (int x = 0; x < 8; x++, flags >>= 2) { pBuffer[x] = pBuffer[x + videoStride] = P[flags & 0x03]; } pBuffer += videoStride * 2; } } } } void InterplayDecoder::DecodeBlock10(int32_t offset) { uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[8]; fr.Read(P, 4); // 4-color encoding for each 4x4 quadrant, or 4-color encoding on either top and bottom or left and right halves if (P[0] <= P[1]) { int flags = 0; // 4-color encoding for each quadrant; need 32 bytes for (int y = 0; y < 16; y++) { // new values for each 4x4 block if (!(y & 3)) { if (y) fr.Read(P, 4); flags = fr.ReadUInt32(); } for (int x = 0; x < 4; x++, flags >>= 2) { *pBuffer++ = P[flags & 0x03]; } pBuffer += videoStride - 4; // switch to right half if (y == 7) pBuffer -= 8 * videoStride - 4; } } else { // vertical split? int vert; uint64_t flags = fr.ReadUInt64(); fr.Read(P + 4, 4); vert = P[4] <= P[5]; // 4-color encoding for either left and right or top and bottom halves for (int y = 0; y < 16; y++) { for (int x = 0; x < 4; x++, flags >>= 2) *pBuffer++ = P[flags & 0x03]; if (vert) { pBuffer += videoStride - 4; // switch to right half if (y == 7) pBuffer -= 8 * videoStride - 4; } else if (y & 1) pBuffer += (videoStride - 8); // load values for second half if (y == 7) { memcpy(P, P + 4, 4); flags = fr.ReadUInt64(); } } } } void InterplayDecoder::DecodeBlock11(int32_t offset) { // 64-color encoding (each pixel in block is a different color) uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; for (int y = 0; y < 8; y++) { fr.Read(pBuffer, 8); pBuffer += videoStride; } } void InterplayDecoder::DecodeBlock12(int32_t offset) { // 16-color block encoding: each 2x2 block is a different color uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; for (int y = 0; y < 8; y += 2) { for (int x = 0; x < 8; x += 2) { pBuffer[x] = pBuffer[x + 1] = pBuffer[x + videoStride] = pBuffer[x + 1 + videoStride] = fr.ReadUInt8(); } pBuffer += videoStride * 2; } } void InterplayDecoder::DecodeBlock13(int32_t offset) { // 4-color block encoding: each 4x4 block is a different color uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[2]; for (int y = 0; y < 8; y++) { if (!(y & 3)) { P[0] = fr.ReadUInt8(); P[1] = fr.ReadUInt8(); } memset(pBuffer, P[0], 4); memset(pBuffer + 4, P[1], 4); pBuffer += videoStride; } } void InterplayDecoder::DecodeBlock14(int32_t offset) { // 1-color encoding : the whole block is 1 solid color uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t pix = fr.ReadUInt8(); for (int y = 0; y < 8; y++) { memset(pBuffer, pix, 8); pBuffer += videoStride; } } void InterplayDecoder::DecodeBlock15(int32_t offset) { // dithered encoding uint8_t* pBuffer = GetCurrentFrame() + (intptr_t)offset; uint8_t P[2]; P[0] = fr.ReadUInt8(); P[1] = fr.ReadUInt8(); for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x += 2) { *pBuffer++ = P[y & 1]; *pBuffer++ = P[!(y & 1)]; } pBuffer += (videoStride - 8); } } uint8_t* InterplayDecoder::GetCurrentFrame() { return pVideoBuffers[nCurrentVideoBuffer]; } uint8_t* InterplayDecoder::GetPreviousFrame() { return pVideoBuffers[nPreviousVideoBuffer]; }