rizlib/
platform.rs

1/*!
2 * A module containing an enum representing a platform that Rizline can run on.
3 */
4
5use std::fmt::{self, Display};
6
7/// An enum representing a platform that Rizline can run on.
8///
9/// This enum can be used for downloading assets, as the URL that the assets are downloaded from includes the name of the platform that the game runs on.
10/// Currently, only two valid platforms are known: [`Android`][Platform::Android] and [`iOS`][Platform::IOS].
11#[derive(Debug, Clone, Copy)]
12pub enum Platform {
13    Android,
14    IOS,
15}
16
17/// Creates a URL to a file on the server, using a `resource_url`, a [`Platform`], and a filename (`server_filename`).
18///
19/// This function takes three arguments:
20/// - `resource_url` - This is a URL from the [`GameConfig`][crate::game_config::GameConfig] file.
21///   It can begin with either `http://` or `https://`; this function will convert the `http://` prefix to `https://` automatically if necessary.
22/// - `platform` - Every asset URL includes a platform name.
23///   This function uses the [`Platform::platform_name_in_url`] function to get the platform name from the [`Platform`] enum.
24/// - `server_filename` - This is the name/path of the file you want to download.
25///
26/// The function combines those three arguments to create a URL to the final file.
27///
28/// # Examples
29///
30/// ```
31/// # use rizlib::platform::{Platform, create_url_to_file};
32/// # use rizlib::catalog::raw::catalog_file::RawCatalog;
33/// let url_catalog = create_url_to_file("https://rizlineassetstore.pigeongames.cn/versions/v22_1_2_7_b860e8a524", Platform::IOS, RawCatalog::CATALOG_FILENAME);
34/// assert_eq!(url_catalog, "https://rizlineassetstore.pigeongames.cn/versions/v22_1_2_7_b860e8a524/iOS/catalog_catalog.json");
35/// ```
36/// ```
37/// # use rizlib::platform::{Platform, create_url_to_file};
38/// let url_bundle = create_url_to_file("http://rizlineassetstore.pigeongames.cn/versions/v20_1_2_7_1fd57802dc", Platform::Android, "674544e09818e5daf6a95e52f07f45eb.bundle");
39/// assert_eq!(url_bundle, "https://rizlineassetstore.pigeongames.cn/versions/v20_1_2_7_1fd57802dc/Android/674544e09818e5daf6a95e52f07f45eb.bundle");
40/// ```
41/// ```
42/// # use rizlib::platform::{Platform, create_url_to_file};
43/// let url_music = create_url_to_file("https://rizlineassetstore.pigeongames.cn/versions/v20_1_2_7_1fd57802dc", Platform::Android, "cridata_assets_criaddressables/alexandrite.onoken.0.acb=4b0f80.bundle");
44/// assert_eq!(url_music, "https://rizlineassetstore.pigeongames.cn/versions/v20_1_2_7_1fd57802dc/Android/cridata_assets_criaddressables/alexandrite.onoken.0.acb=4b0f80.bundle");
45/// ```
46///
47/// # URL format
48///
49/// The URL created by the function is in the form of:
50///
51/// `{https_resource_url}/{platform_name}/{server_filename}`
52///
53/// where:
54/// - `https_resource_url` is the `resource_url` argument, but with a `https://` prefix.
55/// - `platform_name` is the result of calling [`Platform::platform_name_in_url`] on the `platform` argument.
56/// - `server_filename` is given as an argument.
57///
58/// ## Resource URL argument (`resource_url`)
59///
60/// The `resource_url` can be read from a downloaded [`GameConfig`][crate::game_config::GameConfig] file.
61/// Specifically, the exact `resource_url` is stored in the [`ConfigEntry::resource_url`][crate::game_config::ConfigEntry::resource_url] field of an element in the [`GameConfig::configs`][crate::game_config::GameConfig::configs] array.
62///
63/// In the examples, the resource URL will be hardcoded for simplicity.
64///
65/// ## Server filename argument (`server_filename`)
66///
67/// The name of the file that contains the wanted asset is often not obvious.
68/// Most of the game's assets are stored in asset bundles, which are files, that may contain multiple files.
69/// Each asset bundle has a so-called *asset bundle hash*, which is a 32-character long string of lowercase hexadecimal characters.
70///
71/// The filename of the asset bundle file on the server is the *asset bundle hash*.
72/// This *asset bundle hash* value is also stored in the [`AssetBundleRequestOptions::hash`][crate::catalog::extra_data::AssetBundleRequestOptions::hash] (`m_Hash`) field.
73///
74/// To get this filename, you can use the [`Catalog::find_asset`][crate::catalog::catalog_file::Catalog::find_asset] function to find the asset bundles associated with a specific asset key.
75/// For example, calling the function with the parameter `chart.DropDown.Cosmograph.0.IN` will give you the string `46409164c5b094d1d569546a02839fc5.bundle`, which is the name of the asset bundle that contains the IN chart for the song "[DropDown](https://rgwiki.stary.pc.pl/wiki/Rizline:DropDown)":
76///
77/// ```
78/// # use rizlib::catalog::catalog_file::Catalog;
79/// # use std::path::PathBuf;
80/// # let catalog_path = PathBuf::from("test_data").join("catalog").join("catalog_catalog.json");
81/// let catalog = Catalog::from_json_file(&catalog_path).unwrap();
82/// let asset_info = catalog.find_asset("chart.DropDown.Cosmograph.0.IN").unwrap();
83/// let first_dependency = asset_info.dependencies.first().unwrap();
84/// let server_filename = first_dependency.server_filename().unwrap();
85/// assert_eq!(server_filename, "46409164c5b094d1d569546a02839fc5.bundle");
86/// ```
87///
88/// You can then use this filename to create a full URL that you can download the asset bundle from:
89///
90/// ```
91/// # use rizlib::catalog::catalog_file::Catalog;
92/// # use rizlib::platform::{create_url_to_file, Platform};
93/// # use std::path::PathBuf;
94/// # let catalog_path = PathBuf::from("test_data").join("catalog").join("catalog_catalog.json");
95/// # let catalog = Catalog::from_json_file(&catalog_path).unwrap();
96/// # let asset_info = catalog.find_asset("chart.DropDown.Cosmograph.0.IN").unwrap();
97/// # let first_dependency = asset_info.dependencies.first().unwrap();
98/// # let server_filename = first_dependency.server_filename().unwrap();
99/// let url = create_url_to_file("https://rizlineassetstore.pigeongames.cn/versions/v20_1_2_7_1fd57802dc", Platform::Android, &server_filename);
100/// assert_eq!(url, "https://rizlineassetstore.pigeongames.cn/versions/v20_1_2_7_1fd57802dc/Android/46409164c5b094d1d569546a02839fc5.bundle");
101/// ```
102pub fn create_url_to_file(resource_url: &str, platform: Platform, server_filename: &str) -> String {
103    let https_resource_url = resource_url.replace("http://", "https://");
104    let platform_name = platform.platform_name_in_url();
105    format!("{https_resource_url}/{platform_name}/{server_filename}")
106}
107
108impl Platform {
109    /// All valid platforms.
110    pub const ALL_PLATFORMS: [Platform; 2] = [Platform::Android, Platform::IOS];
111
112    /// All valid platform names, as strings.
113    pub const ALL_PLATFORM_NAMES: [&str; 2] = ["Android", "iOS"];
114
115    /// Name of the platform in a URL.
116    ///
117    /// This function returns the name of the platform as it is used in the URL that the assets can be downloaded from.
118    /// For [`Platform::Android`] this is "Android", and for [`Platform::IOS`] this is "iOS".
119    ///
120    /// **Note:** [`Self::to_string`][ToString::to_string], [`Self::platform_name_in_url`] and [`Self::platform_name_in_directory`] currently return the same value, however this may change in the future.
121    pub fn platform_name_in_url(&self) -> String {
122        match self {
123            Platform::Android => "Android",
124            Platform::IOS => "iOS",
125        }
126        .to_string()
127    }
128
129    /// Name of the platform as a directory name.
130    ///
131    /// This function returns the name of the platform as a valid directory name, with no special symbols.
132    /// For [`Platform::Android`] this is "Android", and for [`Platform::IOS`] this is "iOS".
133    ///
134    /// **Note:** [`Self::to_string`][ToString::to_string], [`Self::platform_name_in_url`] and [`Self::platform_name_in_directory`] currently return the same value, however this may change in the future.
135    pub fn platform_name_in_directory(&self) -> String {
136        match self {
137            Platform::Android => "Android",
138            Platform::IOS => "iOS",
139        }
140        .to_string()
141    }
142}
143
144impl Display for Platform {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.write_str(match self {
147            Platform::Android => "Android",
148            Platform::IOS => "iOS",
149        })
150    }
151}
152
153impl TryFrom<&str> for Platform {
154    type Error = ();
155    /// Try to convert a platform name to a [`Platform`].
156    fn try_from(value: &str) -> Result<Self, Self::Error> {
157        match value {
158            "Android" => Ok(Self::Android),
159            "iOS" => Ok(Self::IOS),
160            _ => Err(()),
161        }
162    }
163}