diff --git a/release.ps1 b/release.ps1 index 07d5ec4..3f3f09f 100644 --- a/release.ps1 +++ b/release.ps1 @@ -1,3 +1,5 @@ +Write-Output "Generating release for git SHA $(git rev-parse HEAD)" + cargo build --target i686-pc-windows-msvc --release if (!(Test-Path ./saekawa.pfx)) { diff --git a/src/types/chuni/mod.rs b/src/types/chuni/mod.rs index 824dc08..a7695c5 100644 --- a/src/types/chuni/mod.rs +++ b/src/types/chuni/mod.rs @@ -29,3 +29,43 @@ where BooleanishTypes::Number(n) => Ok(n > 0), } } + +mod serde_user_play_date { + use chrono::NaiveDateTime; + use serde::{ser, de}; + + const DT_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; + + #[derive(Debug)] + struct UserPlayDateVisitor; + + pub fn serialize(dt: &NaiveDateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&dt.format(DT_FORMAT).to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_str(UserPlayDateVisitor) + } + + impl<'de> de::Visitor<'de> for UserPlayDateVisitor { + type Value = NaiveDateTime; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a string in the format of \"{}\"", DT_FORMAT) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + NaiveDateTime::parse_from_str(v, DT_FORMAT) + .map_err(E::custom) + } + } +} diff --git a/src/types/chuni/upsert.rs b/src/types/chuni/upsert.rs index 854f844..4fe9ac3 100644 --- a/src/types/chuni/upsert.rs +++ b/src/types/chuni/upsert.rs @@ -1,7 +1,8 @@ +use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use serde_aux::prelude::*; -use super::deserialize_bool; +use super::{deserialize_bool, serde_user_play_date}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -46,15 +47,17 @@ pub struct UserDataEx { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserPlaylog { - // This decides what `level` indices mean. - // rom version 1.xx.yy: 0->4 for BASIC/ADVANCED/EXPERT/MASTER/WORLD'S END - // rom version 2.xx.yy: 0->5 for BASIC/ADVANCED/EXPERT/MASTER/ULTIMA/WORLD'S END + /// This decides what `level` indices mean. + /// rom version 1.xx.yy: 0->4 for BASIC/ADVANCED/EXPERT/MASTER/WORLD'S END + /// rom version 2.xx.yy: 0->5 for BASIC/ADVANCED/EXPERT/MASTER/ULTIMA/WORLD'S END pub rom_version: String, pub music_id: String, - // This is in UTC+9 - pub user_play_date: String, + /// The date and time the player set this score with, in the local time + /// perceived by the game. On most setups this will be UTC+9. + #[serde(with = "serde_user_play_date")] + pub user_play_date: NaiveDateTime, #[serde(deserialize_with = "deserialize_number_from_string")] pub level: u32, @@ -90,6 +93,10 @@ pub struct UserPlaylog { #[serde(deserialize_with = "deserialize_bool")] pub is_full_combo: bool, + /// In CHUNITHM SUN+ and beyond this is actually an integer, with different + /// indexes for different clear lamps, ranging from a normal CLEAR to a CATASTROPHY + /// (similar to EX HARD CLEAR). To keep things simple it's all smushed to a boolean, + /// since Tachi doesn't implement those clear lamps. #[serde(deserialize_with = "deserialize_bool")] pub is_clear: bool, } diff --git a/src/types/mod.rs b/src/types/mod.rs index 291ad38..3a9d474 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,7 +1,7 @@ pub mod chuni; pub mod tachi; -use chrono::{FixedOffset, NaiveDateTime, TimeZone}; +use chrono::{FixedOffset, TimeZone}; use num_enum::TryFromPrimitiveError; use snafu::{ResultExt, Snafu}; @@ -20,9 +20,6 @@ pub enum ScoreConversionError { InvalidDifficulty { source: TryFromPrimitiveError, }, - - #[snafu(display("Invalid play date."))] - InvalidPlayDate { source: chrono::format::ParseError }, } impl UserPlaylog { @@ -60,10 +57,8 @@ impl UserPlaylog { Difficulty::try_from(self.level).context(InvalidDifficultySnafu)? }; - let datetime = NaiveDateTime::parse_from_str(&self.user_play_date, "%Y-%m-%d %H:%M:%S") - .context(InvalidPlayDateSnafu)?; let jst_offset = FixedOffset::east_opt(9 * 3600).expect("chrono should parse JST timezone"); - let jst_time = jst_offset.from_local_datetime(&datetime).unwrap(); + let jst_time = jst_offset.from_local_datetime(&self.user_play_date).unwrap(); Ok(BatchManualScore { score: self.score,