Ejemplo n.º 1
0
        public void parseSubChunks()
        {
            int index = 8; // skip self

            while (index > 0 && index + 8 <= _data.Length)
            {
                string header = Encoding.UTF8.GetString(_data, index, 4);
                if (ChunkedFile.IsInterestingChunk(header))
                {
                    uint subsize = BitConverter.ToUInt32(_data, index + 4);
                    if (subsize < _size)
                    {
                        header = ChunkedFile.InterestingChunks[header];
                        FileChunk chunk = new FileChunk(_data, index, subsize);
                        chunk.parseSubChunks();
                        if (!subchunks.ContainsKey(header))
                        {
                            subchunks[header] = new List <FileChunk>();
                        }

                        subchunks[header].Add(chunk);
                    }

                    index += (int)(subsize + 8);
                }
                else
                {
                    index += 4;
                }
            }
        }
Ejemplo n.º 2
0
        static void ExtractMaps()
        {
            ExtractDbcFiles();
            ExtractCameraFiles();
            ExtractGameTablesFiles();

            Console.WriteLine("Extracting maps...");

            string path = $"{BaseDirectory}/maps";

            CreateDirectory(path);

            Console.WriteLine("Convert map files");
            int count = 1;

            Console.WriteLine("Loading DB2 files");
            var mapStorage = DBReader.Read <MapRecord>("DBFilesClient\\Map.db2");

            if (mapStorage == null)
            {
                Console.WriteLine("Fatal error: Invalid Map.db2 file format!");
                return;
            }

            foreach (var record in mapStorage.Values)
            {
                Console.Write($"Extract {record.Directory} ({count++}/{mapStorage.Count})                  \n");
                // Loadup map grid data
                string      storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}.wdt";
                ChunkedFile wdt         = new ChunkedFile();
                if (wdt.loadFile(CascHandler, storagePath))
                {
                    wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>();
                    for (int y = 0; y < 64; ++y)
                    {
                        for (int x = 0; x < 64; ++x)
                        {
                            if (Convert.ToBoolean(main.adt_list[y][x].flag & 0x1))
                            {
                                storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}_{x}_{y}.adt";
                                string outputFileName = $"{path}/{record.Id:D4}_{y:D2}_{x:D2}.map";
                                MapFile.ConvertADT(CascHandler, storagePath, outputFileName, y, x, MapFile.IsDeepWaterIgnored(record.Id, y, x));
                            }
                        }
                        // draw progress bar
                        Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r");
                    }
                }
            }
            Console.WriteLine("\n");
        }
Ejemplo n.º 3
0
        static void ExtractMaps(uint build)
        {
            Console.WriteLine("Extracting maps...\n");

            string path = $"{baseDirectory}/maps";

            CreateDirectory(path);

            Console.WriteLine("Convert map files\n");
            int count = 1;

            foreach (var record in CliDB.MapStorage.Values)
            {
                Console.Write($"Extract {record.Directory} ({count++}/{CliDB.MapStorage.Count})                  \n");
                // Loadup map grid data
                string      storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}.wdt";
                ChunkedFile wdt         = new ChunkedFile();
                if (wdt.loadFile(cascHandler, storagePath))
                {
                    wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>();
                    for (int y = 0; y < 64; ++y)
                    {
                        for (int x = 0; x < 64; ++x)
                        {
                            if (Convert.ToBoolean(main.adt_list[y][x].flag & 0x1))
                            {
                                storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}_{x}_{y}.adt";
                                string outputFileName  = $"{path}/{record.Id:D4}_{y:D2}_{x:D2}.map";
                                bool   ignoreDeepWater = MapFile.IsDeepWaterIgnored(record.Id, y, x);
                                MapFile.ConvertADT(cascHandler, storagePath, outputFileName, y, x, build, ignoreDeepWater);
                            }
                        }
                        // draw progress bar
                        Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r");
                    }
                }
            }
            Console.WriteLine("\n");
        }
