siglus_cfx_decompiler/
names.rs1use std::collections::BTreeSet;
2use std::path::Path;
3
4use crate::cfx::ShaderBlob;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum StringEncoding {
8 Ascii,
9 Utf16Le,
10}
11
12impl StringEncoding {
13 pub fn as_str(self) -> &'static str {
14 match self {
15 Self::Ascii => "ascii",
16 Self::Utf16Le => "utf16le",
17 }
18 }
19}
20
21#[derive(Debug, Clone)]
22pub struct ExtractedString {
23 pub offset: usize,
24 pub encoding: StringEncoding,
25 pub text: String,
26}
27
28pub fn extract_strings(data: &[u8]) -> Vec<ExtractedString> {
29 let mut out = Vec::new();
30 out.extend(extract_ascii_strings(data, 3));
31 out.extend(extract_utf16le_strings(data, 3));
32 out.sort_by_key(|s| {
33 (
34 s.offset,
35 match s.encoding {
36 StringEncoding::Ascii => 0u8,
37 StringEncoding::Utf16Le => 1u8,
38 },
39 )
40 });
41 out.dedup_by(|a, b| a.offset == b.offset && a.encoding == b.encoding && a.text == b.text);
42 out
43}
44
45fn extract_ascii_strings(data: &[u8], min_len: usize) -> Vec<ExtractedString> {
46 let mut out = Vec::new();
47 let mut i = 0usize;
48 while i < data.len() {
49 while i < data.len() && !is_printable_ascii(data[i]) {
50 i += 1;
51 }
52 let start = i;
53 while i < data.len() && is_printable_ascii(data[i]) {
54 i += 1;
55 }
56 if i.saturating_sub(start) >= min_len {
57 let text = String::from_utf8_lossy(&data[start..i]).to_string();
58 out.push(ExtractedString {
59 offset: start,
60 encoding: StringEncoding::Ascii,
61 text,
62 });
63 }
64 }
65 out
66}
67
68fn extract_utf16le_strings(data: &[u8], min_chars: usize) -> Vec<ExtractedString> {
69 let mut out = Vec::new();
70 let mut i = 0usize;
71 while i + 1 < data.len() {
72 while i + 1 < data.len() && !is_printable_utf16le_at(data, i) {
73 i += 2;
74 }
75 let start = i;
76 let mut chars = Vec::new();
77 while i + 1 < data.len() && is_printable_utf16le_at(data, i) {
78 chars.push(u16::from_le_bytes([data[i], data[i + 1]]));
79 i += 2;
80 }
81 if chars.len() >= min_chars
82 && let Ok(text) = String::from_utf16(&chars)
83 {
84 out.push(ExtractedString {
85 offset: start,
86 encoding: StringEncoding::Utf16Le,
87 text,
88 });
89 }
90 if i == start {
91 i += 2;
92 }
93 }
94 out
95}
96
97fn is_printable_ascii(b: u8) -> bool {
98 matches!(b, 0x20..=0x7e)
99}
100
101fn is_printable_utf16le_at(data: &[u8], off: usize) -> bool {
102 if off + 1 >= data.len() {
103 return false;
104 }
105 let c = u16::from_le_bytes([data[off], data[off + 1]]);
106 matches!(c, 0x20..=0x7e)
107}
108
109pub fn is_hlsl_identifier(s: &str) -> bool {
110 let mut chars = s.chars();
111 let Some(first) = chars.next() else {
112 return false;
113 };
114 if !(first == '_' || first.is_ascii_alphabetic()) {
115 return false;
116 }
117 chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
118}
119
120pub fn looks_like_technique_name(s: &str) -> bool {
121 s.starts_with("tec_") || s.starts_with("tech_") || s.starts_with("technique")
122}
123
124pub fn looks_like_shader_function_name(s: &str) -> bool {
125 if !is_hlsl_identifier(s) {
126 return false;
127 }
128 let lower = s.to_ascii_lowercase();
129 lower.starts_with("vs_")
130 || lower.starts_with("ps_")
131 || lower.starts_with("v_")
132 || lower.starts_with("p_")
133 || lower.starts_with("vertex")
134 || lower.starts_with("pixel")
135}
136
137pub fn looks_like_interface_or_struct_name(s: &str) -> bool {
138 if !is_hlsl_identifier(s) {
139 return false;
140 }
141 let lower = s.to_ascii_lowercase();
142 lower.contains("input")
143 || lower.contains("output")
144 || lower.ends_with("_in")
145 || lower.ends_with("_out")
146 || lower.ends_with("interface")
147 || lower.ends_with("struct")
148}
149
150pub fn format_original_name_report(input: &Path, data: &[u8], shaders: &[ShaderBlob]) -> String {
151 let strings = extract_strings(data);
152 let mut identifiers = BTreeSet::new();
153 let mut techniques = BTreeSet::new();
154 let mut shader_function_candidates = BTreeSet::new();
155 let mut interface_candidates = BTreeSet::new();
156
157 for s in &strings {
158 if is_hlsl_identifier(&s.text) {
159 identifiers.insert(s.text.clone());
160 }
161 if looks_like_technique_name(&s.text) {
162 techniques.insert(s.text.clone());
163 }
164 if looks_like_shader_function_name(&s.text) {
165 shader_function_candidates.insert(s.text.clone());
166 }
167 if looks_like_interface_or_struct_name(&s.text) {
168 interface_candidates.insert(s.text.clone());
169 }
170 }
171
172 let mut out = String::new();
173 out.push_str(&format!("input: {}\n", input.display()));
174 out.push('\n');
175 out.push_str("name_recovery_policy:\n");
176 out.push_str(" exact_shader_entry_function_name: only used if explicitly stored in source/debug/effect metadata; SM2 bytecode CTAB does not carry it.\n");
177 out.push_str(" exact_interface_type_name: only used if explicitly stored in source/debug/effect metadata; SM2 bytecode declarations carry semantics, not original HLSL struct/interface type names.\n");
178 out.push_str(" uniforms_samplers_and_struct_members: recovered from CTAB when present.\n");
179 out.push_str(" technique_pass_names: recovered only after effect-container parsing; raw shader scanning alone cannot map them to a shader safely.\n");
180 out.push('\n');
181
182 out.push_str("shader_ctab_names:\n");
183 for shader in shaders {
184 out.push_str(&format!(
185 " {} offset=0x{:08x} profile={}\n",
186 shader.file_prefix(),
187 shader.offset,
188 shader.profile()
189 ));
190 if let Some(ctab) = &shader.ctab {
191 if let Some(creator) = &ctab.creator {
192 out.push_str(&format!(" creator: {}\n", creator));
193 }
194 if let Some(target) = &ctab.target {
195 out.push_str(&format!(" target: {}\n", target));
196 }
197 for c in &ctab.constants {
198 out.push_str(&format!(
199 " {} {} {} count={}\n",
200 c.register_name(),
201 c.hlsl_decl_type(),
202 c.name,
203 c.register_count
204 ));
205 if let Some(t) = &c.type_info {
206 for m in &t.members {
207 out.push_str(&format!(
208 " member {} {}\n",
209 m.type_info.hlsl_type_name(),
210 m.name
211 ));
212 }
213 }
214 }
215 } else {
216 out.push_str(" no CTAB\n");
217 }
218 }
219
220 out.push_str("\ntechnique_name_candidates_from_container_strings:\n");
221 for name in techniques.iter().take(2000) {
222 out.push_str(&format!(" {}\n", name));
223 }
224 if techniques.len() > 2000 {
225 out.push_str(&format!(" ... {} more\n", techniques.len() - 2000));
226 }
227
228 out.push_str("\nshader_function_name_candidates_from_container_strings:\n");
229 for name in shader_function_candidates.iter().take(2000) {
230 out.push_str(&format!(" {}\n", name));
231 }
232 if shader_function_candidates.len() > 2000 {
233 out.push_str(&format!(
234 " ... {} more\n",
235 shader_function_candidates.len() - 2000
236 ));
237 }
238
239 out.push_str("\ninterface_or_struct_name_candidates_from_container_strings:\n");
240 for name in interface_candidates.iter().take(2000) {
241 out.push_str(&format!(" {}\n", name));
242 }
243 if interface_candidates.len() > 2000 {
244 out.push_str(&format!(
245 " ... {} more\n",
246 interface_candidates.len() - 2000
247 ));
248 }
249
250 out.push_str("\nall_identifier_strings_from_container:\n");
251 for name in identifiers.iter().take(5000) {
252 out.push_str(&format!(" {}\n", name));
253 }
254 if identifiers.len() > 5000 {
255 out.push_str(&format!(" ... {} more\n", identifiers.len() - 5000));
256 }
257
258 out.push_str("\nall_strings_with_offsets:\n");
259 for s in strings.iter().take(10000) {
260 out.push_str(&format!(
261 " 0x{:08x} {} {}\n",
262 s.offset,
263 s.encoding.as_str(),
264 s.text
265 ));
266 }
267 if strings.len() > 10000 {
268 out.push_str(&format!(" ... {} more\n", strings.len() - 10000));
269 }
270
271 out
272}