记录 v4 CTree 数据库的内部结构.. 解析二进制文件?

Documenting Internal Structures of v4 CTree Database.. parsing binary?

我有一个非常古老的(大约 1993 年,v4 Faircom CTree 数据库)被一家现已倒闭的测试设备制造商使用。我无权访问原始配置,但似乎只有两个表(两个文件),然后是另外两个匹配文件,我认为它们是它们的同级索引文件。

我试图在较新的 C# 程序中解析这些旧文件以加载数据用于其他目的,所以我正在查看二进制文件....我了解它们的结构,有一个 header和各种指针(这是用 Turbo C 编写和编译的),我什至在其中看到我的数据作为文本。

binary snapshot picture #1

从上图可以看出,记录数据的偏移量是主文件的第二个字header。现在我们进入记录的特定结构...

binary snapshot picture #2

此处显示 4 条记录 - 每条颜色不同

第一个字 - 表示记录类型的两个匹配字节? (FA,FB,FC,FD,FE,FF=结束)

第二个字 - 是记录的长度(在 6 header 字节之后)。

第三个单词 - 未知(有时与第二个单词匹配,有时为零,有时为其他)

此 post 的实际问题:

  1. 看来我需要自己通过分析几个不同的数据文件来确定记录的字段以确定长度。有谁知道那里是否有可以 'understand' Faircom 数据库结构的免费工具?我已经联系了 Faircom,如果我购买支持合同,他们会帮助我,但我并不是完全希望为此花钱,但我可能不得不花钱。
  2. 我试图找到 C headers 来获取这些旧版本中发出的二进制文件,但我就是找不到这么低的东西。任何人都对 Faircom DB 内部结构或超低级别参考信息有任何指示或知识。了解那些 0xFA-0xFF 枚举也很方便。

好吧,总结一下,这就是我的着陆点..最后,我自己将二进制文件解析为相关记录,因为我没有需要使用的原始模式文件任何 Faircom 的东西。重新创建文件的任务与在 C# 中解析二进制文件所花费的时间一样多,因为这非常简单。

我确实看到 0xFA 记录似乎是 'real' 记录,而 0xFD 记录表示它们是 'deleted'。原始程序确实能够 'revert' 如果您更改了文件但尚未保存,因此 faircom 数据库结构允许它在 1994 年就可以完成。

其他可能特定于或不特定于我的架构的一般观察结果...我有以下数据类型...字符串、StringArray、ByteArray、Number

我创建了一个看起来像这样的小 SchemaDefinition..

schema = new CTreeSchema();
schema.Columns.Add(new CTreeColumn("Ordinal", CTreeColumnType.Number, 4));
schema.Columns.Add(new CTreeColumn("Unknown3", CTreeColumnType.Number, 4));
schema.Columns.Add(new CTreeColumn("Unknown4", CTreeColumnType.Number, 4));
schema.Columns.Add(new CTreeColumn("Name", CTreeColumnType.String, 0));
schema.Columns.Add(new CTreeColumn("ExtendedData", CTreeColumnType.StringArray, 0));

然后我用这样的东西解析它...

public CTreeRecord(CTreeSchema schema, byte[] bytes)
    {
        _internalBytes = bytes;
        RecordTypeId = bytes[0];
        int byteIndex = 6;
        foreach(var i in schema.Columns)
        {
            int endIndex = 0;
            switch (i.Type)
            {
                case CTreeColumn.CTreeColumnType.String:
                    if (i.Length == 0)
                    {
                        //null terminated
                        endIndex = Array.IndexOf<byte>(bytes, 0x00, byteIndex);
                        i.Value = Encoding.ASCII.GetString(bytes.Skip(byteIndex).Take(endIndex - byteIndex).ToArray());
                        byteIndex += (1+ endIndex - byteIndex);
                    }
                    if (i.Length > 0)
                    {
                        //specific length
                        throw new NotSupportedException("Static Length String columns not supported.");
                        //byteIndex += i.Length;
                    }
                    break;
                case CTreeColumn.CTreeColumnType.StringArray:
                    List<string> headerStrings = new List<string>();
                    endIndex = Array.IndexOf<byte>(bytes, 0x00, byteIndex);
                    byte[] arrayBytes = bytes.Skip(byteIndex).Take(endIndex - byteIndex).ToArray();
                    byteIndex += (1 + endIndex - byteIndex);
                    List<byte[]> headerBytes = SplitByteString(arrayBytes, 0x01, false);
                    foreach (byte[] b in headerBytes)
                    {
                        headerStrings.Add(Encoding.ASCII.GetString(b));
                    }
                    i.Value = headerStrings;
                    break;
                case CTreeColumn.CTreeColumnType.ByteArray:
                    List<byte[]> byteArray = new List<byte[]>();
                    for (int a = 0; a < i.Length; a++)
                    {
                        endIndex = Array.IndexOf<byte>(bytes, 0x00, byteIndex);
                        byteArray.Add(bytes.Skip(byteIndex).Take(endIndex - byteIndex).ToArray());
                        byteIndex += (1 + endIndex - byteIndex);
                    }
                    i.Value = byteArray;
                    break;
                case CTreeColumn.CTreeColumnType.Number:
                    switch (i.Length)
                    {
                        case 2:
                            i.Value = BitConverter.ToUInt16(bytes, byteIndex);
                            break;
                        case 4:
                            i.Value = BitConverter.ToUInt32(bytes, byteIndex);
                            break;
                    }
                    byteIndex += i.Length;
                    break;
                case CTreeColumn.CTreeColumnType.Date:
                    throw new NotSupportedException("Date columns not supported.");
                    break;
            }
            Columns.Add(i);
        }
    }

    private List<byte[]> SplitByteString(byte[] bytes, byte splitValue, bool removeEmptyEntries)
    {
        List<byte[]> splitBytes = new List<byte[]>();
        int currentIndex = 0;
        while (currentIndex < bytes.Length)
        {
            int splitIndex = Array.IndexOf<byte>(bytes, splitValue, currentIndex);
            if (splitIndex >= 0)
            {
                //found one
                int currentLength = splitIndex - currentIndex;
                if (!(currentLength == 0 && removeEmptyEntries))
                {
                    splitBytes.Add(bytes.Skip(currentIndex).Take(currentLength).ToArray());
                    currentIndex += (1 + currentLength);
                }
                else
                {
                    currentIndex++;
                }
            }
            else
            {
                //not found, just take until end now..
                splitBytes.Add(bytes.Skip(currentIndex).Take(bytes.Length - currentIndex).ToArray());
                currentIndex += (bytes.Length - currentIndex);
            }
            
        }
        return splitBytes;
    }

总而言之,它非常丑陋并且非常针对此应用程序,但如果有人必须处理 C Faircom 数据库文件,这可能会提供一些理智。