Files
wc3re/MveDecoder.cc
Matthias Blankertz ae51cd24c4 MmapFile helper; MveDecoder WIP
Added an MmapFile helper and changed iffexplore and treexplore to use it.
WIP: MveDecoder
Misc. changes/fixes
2015-04-24 22:13:32 +02:00

295 lines
7.8 KiB
C++

#include <algorithm>
#include "common.hh"
#include "IffFile.hh"
#include "MveDecoder.hh"
struct PCChunk {
uint32_t unknown[2];
uint32_t PALTcount;
} __attribute__((__packed__));
MveDecoder::MveDecoder(char const* base, size_t length)
: iff_(base, length)
{
//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.getSize() != 12))
throw FormatException{"_PC_ chunk missing or wrong size"};
PCChunk const* PCc = reinterpret_cast<PCChunk const*>(PC.begin());
#ifndef NDEBUG
printf("_PC_: %u PALTs\n", PCc->PALTcount);
#endif
// Parse SOND chunk
auto& SOND = *it++;
if ((SOND.getType() != "SOND") ||
(SOND.getSize() != 4))
throw FormatException{"SOND chunk missing or wrong size"};
uint32_t const* SONDc = reinterpret_cast<uint32_t const*>(SOND.begin());
if (*SONDc != 3)
throw FormatException{"Unexpected SOND value"};
// Parse data
int curSHOT = -1;
unsigned curPALT = 0;
std::vector<IffFile::Object const*> PALTs;
for (;it != rootForm.childrenEnd();++it) {
if (it->getType() == "PALT") {
if (curPALT > PCc->PALTcount)
throw FormatException{"Number of PALT chunks exceeds amount specified in _PC_"};
if (it->getSize() != 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") {
uint32_t const* SHOTc = reinterpret_cast<uint32_t const*>(it->begin());
if (*SHOTc > PCc->PALTcount)
throw FormatException{"SHOT refers to PALT outside amount specified in _PC_"};
++curSHOT;
Shot shot{palts_.at(*SHOTc), {}, {}};
shots_.push_back(shot);
} else if (it->getType() == "VGA ") {
if (curSHOT < 0)
throw FormatException{"VGA outside SHOT"};
shots_.back().VGAs.push_back(parseVGA(*it, shots_.back().VGAs.empty()?nullptr:&shots_.back().VGAs.back()));
} else if (it->getType() == "AUDI") {
if (curSHOT < 0)
throw FormatException{"AUDI outside SHOT"};
if (it->getSize() != 2940)
throw FormatException{"Unexpected AUDI size"};
shots_.back().AUDIs.push_back(&*it);
} else if ((it->getType() == "INDX") ||
(it->getType() == "BRCH")) {
// Index/branches NYI
} else
throw FormatException{"Encountered unexpected chunk: " + it->getType()};
}
unsigned i = 0;
for(auto& shot : shots_) {
#ifndef NDEBUG
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.VGAs.size() != shot.AUDIs.size())
throw FormatException{"Video/audio block count mismatch"};
}
}
char *binify(uint8_t a) {
static char buf[9];
buf[8] = '\0';
for(unsigned i = 0;i < 8;++i) {
if(a&0x80)
buf[i] = '1';
else
buf[i] = '0';
a <<= 1;
}
return buf;
}
std::vector<char> parseHuff_(char const* data, size_t len)
{
uint8_t numHuffVals;
memcpy(&numHuffVals, data, 1);
if (numHuffVals != 22)
throw FormatException{"Unexpected huffman tree size"};
std::array<uint8_t, 44> huffTree;
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
int byteValid = 0;
uint8_t byteBuf = 0;
unsigned bytePos = 45;
std::vector<char> commands;
unsigned huffIdx = huffTree.size();
while (huffIdx != 22) {
// Read next bit
unsigned bit;
if (byteValid) {
bit = byteBuf&0x1;
byteBuf >>= 1;
--byteValid;
} else {
if (bytePos >= len)
throw FormatException{"Huffman stream overrun"};
memcpy(&byteBuf, data+bytePos, 1);
//printf("Byte: %.2hhx (%s)\n", byteBuf, binify(byteBuf));
++bytePos;
bit = byteBuf&0x1;
byteBuf >>= 1;
byteValid = 7;
}
huffIdx = huffTree.at(huffIdx-(bit?1:23));
//printf("step: %d %.2x\n", bit, huffIdx);
if (huffIdx < 22) {
commands.push_back(huffIdx);
//printf("out: %.2hhx\n", commands.back());
huffIdx = huffTree.size();
}
}
if (commands.empty())
throw FormatException{"No commands decoded"};
#ifdef HUFFDEBUG_
for(auto ent : commands)
printf("%.2hhx ", ent);
printf("\n");
#endif
return commands;
}
std::vector<uint8_t> parsePixels_(char const* data, size_t len)
{
std::vector<uint8_t> ret;
if (*data == 0x02) {
size_t pos = 1;
while (pos < len) {
uint8_t b = *(data+pos++);
if (!((b&0xe0)==0xe0)) {
unsigned size = 0, replSize = 0;
unsigned replOfs = 0;
if (!(b&0x80)) {
//printf("Code: Repl1 %.2hhx\n", b);
if (pos >= len)
throw FormatException{"Pixel data overrun"};
uint8_t ofs = *(data+pos++);
size = b&0x3;
if (pos+size >= len)
throw FormatException{"Pixel data overrun"};
replSize = ((b&0x1c)>>2) + 3;
replOfs = ret.size()-(((b&0x60)<<3)+ofs+1)+size;
} else if (!(b&0x40)) {
//printf("Code: Repl3 %.2hhx\n", b);
if (pos+1 >= len)
throw FormatException{"Pixel data overrun"};
uint8_t b1 = *(data+pos++);
uint8_t b2 = *(data+pos++);
size = (b1&0xc0)>>6;
if (pos+size >= len)
throw FormatException{"Pixel data overrun"};
replSize = (b&0x3f)+4;
replOfs = ret.size()-(((b1&0x3f)<<8)+b2+1)+size;
} else if (!(b&0x20)) {
//printf("Code: Repl2 %.2hhx\n", b);
if (pos+2 >= len)
throw FormatException{"Pixel data overrun"};
uint8_t b1 = *(data+pos++);
uint8_t b2 = *(data+pos++);
uint8_t b3 = *(data+pos++);
size = b&0x3;
if (pos+size >= len)
throw FormatException{"Pixel data overrun"};
replSize = b3+5+((b&0xc)<<6);
replOfs = ret.size()-(((b&0x10)<<12)+1+(b1<<8)+b2)+size;
}
for (unsigned i = 0;i < size;++i)
ret.push_back(*(data+pos++));
for (unsigned i = 0;i < replSize;++i)
ret.push_back(ret[replOfs+i]);
} else {
//printf("Code: Copy %.2hhx\n", b);
unsigned size = (b&0x1f)*4+4;
if (size > 0x70)
break;
if (pos+size > len)
throw FormatException{"Pixel data overrun"};
for (unsigned i = 0;i < size;++i)
ret.push_back(*(data+pos++));
}
}
} else
std::copy(data+1, data+len, std::back_inserter(ret));
return ret;
}
MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* prev)
{
// Parse header
if (vga.getSize() < 8)
throw FormatException{"VGA chunk smaller than VGA header"};
std::array<uint16_t, 4> segOfs;
memcpy(segOfs.data(), vga.begin(), 8);
for(unsigned i = 0;i < segOfs.size();++i) {
if (segOfs[i] >= vga.getSize())
throw FormatException{"Segment offset exceeds chunk"};
}
// Parse segment 1
if ((segOfs[1] - segOfs[0]) < 45)
throw FormatException{"Segment 1 too small"};
auto commands = parseHuff_(vga.begin()+segOfs[0], segOfs[1]-segOfs[0]);
std::vector<uint8_t> pixels;
if (segOfs[3] > 0)
pixels = parsePixels_(vga.begin()+segOfs[3], vga.getSize()-segOfs[3]);
if (pixels.size() == 52800) {
FILEUPtr outFile{fopen("pixel.data", "wb")};
if (!outFile)
throw POSIXException{errno, "Could not open pixel.data"};
if (fwrite(pixels.data(), pixels.size(), 1, outFile.get()) != 1)
throw POSIXException{errno, "Could not write"};
// exit(0);
}
}