Ejemplo n.º 4
0
        public static bool ConvertADT(CASCHandler cascHandler, string inputPath, string outputPath, int cell_y, int cell_x, uint build)
        {
            ChunkedFile adt = new ChunkedFile();

            if (!adt.loadFile(cascHandler, inputPath))
            {
                return(false);
            }

            // Prepare map header
            map_fileheader map;

            map.mapMagic     = MAP_MAGIC;
            map.versionMagic = MAP_VERSION_MAGIC;
            map.buildMagic   = build;

            // Get area flags data
            for (var x = 0; x < ADT_CELLS_PER_GRID; ++x)
            {
                for (var y = 0; y < ADT_CELLS_PER_GRID; ++y)
                {
                    area_ids[x][y]     = 0;
                    liquid_entry[x][y] = 0;
                    liquid_flags[x][y] = 0;
                }
            }

            for (var x = 0; x < ADT_GRID_SIZE; ++x)
            {
                for (var y = 0; y < ADT_GRID_SIZE; ++y)
                {
                    V8[x][y]          = 0;
                    liquid_show[x][y] = false;
                }
            }

            for (var x = 0; x < ADT_GRID_SIZE + 1; ++x)
            {
                for (var y = 0; y < ADT_GRID_SIZE + 1; ++y)
                {
                    V9[x][y] = 0;
                }
            }

            for (var x = 0; x < ADT_CELLS_PER_GRID; ++x)
            {
                for (var y = 0; y < ADT_CELLS_PER_GRID; ++y)
                {
                    for (var z = 0; z < 8; ++z)
                    {
                        holes[x][y][z] = 0;
                    }
                }
            }

            bool hasHoles     = false;
            bool hasFlightBox = false;

            foreach (var fileChunk in adt.chunks.LookupByKey("MCNK"))
            {
                adt_MCNK mcnk = fileChunk.As <adt_MCNK>();

                // Area data
                area_ids[mcnk.iy][mcnk.ix] = (ushort)mcnk.areaid;

                // Height
                // Height values for triangles stored in order:
                // 1     2     3     4     5     6     7     8     9
                //    10    11    12    13    14    15    16    17
                // 18    19    20    21    22    23    24    25    26
                //    27    28    29    30    31    32    33    34
                // . . . . . . . .
                // For better get height values merge it to V9 and V8 map
                // V9 height map:
                // 1     2     3     4     5     6     7     8     9
                // 18    19    20    21    22    23    24    25    26
                // . . . . . . . .
                // V8 height map:
                //    10    11    12    13    14    15    16    17
                //    27    28    29    30    31    32    33    34
                // . . . . . . . .

                // Set map height as grid height
                for (int y = 0; y <= ADT_CELL_SIZE; y++)
                {
                    int cy = (int)mcnk.iy * ADT_CELL_SIZE + y;
                    for (int x = 0; x <= ADT_CELL_SIZE; x++)
                    {
                        int cx = (int)mcnk.ix * ADT_CELL_SIZE + x;
                        V9[cy][cx] = mcnk.ypos;
                    }
                }

                for (int y = 0; y < ADT_CELL_SIZE; y++)
                {
                    int cy = (int)mcnk.iy * ADT_CELL_SIZE + y;
                    for (int x = 0; x < ADT_CELL_SIZE; x++)
                    {
                        int cx = (int)mcnk.ix * ADT_CELL_SIZE + x;
                        V8[cy][cx] = mcnk.ypos;
                    }
                }

                // Get custom height
                FileChunk chunk = fileChunk.GetSubChunk("MCVT");
                if (chunk != null)
                {
                    adt_MCVT mcvt = chunk.As <adt_MCVT>();

                    // get V9 height map
                    for (int y = 0; y <= ADT_CELL_SIZE; y++)
                    {
                        int cy = (int)mcnk.iy * ADT_CELL_SIZE + y;
                        for (int x = 0; x <= ADT_CELL_SIZE; x++)
                        {
                            int cx = (int)mcnk.ix * ADT_CELL_SIZE + x;
                            V9[cy][cx] += mcvt.height_map[y * (ADT_CELL_SIZE * 2 + 1) + x];
                        }
                    }
                    // get V8 height map
                    for (int y = 0; y < ADT_CELL_SIZE; y++)
                    {
                        int cy = (int)mcnk.iy * ADT_CELL_SIZE + y;
                        for (int x = 0; x < ADT_CELL_SIZE; x++)
                        {
                            int cx = (int)mcnk.ix * ADT_CELL_SIZE + x;
                            V8[cy][cx] += mcvt.height_map[y * (ADT_CELL_SIZE * 2 + 1) + ADT_CELL_SIZE + 1 + x];
                        }
                    }
                }

                // Liquid data
                if (mcnk.sizeMCLQ > 8)
                {
                    FileChunk LiquidChunk = fileChunk.GetSubChunk("MCLQ");
                    if (LiquidChunk != null)
                    {
                        adt_MCLQ liquid = LiquidChunk.As <adt_MCLQ>();
                        int      count  = 0;
                        for (int y = 0; y < ADT_CELL_SIZE; ++y)
                        {
                            int cy = (int)mcnk.iy * ADT_CELL_SIZE + y;
                            for (int x = 0; x < ADT_CELL_SIZE; ++x)
                            {
                                int cx = (int)mcnk.ix * ADT_CELL_SIZE + x;
                                if (liquid.flags[y][x] != 0x0F)
                                {
                                    liquid_show[cy][cx] = true;
                                    if (Convert.ToBoolean(liquid.flags[y][x] & (1 << 7)))
                                    {
                                        liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.DarkWater;
                                    }
                                    ++count;
                                }
                            }
                        }

                        uint c_flag = mcnk.flags;
                        if (Convert.ToBoolean(c_flag & (1 << 2)))
                        {
                            liquid_entry[mcnk.iy][mcnk.ix]  = 1;
                            liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.Water;            // water
                        }
                        if (Convert.ToBoolean(c_flag & (1 << 3)))
                        {
                            liquid_entry[mcnk.iy][mcnk.ix]  = 2;
                            liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.Ocean;            // ocean
                        }
                        if (Convert.ToBoolean(c_flag & (1 << 4)))
                        {
                            liquid_entry[mcnk.iy][mcnk.ix]  = 3;
                            liquid_flags[mcnk.iy][mcnk.ix] |= (byte)LiquidTypeMask.Magma;            // magma/slime
                        }

                        if (count == 0 && liquid_flags[mcnk.iy][mcnk.ix] != 0)
                        {
                            Console.WriteLine("Wrong liquid detect in MCLQ chunk");
                        }

                        for (int y = 0; y <= ADT_CELL_SIZE; ++y)
                        {
                            int cy = (int)mcnk.iy * ADT_CELL_SIZE + y;
                            for (int x = 0; x <= ADT_CELL_SIZE; ++x)
                            {
                                int cx = (int)mcnk.ix * ADT_CELL_SIZE + x;
                                liquid_height[cy][cx] = liquid.liquid[y][x].height;
                            }
                        }
                    }
                }

                // Hole data
                if (!Convert.ToBoolean(mcnk.flags & 0x10000))
                {
                    uint hole = mcnk.holes;
                    if (hole != 0)
                    {
                        if (TransformToHighRes((ushort)hole, holes[mcnk.iy][mcnk.ix]))
                        {
                            hasHoles = true;
                        }
                    }
                }
                else
                {
                    Buffer.BlockCopy(mcnk.HighResHoles, 0, holes[mcnk.iy][mcnk.ix], 0, 8);
                    if (BitConverter.ToUInt64(holes[mcnk.iy][mcnk.ix], 0) != 0)
                    {
                        hasHoles = true;
                    }
                }
            }

            // Get liquid map for grid (in WOTLK used MH2O chunk)
            FileChunk chunkMH20 = adt.GetChunk("MH2O");

            if (chunkMH20 != null)
            {
                adt_MH2O h2o = chunkMH20.As <adt_MH2O>();
                for (int i = 0; i < ADT_CELLS_PER_GRID; i++)
                {
                    for (int j = 0; j < ADT_CELLS_PER_GRID; j++)
                    {
                        adt_liquid_header?h = h2o.getLiquidData(i, j);
                        if (!h.HasValue)
                        {
                            continue;
                        }

                        adt_liquid_header adtLiquidHeader = h.Value;

                        int   count = 0;
                        ulong show  = h2o.getLiquidShowMap(adtLiquidHeader);
                        for (int y = 0; y < adtLiquidHeader.height; y++)
                        {
                            int cy = i * ADT_CELL_SIZE + y + adtLiquidHeader.yOffset;
                            for (int x = 0; x < adtLiquidHeader.width; x++)
                            {
                                int cx = j * ADT_CELL_SIZE + x + adtLiquidHeader.xOffset;
                                if (Convert.ToBoolean(show & 1))
                                {
                                    liquid_show[cy][cx] = true;
                                    ++count;
                                }
                                show >>= 1;
                            }
                        }

                        liquid_entry[i][j] = adtLiquidHeader.liquidType;
                        var liquidTypeRecord = Program.liquidTypeStorage[adtLiquidHeader.liquidType];
                        switch ((LiquidType)liquidTypeRecord.LiquidType)
                        {
                        case LiquidType.Water:
                            liquid_flags[i][j] |= (byte)LiquidTypeMask.Water;
                            break;

                        case LiquidType.Ocean:
                            liquid_flags[i][j] |= (byte)LiquidTypeMask.Ocean;
                            break;

                        case LiquidType.Magma:
                            liquid_flags[i][j] |= (byte)LiquidTypeMask.Magma;
                            break;

                        case LiquidType.Slime:
                            liquid_flags[i][j] |= (byte)LiquidTypeMask.Slime;
                            break;

                        default:
                            Console.WriteLine($"\nCan't find Liquid type {adtLiquidHeader.liquidType} for map {inputPath}\nchunk {i},{j}\n");
                            break;
                        }
                        // Dark water detect
                        if ((LiquidType)liquidTypeRecord.LiquidType == LiquidType.Ocean)
                        {
                            byte[] lm = h2o.getLiquidLightMap(adtLiquidHeader);
                            if (lm == null)
                            {
                                liquid_flags[i][j] |= (byte)LiquidTypeMask.DarkWater;
                            }
                        }

                        if (count == 0 && liquid_flags[i][j] != 0)
                        {
                            Console.WriteLine("Wrong liquid detect in MH2O chunk");
                        }

                        float[] height = h2o.getLiquidHeightMap(adtLiquidHeader);
                        int     pos    = 0;
                        for (int y = 0; y <= adtLiquidHeader.height; y++)
                        {
                            int cy = i * ADT_CELL_SIZE + y + adtLiquidHeader.yOffset;
                            for (int x = 0; x <= adtLiquidHeader.width; x++)
                            {
                                int cx = j * ADT_CELL_SIZE + x + adtLiquidHeader.xOffset;

                                if (height != null)
                                {
                                    liquid_height[cy][cx] = height[pos];
                                }
                                else
                                {
                                    liquid_height[cy][cx] = adtLiquidHeader.heightLevel1;
                                }

                                pos++;
                            }
                        }
                    }
                }
            }

            FileChunk chunkMFBO = adt.GetChunk("MFBO");

            if (chunkMFBO != null)
            {
                adt_MFBO mfbo = chunkMFBO.As <adt_MFBO>();
                for (var i = 0; i < 3; ++i)
                {
                    flight_box_max[i][0] = mfbo.max.coords[0 + i * 3];
                    flight_box_max[i][1] = mfbo.max.coords[1 + i * 3];
                    flight_box_max[i][2] = mfbo.max.coords[2 + i * 3];

                    flight_box_min[i][0] = mfbo.min.coords[0 + i * 3];
                    flight_box_min[i][1] = mfbo.min.coords[1 + i * 3];
                    flight_box_min[i][2] = mfbo.min.coords[2 + i * 3];
                }
                hasFlightBox = true;
            }

            //============================================
            // Try pack area data
            //============================================
            bool fullAreaData = false;
            uint areaId       = area_ids[0][0];

            for (int y = 0; y < ADT_CELLS_PER_GRID; ++y)
            {
                for (int x = 0; x < ADT_CELLS_PER_GRID; ++x)
                {
                    if (area_ids[y][x] != areaId)
                    {
                        fullAreaData = true;
                        break;
                    }
                }
            }

            map.areaMapOffset = 44;
            map.areaMapSize   = 8;

            map_areaHeader areaHeader;

            areaHeader.fourcc = MAP_AREA_MAGIC;
            areaHeader.flags  = 0;
            if (fullAreaData)
            {
                areaHeader.gridArea = 0;
                map.areaMapSize    += 512;
            }
            else
            {
                areaHeader.flags   |= 0x0001;
                areaHeader.gridArea = (ushort)areaId;
            }

            //============================================
            // Try pack height data
            //============================================
            float maxHeight = -20000;
            float minHeight = 20000;

            for (int y = 0; y < ADT_GRID_SIZE; y++)
            {
                for (int x = 0; x < ADT_GRID_SIZE; x++)
                {
                    float h = V8[y][x];
                    if (maxHeight < h)
                    {
                        maxHeight = h;
                    }
                    if (minHeight > h)
                    {
                        minHeight = h;
                    }
                }
            }
            for (int y = 0; y <= ADT_GRID_SIZE; y++)
            {
                for (int x = 0; x <= ADT_GRID_SIZE; x++)
                {
                    float h = V9[y][x];
                    if (maxHeight < h)
                    {
                        maxHeight = h;
                    }
                    if (minHeight > h)
                    {
                        minHeight = h;
                    }
                }
            }

            // Check for allow limit minimum height (not store height in deep ochean - allow save some memory)
            if (minHeight < -500.0f)
            {
                for (int y = 0; y < ADT_GRID_SIZE; y++)
                {
                    for (int x = 0; x < ADT_GRID_SIZE; x++)
                    {
                        if (V8[y][x] < -500.0f)
                        {
                            V8[y][x] = -500.0f;
                        }
                    }
                }
                for (int y = 0; y <= ADT_GRID_SIZE; y++)
                {
                    for (int x = 0; x <= ADT_GRID_SIZE; x++)
                    {
                        if (V9[y][x] < -500.0f)
                        {
                            V9[y][x] = -500.0f;
                        }
                    }
                }

                if (minHeight < -500.0f)
                {
                    minHeight = -500.0f;
                }
                if (maxHeight < -500.0f)
                {
                    maxHeight = -500.0f;
                }
            }

            map.heightMapOffset = map.areaMapOffset + map.areaMapSize;
            map.heightMapSize   = (uint)Marshal.SizeOf <map_heightHeader>();

            map_heightHeader heightHeader;

            heightHeader.fourcc        = MAP_HEIGHT_MAGIC;
            heightHeader.flags         = 0;
            heightHeader.gridHeight    = minHeight;
            heightHeader.gridMaxHeight = maxHeight;

            if (maxHeight == minHeight)
            {
                heightHeader.flags |= (uint)MapHeightFlags.NoHeight;
            }

            // Not need store if flat surface
            if ((maxHeight - minHeight) < 0.005f)
            {
                heightHeader.flags |= (uint)MapHeightFlags.NoHeight;
            }

            if (hasFlightBox)
            {
                heightHeader.flags |= (uint)MapHeightFlags.HasFlightBounds;
                map.heightMapSize  += 18 + 18;
            }

            // Try store as packed in uint16 or uint8 values
            if (!Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.NoHeight))
            {
                float step = 0;
                // Try Store as uint values
                if (true)//CONF_allow_float_to_int
                {
                    float diff = maxHeight - minHeight;
                    if (diff < 2.0f)      // As uint8 (max accuracy = CONF_float_to_int8_limit/256)
                    {
                        heightHeader.flags |= (uint)MapHeightFlags.AsInt8;
                        step = 255 / diff;
                    }
                    else if (diff < 2048.0f)  // As uint16 (max accuracy = CONF_float_to_int16_limit/65536)
                    {
                        heightHeader.flags |= (uint)MapHeightFlags.AsInt16;
                        step = 65535 / diff;
                    }
                }

                // Pack it to int values if need
                if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt8))
                {
                    for (int y = 0; y < ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x < ADT_GRID_SIZE; x++)
                        {
                            uint8_V8[y][x] = (byte)((V8[y][x] - minHeight) * step + 0.5f);
                        }
                    }
                    for (int y = 0; y <= ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x <= ADT_GRID_SIZE; x++)
                        {
                            uint8_V9[y][x] = (byte)((V9[y][x] - minHeight) * step + 0.5f);
                        }
                    }
                    map.heightMapSize += 16641 + 16384;
                }
                else if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt16))
                {
                    for (int y = 0; y < ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x < ADT_GRID_SIZE; x++)
                        {
                            uint16_V8[y][x] = (ushort)((V8[y][x] - minHeight) * step + 0.5f);
                        }
                    }

                    for (int y = 0; y <= ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x <= ADT_GRID_SIZE; x++)
                        {
                            uint16_V9[y][x] = (ushort)((V9[y][x] - minHeight) * step + 0.5f);
                        }
                    }

                    map.heightMapSize += 33282 + 32768;
                }
                else
                {
                    map.heightMapSize += 66564 + 65536;
                }
            }

            //============================================
            // Pack liquid data
            //============================================
            byte type     = liquid_flags[0][0];
            bool fullType = false;

            for (int y = 0; y < ADT_CELLS_PER_GRID; y++)
            {
                for (int x = 0; x < ADT_CELLS_PER_GRID; x++)
                {
                    if (liquid_flags[y][x] != type)
                    {
                        fullType = true;
                        y        = ADT_CELLS_PER_GRID;
                        break;
                    }
                }
            }

            map_liquidHeader mapLiquidHeader = new map_liquidHeader();

            // no water data (if all grid have 0 liquid type)
            if (type == 0 && !fullType)
            {
                // No liquid data
                map.liquidMapOffset = 0;
                map.liquidMapSize   = 0;
            }
            else
            {
                int minX = 255, minY = 255;
                int maxX = 0, maxY = 0;
                maxHeight = -20000;
                minHeight = 20000;
                for (int y = 0; y < ADT_GRID_SIZE; y++)
                {
                    for (int x = 0; x < ADT_GRID_SIZE; x++)
                    {
                        if (liquid_show[y][x])
                        {
                            if (minX > x)
                            {
                                minX = x;
                            }
                            if (maxX < x)
                            {
                                maxX = x;
                            }
                            if (minY > y)
                            {
                                minY = y;
                            }
                            if (maxY < y)
                            {
                                maxY = y;
                            }
                            float h = liquid_height[y][x];
                            if (maxHeight < h)
                            {
                                maxHeight = h;
                            }
                            if (minHeight > h)
                            {
                                minHeight = h;
                            }
                        }
                        else
                        {
                            liquid_height[y][x] = -500.0f;
                        }
                    }
                }
                map.liquidMapOffset         = map.heightMapOffset + map.heightMapSize;
                map.liquidMapSize           = (uint)Marshal.SizeOf <map_liquidHeader>();
                mapLiquidHeader.fourcc      = MAP_LIQUID_MAGIC;
                mapLiquidHeader.flags       = 0;
                mapLiquidHeader.liquidType  = 0;
                mapLiquidHeader.offsetX     = (byte)minX;
                mapLiquidHeader.offsetY     = (byte)minY;
                mapLiquidHeader.width       = (byte)(maxX - minX + 1 + 1);
                mapLiquidHeader.height      = (byte)(maxY - minY + 1 + 1);
                mapLiquidHeader.liquidLevel = minHeight;

                if (maxHeight == minHeight)
                {
                    mapLiquidHeader.flags |= 0x0002;
                }

                // Not need store if flat surface
                if ((maxHeight - minHeight) < 0.001f)
                {
                    mapLiquidHeader.flags |= 0x0002;
                }

                if (!fullType)
                {
                    mapLiquidHeader.flags |= 0x0001;
                }

                if (Convert.ToBoolean(mapLiquidHeader.flags & 0x0001))
                {
                    mapLiquidHeader.liquidType = type;
                }
                else
                {
                    map.liquidMapSize += 512 + 256;
                }

                if (!Convert.ToBoolean(mapLiquidHeader.flags & 0x0002))
                {
                    map.liquidMapSize += (uint)(sizeof(float) * mapLiquidHeader.width * mapLiquidHeader.height);
                }
            }

            if (map.liquidMapOffset != 0)
            {
                map.holesOffset = map.liquidMapOffset + map.liquidMapSize;
            }
            else
            {
                map.holesOffset = map.heightMapOffset + map.heightMapSize;
            }

            if (hasHoles)
            {
                map.holesSize = 2048;// (uint)(1 * holes.Length);
            }
            else
            {
                map.holesSize = 0;
            }

            // Ok all data prepared - store it
            using (BinaryWriter binaryWriter = new BinaryWriter(File.Open(outputPath, FileMode.Create, FileAccess.Write)))
            {
                binaryWriter.WriteStruct(map);
                // Store area data
                binaryWriter.WriteStruct(areaHeader);
                if (!Convert.ToBoolean(areaHeader.flags & 0x0001))
                {
                    for (var x = 0; x < area_ids.Length; ++x)
                    {
                        for (var y = 0; y < area_ids[x].Length; ++y)
                        {
                            binaryWriter.Write(area_ids[x][y]);
                        }
                    }
                }

                // Store height data
                binaryWriter.WriteStruct(heightHeader);
                if (!Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.NoHeight))
                {
                    if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt16))
                    {
                        for (var x = 0; x < uint16_V9.Length; ++x)
                        {
                            for (var y = 0; y < uint16_V9[x].Length; ++y)
                            {
                                binaryWriter.Write(uint16_V9[x][y]);
                            }
                        }

                        for (var x = 0; x < uint16_V8.Length; ++x)
                        {
                            for (var y = 0; y < uint16_V8[x].Length; ++y)
                            {
                                binaryWriter.Write(uint16_V8[x][y]);
                            }
                        }
                    }
                    else if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.AsInt8))
                    {
                        for (var x = 0; x < uint8_V9.Length; ++x)
                        {
                            for (var y = 0; y < uint8_V9[x].Length; ++y)
                            {
                                binaryWriter.Write(uint8_V9[x][y]);
                            }
                        }

                        for (var x = 0; x < uint8_V8.Length; ++x)
                        {
                            for (var y = 0; y < uint8_V8[x].Length; ++y)
                            {
                                binaryWriter.Write(uint8_V8[x][y]);
                            }
                        }
                    }
                    else
                    {
                        for (var x = 0; x < V9.Length; ++x)
                        {
                            for (var y = 0; y < V9[x].Length; ++y)
                            {
                                binaryWriter.Write(V9[x][y]);
                            }
                        }

                        for (var x = 0; x < V8.Length; ++x)
                        {
                            for (var y = 0; y < V8[x].Length; ++y)
                            {
                                binaryWriter.Write(V8[x][y]);
                            }
                        }
                    }
                }

                if (Convert.ToBoolean(heightHeader.flags & (uint)MapHeightFlags.HasFlightBounds))
                {
                    for (var x = 0; x < 3; ++x)
                    {
                        for (var y = 0; y < 3; ++y)
                        {
                            binaryWriter.Write(flight_box_max[x][y]);
                        }
                    }

                    for (var x = 0; x < 3; ++x)
                    {
                        for (var y = 0; y < 3; ++y)
                        {
                            binaryWriter.Write(flight_box_min[x][y]);
                        }
                    }
                }

                // Store liquid data if need
                if (map.liquidMapOffset != 0)
                {
                    binaryWriter.WriteStruct(mapLiquidHeader);
                    if (!Convert.ToBoolean(mapLiquidHeader.flags & 0x0001))
                    {
                        for (var x = 0; x < liquid_entry.Length; ++x)
                        {
                            for (var y = 0; y < liquid_entry[x].Length; ++y)
                            {
                                binaryWriter.Write(liquid_entry[x][y]);
                            }
                        }

                        for (var x = 0; x < liquid_flags.Length; ++x)
                        {
                            for (var y = 0; y < liquid_flags[x].Length; ++y)
                            {
                                binaryWriter.Write(liquid_flags[x][y]);
                            }
                        }
                    }

                    if (!Convert.ToBoolean(mapLiquidHeader.flags & 0x0002))
                    {
                        for (int y = 0; y < mapLiquidHeader.height; y++)
                        {
                            for (int x = 0; x < mapLiquidHeader.width; x++)
                            {
                                binaryWriter.Write(liquid_height[y + mapLiquidHeader.offsetY][x + mapLiquidHeader.offsetX]);
                            }
                        }
                    }
                }

                // store hole data
                if (hasHoles)
                {
                    for (var x = 0; x < holes.Length; ++x)
                    {
                        for (var y = 0; y < holes[x].Length; ++y)
                        {
                            for (var z = 0; z < holes[x][y].Length; ++z)
                            {
                                binaryWriter.Write(holes[x][y][z]);
                            }
                        }
                    }
                }
            }

            return(true);
        }
