Added an MmapFile helper and changed iffexplore and treexplore to use it. WIP: MveDecoder Misc. changes/fixes
295 lines
7.8 KiB
C++
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);
|
|
}
|
|
}
|