Ejemplo n.º 1
0
        public static IEnumerable <KeyValuePair <int, WDC3Row> > EnumerateTable(this CASCFolder folder, string name, CASCHandler handler)
        {
            var entry = folder.GetEntry(name + ".db2");

            using var stream = handler.OpenFile(entry.Hash);
            var reader = new WDC3Reader(stream);

            foreach (var pair in reader)
            {
                yield return(pair);
            }
        }
Ejemplo n.º 2
0
        public Storage(string fileName)
        {
            DB2Reader reader;

            var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);

            using (var bin = new BinaryReader(stream))
            {
                var identifier = new string(bin.ReadChars(4));
                stream.Position = 0;
                switch (identifier)
                {
                case "WDC3":
                    reader = new WDC3Reader(stream);
                    break;

                case "WDC2":
                    reader = new WDC2Reader(stream);
                    break;

                case "WDC1":
                    reader = new WDC1Reader(stream);
                    break;

                default:
                    throw new Exception("DBC type " + identifier + " is not supported!");
                }
            }

            FieldInfo[] fields = typeof(T).GetFields();

            FieldCache <T>[] fieldCache = new FieldCache <T> [fields.Length];

            for (int i = 0; i < fields.Length; ++i)
            {
                bool indexMapAttribute = reader.Flags.HasFlagExt(DB2Flags.Index) ? Attribute.IsDefined(fields[i], typeof(IndexAttribute)) : false;

                fieldCache[i] = new FieldCache <T>(fields[i], fields[i].FieldType.IsArray, fields[i].GetSetter <T>(), indexMapAttribute);
            }

            Parallel.ForEach(reader.AsEnumerable(), row =>
            {
                T entry = new T();

                row.Value.GetFields(fieldCache, entry);

                TryAdd(row.Key, entry);
            });
        }
Ejemplo n.º 3
0
        public Storage(Stream stream)
        {
            DB2Reader reader;

            using (stream)
                using (var bin = new BinaryReader(stream))
                {
                    var identifier = new string(bin.ReadChars(4));
                    stream.Position = 0;
                    switch (identifier)
                    {
                    case "WDC3":
                        reader = new WDC3Reader(stream);
                        break;

                    case "WDC2":
                    case "1SLC":
                        reader = new WDC2Reader(stream);
                        break;

                    case "WDC1":
                        reader = new WDC1Reader(stream);
                        break;

                    case "WDB6":
                        reader = new WDB6Reader(stream);
                        break;

                    case "WDB5":
                        reader = new WDB5Reader(stream);
                        break;

                    case "WDB4":
                        reader = new WDB4Reader(stream);
                        break;

                    case "WDB3":
                        reader = new WDB3Reader(stream);
                        break;

                    case "WDB2":
                        reader = new WDB2Reader(stream);
                        break;

                    case "WDBC":
                        reader = new WDBCReader(stream);
                        break;

                    default:
                        throw new Exception("DB type " + identifier + " is not supported!");
                    }
                }

            FieldInfo[] fields = typeof(T).GetFields();

            FieldCache <T>[] fieldCache = new FieldCache <T> [fields.Length];
            for (int i = 0; i < fields.Length; ++i)
            {
                bool indexMapAttribute = reader.Flags.HasFlagExt(DB2Flags.Index) ? Attribute.IsDefined(fields[i], typeof(IndexAttribute)) : false;
                fieldCache[i] = new FieldCache <T>(fields[i], indexMapAttribute);
            }

            Parallel.ForEach(reader.AsEnumerable(), new ParallelOptions()
            {
                MaxDegreeOfParallelism = 1
            }, row =>
            {
                T entry = new T();
                row.Value.GetFields(fieldCache, entry);
                lock (this)
                    Add(row.Value.Id, entry);
            });
        }
