たぶん、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) >> 5
if 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) - offset
for 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) - offset
for i := 0; i < length; i++ {
output = append(output, output[begin + i])
}
コード例でふつーにappendしてますが、これには理由がちょっとだけありまして.
というのも、LZパケットの性質上
となる場合がありまして。普通にmemcpyとかしようものならバッファオーバーしかねないんですわ.
この、長さ以上の部分にはみ出るというのはつまり、順番に後ろに追加していけば前のバイトが繰り返される、ということです.
(この辺はNomenのソースだと隠れ仕様扱いでした)