详细代码:https://github.com/cxcn/dtool

前言

微软拼音和微软五笔通用的用户自定义短语 dat 格式。

解析

前 8 个字节标识文件格式 machxudp,微软五笔的 lex 格式是 imscwubi

下面 8 个字节应该是版本号。

接下来每 4 字节一组,分别表示偏移表开始词条开始文件总长词条数导出的时间戳

然后补 0 一直到偏移表开始

偏移表记录了每个词条从词条开始的偏移量,每 4 个字节一组。

接下来就是词条本体部分:

#占用字节数描述
410 00 10 00 标记
a2该词条总字节长 – 词占用的字节长
1在候选中的位置
10x060x13,未知
40
42010-01-01开始的时间戳
a – 16编码(utf-16le),00 标识结束
词条总字节长 – a词(utf-16le),00 标识结束

代码实现:

func (MsUDP) Parse(filename string) Table {    data, _ := os.ReadFile(filename)    r := bytes.NewReader(data)    ret := make(Table, 0, r.Len()>>8)    // 词库偏移量    r.Seek(0x10, 0)    offset_start := ReadUint32(r) // 偏移表开始    entry_start := ReadUint32(r)  // 词条开始    entry_end := ReadUint32(r)    // 词条结束    entry_count := ReadUint32(r)  // 词条数    export_time := ReadUint32(r)  // 导出的时间    t := time.Unix(int64(export_time), 0)    fmt.Println(t, entry_end)    // 第一个偏移量    offset := 0    for i := 0; i < entry_count; i++ {        var next, length int        if i == entry_count-1 {            length = entry_end - entry_start - offset        } else {            r.Seek(int64(offset_start+4*(i+1)), 0)            next = ReadUint32(r)            length = next - offset        }        // fmt.Println(offset, next, length)        r.Seek(int64(offset+entry_start), 0)        offset = next        ReadUint32(r)            // 0x10001000        codeLen := ReadUint16(r) // 编码字节长+0x12        order, _ := r.ReadByte() // 顺序        _, _ = r.ReadByte()      // 0x06 不明        ReadUint32(r)            // 4 个空字节        ReadUint32(r)            // 时间戳        tmp := make([]byte, codeLen-0x12)        r.Read(tmp)        code, _ := util.Decode(tmp, "UTF-16LE")        ReadUint16(r) // 两个空字节        tmp = make([]byte, length-codeLen-2)        r.Read(tmp)        word, _ := util.Decode(tmp, "UTF-16LE")        fmt.Println(code, word)        ret = append(ret, Entry{word, code, order})    }    return ret}

生成

只需注意文件总长先用空字节代替,最后才写入。

代码实现:

func (MsUDP) Gen(table Table) []byte {    var buf bytes.Buffer    stamp := util.GetUint32(int(time.Now().Unix()))    buf.Write([]byte{0x6D, 0x73, 0x63, 0x68, 0x78, 0x75, 0x64, 0x70,        0x02, 0x00, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00})    buf.Write(util.GetUint32(0x40))    buf.Write(util.GetUint32(0x40 + 4*len(table)))    buf.Write(make([]byte, 4)) // 待定 文件总长    buf.Write(util.GetUint32(len(table)))    buf.Write(stamp)    buf.Write(make([]byte, 28))    buf.Write(make([]byte, 4))    words := make([][]byte, 0, len(table))    codes := make([][]byte, 0, len(table))    sum := 0    for i := range table {        word, _ := util.Encode([]byte(table[i].Word), "UTF-16LE")        code, _ := util.Encode([]byte(table[i].Code), "UTF-16LE")        words = append(words, word)        codes = append(codes, code)        if i != len(table)-1 {            sum += len(word) + len(code) + 20            buf.Write(util.GetUint32(sum))        }    }    for i := range table {        buf.Write([]byte{0x10, 0x00, 0x10, 0x00})        // fmt.Println(words[i], len(words[i]), codes[i], len(codes[i]))        buf.Write(util.GetUint16(len(codes[i]) + 18))        buf.WriteByte(table[i].Order)        buf.WriteByte(0x06)        buf.Write(make([]byte, 4))        buf.Write(stamp)        buf.Write(codes[i])        buf.Write([]byte{0, 0})        buf.Write(words[i])        buf.Write([]byte{0, 0})    }    b := buf.Bytes()    copy(b[0x18:0x1c], util.GetUint32(len(b)))    return b}