Beispiel #1
0
        private void ParseWDC1(MemoryStream stream, string file)
        {
            stream.Position = 0;

            Dictionary <int, FieldType> FieldTypes = new Dictionary <int, FieldType>()
            {
                { 8, FieldType.ULONG },
                { 4, FieldType.INT },
                { 2, FieldType.USHORT },
                { 1, FieldType.BYTE },
            };

            using (var dbReader = new BinaryReader(stream, Encoding.UTF8))
            {
                WDC1 header = ExtractHeader(dbReader) as WDC1;
                if (header == null)
                {
                    return;
                }
                if (header.RecordCount == 0 || header.RecordSize == 0)
                {
                    return;
                }

                long pos = dbReader.BaseStream.Position;
                Dictionary <int, string> StringTable = new StringTable().Read(dbReader, pos, pos + header.StringBlockSize);
                bool stringtableused = StringTable.Values.Any(x => !string.IsNullOrWhiteSpace(x)) && !header.HasOffsetTable;

                List <FieldInfo> fields = new List <FieldInfo>();
                var copyTable           = header.ReadOffsetData(dbReader, dbReader.BaseStream.Position);

                for (int f = 0; f < header.ColumnMeta.Count; f++)
                {
                    FieldType byteType;

                    if (f == header.IdIndex || (f == 0 && header.HasIndexTable))
                    {
                        fields.Add(new FieldInfo()
                        {
                            ArraySize = 1, Type = FieldType.INT
                        });
                        continue;
                    }

                    if (header.ColumnMeta[f].CompressionType == CompressionType.None)
                    {
                        int bitSize = header.FieldStructure[f].BitCount;
                        byteType = FieldTypes[NextPow2(~~(bitSize + 7) / 8)];
                    }
                    else if (header.ColumnMeta[f].CompressionType > CompressionType.Immediate)
                    {
                        byteType = FieldType.INT;
                    }
                    else
                    {
                        byteType = FieldTypes[NextPow2(~~(header.ColumnMeta[f].BitWidth + 7) / 8)];
                    }

                    fields.Add(new FieldInfo()
                    {
                        ArraySize = header.ColumnMeta[f].ArraySize,
                        Type      = byteType == FieldType.INT ? FieldType.UNKNOWN : byteType
                    });
                }

                int offset = 0;
                for (int i = 0; i < fields.Count; i++)
                {
                    switch (fields[i].Type)
                    {
                    case FieldType.BYTE:
                        offset++;
                        continue;

                    case FieldType.USHORT:
                        offset += 2;
                        continue;

                    case FieldType.INT:
                        offset += 4;
                        continue;

                    case FieldType.ULONG:
                        offset += 8;
                        continue;
                    }

                    List <FieldType> options = new List <FieldType>()
                    {
                        FieldType.INT, FieldType.FLOAT, FieldType.STRING
                    };
                    if (!stringtableused)
                    {
                        options.Remove(FieldType.STRING);                         //Stringtable not used
                    }
                    List <int>   ints   = new List <int>();
                    List <float> floats = new List <float>();
                    foreach (var c in copyTable)
                    {
                        for (int x = 0; x < fields[i].ArraySize; x++)
                        {
                            int asInt = BitConverter.ToInt32(c.Value.Skip(offset + (4 * x)).Take(4).ToArray(), 0);
                            if (asInt > 0)
                            {
                                ints.Add(asInt);

                                if (FloatUtil.IsLikelyFloat(asInt))
                                {
                                    floats.Add(BitConverter.ToSingle(BitConverter.GetBytes(asInt), 0));
                                }
                            }
                        }
                    }

                    // remove 0's as they could be anything - if all removed then guess its an int
                    ints.RemoveAll(x => x == 0);
                    if (ints.Count == 0)
                    {
                        fields[i].Type = FieldType.INT;
                        offset        += (4 * fields[i].ArraySize);
                        continue;
                    }

                    // stringtable doesn't contain string so cant be a string
                    if (options.Contains(FieldType.STRING) && ints.Any(x => !StringTable.ContainsKey(x)))
                    {
                        options.Remove(FieldType.STRING);
                    }

                    if (floats.Count / (float)ints.Count >= 0.85)
                    {
                        fields[i].Type = FieldType.FLOAT;
                    }
                    else if (options.Contains(FieldType.STRING))
                    {
                        fields[i].Type = FieldType.STRING;
                    }
                    else if (header.ColumnMeta[i].CompressionType == CompressionType.Immediate && header.ColumnMeta[i].Cardinality == 0)
                    {
                        fields[i].Type = FieldType.UINT;
                    }
                    else
                    {
                        fields[i].Type = FieldType.INT;
                    }

                    offset += (4 * fields[i].ArraySize);
                }

                Table table = new Table();
                table.Name   = Path.GetFileNameWithoutExtension(file);
                table.Fields = new List <Field>();
                string format = $"X{fields.Count.ToString("X").Length}";                 //X2, X3 etc

                for (int i = 0; i < fields.Count; i++)
                {
                    if (header.RelationshipCount > 0 && i == fields.Count - 1)
                    {
                        continue;
                    }

                    Field field = new Field();
                    field.Name      = (i == header.IdIndex ? "ID" : $"field{i.ToString(format)}");
                    field.IsIndex   = (i == header.IdIndex);
                    field.ArraySize = (field.IsIndex ? 1 : fields[i].ArraySize);
                    field.Type      = fields[i].Type.ToString().ToLower();
                    table.Fields.Add(field);
                    Console.WriteLine($"Name: {field.Name} | Array: {field.ArraySize} | Type: {field.Type}");
                }

                tables.Add(table);
                Database.ForceGC();
            }
        }
