1 /*
2  * (c) Copyright 2015 by Einar Saukas. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *     * Redistributions of source code must retain the above copyright
7  *       notice, this list of conditions and the following disclaimer.
8  *     * Redistributions in binary form must reproduce the above copyright
9  *       notice, this list of conditions and the following disclaimer in the
10  *       documentation and/or other materials provided with the distribution.
11  *     * The name of its author may not be used to endorse or promote products
12  *       derived from this software without specific prior written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * Modified for ZX Next by z88dk.org.  Program performs identically.
28  * zcc +zxn -v -startup=30 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 --opt-code-size @zproject.lst -o dzx7 -pragma-include:zpragma.inc -subtype=dotn -Cz"--clean --exclude-sections IGNORE" -create-app
29  */
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <stdarg.h>
35 #include <arch/zxn.h>
36 #include <arch/zxn/esxdos.h>
37 
38 #include "user_interaction.h"
39 
40 #define BUFFER_SIZE     16384   // must be > MAX_OFFSET
41 
42 unsigned char ifp = 0xff;
43 unsigned char ofp = 0xff;
44 
45 unsigned char *input_name;
46 unsigned int   input_name_sz;
47 
48 unsigned char  output_name[ESX_FILENAME_LFN_MAX + 1];
49 
50 extern unsigned char input_data[BUFFER_SIZE];
51 extern unsigned char output_data[BUFFER_SIZE];
52 
53 unsigned int input_index;
54 unsigned int output_index;
55 
56 unsigned long input_size;
57 unsigned long output_size;
58 
59 struct esx_stat es;
60 
61 struct esx_cat catalog;
62 struct esx_lfn lfn;
63 
64 unsigned int partial_counter;
65 
66 unsigned char bit_mask;
67 unsigned char bit_value;
68 
69 // custom esxdos error report
70 
71 #define ebuf input_data
72 
error(char * fmt,...)73 int error(char *fmt, ...)
74 {
75    unsigned char *p;
76 
77    va_list v;
78    va_start(v, fmt);
79 
80 #ifdef __SCCZ80
81    vsnprintf(ebuf, sizeof(ebuf), va_ptr(v,char *), v);
82 #else
83    vsnprintf(ebuf, sizeof(ebuf), fmt, v);
84 #endif
85 
86    for (p = ebuf; p = strchr(p, '\n'); )
87       *p = '\r';
88 
89    ebuf[strlen(ebuf) - 1] += 0x80;
90    return (int)ebuf;
91 }
92 
93 // dzx7 functions
94 
read_byte(void)95 unsigned char read_byte(void)
96 {
97    if (input_index == partial_counter)
98    {
99       input_index = 0;
100       partial_counter = esx_f_read(ifp, input_data, BUFFER_SIZE);
101       input_size += partial_counter;
102 
103       if (partial_counter == 0)
104          exit(error(input_size ? "Truncated input file %s" : "Empty input file %s", input_name));
105    }
106 
107    return input_data[input_index++];
108 }
109 
read_bit(void)110 unsigned char read_bit(void)
111 {
112    bit_mask >>= 1;
113 
114    if (bit_mask == 0)
115    {
116       bit_mask = 0x80;
117       bit_value = read_byte();
118    }
119 
120    return (bit_value & bit_mask) ? 1 : 0;
121 }
122 
read_elias_gamma(void)123 unsigned int read_elias_gamma(void)
124 {
125    unsigned int value;
126    unsigned char i;
127 
128    for (i = 0; !read_bit(); ++i) ;
129 
130    if (i > 15)
131       return -1;
132 
133    for (value = 1; i; --i)
134       value = (value << 1) | read_bit();
135 
136    return value;
137 }
138 
read_offset(void)139 unsigned int read_offset(void)
140 {
141    unsigned int value;
142    unsigned char i;
143 
144    value = read_byte();
145 
146    if (value < 128)
147       return value;
148 
149    i = read_bit();
150    i = (i << 1) | read_bit();
151    i = (i << 1) | read_bit();
152    i = (i << 1) | read_bit();
153 
154    return (value & 0x7f) | ((unsigned int)i << 7) + 0x80;
155 }
156 
save_output(void)157 void save_output(void)
158 {
159    if (output_index)
160    {
161       if (esx_f_write(ofp, output_data, output_index) != output_index)
162          exit(error("Can't write output file %s", output_name));
163 
164       output_size += output_index;
165       output_index = 0;
166 
167       // print percentage progress
168 
169       printf("%03u%%" "\x08\x08\x08\x08", (unsigned int)(input_size * 100 / es.size));
170       user_interaction();
171    }
172 }
173 
write_byte(unsigned char value)174 void write_byte(unsigned char value)
175 {
176    output_data[output_index++] = value;
177 
178    if (output_index == BUFFER_SIZE)
179       save_output();
180 }
181 
write_bytes(unsigned int offset,unsigned int length)182 void write_bytes(unsigned int offset, unsigned int length)
183 {
184    int i;
185 
186    if (offset > output_size+output_index)
187       exit(error("Invalid data in input file %s", input_name));
188 
189    while (length-- > 0)
190    {
191       i = output_index - offset;
192       write_byte(output_data[(i >= 0) ? i : BUFFER_SIZE+i]);
193    }
194 }
195 
decompress(void)196 void decompress(void)
197 {
198    unsigned int length;
199 
200    input_size = 0;
201    input_index = 0;
202    partial_counter = 0;
203    output_index = 0;
204    output_size = 0;
205    bit_mask = 0;
206 
207    write_byte(read_byte());
208    while (1)
209    {
210       if (!read_bit())
211          write_byte(read_byte());
212       else
213       {
214          length = read_elias_gamma() + 1;
215 
216          if (length == 0)
217          {
218             save_output();
219 
220             if (input_index != partial_counter)
221                exit(error("Input file %s too long", input_name));
222 
223             return;
224          }
225 
226          write_bytes(read_offset() + 1, length);
227       }
228 
229       // allow user to interrupt
230 
231       user_interaction();
232    }
233 }
234 
235 // cleanup on exit
236 
237 static unsigned char old_cpu_speed;
238 
cleanup(void)239 void cleanup(void)
240 {
241    if (ifp != 0xff) esx_f_close(ifp);
242    if (ofp != 0xff) esx_f_close(ofp);
243 
244    puts("    ");
245 
246    ZXN_NEXTREGA(REG_TURBO_MODE, old_cpu_speed);
247 }
248 
249 // program start
250 
main(int argc,char ** argv)251 int main(int argc, char **argv)
252 {
253    static unsigned char forced_mode;
254    unsigned char i;
255 
256    // initialization
257 
258    old_cpu_speed = ZXN_READ_REG(REG_TURBO_MODE);
259    ZXN_NEXTREG(REG_TURBO_MODE, RTM_14MHZ);
260 
261    atexit(cleanup);
262 
263    puts("\nDZX7: LZ77/LZSS decompression\n(C) 2015 Einar Saukas\n\nv1.1 zx-next 128k z88dk.org\n");
264 
265    // process optional parameters
266 
267    for (i = 1; (i < (unsigned char)argc) && (*argv[i] == '-'); ++i)
268    {
269       if (stricmp(argv[i], "-f") == 0)
270          forced_mode = 1;
271       else
272          exit(error("Invalid parameter %s", argv[i]));
273    }
274 
275    // determine output filename
276 
277    if (argc == i + 1)
278    {
279       // operate on the lfn name to ensure file extension can be inferred
280 
281       strcpy(output_name, argv[i]);
282 
283       catalog.filter = ESX_CAT_FILTER_SYSTEM | ESX_CAT_FILTER_LFN;
284       catalog.filename = p3dos_cstr_to_pstr(output_name);
285       catalog.cat_sz = 2;
286 
287       lfn.cat = &catalog;
288 
289       if (esx_dos_catalog(&catalog) == 1)
290       {
291          esx_ide_get_lfn(&lfn, &catalog.cat[1]);
292 
293          input_name = lfn.filename;
294          input_name_sz = strlen(lfn.filename);
295       }
296       else
297       {
298          input_name = argv[i];
299          input_name_sz = strlen(input_name);
300       }
301 
302       // generate output filename
303 
304       if ((input_name_sz > 4) && (stricmp(input_name + input_name_sz - 4, ".ZX7") == 0))
305          snprintf(output_name, sizeof(output_name), "%.*s", input_name_sz - 4, input_name);
306       else
307          exit(error("Can't infer output filename"));
308    }
309    else if (argc == i + 2)
310    {
311       input_name = argv[i];
312       snprintf(output_name, sizeof(output_name), "%s", argv[i + 1]);
313    }
314    else
315    {
316       printf(".%s [-f] inname.zx7 [outname]\n"
317              "-f Overwrite output file\n", argv[0]);
318       exit(0);
319    }
320 
321    if (stricmp(output_name, input_name) == 0)
322       exit(error("In and out files are the same"));
323 
324    // open input file
325 
326    if ((ifp = esx_f_open(input_name, ESX_MODE_OPEN_EXIST | ESX_MODE_R)) == 0xff)
327       exit(error("Can't open input file %s", input_name));
328 
329    if (esx_f_fstat(ifp, &es))
330       exit(error("Can't stat input file %s", input_name));
331 
332    // check output file
333 
334    if ((ofp = esx_f_open(output_name, forced_mode ? (ESX_MODE_OPEN_CREAT_TRUNC | ESX_MODE_W) : (ESX_MODE_OPEN_CREAT_NOEXIST | ESX_MODE_W))) == 0xff)
335       exit(error("Can't create output file %s", output_name));
336 
337    // generate output file
338 
339    decompress();
340 
341    // done!
342 
343    printf("File decompressed from %lu to %lu bytes!", input_size, output_size);
344    return 0;
345 }
346