Skip to main content

pe_assembler/formats/lib/reader/
mod.rs

1use crate::{
2    helpers::CoffReader,
3    types::{
4        coff::{ArchiveMember, ArchiveMemberHeader, CoffFileType, CoffInfo, StaticLibrary},
5        CoffHeader, CoffObject, SectionHeader,
6    },
7};
8use gaia_binary::ReadBytesExt;
9use gaia_types::{
10    helpers::{Architecture, Url},
11    GaiaDiagnostics, GaiaError,
12};
13use std::io::{Read, Seek};
14
15/// LIB structure, lazy reader
16#[derive(Debug)]
17pub struct LibReader<R> {
18    reader: R,
19    url: Option<Url>,
20    lazy_library: Option<StaticLibrary>,
21    lazy_info: Option<CoffInfo>,
22    errors: Vec<GaiaError>,
23}
24
25impl<R> LibReader<R> {
26    pub fn new(reader: R) -> Self {
27        Self { reader, url: None, lazy_library: None, lazy_info: None, errors: vec![] }
28    }
29    pub fn with_url(mut self, url: Url) -> Self {
30        self.url = Some(url);
31        self
32    }
33    pub fn finish(mut self) -> GaiaDiagnostics<StaticLibrary>
34    where
35        R: Read + Seek,
36    {
37        if self.lazy_library.is_none() {
38            if let Err(e) = self.read_library() {
39                return GaiaDiagnostics { result: Err(e), diagnostics: self.errors };
40            }
41        }
42        match self.lazy_library {
43            Some(s) => GaiaDiagnostics { result: Ok(s), diagnostics: self.errors },
44            None => unreachable!(),
45        }
46    }
47}
48
49impl<R: Read + Seek> CoffReader<R> for LibReader<R> {
50    fn get_viewer(&mut self) -> &mut R {
51        &mut self.reader
52    }
53
54    fn add_diagnostics(&mut self, error: impl Into<GaiaError>) {
55        self.errors.push(error.into())
56    }
57
58    fn get_coff_header(&mut self) -> Result<&CoffHeader, GaiaError> {
59        Err(GaiaError::not_implemented("LibReader does not support direct reading of COFF headers, please use member objects"))
60    }
61
62    fn set_coff_header(&mut self, _head: CoffHeader) -> Option<CoffHeader> {
63        None // LibReader does not support setting COFF headers
64    }
65
66    fn get_section_headers(&mut self) -> Result<&[SectionHeader], GaiaError> {
67        Err(GaiaError::not_implemented(
68            "LibReader does not support direct reading of section headers, please use member objects",
69        ))
70    }
71
72    fn set_section_headers(&mut self, _headers: Vec<SectionHeader>) -> Vec<SectionHeader> {
73        Vec::new() // LibReader does not support setting section headers
74    }
75
76    fn get_coff_object(&mut self) -> Result<&CoffObject, GaiaError> {
77        Err(GaiaError::not_implemented("LibReader does not support direct reading of COFF objects, please use member objects"))
78    }
79
80    fn set_coff_object(&mut self, _object: CoffObject) -> Option<CoffObject> {
81        None // LibReader does not support setting COFF objects
82    }
83
84    fn get_coff_info(&mut self) -> Result<&CoffInfo, GaiaError> {
85        if self.lazy_info.is_none() {
86            let info = self.create_lib_info()?;
87            self.lazy_info = Some(info);
88        }
89        Ok(self.lazy_info.as_ref().unwrap())
90    }
91
92    fn set_coff_info(&mut self, info: CoffInfo) -> Option<CoffInfo> {
93        self.lazy_info.replace(info)
94    }
95}
96
97impl<R: Read + Seek> LibReader<R> {
98    /// Check if it is a valid static library file
99    pub fn is_valid_lib(&mut self) -> Result<bool, GaiaError> {
100        let mut magic = [0u8; 8];
101        self.reader.read_exact(&mut magic)?;
102        self.reader.seek(std::io::SeekFrom::Start(0))?;
103        Ok(&magic == b"!<arch>\n")
104    }
105
106    /// View static library file information
107    pub fn view(&mut self) -> Result<CoffInfo, GaiaError> {
108        if let Some(ref info) = self.lazy_info {
109            return Ok(info.clone());
110        }
111
112        let info = self.create_lib_info()?;
113        self.lazy_info = Some(info.clone());
114        Ok(info)
115    }
116
117    /// Read static library
118    pub fn read_library(&mut self) -> Result<&StaticLibrary, GaiaError> {
119        if self.lazy_library.is_none() {
120            self.lazy_library = Some(self.read_library_force()?);
121        }
122        match self.lazy_library.as_ref() {
123            Some(s) => Ok(s),
124            None => unreachable!(),
125        }
126    }
127
128    /// Force read static library (without cache)
129    fn read_library_force(&mut self) -> Result<StaticLibrary, GaiaError> {
130        // Verify file header
131        if !self.is_valid_lib()? {
132            return Err(GaiaError::invalid_data("Not a valid static library file"));
133        }
134
135        // Skip file header "!<arch>\n" (8 bytes)
136        self.reader.seek(std::io::SeekFrom::Start(8))?;
137
138        let mut members = Vec::new();
139        let mut symbol_index = Vec::new();
140        let file_size = self.get_file_size()?;
141
142        println!("Starting library file analysis, file size: {} bytes", file_size);
143        println!("After skipping the file header, reading members starting from position 8");
144
145        // Read all members
146        while self.get_position()? < file_size {
147            let current_pos = self.get_position()?;
148            println!("Current position: {}, Remaining: {} bytes", current_pos, file_size - current_pos);
149
150            // Check if there is enough data to read the member header (60 bytes)
151            if current_pos + 60 > file_size {
152                println!("Remaining data less than 60 bytes, stopping analysis");
153                break;
154            }
155
156            match self.read_member() {
157                Ok(member) => {
158                    println!("Read member: '{}', Size: {} bytes", member.header.name, member.header.size);
159
160                    // Check if it's a symbol table (supports traditional format "/" and modern format "/<ECSYMBOLS>")
161                    if member.header.name == "/" || member.header.name.starts_with("/<ECSYMBOLS>") {
162                        println!("Found symbol table: '{}', starting symbol analysis", member.header.name);
163                        println!("Symbol table data size: {} bytes", member.data.len());
164                        if member.data.len() >= 4 {
165                            let symbol_count_be =
166                                u32::from_be_bytes([member.data[0], member.data[1], member.data[2], member.data[3]]);
167                            println!("Symbol table header shows symbol count: {}", symbol_count_be);
168                            // Print the hex content of the first 16 bytes
169                            let preview_len = std::cmp::min(16, member.data.len());
170                            let hex_preview: String =
171                                member.data[..preview_len].iter().map(|b| format!("{:02X}", b)).collect::<Vec<_>>().join(" ");
172                            println!("First {} bytes of symbol table content: {}", preview_len, hex_preview);
173                        }
174                        // This is a symbol table, parsing symbols
175                        match self.parse_symbol_table(&member.data, members.len()) {
176                            Ok(symbols) => {
177                                println!("Successfully parsed {} symbols", symbols.len());
178                                if !symbols.is_empty() {
179                                    println!("First 5 symbols: {:?}", &symbols[..std::cmp::min(5, symbols.len())]);
180                                }
181                                symbol_index.extend(symbols);
182                            }
183                            Err(e) => {
184                                println!("Symbol table analysis failed: {:?}", e);
185                            }
186                        }
187                    }
188                    else if member.header.name == "//" {
189                        println!("Found extended name table, skipping");
190                        // This is an extended name table, skipping
191                    }
192                    else {
193                        println!("Found ordinary member: {}", member.header.name);
194                        // This is an ordinary member
195                    }
196                    members.push(member);
197                }
198                Err(e) => {
199                    // If reading fails, record the error but continue
200                    println!("Failed to read member: {:?}", e);
201                    self.add_diagnostics(e);
202                    break;
203                }
204            }
205        }
206
207        println!("Analysis complete, total members: {}, total symbols: {}", members.len(), symbol_index.len());
208        Ok(StaticLibrary { signature: "!<arch>\n".to_string(), members, symbol_index })
209    }
210
211    /// Create library info
212    fn create_lib_info(&mut self) -> Result<CoffInfo, GaiaError> {
213        let file_size = self.get_file_size()?;
214        let library = self.read_library()?;
215
216        Ok(CoffInfo {
217            file_type: CoffFileType::StaticLibrary,
218            target_arch: Architecture::Unknown,
219            section_count: 0,
220            symbol_count: library.symbol_index.len() as u32,
221            file_size,
222            timestamp: 0,
223        })
224    }
225
226    /// Get file size
227    pub fn get_file_size(&mut self) -> Result<u64, GaiaError> {
228        let current_pos = self.get_position()?;
229        let size = self.reader.seek(std::io::SeekFrom::End(0))?;
230        self.set_position(current_pos)?;
231        Ok(size)
232    }
233
234    /// Read member
235    fn read_member(&mut self) -> Result<ArchiveMember, GaiaError> {
236        let header = self.read_member_header()?;
237        let mut data = vec![0u8; header.size as usize];
238        self.reader.read_exact(&mut data)?;
239
240        // Align to even boundaries
241        if header.size % 2 == 1 {
242            self.reader.read_u8()?;
243        }
244
245        // Attempt to parse COFF object (if data is in valid COFF format)
246        let coff_object = if data.len() > 20 {
247            // Attempt to read COFF object from data
248            // Return None for now as a specific CoffReader implementation is needed
249            // TODO: Implement a simple COFF object parser
250            None
251        }
252        else {
253            None
254        };
255
256        Ok(ArchiveMember { header, data, coff_object })
257    }
258
259    /// Parse symbol table
260    fn parse_symbol_table(&self, data: &[u8], member_index: usize) -> Result<Vec<(String, usize)>, GaiaError> {
261        let mut symbols = Vec::new();
262
263        if data.len() < 4 {
264            return Ok(symbols);
265        }
266
267        // Read symbol count (first 4 bytes, big-endian)
268        let symbol_count = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
269
270        if symbol_count == 0 || symbol_count > 100000 {
271            // Unreasonable symbol count, might not be a standard symbol table format
272            return Ok(symbols);
273        }
274
275        // Skip symbol offset table (4 bytes offset per symbol)
276        let string_table_start = 4 + symbol_count * 4;
277
278        if string_table_start >= data.len() {
279            return Ok(symbols);
280        }
281
282        // Parse string table
283        let string_data = &data[string_table_start..];
284        let mut current_pos = 0;
285
286        while current_pos < string_data.len() && symbols.len() < symbol_count {
287            // Find the next null terminator
288            if let Some(null_pos) = string_data[current_pos..].iter().position(|&b| b == 0) {
289                if null_pos > 0 {
290                    if let Ok(symbol_name) = std::str::from_utf8(&string_data[current_pos..current_pos + null_pos]) {
291                        symbols.push((symbol_name.to_string(), member_index));
292                    }
293                }
294                current_pos += null_pos + 1;
295            }
296            else {
297                break;
298            }
299        }
300
301        Ok(symbols)
302    }
303
304    /// Read member header
305    fn read_member_header(&mut self) -> Result<ArchiveMemberHeader, GaiaError> {
306        let mut name = [0u8; 16];
307        self.reader.read_exact(&mut name)?;
308
309        let mut date = [0u8; 12];
310        self.reader.read_exact(&mut date)?;
311
312        let mut uid = [0u8; 6];
313        self.reader.read_exact(&mut uid)?;
314
315        let mut gid = [0u8; 6];
316        self.reader.read_exact(&mut gid)?;
317
318        let mut mode = [0u8; 8];
319        self.reader.read_exact(&mut mode)?;
320
321        let mut size = [0u8; 10];
322        self.reader.read_exact(&mut size)?;
323
324        let mut end_chars = [0u8; 2];
325        self.reader.read_exact(&mut end_chars)?;
326
327        println!("Member header terminator: {:02X} {:02X} (Expected: 60 0A)", end_chars[0], end_chars[1]);
328
329        if &end_chars != b"`\n" {
330            return Err(GaiaError::invalid_data("Invalid member header terminator"));
331        }
332
333        // Parse fields - fields in ar format are ASCII strings, padded with spaces on the right
334        let name_str = std::str::from_utf8(&name).map_err(|_| GaiaError::invalid_data("Invalid name field"))?;
335        // Names in ar format end with a slash, then padded with spaces
336        let name = name_str.trim_end_matches(' ').trim_end_matches('/').to_string();
337
338        let date_str = std::str::from_utf8(&date).map_err(|_| GaiaError::invalid_data("Invalid date field"))?;
339        let timestamp = date_str.trim_end_matches(' ').parse::<u32>().unwrap_or(0);
340
341        let uid_str = std::str::from_utf8(&uid).map_err(|_| GaiaError::invalid_data("Invalid user ID field"))?;
342        let user_id = uid_str.trim_end_matches(' ').parse::<u16>().unwrap_or(0);
343
344        let gid_str = std::str::from_utf8(&gid).map_err(|_| GaiaError::invalid_data("Invalid group ID field"))?;
345        let group_id = gid_str.trim_end_matches(' ').parse::<u16>().unwrap_or(0);
346
347        let mode_str = std::str::from_utf8(&mode).map_err(|_| GaiaError::invalid_data("Invalid mode field"))?;
348        let mode = u32::from_str_radix(mode_str.trim_end_matches(' '), 8).unwrap_or(0); // Mode field is octal
349
350        let size_str = std::str::from_utf8(&size).map_err(|_| GaiaError::invalid_data("Invalid size field"))?;
351        let size = size_str.trim_end_matches(' ').parse::<u32>().unwrap_or(0);
352        Ok(ArchiveMemberHeader { name, timestamp, user_id, group_id, mode, size })
353    }
354}