1 /*
2     psftools: Manipulate console fonts in the .PSF format
3     Copyright (C) 2005,2006,2007  John Elliott
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 /* Load a CPI file into memory */
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include "cpi.h"
25 
26 #ifndef SEEK_SET
27 #define SEEK_SET 0
28 #define SEEK_CUR 1
29 #define SEEK_END 2
30 #endif
31 
32 static const cpi_byte font_magic[8]   = { 0xFF,'F','O','N','T',' ',' ',' ' };
33 static const cpi_byte fontnt_magic[8] = { 0xFF,'F','O','N','T','.','N','T' };
34 static const cpi_byte drfont_magic[8] = { 0x7F,'D','R','F','O','N','T',' ' };
35 
peek2(cpi_byte * p)36 static unsigned short peek2(cpi_byte *p)
37 {
38 	return (((unsigned short)p[1]) << 8) | p[0];
39 }
peek4(cpi_byte * p)40 static unsigned long peek4(cpi_byte *p)
41 {
42 	return (((unsigned long)p[3]) << 24) |
43 	       (((unsigned long)p[2]) << 16) |
44 	       (((unsigned long)p[1]) <<  8) | p[0];
45 }
46 
47 /* 1.0.5: Support CPI files with segment:offset pointers */
peek4so(cpi_byte * p)48 static unsigned long peek4so(cpi_byte *p)
49 {
50 	return peek2(p+2) * 16L + peek2(p);
51 }
52 
read2(FILE * fp,unsigned short * s)53 static int read2(FILE *fp, unsigned short *s)
54 {
55 	cpi_byte b[2];
56 	if (fread(b, 1, 2, fp) < 2) return CPI_ERR_ERRNO;
57 	*s = peek2(b);
58 	return 0;
59 }
60 
read4(FILE * fp,unsigned long * l)61 static int read4(FILE *fp, unsigned long *l)
62 {
63 	cpi_byte b[4];
64 	if (fread(b, 1, 4, fp) < 4) return CPI_ERR_ERRNO;
65 	*l = peek4(b);
66 	return 0;
67 }
68 
69 
load_fontdata(FILE * fp,CP_HEAD * cph,cpi_byte * fonts_head)70 static int load_fontdata(FILE *fp, CP_HEAD *cph, cpi_byte *fonts_head)
71 {
72 	unsigned fonttype, numfonts, m;
73 	CP_FONT *cpf;
74 	cpi_byte font_blk[6];
75 	long pos;
76 
77 	fonttype      = peek2(fonts_head);
78 	numfonts      = peek2(fonts_head + 2);
79 	cph->font_len = peek2(fonts_head + 4);
80 	/* Can't have DRFONT fonts in a FONT CPI */
81 	/* Toshiba LCD.CPI has FONTs of type 0, just to be awkward */
82 	if (fonttype != 1 && fonttype != 0) return CPI_ERR_BADFMT;
83 /* Load the individual fonts */
84 /* Fix for some DRDOS CPIs: printer CPIs report 2 but there is only one font */
85 	if (cph->dev_type == 2) numfonts = 1;
86  	cph->font_count = numfonts;
87 	for (m = 0; m < numfonts; m++)
88 	{
89 		if (cph->dev_type == 1)
90 		{
91 			pos = ftell(fp);
92 			if (fread(font_blk, 1, sizeof(font_blk), fp) < (int)sizeof(font_blk))
93 				return CPI_ERR_BADFMT;
94 			cpf = cph_add_font(cph);
95 			if (!cpf) return CPI_ERR_NOMEM;
96 /* Codepage header created. */
97 			cpf->height = font_blk[0];
98 			cpf->width  = font_blk[1];
99 			cpf->yaspect= font_blk[2];
100 			cpf->xaspect= font_blk[3];
101 			cpf->nchars = peek2(font_blk + 4);
102 			cpf->fontsize = ((cpf->width + 7) / 8) * cpf->height * cpf->nchars;
103 			cpf->self_offset = pos;
104 			cpf->data = malloc(cpf->fontsize);
105 			if (cpf->data == NULL)
106 			{
107 				cpf->height = cpf->width = cpf->nchars = 0;
108 				return CPI_ERR_NOMEM;
109 			}
110 			if (fread(cpf->data, 1, cpf->fontsize, fp) < cpf->fontsize)
111 				return CPI_ERR_BADFMT;
112 		}
113 		else if (cph->dev_type == 2)
114 		{
115 			pos = ftell(fp);
116 			if (fread(font_blk, 1, 4, fp) < 4)
117 				return CPI_ERR_BADFMT;
118 			cpf = cph_add_font(cph);
119 			if (!cpf) return CPI_ERR_NOMEM;
120 			cpf->pr_type    = peek2(font_blk);
121 			cpf->pr_seqsize = peek2(font_blk + 2);
122 			cpf->self_offset = pos;
123 			cpf->fontsize = cph->font_len - 4;
124 			cpf->data = malloc(cpf->fontsize);
125 			if (cpf->data == NULL)
126 			{
127 				cpf->fontsize = 0;
128 				return CPI_ERR_NOMEM;
129 			}
130 			if (fread(cpf->data, 1, cpf->fontsize, fp) < cpf->fontsize)
131 				return CPI_ERR_BADFMT;
132 		}
133 	}
134 	return CPI_ERR_OK;
135 }
136 
137 
cpi_loadfont(CPI_FILE * f,FILE * fp,cpi_byte * header)138 static int cpi_loadfont(CPI_FILE *f, FILE *fp, cpi_byte *header)
139 {
140 	unsigned short count;
141 	unsigned n;
142 	int err;
143 	cpi_byte fonts_head[6], cphead[28];
144 	CP_HEAD *cph;
145 	long pos, maxpos;
146 	int fontnt = 0;
147 	long filesize;
148 	int use_so = 0;
149 
150 	if (!memcmp(header, fontnt_magic, 8)) fontnt = 1;
151 
152 
153 	maxpos = 0;
154 	cpi_new(f, fontnt ? CPI_FONTNT : CPI_FONT);
155 /* Get the file size. If there's a pointer pointing beyond the end of
156  * the file, it may indicate that we need to change to segment:offset
157  * mode. */
158 	if (fseek(fp, 0, SEEK_END)) return CPI_ERR_ERRNO;
159 	filesize = ftell(fp);
160 	if (fseek(fp, peek4(header + 19), SEEK_SET)) return CPI_ERR_ERRNO;
161 	err = read2(fp, &count);
162 	if (err) return err;
163 
164 	for (n = 0; n < count; n++)
165 	{
166 		pos = ftell(fp);
167 		if (fread(cphead, 1, sizeof(cphead), fp) < (int)sizeof(cphead))
168 			return CPI_ERR_BADFMT;
169 		if (peek2(cphead) > 28)
170 		{
171 			/* More data in the header. May want to do something
172 			 * with this, later. */
173 		}
174 		cph = cpi_add_page(f, peek2(cphead + 16));
175 		if (!cph) return CPI_ERR_NOMEM;
176 		cph->headsize    = peek2(cphead);
177 		cph->self_offset = pos;
178 		cph->dev_type    = peek2(cphead + 6);
179 		sprintf(cph->dev_name, "%-8.8s", cphead + 8);
180 
181 		if (use_so)
182 		{
183 			cph->next_offset = peek4so(cphead + 2);
184 			cph->cpih_offset = peek4so(cphead + 24);
185 		}
186 		else
187 		{
188 			cph->next_offset = peek4(cphead + 2);
189 			cph->cpih_offset = peek4(cphead + 24);
190 /* 1.0.5: Check for segment:offset; if it's in use, then one or other
191  *       pointer (interpreted as a 32-bit integer) will almost certainly
192  *       be pointed off the end of the file.
193  *
194  * 	 Note that psftools cannot save CPIs with segment:offset pointers,
195  * 	 so the fact that segment:offset pointers are in use is not recorded
196  * 	 in the memory structure.
197  *
198  * 	 Also don't allow a next_offset of FFFF:FFFF to force segment:offset,
199  * 	 because that's an end-of-file marker.
200  *       */
201 			if (cph->cpih_offset > filesize ||
202 			    (cph->next_offset > filesize &&
203 			     cph->next_offset != 0xFFFFFFFFL))
204 			{
205 				use_so = 1;
206 				cph->next_offset = peek4so(cphead + 2);
207 				cph->cpih_offset = peek4so(cphead + 24);
208 			}
209 		}
210 /* FONT.NT fonts use relative offsets */
211 		if (fontnt)
212 		{
213 			cph->cpih_offset += pos;
214 			cph->next_offset += pos;
215 		}
216 /* Codepage header created. */
217 		if (fseek(fp, cph->cpih_offset, SEEK_SET))
218 			return CPI_ERR_ERRNO;
219 		if (fread(fonts_head, 1, sizeof(fonts_head), fp) < (int)sizeof(fonts_head))
220 			return CPI_ERR_BADFMT;
221 
222 		err = load_fontdata(fp, cph, fonts_head);
223 		if (err) return err;
224 
225 		if (ftell(fp) > maxpos) maxpos = ftell(fp);
226 
227 		if (cph->next_offset == 0xFFFFFFFFL || cph->next_offset == 0)
228 			break;
229 		if (fseek(fp, cph->next_offset, SEEK_SET))
230 			return CPI_ERR_ERRNO;
231 
232 	}
233 	fseek(fp, 0, SEEK_END);
234 	if (ftell(fp) > maxpos)
235 	{
236 		f->comment_len = ftell(fp) - maxpos;
237 		f->comment = malloc(f->comment_len);
238 		if (!f->comment) return CPI_ERR_NOMEM;
239 		if (fseek(fp, maxpos, SEEK_SET)) return CPI_ERR_ERRNO;
240 		if (fread(f->comment, 1, f->comment_len, fp) < f->comment_len)
241 			return CPI_ERR_ERRNO;
242 	}
243 	return 0;
244 }
245 
246 
cpi_loaddrfont(CPI_FILE * f,FILE * fp,cpi_byte * header)247 int cpi_loaddrfont(CPI_FILE *f, FILE *fp, cpi_byte *header)
248 {
249 	unsigned short count;
250 	unsigned m, n, q;
251 	cpi_byte cphead[28], fonts_head[6], font_blk[6];
252 	CP_HEAD *cph;
253 	CP_FONT *cpf;
254 	long pos, maxpos;
255 	unsigned fonttype, numfonts, maxchar;
256 	int c, d, err, nchars;
257 
258 	maxpos = 0;
259 	cpi_new(f, CPI_DRFONT);
260 	c = fgetc(fp);	/* Length of extended header */
261 	if (c == EOF) return CPI_ERR_BADFMT;
262 	f->drcount = c;
263 	f->drfonts = malloc(c * sizeof(CP_DRFONT));
264 	if (!f->drfonts) return CPI_ERR_NOMEM;
265 	for (n = 0; n < c; n++)
266 	{
267 		d = fgetc(fp);
268 		if (d == EOF) return CPI_ERR_BADFMT;
269 		f->drfonts[n].char_len = d;
270 	}
271 	for (n = 0; n < c; n++)
272 	{
273 		err = read4(fp, (unsigned long *)&f->drfonts[n].offset);
274 		if (err) return err;
275 	}
276 	/* Header read. Now seek to start of data. */
277 	if (fseek(fp, peek4(header + 19), SEEK_SET)) return CPI_ERR_ERRNO;
278 	err = read2(fp, &count);
279 	if (err) return err;
280 
281 	maxchar = 0;
282 	for (n = 0; n < count; n++)
283 	{
284 		pos = ftell(fp);
285 		if (fread(cphead, 1, sizeof(cphead), fp) < (int)sizeof(cphead))
286 			return CPI_ERR_BADFMT;
287 		if (peek2(cphead) > 28)
288 		{
289 			/* More data in the header. May want to do something
290 			 * with this, later. */
291 		}
292 		cph = cpi_add_page(f, peek2(cphead + 16));
293 		if (!cph) return CPI_ERR_NOMEM;
294 		cph->headsize    = peek2(cphead);
295 		cph->self_offset = pos;
296 		cph->next_offset = peek4(cphead + 2);
297 		cph->dev_type    = peek2(cphead + 6);
298 		sprintf(cph->dev_name, "%-8.8s", cphead + 8);
299 		cph->cpih_offset = peek4(cphead + 24);
300 /* Codepage header created. */
301 		if (fseek(fp, cph->cpih_offset, SEEK_SET))
302 			return CPI_ERR_ERRNO;
303 		if (fread(fonts_head, 1, sizeof(fonts_head), fp) < (int)sizeof(fonts_head))
304 			return CPI_ERR_BADFMT;
305 		fonttype      = peek2(fonts_head);
306 		numfonts      = peek2(fonts_head + 2);
307 		cph->font_len = peek2(fonts_head + 4);
308 		if (fonttype == 1) /* Wow. FONT record in a DRFONT. DRDOS
309 	       mode doesn't seem to mind this, so...	*/
310 		{
311 			err = load_fontdata(fp, cph, fonts_head);
312 			if (err) return err;
313 		}
314 		else if (fonttype != 2) return CPI_ERR_BADFMT;
315 		else
316 		{
317 			for (m = 0; m < c; m++)
318 /* Load the individual fonts */
319 			{
320 				if (fread(font_blk, 1, sizeof(font_blk), fp) < (int)sizeof(font_blk))
321 					return CPI_ERR_BADFMT;
322 				cpf = cph_add_font(cph);
323 				if (!cpf) return CPI_ERR_NOMEM;
324 				cpf->height = font_blk[0];
325 				cpf->width  = font_blk[1];
326 				cpf->yaspect= font_blk[2];
327 				cpf->xaspect= font_blk[3];
328 				cpf->nchars = peek2(font_blk + 4);
329 				cpf->fontsize = 0;
330 				cpf->self_offset = pos;
331 				cpf->data = NULL;
332 			}
333 			nchars = cpi_count_chars(cph);
334 /* Now load the lookup table into the cph.
335  * DRDOS utilities all assume the table is 256 chars long. We will be liberal
336  * in what we read and conservative in what we write: the mallocced table will
337  * always be at least 256 chars long, but we will only read from the file for
338  * characters that are actually there. */
339 			if (nchars < 256) nchars = 256;
340 			cph->dr_lookup = malloc(nchars * sizeof(unsigned short));
341 			if (!cph->dr_lookup) return CPI_ERR_NOMEM;
342 			nchars = cpi_count_chars(cph);
343 			for (q = 0; q < nchars; q++)
344 			{
345 				err = read2(fp, &cph->dr_lookup[q]);
346 				if (err) return err;
347 				if (cph->dr_lookup[q] > maxchar)
348 					maxchar = cph->dr_lookup[q];
349 			}
350 			for (;q < 256; q++)
351 			{
352 				cph->dr_lookup[q] = 0;
353 			}
354 		}
355 /* Go to next codepage */
356 		if (maxpos < ftell(fp)) maxpos = ftell(fp);
357 		if (cph->next_offset == 0xFFFFFFFFL || cph->next_offset == 0)
358 			break;
359 		if (fseek(fp, cph->next_offset, SEEK_SET))
360 			return CPI_ERR_ERRNO;
361 	}
362 /* Now we need to load the font bitmaps */
363 	for (n = 0; n < f->drcount; n++)
364 	{
365 		if (fseek(fp, f->drfonts[n].offset, SEEK_SET))
366 			return CPI_ERR_ERRNO;
367 		f->drfonts[n].bitmap_len = (maxchar + 1) * f->drfonts[n].char_len;
368 		f->drfonts[n].bitmap = malloc(f->drfonts[n].bitmap_len);
369 		if (!f->drfonts[n].bitmap) return CPI_ERR_NOMEM;
370 		if (fread(f->drfonts[n].bitmap, 1, f->drfonts[n].bitmap_len, fp) < f->drfonts[n].bitmap_len) return CPI_ERR_BADFMT;
371 		if (maxpos < ftell(fp)) maxpos = ftell(fp);
372 	}
373 	fseek(fp, 0, SEEK_END);
374 	if (ftell(fp) > maxpos)
375 	{
376 		f->comment_len = ftell(fp) - maxpos;
377 		f->comment = malloc(f->comment_len);
378 		if (!f->comment) return CPI_ERR_NOMEM;
379 		if (fseek(fp, maxpos, SEEK_SET)) return CPI_ERR_ERRNO;
380 		if (fread(f->comment, 1, f->comment_len, fp) < f->comment_len)
381 			return CPI_ERR_ERRNO;
382 	}
383 	return 0;
384 }
385 
386 
cpi_loadnaked(CPI_FILE * f,FILE * fp)387 int cpi_loadnaked(CPI_FILE *f, FILE *fp)
388 {
389 	int err;
390 	cpi_byte cphead[28], fonts_head[6];
391 	CP_HEAD *cph;
392 	long maxpos;
393 
394 	maxpos = 0;
395 	cpi_new(f, CPI_NAKED);
396 
397 	if (fread(cphead, 1, sizeof(cphead), fp) < (int)sizeof(cphead))
398 		return CPI_ERR_BADFMT;
399 	if (peek2(cphead) > 28)
400 	{
401 		/* This length doesn't seem to make any difference in a
402 		 * naked CP file */
403 	}
404 	cph = cpi_add_page(f, peek2(cphead + 16));
405 	if (!cph) return CPI_ERR_NOMEM;
406 	cph->headsize    = peek2(cphead);
407 /* These pointers are meaningless in a .CP file, but do them anyway... */
408 	cph->self_offset = 0;
409 	cph->next_offset = peek4(cphead + 2);
410 	cph->dev_type    = peek2(cphead + 6);
411 	sprintf(cph->dev_name, "%-8.8s", cphead + 8);
412 	cph->cpih_offset = peek4(cphead + 24);
413 /* Codepage header created. Don't bother to seek to the image header; it
414  * must follow this header immediately. */
415 	if (fread(fonts_head, 1, sizeof(fonts_head), fp) < (int)sizeof(fonts_head))
416 		return CPI_ERR_BADFMT;
417 /* Force the type in the header to 1; some files have 0. */
418 	fonts_head[0] = 1;
419 	fonts_head[1] = 0;
420 	err = load_fontdata(fp, cph, fonts_head);
421 	if (err) return err;
422 
423 	if (ftell(fp) > maxpos) maxpos = ftell(fp);
424 	fseek(fp, 0, SEEK_END);
425 	if (ftell(fp) > maxpos)
426 	{
427 		f->comment_len = ftell(fp) - maxpos;
428 		f->comment = malloc(f->comment_len);
429 		if (!f->comment) return CPI_ERR_NOMEM;
430 		if (fseek(fp, maxpos, SEEK_SET)) return CPI_ERR_ERRNO;
431 		if (fread(f->comment, 1, f->comment_len, fp) < f->comment_len)
432 			return CPI_ERR_ERRNO;
433 	}
434 	return 0;
435 }
436 
437 
438 
cpi_load(CPI_FILE * f,const char * filename)439 int cpi_load(CPI_FILE *f, const char *filename)
440 {
441 	int err;
442 	FILE *fp;
443 
444 	fp = fopen(filename, "rb");
445 	if (!fp) return CPI_ERR_ERRNO;
446 	err = cpi_loadfile(f, fp);
447 	if (fclose(fp)) return CPI_ERR_ERRNO;
448 	return err;
449 }
450 
cpi_loadfile(CPI_FILE * f,FILE * fp)451 int cpi_loadfile(CPI_FILE *f, FILE *fp)
452 {
453 	cpi_byte magic[23];
454 	int err;
455 
456 	if (fread(magic, 1, 23, fp) < 23)
457 	{
458 		fclose(fp);
459 		return CPI_ERR_BADFMT;
460 	}
461 	if (!memcmp(magic, font_magic, 8) || !memcmp(magic, fontnt_magic, 8))
462 	{
463 		f->magic0 = magic[0];
464 		sprintf(f->format, "%-7.7s", magic + 1);
465 		err = cpi_loadfont(f, fp, magic);
466 	}
467 	else if (!memcmp(magic, drfont_magic, 8))
468 	{
469 		f->magic0 = magic[0];
470 		sprintf(f->format, "%-7.7s", magic + 1);
471 		err = cpi_loaddrfont(f, fp, magic);
472 	}
473 /* .CP: "Naked" font extracted from a CPI file */
474 	else if (magic[0] >= 0x1A && magic[0] <= 0x20 && magic[1] == 0)
475 	{
476 		f->magic0 = 0xFF;
477 		sprintf(f->format, "%-7.7s", "FONT");
478 		fseek(fp, 0, SEEK_SET);
479 		err = cpi_loadnaked(f, fp);
480 	}
481 	else err = CPI_ERR_BADFMT;
482 	return err;
483 }
484 
485