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