Skip to main content

ygopro_data/data/
card.rs

1//! Card data and loading from the card database.
2//!
3//! Provides the [`CoreCard`] FFI structure shared with ygocore, plus the higher-level
4//! [`Card`] with localized name and text.
5//!
6//! The `card` feature gates the database-loading functions (`load_db`, `load_db_from_file`,
7//! `load_db_from_bytes`), because they pull in the `rusqlite` dependency. Everything else
8//! in this module is available without the feature.
9
10use std::ops::Deref;
11use std::ops::DerefMut;
12
13#[cfg(feature = "card")]
14use rusqlite::Connection;
15#[cfg(feature = "card")]
16use rusqlite::Row;
17
18use crate::constants::*;
19
20
21const SIZE_SETCODE: usize = 16;
22const SIZE_DESC: usize = 16;
23
24/// A card's core data, whose memory layout is identical to ygocore's `CardData`.
25///
26/// This is the FFI boundary structure passed to/from the core: the C++ `card_reader`
27/// callback fills a `*mut CoreCard`, so the `#[repr(C)]` layout must match ygocore's C++
28/// struct byte-for-byte. Because the layout is shared, a `CoreCard` can be handed directly
29/// to the core without conversion.
30#[repr(C)]
31#[derive(Clone, Default, Debug)]
32pub struct CoreCard {
33    pub code: u32,
34    pub alias: u32,
35    pub setcode: [u16; SIZE_SETCODE],
36    pub card_type: Type,
37    pub level: u32,
38    pub attribute: Attribute,
39    pub race: Race,
40    pub attack: i32,
41    pub defense: i32,
42    pub left_scale: u32,
43    pub right_scale: u32,
44    pub link_marker: Linkmarkers,
45    pub rule_code: u32,
46}
47
48impl CoreCard {
49    /// The code used to unify cards, falling back to `code` when there is no alias.
50    pub fn original_code(&self) -> u32 {
51        if self.alias != 0 { self.alias } else { self.code }
52    }
53
54    /// The code used in a duel, preferring the rule code over the original code.
55    pub fn duel_code(&self) -> u32 {
56        if self.rule_code != 0 { self.rule_code } else { self.original_code() }
57    }
58
59    /// Check whether any set code matches the given value.
60    pub fn is_setcodes(&self, value: u32) -> bool {
61        for x in &self.setcode {
62            if *x == 0 { return false; }
63            if check_setcode(*x as u32, value) {
64                return true;
65            }
66        }
67        false
68    }
69}
70
71/// Check whether a set code matches a value.
72pub fn check_setcode(setcode: u32, value: u32) -> bool {
73    setcode > 0 && 
74        (setcode & 0x0fffu32) == (value & 0x0fffu32) && 
75        (setcode & (value & 0xf000u32)) == (value & 0xf000u32)
76}
77
78/// A full card, combining the core data with the localized name and text.
79#[derive(Clone, Default, Debug)]
80pub struct Card {
81    /// The core card data.
82    pub card: CoreCard,
83    /// The card's official/ocg/tcg availability.
84    pub ot: OT,
85    /// The card's category.
86    pub category: Category,
87    /// The card's localized name.
88    pub name: String,
89    /// The card's localized text.
90    pub text: String,
91    /// The card's desc strings.
92    pub desc: [String; SIZE_DESC],
93}
94
95impl Deref for Card {
96    type Target = CoreCard;
97
98    fn deref(&self) -> &Self::Target {
99        &self.card
100    }
101}
102
103impl DerefMut for Card {
104    fn deref_mut(&mut self) -> &mut Self::Target {
105        &mut self.card
106    }
107}
108
109#[cfg(feature = "card")]
110impl<'row, 'stmt> TryFrom<&'row Row<'stmt>> for CoreCard {
111    type Error = rusqlite::Error;
112
113    fn try_from(row: &'row Row<'stmt>) -> Result<Self, Self::Error> {
114        let level_raw: u32 = row.get(7)?;
115        let defense_raw: i32 = row.get(6)?;
116        let setcode_raw: i64 = row.get(3)?;
117        let card_type: Type = Type::from_bits_retain(row.get(4)?);
118        let is_link = card_type.contains(Type::Link);
119        Ok(CoreCard {
120            code: row.get(0)?,
121            alias: row.get(2)?,
122            setcode: {
123                let mut sc = [0u16; SIZE_SETCODE];
124                sc[0] = (setcode_raw & 0xFFFF) as u16;
125                sc[1] = ((setcode_raw >> 16) & 0xFFFF) as u16;
126                sc[2] = ((setcode_raw >> 32) & 0xFFFF) as u16;
127                sc[3] = ((setcode_raw >> 48) & 0xFFFF) as u16;
128                sc
129            },
130            card_type,
131            level: level_raw & 0xFF,
132            attribute: Attribute::from_bits_retain(row.get(9)?),
133            race: Race::from_bits_retain(row.get(8)?),
134            attack: row.get(5)?,
135            defense: defense_raw,
136            left_scale: (level_raw >> 24) & 0xFF,
137            right_scale: (level_raw >> 16) & 0xFF,
138            link_marker: if is_link {
139                Linkmarkers::from_bits_retain(defense_raw as u32)
140            } else {
141                Linkmarkers::empty()
142            },
143            rule_code: 0,
144        })
145    }
146}
147
148#[cfg(feature = "card")]
149impl<'row, 'stmt> TryFrom<&'row Row<'stmt>> for Card {
150    type Error = rusqlite::Error;
151
152    fn try_from(row: &'row Row<'stmt>) -> Result<Self, Self::Error> {
153        Ok(Card {
154            card: CoreCard::try_from(row)?,
155            ot: OT::from_bits_retain(row.get::<_, i64>(1)? as u8),
156            category: Category::from_bits_retain(row.get::<_, i64>(10)? as u32),
157            name: row.get(11)?,
158            text: row.get(12)?,
159            desc: [
160                row.get(13)?,
161                row.get(14)?,
162                row.get(15)?,
163                row.get(16)?,
164                row.get(17)?,
165                row.get(18)?,
166                row.get(19)?,
167                row.get(20)?,
168                row.get(21)?,
169                row.get(22)?,
170                row.get(23)?,
171                row.get(24)?,
172                row.get(25)?,
173                row.get(26)?,
174                row.get(27)?,
175                row.get(28)?,
176            ],
177        })
178    }
179}
180
181/// Load cards from a SQLite connection.
182#[cfg(feature = "card")]
183pub fn load_db<C>(connection: Connection) -> Result<Vec<C>, rusqlite::Error>
184where
185    C: for<'row, 'stmt> TryFrom<&'row Row<'stmt>, Error = rusqlite::Error>,
186{
187    let query = concat!(
188        "SELECT d.id, d.ot, d.alias, d.setcode, d.type, d.atk, d.def, d.level, d.race, d.attribute, d.category,",
189        " t.name, t.desc, t.str1, t.str2, t.str3, t.str4, t.str5, t.str6, t.str7, t.str8,",
190        " t.str9, t.str10, t.str11, t.str12, t.str13, t.str14, t.str15, t.str16",
191        " FROM datas d LEFT JOIN texts t ON d.id = t.id",
192    );
193    let mut stmt = connection.prepare(query)?;
194    let res = stmt.query_map([], |row| C::try_from(row))?
195        .collect::<Result<Vec<_>, _>>()?;
196    Ok(res)
197}
198
199/// Load cards from a SQLite database file.
200#[cfg(feature = "card")]
201pub fn load_db_from_file<C>(file: &str) -> Result<Vec<C>, rusqlite::Error>
202where
203    C: for<'row, 'stmt> TryFrom<&'row Row<'stmt>, Error = rusqlite::Error>,
204{
205    let connection = Connection::open(file)?;
206    load_db(connection)
207}
208
209/// Load cards from an in-memory SQLite database read from raw bytes.
210#[cfg(feature = "card")]
211pub fn load_db_from_bytes<C>(bytes: &[u8]) -> Result<Vec<C>, rusqlite::Error>
212where
213    C: for<'row, 'stmt> TryFrom<&'row Row<'stmt>, Error = rusqlite::Error>,
214{
215    let mut connection = Connection::open_in_memory()?;
216    let mut cursor = std::io::Cursor::new(bytes);
217    connection.deserialize_read_exact("main", &mut cursor, bytes.len(), true)?;
218    load_db(connection)
219}
220
221mod test {
222    #![allow(unused_imports)]
223
224    use crate::data::Card;
225    use crate::data::CoreCard;
226    use crate::constants::*;
227    use crate::data::card::SIZE_SETCODE;
228
229    #[test]
230    fn validate_core_card_raw_bytes() {
231        let card = CoreCard {
232            code: 0xAABBCCDD,
233            alias: 0x11223344,
234            setcode: {
235                let mut sc = [0u16; SIZE_SETCODE];
236                sc[0] = 0x5566;
237                sc[1] = 0x7788;
238                sc[2] = 0x99aa;
239                sc
240            },
241            card_type: Type::Monster | Type::Effect,
242            level: 8,
243            attribute: Attribute::Dark,
244            race: Race::Dragon,
245            attack: 3000,
246            defense: 2500,
247            left_scale: 4,
248            right_scale: 4,
249            link_marker: Linkmarkers::Bottom | Linkmarkers::Top,
250            rule_code: 0xDEADBEEF,
251        };
252        unsafe {
253            let p = &card as *const CoreCard as *const u8;
254            let n = std::mem::size_of::<CoreCard>();
255            let bytes = std::slice::from_raw_parts(p, n);
256            assert_eq!(
257                bytes,
258                &[
259                    0xdd, 0xcc, 0xbb, 0xaa, // code
260                    0x44, 0x33, 0x22, 0x11, // alias
261                    // setcode[0..16] = 32 bytes
262                    0x66, 0x55, 0x88, 0x77, 0xaa, 0x99, 0x00, 0x00,
263                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
264                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
265                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
266                    0x21, 0x00, 0x00, 0x00, // card_type
267                    0x08, 0x00, 0x00, 0x00, // level
268                    0x20, 0x00, 0x00, 0x00, // attribute
269                    0x00, 0x20, 0x00, 0x00, // race
270                    0xb8, 0x0b, 0x00, 0x00, // attack
271                    0xc4, 0x09, 0x00, 0x00, // defense
272                    0x04, 0x00, 0x00, 0x00, // left_scale
273                    0x04, 0x00, 0x00, 0x00, // right_scale
274                    0x82, 0x00, 0x00, 0x00, // link_marker
275                    0xef, 0xbe, 0xad, 0xde, // rule_code
276                ][..]
277            );
278        }
279    }
280}