#include #include #include #include #include #include #define R32(x) (*(uint32_t*)&(x)) #define R 0x01 #define L 0x02 #define U 0x04 #define D 0x08 #define B 0x10 #define A 0x20 #define SE 0x40 #define ST 0x80 static const std::wstring ReadAscii(const unsigned char* s, unsigned len) { std::wstring result; while(len--) result += *s++; return result; } static const std::wstring ReadU16str(const unsigned char* s, unsigned len) { std::wstring result; while(len > 0) { result += (*s) | (s[1] << 8); s += 2; } return result; } static const std::wstring ReadUTF8(const unsigned char* s, unsigned len) { std::wstring result; const unsigned char*const end = s+len; /* Compared to the beast found in iconvircproxy, * this utf8 decoder is quite nice. But it isn't * as robust. */ while(s> 4) { case 0x0F: val &= 0x07; goto b3; case 0x0E: val &= 0x0F; goto b2; case 0x0D: case 0x0C: val &= 0x1F; goto b1; default: val &= 0x7F; break; b3: val <<= 6; val |= (*s++) & 63; b2: val <<= 6; val |= (*s++) & 63; b1: val <<= 6; val |= (*s++) & 63; } result += (wchar_t)val; } return result; } static void Write32(std::vector& target, uint32_t value) { target.push_back( } static void Write16(std::vector& target, uint16_t value) { target.push_back(value & 0xFF); target.push_Back(value >> 8); } static void Write32(std::vector& target, uint32_t value) { Write16(target, value & 0xFFFF); Write16(target, value >> 16); } static void WriteAsciiFZ(std::vector& target, const std::wstring& s, unsigned length) { for(unsigned a=0; a& data) { } // load fceu save LoadFCS(const std::vector& data) { } // load virtuanes save LoadVMS(const std::vector& data) { } }; class Movie { bool PAL; bool Save; unsigned FrameCount; unsigned RecordCount; bool Ctrl1; bool Ctrl2; bool Ctrl3; bool Ctrl4; bool FDS; std::wstring EmuName; std::wstring MovieName; std::wstring ROMName; std::map C1data, C2data, C3data, C4data, FDSdata; SaveState State; public: Movie() { } bool ReadFMV(const std::vector& data) { if(R32(data[0]) != 0x1A564D46) return false; PAL = false; Save = data[4] & 0x80; Ctrl1 = data[5] & 0x80; Ctrl2 = data[5] & 0x40; Ctrl3 = Ctrl4 = false; FDS = data[5] & 0x20; FrameCount = data.size() - 0x90; RecordCount = R32(data[0x0A]) + 1; EmuName = ReadAscii(&data[0x10], std::strlen(&data[0x10])); MovieName = ReadAscii(&data[0x50], std::strlen(&data[0x50])); /* TODO: Read save if exists */ unsigned pos = 0x90; for(unsigned frame=0; frame& data) { if(R32(data[0]) != 0x1A4D4346) return false; if(R32(data[4]) != 2) return false; PAL = data[8] & 0x04; Save = true; /* Set to false if Reset command encountered. */ Ctrl1 = Ctrl2 = Ctrl3 = Ctrl4 = FDS = false; /* ^ fill in when encountered in input. */ FrameCount = R32(data[0x0C]); RecordCount = R32(data[0x10]); unsigned SaveOffs = R32(data[0x18]); unsigned CtrlOffs = R32(data[0x1C]); unsigned CtrlLength = R32(data[0x14]); /* TODO: Read md5sum */ unsigned rnlength = std::strlen(&data[0x34]); ROMName = ReadUTF8(&data[0x34], rnlength); unsigned mnlength = std::strlen(&data[0x34+rnlength+1]); MovieName = ReadUTF8(&data[0x34+rnlength+1], mnlength); std::vector statedata(data.begin()+SaveOffs, data.begin()+CtrlOffs); State.LoadFCS(statedata); unsigned char joop[4] = {0,0,0,0}; unsigned frame = 0, pos = CtrlOffs; while(CtrlLength > 0) { bool Type = data[pos]; unsigned NDelta = (data[pos] >> 5) & 3; unsigned Data = data[pos] & 0x1F; ++pos; --CtrlLength; if(Type == false) // Controller data { unsigned ctrlno = (Data >> 3); /* convert FCM buttons to FMV buttons */ static const unsigned char masks[8] = {A,B,SE,ST,U,D,L,R}; unsigned char result = (joop[ctrlno] ^= masks[Data & 7]); if(ctrlno == 0) { Ctrl1 = true; C1data[frame] = result; } if(ctrlno == 1) { Ctrl2 = true; C2data[frame] = result; } if(ctrlno == 2) { Ctrl3 = true; C3data[frame] = result; } if(ctrlno == 3) { Ctrl4 = true; C4data[frame] = result; } } else // Control data switch(Data) { case 0: break; // nothing case 1: Save=false; break; // reset case 2: break; // power cycle case 7: break; // VS coin case 8: break; // VS dip0 case 24: FDS=true; FDSdata[frame] |= 1; break; /* FDS insert, FIXME */ case 25: FDS=true; FDSdata[frame] |= 2; break; /* FDS eject, FIXME */ case 26: FDS=true; FDSdata[frame] |= 4; break; /* FDS swap, FIXME */ } unsigned delta = 0; switch(NDelta) { case 0: break; case 1: delta |= data[pos++]; break; case 2: delta |= data[pos++]; delta |= data[pos++] << 8; break; case 3: delta |= data[pos++]; delta |= data[pos++] << 8; delta |= data[pos++] << 16; break; } while(delta > 0) { ++frame; C1data[frame] = C1data[frame-1]; C2data[frame] = C2data[frame-1]; C3data[frame] = C3data[frame-1]; C4data[frame] = C4data[frame-1]; --delta; } if(CtrlLength > NDelta) CtrlLength -= NDelta; else CtrlLength = 0; } return true; } bool ReadVMV(const std::vector& data) { if(ReadAscii(&data[0], 12) != L"VirtuaNES MV") return false; Save = true; Ctrl1 = data[0x10] & 0x01; Ctrl2 = data[0x10] & 0x02; Ctrl3 = data[0x10] & 0x04; Ctrl4 = data[0x10] & 0x08; RecordCount = R32(data[0x1C]); // TODO: ROM CRC // TODO: render method // TODO: irq type // TODO: frame ireq PAL = data[0x23] & 0x1; unsigned SaveBegin = R32(data[0x2C]); unsigned SaveEnd = R32(data[0x30]); unsigned CtrlBegin = R32(data[0x2C]); unsigned CtrlEnd = R32(data[0x30]); // TODO: Movie CRC std::vector statedata(data.begin()+SaveBegin, data.begin()+SaveEnd); State.LoadVMS(savedata); bool Masks[4] = {Ctrl1,Ctrl2,Ctrl3,Ctrl4}; unsigned pos = CtrlBegin; unsigned frame = 0; unsigned ctrl = 0; while(pos < CtrlEnd) { unsigned char Data = data[pos++]; if(Data >= 0xF0) // command? { if(Data == 0xF0) { if(data[pos+1] == 0) { /* command (NESCOMMAND) data[pos], 0 */ } else { /* command NESCMD_EXCONTROLLER, data[pos] */ /* sets the extra controller to given value */ } pos += 2; } else if(Data == 0xF3) { unsigned dwdata = R32(data[pos]); pos += 4; /* SetSyncExData(dwdata) */ } } else { unsigned char Ctrl=0; if(data&0x01) Ctrl|=A; if(data&0x02) Ctrl|=B; if(data&0x04) Ctrl|=SE; if(data&0x08) Ctrl|=ST; if(data&0x10) Ctrl|=U; if(data&0x20) Ctrl|=D; if(data&0x40) Ctrl|=L; if(data&0x80) Ctrl|=R; /* This may look strange, but it's what VirtuaNES does. */ while(!Masks[ctrl] && ctrl < 4) ++ctrl; switch(ctrl) { case 0: C1data[frame] = Ctrl; break; case 1: C2data[frame] = Ctrl; break; case 2: C3data[frame] = Ctrl; break; case 3: C4data[frame] = Ctrl; break; } while(!Masks[ctrl] && ctrl < 4) ++ctrl; if(ctrl >= 4) { ctrl = 0; ++frame; } } } return true; } bool ReadNSM(const std::vector& data) { return false; } void WriteFMV(std::vector& data) { Write32(data, 0x1A564D46); data.push_back(0); unsigned char tmp; if(Ctrl1) tmp|= 0x80; if(Ctrl2) tmp|= 0x40; if(FDS) tmp|= 0x20; data.push_back(tmp); Write32(data, 0); Write32(data, RecordCount-1); Write16(data, 0); WriteAsciiFZ(data, EmuName, 64); WriteAsciiFZ(data, MovieName, 64); for(unsigned a=0; a& data) { } void WriteVMV(std::vector& data) { } void WriteNSM(std::vector& data) { } }; int main(void) { }