Ejemplo n.º 4
0
        public async Task AnalyzeUnknownFiles(Action <int> progressCallback)
        {
            if (_casc == null)
            {
                return;
            }

            IProgress <int> progress = new Progress <int>(progressCallback);

            await Task.Run(() =>
            {
                FileScanner scanner = new FileScanner(_casc, _root);

                Dictionary <int, List <string> > idToName = new Dictionary <int, List <string> >();

                if (_casc.Config.GameType == CASCGameType.WoW && AnalyzeSoundFiles)
                {
                    if (_casc.FileExists("DBFilesClient\\SoundEntries.db2"))
                    {
                        using (Stream stream = _casc.OpenFile("DBFilesClient\\SoundEntries.db2"))
                        {
                            WDB2Reader se = new WDB2Reader(stream);

                            foreach (var row in se)
                            {
                                string name = row.Value.GetField <string>(2);

                                int type = row.Value.GetField <int>(1);

                                bool many = row.Value.GetField <int>(4) > 0;

                                for (int i = 3; i < 23; i++)
                                {
                                    int id = row.Value.GetField <int>(i);

                                    if (!idToName.ContainsKey(id))
                                    {
                                        idToName[id] = new List <string>();
                                    }

                                    idToName[id].Add("unknown\\sound\\" + name + (many ? "_" + (i - 2).ToString("D2") : "") + (type == 28 ? ".mp3" : ".ogg"));
                                }
                            }
                        }
                    }

                    if (_casc.FileExists("DBFilesClient\\SoundKit.db2") && _casc.FileExists("DBFilesClient\\SoundKitEntry.db2") && _casc.FileExists("DBFilesClient\\SoundKitName.db2"))
                    {
                        using (Stream skStream = _casc.OpenFile("DBFilesClient\\SoundKit.db2"))
                            using (Stream skeStream = _casc.OpenFile("DBFilesClient\\SoundKitEntry.db2"))
                                using (Stream sknStream = _casc.OpenFile("DBFilesClient\\SoundKitName.db2"))
                                {
                                    WDC3Reader sk  = new WDC3Reader(skStream);
                                    WDC3Reader ske = new WDC3Reader(skeStream);
                                    WDC3Reader skn = new WDC3Reader(sknStream);

                                    Dictionary <int, List <int> > lookup = new Dictionary <int, List <int> >();

                                    foreach (var row in ske)
                                    {
                                        int soundKitId = row.Value.GetField <int>(0);

                                        if (!lookup.ContainsKey(soundKitId))
                                        {
                                            lookup[soundKitId] = new List <int>();
                                        }

                                        lookup[soundKitId].Add(row.Value.GetField <int>(1));
                                    }

                                    foreach (var row in sk)
                                    {
                                        string name = skn.GetRow(row.Key).GetField <string>(0).Replace(':', '_');
                                        //string name = row.Value.GetField<string>(0).Replace(':', '_');

                                        int type = row.Value.GetField <byte>(6);

                                        if (!lookup.TryGetValue(row.Key, out List <int> ske_entries))
                                        {
                                            continue;
                                        }

                                        bool many = ske_entries.Count > 1;

                                        int i = 0;

                                        foreach (var fid in ske_entries)
                                        {
                                            if (!idToName.ContainsKey(fid))
                                            {
                                                idToName[fid] = new List <string>();
                                            }

                                            if (AddFileDataIdToSoundFiles)
                                            {
                                                idToName[fid].Add("unknown\\sound\\" + name + (many ? "_" + (i + 1).ToString("D2") : "") + "_" + fid + (type == 28 ? ".mp3" : ".ogg"));
                                            }
                                            else
                                            {
                                                idToName[fid].Add("unknown\\sound\\" + name + (many ? "_" + (i + 1).ToString("D2") : "") + (type == 28 ? ".mp3" : ".ogg"));
                                            }

                                            i++;
                                        }
                                    }
                                }
                    }
                }

                CASCFolder unknownFolder = _root.GetEntry("unknown") as CASCFolder;

                if (unknownFolder == null)
                {
                    return;
                }

                IEnumerable <CASCFile> files = CASCFolder.GetFiles(unknownFolder.Entries.Select(kv => kv.Value), null, true).ToList();
                int numTotal = files.Count();
                int numDone  = 0;

                WowRootHandler wowRoot = _casc.Root as WowRootHandler;

                Jenkins96 Hasher      = new Jenkins96();
                char[] PathDelimiters = new char[] { '/', '\\' };

                foreach (var unknownEntry in files)
                {
                    CASCFile unknownFile = unknownEntry as CASCFile;

                    if (idToName.TryGetValue(wowRoot.GetFileDataIdByHash(unknownFile.Hash), out List <string> name))
                    {
                        if (name.Count == 1)
                        {
                            unknownFile.FullName = name[0];
                        }
                        else
                        {
                            unknownFolder.Entries.Remove(unknownFile.Name);

                            foreach (var file in name)
                            {
                                Logger.WriteLine(file);

                                string[] parts = file.Split(PathDelimiters);

                                string entryName = parts[parts.Length - 1];

                                ulong filehash = unknownFile.Hash;

                                CASCFile entry           = new CASCFile(filehash, file);
                                CASCFile.Files[filehash] = entry;

                                unknownFolder.Entries[entryName] = entry;
                            }
                        }
                    }
                    else
                    {
                        string ext            = scanner.GetFileExtension(unknownFile);
                        unknownFile.FullName += ext;

                        if (ext == ".m2")
                        {
                            using (var m2file = _casc.OpenFile(unknownFile.Hash))
                                using (var br = new BinaryReader(m2file))
                                {
                                    m2file.Position = 0x14;
                                    int nameOffs    = br.ReadInt32();

                                    m2file.Position = nameOffs + 8; // + sizeof(MD21)
                                    string m2name   = br.ReadCString();

                                    unknownFile.FullName = "unknown\\" + m2name + ".m2";
                                }
                        }
                    }

                    progress.Report((int)(++numDone / (float)numTotal * 100));
                }

                _casc.Root.Dump();
            });
        }
