diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7b09ca7d3..8c93ef5bd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -19,6 +19,7 @@ #include "engine.h" #include "../ta-log.h" +#include "instrument.h" #include "song.h" #include #include @@ -2034,11 +2035,28 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { return success; } +#define CHECK_BLOCK_VERSION(x) \ + if (blockVersion>x) { \ + logE("incompatible block version %d for %s!",blockVersion,blockName); \ + lastError="incompatible block version"; \ + delete[] file; \ + return false; \ + } + bool DivEngine::loadFTM(unsigned char* file, size_t len) { SafeReader reader=SafeReader(file,len); warnings=""; try { DivSong ds; + String blockName; + unsigned char expansions=0; + unsigned int tchans=0; + unsigned int n163Chans=0; + bool hasSequence[256][8]; + unsigned char sequenceIndex[256][8]; + + memset(hasSequence,0,256*8*sizeof(bool)); + memset(sequenceIndex,0,256*8); if (!reader.seek(18,SEEK_SET)) { logE("premature end of file!"); @@ -2046,17 +2064,327 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { delete[] file; return false; } - ds.version=(unsigned short)reader.readS(); + ds.version=(unsigned short)reader.readI(); logI("module version %d (0x%.4x)",ds.version,ds.version); - if (ds.version>0x0440) { + if (ds.version>0x0450) { logE("incompatible version %x!",ds.version); lastError="incompatible version"; delete[] file; return false; } - + while (true) { + blockName=reader.readString(3); + if (blockName=="END") { + // end of module + logD("end of data"); + break; + } + + // not the end + reader.seek(-3,SEEK_CUR); + blockName=reader.readString(16); + unsigned int blockVersion=(unsigned int)reader.readI(); + unsigned int blockSize=(unsigned int)reader.readI(); + size_t blockStart=reader.tell(); + + logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize); + if (blockName=="PARAMS") { + CHECK_BLOCK_VERSION(6); + unsigned int oldSpeedTempo=0; + if (blockVersion<=1) { + oldSpeedTempo=reader.readI(); + } + if (blockVersion>=2) { + expansions=reader.readC(); + } + tchans=reader.readI(); + unsigned int pal=reader.readI(); + unsigned int customHz=reader.readI(); + unsigned int newVibrato=0; + unsigned int speedSplitPoint=0; + if (blockVersion>=3) { + newVibrato=reader.readI(); + } + if (blockVersion>=4) { + ds.hilightA=reader.readI(); + ds.hilightB=reader.readI(); + } + if (expansions&8) if (blockVersion>=5) { // N163 channels + n163Chans=reader.readI(); + } + if (blockVersion>=6) { + speedSplitPoint=reader.readI(); + } + + logV("old speed/tempo: %d",oldSpeedTempo); + logV("expansions: %x",expansions); + logV("channels: %d",tchans); + logV("PAL: %d",pal); + logV("custom Hz: %d",customHz); + logV("new vibrato: %d",newVibrato); + logV("N163 channels: %d",n163Chans); + logV("highlight 1: %d",ds.hilightA); + logV("highlight 2: %d",ds.hilightB); + logV("split point: %d",speedSplitPoint); + + if (customHz!=0) { + ds.hz=customHz; + } + + // initialize channels + int systemID=0; + ds.system[systemID++]=DIV_SYSTEM_NES; + if (expansions&1) { + ds.system[systemID++]=DIV_SYSTEM_VRC6; + } + if (expansions&2) { + ds.system[systemID++]=DIV_SYSTEM_VRC7; + } + if (expansions&4) { + ds.system[systemID++]=DIV_SYSTEM_FDS; + } + if (expansions&8) { + ds.system[systemID++]=DIV_SYSTEM_MMC5; + } + if (expansions&16) { + ds.system[systemID]=DIV_SYSTEM_N163; + ds.systemFlags[systemID++]=n163Chans; + } + if (expansions&32) { + ds.system[systemID]=DIV_SYSTEM_AY8910; + ds.systemFlags[systemID++]=38; // Sunsoft 5B + } + ds.systemLen=systemID; + + unsigned int calcChans=0; + for (int i=0; iDIV_MAX_CHANS) { + tchans=DIV_MAX_CHANS; + logW("too many channels!"); + } + } else if (blockName=="INFO") { + CHECK_BLOCK_VERSION(1); + ds.name=reader.readString(32); + ds.author=reader.readString(32); + ds.copyright=reader.readString(32); + } else if (blockName=="HEADER") { + CHECK_BLOCK_VERSION(3); + unsigned char totalSongs=reader.readC(); + logV("%d songs:",totalSongs+1); + for (int i=0; i<=totalSongs; i++) { + String subSongName=reader.readString(); + logV("- %s",subSongName); + } + for (unsigned int i=0; i256) { + logE("too many instruments/out of range!"); + lastError="too many instruments/out of range"; + delete[] file; + return false; + } + + for (int i=0; i=ds.ins.size()) { + logE("instrument index %d is out of range!",insIndex); + lastError="instrument index out of range"; + delete[] file; + return false; + } + + DivInstrument* ins=ds.ins[insIndex]; + unsigned char insType=reader.readC(); + switch (insType) { + case 1: + ins->type=DIV_INS_STD; + break; + case 2: // TODO: tell VRC6 and VRC6 saw instruments apart + ins->type=DIV_INS_VRC6; + break; + case 3: + ins->type=DIV_INS_OPLL; + break; + case 4: + ins->type=DIV_INS_FDS; + break; + case 5: + ins->type=DIV_INS_N163; + break; + case 6: // 5B? + ins->type=DIV_INS_AY; + break; + default: { + logE("%d: invalid instrument type %d",insIndex,insType); + lastError="invalid instrument type"; + delete[] file; + return false; + } + } + + // instrument data + switch (ins->type) { + case DIV_INS_STD: { + unsigned int totalSeqs=reader.readI(); + if (totalSeqs>5) { + logE("%d: too many sequences!",insIndex); + lastError="too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j=0; j=2)?96:72; + for (int j=0; jamiga.noteMap[j]=(short)((unsigned char)reader.readC())-1; + ins->amiga.noteFreq[j]=(unsigned char)reader.readC(); + if (blockVersion>=6) { + reader.readC(); // DMC value + } + } + break; + } + case DIV_INS_VRC6: { + unsigned int totalSeqs=reader.readI(); + if (totalSeqs>4) { + logE("%d: too many sequences!",insIndex); + lastError="too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j=0; jfm.opllPreset=(unsigned int)reader.readI(); + // TODO + break; + } + case DIV_INS_FDS: { + DivWavetable* wave=new DivWavetable; + wave->len=64; + wave->max=64; + for (int j=0; j<64; j++) { + wave->data[j]=reader.readC(); + } + ins->std.waveMacro.len=1; + ins->std.waveMacro.val[0]=ds.wave.size(); + for (int j=0; j<32; j++) { + ins->fds.modTable[j]=reader.readC()-3; + } + ins->fds.modSpeed=reader.readI(); + ins->fds.modDepth=reader.readI(); + reader.readI(); // this is delay. currently ignored. TODO. + ds.wave.push_back(wave); + + ins->std.volMacro.len=reader.readC(); + ins->std.volMacro.loop=reader.readI(); + ins->std.volMacro.rel=reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j=0; jstd.volMacro.len; j++) { + ins->std.volMacro.val[j]=reader.readC(); + } + + ins->std.arpMacro.len=reader.readC(); + ins->std.arpMacro.loop=reader.readI(); + ins->std.arpMacro.rel=reader.readI(); + ins->std.arpMacro.mode=reader.readI(); + for (int j=0; jstd.arpMacro.len; j++) { + ins->std.arpMacro.val[j]=reader.readC(); + } + + ins->std.pitchMacro.len=reader.readC(); + ins->std.pitchMacro.loop=reader.readI(); + ins->std.pitchMacro.rel=reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j=0; jstd.pitchMacro.len; j++) { + ins->std.pitchMacro.val[j]=reader.readC(); + } + + break; + } + case DIV_INS_N163: { + // TODO! + break; + } + // TODO: 5B! + default: { + logE("%d: what's going on here?",insIndex); + lastError="invalid instrument type"; + delete[] file; + return false; + } + } + + // name + ins->name=reader.readString((unsigned int)reader.readI()); + logV("- %d: %s",insIndex,ins->name); + } + } else if (blockName=="SEQUENCES") { + CHECK_BLOCK_VERSION(6); + } else if (blockName=="FRAMES") { + CHECK_BLOCK_VERSION(3); + } else if (blockName=="PATTERNS") { + CHECK_BLOCK_VERSION(5); + } else if (blockName=="DPCM SAMPLES") { + CHECK_BLOCK_VERSION(1); + } else if (blockName=="SEQUENCES_VRC6") { + // where are the 5B and FDS sequences? + CHECK_BLOCK_VERSION(6); + } else if (blockName=="SEQUENCES_N163") { + CHECK_BLOCK_VERSION(1); + } else if (blockName=="COMMENTS") { + CHECK_BLOCK_VERSION(1); + } else { + logE("block %s is unknown!",blockName); + lastError="unknown block "+blockName; + delete[] file; + return false; + } + + if ((reader.tell()-blockStart)!=blockSize) { + logE("block %s is incomplete!",blockName); + lastError="incomplete block "+blockName; + delete[] file; + return false; + } + } } catch (EndOfFileException& e) { logE("premature end of file!"); lastError="incomplete file";