python_assembler/formats/pyc/reader/
mod.rs1use 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#[derive(Debug)]
13pub struct LuacReader<'config, R> {
14 pub(crate) reader: R,
15 pub(crate) view: PycView,
16 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 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 self.view.code_object_bytes = code_object_bytes;
73 }
74 }
75
76 Ok(())
77 }
78
79 fn extract_co_code_via_python(marshal_payload: &[u8]) -> Result<Option<Vec<u8>>, GaiaError> {
81 let hex = marshal_payload.iter().map(|b| format!("{:02x}", b)).collect::<String>();
83
84 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 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 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") { return Ok(Some(bytes));
136 }
137 Ok(None)
139 }
140}