1#[cfg(feature = "std")]
2extern crate std;
3
4extern crate alloc;
5
6#[cfg(feature = "std")]
7use std::io;
8
9use {
10 alloc::ffi::CString, alloc::format, alloc::string::String, alloc::vec::Vec,
11 core::ffi::CStr,
12};
13
14#[cfg(feature = "std")]
15use crate::{WriteAttempt, WriteError};
16
17pub type ValidationResult = Result<(), String>;
19
20pub trait CheckWritable {
22 fn check_writable(&self) -> ValidationResult;
29}
30
31pub type Point = [f64; 3];
33pub type Vec3 = [f64; 3];
34pub type Vec2 = [f64; 2];
35
36#[derive(Clone, Debug)]
41pub struct QuakeMap {
42 pub entities: Vec<Entity>,
43}
44
45impl QuakeMap {
46 pub const fn new() -> Self {
48 QuakeMap {
49 entities: Vec::new(),
50 }
51 }
52
53 #[cfg(feature = "std")]
56 pub fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
57 for ent in &self.entities {
58 ent.write_to(writer)?;
59 }
60 Ok(())
61 }
62}
63
64impl Default for QuakeMap {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl CheckWritable for QuakeMap {
71 fn check_writable(&self) -> ValidationResult {
72 for ent in &self.entities {
73 ent.check_writable()?;
74 }
75
76 Ok(())
77 }
78}
79
80#[derive(Clone, Copy, PartialEq, Eq, Debug)]
82pub enum EntityKind {
83 Point,
84 Brush,
85}
86
87#[derive(Clone, Debug)]
90pub struct Entity {
91 pub edict: Edict,
92 pub brushes: Vec<Brush>,
93}
94
95impl Entity {
96 pub fn new() -> Self {
98 Entity {
99 edict: Edict::new(),
100 brushes: Vec::new(),
101 }
102 }
103
104 pub fn kind(&self) -> EntityKind {
106 if self.brushes.is_empty() {
107 EntityKind::Point
108 } else {
109 EntityKind::Brush
110 }
111 }
112
113 #[cfg(feature = "std")]
114 pub fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
115 self.check_writable().map_err(WriteError::Validation)?;
116
117 writer.write_all(b"{\r\n").map_err(WriteError::Io)?;
118
119 write_edict_to(&self.edict, writer)?;
120
121 for brush in &self.brushes {
122 write_brush_to(brush, writer)?;
123 }
124
125 writer.write_all(b"}\r\n").map_err(WriteError::Io)?;
126 Ok(())
127 }
128}
129
130impl Default for Entity {
131 fn default() -> Self {
133 Entity::new()
134 }
135}
136
137impl CheckWritable for Entity {
138 fn check_writable(&self) -> ValidationResult {
139 self.edict.check_writable()?;
140
141 for brush in &self.brushes {
142 brush.check_writable()?
143 }
144
145 Ok(())
146 }
147}
148
149pub type Edict = Vec<(CString, CString)>;
151
152impl CheckWritable for Edict {
153 fn check_writable(&self) -> ValidationResult {
154 for (k, v) in self {
155 check_writable_quoted(k)?;
156 check_writable_quoted(v)?;
157 }
158
159 Ok(())
160 }
161}
162
163pub type Brush = Vec<Surface>;
165
166impl CheckWritable for Brush {
167 fn check_writable(&self) -> ValidationResult {
168 for surface in self {
169 surface.check_writable()?;
170 }
171
172 Ok(())
173 }
174}
175
176#[derive(Clone, Debug)]
181pub struct Surface {
182 pub half_space: HalfSpace,
183 pub texture: CString,
184 pub alignment: Alignment,
185 pub q2ext: Quake2SurfaceExtension,
186}
187
188impl Surface {
189 #[cfg(feature = "std")]
190 fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
191 write_half_space_to(&self.half_space, writer)?;
192 writer.write_all(b" ").map_err(WriteError::Io)?;
193 write_texture_to(&self.texture, writer)?;
194 writer.write_all(b" ").map_err(WriteError::Io)?;
195 self.alignment.write_to(writer)?;
196
197 if !self.q2ext.is_zeroed() {
198 writer.write_all(b" ").map_err(WriteError::Io)?;
199 self.q2ext.write_to(writer)?;
200 }
201
202 Ok(())
203 }
204}
205
206impl CheckWritable for Surface {
207 fn check_writable(&self) -> ValidationResult {
208 self.half_space.check_writable()?;
209 check_writable_texture(&self.texture)?;
210 self.alignment.check_writable()
211 }
212}
213
214pub type HalfSpace = [Point; 3];
217
218impl CheckWritable for HalfSpace {
219 fn check_writable(&self) -> ValidationResult {
220 for num in self.iter().flatten() {
221 check_writable_f64(*num)?;
222 }
223
224 Ok(())
225 }
226}
227
228#[derive(Clone, Copy, Debug)]
233pub struct Alignment {
234 pub offset: Vec2,
235 pub rotation: f64,
236 pub scale: Vec2,
237
238 pub axes: Option<[Vec3; 2]>,
240}
241
242impl Alignment {
243 #[cfg(feature = "std")]
244 fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
245 match self.axes {
246 None => {
247 write!(
248 writer,
249 "{} {} {} {} {}",
250 self.offset[0],
251 self.offset[1],
252 self.rotation,
253 self.scale[0],
254 self.scale[1]
255 )
256 .map_err(WriteError::Io)?;
257 }
258 Some([u, v]) => {
259 write!(
260 writer,
261 "[ {} {} {} {} ] [ {} {} {} {} ] {} {} {}",
262 u[0],
263 u[1],
264 u[2],
265 self.offset[0],
266 v[0],
267 v[1],
268 v[2],
269 self.offset[1],
270 self.rotation,
271 self.scale[0],
272 self.scale[1]
273 )
274 .map_err(WriteError::Io)?;
275 }
276 }
277 Ok(())
278 }
279}
280
281#[derive(Clone, Copy, Debug)]
289pub struct Quake2SurfaceExtension {
290 pub content_flags: i32,
292
293 pub surface_flags: i32,
295
296 pub surface_value: f64,
298}
299
300impl Quake2SurfaceExtension {
301 pub fn is_zeroed(&self) -> bool {
305 self.content_flags == 0
306 && self.surface_flags == 0
307 && self.surface_value == 0.0
308 }
309
310 #[cfg(feature = "std")]
311 fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
312 write!(
313 writer,
314 "{} {} {}",
315 self.content_flags, self.surface_flags, self.surface_value,
316 )
317 .map_err(WriteError::Io)?;
318
319 Ok(())
320 }
321}
322
323impl Default for Quake2SurfaceExtension {
324 fn default() -> Self {
325 Self {
326 content_flags: 0,
327 surface_flags: 0,
328 surface_value: 0.0,
329 }
330 }
331}
332
333impl CheckWritable for Alignment {
334 fn check_writable(&self) -> ValidationResult {
335 check_writable_array(self.offset)?;
336 check_writable_f64(self.rotation)?;
337 check_writable_array(self.scale)?;
338
339 if let Some(axes) = self.axes {
340 for axis in axes {
341 check_writable_array(axis)?;
342 }
343 }
344
345 Ok(())
346 }
347}
348
349#[cfg(feature = "std")]
350fn write_edict_to<W: io::Write>(edict: &Edict, writer: &mut W) -> WriteAttempt {
351 for (key, value) in edict {
352 writer.write_all(b"\"").map_err(WriteError::Io)?;
353 writer.write_all(key.as_bytes()).map_err(WriteError::Io)?;
354 writer.write_all(b"\" \"").map_err(WriteError::Io)?;
355 writer.write_all(value.as_bytes()).map_err(WriteError::Io)?;
356 writer.write_all(b"\"\r\n").map_err(WriteError::Io)?;
357 }
358 Ok(())
359}
360
361#[cfg(feature = "std")]
362fn write_brush_to<W: io::Write>(brush: &Brush, writer: &mut W) -> WriteAttempt {
363 writer.write_all(b"{\r\n").map_err(WriteError::Io)?;
364
365 for surf in brush {
366 surf.write_to(writer)?;
367 writer.write_all(b"\r\n").map_err(WriteError::Io)?;
368 }
369
370 writer.write_all(b"}\r\n").map_err(WriteError::Io)?;
371 Ok(())
372}
373
374#[cfg(feature = "std")]
375fn write_half_space_to<W: io::Write>(
376 half_space: &HalfSpace,
377 writer: &mut W,
378) -> WriteAttempt {
379 for (index, pt) in half_space.iter().enumerate() {
380 writer.write_all(b"( ").map_err(WriteError::Io)?;
381
382 for element in pt.iter() {
383 write!(writer, "{} ", element).map_err(WriteError::Io)?;
384 }
385
386 writer.write_all(b")").map_err(WriteError::Io)?;
387
388 if index != 2 {
389 writer.write_all(b" ").map_err(WriteError::Io)?;
390 }
391 }
392 Ok(())
393}
394
395#[cfg(feature = "std")]
396fn write_texture_to<W: io::Write>(
397 texture: &CStr,
398 writer: &mut W,
399) -> WriteAttempt {
400 let needs_quotes =
401 texture.to_bytes().iter().any(|c| c.is_ascii_whitespace())
402 || texture.to_bytes().is_empty();
403
404 if needs_quotes {
405 writer.write_all(b"\"").map_err(WriteError::Io)?;
406 }
407
408 writer
409 .write_all(texture.to_bytes())
410 .map_err(WriteError::Io)?;
411
412 if needs_quotes {
413 writer.write_all(b"\"").map_err(WriteError::Io)?;
414 }
415
416 Ok(())
417}
418
419fn check_writable_array<const N: usize>(arr: [f64; N]) -> ValidationResult {
420 for num in arr {
421 check_writable_f64(num)?;
422 }
423
424 Ok(())
425}
426
427fn check_writable_f64(num: f64) -> ValidationResult {
428 if num.is_finite() {
429 Ok(())
430 } else {
431 Err(format!("Non-finite number ({})", num))
432 }
433}
434
435fn check_writable_texture(s: &CStr) -> ValidationResult {
436 if check_writable_unquoted(s).is_ok() {
437 return Ok(());
438 }
439
440 match check_writable_quoted(s) {
441 Ok(_) => Ok(()),
442 Err(_) => Err(format!(
443 "Cannot write texture {:?}, not quotable and contains whitespace",
444 s
445 )),
446 }
447}
448
449fn check_writable_quoted(s: &CStr) -> ValidationResult {
450 let bad_chars = [b'"', b'\r', b'\n'];
451
452 for c in s.to_bytes() {
453 if bad_chars.contains(c) {
454 return Err(format!(
455 "Cannot write quote-wrapped string, contains {:?}",
456 char::from(*c)
457 ));
458 }
459 }
460
461 Ok(())
462}
463
464fn check_writable_unquoted(s: &CStr) -> ValidationResult {
465 let s_bytes = s.to_bytes();
466
467 if s_bytes.is_empty() {
468 return Err(String::from("Cannot write unquoted empty string"));
469 }
470
471 if s_bytes[0] == b'"' {
472 return Err(String::from("Cannot lead unquoted string with quote"));
473 }
474
475 if contains_ascii_whitespace(s) {
476 Err(String::from(
477 "Cannot write unquoted string, contains whitespace",
478 ))
479 } else {
480 Ok(())
481 }
482}
483
484fn contains_ascii_whitespace(s: &CStr) -> bool {
485 s.to_bytes().iter().any(|c| c.is_ascii_whitespace())
486}