public HcaDecoder(Stream hcaStream, ulong key)
        {
            hca = new HcaContext();

            Header.DecodeHeader(hca, hcaStream);
            SetKey(key);
        }
Example #2
0
        public static void DecodeHeader(HcaContext hca, Stream hcaStream)
        {
            if (!IsHeaderValid(hcaStream, out int headerSize))
            {
                throw new InvalidDataException("Invalid HCA header.");
            }

            BinaryReader binaryReader = new(hcaStream);

            binaryReader.BaseStream.Position = 0;

            BitReader bitReader = new(binaryReader.ReadBytes((int)headerSize));

            if ((bitReader.Peek(32) & Mask) == StringToUInt32("HCA"))
            {
                bitReader.Skip(32);
                hca.Version    = bitReader.Read(16);
                hca.HeaderSize = bitReader.Read(16);

                headerSize -= 8;
            }
            else
            {
                throw new InvalidDataException("Not an HCA file.");
            }

            if (headerSize >= 16 && (bitReader.Peek(32) & Mask) == StringToUInt32("fmt"))
            {
                bitReader.Skip(32);
                hca.ChannelCount   = bitReader.Read(8);
                hca.SampleRate     = bitReader.Read(24);
                hca.FrameCount     = bitReader.Read(32);
                hca.EncoderDelay   = bitReader.Read(16);
                hca.EncoderPadding = bitReader.Read(16);

                if (hca.ChannelCount < MinChannels || hca.ChannelCount > MaxChannels)
                {
                    throw new InvalidDataException("Invalid channel count.");
                }

                if (hca.FrameCount == 0)
                {
                    throw new InvalidDataException("Frame count is zero.");
                }

                if (hca.SampleRate < MinSampleRate || hca.SampleRate > MaxSampleRate)
                {
                    throw new InvalidDataException("Invalid sample rate.");
                }

                headerSize -= 16;
            }
            else
            {
                throw new InvalidDataException("No format chunk.");
            }

            if (headerSize >= 16 && (bitReader.Peek(32) & Mask) == StringToUInt32("comp"))
            {
                bitReader.Skip(32);
                hca.FrameSize        = bitReader.Read(16);
                hca.MinResolution    = bitReader.Read(8);
                hca.MaxResolution    = bitReader.Read(8);
                hca.TrackCount       = bitReader.Read(8);
                hca.ChannelConfig    = bitReader.Read(8);
                hca.TotalBandCount   = bitReader.Read(8);
                hca.BaseBandCount    = bitReader.Read(8);
                hca.StereoBandCount  = bitReader.Read(8);
                hca.BandsPerHfrGroup = bitReader.Read(8);
                hca.MsStereo         = bitReader.Read(8);
                hca.Reserved         = bitReader.Read(8);

                headerSize -= 16;
            }
            else if (headerSize >= 0x0c && (bitReader.Peek(32) & Mask) == StringToUInt32("dec"))
            {
                bitReader.Skip(32);
                hca.FrameSize      = bitReader.Read(16);
                hca.MinResolution  = bitReader.Read(8);
                hca.MaxResolution  = bitReader.Read(8);
                hca.TotalBandCount = bitReader.Read(8) + 1;
                hca.BaseBandCount  = bitReader.Read(8) + 1;
                hca.TrackCount     = bitReader.Read(4);
                hca.ChannelConfig  = bitReader.Read(4);
                hca.StereoType     = bitReader.Read(8);

                if (hca.StereoType == 0)
                {
                    hca.BaseBandCount = hca.TotalBandCount;
                }
                hca.StereoBandCount  = hca.TotalBandCount - hca.BaseBandCount;
                hca.BandsPerHfrGroup = 0;

                headerSize -= 12;
            }
            else
            {
                throw new InvalidDataException("No compression or decode chunk.");
            }

            if (headerSize >= 8 && (bitReader.Peek(32) & Mask) == StringToUInt32("vbr"))
            {
                bitReader.Skip(32);
                hca.VbrMaxFrameSize = bitReader.Read(16);
                hca.VbrNoiseLevel   = bitReader.Read(16);

                if (hca.FrameSize != 0 || hca.VbrMaxFrameSize <= 8 || hca.VbrMaxFrameSize > 0x1FF)
                {
                    throw new InvalidDataException("Invalid frame size.");
                }

                headerSize -= 8;
            }

            if (headerSize >= 6 && (bitReader.Peek(32) & Mask) == StringToUInt32("ath"))
            {
                bitReader.Skip(32);
                hca.AthType = bitReader.Read(16);
            }
            else
            {
                hca.AthType = (hca.Version < Version200) ? 1 : 0;
            }

            if (headerSize >= 16 && (bitReader.Peek(32) & Mask) == StringToUInt32("loop"))
            {
                bitReader.Skip(32);
                hca.LoopStartFrame = bitReader.Read(32);
                hca.LoopEndFrame   = bitReader.Read(32);
                hca.LoopStartDelay = bitReader.Read(16);
                hca.LoopEndPadding = bitReader.Read(16);

                hca.LoopFlag = true;

                if (hca.LoopStartFrame < 0 || hca.LoopStartFrame > hca.LoopEndFrame || hca.LoopEndFrame >= hca.FrameCount)
                {
                    throw new InvalidDataException("Invalid loop frames.");
                }

                headerSize -= 16;
            }
            else
            {
                hca.LoopStartFrame = 0;
                hca.LoopEndFrame   = 0;
                hca.LoopStartDelay = 0;
                hca.LoopEndPadding = 0;

                hca.LoopFlag = false;
            }

            if (headerSize >= 6 && (bitReader.Peek(32) & Mask) == StringToUInt32("ciph"))
            {
                bitReader.Skip(32);
                hca.CiphType = bitReader.Read(16);

                if (hca.CiphType != 0 && hca.CiphType != 1 && hca.CiphType != 56)
                {
                    throw new InvalidDataException("Invalid cipher type.");
                }

                headerSize -= 6;
            }

            if (headerSize >= 8 && (bitReader.Peek(32) & Mask) == StringToUInt32("rva"))
            {
                bitReader.Skip(32);
                int rvaVolumeInt = bitReader.Read(32);
                hca.RvaVolume = BitConverter.ToSingle(BitConverter.GetBytes(rvaVolumeInt));

                headerSize -= 8;
            }
            else
            {
                hca.RvaVolume = 1.0f;
            }

            if (headerSize >= 5 && (bitReader.Peek(32) & Mask) == StringToUInt32("comm"))
            {
                bitReader.Skip(32);
                hca.CommentLength = bitReader.Read(8);

                if (hca.CommentLength > headerSize)
                {
                    throw new InvalidDataException("Comment string out of bounds.");
                }

                StringBuilder commentStringBuilder = new();

                for (int i = 0; i < hca.CommentLength; i++)
                {
                    commentStringBuilder.Append(bitReader.Read(8));
                }

                hca.Comment = commentStringBuilder.ToString();
            }
            else
            {
                hca.CommentLength = 0;
            }

            // IDE0059
            //if (headerSize >= 4 && (bitReader.Peek(32) & Mask) == StringToUInt32("pad"))
            //{
            //    headerSize -= (headerSize - 2);
            //}

            if (hca.FrameSize < MinFrameSize || hca.FrameSize > MaxFrameSize)
            {
                throw new InvalidDataException("Invalid frame size.");
            }

            if (hca.Version <= Version200)
            {
                if (hca.MinResolution != 1 || hca.MaxResolution != 15)
                {
                    throw new InvalidDataException("Incompatible resolution.");
                }
            }

            if (hca.TrackCount == 0)
            {
                hca.TrackCount = 1;
            }

            if (hca.TrackCount > hca.ChannelCount)
            {
                throw new InvalidDataException("Invalid track count.");
            }

            if (hca.TotalBandCount > SamplesPerSubframe ||
                hca.BaseBandCount > SamplesPerSubframe ||
                hca.StereoBandCount > SamplesPerSubframe ||
                hca.BaseBandCount + hca.StereoBandCount > SamplesPerSubframe ||
                hca.BandsPerHfrGroup > SamplesPerSubframe)
            {
                throw new InvalidDataException("Invalid bands.");
            }

            hca.HfrGroupCount = HeaderCeil2(
                hca.TotalBandCount - hca.BaseBandCount - hca.StereoBandCount,
                hca.BandsPerHfrGroup);

            hca.AthCurve    = Ath.Init(hca.AthType, hca.SampleRate);
            hca.CipherTable = Cipher.Init(hca.CiphType, hca.KeyCode);

            int channelsPerTrack = hca.ChannelCount / hca.TrackCount;

            ChannelType[] channelTypes = new ChannelType[channelsPerTrack];

            for (int i = 0; i < channelTypes.Length; i++)
            {
                channelTypes[i] = ChannelType.Discrete;
            }

            if (hca.StereoBandCount > 0 && channelsPerTrack > 1)
            {
                for (int i = 0; i < hca.TrackCount; i++)
                {
                    switch (channelsPerTrack)
                    {
                    case 2:
                    case 3:
                        channelTypes[0] = ChannelType.StereoPrimary;
                        channelTypes[1] = ChannelType.StereoSecondary;
                        break;

                    case 4:
                        channelTypes[0] = ChannelType.StereoPrimary;
                        channelTypes[1] = ChannelType.StereoSecondary;
                        if (hca.ChannelConfig == 0)
                        {
                            channelTypes[2] = ChannelType.StereoPrimary;
                            channelTypes[3] = ChannelType.StereoSecondary;
                        }
                        break;

                    case 5:
                        channelTypes[0] = ChannelType.StereoPrimary;
                        channelTypes[1] = ChannelType.StereoSecondary;
                        if (hca.ChannelConfig <= 2)
                        {
                            channelTypes[3] = ChannelType.StereoPrimary;
                            channelTypes[4] = ChannelType.StereoSecondary;
                        }
                        break;

                    case 6:
                    case 7:
                        channelTypes[0] = ChannelType.StereoPrimary;
                        channelTypes[1] = ChannelType.StereoSecondary;
                        channelTypes[4] = ChannelType.StereoPrimary;
                        channelTypes[5] = ChannelType.StereoSecondary;
                        break;

                    case 8:
                        channelTypes[0] = ChannelType.StereoPrimary;
                        channelTypes[1] = ChannelType.StereoSecondary;
                        channelTypes[4] = ChannelType.StereoPrimary;
                        channelTypes[5] = ChannelType.StereoSecondary;
                        channelTypes[6] = ChannelType.StereoPrimary;
                        channelTypes[7] = ChannelType.StereoSecondary;
                        break;

                    default:
                        break;
                    }
                }
            }

            hca.Channels = new Channel[hca.ChannelCount];
            for (int i = 0; i < hca.ChannelCount; i++)
            {
                hca.Channels[i] = new Channel
                {
                    Type       = channelTypes[i],
                    CodedCount =
                        channelTypes[i] != ChannelType.StereoSecondary ?
                        hca.BaseBandCount + hca.StereoBandCount :
                        hca.BaseBandCount
                };
            }

            hca.Random = DefaultRandom;

            if (hca.MsStereo > 0)
            {
                throw new InvalidDataException();
            }
            if (hca.HfrGroupCount > 0 && hca.Version == Version300)
            {
                throw new InvalidDataException();
            }
        }