private static HgxImage ExtractHg2Internal(HGXHDR hdr, BinaryReader reader, string fileName, string outputDir,
                                                   HgxOptions options)
        {
            Stream stream = reader.BaseStream;

            List <Hg2FrameInfo> frameInfos = new List <Hg2FrameInfo>();
            Hg2FrameInfo        frameInfo  = null;
            long startPosition             = stream.Position;

            do
            {
                stream.Position = startPosition + (frameInfo?.Img.OffsetNext ?? 0);
                startPosition   = stream.Position;

                frameInfo = ReadHg2FrameInfo(reader, hdr, false);
                frameInfos.Add(frameInfo);
            } while (frameInfo.Img.OffsetNext != 0);

            HgxImage hg2Image = new HgxImage(Path.GetFileName(fileName), hdr, frameInfos.ToArray(), options);

            if (outputDir != null)
            {
                for (int imgIndex = 0; imgIndex < frameInfos.Count; imgIndex++)
                {
                    frameInfo = frameInfos[imgIndex];
                    string pngFile = hg2Image.Frames[imgIndex].GetFrameFilePath(outputDir, false);
                    ExtractHg2Image(reader, frameInfo, options, pngFile);
                }
            }

            return(hg2Image);
        }
        private static HgxImage ExtractInternal(Stream stream, string fileName, string outputDir, HgxOptions options)
        {
            BinaryReader reader = new BinaryReader(stream);
            HGXHDR       hdr    = reader.ReadUnmanaged <HGXHDR>();

            try {
                if (hdr.Signature == HGXHDR.ExpectedHg3Signature)
                {
                    return(ExtractHg3Internal(hdr, reader, fileName, outputDir, options));
                }
                if (hdr.Signature == HGXHDR.ExpectedHg2Signature)
                {
                    return(ExtractHg2Internal(hdr, reader, fileName, outputDir, options));
                }
            } catch (ArgumentNullException) {
                // This exception shouldn't be triggered by failure to read the file, so
                // we'll throw it as an error as itself because it's not supposed to happen.
                throw;
            } catch (NullReferenceException) {
                // This exception shouldn't be triggered by failure to read the file, so
                // we'll throw it as an error as itself because it's not supposed to happen.
                throw;
            } catch (Exception ex) {
                throw new HgxException(ex);
            }             /*catch (EndOfStreamException ex) {
                           *    throw new HgxException(ex);
                           * }*/
            throw new UnexpectedFileTypeException($"{HGXHDR.ExpectedHg2Signature} or {HGXHDR.ExpectedHg3Signature}");
        }
        private static void ExtractHg3ImageFromFrame(Stream stream, HgxFrame frame, string pngFile, HgxOptions options)
        {
            BinaryReader reader = new BinaryReader(stream);

            stream.Position = frame.FrameOffset;

            HGXHDR hdr = new HGXHDR {
                Type = frame.HgxImage.Type
            };
            Hg3FrameInfo frameInfo = ReadHg3FrameInfo(reader, hdr, true);

            ExtractHg3Image(reader, frameInfo, options, pngFile);
        }
        private static Hg2FrameInfo ReadHg2FrameInfo(BinaryReader reader, HGXHDR hdr, bool frameOnly)
        {
            Stream stream      = reader.BaseStream;
            long   frameOffset = reader.BaseStream.Position;

            HG2IMG      img     = reader.ReadUnmanaged <HG2IMG>();
            HG2IMG_BASE?imgBase = null;

            if (hdr.Type == 0x25)
            {
                if (frameOnly)
                {
                    stream.Position += HG2IMG_BASE.CbSize;
                }
                else
                {
                    imgBase = reader.ReadUnmanaged <HG2IMG_BASE>();
                }
            }

            return(new Hg2FrameInfo(reader.BaseStream, img, imgBase, frameOffset));
        }
        private static HgxImage ExtractHg3Internal(HGXHDR hdr, BinaryReader reader, string fileName, string outputDir,
                                                   HgxOptions options)
        {
            Stream stream = reader.BaseStream;

            List <Hg3FrameInfo> frameInfos = new List <Hg3FrameInfo>();
            Hg3FrameInfo        frameInfo  = null;
            long startPosition             = stream.Position;

            do
            {
                stream.Position = startPosition + (frameInfo?.Header.OffsetNext ?? 0);

                // NEW-NEW METHOD: We now know the next offset ahead
                // of time from the HG3OFFSET we're going to read.
                // Usually skips 0 bytes, otherwise usually 1-7 bytes.
                startPosition = stream.Position;

                frameInfo = ReadHg3FrameInfo(reader, hdr, false);
                frameInfos.Add(frameInfo);
            } while (frameInfo.Header.OffsetNext != 0);

            HgxImage hg3Image = new HgxImage(Path.GetFileName(fileName), hdr, frameInfos.ToArray(), options);

            if (outputDir != null)
            {
                for (int imgIndex = 0; imgIndex < frameInfos.Count; imgIndex++)
                {
                    frameInfo = frameInfos[imgIndex];
                    string pngFile = hg3Image.Frames[imgIndex].GetFrameFilePath(outputDir, false);
                    ExtractHg3Image(reader, frameInfo, options, pngFile);
                }
            }

            return(hg3Image);
        }
        private static Hg3FrameInfo ReadHg3FrameInfo(BinaryReader reader, HGXHDR hdr, bool frameOnly)
        {
            Stream stream      = reader.BaseStream;
            long   frameOffset = stream.Position;

            HG3FRAMEHDR frameHdr = reader.ReadUnmanaged <HG3FRAMEHDR>();

            long   tagStartPosition = stream.Position;
            HG3TAG tag = reader.ReadUnmanaged <HG3TAG>();

            if (!HG3STDINFO.HasTagSignature(tag.Signature))
            {
                throw new Exception("Expected \"stdinfo\" tag!");
            }

            HG3STDINFO   stdInfo   = reader.ReadUnmanaged <HG3STDINFO>();
            Hg3FrameInfo frameInfo = new Hg3FrameInfo(frameHdr, stdInfo, frameOffset);

            while (tag.OffsetNext != 0)
            {
                stream.Position  = tagStartPosition + tag.OffsetNext;
                tagStartPosition = stream.Position;
                tag = reader.ReadUnmanaged <HG3TAG>();

                string signature = tag.Signature;

                if (HG3IMG.HasTagSignature(signature, out int imdId))                   // "img####"
                {
                    HG3IMG img = reader.ReadUnmanaged <HG3IMG>();
                    frameInfo.AddTagImg(stream, tag, img, imdId);
                }
                else if (HG3IMG_AL.HasTagSignature(signature))                   // "img_al"
                {
                    HG3IMG_AL img = reader.ReadUnmanaged <HG3IMG_AL>();
                    frameInfo.AddTagImg(stream, tag, img, 0);
                }
                else if (HG3IMG_JPG.HasTagSignature(signature))                   // "img_jpg"
                // There is no image info, reading it would increment by one byte which we don't want
                {
                    frameInfo.AddTagImg(stream, tag, new HG3IMG_JPG(), 0);
                }
                else if (frameOnly)
                {
                    if (frameInfo.Type != Hg3ImageType.None)
                    {
                        return(frameInfo);
                    }
                }
                else if (HG3ATS.HasTagSignature(signature, out int atsId))                   // "ats####"
                {
                    HG3ATS ats = reader.ReadUnmanaged <HG3ATS>();
                    frameInfo.Ats.Add(atsId, ats);
                }
                else if (HG3CPTYPE.HasTagSignature(signature))                   // "cptype"
                {
                    HG3CPTYPE cpType = reader.ReadUnmanaged <HG3CPTYPE>();
                    frameInfo.CpType = cpType;
                }
                else if (HG3IMGMODE.HasTagSignature(signature))                   // "imgmode"
                {
                    HG3IMGMODE imgMode = reader.ReadUnmanaged <HG3IMGMODE>();
                    frameInfo.ImgMode = imgMode;
                }
                else
                {
                    Trace.WriteLine($"UNKNOWN TAG: \"{signature}\"");
                }
            }
            return(frameInfo);
        }