Ejemplo n.º 5
0
        static void ExtractMaps()
        {
            ExtractDbcFiles();
            ExtractCameraFiles();
            ExtractGameTablesFiles();

            Console.WriteLine("Extracting maps...");

            string path = $"{BaseDirectory}/maps";

            CreateDirectory(path);

            Console.WriteLine("Convert map files");
            int count = 1;

            Console.WriteLine("Loading DB2 files");
            var mapStorage = DBReader.Read <MapRecord>(1349477);

            if (mapStorage == null)
            {
                Console.WriteLine("Fatal error: Invalid Map.db2 file format!");
                return;
            }

            foreach (var record in mapStorage.Values)
            {
                Console.Write($"Extract {record.Directory} ({count++}/{mapStorage.Count})                  \n");
                // Loadup map grid data
                ChunkedFile wdt = new ChunkedFile();
                if (wdt.loadFile(CascHandler, record.WdtFileDataID, $"WDT for map {record.Id}"))
                {
                    wdt_MPHD mphd = wdt.GetChunk("MPHD").As <wdt_MPHD>();
                    wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>();
                    for (int y = 0; y < 64; ++y)
                    {
                        for (int x = 0; x < 64; ++x)
                        {
                            if (!Convert.ToBoolean(main.adt_list[y][x].flag & 0x1))
                            {
                                continue;
                            }

                            string outputFileName  = $"{path}/{record.Id:D4}_{y:D2}_{x:D2}.map";
                            bool   ignoreDeepWater = MapFile.IsDeepWaterIgnored(record.Id, y, x);
                            if (mphd != null && (mphd.flags & 0x200) != 0)
                            {
                                wdt_MAID maid = wdt.GetChunk("MAID").As <wdt_MAID>();
                                MapFile.ConvertADT(CascHandler, maid.adt_files[y][x].rootADT, record.MapName, outputFileName, y, x, ignoreDeepWater);
                            }
                            else
                            {
                                string storagePath = $"World\\Maps\\{record.Directory}\\{record.Directory}_{x}_{y}.adt";
                                MapFile.ConvertADT(CascHandler, storagePath, record.MapName, outputFileName, y, x, ignoreDeepWater);
                            }
                        }
                        // draw progress bar
                        Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r");
                    }
                }
            }
            Console.WriteLine("\n");
        }
