aboutsummaryrefslogtreecommitdiff
path: root/src/io.c
blob: e2b982c1ccae2a584f2c3c9c6e3658af559cf586 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#include "unalf.h"
#include "addrs.h"

static int headers_read = 0;
static int convert_eols = 0;

static void die_arc(void) {
	fprintf(stderr, "%s: fatal: this is an ARC file, not ALF\n", self);
	exit(1);
}

static void die_not_alf(void) {
	fprintf(stderr, "%s: fatal: not an ALF file\n", self);
	exit(1);
}

/* truncating the file by name could be a race condition, but I don't
   think it's going to be a real-world problem for anyone. */
static void eof_junk(long pos) {
	fprintf(stderr, "%s: junk at EOF (%s)\n", self, opts.fixjunk ? "removing" : "ignoring");
	if(opts.fixjunk) {
		if(truncate(in_filename, pos) < 0) {
			fprintf(stderr, "%s: could not remove junk: ", self);
			perror(in_filename);
		}
	}
}

static void check_hdr_size(const char *name, unsigned long size) {
	const char *desc;
	int fatal = 0;

	/* fits on a double-density disk? */
	if(size < 184320)
		return;

	/* >= 16MB files are impossible because the decrunch algorithm
		ignores the high byte of the 4-byte length. */
	if(size >= 16777216L) {
		desc = "impossibly large (>=16MB)";
		fatal = !opts.listonly; /* listing the file isn't an error, extracting it is. */
	/* >1MB files are possible (e.g. with a hard drive on SpartaDOS X)
		but exceedingly rare in the Atari world. */
	} else if(size > 1048576L) {
		desc = "suspiciously large (>1MB)";
	} else {
		desc = "too large for a floppy disk (>180KB)";
	}

	fprintf(stderr, "%s: %s: header #%d %s size is %s.\n",
			self, (fatal ? "fatal" : "warning"), headers_read, name, desc);
	if(fatal) exit(1);
}

static void sanity_check_header(long pos) {
	struct stat s;
	unsigned long origsize, compsize;
	int fatal;

	origsize = getquad(alf_hdr_origsize0);
	compsize = getquad(alf_hdr_compsize0);

	check_hdr_size("original", origsize);
	check_hdr_size("compressed", compsize);

	pos += 29; /* skip header */
	if(fstat(fileno(in_file), &s) < 0) {
		fprintf(stderr, "%s: fatal: fstat on %s ", self, in_filename);
		perror("failed");
		return;
	}

	if(compsize > (s.st_size - pos)) {
		fatal = !(opts.force || opts.listonly);
		fprintf(stderr, "%s: %s: compressed size for header #%d is bigger than the rest of the file (truncated?)", self, fatal ? "fatal" : "warning", headers_read);
		if(fatal) {
			fputs(", use -F to override.\n", stderr);
			exit(1);
		}
		fputs("\n", stderr);
	}

	/* 0 byte input gives a 2-byte output, 1 byte input gives 4,
	   2 bytes gives 5.
	   don't complain about these. starting with any 3 byte input,
	   the compressed size will always be under 2x the input size. */
	if(origsize > 2 && compsize > 5 && compsize > origsize * 2) {
		fatal = !(opts.force || opts.listonly);
		fprintf(stderr, "%s: %s: compressed size for header #%d is over twice the uncompressed size (corrupt?)", self, fatal ? "fatal": "warning", headers_read);
		if(fatal) {
			fputs(", use -F to override.\n", stderr);
			exit(1);
		}
		fputs("\n", stderr);
	}
}

/* return 1 if a header is read, 0 if not */
int read_alf_header(void) {
	u8 h1, h2;
	int bytes;
	long read_pos;

	read_pos = ftell(in_file);

	bytes = fread(mem + alf_header, 1, 29, in_file);

	if(!bytes) {
		if(headers_read)
			return 0;
		else
			die_not_alf();
	} else if(bytes < 29) {
		if(headers_read) {
			eof_junk(read_pos);
			return 0;
		} else {
			die_not_alf();
		}
	}

	h1 = mem[alf_header];
	h2 = mem[alf_hdr_sig];

	if(h1 == 0x1a) {
		if(h2 < 0x0f) die_arc();
		if(h2 == 0x0f) {
			headers_read++;
			sanity_check_header(read_pos);
			return 1; /* signature matches */
		}
	}

	if(headers_read)
		eof_junk(read_pos);
	else
		die_not_alf();

	return 0;
}

/* read buf_len_l/h bytes into buf_adr_l/h, then store the number
   of bytes actually read in buf_len_l/h. EOF is handled like the
   Atari does: you get a partial buffer *and* EOF status. */
void readblock(void) {
	int bytes, len, bufadr;
	u8 *buf;

	bufadr = dpeek(buf_adr_l);
	buf = mem + bufadr;
	len = dpeek(buf_len_l);

	bytes = fread(buf, 1, len, in_file);

	/* mimic CIO's behaviour: Y=1 means OK, Y>=0x80 means error */
	if(feof(in_file)) {
		ldy_i(0x88);
	} else {
		ldy_i(1);
	}
	dpoke(buf_len_l, bytes);
}

/* Atari-specific (can't use isprint()) */
static int is_printable(u8 c) {
	return (c == 0x9b || (c >= ' ' && c <= 124));
}

static int is_text_file(u8 *buf) {
	return is_printable(buf[0]) && is_printable(buf[1]);
}

/* mirror of readblock(), plus EOL conversion if needed. With -a,
   a file is considered text if its first 2 bytes are printable ATASCII,
   including EOLs. With -aa, all files are converted. */
void writeblock(void) {
	int i, bytes, len, bufadr;
	u8 *buf;

	bufadr = dpeek(buf_adr_l);
	buf = mem + bufadr;
	len = dpeek(buf_len_l);

	if(new_file) {
		if(opts.txtconv > 1) {
			convert_eols = 1;
		} else if(opts.txtconv == 1 && len > 1) {
			convert_eols = is_text_file(buf);
		} else {
			convert_eols = 0;
		}
	}
	new_file = 0;

	if(convert_eols) {
		for(i = 0; i < len; i++) {
			if(buf[i] == 0x9b) buf[i] = '\n';
			if(buf[i] == 0x7f) buf[i] = '\t';
		}
	}

	bytes = fwrite(buf, 1, len, out_file);
	if(bytes < 0) {
		extern char *out_filename; /* extract.c */
		fprintf(stderr, "%s: fatal: ", self);
		perror(out_filename);
		exit(1);
	}
	bytes_written += bytes;
	dpoke(buf_len_l, bytes);
}