diff --git a/NScumm.Audio.Player/NScumm.Audio.ALPlayer.csproj b/NScumm.Audio.Player/NScumm.Audio.ALPlayer.csproj index d661e22..aad5757 100644 --- a/NScumm.Audio.Player/NScumm.Audio.ALPlayer.csproj +++ b/NScumm.Audio.Player/NScumm.Audio.ALPlayer.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 diff --git a/NScumm.Audio.Players.Tests/NScumm.Audio.Players.Tests.csproj b/NScumm.Audio.Players.Tests/NScumm.Audio.Players.Tests.csproj index 642034b..a98e0b2 100644 --- a/NScumm.Audio.Players.Tests/NScumm.Audio.Players.Tests.csproj +++ b/NScumm.Audio.Players.Tests/NScumm.Audio.Players.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + net5.0 false diff --git a/NScumm.Audio.Players/AdlPlayer.cs b/NScumm.Audio.Players/AdlPlayer.cs index 9ccb921..cc0056e 100644 --- a/NScumm.Audio.Players/AdlPlayer.cs +++ b/NScumm.Audio.Players/AdlPlayer.cs @@ -60,119 +60,124 @@ public bool Load(string path) { return false; } - Init(); using (var fs = File.OpenRead(path)) { - if (fs.Length < 720) return false; + return Load(fs); + } + } - unk2(); - unk1(); + public bool Load(Stream stream) + { + Init(); + if (stream.Length < 720) return false; + + unk2(); + unk1(); - // detect format version - var br = new BinaryReader(fs); - _version = 3; // assuming we have v3 - for (int i = 0; i < 120; i += 2) + // detect format version + var br = new BinaryReader(stream); + _version = 3; // assuming we have v3 + for (int i = 0; i < 120; i += 2) + { + ushort w = br.ReadUInt16(); + // all entries should be in range 0..500-1 or 0xFFFF + if (w >= 500 && w < 0xffff) { - ushort w = br.ReadUInt16(); - // all entries should be in range 0..500-1 or 0xFFFF - if (w >= 500 && w < 0xffff) - { - _version = 1; // actually 1 or 2 - break; - } + _version = 1; // actually 1 or 2 + break; } - if (_version == 1) - { // detect whether v1 or v2 - fs.Seek(120, SeekOrigin.Begin); - _version = 2; // assuming we have v2 - for (int i = 0; i < 150; i += 2) - { - ushort w = br.ReadUInt16(); - if (w > 0 && w < 600) - { // minimum track offset for v1 is 600 - return false; - } - // minimum track offset for v2 is 1000 - if (w > 0 && w < 1000) - _version = 1; + } + if (_version == 1) + { // detect whether v1 or v2 + stream.Seek(120, SeekOrigin.Begin); + _version = 2; // assuming we have v2 + for (int i = 0; i < 150; i += 2) + { + ushort w = br.ReadUInt16(); + if (w > 0 && w < 600) + { // minimum track offset for v1 is 600 + return false; } + // minimum track offset for v2 is 1000 + if (w > 0 && w < 1000) + _version = 1; } - if (_version == 2 && fs.Length < 1120) - { // minimum file size of v2 - return false; - } - if (_version == 3 && fs.Length < 2500) - { // minimum file size of v3 - return false; - } + } + if (_version == 2 && stream.Length < 1120) + { // minimum file size of v2 + return false; + } + if (_version == 3 && stream.Length < 2500) + { // minimum file size of v3 + return false; + } - fs.Seek(0, SeekOrigin.Begin); - var file_size = (int)fs.Length; + stream.Seek(0, SeekOrigin.Begin); + var file_size = (int)stream.Length; - _driver.snd_unkOpcode3(-1); - _soundDataPtr = null; + _driver.snd_unkOpcode3(-1); + _soundDataPtr = null; - ushort _EntriesSize; - if (_version < 3) - { - _EntriesSize = 120; - _trackEntries = br.ReadBytes(_EntriesSize); - } - else + ushort _EntriesSize; + if (_version < 3) + { + _EntriesSize = 120; + _trackEntries = br.ReadBytes(_EntriesSize); + } + else + { + _EntriesSize = 250 * 2; + for (int i = 0; i < 250; i++) { - _EntriesSize = 250 * 2; - for (int i = 0; i < 250; i++) - { - _trackEntries16[i] = br.ReadUInt16(); - } + _trackEntries16[i] = br.ReadUInt16(); } + } - int soundDataSize = file_size - _EntriesSize; + int soundDataSize = file_size - _EntriesSize; - _soundDataPtr = br.ReadBytes(soundDataSize); + _soundDataPtr = br.ReadBytes(soundDataSize); - file_size = 0; + file_size = 0; - _driver.snd_setSoundData(_soundDataPtr); + _driver.snd_setSoundData(_soundDataPtr); - // _soundFileLoaded = file; + // _soundFileLoaded = file; - // find last subsong - ushort maxEntry = 0xffff; - switch (_version) - { - case 1: - maxEntry = 150 - 1; - break; - case 2: - maxEntry = 250 - 1; + // find last subsong + ushort maxEntry = 0xffff; + switch (_version) + { + case 1: + maxEntry = 150 - 1; + break; + case 2: + maxEntry = 250 - 1; + break; + case 3: + maxEntry = 500 - 1; + break; + } + if (_version < 3) + { + for (int i = 120 - 1; i >= 0; i--) + if (_trackEntries[i] <= maxEntry) + { + numsubsongs = i + 1; break; - case 3: - maxEntry = 500 - 1; + } + } + else + { + for (int i = 250 - 1; i >= 0; i--) + if (_trackEntries16[i] <= maxEntry) + { + numsubsongs = i + 1; break; - } - if (_version < 3) - { - for (int i = 120 - 1; i >= 0; i--) - if (_trackEntries[i] <= maxEntry) - { - numsubsongs = i + 1; - break; - } - } - else - { - for (int i = 250 - 1; i >= 0; i--) - if (_trackEntries16[i] <= maxEntry) - { - numsubsongs = i + 1; - break; - } - } - - cursubsong = -1; - return true; + } } + + cursubsong = -1; + return true; } public bool Update() diff --git a/NScumm.Audio.Players/BamPlayer.cs b/NScumm.Audio.Players/BamPlayer.cs index dfd8665..d5ad1b7 100644 --- a/NScumm.Audio.Players/BamPlayer.cs +++ b/NScumm.Audio.Players/BamPlayer.cs @@ -67,9 +67,16 @@ public BamPlayer(IOpl opl) public bool Load(string path) { using (var fs = File.OpenRead(path)) - using (var br = new BinaryReader(fs)) { - size = fs.Length - 4; + return Load(fs); + } + } + + public bool Load(Stream stream) + { + using (var br = new BinaryReader(stream)) + { + size = stream.Length - 4; var id = new string(br.ReadChars(4)); if (!string.Equals(id, "CBMF", StringComparison.OrdinalIgnoreCase)) return false; diff --git a/NScumm.Audio.Players/CmfPlayer.cs b/NScumm.Audio.Players/CmfPlayer.cs index cd10b56..1ab1374 100644 --- a/NScumm.Audio.Players/CmfPlayer.cs +++ b/NScumm.Audio.Players/CmfPlayer.cs @@ -163,117 +163,122 @@ public bool Load(string path) { using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - var cSig = new string(br.ReadChars(4)); - if (cSig != "CTMF") - { - // Not a CMF file - return false; - } + return Load(fs); + } + } - ushort iVer = br.ReadUInt16(); - if ((iVer != 0x0101) && (iVer != 0x0100)) - { - Console.Error.WriteLine($"CMF file is not v1.0 or v1.1 (reports {iVer >> 8}.{iVer & 0xFF})"); - return false; - } + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + var cSig = new string(br.ReadChars(4)); + if (cSig != "CTMF") + { + // Not a CMF file + return false; + } - cmfHeader.iInstrumentBlockOffset = br.ReadUInt16(); - cmfHeader.iMusicOffset = br.ReadUInt16(); - cmfHeader.iTicksPerQuarterNote = br.ReadUInt16(); - cmfHeader.iTicksPerSecond = br.ReadUInt16(); - cmfHeader.iTagOffsetTitle = br.ReadUInt16(); - cmfHeader.iTagOffsetComposer = br.ReadUInt16(); - cmfHeader.iTagOffsetRemarks = br.ReadUInt16(); - - // This checks will fix crash for a lot of broken files - // Title, Composer and Remarks blocks usually located before Instrument block - // But if not this will indicate invalid offset value (sometimes even bigger than filesize) - if (cmfHeader.iTagOffsetTitle >= cmfHeader.iInstrumentBlockOffset) - cmfHeader.iTagOffsetTitle = 0; - if (cmfHeader.iTagOffsetComposer >= cmfHeader.iInstrumentBlockOffset) - cmfHeader.iTagOffsetComposer = 0; - if (cmfHeader.iTagOffsetRemarks >= cmfHeader.iInstrumentBlockOffset) - cmfHeader.iTagOffsetRemarks = 0; - - cmfHeader.iChannelsInUse = br.ReadBytes(16); - if (iVer == 0x0100) - { - cmfHeader.iNumInstruments = br.ReadByte(); - cmfHeader.iTempo = 0; - } - else - { // 0x0101 - cmfHeader.iNumInstruments = br.ReadUInt16(); - cmfHeader.iTempo = br.ReadUInt16(); - } + ushort iVer = br.ReadUInt16(); + if ((iVer != 0x0101) && (iVer != 0x0100)) + { + Console.Error.WriteLine($"CMF file is not v1.0 or v1.1 (reports {iVer >> 8}.{iVer & 0xFF})"); + return false; + } - // Load the instruments + cmfHeader.iInstrumentBlockOffset = br.ReadUInt16(); + cmfHeader.iMusicOffset = br.ReadUInt16(); + cmfHeader.iTicksPerQuarterNote = br.ReadUInt16(); + cmfHeader.iTicksPerSecond = br.ReadUInt16(); + cmfHeader.iTagOffsetTitle = br.ReadUInt16(); + cmfHeader.iTagOffsetComposer = br.ReadUInt16(); + cmfHeader.iTagOffsetRemarks = br.ReadUInt16(); + + // This checks will fix crash for a lot of broken files + // Title, Composer and Remarks blocks usually located before Instrument block + // But if not this will indicate invalid offset value (sometimes even bigger than filesize) + if (cmfHeader.iTagOffsetTitle >= cmfHeader.iInstrumentBlockOffset) + cmfHeader.iTagOffsetTitle = 0; + if (cmfHeader.iTagOffsetComposer >= cmfHeader.iInstrumentBlockOffset) + cmfHeader.iTagOffsetComposer = 0; + if (cmfHeader.iTagOffsetRemarks >= cmfHeader.iInstrumentBlockOffset) + cmfHeader.iTagOffsetRemarks = 0; + + cmfHeader.iChannelsInUse = br.ReadBytes(16); + if (iVer == 0x0100) + { + cmfHeader.iNumInstruments = br.ReadByte(); + cmfHeader.iTempo = 0; + } + else + { // 0x0101 + cmfHeader.iNumInstruments = br.ReadUInt16(); + cmfHeader.iTempo = br.ReadUInt16(); + } - fs.Seek(cmfHeader.iInstrumentBlockOffset, SeekOrigin.Begin); - pInstruments = new SBI[ - (cmfHeader.iNumInstruments < 128) ? 128 : cmfHeader.iNumInstruments - ]; // Always at least 128 available for use + // Load the instruments - for (int i = 0; i < cmfHeader.iNumInstruments; i++) - { - pInstruments[i].op = new OPERATOR[2]; - pInstruments[i].op[0].iCharMult = br.ReadByte(); - pInstruments[i].op[1].iCharMult = br.ReadByte(); - pInstruments[i].op[0].iScalingOutput = br.ReadByte(); - pInstruments[i].op[1].iScalingOutput = br.ReadByte(); - pInstruments[i].op[0].iAttackDecay = br.ReadByte(); - pInstruments[i].op[1].iAttackDecay = br.ReadByte(); - pInstruments[i].op[0].iSustainRelease = br.ReadByte(); - pInstruments[i].op[1].iSustainRelease = br.ReadByte(); - pInstruments[i].op[0].iWaveSel = br.ReadByte(); - pInstruments[i].op[1].iWaveSel = br.ReadByte(); - pInstruments[i].iConnection = br.ReadByte(); - fs.Seek(5, SeekOrigin.Current); // skip over the padding bytes - } + stream.Seek(cmfHeader.iInstrumentBlockOffset, SeekOrigin.Begin); + pInstruments = new SBI[ + (cmfHeader.iNumInstruments < 128) ? 128 : cmfHeader.iNumInstruments + ]; // Always at least 128 available for use - // Set the rest of the instruments to the CMF defaults - for (int i = cmfHeader.iNumInstruments; i < 128; i++) - { - pInstruments[i].op = new OPERATOR[2]; - pInstruments[i].op[0].iCharMult = cDefaultPatches[(i % 16) * 11 + 0]; - pInstruments[i].op[1].iCharMult = cDefaultPatches[(i % 16) * 11 + 1]; - pInstruments[i].op[0].iScalingOutput = cDefaultPatches[(i % 16) * 11 + 2]; - pInstruments[i].op[1].iScalingOutput = cDefaultPatches[(i % 16) * 11 + 3]; - pInstruments[i].op[0].iAttackDecay = cDefaultPatches[(i % 16) * 11 + 4]; - pInstruments[i].op[1].iAttackDecay = cDefaultPatches[(i % 16) * 11 + 5]; - pInstruments[i].op[0].iSustainRelease = cDefaultPatches[(i % 16) * 11 + 6]; - pInstruments[i].op[1].iSustainRelease = cDefaultPatches[(i % 16) * 11 + 7]; - pInstruments[i].op[0].iWaveSel = cDefaultPatches[(i % 16) * 11 + 8]; - pInstruments[i].op[1].iWaveSel = cDefaultPatches[(i % 16) * 11 + 9]; - pInstruments[i].iConnection = cDefaultPatches[(i % 16) * 11 + 10]; - } + for (int i = 0; i < cmfHeader.iNumInstruments; i++) + { + pInstruments[i].op = new OPERATOR[2]; + pInstruments[i].op[0].iCharMult = br.ReadByte(); + pInstruments[i].op[1].iCharMult = br.ReadByte(); + pInstruments[i].op[0].iScalingOutput = br.ReadByte(); + pInstruments[i].op[1].iScalingOutput = br.ReadByte(); + pInstruments[i].op[0].iAttackDecay = br.ReadByte(); + pInstruments[i].op[1].iAttackDecay = br.ReadByte(); + pInstruments[i].op[0].iSustainRelease = br.ReadByte(); + pInstruments[i].op[1].iSustainRelease = br.ReadByte(); + pInstruments[i].op[0].iWaveSel = br.ReadByte(); + pInstruments[i].op[1].iWaveSel = br.ReadByte(); + pInstruments[i].iConnection = br.ReadByte(); + stream.Seek(5, SeekOrigin.Current); // skip over the padding bytes + } - if (cmfHeader.iTagOffsetTitle != 0) - { - fs.Seek(cmfHeader.iTagOffsetTitle, SeekOrigin.Begin); - strTitle = ReadString(br); - } - if (cmfHeader.iTagOffsetComposer != 0) - { - fs.Seek(cmfHeader.iTagOffsetComposer, SeekOrigin.Begin); - strComposer = ReadString(br); - } - if (cmfHeader.iTagOffsetRemarks != 0) - { - fs.Seek(cmfHeader.iTagOffsetRemarks, SeekOrigin.Begin); - strRemarks = ReadString(br); - } + // Set the rest of the instruments to the CMF defaults + for (int i = cmfHeader.iNumInstruments; i < 128; i++) + { + pInstruments[i].op = new OPERATOR[2]; + pInstruments[i].op[0].iCharMult = cDefaultPatches[(i % 16) * 11 + 0]; + pInstruments[i].op[1].iCharMult = cDefaultPatches[(i % 16) * 11 + 1]; + pInstruments[i].op[0].iScalingOutput = cDefaultPatches[(i % 16) * 11 + 2]; + pInstruments[i].op[1].iScalingOutput = cDefaultPatches[(i % 16) * 11 + 3]; + pInstruments[i].op[0].iAttackDecay = cDefaultPatches[(i % 16) * 11 + 4]; + pInstruments[i].op[1].iAttackDecay = cDefaultPatches[(i % 16) * 11 + 5]; + pInstruments[i].op[0].iSustainRelease = cDefaultPatches[(i % 16) * 11 + 6]; + pInstruments[i].op[1].iSustainRelease = cDefaultPatches[(i % 16) * 11 + 7]; + pInstruments[i].op[0].iWaveSel = cDefaultPatches[(i % 16) * 11 + 8]; + pInstruments[i].op[1].iWaveSel = cDefaultPatches[(i % 16) * 11 + 9]; + pInstruments[i].iConnection = cDefaultPatches[(i % 16) * 11 + 10]; + } + + if (cmfHeader.iTagOffsetTitle != 0) + { + stream.Seek(cmfHeader.iTagOffsetTitle, SeekOrigin.Begin); + strTitle = ReadString(br); + } + if (cmfHeader.iTagOffsetComposer != 0) + { + stream.Seek(cmfHeader.iTagOffsetComposer, SeekOrigin.Begin); + strComposer = ReadString(br); + } + if (cmfHeader.iTagOffsetRemarks != 0) + { + stream.Seek(cmfHeader.iTagOffsetRemarks, SeekOrigin.Begin); + strRemarks = ReadString(br); + } - // Load the MIDI data into memory - fs.Seek(cmfHeader.iMusicOffset, SeekOrigin.Begin); - iSongLen = (int)(fs.Length - cmfHeader.iMusicOffset); - data = br.ReadBytes(iSongLen); + // Load the MIDI data into memory + stream.Seek(cmfHeader.iMusicOffset, SeekOrigin.Begin); + iSongLen = (int)(stream.Length - cmfHeader.iMusicOffset); + data = br.ReadBytes(iSongLen); - Rewind(0); + Rewind(0); - return true; - } + return true; } public bool Update() diff --git a/NScumm.Audio.Players/Dro2Player.cs b/NScumm.Audio.Players/Dro2Player.cs index eba001e..c8a61ec 100644 --- a/NScumm.Audio.Players/Dro2Player.cs +++ b/NScumm.Audio.Players/Dro2Player.cs @@ -26,189 +26,194 @@ namespace NScumm.Audio.Players { - public class Dro2Player: IMusicPlayer + public class Dro2Player : IMusicPlayer { - private byte iCmdDelayS, iCmdDelayL; - private int iConvTableLen; - private byte[] piConvTable; - - private byte[] data; - private int iLength; - private int iPos; - private int iDelay; - private string author; - private string desc; - private string title; - - public IOpl Opl { get; } - - public float RefreshRate - { - get - { - if (iDelay > 0) return 1000.0f / iDelay; - else return 1000.0f; - } - } - - public Dro2Player(IOpl opl) - { - if (opl == null) throw new ArgumentNullException(nameof(opl)); - Opl = opl; - } - - public bool Load(string path) - { - using (var fs = File.OpenRead(path)) - { - var br = new BinaryReader(fs); - var id = new string(br.ReadChars(8)); - if (id != "DBRAWOPL") return false; - - var version = br.ReadInt32(); - if (version != 0x2) return false; - - iLength = br.ReadInt32(); // should better use an unsigned type - if (iLength <= 0 || iLength >= 1 << 30 || - iLength > br.BaseStream.Length - br.BaseStream.Position) - { - return false; - } - iLength *= 2; // stored in file as number of byte p - br.BaseStream.Seek(4, SeekOrigin.Current); // Length in milliseconds - br.BaseStream.Seek(1, SeekOrigin.Current); /// OPL type (0 == OPL2, 1 == Dual OPL2, 2 == OPL3) - int iFormat = br.ReadByte(); - if (iFormat != 0) - { - return false; - } - int iCompression = br.ReadByte(); - if (iCompression != 0) - { - return false; - } - iCmdDelayS = br.ReadByte(); - iCmdDelayL = br.ReadByte(); - iConvTableLen = br.ReadByte(); - - piConvTable = br.ReadBytes(iConvTableLen); - - // Read the OPL data. - data = br.ReadBytes(iLength); - - int tagsize = (int)(br.BaseStream.Length - br.BaseStream.Position); - if (tagsize >= 3) - { - // The arbitrary Tag Data section begins here. - if (br.ReadByte() != 0xFF || - br.ReadByte() != 0xFF || - br.ReadByte() != 0x1A) - { - // Tag data does not present or truncated. - goto end_section; - } - - // "title" is maximum 40 characters long. - title = ReadString(br, 40); - - // Skip "author" if Tag marker byte is missing. - if (br.ReadByte() != 0x1B) - { - br.BaseStream.Seek(-1, SeekOrigin.Current); - goto desc_section; - } - - // "author" is maximum 40 characters long. - author = ReadString(br, 40); - - desc_section: - // Skip "desc" if Tag marker byte is missing. - if (br.ReadByte() != 0x1C) - { - goto end_section; - } - - // "desc" is now maximum 1023 characters long (it was 140). - desc = ReadString(br, 1023); - } - - end_section: - Rewind(0); - - return true; - } - } - - public bool Update() + private byte iCmdDelayS, iCmdDelayL; + private int iConvTableLen; + private byte[] piConvTable; + + private byte[] data; + private int iLength; + private int iPos; + private int iDelay; + private string author; + private string desc; + private string title; + + public IOpl Opl { get; } + + public float RefreshRate + { + get + { + if (iDelay > 0) return 1000.0f / iDelay; + else return 1000.0f; + } + } + + public Dro2Player(IOpl opl) + { + if (opl == null) throw new ArgumentNullException(nameof(opl)); + Opl = opl; + } + + public bool Load(string path) + { + using (var fs = File.OpenRead(path)) + { + return Load(fs); + } + } + + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + var id = new string(br.ReadChars(8)); + if (id != "DBRAWOPL") return false; + + var version = br.ReadInt32(); + if (version != 0x2) return false; + + iLength = br.ReadInt32(); // should better use an unsigned type + if (iLength <= 0 || iLength >= 1 << 30 || + iLength > br.BaseStream.Length - br.BaseStream.Position) + { + return false; + } + iLength *= 2; // stored in file as number of byte p + br.BaseStream.Seek(4, SeekOrigin.Current); // Length in milliseconds + br.BaseStream.Seek(1, SeekOrigin.Current); /// OPL type (0 == OPL2, 1 == Dual OPL2, 2 == OPL3) + int iFormat = br.ReadByte(); + if (iFormat != 0) + { + return false; + } + int iCompression = br.ReadByte(); + if (iCompression != 0) + { + return false; + } + iCmdDelayS = br.ReadByte(); + iCmdDelayL = br.ReadByte(); + iConvTableLen = br.ReadByte(); + + piConvTable = br.ReadBytes(iConvTableLen); + + // Read the OPL data. + data = br.ReadBytes(iLength); + + int tagsize = (int)(br.BaseStream.Length - br.BaseStream.Position); + if (tagsize >= 3) + { + // The arbitrary Tag Data section begins here. + if (br.ReadByte() != 0xFF || + br.ReadByte() != 0xFF || + br.ReadByte() != 0x1A) + { + // Tag data does not present or truncated. + goto end_section; + } + + // "title" is maximum 40 characters long. + title = ReadString(br, 40); + + // Skip "author" if Tag marker byte is missing. + if (br.ReadByte() != 0x1B) + { + br.BaseStream.Seek(-1, SeekOrigin.Current); + goto desc_section; + } + + // "author" is maximum 40 characters long. + author = ReadString(br, 40); + + desc_section: + // Skip "desc" if Tag marker byte is missing. + if (br.ReadByte() != 0x1C) + { + goto end_section; + } + + // "desc" is now maximum 1023 characters long (it was 140). + desc = ReadString(br, 1023); + } + + end_section: + Rewind(0); + + return true; + } + + public bool Update() { - while (iPos < iLength) - { - int iIndex = data[iPos++]; - int iValue = data[iPos++]; - - // Short delay - if (iIndex == iCmdDelayS) - { - iDelay = iValue + 1; - return true; - - // Long delay - } - else if (iIndex == iCmdDelayL) - { - iDelay = (iValue + 1) << 8; - return true; - - // Normal write - } - else - { - if ((iIndex & 0x80)!=0) - { - // High bit means use second chip in dual-OPL2 config - // TODO:? - //Opl.setchip(1); - iIndex &= 0x7F; - } - else - { - // TODO:? - //Opl.Setchip(0); - } - if (iIndex >= iConvTableLen) - { - Console.WriteLine("DRO2: Error - index beyond end of codemap table! Corrupted .dro?\n"); - return false; // EOF - } - int iReg = piConvTable[iIndex]; - Opl.WriteReg(iReg, iValue); - } - - } - - // This won't result in endless-play using Adplay, but IMHO that code belongs - // in Adplay itself, not here. - return iPos < iLength; - } - - public void Rewind(int subsong) + while (iPos < iLength) + { + int iIndex = data[iPos++]; + int iValue = data[iPos++]; + + // Short delay + if (iIndex == iCmdDelayS) + { + iDelay = iValue + 1; + return true; + + // Long delay + } + else if (iIndex == iCmdDelayL) + { + iDelay = (iValue + 1) << 8; + return true; + + // Normal write + } + else + { + if ((iIndex & 0x80) != 0) + { + // High bit means use second chip in dual-OPL2 config + // TODO:? + //Opl.setchip(1); + iIndex &= 0x7F; + } + else + { + // TODO:? + //Opl.Setchip(0); + } + if (iIndex >= iConvTableLen) + { + Console.WriteLine("DRO2: Error - index beyond end of codemap table! Corrupted .dro?\n"); + return false; // EOF + } + int iReg = piConvTable[iIndex]; + Opl.WriteReg(iReg, iValue); + } + + } + + // This won't result in endless-play using Adplay, but IMHO that code belongs + // in Adplay itself, not here. + return iPos < iLength; + } + + public void Rewind(int subsong) { - iDelay = 0; - iPos = 0; - } + iDelay = 0; + iPos = 0; + } - private static string ReadString(BinaryReader br, int maxLength) + private static string ReadString(BinaryReader br, int maxLength) { - char c; - int i = 0; - var text = new StringBuilder(); - while ((c = br.ReadChar()) != 0 && i - internal sealed class DroPlayer: IMusicPlayer + internal sealed class DroPlayer : IMusicPlayer { private const byte iCmdDelayS = 0x00; private const byte iCmdDelayL = 0x01; @@ -61,82 +61,87 @@ public bool Load(string path) { using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - var id = new string(br.ReadChars(8)); - if (id != "DBRAWOPL") return false; - - var version = br.ReadInt32(); - if (version != 0x10000) return false; - - var lengthInMs = br.ReadInt32(); - var length = br.ReadInt32(); - _data = new byte[length]; - - // Some early .DRO files only used one byte for the hardware type, then - // later changed to four bytes with no version number change. - // OPL type (0 == OPL2, 1 == OPL3, 2 == Dual OPL2) - br.ReadChar(); // Type of opl data this can contain - ignored - int i; - for (i = 0; i < 3; i++) + return Load(fs); + } + } + + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + var id = new string(br.ReadChars(8)); + if (id != "DBRAWOPL") return false; + + var version = br.ReadInt32(); + if (version != 0x10000) return false; + + var lengthInMs = br.ReadInt32(); + var length = br.ReadInt32(); + _data = new byte[length]; + + // Some early .DRO files only used one byte for the hardware type, then + // later changed to four bytes with no version number change. + // OPL type (0 == OPL2, 1 == OPL3, 2 == Dual OPL2) + br.ReadChar(); // Type of opl data this can contain - ignored + int i; + for (i = 0; i < 3; i++) + { + _data[i] = br.ReadByte(); + } + + if (_data[0] == 0 || _data[1] == 0 || _data[2] == 0) + { + // If we're here then this is a later (more popular) file with + // the full four bytes for the hardware-type. + i = 0; // so ignore the three bytes we just read and start again + } + + // Read the OPL data. + br.BaseStream.Read(_data, i, length - i); + + var tagsize = stream.Length - stream.Position; + if (tagsize >= 3) + { + // The arbitrary Tag Data section begins here. + if (br.ReadByte() != 0xFF || + br.ReadByte() != 0xFF || + br.ReadByte() != 0x1A) { - _data[i] = br.ReadByte(); + // Tag data does not present or truncated. + return true; } - if (_data[0] == 0 || _data[1] == 0 || _data[2] == 0) + // "title" is maximum 40 characters long. + var title = new StringBuilder(); + char c; + while ((c = br.ReadChar()) != 0) { - // If we're here then this is a later (more popular) file with - // the full four bytes for the hardware-type. - i = 0; // so ignore the three bytes we just read and start again + title.Append(c); } - // Read the OPL data. - br.BaseStream.Read(_data, i, length - i); - - var tagsize = fs.Length - fs.Position; - if (tagsize >= 3) + // "author" Tag marker byte is present ? + if (br.ReadByte() == 0x1B) { - // The arbitrary Tag Data section begins here. - if (br.ReadByte() != 0xFF || - br.ReadByte() != 0xFF || - br.ReadByte() != 0x1A) - { - // Tag data does not present or truncated. - return true; - } - - // "title" is maximum 40 characters long. - var title = new StringBuilder(); - char c; + // "author" is maximum 40 characters long. + var author = new StringBuilder(); while ((c = br.ReadChar()) != 0) { - title.Append(c); - } - - // "author" Tag marker byte is present ? - if (br.ReadByte() == 0x1B) - { - // "author" is maximum 40 characters long. - var author = new StringBuilder(); - while ((c = br.ReadChar()) != 0) - { - author.Append(c); - } + author.Append(c); } + } - // "desc" Tag marker byte is present.. - if (br.ReadByte() == 0x1C) + // "desc" Tag marker byte is present.. + if (br.ReadByte() == 0x1C) + { + // "desc" is now maximum 1023 characters long (it was 140). + var desc = new StringBuilder(); + while ((c = br.ReadChar()) != 0) { - // "desc" is now maximum 1023 characters long (it was 140). - var desc = new StringBuilder(); - while ((c = br.ReadChar()) != 0) - { - desc.Append(c); - } + desc.Append(c); } } - Rewind(0); - return true; } + Rewind(0); + return true; } public bool Update() @@ -193,12 +198,14 @@ private void Rewind(int subsong) // Registers not initialized to 0 will be corrected // in the data stream. // opl->setchip(0); - for(var i = 0; i < 256; i++) { + for (var i = 0; i < 256; i++) + { Opl.WriteReg(i, 0); } - + // opl->setchip(1); - for(var i = 0; i < 256; i++) { + for (var i = 0; i < 256; i++) + { Opl.WriteReg(i, 0); } diff --git a/NScumm.Audio.Players/HscPlayer.cs b/NScumm.Audio.Players/HscPlayer.cs index 903f42e..4beadea 100644 --- a/NScumm.Audio.Players/HscPlayer.cs +++ b/NScumm.Audio.Players/HscPlayer.cs @@ -71,47 +71,52 @@ public bool Load(string path) using (var fs = File.OpenRead(path)) { - if (fs.Length > (59187 + 1)) // +1 is for some files that have a trailing 0x00 on the end - return false; - if (fs.Length < (1587 + 1152)) // no 0x00 byte here as this is the smallest possible size - return false; - - var br = new BinaryReader(fs); - int total_patterns_in_hsc = (int)((fs.Length - 1587) / 1152); - - // load section - instr = new byte[128][]; - for (var i = 0; i < 128; i++) // load instruments - instr[i] = br.ReadBytes(12); - for (var i = 0; i < 128; i++) - { // correct instruments - instr[i][2] = (byte)(instr[i][2] ^ (instr[i][2] & 0x40) << 1); - instr[i][3] = (byte)(instr[i][3] ^ (instr[i][3] & 0x40) << 1); - instr[i][11] >>= 4; // slide - } - for (var i = 0; i < 51; i++) - { // load tracklist - song[i] = br.ReadByte(); - // if out of range, song ends here - if ( - ((song[i] & 0x7F) > 0x31) - || ((song[i] & 0x7F) >= total_patterns_in_hsc) - ) song[i] = 0xFF; - } - var len = (fs.Length - fs.Position)/2/64/9; - patterns = new hscnote[len, 64 * 9]; - for (var i = 0; i < len; i++) - { // load patterns - for (var j = 0; j < 64 * 9; j++) - { - patterns[i, j].note = br.ReadByte(); - patterns[i, j].effect = br.ReadByte(); - } - } + return Load(fs); + } + } - Rewind(0); // rewind module - return true; + public bool Load(Stream stream) + { + if (stream.Length > (59187 + 1)) // +1 is for some files that have a trailing 0x00 on the end + return false; + if (stream.Length < (1587 + 1152)) // no 0x00 byte here as this is the smallest possible size + return false; + + var br = new BinaryReader(stream); + int total_patterns_in_hsc = (int)((stream.Length - 1587) / 1152); + + // load section + instr = new byte[128][]; + for (var i = 0; i < 128; i++) // load instruments + instr[i] = br.ReadBytes(12); + for (var i = 0; i < 128; i++) + { // correct instruments + instr[i][2] = (byte)(instr[i][2] ^ (instr[i][2] & 0x40) << 1); + instr[i][3] = (byte)(instr[i][3] ^ (instr[i][3] & 0x40) << 1); + instr[i][11] >>= 4; // slide } + for (var i = 0; i < 51; i++) + { // load tracklist + song[i] = br.ReadByte(); + // if out of range, song ends here + if ( + ((song[i] & 0x7F) > 0x31) + || ((song[i] & 0x7F) >= total_patterns_in_hsc) + ) song[i] = 0xFF; + } + var len = (stream.Length - stream.Position) / 2 / 64 / 9; + patterns = new hscnote[len, 64 * 9]; + for (var i = 0; i < len; i++) + { // load patterns + for (var j = 0; j < 64 * 9; j++) + { + patterns[i, j].note = br.ReadByte(); + patterns[i, j].effect = br.ReadByte(); + } + } + + Rewind(0); // rewind module + return true; } public bool Update() @@ -166,14 +171,14 @@ public bool Update() switch (effect & 0xf0) { // effect handling case 0: // global effect - /* The following fx are unimplemented on purpose: - * 02 - Slide Mainvolume up - * 03 - Slide Mainvolume down (here: fade in) - * 04 - Set Mainvolume to 0 - * - * This is because i've never seen any HSC modules using the fx this way. - * All modules use the fx the way, i've implemented it. - */ + /* The following fx are unimplemented on purpose: + * 02 - Slide Mainvolume up + * 03 - Slide Mainvolume down (here: fade in) + * 04 - Set Mainvolume to 0 + * + * This is because i've never seen any HSC modules using the fx this way. + * All modules use the fx the way, i've implemented it. + */ switch (eff_op) { case 1: pattbreak++; break; // jump to next pattern diff --git a/NScumm.Audio.Players/IMusicPlayer.cs b/NScumm.Audio.Players/IMusicPlayer.cs index 4031bcd..47e1669 100644 --- a/NScumm.Audio.Players/IMusicPlayer.cs +++ b/NScumm.Audio.Players/IMusicPlayer.cs @@ -19,6 +19,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.IO; using NScumm.Core.Audio.OPL; namespace NScumm.Audio.Players @@ -29,6 +30,7 @@ public interface IMusicPlayer float RefreshRate { get; } bool Load(string path); + bool Load(Stream stream); bool Update(); } } diff --git a/NScumm.Audio.Players/ImfPlayer.cs b/NScumm.Audio.Players/ImfPlayer.cs index d15271f..34aedb1 100644 --- a/NScumm.Audio.Players/ImfPlayer.cs +++ b/NScumm.Audio.Players/ImfPlayer.cs @@ -30,20 +30,20 @@ namespace NScumm.Audio.Players /// IMF Player by Simon Peter dn.tlp@gmx.net /// This code has been adapted from adplug https://github.com/adplug/adplug /// - internal sealed class ImfPlayer: IMusicPlayer + public class ImfPlayer : IMusicPlayer { private string track_name, game_name, author_name, remarks; private long _size; private Sdata[] _data; private string _footer; - private float _rate; + private float _rate = 700.0f; private int _pos; private bool _songend; private ushort _del; public IOpl Opl { get; } - public float RefreshRate { get; private set; } + public float RefreshRate { get; set; } struct Sdata { @@ -59,87 +59,93 @@ public ImfPlayer(IOpl opl) public bool Load(string path) { + if (!string.Equals(Path.GetExtension(path), ".imf", System.StringComparison.OrdinalIgnoreCase) + && !string.Equals(Path.GetExtension(path), ".wlf", System.StringComparison.OrdinalIgnoreCase)) + { + // It's no IMF file at all + return false; + } + + _rate = GetRate(path); + using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - long fsize, flsize, mfsize = 0; - uint i; + return Load(fs); + } + } - // file validation section + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + long fsize, flsize, mfsize = 0; + uint i; + + // file validation section + { + var header = new string(br.ReadChars(5)); + var version = br.ReadByte(); + + if (header != "ADLIB" || version != 1) { - var header = new string(br.ReadChars(5)); - var version = br.ReadByte(); - - if (header != "ADLIB" || version != 1) - { - if (!string.Equals(Path.GetExtension(path), ".imf", System.StringComparison.OrdinalIgnoreCase) - && !string.Equals(Path.GetExtension(path), ".wlf", System.StringComparison.OrdinalIgnoreCase)) - { - // It's no IMF file at all - return false; - } - fs.Seek(0, SeekOrigin.Begin); // It's a normal IMF file - } - else - { - // It's a IMF file with header - track_name = ReadString(br); - game_name = ReadString(br); - br.ReadByte(); - mfsize = fs.Position + 2; - } + stream.Seek(0, SeekOrigin.Begin); // It's a normal IMF file } - - // load section - if (mfsize > 0) - fsize = br.ReadInt32(); else - fsize = br.ReadInt16(); - flsize = fs.Length; - if (fsize == 0) - { // footerless file (raw music data) - if (mfsize != 0) - fs.Seek(-4, SeekOrigin.Current); - else - fs.Seek(-2, SeekOrigin.Current); - _size = (flsize - mfsize) / 4; - } - else // file has got a footer - _size = fsize / 4; - - _data = new Sdata[_size]; - for (i = 0; i < _size; i++) { - _data[i].reg = br.ReadByte(); _data[i].val = br.ReadByte(); - _data[i].time = br.ReadUInt16(); + // It's a IMF file with header + track_name = ReadString(br); + game_name = ReadString(br); + br.ReadByte(); + mfsize = stream.Position + 2; } + } + + // load section + if (mfsize > 0) + fsize = br.ReadInt32(); + else + fsize = br.ReadInt16(); + flsize = stream.Length; + if (fsize == 0) + { // footerless file (raw music data) + if (mfsize != 0) + stream.Seek(-4, SeekOrigin.Current); + else + stream.Seek(-2, SeekOrigin.Current); + _size = (flsize - mfsize) / 4; + } + else // file has got a footer + _size = fsize / 4; + + _data = new Sdata[_size]; + for (i = 0; i < _size; i++) + { + _data[i].reg = br.ReadByte(); _data[i].val = br.ReadByte(); + _data[i].time = br.ReadUInt16(); + } - // read footer, if any - if (fsize != 0 && (fsize < flsize - 2 - mfsize)) + // read footer, if any + if (fsize != 0 && (fsize < flsize - 2 - mfsize)) + { + if (br.ReadByte() == 0x1a) { - if (br.ReadByte() == 0x1a) - { - // Adam Nielsen's footer format - track_name = ReadString(br); - author_name = ReadString(br); - remarks = ReadString(br); - } - else - { - // Generic footer - long footerlen = flsize - fsize - 2 - mfsize; - - _footer = ReadString(br, footerlen); - } + // Adam Nielsen's footer format + track_name = ReadString(br); + author_name = ReadString(br); + remarks = ReadString(br); } + else + { + // Generic footer + long footerlen = flsize - fsize - 2 - mfsize; - _rate = GetRate(path); + _footer = ReadString(br, footerlen); + } + } - _pos = 0; _del = 0; RefreshRate = _rate; _songend = false; - Opl.WriteReg(1, 32); // go to OPL2 mode + _pos = 0; _del = 0; RefreshRate = _rate; _songend = false; + Opl.WriteReg(1, 32); // go to OPL2 mode - return true; - } + return true; } public bool Update() @@ -156,7 +162,7 @@ public bool Update() _pos = 0; _songend = true; } - else RefreshRate = _rate / (float)_del; + else RefreshRate = _rate / _del; return !_songend; } diff --git a/NScumm.Audio.Players/KsmPlayer.cs b/NScumm.Audio.Players/KsmPlayer.cs index b2574b8..67e8c10 100644 --- a/NScumm.Audio.Players/KsmPlayer.cs +++ b/NScumm.Audio.Players/KsmPlayer.cs @@ -72,44 +72,55 @@ public bool Load(string path) } using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - AdPlug_LogWrite($"*** CksmPlayer::load(,\"{path}\") ***\n"); + return Load(fs); + } + } - // Load instruments from 'insts.dat' - var fn = Path.Combine(Path.GetDirectoryName(path), "insts.dat"); - if (!File.Exists(fn)) - { - AdPlug_LogWrite("Couldn't open instruments file! Aborting!\n"); - AdPlug_LogWrite("--- CksmPlayer::load ---\n"); - return false; - } - AdPlug_LogWrite("Instruments file: \"{fn}\"\n"); - loadinsts(fn); + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + AdPlug_LogWrite($"*** CksmPlayer::load ***\n"); - for (var i = 0; i < 16; i++) trinst[i] = br.ReadByte(); - for (var i = 0; i < 16; i++) trquant[i] = br.ReadByte(); - for (var i = 0; i < 16; i++) trchan[i] = br.ReadByte(); - fs.Seek(16, SeekOrigin.Current); - for (var i = 0; i < 16; i++) trvol[i] = br.ReadByte(); - numnotes = br.ReadUInt16(); - note = new int[numnotes]; - for (var i = 0; i < numnotes; i++) note[i] = br.ReadInt32(); + // Load instruments from 'insts.dat' + var fs = stream as FileStream; + if (fs == null) + { + AdPlug_LogWrite("Couldn't open instruments for Ksm from a pure stream! Aborting!\n"); + return false; + } + var fn = Path.Combine(Path.GetDirectoryName(fs.Name), "insts.dat"); + if (!File.Exists(fn)) + { + AdPlug_LogWrite("Couldn't open instruments file! Aborting!\n"); + AdPlug_LogWrite("--- CksmPlayer::load ---\n"); + return false; + } + AdPlug_LogWrite($"Instruments file: \"{fn}\"\n"); + loadinsts(fn); - if (trchan[11] == 0) - { - drumstat = 0; - numchans = 9; - } - else - { - drumstat = 32; - numchans = 6; - } + for (var i = 0; i < 16; i++) trinst[i] = br.ReadByte(); + for (var i = 0; i < 16; i++) trquant[i] = br.ReadByte(); + for (var i = 0; i < 16; i++) trchan[i] = br.ReadByte(); + stream.Seek(16, SeekOrigin.Current); + for (var i = 0; i < 16; i++) trvol[i] = br.ReadByte(); + numnotes = br.ReadUInt16(); + note = new int[numnotes]; + for (var i = 0; i < numnotes; i++) note[i] = br.ReadInt32(); - Rewind(0); - AdPlug_LogWrite("--- CksmPlayer::load ---\n"); - return true; + if (trchan[11] == 0) + { + drumstat = 0; + numchans = 9; } + else + { + drumstat = 32; + numchans = 6; + } + + Rewind(0); + AdPlug_LogWrite("--- CksmPlayer::load ---\n"); + return true; } public bool Update() @@ -339,14 +350,15 @@ private void loadinsts(string path) for (var i = 0; i < 256; i++) { instname[i] = new string(br.ReadChars(20)); - for (var j = 0; j < 11; j++) inst[i,j] = br.ReadByte(); + for (var j = 0; j < 11; j++) inst[i, j] = br.ReadByte(); fs.Seek(2, SeekOrigin.Current); } } } - private void AdPlug_LogWrite(string fmt, params object[] parameters) + private void AdPlug_LogWrite(string message) { + Console.Error.WriteLine(message); } } } \ No newline at end of file diff --git a/NScumm.Audio.Players/MidPlayer.cs b/NScumm.Audio.Players/MidPlayer.cs index 3e274f7..f84b4ec 100644 --- a/NScumm.Audio.Players/MidPlayer.cs +++ b/NScumm.Audio.Players/MidPlayer.cs @@ -179,47 +179,60 @@ public bool Load(string path) { using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - var s = br.ReadBytes(6); - int good = 0; - subsongs = 0; - switch (s[0]) - { - case (byte)'A': - if (s[1] == 'D' && s[2] == 'L') good = FILE_LUCAS; - break; - case (byte)'M': - if (s[1] == 'T' && s[2] == 'h' && s[3] == 'd') good = FILE_MIDI; - break; - case (byte)'C': - if (s[1] == 'T' && s[2] == 'M' && s[3] == 'F') good = FILE_CMF; - break; - case 0x84: - if (s[1] == 0x00 && load_sierra_ins(path)) + return Load(fs); + } + } + + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + var s = br.ReadBytes(6); + int good = 0; + subsongs = 0; + switch (s[0]) + { + case (byte)'A': + if (s[1] == 'D' && s[2] == 'L') good = FILE_LUCAS; + break; + case (byte)'M': + if (s[1] == 'T' && s[2] == 'h' && s[3] == 'd') good = FILE_MIDI; + break; + case (byte)'C': + if (s[1] == 'T' && s[2] == 'M' && s[3] == 'F') good = FILE_CMF; + break; + case 0x84: + if (s[1] == 0x00) + { + var fs = stream as FileStream; + if (fs == null) { - if (s[2] == 0xf0) - good = FILE_ADVSIERRA; - else - good = FILE_SIERRA; + AdPlug_LogWrite("Couldn't open instruments for Mid from a pure stream! Aborting!\n"); + return false; } - break; - default: - fs.Seek(0, SeekOrigin.Begin); - var size = br.ReadUInt32(); // size of FILE_OLDLUCAS - if (size == fs.Length && s[4] == 'A' && s[5] == 'D') good = FILE_OLDLUCAS; - break; - } - if (good == 0) return false; - subsongs = 1; + load_sierra_ins(fs.Name); - type = good; - fs.Seek(0, SeekOrigin.Begin); - flen = fs.Length; - data = br.ReadBytes((int)flen); - - Rewind(0); - return true; + if (s[2] == 0xf0) + good = FILE_ADVSIERRA; + else + good = FILE_SIERRA; + } + break; + default: + stream.Seek(0, SeekOrigin.Begin); + var size = br.ReadUInt32(); // size of FILE_OLDLUCAS + if (size == stream.Length && s[4] == 'A' && s[5] == 'D') good = FILE_OLDLUCAS; + break; } + if (good == 0) return false; + subsongs = 1; + + type = good; + stream.Seek(0, SeekOrigin.Begin); + flen = stream.Length; + data = br.ReadBytes((int)flen); + + Rewind(0); + return true; } public bool Update() @@ -1150,5 +1163,10 @@ private byte datalook(long pos) private void midiprintf(string format, params object[] parameters) { } + + private void AdPlug_LogWrite(string message) + { + Console.Error.WriteLine(message); + } } } diff --git a/NScumm.Audio.Players/MkjPlayer.cs b/NScumm.Audio.Players/MkjPlayer.cs index 92cfe45..0196eba 100644 --- a/NScumm.Audio.Players/MkjPlayer.cs +++ b/NScumm.Audio.Players/MkjPlayer.cs @@ -54,7 +54,14 @@ public MkjPlayer(IOpl opl) public bool Load(string path) { using (var fs = File.OpenRead(path)) - using (var br = new BinaryReader(fs)) + { + return Load(fs); + } + } + + public bool Load(Stream stream) + { + using (var br = new BinaryReader(stream)) { // file validation var id = new string(br.ReadChars(6)); diff --git a/NScumm.Audio.Players/S3mPlayer.cs b/NScumm.Audio.Players/S3mPlayer.cs index 8c12133..4fc6746 100644 --- a/NScumm.Audio.Players/S3mPlayer.cs +++ b/NScumm.Audio.Players/S3mPlayer.cs @@ -112,101 +112,106 @@ public bool Load(string path) { using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - var insptr = new ushort[99]; - var pattptr = new ushort[99]; - int row; - byte bufval, bufval2; - ushort ppatlen; - var adlibins = false; - - // file validation section - var checkhead = LoadHeader(br); - if (checkhead.kennung != 0x1a || checkhead.typ != 16 - || checkhead.insnum > 99) - { - return false; - } - if (checkhead.scrm != "SCRM") - { - return false; - } - fs.Seek(checkhead.ordnum, SeekOrigin.Current); - for (var i = 0; i < checkhead.insnum; i++) - insptr[i] = br.ReadUInt16(); - for (var i = 0; i < checkhead.insnum; i++) + return Load(fs); + } + } + + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + var insptr = new ushort[99]; + var pattptr = new ushort[99]; + int row; + byte bufval, bufval2; + ushort ppatlen; + var adlibins = false; + + // file validation section + var checkhead = LoadHeader(br); + if (checkhead.kennung != 0x1a || checkhead.typ != 16 + || checkhead.insnum > 99) + { + return false; + } + if (checkhead.scrm != "SCRM") + { + return false; + } + stream.Seek(checkhead.ordnum, SeekOrigin.Current); + for (var i = 0; i < checkhead.insnum; i++) + insptr[i] = br.ReadUInt16(); + for (var i = 0; i < checkhead.insnum; i++) + { + stream.Seek(insptr[i] * 16, SeekOrigin.Begin); + if (br.ReadByte() >= 2) { - fs.Seek(insptr[i] * 16, SeekOrigin.Begin); - if (br.ReadByte() >= 2) - { - adlibins = true; - break; - } + adlibins = true; + break; } - if (!adlibins) return false; + } + if (!adlibins) return false; - // load section - fs.Seek(0, SeekOrigin.Begin); // rewind for load - _header = LoadHeader(br); // read header + // load section + stream.Seek(0, SeekOrigin.Begin); // rewind for load + _header = LoadHeader(br); // read header - // security check - if (_header.ordnum > 256 || _header.insnum > 99 || _header.patnum > 99) - { - return false; - } + // security check + if (_header.ordnum > 256 || _header.insnum > 99 || _header.patnum > 99) + { + return false; + } - for (var i = 0; i < _header.ordnum; i++) _orders[i] = br.ReadByte(); // read orders - for (var i = 0; i < _header.insnum; i++) insptr[i] = br.ReadUInt16(); // instrument parapointers - for (var i = 0; i < _header.patnum; i++) pattptr[i] = br.ReadUInt16(); // pattern parapointers - - for (var i = 0; i < _header.insnum; i++) - { // load instruments - fs.Seek(insptr[i] * 16, SeekOrigin.Begin); - _inst[i].type = br.ReadByte(); - _inst[i].filename = new string(br.ReadChars(15)); - _inst[i].d00 = br.ReadByte(); _inst[i].d01 = br.ReadByte(); - _inst[i].d02 = br.ReadByte(); _inst[i].d03 = br.ReadByte(); - _inst[i].d04 = br.ReadByte(); _inst[i].d05 = br.ReadByte(); - _inst[i].d06 = br.ReadByte(); _inst[i].d07 = br.ReadByte(); - _inst[i].d08 = br.ReadByte(); _inst[i].d09 = br.ReadByte(); - _inst[i].d0a = br.ReadByte(); _inst[i].d0b = br.ReadByte(); - _inst[i].volume = br.ReadByte(); _inst[i].dsk = br.ReadByte(); - fs.Seek(2, SeekOrigin.Current); - _inst[i].c2spd = br.ReadUInt32(); - fs.Seek(12, SeekOrigin.Current); - _inst[i].name = new string(br.ReadChars(28)); - _inst[i].scri = new string(br.ReadChars(4)); - } + for (var i = 0; i < _header.ordnum; i++) _orders[i] = br.ReadByte(); // read orders + for (var i = 0; i < _header.insnum; i++) insptr[i] = br.ReadUInt16(); // instrument parapointers + for (var i = 0; i < _header.patnum; i++) pattptr[i] = br.ReadUInt16(); // pattern parapointers + + for (var i = 0; i < _header.insnum; i++) + { // load instruments + stream.Seek(insptr[i] * 16, SeekOrigin.Begin); + _inst[i].type = br.ReadByte(); + _inst[i].filename = new string(br.ReadChars(15)); + _inst[i].d00 = br.ReadByte(); _inst[i].d01 = br.ReadByte(); + _inst[i].d02 = br.ReadByte(); _inst[i].d03 = br.ReadByte(); + _inst[i].d04 = br.ReadByte(); _inst[i].d05 = br.ReadByte(); + _inst[i].d06 = br.ReadByte(); _inst[i].d07 = br.ReadByte(); + _inst[i].d08 = br.ReadByte(); _inst[i].d09 = br.ReadByte(); + _inst[i].d0a = br.ReadByte(); _inst[i].d0b = br.ReadByte(); + _inst[i].volume = br.ReadByte(); _inst[i].dsk = br.ReadByte(); + stream.Seek(2, SeekOrigin.Current); + _inst[i].c2spd = br.ReadUInt32(); + stream.Seek(12, SeekOrigin.Current); + _inst[i].name = new string(br.ReadChars(28)); + _inst[i].scri = new string(br.ReadChars(4)); + } - for (var i = 0; i < _header.patnum; i++) - { // depack patterns - fs.Seek(pattptr[i] * 16, SeekOrigin.Begin); - ppatlen = br.ReadUInt16(); - long pattpos = fs.Position; - for (row = 0; (row < 64) && (pattpos - pattptr[i] * 16 <= ppatlen); row++) - do + for (var i = 0; i < _header.patnum; i++) + { // depack patterns + stream.Seek(pattptr[i] * 16, SeekOrigin.Begin); + ppatlen = br.ReadUInt16(); + long pattpos = stream.Position; + for (row = 0; (row < 64) && (pattpos - pattptr[i] * 16 <= ppatlen); row++) + do + { + bufval = br.ReadByte(); + if ((bufval & 32) != 0) { - bufval = br.ReadByte(); - if ((bufval & 32) != 0) - { - bufval2 = br.ReadByte(); - _pattern[i, row, bufval & 31].note = (byte)(bufval2 & 15); - _pattern[i, row, bufval & 31].oct = (byte)((bufval2 & 240) >> 4); - _pattern[i, row, bufval & 31].instrument = br.ReadByte(); - } - if ((bufval & 64) != 0) - _pattern[i, row, bufval & 31].volume = br.ReadByte(); - if ((bufval & 128) != 0) - { - _pattern[i, row, bufval & 31].command = br.ReadByte(); - _pattern[i, row, bufval & 31].info = br.ReadByte(); - } - } while (bufval != 0); - } - - Rewind(0); - return true; // done + bufval2 = br.ReadByte(); + _pattern[i, row, bufval & 31].note = (byte)(bufval2 & 15); + _pattern[i, row, bufval & 31].oct = (byte)((bufval2 & 240) >> 4); + _pattern[i, row, bufval & 31].instrument = br.ReadByte(); + } + if ((bufval & 64) != 0) + _pattern[i, row, bufval & 31].volume = br.ReadByte(); + if ((bufval & 128) != 0) + { + _pattern[i, row, bufval & 31].command = br.ReadByte(); + _pattern[i, row, bufval & 31].info = br.ReadByte(); + } + } while (bufval != 0); } + + Rewind(0); + return true; // done } public bool Update() diff --git a/NScumm.Audio.Players/SngPlayer.cs b/NScumm.Audio.Players/SngPlayer.cs index 2472e82..7134e7a 100644 --- a/NScumm.Audio.Players/SngPlayer.cs +++ b/NScumm.Audio.Players/SngPlayer.cs @@ -65,29 +65,34 @@ public bool Load(string path) { using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); + return Load(fs); + } + } - // load header - header.id = new string(br.ReadChars(4)); - header.length = br.ReadUInt16(); header.start = br.ReadUInt16(); - header.loop = br.ReadUInt16(); header.delay = br.ReadByte(); - header.compressed = br.ReadByte() != 0; + public bool Load(Stream fs) + { + var br = new BinaryReader(fs); - // file validation section - if (!string.Equals(header.id, "ObsM", System.StringComparison.OrdinalIgnoreCase)) return false; + // load header + header.id = new string(br.ReadChars(4)); + header.length = br.ReadUInt16(); header.start = br.ReadUInt16(); + header.loop = br.ReadUInt16(); header.delay = br.ReadByte(); + header.compressed = br.ReadByte() != 0; - // load section - header.length /= 2; header.start /= 2; header.loop /= 2; - data = new Sdata[header.length]; - for (var i = 0; i < header.length && fs.Position < fs.Length - 2; i++) - { - data[i].val = br.ReadByte(); - data[i].reg = br.ReadByte(); - } + // file validation section + if (!string.Equals(header.id, "ObsM", System.StringComparison.OrdinalIgnoreCase)) return false; - Rewind(0); - return true; + // load section + header.length /= 2; header.start /= 2; header.loop /= 2; + data = new Sdata[header.length]; + for (var i = 0; i < header.length && fs.Position < fs.Length - 2; i++) + { + data[i].val = br.ReadByte(); + data[i].reg = br.ReadByte(); } + + Rewind(0); + return true; } public bool Update() diff --git a/NScumm.Audio.Players/XsmPlayer.cs b/NScumm.Audio.Players/XsmPlayer.cs index c276a14..2fc75cf 100644 --- a/NScumm.Audio.Players/XsmPlayer.cs +++ b/NScumm.Audio.Players/XsmPlayer.cs @@ -57,32 +57,37 @@ public bool Load(string path) { using (var fs = File.OpenRead(path)) { - var br = new BinaryReader(fs); - - // check if header matches - var id = new string(br.ReadChars(6)); songlen = br.ReadUInt16(); - if (!string.Equals(id, "ofTAZ!", StringComparison.Ordinal) || songlen > 3200) return false; - - // read and set instruments - for (var i = 0; i < 9; i++) - { - inst[i] = new Instrument(); - inst[i].value = br.ReadBytes(11); - fs.Seek(5, SeekOrigin.Current); - } - - // read song data - music = new byte[songlen * 9]; - for (var i = 0; i < 9; i++) - for (var j = 0; j < songlen; j++) - music[j * 9 + i] = br.ReadByte(); - - // success - Rewind(0); - return true; + return Load(fs); } } + public bool Load(Stream stream) + { + var br = new BinaryReader(stream); + + // check if header matches + var id = new string(br.ReadChars(6)); songlen = br.ReadUInt16(); + if (!string.Equals(id, "ofTAZ!", StringComparison.Ordinal) || songlen > 3200) return false; + + // read and set instruments + for (var i = 0; i < 9; i++) + { + inst[i] = new Instrument(); + inst[i].value = br.ReadBytes(11); + stream.Seek(5, SeekOrigin.Current); + } + + // read song data + music = new byte[songlen * 9]; + for (var i = 0; i < 9; i++) + for (var j = 0; j < songlen; j++) + music[j * 9 + i] = br.ReadByte(); + + // success + Rewind(0); + return true; + } + private void Rewind(int subsong) { notenum = last = 0; @@ -118,7 +123,7 @@ public bool Update() for (var c = 0; c < 9; c++) { - if (music[notenum * 9 + c]!=0) + if (music[notenum * 9 + c] != 0) PlayNote(c, music[notenum * 9 + c] % 12, music[notenum * 9 + c] / 12); else PlayNote(c, 0, 0);