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 {
299 self.content_flags == 0
300 && self.surface_flags == 0
301 && self.surface_value == 0.0
302 }
303
304 #[cfg(feature = "std")]
305 fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteResult {
306 write!(
307 writer,
308 "{} {} {}",
309 self.content_flags, self.surface_flags, self.surface_value,
310 )
311 .map_err(WriteError::Io)?;
312
313 Ok(())
314 }
315}
316
317impl Default for Quake2SurfaceExtension {
318 fn default() -> Self {
319 Self {
320 content_flags: 0,
321 surface_flags: 0,
322 surface_value: 0.0,
323 }
324 }
325}
326
327impl CheckWritable for Alignment {
328 fn check_writable(&self) -> ValidationResult {
329 check_writable_array(self.offset)?;
330 check_writable_f64(self.rotation)?;
331 check_writable_array(self.scale)?;
332
333 if let Some(axes) = self.axes {
334 for axis in axes {
335 check_writable_array(axis)?;
336 }
337 }
338
339 Ok(())
340 }
341}
342
343#[cfg(feature = "std")]
344fn write_edict_to<W: io::Write>(edict: &Edict, writer: &mut W) -> WriteResult {
345 for (key, value) in edict {
346 writer.write_all(b"\"").map_err(WriteError::Io)?;
347 writer.write_all(key.as_bytes()).map_err(WriteError::Io)?;
348 writer.write_all(b"\" \"").map_err(WriteError::Io)?;
349 writer.write_all(value.as_bytes()).map_err(WriteError::Io)?;
350 writer.write_all(b"\"\r\n").map_err(WriteError::Io)?;
351 }
352 Ok(())
353}
354
355#[cfg(feature = "std")]
356fn write_brush_to<W: io::Write>(brush: &Brush, writer: &mut W) -> WriteResult {
357 writer.write_all(b"{\r\n").map_err(WriteError::Io)?;
358
359 for surf in brush {
360 surf.write_to(writer)?;
361 writer.write_all(b"\r\n").map_err(WriteError::Io)?;
362 }
363
364 writer.write_all(b"}\r\n").map_err(WriteError::Io)?;
365 Ok(())
366}
367
368#[cfg(feature = "std")]
369fn write_half_space_to<W: io::Write>(
370 half_space: &HalfSpace,
371 writer: &mut W,
372) -> WriteResult {
373 for (index, pt) in half_space.iter().enumerate() {
374 writer.write_all(b"( ").map_err(WriteError::Io)?;
375
376 for element in pt.iter() {
377 write!(writer, "{} ", element).map_err(WriteError::Io)?;
378 }
379
380 writer.write_all(b")").map_err(WriteError::Io)?;
381
382 if index != 2 {
383 writer.write_all(b" ").map_err(WriteError::Io)?;
384 }
385 }
386 Ok(())
387}
388
389#[cfg(feature = "std")]
390fn write_texture_to<W: io::Write>(
391 texture: &CStr,
392 writer: &mut W,
393) -> WriteResult {
394 let needs_quotes =
395 texture.to_bytes().iter().any(|c| c.is_ascii_whitespace())
396 || texture.to_bytes().is_empty();
397
398 if needs_quotes {
399 writer.write_all(b"\"").map_err(WriteError::Io)?;
400 }
401
402 writer
403 .write_all(texture.to_bytes())
404 .map_err(WriteError::Io)?;
405
406 if needs_quotes {
407 writer.write_all(b"\"").map_err(WriteError::Io)?;
408 }
409
410 Ok(())
411}
412
413fn check_writable_array<const N: usize>(arr: [f64; N]) -> ValidationResult {
414 for num in arr {
415 check_writable_f64(num)?;
416 }
417
418 Ok(())
419}
420
421fn check_writable_f64(num: f64) -> ValidationResult {
422 if num.is_finite() {
423 Ok(())
424 } else {
425 Err(format!("Non-finite number ({})", num).into())
426 }
427}
428
429fn check_writable_texture(s: &CStr) -> ValidationResult {
430 if check_writable_unquoted(s).is_ok() {
431 return Ok(());
432 }
433
434 match check_writable_quoted(s) {
435 Ok(_) => Ok(()),
436 Err(_) => Err(format!(
437 "Cannot write texture {:?}, not quotable and contains whitespace",
438 s
439 )
440 .into()),
441 }
442}
443
444fn check_writable_quoted(s: &CStr) -> ValidationResult {
445 let bad_chars = [b'"', b'\r', b'\n'];
446
447 for c in s.to_bytes() {
448 if bad_chars.contains(c) {
449 return Err(format!(
450 "Cannot write quote-wrapped string, contains {:?}",
451 char::from(*c)
452 )
453 .into());
454 }
455 }
456
457 Ok(())
458}
459
460fn check_writable_unquoted(s: &CStr) -> ValidationResult {
461 let s_bytes = s.to_bytes();
462
463 if s_bytes.is_empty() {
464 return Err(ValidationError::from(
465 "Cannot write unquoted empty string",
466 ));
467 }
468
469 if s_bytes[0] == b'"' {
470 return Err(ValidationError::from(
471 "Cannot lead unquoted string with quote",
472 ));
473 }
474
475 if contains_ascii_whitespace(s) {
476 Err(ValidationError::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}