diff --git a/.idea/lib_um_crypto.iml b/.idea/lib_um_crypto.iml
index 7f78325..3ac3b73 100644
--- a/.idea/lib_um_crypto.iml
+++ b/.idea/lib_um_crypto.iml
@@ -16,6 +16,7 @@
+
diff --git a/Cargo.lock b/Cargo.lock
index 6ac8607..7443f3f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
[[package]]
name = "aes"
version = "0.8.4"
@@ -376,6 +382,15 @@ dependencies = [
"walkdir",
]
+[[package]]
+name = "miniz_oxide"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+dependencies = [
+ "adler2",
+]
+
[[package]]
name = "once_cell"
version = "1.19.0"
@@ -493,18 +508,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.63"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.63"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
@@ -558,6 +573,7 @@ dependencies = [
"umc_mg3d",
"umc_ncm",
"umc_qmc",
+ "umc_qrc",
"umc_qtfm",
"umc_xiami",
"umc_xmly",
@@ -639,6 +655,18 @@ dependencies = [
"umc_utils",
]
+[[package]]
+name = "umc_qrc"
+version = "0.1.0"
+dependencies = [
+ "byteorder",
+ "hex",
+ "itertools",
+ "miniz_oxide",
+ "thiserror",
+ "umc_qmc",
+]
+
[[package]]
name = "umc_qtfm"
version = "0.1.0"
diff --git a/um_audio/Cargo.toml b/um_audio/Cargo.toml
index 901de54..9e3ca16 100644
--- a/um_audio/Cargo.toml
+++ b/um_audio/Cargo.toml
@@ -5,4 +5,4 @@ edition = "2021"
[dependencies]
byteorder = "1.5.0"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
diff --git a/um_crypto/joox/Cargo.toml b/um_crypto/joox/Cargo.toml
index d5a5a26..70267a7 100644
--- a/um_crypto/joox/Cargo.toml
+++ b/um_crypto/joox/Cargo.toml
@@ -10,6 +10,6 @@ cipher = "0.4.4"
hmac = "0.12.1"
pbkdf2 = "0.12.2"
sha1 = "0.10.5"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_qmc = { path = "../qmc" }
umc_utils = { path = "../utils" }
diff --git a/um_crypto/kgm/Cargo.toml b/um_crypto/kgm/Cargo.toml
index 0e6488d..f7bb0c6 100644
--- a/um_crypto/kgm/Cargo.toml
+++ b/um_crypto/kgm/Cargo.toml
@@ -6,5 +6,5 @@ edition = "2021"
[dependencies]
byteorder = "1.5.0"
itertools = "0.13.0"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_utils = { path = "../utils" }
diff --git a/um_crypto/kuwo/Cargo.toml b/um_crypto/kuwo/Cargo.toml
index 9334307..c7d3721 100644
--- a/um_crypto/kuwo/Cargo.toml
+++ b/um_crypto/kuwo/Cargo.toml
@@ -7,6 +7,6 @@ edition = "2021"
anyhow = "1.0.86"
byteorder = "1.5.0"
itertools = "0.13.0"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_qmc = { path = "../qmc" }
umc_utils = { path = "../utils" }
diff --git a/um_crypto/mg3d/Cargo.toml b/um_crypto/mg3d/Cargo.toml
index f8f6d7e..ab6f487 100644
--- a/um_crypto/mg3d/Cargo.toml
+++ b/um_crypto/mg3d/Cargo.toml
@@ -5,5 +5,5 @@ edition = "2021"
[dependencies]
hex = "0.4.3"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_utils = { path = "../utils" }
diff --git a/um_crypto/ncm/Cargo.toml b/um_crypto/ncm/Cargo.toml
index 465853f..4f2b7ca 100644
--- a/um_crypto/ncm/Cargo.toml
+++ b/um_crypto/ncm/Cargo.toml
@@ -9,7 +9,7 @@ byteorder = "1.5.0"
cipher = { version = "0.4.4", features = ["block-padding"] }
crc = "3.2.1"
itertools = "0.13.0"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_utils = { path = "../utils" }
[dev-dependencies]
diff --git a/um_crypto/qmc/Cargo.toml b/um_crypto/qmc/Cargo.toml
index 5b053d3..e2ce0e0 100644
--- a/um_crypto/qmc/Cargo.toml
+++ b/um_crypto/qmc/Cargo.toml
@@ -9,5 +9,5 @@ byteorder = "1.5.0"
itertools = "0.13.0"
lazy_static = "1.5.0"
tc_tea = { version = "0.2.0", default-features = false }
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_utils = { path = "../utils" }
diff --git a/um_crypto/qrc/Cargo.toml b/um_crypto/qrc/Cargo.toml
new file mode 100644
index 0000000..b4918b5
--- /dev/null
+++ b/um_crypto/qrc/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "umc_qrc"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+byteorder = "1.5.0"
+itertools = "0.13.0"
+miniz_oxide = "0.8.0"
+thiserror = "1.0.64"
+umc_qmc = { path = "../qmc" }
+hex = "0.4.3"
diff --git a/um_crypto/qrc/src/__fixture__/qrc_network_1_jp.txt b/um_crypto/qrc/src/__fixture__/qrc_network_1_jp.txt
new file mode 100644
index 0000000..1030d0b
--- /dev/null
+++ b/um_crypto/qrc/src/__fixture__/qrc_network_1_jp.txt
@@ -0,0 +1 @@
+22F8DE184F1B3E0E55A89BF6DBC92B01605A307A1899CEC592651344D8EAA498FAB25C4FCAD877CC3D713440E0351553A3943786461A790338730D7C4DD161D74F092671CE352D8C1E147B14FF53945098D4A70BCD1E6B59EB2E3933A819B0D3DD3D2D2F92F3CDCAAE878526D9FDD3B2B14CEA2BEA9E9CB9589F823D5FC1F1AE7A25EB1780150772686B8BCBC99434648AC8D29BCE074CCD59CB95F7AE2DE9D6CD95CCA0B9332FF32D62F8F651F2B324E5859F38988BBD178E3C7C52492A168B0E653BB548E2A25E8FC25D710304C7A9B3EFFBE2806DDA1FBB2BD044670696EAB61D421B71BB97F12CDCDA3001172BB5687EE2FE0B51577EB244CAB89E112C5B945A9769201426B8EF74271754301FAE3FB933C3CCED9478823124C72C0DC02465120AD417D8BF01701D082B6F0302075AB5DA95458CD85E2BB5F0B99A8DADD3E252CFE9BE631A700E1870DACD0DA384BE69C3D72601986D9E30283D92D4364F60E53EEFFBC06E06F25AF81366440125B56D90B79E5E7DA193FF63484E9BF846A1A5756CFB8176BA3FB720D330884509EDE1E5E5E7D873BB582EA78FAB0FB8BD915029427DAA1DE49450FEBA83ADD12343AB399B35B311434DEEF630C804AD65A361E8700E227DA75DD50F915700BEB9D684EC5640C1278B89C4172802B8A38C39B3CB0A2D17F11CCD094BCD4DF2F535CF57AEDAE8AB703AA9DF59CE78A5E66B314395B50655B957C5B9EBD5A77113790AE3D2F33600C09F34240C90EA818A7AEEE6F554943F5C62A1B0971F4631793CD022EFD230F86219B0E63F35EEC3CBE096E12E7AF9904C7D0FEC5CFD34D879479271CF84A8531CB9CE1DC68F88360463B01B1B3CA3FE2FF94670933B7A47CBB3C408D68C7DC2E457AC3459B9A1FEECCC9AA21EDB89E8068498A2B5C579E53DB78299D7A0A952BB866CB21DA7B9DEB696877065797BFD39CBF126DD9B215FD875C5902C4ECD607F704EDC3437D69DCF60CCF3C039C2C8EA78F6B5332C17C19B02C47CCA68205ACF1A9DDB0B273A0F5242A5E225364525F8D9542189430554959D6039B78AC66A1613EDC5CFA1D11F27885893FB56491F1A5C477C2EEDFC5C76F661F1CDB2E0769858DEA38ABF1F766BCCCB41170C3D4184B13C6C2EB4F4A42BA9C48F5B489EB0FE4231C911C80B8B5998D332AB4F50E34DC06ED479A07497471379CF89ABEB2815FDA094CAE4069811FCEFDBC3CF26ACB5B120291126386D9A407B08F492BD8821670947653AE563F1BBD6C28EB7F71F78D2DB8E1599E6797E4E3E75CAB3314CD4EEB2027D223A8B4AA8994C0AB20D53CC4C3335CC5B19AD05FEDF3F8043975DE9EB93CB1A58ECD1D858075B8AA50FFF54AF3645C504334846AB772593F87430FC8CD3F296D05F4A11690537F2C7197B1DCFB34515099CD98087E7BDBCAFAB4D6C5EE7968282CED513C5A3D6B7F9AE7DA09BB54B603668EC4D44140FCAAAAF904BB182C98EB54ED4CEBC93A5054531EC49CF6B9A2422580B5BB2A7FCCA9BF73024B3811A742C2A4FA331C8D58A3B00E14ADE2DE4B75ABC18E7EF3F0AD4D7F566B4FA43D86A1CEDF43715A2195A042736C051A062F0444B943C5B713DB9777E7280897AF124ECBC9F4BAA7332E2B818A0410AEB99F29662197E786E12179FCCF2B8B0040438C69F81C11456CA7459AF8A53DFC30C2B292427731D47883E7F82A54A97C3410E767D0BDF734420CA2C70792DC843BEAD45295B663B340979CE166FBA2041F3D269FD455605E356A23A855FBE768D319C209AD9C261DA87934D0457BF89415D912F6AB87D715DC8C4EC6DFFB31F526DE5EB9FA9EB3F8AC695B090FBC8E84A7A1CA7423536A47DF88E38D899EEC12CA18BDFA2B8110BC4D474338906497050CE048BA2BFE079EB02AF72121D68EB4055810D3713D2D22AAF8462BC607CE6A9F17D2E1B3964590FB1F0B9D98B12556AB5297A13C158FCB5524548E662964EC41F8CCB3C2697814513690A550631D1D39164413C5B449DB5C84120F985F94A8C9CF75ABBEE448104940498561ED71390FBD3DF00C895DD09FA1AA7C668E633CC047C6596191410BB9F9C3CCE6615B73227B2D0464213EF828EA77244C84A899B946C1229F6E19BEA2268A42DA2A4E3A3FA29E608A30B31694FC1C76C8777B6558052EBD80FA42BAB52CC20B52A10E64C9AAACC13289793414F10FF9570AB9F2F2244F6109C2D3B14F64A4CC5E8209E5979D8740E9CB561639FDC32703DCA930242D51E0E1E8EBC19394BEBBB4612988B898E6AA5846C9F534A086EB9DFB8C69EF64B3DCF21D1C6B0E088C39C50AD7CB6F6DA2C1F0793193821F83C15F95879439897520D18C29C1FA6A89EAD3105F22D803EE4F0B00E5D50649C1E0B5F97D14ADA06414546FC5291F2CD72B7F0A4384B34D73970C8BC282ACECE0642CD4B8C6DBC2F60B3AF00AE0174F404F81FD07CB3F6223E89A0930D30BB9D04F0AB8C124A44B7CD63CE1F8A75CDEB40FFA31962B182A785460E0F03EDBEEE227ABD614CA1EDAD77FF91EA6B4D401987D29CFB6A1CC917CBB0647C2D50EF026AAF242F3BC35DA5208CBC0FD1DF02852C184D1C31C54FEB00E890B45CF6C3DD2F7320C1966F43FF4E38CC66767633FCF69317B56ACFDF6B4B2CC0B290D87A77870B0B775A63F116D3EC940D4B23183D84FE837C74F8A8594868CF9178946BAB715A2161A63AFF6FAD421CE29EAC1EB1581BC618BBE5B9DF7DF27EB5552DFD54C9AC7867E6C156F13BED4644F57B9FB09395A03A3A20B34E979E0A569950A098E09479A4083F11B9B4AA9C5BA3B1CFAD0086C647C876AA4FED71C20BF2D60CC78DE1CFBF2BF00B33A8C0E9608F111C9274D9822DBD49CC607646C735055A47BCAECC147F3B4E73915454AF4ED104479FEC5EC201D49A6C0B92DD66F0EACAAA38B20635A98993F87E52D0964D7AB13656D55973216D54D88ED7D56EF7210DAA155926D26597751955382885E7F72AD55F62F3DE265EE58D6C8592C20A0826F6F09F449B2EFB95AA9E9A7A52F5AAA3F62331DF47ABE8A0CAEDAB0C8B30077AA3A15224298E4BCA19DD4E9C77511B9F29AE498779EC48DD6C7790AE2246303770E563095DA2990BCEDF3B6C2A4349573F642BA857893D282269E331675AEFED2740AAF691CDBAD3A4D308469031ED9D659E7CF1F7D6E84AA71AFB681B9DC1812FDC8ABCFB90031D5E785E227106D1B64613DFB2124F482EC63AA38021E4E11EE5366865D7B90648A0AC0A2B8F766212CAE0815BD6465B6A0676798AB3B1EFD2963D977C484E4184FEFBC3E5C21D80FA7B3AEFCD2C5C7CD64F281C24B390B61C3CDF77691AB662FF375D4F87CC9D4865DC418ED8EBC685C9A1B4B23D03FC24F147CBE64D5B6D81CE2B35069616CD55E2D42FD7923BD2CD336005375F770B64961118F2274BF781C3A6ECA12B42647A8072F5C899688E783A731EAAC6FAAC786C1B38534067BF97CDACABACB64E782D757592003B4EBDF608DE368436955A0E95EE76C4F3555DBF1CD7AFDBC6D6993865DBAB021A55A742EA508DB6AE1B7702A2D169E65EAFAD0D8F105162919F8B446BF55CCD1A355927E3773F4D39247BD915BED7A0BEBB7F2F62CFA953CBA8794D3E3B20965F96B1A40652C9B1956B16EEF8E13292E32033931C885D813DB079E39A3D6FA2424FB00F94FBE082677D1D9F71DDFD052AA99C095EA9EC20A65590A4D96E544E010BD7A7325BA256C9D9C424A3D798EEC03BED24BAB7AC8D5BBEF344417C57AF7654F5CB76FCBCD0869704B1927FB5421846540C735D74109182CCDA5D8279097D904EB67ED13A04E1FFAA0D5C1EF7DAEA8692659122D6D9C7291760DE667FC9A045FE2CF4A9A6805CA940BC56700D5CCB76AC910806399D8D6A5D01EE68E7335B2E24D9FDA8E98FEFFA8633E69D96B86A64F3525FCB6255D1C00A1DEF5C45FEA3EF275198731B128FC44AF3C0655454114CCA333983F5EAC5684B2384C346F21E5CC8085C1CC12EF3040466D8CD912F0EEEC68430ABF6DB5EBA4E182BCC1F48E0C2FB28E41D7DDBE2F427C8EAC94FDCB42D6C4585190E47965CBE6CD37F9A4E7D4A6DE4D68097C160D4E81FE5DDB4CE246D0D1F8F09B7F5727BA560DE50B842B8DC3CCF78D6D676077D4B5AE3BA4460DF1E907FC1B6DBDE02A8AC5EC49AC7306586D21C386812D6B38183BE8ECB3BE42BCB7A01BE00DFE46CE5FE0C89A6660018C676590081AA2C5C2E205E6BC3DDEF9353BA1167AA4279D14C5715235AEB1841E364295D6EA341B02A9EFAF4CE4D86FC2C5D82422A6D8C5B9734C8E232FD33782595D3D1000F8A50CA773B5B622579A15EAB77E72C471D06555C296A718ED566A3DE9FF8BE75D588A0A4DD8011583B4E4FD068B3B87D8AA052F439653B477AF6571B846DBAC1B4BBAAA70ED2F2CBDE6C97A995DD5762B31F9BF863A2821530AD26FE180A6EF14AF7422FEB30329E2E4BEE7FC721E2835483A2500908B226B45A9AEB5EF52ADFAFD574C110B2A166A9F8281AA6B4DC96D4C682154557BDBBEB9BFF2E58AB7EAC9BD066C9D7ACF19B6CF6841B450EA05F8DE7FE45CAA4ACF6D1C776A02B2A96173A525B2A4AE7C3BA9EFC236641352F9BB07DFBD230B58BF5E6CCE668247A663BB62AA1B613A05FC827CC3444495096D9EF1A0B155837F80386F393E955A57F274BB591C82743706B51940E63F141250809198ACE0CC13E5D9723BD823AC8A43CD5828DB024A78408435D57BEE7257659094622192379076C96D186D9B13E5C42F980C8952FAB4B96DF7632CBFCE34853C65DE0E9B557BF5FFD6D4AAC343F470EB0855189C4F3D1CBB642CEA82DD3B3882112CD5A21502357DF66819B021809A962D865D2ABA7D0711B655F2D53E2E36CB487A046E3D2031C494FF1FC87C77EB9315A7E3E736588BB1300FDB97400EFEF7EC61D09E1551F76B2535F3D91B076DAD2DE94E4427C9706BDA07A61449F79EDC1FBEDF57740641AC293F12B31AB451627E3CCF9034184958F0862860EFE75667AE157F7AA7F60DF0A04A5B000B0FE33491C77845F76EF6DDB0DFD652E7905752B74277D5F35D18B91286072C0D7E9F808D22D14F3792402A712C8D1FA428E9F16E8B05AC2AEBF7F06641D5EC8D48832866C010827166D75543B8485C4CAB0A27C96819F1200EB00E61FDCF4499DE993EF144D6F6E9187510DAC563DDDA47DE5439D57C7448B80F5AE3A79CCCC0351E70625AA3C69BB1D9F26E03589E7F2037FC90FFE81BB3DDE2B5B1206DFC77FCC2CCF91EFB9C84B15AFB015BD804A7D471B01B2CBFB9392200975FC011CEAE224E2A3F4E72C844614AEDD6FD71EAE2320700E9635174EBFEDECBE8031BE5C1FA809B629E9E39EF3F7D0C038967D0423DE785CE6C131083E1FEF135CB3438FF1FA27270DDC8F1072C382C487F215B85A4FD83B8440DC7CFB7395B0701B1E712E048082338AF3AE0ACB170BABF53CE680502ED70E5DAAF0384CF299FB7D561AC471CFC59EFC96DD0DD44D57C3455882CAADE9A37A724E66B7C1769EDFF31C6B9548112A759122FE2304BB086F02059F13E15F17D8BEA068552373E31403552DE32B6C0A80AB8D0D489F8788BE230AAECB2B82F41875464F8CF679B88EB01CD9BED89A228F6BC7575D8CB73256F49D55A0ACB27326A9FD3BBCBF26699BCE327357D34578354CC5DB30D60CF0593799EB19C6142249BF1C6A4CAAEF52AA244E606311E08C13678E3FE2CD7C0CDE9C98AD2AF3F1465BBEE6FAE68A9285C81A8E5AAC728C1BE72574C730CE11660D98D2EAA786A9EC6768A350A9A176688FE56EF67E1534DBB54B3752A446A00DF2AD28A5AA526CAD40ABA6529E6EEB1A60E7D861A89C1013334D0C9449021A315C2B3BABD674773C15610312B4ED0AB3C0090A83CC2B32485B3369BC4D7EFD29404E50DBA01E774C8CB11DF6DEA4550372D01256DBCAF9DFD54E067263DB00E98395D16B6F239EAE74F10BA4564DA4C98A04E0CA14F09BADCF76628256637F1121CF00D90E085C04CA5E10C180B6DE9D5E7BF6DA7A50B20146EE663D93D46E13CF3A61D18530A886DE284E23C6E045BF1B286467A4CCB74B89C00BA3518B58684418D935990C7BAC23BC969025D6EE4E937EAE8B6A8F4295C792E24729B3ABC93B26759A9550565E5B4852CEFAA1D508D1FD2DD47BDBD437E4A443A18A24DE884C276B97383EDB6C660EA9FD677E67FF5E6319E74188D83F27E8E370B42D742519A4F10FA0E2792EB43017F8F54D16F03493FEEE0C8EC69C8FABAC10F946E6A637BDDFA17485694D3F0E459B0641A20B758FFFC95D50D097844252BE6CDF0327F22EE9F42ED7A12FE03554561CC64BF5573012664775A902DA438DB3999B1DA18F42DD4E2C59CE911BC7C6F3599C2E7B7F0A7E29B78CC05C2568804B361C3BB675F0C6F164D8699999F94871F7CDDF1E3BAA3B49913617F00AD19494FEE9C1B421D426AB63B1A7B5D0495F3F77CE3B2D340BCB138BF28402DEDF114D70BB6560644CB3E2FA0A01A734BDAFB1CF62F474A46575B5C946B65E38D8CDFC509CF4A34C1F3699D9A2D69365A623B3D3A1663555F4907885664AB49572A0F3DFD80A357886DC02A5EFBB3895C4B4B156EBFCB8139E861FE387795FEEA4DBD59577FA48F85614C17F848EBED092929908FE1809C008CFB8DEAA26F96DE28B6854517BE924D71E909C2F090CCFF9A6255CE3B2315D4CCE4570E9894604B47F6CC1335C0C67D6017916BC0AA74ED043EF28D90FAC23FA919AEB58B8858EDA9B373FC575B942B9DCE27068C6BBCC5E71919EF1326450A17A97B5A6398D9B962F143E0B5584F7116FBE9B6A7C04CE945139B63E193EC6F24045E168B940D4A1FA3DFE2A5A199C207FFAC0BE9F53881E888D6261D8EC2A653C2C55F8395405833647C45E2F6A1D530FC6A30693D0E546BB972DA4AB8CDD2C2D7465E10DEFF8FF996E04CC2D37941B2981A19AF183009E874ED53298DD9386E849894D05D98B26793AF925DB8917D6FCA51765A0044357ED05EDA7510AB040BD4D9CFF4E9B83C2F55E3DD13E52AA60C00D54EDBD30D2AE355CA6F9A26C7A863A64E47C68A3C74961FCB6EFF7EE5FDEDE9BE73E4A65B77AFABC32D183ADCDEC605C1C180C5B5807944A2076D718927CA777B5885FC84C4CDEFCA7947AF5C25903A0B76B5F97B653D219E37305ACF23F942A4926FA0ADD24888AE1FFD2D3ED6372EDF6F7BA58192968D3FA63729303692CCC8639370402F77969B473843BCC7EC159A6D5A31116785835F3B78173ED086C19817C2AA42C89CFE428A4189FC03DA05F0091C81D5861D13B6FFBD0B78393D35C22CA362872FC9EA72CB598487091FB8CEC23988B8F696BF809065A66050473208A6CDD0A4BE0517BA463A03DB5B86B5FC15C4D11028C62471E6012DB2B6AD6D920461716D3071020B9FC145D5F8B525E6B3C8ABAF3BA4708D1493FE2E8B61DAC
\ No newline at end of file
diff --git a/um_crypto/qrc/src/__fixture__/qrc_network_1_roma.txt b/um_crypto/qrc/src/__fixture__/qrc_network_1_roma.txt
new file mode 100644
index 0000000..b03791b
--- /dev/null
+++ b/um_crypto/qrc/src/__fixture__/qrc_network_1_roma.txt
@@ -0,0 +1 @@
+3ADA47847D89E54BC157E7B60D363D02BA707F4C827F14DFB54834DE98A660D93C5AF7840FC93E8D3A4CA97393E17FC4E370E32171FE8792D1CC00015FBD2544C952D1A45C88D44FBA0C781ECF1A5C2D78EE0C37C9CD899CEF302B035C1EB69B29DC7C54D02463D2E4B8DC8E644AB8CC3F02E1B94F0FDD139C8483599C6D511E20625FC91A73D0F39CD9A0339DB239B4D0C9D2A2F762AE8F79D0D915E08C30BB5848B3873B35AE4AA810C252C6E861A35108A3C702B054EEA91189F4B530A60C88AD7A08F3C9395E07312108E3DDE458D34E65245FBAB0C09C150B589C27F295AA725E2D6B9F22F594F5FB64D46BA415EB4ADF960BE5916B3AC9442DAC193EF2C244A97E49B56D504D157368BADAF4662B826B754F826C9E655B72EABD40C23274757F52B22D8E0FC7CA7E319F28CDFE764D5986E5A5872DF71917320694E1A8EAD7ABB635D66442B6765DE2330E7276678BA9EA64A97BF4170DC435E6E9011CD70524A07748602FBEFE7774EC0F778E7715589EB06D81B92C6C8D5DFB18A3FA0E958AB227E5E31A0915FFD3DF9F35CE27154B5644959E0CECD81DE2F13A444FE34E465746B259B80A542847F3AAB1A44461364ED0E239B4C3902E2C30402899BB8A75AE038B34D6BCF991844D906208AA28FA9C36A298AD34A4E33EAEBA61658D86521CEC140E733B0F67397A910CCD95F98081D8D4332159BFD1934C512650EDC9EC90CC5BDFEA8187AFA102C2E5E8B3225273C48941D41B1D8F1075144954E21B58A33FA75A9F51ECC4F6FF04113485653F04EB1D621F0E376F1960F63B33E79B3DC0CDB749BC0E05CA94140107E1A08E72F533F579DCDB44E5187F798C326BCCF7391BFBD1620F3AA58C1F9BE8188B7CF376B427064A0392D0C55638F8DEE61490A429978E41C58297A5C3816532DA581FAB89995A568B6A0A54330F2E870845B8554A12C5B80A00B400B8B77289BAE2DA113BCC13C1A936E20269E06AEE2926FC188AEBB18B580C3D11F9B68A4BE160944D479840C28CB6338C6B2C0B5961915770D01B7648488D52DC291B164D3AE5039683F67D4C61A6E28C087163AF3A3B8F40DAF661C1A3A33CD2623F7A31EA116E59E117625D57EEF1717E02712DBD7DF056379554DD89C1FC3995BFCAF41842D6B03711AF0C2751DA8AC680B25852581637596BE7565A9EC6AD861A4CCD74C9338692589D249D90D27B4465F4829AC8DB68FEA878BA410922DEFD026B04A8BFE3FCC56C4B27FE4A9F4D91806B21499016778D30529A8A5ABBF4588FA84B8F641E21B7972DB430393B7CE64A9D63A66F0E03B486C625C417117170DA82F0CEE17D5AE4E35FF11404D5FCDDA84EE60B088565D1870CC9B20A59334DB0B480F209A1564F90CF15474035BF0C865379C15A2EB82B2C48C731880F9282A571B1037685D3D6F354B91C6B1F7330D7DE374281964FA184EAD8C5A191D50592D39D587CEA6F5367448AF5B26B797903833434F454D29C2AC9748BBDA296CCFB0C8C3574F72112311878D29C385978731753CEB40378860992DA6E4D43D3854A6E95D71500DF1D299A8DE0BFEB88D6F282FF703FE3D240C5885AF23AB18B5AA6088229A5F66230F5A654BFBF0F21ECBA9D42708AE5B953AD15B1254AC62265EFDAD29DB602EBB304806EB487F72409F978E9B0F2362BC15FFE8AEFEDDC8416DACBC35E3E2897DEA8B315C2B11EA3C4B2F79FD338E1C9A9B0FFCC33B0322344240A97A37406E1A25386DA32917A5DA4428385A45E2318E7730D933E8CAE67292E3BDEFA20C80F665C412AB1DEEA5A08B5207FA559305865DC3D3A963F284A42D1B265D906FFF27EA1528E4D898D3C907EDBF55BCB2E343983FF0FDA37B876D2820777577ADC8A69F0FBFDA8993C61261B629378639A456A8F999A1A1903C0BDAC7116EFBEBF5262EE583BEE8A910297B6AB981808F4F5879688BBBF89046D920D5C39FDE03B24C073D3BA72BCD06B5BADB75CD5051E5BBB3A2B817F933B3C495D7DED017C800FF4A82D6A206CF6B7499FA31CD1A71BE6E02EF41D88F8A75DA990699D803CF08A8F3625FAAF7BF5601648D62F4FD9CA4A128DBEF9D7F6C9281C8429980D8D48B1B6037AA72693E73A485BB87D33B16AB35A5A4858CB7C918108316812B19D492D15AFBB0EB8E82F32BDC1E13ADE691B9A460309C0F6634961B8FDF39916CD7217A23C03856E200424CF794D2EAC141E4E597CB9385E50EBE0D74F9D0EB5F4AB260EEED15E3843FD967AD89F4F063F30FDA63F5FE598CEC72B1E6E74A8EDAA22BA22033A0A40480F6FEC413910AB3AAAE2DBA2712CB467422322BC0D4487CFCD2211E7B8D460443DECF80FFA8DCCB81859336F6886B3253A78200A4DF47163DAAA7ABFE14FE9A174A5ED28B745B8528A15D14C3C2BCD204088A8665FCED71E71D0F14A192566C33FABC72BD84655EF537EB856B2B6A53D25FB3BDF79CDDEDEE50DB59225184B44018D2791B5D079EA0979947F1FDF1778BEF03969A12CE56E9F88E25DBD3C04741CD45C13EBE5C41EBC0259282CB8EEDE525AD710C378EEC7F20106725173C04E73C2C78A6E2CEAF278A4923BAE9B3BCD280D450C43E7FF945F5C78DB8BE6F2374EB61BAB930DA5536535A1C405516D6238041A03BF705F6C595AB4F5FF9B4BDB34008711983C729FED951B751A87D17B4C5D2F271A935E009B7090753AAEA669C678DEC1DC304B894D7526A198C9F683C4807CA2AE8A1CA27116C493A732F8237779B91AE9E12601A544720609DFF4A8DAE3C42FAA59E188C7E923A38B991C00FB8361DE82EA65E8EC0042BBC16DB3AEF0F4323CFE8134985DD44A154F413917557C336067C116FEE8E9E93BE118232317D7111CF4256A1DE7C7DE092B8E48F615D9E9C14ADFEE2D53ED5E261D8832BE14BBBCDEA294DBF43556B3AB8C9BD8C0644C97CA0FF7E109C30E5EF0A20CD61A2F32B62FF990BC2DC26D08C26350B18B86FB38D067556E7997C01B82EA6C1EF22487079D9A124C2935D58FF53C2B52938B9EFBBC5F387D5AEA00EF00EC30EAF49A4228E2FD6B9EBA98AD944AA043B74F93417425F31F75A5DEA3544F965C4C7747015134C30A48B2161B95CF77550F2F1AFCD044182295980B8C8601B3FE5EFDA6737E55B38AA3B38AFF7F6718BB17FE4CBCCA93AB260D4436F001CD1EAECBDAE66151B1CE86793B6828CBFAD615BCC1E026656900CF420488E2548BAD7DB11FE3E83CAB7C1B37A93949CC3DA92499B29C791F185EC1E18BD9B7BEEAC2616DFF1B56E2D9899ECCDADC93A6D070682A66D1EAD3FDB8A49DFED828B4454567FAFDAECF1ABE8C25D48BA92ED7A4A0B108CF9D8211D9715FF6EE14FE9E194860D5AC244C79B44C5A87D6F4F8B990B9D46613822A1340FDD09F70682A5208A35492272A4D75CBF4D6B9A21024C4EC0F642FE792D01C39B3B558251F831F7CA25903ECDE5EC4AAA6C5A1712E9F3BF5C2A36A0A79445A1025AEE0B3030921ACA6D11A43B4937D846B25A18FE3E2F649957B172C6D4C056D6FD739E193F91B4BAEA9D0A187D164C13F7D281C1B5B4F5F69F256D43A7E5B9C5248E9092B7845F293E1E217928CEF441E8436BFA3F879A892E92601C47C5BA0ED89AAA84AEF54D5AD123D6F2C75292E9C63DAD0AAB0253BB24DE39602EDB3CE2620DE21CAE4C2EA3CB42A101541ED90E07C87ED8298CD90D6447AE3A561E933CB185C782D9B8448821E8457BC54B8761692C503C50407006CEF5066A07E567DF808FEABDFE748DD5EC7F3476D19AA868834855167FC83212F64D228B567B9103CBC0CE1BB2E29BEDC2FD4D8C74EF3B7E33300D2569C7BF4CE3378C6F39BAB3C44B8768A0B736C32A04C9DB2F66CDDA6914E9338C34E7F38345762D7B9B001B3CE5DEFD8847D378FC5512094437495BFF1A684D9C5D058ACF7703B2335A8599C262DC474739DA7430227B04DBF00133189962CDF56EEF063025035AD955F03BFE43DD995394E2FD3E7DC117429363788AD424D3DFFC44ECCFF817A7A083BEAB7362ECBB1D094AB7997932FA1C6D64A8D60F7D8ED789F80A7F5669E8EB3E72DBCB6D395A4B447726F0C6676C7EB00F1EEF918FE869B69E43B75EC01FADE163523525F3283C53E0888B0D961619946324336C4BAB434C878701DA77F672F778D9BC1C376457718800F9597D366FFBA3C04ED0E085ABFEDBC5D3366D9EE2E2336562AF2BDA720D972FDE9D3B17FCC11E5BF024A50FD31E13C6431F8D91C4938FB1523CE606AF02386FB128639A33B8E9A33C81F9535CC20A1F8DFF39050CEE969CAC31C16900375B39DEF7042269C8B6F6F5536974BB4E2DDCBDBE6184EE8D1BD7E9206C6D7625814BEB5411801DAB7C87E2C98E3AA402305D63026D257DBB7F61B12838CCB5398614209FE02EAA49DD494B96786EB7A9B2F7D45971915697F54515FE4CE1EA5ED01ECB30D389A6534B9A82F3F011FE1E18642C9E9D2BB200D1F711011128DE485B6B82D9DFEB03B1DA06EE973FC201F4F5B50AACB6D0AE17DCCAD64AE964D8313778F4DD1E581662A4E53A083C4360E8B7A21BD490E78AA3E29F77B5C1105DDEC930A69120AC509AC7B7761BD540159EFE54DBEFB52EC3846658C7088761264D6E02CE79E6BB36D0ACA38D6C0C9E0FFF37F5ED17BC73226A8F39D5FD08BDEEAE31C66FB546F0126AD79D50180732E43D72CABA8A0875E37B81ABE992346481678467945CA7758E060DBFA475610E43D8638ECAFE65B15523D218DE4F31A260F5F60F37463D6CCC320C08B2FEA2D3E1F579C36A0CB7D929EDC33E5707CFADAC7D85B405437678191622FF2B35054B163A8848FDB5900A614C629E3F73E0BD24ACC82319F3081EA5F16D56EA7577E128A8165A7E15185D9D3BBFC9EB987F5374BFCB609CE81A3B1AB36CFD8B3571CA4A381BC0170527CE81CEC0A6B57FBB0D701ABAB5A5BEC301ED82F68E293B8496BC746EF9F5C5D7935E66AFDEC70C148BCAEC2AE3952D6EB4D60373DE3E6A20FE8BA56CA1F9AB335CFFC2F63DCB9788A340725FBC8E54CAA7143501EDCB9CE487178B9DBB859E6B56BAB39D20EAFA28D6DF336B148D1D0AF6E5BE3EDAC14CA15096528C1034C1DF203B636C54ECB46B7295A3D0FCA77D59C53FDD3DFA6B3591B920ABDF7D84864BBDE16B8CA985CFCFC61DC1E1906BB61DCB64D072A4E2A76E917DD31CFF63E1DCF600D540D49B8B438B19097B2F69C1D935FB6D6DF6A296D1CA8E923BEE3BB7B39D4630420D3A85CED99833DE7B5100EFCDED98CAAC603F7D08D2CB5404C67EAEE4CC7DC17546B68F1BC3A148DB2CD2807EC155A202207A79C05B0D28816D21DB48C835C9A4EEEECFE0CBCEDDA0F194F6B75F98356F3847CF5B5004A0190D59CA8692A49E81872177D828FBDCAF2C868616B7A8E0DB8237EA89BECC0939FE0E2F8BEF4BDF939BC65E7C257B74F493C54A6C914ADFD7788230EFE5719A8D75D5730221AA78CF518729F7E88C8CA7F8AB08D76F351F04F08A82D53A6DB9CDEF670F1876D8247BDA5383EA6E2D5F9678066F0500C8D01304638EB379BEB0E118F84A6DF2F81E1A7307F5150B8B5C098B151BBE07B3F258683D124716A2780D90563FC15187C38F804E0A7A352DA3F883B2708FC0C37740ECD41583BA5ED6EE8475602D8FDB5117E25C41651ACE32FAD1378AB9D7A88A9D194D5199C2D31228FA5744278E7D91AC4E762DDD7B89C7CDA0A4A3AE606299B00AB319D71FC107DC975B9291817FA2DA0221A4AFCC55733A829578FA91605BE021F82CBE37ABD07D3DE230447EC59659A0810CC9088EEA6B1505E97100E8C563FA3D23A4191AE012BAD2BACC1E542B4BD60612186B4AD951FCB0AAF49F5FC4DF3575CDA23629301CC15C4D8B2E1C4ADF79FE3F63BE99047BACB464424DC5A22B7F529EA6CCEF99DBF900DF21C48A43C2240D4BCE0D51765CF3C2EE9CBA5FBB71AB45C515F794249EB22BF7E005EFD52413AA092090B39CCE7119683DE6D89933C3A2EA9194AD523EED76C3FC1F7C74445F097EEE6406F67982E242D922A126B135176FCAE5203524B333048C4D036BC827BA68246E71B581879BC3C08B164DCFF0A192D4732D0DF82501609DBC0D17FA25BB4785886192E8C834E0F836C6017773C30C4E9C54C718F77099887849A87642151080B575D8605CDD5F2C68AF8EA8B1BEC1AACB6EC4EC007DAF9DC3323887E51CBF20CFCBA3067DA8AE40EDBA967A135D6A3841FA584EB0EA78F26ABF7214ADD70DAFAAF6108E5172C633E32E14215F5BE412BA93F6881FD9D42AAEE7F2C55724DEB4110EE9EB2F04A751749D233992B3E2D2D9F97D182BCF98787FC2F75FA1A0BF5E20FEC471B558DF975D0C6C5689D4EBD52C87E64E8AC7000C296C65CFA09BF898D8E2D41B379E50D312214C2D9880F5821149BC288EDC7EB9D28BF65E7CDC8D89306A34209E508746BBD8D9F145CC557A4594070E775236DB3C548FA1E55016BEF35AF662E4C51B7A76F439F9BC170F2CD6F5F0788E20D35BF090094485B16BFF9EFC17660DE57399DADD967439D01D079296E82FAA7B4657D99134D8DCD83E84965A64E6F07863347284F6BD887D0B870253D835971E4CDAE9C13665A4614FF39981B8C60331B33B565DCDEE5C0EEE071AE48AAD1ABF1C1DD68E0500076667E76A0A676EA5CCC6CA6FB3A807AA88165093FDF571D98DA5F2646C91AD49B9518E0057C0E83EAD35D68E6B222104E6AF27BD2AFE057F733AF9E3C9D4FEA52221CBC784B04E61FFF636A59B9F13090DD0E75BE8E127F74C815506AE05F76DCD37BF89C1A327247FC88B0E40C206D7037291AECAD6D798297A78B0F8769228297E312C0B86B621BE5C3223232819E1C0873D536E674C657AB6648C673E2405526CB3C3F10B919AB7976B64B7761DEE1DE7D2D5DE1FE223B182D9B87BB6FA520DEDFEA487C20BC0B760070FB8411688E88ED9437A464D84A2510008B42E8EB5BD8F67977F546FB066E4BEE07B4261A774DD622D5EDDFCF4E526B23B5BA853BD716106C2D477A5CD937CACAF40CEE881B1147945DF7FCDE20EBAA31932BA7438FD4E64F6C5108ED5E4BA5664B3CB8E62BA42CA6114659D8BE1F5AE2EA628BE032B5B3568D0F03229516F9A47BDF7536DEC8B4C77412550A4F2517697EE928AE409773C7DD182446F67074612BCEDBD31D73BCF89E89EB3650E74CC778FCADFB6286E6E181F76706C0FC96BBA7507F269277472533C5EDF5BE4F2B608718A415AF2A547CAC79A0F87D5CA2D97393479D8F075B55A434DD0FDB9DAC8A7E7550CE65544D9BAF750099E6E31C904426145CC1A3
\ No newline at end of file
diff --git a/um_crypto/qrc/src/__fixture__/qrc_network_1_trans.txt b/um_crypto/qrc/src/__fixture__/qrc_network_1_trans.txt
new file mode 100644
index 0000000..e70e2f3
--- /dev/null
+++ b/um_crypto/qrc/src/__fixture__/qrc_network_1_trans.txt
@@ -0,0 +1 @@
+B2C78CE32CF1913A99A770B577281231519C5CEA3E75D8B9E80B77B6B6DC5857E948004C2C3D1BC62F22DF38BB8AE5BE33950BAB349485EEFB1CB2BC92657302BF90B85913E2349699AF483235ACF654FC89E8F1D32963D3463CD98D1CF4359AD509CE87B157537CE60F56598AD498D2AA4F46FF0E3948D5F88081E519026C3D952F58ED5E5BCA16827482D37BE303D39091BA5CFF7A1816AA3D25F3E1668BF4E4EDCA0F5FA3833536A6ED3A4D9C213B911BE32B33553C7D4730753E4023ACCEB54AFD6FDE8316F07AB12AC4C53147D26F2927F1C32090711B4C0F5A6B25442CA687C9BF0B5210D51F2E9735E2BEFD6C0C8BBA24FA622AA260851AB2FA48155193B6FE3BEB28BB5604E3ACBAD074398C3FD92E0EB439C87CF730044C550AC3580D228AE18178FB7800F809D244EDB18BE65DD4EA1DCA7B9E8F0C8ECCE028D3C26BDA11D5FE466665C124BD21187D0E000B127D59A6CADECE061F3E327D21CB2811E8D9B82B7FA8BAC20C3839C28A34FBB1AEDDD2524D393363D892F37032C2D1FCD45C92BF1C19643CBB2D58841C8D56DD8C43F03CE44432565BF91916DFC94BAB104A9EBE0B40731CA5F2D8B1EBE7A09CC7DB66E384F9CFEAE38ED674B8BCB62D095C838F84781624598BA7AD028A1D7A924E14B5352D059B5A1AFBEA82C46CDBBCC96FE0265D775ACB34D25D7181FDA6C06952C5AFF7DAC6164C0ABBFF048ED0EB1997171D944AA6F6FC250DFCB65719A6E5F27A55D0024BD46472403B3A833669CB5B50E44D1F4ACE79C55209744FFF8C3EFD9B26B805C9C97CAA13A4F9CB2F1A4841817648BEA9527C72492FF33458DE512E2BA4A578718D983DD264AE5FF0A8BF2E4BE121C4D862017A8CBDA32088B3A922C2B7CE3A7BB01DAB6AAC48AC42CF3CB1A644A5DF083D4313DD5B42439E0DA4A65A35FF914B33ECC7D4B639E5031AC3F4CCA13360F0EBAB4F3C40BC47B7011C4589A2D493C752B10DF637B21216E7213913ED438D500E1BF3FC60FCC95623924B833FEB06B52B7D3000914EE749272087A34330F801BE8C7432A7CE2A234F9D31354A9AD2449F1097B3AC1F25DCDB0E5EC8246B5065A434B9620D245A132363BB0B4CA4A773526233EA7B429E2EC84C266B235DC01E13FC342A2FA2D26382ABCF0FD93F9A05B3FA0FCDF6FAA3209D5193871187890E2115AEB65F543E6AA7AF8E22C3552AE3C81BBEA9FFC85AFAF404573D60A6523192246FCA2015DE5D8E4B22436F70AE84C458A6AD3A71932CD035D05AA6CC98A6D77B5DD7531DE9BBA04960C3C3ACD610E3209A0D09995B0608DC03D8602AD1CFC2BACA632AB07CD10D3D489F33F30753FFF930F07C58182CDDEFE417A8644F71C1F637B99E4BC85FCCB3D8966DA11849D56605FAAD7D89649753447DC258328D02D3972BAF5B4C8AD0581FE1AC3F5E1975F815A4D66F61A6F0AC8FA3D2F806063AB0054D43018592059B79F74CE032884F584BAD3A9FDAD5EDBB35FA7A6756A85E9B102F9ACCEAC467358F3E34734BBADD50645E6870F84B375226A3D68C29943856A32B3AA138C6ED1F4F17DFE9944D512694E974784344DFA32D4B68118779363CBB4D9AB6D9DB545358E39A5B2E4A2F98F0A7506A9945F6C3549C1DCF563A125D7CF3E42D25F60D44D8C6C00774E1A792FB62337595A31742A7D5CB9C84E45487995DF6ED239A0DD58310D8F9398BE23EEA9608373D3490830CD4B5659E1DF896A0E4B934770483482251B2D3C549630D4E1E1EA54DE95414627AB6A76020A27E0D96D5685DCD244071C3D2FA22431B53313FADE7B3D715C84547FCC9E329FDB9CCD19EFD704E8288B8558C7DC626DA319C18829C65777BC6A53843F3FCF78309CBAA748C75E124F4D839325455C5034F47323124F4A498A090562583C2F7409C8EBCC0C539A4815A1F740297D5C6E0FCBA4081A6B0C76D276E774407A4B7A1A4413EC948D87F53B2BA87D23828F899836332DF86AEA9A6D93208AA4317B4DEDCEAF7D8413B29476DDE79296786E8AC8EDD7306C67851AD7E7EEF222F7F7A4D3B9005CC9A06FA2E9F0C36753C125F4FA8CD42F9607C813384F2AD8C2C79952E5A3CAC41EC159890864C20A1F43F10406F021EF9227C187939E6E2FC7E9B91A16063D616A7B988A2FA65EAA5CB93EF434F2D0DDD763FF1EB4C2E04B5B9D0B4D0F3A5AB8834B220E98B89C3F4EE61C2FF52451213DA4EB7ECE204947CD5A79DE6ECDE7FD53B80F7F9DF4797EBBD9A6FE9FE1D4898276D
\ No newline at end of file
diff --git a/um_crypto/qrc/src/des/constants.rs b/um_crypto/qrc/src/des/constants.rs
new file mode 100644
index 0000000..a7b94e7
--- /dev/null
+++ b/um_crypto/qrc/src/des/constants.rs
@@ -0,0 +1,94 @@
+pub const KEY_RND_SHIFTS: [u8; 16] = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];
+pub const SBOXES: [[u8; 64]; 8] = [
+ [
+ 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, 3, 10, 10, 6, 6, 12, 12, 11, 5, 9,
+ 9, 5, 0, 3, 7, 8, 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, 15, 5, 12, 11, 9, 3,
+ 7, 14, 3, 10, 10, 0, 5, 6, 0, 13,
+ ],
+ [
+ 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 15, 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0,
+ 9, 5, 11, 10, 5, 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, 5, 11, 8, 6, 12, 7,
+ 6, 12, 9, 0, 3, 5, 2, 14, 15, 9,
+ ],
+ [
+ 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4,
+ 11, 2, 15, 8, 1, 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, 11, 4, 1, 15, 2, 14,
+ 12, 3, 5, 11, 10, 5, 14, 2, 7, 12,
+ ],
+ [
+ 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12,
+ 10, 4, 14, 15, 9, 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 10, 7, 13, 13, 8, 15, 9, 1, 4, 3,
+ 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14,
+ ],
+ [
+ 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0,
+ 9, 14, 8, 9, 6, 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, 15, 6, 9, 15, 12, 0,
+ 5, 9, 6, 10, 3, 4, 0, 5, 14, 3,
+ ],
+ [
+ 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7,
+ 11, 5, 3, 11, 8, 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, 7, 11, 0, 14, 4, 1,
+ 10, 7, 1, 6, 13, 0, 11, 8, 6, 13,
+ ],
+ [
+ 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10,
+ 15, 6, 8, 1, 6, 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, 10, 9, 15, 5, 6, 0,
+ 8, 15, 0, 14, 5, 2, 9, 3, 2, 12,
+ ],
+ [
+ 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0,
+ 14, 12, 9, 7, 2, 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, 0, 15, 6, 12, 10, 9,
+ 13, 0, 15, 3, 3, 5, 5, 6, 8, 11,
+ ],
+];
+
+pub const PBOX: [u8; 32] = [
+ 15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12,
+ 29, 5, 21, 10, 3, 24,
+];
+
+pub const IP: [u8; 64] = [
+ 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63,
+ 55, 47, 39, 31, 23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52,
+ 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6,
+];
+
+pub const IP_INV: [u8; 64] = [
+ 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
+ 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26,
+ 33, 1, 41, 9, 49, 17, 57, 25, 32, 0, 40, 8, 48, 16, 56, 24,
+];
+
+pub const KEY_PERMUTATION_TABLE: [u8; 56] = [
+ // key_param_c
+ 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59,
+ 51, 43, 35, //
+ // key_param_d
+ 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27,
+ 19, 11, 3,
+];
+
+pub const KEY_COMPRESSION: [u8; 48] = [
+ // part 1
+ 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
+ // part 2
+ 45, 56, 35, 41, 51, 59, 34, 44, 55, 49, 37, 52, 48, 53, 43, 60, 38, 57, 50, 46, 54, 40, 33, 36,
+];
+
+pub const KEY_EXPANSION: [u8; 48] = [
+ 31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17,
+ 18, 19, 20, 19, 20, 21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0,
+];
+
+pub const U64_SHIFT_TABLE_CACHE: [u64; 64] = {
+ let mut data = [0u64; 64];
+
+ let mut i = 0;
+ while i < 32 {
+ data[i] = 1u64 << (31 - i);
+ data[i + 32] = 1u64 << (31 - i + 32);
+ i += 1;
+ }
+
+ data
+};
diff --git a/um_crypto/qrc/src/des/des.rs b/um_crypto/qrc/src/des/des.rs
new file mode 100644
index 0000000..9666df2
--- /dev/null
+++ b/um_crypto/qrc/src/des/des.rs
@@ -0,0 +1,148 @@
+use super::constants;
+use super::utils::{hi32, lo32, make_u64, map_u32_bits, map_u64, swap_u64};
+use crate::QrcError;
+use byteorder::{ByteOrder, LE};
+use itertools::Either;
+
+type DesSubKeys = [u64; 16];
+
+pub enum DESMode {
+ Encrypt,
+ Decrypt,
+}
+
+/// QRC's modified DES implementation
+#[derive(Debug, Default, Clone, Copy)]
+pub struct QrcDes {
+ keys: DesSubKeys,
+}
+
+impl QrcDes {
+ fn ip(data: u64) -> u64 {
+ map_u64(data, &constants::IP)
+ }
+
+ fn ip_inv(data: u64) -> u64 {
+ map_u64(data, &constants::IP_INV)
+ }
+
+ const SBOX_SHIFTS: [u8; 8] = [26, 20, 14, 8, 58, 52, 46, 40];
+ fn sbox_transform(state: u64) -> u32 {
+ let stream = constants::SBOXES.iter().zip(Self::SBOX_SHIFTS);
+
+ stream.fold(0u32, |result, (sbox, large_state_shift)| {
+ let sbox_idx = (state >> large_state_shift) & 0b111111;
+ (result << 4) | (sbox[sbox_idx as usize] as u32)
+ })
+ }
+
+ fn des_crypt_proc(state: u64, key: u64) -> u64 {
+ let mut state = state;
+ let state_hi32 = hi32(state);
+ let state_lo32 = lo32(state);
+
+ state = map_u64(make_u64(state_hi32, state_hi32), &constants::KEY_EXPANSION);
+ state ^= key;
+
+ let mut next_lo32 = Self::sbox_transform(state);
+ next_lo32 = map_u32_bits(next_lo32, &constants::PBOX);
+ next_lo32 ^= state_lo32;
+ make_u64(next_lo32, state_hi32)
+ }
+
+ /// Create a new QrcDes Instance
+ pub fn new(key: &[u8; 8], mode: DESMode) -> Self {
+ Self {
+ keys: Self::derive_subkeys(key, mode),
+ }
+ }
+
+ fn derive_subkeys(key: &[u8; 8], mode: DESMode) -> DesSubKeys {
+ let key = u64::from_le_bytes(*key);
+
+ let param = map_u64(key, &constants::KEY_PERMUTATION_TABLE);
+ let mut param_c = lo32(param);
+ let mut param_d = hi32(param);
+
+ let update_param = |param: &mut u32, shift_left: u8| {
+ let shift_right = 28 - shift_left;
+ *param = (*param << shift_left) | ((*param >> shift_right) & 0xFFFFFFF0);
+ };
+
+ let mut subkeys = DesSubKeys::default();
+
+ let key_iter = match mode {
+ DESMode::Decrypt => Either::Left(subkeys.iter_mut().rev()),
+ DESMode::Encrypt => Either::Right(subkeys.iter_mut()),
+ };
+
+ for (subkey, shift_left) in key_iter.zip(constants::KEY_RND_SHIFTS) {
+ update_param(&mut param_c, shift_left);
+ update_param(&mut param_d, shift_left);
+
+ let key = make_u64(param_d, param_c);
+ *subkey = map_u64(key, &constants::KEY_COMPRESSION);
+ }
+
+ subkeys
+ }
+
+ pub fn transform_block(&self, data: u64) -> u64 {
+ let mut state = Self::ip(data);
+
+ let keys = self.keys.iter();
+ state = keys.fold(state, |state, &key| Self::des_crypt_proc(state, key));
+
+ // Swap data hi32/lo32
+ state = swap_u64(state);
+
+ // Final permutation
+ state = Self::ip_inv(state);
+
+ state
+ }
+
+ pub fn transform_bytes(&self, data: &mut [u8]) -> Result<(), QrcError> {
+ if data.len() % 8 != 0 {
+ Err(QrcError::QRCDesInputSizeError)?;
+ }
+
+ for block in data.chunks_exact_mut(8) {
+ let value = LE::read_u64(block);
+ let transformed = self.transform_block(value);
+ LE::write_u64(block, transformed);
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{DESMode, QrcDes};
+
+ #[test]
+ fn test_des_decrypt() {
+ let mut input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6];
+ let expected_data = [
+ 0xFD, 0x0E, 0x64, 0x06, 0x65, 0xBE, 0x74, 0x13, //
+ 0x77, 0x63, 0x3B, 0x02, 0x45, 0x4E, 0x70, 0x7A, //
+ ];
+
+ let des = QrcDes::new(b"TEST!KEY", DESMode::Decrypt);
+ des.transform_bytes(&mut input).unwrap();
+ assert_eq!(input, expected_data);
+ }
+
+ #[test]
+ fn test_des_encrypt() {
+ let mut input = [
+ 0xFD, 0x0E, 0x64, 0x06, 0x65, 0xBE, 0x74, 0x13, //
+ 0x77, 0x63, 0x3B, 0x02, 0x45, 0x4E, 0x70, 0x7A, //
+ ];
+ let expected_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6];
+
+ let des = QrcDes::new(b"TEST!KEY", DESMode::Encrypt);
+ des.transform_bytes(&mut input).unwrap();
+ assert_eq!(input, expected_data);
+ }
+}
diff --git a/um_crypto/qrc/src/des/mod.rs b/um_crypto/qrc/src/des/mod.rs
new file mode 100644
index 0000000..231ab11
--- /dev/null
+++ b/um_crypto/qrc/src/des/mod.rs
@@ -0,0 +1,5 @@
+mod constants;
+mod des;
+mod utils;
+
+pub use des::{DESMode, QrcDes};
diff --git a/um_crypto/qrc/src/des/utils.rs b/um_crypto/qrc/src/des/utils.rs
new file mode 100644
index 0000000..646babb
--- /dev/null
+++ b/um_crypto/qrc/src/des/utils.rs
@@ -0,0 +1,55 @@
+use crate::des::constants::U64_SHIFT_TABLE_CACHE;
+
+pub const fn make_u64(hi32: u32, lo32: u32) -> u64 {
+ ((hi32 as u64) << 32) | (lo32 as u64)
+}
+
+pub const fn swap_u64(value: u64) -> u64 {
+ (value.wrapping_shr(32)) | (value.wrapping_shl(32))
+}
+
+pub const fn lo32(value: u64) -> u32 {
+ value as u32
+}
+
+pub const fn hi32(value: u64) -> u32 {
+ value.wrapping_shr(32) as u32
+}
+
+pub const fn get_u64_by_shift_idx(value: u8) -> u64 {
+ // 1u64.wrapping_shl(31u32.wrapping_sub(value as u32))
+ // This is not portable, so let's use a pre-computed table...
+
+ U64_SHIFT_TABLE_CACHE[value as usize]
+}
+
+pub fn map_bit(result: u64, src: u64, check: u8, set: u8) -> u64 {
+ match get_u64_by_shift_idx(check) & src {
+ 0 => result,
+ _ => result | get_u64_by_shift_idx(set),
+ }
+}
+
+pub fn map_u32_bits(src_value: u32, table: &[u8]) -> u32 {
+ let stream = table.iter().enumerate();
+
+ stream.fold(0u64, |result, (i, &check_idx)| {
+ map_bit(result, src_value as u64, check_idx, i as u8)
+ }) as u32
+}
+
+pub fn map_u64(src_value: u64, table: &[u8]) -> u64 {
+ assert_eq!(table.len() % 2, 0, "table.len() should be even");
+
+ let (table_lo32, table_hi32) = table.split_at(table.len() / 2);
+
+ let mut lo32 = 0u64;
+ let mut hi32 = 0u64;
+
+ for (i, (&idx_lo32, &idx_hi32)) in table_lo32.iter().zip(table_hi32).enumerate() {
+ lo32 = map_bit(lo32, src_value, idx_lo32, i as u8);
+ hi32 = map_bit(hi32, src_value, idx_hi32, i as u8);
+ }
+
+ make_u64(hi32 as u32, lo32 as u32)
+}
diff --git a/um_crypto/qrc/src/lib.rs b/um_crypto/qrc/src/lib.rs
new file mode 100644
index 0000000..8c223fc
--- /dev/null
+++ b/um_crypto/qrc/src/lib.rs
@@ -0,0 +1,118 @@
+use hex::FromHexError;
+use miniz_oxide::inflate::{decompress_to_vec_zlib_with_limit as inflate, DecompressError};
+use thiserror::Error;
+
+mod des;
+use crate::des::DESMode;
+use des::QrcDes;
+
+#[derive(Error, Debug)]
+pub enum QrcError {
+ #[error("QRCDes: input is not block of 8 bytes")]
+ QRCDesInputSizeError,
+
+ #[error("QRC: Failed to inflate: {0}")]
+ QRCInflateError(DecompressError),
+
+ #[error("QRC: Failed to decode hex: {0}")]
+ QRCHexDecodeError(FromHexError),
+
+ #[error("QRC: Invalid file magic header")]
+ QRCInvalidMagicHeader,
+}
+
+// Max 4MiB for QRC
+const MAX_QRC_SIZE: usize = 4 * 1024 * 1024;
+
+const DES_KEY_1: &[u8; 8] = b"!@#)(NHL";
+const DES_KEY_2: &[u8; 8] = b"123ZXC!@";
+const DES_KEY_3: &[u8; 8] = b"!@#)(*$%";
+
+pub fn decrypt_qrc(data: &[u8]) -> Result, QrcError> {
+ let mut temp = data.to_vec();
+ QrcDes::new(DES_KEY_1, DESMode::Decrypt).transform_bytes(&mut temp)?;
+ QrcDes::new(DES_KEY_2, DESMode::Encrypt).transform_bytes(&mut temp)?;
+ QrcDes::new(DES_KEY_3, DESMode::Decrypt).transform_bytes(&mut temp)?;
+ let result = inflate(&temp[..], MAX_QRC_SIZE).map_err(QrcError::QRCInflateError)?;
+ Ok(result)
+}
+
+/// Decrypt QRC data from API response
+pub fn decrypt_qrc_network(data: &str) -> Result, QrcError> {
+ let data = hex::decode(data).map_err(QrcError::QRCHexDecodeError)?;
+ decrypt_qrc(&data[..])
+}
+
+const QRC_MAGIC: [u8; 11] = [
+ 0x98, 0x25, 0xB0, 0xAC, 0xE3, 0x02, 0x83, 0x68, 0xE8, 0xFC, 0x6C,
+];
+
+/// Decrypt QRC data from cached local file
+pub fn decrypt_qrc_file(data: &[u8]) -> Result, QrcError> {
+ let data = match data.strip_prefix(&QRC_MAGIC) {
+ None => Err(QrcError::QRCInvalidMagicHeader)?,
+ Some(data) => data,
+ };
+ let mut temp = data.to_vec();
+ umc_qmc::v1::decrypt(&mut temp, QRC_MAGIC.len());
+ decrypt_qrc(&temp[..])
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{decrypt_qrc_file, decrypt_qrc_network};
+
+ #[test]
+ fn test_qrc_file() {
+ let data = [
+ 0x98, 0x25, 0xB0, 0xAC, 0xE3, 0x02, 0x83, 0x68, 0xE8, 0xFC, 0x6C, 0xAB, 0x9A, 0x34,
+ 0xE2, 0x31, 0x26, 0xAF, 0x6E, 0x2A, 0x23, 0xB3, 0x56, 0xC3, 0xBF, 0x8A, 0xA6,
+ ];
+
+ let result = decrypt_qrc_file(&data).expect("Decryption failed.");
+ assert_eq!(result, b"nothing");
+ }
+
+ #[test]
+ fn test_qrc_file_2() {
+ let data = [
+ 0x98, 0x25, 0xB0, 0xAC, 0xE3, 0x02, 0x83, 0x68, 0xE8, 0xFC, 0x6C, 0x07, 0xBC, 0x8C,
+ 0x46, 0x97, 0x36, 0xDF, 0x06, 0x13, 0xE0, 0x31, 0xD1, 0xF8, 0x98, 0xEF, 0xD0, 0x1B,
+ 0xEA, 0x6B, 0x04, 0x1D, 0xDB, 0xE0, 0x0F, 0x33, 0x2B, 0xBE, 0x95, 0x27, 0xB9, 0xF6,
+ 0xEE, 0x0C, 0x75, 0x0C, 0x46, 0x4C, 0xA8, 0xE8, 0x37, 0x93, 0x03, 0xC0, 0xA6, 0x98,
+ 0xD0, 0x4B, 0x6E, 0xBB, 0x2A, 0x8C, 0x3E, 0xE8, 0x7F, 0xC2, 0x0F, 0x6E, 0x2E, 0x3E,
+ 0xAD, 0x38, 0xCF, 0x74, 0x01, 0x17, 0xDA, 0xE0, 0x62, 0x45, 0x4F, 0xF8, 0x35,
+ ];
+
+ let result = decrypt_qrc_file(&data).expect("Decryption failed.");
+ assert_eq!(
+ String::from_utf8_lossy(&result),
+ "[00:00:00]此歌曲为没有填词的纯音乐,请您欣赏"
+ );
+ }
+
+ #[test]
+ fn test_qrc_from_network() {
+ // Original QRC
+ let input_data = include_str!("__fixture__/qrc_network_1_jp.txt");
+ let qrc_original_b = decrypt_qrc_network(input_data).expect("decrypt failed");
+ let qrc_original = String::from_utf8(qrc_original_b).expect("decode failed");
+ assert!(qrc_original.contains("太(1399,388)陽(1787,486)系(2273,1433)を(3706,404)"));
+
+ // QRC 罗马字
+ let input_data = include_str!("__fixture__/qrc_network_1_roma.txt");
+ let qrc_romaji_b = decrypt_qrc_network(input_data).expect("decrypt failed");
+ let qrc_romaji = String::from_utf8(qrc_romaji_b).expect("decode failed");
+ assert!(qrc_romaji.contains("ta (1399,194)i (1593,194)yo (1787,243)u (2029,243)ke"));
+
+ // QRC 翻译 (LRC)
+ let input_data = include_str!("__fixture__/qrc_network_1_trans.txt");
+ let qrc_translation_b = decrypt_qrc_network(input_data).expect("decrypt failed");
+ let qrc_translation = String::from_utf8(qrc_translation_b).expect("decode failed");
+ assert!(qrc_translation.contains("[00:01.39]摆脱太阳系"));
+
+ println!("qrc_original: {}", qrc_original);
+ println!("qrc_romaji: {}", qrc_romaji);
+ println!("qrc_translation: {}", qrc_translation);
+ }
+}
diff --git a/um_crypto/qtfm/Cargo.toml b/um_crypto/qtfm/Cargo.toml
index 24a7eee..5628cda 100644
--- a/um_crypto/qtfm/Cargo.toml
+++ b/um_crypto/qtfm/Cargo.toml
@@ -8,5 +8,5 @@ aes = "0.8.4"
byteorder = "1.5.0"
cbc = "0.1.2"
ctr = "0.9.2"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_utils = { path = "../utils" }
diff --git a/um_crypto/xiami/Cargo.toml b/um_crypto/xiami/Cargo.toml
index 21d17d7..04228d5 100644
--- a/um_crypto/xiami/Cargo.toml
+++ b/um_crypto/xiami/Cargo.toml
@@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-thiserror = "1.0.63"
+thiserror = "1.0.64"
diff --git a/um_crypto/xmly/Cargo.toml b/um_crypto/xmly/Cargo.toml
index f52aac4..d843e66 100644
--- a/um_crypto/xmly/Cargo.toml
+++ b/um_crypto/xmly/Cargo.toml
@@ -10,5 +10,5 @@ cbc = "0.1.2"
cipher = "0.4.4"
hex = "0.4.3"
lazy_static = "1.5.0"
-thiserror = "1.0.63"
+thiserror = "1.0.64"
umc_utils = { path = "../utils" }
diff --git a/um_wasm/Cargo.toml b/um_wasm/Cargo.toml
index 29d65f0..3894fa2 100644
--- a/um_wasm/Cargo.toml
+++ b/um_wasm/Cargo.toml
@@ -29,6 +29,7 @@ umc_kuwo = { path = "../um_crypto/kuwo" }
umc_mg3d = { path = "../um_crypto/mg3d" }
umc_ncm = { path = "../um_crypto/ncm" }
umc_qmc = { path = "../um_crypto/qmc" }
+umc_qrc = { path = "../um_crypto/qrc" }
umc_qtfm = { path = "../um_crypto/qtfm" }
umc_xiami = { path = "../um_crypto/xiami" }
umc_xmly = { path = "../um_crypto/xmly" }
diff --git a/um_wasm/src/exports/mod.rs b/um_wasm/src/exports/mod.rs
index 8dc3aae..3d41450 100644
--- a/um_wasm/src/exports/mod.rs
+++ b/um_wasm/src/exports/mod.rs
@@ -5,6 +5,7 @@ pub mod kuwo;
pub mod mg3d;
pub mod ncm;
pub mod qmc;
+mod qrc;
pub mod qtfm;
pub mod xiami;
pub mod xmly;
diff --git a/um_wasm/src/exports/qrc.rs b/um_wasm/src/exports/qrc.rs
new file mode 100644
index 0000000..8c4b989
--- /dev/null
+++ b/um_wasm/src/exports/qrc.rs
@@ -0,0 +1,14 @@
+use wasm_bindgen::prelude::wasm_bindgen;
+use wasm_bindgen::JsError;
+
+/// QRC Decrypt ("*.qrc" cache file)
+#[wasm_bindgen(js_name=decryptQRCFile)]
+pub fn js_decrypt_qrc(buffer: &mut [u8]) -> Result, JsError> {
+ umc_qrc::decrypt_qrc_file(buffer).map_err(JsError::from)
+}
+
+/// QRC Decrypt (network response)
+#[wasm_bindgen(js_name=decryptQRCNetwork)]
+pub fn js_decrypt_qrc_network(buffer: &str) -> Result, JsError> {
+ umc_qrc::decrypt_qrc_network(buffer).map_err(JsError::from)
+}
diff --git a/um_wasm_loader/package.json b/um_wasm_loader/package.json
index f64d77e..2fd21c6 100644
--- a/um_wasm_loader/package.json
+++ b/um_wasm_loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@unlock-music/crypto",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Project Unlock Music: 加解密支持库",
"scripts": {
"build": "node build.js",