Compare commits

...

21 Commits

Author SHA1 Message Date
Piotr Siuszko 067bde3454 Deps update 2025-01-06 16:36:14 +01:00
Piotr Siuszko c526585f77 namespace fix 2024-08-14 10:18:59 +02:00
Piotr Siuszko 3576df7d27 Update and arg check 2024-07-28 20:47:06 +02:00
Piotr Siuszko d9bfa6e639 Better Unity material reading and parsing functionality 2024-01-02 11:07:44 +01:00
Piotr Siuszko b9729d8579
Merge pull request #1 from Leinnan/gltfMaterials
Gltf materials
2023-12-29 15:32:12 +01:00
Piotr Siuszko 6483ebfc56 Update deps 2023-12-29 15:30:30 +01:00
Piotr Siuszko 760dded5eb Refactor unpacker.rs- asset filtering 2023-12-29 15:28:49 +01:00
Piotr Siuszko 6a19b0fbb0 Remove pathdiff dependency and unused function 2023-12-29 15:25:23 +01:00
Piotr Siuszko 1d7e43fa5c Update changelog, README 2023-12-29 15:20:28 +01:00
Piotr Siuszko c3b959f632 New flag: get_materials_from_prefabs 2023-12-29 15:16:51 +01:00
Piotr Siuszko c46de6ebae Simplify process of FBX model conversion. 2023-12-29 15:06:28 +01:00
Piotr Siuszko 4868671e78 Implement material modification in model unpacking 2023-12-29 15:00:48 +01:00
Piotr Siuszko 8ef65376b8 Find texture path
now missing part is reading gltf file, changing materials texture and saving it
2023-12-28 20:41:49 +01:00
Piotr Siuszko 6e75fff4e4 Implement GLTF material update in unpacker- first part 2023-12-28 18:36:20 +01:00
Piotr Siuszko 08aecd4004 extract fn, store assets in Unpacker struct 2023-12-28 17:42:01 +01:00
Piotr Siuszko ef54eef2e0 copy meta files alongside regular files, v0.3.0 2023-12-27 23:42:22 +01:00
Piotr Siuszko a9426fc3b9 Add Asset structure 2023-12-27 23:30:14 +01:00
Piotr Siuszko 25fa59820f Changelog update 2023-12-27 23:06:55 +01:00
Piotr Siuszko 5c963e1d9d Refactor unpacking functionality into separate module 2023-12-27 23:04:41 +01:00
Piotr Siuszko 533ad95e11 Refactor code and add hashbrown dependency 2023-12-27 22:27:58 +01:00
Piotr Siuszko 5851f52780 Update changelog and README 2023-12-27 22:04:55 +01:00
12 changed files with 1092 additions and 289 deletions

View File

