Skip to main content

winereg/
registry_parser.rs

1use std::fs;
2use std::path::Path;
3
4use crate::architecture::Architecture;
5use crate::registry_key::{KeyNode, RegistryKey};
6use crate::registry_utils::{timestamp_to_filetime};
7use crate::registry_value::{RegistryValue, RegistryValueData, REG_BINARY, REG_QWORD};
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum ParseError {
12    #[error("invalid header")]
13    InvalidHeader,
14    #[error("io error: {0}")]
15    Io(#[from] std::io::Error),
16    #[error("parse error at line {line}: {msg}")]
17    Line { line: usize, msg: String },
18}
19
20#[derive(Debug)]
21pub struct LoadResult {
22    pub root_key: KeyNode,
23    pub relative_base: String,
24    pub architecture: Architecture,
25}
26
27pub struct RegistryParser;
28
29impl RegistryParser {
30    pub fn load_from_file<P: AsRef<Path>>(&self, path: P) -> Result<LoadResult, ParseError> {
31        let text = fs::read_to_string(path)?;
32        self.load_from_text(&text)
33    }
34
35    pub fn load_from_text(&self, text: &str) -> Result<LoadResult, ParseError> {
36        let lines: Vec<&str> = text.lines().collect();
37        if lines.is_empty() {
38            return Err(ParseError::InvalidHeader);
39        }
40        let mut line_idx = 0usize;
41
42        // header
43        if lines[0].trim() != "WINE REGISTRY Version 2" {
44            return Err(ParseError::InvalidHeader);
45        }
46        line_idx += 1;
47
48        let root = RegistryKey::create_root();
49        let mut relative_base = String::new();
50        let mut architecture = Architecture::Unknown;
51        let mut current_key: Option<KeyNode> = None;
52
53        while line_idx < lines.len() {
54            let raw = lines[line_idx];
55            let trimmed = raw.trim();
56            line_idx += 1;
57            if trimmed.is_empty() {
58                continue;
59            }
60            if trimmed.starts_with(";; All keys relative to ") {
61                relative_base = trimmed[";; All keys relative to ".len()..].to_string();
62                continue;
63            }
64            if trimmed.starts_with(';') {
65                continue;
66            }
67            if trimmed.starts_with("#arch=") {
68                if let Some(a) = Architecture::from_tag(&trimmed["#arch=".len()..]) {
69                    architecture = a;
70                }
71                continue;
72            }
73            if trimmed.starts_with('[') {
74                let (path, timestamp) = parse_key_header(trimmed).map_err(|msg| ParseError::Line { line: line_idx, msg })?;
75                let key_path = unescape_key_path(&path);
76                let key_node = RegistryKey::create_key_recursive(&root, &key_path);
77                {
78                    let mut guard = key_node.borrow_mut();
79                    guard.modification_time = timestamp_to_filetime(timestamp);
80                }
81                current_key = Some(key_node);
82                continue;
83            }
84            if trimmed.starts_with("#time=") {
85                if let Some(ref key) = current_key {
86                    if let Ok(val) = u64::from_str_radix(trimmed["#time=".len()..].trim(), 16) {
87                        key.borrow_mut().modification_time = val;
88                    }
89                }
90                continue;
91            }
92            if trimmed.starts_with("#class=") {
93                if let Some(ref key) = current_key {
94                    let cls = trimmed["#class=".len()..].trim();
95                    let unquoted = cls.trim_matches('"').to_string();
96                    key.borrow_mut().class_name = Some(unescape_string(&unquoted));
97                }
98                continue;
99            }
100            if trimmed.starts_with("#link") {
101                if let Some(ref key) = current_key {
102                    key.borrow_mut().is_symlink = true;
103                }
104                continue;
105            }
106
107            // value line
108            if trimmed.starts_with('@') || trimmed.starts_with('"') {
109                let (value, consumed) = parse_value_line(trimmed, &lines[(line_idx)..]).map_err(|msg| ParseError::Line { line: line_idx, msg })?;
110                if let Some(ref key) = current_key {
111                    key.borrow_mut().set_value_for_loading(value.name.clone(), value);
112                } else {
113                    return Err(ParseError::Line { line: line_idx, msg: "value without key".into() });
114                }
115                line_idx += consumed;
116                continue;
117            }
118
119            // unknown line - skip
120        }
121
122        Ok(LoadResult {
123            root_key: root,
124            relative_base,
125            architecture,
126        })
127    }
128}
129
130fn parse_key_header(line: &str) -> Result<(String, u64), String> {
131    if !line.starts_with('[') || !line.contains(']') {
132        return Err(format!("malformed key header: {}", line));
133    }
134    let end = line.find(']').unwrap();
135    let key_path = line[1..end].trim().to_string();
136    let rest = line[end + 1..].trim();
137    let timestamp = if rest.is_empty() {
138        0
139    } else {
140        rest.parse::<u64>().unwrap_or(0)
141    };
142    Ok((key_path, timestamp))
143}
144
145fn parse_value_line(first_line: &str, rest: &[&str]) -> Result<(RegistryValue, usize), String> {
146    let mut buffer = String::new();
147    buffer.push_str(first_line.trim_end());
148    let mut consumed = 0usize;
149
150    // consume continuation lines for hex data
151    while buffer.trim_end().ends_with('\\') {
152        buffer = buffer.trim_end_matches('\\').trim_end().to_string();
153        if consumed >= rest.len() {
154            break;
155        }
156        buffer.push_str(rest[consumed].trim());
157        consumed += 1;
158    }
159
160    let name;
161    let cursor: usize;
162    if buffer.starts_with("@=") {
163        name = String::new();
164        cursor = 1; // position at '='
165    } else if buffer.starts_with('"') {
166        let mut i = 1;
167        let bytes = buffer.as_bytes();
168        while i < bytes.len() {
169            if bytes[i] == b'\\' {
170                i += 2;
171                continue;
172            }
173            if bytes[i] == b'"' {
174                break;
175            }
176            i += 1;
177        }
178        if i >= bytes.len() {
179            return Err("unterminated value name".into());
180        }
181        let raw_name = &buffer[1..i];
182        name = unescape_string(raw_name);
183        cursor = i;
184    } else {
185        return Err("invalid value line".into());
186    }
187
188    let mut after_name = buffer[cursor + 1..].trim_start(); // skip '='
189    if after_name.starts_with('=') {
190        after_name = after_name[1..].trim_start();
191    }
192    let value = parse_value_data(after_name, name.clone())?;
193    Ok((value, consumed))
194}
195
196fn parse_value_data(data: &str, name: String) -> Result<RegistryValue, String> {
197    if data.starts_with("str(2):") {
198        let s = parse_quoted_string(&data["str(2):".len()..])?;
199        return Ok(RegistryValue::new(name, RegistryValueData::ExpandString(s)));
200    }
201    if data.starts_with("str(7):") {
202        let s = parse_quoted_string(&data["str(7):".len()..])?;
203        let parts: Vec<String> = s.split('\u{0}').filter(|v| !v.is_empty()).map(|v| v.to_string()).collect();
204        return Ok(RegistryValue::new(name, RegistryValueData::MultiString(parts)));
205    }
206    if data.starts_with("dword:") {
207        let hex = data["dword:".len()..].trim();
208        let val = u32::from_str_radix(hex, 16).map_err(|e| e.to_string())?;
209        return Ok(RegistryValue::new(name, RegistryValueData::Dword(val)));
210    }
211    if data.starts_with("qword:") {
212        let hex = data["qword:".len()..].trim();
213        let val = u64::from_str_radix(hex, 16).map_err(|e| e.to_string())?;
214        return Ok(RegistryValue::new(name, RegistryValueData::Qword(val)));
215    }
216    if data.starts_with("hex(") {
217        let end = data.find("):").ok_or("malformed hex type")?;
218        let type_hex = &data[4..end];
219        let ty = u32::from_str_radix(type_hex, 16).map_err(|e| e.to_string())?;
220        let bytes = parse_hex_bytes(&data[end + 2..])?;
221        if ty == REG_QWORD && bytes.len() == 8 {
222            let mut arr = [0u8; 8];
223            arr.copy_from_slice(&bytes[..8]);
224            let val = u64::from_le_bytes(arr);
225            return Ok(RegistryValue::new(name, RegistryValueData::Qword(val)));
226        }
227        return Ok(RegistryValue::new(name, RegistryValueData::Binary(bytes, ty)));
228    }
229    if data.starts_with("hex:") {
230        let bytes = parse_hex_bytes(&data["hex:".len()..])?;
231        return Ok(RegistryValue::new(name, RegistryValueData::Binary(bytes, REG_BINARY)));
232    }
233    if data.starts_with("hex(b):") {
234        let bytes = parse_hex_bytes(&data["hex(b):".len()..])?;
235        if bytes.len() == 8 {
236            let mut arr = [0u8; 8];
237            arr.copy_from_slice(&bytes[..8]);
238            let val = u64::from_le_bytes(arr);
239            return Ok(RegistryValue::new(name, RegistryValueData::Qword(val)));
240        }
241        return Ok(RegistryValue::new(name, RegistryValueData::Binary(bytes, REG_QWORD)));
242    }
243    // default string
244    let s = parse_quoted_string(data)?;
245    Ok(RegistryValue::new(name, RegistryValueData::String(s)))
246}
247
248fn parse_hex_bytes(s: &str) -> Result<Vec<u8>, String> {
249    let mut bytes = Vec::new();
250    for part in s.split(',') {
251        let trimmed = part.trim();
252        if trimmed.is_empty() {
253            continue;
254        }
255        let byte = u8::from_str_radix(trimmed, 16).map_err(|e| e.to_string())?;
256        bytes.push(byte);
257    }
258    Ok(bytes)
259}
260
261fn parse_quoted_string(data: &str) -> Result<String, String> {
262    let trimmed = data.trim();
263    if !trimmed.starts_with('"') || !trimmed.ends_with('"') {
264        return Err("expected quoted string".into());
265    }
266    Ok(unescape_string(&trimmed[1..trimmed.len() - 1]))
267}
268
269fn unescape_string(s: &str) -> String {
270    let mut out = String::new();
271    let chars: Vec<char> = s.chars().collect();
272    let mut i = 0;
273    while i < chars.len() {
274        let c = chars[i];
275        if c == '\\' && i + 1 < chars.len() {
276            let next = chars[i + 1];
277            match next {
278                'n' => out.push('\n'),
279                'r' => out.push('\r'),
280                't' => out.push('\t'),
281                '0' => out.push('\u{0}'),
282                '\\' => out.push('\\'),
283                '"' => out.push('"'),
284                _ => out.push(next),
285            }
286            i += 2;
287        } else {
288            out.push(c);
289            i += 1;
290        }
291    }
292    out
293}
294
295fn unescape_key_path(s: &str) -> String {
296    s.replace("\\\\", "\\")
297}
298