assembler/
main.rs

1use anyhow::{bail, Result};
2use clap::Parser;
3use inst::Inst;
4use rfvp::script::{opcode::Opcode, parser::Nls};
5use serde::{de::value, Deserialize, Serialize};
6use std::{
7    cell::RefCell,
8    collections::{BTreeMap, BTreeSet},
9    path::{Path, PathBuf},
10    rc::Rc,
11};
12
13use inst::*;
14use utils::*;
15
16mod inst;
17mod utils;
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct FVPProject {
21    config_file: PathBuf,
22    disassembly_file: PathBuf,
23}
24
25impl FVPProject {
26    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
27        let config_file = PathBuf::from(path.as_ref());
28        let config_str = std::fs::read_to_string(config_file)?;
29        let config: FVPProject = toml::from_str(&config_str)?;
30        Ok(config)
31    }
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35pub struct SyscallEntry {
36    id: u32,
37    name: String,
38    args_count: u8,
39}
40
41#[derive(Debug, Serialize, Deserialize)]
42pub struct ProjectConfig {
43    entry_point: u32,
44    non_volatile_global_count: u16,
45    volatile_global_count: u16,
46    game_mode: u8,
47    // NOTE: This is not comfirmed for all HD versions
48    game_mode_reserved: u8,
49    game_title: String,
50    syscalls: Vec<SyscallEntry>,
51    custom_syscall_count: u16,
52}
53
54impl ProjectConfig {
55    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
56        let config_file = PathBuf::from(path.as_ref());
57        let config_str = std::fs::read_to_string(config_file)?;
58        let config: ProjectConfig = serde_yaml::from_str(&config_str)?;
59        Ok(config)
60    }
61
62    pub fn put_u8(value: u8, buffer: &mut Vec<u8>) {
63        buffer.push(value);
64    }
65
66    pub fn put_u16_le(value: u16, buffer: &mut Vec<u8>) {
67        buffer.push((value & 0xff) as u8);
68        buffer.push(((value >> 8) & 0xff) as u8);
69    }
70
71    pub fn put_u32_le(value: u32, buffer: &mut Vec<u8>) {
72        buffer.push((value & 0xff) as u8);
73        buffer.push(((value >> 8) & 0xff) as u8);
74        buffer.push(((value >> 16) & 0xff) as u8);
75        buffer.push(((value >> 24) & 0xff) as u8);
76    }
77
78    fn string_to_blob(content: &str, nls: Nls) -> Vec<u8> {
79        // convert utf-8 string to local string via Nls
80        let mut content_bytes = match nls {
81            Nls::GBK => encoding_rs::GBK.encode(content).0.to_vec(),
82            Nls::ShiftJIS => encoding_rs::SHIFT_JIS.encode(content).0.to_vec(),
83            Nls::UTF8 => content.as_bytes().to_vec(),
84        };
85
86        if !content_bytes.ends_with(&[0]) {
87            content_bytes.push(0);
88        }
89
90        content_bytes
91    }
92
93    fn serialize_to_binary(&mut self, nls: Nls) -> Result<Vec<u8>> {
94        let mut data = Vec::new();
95        Self::put_u32_le(self.entry_point, &mut data);
96        Self::put_u16_le(self.non_volatile_global_count, &mut data);
97        Self::put_u16_le(self.volatile_global_count, &mut data);
98        Self::put_u8(self.game_mode, &mut data);
99        Self::put_u8(self.game_mode_reserved, &mut data);
100
101        let game_title = Self::string_to_blob(&self.game_title, nls.clone());
102        let game_title_len = game_title.len() as u8;
103        Self::put_u8(game_title_len, &mut data);
104        data.extend_from_slice(&game_title);
105
106        Self::put_u16_le(self.syscalls.len() as u16, &mut data);
107        self.syscalls.sort_by_key(|x| x.id);
108        for syscall in &self.syscalls {
109            Self::put_u8(syscall.args_count, &mut data);
110            let syscall_name = Self::string_to_blob(&syscall.name, nls.clone());
111            let syscall_name_len = syscall_name.len() as u8;
112            Self::put_u8(syscall_name_len, &mut data);
113            data.extend_from_slice(&syscall_name);
114        }
115
116        if self.custom_syscall_count > 0 {
117            bail!("custom syscall not supported");
118        }
119
120        Self::put_u16_le(self.custom_syscall_count, &mut data);
121
122        Ok(data)
123    }
124
125    pub fn link(&mut self, entry_point: u32, nls: Nls) -> Result<Vec<u8>> {
126        self.entry_point = entry_point;
127        self.serialize_to_binary(nls)
128    }
129}
130
131pub struct Assembler {
132    project: FVPProject,
133    config: ProjectConfig,
134    functions: Vec<Function>,
135    nls: Nls,
136
137    code_section: Vec<u8>,
138}
139
140pub enum InstSet {
141    Nop(NopInst),
142    InitStack(InitStackInst),
143    Call(CallInst),
144    Syscall(SyscallInst),
145    Ret(RetInst),
146    RetV(RetVInst),
147    Jmp(JmpInst),
148    Jz(JzInst),
149    PushNil(PushNilInst),
150    PushTrue(PushTrueInst),
151    PushI32(PushI32Inst),
152    PushI16(PushI16Inst),
153    PushI8(PushI8Inst),
154    PushF32(PushF32Inst),
155    PushString(PushStringInst),
156    PushGlobal(PushGlobalInst),
157    PushStack(PushStackInst),
158    PushGlobalTable(PushGlobalTableInst),
159    PushLocalTable(PushLocalTableInst),
160    PushTop(PushTopInst),
161    PushReturn(PushReturnInst),
162    PopGlobal(PopGlobalInst),
163    PopStack(PopStackInst),
164    PopGlobalTable(PopGlobalTableInst),
165    PopLocalTable(PopLocalTableInst),
166    Neg(NegInst),
167    Add(AddInst),
168    Sub(SubInst),
169    Mul(MulInst),
170    Div(DivInst),
171    Mod(ModInst),
172    BitTest(BitTestInst),
173    And(AndInst),
174    Or(OrInst),
175    SetE(SetEInst),
176    SetNE(SetNEInst),
177    SetG(SetGInst),
178    SetLE(SetLEInst),
179    SetL(SetLInst),
180    SetGE(SetGEInst),
181}
182
183impl InstSet {
184    pub fn set_address(&mut self, address: u32) {
185        match self {
186            InstSet::Nop(inst) => inst.set_address(address),
187            InstSet::InitStack(inst) => inst.set_address(address),
188            InstSet::Call(inst) => inst.set_address(address),
189            InstSet::Syscall(inst) => inst.set_address(address),
190            InstSet::Ret(inst) => inst.set_address(address),
191            InstSet::RetV(inst) => inst.set_address(address),
192            InstSet::Jmp(inst) => inst.set_address(address),
193            InstSet::Jz(inst) => inst.set_address(address),
194            InstSet::PushNil(inst) => inst.set_address(address),
195            InstSet::PushTrue(inst) => inst.set_address(address),
196            InstSet::PushI32(inst) => inst.set_address(address),
197            InstSet::PushI16(inst) => inst.set_address(address),
198            InstSet::PushI8(inst) => inst.set_address(address),
199            InstSet::PushF32(inst) => inst.set_address(address),
200            InstSet::PushString(inst) => inst.set_address(address),
201            InstSet::PushGlobal(inst) => inst.set_address(address),
202            InstSet::PushStack(inst) => inst.set_address(address),
203            InstSet::PushGlobalTable(inst) => inst.set_address(address),
204            InstSet::PushLocalTable(inst) => inst.set_address(address),
205            InstSet::PushTop(inst) => inst.set_address(address),
206            InstSet::PushReturn(inst) => inst.set_address(address),
207            InstSet::PopGlobal(inst) => inst.set_address(address),
208            InstSet::PopStack(inst) => inst.set_address(address),
209            InstSet::PopGlobalTable(inst) => inst.set_address(address),
210            InstSet::PopLocalTable(inst) => inst.set_address(address),
211            InstSet::Neg(inst) => inst.set_address(address),
212            InstSet::Add(inst) => inst.set_address(address),
213            InstSet::Sub(inst) => inst.set_address(address),
214            InstSet::Mul(inst) => inst.set_address(address),
215            InstSet::Div(inst) => inst.set_address(address),
216            InstSet::Mod(inst) => inst.set_address(address),
217            InstSet::BitTest(inst) => inst.set_address(address),
218            InstSet::And(inst) => inst.set_address(address),
219            InstSet::Or(inst) => inst.set_address(address),
220            InstSet::SetE(inst) => inst.set_address(address),
221            InstSet::SetNE(inst) => inst.set_address(address),
222            InstSet::SetG(inst) => inst.set_address(address),
223            InstSet::SetLE(inst) => inst.set_address(address),
224            InstSet::SetL(inst) => inst.set_address(address),
225            InstSet::SetGE(inst) => inst.set_address(address),
226        }
227    }
228
229    pub fn get_address(&self) -> u32 {
230        match self {
231            InstSet::Nop(inst) => inst.address(),
232            InstSet::InitStack(inst) => inst.address(),
233            InstSet::Call(inst) => inst.address(),
234            InstSet::Syscall(inst) => inst.address(),
235            InstSet::Ret(inst) => inst.address(),
236            InstSet::RetV(inst) => inst.address(),
237            InstSet::Jmp(inst) => inst.address(),
238            InstSet::Jz(inst) => inst.address(),
239            InstSet::PushNil(inst) => inst.address(),
240            InstSet::PushTrue(inst) => inst.address(),
241            InstSet::PushI32(inst) => inst.address(),
242            InstSet::PushI16(inst) => inst.address(),
243            InstSet::PushI8(inst) => inst.address(),
244            InstSet::PushF32(inst) => inst.address(),
245            InstSet::PushString(inst) => inst.address(),
246            InstSet::PushGlobal(inst) => inst.address(),
247            InstSet::PushStack(inst) => inst.address(),
248            InstSet::PushGlobalTable(inst) => inst.address(),
249            InstSet::PushLocalTable(inst) => inst.address(),
250            InstSet::PushTop(inst) => inst.address(),
251            InstSet::PushReturn(inst) => inst.address(),
252            InstSet::PopGlobal(inst) => inst.address(),
253            InstSet::PopStack(inst) => inst.address(),
254            InstSet::PopGlobalTable(inst) => inst.address(),
255            InstSet::PopLocalTable(inst) => inst.address(),
256            InstSet::Neg(inst) => inst.address(),
257            InstSet::Add(inst) => inst.address(),
258            InstSet::Sub(inst) => inst.address(),
259            InstSet::Mul(inst) => inst.address(),
260            InstSet::Div(inst) => inst.address(),
261            InstSet::Mod(inst) => inst.address(),
262            InstSet::BitTest(inst) => inst.address(),
263            InstSet::And(inst) => inst.address(),
264            InstSet::Or(inst) => inst.address(),
265            InstSet::SetE(inst) => inst.address(),
266            InstSet::SetNE(inst) => inst.address(),
267            InstSet::SetG(inst) => inst.address(),
268            InstSet::SetLE(inst) => inst.address(),
269            InstSet::SetL(inst) => inst.address(),
270            InstSet::SetGE(inst) => inst.address(),
271        }
272    }
273
274    pub fn size(&self) -> u32 {
275        match self {
276            InstSet::Nop(inst) => inst.size(),
277            InstSet::InitStack(inst) => inst.size(),
278            InstSet::Call(inst) => inst.size(),
279            InstSet::Syscall(inst) => inst.size(),
280            InstSet::Ret(inst) => inst.size(),
281            InstSet::RetV(inst) => inst.size(),
282            InstSet::Jmp(inst) => inst.size(),
283            InstSet::Jz(inst) => inst.size(),
284            InstSet::PushNil(inst) => inst.size(),
285            InstSet::PushTrue(inst) => inst.size(),
286            InstSet::PushI32(inst) => inst.size(),
287            InstSet::PushI16(inst) => inst.size(),
288            InstSet::PushI8(inst) => inst.size(),
289            InstSet::PushF32(inst) => inst.size(),
290            InstSet::PushString(inst) => inst.size(),
291            InstSet::PushGlobal(inst) => inst.size(),
292            InstSet::PushStack(inst) => inst.size(),
293            InstSet::PushGlobalTable(inst) => inst.size(),
294            InstSet::PushLocalTable(inst) => inst.size(),
295            InstSet::PushTop(inst) => inst.size(),
296            InstSet::PushReturn(inst) => inst.size(),
297            InstSet::PopGlobal(inst) => inst.size(),
298            InstSet::PopStack(inst) => inst.size(),
299            InstSet::PopGlobalTable(inst) => inst.size(),
300            InstSet::PopLocalTable(inst) => inst.size(),
301            InstSet::Neg(inst) => inst.size(),
302            InstSet::Add(inst) => inst.size(),
303            InstSet::Sub(inst) => inst.size(),
304            InstSet::Mul(inst) => inst.size(),
305            InstSet::Div(inst) => inst.size(),
306            InstSet::Mod(inst) => inst.size(),
307            InstSet::BitTest(inst) => inst.size(),
308            InstSet::And(inst) => inst.size(),
309            InstSet::Or(inst) => inst.size(),
310            InstSet::SetE(inst) => inst.size(),
311            InstSet::SetNE(inst) => inst.size(),
312            InstSet::SetG(inst) => inst.size(),
313            InstSet::SetLE(inst) => inst.size(),
314            InstSet::SetL(inst) => inst.size(),
315            InstSet::SetGE(inst) => inst.size(),
316        }
317    }
318
319    pub fn serialize_to_binary(&self) -> Vec<u8> {
320        match self {
321            InstSet::Nop(inst) => inst.serialize_to_binary(),
322            InstSet::InitStack(inst) => inst.serialize_to_binary(),
323            InstSet::Call(inst) => inst.serialize_to_binary(),
324            InstSet::Syscall(inst) => inst.serialize_to_binary(),
325            InstSet::Ret(inst) => inst.serialize_to_binary(),
326            InstSet::RetV(inst) => inst.serialize_to_binary(),
327            InstSet::Jmp(inst) => inst.serialize_to_binary(),
328            InstSet::Jz(inst) => inst.serialize_to_binary(),
329            InstSet::PushNil(inst) => inst.serialize_to_binary(),
330            InstSet::PushTrue(inst) => inst.serialize_to_binary(),
331            InstSet::PushI32(inst) => inst.serialize_to_binary(),
332            InstSet::PushI16(inst) => inst.serialize_to_binary(),
333            InstSet::PushI8(inst) => inst.serialize_to_binary(),
334            InstSet::PushF32(inst) => inst.serialize_to_binary(),
335            InstSet::PushString(inst) => inst.serialize_to_binary(),
336            InstSet::PushGlobal(inst) => inst.serialize_to_binary(),
337            InstSet::PushStack(inst) => inst.serialize_to_binary(),
338            InstSet::PushGlobalTable(inst) => inst.serialize_to_binary(),
339            InstSet::PushLocalTable(inst) => inst.serialize_to_binary(),
340            InstSet::PushTop(inst) => inst.serialize_to_binary(),
341            InstSet::PushReturn(inst) => inst.serialize_to_binary(),
342            InstSet::PopGlobal(inst) => inst.serialize_to_binary(),
343            InstSet::PopStack(inst) => inst.serialize_to_binary(),
344            InstSet::PopGlobalTable(inst) => inst.serialize_to_binary(),
345            InstSet::PopLocalTable(inst) => inst.serialize_to_binary(),
346            InstSet::Neg(inst) => inst.serialize_to_binary(),
347            InstSet::Add(inst) => inst.serialize_to_binary(),
348            InstSet::Sub(inst) => inst.serialize_to_binary(),
349            InstSet::Mul(inst) => inst.serialize_to_binary(),
350            InstSet::Div(inst) => inst.serialize_to_binary(),
351            InstSet::Mod(inst) => inst.serialize_to_binary(),
352            InstSet::BitTest(inst) => inst.serialize_to_binary(),
353            InstSet::And(inst) => inst.serialize_to_binary(),
354            InstSet::Or(inst) => inst.serialize_to_binary(),
355            InstSet::SetE(inst) => inst.serialize_to_binary(),
356            InstSet::SetNE(inst) => inst.serialize_to_binary(),
357            InstSet::SetG(inst) => inst.serialize_to_binary(),
358            InstSet::SetLE(inst) => inst.serialize_to_binary(),
359            InstSet::SetL(inst) => inst.serialize_to_binary(),
360            InstSet::SetGE(inst) => inst.serialize_to_binary(),
361        }
362    }
363}
364
365impl Assembler {
366    pub fn new(project_dir: impl AsRef<Path>, nls: Nls) -> Result<Self> {
367        let proj_path = project_dir.as_ref().join("project.toml");
368
369        let project = FVPProject::new(proj_path)?;
370        let disassembly_path = project_dir.as_ref().join(&project.disassembly_file);
371        let config_path = project_dir.as_ref().join(&project.config_file);
372        let config = ProjectConfig::new(config_path)?;
373        let functions = std::fs::read_to_string(disassembly_path)?;
374        let functions: Vec<Function> = serde_yaml::from_str(&functions)?;
375
376        Ok(Self {
377            project,
378            config,
379            functions,
380            nls,
381
382            code_section: Vec::new(),
383        })
384    }
385
386    fn inst2_to_inst(
387        inst: &Inst2,
388        nls: &Nls,
389        syscall_table: &BTreeMap<String, u32>,
390    ) -> Result<InstSet> {
391        let opcode = inst.get_opcode()?;
392        let wrapped_inst = match opcode {
393            Opcode::Nop => InstSet::Nop(to_nop(inst)?),
394            Opcode::InitStack => InstSet::InitStack(to_init_stack(inst)?),
395            Opcode::Call => InstSet::Call(to_call(inst)?),
396            Opcode::Syscall => InstSet::Syscall(to_syscall(inst, syscall_table)?),
397            Opcode::Ret => InstSet::Ret(to_ret(inst)?),
398            Opcode::RetV => InstSet::RetV(to_ret_v(inst)?),
399            Opcode::Jmp => InstSet::Jmp(to_jmp(inst)?),
400            Opcode::Jz => InstSet::Jz(to_jz(inst)?),
401            Opcode::PushNil => InstSet::PushNil(to_push_nil(inst)?),
402            Opcode::PushTrue => InstSet::PushTrue(to_push_true(inst)?),
403            Opcode::PushI32 => InstSet::PushI32(to_push_i32(inst)?),
404            Opcode::PushI16 => InstSet::PushI16(to_push_i16(inst)?),
405            Opcode::PushI8 => InstSet::PushI8(to_push_i8(inst)?),
406            Opcode::PushF32 => InstSet::PushF32(to_push_f32(inst)?),
407            Opcode::PushString => InstSet::PushString(to_push_string(inst, nls.clone())?),
408            Opcode::PushGlobal => InstSet::PushGlobal(to_push_global(inst)?),
409            Opcode::PushStack => InstSet::PushStack(to_push_stack(inst)?),
410            Opcode::PushGlobalTable => InstSet::PushGlobalTable(to_push_global_table(inst)?),
411            Opcode::PushLocalTable => InstSet::PushLocalTable(to_push_local_table(inst)?),
412            Opcode::PushTop => InstSet::PushTop(to_push_top(inst)?),
413            Opcode::PushReturn => InstSet::PushReturn(to_push_return(inst)?),
414            Opcode::PopGlobal => InstSet::PopGlobal(to_pop_global(inst)?),
415            Opcode::PopStack => InstSet::PopStack(to_pop_stack(inst)?),
416            Opcode::PopGlobalTable => InstSet::PopGlobalTable(to_pop_global_table(inst)?),
417            Opcode::PopLocalTable => InstSet::PopLocalTable(to_pop_local_table(inst)?),
418            Opcode::Neg => InstSet::Neg(to_neg(inst)?),
419            Opcode::Add => InstSet::Add(to_add(inst)?),
420            Opcode::Sub => InstSet::Sub(to_sub(inst)?),
421            Opcode::Mul => InstSet::Mul(to_mul(inst)?),
422            Opcode::Div => InstSet::Div(to_div(inst)?),
423            Opcode::Mod => InstSet::Mod(to_mod(inst)?),
424            Opcode::BitTest => InstSet::BitTest(to_bit_test(inst)?),
425            Opcode::And => InstSet::And(to_and(inst)?),
426            Opcode::Or => InstSet::Or(to_or(inst)?),
427            Opcode::SetE => InstSet::SetE(to_set_e(inst)?),
428            Opcode::SetNE => InstSet::SetNE(to_set_ne(inst)?),
429            Opcode::SetG => InstSet::SetG(to_set_g(inst)?),
430            Opcode::SetLE => InstSet::SetLE(to_set_le(inst)?),
431            Opcode::SetL => InstSet::SetL(to_set_l(inst)?),
432            Opcode::SetGE => InstSet::SetGE(to_set_ge(inst)?),
433        };
434
435        Ok(wrapped_inst)
436    }
437
438    fn compile(&mut self, old_entry_point: u32) -> Result<u32> {
439        let mut func_entry_addrs = BTreeSet::new();
440        let mut threadstart_sites = Vec::new();
441
442        let mut map = BTreeMap::new();
443        for func in &self.functions {
444            func_entry_addrs.insert(func.address());
445            for inst in func.get_insts() {
446                let addr = inst.get_address();
447
448                // ThreadStart takes an absolute code pointer (PC) on the stack.
449                // If PushString changes size, absolute offsets shift; we must relocate that pointer.
450                if inst.get_opcode()? == Opcode::Syscall {
451                    if let Some(name) = inst.operands().first() {
452                        if name.eq_ignore_ascii_case("ThreadStart") {
453                            threadstart_sites.push(addr);
454                        }
455                    }
456                }
457                map.insert(addr, inst);
458            }
459        }
460
461        // phase 1: set address
462        let mut syscall_table = BTreeMap::new();
463        for entry in self.config.syscalls.iter() {
464            syscall_table.insert(entry.name.clone(), entry.id);
465        }
466        let mut insts = BTreeMap::new();
467        let mut old_order = Vec::new();
468        let mut cursor = 4u32;
469        for (addr, inst) in map {
470            let mut wrapped_inst = Self::inst2_to_inst(inst, &self.nls, &syscall_table)?;
471            wrapped_inst.set_address(cursor);
472            let size = wrapped_inst.size();
473            let wrapped_inst = Rc::new(RefCell::new(wrapped_inst));
474            insts.insert(addr, wrapped_inst);
475            old_order.push(addr);
476            cursor += size;
477        }
478        let entry_point = insts
479            .get(&old_entry_point)
480            .ok_or_else(|| anyhow::anyhow!("entry point not found"))?
481            .borrow()
482            .get_address();
483
484        // phase 2: set jump target
485        for (_, inst) in &insts {
486            let inst = &mut *inst.borrow_mut();
487            match inst {
488                InstSet::Jmp(inst) => {
489                    let old_target = inst.get_old_target();
490                    let target_inst = insts
491                        .get(&old_target)
492                        .ok_or_else(|| anyhow::anyhow!(format!("target not found: {}", old_target)))?;
493                    inst.set_target(target_inst.borrow().get_address());
494                }
495                InstSet::Jz(inst) => {
496                    let old_target = inst.get_old_target();
497                    let target_inst = insts
498                        .get(&old_target)
499                        .ok_or_else(|| anyhow::anyhow!(format!("target not found: {}", old_target)))?;
500                    inst.set_target(target_inst.borrow().get_address());
501                }
502                InstSet::Call(inst) => {
503                    let old_target = inst.get_old_func_target();
504                    let target_inst = insts
505                        .get(&old_target)
506                        .ok_or_else(|| anyhow::anyhow!(format!("target not found: {}", old_target)))?;
507                    inst.set_func_target(target_inst.borrow().get_address());
508                }
509                _ => {}
510            }
511        }
512
513        // phase 2b: relocate absolute code pointers passed to ThreadStart.
514        if !threadstart_sites.is_empty() {
515            let mut index_by_old = BTreeMap::new();
516            for (i, a) in old_order.iter().enumerate() {
517                index_by_old.insert(*a, i);
518            }
519
520            for ts_old_addr in threadstart_sites {
521                let idx = *index_by_old
522                    .get(&ts_old_addr)
523                    .ok_or_else(|| anyhow::anyhow!(format!("ThreadStart site not found: {ts_old_addr}")))?;
524
525                // Scan a small window before ThreadStart and find exactly one push of a function entry address.
526                let mut candidates: Vec<(u32, u32)> = Vec::new();
527                for back in 1..=8usize {
528                    if idx < back {
529                        break;
530                    }
531                    let prev_old = old_order[idx - back];
532                    let inst_ref = insts
533                        .get(&prev_old)
534                        .ok_or_else(|| anyhow::anyhow!(format!("inst missing at {prev_old}")))?;
535                    match &*inst_ref.borrow() {
536                        InstSet::PushI32(p) => {
537                            let v = p.get_value();
538                            if v >= 0 {
539                                let u = v as u32;
540                                if func_entry_addrs.contains(&u) {
541                                    candidates.push((prev_old, u));
542                                }
543                            }
544                        }
545                        InstSet::PushI16(p) => {
546                            let v = p.get_value() as i32;
547                            if v >= 0 {
548                                let u = v as u32;
549                                if func_entry_addrs.contains(&u) {
550                                    candidates.push((prev_old, u));
551                                }
552                            }
553                        }
554                        _ => {}
555                    }
556                }
557
558                if candidates.is_empty() {
559                    bail!(
560                        "ThreadStart at old PC {}: could not find a preceding push of a function entry address",
561                        ts_old_addr
562                    );
563                }
564                if candidates.len() != 1 {
565                    bail!(
566                        "ThreadStart at old PC {}: ambiguous code-pointer candidates: {:?}",
567                        ts_old_addr,
568                        candidates
569                    );
570                }
571
572                let (push_old_addr, target_old_func_addr) = candidates[0];
573                let target_new = insts
574                    .get(&target_old_func_addr)
575                    .ok_or_else(|| {
576                        anyhow::anyhow!(format!(
577                            "ThreadStart target not found in inst map: {}",
578                            target_old_func_addr
579                        ))
580                    })?
581                    .borrow()
582                    .get_address();
583
584                let push_inst = insts
585                    .get(&push_old_addr)
586                    .ok_or_else(|| anyhow::anyhow!(format!("push inst missing at {push_old_addr}")))?;
587                match &mut *push_inst.borrow_mut() {
588                    InstSet::PushI32(p) => p.set_value(target_new as i32),
589                    InstSet::PushI16(p) => {
590                        if target_new > i16::MAX as u32 {
591                            bail!(
592                                "ThreadStart relocated target too large for PushI16: {}",
593                                target_new
594                            );
595                        }
596                        p.set_value(target_new as i16);
597                    }
598                    _ => bail!("Internal error: selected candidate is not a push"),
599                }
600            }
601        }
602
603        // phase 3: serialize
604        self.code_section.clear();
605        for (_, inst) in insts {
606            let blob = inst.borrow().serialize_to_binary();
607            self.code_section.extend_from_slice(&blob);
608        }
609
610        Ok(entry_point)
611    }
612
613    fn size(&self) -> u32 {
614        self.code_section.len() as u32
615    }
616
617    fn link(&mut self, new_entry_point: u32) -> Result<Vec<u8>> {
618        let mut data = Vec::new();
619        let header_offset = 4 + self.size();
620
621        ProjectConfig::put_u32_le(header_offset, &mut data);
622        data.extend_from_slice(&self.code_section);
623
624        let header = self.config.link(new_entry_point, self.nls.clone())?;
625        data.extend_from_slice(&header);
626
627        Ok(data)
628    }
629}
630
631fn compile(project_dir: impl AsRef<Path>, output: impl AsRef<Path>, nls: Nls) -> Result<()> {
632    let mut assembler = Assembler::new(project_dir, nls)?;
633    let entry_point = assembler.compile(assembler.config.entry_point)?;
634    let data = assembler.link(entry_point)?;
635    let output_path = output.as_ref();
636    std::fs::write(output_path, data)?;
637
638    Ok(())
639}
640
641#[derive(Parser, Debug)]
642#[command(version, about, long_about = None)]
643struct Args {
644    #[clap(short, long)]
645    project_dir: String,
646    #[clap(short, long)]
647    output: String,
648    #[clap(short, long)]
649    nls: Nls,
650}
651
652fn main() {
653    env_logger::init();
654    let args = Args::parse();
655    if let Err(e) = compile(args.project_dir, args.output, args.nls) {
656        log::error!("Error: {}", e);
657    }
658}
659
660#[cfg(test)]
661mod tests {
662    use super::*;
663
664    #[test]
665    fn test_compile() {
666        let input = Path::new(concat!(
667            env!("CARGO_MANIFEST_DIR"),
668            "/../disassembler/testcase/Snow"
669        ));
670        let output = Path::new(concat!(
671            env!("CARGO_MANIFEST_DIR"),
672            "/testcase/Snow_new.bin"
673        ));
674        let nls = Nls::ShiftJIS;
675        compile(input, output, nls.clone()).unwrap();
676        let _parser = rfvp::script::parser::Parser::new(output, nls).unwrap();
677    }
678}