1 /* ======================================================================== */
2 /*  DEMO to ASM conversion                                      J. Zbiciak  */
3 /*                                                                          */
4 /*  This reads a .dmo file recorded by jzIntv, and produces an assembly     */
5 /*  file intended for a demo player running on an Intellivision.  The goal  */
6 /*  here is to make "demo carts" from existing games that do not contain    */
7 /*  the games themselves.  This theoretically should simplify the process   */
8 /*  of making demo carts.                                                   */
9 /*                                                                          */
10 /*  Since we try to optimize the encoded demo, the process becomes very     */
11 /*  memory intensive.  We suck the entire recorded demo into memory,        */
12 /*  recreating the entire BTAB, STIC and PSG state for each frame.  On      */
13 /*  the commandline, the user can specify which frame ranges should get     */
14 /*  written to the resulting assembly file.                                 */
15 /*                                                                          */
16 /*  Ultimately, this tool should be able to optimize across multiple demo   */
17 /*  recordings.  Pipe dreams... pipe dreams.                                */
18 /*                                                                          */
19 /*  The assembly format output by this converter will evolve alongside the  */
20 /*  player.  Make sure that the player and the converter are matched!       */
21 /* ======================================================================== */
22 
23 #include "config.h"
24 
25 /* ======================================================================== */
26 /*  Utility functions for pulling apart frames.                             */
27 /* ======================================================================== */
get_32(uint8_t ** buf)28 static inline uint32_t get_32(uint8_t **buf)
29 {
30     uint32_t word;
31 
32     word = ((*buf)[3] << 24) |
33            ((*buf)[2] << 16) |
34            ((*buf)[1] <<  8) |
35            ((*buf)[0] <<  0);
36 
37     *buf += 4;
38 
39     return word;
40 }
41 
get_16(uint8_t ** buf)42 static inline uint16_t get_16(uint8_t **buf)
43 {
44     uint16_t word;
45 
46     word = ((*buf)[1] <<  8) |
47            ((*buf)[0] <<  0);
48 
49     *buf += 2;
50 
51     return word;
52 }
53 
get_8(uint8_t ** buf)54 static inline uint8_t get_8(uint8_t **buf)
55 {
56     return *(*buf)++;
57 }
58 
59 #define GET_32(buf) get_32(&buf)
60 #define GET_16(buf) get_16(&buf)
61 #define GET_8(buf)  get_8 (&buf)
62 
63 /* ======================================================================== */
64 /*  Frame format:                                                           */
65 /*                                                                          */
66 /*      4 bytes     0x2A3A4A5A  Frame header                                */
67 /*      4 bytes     Bitmap of changed STIC registers                        */
68 /*      8 bytes     Bitmap of changed GRAM cards                            */
69 /*      30 bytes    Bitmap of changed BTAB cards                            */
70 /*      2 bytes     Bitmap of changed PSG0 registers                        */
71 /*      2 bytes     Bitmap of changed PSG1 registers                        */
72 /*      N bytes     STIC register values (2 bytes each)                     */
73 /*      N bytes     GRAM tiles (8 bytes each)                               */
74 /*      N bytes     BTAB cards (2 bytes each)                               */
75 /*      N bytes     PSG0 registers (1 byte each)                            */
76 /*      N bytes     PSG1 registers (1 byte each)                            */
77 /*                                                                          */
78 /* ======================================================================== */
79 
80 #define HDR_SZ (4+4+8+30+2+2)
81 
82 typedef struct frame_t
83 {
84     struct frame_t *next, *prev;
85 
86     uint32_t stic_chg;
87     uint32_t gram_chg[2];
88     uint32_t btab_chg[8];
89     uint32_t psg0_chg;
90     uint32_t psg1_chg;
91 
92     uint16_t stic[32 ];
93     uint16_t gram[64 ];  // tile_db indices, not images.
94     uint16_t btab[240];
95     uint8_t  psg0[14 ];
96     uint8_t  psg1[14 ];
97 } frame_t;
98 
99 /* ======================================================================== */
100 /*  TILE_DB          -- We keep a database of all GRAM images we've seen.   */
101 /*                      Rather than store each GRAM bitmap explicitly, we   */
102 /*                      store a database index.  The GRAM_HASH maintains    */
103 /*                      this database.                                      */
104 /* ======================================================================== */
105 
106 typedef struct tile_t
107 {
108     uint8_t tile[8];
109     struct tile_t *hs_next; /* chained hashing.                             */
110 } tile_t;
111 
112 #define MAX_TILES (4096)    /* 16Kwords worth of tiles.                     */
113 #define HASH_SZ   (139)
114 
115 tile_t *tile_hash[HASH_SZ];
116 tile_t  tile_db[MAX_TILES];
117 int num_tiles = 0;
118 
tile_to_id(uint8_t * tile)119 int tile_to_id(uint8_t *tile)
120 {
121     uint32_t r0, r1;
122     int hash;
123     int i;
124     tile_t *t;
125 
126     r0 = tile[0] ^ (tile[1] << 7) ^ (tile[2] << 14) ^ (tile[3] << 21);
127     r1 = tile[4] ^ (tile[5] << 7) ^ (tile[6] << 14) ^ (tile[7] << 21);
128 
129     hash = (r0 * 33 + r1 * 31) % HASH_SZ;
130 
131     t = tile_hash[hash];
132 
133     while (t)
134     {
135         if (!memcmp(tile, t->tile, 8)) // match?
136             return t - tile_db;
137 
138         t = t->hs_next;
139     }
140 
141     if (num_tiles == MAX_TILES)
142     {
143         fprintf(stderr, "GRAM tile database overflow!\n");
144         exit(1);
145     }
146 
147     t               = &tile_db[num_tiles++];
148     t->hs_next      = tile_hash[hash];
149     tile_hash[hash] = t;
150 
151     memcpy(t->tile, tile, 8);
152 
153     return t - tile_db;
154 }
155 
156 /* ======================================================================== */
157 /*  For now let's start small.  Read in the demo file, populate a bunch     */
158 /*  of frames, and see if we can, sloppily, play back GRAM, BTAB and STIC.  */
159 /* ======================================================================== */
160 
161 int gr_chg_hist[64];
162 int bt_chg_hist[240];
163 
164 
165 /* ======================================================================== */
166 /*  READ_DEMO_FILE   -- Load up all the frames from a demo file and         */
167 /*                      decompress them into our linked list structure.     */
168 /*                                                                          */
169 /*  This isn't elegant code at all.  It's the worst sort of case-n-paste.   */
170 /* ======================================================================== */
read_demo_file(char * fname)171 frame_t *read_demo_file(char *fname)
172 {
173     frame_t *head = NULL;
174     frame_t *curr = NULL;
175     frame_t *prev = NULL;
176     FILE *f;
177 
178     uint8_t hdr[HDR_SZ], *buf;
179     uint8_t stic_tmp[32*2];
180     uint8_t gram_tmp[512];
181     uint8_t btab_tmp[240*2];
182     uint8_t psg0_tmp[14];
183     uint8_t psg1_tmp[14];
184     uint32_t sig;
185     int r;
186     int i, j;
187     int stic_cnt, gram_cnt, btab_cnt, psg0_cnt, psg1_cnt;
188     int frame_no = 0;
189 
190     /* -------------------------------------------------------------------- */
191     /*  Open up the file and prepare to parse!                              */
192     /* -------------------------------------------------------------------- */
193     if (!(f = fopen(fname, "rb")))
194     {
195         perror("fopen()");
196         fprintf(stderr, "Could not open '%s' for reading\n", fname);
197         exit(1);
198     }
199 
200     while ((r = fread(hdr, 1, HDR_SZ, f)) == HDR_SZ)
201     {
202         /* ---------------------------------------------------------------- */
203         /*  Allocate structure and prepare to read the header.              */
204         /* ---------------------------------------------------------------- */
205         curr = calloc(sizeof(frame_t), 1);
206         if (!curr)
207         {
208             perror("calloc()");
209             fprintf(stderr, "Out of memory in read_demo_file\n");
210             exit(1);
211         }
212 
213         curr->prev = prev;
214         if (prev)
215             prev->next = curr;
216         if (!head)
217             head = curr;
218 
219         buf = hdr;
220 
221         sig = GET_32(buf);
222 
223         if (sig != 0x2A3A4A5A)
224         {
225             fprintf(stderr,
226                     "Expected frame signature, got %.8X instead\n"
227                     "File offset:  %llu\n"
228                     "Frame number: %d\n",
229                     sig, (uint64_t)ftell(f), frame_no);
230             exit(1);
231         }
232 
233         /* ---------------------------------------------------------------- */
234         /*      4 bytes     Bitmap of changed STIC registers                */
235         /*      8 bytes     Bitmap of changed GRAM cards                    */
236         /*      30 bytes    Bitmap of changed BTAB cards                    */
237         /*      2 bytes     Bitmap of changed PSG0 registers                */
238         /*      2 bytes     Bitmap of changed PSG1 registers                */
239         /* ---------------------------------------------------------------- */
240         curr->stic_chg    = GET_32(buf);
241         curr->gram_chg[0] = GET_32(buf);
242         curr->gram_chg[1] = GET_32(buf);
243         curr->btab_chg[0] = GET_32(buf);
244         curr->btab_chg[1] = GET_32(buf);
245         curr->btab_chg[2] = GET_32(buf);
246         curr->btab_chg[3] = GET_32(buf);
247         curr->btab_chg[4] = GET_32(buf);
248         curr->btab_chg[5] = GET_32(buf);
249         curr->btab_chg[6] = GET_32(buf);
250         curr->btab_chg[7] = GET_16(buf); /* note GET_16... */
251         curr->psg0_chg    = GET_16(buf);
252         curr->psg1_chg    = GET_16(buf);
253 
254 
255         /* ---------------------------------------------------------------- */
256         /*  Now read all the elements, if they were sent.                   */
257         /* ---------------------------------------------------------------- */
258         stic_cnt = gram_cnt = btab_cnt = psg0_cnt = psg1_cnt = 0;
259 
260         for (i = 0; i < 32; i++)
261         {
262             if ((curr->stic_chg    >> i) & 1) stic_cnt++;
263             if ((curr->gram_chg[0] >> i) & 1) gram_cnt++;
264             if ((curr->gram_chg[1] >> i) & 1) gram_cnt++;
265             if ((curr->btab_chg[0] >> i) & 1) btab_cnt++;
266             if ((curr->btab_chg[1] >> i) & 1) btab_cnt++;
267             if ((curr->btab_chg[2] >> i) & 1) btab_cnt++;
268             if ((curr->btab_chg[3] >> i) & 1) btab_cnt++;
269             if ((curr->btab_chg[4] >> i) & 1) btab_cnt++;
270             if ((curr->btab_chg[5] >> i) & 1) btab_cnt++;
271             if ((curr->btab_chg[6] >> i) & 1) btab_cnt++;
272             if ((curr->btab_chg[7] >> i) & 1) btab_cnt++;
273             if ((curr->psg0_chg    >> i) & 1) psg0_cnt++;
274             if ((curr->psg1_chg    >> i) & 1) psg1_cnt++;
275         }
276 
277         if (stic_cnt &&
278             (r = fread(stic_tmp, 2, stic_cnt, f)) != stic_cnt)
279         {
280             fprintf(stderr,
281                     "Short read getting STIC regs from frame.\n"
282                     "File offset:  %llu\n", (uint64_t)ftell(f));
283             exit(1);
284         }
285 
286         if (gram_cnt &&
287             (r = fread(gram_tmp, 8, gram_cnt, f)) != gram_cnt)
288         {
289             fprintf(stderr,
290                     "Short read getting GRAM tiles from frame.\n"
291                     "File offset:  %llu\n", (uint64_t)ftell(f));
292             exit(1);
293         }
294 
295         if (btab_cnt &&
296             (r = fread(btab_tmp, 2, btab_cnt, f)) != btab_cnt)
297         {
298             fprintf(stderr,
299                     "Short read getting BTAB cards from frame.\n"
300                     "File offset:  %llu\n", (uint64_t)ftell(f));
301             exit(1);
302         }
303 
304         if (psg0_cnt &&
305             (r = fread(psg0_tmp, 1, psg0_cnt, f)) != psg0_cnt)
306         {
307             fprintf(stderr,
308                     "Short read getting PSG0 registers from frame.\n"
309                     "File offset:  %llu\n", (uint64_t)ftell(f));
310             exit(1);
311         }
312 
313         if (psg1_cnt &&
314             (r = fread(psg1_tmp, 1, psg1_cnt, f)) != psg1_cnt)
315         {
316             fprintf(stderr,
317                     "Short read getting PSG1 registers from frame.\n"
318                     "File offset:  %llu\n", (uint64_t)ftell(f));
319             exit(1);
320         }
321 
322         /* ---------------------------------------------------------------- */
323         /*  Unpack all the elements into the frame structure.               */
324         /* ---------------------------------------------------------------- */
325         if (prev)
326         {
327             memcpy(curr->stic, prev->stic, sizeof(curr->stic));
328             memcpy(curr->gram, prev->gram, sizeof(curr->gram));
329             memcpy(curr->btab, prev->btab, sizeof(curr->btab));
330             memcpy(curr->psg0, prev->psg0, sizeof(curr->psg0));
331             memcpy(curr->psg1, prev->psg1, sizeof(curr->psg1));
332         }
333 
334         buf = stic_tmp;
335         for (i = 0; i < 32; i++)
336             if (1 & (curr->stic_chg >> i))
337                 curr->stic[i] = GET_16(buf);
338 
339         buf = gram_tmp;
340         for (i = 0; i < 64; i++)
341             if (1 & (curr->gram_chg[i >> 5] >> (i & 31)))
342             {
343                 curr->gram[i] = tile_to_id(buf);
344                 buf += 8;
345             }
346 
347         buf = btab_tmp;
348         for (i = 0; i < 240; i++)
349             if (1 & (curr->btab_chg[i >> 5] >> (i & 31)))
350                 curr->btab[i] = GET_16(buf);
351 
352         buf = psg0_tmp;
353         for (i = 0; i < 14; i++)
354             if (1 & (curr->psg0_chg >> i))
355                 curr->psg0[i] = GET_8(buf);
356 
357         buf = psg1_tmp;
358         for (i = 0; i < 14; i++)
359             if (1 & (curr->psg1_chg >> i))
360                 curr->psg1[i] = GET_8(buf);
361 
362         prev = curr;
363         frame_no++;
364 
365         /* ---------------------------------------------------------------- */
366         /*  Stats keeping.                                                  */
367         /* ---------------------------------------------------------------- */
368         gr_chg_hist[gram_cnt]++;
369         bt_chg_hist[btab_cnt]++;
370     }
371 
372     /* -------------------------------------------------------------------- */
373     /*  And that's it.  Seriously!                                          */
374     /* -------------------------------------------------------------------- */
375     return head;
376 }
377 
378 
main(int argc,char * argv[])379 main(int argc, char *argv[])
380 {
381     int i, j, k;
382 
383     read_demo_file(argv[1]);
384     printf("did it!\n");
385     printf("%d unique GRAM tiles\n", num_tiles);
386 
387     for (i = 0; i < num_tiles; i++)
388     {
389         printf("tile %d\n", i);
390         for (j = 0; j < 8; j++)
391         {
392             for (k = 0; k < 8; k++)
393                 putchar((tile_db[i].tile[j] << k) & 0x80 ? '#': '.');
394             putchar('\n');
395         }
396     }
397 
398     for (i = 0; i < 64; i++)
399         printf("%4d", gr_chg_hist[i]);
400 
401     return 0;
402 }
403