Ejemplo n.º 5
0
        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine("Not enough arguments: inputdb2 outputcsv (optional: build)");
                return;
            }

            var filename = args[0];
            var outputcsv = args[1];

            if (!File.Exists(filename))
            {
                throw new Exception("Input DB2 file does not exist!");
            }

            if (!Directory.Exists(Path.GetDirectoryName(outputcsv)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(outputcsv));
            }

            var build = "";
            if(args.Length == 3)
            {
                build = args[2];
            }

            DB2Reader reader;

            var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
            using (var bin = new BinaryReader(stream))
            {
                var identifier = new string(bin.ReadChars(4));
                stream.Position = 0;
                switch (identifier)
                {
                    case "WDC3":
                        reader = new WDC3Reader(stream);
                        break;
                    case "WDC2":
                        reader = new WDC2Reader(stream);
                        break;
                    case "WDC1":
                        reader = new WDC1Reader(stream);
                        break;
                    default:
                        throw new Exception("DBC type " + identifier + " is not supported!");
                }
            }

            var defs = new Structs.DBDefinition();

            foreach (var file in Directory.GetFiles("definitions/"))
            {
                if (Path.GetFileNameWithoutExtension(file).ToLower() == Path.GetFileNameWithoutExtension(filename.ToLower()))
                {
                    defs = new DBDReader().Read(file);
                }
            }

            var writer = new StreamWriter(outputcsv);

            Structs.VersionDefinitions? versionToUse;

            if (!Utils.GetVersionDefinitionByLayoutHash(defs, reader.LayoutHash.ToString("X8"), out versionToUse))
            {
                if (!string.IsNullOrWhiteSpace(build))
                {
                    if (!Utils.GetVersionDefinitionByBuild(defs, new Build(build), out versionToUse))
                    {
                        throw new Exception("No valid definition found for this layouthash or build!");
                    }
                }
                else
                {
                    throw new Exception("No valid definition found for this layouthash and was not able to search by build!");
                }
            }

            var aName = new AssemblyName("DynamicAssemblyExample");
            var ab = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
            var mb = ab.DefineDynamicModule(aName.Name);
            var tb = mb.DefineType(Path.GetFileNameWithoutExtension(filename) + "Struct", TypeAttributes.Public);

            foreach (var field in versionToUse.Value.definitions)
            {
                var fbNumber = tb.DefineField(field.name, DBDefTypeToType(defs.columnDefinitions[field.name].type, field.size, field.isSigned, field.arrLength), FieldAttributes.Public);
                if (field.isID)
                {
                    var constructorParameters = new Type[] { };
                    var constructorInfo = typeof(IndexAttribute).GetConstructor(constructorParameters);
                    var displayNameAttributeBuilder = new CustomAttributeBuilder(constructorInfo, new object[] { });
                    fbNumber.SetCustomAttribute(displayNameAttributeBuilder);
                }
            }

            var type = tb.CreateType();
            var genericType = typeof(Storage<>).MakeGenericType(type);
            var storage = (IDictionary)Activator.CreateInstance(genericType, filename);

            if (storage.Values.Count == 0)
            {
                throw new Exception("No rows found!");
            }

            var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic | BindingFlags.Instance);

            var headerWritten = false;

            foreach (var item in storage.Values)
            {
                // Write CSV header
                if (!headerWritten)
                {
                    for (var j = 0; j < fields.Length; ++j)
                    {
                        var field = fields[j];

                        var isEndOfRecord = fields.Length - 1 == j;

                        if (field.FieldType.IsArray)
                        {
                            var a = (Array)field.GetValue(item);
                            for (var i = 0; i < a.Length; i++)
                            {
                                var isEndOfArray = a.Length - 1 == i;

                                writer.Write($"{field.Name}[{i}]");
                                if (!isEndOfArray)
                                    writer.Write(",");
                            }
                        }
                        else
                        {
                            writer.Write(field.Name);
                        }

                        if (!isEndOfRecord)
                            writer.Write(",");
                    }
                    headerWritten = true;
                    writer.WriteLine();
                }

                for (var i = 0; i < fields.Length; ++i)
                {
                    var field = fields[i];

                    var isEndOfRecord = fields.Length - 1 == i;

                    if (field.FieldType.IsArray)
                    {
                        var a = (Array)field.GetValue(item);

                        for (var j = 0; j < a.Length; j++)
                        {
                            var isEndOfArray = a.Length - 1 == j;
                            writer.Write(a.GetValue(j));

                            if (!isEndOfArray)
                                writer.Write(",");
                        }
                    }
                    else
                    {
                        var value = field.GetValue(item);
                        if (value.GetType() == typeof(string))
                            value = StringToCSVCell((string)value);

                        writer.Write(value);
                    }

                    if (!isEndOfRecord)
                        writer.Write(",");
                }

                writer.WriteLine();
            }

            writer.Dispose();
            Environment.Exit(0);
        }
