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 "loader_atarixl.h"
37 #include "vm68k_macros.h"
38 
39 #define	LOADER_ATARIXL_OK	0
40 #define	LOADER_ATARIXL_NOK -1
41 
42 typedef struct _tGameInfo
43 {
44 	unsigned char name[22];
45 	unsigned char version;		// 0=The Pawn. 1=The Guild of Thieves. 2=Jinxter
46 	unsigned int offs_code1;	// the offset of the first code section
47 	unsigned int offs_code2;	// the offset to the second code section
48 	unsigned int offs_string1;	// the offset to the string1 section
49 	unsigned int offs_string2;	// the offset to the string2 section
50 	unsigned int offs_dict;		// the offset to the dict section
51 
52 	unsigned int offs_pictures[30];	// the offset to the pictures within the .ATR files.
53 } tGameInfo;
54 
55 #define	DISK_SIZE	133136
56 #define	DISK_OFFSETMASK	0x3ffff
57 #define	DISK1_FLAG	0x40000
58 #define	DISK2_FLAG	0x80000
59 
60 
61 // i was not able to find the proper directory.
62 // but i was able to find the code/string/dict sections through correlations and other means.
63 // this is how the following table has been created.
64 #define GAMENUM			3
65 const tGameInfo	loader_atarixl_cGameInfo[GAMENUM]={
66 	{"The Pawn",0,
67 		0x3990|DISK1_FLAG,0,0x11310|DISK1_FLAG,0x1c710|DISK1_FLAG,0,
68 		{
69 			DISK2_FLAG|0x00010,DISK2_FLAG|0x00a90,DISK2_FLAG|0x01a10,DISK2_FLAG|0x02410,
70 			DISK2_FLAG|0x1d190,DISK2_FLAG|0x03310,DISK2_FLAG|0x04110,DISK2_FLAG|0x04a10,
71 			DISK2_FLAG|0x05210,DISK2_FLAG|0x05890,DISK2_FLAG|0x1f910,DISK2_FLAG|0x06390,
72 			DISK2_FLAG|0x11310,DISK2_FLAG|0x11f10,DISK2_FLAG|0x12e90,DISK2_FLAG|0x13990,
73 
74 			DISK2_FLAG|0x14190,DISK2_FLAG|0x14f90,DISK2_FLAG|0x15790,DISK2_FLAG|0x1ef10,
75 			DISK2_FLAG|0x06f90,DISK2_FLAG|0x16510,DISK2_FLAG|0x17010,DISK2_FLAG|0x1dd90,
76 			DISK2_FLAG|0x1e510,DISK2_FLAG|0x18010,DISK2_FLAG|0x18600,DISK2_FLAG|0x18f90,
77 			DISK2_FLAG|0x19e10,DISK2_FLAG|0x1a610
78 		}
79 	},
80 	{"The Guild Of Thieves",1,
81 		0x3890|DISK1_FLAG,0x10|DISK2_FLAG,0xc010|DISK2_FLAG,0x1b110|DISK2_FLAG,0,
82 		{
83 			DISK1_FLAG|0x1b310,DISK1_FLAG|0x09690,DISK1_FLAG|0x14210,DISK2_FLAG|0x1be90,
84 			DISK1_FLAG|0x11d90,DISK1_FLAG|0x08990,DISK1_FLAG|0x10090,DISK1_FLAG|0x17490,
85 			DISK1_FLAG|0x11110,DISK1_FLAG|0x08190,DISK2_FLAG|0x1e010,DISK1_FLAG|0x0d610,
86 			DISK1_FLAG|0x0ed10,DISK1_FLAG|0x18490,DISK1_FLAG|0x1f710,DISK2_FLAG|0x1d190,
87 
88 			DISK1_FLAG|0x0c690,DISK1_FLAG|0x0e010,DISK1_FLAG|0x1ea10,DISK1_FLAG|0x16c90,
89 			DISK1_FLAG|0x0aa10,DISK1_FLAG|0x15910,DISK1_FLAG|0x0cc10,DISK1_FLAG|0x09f90,
90 			DISK1_FLAG|0x0ba10,DISK1_FLAG|0x19210,DISK1_FLAG|0x1a810,DISK1_FLAG|0x12b90,
91 			DISK1_FLAG|0x14d90,0
92 		}
93 	},
94 	{"Jinxter",2,
95 		0x3790|DISK1_FLAG,0x10|DISK2_FLAG,0xc710|DISK2_FLAG,0x1a710|DISK2_FLAG,0x6490|DISK1_FLAG,
96 		{
97 			DISK1_FLAG|0x08690,DISK1_FLAG|0x09990,DISK1_FLAG|0x0a690,DISK1_FLAG|0x0b390,
98 			DISK1_FLAG|0x0c490,                 0,                 0,DISK1_FLAG|0x0d840,
99 			DISK1_FLAG|0x0e690,                 0,DISK1_FLAG|0x0f210,DISK1_FLAG|0x10090,
100 			DISK1_FLAG|0x10f10,DISK1_FLAG|0x11d10,DISK1_FLAG|0x12c10,DISK1_FLAG|0x13910,
101 
102 			DISK1_FLAG|0x14590,DISK1_FLAG|0x15290,DISK1_FLAG|0x15d10,                 0,
103 			DISK1_FLAG|0x16d90,DISK1_FLAG|0x17f90,DISK1_FLAG|0x18810,DISK1_FLAG|0x19590,
104 			DISK1_FLAG|0x1a410,DISK1_FLAG|0x1b910,DISK1_FLAG|0x1c510,                 0,
105 			DISK1_FLAG|0x1ce90, 0
106 		}
107 	}
108 };
109 
110 #define	BLOCKSIZE	256
111 #define	MAXPIVOT	8
112 #define	GETIDX(idx,offset,disk1offs,disk2offs) \
113 		idx=(offset); \
114 		if ((idx)&DISK1_FLAG) idx=((idx)&DISK_OFFSETMASK)+(disk1offs); \
115 		if ((idx)&DISK2_FLAG) idx=((idx)&DISK_OFFSETMASK)+(disk2offs);
116 
loader_atarixl_detectgame(unsigned char * diskbuf,int * disk1offs,int * disk2offs)117 int loader_atarixl_detectgame(unsigned char* diskbuf,int* disk1offs,int* disk2offs)
118 {
119 	int d1,d2;
120 	int found;
121 	int i;
122 	unsigned char tmp[256];
123 
124 	d1=*disk1offs;
125 	d2=*disk2offs;
126 	// the game code always starts with the same instruction 49FA FFFE.
127 	// in each game release, this is at a different postion within the
128 	// floppy disks. this can be used to determine which game it is.
129 	// since the game code has been scrambled, it needs to be descrambled first.
130 
131 	// first, assume that the first argument was also the first floppy disk
132 	found=-1;
133 	for (i=0;i<GAMENUM && found==-1;i++)
134 	{
135 		unsigned char lc;
136 		lc=0xff;
137 		loader_common_descramble(&diskbuf[d1+(DISK_OFFSETMASK&loader_atarixl_cGameInfo[i].offs_code1)],tmp,0,&lc,0);
138 		if (tmp[ 0]==0x49 && tmp[ 1]==0xfa && tmp[ 2]==0xff && tmp[ 3]==0xfe) found=i;
139 		if (tmp[2+ 0]==0x49 && tmp[2+ 1]==0xfa && tmp[2+ 2]==0xff && tmp[2+ 3]==0xfe) found=i;
140 	}
141 	if (found!=-1) return found;
142 
143 	// if this failed, try to swap the disks
144 	*disk1offs=d2;
145 	*disk2offs=d1;
146 	for (i=0;i<GAMENUM;i++)
147 	{
148 		unsigned char lc;
149 		lc=0xff;
150 		loader_common_descramble(&diskbuf[d2+(DISK_OFFSETMASK&loader_atarixl_cGameInfo[i].offs_code1)],tmp,0,&lc,0);
151 		if (tmp[ 0]==0x49 && tmp[ 1]==0xfa && tmp[ 2]==0xff && tmp[ 3]==0xfe) found=i;
152 		if (tmp[2+ 0]==0x49 && tmp[2+ 1]==0xfa && tmp[2+ 2]==0xff && tmp[2+ 3]==0xfe) found=i;
153 	}
154 	return found;
155 }
156 
loader_atarixl_mkmag(unsigned char * diskbuf,int disksize,int disk1offs,int disk2offs,unsigned char * magbuf,int * magbufsize,const tGameInfo * pGameInfo)157 int loader_atarixl_mkmag(unsigned char* diskbuf,int disksize,int disk1offs,int disk2offs,unsigned char* magbuf,int* magbufsize,const tGameInfo *pGameInfo)
158 {
159 	int magidx;
160 	int code1size;
161 	int code2size;
162 	int string1size;
163 	int string2size;
164 	int dictsize;
165 	int huffmantreeidx;
166 
167 
168 
169 	magidx=42;
170 
171 	code1size=0;
172 	code2size=0;
173 	{
174 		int idx;
175 		unsigned char lc;
176 		int n;
177 		int rle;
178 		int codeleft;
179 		int pivot;
180 
181 		codeleft=0x10000-0x100;
182 		rle=0;
183 		if (pGameInfo->version!=0)
184 		{
185 			magidx-=2;
186 			rle=1;
187 		}
188 
189 		pivot=0;
190 		lc=0xff;
191 		GETIDX(idx,pGameInfo->offs_code1,disk1offs,disk2offs);
192 		n=loader_common_descramble(&diskbuf[idx],&magbuf[magidx],pivot,&lc,rle);
193 		if (pGameInfo->version!=0)
194 		{
195 			codeleft=READ_INT16BE(magbuf,magidx);
196 		}
197 		code1size+=n;
198 		idx+=BLOCKSIZE;
199 		magidx+=n;
200 		while (codeleft>=BLOCKSIZE)
201 		{
202 			pivot=(pivot+1)%MAXPIVOT;
203 			n=loader_common_descramble(&diskbuf[idx],&magbuf[magidx],pivot,&lc,rle);
204 			codeleft-=BLOCKSIZE;
205 			idx+=BLOCKSIZE;
206 			magidx+=n;
207 			code1size+=n;
208 		}
209 		if (codeleft!=0)
210 		{
211 			codeleft=0x100-codeleft;
212 			code1size-=(codeleft+2);
213 			magidx-=codeleft;
214 		}
215 		GETIDX(idx,pGameInfo->offs_code2,disk1offs,disk2offs);
216 		codeleft=0x10000-code1size;
217 		while (codeleft>0)
218 		{
219 			pivot=(pivot+1)%MAXPIVOT;
220 			n=loader_common_descramble(&diskbuf[idx],&magbuf[magidx],pivot,&lc,0);
221 			codeleft-=BLOCKSIZE;
222 			idx+=BLOCKSIZE;
223 			magidx+=n;
224 			code2size+=n;
225 		}
226 	}
227 
228 
229 	// the strings are not scrambled. they are being copied over from the disk buffer into the .mag buffer.
230 	string1size=0;
231 	string2size=0;
232 	{
233 		int idx1;
234 		int idx2;
235 		int nxtdisk;
236 		int magidx0;
237 
238 
239 		magidx0=magidx;
240 		GETIDX(idx1,pGameInfo->offs_string1,disk1offs,disk2offs);
241 		GETIDX(idx2,pGameInfo->offs_string2,disk1offs,disk2offs);
242 		nxtdisk=disksize;
243 		if (idx1<disk1offs) nxtdisk=disk1offs;
244 		if (idx1<disk2offs) nxtdisk=disk2offs;
245 
246 		while (idx1<idx2 && idx1<nxtdisk)
247 		{
248 			magbuf[magidx++]=diskbuf[idx1++];
249 			string1size++;
250 		}
251 		// TODO: string2 is waaay too big.
252 		nxtdisk=disksize;
253 		if (idx2<disk1offs) nxtdisk=disk1offs;
254 		if (idx2<disk2offs) nxtdisk=disk2offs;
255 		while (idx2<nxtdisk)
256 		{
257 			magbuf[magidx++]=diskbuf[idx2++];
258 			string2size++;
259 		}
260 		if (pGameInfo->version==0)
261 		{
262 			huffmantreeidx=string1size;
263 		} else {
264 			int i;
265 			huffmantreeidx=0;
266 			// the huffman tree starts with the sequence 01 02 03 ?? 05.
267 
268 			for (i=magidx0;i<magidx-3 && huffmantreeidx==0;i++)
269 			{
270 				if (magbuf[i+0]==0x01 && magbuf[i+1]==0x02 && magbuf[i+2]==0x03 && magbuf[i+4]==0x05)
271 				{
272 					huffmantreeidx=i-magidx0;
273 				}
274 			}
275 		}
276 		if (string1size>=0x10000)
277 		{
278 			int x;
279 			x=string1size+string2size;
280 			string1size=0x10000;
281 			string2size=x-string1size;
282 		}
283 	}
284 
285 
286 	dictsize=0;
287 	if (pGameInfo->offs_dict!=0)
288 	{
289 		int n;
290 		int idx;
291 		int pivot;
292 		unsigned char lc;
293 		GETIDX(idx,pGameInfo->offs_dict,disk1offs,disk2offs);
294 		pivot=0;
295 		while (dictsize<8704)	// TODO: magic number
296 		{
297 			lc=0xff;
298 			n=loader_common_descramble(&diskbuf[idx],&magbuf[magidx],pivot,&lc,0);
299 
300 			magidx+=n;
301 			dictsize+=n;
302 			pivot=(pivot+1)%MAXPIVOT;
303 			idx+=BLOCKSIZE;
304 		}
305 	}
306 	loader_common_addmagheader(magbuf,magidx,pGameInfo->version,code1size+code2size,string1size,string2size,dictsize,huffmantreeidx);
307 	*magbufsize=magidx;
308 
309 
310 	return LOADER_ATARIXL_OK;
311 }
loader_atarixl_mkgfx(unsigned char * gfxbuf,int * gfxbufsize,int disk1offs,int disk2offs,const tGameInfo * pGameInfo)312 int loader_atarixl_mkgfx(unsigned char* gfxbuf,int *gfxbufsize,int disk1offs,int disk2offs,const tGameInfo* pGameInfo)
313 {
314 	int i;
315 	int idx;
316 	int gfxidx;
317 
318 	// i am lazy
319 	// just translate the pre-determined offsets into the gfx buffer index
320 	gfxidx=0;
321 
322 	gfxbuf[gfxidx++]='M';
323 	gfxbuf[gfxidx++]='a';
324 	gfxbuf[gfxidx++]='P';
325 	gfxbuf[gfxidx++]='7';
326 	for (i=0;i<30;i++)
327 	{
328 		GETIDX(idx,pGameInfo->offs_pictures[i],disk1offs,disk2offs);
329 		WRITE_INT32BE(gfxbuf,gfxidx,idx);gfxidx+=4;
330 	}
331 	// that's it
332 
333 	return LOADER_ATARIXL_OK;
334 }
loader_atarixl(char * atarixlname,char * magbuf,int * magbufsize,char * gfxbuf,int * gfxbufsize)335 int loader_atarixl(char* atarixlname,
336 	char* magbuf,int* magbufsize,
337 	char* gfxbuf,int* gfxbufsize)
338 {
339 	int disk1offs;
340 	int disk2offs;
341 	int disksize;
342 	int detectedgame;
343 	FILE *f;
344 	char* diskfile1;
345 	char* diskfile2;
346 	int i;
347 
348 	diskfile1=atarixlname;
349 	diskfile2=atarixlname;
350 	for (i=0;i<strlen(atarixlname);i++)
351 	{
352 		if (atarixlname[i]==',')
353 		{
354 			diskfile2=&atarixlname[i+1];
355 			atarixlname[i]=0;
356 		}
357 	}
358 
359 
360 	disk1offs=256;	// leave some room at the beginning for the directory
361 	disk2offs=0;
362 
363 
364 	f=fopen(diskfile1,"rb");
365 	if (!f)
366 	{
367 		printf("unable to open [%s]. sorry\n",diskfile1);
368 		return LOADER_ATARIXL_NOK;
369 	}
370 	disksize=fread(&gfxbuf[disk1offs],sizeof(char),*gfxbufsize-disk1offs,f);
371 	if (disksize!=DISK_SIZE)
372 	{
373 		printf("[%s] does not look like an .ATR image. Sorry\n",diskfile1);
374 		return LOADER_ATARIXL_NOK;
375 	}
376 	fclose(f);
377 	disk2offs=disk1offs+disksize;
378 
379 	f=fopen(diskfile2,"rb");
380 	if (!f)
381 	{
382 		printf("unable to open [%s]. sorry\n",diskfile2);
383 		return LOADER_ATARIXL_NOK;
384 	}
385 	disksize=fread(&gfxbuf[disk2offs],sizeof(char),*gfxbufsize-disk2offs,f);
386 	fclose(f);
387 	if (disksize!=DISK_SIZE)
388 	{
389 		printf("[%s] does not look like an .ATR image. Sorry\n",diskfile2);
390 		return LOADER_ATARIXL_NOK;
391 	}
392 	*gfxbufsize=disksize+disk2offs;
393 
394 	////////////////////
395 	detectedgame=loader_atarixl_detectgame((unsigned char*)gfxbuf,&disk1offs,&disk2offs);
396 	if (detectedgame==-1)
397 	{
398 		printf("unable to detect the game. Sorry!\n");
399 		return LOADER_ATARIXL_NOK;
400 	}
401 	printf("Detected [%s]\n",loader_atarixl_cGameInfo[detectedgame].name);
402 
403 
404 	////////////////////
405 	if (loader_atarixl_mkmag((unsigned char*)gfxbuf,*gfxbufsize,disk1offs,disk2offs,(unsigned char*)magbuf,magbufsize,&loader_atarixl_cGameInfo[detectedgame])!=LOADER_ATARIXL_OK)
406 	{
407 		return LOADER_ATARIXL_NOK;
408 	}
409 
410 	if (loader_atarixl_mkgfx((unsigned char*)gfxbuf,gfxbufsize,disk1offs,disk2offs,&loader_atarixl_cGameInfo[detectedgame])!=LOADER_ATARIXL_OK)
411 	{
412 		return LOADER_ATARIXL_NOK;
413 	}
414 
415 
416 
417 	return LOADER_ATARIXL_OK;
418 }
419 
420 
421