python_assembler/formats/pyc/reader/
mod.rs

1use crate::{
2    formats::pyc::{view::PycView, PycReadConfig},
3    program::PycHeader,
4};
5use byteorder::{LittleEndian, ReadBytesExt};
6use gaia_types::GaiaError;
7use std::io::{Read, Seek, SeekFrom};
8use std::process::{Command, Stdio};
9use std::io::Write as IoWrite;
10
11/// 负责解析 .pyc 文件到 LuacFile 的读取器
12#[derive(Debug)]
13pub struct LuacReader<'config, R> {
14    pub(crate) reader: R,
15    pub(crate) view: PycView,
16    // week errors
17    pub(crate) errors: Vec<GaiaError>,
18    pub(crate) config: &'config PycReadConfig,
19    pub(crate) offset: u64,
20}
21
22impl PycReadConfig {
23    pub fn as_reader<R: Read>(&self, reader: R) -> LuacReader<R> {
24        LuacReader::new(reader, self)
25    }
26}
27
28impl<'config, R> LuacReader<'config, R> {
29    pub fn new(reader: R, config: &'config PycReadConfig) -> Self {
30        Self { reader, view: PycView::default(), errors: vec![], config, offset: 0 }
31    }
32
33    pub fn get_offset(&self) -> u64 {
34        self.offset
35    }
36
37    pub fn set_offset(&mut self, offset: u64) -> Result<(), GaiaError>
38    where
39        R: Seek,
40    {
41        self.reader.seek(SeekFrom::Start(offset))?;
42        Ok(self.offset = offset)
43    }
44}
45
46impl<'config, R: Read + Seek> LuacReader<'config, R> {
47    pub fn read(mut self) -> Result<PycView, GaiaError> {
48        self.set_offset(0)?;
49        self.read_to_end()?;
50        Ok(self.view)
51    }
52
53    pub fn read_to_end(&mut self) -> Result<(), GaiaError> {
54        let magic = self.reader.read_u32::<LittleEndian>()?;
55        let flags = self.reader.read_u32::<LittleEndian>()?;
56        let timestamp = self.reader.read_u32::<LittleEndian>()?;
57        let size = self.reader.read_u32::<LittleEndian>()?;
58
59        let mut code_object_bytes = Vec::new();
60        self.reader.read_to_end(&mut code_object_bytes)?;
61
62        self.view.header = PycHeader { magic: magic.to_le_bytes(), flags, timestamp, size };
63
64        // 尝试使用系统 Python 解包 marshal,提取真正的 co_code 字节
65        // 如果失败,则保留原始数据(兼容旧逻辑,但会导致大量 UNKNOWN 指令)
66        match Self::extract_co_code_via_python(&code_object_bytes) {
67            Ok(Some(co_code)) => {
68                self.view.code_object_bytes = co_code;
69            }
70            _ => {
71                // 回退:保持原始字节(不建议,但至少不崩溃)
72                self.view.code_object_bytes = code_object_bytes;
73            }
74        }
75
76        Ok(())
77    }
78
79    /// 调用本机 Python,将 header 之后的 marshal 数据解包为 Code 对象,并返回其 co_code
80    fn extract_co_code_via_python(marshal_payload: &[u8]) -> Result<Option<Vec<u8>>, GaiaError> {
81        // 为了避免 base64 依赖,这里用十六进制传输数据
82        let hex = marshal_payload.iter().map(|b| format!("{:02x}", b)).collect::<String>();
83
84        // Python 端脚本:从 stdin 读取 hex,unhexlify -> marshal.loads -> 输出 co_code 原始字节
85        let py_snippet = r#"import sys, marshal, binascii
86data_hex = sys.stdin.read().strip()
87if not data_hex:
88    sys.exit(1)
89payload = binascii.unhexlify(data_hex)
90try:
91    co = marshal.loads(payload)
92except Exception as e:
93    sys.stderr.write(f"marshal.loads failed: {e}\n")
94    sys.exit(2)
95try:
96    sys.stdout.buffer.write(co.co_code)
97except Exception as e:
98    sys.stderr.write(f"emit co_code failed: {e}\n")
99    sys.exit(3)
100"#;
101
102        // 优先使用 "python";在 Windows 也可能存在 "py" 启动器
103        let try_cmd = |exe: &str| -> Result<Option<Vec<u8>>, GaiaError> {
104            let mut child = Command::new(exe)
105                .arg("-c")
106                .arg(py_snippet)
107                .stdin(Stdio::piped())
108                .stdout(Stdio::piped())
109                .stderr(Stdio::piped())
110                .spawn();
111
112            let mut child = match child {
113                Ok(c) => c,
114                Err(_) => return Ok(None),
115            };
116
117            if let Some(stdin) = child.stdin.as_mut() {
118                stdin.write_all(hex.as_bytes()).ok();
119            }
120
121            let output = child.wait_with_output()?;
122
123            if !output.status.success() {
124                // 非致命,返回 None 让外层继续尝试其它 exe 或回退
125                return Ok(None);
126            }
127
128            Ok(Some(output.stdout))
129        };
130
131        if let Ok(Some(bytes)) = try_cmd("python") {
132            return Ok(Some(bytes));
133        }
134        if let Ok(Some(bytes)) = try_cmd("py") { // Windows Python launcher
135            return Ok(Some(bytes));
136        }
137        // 无可用 Python,返回 None 触发回退
138        Ok(None)
139    }
140}