Ejemplo n.º 6
0
        public static async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            [Blob("wow")] CloudBlobContainer container,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string localeString = req.Query["locale"];

            string  requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data        = JsonConvert.DeserializeObject(requestBody);
            var     localeName  = localeString ?? "en-US";

            CASCConfig  config  = CASCConfig.LoadLocalStorageConfig(@"C:\Games\World of Warcraft", "wow");
            CASCHandler handler = CASCHandler.OpenStorage(config, null);

            handler.Root.LoadListFile(Path.Combine(Environment.CurrentDirectory, "listfile8x.csv"));

            var locale = WowLocales.Locales.Single(x => x.Name.Equals(localeName, StringComparison.OrdinalIgnoreCase));

            CASCFolder root = handler.Root.SetFlags(locale.Flag);

            handler.Root.MergeInstall(handler.Install);

            var dbfiles = (CASCFolder)root.Entries["dbfilesclient"];

            await container.CreateIfNotExistsAsync();

            // Apply translations to expansion / tiers
            var tiers = WowExpansion.All;
            {
                var entry = dbfiles.GetEntry("journaltier.db2");
                await using var stream = handler.OpenFile(entry.Hash);
                var reader = new WDC3Reader(stream);

                foreach (var pair in reader)
                {
                    var tier = tiers.Single(x => x.Value.TierId == pair.Key);
                    tier.Value.Name = pair.Value.GetField <string>(0);
                }
            }
            await container.GetBlockBlobReference("expansions." + localeName + ".json")
            .UploadTextAsync(JsonConvert.SerializeObject(tiers.Values, serializerSettings));

            var instanceTiers = new Dictionary <int, int>();
            {
                var entry = dbfiles.GetEntry("JournalTierXInstance.db2");
                await using var stream = handler.OpenFile(entry.Hash);
                var reader = new WDC3Reader(stream);
                foreach (var pair in reader)
                {
                    var tierId     = pair.Value.GetField <int>(0);
                    var instanceId = pair.Value.GetField <int>(1);
                    instanceTiers[instanceId] = tierId;
                }
            }

            // Get all instances
            var instances = new List <Instance>();
            {
                var entry = dbfiles.GetEntry("JournalInstance.db2");
                await using var stream = handler.OpenFile(entry.Hash);
                var reader = new WDC3Reader(stream);
                foreach (var pair in reader)
                {
                    if (!instanceTiers.ContainsKey(pair.Key))
                    {
                        continue;
                    }
                    var tierId = instanceTiers[pair.Key];

                    var instance = new Instance
                    {
                        Id                 = pair.Key,
                        Name               = pair.Value.GetField <string>(0),
                        Description        = pair.Value.GetField <string>(1),
                        MapId              = pair.Value.GetField <int>(3),
                        BackgroundImageId  = pair.Value.GetField <int>(4),
                        ButtonImageId      = pair.Value.GetField <int>(5),
                        ButtonSmallImageId = pair.Value.GetField <int>(6),
                        LoreImageId        = pair.Value.GetField <int>(7),
                        Order              = pair.Value.GetField <int>(8),
                        Flags              = pair.Value.GetField <int>(9),
                        TierId             = tierId
                    };

                    instances.Add(instance);
                }
            }

            // Save the list of all instances
            await container.GetBlockBlobReference("data/dungeons." + localeName + ".json")
            .UploadTextAsync(JsonConvert.SerializeObject(instances, serializerSettings));

            foreach (var tier in tiers.Values)
            {
                var dungeons = new List <Instance>();

                // Get details and images for each dungeon
                foreach (var instance in instances)
                {
                    // Only encounters for this tier
                    if (instance.TierId != tier.TierId)
                    {
                        continue;
                    }

                    // World instances are flagged with 0x2
                    if ((instance.Flags & 0x2) == 0x2)
                    {
                        continue;
                    }

                    // Raids have an order number, dungeons have order of 0 and sorted by name
                    if (instance.Order != 0)
                    {
                        continue;
                    }

                    dungeons.Add(instance);

                    var blobPath = "static/img/instance/" + instance.Id + "/";

                    // Get the dungeon images
                    using var loreStream = handler.OpenFile(instance.LoreImageId);
                    await container.GetBlockBlobReference(blobPath + "lg." + localeName + ".blp").UploadFromStreamAsync(loreStream);

                    using var bg = handler.OpenFile(instance.BackgroundImageId);
                    await container.GetBlockBlobReference(blobPath + "xl." + localeName + ".blp").UploadFromStreamAsync(bg);

                    using var btnLarge = handler.OpenFile(instance.ButtonImageId);
                    await container.GetBlockBlobReference(blobPath + "sm." + localeName + ".blp").UploadFromStreamAsync(btnLarge);

                    using var btnSmall = handler.OpenFile(instance.ButtonImageId);
                    await container.GetBlockBlobReference(blobPath + "xs." + localeName + ".blp").UploadFromStreamAsync(btnSmall);

                    var encounters = new List <Encounter>();

                    foreach (var encounter in dbfiles.EnumerateTable("JournalEncounter", handler))
                    {
                        var instanceId = encounter.Value.GetField <int>(3);
                        if (instanceId != instance.Id)
                        {
                            continue;
                        }

                        var journalEncounter = new Encounter
                        {
                            Id                    = encounter.Key,
                            Name                  = encounter.Value.GetField <string>(0),
                            Description           = encounter.Value.GetField <string>(1),
                            MapX                  = encounter.Value.GetField <float>(2, 0),
                            MapY                  = encounter.Value.GetField <float>(2, 1),
                            InstanceId            = instanceId,
                            EncounterId           = encounter.Value.GetField <int>(4),
                            Order                 = encounter.Value.GetField <int>(5),
                            FirstSectionId        = encounter.Value.GetField <int>(6),
                            UiMapId               = encounter.Value.GetField <int>(7),
                            MapDisplayConditionId = encounter.Value.GetField <int>(8),
                            Flags                 = encounter.Value.GetField <byte>(9),
                            Difficulty            = encounter.Value.GetField <byte>(10),
                            Sections              = new List <JournalSection>()
                        };

                        var sections = new Dictionary <int, JournalSection>();

                        foreach (var encounterSection in dbfiles.EnumerateTable("JournalEncounterSection", handler))
                        {
                            var section = new JournalSection
                            {
                                Id                    = encounterSection.Key,
                                Name                  = encounterSection.Value.GetField <string>(0),
                                Description           = encounterSection.Value.GetField <string>(1),
                                JournalEncounterId    = encounterSection.Value.GetField <ushort>(2),
                                Order                 = encounterSection.Value.GetField <byte>(3),
                                ParentSectionId       = encounterSection.Value.GetField <ushort>(4),
                                FirstChildSectionId   = encounterSection.Value.GetField <ushort>(5),
                                NextSiblingSectionId  = encounterSection.Value.GetField <ushort>(6),
                                SectionType           = encounterSection.Value.GetField <byte>(7), // 3 = overview, 1 = creature, 2 = spell
                                IconCreatureDisplayId = encounterSection.Value.GetField <uint>(8),
                                UiModelSceneId        = encounterSection.Value.GetField <int>(9),
                                SpellId               = encounterSection.Value.GetField <int>(10),
                                IconFileDataId        = encounterSection.Value.GetField <int>(11),
                                Flags                 = encounterSection.Value.GetField <ushort>(12),
                                IconFlags             = encounterSection.Value.GetField <ushort>(13), // 1=tank, 2=dps, 4=healer,
                                DifficultyMask        = encounterSection.Value.GetField <byte>(14),
                            };

                            if (section.JournalEncounterId != journalEncounter.Id)
                            {
                                continue;
                            }

                            sections[section.Id] = section;
                        }

                        journalEncounter.Sections = BuildSectionTree(sections);

                        encounters.Add(journalEncounter);
                    }

                    await container.GetBlockBlobReference("data/dungeon/" + instance.Id + "/encounters." + localeName + ".json")
                    .UploadTextAsync(JsonConvert.SerializeObject(encounters, serializerSettings));
                }

                // Save the list of dungeons for this tier
                await container.GetBlockBlobReference("data/" + tier.Id + "/dungeons." + localeName + ".json")
                .UploadTextAsync(JsonConvert.SerializeObject(dungeons, serializerSettings));
            }

            return(new OkObjectResult(tiers));
        }
