1 Commits

Author SHA1 Message Date
48727040b3 Merge pull request #5 from ped7g/z80_ped7g
Z80 ped7g - further optimisations
2022-09-27 22:35:44 +02:00
33 changed files with 267 additions and 2817 deletions

536
Cargo.lock generated
View File

@@ -10,15 +10,9 @@ checksum = "38d9ff5d688f1c13395289f67db01d4826b46dd694e7580accdc3e8430f2d98e"
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cc"
@@ -36,34 +30,19 @@ dependencies = [
"sacabase",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-utils",
]
@@ -73,194 +52,21 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"lazy_static",
]
[[package]]
name = "crossterm"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "207a948d1b4ff59e5aec9bb9426cc4fd3d17b719e5c7b74e27f0a60c4cc2d095"
dependencies = [
"bitflags",
"crossterm_winapi 0.6.2",
"lazy_static",
"libc",
"mio 0.6.23",
"parking_lot 0.10.2",
"signal-hook 0.1.17",
"winapi 0.3.9",
]
[[package]]
name = "crossterm"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags",
"crossterm_winapi 0.9.0",
"libc",
"mio 0.8.4",
"parking_lot 0.12.1",
"signal-hook 0.3.14",
"signal-hook-mio",
"winapi 0.3.9",
]
[[package]]
name = "crossterm_winapi"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
"bitflags",
"fuchsia-zircon-sys",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexopt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]]
name = "libc"
version = "0.2.135"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "lock_api"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
dependencies = [
"scopeguard",
]
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "mio"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
dependencies = [
"cfg-if 0.1.10",
"fuchsia-zircon",
"fuchsia-zircon-sys",
"iovec",
"kernel32-sys",
"libc",
"log",
"miow",
"net2",
"slab",
"winapi 0.2.8",
]
[[package]]
name = "mio"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.36.1",
]
[[package]]
name = "miow"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
dependencies = [
"kernel32-sys",
"net2",
"winapi 0.2.8",
"ws2_32-sys",
]
[[package]]
name = "net2"
version = "0.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631"
dependencies = [
"cfg-if 0.1.10",
"libc",
"winapi 0.3.9",
]
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
[[package]]
name = "num-traits"
@@ -271,53 +77,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "parking_lot"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
"lock_api 0.3.4",
"parking_lot_core 0.7.2",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api 0.4.9",
"parking_lot_core 0.9.4",
]
[[package]]
name = "parking_lot_core"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
"cfg-if 0.1.10",
"cloudabi",
"libc",
"redox_syscall 0.1.57",
"smallvec",
"winapi 0.3.9",
]
[[package]]
name = "parking_lot_core"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"windows-sys 0.42.0",
]
[[package]]
name = "pbr"
version = "1.0.4"
@@ -327,41 +86,14 @@ dependencies = [
"crossbeam-channel",
"libc",
"time",
"winapi 0.3.9",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.44"
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "sacabase"
@@ -372,109 +104,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "signal-hook"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
dependencies = [
"libc",
"mio 0.6.23",
"signal-hook-registry",
]
[[package]]
name = "signal-hook"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio 0.8.4",
"signal-hook 0.3.14",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "terminal"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd146db3fbe90911d0bb5498a393abda0af71a2c11e287ecb9ae30cf5d43667"
dependencies = [
"bitflags",
"crossterm 0.15.0",
]
[[package]]
name = "thiserror"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
@@ -482,27 +111,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
"wasi",
"winapi",
]
[[package]]
name = "unicode-ident"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "upkr"
version = "0.2.0"
version = "0.1.0"
dependencies = [
"anyhow",
"cdivsufsort",
"crossterm 0.25.0",
"lexopt",
"pbr",
"terminal",
"thiserror",
"pico-args",
]
[[package]]
@@ -511,18 +131,6 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
[[package]]
name = "winapi"
version = "0.3.9"
@@ -533,12 +141,6 @@ dependencies = [
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
@@ -550,113 +152,3 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc 0.36.1",
"windows_i686_gnu 0.36.1",
"windows_i686_msvc 0.36.1",
"windows_x86_64_gnu 0.36.1",
"windows_x86_64_msvc 0.36.1",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]

View File

@@ -1,16 +1,12 @@
[package]
name = "upkr"
version = "0.2.0"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = "debuginfo"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cdivsufsort = "2"
lexopt = "0.2.1"
pico-args = "0.4"
anyhow = "1"
thiserror = "1.0.36"
pbr = "1"
crossterm = { version = "0.25.0", default-features = false }
terminal = { version = "0.2.1", default-features = false, features = ["crossterm-backend"] }
pbr = "1"

View File

