1use clap::{Parser, ValueEnum};
2use cpclib_common::camino::{Utf8Path, Utf8PathBuf};
3use cpclib_common::clap;
4use cpclib_common::clap::CommandFactory;
5use cpclib_crunchers::CompressMethod;
6use cpclib_crunchers::lzsa::LzsaVersion;
7use cpclib_disc::amsdos::AmsdosAddBehavior;
8use cpclib_files::FileAndSupport;
9
10pub mod built_info {
11 include!(concat!(env!("OUT_DIR"), "/built.rs"));
12}
13
14#[derive(Parser, Debug)]
15#[command(version, about, long_about = None)]
16pub struct CrunchArgs {
17 #[arg(short, long, help = "Cruncher of interest")]
18 cruncher: Cruncher,
19
20 #[arg(
21 short,
22 long,
23 help = "Input file to compress. Can be a binary (my_file.o), an Amsdos file (FILE.BIN), a file in a disc (my_disc.dsk#FILE.BIN"
24 )]
25 input: Option<Utf8PathBuf>,
26
27 #[arg(
28 short,
29 long,
30 help = "Also crunch the header. This is useful for binary files where the first bytes still correspond to a valid amsdos header",
31 default_value_t = false
32 )]
33 keep_header: bool,
34
35 #[arg(
36 short,
37 long,
38 help = "Compressed output file. Can be a binary, an Amsdos file, a file in a disc",
39 requires = "input"
40 )]
41 output: Option<Utf8PathBuf>,
42
43 #[arg(
44 short = 'H',
45 long,
46 help = "Add a header when storing the file on the host",
47 default_value_t = false
48 )]
49 header: bool,
50
51 #[arg(
52 short,
53 long,
54 help = "Show the z80 decompression source",
55 default_value_t = false,
56 conflicts_with = "input"
57 )]
58 z80: bool
59}
60
61#[derive(Debug, ValueEnum, Clone)]
62pub enum Cruncher {
63 Apultra,
64 Exomizer,
65 Lz4,
66 Lz48,
67 Lz49,
68 Lzsa1,
69 Lzsa2,
70 Shrinkler,
71 Upkr,
72 Zx0
73}
74
75impl Cruncher {
76 pub fn z80(&self) -> &Utf8Path {
77 let fname = match self {
78 Cruncher::Apultra => "inner://unaplib.asm",
79 Cruncher::Exomizer => "inner://deexo.asm",
80 Cruncher::Lz4 => "inner://lz4_docent.asm",
81 Cruncher::Lz48 => "inner://lz48decrunch.asm",
82 Cruncher::Lz49 => "inner://lz49decrunch.asm",
83 Cruncher::Lzsa1 => "inner://unlzsa1_fast.asm",
84 Cruncher::Lzsa2 => "inner://unlzsa1_fast.asm",
85 Cruncher::Shrinkler => "inner://deshrink.asm",
86 Cruncher::Zx0 => "inner://dzx0_fast.asm",
87 Cruncher::Upkr => "inner://uncrunch/upkr.asm"
88 };
89 fname.into()
90 }
91}
92
93pub fn command() -> clap::Command {
94 CrunchArgs::command()
95}
96
97pub fn process(args: CrunchArgs) -> Result<(), String> {
98 if args.z80 {
99 let fname = args.cruncher.z80();
100 let content = cpclib_asm::file::load_file(fname, &Default::default())
101 .unwrap()
102 .0;
103 let content = Vec::from(content);
104 let content = String::from_utf8(content).unwrap();
105 println!("; Import \"{fname}\" in basm or include the following content:\n{content}");
106 return Ok(());
107 }
108
109 let (data, header) =
111 cpclib_asm::file::load_file(args.input.as_ref().unwrap().as_path(), &Default::default())
112 .map_err(|e| {
113 format!(
114 "Unable to load the input file {}.\n{}",
115 args.input.unwrap(),
116 e
117 )
118 })
119 .map_err(|e| format!("Error while loading the file. {e}"))?;
120
121 let data = if args.keep_header {
123 if let Some(header) = header {
124 let mut header = header.as_bytes().to_vec();
125 let data: Vec<u8> = data.into();
126 header.extend_from_slice(&data);
127 header
128 }
129 else {
130 eprintln!("There is no header in the input file");
131 data.into()
132 }
133 }
134 else {
135 data.into()
136 };
137
138 let cruncher = match args.cruncher {
140 Cruncher::Apultra => CompressMethod::Apultra,
141 Cruncher::Exomizer => CompressMethod::Exomizer,
142 Cruncher::Lz4 => CompressMethod::Lz4,
143 Cruncher::Lz48 => CompressMethod::Lz48,
144 Cruncher::Lz49 => CompressMethod::Lz49,
145 Cruncher::Lzsa1 => CompressMethod::Lzsa(LzsaVersion::V1, None),
146 Cruncher::Lzsa2 => CompressMethod::Lzsa(LzsaVersion::V1, None),
147 Cruncher::Shrinkler => CompressMethod::Shrinkler(Default::default()),
148 Cruncher::Zx0 => CompressMethod::Zx0,
149 Cruncher::Upkr => CompressMethod::Upkr
150 };
151
152 let crunched = cruncher
153 .compress(&data)
154 .map_err(|e| "Error when crunching file.".to_string())?;
155
156 let file_and_support = FileAndSupport::new_auto(args.output.unwrap(), args.header);
157
158 file_and_support
159 .save(
160 &crunched,
161 Some(0xC000),
162 None,
163 Some(AmsdosAddBehavior::ReplaceAndEraseIfPresent)
164 )
165 .map_err(|e| format!("Error when saving the file. {e}"))?;
166
167 Ok(())
168}