Ejemplo n.º 7
0
        private const uint WDC3FmtSig = 0x33434457; // WDC3

        public WDC3Writer(WDC3Reader reader, IDictionary <int, T> storage, Stream stream) : base(reader)
        {
            // always 2 empties
            StringTableSize++;

            WDC3RowSerializer <T> serializer = new WDC3RowSerializer <T>(this);

            serializer.Serialize(storage);
            serializer.GetCopyRows();
            serializer.UpdateStringOffsets(storage);

            RecordsCount = serializer.Records.Count - CopyData.Count;

            var(commonDataSize, palletDataSize, referenceDataSize) = GetDataSizes();

            using (var writer = new BinaryWriter(stream))
            {
                int minIndex = storage.Keys.Min();
                int maxIndex = storage.Keys.Max();

                writer.Write(WDC3FmtSig);
                writer.Write(RecordsCount);
                writer.Write(FieldsCount);
                writer.Write(RecordSize);
                writer.Write(StringTableSize);
                writer.Write(reader.TableHash);
                writer.Write(reader.LayoutHash);
                writer.Write(minIndex);
                writer.Write(maxIndex);
                writer.Write(reader.Locale);
                writer.Write((ushort)Flags);
                writer.Write((ushort)IdFieldIndex);

                writer.Write(FieldsCount);                      // totalFieldCount
                writer.Write(reader.PackedDataOffset);
                writer.Write(ReferenceData.Count > 0 ? 1 : 0);  // RelationshipColumnCount
                writer.Write(ColumnMeta.Length * 24);           // ColumnMetaDataSize
                writer.Write(commonDataSize);
                writer.Write(palletDataSize);
                writer.Write(1);                                // sections count

                if (storage.Count == 0)
                {
                    return;
                }

                // section header
                int fileOffset = HeaderSize + (Meta.Length * 4) + (ColumnMeta.Length * 24) + Unsafe.SizeOf <SectionHeaderWDC3>() + palletDataSize + commonDataSize;

                writer.Write(0UL);                                                  // TactKeyLookup
                writer.Write(fileOffset);                                           // FileOffset
                writer.Write(RecordsCount);                                         // NumRecords
                writer.Write(StringTableSize);
                writer.Write(0);                                                    // OffsetRecordsEndOffset
                writer.Write(RecordsCount * 4);                                     // IndexDataSize
                writer.Write(referenceDataSize);                                    // ParentLookupDataSize
                writer.Write(Flags.HasFlagExt(DB2Flags.Sparse) ? RecordsCount : 0); // OffsetMapIDCount
                writer.Write(CopyData.Count);                                       // CopyTableCount

                // field meta
                writer.WriteArray(Meta);

                // column meta data
                writer.WriteArray(ColumnMeta);

                // pallet data
                for (int i = 0; i < ColumnMeta.Length; i++)
                {
                    if (ColumnMeta[i].CompressionType == CompressionType.Pallet || ColumnMeta[i].CompressionType == CompressionType.PalletArray)
                    {
                        foreach (var palletData in PalletData[i])
                        {
                            writer.WriteArray(palletData);
                        }
                    }
                }

                // common data
                for (int i = 0; i < ColumnMeta.Length; i++)
                {
                    if (ColumnMeta[i].CompressionType == CompressionType.Common)
                    {
                        foreach (var commondata in CommonData[i])
                        {
                            writer.Write(commondata.Key);
                            writer.Write(commondata.Value.GetValue <int>());
                        }
                    }
                }

                // record data
                var m_sparseEntries = new Dictionary <int, SparseEntry>(storage.Count);
                foreach (var record in serializer.Records)
                {
                    if (!CopyData.TryGetValue(record.Key, out int parent))
                    {
                        m_sparseEntries.Add(record.Key, new SparseEntry()
                        {
                            Offset = (uint)writer.BaseStream.Position,
                            Size   = (ushort)record.Value.TotalBytesWrittenOut
                        });

                        record.Value.CopyTo(writer.BaseStream);
                    }
                }

                // string table
                if (!Flags.HasFlagExt(DB2Flags.Sparse))
                {
                    writer.WriteCString("");
                    foreach (var str in StringTable)
                    {
                        writer.WriteCString(str.Key);
                    }
                }

                // set the OffsetRecordsEndOffset
                if (Flags.HasFlagExt(DB2Flags.Sparse))
                {
                    long oldPos = writer.BaseStream.Position;
                    writer.BaseStream.Position = 92;
                    writer.Write((uint)oldPos);
                    writer.BaseStream.Position = oldPos;
                }

                // index table
                if (Flags.HasFlagExt(DB2Flags.Index))
                {
                    writer.WriteArray(serializer.Records.Keys.Except(CopyData.Keys).ToArray());
                }

                // copy table
                foreach (var copyRecord in CopyData)
                {
                    writer.Write(copyRecord.Key);
                    writer.Write(copyRecord.Value);
                }

                // sparse data
                if (Flags.HasFlagExt(DB2Flags.Sparse))
                {
                    writer.WriteArray(m_sparseEntries.Values.ToArray());
                }

                // reference data
                if (ReferenceData.Count > 0)
                {
                    writer.Write(ReferenceData.Count);
                    writer.Write(ReferenceData.Min());
                    writer.Write(ReferenceData.Max());

                    for (int i = 0; i < ReferenceData.Count; i++)
                    {
                        writer.Write(ReferenceData[i]);
                        writer.Write(i);
                    }
                }

                // sparse data idss
                if (Flags.HasFlagExt(DB2Flags.Sparse))
                {
                    writer.WriteArray(m_sparseEntries.Keys.ToArray());
                }
            }
        }