Beispiel #2
0
        public static DBEntry Read(string dbFile, int build, out string error)
        {
            error = "";

            using (var fs = new FileStream(dbFile, FileMode.Open, FileAccess.Read))
                using (var br = new BinaryReader(fs, Encoding.UTF8))
                {
                    DBHeader header = ReadHeader(br, dbFile, build);
                    if (IsKnown(header, out DBEntry known))
                    {
                        return(known);
                    }

                    if (!ValidationChecks(header, dbFile, out error))
                    {
                        return(null);
                    }

                    if (header.RecordSize / header.FieldCount != 4)            // dbc has byte column
                    {
                        error = "Has byte columns.";
                        return(new DBEntry()
                        {
                            Name = Path.GetFileName(dbFile).ToUpper(),
                            Builds = new List <int>()
                            {
                                build
                            },
                            Fields = new List <DBField>()
                        });
                    }

                    Dictionary <int, string> stringTable = new Dictionary <int, string>();
                    List <byte[]>            dataTable   = new List <byte[]>();
                    FieldInfo[] fieldInfo   = new FieldInfo[header.FieldCount];
                    bool        hasStrings  = false;
                    int         nullStrings = 0;

                    // stringtable stuff
                    long pos = br.BaseStream.Position;
                    long stringTableStart = br.BaseStream.Position += header.RecordCount * header.RecordSize;
                    stringTable = ReadStringTable(br, stringTableStart);             //Get stringtable
                    br.Scrub(pos);

                    // if 1 or 2 empty strings only then strings aren't unused
                    hasStrings = !(stringTable.Values.Count <= 2 && stringTable.Values.All(x => string.IsNullOrWhiteSpace(x)));
                    // count empties
                    nullStrings = stringTable.Count(x => string.IsNullOrWhiteSpace(x.Value));

                    // read data
                    for (int i = 0; i < header.RecordCount; i++)
                    {
                        dataTable.Add(br.ReadBytes((int)header.RecordSize));
                    }

                    // compute possible types
                    for (int i = 0; i < header.FieldCount; i++)
                    {
                        // no pdb dbc struct has uint fields ITS ALL A LIE!!
                        List <FieldType> options = new List <FieldType>()
                        {
                            FieldType.INT, /*FieldType.UINT,*/ FieldType.FLOAT, FieldType.STRING
                        };
                        if (!hasStrings)
                        {
                            options.Remove(FieldType.STRING);                     // strings not used
                        }
                        List <int>    intVals    = new List <int>();
                        List <string> stringVals = new List <string>();
                        List <float>  floatVals  = new List <float>();

                        for (int r = 0; r < dataTable.Count; r++)
                        {
                            byte[] data = dataTable[r].Skip(i * 4).Take(4).ToArray();

                            // ignore 0 byte columns as they could be anything
                            if (data.All(x => x == 0))
                            {
                                continue;
                            }

                            // int value
                            int asInt = BitConverter.ToInt32(data, 0);
                            intVals.Add(asInt);

                            // string check
                            if (options.Contains(FieldType.STRING))
                            {
                                if (!stringTable.ContainsKey(asInt))
                                {
                                    options.Remove(FieldType.STRING);
                                    stringVals.Clear();                             // 100% not a string!
                                }
                                else
                                {
                                    stringVals.Add(stringTable[asInt]);
                                }
                            }

                            // float check
                            if (options.Contains(FieldType.FLOAT) && FloatUtil.IsLikelyFloat(asInt))
                            {
                                floatVals.Add(BitConverter.ToSingle(data, 0));
                            }

                            // uint check - prefer signed over unsigned as per the wow client
                            if (options.Contains(FieldType.UINT) && asInt < 0)
                            {
                                options.Remove(FieldType.UINT);
                            }
                        }

                        fieldInfo[i] = new FieldInfo()
                        {
                            IsEmpty         = intVals.Count == 0,
                            FloatPercentage = (floatVals.Count / (float)intVals.Count) * 100f,                     // % of valid floats
                            Options         = options,
                            UniqueStrings   = stringVals.Distinct().Count(),
                            UniqueInts      = intVals.Distinct().Count()
                        };
                    }

                    // calculate field types
                    List <FieldType> temp = new List <FieldType>();
                    for (int i = 0; i < fieldInfo.Length; i++)
                    {
                        var info = fieldInfo[i];

                        if (info.IsEmpty)                 // all records are 0
                        {
                            // most likely to be int, less likely to be float, very unlikely to be a string
                            temp.Add(info.Options.Contains(FieldType.UINT) ? FieldType.UINT : FieldType.INT);
                        }
                        else if (info.Options.Contains(FieldType.FLOAT) && info.FloatPercentage > FLOAT_THRESHOLD) // threshold needs tweaking?
                        {
                            temp.Add(FieldType.FLOAT);                                                             // high % of valid floats
                        }
                        else if (info.Options.Contains(FieldType.STRING) && info.UniqueStrings > 0)
                        {
                            // 1 string, 1st field is more likely an ID not a string
                            if (stringTable.Count - nullStrings < header.FieldCount && header.RecordCount == 1)
                            {
                                if (i == 0)
                                {
                                    temp.Add(info.Options.Contains(FieldType.UINT) ? FieldType.UINT : FieldType.INT);
                                }
                                else
                                {
                                    temp.Add(FieldType.STRING);
                                }
                            }
                            else if (info.UniqueStrings == 1)
                            {
                                // very unlikely to have a column with the same string in every row if there is
                                temp.Add(info.Options.Contains(FieldType.UINT) ? FieldType.UINT : FieldType.INT);
                            }
                            else
                            {
                                temp.Add(FieldType.STRING);                         // case of 0 = "" and 1 = "" in stringtable
                            }
                        }
                        else
                        {
                            temp.Add(info.Options.Contains(FieldType.UINT) ? FieldType.UINT : FieldType.INT);                     // uint over int
                        }

                        // LANGREFSTRING check
                        if (temp[temp.Count - 1] == FieldType.STRING)
                        {
                            if (IsLangStringRef(fieldInfo, i + 1, build, out int offset))
                            {
                                temp[temp.Count - 1] = FieldType.LANGSTRINGREF;
                                i += offset;
                            }
                        }
                    }

                    return(new DBEntry()
                    {
                        Name = Path.GetFileName(dbFile).ToUpper(),
                        Builds = new List <int>()
                        {
                            build
                        },
                        Fields = temp.Select(x => new DBField()
                        {
                            Name = "", Type = x.ToString().ToUpper()
                        }).ToList()
                    });
                }
        }