winereg/
registry_parser.rs1use 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 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 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 }
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 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; } 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(); 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 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