Open-source multiplayer game server compatible with the RuneScape client
https://www.openrs2.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.3 KiB
210 lines
5.3 KiB
package org.openrs2.compress.bzip2
|
|
|
|
import java.io.FilterOutputStream
|
|
import java.io.OutputStream
|
|
import java.util.zip.CRC32
|
|
|
|
public class Bzip2OutputStream(
|
|
output: OutputStream,
|
|
private val blockSize100k: Int,
|
|
private val workFactor: Int = DEFAULT_WORK_FACTOR
|
|
) : FilterOutputStream(output) {
|
|
// bit stream to byte stream encoder
|
|
private val output = BitOutputStream(output)
|
|
|
|
// used to implement write(int) efficiently
|
|
private val temp = ByteArray(1)
|
|
|
|
// current uncompressed block
|
|
private val block: ByteArray
|
|
private val blockCrc = CRC32()
|
|
private var blockSize = 0 // nblock
|
|
private var blockSizeMax: Int // nblockMAX
|
|
|
|
private var combinedCrc = 0
|
|
private var origPtr = 0
|
|
private val ftab = IntArray(65537)
|
|
private val ptr: IntArray
|
|
|
|
// run length encoder
|
|
private var stateInCh = 256
|
|
private var stateInLen = 0
|
|
|
|
private val inUse = BooleanArray(256)
|
|
|
|
init {
|
|
require(blockSize100k in 1..9)
|
|
require(workFactor in 1..250)
|
|
|
|
val n = blockSize100k * 100_000
|
|
block = ByteArray(n)
|
|
blockSizeMax = n - 19
|
|
ptr = IntArray(n)
|
|
|
|
writeHeader()
|
|
}
|
|
|
|
override fun write(b: Int) {
|
|
temp[0] = b.toByte()
|
|
write(temp)
|
|
}
|
|
|
|
override fun write(b: ByteArray, off: Int, len: Int) {
|
|
// run length encode input, compress when block is almost full
|
|
for (i in off until off + len) {
|
|
val ch = b[i].toInt() and 0xFF
|
|
|
|
if (ch != stateInCh && stateInLen == 1) {
|
|
// optimize common case
|
|
blockCrc.update(stateInCh)
|
|
inUse[stateInCh] = true
|
|
block[blockSize++] = stateInCh.toByte()
|
|
stateInCh = ch
|
|
} else if (ch != stateInCh || stateInLen == 255) {
|
|
flushRunLength()
|
|
|
|
stateInCh = ch
|
|
stateInLen = 1
|
|
} else {
|
|
stateInLen++
|
|
}
|
|
|
|
if (blockSize >= blockSizeMax) {
|
|
compressBlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun flushRunLength() {
|
|
if (stateInCh == 256) {
|
|
return
|
|
}
|
|
|
|
for (i in 0 until stateInLen) {
|
|
blockCrc.update(stateInCh)
|
|
}
|
|
|
|
inUse[stateInCh] = true
|
|
|
|
when (stateInLen) {
|
|
1 -> {
|
|
block[blockSize++] = stateInCh.toByte()
|
|
}
|
|
2 -> {
|
|
block[blockSize++] = stateInCh.toByte()
|
|
block[blockSize++] = stateInCh.toByte()
|
|
}
|
|
3 -> {
|
|
block[blockSize++] = stateInCh.toByte()
|
|
block[blockSize++] = stateInCh.toByte()
|
|
block[blockSize++] = stateInCh.toByte()
|
|
}
|
|
else -> {
|
|
block[blockSize++] = stateInCh.toByte()
|
|
block[blockSize++] = stateInCh.toByte()
|
|
block[blockSize++] = stateInCh.toByte()
|
|
block[blockSize++] = stateInCh.toByte()
|
|
|
|
val runLength = stateInLen - 4
|
|
inUse[runLength] = true
|
|
block[blockSize++] = runLength.toByte()
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun flush() {
|
|
flushRunLength()
|
|
compressBlock()
|
|
}
|
|
|
|
override fun close() {
|
|
flush()
|
|
writeTrailer()
|
|
output.close()
|
|
}
|
|
|
|
private fun writeHeader() {
|
|
// stream header
|
|
output.writeByte('B'.code)
|
|
output.writeByte('Z'.code)
|
|
output.writeByte('h'.code)
|
|
output.writeByte(('0' + blockSize100k).code)
|
|
}
|
|
|
|
private fun compressBlock() {
|
|
if (blockSize != 0) {
|
|
blockSort()
|
|
|
|
// block magic
|
|
output.writeByte(0x31)
|
|
output.writeByte(0x41)
|
|
output.writeByte(0x59)
|
|
output.writeByte(0x26)
|
|
output.writeByte(0x53)
|
|
output.writeByte(0x59)
|
|
|
|
// block checksum
|
|
val blockCrc = blockCrc.value.toInt()
|
|
combinedCrc = (combinedCrc shl 1) or (combinedCrc ushr 31)
|
|
combinedCrc = combinedCrc xor blockCrc
|
|
|
|
output.writeInt(blockCrc)
|
|
|
|
// not randomised
|
|
output.writeBoolean(false)
|
|
|
|
// original pointer
|
|
output.writeBits(24, origPtr)
|
|
|
|
TODO() // MTF
|
|
}
|
|
|
|
// reset state ready for next block
|
|
blockCrc.reset()
|
|
blockSize = 0
|
|
stateInCh = 256
|
|
stateInLen = 0
|
|
inUse.fill(false)
|
|
}
|
|
|
|
private fun writeTrailer() {
|
|
// stream magic
|
|
output.writeByte(0x17)
|
|
output.writeByte(0x72)
|
|
output.writeByte(0x45)
|
|
output.writeByte(0x38)
|
|
output.writeByte(0x50)
|
|
output.writeByte(0x90)
|
|
|
|
// stream checksum
|
|
output.writeInt(combinedCrc)
|
|
}
|
|
|
|
private fun blockSort() {
|
|
fallbackSort() // TODO: add mainSort() support
|
|
|
|
origPtr = -1
|
|
|
|
for (i in 0 until blockSize) {
|
|
if (ptr[i] == 0) {
|
|
origPtr = i
|
|
break
|
|
}
|
|
}
|
|
|
|
check(origPtr != -1)
|
|
}
|
|
|
|
private fun fallbackSort() {
|
|
TODO()
|
|
}
|
|
|
|
public companion object {
|
|
public const val DEFAULT_WORK_FACTOR: Int = 30
|
|
|
|
private const val N_RADIX = 2
|
|
private const val N_QSORT = 12
|
|
private const val N_SHELL = 18
|
|
private const val N_OVERSHOOT = N_RADIX + N_QSORT + N_SHELL + 2
|
|
}
|
|
}
|
|
|