Files
wc3re/MveDecoder.cc

719 lines
19 KiB
C++

#include <algorithm>
#include <cstring>
#include "common.hh"
#include "compiler.hh"
#include "util.hh"
#include "IffFile.hh"
#include "decompress.hh"
#include "MveDecoder.hh"
MveDecoder::MveDecoder(Resource const& res)
: iff_(res), width_(320), height_(165)
{
//iff_.printStructure();
auto& root = iff_.getRoot();
if (!root.isForm())
throw FormatException{"Root node not FORM"};
auto& rootForm = dynamic_cast<IffFile::Form const&>(root);
if (rootForm.getSubtype() != "MOVE")
throw FormatException{"Root form not MOVE"};
auto it = rootForm.childrenBegin();
// Parse _PC_ chunk
auto& PC = *it++;
if ((PC.getType() != "_PC_") ||
(PC.size() != 12))
throw FormatException{"_PC_ chunk missing or wrong size"};
const uint32_t PC_PALTcount = readU32LE(PC.begin()+8);
#ifndef NDEBUG
printf("_PC_: %u PALTs\n", PC_PALTcount);
#endif
// // Parse SOND chunk
// auto& SOND = *it++;
// if ((SOND.getType() != "SOND") ||
// (SOND.size() != 4))
// throw FormatException{"SOND chunk missing or wrong size"};
// const uint32_t SONDc = readU32LE(SOND.begin());
// if (SONDc != 3)
// throw FormatException{"Unexpected SOND value"};
// Parse data
unsigned curPALT = 0, curBRCH = 0;
bool haveIndex = false;
// Store branch offsets, resolve to shots later
std::vector<uint32_t> branches;
Shot *curSHOT = nullptr;
for (;it != rootForm.childrenEnd();++it) {
if (it->getType() == "SIZE") {
if (it->size() != 8)
throw FormatException{"Unexpected SIZE size"};
width_ = readU32LE(it->begin());
height_ = readU32LE(it->begin()+4);
} else if (it->getType() == "PALT") {
if (curPALT > PC_PALTcount)
throw FormatException{"Number of PALT chunks exceeds amount specified in _PC_"};
if (it->size() != 768)
throw FormatException{"Unexpected PALT size"};
palts_.emplace_back();
std::transform(it->begin(), it->end(), palts_.back().begin(),
[](const char& in) -> uint8_t {return (in << 2) | ((in >> 6)&0x3);});
++curPALT;
} else if (it->getType() == "SHOT") {
if (!haveIndex) {
branches_.resize(1);
curBRCH = 0;
}
const uint32_t SHOTc = readU32LE(it->begin());
if (SHOTc > PC_PALTcount)
throw FormatException{"SHOT refers to PALT outside amount specified in _PC_"};
Shot shot{palts_.at(SHOTc), {}, {}};
branches_[curBRCH].push_back(shot);
curSHOT = &branches_[curBRCH].back();
} else if (it->getType() == "VGA ") {
if (!curSHOT)
throw FormatException{"VGA outside SHOT"};
curSHOT->VGAs.push_back(&*it);
} else if (it->getType() == "AUDI") {
if (!curSHOT)
throw FormatException{"AUDI outside SHOT"};
curSHOT->AUDIs.push_back(&*it);
} else if (it->getType() == "INDX") {
if (haveIndex)
throw FormatException{"Multiple INDX"};
if (curSHOT)
throw FormatException{"INDX after SHOT"};
haveIndex = true;
for(unsigned i = 0;i < it->size()/4;++i)
branches.push_back(readU32LE(it->begin()+i*4));
branches_.resize(branches.size());
} else if (it->getType() == "BRCH") {
size_t ofs = it->begin()-res.data()-8;
auto idxIt = std::find(branches.begin(), branches.end(), ofs);
if (idxIt == branches.end())
throw FormatException{"Could not resolve branch " + std::to_string(ofs)};
curBRCH = (idxIt-branches.begin());
} else if (it->getType() == "TEXT") {
// Subtitle NYI
} else if (it->getType() == "SOND") {
// ignore
} else
throw FormatException{"Encountered unexpected chunk: " + it->getType()};
}
#ifdef VIDDEBUG_
unsigned i = 0;
#endif
for (auto& brch : branches_) {
for (auto& shot : brch) {
#ifdef VIDDEBUG_
printf("Shot %u: Palette %ld, %lu video frames, %lu audio blocks\n",
i++, std::find(palts_.begin(), palts_.end(), shot.palt)-palts_.begin(),
shot.VGAs.size(), shot.AUDIs.size());
#endif
if ((shot.AUDIs.size() != 0) &&
(shot.VGAs.size() != shot.AUDIs.size()))
throw FormatException{"Video/audio block count mismatch: V " +
std::to_string(shot.VGAs.size()) + ", A " +
std::to_string(shot.AUDIs.size())};
}
}
}
template<typename OutputIt>
size_t parseHuff_(uint8_t const* data, size_t len, OutputIt commands, size_t maxOut)
{
const uint8_t numHuffVals = *data;
if (numHuffVals != 22)
throw FormatException{"Unexpected huffman tree size"};
std::array<uint8_t, 44> huffTree;
std::copy(data+1, data+45,
huffTree.begin());
// memcpy(huffTree.data(), data+1, 44);
#ifdef HUFFDEBUG_
printf("Coded data length: %d\nTree:\n", segOfs[1]-(segOfs[0]+45));
printf(" 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
printf("0x00: ");
for(unsigned i = 0;i < huffTree.size();++i) {
printf("%.2hhx", huffTree[i]);
if ((i+1)%16 == 0)
printf("\n0x%.2hhx: ", (i+1)&0xf0);
else
printf(" ");
}
printf("\n");
#endif
BitReader bitReader(data+45, len-45);
unsigned huffIdx = huffTree.size();
size_t count = 0;
while (huffIdx != 22) {
// Read next bit
unsigned bit = bitReader.getBit();
huffIdx = huffTree.at(huffIdx-(bit?1:23));
if (huffIdx < 22) {
*commands++ = huffIdx;
if (++count >= maxOut)
throw Exception{"Output buffer overflow"};
huffIdx = huffTree.size();
}
}
if (count == 0)
throw FormatException{"No commands decoded"};
return count;
}
size_t parsePixels_(uint8_t const* RESTRICT data, size_t len, uint8_t * RESTRICT out, size_t maxOut)
{
if (*data == 0x02) {
return decompressLZInto(data+1, len-1, out, maxOut);
} else {
if (len-1 > maxOut)
throw Exception{"Output buffer overflow"};
std::copy(data+1, data+len, out);
return len-1;
}
}
MveDecoder::Movie MveDecoder::open(size_t branch) const
{
auto& br = branches_.at(branch);
return Movie(br.cbegin(), br.cend(),
*this);
}
MveDecoder::Movie::Movie(std::vector<Shot>::const_iterator shotIt,
std::vector<Shot>::const_iterator shotEnd,
MveDecoder const& mve)
: shotIt_(shotIt), shotEnd_(shotEnd),
frameIt_(shotIt_->VGAs.cbegin()),
audioIt_(shotIt_->AUDIs.cbegin()),
mve_(mve), maxPixel_(0), maxCommand_(0)
{
pixelBuf_.resize(mve_.width_*mve_.height_);
commandBuf_.reserve(32768);
frameBuf_.reserve(mve_.width_*mve_.height_);
frame_.reserve(mve_.width_*mve_.height_);
}
MveDecoder::Movie::~Movie()
{
#ifndef NDEBUG
printf("Maximum command buffer: %zu\n", maxCommand_);
#endif
}
bool MveDecoder::Movie::hasNext() const {
if (shotIt_ == shotEnd_)
return false;
return true;
}
std::tuple<MveDecoder::Frame const&, MveDecoder::Palette const&> MveDecoder::Movie::decodeNext()
{
parseVGA(**frameIt_++);
auto& palette = shotIt_->palt;
if (frameIt_ == shotIt_->VGAs.cend()) {
++shotIt_;
if (shotIt_ != shotEnd_) {
frameIt_ = shotIt_->VGAs.cbegin();
audioIt_ = shotIt_->AUDIs.cbegin();
}
}
return std::make_tuple(std::ref(frame_), std::ref(palette));
}
bool MveDecoder::Movie::hasAudio() const
{
return (audioIt_ != shotIt_->AUDIs.cend());
}
MveDecoder::Audio MveDecoder::Movie::getNextAudio()
{
MveDecoder::Audio ret;
if ((shotIt_ != shotEnd_) && (audioIt_ != shotIt_->AUDIs.cend())) {
ret.reserve((shotIt_->AUDIs.cend()-audioIt_)*2940);
for(;audioIt_ != shotIt_->AUDIs.cend();++audioIt_)
for(auto it = (*audioIt_)->begin();it != (*audioIt_)->end();it += 2)
ret.push_back(readU16LE(it));
}
return ret;
}
unsigned MveDecoder::Movie::getWidth() const
{
return mve_.width_;
}
unsigned MveDecoder::Movie::getHeight() const
{
return mve_.height_;
}
void MveDecoder::Movie::parseVGA(IffFile::Object const& vga)
{
// Parse header
if (vga.size() < 8)
throw FormatException{"VGA chunk smaller than VGA header"};
std::array<uint16_t, 4> segOfs;
for(unsigned i = 0;i < segOfs.size();++i) {
segOfs[i] = readU16LE(vga.begin()+i*2);
if (segOfs[i] >= vga.size())
throw FormatException{"Segment offset exceeds chunk"};
}
// Parse segment 1
if (!segOfs[0] ||
((segOfs[1] && (segOfs[1] - segOfs[0]) < 45)) ||
((segOfs[2] && (segOfs[2] - segOfs[0]) < 45)) ||
((segOfs[3] && (segOfs[3] - segOfs[0]) < 45)) ||
(vga.size() - segOfs[0] < 45))
throw FormatException{"Segment 1 too small"};
commandBuf_.clear();
parseHuff_(vga.begin()+segOfs[0], segOfs[1]-segOfs[0],
std::back_inserter(commandBuf_), commandBuf_.capacity());
maxCommand_ = std::max(maxCommand_, commandBuf_.size());
size_t pixelCount = 0;
if (segOfs[3]) {
pixelCount = parsePixels_(vga.begin()+segOfs[3], vga.size()-segOfs[3],
pixelBuf_.data(), pixelBuf_.size());
maxPixel_ = std::max(maxPixel_, pixelCount);
}
#ifdef VIDDEBUG_
printf("%lu commands, %lu pixel data, %u mvecs, %lu size data\n",
commands.size(), pixels.size(), (segOfs[2]?(segOfs[3]-segOfs[2]):0),
segOfs[2]?(segOfs[2]-segOfs[1]):(segOfs[3]?(segOfs[3]-segOfs[1]):(vga.size()-segOfs[1])));
#endif
// Interpret command stream to render frame
frameBuf_.clear();
bool flag = false;
size_t seg2Idx = 0;
size_t seg3Idx = 0;
size_t seg4Idx = 0;
unsigned size = 0;
for (auto cmd : commandBuf_) {
#ifdef CMDDEBUG_
printf("CMD %u ", cmd);
#endif
switch(cmd) {
case 0:
flag = !flag;
continue;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
size = cmd;
break;
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
size = (cmd - 10);
break;
case 9:
case 19:
if (!segOfs[1] ||
(segOfs[2] && (segOfs[1]+seg2Idx >= segOfs[2])) ||
(segOfs[3] && (segOfs[1]+seg2Idx >= segOfs[3])) ||
(segOfs[1]+seg2Idx >= vga.size()))
throw FormatException{"Segment 2 overrun"};
size = vga.begin()[segOfs[1]+seg2Idx++]&0xff;
break;
case 10:
case 20:
if (!segOfs[1] ||
(segOfs[2] && (segOfs[1]+seg2Idx+1 >= segOfs[2])) ||
(segOfs[3] && (segOfs[1]+seg2Idx >= segOfs[3])) ||
(segOfs[1]+seg2Idx+1 >= vga.size()))
throw FormatException{"Segment 2 overrun"};
size = readU16BE(vga.begin()+segOfs[1]+seg2Idx);
seg2Idx += 2;
break;
case 11:
case 21:
if (!segOfs[1] ||
(segOfs[2] && (segOfs[1]+seg2Idx+2 >= segOfs[2])) ||
(segOfs[3] && (segOfs[1]+seg2Idx >= segOfs[3])) ||
(segOfs[1]+seg2Idx+2 >= vga.size()))
throw FormatException{"Segment 2 overrun"};
size = readU24BE(vga.begin()+segOfs[1]+seg2Idx);
seg2Idx += 3;
break;
default:
throw FormatException{"Invalid command"};
}
if (cmd < 12) {
flag = !flag;
if (flag) {
#ifdef CMDDEBUG_
printf("PrevCopy: %u\n", size);
#endif
if (frameBuf_.size()+size > frame_.size())
throw FormatException{"Copy from prev exceeds frame"};
std::copy(frame_.begin()+frameBuf_.size(),
frame_.begin()+frameBuf_.size()+size,
std::back_inserter(frameBuf_));
} else {
#ifdef CMDDEBUG_
printf("DataCopy: %u @%lu\n", size, seg4Idx);
#endif
if (seg4Idx+size > pixelCount)
throw FormatException{"Copy from seg4 exceeds bounds"};
std::copy(pixelBuf_.begin()+seg4Idx,
pixelBuf_.begin()+seg4Idx+size,
std::back_inserter(frameBuf_));
seg4Idx += size;
}
} else {
#ifdef CMDDEBUG_
printf("MvecCopy: %u\n", size);
#endif
if (!segOfs[2] ||
(segOfs[3] && (segOfs[2]+seg3Idx > segOfs[3])) ||
(segOfs[2]+seg3Idx > vga.size()))
throw FormatException{"Segment 3 overrun"};
const uint8_t mpos = vga.begin()[segOfs[2]+seg3Idx++];
const signed x = sextend(mpos>>4, 4), y = sextend(mpos&0xf,4);
const signed delta = y*mve_.width_+x;
if ((delta < 0) && (frameBuf_.size() < static_cast<size_t>(-delta)))
throw FormatException{"Motion vector outside frame"};
const unsigned ref = frameBuf_.size()+delta;
#ifdef CMDDEBUG_
printf("@%lu %.2hhx -> (%d, %d) -> d %d -> %u\n", seg3Idx-1, mpos, x, y, delta, ref);
#endif
if (ref+size > frame_.size())
throw FormatException{"Copy from prev exceeds frame"};
std::copy(frame_.begin()+ref,
frame_.begin()+ref+size,
std::back_inserter(frameBuf_));
flag = false;
}
}
if (frameBuf_.size() != mve_.width_*mve_.height_)
throw FormatException{"Decoded frame dimension mismatch"};
#ifndef NDEBUG
if (seg4Idx < pixelCount)
printf("%lu unused pixel data\n", pixelCount-seg4Idx);
#endif
frame_.swap(frameBuf_);
}
/*
#include <SDL2/SDL.h>
struct AudioCBData {
SDL_AudioSpec spec;
MveDecoder::Shot const *cur, *next;
std::vector<IffFile::Object const*>::const_iterator aCur;
size_t pos;
};
void audioCB_(void* userdata, uint8_t *buf, int len)
{
AudioCBData *cbdata = static_cast<AudioCBData*>(userdata);
if (!cbdata->cur && cbdata->next) {
cbdata->cur = cbdata->next;
cbdata->next = nullptr;
cbdata->aCur = cbdata->cur->AUDIs.begin();
cbdata->pos = 0;
}
while ((len > 0) && cbdata->cur) {
if (cbdata->pos >= (*cbdata->aCur)->size()) {
cbdata->pos = 0;
++cbdata->aCur;
if (cbdata->aCur == cbdata->cur->AUDIs.end()) {
cbdata->cur = cbdata->next;
if (cbdata->next) {
cbdata->next = nullptr;
cbdata->aCur = cbdata->cur->AUDIs.begin();
continue;
}
break;
}
}
auto& aCur = *cbdata->aCur;
size_t toCopy = len;
if (cbdata->pos+toCopy > aCur->size())
toCopy = aCur->size()-cbdata->pos;
std::copy(aCur->begin()+cbdata->pos, aCur->begin()+cbdata->pos+toCopy,
buf);
len -= toCopy;
buf += toCopy;
cbdata->pos += toCopy;
}
if (len) {
memset(buf, cbdata->spec.silence, len);
return;
}
}
void MveDecoder::play(unsigned branch) const
{
SDL_Window *window;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
printf("Could not init SDL: %s\n", SDL_GetError());
return;
}
window = SDL_CreateWindow("SDL2 Test",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
width_*2,
// 1.2 scale in height to compensate for non-square pixels in height
// in original MCGA/VGA 320x200 mode
height_*2*1.2,
0);
if (!window) {
printf("Could not create window: %s\n", SDL_GetError());
SDL_Quit();
return;
}
SDL_Surface *screen = SDL_GetWindowSurface(window);
if (!screen) {
printf("Could not get window surface: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
SDL_Palette *palette = SDL_AllocPalette(256);
if(!palette) {
printf("Could not create palette: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
auto shot = branches_.at(branch).begin();
auto curShot = shot++;
std::array<SDL_Color, 256> colors;
for(unsigned i = 0;i < 256;++i) {
colors[i].r = curShot->palt[i*3];
colors[i].g = curShot->palt[i*3+1];
colors[i].b = curShot->palt[i*3+2];
colors[i].a = 255;
}
if (SDL_SetPaletteColors(palette, colors.data(), 0, 256) != 0) {
printf("Could not set palette: %s\n", SDL_GetError());
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
AudioCBData audioCBData;
if (curShot->AUDIs.size() > 0) {
audioCBData.cur = &*curShot;
audioCBData.aCur = curShot->AUDIs.cbegin();
} else {
audioCBData.cur = nullptr;
}
audioCBData.next = nullptr;
audioCBData.pos = 0;
SDL_AudioSpec want;
want.freq = 22050;
want.format = AUDIO_S16LSB;
want.channels = 1;
want.samples = 4096;
want.callback = &audioCB_;
want.userdata = &audioCBData;
auto audioDev = SDL_OpenAudioDevice(nullptr, false, &want, &audioCBData.spec, 0);
if (!audioDev) {
printf("Failed to open audio: %s\n", SDL_GetError());
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
auto vga = curShot->VGAs.begin();
uint32_t last = 0;
const double frameTime = 1000/15.0;
double nextFT = frameTime;
Frame frame = parseVGA(**vga++, nullptr);
decltype(last) minFT = std::numeric_limits<decltype(last)>::max(), maxFT = 0;
bool close = false;
SDL_PauseAudioDevice(audioDev, 0);
while (!close) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch(event.type) {
case SDL_KEYDOWN:
close = true;
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_CLOSE)
close = true;
break;
}
}
auto now = SDL_GetTicks();
if (now-last >= nextFT) {
if (last) {
minFT = std::min(minFT, (now-last));
maxFT = std::max(maxFT, (now-last));
if ((now-last)-nextFT <= frameTime)
nextFT = frameTime-((now-last)-nextFT);
else {
printf("NYI: Frame dropping\n");
nextFT = frameTime;
}
}
SDL_Surface *vidSurf = SDL_CreateRGBSurfaceFrom((void*)frame.pixels.data(), width_, height_, 8, width_, 0, 0, 0, 0);
if (!vidSurf) {
printf("Could not get video surface: %s\n", SDL_GetError());
SDL_CloseAudioDevice(audioDev);
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
if (SDL_SetSurfacePalette(vidSurf, palette) != 0) {
printf("Could not set surface palette: %s\n", SDL_GetError());
SDL_FreeSurface(vidSurf);
SDL_CloseAudioDevice(audioDev);
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
SDL_Surface *vidSurfRGB = SDL_ConvertSurface(vidSurf, screen->format, 0);
if (!vidSurfRGB) {
printf("Could not convert video surface: %s\n", SDL_GetError());
SDL_FreeSurface(vidSurf);
SDL_CloseAudioDevice(audioDev);
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
if (SDL_BlitScaled(vidSurfRGB, nullptr, screen, nullptr) != 0) {
printf("Could not blit video surface: %s\n", SDL_GetError());
SDL_FreeSurface(vidSurfRGB);
SDL_FreeSurface(vidSurf);
SDL_CloseAudioDevice(audioDev);
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
SDL_UpdateWindowSurface(window);
last = now;
SDL_FreeSurface(vidSurfRGB);
SDL_FreeSurface(vidSurf);
// Decode next frame
if (vga == curShot->VGAs.end()) {
if (shot == branches_.at(branch).end())
break;
else {
curShot = shot++;
vga = curShot->VGAs.begin();
std::array<SDL_Color, 256> colors;
for(unsigned i = 0;i < 256;++i) {
colors[i].r = curShot->palt[i*3];
colors[i].g = curShot->palt[i*3+1];
colors[i].b = curShot->palt[i*3+2];
colors[i].a = 255;
}
if (SDL_SetPaletteColors(palette, colors.data(), 0, 256) != 0) {
printf("Could not set palette: %s\n", SDL_GetError());
SDL_CloseAudioDevice(audioDev);
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
return;
}
SDL_LockAudioDevice(audioDev);
audioCBData.next = &*curShot;
SDL_UnlockAudioDevice(audioDev);
}
}
frame = parseVGA(**vga++, &frame);
} else if ((nextFT-(now-last)) >= 10)
SDL_Delay(nextFT-(now-last));
}
SDL_PauseAudioDevice(audioDev, 1);
SDL_CloseAudioDevice(audioDev);
SDL_FreePalette(palette);
SDL_DestroyWindow(window);
SDL_Quit();
printf("Frame times: [%u, %u]\n",
minFT, maxFT);
}
*/