From 2d8193c0f60e139471adc075587bf4cb06930de7 Mon Sep 17 00:00:00 2001 From: Aleteoryx Date: Mon, 6 Jan 2025 15:00:21 -0500 Subject: [PATCH] generate all the things! --- .gitignore | 1 + Cargo.lock | 327 ++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 + slugs_en.json | 11 -- src/en.rs | 264 ++-------------------------------------- src/main.rs | 142 ++++++++++++++++------ src/model.rs | 256 +++++++++++++++++++++++++++++++++++++++ src/slug.rs | 28 +++-- 8 files changed, 707 insertions(+), 324 deletions(-) delete mode 100644 slugs_en.json create mode 100644 src/model.rs diff --git a/.gitignore b/.gitignore index 4c9383da162863343656445b3572dbdb7d28d186..769643f5cefa4f50427c498572ced4a774655a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target *~ /stamps_cache +/stamp_packs diff --git a/Cargo.lock b/Cargo.lock index e3f09657697e8f735aa9e79972dc1e0095d2da70..9c37e099015955e34f3abe77a187424b4d53dd38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ dependencies = [ "accesskit_consumer", "atspi-common", "serde", - "thiserror", + "thiserror 1.0.69", "zvariant", ] @@ -125,6 +125,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -156,7 +167,7 @@ dependencies = [ "ndk-context", "ndk-sys 0.6.0+11769913", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -174,6 +185,15 @@ dependencies = [ "libc", ] +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arboard" version = "3.4.1" @@ -551,6 +571,27 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "calloop" version = "0.13.0" @@ -562,7 +603,7 @@ dependencies = [ "polling", "rustix", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -621,6 +662,16 @@ dependencies = [ "libc", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clipboard-win" version = "5.4.0" @@ -659,6 +710,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -718,6 +775,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -749,6 +821,32 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -757,6 +855,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -880,7 +979,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror", + "thiserror 1.0.69", "type-map", "web-time", "wgpu", @@ -1464,6 +1563,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -1755,6 +1863,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1778,7 +1895,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -1880,12 +1997,28 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1988,7 +2121,7 @@ dependencies = [ "rustc-hash", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -2021,7 +2154,7 @@ dependencies = [ "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2067,6 +2200,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -2431,6 +2570,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2490,7 +2639,9 @@ dependencies = [ "reqwest", "serde", "serde_json", + "sha256", "strum", + "zip", ] [[package]] @@ -2527,6 +2678,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2916,6 +3073,30 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2975,7 +3156,7 @@ dependencies = [ "log", "memmap2", "rustix", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -3158,7 +3339,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -3172,6 +3362,36 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3722,7 +3942,7 @@ dependencies = [ "raw-window-handle", "rustc-hash", "smallvec", - "thiserror", + "thiserror 1.0.69", "wgpu-hal", "wgpu-types", ] @@ -3760,7 +3980,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types", @@ -4405,6 +4625,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerovec" @@ -4428,6 +4662,77 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "rand", + "sha1", + "thiserror 2.0.9", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "4.2.0" diff --git a/Cargo.toml b/Cargo.toml index 922c634d0c0dc67675c39423e8c82c7a9fce4f6a..ff1e1556afa22c0a3ff1f50ea2077017e7b9de95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,6 @@ egui_extras = { version = "0.30.0", features = ["image", "file"] } reqwest = { version = "0.12.12", features = ["blocking"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.134" +sha256 = "1.5.0" strum = { version = "0.26.3", features = ["derive"] } +zip = "2.2.2" diff --git a/slugs_en.json b/slugs_en.json deleted file mode 100644 index e8e131cb36a8ee364da8468bb0d984ab20f86c81..0000000000000000000000000000000000000000 --- a/slugs_en.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "701": "nene_i_dont_want_to_lose", - "1": "miku_hello", - "64": "honami_oh", - "480": "text_nice_to_meet_you", - "574": "ena_can_i_keep_drawing", - "437": "text_have_some_bonus_energy", - "505": "minori_shocked", - "250": "mizuki_laugh", - "723": "mizuki_satisfied" -} \ No newline at end of file diff --git a/src/en.rs b/src/en.rs index e68213656d21e7cef0d5a6c95f9fd25e6bb0fcb6..5ca8e3bbfc084c9ed321af5202935097c4a9eb90 100644 --- a/src/en.rs +++ b/src/en.rs @@ -1,256 +1,18 @@ -use std::collections::HashSet; - -use serde::Deserialize; -use strum::{EnumIter, IntoEnumIterator, EnumProperty}; - -#[derive(Clone, Debug, Default, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct RawStamp { - id: usize, - seq: usize, - - name: String, - description: Option, - stamp_type: String, - - assetbundle_name: String, - balloon_assetbundle_name: String, - - // ignored by us, used in sekai-viewer UI - archive_published_at: usize, - // ignored by us, used in sekai-viewer UI - archive_display_type: Option, - - character_id_1: Option, - character_id_2: Option, - game_character_unit_id: Option -} - -impl RawStamp { - pub fn to_stamp(self) -> Stamp { - use StampCharacter::*; - - let RawStamp { id, seq, - name, description, stamp_type, - assetbundle_name, balloon_assetbundle_name, - character_id_1, character_id_2, game_character_unit_id, .. } = self; - - let character = match (character_id_1, character_id_2, game_character_unit_id) { - (None, None, None) - => Nobody, - (Some(character_id), None, None) - => Solo{character_id}, - (Some(character_id_1), Some(character_id_2), None) - => Duo{character_id_1, character_id_2}, - (Some(character_id), None, Some(game_character_unit_id)) - => SoloExtra{character_id, game_character_unit_id}, - (None, None, Some(game_character_unit_id)) - => GCUID{game_character_unit_id}, - - x => { panic!("They added a new character format: {x:?}"); } - }; - - Stamp { id, seq, - name, description, stamp_type, - assetbundle_name, balloon_assetbundle_name, - character } - } -} - -#[derive(Clone, Debug, Default)] -pub struct Stamp { - pub id: usize, - pub seq: usize, - - pub name: String, - pub description: Option, - pub stamp_type: String, - - pub assetbundle_name: String, - pub balloon_assetbundle_name: String, - - pub character: StampCharacter -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Hash)] -pub enum StampCharacter { - #[default] - Nobody, - Solo{character_id: Character}, - Duo{character_id_1: Character, character_id_2: Character}, - SoloExtra{character_id: Character, game_character_unit_id: Character}, - GCUID{game_character_unit_id: Character}, -} -impl std::fmt::Display for StampCharacter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Self::Nobody => write!(f, "text"), - Self::Solo{character_id} => write!(f, "{}", character_id.get_str("prefix").unwrap()), - Self::Duo{character_id_1, character_id_2} => write!(f, "{}_{}", character_id_1.get_str("prefix").unwrap(), character_id_2.get_str("prefix").unwrap()), - Self::SoloExtra{game_character_unit_id, ..} => write!(f, "{}", game_character_unit_id.get_str("prefix").unwrap()), - Self::GCUID{game_character_unit_id} => write!(f, "text_{}", game_character_unit_id.get_str("prefix").unwrap()) - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Hash, EnumIter, EnumProperty)] -#[repr(u8)] -#[serde(from = "u8")] -pub enum Character { - // Leo / Need - #[strum(props(prefix = "ichika"))] - Ichika = 1, - #[strum(props(prefix = "saki"))] - Saki = 2, - #[strum(props(prefix = "honami"))] - Honami = 3, - #[strum(props(prefix = "shiho"))] - Shiho = 4, - - // MORE MORE JUMP! - #[strum(props(prefix = "minori"))] - Minori = 5, - #[strum(props(prefix = "haruka"))] - Haruka = 6, - #[strum(props(prefix = "airi"))] - Airi = 7, - #[strum(props(prefix = "shizuku"))] - Shizuku = 8, - - // Vivid BAD SQUAD - #[strum(props(prefix = "kohane"))] - Kohane = 9, - #[strum(props(prefix = "an"))] - An = 10, - #[strum(props(prefix = "akito"))] - Akito = 11, - #[strum(props(prefix = "toya"))] - Toya = 12, - - // Wonderlands x Showtime - #[strum(props(prefix = "tsukasa"))] - Tsukasa = 13, - #[strum(props(prefix = "emu"))] - Emu = 14, - #[strum(props(prefix = "nene"))] - Nene = 15, - #[strum(props(prefix = "rui"))] - Rui = 16, - - // Nightcord at 25:00 - #[strum(props(prefix = "kanade"))] - Kanade = 17, - #[strum(props(prefix = "mafuyu"))] - Mafuyu = 18, - #[strum(props(prefix = "ena"))] - Ena = 19, - #[strum(props(prefix = "mizuki"))] - Mizuki = 20, - - // VOCALOID - #[strum(props(prefix = "miku"))] - Miku = 21, - #[strum(props(prefix = "rin"))] - Rin = 22, - #[strum(props(prefix = "len"))] - Len = 23, - #[strum(props(prefix = "luka"))] - Luka = 24, - #[strum(props(prefix = "meiko"))] - MEIKO = 25, - #[strum(props(prefix = "kaito"))] - KAITO = 26, - - // "Game Character Unit ID" - // Basically just, character IDs associated to a particular group - // - Miku - #[strum(props(prefix = "ln_miku"))] - LnMiku = 27, - #[strum(props(prefix = "mmj_miku"))] - MmjMiku = 28, - #[strum(props(prefix = "vbs_miku"))] - VbsMiku = 29, - #[strum(props(prefix = "wxs_miku"))] - WxsMiku = 30, - #[strum(props(prefix = "n25_miku"))] - N25Miku = 31, - - // - Rin - #[strum(props(prefix = "ln_rin"))] - LnRin = 32, - #[strum(props(prefix = "mmj_rin"))] - MmjRin = 33, - #[strum(props(prefix = "vbs_rin"))] - VbsRin = 34, - #[strum(props(prefix = "wxs_rin"))] - WxsRin = 35, - #[strum(props(prefix = "n25_rin"))] - N25Rin = 36, - - // - Len - #[strum(props(prefix = "ln_len"))] - LnLen = 37, - #[strum(props(prefix = "mmj_len"))] - MmjLen = 38, - #[strum(props(prefix = "vbs_len"))] - VbsLen = 39, - #[strum(props(prefix = "wxs_len"))] - WxsLen = 40, - #[strum(props(prefix = "n25_len"))] - N25Len = 41, - - // - Luka - #[strum(props(prefix = "ln_luka"))] - LnLuka = 42, - #[strum(props(prefix = "mmj_luka"))] - MmjLuka = 43, - #[strum(props(prefix = "vbs_luka"))] - VbsLuka = 44, - #[strum(props(prefix = "wxs_luka"))] - WxsLuka = 45, - #[strum(props(prefix = "n25_luka"))] - N25Luka = 46, - - // - MEIKO - #[strum(props(prefix = "ln_meiko"))] - LnMEIKO = 47, - #[strum(props(prefix = "mmj_meiko"))] - MmjMEIKO = 48, - #[strum(props(prefix = "vbs_meiko"))] - VbsMEIKO = 49, - #[strum(props(prefix = "wxs_meiko"))] - WxsMEIKO = 50, - #[strum(props(prefix = "n25_meiko"))] - N25MEIKO = 51, - - // - KAITO - #[strum(props(prefix = "ln_kaito"))] - LnKAITO = 52, - #[strum(props(prefix = "mmj_kaito"))] - MmjKAITO = 53, - #[strum(props(prefix = "vbs_kaito"))] - VbsKAITO = 54, - #[strum(props(prefix = "wxs_kaito"))] - WxsKAITO = 55, - #[strum(props(prefix = "n25_kaito"))] - N25KAITO = 56, -} -impl From for Character { - fn from(val: u8) -> Self { - for var in Self::iter() { - if var as u8 == val { - return var; +pub struct Helpers; +impl crate::Helpers for Helpers { + fn escape_name(name: &str) -> String { + let mut ret = String::new(); + + let name = name.split(":").nth(1).unwrap().trim(); + + for c in name.chars() { + if c.is_ascii_alphanumeric() { + ret.push(c.to_ascii_lowercase()); + } else if c.is_ascii_whitespace() { + ret.push('_'); } } - panic!("Unknown Character ID: {val}"); - } -} -impl std::fmt::Display for Character { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{self:?}") + ret } } - -impl Stamp { -} diff --git a/src/main.rs b/src/main.rs index a77016dc2b2cbee3d12d9808bac9c29fa23086f8..6d201856ca8146cdc88076f967f3e722536b75c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,35 @@ #![feature(iter_intersperse)] mod en; + +mod model; mod slug; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::fs::{File, self}; -use std::path::PathBuf; +use std::io::Write; +use std::path::{Path, PathBuf}; use bytes::Bytes; use reqwest::blocking::Client; +use strum::{EnumIs, EnumProperty}; +use zip::write::{SimpleFileOptions, ZipWriter}; + +const BASE_DOWNLOAD_PATH: &'static str = "https://aleteoryx.me/downloads2/emotes/pleroma/"; + +trait Helpers { + fn escape_name(name: &str) -> String; +} + +#[derive(Default, serde::Serialize)] +struct PackMetadata { + name: String, + description: String, + homepage: String, + files: String, + src: String, + src_sha256: String, + license: String +} fn get_stamp_asset_png(client: &Client, id: &str) -> Bytes { client @@ -22,25 +44,28 @@ fn get_stamp_asset_png(client: &Client, id: &str) -> Bytes { fn main() { let repo_dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_owned()).into(); - let client = Client::new(); // English region (global) - let sekai_en_dir = repo_dir.join("sekai_world/db_en_diff"); - let stamps_en_path = sekai_en_dir.join("stamps.json"); - let stamps_en = - serde_json::from_reader::<_, Vec>( - File::open(&stamps_en_path) + handle_region::(&client, &repo_dir, "en"); +} + +fn handle_region(client: &Client, repo_dir: &Path, region: &'static str) { + let sekai_dir = repo_dir.join(format!("sekai_world/db_{region}_diff")); + + let stamps = + serde_json::from_reader::<_, Vec>( + File::open(sekai_dir.join("stamps.json")) .expect("Couldn't open stamps.json")) .expect("stamps.json invalid") .into_iter() .map(|a| a.to_stamp()) .collect::>(); - let cache_dir = repo_dir.join("stamps_cache/en"); + let cache_dir = repo_dir.join("stamps_cache").join(®ion); fs::create_dir_all(&cache_dir); - for stamp in &stamps_en { + for stamp in &stamps { let download_path = cache_dir.join(&stamp.assetbundle_name).with_extension("png"); if !fs::exists(&download_path).unwrap() { println!("Downlading asset {}...", &stamp.assetbundle_name); @@ -50,44 +75,81 @@ fn main() { } let character_stamps = - stamps_en.clone().into_iter() - .filter(|a| - match a.character { - en::StampCharacter::Solo { .. } | en::StampCharacter::SoloExtra { .. } => true, - _ => false - }) + stamps.clone().into_iter() + .filter(|a| a.character.is_solo() || a.character.is_solo_extra()) .collect::>(); let text_stamps = - stamps_en.clone().into_iter() - .filter(|a| - match a.character { - en::StampCharacter::Nobody => true, - _ => false - }) + stamps.clone().into_iter() + .filter(|a| a.character.is_nobody()) .collect::>(); - - for stamp in text_stamps { - println!("{} => {}", stamp.name, stamp.name.to_ascii_lowercase().replace(" ", "_")); - } - let name_stamps = - stamps_en.clone().into_iter() - .filter(|a| - match a.character { - en::StampCharacter::GCUID { .. } => true, - _ => false - }) + stamps.clone().into_iter() + .filter(|a| a.character.is_gcuid()) .collect::>(); let pair_stamps = - stamps_en.clone().into_iter() - .filter(|a| - match a.character { - en::StampCharacter::Duo { .. } => true, - _ => false - }) + stamps.clone().into_iter() + .filter(|a| a.character.is_duo()) .collect::>(); - slug::SlugPrompter::run("en".into(), &repo_dir, character_stamps); + let packs_dir = repo_dir.join("stamp_packs"); + let out_dir = packs_dir.join("Project SEKAI").join(region); + fs::create_dir_all(&out_dir); + let mut packs_list: BTreeMap = BTreeMap::new(); + + let character_out_dir = out_dir.join("characters"); + fs::create_dir_all(&character_out_dir); + + let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated); + for cid in 1..=26 { + let character = model::Character::from(cid); + let character_slug = character.get_str("prefix").unwrap(); + let zip_path = character_out_dir.join(&character_slug).with_extension("zip"); + let mut writer = + ZipWriter::new( + File::create(&zip_path).unwrap()); + let mut emotes: BTreeMap = BTreeMap::new(); + + println!("Generating character stamp pack for {}...", character); + + for stamp in &character_stamps { + match stamp.character { + model::StampCharacter::Solo{ character_id } | + model::StampCharacter::SoloExtra{ character_id, .. } if character_id as u8 == cid => { + let character_slug = stamp.character.to_string() + "_" + &H::escape_name(&stamp.name); + writer.start_file(character_slug.clone() + ".png", options); + writer.write_all(&fs::read(cache_dir.join(&stamp.assetbundle_name).with_extension("png")).unwrap()); + emotes.insert(character_slug.clone(), character_slug + ".png"); + }, + _ => () + } + } + + writer.finish(); + + File::create( + character_out_dir + .join(&character_slug) + .with_extension("json")) + .unwrap() + .write_all(&serde_json::to_vec(&emotes).unwrap()); + + let pack_path = format!("Project SEKAI/{region}/characters/{character_slug}"); + + packs_list.insert( + pack_path.clone(), + PackMetadata { + name: format!("Project SEKAI ({region}): {}", character.get_str(&format!("name_{region}")).unwrap()), + description: "Made possible by the sekai-viewer project. ".into(), + homepage: "https://git.amehut.dev/~aleteoryx/pjsekai_emote_packs".into(), + files: BASE_DOWNLOAD_PATH.to_string() + &pack_path + ".json", + src: BASE_DOWNLOAD_PATH.to_string() + &pack_path + ".zip", + src_sha256: sha256::try_digest(&zip_path).unwrap(), + license: "N/A".into() + }); + } + File::create(packs_dir.join("manifest.json")) + .unwrap() + .write_all(&serde_json::to_vec_pretty(&packs_list).unwrap()); } diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000000000000000000000000000000000000..01e624265bdbb559dfd784b2e486eceb5237c6ef --- /dev/null +++ b/src/model.rs @@ -0,0 +1,256 @@ +use std::collections::HashSet; + +use serde::Deserialize; +use strum::{EnumIter, IntoEnumIterator, EnumProperty, EnumIs}; + +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct RawStamp { + id: usize, + seq: usize, + + name: String, + description: Option, + stamp_type: String, + + assetbundle_name: String, + balloon_assetbundle_name: String, + + // ignored by us, used in sekai-viewer UI + archive_published_at: usize, + // ignored by us, used in sekai-viewer UI + archive_display_type: Option, + + character_id_1: Option, + character_id_2: Option, + game_character_unit_id: Option +} + +impl RawStamp { + pub fn to_stamp(self) -> Stamp { + use StampCharacter::*; + + let RawStamp { id, seq, + name, description, stamp_type, + assetbundle_name, balloon_assetbundle_name, + character_id_1, character_id_2, game_character_unit_id, .. } = self; + + let character = match (character_id_1, character_id_2, game_character_unit_id) { + (None, None, None) + => Nobody, + (Some(character_id), None, None) + => Solo{character_id}, + (Some(character_id_1), Some(character_id_2), None) + => Duo{character_id_1, character_id_2}, + (Some(character_id), None, Some(game_character_unit_id)) + => SoloExtra{character_id, game_character_unit_id}, + (None, None, Some(game_character_unit_id)) + => GCUID{game_character_unit_id}, + + x => { panic!("They added a new character format: {x:?}"); } + }; + + Stamp { id, seq, + name, description, stamp_type, + assetbundle_name, balloon_assetbundle_name, + character } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stamp { + pub id: usize, + pub seq: usize, + + pub name: String, + pub description: Option, + pub stamp_type: String, + + pub assetbundle_name: String, + pub balloon_assetbundle_name: String, + + pub character: StampCharacter +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Hash, EnumIs)] +pub enum StampCharacter { + #[default] + Nobody, + Solo{character_id: Character}, + Duo{character_id_1: Character, character_id_2: Character}, + SoloExtra{character_id: Character, game_character_unit_id: Character}, + GCUID{game_character_unit_id: Character}, +} +impl std::fmt::Display for StampCharacter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Self::Nobody => write!(f, "text"), + Self::Solo{character_id} => write!(f, "{}", character_id.get_str("prefix").unwrap()), + Self::Duo{character_id_1, character_id_2} => write!(f, "{}_{}", character_id_1.get_str("prefix").unwrap(), character_id_2.get_str("prefix").unwrap()), + Self::SoloExtra{game_character_unit_id, ..} => write!(f, "{}", game_character_unit_id.get_str("prefix").unwrap()), + Self::GCUID{game_character_unit_id} => write!(f, "text_{}", game_character_unit_id.get_str("prefix").unwrap()) + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Hash, EnumIter, EnumProperty)] +#[repr(u8)] +#[serde(from = "u8")] +pub enum Character { + // Leo / Need + #[strum(props(prefix = "ichika", name_en = "Ichika Hoshino"))] + Ichika = 1, + #[strum(props(prefix = "saki", name_en = "Saki Tenma"))] + Saki = 2, + #[strum(props(prefix = "honami", name_en = "Honami Mochizuki"))] + Honami = 3, + #[strum(props(prefix = "shiho", name_en = "Shiho Hinomori"))] + Shiho = 4, + + // MORE MORE JUMP! + #[strum(props(prefix = "minori", name_en = "Minori Hanasato"))] + Minori = 5, + #[strum(props(prefix = "haruka", name_en = "Haruka Kiritani"))] + Haruka = 6, + #[strum(props(prefix = "airi", name_en = "Airi Momoi"))] + Airi = 7, + #[strum(props(prefix = "shizuku", name_en = "Shizuku Hinomori"))] + Shizuku = 8, + + // Vivid BAD SQUAD + #[strum(props(prefix = "kohane", name_en = "Kohane Azusawa"))] + Kohane = 9, + #[strum(props(prefix = "an", name_en = "An Shiraishi"))] + An = 10, + #[strum(props(prefix = "akito", name_en = "Akito Shinonome"))] + Akito = 11, + #[strum(props(prefix = "toya", name_en = "Toya Aoyagi"))] + Toya = 12, + + // Wonderlands x Showtime + #[strum(props(prefix = "tsukasa", name_en = "Tsukasa Tenma"))] + Tsukasa = 13, + #[strum(props(prefix = "emu", name_en = "Emu Otori"))] + Emu = 14, + #[strum(props(prefix = "nene", name_en = "Nene Kusanagi"))] + Nene = 15, + #[strum(props(prefix = "rui", name_en = "Rui Kamishiro"))] + Rui = 16, + + // Nightcord at 25:00 + #[strum(props(prefix = "kanade", name_en = "Kanade Yoisaki"))] + Kanade = 17, + #[strum(props(prefix = "mafuyu", name_en = "Mafuyu Asahina"))] + Mafuyu = 18, + #[strum(props(prefix = "ena", name_en = "Ena Shinonome"))] + Ena = 19, + #[strum(props(prefix = "mizuki", name_en = "Mizuki Akiyama"))] + Mizuki = 20, + + // VOCALOID + #[strum(props(prefix = "miku", name_en = "Hatsune Miku"))] + Miku = 21, + #[strum(props(prefix = "rin", name_en = "Kagamine Rin"))] + Rin = 22, + #[strum(props(prefix = "len", name_en = "Kagamine Len"))] + Len = 23, + #[strum(props(prefix = "luka", name_en = "Megurine Luka"))] + Luka = 24, + #[strum(props(prefix = "meiko", name_en = "MEIKO"))] + MEIKO = 25, + #[strum(props(prefix = "kaito", name_en = "KAITO"))] + KAITO = 26, + + // "Game Character Unit ID" + // Basically just, character IDs associated to a particular group + // - Miku + #[strum(props(prefix = "ln_miku", name_en = "Leo / Need Miku"))] + LnMiku = 27, + #[strum(props(prefix = "mmj_miku", name_en = "MORE MORE JUMP! Miku"))] + MmjMiku = 28, + #[strum(props(prefix = "vbs_miku", name_en = "Vivid BAD SQUAD Miku"))] + VbsMiku = 29, + #[strum(props(prefix = "wxs_miku", name_en = "Wonderlands x Showtime Miku"))] + WxsMiku = 30, + #[strum(props(prefix = "n25_miku", name_en = "Nightcord at 25:00 Miku"))] + N25Miku = 31, + + // - Rin + #[strum(props(prefix = "ln_rin", name_en = "Leo / Need Rin"))] + LnRin = 32, + #[strum(props(prefix = "mmj_rin", name_en = "MORE MORE JUMP! Rin"))] + MmjRin = 33, + #[strum(props(prefix = "vbs_rin", name_en = "Vivid BAD SQUAD Rin"))] + VbsRin = 34, + #[strum(props(prefix = "wxs_rin", name_en = "Wonderlands x Showtime Rin"))] + WxsRin = 35, + #[strum(props(prefix = "n25_rin", name_en = "Nightcord at 25:00 Rin"))] + N25Rin = 36, + + // - Len + #[strum(props(prefix = "ln_len", name_en = "Leo / Need Len"))] + LnLen = 37, + #[strum(props(prefix = "mmj_len", name_en = "MORE MORE JUMP! Len"))] + MmjLen = 38, + #[strum(props(prefix = "vbs_len", name_en = "Vivid BAD SQUAD Len"))] + VbsLen = 39, + #[strum(props(prefix = "wxs_len", name_en = "Wonderlands x Showtime Len"))] + WxsLen = 40, + #[strum(props(prefix = "n25_len", name_en = "Nightcord at 25:00 Len"))] + N25Len = 41, + + // - Luka + #[strum(props(prefix = "ln_luka", name_en = "Leo / Need Luka"))] + LnLuka = 42, + #[strum(props(prefix = "mmj_luka", name_en = "MORE MORE JUMP! Luka"))] + MmjLuka = 43, + #[strum(props(prefix = "vbs_luka", name_en = "Vivid BAD SQUAD Luka"))] + VbsLuka = 44, + #[strum(props(prefix = "wxs_luka", name_en = "Wonderlands x Showtime Luka"))] + WxsLuka = 45, + #[strum(props(prefix = "n25_luka", name_en = "Nightcord at 25:00 Luka"))] + N25Luka = 46, + + // - MEIKO + #[strum(props(prefix = "ln_meiko", name_en = "Leo / Need MEIKO"))] + LnMEIKO = 47, + #[strum(props(prefix = "mmj_meiko", name_en = "MORE MORE JUMP! MEIKO"))] + MmjMEIKO = 48, + #[strum(props(prefix = "vbs_meiko", name_en = "Vivid BAD SQUAD MEIKO"))] + VbsMEIKO = 49, + #[strum(props(prefix = "wxs_meiko", name_en = "Wonderlands x Showtime MEIKO"))] + WxsMEIKO = 50, + #[strum(props(prefix = "n25_meiko", name_en = "Nightcord at 25:00 MEIKO"))] + N25MEIKO = 51, + + // - KAITO + #[strum(props(prefix = "ln_kaito", name_en = "Leo / Need KAITO"))] + LnKAITO = 52, + #[strum(props(prefix = "mmj_kaito", name_en = "MORE MORE JUMP! KAITO"))] + MmjKAITO = 53, + #[strum(props(prefix = "vbs_kaito", name_en = "Vivid BAD SQUAD KAITO"))] + VbsKAITO = 54, + #[strum(props(prefix = "wxs_kaito", name_en = "Wonderlands x Showtime KAITO"))] + WxsKAITO = 55, + #[strum(props(prefix = "n25_kaito", name_en = "Nightcord at 25:00 KAITO"))] + N25KAITO = 56, +} +impl From for Character { + fn from(val: u8) -> Self { + for var in Self::iter() { + if var as u8 == val { + return var; + } + } + + panic!("Unknown Character ID: {val}"); + } +} +impl std::fmt::Display for Character { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{self:?}") + } +} + +impl Stamp { +} diff --git a/src/slug.rs b/src/slug.rs index 34cb9399d830ff4da9bae0f68fc23cd1d0d245b2..3fa0ebfcc88cb55a84bb7a64a4ce5fd72040f5c3 100644 --- a/src/slug.rs +++ b/src/slug.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fs::{File, self}; use std::path::{Path, PathBuf}; @@ -6,9 +6,9 @@ use eframe::egui; #[derive(Default)] pub struct SlugPrompter { - id_to_stamp_map: HashMap, - id_to_slug_map: HashMap>, - slug_to_id_map: HashMap, + id_to_stamp_map: BTreeMap, + id_to_slug_map: BTreeMap>, + slug_to_id_map: BTreeMap, current_id: usize, current_slug: String, @@ -20,9 +20,9 @@ pub struct SlugPrompter { } impl SlugPrompter { - pub fn run(locale: String, repo_dir: &Path, stamps: Vec) { + pub fn run(locale: String, repo_dir: &Path, stamps: Vec) { let file_path = repo_dir.join(format!("slugs_{locale}.json")); - let id_to_slug_map: HashMap>; + let id_to_slug_map: BTreeMap>; if fs::exists(&file_path).unwrap() { id_to_slug_map = serde_json::from_reader( @@ -40,7 +40,7 @@ impl SlugPrompter { Box::new(|cc| Ok(Box::new(SlugPrompter::new(cc, id_to_slug_map, stamps, repo_dir.join("stamps_cache").join(&locale), file_path))))); } - fn new(_cc: &eframe::CreationContext<'_>, id_to_slug_map: HashMap>, stamps: Vec, cache_dir: PathBuf, file_path: PathBuf) -> Self { + fn new(_cc: &eframe::CreationContext<'_>, id_to_slug_map: BTreeMap>, stamps: Vec, cache_dir: PathBuf, file_path: PathBuf) -> Self { let current_id = stamps[0].id; let slug_to_id_map = id_to_slug_map.clone().into_iter().filter_map(|(a, b)| b.map(|b2| (b2, a))).collect(); @@ -97,18 +97,24 @@ impl eframe::App for SlugPrompter { .split("]").nth(1).unwrap() .trim()); }); - ui.text_edit_singleline(&mut self.current_slug); + let text_res = ui.text_edit_singleline(&mut self.current_slug); + + ui.label(format!("{}/{}", self.id_to_slug_map.len(), self.id_to_stamp_map.len())); if let Some(s) = self.current_err.as_ref() { ui.colored_label(egui::Color32::RED, s); } - if ui.button("Confirm").clicked() { + if ui.button("Confirm").clicked() || text_res.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { if self.slug_to_id_map.contains_key(&self.current_slug) { self.current_err = Some(format!("Slug \"{}\" already taken by {}!", self.current_slug, self.slug_to_id_map[&self.current_slug])); } else { - self.slug_to_id_map.insert(self.current_slug.clone(), self.current_id); - self.id_to_slug_map.insert(self.current_id, Some(self.current_slug.clone())); + if self.current_slug == "" { + self.id_to_slug_map.insert(self.current_id, None); + } else { + self.slug_to_id_map.insert(self.current_slug.clone(), self.current_id); + self.id_to_slug_map.insert(self.current_id, Some(self.current_slug.clone())); + } self.write_file(); }