openstranded_s2mod_tool/
convert.rs1use std::collections::HashMap;
21use std::fs;
22use std::path::{Path, PathBuf};
23
24use anyhow::{Context, Result};
25use bmpf2fnt::{build_font_atlas, generate_bmfont, BmpfFont};
26
27use crate::registry::register_ron;
28use crate::script::{format_script_ref, write_script_lua};
29use crate::util::{relative_path, write_ron};
30
31pub fn convert_inf_to_ron(
43 inf_path: &Path,
44 input_root: &Path,
45 stage_root: &Path,
46 entries: &mut [inf2ron::InfEntry],
47 registry: &mut HashMap<String, Vec<String>>,
48 debug: bool,
49) -> Result<()> {
50 let ron_rel = relative_path(inf_path, input_root).with_extension("ron");
51 let ron_path = stage_root.join(&ron_rel);
52
53 if let Some(parent) = ron_path.parent() {
54 fs::create_dir_all(parent)?;
55 }
56
57 let mut seq = 0usize;
60 for entry in entries.iter_mut() {
61 if let Some(script_blocks) = entry.blocks.get_mut("script") {
62 for block in script_blocks.iter_mut() {
63 if let inf2ron::BlockContent::Text(script_s2s) = &mut block.content {
64 seq += 1;
65 let s2s = std::mem::take(script_s2s);
66 write_script_lua(inf_path, input_root, stage_root, seq, &s2s, debug)?;
68 block.content = inf2ron::BlockContent::Text(format_script_ref(&ron_rel, seq));
70 }
71 }
72 }
73 }
74
75 let entries_slice: &[inf2ron::InfEntry] = &*entries;
77 write_ron(&ron_path, entries_slice)?;
78
79 register_ron(&ron_rel, registry);
81
82 if debug {
83 eprintln!(" {:?} → {:?} ({} entries, {} scripts)", inf_path, ron_path, entries.len(), seq);
84 }
85
86 Ok(())
87}
88
89pub fn convert_b3d_to_glb(
91 b3d_path: &Path,
92 glb_path: &Path,
93 game_root: &Path,
94 _stem: &str,
95) -> Result<()> {
96 let data = fs::read(b3d_path).with_context(|| format!("reading {:?}", b3d_path))?;
97
98 let b3d = b3d2glb::b3d_parser::B3D::read(&data)
99 .map_err(|e| anyhow::anyhow!("B3D parse error in {:?}: {}", b3d_path, e))?;
100
101 let mesh = b3d2glb::b3d::collect_mesh(&b3d);
102 let mut joints = Vec::new();
103 let mut vertex_joint = vec![None; mesh.positions.len()];
104 b3d2glb::b3d::collect_joints(&b3d.node, None, &mut joints, &mut vertex_joint, mesh.positions.len(), true);
105 let clips = b3d2glb::b3d::collect_anims(&b3d.node);
106
107 let tex_cache = glb_path.parent().unwrap_or(Path::new(".")).join(".tex_cache");
109 fs::create_dir_all(&tex_cache)?;
110
111 b3d2glb::writer::write_glb(
112 &mesh,
113 &joints,
114 &clips,
115 &b3d.textures,
116 &b3d.brushes,
117 _stem,
118 game_root,
119 &tex_cache,
120 glb_path,
121 None, None, )
124 .map_err(|e| anyhow::anyhow!("GLB conversion error in {:?}: {}", b3d_path, e))?;
125
126 Ok(())
127}
128
129pub fn convert_bmp_to_png(bmp_path: &Path, png_path: &Path) -> Result<()> {
136 let img = image::ImageReader::open(bmp_path)
137 .with_context(|| format!("opening {:?}", bmp_path))?
138 .decode()
139 .with_context(|| format!("decoding {:?}", bmp_path))?;
140
141 let mut rgba = img.to_rgba8();
143 let tolerance = 10u8;
144
145 for pixel in rgba.pixels_mut() {
146 let dr = pixel[0].abs_diff(255);
148 let dg = pixel[1].abs_diff(0);
149 let db = pixel[2].abs_diff(255);
150 if dr <= tolerance && dg <= tolerance && db <= tolerance {
151 pixel[3] = 0; }
153 }
154
155 if let Some(parent) = png_path.parent() {
156 fs::create_dir_all(parent)?;
157 }
158 rgba.save(png_path)
159 .with_context(|| format!("saving {:?}", png_path))?;
160
161 Ok(())
162}
163
164pub fn convert_bmpf_to_fnt(
166 bmpf_path: &Path,
167 bmp_path: &Path,
168 fnt_path: &Path,
169 png_path: &Path,
170 stem: &str,
171 _input_root: &Path,
172) -> Result<()> {
173 let bmpf_data = fs::read(bmpf_path)
175 .with_context(|| format!("reading {:?}", bmpf_path))?;
176 let bmpf = BmpfFont::parse(&bmpf_data)
177 .map_err(|e| anyhow::anyhow!("invalid .bmpf {:?}: {e}", bmpf_path))?;
178
179 let img = image::ImageReader::open(bmp_path)
181 .with_context(|| format!("opening {:?}", bmp_path))?
182 .decode()
183 .with_context(|| format!("decoding {:?}", bmp_path))?;
184 let rgba = img.to_rgba8();
185 let (img_w, img_h) = rgba.dimensions();
186 let pixels = rgba.into_raw();
187
188 let atlas = build_font_atlas(&pixels, img_w, img_h, &bmpf)?;
190
191 convert_bmp_to_png(bmp_path, png_path)?;
193
194 let png_rel = if let Some(fnt_parent) = fnt_path.parent() {
196 pathdiff::diff_paths(png_path, fnt_parent)
197 .unwrap_or_else(|| PathBuf::from(stem).with_extension("png"))
198 } else {
199 PathBuf::from(stem).with_extension("png")
200 };
201
202 let fnt_content = generate_bmfont(&atlas, stem, png_rel.to_string_lossy().as_ref());
203 fs::write(fnt_path, &fnt_content)
204 .with_context(|| format!("writing {:?}", fnt_path))?;
205
206 Ok(())
207}
208
209pub fn convert_s2_to_osmap(
214 s2_path: &Path,
215 osmap_path: &Path,
216 input_root: &Path,
217) -> Result<String> {
218 let source_name = s2_path
219 .file_stem()
220 .and_then(|s| s.to_str())
221 .unwrap_or("map");
222
223 let data = fs::read(s2_path)
224 .with_context(|| format!("reading {:?}", s2_path))?;
225
226 let s2 = openstranded_map_tool::parse_s2(&data)
227 .map_err(|e| anyhow::anyhow!("failed to parse {:?}: {}", s2_path, e))?;
228
229 let source_filename = source_name.to_string() + ".s2";
230 let osmap = openstranded_map_tool::s2_to_osmap(s2, &source_filename);
231
232 if let Some(parent) = osmap_path.parent() {
233 fs::create_dir_all(parent)?;
234 }
235 openstranded_map_tool::save_osmap_ron(&osmap, osmap_path)?;
236
237 let orig_rel = super::util::relative_path(s2_path, input_root)
238 .to_string_lossy()
239 .to_string();
240 Ok(orig_rel)
241}