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