@ -1,10 +1,39 @@
# Changelog
## [0.4.1]
### Fixed
- Improved material parsing, big thanks to [bevity](https://github.com/gamedolphin/bevity).
## [0.4.0]
### Added
- New flag `--get_materials_from_prefabs` for updating model textures based on prefabs and materials.
## [0.3.0]
### Added
- New flag `--copy-meta-files` for copying meta files alongside assets.
### Changed
- Multithreaded unpacking improvements.
- Split code.
## [0.2.1]
### Added
- Working deploy for major platforms.
## [0.2.0]
### Added
- Ignoring extensions during unpacking
- Ignoring extensions during unpacking.
## [0.1.0]

527
Cargo.lock generated
View File

@ -1,66 +1,88 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "adler"
version = "1.0.2"
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.5"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.4"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.1.0"
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bitflags"
@ -70,9 +92,27 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bytemuck"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cfg-if"
@ -82,9 +122,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.11"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
@ -92,9 +132,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.11"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
@ -104,9 +144,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.7"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
@ -116,164 +156,301 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.6.0"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "crc32fast"
version = "1.3.2"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.4"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.16"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.17"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
dependencies = [
"cfg-if",
]
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.9.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "filetime"
version = "0.2.23"
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"libredox",
"windows-sys",
]
[[package]]
name = "flate2"
version = "1.0.28"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "heck"
version = "0.4.1"
name = "gltf"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7"
dependencies = [
"base64",
"byteorder",
"gltf-json",
"image",
"lazy_static",
"serde_json",
"urlencoding",
]
[[package]]
name = "gltf-derive"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51"
dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "gltf-json"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14"
dependencies = [
"gltf-derive",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "image"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [
"bytemuck",
"byteorder-lite",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "indexmap"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "inflections"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.151"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.6.0",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "lwa_unity_unpack"
version = "0.2.0"
version = "0.4.1"
dependencies = [
"anyhow",
"clap",
"flate2",
"gltf",
"rayon",
"regex",
"serde",
"serde_yaml",
"tar",
]
[[package]]
name = "memoffset"
version = "0.9.0"
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"adler",
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
@ -281,9 +458,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@ -291,20 +468,49 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.4.1"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.6.0",
]
[[package]]
name = "rustix"
version = "0.38.28"
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"bitflags 2.4.1",
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
@ -312,16 +518,73 @@ dependencies = [
]
[[package]]
name = "strsim"
version = "0.10.0"
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.41"
version = "2.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
dependencies = [
"proc-macro2",
"quote",
@ -330,9 +593,9 @@ dependencies = [
[[package]]
name = "tar"
version = "0.4.40"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6"
dependencies = [
"filetime",
"libc",
@ -341,34 +604,47 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf8parse"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.52.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -377,53 +653,74 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "xattr"
version = "1.1.3"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995"
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
dependencies = [
"libc",
"linux-raw-sys",
"rustix",
]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-jpeg"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
dependencies = [
"zune-core",
]

View File

@ -1,6 +1,6 @@
[package]
name = "lwa_unity_unpack"
version = "0.2.0"
version = "0.4.1"
edition = "2021"
repository = "https://github.com/Leinnan/lwa_unity_unpack"
homepage = "https://github.com/Leinnan/lwa_unity_unpack"
@ -20,7 +20,12 @@ lto = true
opt-level = 2
[dependencies]
clap = { version = "4.4", features = ["derive"] }
flate2 = "1.0"
rayon = "1.8.0"
clap = { version = "4.5", features = ["derive"] }
flate2 = "1"
gltf = "1"
rayon = "1"
regex = "1"
tar = "0.4"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"
anyhow = "1"

View File

@ -16,8 +16,12 @@ Options:
-i, --input <INPUT> .unitypackage file to extract
-o, --output <OUTPUT> target directory
-f, --fbx-to-gltf <FBX_TO_GLTF> optional- path to the tool that will auto convert fbx files to gltf during unpacking
--get-materials-from-prefabs
checks if material base texture in prefabs differ from the one specified in fbx model that is converted to GLTF and overrides it with the one from prefab and copy texture to models folder
--ignore-extensions <IGNORE_EXTENSIONS>
optional- extensions that will be ignored during unpacking
--copy-meta-files
copy meta files alongside regular files
-h, --help Print help
-V, --version Print version
```
@ -28,7 +32,9 @@ Options:
## Install
For now Rust Cargo is required:
It can be downloaded by going to Releases page.
It can be also installed using Rust Cargo:
```sh
cargo install lwa_unity_unpack

62
src/args.rs Normal file
View File

@ -0,0 +1,62 @@
use clap::Parser;
use std::path::PathBuf;
/// Program for unpacking unitypackages files.
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Args {
/// .unitypackage file to extract
#[arg(short, long)]
pub input: PathBuf,
/// target directory
#[arg(short, long)]
pub output: PathBuf,
/// optional- path to the tool that will auto convert fbx files to gltf during unpacking
#[arg(long)]
pub fbx_to_gltf: Option<PathBuf>,
/// checks if material base texture in prefabs differ from the one specified in fbx model
/// that is converted to GLTF and overrides it with the one from prefab and copy texture to models folder
#[arg(long, default_value = "false", default_missing_value = "true")]
pub get_materials_from_prefabs: bool,
/// optional- extensions that will be ignored during unpacking
#[arg(long, action = clap::ArgAction::Append)]
pub ignore_extensions: Option<Vec<String>>,
/// copy meta files alongside regular files
#[arg(long, default_value = "false", default_missing_value = "true")]
pub copy_meta_files: bool,
}
impl Args {
pub fn check(&self) {
if let Some(path) = &self.fbx_to_gltf {
assert!(
is_executable(&path),
"fbx_to_gltf require a path to executable"
)
}
}
}
#[cfg(target_os = "windows")]
fn is_executable(path: &PathBuf) -> bool {
if let Some(extension) = path.extension() {
extension.to_str().unwrap().ends_with("exe")
} else {
false
}
}
#[cfg(not(target_os = "windows"))]
fn is_executable(path: &PathBuf) -> bool {
use std::os::unix::fs::PermissionsExt;
if let Ok(metadata) = std::fs::metadata(path) {
let permissions = metadata.permissions();
permissions.mode() & 0o111 != 0
} else {
false
}
}

125
src/asset.rs Normal file
View File

@ -0,0 +1,125 @@
use crate::primitives::materials::read_single_material;
use std::ffi::OsStr;
use std::fs;
use std::fs::DirEntry;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
#[derive(Clone)]
pub struct Asset {
pub extension: Option<String>,
pub guid: String,
pub path: String,
pub has_meta: bool,
pub asset_type: AssetType,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum AssetType {
FbxModel,
Material,
Prefab,
Scene,
Other(String),
}
impl Asset {
pub fn try_get_mat_texture_guid(&self) -> Option<String> {
match &self.asset_type {
AssetType::Material => {}
_ => return None,
}
let content = fs::read_to_string(&self.path).unwrap();
let material = read_single_material(&content);
if let Ok(mat) = material {
return mat
.properties
.tex_envs
.iter()
.find_map(|tex| tex.get("_MainTex"))
.and_then(|t| t.texture.guid.clone());
}
None
}
pub fn prepare_directory(&self) {
println!("{}: {:?}", self.guid, self.path);
let base_path = Path::new(&self.path);
let result_dir = base_path.parent();
if result_dir.is_none() {
eprintln!("{} is none", &self.path);
}
let result_dir = result_dir.unwrap();
if !result_dir.exists() {
let result = fs::create_dir_all(result_dir);
if result.is_err() {
eprintln!(
"Error {}: {}",
result_dir.to_str().unwrap(),
result.err().unwrap()
);
}
}
}
pub fn from_path(entry: &DirEntry, output_dir: &Path) -> Option<Asset> {
let root_file = entry.path();
if !root_file.is_dir() {
return None;
}
let guid = entry.file_name().into_string().unwrap();
let mut real_path = String::new();
let mut extension = None;
let mut has_asset = false;
let mut has_meta = false;
for sub_entry in fs::read_dir(root_file.clone()).unwrap() {
let sub_entry = sub_entry.unwrap();
let file_name = sub_entry.file_name().into_string().unwrap();
match file_name.as_str() {
"pathname" => {
let path = sub_entry.path();
let file = File::open(path).unwrap();
let buf_reader = BufReader::new(file);
let line = buf_reader.lines().next();
match line {
Some(Ok(path)) => {
real_path = output_dir.join(path).to_str().unwrap().to_string();
if let Some(e) =
Path::new(&real_path).extension().and_then(OsStr::to_str)
{
extension = Some(String::from(e));
}
}
_ => continue,
}
}
"asset" => has_asset = true,
"asset.meta" => has_meta = true,
_ => continue,
}
}
if has_asset {
let asset_type = match &extension {
Some(str) => match str.as_str() {
"fbx" => AssetType::FbxModel,
"prefab" => AssetType::Prefab,
"unity" => AssetType::Scene,
"mat" => AssetType::Material,
_ => AssetType::Other(str.clone()),
},
_ => AssetType::Other(String::new()),
};
Some(Asset {
extension,
guid,
path: real_path,
has_meta,
asset_type,
})
} else {
None
}
}
}

View File

@ -1,173 +1,19 @@
use flate2::read::GzDecoder;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::{fs, io, sync::Arc};
use tar::Archive;
mod args;
pub mod asset;
pub mod primitives;
mod unpacker;
mod yaml_helpers;
use clap::Parser;
use rayon::prelude::*;
use std::io::prelude::*;
use std::io::BufReader;
use std::process::Command;
/// Program for unpacking unitypackages files.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// .unitypackage file to extract
#[arg(short, long)]
input: PathBuf,
/// target directory
#[arg(short, long)]
output: PathBuf,
/// optional- path to the tool that will auto convert fbx files to gltf during unpacking
#[arg(short, long)]
fbx_to_gltf: Option<PathBuf>,
/// optional- extensions that will be ignored during unpacking
#[arg(long, action = clap::ArgAction::Append)]
ignore_extensions: Option<Vec<String>>,
}
pub fn extract_archive(archive_path: &Path, extract_to: &Path) -> io::Result<()> {
let tar_gz = File::open(archive_path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(extract_to)?;
Ok(())
}
fn main() {
let args: Args = Args::parse();
let ignored_extensions = args.ignore_extensions.unwrap_or_default();
let archive_path = Path::new(&args.input);
let tmp_dir = Path::new("./tmp_dir");
let output_dir = Path::new(&args.output);
if !archive_path.exists() {
panic!("Input file does not exits");
}
if tmp_dir.exists() {
println!("Temp directory exits, cleaning up first.");
fs::remove_dir_all(tmp_dir).unwrap();
}
if let Err(e) = extract_archive(archive_path, tmp_dir) {
println!("Failed to extract archive: {}", e);
}
if output_dir.exists() {
println!("Output directory exits, cleaning up first.");
fs::remove_dir_all(output_dir).unwrap();
}
fs::create_dir(output_dir).unwrap();
let mut mapping: HashMap<String, String> = HashMap::new();
let args = crate::args::Args::parse();
let mut unpacker = crate::unpacker::Unpacker {
args,
assets: vec![],
};
for entry in fs::read_dir(tmp_dir).unwrap() {
let entry = entry.unwrap();
let root_file = entry.path();
let asset = entry.file_name().into_string().unwrap();
if root_file.is_dir() {
let mut real_path = String::new();
let mut extension = None;
let mut has_asset = false;
for sub_entry in fs::read_dir(root_file.clone()).unwrap() {
let sub_entry = sub_entry.unwrap();
let file_name = sub_entry.file_name().into_string().unwrap();
if file_name == "pathname" {
let path = sub_entry.path();
let file = File::open(path).unwrap();
let buf_reader = BufReader::new(file);
let line = buf_reader.lines().next();
match line {
Some(Ok(path)) => {
real_path = path;
if let Some(e) =
Path::new(&real_path).extension().and_then(OsStr::to_str)
{
extension = Some(String::from(e));
}
}
_ => continue,
}
} else if file_name == "asset" {
has_asset = true;
}
}
if has_asset && !ignored_extensions.contains(&extension.unwrap_or_default()) {
mapping.insert(asset, real_path);
}
}
}
println!("Results:");
let mapping_arc = Arc::new(mapping);
let tmp_dir = Arc::new(tmp_dir);
let output_dir = Arc::new(output_dir);
mapping_arc.par_iter().for_each(|(asset_hash, asset_path)| {
let path = Path::new(asset_path);
let source_asset = Path::new(&*tmp_dir).join(asset_hash).join("asset");
let result_path = output_dir.join(path);
process_directory(asset_hash, asset_path, &result_path);
check_source_asset_exists(&source_asset);
if args.fbx_to_gltf.is_some() {
if let Some("fbx") = path.extension().and_then(OsStr::to_str) {
process_fbx_file(
&source_asset,
&result_path,
&args.fbx_to_gltf.clone().unwrap(),
);
return;
}
}
process_non_fbx_file(&source_asset, &result_path);
});
fs::remove_dir_all(Path::new(&*tmp_dir)).unwrap();
fn process_directory(asset_hash: &str, asset_path: &str, result_path: &Path) {
println!("{}: {:?}", asset_hash, asset_path);
let result_dir = result_path.parent().unwrap();
if !result_dir.exists() {
fs::create_dir_all(result_dir).unwrap();
}
}
fn check_source_asset_exists(source_asset: &Path) {
if !source_asset.exists() {
panic!("SOURCE ASSET DOES NOT EXIST: {}", source_asset.display());
}
}
fn process_fbx_file(source_asset: &Path, result_path: &Path, tool: &PathBuf) {
let out_path = result_path.with_extension("");
println!(
"{:?}",
&[
"--input",
source_asset.to_str().unwrap(),
"--output",
out_path.to_str().unwrap()
]
);
let output = Command::new(tool)
.args([
"--input",
source_asset.to_str().unwrap(),
"-b",
"--output",
out_path.to_str().unwrap(),
])
.output()
.unwrap();
let output_result = String::from_utf8_lossy(&output.stdout);
println!("output: {}", output_result);
}
fn process_non_fbx_file(source_asset: &Path, result_path: &Path) {
fs::rename(source_asset, result_path).unwrap();
}
unpacker.prepare_environment();
unpacker.extract();
unpacker.process_data();
unpacker.update_gltf_materials();
}

101
src/primitives/materials.rs Normal file
View File

@ -0,0 +1,101 @@
use crate::primitives::reference::FileReference;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// use bevy::prelude::*;
use crate::yaml_helpers::parse_unity_yaml;
use anyhow::{bail, Context, Result};
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct UnityMaterial {
#[serde(alias = "m_Name")]
pub name: String,
#[serde(alias = "m_Shader")]
pub shader: FileReference,
#[serde(alias = "m_SavedProperties")]
pub properties: SavedProperties,
#[serde(default, alias = "stringTagMap")]
pub string_tags: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct SavedProperties {
#[serde(alias = "serializedVersion")]
pub serialized_version: u64,
#[serde(alias = "m_TexEnvs")]
pub tex_envs: Vec<HashMap<String, TextureInfo>>,
#[serde(alias = "m_Floats")]
pub floats: Vec<HashMap<String, f32>>,
#[serde(alias = "m_Colors")]
pub colors: Vec<HashMap<String, UnityColor>>,
}
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct TextureInfo {
#[serde(alias = "m_Texture")]
pub texture: FileReference,
#[serde(alias = "m_Scale")]
pub scale: UnityVector2,
#[serde(alias = "m_Offset")]
pub offset: UnityVector2,
}
#[derive(Serialize, Deserialize, Debug, Default, Copy, Clone)]
pub struct UnityVector2 {
pub x: f32,
pub y: f32,
}
#[derive(Serialize, Deserialize, Debug, Default, Copy, Clone)]
pub struct UnityColor {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
// impl From<UnityColor> for Color {
// fn from(value: UnityColor) -> Self {
// Color::Rgba {
// red: value.r,
// green: value.g,
// blue: value.b,
// alpha: value.a,
// }
// }
// }
//
// impl From<&UnityColor> for Color {
// fn from(value: &UnityColor) -> Self {
// Color::Rgba {
// red: value.r,
// green: value.g,
// blue: value.b,
// alpha: value.a,
// }
// }
// }
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "object_type")]
enum MaterialContainer {
Material(UnityMaterial),
#[serde(other)]
DontCare,
}
pub fn read_single_material(contents: &str) -> Result<UnityMaterial> {
let map = parse_unity_yaml(contents)?;
let (_, output) = map.into_iter().next().context("0 items in material file")?;
let MaterialContainer::Material(mat) = output else {
bail!("invalid material file");
};
Ok(mat)
}

2
src/primitives/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod materials;
pub mod reference;

View File

@ -0,0 +1,53 @@
use serde::{
de::{self, Visitor},
Deserialize, Deserializer, Serialize,
};
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct FileReference {
#[serde(alias = "fileID")]
pub file_id: i64,
#[serde(default, deserialize_with = "deserialize_option_string_or_float")]
pub guid: Option<String>,
}
fn deserialize_option_string_or_float<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
struct StringOrFloat;
impl<'de> Visitor<'de> for StringOrFloat {
type Value = Option<String>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string or a float")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
Ok(Some(value.to_owned()))
}
fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
Ok(Some(value))
}
fn visit_f64<E: de::Error>(self, _: f64) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_f32<E: de::Error>(self, _: f32) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
}
deserializer.deserialize_any(StringOrFloat)
}

236
src/unpacker.rs Normal file
View File

@ -0,0 +1,236 @@
use crate::asset::{Asset, AssetType};
use flate2::read::GzDecoder;
use gltf::{json, Document};
use rayon::prelude::*;
use std::borrow::Cow;
use std::fs::File;
use std::path::Path;
use std::process::Command;
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::{fs, io};
use tar::Archive;
#[derive(Clone)]
pub struct Unpacker {
pub args: crate::args::Args,
pub assets: Vec<Asset>,
}
impl Unpacker {
pub fn prepare_environment(&self) {
self.args.check();
let archive_path = Path::new(&self.args.input);
let output_dir = Path::new(&self.args.output);
let tmp_path = Path::new("./tmp_dir");
if !archive_path.exists() {
panic!("Input file does not exits");
}
if tmp_path.exists() {
println!("Temp directory exits, cleaning up first.");
fs::remove_dir_all(tmp_path).unwrap();
}
if output_dir.exists() {
println!("Output directory exits, cleaning up first.");
fs::remove_dir_all(output_dir).unwrap();
}
}
pub fn extract(&mut self) {
let archive_path = Path::new(&self.args.input);
let tmp_path = Path::new("./tmp_dir");
let output_dir = Path::new(&self.args.output);
if let Err(e) = Unpacker::extract_archive(archive_path, tmp_path) {
println!("Failed to extract archive: {}", e);
}
let (sender, receiver) = channel();
let ignored_extensions = self.args.clone().ignore_extensions.unwrap_or_default();
fs::read_dir(tmp_path)
.unwrap()
.par_bridge()
.for_each_with(sender, |s, entry| {
let entry = entry.unwrap();
let asset = crate::asset::Asset::from_path(&entry, output_dir);
if let Some(asset) = asset {
let extension = &asset.extension.clone().unwrap_or_default();
if !ignored_extensions.contains(extension) {
s.send(asset).unwrap();
}
}
});
self.assets = receiver.iter().collect();
}
pub fn assets_of_type(&self, asset_type: AssetType) -> Vec<Asset> {
self.assets
.clone()
.into_iter()
.filter(|a| a.asset_type == asset_type)
.collect()
}
pub fn update_gltf_materials(&self) {
if self.args.fbx_to_gltf.is_none() || !self.args.get_materials_from_prefabs {
return;
}
let fbx_models = self.assets_of_type(AssetType::FbxModel);
let prefabs = self.assets_of_type(AssetType::Prefab);
let materials = self.assets_of_type(AssetType::Material);
println!(
"There are {} models, {} prefabs and {} materials",
fbx_models.len(),
prefabs.len(),
materials.len()
);
prefabs.par_iter().for_each(|prefab| {
let path = Path::new(&prefab.path);
let prefab_content = fs::read_to_string(path).unwrap();
let matching_materials: Vec<Asset> = materials
.clone()
.into_iter()
.filter(|a| prefab_content.contains(&a.guid))
.collect();
let matching_models: Vec<Asset> = fbx_models
.clone()
.into_iter()
.filter(|a| prefab_content.contains(&a.guid))
.collect();
if matching_materials.len() != 1 || 1 != matching_models.len() {
return;
}
let material = matching_materials.first().unwrap();
let model: &Asset = matching_models.first().unwrap();
let texture_guid: Option<String> = material.try_get_mat_texture_guid();
let texture_asset: &Asset = match &texture_guid {
Some(guid) => self.assets.iter().find(|a| guid.eq(&a.guid)).unwrap(),
None => return,
};
// here we should read gltf file and replace material texture with Uri based on texture_asset
let model_path = Path::new(&model.path).with_extension("glb");
Self::update_material(&model_path, Path::new(&texture_asset.path));
});
}
fn align_to_multiple_of_four(n: &mut usize) {
*n = (*n + 3) & !3;
}
fn update_material(gltf_path: &Path, texture_asset: &Path) {
let file = fs::File::open(gltf_path).unwrap();
let reader = io::BufReader::new(file);
let mut gltf = gltf::Gltf::from_reader(reader).unwrap();
let mut json = gltf.document.into_json();
for image in json.images.iter_mut() {
let result = texture_asset
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
let required_file = gltf_path.with_file_name(&result);
if !required_file.exists() {
fs::copy(texture_asset, gltf_path.with_file_name(&result)).unwrap();
}
if let Some(old_path) = &image.uri {
if old_path.eq(&result) {
return;
}
}
println!(
"Image{:?}: {:?} to be replaced with: {}",
image.name, image.uri, &result
);
image.uri = Some(result);
}
gltf.document = Document::from_json(json.clone()).unwrap();
// Save the modified glTF
let json_string = json::serialize::to_string(&json).expect("Serialization error");
let mut json_offset = json_string.len();
Self::align_to_multiple_of_four(&mut json_offset);
let blob = gltf.blob.clone().unwrap_or_default();
let buffer_length = blob.len();
let glb = gltf::binary::Glb {
header: gltf::binary::Header {
magic: *b"glTF",
version: 2,
// N.B., the size of binary glTF file is limited to range of `u32`.
length: (json_offset + buffer_length)
.try_into()
.expect("file size exceeds binary glTF limit"),
},
bin: Some(Cow::Owned(gltf.blob.unwrap_or_default())),
json: Cow::Owned(json_string.into_bytes()),
};
let writer = std::fs::File::create(gltf_path).expect("I/O error");
glb.to_writer(writer).expect("glTF binary output error");
}
pub fn process_data(&self) {
let output_dir = Path::new(&self.args.output);
let copy_meta_files = self.args.copy_meta_files;
let tmp_path = Path::new("./tmp_dir");
let tmp_dir = Arc::new(tmp_path);
fs::create_dir(output_dir).unwrap();
self.assets.par_iter().for_each(|asset| {
let asset_hash = &asset.guid;
let path = Path::new(&asset.path);
let source_asset = Path::new(&*tmp_dir).join(asset_hash).join("asset");
asset.prepare_directory();
if copy_meta_files && asset.has_meta {
let source_meta = Path::new(&*tmp_dir).join(asset_hash).join("asset.meta");
let mut meta_path = asset.path.clone();
meta_path.push_str(".meta");
fs::rename(source_meta, meta_path).unwrap();
}
if !source_asset.exists() {
panic!("SOURCE ASSET DOES NOT EXIST: {}", source_asset.display());
}
if self.args.fbx_to_gltf.is_some() && asset.asset_type == AssetType::FbxModel {
self.process_fbx_file(&source_asset, path);
} else {
fs::rename(source_asset, path).unwrap();
}
});
fs::remove_dir_all(Path::new(&*tmp_dir)).unwrap();
}
fn process_fbx_file(&self, source_asset: &Path, result_path: &Path) {
let tool = self.args.fbx_to_gltf.clone().unwrap();
let out_path = result_path.with_extension("");
let _output = Command::new(tool)
.args([
"--input",
source_asset.to_str().unwrap(),
"-b",
"--output",
out_path.to_str().unwrap(),
])
.output()
.unwrap();
println!(
"Fbx converted to GLTF: {}",
out_path.with_extension("glb").to_str().unwrap()
);
}
fn extract_archive(archive_path: &Path, extract_to: &Path) -> io::Result<()> {
let tar_gz = File::open(archive_path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(extract_to)?;
Ok(())
}
}

41
src/yaml_helpers.rs Normal file
View File

@ -0,0 +1,41 @@
use anyhow::Result;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
pub fn parse_unity_yaml<T: DeserializeOwned>(file: &str) -> Result<HashMap<i64, T>> {
let file = cleanup_unity_yaml(file)?;
let parse: HashMap<i64, T> = serde_yaml::from_str(&file)?;
Ok(parse)
}
fn cleanup_unity_yaml(yaml: &str) -> Result<String> {
let lines: Vec<String> = yaml
.lines()
.filter_map(|line| {
if line.starts_with("%YAML") || line.starts_with("%TAG") {
// unity specific headers. SKIP!
None
} else if line.starts_with("--- !u!") {
// unity object id declared on this line
// --- !u!104 &2 => 104 is object type and 2 is object id
let mut splits = line.split_whitespace();
let object_id: i64 = splits
.find(|&part| part.starts_with('&'))
.and_then(|num| num[1..].parse().ok())?;
Some(format!("{}:", object_id))
} else if line.starts_with(' ') {
Some(line.to_string())
} else {
Some(format!(" object_type: {}", line.replace(':', "")))
}
})
.collect();
let mut lines = lines.join("\n");
lines.push('\n'); // insert new line at the end
Ok(lines)
}