Skip to main content

ygopro_data/data/
lflist.rs

1//! The forbidden/limited list (limit list).
2//!
3//! Provides [`LFList`], which holds the card restrictions and computes the list hash.
4
5use std::collections::HashMap;
6
7const HASH_INITIAL_VALUE: u32 = 0x7dfcee6a;
8const GENESYS_HASH_MARKER: u32 = 0x43524544;
9const GENESYS_HASH_KEY: u32 = 0x965f7da9;
10
11#[derive(Debug, Clone)]
12pub struct LFList {
13    pub hash: u32,
14    pub name: String,
15    pub content: HashMap<u32, u8>,
16    pub genesys: u32,
17    pub glist: HashMap<u32, u32>,
18}
19
20impl LFList {
21    pub fn new(name: String) -> Self {
22        Self {
23            hash: HASH_INITIAL_VALUE,
24            name,
25            genesys: 0,
26            content: HashMap::new(),
27            glist: HashMap::new(),
28        }
29    }
30
31    pub fn from(name: String, content: HashMap<u32, u8>, genesys: u32, glist: HashMap<u32, u32>) -> Self {
32        let mut v = Self {
33            hash: HASH_INITIAL_VALUE,
34            name,
35            content,
36            genesys,
37            glist,
38        };
39        v.calculate_hash();
40        v
41    }
42
43    pub fn calculate_hash(&mut self) {
44        let mut hash = HASH_INITIAL_VALUE;
45        if self.genesys > 0 {
46            hash ^= ((GENESYS_HASH_KEY  << 18) | (GENESYS_HASH_KEY  >> 14)) ^ ((self.genesys << 9) | (self.genesys >> 23)) ^ ((GENESYS_HASH_MARKER << 27) | (GENESYS_HASH_MARKER >> 5));
47        }
48        for (&code, &ct) in &self.content {
49            hash ^= ((code << 18) | (code >> 14)) ^ ((code << (27 + ct)) | (code >> (5 - ct)));
50        }
51        for (&code, &ct) in &self.glist {
52            hash ^= ((code << 18) | (code >> 14)) ^ ((GENESYS_HASH_KEY  << 9) | (GENESYS_HASH_KEY  >> 23)) ^ ((ct << 27) | (ct >> 5));
53        }
54        self.hash = hash;
55    }
56    
57}
58
59pub fn parse_lflist_content(content: &str) -> Vec<LFList> {
60    let mut lists = Vec::new();
61    let mut name = String::new();
62    let mut limits = HashMap::new();
63    let mut genesys = 0;
64    let mut genesys_limits = HashMap::new();
65    for line in content.lines().map(|l| l.trim()) {
66        if line.is_empty() || line.starts_with('#') { continue; }
67        if line.starts_with('!') {
68            if !name.is_empty() {
69                lists.push(LFList::from(
70                    std::mem::take(&mut name),
71                    std::mem::take(&mut limits),
72                    std::mem::take(&mut genesys),
73                    std::mem::take(&mut genesys_limits),
74                ));
75            }
76            name = line[1..].to_string();
77            continue;
78        }
79        let parts: Vec<&str> = line.split_whitespace().collect();
80        if parts.len() >= 2 {
81            if parts[0] == "$genesys" {
82                genesys = parts[1].trim().parse::<u32>().unwrap_or(0);
83            } else if let (Ok(card_code), Ok(limit)) = (u32::from_str_radix(parts[0].trim(), 10), parts[1].trim().parse::<u8>()) {
84                if limit <= 2 { limits.insert(card_code, limit); }
85            } else if parts.len() >= 3 && parts[1] == "$genesys" {
86                if let (Ok(card_code), Ok(limit)) = (u32::from_str_radix(parts[0].trim(), 10), parts[2].trim().parse::<u32>()) {
87                    genesys_limits.insert(card_code, limit);
88                }
89            }
90        }
91    }
92    if !name.is_empty() { lists.push(LFList::from(name, limits, genesys, genesys_limits)); }
93    lists
94}
95
96#[cfg(test)]
97mod tests {
98    use crate::data::lflist::GENESYS_HASH_KEY;
99    use crate::data::lflist::parse_lflist_content;
100
101    /// How we get genesys hash key.
102    #[test]
103    #[ignore]
104    fn genesys_genesys_hash_key() {
105        let mut hash: u32 = 2166136261;
106        for byte in "genesys".bytes() {
107            hash ^= byte as u32;
108            hash = hash.wrapping_mul(16777619);
109        }
110        println!("Computed genesys hash key: 0x{:08x}", hash);
111        assert_eq!(GENESYS_HASH_KEY, hash);
112    }
113
114    /// Reference C++ code used to compute the expected hash:
115    ///
116    /// ```
117    /// #include <cstdint>
118    /// #include <cstdio>
119    /// #include <unordered_map>
120    ///
121    /// int main() {
122    ///     const uint32_t HASH_INITIAL_VALUE = 0x7dfcee6a;
123    ///     std::unordered_map<uint32_t, uint8_t> content = {
124    ///         {20292186, 0},
125    ///         {91869203, 0},
126    ///         {8633261, 1},
127    ///         {79606837, 1},
128    ///         {23434538, 1},
129    ///         {72270339, 2},
130    ///     };
131    ///     uint32_t hash = HASH_INITIAL_VALUE;
132    ///     for (auto& [code, count] : content) {
133    ///         hash = hash ^ ((code << 18) | (code >> 14)) ^ ((code << (27 + count)) | (code >> (5 - count)));
134    ///     }
135    ///     std::printf("hash = 0x%08x\n", hash);
136    /// }
137    /// ```
138    #[test]
139    fn calculate_hash_matches_cpp_reference() {
140        let content = "
141!test
14220292186 0
14391869203 0
1448633261 1
14579606837 1
14623434538 1
14772270339 2
148";
149        let lists = parse_lflist_content(content);
150        assert_eq!(lists.len(), 1);
151        assert_eq!(lists[0].hash, 0xdee2c97b);
152    }
153}