Initial commit
This commit is contained in:
commit
8319ee42d9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/testfiles
|
568
Cargo.lock
generated
Normal file
568
Cargo.lock
generated
Normal file
|
@ -0,0 +1,568 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
|
||||
dependencies = [
|
||||
"nodrop",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "brd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"clap",
|
||||
"log",
|
||||
"nom",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"pretty_env_logger",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.9+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad3b39a260062fca31f7b0b12f207e8f2590a67d32ec7d59c20484b07ea7285e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb51c9e75b94452505acd21d929323f5a5c6c4735a852adbd39ef5fb1b014f30"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"rustc_version",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
|
||||
dependencies = [
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06de47b848347d8c4c94219ad8ecd35eb90231704b067e67e6ae2e36ee023510"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
|
||||
[[package]]
|
||||
name = "podio"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b18befed8bc2b61abc79a457295e7e838417326da1586050b919414073977f19"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn-mid",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn-mid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
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-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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 = "zip"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6df134e83b8f0f8153a094c7b0fd79dfebe437f1d76e7715afa18ed95ebe2fd7"
|
||||
dependencies = [
|
||||
"bzip2",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"podio",
|
||||
"time",
|
||||
]
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "brd"
|
||||
version = "0.1.0"
|
||||
authors = ["Simon Bruder <simon@sbruder.de>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.31"
|
||||
byteorder = "1.3.4"
|
||||
log = "0.4.8"
|
||||
nom = "5.1.1"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
pretty_env_logger = "0.4"
|
||||
zip = "0.5.5"
|
||||
clap = "3.0.0-beta.1"
|
15
LICENSE
Normal file
15
LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
|||
ISC License (ISC)
|
||||
|
||||
Copyright 2020 Simon Bruder
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
87
README.md
Normal file
87
README.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# BRD
|
||||
|
||||
BRD is a tool for working with [*Dance Dance Revolution*][ddr] step charts and
|
||||
wave banks. It currently supports conversion to [*osu!mania*][osu!mania]
|
||||
beatmaps and extraction of sounds from wave banks.
|
||||
|
||||
## Installation
|
||||
|
||||
Currently this is not published as a crate so you either have to clone the
|
||||
repository manually and run `cargo build --release` or you can use `cargo
|
||||
install --git https://github.com/sbruder/brd` to install the binary without
|
||||
cloning.
|
||||
|
||||
## Modes
|
||||
|
||||
### ddr2osu
|
||||
|
||||
This converts DDR step charts (.ssq files) and the corresponding audio (from
|
||||
.xwb files) to osu beatmaps (in an .osz container).
|
||||
|
||||
Basic usage:
|
||||
|
||||
brd ddr2osu -s file.ssq -x file.xwb -o file.osz --title "Song Title" --artist "Song Artist"
|
||||
|
||||
To learn more about supported options run `brd ddr2osu --help`
|
||||
|
||||
Batch conversion is possible with the included shell script `batch_convert.sh`
|
||||
(usage guide at the top of the script).
|
||||
|
||||
#### Known Problems
|
||||
|
||||
* Since *osu!mania* does not support shock arrows, it either ignores them or
|
||||
(by default) replaces them with a two-key combination (↑↓ or ←→); you can
|
||||
change this with the (`--shock-action` option)
|
||||
* Freezes do not work (I do not know how to get the start time yet) and
|
||||
therefore are disabled in code (`ddr::ssq::FREEZE`)
|
||||
|
||||
### unxwb
|
||||
|
||||
This can list and extract sounds from XWB wave banks. It currently only
|
||||
supports [ADPCM][ADPCM] sounds.
|
||||
Basic Usage:
|
||||
|
||||
brd unxwb file.xwb
|
||||
brd unxwb -l file.xwb
|
||||
|
||||
#### Known Problems
|
||||
|
||||
If you want to extract sounds that are stored in other formats, you can use
|
||||
[Luigi Auriemma’s unxwb][unxwb] (<kbd>Ctrl</kbd>+<kbd>F</kbd> unxwb).
|
||||
|
||||
## About this project
|
||||
|
||||
This is my first rust project. Don’t expect too much from the code in terms of
|
||||
quality, robustness or idiomacity (especially regarding error handling). There
|
||||
currently are no tests.
|
||||
|
||||
Large portions of this tool would not have been possible without the following
|
||||
resources:
|
||||
|
||||
* [SaxxonPike][SaxxonPike]’s [scharfrichter][scharfrichter] which implements
|
||||
[SSQ][scharfrichter-ssq] and XWB ([1][scharfrichter-xwb1],
|
||||
[2][scharfrichter-xwb2]) and their [documentation about SSQ][ssq-doc]
|
||||
* The [official osu! file format documentation][osu-doc]
|
||||
* [MonoGame][MonoGame]’s [XWB implementation][MonoGame-xwb]
|
||||
* [Luigi Auriemma][aluigi]’s [unxwb][unxwb] (especially the ADPCM header part)
|
||||
|
||||
## License
|
||||
|
||||
[ISC License](LICENSE)
|
||||
|
||||
This project is not affiliated with ppy or Konami.
|
||||
|
||||
[ADPCM]: https://en.wikipedia.org/wiki/Adaptive_differential_pulse-code_modulation
|
||||
[MonoGame-xwb]: https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Audio/Xact/WaveBank.cs
|
||||
[MonoGame]: https://github.com/MonoGame/MonoGame
|
||||
[SaxxonPike]: https://github.com/SaxxonPike
|
||||
[aluigi]: http://aluigi.altervista.org/
|
||||
[ddr]: https://en.wikipedia.org/wiki/Dance_Dance_Revolution
|
||||
[osu!mania]: https://osu.ppy.sh/help/wiki/Game_Modes/osu%21mania
|
||||
[osu-doc]: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)
|
||||
[scharfrichter-ssq]: https://github.com/SaxxonPike/scharfrichter/blob/master/Scharfrichter/Archives/BemaniSSQ.cs
|
||||
[scharfrichter-xwb1]: https://github.com/SaxxonPike/scharfrichter/blob/master/Scharfrichter/Archives/MicrosoftXWB.cs
|
||||
[scharfrichter-xwb2]: https://github.com/SaxxonPike/scharfrichter/blob/master/Scharfrichter/XACT3/Xact3WaveBank.cs
|
||||
[scharfrichter]: https://github.com/SaxxonPike/scharfrichter
|
||||
[ssq-doc]: https://github.com/SaxxonPike/rhythm-game-formats/blob/master/ddr/ssq.md
|
||||
[unxwb]: http://aluigi.altervista.org/papers.htm
|
59
batch_convert.sh
Executable file
59
batch_convert.sh
Executable file
|
@ -0,0 +1,59 @@
|
|||
#!/bin/bash
|
||||
# Batch conversion helper script
|
||||
#
|
||||
# Create a new directory with two child directories (ssq and xwb). Put the step
|
||||
# charts (*.ssq) into ssq and the wave banks (*.xwb) into xwb. If the directory
|
||||
# is a child of this project, you can directly run the script, otherwise you
|
||||
# have to execute it with the environment variable RUN_COMMAND that points to
|
||||
# the executable. If you want to specify additional flags to pass to brd you
|
||||
# can just pass them to this script. The converted beatmaps will be placed in
|
||||
# the osz directory.
|
||||
# If you want your beatmaps to have the right metadata (title and artist),
|
||||
# create a file “metadata.csv” in the directory where you run this script with
|
||||
# the following structure: name,Title,Artist (name is the name of the ssq file
|
||||
# without the extension)
|
||||
#
|
||||
# Example:
|
||||
# $ RUN_COMMAND=/path/to/target/release/brd /path/to/batch_covert.sh --source "Dance Dance Revolution x3"
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p osz
|
||||
|
||||
RUN_COMMAND=${RUN_COMMAND:-"cargo run --release --"}
|
||||
|
||||
echo "Extracting wave bank sound names"
|
||||
for i in xwb/*.xwb; do
|
||||
outfile="xwb/$(basename $i .xwb).xwb.sounds"
|
||||
if ! [ -f "$outfile" ]; then
|
||||
$RUN_COMMAND unxwb -l $i > "$outfile"
|
||||
fi
|
||||
done
|
||||
|
||||
for ssq_file in ssq/*.ssq; do
|
||||
name=$(basename $ssq_file .ssq)
|
||||
|
||||
if [ -f "xwb/${name}.xwb" ]; then
|
||||
xwb_file="xwb/${name}.xwb"
|
||||
else
|
||||
xwb_file="$(grep -lE "^${name}$" xwb/*.xwb.sounds|head -n 1)"
|
||||
# strip .sounds
|
||||
xwb_file="${xwb_file%.*}"
|
||||
if [ -z "$xwb_file" ]; then
|
||||
echo "ERR: Could not find wave bank for $name" >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
metadata=$(grep -sE "^${name}," metadata.csv|head -n 1)
|
||||
if ! [ -z "$metadata" ]; then
|
||||
title="$(cut -d, -f2 <<< $metadata)"
|
||||
artist="$(cut -d, -f3 <<< $metadata)"
|
||||
else
|
||||
title="$name"
|
||||
artist="unknown artist"
|
||||
fi
|
||||
|
||||
echo "Converting $name"
|
||||
$RUN_COMMAND ddr2osu -s "$ssq_file" -x "$xwb_file" -o "osz/${name}.osz" --title "$title" --artist "$artist" "$@"
|
||||
done
|
1
src/converter.rs
Normal file
1
src/converter.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod ddr2osu;
|
421
src/converter/ddr2osu.rs
Normal file
421
src/converter/ddr2osu.rs
Normal file
|
@ -0,0 +1,421 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Clap;
|
||||
use log::{debug, info, trace};
|
||||
|
||||
use crate::ddr::ssq;
|
||||
use crate::osu::beatmap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigRange(f32, f32);
|
||||
|
||||
impl ConfigRange {
|
||||
/// Map value from 0 to 1 onto the range
|
||||
fn map_from(&self, value: f32) -> f32 {
|
||||
(value * (self.1 - self.0)) + self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigRange {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ConfigRange {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self> {
|
||||
match string.split(':').collect::<Vec<&str>>()[..] {
|
||||
[start, end] => Ok(ConfigRange(start.parse::<f32>()?, end.parse::<f32>()?)),
|
||||
_ => Err(anyhow!("Invalid range format (expected start:end)")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clap)]
|
||||
pub struct Config {
|
||||
#[clap(skip = "audio.wav")]
|
||||
pub audio_filename: String,
|
||||
#[clap(
|
||||
long,
|
||||
default_value = "180",
|
||||
about = "Offset in milliseconds",
|
||||
display_order = 5
|
||||
)]
|
||||
pub offset: i32,
|
||||
#[clap(
|
||||
long = "no-stops",
|
||||
about = "Disable stops",
|
||||
parse(from_flag = std::ops::Not::not),
|
||||
display_order = 5
|
||||
)]
|
||||
pub stops: bool,
|
||||
#[clap(
|
||||
arg_enum,
|
||||
long,
|
||||
default_value = "step",
|
||||
about = "What to do with shocks",
|
||||
display_order = 5
|
||||
)]
|
||||
pub shock_action: ShockAction,
|
||||
#[clap(
|
||||
long = "hp",
|
||||
about = "Range of HP drain (beginner:challenge)",
|
||||
default_value = "4:8"
|
||||
)]
|
||||
pub hp_drain: ConfigRange,
|
||||
#[clap(
|
||||
long = "acc",
|
||||
about = "Range of Accuracy (beginner:challenge)",
|
||||
default_value = "7:8"
|
||||
)]
|
||||
pub accuracy: ConfigRange,
|
||||
#[clap(flatten)]
|
||||
pub metadata: ConfigMetadata,
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct ConfigMetadata {
|
||||
#[clap(long, about = "Song title to use in beatmap", display_order = 6)]
|
||||
pub title: String,
|
||||
#[clap(long, about = "Artist name to use in beatmap", display_order = 6)]
|
||||
pub artist: String,
|
||||
#[clap(
|
||||
long,
|
||||
default_value = "Dance Dance Revolution",
|
||||
about = "Source to use in beatmap",
|
||||
display_order = 6
|
||||
)]
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Config {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ddr2osu (+{}ms{} shock→{:?} hp{} acc{})",
|
||||
self.offset,
|
||||
if self.stops { " stops" } else { "" },
|
||||
self.shock_action,
|
||||
self.hp_drain,
|
||||
self.accuracy
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clap, Clone, Debug)]
|
||||
pub enum ShockAction {
|
||||
Ignore,
|
||||
Step,
|
||||
//Static(Vec<u8>),
|
||||
}
|
||||
|
||||
struct ShockStepGenerator {
|
||||
last: u8,
|
||||
columns: u8,
|
||||
mode: ShockAction,
|
||||
}
|
||||
|
||||
impl Iterator for ShockStepGenerator {
|
||||
type Item = Vec<u8>;
|
||||
|
||||
fn next(&mut self) -> Option<Vec<u8>> {
|
||||
match &self.mode {
|
||||
ShockAction::Ignore => None,
|
||||
ShockAction::Step => {
|
||||
let columns = match self.last {
|
||||
0 | 3 => vec![0, 3],
|
||||
1 | 2 => vec![1, 2],
|
||||
4 | 7 => vec![4, 7],
|
||||
5 | 6 => vec![5, 6],
|
||||
_ => vec![],
|
||||
};
|
||||
self.last = (self.last + 1) % self.columns;
|
||||
Some(columns)
|
||||
} //ShockAction::Static(columns) => Some(columns.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShockStepGenerator {
|
||||
fn new(columns: u8, mode: ShockAction) -> Self {
|
||||
Self {
|
||||
last: 0,
|
||||
columns,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_time_from_beats(beats: f32, tempo_changes: &[ssq::TempoChange]) -> Result<i32> {
|
||||
for tempo_change in tempo_changes {
|
||||
// For TempoChanges that are infinitely short but exactly cover that beat, use the start
|
||||
// time of that TempoChange
|
||||
if (beats - tempo_change.start_beats).abs() < 0.001
|
||||
&& (beats - tempo_change.end_beats).abs() < 0.001
|
||||
{
|
||||
return Ok(tempo_change.start_ms);
|
||||
}
|
||||
|
||||
if beats < tempo_change.end_beats {
|
||||
return Ok(tempo_change.start_ms
|
||||
+ ((beats - tempo_change.start_beats) * tempo_change.beat_length) as i32);
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"Conversion of Step to HitObject failed: Beat lies outside of TimingPoints range"
|
||||
))
|
||||
}
|
||||
|
||||
impl From<ssq::TempoChange> for beatmap::TimingPoint {
|
||||
fn from(tempo_change: ssq::TempoChange) -> Self {
|
||||
beatmap::TimingPoint {
|
||||
time: tempo_change.start_ms,
|
||||
beat_length: if tempo_change.beat_length == f32::INFINITY {
|
||||
10000.0
|
||||
} else {
|
||||
tempo_change.beat_length
|
||||
},
|
||||
meter: 4,
|
||||
sample_set: beatmap::SampleSet::BeatmapDefault,
|
||||
sample_index: 0,
|
||||
volume: 100,
|
||||
uninherited: true,
|
||||
effects: beatmap::TimingPointEffects {
|
||||
kiai_time: false,
|
||||
omit_first_barline: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ssq::Step {
|
||||
fn to_hit_objects(
|
||||
&self,
|
||||
num_columns: u8,
|
||||
tempo_changes: &ssq::TempoChanges,
|
||||
shock_step_generator: &mut ShockStepGenerator,
|
||||
) -> Result<Vec<beatmap::HitObject>> {
|
||||
let mut hit_objects = Vec::new();
|
||||
|
||||
match self {
|
||||
ssq::Step::Step { beats, row } => {
|
||||
let time = get_time_from_beats(*beats, &tempo_changes.0)?;
|
||||
|
||||
let columns: Vec<bool> = row.clone().into();
|
||||
|
||||
for (column, active) in columns.iter().enumerate() {
|
||||
if *active {
|
||||
hit_objects.push(beatmap::HitObject::HitCircle {
|
||||
x: beatmap::column_to_x(column as u8, num_columns),
|
||||
y: 192,
|
||||
time,
|
||||
hit_sound: beatmap::HitSound {
|
||||
normal: true,
|
||||
whistle: false,
|
||||
finish: false,
|
||||
clap: false,
|
||||
},
|
||||
new_combo: false,
|
||||
skip_combo_colours: 0,
|
||||
hit_sample: beatmap::HitSample {
|
||||
normal_set: 0,
|
||||
addition_set: 0,
|
||||
index: 0,
|
||||
volume: 0,
|
||||
filename: "".to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
ssq::Step::Freeze { start, end, row } => {
|
||||
let time = get_time_from_beats(*start, &tempo_changes.0)?;
|
||||
let end_time = get_time_from_beats(*end, &tempo_changes.0)?;
|
||||
|
||||
let columns: Vec<bool> = row.clone().into();
|
||||
|
||||
for (column, active) in columns.iter().enumerate() {
|
||||
if *active {
|
||||
hit_objects.push(beatmap::HitObject::Hold {
|
||||
column: column as u8,
|
||||
columns: num_columns,
|
||||
time,
|
||||
end_time,
|
||||
hit_sound: beatmap::HitSound {
|
||||
normal: true,
|
||||
whistle: false,
|
||||
finish: true,
|
||||
clap: false,
|
||||
},
|
||||
new_combo: false,
|
||||
skip_combo_colours: 0,
|
||||
hit_sample: beatmap::HitSample {
|
||||
normal_set: 0,
|
||||
addition_set: 0,
|
||||
index: 0,
|
||||
volume: 0,
|
||||
filename: "".to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
ssq::Step::Shock { beats } => {
|
||||
let columns = match shock_step_generator.next() {
|
||||
Some(columns) => columns,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
for column in columns {
|
||||
hit_objects.push(beatmap::HitObject::HitCircle {
|
||||
x: beatmap::column_to_x(column as u8, num_columns),
|
||||
y: 192,
|
||||
time: get_time_from_beats(*beats, &tempo_changes.0)?,
|
||||
hit_sound: beatmap::HitSound {
|
||||
normal: true,
|
||||
whistle: false,
|
||||
finish: false,
|
||||
clap: false,
|
||||
},
|
||||
new_combo: false,
|
||||
skip_combo_colours: 0,
|
||||
hit_sample: beatmap::HitSample {
|
||||
normal_set: 0,
|
||||
addition_set: 0,
|
||||
index: 0,
|
||||
volume: 0,
|
||||
filename: "".to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hit_objects)
|
||||
}
|
||||
}
|
||||
|
||||
struct ConvertedChart {
|
||||
difficulty: ssq::Difficulty,
|
||||
hit_objects: beatmap::HitObjects,
|
||||
timing_points: beatmap::TimingPoints,
|
||||
}
|
||||
|
||||
impl ConvertedChart {
|
||||
fn to_beatmap(&self, config: &Config) -> beatmap::Beatmap {
|
||||
beatmap::Beatmap {
|
||||
version: 14,
|
||||
general: beatmap::General {
|
||||
audio_filename: config.audio_filename.clone(),
|
||||
audio_lead_in: 0,
|
||||
preview_time: 0,
|
||||
countdown: beatmap::Countdown::No,
|
||||
sample_set: beatmap::SampleSet::Soft,
|
||||
mode: beatmap::Mode::Mania,
|
||||
},
|
||||
editor: beatmap::Editor {},
|
||||
metadata: beatmap::Metadata {
|
||||
title: config.metadata.title.clone(),
|
||||
artist: config.metadata.artist.clone(),
|
||||
creator: format!("{}", config),
|
||||
version: format!("{}", self.difficulty),
|
||||
source: config.metadata.source.clone(),
|
||||
tags: vec![],
|
||||
},
|
||||
difficulty: beatmap::Difficulty {
|
||||
hp_drain_rate: config.hp_drain.map_from(self.difficulty.clone().into()),
|
||||
circle_size: self.difficulty.players as f32 * 4.0,
|
||||
overall_difficulty: config.accuracy.map_from(self.difficulty.clone().into()),
|
||||
approach_rate: 8.0,
|
||||
slider_multiplier: 0.64,
|
||||
slider_tick_rate: 1.0,
|
||||
},
|
||||
events: beatmap::Events(vec![]),
|
||||
timing_points: self.timing_points.clone(),
|
||||
colours: beatmap::Colours(vec![]),
|
||||
hit_objects: self.hit_objects.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ssq::SSQ {
|
||||
pub fn to_beatmaps(&self, config: &Config) -> Result<Vec<beatmap::Beatmap>> {
|
||||
debug!("Configuration: {:?}", config);
|
||||
|
||||
let mut timing_points = Vec::new();
|
||||
|
||||
timing_points.push(beatmap::TimingPoint {
|
||||
time: 0,
|
||||
beat_length: config.offset as f32,
|
||||
meter: 4,
|
||||
sample_set: beatmap::SampleSet::BeatmapDefault,
|
||||
sample_index: 0,
|
||||
volume: 0,
|
||||
uninherited: true,
|
||||
effects: beatmap::TimingPointEffects {
|
||||
kiai_time: false,
|
||||
omit_first_barline: false,
|
||||
},
|
||||
});
|
||||
|
||||
for entry in &self.tempo_changes.0 {
|
||||
if config.stops || entry.beat_length != f32::INFINITY {
|
||||
trace!("Converting {:?} to to timing point", entry);
|
||||
let timing_point: beatmap::TimingPoint = entry.clone().into();
|
||||
timing_points.push(timing_point);
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
"Converted {} tempo changes to timing points",
|
||||
self.tempo_changes.0.len()
|
||||
);
|
||||
|
||||
let mut converted_charts = Vec::new();
|
||||
|
||||
for chart in &self.charts {
|
||||
debug!("Converting chart {} to beatmap", chart.difficulty);
|
||||
let mut hit_objects = beatmap::HitObjects(Vec::new());
|
||||
|
||||
let mut shock_step_generator =
|
||||
ShockStepGenerator::new(chart.difficulty.players * 4, config.shock_action.clone());
|
||||
for step in &chart.steps.0 {
|
||||
trace!("Converting {:?} to hit object", step);
|
||||
let mut step_hit_objects = step.to_hit_objects(
|
||||
chart.difficulty.players * 4,
|
||||
&self.tempo_changes,
|
||||
&mut shock_step_generator,
|
||||
)?;
|
||||
hit_objects.0.append(&mut step_hit_objects);
|
||||
}
|
||||
|
||||
let converted_chart = ConvertedChart {
|
||||
difficulty: chart.difficulty.clone(),
|
||||
hit_objects,
|
||||
timing_points: beatmap::TimingPoints(timing_points.clone()),
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Converted to beatmap with {} hit objects",
|
||||
converted_chart.hit_objects.0.len(),
|
||||
);
|
||||
|
||||
converted_charts.push(converted_chart);
|
||||
}
|
||||
|
||||
let mut beatmaps = Vec::new();
|
||||
|
||||
for converted_chart in converted_charts {
|
||||
let beatmap = converted_chart.to_beatmap(config);
|
||||
beatmaps.push(beatmap);
|
||||
}
|
||||
|
||||
info!("Converted {} step charts to beatmaps", beatmaps.len());
|
||||
|
||||
Ok(beatmaps)
|
||||
}
|
||||
}
|
1
src/ddr.rs
Normal file
1
src/ddr.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod ssq;
|
420
src/ddr/ssq.rs
Normal file
420
src/ddr/ssq.rs
Normal file
|
@ -0,0 +1,420 @@
|
|||
use std::convert::From;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::{debug, info, trace, warn};
|
||||
use nom::bytes::complete::take;
|
||||
use nom::multi::many0;
|
||||
use nom::number::complete::{le_i16, le_i32, le_u16};
|
||||
use nom::IResult;
|
||||
|
||||
use crate::utils;
|
||||
use crate::utils::exec_nom_parser;
|
||||
|
||||
const MEASURE_LENGTH: i32 = 4096;
|
||||
const FREEZE: bool = false;
|
||||
|
||||
// Convert time offset to beats
|
||||
// time offset is the measure times MEASURE_LENGTH
|
||||
fn measure_to_beats(metric: i32) -> f32 {
|
||||
4.0 * metric as f32 / MEASURE_LENGTH as f32
|
||||
}
|
||||
|
||||
fn parse_n_i32(n: usize, input: &[u8]) -> IResult<&[u8], Vec<i32>> {
|
||||
let (input, bytes) = take(n as usize * 4)(input)?;
|
||||
let (unprocessed_input, values) = many0(le_i32)(bytes)?;
|
||||
assert_eq!(unprocessed_input.len(), 0);
|
||||
Ok((input, values))
|
||||
}
|
||||
|
||||
fn parse_usize(input: &[u8]) -> IResult<&[u8], usize> {
|
||||
let (input, value) = le_i32(input)?;
|
||||
Ok((input, value as usize))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TempoChange {
|
||||
pub start_ms: i32,
|
||||
pub start_beats: f32,
|
||||
pub end_beats: f32,
|
||||
pub beat_length: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TempoChanges(pub Vec<TempoChange>);
|
||||
|
||||
impl TempoChanges {
|
||||
fn parse(ticks_per_second: i32, input: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (input, count) = parse_usize(input)?;
|
||||
let (input, measure) = parse_n_i32(count, input)?;
|
||||
let (input, tempo_data) = parse_n_i32(count, input)?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
let mut elapsed_ms = 0;
|
||||
let mut elapsed_beats = 0.0;
|
||||
for i in 1..count {
|
||||
let delta_measure = measure[i] - measure[i - 1];
|
||||
let delta_ticks = tempo_data[i] - tempo_data[i - 1];
|
||||
|
||||
let length_ms = 1000 * delta_ticks / ticks_per_second;
|
||||
let length_beats = measure_to_beats(delta_measure);
|
||||
|
||||
let beat_length = length_ms as f32 / length_beats;
|
||||
|
||||
let entry = TempoChange {
|
||||
start_ms: elapsed_ms,
|
||||
start_beats: elapsed_beats,
|
||||
end_beats: elapsed_beats + length_beats,
|
||||
beat_length,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
|
||||
elapsed_ms += length_ms;
|
||||
elapsed_beats += length_beats;
|
||||
}
|
||||
|
||||
Ok((input, Self(entries)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Step {
|
||||
Step { beats: f32, row: Row },
|
||||
Freeze { start: f32, end: f32, row: Row },
|
||||
Shock { beats: f32 },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Steps(pub Vec<Step>);
|
||||
|
||||
impl Steps {
|
||||
fn parse(input: &[u8], players: u8) -> IResult<&[u8], Self> {
|
||||
let (input, count) = parse_usize(input)?;
|
||||
let (input, measure) = parse_n_i32(count, input)?;
|
||||
let (input, steps) = take(count)(input)?;
|
||||
|
||||
// freeze data can be padded with zeroes
|
||||
let (input, freeze_data) = take(input.len())(input)?;
|
||||
let mut freeze = freeze_data.iter().skip_while(|x| **x == 0).copied();
|
||||
|
||||
let mut parsed_steps = Vec::new();
|
||||
|
||||
for i in 0..count {
|
||||
let beats = measure_to_beats(measure[i]);
|
||||
|
||||
// check if either all eight bits are set (shock for double) or the first four (shock for
|
||||
// single)
|
||||
if steps[i] == 0xff || steps[i] == 0xf {
|
||||
// shock
|
||||
trace!("Shock arrow at {}", beats);
|
||||
|
||||
parsed_steps.push(Step::Shock { beats });
|
||||
} else if steps[i] == 0x00 {
|
||||
// extra data
|
||||
let columns = freeze.next().unwrap();
|
||||
let extra_type = freeze.next().unwrap();
|
||||
|
||||
if extra_type == 1 {
|
||||
// freeze end (start is the last normal step in that column)
|
||||
trace!("Freeze arrow at {}", beats);
|
||||
|
||||
let row = Row::new(columns, players);
|
||||
if row.count_active() != 1 {
|
||||
warn!("Found freeze with not exactly one column, which is not implemented, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
let last_step = match Self::find_last(Self(parsed_steps.clone()), &row) {
|
||||
Ok(last_step) => last_step,
|
||||
Err(err) => {
|
||||
warn!("Could not add freeze arrow: {}; adding normal step", err);
|
||||
parsed_steps.push(Step::Step { beats, row });
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if FREEZE {
|
||||
parsed_steps.push(Step::Freeze {
|
||||
start: if let Step::Step { beats, .. } = parsed_steps[last_step] {
|
||||
beats
|
||||
} else {
|
||||
unreachable!()
|
||||
},
|
||||
end: beats,
|
||||
row,
|
||||
});
|
||||
|
||||
parsed_steps.remove(last_step);
|
||||
} else {
|
||||
trace!("Freeze disabled, adding normal step");
|
||||
parsed_steps.push(Step::Step { beats, row });
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Encountered unknown extra step with type {}, ignoring",
|
||||
extra_type
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// normal step
|
||||
trace!("Normal step at {}", beats);
|
||||
|
||||
parsed_steps.push(Step::Step {
|
||||
beats,
|
||||
row: Row::new(steps[i], players),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Parsed {} steps", parsed_steps.len());
|
||||
|
||||
Ok((input, Self(parsed_steps)))
|
||||
}
|
||||
|
||||
fn find_last(steps: Self, row: &Row) -> Result<usize> {
|
||||
for i in (0..steps.0.len()).rev() {
|
||||
if let Step::Step { row: step_row, .. } = &steps.0[i] {
|
||||
if step_row.clone().intersects(row.clone()) {
|
||||
return Ok(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("No previous step found on that column"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Difficulty {
|
||||
pub players: u8,
|
||||
difficulty: u8,
|
||||
}
|
||||
|
||||
impl From<u16> for Difficulty {
|
||||
fn from(parameter: u16) -> Self {
|
||||
Self {
|
||||
difficulty: ((parameter & 0xFF00) >> 8) as u8,
|
||||
players: (parameter & 0xF) as u8 / 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<f32> for Difficulty {
|
||||
fn into(self) -> f32 {
|
||||
match self.difficulty {
|
||||
1 => 0.25,
|
||||
2 => 0.5,
|
||||
3 => 1.0,
|
||||
4 => 0.0,
|
||||
6 => 0.75,
|
||||
_ => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Difficulty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let players = match self.players {
|
||||
1 => "Single",
|
||||
2 => "Double",
|
||||
_ => "Unknown Number of Players",
|
||||
};
|
||||
let difficulty = match self.difficulty {
|
||||
1 => "Basic",
|
||||
2 => "Difficult",
|
||||
3 => "Challenge",
|
||||
4 => "Beginner",
|
||||
6 => "Expert",
|
||||
_ => "Unknown Difficulty",
|
||||
};
|
||||
write!(f, "{} {}", players, difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Chart {
|
||||
pub difficulty: Difficulty,
|
||||
pub steps: Steps,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SSQ {
|
||||
pub tempo_changes: TempoChanges,
|
||||
pub charts: Vec<Chart>,
|
||||
}
|
||||
|
||||
impl From<Chunks> for SSQ {
|
||||
fn from(chunks: Chunks) -> Self {
|
||||
let mut ssq = Self {
|
||||
tempo_changes: TempoChanges(Vec::new()),
|
||||
charts: Vec::new(),
|
||||
};
|
||||
for chunk in chunks.0 {
|
||||
match chunk {
|
||||
Chunk::TempoChanges(mut tempo_changes) => {
|
||||
ssq.tempo_changes.0.append(&mut tempo_changes.0)
|
||||
}
|
||||
Chunk::Chart(chart) => ssq.charts.push(chart),
|
||||
Chunk::Extra(..) => {}
|
||||
}
|
||||
}
|
||||
info!("Parsed {} charts", ssq.charts.len());
|
||||
ssq
|
||||
}
|
||||
}
|
||||
|
||||
impl SSQ {
|
||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||
debug!(
|
||||
"Configuration: measure length: {}, use freezes: {}",
|
||||
MEASURE_LENGTH, FREEZE
|
||||
);
|
||||
let chunks = exec_nom_parser(Chunks::parse, data)?;
|
||||
|
||||
Ok(Self::from(chunks))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum Chunk {
|
||||
Chart(Chart),
|
||||
TempoChanges(TempoChanges),
|
||||
Extra(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (input, length) = le_i32(input)?;
|
||||
|
||||
let (input, chunk_type) = le_i16(input)?;
|
||||
let (input, parameter) = le_u16(input)?;
|
||||
|
||||
// length without i32 and 2 × i16
|
||||
let (input, data) = take(length as usize - 8)(input)?;
|
||||
|
||||
let chunk = match chunk_type {
|
||||
1 => {
|
||||
debug!("Parsing tempo changes (ticks/s: {})", parameter);
|
||||
let (_, TempoChanges(tempo_changes)) = TempoChanges::parse(parameter as i32, data)?;
|
||||
Self::TempoChanges(TempoChanges(tempo_changes))
|
||||
}
|
||||
3 => {
|
||||
let difficulty = Difficulty::from(parameter);
|
||||
debug!("Parsing step chunk ({})", difficulty);
|
||||
let (_, steps) = Steps::parse(data, difficulty.players)?;
|
||||
Self::Chart(Chart { difficulty, steps })
|
||||
}
|
||||
_ => {
|
||||
debug!("Found extra chunk (length {})", data.len());
|
||||
Self::Extra(data.to_vec())
|
||||
}
|
||||
};
|
||||
Ok((input, chunk))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Chunks(Vec<Chunk>);
|
||||
|
||||
impl Chunks {
|
||||
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
|
||||
let (input, chunks) = many0(Chunk::parse)(input)?;
|
||||
Ok((input, Self(chunks)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PlayerRow {
|
||||
pub left: bool,
|
||||
pub down: bool,
|
||||
pub up: bool,
|
||||
pub right: bool,
|
||||
}
|
||||
|
||||
impl From<u8> for PlayerRow {
|
||||
fn from(byte: u8) -> Self {
|
||||
let columns = utils::byte_to_bitarray(byte);
|
||||
PlayerRow {
|
||||
left: columns[0],
|
||||
down: columns[1],
|
||||
up: columns[2],
|
||||
right: columns[3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<bool>> for PlayerRow {
|
||||
fn into(self) -> Vec<bool> {
|
||||
vec![self.left, self.down, self.up, self.right]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Row {
|
||||
Single(PlayerRow),
|
||||
Double(PlayerRow, PlayerRow),
|
||||
}
|
||||
|
||||
impl Into<Vec<bool>> for Row {
|
||||
fn into(self) -> Vec<bool> {
|
||||
match self {
|
||||
Self::Single(row) => row.into(),
|
||||
Self::Double(row1, row2) => {
|
||||
let mut row: Vec<bool> = Vec::new();
|
||||
row.append(&mut row1.into());
|
||||
row.append(&mut row2.into());
|
||||
row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Row {
|
||||
fn new(byte: u8, players: u8) -> Self {
|
||||
match players {
|
||||
1 => Self::Single(PlayerRow::from(byte)),
|
||||
2 => Self::Double(PlayerRow::from(byte), PlayerRow::from(byte >> 4)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn count_active(&self) -> u8 {
|
||||
let mut rows = Vec::<bool>::new();
|
||||
|
||||