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