public void ApplyNGridOverlay(FileWrapper overlayFile, int overlayMajorVersion, int overlayMinorVersion)
        {
            this.overlayFile = overlayFile;


            // this is pretty much just a giant bounding box for all of the modified cells, with only cells in this box being stored in the overlay
            //
            // in practice, this cuts out 50-60% of the required data, however it could be significantly smaller if multiple bounding boxes were used

            int startX = overlayFile.ReadInt();
            int startZ = overlayFile.ReadInt();
            int countX = overlayFile.ReadInt();
            int countZ = overlayFile.ReadInt();

            for (int i = 0; i < countZ; i++)
            {
                int z = startZ + i;

                for (int j = 0; j < countX; j++)
                {
                    int                x             = startX + j;
                    int                cellIndex     = (z * cellCountX) + x;
                    NavGridCell        cell          = this.cells[cellIndex];
                    VisionPathingFlags filteredFlags = cell.visionPathingFlags;


                    VisionPathingFlags overrideFlag = (VisionPathingFlags)overlayFile.ReadShort();


                    if (CheckVisionPathingFlag(filteredFlags, VisionPathingFlags.StructureWall) == true)
                    {
                        // don't allow structure wall cells to be overridden
                        //
                        // overlay files tend to not include structure wall flags, which means that if we don't filter them ourselves, then
                        // we'll end up completely wiping them
                    }
                    else
                    {
                        filteredFlags &= ~VisionPathingFlags.Unknown128;
                        filteredFlags &= ~VisionPathingFlags.TransparentWall;
                        VisionPathingFlags filteredOverrideFlag = overrideFlag & ~VisionPathingFlags.TransparentWall;

                        if (filteredFlags != filteredOverrideFlag)
                        {
                            cell.hasOverride = true;
                        }

                        cell.visionPathingFlags = overrideFlag;
                    }
                }
            }


            Console.WriteLine("\nlast read location:  " + overlayFile.GetFilePosition());
            Console.WriteLine("missed bytes:  " + (overlayFile.GetLength() - overlayFile.GetFilePosition()));
        }
        private void WriteLSGNGRIDFile()
        {
            string baseFileName = this.ngridFile.GetFolderPath() + this.ngridFile.GetName();

            string outputFileName = baseFileName;

            if (this.overlayFile != null)
            {
                outputFileName += "." + this.overlayFile.GetName();
            }
            FileWrapper outputLSGNGRID = new FileWrapper(outputFileName + ".LSGNGRID");

            outputLSGNGRID.Clear();  // don't want excess bytes remaining in the new file


            string magic = "LSGNGRID";

            outputLSGNGRID.WriteChars(magic.ToCharArray());

            outputLSGNGRID.WriteInt(2);  // version number

            outputLSGNGRID.WriteVector3(minBounds);
            outputLSGNGRID.WriteVector3(maxBounds);

            outputLSGNGRID.WriteFloat(cellSize);
            outputLSGNGRID.WriteInt(cellCountX);
            outputLSGNGRID.WriteInt(cellCountZ);

            outputLSGNGRID.WriteInt(heightSampleCountX);
            outputLSGNGRID.WriteInt(heightSampleCountZ);
            outputLSGNGRID.WriteFloat(heightSampleOffsetX);
            outputLSGNGRID.WriteFloat(heightSampleOffsetZ);


            // height sample data is written first because cells need to use it for calculating their own height values

            for (int i = 0; i < heightSamples.Count; i++)
            {
                float sample = heightSamples[i];

                outputLSGNGRID.WriteFloat(sample);
            }


            for (int i = 0; i < cells.Count; i++)
            {
                NavGridCell cell = cells[i];


                outputLSGNGRID.WriteShort((int)cell.visionPathingFlags);
                outputLSGNGRID.WriteByte((int)cell.riverRegionFlags);

                int jungleQuadrantAndMainRegionFlags = ((int)cell.jungleQuadrantFlags) | ((int)cell.mainRegionFlags << 4);
                outputLSGNGRID.WriteByte(jungleQuadrantAndMainRegionFlags);

                int nearestLaneAndPOIFlags = ((int)cell.nearestLaneFlags) | ((int)cell.poiFlags << 4);
                outputLSGNGRID.WriteByte(nearestLaneAndPOIFlags);

                int ringAndSRXFlags = ((int)cell.ringFlags) | ((int)cell.srxFlags << 4);
                outputLSGNGRID.WriteByte(ringAndSRXFlags);
            }


            outputLSGNGRID.Close();
        }
        private void CheckForMissingFlags()
        {
            VisionPathingFlags mergedVisionPathingFlags = (VisionPathingFlags)0;
            RiverRegionFlags   mergedRiverRegionFlags   = (RiverRegionFlags)0;

            List <JungleQuadrantFlags> newJungleQuadrantFlags = new List <JungleQuadrantFlags>();
            List <MainRegionFlags>     newMainRegionFlags     = new List <MainRegionFlags>();
            List <NearestLaneFlags>    newNearestLaneFlags    = new List <NearestLaneFlags>();
            List <POIFlags>            newPOIFlags            = new List <POIFlags>();
            List <RingFlags>           newRingFlags           = new List <RingFlags>();
            List <UnknownSRXFlags>     newSRXFlags            = new List <UnknownSRXFlags>();


            for (int i = 0; i < cells.Count; i++)
            {
                NavGridCell cell = cells[i];


                mergedVisionPathingFlags |= cell.visionPathingFlags;
                mergedRiverRegionFlags   |= cell.riverRegionFlags;


                // these values are always read as a single byte, so no need to worry about signed comparisons

                if (cell.jungleQuadrantFlags > JungleQuadrantFlags.LastKnownFlag && newJungleQuadrantFlags.Contains(cell.jungleQuadrantFlags) == false)
                {
                    newJungleQuadrantFlags.Add(cell.jungleQuadrantFlags);
                }

                if (cell.mainRegionFlags > MainRegionFlags.LastKnownFlag && newMainRegionFlags.Contains(cell.mainRegionFlags) == false)
                {
                    newMainRegionFlags.Add(cell.mainRegionFlags);
                }

                if (cell.nearestLaneFlags > NearestLaneFlags.LastKnownFlag && newNearestLaneFlags.Contains(cell.nearestLaneFlags) == false)
                {
                    newNearestLaneFlags.Add(cell.nearestLaneFlags);
                }

                if (cell.poiFlags > POIFlags.LastKnownFlag && newPOIFlags.Contains(cell.poiFlags) == false)
                {
                    newPOIFlags.Add(cell.poiFlags);
                }

                if (cell.ringFlags > RingFlags.LastKnownFlag && newRingFlags.Contains(cell.ringFlags) == false)
                {
                    newRingFlags.Add(cell.ringFlags);
                }

                if (cell.srxFlags > UnknownSRXFlags.LastKnownFlag && newSRXFlags.Contains(cell.srxFlags) == false)
                {
                    newSRXFlags.Add(cell.srxFlags);
                }
            }


            newJungleQuadrantFlags.Sort();
            newMainRegionFlags.Sort();
            newNearestLaneFlags.Sort();
            newPOIFlags.Sort();
            newRingFlags.Sort();
            newSRXFlags.Sort();


            bool foundNewFlags = false;

            int newVisionPathingFlags = (int)(mergedVisionPathingFlags & ~VisionPathingFlags.KnownFlags);

            if (newVisionPathingFlags != 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new VisionPathingFlags:");

                for (int i = 0; i < 16; i++)
                {
                    int newFlag = newVisionPathingFlags & (1 << i);
                    if (newFlag != 0)
                    {
                        Console.WriteLine(" - " + newFlag);
                    }
                }
            }

            int newRiverRegionFlags = (int)(mergedRiverRegionFlags & ~RiverRegionFlags.KnownFlags);

            if (newRiverRegionFlags != 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new RiverRegionFlags:");

                for (int i = 0; i < 16; i++)
                {
                    int newFlag = newRiverRegionFlags & (1 << i);
                    if (newFlag != 0)
                    {
                        Console.WriteLine(" - " + newFlag);
                    }
                }
            }


            if (newJungleQuadrantFlags.Count > 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new JungleQuadrantFlags:");

                for (int i = 0; i < newJungleQuadrantFlags.Count; i++)
                {
                    Console.WriteLine(" - " + (int)newJungleQuadrantFlags[i]);
                }
            }

            if (newMainRegionFlags.Count > 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new MainRegionFlags:");

                for (int i = 0; i < newMainRegionFlags.Count; i++)
                {
                    Console.WriteLine(" - " + (int)newMainRegionFlags[i]);
                }
            }

            if (newNearestLaneFlags.Count > 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new NearestLaneFlags:");

                for (int i = 0; i < newNearestLaneFlags.Count; i++)
                {
                    Console.WriteLine(" - " + (int)newNearestLaneFlags[i]);
                }
            }

            if (newPOIFlags.Count > 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new POIFlags:");

                for (int i = 0; i < newPOIFlags.Count; i++)
                {
                    Console.WriteLine(" - " + (int)newPOIFlags[i]);
                }
            }

            if (newRingFlags.Count > 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new RingFlags:");

                for (int i = 0; i < newRingFlags.Count; i++)
                {
                    Console.WriteLine(" - " + (int)newRingFlags[i]);
                }
            }

            if (newSRXFlags.Count > 0)
            {
                foundNewFlags = true;
                Console.WriteLine("\nfound new SRXFlags:");

                for (int i = 0; i < newSRXFlags.Count; i++)
                {
                    Console.WriteLine(" - " + (int)newSRXFlags[i]);
                }
            }


            if (foundNewFlags == true)
            {
                Console.WriteLine("\nreport any new flags found (except for on the very first Nexus Blitz layout)");
                Program.Pause();
            }
            else
            {
                Console.WriteLine("\nno unexpected flags found");
            }
        }
        private void ReadCellsVersion5()
        {
            int totalCellCount = cellCountX * cellCountZ;

            for (int i = 0; i < totalCellCount; i++)
            {
                NavGridCell cell = new NavGridCell();

                cell.index = i;


                ngridFile.ReadFloat(); // center height (overridden by height samples)
                ngridFile.ReadInt();   // session ID
                ngridFile.ReadFloat(); // arrival cost
                ngridFile.ReadInt();   // is open
                ngridFile.ReadFloat(); // heuristic
                ngridFile.ReadInt();   // actor list

                cell.x = ngridFile.ReadShort();
                cell.z = ngridFile.ReadShort();

                ngridFile.ReadFloat();                          // additional cost
                ngridFile.ReadFloat();                          // hint as good cell
                ngridFile.ReadInt();                            // additional cost count
                ngridFile.ReadInt();                            // good cell session ID
                ngridFile.ReadFloat();                          // hint weight

                int arrivalDirection   = ngridFile.ReadShort(); // arrival direction
                int visionPathingFlags = ngridFile.ReadShort();
                int hintNode1          = ngridFile.ReadShort(); // hint node 1
                int hintNode2          = ngridFile.ReadShort(); // hint node 2


                if (ngridMajorVersion == 2 && hintNode2 == 0)
                {
                    // older versions only gave one byte to arrival direction and vision pathing flags instead of two bytes,
                    // so we have to do some reshuffling since there was no change in major version number to reflect this,
                    // and minor version numbers didn't exist yet (both one-byte and two-byte variants use version 2)
                    //
                    // this leads to the unofficial version numbers 2.0 and 2.1

                    hintNode2 = hintNode1;
                    hintNode1 = visionPathingFlags;

                    visionPathingFlags = (arrivalDirection & ~0xff) >> 8;
                    arrivalDirection  &= 0xff;
                }

                cell.visionPathingFlags = (VisionPathingFlags)visionPathingFlags;


                cells.Add(cell);
            }


            if (ngridMajorVersion == 5)
            {
                // version 5 only has 2 bytes per cell instead of version 7's 4 bytes per cell, meaning that some flag layers are missing in version 5
                Console.WriteLine("reading flag block:  " + ngridFile.GetFilePosition());
                for (int i = 0; i < totalCellCount; i++)
                {
                    cells[i].riverRegionFlags = (RiverRegionFlags)ngridFile.ReadByte();

                    int jungleQuadrantAndMainRegionFlags = ngridFile.ReadByte();
                    cells[i].jungleQuadrantFlags = (JungleQuadrantFlags)(jungleQuadrantAndMainRegionFlags & 0x0f);
                    cells[i].mainRegionFlags     = (MainRegionFlags)((jungleQuadrantAndMainRegionFlags & ~0x0f) >> 4);
                }


                // version 5 only has 4 blocks of 132 bytes each instead of version 7's 8 blocks of 132 bytes each
                Console.WriteLine("reading unknown block:  " + ngridFile.GetFilePosition());
                for (int i = 0; i < 4; i++)
                {
                    for (int j = 0; j < 132; j++)
                    {
                        ngridFile.ReadByte();
                    }
                }
            }
            else
            {
                // version 2 and version 3 lack an extra flag block and jump straight into height samples after the last cell
            }
        }
        private void ReadCellsVersion7()
        {
            int totalCellCount = cellCountX * cellCountZ;

            for (int i = 0; i < totalCellCount; i++)
            {
                NavGridCell cell = new NavGridCell();

                cell.index = i;


                ngridFile.ReadFloat(); // center height (overridden by height samples)
                ngridFile.ReadInt();   // session ID
                ngridFile.ReadFloat(); // arrival cost
                ngridFile.ReadInt();   // is open
                ngridFile.ReadFloat(); // heuristic

                cell.x = ngridFile.ReadShort();
                cell.z = ngridFile.ReadShort();

                ngridFile.ReadInt();   // actor list
                ngridFile.ReadInt();   // unknown 1
                ngridFile.ReadInt();   // good cell session ID
                ngridFile.ReadFloat(); // hint weight
                ngridFile.ReadShort(); // unknown 2
                ngridFile.ReadShort(); // arrival direction
                ngridFile.ReadShort(); // hint node 1
                ngridFile.ReadShort(); // hint node 2


                cells.Add(cell);
            }


            for (int i = 0; i < totalCellCount; i++)
            {
                cells[i].visionPathingFlags = (VisionPathingFlags)ngridFile.ReadShort();
            }

            for (int i = 0; i < totalCellCount; i++)
            {
                cells[i].riverRegionFlags = (RiverRegionFlags)ngridFile.ReadByte();

                int jungleQuadrantAndMainRegionFlags = ngridFile.ReadByte();
                cells[i].jungleQuadrantFlags = (JungleQuadrantFlags)(jungleQuadrantAndMainRegionFlags & 0x0f);
                cells[i].mainRegionFlags     = (MainRegionFlags)((jungleQuadrantAndMainRegionFlags & ~0x0f) >> 4);

                int nearestLaneAndPOIFlags = ngridFile.ReadByte();
                cells[i].nearestLaneFlags = (NearestLaneFlags)(nearestLaneAndPOIFlags & 0x0f);
                cells[i].poiFlags         = (POIFlags)((nearestLaneAndPOIFlags & ~0x0f) >> 4);

                int ringAndSRXFlags = ngridFile.ReadByte();
                cells[i].ringFlags = (RingFlags)(ringAndSRXFlags & 0x0f);
                cells[i].srxFlags  = (UnknownSRXFlags)((ringAndSRXFlags & ~0x0f) >> 4);
            }


            // appears to be 8 blocks of 132 bytes each, but in practice only 7 are used and the 8th is all zeros
            //
            // roughly appears to be 8 bytes of maybe some sort of hash followed by alternating between four bytes of zero and four bytes
            // of garbage (a couple make valid floats, most are invalid floats, maybe more hashes?)
            //
            // at a certain point, each block becomes all zero for the rest of the block, but this varies by block (appears to be around
            // 40-48 bytes after the first 8 bytes until the rest is all zero)

            Console.WriteLine("reading unknown block:  " + ngridFile.GetFilePosition());
            for (int i = 0; i < 8; i++)
            {
                for (int j = 0; j < 132; j++)
                {
                    ngridFile.ReadByte();
                }
            }
        }