lunar_magic_wrapper/lib.rs
1//! This crate provides a lightweight wrapper around [Lunar Magic](http://fusoya.eludevisibility.org/lm/index.html)'s
2//! command line functions.
3//! It supports all available command line functions as of Lunar Magic 3.40.
4//!
5//! Note that this crate requires [wine](https://www.winehq.org/) to be installed, accessible as a command and working on operating systems other than Windows.
6//!
7//! Paths passed to functions can be any type that can be turned into `AsRef<Path>`, e.g., the following will
8//! all work equally well:
9//!
10//!```
11//! # use lunar_magic_wrapper::Wrapper;
12//! # use std::path::Path;
13//! # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
14//! // `&str` works fine
15//! let output = lm_wrapper.export_gfx("C:/hacks/my_project/my_hack.smc");
16//!
17//! // So does a `Path`
18//! let output = lm_wrapper.export_gfx(Path::new("C:/hacks/my_project/my_hack.smc"));
19//!
20//! // So does a `String`
21//! let output = lm_wrapper.export_gfx(String::from("C:/hacks/my_project/my_hack.smc"));
22//!
23//! // And so on, but note that separators should be your operating system's separators, i.e. `\` should be used on Windows.
24//! ```
25
26use std::{
27 error::Error,
28 fmt::{self, Display},
29 fs::File,
30 io::{BufRead, BufReader, ErrorKind},
31 path::{Path, PathBuf},
32 process::Command,
33};
34
35use bitflags::bitflags;
36use tempfile::tempdir;
37
38/// A wrapper around Lunar Magic's command line functionality.
39///
40/// Up to date as of Lunar Magic 3.40.
41#[derive(Debug)]
42pub struct Wrapper {
43 lunar_magic_path: PathBuf,
44}
45
46/// Contains errors raised as a result of an operation using
47/// a [Wrapper].
48#[derive(Debug)]
49pub enum WrapperErr {
50 /// Raised when no Lunar Magic is found at the path given to the wrapper.
51 LunarMagicMissing { command: String },
52
53 /// Raised when an operation by Lunar Magic fails.
54 Operation {
55 code: Option<i32>,
56 command: String,
57 output: Vec<String>,
58 },
59
60 /// Raised when the underlying command couldn't be executed by the OS.
61 FailedToExecute {
62 command: String,
63 error_kind: ErrorKind,
64 },
65
66 /// Raised when no temp file for logging Lunar Magic's output was found.
67 NoTempFile { command: String },
68
69 /// Raised when no temporary directory to keep the Lunar Magic log
70 /// file could be created.
71 NoTempDir { command: String },
72
73 #[cfg(not(target_os = "windows"))]
74 NoWine,
75}
76
77impl Display for WrapperErr {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 let err_msg =
80 match self {
81 WrapperErr::LunarMagicMissing { command } => {
82 format!(
83 "Lunar Magic not found while performing operation '{}'",
84 command
85 )
86 }
87 WrapperErr::Operation {
88 code,
89 command,
90 output,
91 } => {
92 if let Some(code) = code {
93 format!(
94 "Lunar Magic returned with error code {} while performing operation '{}' \
95 with the following output:\n\t{}",
96 code, command, output.join("\n\t")
97 )
98 } else {
99 format!(
100 "Lunar Magic failed while performing operation '{}' \
101 with the following output:\n\t{}",
102 command,
103 output.join("\n\t")
104 )
105 }
106 }
107 WrapperErr::FailedToExecute {
108 command,
109 error_kind,
110 } => {
111 format!(
112 "Failed to execute Lunar Magic while attempting to perform \
113 operation '{}', error kind was {}",
114 command, error_kind
115 )
116 }
117 WrapperErr::NoTempDir { command } => {
118 format!(
119 "Failed to create temporary folder while attempting to perform \
120 operation '{}'",
121 command
122 )
123 }
124 WrapperErr::NoTempFile { command } => {
125 format!(
126 "Failed to read temporary log file while attempting to perform \
127 operation '{}'",
128 command
129 )
130 }
131 #[cfg(not(target_os = "windows"))]
132 WrapperErr::NoWine => {
133 format!("Wine is not installed, cannot perform operation")
134 }
135 };
136
137 write!(f, "{}", err_msg)
138 }
139}
140
141impl Error for WrapperErr {
142 fn source(&self) -> Option<&(dyn Error + 'static)> {
143 None
144 }
145}
146
147/// Contains all valid ROM sizes that can be used with
148/// [Wrapper::expand_rom].
149#[derive(Debug)]
150pub enum RomSize {
151 _2mb,
152 _3mb,
153 _4mb,
154 _6mbSa1,
155 _8mbSa1,
156}
157
158impl Display for RomSize {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 let string = match self {
161 RomSize::_2mb => "2MB",
162 RomSize::_3mb => "3MB",
163 RomSize::_4mb => "4MB",
164 RomSize::_6mbSa1 => "6MB_SA1",
165 RomSize::_8mbSa1 => "8MB_SA1",
166 };
167
168 write!(f, "{}", string)
169 }
170}
171
172/// Result of invoking an operation through a [Wrapper].
173///
174/// Contains the text output of Lunar Magic if the operation succeeded
175/// or a [WrapperErr] otherwise.
176pub type ResultL = Result<Vec<String>, WrapperErr>;
177
178/// Contains all valid ROM compression formats that can be used with
179/// [Wrapper::change_compression].
180#[derive(Debug)]
181pub enum CompressionFormat {
182 LcLz2Orig,
183 LcLz2Speed,
184 LcLz3,
185}
186
187impl Display for CompressionFormat {
188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189 let string = match self {
190 CompressionFormat::LcLz2Orig => "LC_LZ2_Orig",
191 CompressionFormat::LcLz2Speed => "LC_LZ2_Speed",
192 CompressionFormat::LcLz3 => "LC_LZ3",
193 };
194
195 write!(f, "{}", string)
196 }
197}
198
199bitflags! {
200 /// Contains currently available flags when importing
201 /// multiple levels into the ROM using [Wrapper::import_mult_levels].
202 pub struct LevelImportFlag: u32 {
203 const None = 0b0000;
204 const ClearSecondaryExits = 0b0001;
205 }
206
207 /// Contains currently available flags when importing
208 /// multiple levels into the ROM using [Wrapper::export_mult_levels].
209 pub struct LevelExportFlag: u32 {
210 const None = 0b0000;
211 const OnlyModifiedLevels = 0b0001;
212 }
213}
214
215impl Wrapper {
216 /// Returns a new [Wrapper].
217 ///
218 /// Note that there doesn't necessarily have to be a
219 /// Lunar Magic executable at the passed path at time
220 /// of creation. It only needs to exist once you try
221 /// to actually use the wrapper to perform operations.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use lunar_magic_wrapper::Wrapper;
227 ///
228 /// let lm_wrapper = Wrapper::new("C:/programs/LunarMagic/lunar_magic.exe");
229 /// ```
230 pub fn new<P: Into<PathBuf>>(path: P) -> Self {
231 Wrapper {
232 lunar_magic_path: path.into(),
233 }
234 }
235
236 /// Exports Graphics from the passed ROM and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// # use lunar_magic_wrapper::Wrapper;
242 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
243 /// let output = lm_wrapper.export_gfx("C:/hacks/my_project/my_hack.smc");
244 /// ```
245 pub fn export_gfx<P: AsRef<Path>>(&self, rom_path: P) -> ResultL {
246 self.run_command(&format!(
247 "-ExportGFX {}",
248 rom_path.as_ref().to_string_lossy()
249 ))
250 }
251
252 /// Exports ExGraphics from the passed ROM and returns Lunar Magic's
253 /// text output or a [WrapperErr] if something went wrong.
254 ///
255 /// # Examples
256 ///
257 /// ```
258 /// # use lunar_magic_wrapper::Wrapper;
259 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
260 /// let output = lm_wrapper.export_exgfx("C:/hacks/my_project/my_hack.smc");
261 /// ```
262 pub fn export_exgfx<P: AsRef<Path>>(&self, rom_path: P) -> ResultL {
263 self.run_command(&format!(
264 "-ExportExGFX {}",
265 rom_path.as_ref().to_string_lossy()
266 ))
267 }
268
269 /// Imports Graphics into the passed ROM and returns Lunar Magic's
270 /// text output or a [WrapperErr] if something went wrong.
271 ///
272 /// # Examples
273 ///
274 /// ```
275 /// # use lunar_magic_wrapper::Wrapper;
276 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
277 /// let output = lm_wrapper.import_gfx("C:/hacks/my_project/my_hack.smc");
278 /// ```
279 pub fn import_gfx<P: AsRef<Path>>(&self, rom_path: P) -> ResultL {
280 self.run_command(&format!(
281 "-ImportGFX {}",
282 rom_path.as_ref().to_string_lossy()
283 ))
284 }
285
286 /// Imports ExGraphics into the passed ROM and returns Lunar Magic's
287 /// text output or a [WrapperErr] if something went wrong.
288 ///
289 /// # Examples
290 ///
291 /// ```
292 /// # use lunar_magic_wrapper::Wrapper;
293 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
294 /// let output = lm_wrapper.import_exgfx("C:/hacks/my_project/my_hack.smc");
295 /// ```
296 pub fn import_exgfx<P: AsRef<Path>>(&self, rom_path: P) -> ResultL {
297 self.run_command(&format!(
298 "-ImportExGFX {}",
299 rom_path.as_ref().to_string_lossy()
300 ))
301 }
302
303 /// Imports all graphics into the passed ROM and returns Lunar Magic's
304 /// text output or a [WrapperErr] if something went wrong.
305 ///
306 /// # Examples
307 ///
308 /// ```
309 /// # use lunar_magic_wrapper::Wrapper;
310 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
311 /// let output = lm_wrapper.import_all_graphics("C:/hacks/my_project/my_hack.smc");
312 /// ```
313 pub fn import_all_graphics<P: AsRef<Path>>(&self, rom_path: P) -> ResultL {
314 self.run_command(&format!(
315 "-ImportAllGraphics {}",
316 rom_path.as_ref().to_string_lossy()
317 ))
318 }
319
320 /// Exports the specified level number as an MWL at the specified location from the passed ROM
321 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
322 ///
323 /// # Examples
324 ///
325 /// ```
326 /// # use lunar_magic_wrapper::Wrapper;
327 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
328 /// let output = lm_wrapper.export_level(
329 /// "C:/hacks/my_project/my_hack.smc",
330 /// "C:/hacks/my_project/levels/level 105.mwl",
331 /// 105
332 /// );
333 /// ```
334 pub fn export_level<P, M>(&self, rom_path: P, mwl_path: M, level_number: u16) -> ResultL
335 where
336 P: AsRef<Path>,
337 M: AsRef<Path>,
338 {
339 self.run_command(&format!(
340 "-ExportLevel {} {} {}",
341 rom_path.as_ref().to_string_lossy(),
342 mwl_path.as_ref().to_string_lossy(),
343 level_number
344 ))
345 }
346
347 /// Imports the specified MWL file as the (optionally) specified level number
348 /// into the passed ROM and returns Lunar Magic's text output or
349 /// a [WrapperErr] if something went wrong.
350 ///
351 /// # Examples
352 ///
353 /// Without specifying a level number:
354 /// ```
355 /// # use lunar_magic_wrapper::Wrapper;
356 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
357 /// let output = lm_wrapper.import_level(
358 /// "C:/hacks/my_project/my_hack.smc",
359 /// "C:/hacks/my_project/levels/level 105.mwl",
360 /// None
361 /// );
362 /// ```
363 ///
364 /// With specifying a level number:
365 /// ```
366 /// # use lunar_magic_wrapper::Wrapper;
367 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
368 /// let output = lm_wrapper.import_level(
369 /// "C:/hacks/my_project/my_hack.smc",
370 /// "C:/hacks/my_project/levels/level 105.mwl",
371 /// Some(107)
372 /// );
373 /// ```
374 pub fn import_level<P, M>(&self, rom_path: P, mwl_path: M, level_number: Option<u16>) -> ResultL
375 where
376 P: AsRef<Path>,
377 M: AsRef<Path>,
378 {
379 if let Some(level_number) = level_number {
380 self.run_command(&format!(
381 "-ImportLevel {} {} {}",
382 rom_path.as_ref().to_string_lossy(),
383 mwl_path.as_ref().to_string_lossy(),
384 level_number
385 ))
386 } else {
387 self.run_command(&format!(
388 "-ImportLevel {} {}",
389 rom_path.as_ref().to_string_lossy(),
390 mwl_path.as_ref().to_string_lossy()
391 ))
392 }
393 }
394
395 /// Imports the specified map16 file into the passed ROM at the (optionally)
396 /// specified X, Y map16 location using the tileset of the specified level
397 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
398 ///
399 /// # Examples
400 ///
401 /// Without a location:
402 /// ```
403 /// # use lunar_magic_wrapper::*;
404 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
405 /// let output = lm_wrapper.import_map16(
406 /// "C:/hacks/my_project/my_hack.smc",
407 /// "C:/hacks/my_project/resources/tiles.map16",
408 /// 105,
409 /// None
410 /// );
411 /// ```
412 ///
413 /// With a location:
414 /// ```
415 /// # use lunar_magic_wrapper::*;
416 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
417 /// let output = lm_wrapper.import_map16(
418 /// "C:/hacks/my_project/my_hack.smc",
419 /// "C:/hacks/my_project/resources/tiles.map16",
420 /// 105,
421 /// Some((0x02, 0x40))
422 /// );
423 /// ```
424 pub fn import_map16<P, M>(
425 &self,
426 rom_path: P,
427 map16_path: M,
428 level_number: u16,
429 location: Option<(u32, u32)>,
430 ) -> ResultL
431 where
432 P: AsRef<Path>,
433 M: AsRef<Path>,
434 {
435 if let Some((x, y)) = location {
436 self.run_command(&format!(
437 "-ImportMap16 {} {} {} {},{}",
438 rom_path.as_ref().to_string_lossy(),
439 map16_path.as_ref().to_string_lossy(),
440 level_number,
441 x,
442 y
443 ))
444 } else {
445 self.run_command(&format!(
446 "-ImportMap16 {} {} {}",
447 rom_path.as_ref().to_string_lossy(),
448 map16_path.as_ref().to_string_lossy(),
449 level_number
450 ))
451 }
452 }
453
454 /// Imports the passed custom palette file into the specified level in the passed
455 /// ROM and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
456 ///
457 /// # Examples
458 /// ```
459 /// # use lunar_magic_wrapper::*;
460 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
461 /// let output = lm_wrapper.import_custom_palette(
462 /// "C:/hacks/my_project/my_hack.smc",
463 /// "C:/hacks/my_project/resources/my_palette.pal",
464 /// 105
465 /// );
466 /// ```
467 pub fn import_custom_palette<P: AsRef<Path>, Q: AsRef<Path>>(
468 &self,
469 rom_path: P,
470 palette_path: Q,
471 level_number: u16,
472 ) -> ResultL {
473 self.run_command(&format!(
474 "-ImportCustomPalette {} {} {}",
475 rom_path.as_ref().to_string_lossy(),
476 palette_path.as_ref().to_string_lossy(),
477 level_number
478 ))
479 }
480
481 /// Exports shared palette from the passed ROM to the specified output path
482 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
483 ///
484 /// # Examples
485 /// ```
486 /// # use lunar_magic_wrapper::*;
487 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
488 /// let output = lm_wrapper.export_shared_palette(
489 /// "C:/hacks/my_project/my_hack.smc",
490 /// "C:/hacks/my_project/resources/shared.pal"
491 /// );
492 /// ```
493 pub fn export_shared_palette<P, Q>(&self, rom_path: P, palette_path: Q) -> ResultL
494 where
495 P: AsRef<Path>,
496 Q: AsRef<Path>,
497 {
498 self.run_command(&format!(
499 "-ExportSharedPalette {} {}",
500 rom_path.as_ref().to_string_lossy(),
501 palette_path.as_ref().to_string_lossy()
502 ))
503 }
504
505 /// Imports passed shared palette into the passed ROM
506 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
507 ///
508 /// # Examples
509 /// ```
510 /// # use lunar_magic_wrapper::*;
511 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
512 /// let output = lm_wrapper.import_shared_palette(
513 /// "C:/hacks/my_project/my_hack.smc",
514 /// "C:/hacks/my_project/resources/shared.pal"
515 /// );
516 /// ```
517 pub fn import_shared_palette<P, Q>(&self, rom_path: P, palette_path: Q) -> ResultL
518 where
519 P: AsRef<Path>,
520 Q: AsRef<Path>,
521 {
522 self.run_command(&format!(
523 "-ImportSharedPalette {} {}",
524 rom_path.as_ref().to_string_lossy(),
525 palette_path.as_ref().to_string_lossy()
526 ))
527 }
528
529 /// Exports all map16 data from the passed ROM to the specified output path
530 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
531 ///
532 /// # Examples
533 /// ```
534 /// # use lunar_magic_wrapper::*;
535 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
536 /// let output = lm_wrapper.export_all_map16(
537 /// "C:/hacks/my_project/my_hack.smc",
538 /// "C:/hacks/my_project/resources/all.map16"
539 /// );
540 /// ```
541 pub fn export_all_map16<P, M>(&self, rom_path: P, map16_path: M) -> ResultL
542 where
543 P: AsRef<Path>,
544 M: AsRef<Path>,
545 {
546 self.run_command(&format!(
547 "-ExportAllMap16 {} {}",
548 rom_path.as_ref().to_string_lossy(),
549 map16_path.as_ref().to_string_lossy()
550 ))
551 }
552
553 /// Imports the passed all map16 file into the passed ROM
554 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
555 ///
556 /// # Examples
557 /// ```
558 /// # use lunar_magic_wrapper::*;
559 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
560 /// let output = lm_wrapper.import_all_map16(
561 /// "C:/hacks/my_project/my_hack.smc",
562 /// "C:/hacks/my_project/resources/all.map16"
563 /// );
564 /// ```
565 pub fn import_all_map16<P, M>(&self, rom_path: P, map16_path: M) -> ResultL
566 where
567 P: AsRef<Path>,
568 M: AsRef<Path>,
569 {
570 self.run_command(&format!(
571 "-ImportAllMap16 {} {}",
572 rom_path.as_ref().to_string_lossy(),
573 map16_path.as_ref().to_string_lossy()
574 ))
575 }
576
577 /// Exports multiple levels from the passed ROM to the specified
578 /// location using the (optionally) specified flags and returns
579 /// Lunar Magic's text output or a [WrapperErr] if something went wrong.
580 ///
581 /// Flags can be specified using the [LevelExportFlag] enum.
582 /// Note that if flags are omitted, Lunar Magic will use its
583 /// default settings for them.
584 ///
585 /// # Examples
586 ///
587 /// Without flags:
588 /// ```
589 /// # use lunar_magic_wrapper::*;
590 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
591 /// // MWL files will be prefixed with "level ", i.e. "level 105.mwl", etc.
592 /// // and be contained in `C:/hacks/my_project/resources/levels`
593 /// let output = lm_wrapper.export_mult_levels(
594 /// "C:/hacks/my_project/my_hack.smc",
595 /// "C:/hacks/my_project/resources/levels/level ",
596 /// None
597 /// );
598 /// ```
599 ///
600 /// With flags:
601 /// ```
602 /// # use lunar_magic_wrapper::*;
603 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
604 /// // MWL files will be prefixed with "level ", i.e. "level 105.mwl", etc.
605 /// // and be contained in `C:/hacks/my_project/resources/levels`
606 /// let output = lm_wrapper.export_mult_levels(
607 /// "C:/hacks/my_project/my_hack.smc",
608 /// "C:/hacks/my_project/resources/levels/level ",
609 /// Some(LevelExportFlag::None)
610 /// );
611 /// ```
612 pub fn export_mult_levels<P: AsRef<Path>, M: AsRef<Path>>(
613 &self,
614 rom_path: P,
615 mwl_path: M,
616 flags: Option<LevelExportFlag>,
617 ) -> ResultL {
618 if let Some(flags) = flags {
619 self.run_command(&format!(
620 "-ExportMultLevels {} {} {}",
621 rom_path.as_ref().to_string_lossy(),
622 mwl_path.as_ref().to_string_lossy(),
623 flags.bits()
624 ))
625 } else {
626 self.run_command(&format!(
627 "-ExportMultLevels {} {}",
628 rom_path.as_ref().to_string_lossy(),
629 mwl_path.as_ref().to_string_lossy()
630 ))
631 }
632 }
633
634 /// Imports multiple levels into the passed ROM from the specified
635 /// location using the (optionally) specified flags and returns
636 /// Lunar Magic's text output or a [WrapperErr] if something went wrong.
637 ///
638 /// Flags can be specified using the [LevelImportFlag] enum.
639 /// Note that if flags are omitted, Lunar Magic will use its
640 /// default settings for them.
641 ///
642 /// # Examples
643 ///
644 /// Without flags:
645 /// ```
646 /// # use lunar_magic_wrapper::*;
647 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
648 /// let output = lm_wrapper.import_mult_levels(
649 /// "C:/hacks/my_project/my_hack.smc",
650 /// "C:/hacks/my_project/resources/levels",
651 /// None
652 /// );
653 /// ```
654 ///
655 /// With flags:
656 /// ```
657 /// # use lunar_magic_wrapper::*;
658 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
659 /// let output = lm_wrapper.import_mult_levels(
660 /// "C:/hacks/my_project/my_hack.smc",
661 /// "C:/hacks/my_project/resources/levels",
662 /// Some(LevelImportFlag::None)
663 /// );
664 /// ```
665 pub fn import_mult_levels<P: AsRef<Path>, L: AsRef<Path>>(
666 &self,
667 rom_path: P,
668 level_directory: L,
669 flags: Option<LevelImportFlag>,
670 ) -> ResultL {
671 if let Some(flags) = flags {
672 self.run_command(&format!(
673 "-ImportMultLevels {} {} {}",
674 rom_path.as_ref().to_string_lossy(),
675 level_directory.as_ref().to_string_lossy(),
676 flags.bits()
677 ))
678 } else {
679 self.run_command(&format!(
680 "-ImportMultLevels {} {}",
681 rom_path.as_ref().to_string_lossy(),
682 level_directory.as_ref().to_string_lossy()
683 ))
684 }
685 }
686
687 /// Expands the passed ROM to the specified size
688 /// and returns Lunar Magic's text output or an
689 /// [WrapperErr] if something went wrong.
690 ///
691 /// # Examples
692 /// ```
693 /// # use lunar_magic_wrapper::*;
694 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
695 /// let output = lm_wrapper.expand_rom(
696 /// "C:/hacks/my_project/my_hack.smc",
697 /// RomSize::_4mb
698 /// );
699 /// ```
700 pub fn expand_rom<P: AsRef<Path>>(&self, rom_path: P, rom_size: RomSize) -> ResultL {
701 self.run_command(&format!(
702 "-ExpandROM {} {}",
703 rom_path.as_ref().to_string_lossy(),
704 rom_size
705 ))
706 }
707
708 /// Changes the compression of the passed ROM to the specified format
709 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
710 ///
711 /// # Examples
712 /// ```
713 /// # use lunar_magic_wrapper::*;
714 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
715 /// let output = lm_wrapper.change_compression(
716 /// "C:/hacks/my_project/my_hack.smc",
717 /// CompressionFormat::LcLz2Speed
718 /// );
719 /// ```
720 pub fn change_compression<P: AsRef<Path>>(
721 &self,
722 rom_path: P,
723 compression_format: CompressionFormat,
724 ) -> ResultL {
725 self.run_command(&format!(
726 "-ChangeCompression {} {}",
727 rom_path.as_ref().to_string_lossy(),
728 compression_format
729 ))
730 }
731
732 /// Transfers level global ExAnimation data from source ROM to destination ROM and
733 /// return Lunar Magic's text output or a [WrapperErr] if something went wrong.
734 ///
735 /// # Examples
736 /// ```
737 /// # use lunar_magic_wrapper::*;
738 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
739 /// let output = lm_wrapper.transfer_level_global_exanim(
740 /// "C:/hacks/my_project/destination.smc",
741 /// "C:/hacks/my_project/source.smc"
742 /// );
743 /// ```
744 pub fn transfer_level_global_exanim<D, S>(&self, dest_rom_path: D, src_rom_path: S) -> ResultL
745 where
746 D: AsRef<Path>,
747 S: AsRef<Path>,
748 {
749 self.run_command(&format!(
750 "-TransferLevelGlobalExAnim {} {}",
751 dest_rom_path.as_ref().to_string_lossy(),
752 src_rom_path.as_ref().to_string_lossy()
753 ))
754 }
755
756 /// Transfers overworld data from source ROM to destination ROM and
757 /// return Lunar Magic's text output or a [WrapperErr] if something went wrong.
758 ///
759 /// # Examples
760 /// ```
761 /// # use lunar_magic_wrapper::*;
762 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
763 /// let output = lm_wrapper.transfer_overworld(
764 /// "C:/hacks/my_project/destination.smc",
765 /// "C:/hacks/my_project/source.smc"
766 /// );
767 /// ```
768 pub fn transfer_overworld<D, S>(&self, dest_rom_path: D, src_rom_path: S) -> ResultL
769 where
770 D: AsRef<Path>,
771 S: AsRef<Path>,
772 {
773 self.run_command(&format!(
774 "-TransferOverworld {} {}",
775 dest_rom_path.as_ref().to_string_lossy(),
776 src_rom_path.as_ref().to_string_lossy()
777 ))
778 }
779
780 /// Transfers title screen data from source ROM to destination ROM and
781 /// return Lunar Magic's text output or a [WrapperErr] if something went wrong.
782 ///
783 /// # Examples
784 /// ```
785 /// # use lunar_magic_wrapper::*;
786 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
787 /// let output = lm_wrapper.transfer_title_screen(
788 /// "C:/hacks/my_project/destination.smc",
789 /// "C:/hacks/my_project/source.smc"
790 /// );
791 /// ```
792 pub fn transfer_title_screen<D, S>(&self, dest_rom_path: D, src_rom_path: S) -> ResultL
793 where
794 D: AsRef<Path>,
795 S: AsRef<Path>,
796 {
797 self.run_command(&format!(
798 "-TransferTitleScreen {} {}",
799 dest_rom_path.as_ref().to_string_lossy(),
800 src_rom_path.as_ref().to_string_lossy()
801 ))
802 }
803
804 /// Transfers credit data from source ROM to destination ROM and
805 /// return Lunar Magic's text output or a [WrapperErr] if something went wrong.
806 ///
807 /// # Examples
808 /// ```
809 /// # use lunar_magic_wrapper::*;
810 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
811 /// let output = lm_wrapper.transfer_credits(
812 /// "C:/hacks/my_project/destination.smc",
813 /// "C:/hacks/my_project/source.smc"
814 /// );
815 /// ```
816 pub fn transfer_credits<D, S>(&self, dest_rom_path: D, src_rom_path: S) -> ResultL
817 where
818 D: AsRef<Path>,
819 S: AsRef<Path>,
820 {
821 self.run_command(&format!(
822 "-TransferCredits {} {}",
823 dest_rom_path.as_ref().to_string_lossy(),
824 src_rom_path.as_ref().to_string_lossy()
825 ))
826 }
827
828 /// Exports title screen movement data from the passed ROM to the specified location
829 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
830 ///
831 /// # Examples
832 /// ```
833 /// # use lunar_magic_wrapper::*;
834 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
835 /// let output = lm_wrapper.export_title_moves(
836 /// "C:/hacks/my_project/my_hack.smc",
837 /// "C:/hacks/my_project/resources/title_screen_movement.zst"
838 /// );
839 /// ```
840 pub fn export_title_moves<D, S>(&self, rom_path: D, title_moves_path: S) -> ResultL
841 where
842 D: AsRef<Path>,
843 S: AsRef<Path>,
844 {
845 self.run_command(&format!(
846 "-ExportTitleMoves {} {}",
847 rom_path.as_ref().to_string_lossy(),
848 title_moves_path.as_ref().to_string_lossy()
849 ))
850 }
851
852 /// Imports title screen movement data into the passed ROM from the specified location
853 /// and returns Lunar Magic's text output or a [WrapperErr] if something went wrong.
854 ///
855 /// # Examples
856 /// ```
857 /// # use lunar_magic_wrapper::*;
858 /// # let lm_wrapper = Wrapper::new("C:/lunar_magic.exe");
859 /// let output = lm_wrapper.import_title_moves(
860 /// "C:/hacks/my_project/my_hack.smc",
861 /// "C:/hacks/my_project/resources/title_screen_movement.zst"
862 /// );
863 /// ```
864 pub fn import_title_moves<P, T>(&self, rom_path: P, title_moves_path: T) -> ResultL
865 where
866 P: AsRef<Path>,
867 T: AsRef<Path>,
868 {
869 self.run_command(&format!(
870 "-ImportTitleMoves {} {}",
871 rom_path.as_ref().to_string_lossy(),
872 title_moves_path.as_ref().to_string_lossy()
873 ))
874 }
875
876 fn run_command(&self, command_string: &str) -> ResultL {
877 if !self.lunar_magic_path.exists() {
878 return Err(WrapperErr::LunarMagicMissing {
879 command: format!(
880 "{} {}",
881 self.lunar_magic_path.to_string_lossy(),
882 command_string
883 ),
884 });
885 }
886
887 self.run_and_log(command_string)
888 }
889
890 #[cfg(target_os = "windows")]
891 fn build_command(main_command: &str, log_file_path: &Path) -> Command {
892 let args = format!("{} > {}", &main_command, log_file_path.to_string_lossy());
893
894 // Unfortunately, Lunar Magic writes directly to the console rather than to
895 // standard output/error and the only way I've found to suppress and get
896 // its output is to pipe it into a file with >, which I think I can only
897 // really manage by running via cmd here
898 let mut cmd = Command::new("cmd");
899 cmd.args(["/C", &args]);
900
901 cmd
902 }
903
904 #[cfg(not(target_os = "windows"))]
905 fn build_command(main_command: &str, log_file_path: &PathBuf) -> Command {
906 let args = format!("{} > {}", &main_command, log_file_path.to_string_lossy());
907
908 let mut cmd = Command::new("wine");
909 cmd.args(["cmd", "/C", &args]);
910
911 cmd
912 }
913
914 fn run_and_log(&self, command_string: &str) -> ResultL {
915 let main_command = format!(
916 "{} {}",
917 self.lunar_magic_path.to_string_lossy(),
918 command_string,
919 );
920
921 if let Ok(log_dir) = tempdir() {
922 let log_file_path = log_dir.path().join("lunar_magic.log");
923
924 let mut cmd = Self::build_command(&main_command, &log_file_path);
925 let output = cmd.output();
926
927 if let Ok(result) = output {
928 if let Ok(log_file) = File::open(log_file_path) {
929 let lines = BufReader::new(log_file).lines();
930 let output = lines.map(|l| l.expect("Failed to read line")).collect();
931
932 if !result.status.success() {
933 Err(WrapperErr::Operation {
934 code: result.status.code(),
935 command: main_command,
936 output,
937 })
938 } else {
939 Ok(output)
940 }
941 } else {
942 Err(WrapperErr::NoTempFile {
943 command: main_command,
944 })
945 }
946 } else {
947 let kind = output.err().unwrap().kind();
948
949 #[cfg(not(target_os = "windows"))]
950 if kind == ErrorKind::NotFound {
951 return Err(WrapperErr::NoWine);
952 }
953
954 Err(WrapperErr::FailedToExecute {
955 command: main_command,
956 error_kind: kind,
957 })
958 }
959 } else {
960 Err(WrapperErr::NoTempDir {
961 command: main_command,
962 })
963 }
964 }
965}