ygopro_data/data/
lflist.rs1use 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 #[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 #[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}