たぶん、LZ符号を利用したもの.
LZ符号自体はLZ77とかWikipediaにあるもの見たほうが早い. 方式的には、同じデータがある場合はその場所と長さだけを記録することで、無駄なデータを持たない方式…だと思う.
メモしてて気が付いたんだけど、スライド窓1KBのLZ符号なのかこれ.
圧縮データ種類
圧縮データを持つパケットデータと、それのメタデータっぽい制御パケット、計5種類
一連のパケット (最大8個連続する) の前に存在する、メタデータ. バイト列だとまず最初にこれを読むことになる.
各ビットがフラグ扱いで、下位ビットから上位ビットの順に並んでいる.
nビット目に対応する、nパケット目がRLEパケットかLZパケットか、を示すのです.
なお、
となってます
参考例
ビット列が
だとしたら、次に来るパケットは
として出現するということ.
Goで実装すると
こうなる.
item, _ := input.ReadByte(); flags := []bool { item & 0x01 != 0, item & 0x02 != 0, item & 0x04 != 0, item & 0x08 != 0, item & 0x10 != 0, item & 0x20 != 0, item & 0x40 != 0, item & 0x80 != 0, }1バイトで構成される連長圧縮パケット.
構造
2バイトで構成される連長圧縮パケット.
繰り返し回数が8~263の幅になっていることに注意 (つまり、読みだした繰り返し回数に+8というゲタを履かせるのだ)
構造
RLEパケット解析コード例
byte1 := input.ReadByte()value := (byte1 & 0x1F)count := (byte1 & 0xE0) >> 5if count == 0 { // Long RLE Packetなので、次のバイトが必要 byte2 := input.ReadByte() count = byte2 + 8}// 展開for i := 0; i < count; i++ { output = append(output, value)}LZのデータは
という形式です.
この既存展開済みデータから複製する処理をWikiだとLZ copyって言ってました
やりかた
begin := len(output) - offsetfor i := 0; i < length; i++ { output = append(output, output[begin + i])}上記はGo言語っぽい何か. C言語版は公式Wikiにある('ω')
1バイト、または2バイトで構成されているLZ符号化データ.
データ的には64バイトまでのバイトを256バイト範囲で複製するもので、1バイト目の下位6bitに長さを詰めている.
通常では2バイト目が複製開始位置…なんだけど、この2バイト目がないものが4回に1回ある.
というのも、1バイト目の上位2ビットは余っているわけではなく、4回合わせて8bit=1バイトとなるようにされている. 曰くRecycle bits.
パケット構造
Recycle Bits
Recycle BitsはShort LZ Packetの1バイト目上位2bitの集合で、4回集まると1バイト分のオフセットデータになり、4回目のShort LZ Packetはそのバイトが複製開始位置となる (そのため、4回目のものには2バイト目がない)
このRecycle Bitsは上位から順番に出現するようにできている. つまり、
である (Wikiにもそう書いてある)
1024バイト前から最大258バイトを複製するLZ符号パケット.
バイト列構造
バイト列備考 (収まらなかった)
LZ展開コード例
// 先頭バイトbyte1 := input.ReadByte()if (byte1 & 0x3F) == 0x00 { // Long LZ Packet byte2 := input.ReadByte() byte3 := input.ReadByte() offset := ((byte1 & 0xC0) << 2) + (byte2 + 1) length := (byte3 + 3)} else { // Short LZ Packet length := (byte1 & 0x3F) + 1 packcnt += 1 // Short LZパケット数 recycle := (recycle << 2) + ((byte1 & 0xC0) >> 6) // リサイクルビットを連結 if packcnt % 4 == 0 { // 4回目のShort LZ Packetは次のバイトがなく、recycleを使う offset := recycle + 1 recycle = 0 } else { // 1~3回目のShort LZ Packetは次のバイトを使う byte2 := input.ReadByte() length := byte2 + 1 }}// 複製するbegin := len(output) - offsetfor i := 0; i < length; i++ { output = append(output, output[begin + i])}コード例でふつーにappendしてますが、これには理由がちょっとだけありまして.
というのも、LZパケットの性質上
となる場合がありまして。普通にmemcpyとかしようものならバッファオーバーしかねないんですわ.
この、長さ以上の部分にはみ出るというのはつまり、順番に後ろに追加していけば前のバイトが繰り返される、ということです.
(この辺はNomenのソースだと隠れ仕様扱いでした)