Ejemplo n.º 6
0
        static void ExtractMaps(uint build)
        {
            Console.WriteLine("Extracting maps...\n");
            Console.WriteLine("Loading db2 files...\n");

            var stream = cascHandler.ReadFile("DBFilesClient\\Map.db2");

            if (stream == null)
            {
                Console.WriteLine("Unable to open file DBFilesClient\\Map.db2 in the archive\n");
                return;
            }
            mapStorage = DB6Reader.Read <MapRecord>(stream, DB6Metas.MapMeta);
            if (mapStorage == null)
            {
                Console.WriteLine("Fatal error: Invalid Map.db2 file format!\n");
                return;
            }

            stream = cascHandler.ReadFile("DBFilesClient\\LiquidType.db2");
            if (stream == null)
            {
                Console.WriteLine("Unable to open file DBFilesClient\\LiquidType.db2 in the archive\n");
                return;
            }

            liquidTypeStorage = DB6Reader.Read <LiquidTypeRecord>(stream, DB6Metas.LiquidTypeMeta);
            if (liquidTypeStorage == null)
            {
                Console.WriteLine("Fatal error: Invalid LiquidType.db2 file format!\n");
                return;
            }

            string path = $"{baseDirectory}/maps";

            CreateDirectory(path);

            Console.WriteLine("Convert map files\n");
            int count = 1;

            foreach (var map in mapStorage.Values)
            {
                Console.Write($"Extract {map.MapName} ({count}/{mapStorage.Count})                  \n");
                // Loadup map grid data
                string      storagePath = $"World\\Maps\\{map.Directory}\\{map.Directory}.wdt";
                ChunkedFile wdt         = new ChunkedFile();
                if (wdt.loadFile(cascHandler, storagePath))
                {
                    wdt_MAIN main = wdt.GetChunk("MAIN").As <wdt_MAIN>();
                    for (int y = 0; y < 64; ++y)
                    {
                        for (int x = 0; x < 64; ++x)
                        {
                            if (Convert.ToBoolean(main.adt_list[y][x].flag & 0x1))
                            {
                                storagePath = $"World\\Maps\\{map.Directory}\\{map.Directory}_{x}_{y}.adt";
                                string outputFileName = $"{path}/{map.Id:D4}_{y:D2}_{x:D2}.map";
                                MapFile.ConvertADT(cascHandler, storagePath, outputFileName, y, x, build);
                            }
                        }
                        // draw progress bar
                        Console.Write($"Processing........................{(100 * (y + 1)) / 64}%\r");
                    }
                    count++;
                }
            }
            Console.WriteLine("\n");
        }
