1 /*
2 
3    Copyright 2021, dettus@dettus.net
4 
5    Redistribution and use in source and binary forms, with or without modification,
6    are permitted provided that the following conditions are met:
7 
8    1. Redistributions of source code must retain the above copyright notice, this
9    list of conditions and the following disclaimer.
10 
11    2. Redistributions in binary form must reproduce the above copyright notice,
12    this list of conditions and the following disclaimer in the documentation
13    and/or other materials provided with the distribution.
14 
15    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 
27  */
28 
29 // the purpose of this file is to read the MS DOS Game binaries,
30 // and to translate them into the .mag/.gfx format.
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "loader_common.h"
36 #include "vm68k_macros.h"
37 
38 #define	READ_INT24LE(ptr,idx)	(\
39 	(((unsigned int)((ptr)[((idx)+2)])&0xff)<<16)	|\
40 	(((unsigned int)((ptr)[((idx)+1)])&0xff)<< 8)	|\
41 	(((unsigned int)((ptr)[((idx)+0)])&0xff)<< 0)	|\
42 	0)
43 
44 ///////////////////////////////////////////
45 #define	ADFS_IMAGESIZE	819200
46 #define	ADFS_NICKMAP_OFFSET	0x40
47 #define	ADFS_NICKMAP_END	0x400
48 #define	RECURSIVEMAX	5
49 
50 typedef	struct _tGames
51 {
52 	char magicdirname[10];
53 	char version;
54 	char gamename[32];
55 	unsigned int expectedmask;
56 	unsigned char pictureorder[32];
57 } tGames;
58 #define	MAXGAMES	5
59 #define	MAXFILENAMENUM	10
60 #define	F6CODE	6
61 #define	F7DICT	7
62 #define	F8STRING2	8
63 #define	F9STRING1	9
64 #define	F10PICTURES	10
65 
66 const tGames loader_archimedes_cGames[MAXGAMES]={
67 		{"Pawn",		0,"The Pawn",			(1<<F6CODE)|            (1<<F8STRING2)|(1<<F9STRING1)|(1<<F10PICTURES),{0,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}},
68 		{"GUILD",		1,"The Guild of Thieves",	(1<<F6CODE)|(1<<F7DICT)|(1<<F8STRING2)|(1<<F9STRING1)|(1<<F10PICTURES),{0,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}},
69 		{"Jinxter",		2,"Jinxter",			(1<<F6CODE)|(1<<F7DICT)|(1<<F8STRING2)|(1<<F9STRING1)|(1<<F10PICTURES),{16,21,22,11, 0, 6, 3,15,24, 7,12,13, 1,28,26,17,23, 9, 4,18,25,20,10, 8,19,14, 2, 0, 0, 0, 0, 0}},
70 		{"Corruption",		3,"Corruption",			(1<<F6CODE)|(1<<F7DICT)|(1<<F8STRING2)|(1<<F9STRING1)|(1<<F10PICTURES),{0,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}},
71 		{"Fish!",		3,"Fish!",			(1<<F6CODE)|(1<<F7DICT)|(1<<F8STRING2)|(1<<F9STRING1)|(1<<F10PICTURES),{0,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}}
72 };
loader_archimedes_findoffset(unsigned char * map,int mapsize,int sectorsize,int indicator,int indlen,int bytespermapbit,int * pStart)73 int loader_archimedes_findoffset(unsigned char* map,int mapsize,int sectorsize,int indicator,int indlen,int bytespermapbit,int* pStart)
74 {
75 	int i;
76 	int status;
77 	unsigned int mask;
78 	unsigned short mapind;
79 	int sectoroffset;
80 	int indfound;
81 	int starti;
82 	i=0;
83 	status=0;
84 	mask=(1<<indlen)-1;
85 
86 	sectoroffset=0;
87 	// in case the last 8 bits of the indicator are set, one map entry shares the pointer to several sectors.
88 	// the last 8 bits are said offset.
89 	if (indicator&0xff)
90 	{
91 		sectoroffset=((indicator&0xff)-1)*sectorsize;
92 	}
93 	indicator>>=8;
94 	indfound=0;
95 	// go through the allocation map, search for the indicator
96 	starti=-1;
97 	while (i<mapsize && indfound==0)
98 	{
99 		// TODO: for simplicity reasons, assume that the indicators are byte-aligned within the map.
100 		// in addition to this, do not think about fragmented files (yet)
101 		if (status==0)
102 		{
103 			mapind=READ_INT16LE(map,i);
104 			if (mapind)
105 			{
106 				if ((mapind&mask)==(indicator))
107 				{
108 					indfound=1;
109 					starti=i;
110 				}
111 				if ((mapind&~mask)==0) status=1;
112 			}
113 			i+=2;
114 		} else {
115 			if (map[i]&0x80)	// byte aligned assumption
116 			{
117 				status=0;
118 			}
119 			i++;
120 		}
121 	}
122 	if (indfound)
123 	{
124 		if (pStart!=NULL)
125 		{
126 			*pStart=starti*8*bytespermapbit+sectoroffset;	// this is assuming that the entries are byte-aligned
127 		}
128 	}
129 	return 0;	// did not find the indicator
130 }
loader_archimedes_recursivedir(unsigned char * dskimg,int dsksize,int recursivettl,char * dirname,int hugo0nick1,int sectorsize,int indlen,int bytespermapbit,int diridx,int * pGameId,int * pOffsets,int * pLengths)131 unsigned int loader_archimedes_recursivedir(unsigned char* dskimg,int dsksize,int recursivettl,char* dirname,int hugo0nick1,int sectorsize,int indlen,int bytespermapbit,int diridx, int* pGameId,int *pOffsets,int* pLengths)
132 {
133 	int dirsize[2]={0x4cc-5,0x7dd-5};	// Hugo/Nick have different sizes for the directory
134 	int i;
135 	int done;
136 	int retval;
137 	int found;
138 
139 	if (recursivettl==-1) return 0;	// too many recursions. something when wrong
140 
141 	retval=0;
142 	i=0;
143 	done=0;
144 	found=0;
145 	diridx+=5;	// TODO: check for Hugo/Nick
146 	while (i<dirsize[hugo0nick1] && !done && retval==0)
147 	{
148 		// directories are 16 26 bytes long.
149 		// 0..9:  10 bytes name
150 		// 10..13: 4 bytes loadaddr
151 		// 14..17: 4 bytes execaddr
152 		// 18..21: 4 bytes length
153 		// 22..24: 3 bytes indicator
154 		// 25: 1 byte file type/permissions
155 		unsigned char name[5];	// looking for filenames F6,F7,F8,F9,F10 requires no more than 4 bytes.
156 		unsigned int indicator;
157 		int offset;
158 		int length;
159 		unsigned char dirtype;
160 
161 		name[0]=dskimg[diridx+i+0];	// make upper case
162 		name[1]=dskimg[diridx+i+1];
163 		name[2]=dskimg[diridx+i+2];
164 		name[3]=dskimg[diridx+i+3];
165 		name[4]=0;
166 
167 		length=READ_INT32LE(dskimg,diridx+i+18);
168 		indicator=READ_INT24LE(dskimg,diridx+i+22);
169 		dirtype=dskimg[diridx+i+25];
170 		if (name[0]==0) done=1;	// the last entry in the directory starts with 0.
171 		else {
172 
173 			if (hugo0nick1)
174 			{
175 				loader_archimedes_findoffset(&dskimg[ADFS_NICKMAP_OFFSET],ADFS_NICKMAP_END-ADFS_NICKMAP_OFFSET,sectorsize,indicator,indlen,bytespermapbit,&offset);
176 			} else {
177 				offset=indicator<<8;
178 			}
179 
180 			if ((dirtype&0x8)==0x8)	// incase it is another directory
181 			{
182 				retval=loader_archimedes_recursivedir(dskimg,dsksize,recursivettl-1,(char*)&dskimg[diridx+i+0],hugo0nick1,sectorsize,indlen,bytespermapbit,offset,pGameId,pOffsets,pLengths);
183 			} else {
184 				if (name[0]=='F' || name[0]=='f' || name[0]=='c' )	// when the filename starts with an 'f' or 'F'. For "corruption", it is a lower case 'c'. those are the files we are looking for
185 				{
186 					int num;	// turn the number behind the F,f, or c into an integer
187 					num=0;
188 					if (name[1]>='0' && name[1]<='9')
189 					{
190 						num*=10;
191 						num+=name[1]-'0';
192 					} else num=-1;
193 					if (name[2]>='0' && name[2]<='9')
194 					{
195 						num*=10;
196 						num+=name[2]-'0';
197 					} else if (name[2]!=0xd && name[2]!=0x00) num=-1;
198 					if (num>=0 && num<=MAXFILENAMENUM)
199 					{
200 						pOffsets[num]=offset;
201 						pLengths[num]=length;
202 						found|=(1<<num);
203 					}
204 				}
205 			}
206 		}
207 
208 		i+=26;
209 	}
210 	if (found && *pGameId==-1)
211 	{
212 		int j;
213 		int k;
214 		int match;
215 
216 		for (j=0;j<MAXGAMES && *pGameId==-1;j++)
217 		{
218 			match=1;
219 			for (k=0;k<10 && match;k++)
220 			{
221 				if (loader_archimedes_cGames[j].magicdirname[k]!=dirname[k] && dirname[k]>' ') match=0;
222 			}
223 			if (match)
224 			{
225 				*pGameId=j;
226 			}
227 		}
228 		return found;
229 	}
230 	return retval;
231 }
loader_archimedes_findfiles(unsigned char * dskimg,int dsksize,int * pGameId,int * pOffsets,int * pLengths)232 unsigned int loader_archimedes_findfiles(unsigned char* dskimg,int dsksize,
233 	int *pGameId,int* pOffsets,int* pLengths)		// pOffsets are pointers to the files F6 (code), F7 (dict), f8 (string2), f9(string1), F10 (pictures). IN THAT ORDER. the same goes for the lengths
234 {
235 	int hugo0nick1;
236 	int sectorsize;
237 	int indlen;
238 	int bytespermapbit;
239 	int diridx;
240 	unsigned int filemask;
241 
242 	if (dsksize!=ADFS_IMAGESIZE)
243 	{
244 		fprintf(stderr,"The diskiamge size is off: %d was expected, but %d bytes were read\n",ADFS_IMAGESIZE,dsksize);
245 		return LOADER_NOK;
246 	}
247 
248 	// step 1: find out, if the disk image contains a file system in the 'Hugo' or 'Nick' format.
249 	hugo0nick1=-1;
250 	// do it the lazy way. Just check if at the position where the root directory is expected to be,
251 	// the magic word "Hugo" or "Nick" appears.
252 #define	HUGOOFFS 0x400
253 #define	NICKOFFS 0x800
254 	// for Hugo disks, this is at the beginning of sector1, so @0x401-0x404
255 	if (dskimg[HUGOOFFS+1]=='H' && dskimg[HUGOOFFS+2]=='u' && dskimg[HUGOOFFS+3]=='g' && dskimg[HUGOOFFS+4]=='o') hugo0nick1=0;
256 	// for the Nick disks, this is at the beginning of sector2, so 0x801-0x803
257 	else if (dskimg[NICKOFFS+1]=='N' && dskimg[NICKOFFS+2]=='i' && dskimg[NICKOFFS+3]=='c' && dskimg[NICKOFFS+4]=='k') hugo0nick1=1;
258 	else {
259 		fprintf(stderr,"unable to determine the file system type.\n");
260 		return LOADER_NOK;
261 	}
262 	sectorsize=1024;
263 	indlen=-1;
264 	bytespermapbit=-1;
265 	diridx=0x400;
266 	// step 2: if it is the "Nick" format, it comes with a header
267 	if (hugo0nick1==1)
268 	{
269 		int rootind;
270 
271 		// according to //http://www.riscos.com/support/developers/prm/filecore.html#85862
272 		// the header for the "Nick" format is in the first 36 bytes of the dsk image:
273 		// since the archimedes had an ARM processor, the values are little endian
274 		// 0..3: 4 bytes UNKNOWN
275 		// 4:    1 byte log2sector size  *
276 		// 5:    1 byte sectors/track
277 		// 6:    1 bytes heads/track
278 		// 7:    1 byte density
279 		// 8:    1 byte indlen           *
280 		// 9:    1 byte byterspermapbit  *
281 		// 10:   1 byte skey
282 		// 11:   1 byte boot option
283 		// 12:   1 byte lowsector
284 		// 13:   1 byte number of zones
285 		// 14:   2 byes zone spare
286 		// 16:   4 bytes root indicator	*
287 		// 20:   4 bytes disk size
288 		// 24:   4 bytes disk id
289 		// 26..35: disk name
290 		// only the ones with the * are important
291 
292 		sectorsize=1<<dskimg[4];
293 		indlen=dskimg[8];
294 		bytespermapbit=1<<dskimg[9];
295 		rootind=READ_INT32LE(dskimg,16);
296 
297 
298 		loader_archimedes_findoffset(&dskimg[ADFS_NICKMAP_OFFSET],ADFS_NICKMAP_END-ADFS_NICKMAP_OFFSET,sectorsize,rootind,indlen,bytespermapbit,&diridx);
299 	}
300 
301 
302 	// at this point, the location of the root directory is known.
303 	// to find the files named F6, F7, F8, F9, F10, start a recursive search
304 	filemask=loader_archimedes_recursivedir(dskimg,dsksize,RECURSIVEMAX,"$",	hugo0nick1,sectorsize,indlen,bytespermapbit,diridx,	pGameId,pOffsets,pLengths);
305 
306 	if (filemask && *pGameId!=-1)
307 	{
308 		fprintf(stderr,"loader detected %s --> ",loader_archimedes_cGames[*pGameId].gamename);
309 		if ((filemask&loader_archimedes_cGames[*pGameId].expectedmask)!=loader_archimedes_cGames[*pGameId].expectedmask)
310 		{
311 			fprintf(stderr,"FAIL\n");
312 			fprintf(stderr,"insufficient files on the disk. sorry\n");
313 			return LOADER_NOK;
314 		}
315 		fprintf(stderr,"okay\n");
316 		return LOADER_OK;
317 	}
318 	return LOADER_NOK;
319 
320 }
loader_archimedes_mkmag(unsigned char * dskimg,int dsksize,unsigned char * magbuf,int * magsize,int gameId,int * offsets,int * lengths)321 int loader_archimedes_mkmag(unsigned char *dskimg,int dsksize,unsigned char* magbuf,int* magsize,
322 		int gameId,int* offsets,int *lengths)
323 {
324 	int magidx;
325 	int codesize;
326 	int string1size;
327 	int string2size;
328 	int dictsize;
329 
330 	magidx=42;
331 	// the game code is stored in F6, it is packed
332 	codesize=loader_common_unhuffer(&dskimg[offsets[F6CODE]],lengths[F6CODE],&magbuf[magidx]);
333 	magidx+=codesize;
334 	// the string1 is stored in F9
335 	memcpy(&magbuf[magidx],&dskimg[offsets[F9STRING1]],lengths[F9STRING1]);
336 	string1size=lengths[F9STRING1];
337 	magidx+=string1size;
338 	// string2 is in F8, it is packed
339 	string2size=loader_common_unhuffer(&dskimg[offsets[F8STRING2]],lengths[F8STRING2],&magbuf[magidx]);
340 	magidx+=string2size;
341 	if (loader_archimedes_cGames[gameId].version)	// the pawn did not have a dictionary file.
342 	{
343 		// the dict is stored in F7, it is packed
344 		dictsize=loader_common_unhuffer(&dskimg[offsets[F7DICT]],lengths[F7DICT],&magbuf[magidx]);
345 		magidx+=dictsize;
346 	} else {
347 		dictsize=0;
348 	}
349 
350 	loader_common_addmagheader(magbuf,magidx,loader_archimedes_cGames[gameId].version,codesize,string1size,string2size,dictsize,-1);
351 
352 	*magsize=magidx;
353 
354 	return LOADER_OK;
355 }
356 // the archimedes basically uses the same graphic format as the Amiga and the Atari.
357 // all that is needed is to find the offsets to the pictures.
loader_archimedes_mkgfx(unsigned char * dskimg,int dsksize,unsigned char * gfxbuf,int * gfxsize,int gameId,int * offsets,int * lengths)358 int loader_archimedes_mkgfx(unsigned char *dskimg,int dsksize,unsigned char* gfxbuf,int* gfxsize,
359 		int gameId,int* offsets,int *lengths)
360 {
361 
362 	int gfxcnt;
363 	int idx;
364 	// TODO: since the memory is shared, make sure that offsets[10]>260!!!
365 	gfxbuf[0]='M';gfxbuf[1]='a';gfxbuf[2]='P';gfxbuf[3]='i';
366 	WRITE_INT32BE(gfxbuf,4,*gfxsize);
367 	idx=offsets[F10PICTURES];
368 	gfxcnt=0;
369 	while (idx<(offsets[F10PICTURES]+lengths[F10PICTURES]-48))
370 	{
371 		// i do not know where the index for the pictures is.
372 		// their format is as followed: there is a 48 byte header. then comes the picture data.
373 		// luckily, within this header, at position 0x10, there is a magic sequece 00 00 07 77.
374 		// that is sufficient to find the end of the header, and consequently, the beginning of the picture data.
375 
376 		if (dskimg[idx+0x10]==0x00 && dskimg[idx+0x11]==0x00 && dskimg[idx+0x12]==0x07 && dskimg[idx+0x13]==0x77)
377 		{
378 			WRITE_INT32BE(gfxbuf,4*loader_archimedes_cGames[gameId].pictureorder[gfxcnt]+8,idx+48);
379 			gfxcnt++;
380 		}
381 		idx++;
382 	}
383 	return LOADER_OK;
384 }
385 
loader_archimedes(char * filename,char * magbuf,int * magsize,char * gfxbuf,int * gfxsize)386 int loader_archimedes(char* filename,char* magbuf,int* magsize,char* gfxbuf,int* gfxsize)
387 {
388 	FILE *f;
389 	int newgfxsize;
390 	int offsets[MAXFILENAMENUM+1]={0};
391 	int lengths[MAXFILENAMENUM+1]={0};
392 	int gameId=-1;
393 
394 
395 
396 
397 	// read the input file.
398 	// actually, use the gfx buffer for that one. the data will be read linearly, the
399 	// pictures are already in the GFX1 format. all that is needed is a header and
400 	// an index.
401 	newgfxsize=*gfxsize;
402 	f=fopen(filename,"rb");
403 	newgfxsize=fread(gfxbuf,sizeof(char),newgfxsize,f);
404 	fclose(f);
405 
406 
407 	if (loader_archimedes_findfiles((unsigned char*)gfxbuf,newgfxsize,&gameId,offsets,lengths)!=LOADER_OK)
408 	{
409 		return LOADER_NOK;
410 	}
411 
412 	// since the data is in the gfx buf, we need to generate the magbuf first.
413 	if (loader_archimedes_mkmag((unsigned char*)gfxbuf,newgfxsize,(unsigned char*)magbuf,magsize, gameId,offsets,lengths)!=LOADER_OK)
414 	{
415 		return LOADER_NOK;
416 	}
417 	// now all the relevant data from the gfxbuf has been read, it is okay to overwrite it.
418 	if (loader_archimedes_mkgfx((unsigned char*)gfxbuf,newgfxsize,(unsigned char*)gfxbuf,gfxsize, gameId,offsets,lengths)!=LOADER_OK)
419 	{
420 		return LOADER_NOK;
421 	}
422 
423 	*gfxsize=newgfxsize;
424 	return LOADER_OK;
425 }
426 
427 
428