@@ -2,69 +2,13 @@
Upkr is a simple general purpose lz packer designed to be used in the [MicroW8](https://github.com/exoticorn/microw8) platform.
The compressed format is losely based on [Shrinkler](https://github.com/askeksa/Shrinkler) with the main difference being that
Upkr doesn't differentiate between literals at odd or even addresses (by default) and that I went with rANS/rABS instead of a range coder.
Upkr doesn't differnetiate between literals at odd or even addresses and that I went with rANS/rABS instead of a range coder.
Compression rate is on par with Shrinkler.
The differences compare to Shrinkler also makes it interesting on 8bit platforms. The z80 unpacker included in the release
is both about twice as fast and smaller than the Shrinkler unpacker.
At this point, Upkr should still be considered unstable - the compressed format is not very likely to change but I still want
to keep that option open a little longer.
## Inspirations:
* Ferris' blog about his [C64 intro packer](https://yupferris.github.io/blog/2020/08/31/c64-4k-intro-packer-deep-dive.html)
* [Shrinkler](https://github.com/askeksa/Shrinkler)
* Ryg's [sample rANS implementation](https://github.com/rygorous/ryg_rans)
## Unpackers
The release includes a reference c unpacker, as well as some optimized asm unpackers (arm and riscv). The unpckers in
c_unpacker and asm_unpackers unpack the default upkr compressed format. The z80_unpacker
is based on some variations to the compressed format. (Use `upkr --z80` to select those variations.)
The 16 bit dos unpacker also uses some variations. (`upkr --x86`)
## Usage
```
upkr [-l level(0-9)] [config options] <infile> [<outfile>]
upkr -u [config options] <infile> [<outfile>]
upkr --heatmap [config options] <infile> [<outfile>]
upkr --margin [config options] <infile>
-l, --level N compression level 0-9
-0, ..., -9 short form for setting compression level
-u, --unpack unpack infile
--heatmap calculate heatmap from compressed file
--margin calculate margin for overlapped unpacking of a packed file
Config presets for specific unpackers:
--z80 --big-endian-bitstream --invert-bit-encoding --simplified-prob-update -9
--x86 --bitstream --invert-is-match-bit --invert-continue-value-bit --invert-new-offset-bit
--x86b --bitstream --invert-continue-value-bit --no-repeated-offsets -9
Config options (need to match when packing/unpacking):
-b, --bitstream bitstream mode
-p, --parity N use N (2/4) parity contexts
-r, --reverse reverse input & output
Config options to tailor output to specific optimized unpackers:
--invert-is-match-bit
--invert-new-offset-bit
--invert-continue-value-bit
--invert-bit-encoding
--simplified-prob-update
--big-endian-bitstream (implies --bitstream)
--no-repeated-offsets
--eof-in-length
--max-offset N
--max-length N
```
## Heatmap
By default, the `--heatmap` flag writes out the heatmap data as a binary file. The heatmap file is
the same size as the unpacked data. Each byte can be interpreted like this:
```
is_literal = byte & 1; // whether the byte was encoded as a literal (as opposed to a match)
size_in_bits = 2.0 ** (((byte >> 1) - 64) / 8.0); // the size this byte takes up in the compressed data
```
* Ryg's [sample rANS implementation](https://github.com/rygorous/ryg_rans)

View File

@@ -1 +0,0 @@
/build/

View File

@@ -1,64 +0,0 @@
build/unpack_riscv64: ../c_unpacker/main.c unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -g -static -o $@ $^
test_riscv64: build/unpack_riscv64
qemu-riscv64 $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
build/unpack_riscv64.o: unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -c -o $@ $?
build/unpack_riscv64.bin: build/unpack_riscv64.o
riscv64-linux-gnu-objcopy -O binary --only-section=.text $? $@
disas-riscv64: build/unpack_riscv64.o
riscv64-linux-gnu-objdump -d $?
build/unpack_riscv32.o: unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -march=rv32imc -mabi=ilp32 -c -o $@ $?
build/unpack_riscv32.bin: build/unpack_riscv32.o
riscv64-linux-gnu-objcopy -O binary --only-section=.text $? $@
disas-riscv32: build/unpack_riscv32.o
riscv64-linux-gnu-objdump -d $?
build/unpack_armv6m: ../c_unpacker/main.c unpack_armv6m.S
mkdir -p build
arm-linux-gnueabihf-gcc -g -static -o $@ $^
test_armv6m: build/unpack_armv6m
qemu-arm $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
build/unpack_armv6m.bin: unpack_armv6m.S
mkdir -p build
arm-none-eabi-gcc -march=armv6-m -c -o build/unpack_armv6m.o $?
arm-none-eabi-objcopy -O binary --only-section=.text build/unpack_armv6m.o $@
build/unpack_arm32: ../c_unpacker/main.c unpack_arm32.S
mkdir -p build
arm-linux-gnueabihf-gcc -g -static -o $@ $^
test_arm32: build/unpack_arm32
qemu-arm $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
build/unpack_arm32.bin: unpack_arm32.S
mkdir -p build
arm-none-eabi-gcc -c -o build/unpack_arm32.o $?
arm-none-eabi-objcopy -O binary --only-section=.text build/unpack_arm32.o $@
build/unpack_c: ../c_unpacker/main.c ../c_unpacker/unpack.c
mkdir -p build
gcc -g -o $@ $^
test_c: build/unpack_c
$< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
sizes: build/unpack_armv6m.bin build/unpack_riscv64.bin build/unpack_riscv32.bin build/unpack_arm32.bin
ls -l build/*.bin

View File

@@ -1,99 +0,0 @@
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
u8* upkr_data_ptr;
u8 upkr_probs[1 + 255 + 1 + 2*32 + 2*32];
#ifdef UPKR_BITSTREAM
u16 upkr_state;
u8 upkr_current_byte;
int upkr_bits_left;
#else
u32 upkr_state;
#endif
int upkr_decode_bit(int context_index) {
#ifdef UPKR_BITSTREAM
while(upkr_state < 32768) {
if(upkr_bits_left == 0) {
upkr_current_byte = *upkr_data_ptr++;
upkr_bits_left = 8;
}
upkr_state = (upkr_state << 1) + (upkr_current_byte & 1);
upkr_current_byte >>= 1;
--upkr_bits_left;
}
#else
while(upkr_state < 4096) {
upkr_state = (upkr_state << 8) | *upkr_data_ptr++;
}
#endif
int prob = upkr_probs[context_index];
int bit = (upkr_state & 255) < prob ? 1 : 0;
int tmp = prob;
if(!bit) {
tmp = 256 - tmp;
}
upkr_state = tmp * (upkr_state >> 8) + (upkr_state & 255);
tmp += (256 - tmp + 8) >> 4;
if(!bit) {
upkr_state -= prob;
tmp = 256 - tmp;
}
upkr_probs[context_index] = tmp;
return bit;
}
int upkr_decode_length(int context_index) {
int length = 0;
int bit_pos = 0;
while(upkr_decode_bit(context_index)) {
length |= upkr_decode_bit(context_index + 1) << bit_pos++;
context_index += 2;
}
return length | (1 << bit_pos);
}
void* upkr_unpack(void* destination, void* compressed_data) {
upkr_data_ptr = (u8*)compressed_data;
upkr_state = 0;
#ifdef UPKR_BITSTREAM
upkr_bits_left = 0;
#endif
for(int i = 0; i < sizeof(upkr_probs); ++i)
upkr_probs[i] = 128;
u8* write_ptr = (u8*)destination;
int prev_was_match = 0;
int offset = 0;
for(;;) {
if(upkr_decode_bit(0)) {
if(prev_was_match || upkr_decode_bit(256)) {
offset = upkr_decode_length(257) - 1;
if(offset == 0) {
break;
}
}
int length = upkr_decode_length(257 + 64);
while(length--) {
*write_ptr = write_ptr[-offset];
++write_ptr;
}
prev_was_match = 1;
} else {
int byte = 1;
while(byte < 256) {
int bit = upkr_decode_bit(byte);
byte = (byte << 1) + bit;
}
*write_ptr++ = byte;
prev_was_match = 0;
}
}
return write_ptr;
}

Binary file not shown.

View File

@@ -1,100 +0,0 @@
.arm
.section .text
.global upkr_unpack
.type upkr_unpack, %function
// r0 .. out_ptr (returned)
// r1 .. in_ptr (returned)
// r2 .. state
// r3 .. offset
// r4 .. prev_was_literal / decode_length ret
// r5 .. context index
// r6 .. decode_length temp
// r7 .. probs ptr
// r8-r11 .. decode_bit temp
// r12 .. decode_length return address
upkr_unpack:
push { r3-r11, lr }
mov r2, #384
mov r3, #128
.Lclear:
subs r2, r2, #1
strb r3, [sp, -r2]
bne .Lclear
.Lloop:
mov r5, #0
bl upkr_decode_bit
bcc .Ldata
.Lmatch:
mov r5, #256
rsbs r6, r4, #0
blcc upkr_decode_bit
bcc .Lskip_offset
bl upkr_decode_length
adds r3, r4, #1
popeq { r3-r11, pc }
.Lskip_offset:
mov r5, #256+64
bl upkr_decode_length
.Lcopy_loop:
ldrb r5, [r0, r3]
.Lstore:
strb r5, [r0], #1
adds r4, r4, #1
blt .Lcopy_loop
b .Lloop
.Ldata:
mov r5, #1
.Ldata_loop:
bl upkr_decode_bit
adc r5, r5, r5
movs r4, r5, lsr #8
beq .Ldata_loop
b .Lstore
.type upkr_decode_length, %function
upkr_decode_length:
mov r12, lr
mov r4, #0
mvn r6, #0
.Lbit_loop:
bl upkr_decode_bit_inc
addcc r4, r4, r6
movcc pc, r12
bl upkr_decode_bit_inc
addcs r4, r4, r6
mov r6, r6, lsl #1
b .Lbit_loop
.type upkr_decode_bit, %function
upkr_decode_bit_inc:
add r5, r5, #1
upkr_decode_bit:
cmp r2, #4096
ldrltb r8, [r1], #1
orrlt r2, r8, r2, lsl#8
blt upkr_decode_bit
ldrb r8, [sp, -r5]
and r9, r2, #255
add r9, r9, #1
cmp r8, r9
rsbcs r8, r8, #256
mvn r9, r2, lsr#8
addcs r9, r9, #1
mla r2, r8, r9, r2
add r9, r8, #8
sub r8, r8, r9, lsr#4
rsbcs r8, r8, #256
strb r8, [sp, -r5]
mov pc, r14

View File

@@ -1,162 +0,0 @@
// armv6-m upkr unpacker by yrlf
// some optimizations by exoticorn
.syntax unified
.thumb
.section .text
#define ALIGNUP(n, align) (((n) + (align) - 1) & ~((align) - 1))
#define PROB_LEN (1 + 255 + 1 + 2*32 + 2*32)
#define FRAME_SIZE ALIGNUP(PROB_LEN, 4)
// auto upkr_unpack(uint8_t * out, uint8_t * in) -> tuple<uint8_t *, uint8_t *>
.global upkr_unpack
.type upkr_unpack, %function
// r0 .. out_ptr (returned)
// r1 .. in_ptr (returned)
// r2 .. state
// r3 .. offset
// r4 .. prev_was_literal / decode_length ret
// r5 .. subroutine arg (preserved)
// r6 .. decode_bit ret
// r7 .. probs ptr
upkr_unpack:
push { r4, r5, r6, r7, lr }
sub sp, sp, #FRAME_SIZE
mov r7, sp
movs r2, #255
adds r2, r2, #(PROB_LEN - 255)
movs r3, #128
.Lclear:
subs r2, r2, #1
strb r3, [r7, r2]
bne .Lclear
.Lloop:
movs r5, #0
bl upkr_decode_bit
beq .Ldata
.Lmatch:
// r6 = 1
lsls r5, r6, #8
cmp r4, #0
beq 1f
bl upkr_decode_bit
beq 2f
1:
bl upkr_decode_length
adds r3, r4, #1
beq .Lend
2:
adds r5, r5, #64
bl upkr_decode_length
.Lcopy_loop:
ldrb r5, [r0, r3]
.Lstore:
strb r5, [r0]
adds r0, r0, #1
adds r4, r4, #1
blt .Lcopy_loop
b .Lloop
.Ldata:
movs r5, #1
.Ldata_loop:
bl upkr_decode_bit
adcs r5, r5, r5
lsrs r4, r5, #8
beq .Ldata_loop
b .Lstore
.Lend:
add sp, sp, #FRAME_SIZE
pop { r4, r5, r6, r7, pc }
.type upkr_decode_length, %function
// r0 .. -length tmp (saved)
// r1 ..
// r2 ..
// r3 ..
// r4 .. -length (returned)
// r5 .. context index (saved)
// r6 .. (saved)
// r7 ..
upkr_decode_length:
push { r0, r5, r6, lr }
movs r0, #0
subs r4, r0, #1
.Lbit_loop:
adds r5, r5, #1
bl upkr_decode_bit
beq 1f
adds r5, r5, #1
bl upkr_decode_bit
beq 2f
adds r0, r0, r4
2:
lsls r4, r4, #1
b .Lbit_loop
1:
adds r4, r4, r0
pop { r0, r5, r6, pc }
.type upkr_decode_bit, %function
// r0 .. tmp / prob (saved)
// r1 .. in_ptr (modified)
// r2 .. state (modified)
// r3 .. scratch (saved)
// r4 ..
// r5 .. context index (preserved)
// r6 .. bit (returned)
// r7 .. probs ptr (preserved)
upkr_fill_state:
lsls r2, r2, #8
ldrb r6, [r1]
adds r1, r1, #1
orrs r2, r2, r6
upkr_decode_bit:
lsrs r6, r2, #12
beq upkr_fill_state
push { r0, r1, r3, lr }
ldrb r0, [r7, r5]
lsrs r3, r2, #8
uxtb r1, r2
subs r6, r1, r0
blt 1f
subs r1, r2, r0
rsbs r0, r0, #0
1:
muls r3, r3, r0
adds r2, r1, r3
rsbs r3, r0, #0
uxtb r3, r3
lsrs r3, r3, #4
adcs r0, r0, r3
cmp r6, #0
blt 1f
rsbs r0, r0, #0
1:
strb r0, [r7, r5]
lsrs r6, r6, #31
pop { r0, r1, r3, pc }

View File

@@ -1,131 +0,0 @@
.section .text
// x9 prev was literal
// x10 out ptr
// x11 in ptr
// x12 offset
// x13 state
// x14 context index
.global upkr_unpack
.type upkr_unpack, %function
upkr_unpack:
mv t4, ra
mv x17, x8
mv t6, x9
li x9, 256 + 128
mv x13, x9
1:
sub x8, sp, x13
sb x9, 0(x8)
addi x13, x13, -1
bnez x13, 1b
.Lmainloop:
li x14, 0
jal upkr_decode_bit
beqz x15, .Lliteral
slli x14, x14, 8
beqz x9, .Lread_offset_inc_x14
jal upkr_decode_bit
bnez x15, .Lread_offset
.Lfinished_offset:
addi x14, x14, 64
jalr ra // jal upkr_decode_number
1:
add x14, x10, t0
lbu x14, (x14)
.Lstore_byte:
sb x14, (x10)
addi x10, x10, 1
addi x9, x9, 1
blt x9, x0, 1b
j .Lmainloop
.Lliteral:
jal upkr_decode_bit
addi x14, x14, -1
slli x14, x14, 1
add x14, x14, x15
srli x9, x14, 8
beqz x9, .Lliteral
j .Lstore_byte
.Lread_offset_inc_x14:
addi x14, x14, 1
.Lread_offset:
jalr ra // jal upkr_decode_number
addi t0, x9, 1
bnez t0, .Lfinished_offset
.Ldone:
mv x8, x17
mv x9, t6
jr t4
upkr_load_byte:
lbu x15, 0(x11)
addi x11, x11, 1
slli x13, x13, 8
add x13, x13, x15
// x8 prob array ptr
// x11 in ptr
// x13 state
// x14 context index
// return:
// x14 context index + 1
// x15 decoded bit
upkr_decode_bit:
srli x15, x13, 12
beqz x15, upkr_load_byte
addi x14, x14, 1
sub t2, sp, x14
lbu x12, (t2)
andi x8, x13, 255
sltu x15, x8, x12
beqz x15, 1f
xori x12, x12, 255
addi x12, x12, 1
1:
srli x8, x13, 8
addi x8, x8, 1
sub x8, x8, x15
mul x8, x8, x12
sub x13, x13, x8
addi x8, x12, 8
srli x8, x8, 4
sub x12, x12, x8
beqz x15, 1f
sub x12, x0, x12
1:
sb x12, (t2)
jalr ra
// x14 context index
// return: x9 negtive decoded number
upkr_decode_number:
mv t3, ra
mv t5, x14
li x9, 0
li t1, -1
1:
jal upkr_decode_bit
beqz x15, 1f
jal upkr_decode_bit
beqz x15, 2f
add x9, x9, t1
2:
add t1, t1, t1
j 1b
1:
add x9, x9, t1
mv x14, t5
jr t3

View File

@@ -1,33 +0,0 @@
int upkr_decode_bit(int context_index) {
#ifdef UPKR_BITSTREAM
while(upkr_state < 32768) {
if(upkr_bits_left == 0) {
upkr_current_byte = *upkr_data_ptr++;
upkr_bits_left = 8;
}
upkr_state = (upkr_state << 1) + (upkr_current_byte & 1);
upkr_current_byte >>= 1;
--upkr_bits_left;
}
#else
while(upkr_state < 4096) {
upkr_state = (upkr_state << 8) | *upkr_data_ptr++;
}
#endif
int prob = upkr_probs[context_index];
int bit = (upkr_state & 255) < prob ? 1 : 0;
if(bit) {
prob = 256 - prob;
}
upkr_state -= prob * ((upkr_state >> 8) + (bit ^ 1));
prob -= (prob + 8) >> 4;
if(bit) {
prob = -prob;
}
upkr_probs[context_index] = prob;
return bit;
}

View File

@@ -1,7 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
void* upkr_unpack(void* destination, void* compressed_data);
int upkr_unpack(void* destination, void* compressed_data);
int main(int argn, char** argv) {
void* input_buffer = malloc(1024*1024);
@@ -13,8 +13,7 @@ int main(int argn, char** argv) {
printf("Compressed size: %d\n", in_size);
void* end_ptr = upkr_unpack(output_buffer, input_buffer);
int out_size = (char*)end_ptr - (char*)output_buffer;
int out_size = upkr_unpack(output_buffer, input_buffer);
printf("Uncompressed size: %d\n", out_size);

View File

@@ -19,8 +19,8 @@ int upkr_decode_bit(int context_index) {
upkr_current_byte = *upkr_data_ptr++;
upkr_bits_left = 8;
}
upkr_state = (upkr_state << 1) + (upkr_current_byte & 1);
upkr_current_byte >>= 1;
upkr_state = (upkr_state << 1) + (upkr_current_byte >> 7);
upkr_current_byte <<= 1;
--upkr_bits_left;
}
#else
@@ -30,16 +30,18 @@ int upkr_decode_bit(int context_index) {
#endif
int prob = upkr_probs[context_index];
int bit = (upkr_state & 255) < prob ? 1 : 0;
int bit = (upkr_state & 255) >= prob ? 1 : 0;
int prob_offset = 16;
int state_offset = 0;
int state_scale = prob;
if(bit) {
upkr_state = prob * (upkr_state >> 8) + (upkr_state & 255);
prob += (256 - prob + 8) >> 4;
} else {
upkr_state = (256 - prob) * (upkr_state >> 8) + (upkr_state & 255) - prob;
prob -= (prob + 8) >> 4;
state_offset = -prob;
state_scale = 256 - prob;
prob_offset = 0;
}
upkr_probs[context_index] = prob;
upkr_state = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255);
upkr_probs[context_index] = prob_offset + prob - ((prob + 8) >> 4);
return bit;
}
@@ -54,7 +56,7 @@ int upkr_decode_length(int context_index) {
return length | (1 << bit_pos);
}
void* upkr_unpack(void* destination, void* compressed_data) {
int upkr_unpack(void* destination, void* compressed_data) {
upkr_data_ptr = (u8*)compressed_data;
upkr_state = 0;
#ifdef UPKR_BITSTREAM
@@ -92,5 +94,5 @@ void* upkr_unpack(void* destination, void* compressed_data) {
}
}
return write_ptr;
return write_ptr - (u8*)destination;
}

50
compare-variants Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/env ruby
configs = [
[:master, '-b'],
[:z80, '-b'],
[:z80, ['-b', '-r']],
['old-prob-update', '-b']
]
files = Dir[ARGV[0] + '/*'].select {|f| !(f =~ /\.txt$/) }
short_names = files.map {|f| File.basename(f)[..16] }
results = []
def print_results(configs, names, results)
configs.each_with_index do |config, i|
printf "%d: %s\n", i + 1, config
end
print ' '
configs.each_index do |i|
printf " %-4d", i + 1
end
puts
names.each_with_index do |name, i|
printf "%16s", name
for res in results
res = res[i]
printf " %-4s", res if res
end
puts
end
end
for config in configs
raise unless system('git', 'checkout', config[0].to_s)
config_results = []
results << config_results
for file in files
if system(*['cargo', 'run', '--release', 'pack', '-l', '9', config[1], file, '/tmp/out.upk'].flatten) &&
system(*['cargo', 'run', '--release', 'unpack', config[1], '/tmp/out.upk', '/tmp/out.bin'].flatten) &&
File.read(file) == File.read('/tmp/out.bin')
size = File.size('/tmp/out.upk')
config_results << size
else
config_results << 'ERR'
end
print_results(configs, short_names, results)
end
end

View File

@@ -1,13 +0,0 @@
16 bit DOS executable stubs
---------------------------
by pestis and TomCat
unpack_x86_16_DOS.asm:
maximum compatibility, relocates unpacked code to normal start address
unpack_x86_16_DOS_no_relocation.asm:
saves some bytes by not relocating, unpacked code needs to be assembled to
start at 0x3FFE
unpack_x86_16_DOS_no_repeated_offset.asm:
removes support for repeated offsets, potentially at the cost of some compression ratio.
most likely only a win in very narrow circumstances around the 1kb mark

View File

@@ -1,161 +0,0 @@
; Contributions from pestis, TomCat and exoticorn
;
; This is the 16-bit DOS x86 decompression stub for upkr, which is designed for
; maximum compatibility: it relocates the compressed data so it can be
; decompressed starting at the normal .COM starting address. In other words,
; many of the already existing .COM files should be compressable using this
; stub.
;
; How to use:
; 1) Pack your intro using upkr into data.bin with the --x86 command line
; argument:
;
; $ upkr --x86 intro.com data.bin
;
; 2) Compile this .asm file using nasm (or any compatible assembler):
;
; $ nasm unpack_x86_16_DOS.asm -fbin -o intropck.com
;
; The packed size of the intro+stub is limited by max_len (see below) bytes.
;
; In specific cases, the unpacker stub can be further optimized to save a byte
; or two:
; 1) You can remove CLC before RET, if you don't mind carry being set upon
; program entry
; 2) You can also move PUSHA before PUSH SI and put POPA as the first
; operation of the compressed code.
max_len equ 16384
prog_start equ (0x100+max_len+510+relocation-upkr_unpack)
probs equ (((prog_start+max_len+510)+255)/256)*256
org 0x100
; This is will be loaded at 0x100, but relocates the code and data to prog_start
relocation:
push si ; si = 0x100 at DOS start, so save it for later ret
pusha ; pusha to recall all registers before starting intro
push si ; for pop di to start writing the output
mov di, prog_start ; the depacker & data are relocated from 0x100 to prog_start
mov ch, max_len/512
rep movsw
jmp si ; jump to relocated upkr_unpack
; upkr_unpack unpacks the code to 0x100 and runs it when done.
upkr_unpack:
xchg ax, bp ; position in input bitstream (bp) = 0
cwd ; upkr_state (dx) = 0;
xchg ax, cx ; cx = 0x9XX
mov al, 128 ; for(int i = 0; i < sizeof(upkr_probs); ++i) upkr_probs[i] = 128;
rep stosb
pop di ; u8* write_ptr = (u8*)destination;
.mainloop:
mov bx, probs
call upkr_decode_bit
jc .else ; if(upkr_decode_bit(0)) {
mov bh, (probs+256)/256
jcxz .skip_call
call upkr_decode_bit
jc .skipoffset
.skip_call:
stc
call upkr_decode_number ; offset = upkr_decode_length(258) - 1;
loop .notdone ; if(offset == 0)
popa
clc
ret
.notdone:
mov si, di
.sub:
dec si
loop .sub
.skipoffset:
mov bl, 128 ; int length = upkr_decode_length(384);
call upkr_decode_number
rep movsb ; *write_ptr = write_ptr[-offset];
jmp .mainloop
.byteloop:
call upkr_decode_bit ; int bit = upkr_decode_bit(byte);
.else:
adc bl, bl ; byte = (byte << 1) + bit;
jnc .byteloop
xchg ax, bx
stosb
inc si
mov cl, 1
jmp .mainloop ; prev_was_match = 0;
; upkr_decode_bit decodes one bit from the rANS entropy encoded bit stream.
; parameters:
; bx = memory address of the context probability
; dx = decoder state
; bp = bit position in input stream
; returns:
; dx = new decoder state
; bp = new bit position in input stream
; carry = bit
; trashes ax
upkr_load_bit:
bt [compressed_data-relocation+prog_start], bp
inc bp
adc dx, dx
upkr_decode_bit:
inc dx ; inc dx, dec dx is used to test the top (sign) bit of dx
dec dx
jns upkr_load_bit
movzx ax, byte [bx] ; u16 prob = upkr_probs[context_index]
push ax ; save prob, tmp = prob
cmp dl, al ; int bit = (upkr_state & 255) < prob ? 1 : 0; (carry = bit)
pushf ; save bit flags
jc .bit ; (skip if bit)
neg al ; tmp = 256 - tmp;
.bit:
mov [bx], al ; upkr_probs[context_index] = tmp + (256 - tmp + 8) >> 4;
neg byte [bx]
shr byte [bx], 4
adc [bx], al
mul dh ; upkr_state = tmp * (upkr_state >> 8) + (upkr_state & 255);
mov dh, 0
add dx, ax
popf
pop ax
jc .bit2 ; (skip if bit)
neg byte [bx] ; tmp = 256 - tmp;
sub dx, ax ; upkr_state -= prob; note that this will also leave carry always unset, which is what we want
.bit2:
ret ; return the bit in carry
; upkr_decode_number loads a variable length encoded number (up to 16 bits) from
; the compressed stream. Only numbers 1..65535 can be encoded. If the encoded
; number has 4 bits and is 1ABC, it is encoded using a kind of an "interleaved
; elias code": 0A0B0C1. The 1 in the end implies that no more bits are coming.
; parameters:
; cx = must be 0
; bx = memory address of the context probability
; dx = decoder state
; bp = bit position in input stream
; carry = must be 1
; returns:
; cx = length
; dx = new decoder state
; bp = new bit position in input stream
; carry = 1
; trashes bl, ax
upkr_decode_number_loop:
inc bx
call upkr_decode_bit
upkr_decode_number:
rcr cx, 1
inc bx
call upkr_decode_bit
jnc upkr_decode_number_loop ; 0 = there's more bits coming, 1 = no more bits
.loop2:
rcr cx, 1
jnc .loop2
ret
compressed_data:
incbin "data.bin"

View File

@@ -1,152 +0,0 @@
; Contributions from pestis, TomCat and exoticorn
;
; This is the 16-bit DOS x86 decompression stub for upkr, which decompresses the
; code starting at address 0x3FFE (or whatever is defined by the entrypoint
; below). Thus, the packed code needs to be assembled with org 0x3FFE to work.
;
; How to use:
; 1) Put POPA as the first instruction of your compiled code and use org
; 0x3FFE
; 2) Pack your intro using upkr into data.bin with the --x86 command line
; argument:
;
; $ upkr --x86 intro.com data.bin
;
; 2) Compile this .asm file using nasm (or any compatible assembler) e.g.
;
; $ nasm unpack_x86_16_DOS_no_relocation.asm -fbin -o intropck.com
;
; In specific cases, the unpacker stub can be further optimized to save a byte
; or two:
; 1) If your stub+compressed code is 2k or smaller, you can save 1 byte by
; putting probs at 0x900 and initializing DI with SALC; XCHG AX, DI instead
; of MOV DI, probs
; 2) If you remove the PUSHA (and POPA in the compressed code), then you can
; assume the registers as follows: AX = 0x00XX, BX = probs + 0x1XX, CX = 0
; DX = (trash), SI = DI = right after your program, SP = as it was when the
; program started, flags = carry set
;
; Note that even with the PUSHA / POPA, carry will be set (!) unlike normal dos
; program.
entry equ 0x3FFE
probs equ entry - 0x1FE ; must be aligned to 256
org 0x100
; This is will be loaded at 0x100, but relocates the code and data to prog_start
upkr_unpack:
pusha
xchg ax, bp ; position in bitstream = 0
cwd ; upkr_state = 0;
mov di, probs
mov ax, 0x8080 ; for(int i = 0; i < sizeof(upkr_probs); ++i) upkr_probs[i] = 128;
rep stosw
push di
.mainloop:
mov bx, probs
call upkr_decode_bit
jc .else ; if(upkr_decode_bit(0)) {
mov bh, (probs+256)/256
jcxz .skip_call ; if(prev_was_match || upkr_decode_bit(257)) {
call upkr_decode_bit
jc .skipoffset
.skip_call:
stc
call upkr_decode_number ; offset = upkr_decode_number(258) - 1;
mov si, di
loop .sub ; if(offset == 0)
ret
.sub:
dec si
loop .sub
.skipoffset:
mov bl, 128 ; int length = upkr_decode_number(384);
call upkr_decode_number
rep movsb ; *write_ptr = write_ptr[-offset];
jmp .mainloop
.byteloop:
call upkr_decode_bit ; int bit = upkr_decode_bit(byte);
.else:
adc bl, bl ; byte = (byte << 1) + bit;
jnc .byteloop
xchg ax, bx
stosb
inc si
mov cl, 1
jmp .mainloop ; prev_was_match = 0;
; upkr_decode_bit decodes one bit from the rANS entropy encoded bit stream.
; parameters:
; bx = memory address of the context probability
; dx = decoder state
; bp = bit position in input stream
; returns:
; dx = new decoder state
; bp = new bit position in input stream
; carry = bit
; trashes ax
upkr_load_bit:
bt [compressed_data], bp
inc bp
adc dx, dx
upkr_decode_bit:
inc dx
dec dx ; inc dx, dec dx is used to test the top (sign) bit of dx
jns upkr_load_bit
movzx ax, byte [bx] ; int prob = upkr_probs[context_index]
push ax ; save prob
cmp dl, al ; int bit = (upkr_state & 255) < prob ? 1 : 0; (carry = bit)
pushf ; save bit flags
jc .bit ; (skip if bit)
neg al ; tmp = 256 - tmp;
.bit:
mov [bx], al ; tmp_new = tmp + (256 - tmp + 8) >> 4;
neg byte [bx]
shr byte [bx], 4
adc [bx], al
mul dh ; upkr_state = tmp * (upkr_state >> 8) + (upkr_state & 255);
mov dh, 0
add dx, ax
popf
pop ax
jc .bit2 ; (skip if bit)
neg byte [bx] ; tmp = 256 - tmp;
sub dx, ax ; upkr_state -= prob; note that this will also leave carry always unset, which is what we want
.bit2:
ret ; flags = bit
; upkr_decode_number loads a variable length encoded number (up to 16 bits) from
; the compressed stream. Only numbers 1..65535 can be encoded. If the encoded
; number has 4 bits and is 1ABC, it is encoded using a kind of an "interleaved
; elias code": 0A0B0C1. The 1 in the end implies that no more bits are coming.
; parameters:
; cx = must be 0
; bx = memory address of the context probability
; dx = decoder state
; bp = bit position in input stream
; carry = must be 1
; returns:
; cx = length
; dx = new decoder state
; bp = new bit position in input stream
; carry = 1
; trashes bl, ax
upkr_decode_number_loop:
inc bx
call upkr_decode_bit
upkr_decode_number:
rcr cx, 1
inc bx
call upkr_decode_bit
jnc upkr_decode_number_loop ; while(upkr_decode_bit(context_index)) {
.loop2:
rcr cx, 1
jnc .loop2
ret
compressed_data:
incbin "data.bin"

View File

@@ -1,155 +0,0 @@
; Contributions from pestis, TomCat and exoticorn
;
; This is the 16-bit DOS x86 decompression stub for upkr, which is designed for
; the --no-repeated-offsets option of upkr. The decompression stub is slightly
; smaller, but the compressed data might be bigger, so you have to test if
; --no-repeated-offsets pays off in the end. This stub relocates the compressed
; data so it can be decompressed starting at the normal .COM starting address.
;
; How to use:
; 1) Pack your intro using upkr into data.bin with the --x86b command line
; argument: (notice the --x86b, not --x86!)
;
; $ upkr --x86b intro.com data.bin
;
; 2) Compile this .asm file using nasm (or any compatible assembler):
;
; $ nasm unpack_x86_16_DOS_no_repeated_offsets.asm -fbin -o intropck.com
;
; The packed size of the intro+stub is limited by max_len (see below) bytes.
;
; In specific cases, the unpacker stub can be further optimized to save a byte
; or two:
; 1) You can remove CLC before RET, if you don't mind carry being set upon
; program entry
; 2) You can also move PUSHA before PUSH SI and put POPA as the first
; operation of the compressed code.
max_len equ 16384
prog_start equ (0x100+max_len+510+relocation-upkr_unpack)
probs equ (((prog_start+max_len+510)+255)/256)*256
org 0x100
; This is will be loaded at 0x100, but relocates the code and data to prog_start
relocation:
push si ; si = 0x100 at DOS start, so save it for later ret
pusha ; pusha to recall all registers before starting intro
push si ; for pop di to start writing the output
mov di, prog_start ; the depacker & data are relocated from 0x100 to prog_start
mov ch, max_len/512
rep movsw
jmp si ; jump to relocated upkr_unpack
; upkr_unpack unpacks the code to 0x100 and runs it when done.
upkr_unpack:
xchg ax, bp ; position in bitstream = 0
cwd ; upkr_state = 0;
xchg cx, ax ; cx > 0x0200
mov al, 128 ; for(int i = 0; i < sizeof(upkr_probs); ++i) upkr_probs[i] = 128;
rep stosb
pop di ; u8* write_ptr = (u8*)destination;
.mainloop:
mov bx, probs
call upkr_decode_bit
jnc .else ; if(upkr_decode_bit(0)) {
inc bh
call upkr_decode_number ; offset = upkr_decode_number(258) - 1;
loop .notdone ; if(offset == 0)
popa
clc
ret
.notdone:
mov si, di
.sub:
dec si
loop .sub
mov bl, 128 ; int length = upkr_decode_number(384);
call upkr_decode_number
rep movsb ; *write_ptr = write_ptr[-offset];
jmp .mainloop
.else:
inc bx
.byteloop:
call upkr_decode_bit ; int bit = upkr_decode_bit(byte);
adc bl, bl ; byte = (byte << 1) + bit;
jnc .byteloop
xchg ax, bx
stosb
jmp .mainloop ; prev_was_match = 0;
; upkr_decode_bit decodes one bit from the rANS entropy encoded bit stream.
; parameters:
; bx = memory address of the context probability
; dx = decoder state
; bp = bit position in input stream
; returns:
; dx = new decoder state
; bp = new bit position in input stream
; carry = bit
; trashes ax
upkr_load_bit:
bt [compressed_data-relocation+prog_start], bp
inc bp
adc dx, dx
upkr_decode_bit:
inc dx
dec dx ; or whatever other test for the top bit there is
jns upkr_load_bit
movzx ax, byte [bx] ; int prob = upkr_probs[context_index]
push ax ; save prob
cmp dl, al ; int bit = (upkr_state & 255) < prob ? 1 : 0; (carry = bit)
pushf ; save bit flags
jc .bit ; (skip if bit)
neg al ; tmp = 256 - tmp;
.bit:
mov [bx], al ; tmp += (256 - tmp + 8) >> 4;
neg byte [bx]
shr byte [bx],4
adc [bx], al ; upkr_probs[context_index] = tmp;
mul dh ; upkr_state = tmp * (upkr_state >> 8) + (upkr_state & 255);
mov dh, 0
add dx, ax
popf
pop ax
jc .bit2 ; (skip if bit)
neg byte [bx] ; tmp = 256 - tmp;
sub dx, ax ; upkr_state -= prob; note that this will also leave carry always unset, which is what we want
.bit2:
ret ; flags = bit
; upkr_decode_number loads a variable length encoded number (up to 16 bits) from
; the compressed stream. Only numbers 1..65535 can be encoded. If the encoded
; number has 4 bits and is 1ABC, it is encoded using a kind of an "interleaved
; elias code": 0A0B0C1. The 1 in the end implies that no more bits are coming.
; parameters:
; cx = must be 0
; bx = memory address of the context probability
; dx = decoder state
; bp = bit position in input stream
; carry = must be 1
; returns:
; cx = length
; dx = new decoder state
; bp = new bit position in input stream
; carry = 1
; trashes bl, ax
upkr_decode_number_loop:
inc bx
call upkr_decode_bit
upkr_decode_number:
rcr cx, 1
inc bx
call upkr_decode_bit
jnc upkr_decode_number_loop ; 0 = there's more bits coming, 1 = no more bits
.loop2:
rcr cx, 1
jnc .loop2
ret
compressed_data:
incbin "data.bin"

3
fuzz/.gitignore vendored
View File

@@ -1,3 +0,0 @@
target
corpus
artifacts

247
fuzz/Cargo.lock generated
View File

@@ -1,247 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "arbitrary"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44124848854b941eafdb34f05b3bcf59472f643c7e151eba7c2b69daa469ed5"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
dependencies = [
"jobserver",
]
[[package]]
name = "cdivsufsort"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edefce019197609da416762da75bb000bbd2224b2d89a7e722c2296cbff79b8c"
dependencies = [
"cc",
"sacabase",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "jobserver"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
dependencies = [
"libc",
]
[[package]]
name = "lexopt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]]
name = "libc"
version = "0.2.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
[[package]]
name = "libfuzzer-sys"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae185684fe19814afd066da15a7cc41e126886c21282934225d9fc847582da58"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "pbr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2"
dependencies = [
"crossbeam-channel",
"libc",
"time",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sacabase"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9883fc3d6ce3d78bb54d908602f8bc1f7b5f983afe601dabe083009d86267a84"
dependencies = [
"num-traits",
]
[[package]]
name = "syn"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "unicode-ident"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "upkr"
version = "0.2.0-pre3"
dependencies = [
"anyhow",
"cdivsufsort",
"lexopt",
"pbr",
"thiserror",
]
[[package]]
name = "upkr-fuzz"
version = "0.0.0"
dependencies = [
"libfuzzer-sys",
"upkr",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,31 +0,0 @@
[package]
name = "upkr-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
[dependencies.upkr]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "all_configs"
path = "fuzz_targets/all_configs.rs"
test = false
doc = false
[[bin]]
name = "unpack"
path = "fuzz_targets/unpack.rs"
test = false
doc = false

View File

@@ -1,29 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let mut config = upkr::Config::default();
let mut level = 1;
let mut data = data;
if data.len() > 2 {
let flags1 = data[0];
let flags2 = data[1];
data = &data[2..];
config.use_bitstream = (flags1 & 1) != 0;
config.parity_contexts = if (flags1 & 2) == 0 { 1 } else { 2 };
config.invert_bit_encoding = (flags1 & 4) != 0;
config.is_match_bit = (flags1 & 8) != 0;
config.new_offset_bit = (flags1 & 16) != 0;
config.continue_value_bit = (flags1 & 32) != 0;
config.bitstream_is_big_endian = (flags1 & 64) != 0;
config.simplified_prob_update = (flags1 & 128) != 0;
config.no_repeated_offsets = (flags2 & 32) != 0;
config.eof_in_length = (flags2 & 1) != 0;
config.max_offset = if (flags2 & 2) == 0 { usize::MAX } else { 32 };
config.max_length = if (flags2 & 4) == 0 { usize::MAX } else { 5 };
level = (flags2 >> 3) & 3;
}
let packed = upkr::pack(data, level, &config, None);
let unpacked = upkr::unpack(&packed, &config, 1024 * 1024).unwrap();
assert!(unpacked == data);
});

View File

@@ -1,6 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = upkr::unpack(data, &upkr::Config::default(), 64 * 1024);
});

4
release/.gitignore vendored
View File

@@ -1,4 +0,0 @@
*.zip
*.tgz
upkr-linux/
upkr-windows/

View File

@@ -1,35 +0,0 @@
VERSION := $(shell cargo run --release -- --version)
all: clean upkr-linux-$(VERSION).tgz upkr-windows-$(VERSION).zip
clean:
rm -rf upkr-linux
rm -f upkr-linux*.tgz
rm -rf upkr-windows
rm -f upkr-windows*.zip
upkr-linux-$(VERSION).tgz: upkr-linux/upkr PHONY
cp ../README.md upkr-linux
cd .. && git archive HEAD c_unpacker | tar -xC release/upkr-linux
cd .. && git archive HEAD z80_unpacker | tar -xC release/upkr-linux
cd .. && git archive HEAD asm_unpackers | tar -xC release/upkr-linux
tar czf $@ upkr-linux
upkr-windows-$(VERSION).zip: upkr-windows/upkr.exe PHONY
cp ../README.md upkr-windows/
cd .. && git archive HEAD c_unpacker | tar -xC release/upkr-windows
cd .. && git archive HEAD z80_unpacker | tar -xC release/upkr-windows
cd .. && git archive HEAD asm_unpackers | tar -xC release/upkr-windows
zip -r -9 $@ upkr-windows
upkr-linux/upkr:
cargo build --target x86_64-unknown-linux-musl --release
mkdir -p upkr-linux
cp ../target/x86_64-unknown-linux-musl/release/upkr upkr-linux/
upkr-windows/upkr.exe:
cargo build --target x86_64-pc-windows-gnu --release
mkdir -p upkr-windows
cp ../target/x86_64-pc-windows-gnu/release/upkr.exe upkr-windows/
PHONY:

View File

@@ -1,17 +1,12 @@
use crate::{
rans::{ONE_PROB, PROB_BITS},
Config,
};
use crate::rans::{ONE_PROB, PROB_BITS};
const INIT_PROB: u16 = 1 << (PROB_BITS - 1);
const UPDATE_RATE: u32 = 4;
const UPDATE_ADD: u32 = 8;
const UPDATE_RATE: i32 = 4;
const UPDATE_ADD: i32 = 8;
#[derive(Clone)]
pub struct ContextState {
contexts: Vec<u8>,
invert_bit_encoding: bool,
simplified_prob_update: bool,
}
pub struct Context<'a> {
@@ -20,11 +15,9 @@ pub struct Context<'a> {
}
impl ContextState {
pub fn new(size: usize, config: &Config) -> ContextState {
pub fn new(size: usize) -> ContextState {
ContextState {
contexts: vec![INIT_PROB as u8; size],
invert_bit_encoding: config.invert_bit_encoding,
simplified_prob_update: config.simplified_prob_update,
}
}
@@ -40,21 +33,13 @@ impl<'a> Context<'a> {
pub fn update(&mut self, bit: bool) {
let old = self.state.contexts[self.index];
self.state.contexts[self.index] = if self.state.simplified_prob_update {
let offset = if bit ^ self.state.invert_bit_encoding {
ONE_PROB as i32 >> UPDATE_RATE
} else {
0
};
(offset + old as i32 - ((old as i32 + UPDATE_ADD as i32) >> UPDATE_RATE)) as u8
let offset = if !bit {
ONE_PROB as i32 >> UPDATE_RATE
} else {
if bit ^ self.state.invert_bit_encoding {
old + ((ONE_PROB - old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8
} else {
old - ((old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8
}
0
};
self.state.contexts[self.index] =
(offset + old as i32 - ((old as i32 + UPDATE_ADD) >> UPDATE_RATE)) as u8;
}
}

View File

@@ -1,16 +1,16 @@
use crate::lz;
use crate::match_finder::MatchFinder;
use crate::rans::RansCoder;
use crate::ProgressCallback;
use crate::{lz, Config};
pub fn pack(
data: &[u8],
config: &Config,
use_bitstream: bool,
mut progress_callback: Option<ProgressCallback>,
) -> Vec<u8> {
let mut match_finder = MatchFinder::new(data);
let mut rans_coder = RansCoder::new(config);
let mut state = lz::CoderState::new(config);
let mut rans_coder = RansCoder::new(use_bitstream);
let mut state = lz::CoderState::new();
let mut pos = 0;
while pos < data.len() {
@@ -19,16 +19,15 @@ pub fn pack(
}
let mut encoded_match = false;
if let Some(m) = match_finder.matches(pos).next() {
let max_offset = config.max_offset.min(1 << (m.length * 3 - 1).min(31));
let max_offset = 1 << (m.length * 3 - 1).min(31);
let offset = pos - m.pos;
if offset < max_offset && m.length >= config.min_length() {
let length = m.length.min(config.max_length);
if offset < max_offset {
lz::Op::Match {
offset: offset as u32,
len: length as u32,
len: m.length as u32,
}
.encode(&mut rans_coder, &mut state, config);
pos += length;
.encode(&mut rans_coder, &mut state);
pos += m.length;
encoded_match = true;
}
}
@@ -40,14 +39,13 @@ pub fn pack(
.iter()
.zip(data[(pos - offset)..].iter())
.take_while(|(a, b)| a == b)
.count()
.min(config.max_length);
if length >= config.min_length() {
.count();
if length > 0 {
lz::Op::Match {
offset: offset as u32,
len: length as u32,
}
.encode(&mut rans_coder, &mut state, config);
.encode(&mut rans_coder, &mut state);
pos += length;
encoded_match = true;
}
@@ -55,11 +53,11 @@ pub fn pack(
}
if !encoded_match {
lz::Op::Literal(data[pos]).encode(&mut rans_coder, &mut state, config);
lz::Op::Literal(data[pos]).encode(&mut rans_coder, &mut state);
pos += 1;
}
}
lz::encode_eof(&mut rans_coder, &mut state, config);
lz::encode_eof(&mut rans_coder, &mut state);
rans_coder.finish()
}

View File

@@ -1,163 +0,0 @@
pub struct Heatmap {
data: Vec<u8>,
cost: Vec<f32>,
literal_index: Vec<usize>,
}
impl Heatmap {
pub fn new() -> Heatmap {
Heatmap {
data: Vec::new(),
cost: Vec::new(),
literal_index: Vec::new(),
}
}
pub fn add_literal(&mut self, byte: u8, cost: f32) {
self.data.push(byte);
self.cost.push(cost);
self.literal_index.push(self.literal_index.len());
}
pub fn add_match(&mut self, offset: usize, length: usize, mut cost: f32) {
cost /= length as f32;
for _ in 0..length {
self.data.push(self.data[self.data.len() - offset]);
self.literal_index
.push(self.literal_index[self.literal_index.len() - offset]);
self.cost.push(cost);
}
}
pub fn finish(&mut self) {
let mut ref_count = vec![0usize; self.literal_index.len()];
for &index in &self.literal_index {
ref_count[index] += 1;
}
let mut shifted = vec![];
for (&index, &cost) in self.literal_index.iter().zip(self.cost.iter()) {
let delta = (self.cost[index] - cost) / ref_count[index] as f32;
shifted.push(delta);
shifted[index] -= delta;
}
for (cost, delta) in self.cost.iter_mut().zip(shifted.into_iter()) {
*cost += delta;
}
}
pub fn reverse(&mut self) {
self.data.reverse();
self.cost.reverse();
self.literal_index.reverse();
for index in self.literal_index.iter_mut() {
*index = self.data.len() - *index;
}
}
pub fn len(&self) -> usize {
self.cost.len()
}
pub fn is_literal(&self, index: usize) -> bool {
self.literal_index[index] == index
}
pub fn cost(&self, index: usize) -> f32 {
self.cost[index]
}
pub fn byte(&self, index: usize) -> u8 {
self.data[index]
}
pub fn print_as_hex(&self) -> std::io::Result<()> {
use crossterm::{
style::{Attribute, Color, Print, SetAttribute, SetBackgroundColor},
QueueableCommand,
};
use std::io::{stdout, Write};
fn set_color(
mut out: impl QueueableCommand,
heatmap: &Heatmap,
index: usize,
num_colors: u16,
) -> std::io::Result<()> {
let cost = heatmap.cost(index);
if num_colors < 256 {
let colors = [
Color::Red,
Color::Yellow,
Color::Green,
Color::Cyan,
Color::Blue,
Color::DarkBlue,
Color::Black,
];
let color_index = (3. - cost.log2())
.round()
.max(0.)
.min((colors.len() - 1) as f32) as usize;
out.queue(SetBackgroundColor(colors[color_index]))?;
} else {
let colors = [
196, 166, 136, 106, 76, 46, 41, 36, 31, 26, 21, 20, 19, 18, 17, 16,
];
let color_index = ((3. - cost.log2()) * 2.5)
.round()
.max(0.)
.min((colors.len() - 1) as f32) as usize;
out.queue(SetBackgroundColor(Color::AnsiValue(colors[color_index])))?;
}
out.queue(SetAttribute(if heatmap.is_literal(index) {
Attribute::Underlined
} else {
Attribute::NoUnderline
}))?;
Ok(())
}
let num_colors = crossterm::style::available_color_count();
let term_width = crossterm::terminal::size()?.0.min(120) as usize;
let bytes_per_row = (term_width - 8) / 4;
for row_start in (0..self.data.len()).step_by(bytes_per_row) {
let row_range = row_start..self.data.len().min(row_start + bytes_per_row);
let mut stdout = stdout();
stdout.queue(Print(&format!("{:04x} ", row_start)))?;
for i in row_range.clone() {
set_color(&mut stdout, self, i, num_colors)?;
stdout.queue(Print(&format!("{:02x} ", self.data[i])))?;
}
let num_spaces = 1 + (bytes_per_row - (row_range.end - row_range.start)) * 3;
let gap: String = std::iter::repeat(' ').take(num_spaces).collect();
stdout
.queue(SetAttribute(Attribute::Reset))?
.queue(Print(&gap))?;
for i in row_range.clone() {
set_color(&mut stdout, self, i, num_colors)?;
let byte = self.data[i];
if byte >= 32 && byte < 127 {
stdout.queue(Print(format!("{}", byte as char)))?;
} else {
stdout.queue(Print("."))?;
}
}
stdout
.queue(SetAttribute(Attribute::Reset))?
.queue(Print("\n"))?;
stdout.flush()?;
}
Ok(())
}
}

View File

@@ -1,79 +1,24 @@
mod context_state;
mod greedy_packer;
mod heatmap;
mod lz;
mod match_finder;
mod parsing_packer;
mod rans;
pub use heatmap::Heatmap;
pub use lz::{calculate_margin, create_heatmap, unpack, UnpackError};
pub use lz::unpack;
pub type ProgressCallback<'a> = &'a mut dyn FnMut(usize);
#[derive(Debug)]
pub struct Config {
pub use_bitstream: bool,
pub parity_contexts: usize,
pub invert_bit_encoding: bool,
pub is_match_bit: bool,
pub new_offset_bit: bool,
pub continue_value_bit: bool,
pub bitstream_is_big_endian: bool,
pub simplified_prob_update: bool,
pub no_repeated_offsets: bool,
pub eof_in_length: bool,
pub max_offset: usize,
pub max_length: usize,
}
impl Default for Config {
fn default() -> Config {
Config {
use_bitstream: false,
parity_contexts: 1,
invert_bit_encoding: false,
is_match_bit: true,
new_offset_bit: true,
continue_value_bit: true,
bitstream_is_big_endian: false,
simplified_prob_update: false,
no_repeated_offsets: false,
eof_in_length: false,
max_offset: usize::MAX,
max_length: usize::MAX,
}
}
}
impl Config {
pub fn min_length(&self) -> usize {
if self.eof_in_length {
2
} else {
1
}
}
}
pub fn pack(
data: &[u8],
level: u8,
config: &Config,
use_bitstream: bool,
progress_callback: Option<ProgressCallback>,
) -> Vec<u8> {
if level == 0 {
greedy_packer::pack(data, config, progress_callback)
greedy_packer::pack(data, use_bitstream, progress_callback)
} else {
parsing_packer::pack(data, level, config, progress_callback)
parsing_packer::pack(data, level, use_bitstream, progress_callback)
}
}

221
src/lz.rs
View File

@@ -1,8 +1,5 @@
use crate::context_state::ContextState;
use crate::heatmap::Heatmap;
use crate::rans::{EntropyCoder, RansDecoder};
use crate::Config;
use thiserror::Error;
#[derive(Copy, Clone, Debug)]
pub enum Op {
@@ -11,73 +8,42 @@ pub enum Op {
}
impl Op {
pub fn encode(&self, coder: &mut dyn EntropyCoder, state: &mut CoderState, config: &Config) {
let literal_base = state.pos % state.parity_contexts * 256;
pub fn encode(&self, coder: &mut dyn EntropyCoder, state: &mut CoderState) {
match self {
&Op::Literal(lit) => {
encode_bit(coder, state, literal_base, !config.is_match_bit);
encode_bit(coder, state, 0, false);
let mut context_index = 1;
for i in (0..8).rev() {
let bit = (lit >> i) & 1 != 0;
encode_bit(coder, state, literal_base + context_index, bit);
encode_bit(coder, state, context_index, bit);
context_index = (context_index << 1) | bit as usize;
}
state.prev_was_match = false;
state.pos += 1;
}
&Op::Match { offset, len } => {
encode_bit(coder, state, literal_base, config.is_match_bit);
let mut new_offset = true;
if !state.prev_was_match && !config.no_repeated_offsets {
new_offset = offset != state.last_offset;
encode_bit(
coder,
state,
256 * state.parity_contexts,
new_offset == config.new_offset_bit,
);
encode_bit(coder, state, 0, true);
if !state.prev_was_match {
encode_bit(coder, state, 256, offset != state.last_offset);
} else {
assert!(offset != state.last_offset);
}
assert!(offset as usize <= config.max_offset);
if new_offset {
encode_length(
coder,
state,
256 * state.parity_contexts + 1,
offset + if config.eof_in_length { 0 } else { 1 },
config,
);
if offset != state.last_offset {
encode_length(coder, state, 257, offset + 1);
state.last_offset = offset;
}
assert!(len as usize >= config.min_length() && len as usize <= config.max_length);
encode_length(coder, state, 256 * state.parity_contexts + 65, len, config);
encode_length(coder, state, 257 + 64, len);
state.prev_was_match = true;
state.pos += len as usize;
}
}
}
}
pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState, config: &Config) {
encode_bit(
coder,
state,
state.pos % state.parity_contexts * 256,
config.is_match_bit,
);
if !state.prev_was_match && !config.no_repeated_offsets {
encode_bit(
coder,
state,
256 * state.parity_contexts,
config.new_offset_bit ^ config.eof_in_length,
);
}
if !config.eof_in_length || state.prev_was_match || config.no_repeated_offsets {
encode_length(coder, state, 256 * state.parity_contexts + 1, 1, config);
}
if config.eof_in_length {
encode_length(coder, state, 256 * state.parity_contexts + 65, 1, config);
pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState) {
encode_bit(coder, state, 0, true);
if !state.prev_was_match {
encode_bit(coder, state, 256, true);
}
encode_length(coder, state, 257, 1);
}
fn encode_bit(
@@ -94,18 +60,17 @@ fn encode_length(
state: &mut CoderState,
context_start: usize,
mut value: u32,
config: &Config,
) {
assert!(value >= 1);
let mut context_index = context_start;
while value >= 2 {
encode_bit(coder, state, context_index, config.continue_value_bit);
encode_bit(coder, state, context_index, true);
encode_bit(coder, state, context_index + 1, value & 1 != 0);
context_index += 2;
value >>= 1;
}
encode_bit(coder, state, context_index, !config.continue_value_bit);
encode_bit(coder, state, context_index, false);
}
#[derive(Clone)]
@@ -113,18 +78,14 @@ pub struct CoderState {
contexts: ContextState,
last_offset: u32,
prev_was_match: bool,
pos: usize,
parity_contexts: usize,
}
impl CoderState {
pub fn new(config: &Config) -> CoderState {
pub fn new() -> CoderState {
CoderState {
contexts: ContextState::new((1 + 255) * config.parity_contexts + 1 + 64 + 64, config),
contexts: ContextState::new(1 + 255 + 1 + 64 + 64),
last_offset: 0,
prev_was_match: false,
pos: 0,
parity_contexts: config.parity_contexts,
}
}
@@ -133,163 +94,55 @@ impl CoderState {
}
}
#[derive(Error, Debug)]
pub enum UnpackError {
#[error("match offset out of range: {offset} > {position}")]
OffsetOutOfRange { offset: usize, position: usize },
#[error("Unpacked data over size limit: {size} > {limit}")]
OverSize { size: usize, limit: usize },
#[error("Unexpected end of input data")]
UnexpectedEOF {
#[from]
source: crate::rans::UnexpectedEOF,
},
#[error("Overflow while reading value")]
ValueOverflow,
}
pub fn unpack(
packed_data: &[u8],
config: &Config,
max_size: usize,
) -> Result<Vec<u8>, UnpackError> {
pub fn unpack(packed_data: &[u8], use_bitstream: bool) -> Vec<u8> {
let mut decoder = RansDecoder::new(packed_data, use_bitstream);
let mut contexts = ContextState::new(1 + 255 + 1 + 64 + 64);
let mut result = vec![];
let _ = unpack_internal(Some(&mut result), None, packed_data, config, max_size)?;
Ok(result)
}
pub fn calculate_margin(packed_data: &[u8], config: &Config) -> Result<isize, UnpackError> {
unpack_internal(None, None, packed_data, config, usize::MAX)
}
pub fn create_heatmap(
packed_data: &[u8],
config: &Config,
max_size: usize,
) -> Result<Heatmap, UnpackError> {
let mut heatmap = Heatmap::new();
let _ = unpack_internal(None, Some(&mut heatmap), packed_data, config, max_size)?;
Ok(heatmap)
}
fn unpack_internal(
mut result: Option<&mut Vec<u8>>,
mut heatmap: Option<&mut Heatmap>,
packed_data: &[u8],
config: &Config,
max_size: usize,
) -> Result<isize, UnpackError> {
let mut decoder = RansDecoder::new(packed_data, &config)?;
let mut contexts = ContextState::new((1 + 255) * config.parity_contexts + 1 + 64 + 64, &config);
let mut offset = usize::MAX;
let mut position = 0usize;
let mut offset = 0;
let mut prev_was_match = false;
let mut margin = 0isize;
fn decode_length(
decoder: &mut RansDecoder,
contexts: &mut ContextState,
mut context_index: usize,
config: &Config,
) -> Result<usize, UnpackError> {
) -> usize {
let mut length = 0;
let mut bit_pos = 0;
while decoder.decode_with_context(&mut contexts.context_mut(context_index))?
== config.continue_value_bit
{
length |= (decoder.decode_with_context(&mut contexts.context_mut(context_index + 1))?
while decoder.decode_with_context(&mut contexts.context_mut(context_index)) {
length |= (decoder.decode_with_context(&mut contexts.context_mut(context_index + 1))
as usize)
<< bit_pos;
bit_pos += 1;
if bit_pos >= 32 {
return Err(UnpackError::ValueOverflow);
}
context_index += 2;
}
Ok(length | (1 << bit_pos))
length | (1 << bit_pos)
}
loop {
let prev_decoder = decoder.clone();
margin = margin.max(position as isize - decoder.pos() as isize);
let literal_base = position % config.parity_contexts * 256;
if decoder.decode_with_context(&mut contexts.context_mut(literal_base))?
== config.is_match_bit
{
if config.no_repeated_offsets
|| prev_was_match
|| decoder
.decode_with_context(&mut contexts.context_mut(256 * config.parity_contexts))?
== config.new_offset_bit
{
offset = decode_length(
&mut decoder,
&mut contexts,
256 * config.parity_contexts + 1,
&config,
)? - if config.eof_in_length { 0 } else { 1 };
if decoder.decode_with_context(&mut contexts.context_mut(0)) {
if prev_was_match || decoder.decode_with_context(&mut contexts.context_mut(256)) {
offset = decode_length(&mut decoder, &mut contexts, 257) - 1;
if offset == 0 {
break;
}
}
let length = decode_length(
&mut decoder,
&mut contexts,
256 * config.parity_contexts + 65,
&config,
)?;
if config.eof_in_length && length == 1 {
break;
let length = decode_length(&mut decoder, &mut contexts, 257 + 64);
for _ in 0..length {
result.push(result[result.len() - offset]);
}
if offset > position {
return Err(UnpackError::OffsetOutOfRange { offset, position });
}
if let Some(ref mut heatmap) = heatmap {
heatmap.add_match(offset, length, decoder.cost(&prev_decoder));
}
if let Some(ref mut result) = result {
for _ in 0..length {
if result.len() < max_size {
result.push(result[result.len() - offset]);
} else {
break;
}
}
}
position += length;
prev_was_match = true;
} else {
let mut context_index = 1;
let mut byte = 0;
for i in (0..8).rev() {
let bit = decoder
.decode_with_context(&mut contexts.context_mut(literal_base + context_index))?;
let bit = decoder.decode_with_context(&mut contexts.context_mut(context_index));
context_index = (context_index << 1) | bit as usize;
byte |= (bit as u8) << i;
}
if let Some(ref mut heatmap) = heatmap {
heatmap.add_literal(byte, decoder.cost(&prev_decoder));
}
if let Some(ref mut result) = result {
if result.len() < max_size {
result.push(byte);
}
}
position += 1;
result.push(byte);
prev_was_match = false;
}
}
if let Some(heatmap) = heatmap {
heatmap.finish();
}
if position > max_size {
return Err(UnpackError::OverSize {
size: position,
limit: max_size,
});
}
Ok(margin + decoder.pos() as isize - position as isize)
result
}

View File

@@ -1,228 +1,82 @@
use anyhow::Result;
use std::ffi::OsStr;
use anyhow::{bail, Result};
use std::io::prelude::*;
use std::process;
use std::{fs::File, path::PathBuf};
fn main() -> Result<()> {
let mut config = upkr::Config::default();
let mut reverse = false;
let mut unpack = false;
let mut calculate_margin = false;
let mut create_heatmap = false;
let mut do_hexdump = false;
let mut level = 2;
let mut infile: Option<PathBuf> = None;
let mut outfile: Option<PathBuf> = None;
let mut max_unpacked_size = 512 * 1024 * 1024;
let mut args = pico_args::Arguments::from_env();
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
use lexopt::prelude::*;
match arg {
Short('b') | Long("bitstream") => config.use_bitstream = true,
Short('p') | Long("parity") => config.parity_contexts = parser.value()?.parse()?,
Short('r') | Long("reverse") => reverse = true,
Long("invert-is-match-bit") => config.is_match_bit = false,
Long("invert-new-offset-bit") => config.new_offset_bit = false,
Long("invert-continue-value-bit") => config.continue_value_bit = false,
Long("invert-bit-encoding") => config.invert_bit_encoding = true,
Long("simplified-prob-update") => config.simplified_prob_update = true,
Long("big-endian-bitstream") => {
config.use_bitstream = true;
config.bitstream_is_big_endian = true;
}
Long("no-repeated-offsets") => config.no_repeated_offsets = true,
Long("eof-in-length") => config.eof_in_length = true,
match args.subcommand()?.as_ref().map(|s| s.as_str()) {
None => print_help(),
Some("pack") => {
let level = args.opt_value_from_str(["-l", "--level"])?.unwrap_or(2u8);
let use_bitstream = args.contains(["-b", "--bitstream"]);
let reverse = args.contains(["-r", "--reverse"]);
Long("max-offset") => config.max_offset = parser.value()?.parse()?,
Long("max-length") => config.max_length = parser.value()?.parse()?,
let infile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
Long("z80") => {
config.use_bitstream = true;
config.bitstream_is_big_endian = true;
config.invert_bit_encoding = true;
config.simplified_prob_update = true;
level = 9;
}
Long("x86") => {
config.use_bitstream = true;
config.continue_value_bit = false;
config.is_match_bit = false;
config.new_offset_bit = false;
}
Long("x86b") => {
config.use_bitstream = true;
config.continue_value_bit = false;
config.no_repeated_offsets = true;
level = 9;
let mut data = vec![];
File::open(infile)?.read_to_end(&mut data)?;
if reverse {
data.reverse();
}
Short('u') | Long("unpack") => unpack = true,
Long("margin") => calculate_margin = true,
Long("heatmap") => create_heatmap = true,
Long("hexdump") => do_hexdump = true,
Short('l') | Long("level") => level = parser.value()?.parse()?,
Short(n) if n.is_ascii_digit() => level = n as u8 - b'0',
Short('h') | Long("help") => print_help(0),
Long("version") => {
println!("{}", env!("CARGO_PKG_VERSION"));
process::exit(0);
let mut pb = pbr::ProgressBar::new(data.len() as u64);
pb.set_units(pbr::Units::Bytes);
let mut packed_data = upkr::pack(
&data,
level,
use_bitstream,
Some(&mut |pos| {
pb.set(pos as u64);
}),
);
pb.finish();
if reverse {
packed_data.reverse();
}
Long("max-unpacked-size") => max_unpacked_size = parser.value()?.parse()?,
Value(val) if infile.is_none() => infile = Some(val.try_into()?),
Value(val) if outfile.is_none() => outfile = Some(val.try_into()?),
_ => return Err(arg.unexpected().into()),
println!(
"Compressed {} bytes to {} bytes ({}%)",
data.len(),
packed_data.len(),
packed_data.len() as f32 * 100. / data.len() as f32
);
File::create(outfile)?.write_all(&packed_data)?;
}
}
Some("unpack") => {
let use_bitstream = args.contains(["-b", "--bitstream"]);
let reverse = args.contains(["-r", "--reverse"]);
let infile = infile.unwrap_or_else(|| print_help(1));
enum OutFileType {
Packed,
Unpacked,
Heatmap,
}
let outfile = |tpe: OutFileType| {
outfile.clone().unwrap_or_else(|| {
let mut name = infile.clone();
match tpe {
OutFileType::Packed => {
let mut filename = name
.file_name()
.unwrap_or_else(|| OsStr::new(""))
.to_os_string();
filename.push(".upk");
name.set_file_name(filename);
}
OutFileType::Unpacked => {
if name.extension().filter(|&e| e == "upk").is_some() {
name.set_extension("");
} else {
name.set_extension("bin");
}
}
OutFileType::Heatmap => {
name.set_extension("heatmap");
}
let infile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let mut data = vec![];
File::open(infile)?.read_to_end(&mut data)?;
if reverse {
data.reverse();
}
name
})
};
if config.parity_contexts != 1 && config.parity_contexts != 2 && config.parity_contexts != 4 {
eprintln!("--parity has to be 1, 2, or 4");
process::exit(1);
}
if !unpack && !calculate_margin && !create_heatmap {
let mut data = vec![];
File::open(&infile)?.read_to_end(&mut data)?;
if reverse {
data.reverse();
}
let mut pb = pbr::ProgressBar::new(data.len() as u64);
pb.set_units(pbr::Units::Bytes);
let mut packed_data = upkr::pack(
&data,
level,
&config,
Some(&mut |pos| {
pb.set(pos as u64);
}),
);
pb.finish();
if reverse {
packed_data.reverse();
}
println!(
"Compressed {} bytes to {} bytes ({}%)",
data.len(),
packed_data.len(),
packed_data.len() as f32 * 100. / data.len() as f32
);
File::create(outfile(OutFileType::Packed))?.write_all(&packed_data)?;
} else {
let mut data = vec![];
File::open(&infile)?.read_to_end(&mut data)?;
if reverse {
data.reverse();
}
if unpack {
let mut unpacked_data = upkr::unpack(&data, &config, max_unpacked_size)?;
let mut unpacked_data = upkr::unpack(&data, use_bitstream);
if reverse {
unpacked_data.reverse();
}
File::create(outfile(OutFileType::Unpacked))?.write_all(&unpacked_data)?;
File::create(outfile)?.write_all(&unpacked_data)?;
}
if create_heatmap {
let mut heatmap = upkr::create_heatmap(&data, &config, max_unpacked_size)?;
if reverse {
heatmap.reverse();
}
if do_hexdump {
heatmap.print_as_hex()?;
} else {
let mut heatmap_bin = Vec::with_capacity(heatmap.len());
for i in 0..heatmap.len() {
let cost = (heatmap.cost(i).log2() * 8. + 64.)
.round()
.max(0.)
.min(127.) as u8;
heatmap_bin.push((cost << 1) | heatmap.is_literal(i) as u8);
}
File::create(outfile(OutFileType::Heatmap))?.write_all(&heatmap_bin)?;
}
}
if calculate_margin {
println!("{}", upkr::calculate_margin(&data, &config)?);
Some(other) => {
bail!("Unknown subcommand '{}'", other);
}
}
Ok(())
}
fn print_help(exit_code: i32) -> ! {
fn print_help() {
eprintln!("Usage:");
eprintln!(" upkr [-l level(0-9)] [config options] <infile> [<outfile>]");
eprintln!(" upkr -u [config options] <infile> [<outfile>]");
eprintln!(" upkr --heatmap [config options] <infile> [<outfile>]");
eprintln!(" upkr --margin [config options] <infile>");
eprintln!(" upkr pack [-b] [-l level(0-9)] <infile> <outfile>");
eprintln!(" upkr unpack [-b] <infile> <outfile>");
eprintln!();
eprintln!(" -l, --level N compression level 0-9");
eprintln!(" -0, ..., -9 short form for setting compression level");
eprintln!(" -u, --unpack unpack infile");
eprintln!(" --heatmap calculate heatmap from compressed file");
eprintln!(" --margin calculate margin for overlapped unpacking of a packed file");
eprintln!();
eprintln!("Version: {}", env!("CARGO_PKG_VERSION"));
eprintln!();
eprintln!("Config presets for specific unpackers:");
eprintln!(" --z80 --big-endian-bitstream --invert-bit-encoding --simplified-prob-update -9");
eprintln!(
" --x86 --bitstream --invert-is-match-bit --invert-continue-value-bit --invert-new-offset-bit"
);
eprintln!(
" --x86b --bitstream --invert-continue-value-bit --no-repeated-offsets -9"
);
eprintln!();
eprintln!("Config options (need to match when packing/unpacking):");
eprintln!(" -b, --bitstream bitstream mode");
eprintln!(" -p, --parity N use N (2/4) parity contexts");
eprintln!(" -r, --reverse reverse input & output");
eprintln!();
eprintln!("Config options to tailor output to specific optimized unpackers:");
eprintln!(" --invert-is-match-bit");
eprintln!(" --invert-new-offset-bit");
eprintln!(" --invert-continue-value-bit");
eprintln!(" --invert-bit-encoding");
eprintln!(" --simplified-prob-update");
eprintln!(" --big-endian-bitstream (implies --bitstream)");
eprintln!(" --no-repeated-offsets");
eprintln!(" --eof-in-length");
eprintln!(" --max-offset N");
eprintln!(" --max-length N");
process::exit(exit_code);
eprintln!(" -l, --level N compression level 0-9");
std::process::exit(1);
}

View File

@@ -6,24 +6,19 @@ use crate::match_finder::MatchFinder;
use crate::rans::{CostCounter, RansCoder};
use crate::{lz, ProgressCallback};
pub fn pack(
data: &[u8],
level: u8,
config: &crate::Config,
progress_cb: Option<ProgressCallback>,
) -> Vec<u8> {
let mut parse = parse(data, Config::from_level(level), config, progress_cb);
pub fn pack(data: &[u8], level: u8, use_bitstream: bool, progress_cb: Option<ProgressCallback>) -> Vec<u8> {
let mut parse = parse(data, Config::from_level(level), progress_cb);
let mut ops = vec![];
while let Some(link) = parse {
ops.push(link.op);
parse = link.prev.clone();
}
let mut state = lz::CoderState::new(config);
let mut coder = RansCoder::new(config);
let mut state = lz::CoderState::new();
let mut coder = RansCoder::new(use_bitstream);
for op in ops.into_iter().rev() {
op.encode(&mut coder, &mut state, config);
op.encode(&mut coder, &mut state);
}
lz::encode_eof(&mut coder, &mut state, config);
lz::encode_eof(&mut coder, &mut state);
coder.finish()
}
@@ -43,7 +38,6 @@ type Arrivals = HashMap<usize, Vec<Arrival>>;
fn parse(
data: &[u8],
config: Config,
encoding_config: &crate::Config,
mut progress_cb: Option<ProgressCallback>,
) -> Option<Rc<Parse>> {
let mut match_finder = MatchFinder::new(data)
@@ -105,22 +99,17 @@ fn parse(
cost_counter: &mut CostCounter,
pos: usize,
offset: usize,
mut length: usize,
length: usize,
arrival: &Arrival,
max_arrivals: usize,
config: &crate::Config,
) {
if length < config.min_length() {
return;
}
length = length.min(config.max_length);
cost_counter.reset();
let mut state = arrival.state.clone();
let op = lz::Op::Match {
offset: offset as u32,
len: length as u32,
};
op.encode(cost_counter, &mut state, config);
op.encode(cost_counter, &mut state);
add_arrival(
arrivals,
pos + length,
@@ -140,13 +129,13 @@ fn parse(
0,
Arrival {
parse: None,
state: lz::CoderState::new(encoding_config),
state: lz::CoderState::new(),
cost: 0.0,
},
max_arrivals,
);
let cost_counter = &mut CostCounter::new(encoding_config);
let cost_counter = &mut CostCounter::new();
let mut best_per_offset = HashMap::new();
for pos in 0..data.len() {
let match_length = |offset: usize| {
@@ -187,21 +176,18 @@ fn parse(
for m in match_finder.matches(pos) {
closest_match = Some(closest_match.unwrap_or(0).max(m.pos));
let offset = pos - m.pos;
if offset <= encoding_config.max_offset {
found_last_offset |= offset as u32 == arrival.state.last_offset();
add_match(
&mut arrivals,
cost_counter,
pos,
offset,
m.length,
&arrival,
max_arrivals,
encoding_config,
);
if m.length >= config.greedy_size {
break 'arrival_loop;
}
found_last_offset |= offset as u32 == arrival.state.last_offset();
add_match(
&mut arrivals,
cost_counter,
pos,
offset,
m.length,
&arrival,
max_arrivals,
);
if m.length >= config.greedy_size {
break 'arrival_loop;
}
}
@@ -212,9 +198,6 @@ fn parse(
&& closest_match.iter().all(|p| *p < match_pos)
{
let offset = pos - match_pos;
if offset > encoding_config.max_offset {
break;
}
let length = match_length(offset);
assert!(length > 0);
add_match(
@@ -225,7 +208,6 @@ fn parse(
length,
&arrival,
max_arrivals,
encoding_config,
);
found_last_offset |= offset as u32 == arrival.state.last_offset();
if offset < near_matches.len() {
@@ -246,7 +228,6 @@ fn parse(
length,
&arrival,
max_arrivals,
encoding_config,
);
}
}
@@ -254,7 +235,7 @@ fn parse(
cost_counter.reset();
let mut state = arrival.state;
let op = lz::Op::Literal(data[pos]);
op.encode(cost_counter, &mut state, encoding_config);
op.encode(cost_counter, &mut state);
add_arrival(
&mut arrivals,
pos + 1,

View File

@@ -1,5 +1,4 @@
use crate::{context_state::Context, Config};
use thiserror::Error;
use crate::context_state::Context;
pub const PROB_BITS: u32 = 8;
pub const ONE_PROB: u32 = 1 << PROB_BITS;
@@ -16,25 +15,20 @@ pub trait EntropyCoder {
pub struct RansCoder {
bits: Vec<u16>,
use_bitstream: bool,
bitstream_is_big_endian: bool,
invert_bit_encoding: bool,
}
impl EntropyCoder for RansCoder {
fn encode_bit(&mut self, bit: bool, prob: u16) {
assert!(prob < 32768);
self.bits
.push(prob | (((bit ^ self.invert_bit_encoding) as u16) << 15));
self.bits.push(prob | ((bit as u16) << 15));
}
}
impl RansCoder {
pub fn new(config: &Config) -> RansCoder {
pub fn new(use_bitstream: bool) -> RansCoder {
RansCoder {
bits: Vec::new(),
use_bitstream: config.use_bitstream,
bitstream_is_big_endian: config.bitstream_is_big_endian,
invert_bit_encoding: config.invert_bit_encoding,
use_bitstream,
}
}
@@ -44,31 +38,18 @@ impl RansCoder {
let mut state = 1 << l_bits;
let mut byte = 0u8;
let mut bit = if self.bitstream_is_big_endian { 0 } else { 8 };
let mut bit = 0;
let mut flush_state: Box<dyn FnMut(&mut u32)> = if self.use_bitstream {
if self.bitstream_is_big_endian {
Box::new(|state: &mut u32| {
byte |= ((*state & 1) as u8) << bit;
bit += 1;
if bit == 8 {
buffer.push(byte);
byte = 0;
bit = 0;
}
*state >>= 1;
})
} else {
Box::new(|state: &mut u32| {
bit -= 1;
byte |= ((*state & 1) as u8) << bit;
if bit == 0 {
buffer.push(byte);
byte = 0;
bit = 8;
}
*state >>= 1;
})
}
Box::new(|state: &mut u32| {
byte |= ((*state & 1) as u8) << bit;
bit += 1;
if bit == 8 {
buffer.push(byte);
byte = 0;
bit = 0;
}
*state >>= 1;
})
} else {
Box::new(|state: &mut u32| {
buffer.push(*state as u8);
@@ -80,7 +61,7 @@ impl RansCoder {
let max_state_factor: u32 = 1 << (l_bits + num_flush_bits - PROB_BITS);
for step in self.bits.into_iter().rev() {
let prob = step as u32 & 32767;
let (start, prob) = if step & 32768 != 0 {
let (start, prob) = if step & 32768 == 0 {
(0, prob)
} else {
(prob, ONE_PROB - prob)
@@ -110,11 +91,10 @@ impl RansCoder {
pub struct CostCounter {
cost: f64,
log2_table: Vec<f64>,
invert_bit_encoding: bool,
}
impl CostCounter {
pub fn new(config: &Config) -> CostCounter {
pub fn new() -> CostCounter {
let log2_table = (0..ONE_PROB)
.map(|prob| {
let inv_prob = ONE_PROB as f64 / prob as f64;
@@ -124,7 +104,6 @@ impl CostCounter {
CostCounter {
cost: 0.0,
log2_table,
invert_bit_encoding: config.invert_bit_encoding,
}
}
@@ -139,7 +118,7 @@ impl CostCounter {
impl EntropyCoder for CostCounter {
fn encode_bit(&mut self, bit: bool, prob: u16) {
let prob = if bit ^ self.invert_bit_encoding {
let prob = if !bit {
prob as u32
} else {
ONE_PROB - prob as u32
@@ -148,101 +127,62 @@ impl EntropyCoder for CostCounter {
}
}
#[derive(Clone)]
pub struct RansDecoder<'a> {
data: &'a [u8],
pos: usize,
state: u32,
use_bitstream: bool,
byte: u8,
bits_left: u8,
invert_bit_encoding: bool,
bitstream_is_big_endian: bool,
}
const PROB_MASK: u32 = ONE_PROB - 1;
#[derive(Debug, Error)]
#[error("Unexpected end of input")]
pub struct UnexpectedEOF;
impl<'a> RansDecoder<'a> {
pub fn new(data: &'a [u8], config: &Config) -> Result<RansDecoder<'a>, UnexpectedEOF> {
let mut decoder = RansDecoder {
pub fn new(data: &'a [u8], use_bitstream: bool) -> RansDecoder<'a> {
RansDecoder {
data,
pos: 0,
state: 0,
use_bitstream: config.use_bitstream,
use_bitstream,
byte: 0,
bits_left: 0,
invert_bit_encoding: config.invert_bit_encoding,
bitstream_is_big_endian: config.bitstream_is_big_endian,
};
decoder.refill()?;
Ok(decoder)
}
}
pub fn pos(&self) -> usize {
self.pos
}
pub fn decode_with_context(&mut self, context: &mut Context) -> Result<bool, UnexpectedEOF> {
let bit = self.decode_bit(context.prob())?;
pub fn decode_with_context(&mut self, context: &mut Context) -> bool {
let bit = self.decode_bit(context.prob());
context.update(bit);
Ok(bit)
bit
}
fn refill(&mut self) -> Result<(), UnexpectedEOF> {
pub fn decode_bit(&mut self, prob: u16) -> bool {
let prob = prob as u32;
if self.use_bitstream {
while self.state < 32768 {
if self.bits_left == 0 {
if self.pos >= self.data.len() {
return Err(UnexpectedEOF);
}
self.byte = self.data[self.pos];
self.pos += 1;
self.byte = self.data[0];
self.data = &self.data[1..];
self.bits_left = 8;
}
if self.bitstream_is_big_endian {
self.state = (self.state << 1) | (self.byte >> 7) as u32;
self.byte <<= 1;
} else {
self.state = (self.state << 1) | (self.byte & 1) as u32;
self.byte >>= 1;
}
self.state = (self.state << 1) | (self.byte >> 7) as u32;
self.byte <<= 1;
self.bits_left -= 1;
}
} else {
while self.state < 4096 {
if self.pos >= self.data.len() {
return Err(UnexpectedEOF);
}
self.state = (self.state << 8) | self.data[self.pos] as u32;
self.pos += 1;
self.state = (self.state << 8) | self.data[0] as u32;
self.data = &self.data[1..];
}
}
Ok(())
}
pub fn decode_bit(&mut self, prob: u16) -> Result<bool, UnexpectedEOF> {
self.refill()?;
let prob = prob as u32;
let bit = (self.state & PROB_MASK) < prob;
let bit = (self.state & PROB_MASK) >= prob;
let (start, prob) = if bit {
(0, prob)
} else {
(prob, ONE_PROB - prob)
} else {
(0, prob)
};
self.state = prob * (self.state >> PROB_BITS) + (self.state & PROB_MASK) - start;
Ok(bit ^ self.invert_bit_encoding)
}
pub fn cost(&self, prev: &RansDecoder) -> f32 {
f32::log2(prev.state as f32) - f32::log2(self.state as f32)
+ (self.pos - prev.pos) as f32 * 8.
bit
}
}