Ejemplo n.º 7
0
        public static bool ConvertADT(ChunkedFile adt, string mapName, string outputPath, int gx, int gy, uint build, bool ignoreDeepWater)
        {
            // Prepare map header
            MapFileHeader map;

            map.mapMagic     = SharedConst.MAP_MAGIC;
            map.versionMagic = SharedConst.MAP_VERSION_MAGIC;
            map.buildMagic   = build;

            // Get area flags data
            for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x)
            {
                for (var y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y)
                {
                    AreaIDs[x][y]       = 0;
                    LiquidEntries[x][y] = 0;
                    LiquidFlags[x][y]   = 0;
                }
            }

            for (var x = 0; x < SharedConst.ADT_GRID_SIZE; ++x)
            {
                for (var y = 0; y < SharedConst.ADT_GRID_SIZE; ++y)
                {
                    V8[x][y]         = 0;
                    LiquidSnow[x][y] = false;
                }
            }

            for (var x = 0; x < SharedConst.ADT_GRID_SIZE + 1; ++x)
            {
                for (var y = 0; y < SharedConst.ADT_GRID_SIZE + 1; ++y)
                {
                    V9[x][y] = 0;
                }
            }

            for (var x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x)
            {
                for (var y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y)
                {
                    for (var z = 0; z < 8; ++z)
                    {
                        Holes[x][y][z] = 0;
                    }
                }
            }

            bool hasHoles     = false;
            bool hasFlightBox = false;

            foreach (var fileChunk in adt.chunks.LookupByKey("MCNK"))
            {
                MCNK mcnk = fileChunk.As <MCNK>();

                // Area data
                AreaIDs[mcnk.IndexY][mcnk.IndexX] = (ushort)mcnk.AreaID;

                // Height
                // Height values for triangles stored in order:
                // 1     2     3     4     5     6     7     8     9
                //    10    11    12    13    14    15    16    17
                // 18    19    20    21    22    23    24    25    26
                //    27    28    29    30    31    32    33    34
                // . . . . . . . .
                // For better get height values merge it to V9 and V8 map
                // V9 height map:
                // 1     2     3     4     5     6     7     8     9
                // 18    19    20    21    22    23    24    25    26
                // . . . . . . . .
                // V8 height map:
                //    10    11    12    13    14    15    16    17
                //    27    28    29    30    31    32    33    34
                // . . . . . . . .

                // Set map height as grid height
                for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; y++)
                {
                    int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y;
                    for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; x++)
                    {
                        int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x;
                        V9[cy][cx] = mcnk.ypos;
                    }
                }

                for (int y = 0; y < SharedConst.ADT_CELL_SIZE; y++)
                {
                    int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y;
                    for (int x = 0; x < SharedConst.ADT_CELL_SIZE; x++)
                    {
                        int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x;
                        V8[cy][cx] = mcnk.ypos;
                    }
                }

                // Get custom height
                FileChunk chunk = fileChunk.GetSubChunk("MCVT");
                if (chunk != null)
                {
                    MCVT mcvt = chunk.As <MCVT>();

                    // get V9 height map
                    for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; y++)
                    {
                        int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y;
                        for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; x++)
                        {
                            int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x;
                            V9[cy][cx] += mcvt.HeightMap[y * (SharedConst.ADT_CELL_SIZE * 2 + 1) + x];
                        }
                    }
                    // get V8 height map
                    for (int y = 0; y < SharedConst.ADT_CELL_SIZE; y++)
                    {
                        int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y;
                        for (int x = 0; x < SharedConst.ADT_CELL_SIZE; x++)
                        {
                            int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x;
                            V8[cy][cx] += mcvt.HeightMap[y * (SharedConst.ADT_CELL_SIZE * 2 + 1) + SharedConst.ADT_CELL_SIZE + 1 + x];
                        }
                    }
                }

                // Liquid data
                if (mcnk.MCLQCount > 8)
                {
                    FileChunk lquidChunk = fileChunk.GetSubChunk("MCLQ");
                    if (lquidChunk != null)
                    {
                        MCLQ liquid = lquidChunk.As <MCLQ>();
                        int  count  = 0;
                        for (int y = 0; y < SharedConst.ADT_CELL_SIZE; ++y)
                        {
                            int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y;
                            for (int x = 0; x < SharedConst.ADT_CELL_SIZE; ++x)
                            {
                                int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x;
                                if (liquid.Flags[y][x] != 0x0F)
                                {
                                    LiquidSnow[cy][cx] = true;
                                    if (!ignoreDeepWater && Convert.ToBoolean(liquid.Flags[y][x] & (1 << 7)))
                                    {
                                        LiquidFlags[mcnk.IndexY][mcnk.IndexX] |= LiquidHeaderTypeFlags.DarkWater;
                                    }
                                    ++count;
                                }
                            }
                        }

                        if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidRiver))
                        {
                            LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 1;
                            LiquidFlags[mcnk.IndexY][mcnk.IndexX]  |= LiquidHeaderTypeFlags.Water;           // water
                        }
                        if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidOcean))
                        {
                            LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 2;
                            LiquidFlags[mcnk.IndexY][mcnk.IndexX]  |= LiquidHeaderTypeFlags.Ocean;           // ocean
                        }
                        if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidMagma))
                        {
                            LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 3;
                            LiquidFlags[mcnk.IndexY][mcnk.IndexX]  |= LiquidHeaderTypeFlags.Magma;           // magma
                        }
                        if (mcnk.Flags.HasAnyFlag(MCNKFlags.LiquidSlime))
                        {
                            LiquidEntries[mcnk.IndexY][mcnk.IndexX] = 4;
                            LiquidFlags[mcnk.IndexY][mcnk.IndexX]  |= LiquidHeaderTypeFlags.Slime;           // slime
                        }

                        if (count == 0 && LiquidFlags[mcnk.IndexY][mcnk.IndexX] != 0)
                        {
                            Console.WriteLine("Wrong liquid detect in MCLQ chunk");
                        }

                        for (int y = 0; y <= SharedConst.ADT_CELL_SIZE; ++y)
                        {
                            int cy = (int)mcnk.IndexY * SharedConst.ADT_CELL_SIZE + y;
                            for (int x = 0; x <= SharedConst.ADT_CELL_SIZE; ++x)
                            {
                                int cx = (int)mcnk.IndexX * SharedConst.ADT_CELL_SIZE + x;
                                LiquidHeight[cy][cx] = liquid.Liquid[y][x].Height;
                            }
                        }
                    }
                }

                // Hole data
                if (!mcnk.Flags.HasAnyFlag(MCNKFlags.HighResHoles))
                {
                    uint hole = mcnk.HolesLowRes;
                    if (hole != 0)
                    {
                        if (TransformToHighRes((ushort)hole, Holes[mcnk.IndexY][mcnk.IndexX]))
                        {
                            hasHoles = true;
                        }
                    }
                }
                else
                {
                    Buffer.BlockCopy(mcnk.HighResHoles, 0, Holes[mcnk.IndexY][mcnk.IndexX], 0, 8);
                    if (BitConverter.ToUInt64(Holes[mcnk.IndexY][mcnk.IndexX], 0) != 0)
                    {
                        hasHoles = true;
                    }
                }
            }

            // Get liquid map for grid (in WOTLK used MH2O chunk)
            FileChunk chunkMH2O = adt.GetChunk("MH2O");

            if (chunkMH2O != null)
            {
                MH2O h2o = chunkMH2O.As <MH2O>();
                for (int i = 0; i < SharedConst.ADT_CELLS_PER_GRID; i++)
                {
                    for (int j = 0; j < SharedConst.ADT_CELLS_PER_GRID; j++)
                    {
                        MH2OInstance?h = h2o.GetLiquidInstance(i, j);
                        if (!h.HasValue)
                        {
                            continue;
                        }

                        MH2OInstance       adtLiquidHeader = h.Value;
                        MH2OChunkAttribute?attrs           = h2o.GetLiquidAttributes(i, j);

                        int   count      = 0;
                        ulong existsMask = h2o.GetLiquidExistsBitmap(adtLiquidHeader);
                        for (int y = 0; y < adtLiquidHeader.GetHeight(); y++)
                        {
                            int cy = i * SharedConst.ADT_CELL_SIZE + y + adtLiquidHeader.GetOffsetY();
                            for (int x = 0; x < adtLiquidHeader.GetWidth(); x++)
                            {
                                int cx = j * SharedConst.ADT_CELL_SIZE + x + adtLiquidHeader.GetOffsetX();
                                if (Convert.ToBoolean(existsMask & 1))
                                {
                                    LiquidSnow[cy][cx] = true;
                                    ++count;
                                }
                                existsMask >>= 1;
                            }
                        }

                        LiquidEntries[i][j] = h2o.GetLiquidType(adtLiquidHeader);
                        if (LiquidEntries[i][j] != 0)
                        {
                            var liquidTypeRecord = LiquidTypeStorage[LiquidEntries[i][j]];
                            switch ((LiquidType)liquidTypeRecord.SoundBank)
                            {
                            case LiquidType.Water:
                                LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Water;
                                break;

                            case LiquidType.Ocean:
                                LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Ocean;
                                if (!ignoreDeepWater && attrs.Value.Deep != 0)
                                {
                                    LiquidFlags[i][j] |= LiquidHeaderTypeFlags.DarkWater;
                                }
                                break;

                            case LiquidType.Magma:
                                LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Magma;
                                break;

                            case LiquidType.Slime:
                                LiquidFlags[i][j] |= LiquidHeaderTypeFlags.Slime;
                                break;

                            default:
                                Console.WriteLine($"\nCan't find Liquid type {adtLiquidHeader.LiquidType} for map {mapName}\nchunk {i},{j}\n");
                                break;
                            }

                            if (count == 0 && LiquidFlags[i][j] != 0)
                            {
                                Console.WriteLine("Wrong liquid detect in MH2O chunk");
                            }
                        }
                        else
                        {
                            Console.WriteLine($"LiquidEntries is 0 for [{i}][{j}] ({i * j})");
                        }

                        int pos = 0;
                        for (int y = 0; y <= adtLiquidHeader.GetHeight(); y++)
                        {
                            int cy = i * SharedConst.ADT_CELL_SIZE + y + adtLiquidHeader.GetOffsetY();
                            for (int x = 0; x <= adtLiquidHeader.GetWidth(); x++)
                            {
                                int cx = j * SharedConst.ADT_CELL_SIZE + x + adtLiquidHeader.GetOffsetX();

                                LiquidHeight[cy][cx] = h2o.GetLiquidHeight(adtLiquidHeader, i, j, pos);

                                pos++;
                            }
                        }
                    }
                }
            }

            FileChunk chunkMFBO = adt.GetChunk("MFBO");

            if (chunkMFBO != null)
            {
                MFBO mfbo = chunkMFBO.As <MFBO>();
                for (var i = 0; i < 3; ++i)
                {
                    FlightBoxMax[i][0] = mfbo.Max.coords[0 + i * 3];
                    FlightBoxMax[i][1] = mfbo.Max.coords[1 + i * 3];
                    FlightBoxMax[i][2] = mfbo.Max.coords[2 + i * 3];

                    FlightBoxMin[i][0] = mfbo.Min.coords[0 + i * 3];
                    FlightBoxMin[i][1] = mfbo.Min.coords[1 + i * 3];
                    FlightBoxMin[i][2] = mfbo.Min.coords[2 + i * 3];
                }
                hasFlightBox = true;
            }

            //============================================
            // Try pack area data
            //============================================
            bool fullAreaData = false;
            uint areaId       = AreaIDs[0][0];

            for (int y = 0; y < SharedConst.ADT_CELLS_PER_GRID; ++y)
            {
                for (int x = 0; x < SharedConst.ADT_CELLS_PER_GRID; ++x)
                {
                    if (AreaIDs[y][x] != areaId)
                    {
                        fullAreaData = true;
                        break;
                    }
                }
            }

            map.areaMapOffset = 44;
            map.areaMapSize   = 8;

            MapAreaHeader areaHeader;

            areaHeader.fourcc = SharedConst.MAP_AREA_MAGIC;
            areaHeader.flags  = 0;
            if (fullAreaData)
            {
                areaHeader.gridArea = 0;
                map.areaMapSize    += 512;
            }
            else
            {
                areaHeader.flags   |= AreaHeaderFlags.NoArea;
                areaHeader.gridArea = (ushort)areaId;
            }

            //============================================
            // Try pack height data
            //============================================
            float maxHeight = -20000;
            float minHeight = 20000;

            for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++)
            {
                for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++)
                {
                    float h = V8[y][x];
                    if (maxHeight < h)
                    {
                        maxHeight = h;
                    }
                    if (minHeight > h)
                    {
                        minHeight = h;
                    }
                }
            }
            for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++)
            {
                for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++)
                {
                    float h = V9[y][x];
                    if (maxHeight < h)
                    {
                        maxHeight = h;
                    }
                    if (minHeight > h)
                    {
                        minHeight = h;
                    }
                }
            }

            // Check for allow limit minimum height (not store height in deep ochean - allow save some memory)
            if (minHeight < -2000.0f)
            {
                for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++)
                {
                    for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++)
                    {
                        if (V8[y][x] < -2000.0f)
                        {
                            V8[y][x] = -2000.0f;
                        }
                    }
                }
                for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++)
                {
                    for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++)
                    {
                        if (V9[y][x] < -2000.0f)
                        {
                            V9[y][x] = -2000.0f;
                        }
                    }
                }

                if (minHeight < -2000.0f)
                {
                    minHeight = -2000.0f;
                }
                if (maxHeight < -2000.0f)
                {
                    maxHeight = -2000.0f;
                }
            }

            map.heightMapOffset = map.areaMapOffset + map.areaMapSize;
            map.heightMapSize   = (uint)Marshal.SizeOf <MapHeightHeader>();

            MapHeightHeader heightHeader;

            heightHeader.fourcc        = SharedConst.MAP_HEIGHT_MAGIC;
            heightHeader.flags         = 0;
            heightHeader.gridHeight    = minHeight;
            heightHeader.gridMaxHeight = maxHeight;

            if (maxHeight == minHeight)
            {
                heightHeader.flags |= HeightHeaderFlags.NoHeight;
            }

            // Not need store if flat surface
            if ((maxHeight - minHeight) < 0.005f)
            {
                heightHeader.flags |= HeightHeaderFlags.NoHeight;
            }

            if (hasFlightBox)
            {
                heightHeader.flags |= HeightHeaderFlags.HasFlightBounds;
                map.heightMapSize  += 18 + 18;
            }

            // Try store as packed in uint16 or uint8 values
            if (!heightHeader.flags.HasFlag(HeightHeaderFlags.NoHeight))
            {
                float step = 0;
                // Try Store as uint values
                if (true)//CONF_allow_float_to_int
                {
                    float diff = maxHeight - minHeight;
                    if (diff < 2.0f)      // As uint8 (max accuracy = CONF_float_to_int8_limit/256)
                    {
                        heightHeader.flags |= HeightHeaderFlags.AsInt8;
                        step = 255 / diff;
                    }
                    else if (diff < 2048.0f)  // As uint16 (max accuracy = CONF_float_to_int16_limit/65536)
                    {
                        heightHeader.flags |= HeightHeaderFlags.AsInt16;
                        step = 65535 / diff;
                    }
                }

                // Pack it to int values if need
                if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt8))
                {
                    for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++)
                        {
                            UInt8_V8[y][x] = (byte)((V8[y][x] - minHeight) * step + 0.5f);
                        }
                    }
                    for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++)
                        {
                            UInt8_V9[y][x] = (byte)((V9[y][x] - minHeight) * step + 0.5f);
                        }
                    }
                    map.heightMapSize += 16641 + 16384;
                }
                else if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt16))
                {
                    for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++)
                        {
                            UInt16_V8[y][x] = (ushort)((V8[y][x] - minHeight) * step + 0.5f);
                        }
                    }

                    for (int y = 0; y <= SharedConst.ADT_GRID_SIZE; y++)
                    {
                        for (int x = 0; x <= SharedConst.ADT_GRID_SIZE; x++)
                        {
                            UInt16_V9[y][x] = (ushort)((V9[y][x] - minHeight) * step + 0.5f);
                        }
                    }

                    map.heightMapSize += 33282 + 32768;
                }
                else
                {
                    map.heightMapSize += 66564 + 65536;
                }
            }

            //============================================
            // Pack liquid data
            //============================================
            ushort firstLiquidType = LiquidEntries[0][0];
            LiquidHeaderTypeFlags firstLiquidFlag = LiquidFlags[0][0];
            bool fullType = false;

            for (int y = 0; y < SharedConst.ADT_CELLS_PER_GRID; y++)
            {
                for (int x = 0; x < SharedConst.ADT_CELLS_PER_GRID; x++)
                {
                    if (LiquidEntries[y][x] != firstLiquidType || LiquidFlags[y][x] != firstLiquidFlag)
                    {
                        fullType = true;
                        y        = SharedConst.ADT_CELLS_PER_GRID;
                        break;
                    }
                }
            }

            MapLiquidHeader mapLiquidHeader = new();

            // no water data (if all grid have 0 liquid type)
            if (firstLiquidFlag == 0 && !fullType)
            {
                // No liquid data
                map.liquidMapOffset = 0;
                map.liquidMapSize   = 0;
            }
            else
            {
                int minX = 255, minY = 255;
                int maxX = 0, maxY = 0;
                maxHeight = -20000;
                minHeight = 20000;
                for (int y = 0; y < SharedConst.ADT_GRID_SIZE; y++)
                {
                    for (int x = 0; x < SharedConst.ADT_GRID_SIZE; x++)
                    {
                        if (LiquidSnow[y][x])
                        {
                            if (minX > x)
                            {
                                minX = x;
                            }
                            if (maxX < x)
                            {
                                maxX = x;
                            }
                            if (minY > y)
                            {
                                minY = y;
                            }
                            if (maxY < y)
                            {
                                maxY = y;
                            }
                            float h = LiquidHeight[y][x];
                            if (maxHeight < h)
                            {
                                maxHeight = h;
                            }
                            if (minHeight > h)
                            {
                                minHeight = h;
                            }
                        }
                        else
                        {
                            LiquidHeight[y][x] = -2000.0f;
                        }
                    }
                }
                map.liquidMapOffset = map.heightMapOffset + map.heightMapSize;
                map.liquidMapSize   = (uint)Marshal.SizeOf <MapLiquidHeader>();

                mapLiquidHeader.fourcc      = SharedConst.MAP_LIQUID_MAGIC;
                mapLiquidHeader.flags       = LiquidHeaderFlags.None;
                mapLiquidHeader.liquidType  = 0;
                mapLiquidHeader.offsetX     = (byte)minX;
                mapLiquidHeader.offsetY     = (byte)minY;
                mapLiquidHeader.width       = (byte)(maxX - minX + 1 + 1);
                mapLiquidHeader.height      = (byte)(maxY - minY + 1 + 1);
                mapLiquidHeader.liquidLevel = minHeight;

                if (maxHeight == minHeight)
                {
                    mapLiquidHeader.flags |= LiquidHeaderFlags.NoHeight;
                }

                // Not need store if flat surface
                if ((maxHeight - minHeight) < 0.001f)
                {
                    mapLiquidHeader.flags |= LiquidHeaderFlags.NoHeight;
                }

                if (!fullType)
                {
                    mapLiquidHeader.flags |= LiquidHeaderFlags.NoType;
                }

                if (mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoType))
                {
                    mapLiquidHeader.liquidFlags = firstLiquidFlag;
                    mapLiquidHeader.liquidType  = firstLiquidType;
                }
                else
                {
                    map.liquidMapSize += 512 + 256;
                }

                if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoHeight))
                {
                    map.liquidMapSize += (uint)(sizeof(float) * mapLiquidHeader.width * mapLiquidHeader.height);
                }
            }

            if (hasHoles)
            {
                if (map.liquidMapOffset != 0)
                {
                    map.holesOffset = map.liquidMapOffset + map.liquidMapSize;
                }
                else
                {
                    map.holesOffset = map.heightMapOffset + map.heightMapSize;
                }

                map.holesSize = 2048;
            }
            else
            {
                map.holesOffset = 0;
                map.holesSize   = 0;
            }

            // Ok all data prepared - store it
            using (BinaryWriter binaryWriter = new(File.Open(outputPath, FileMode.Create, FileAccess.Write)))
            {
                binaryWriter.WriteStruct(map);
                // Store area data
                binaryWriter.WriteStruct(areaHeader);
                if (!areaHeader.flags.HasFlag(AreaHeaderFlags.NoArea))
                {
                    for (var x = 0; x < AreaIDs.Length; ++x)
                    {
                        for (var y = 0; y < AreaIDs[x].Length; ++y)
                        {
                            binaryWriter.Write(AreaIDs[x][y]);
                        }
                    }
                }

                // Store height data
                binaryWriter.WriteStruct(heightHeader);
                if (!heightHeader.flags.HasFlag(HeightHeaderFlags.NoHeight))
                {
                    if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt16))
                    {
                        for (var x = 0; x < UInt16_V9.Length; ++x)
                        {
                            for (var y = 0; y < UInt16_V9[x].Length; ++y)
                            {
                                binaryWriter.Write(UInt16_V9[x][y]);
                            }
                        }

                        for (var x = 0; x < UInt16_V8.Length; ++x)
                        {
                            for (var y = 0; y < UInt16_V8[x].Length; ++y)
                            {
                                binaryWriter.Write(UInt16_V8[x][y]);
                            }
                        }
                    }
                    else if (heightHeader.flags.HasFlag(HeightHeaderFlags.AsInt8))
                    {
                        for (var x = 0; x < UInt8_V9.Length; ++x)
                        {
                            for (var y = 0; y < UInt8_V9[x].Length; ++y)
                            {
                                binaryWriter.Write(UInt8_V9[x][y]);
                            }
                        }

                        for (var x = 0; x < UInt8_V8.Length; ++x)
                        {
                            for (var y = 0; y < UInt8_V8[x].Length; ++y)
                            {
                                binaryWriter.Write(UInt8_V8[x][y]);
                            }
                        }
                    }
                    else
                    {
                        for (var x = 0; x < V9.Length; ++x)
                        {
                            for (var y = 0; y < V9[x].Length; ++y)
                            {
                                binaryWriter.Write(V9[x][y]);
                            }
                        }

                        for (var x = 0; x < V8.Length; ++x)
                        {
                            for (var y = 0; y < V8[x].Length; ++y)
                            {
                                binaryWriter.Write(V8[x][y]);
                            }
                        }
                    }
                }

                if (heightHeader.flags.HasFlag(HeightHeaderFlags.HasFlightBounds))
                {
                    for (var x = 0; x < 3; ++x)
                    {
                        for (var y = 0; y < 3; ++y)
                        {
                            binaryWriter.Write(FlightBoxMax[x][y]);
                        }
                    }

                    for (var x = 0; x < 3; ++x)
                    {
                        for (var y = 0; y < 3; ++y)
                        {
                            binaryWriter.Write(FlightBoxMin[x][y]);
                        }
                    }
                }

                // Store liquid data if need
                if (map.liquidMapOffset != 0)
                {
                    binaryWriter.WriteStruct(mapLiquidHeader);
                    if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoType))
                    {
                        for (var x = 0; x < LiquidEntries.Length; ++x)
                        {
                            for (var y = 0; y < LiquidEntries[x].Length; ++y)
                            {
                                binaryWriter.Write(LiquidEntries[x][y]);
                            }
                        }

                        for (var x = 0; x < LiquidFlags.Length; ++x)
                        {
                            for (var y = 0; y < LiquidFlags[x].Length; ++y)
                            {
                                binaryWriter.Write((byte)LiquidFlags[x][y]);
                            }
                        }
                    }

                    if (!mapLiquidHeader.flags.HasFlag(LiquidHeaderFlags.NoHeight))
                    {
                        for (int y = 0; y < mapLiquidHeader.height; y++)
                        {
                            for (int x = 0; x < mapLiquidHeader.width; x++)
                            {
                                binaryWriter.Write(LiquidHeight[y + mapLiquidHeader.offsetY][x + mapLiquidHeader.offsetX]);
                            }
                        }
                    }
                }

                // store hole data
                if (hasHoles)
                {
                    for (var x = 0; x < Holes.Length; ++x)
                    {
                        for (var y = 0; y < Holes[x].Length; ++y)
                        {
                            for (var z = 0; z < Holes[x][y].Length; ++z)
                            {
                                binaryWriter.Write(Holes[x][y][z]);
                            }
                        }
                    }
                }
            }

            return(true);
        }