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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
|
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "common/algorithm.h"
#include "common/zlib.h"
#include "common/memstream.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Put integer and boolean read functions in here?
*/
/* Assorted definitions and constants. */
static const sc_uint TAF_MAGIC = 0x5bdcfa41;
enum {
VERSION_HEADER_SIZE = 14,
V400_HEADER_EXTRA = 8
};
enum {
OUT_BUFFER_SIZE = 31744,
IN_BUFFER_SIZE = 16384,
GROW_INCREMENT = 8
};
static const sc_char NEWLINE = '\n';
static const sc_char CARRIAGE_RETURN = '\r';
static const sc_char NUL = '\0';
/* Version 4.0, version 3.9, and version 3.8 TAF file signatures. */
static const sc_byte
V400_SIGNATURE[VERSION_HEADER_SIZE] = {0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87,
0xc2, 0xcf, 0x93, 0x45, 0x3e, 0x61,
0x39, 0xfa
};
static const sc_byte
V390_SIGNATURE[VERSION_HEADER_SIZE] = {0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87,
0xc2, 0xcf, 0x94, 0x45, 0x37, 0x61,
0x39, 0xfa
};
static const sc_byte
V380_SIGNATURE[VERSION_HEADER_SIZE] = {0x3c, 0x42, 0x3f, 0xc9, 0x6a, 0x87,
0xc2, 0xcf, 0x94, 0x45, 0x36, 0x61,
0x39, 0xfa
};
/*
* Game TAF data structure. The game structure contains the original TAF
* file header, a growable array of "slab" descriptors, each of which holds
* metadata for a "slab" (around a decompression buffer full of TAF strings),
* the length of the descriptor array and elements allocated, and a current
* location for iteration.
*
* Saved game files (.TAS) are just like TAF files except that they lack the
* header. So for files of this type, the header is all zeroes.
*/
struct sc_slabdesc_t {
sc_byte *data;
sc_int size;
};
typedef sc_slabdesc_t *sc_slabdescref_t;
struct sc_taf_s {
sc_uint magic;
sc_byte header[VERSION_HEADER_SIZE + V400_HEADER_EXTRA];
sc_int version;
sc_int total_in_bytes;
sc_slabdescref_t slabs;
sc_int slab_count;
sc_int slabs_allocated;
sc_bool is_unterminated;
sc_int current_slab;
sc_int current_offset;
};
typedef sc_taf_s sc_taf_t;
/* Microsoft Visual Basic PRNG magic numbers, initial and current state. */
static const sc_int PRNG_CST1 = 0x43fd43fd,
PRNG_CST2 = 0x00c39ec3,
PRNG_CST3 = 0x00ffffff,
PRNG_INITIAL_STATE = 0x00a09e86;
static sc_int taf_random_state = 0x00a09e86;
/*
* taf_random()
* taf_random_reset()
*
* Version 3.9 and version 3.8 games are obfuscated by xor'ing each character
* with the PRNG in Visual Basic. So here we have to emulate that, to unob-
* fuscate data from such game files. The PRNG generates 0..prng_cst3, which
* we multiply by 255 and then divide by prng_cst3 + 1 to get output in the
* range 0..254. Thanks to Rik Snel for uncovering this obfuscation.
*/
static sc_byte taf_random(void) {
/* Generate and return the next pseudo-random number. */
taf_random_state = (taf_random_state * PRNG_CST1 + PRNG_CST2) & PRNG_CST3;
return (BYTE_MAX * (sc_uint) taf_random_state) / (sc_uint)(PRNG_CST3 + 1);
}
static void taf_random_reset(void) {
/* Reset PRNG to initial conditions. */
taf_random_state = PRNG_INITIAL_STATE;
}
/*
* taf_is_valid()
*
* Return TRUE if pointer is a valid TAF structure, FALSE otherwise.
*/
static sc_bool taf_is_valid(sc_tafref_t taf) {
return taf && taf->magic == TAF_MAGIC;
}
/*
* taf_create_empty()
*
* Allocate and return a new, empty TAF structure.
*/
static sc_tafref_t taf_create_empty(void) {
sc_tafref_t taf;
/* Create an empty TAF structure. */
taf = (sc_tafref_t)sc_malloc(sizeof(*taf));
taf->magic = TAF_MAGIC;
memset(taf->header, 0, sizeof(taf->header));
taf->version = TAF_VERSION_NONE;
taf->total_in_bytes = 0;
taf->slabs = NULL;
taf->slab_count = 0;
taf->slabs_allocated = 0;
taf->is_unterminated = FALSE;
taf->current_slab = 0;
taf->current_offset = 0;
/* Return the new TAF structure. */
return taf;
}
/*
* taf_destroy()
*
* Free TAF memory, and destroy a TAF structure.
*/
void taf_destroy(sc_tafref_t taf) {
sc_int index_;
assert(taf_is_valid(taf));
/* First free each slab in the slabs array,... */
for (index_ = 0; index_ < taf->slab_count; index_++)
sc_free(taf->slabs[index_].data);
/*
* ...then free slabs growable array, and poison and free the TAF structure
* itself.
*/
sc_free(taf->slabs);
memset(taf, 0xaa, sizeof(*taf));
sc_free(taf);
}
/*
* taf_finalize_last_slab()
*
* Insert nul's into slab data so that it turns into a series of nul-terminated
* strings. Nul's are used to replace carriage return and newline pairs.
*/
static void taf_finalize_last_slab(sc_tafref_t taf) {
sc_slabdescref_t slab;
sc_int index_;
/* Locate the final slab in the slab descriptors array. */
assert(taf->slab_count > 0);
slab = taf->slabs + taf->slab_count - 1;
/*
* Replace carriage return and newline pairs with nuls, and individual
* carriage returns with a single newline.
*/
for (index_ = 0; index_ < slab->size; index_++) {
if (slab->data[index_] == CARRIAGE_RETURN) {
if (index_ < slab->size - 1 && slab->data[index_ + 1] == NEWLINE) {
slab->data[index_] = NUL;
slab->data[index_ + 1] = NUL;
index_++;
} else
slab->data[index_] = NEWLINE;
}
/* Also protect against unlikely incoming nul characters. */
else if (slab->data[index_] == NUL)
slab->data[index_] = NEWLINE;
}
}
/*
* taf_find_buffer_extent()
*
* Search backwards from the buffer end for a terminating carriage return and
* line feed. If none, found, return length and set is_unterminated to TRUE.
* Otherwise, return the count of usable bytes found in the buffer.
*/
static sc_int taf_find_buffer_extent(const sc_byte *buffer, sc_int length, sc_bool *is_unterminated) {
sc_int bytes;
/* Search backwards from the buffer end for the final line feed. */
for (bytes = length; bytes > 1; bytes--) {
if (buffer[bytes - 2] == CARRIAGE_RETURN && buffer[bytes - 1] == NEWLINE)
break;
}
if (bytes < 2) {
/* No carriage return and newline termination found. */
*is_unterminated = TRUE;
return length;
}
*is_unterminated = FALSE;
return bytes;
}
/*
* taf_append_buffer()
*
* Append a buffer of TAF lines to an existing TAF structure. Returns the
* number of characters consumed from the buffer.
*/
static sc_int taf_append_buffer(sc_tafref_t taf, const sc_byte *buffer, sc_int length) {
sc_int bytes;
sc_bool is_unterminated;
/* Locate the extent of appendable data in the buffer. */
bytes = taf_find_buffer_extent(buffer, length, &is_unterminated);
/* See if the last buffer handled contained at least one data line. */
if (!taf->is_unterminated) {
sc_slabdescref_t slab;
/* Extend the slabs array if we've reached the current allocation. */
if (taf->slab_count == taf->slabs_allocated) {
taf->slabs_allocated += GROW_INCREMENT;
taf->slabs = (sc_slabdescref_t)sc_realloc(taf->slabs,
taf->slabs_allocated * sizeof(*taf->slabs));
}
/* Advance to the next unused slab in the slab descriptors array. */
slab = taf->slabs + taf->slab_count;
taf->slab_count++;
/* Copy the input buffer into the new slab. */
slab->data = (sc_byte *)sc_malloc(bytes);
memcpy(slab->data, buffer, bytes);
slab->size = bytes;
} else {
sc_slabdescref_t slab;
/* Locate the final slab in the slab descriptors array. */
assert(taf->slab_count > 0);
slab = taf->slabs + taf->slab_count - 1;
/*
* The last buffer we saw had no line endings in it. In this case,
* append the input buffer to the end of the last slab's data, rather
* than creating a new slab. This may cause allocation to overflow
* the system limits on single allocated areas on some platforms.
*/
slab->data = (sc_byte *)sc_realloc(slab->data, slab->size + bytes);
memcpy(slab->data + slab->size, buffer, bytes);
slab->size += bytes;
/*
* Use a special case for the final carriage return and newline pairing
* that are split over two buffers; force correct termination of this
* slab.
*/
if (slab->size > 1
&& slab->data[slab->size - 2] == CARRIAGE_RETURN
&& slab->data[slab->size - 1] == NEWLINE)
is_unterminated = FALSE;
}
/*
* Note if this buffer requires that the next be coalesced with it. If it
* doesn't, finalize the last slab by breaking it into separate lines.
*/
taf->is_unterminated = is_unterminated;
if (!is_unterminated)
taf_finalize_last_slab(taf);
/* Return count of buffer bytes consumed. */
return bytes;
}
/*
* taf_unobfuscate()
*
* Unobfuscate a version 3.9 and version 3.8 TAF file from data read by
* repeated calls to the callback() function. Callback() should return the
* count of bytes placed in the buffer, or 0 if no more (end of file).
* Assumes that the file has been read past the header.
*/
static sc_bool taf_unobfuscate(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
sc_byte *buffer;
sc_int bytes, used_bytes, total_bytes, index_;
/* Reset the PRNG, and synchronize with the header already read. */
taf_random_reset();
for (index_ = 0; index_ < VERSION_HEADER_SIZE; index_++)
taf_random();
/*
* Malloc buffer, done to help systems with limited stacks, and initialize
* count of bytes read and used in the buffer to zero.
*/
buffer = (sc_byte *)sc_malloc(IN_BUFFER_SIZE);
used_bytes = 0;
total_bytes = 0;
/* Unobfuscate in buffer sized chunks. */
do {
/* Try to obtain more data. */
bytes = callback(opaque,
buffer + used_bytes, IN_BUFFER_SIZE - used_bytes);
/* Unobfuscate data read in. */
for (index_ = 0; index_ < bytes; index_++)
buffer[used_bytes + index_] ^= taf_random();
/*
* Add data read in and unobfuscated to buffer used data, and if
* unobfuscated data is available, add it to the TAF.
*/
used_bytes += bytes;
if (used_bytes > 0) {
sc_int consumed;
/* Add lines from this buffer to the TAF. */
consumed = taf_append_buffer(taf, buffer, used_bytes);
/* Move unused buffer data to buffer start. */
memmove(buffer, buffer + consumed, IN_BUFFER_SIZE - consumed);
/* Note counts of bytes consumed and remaining in the buffer. */
used_bytes -= consumed;
total_bytes += consumed;
}
} while (bytes > 0);
/*
* Unobfuscation completed, note the total bytes read. This value is
* actually not used for version 3.9 and version 3.8 games, but we maintain
* it just in case.
*/
taf->total_in_bytes = total_bytes;
if (is_gamefile)
taf->total_in_bytes += VERSION_HEADER_SIZE;
/* Check that we found the end of the input file as expected. */
if (used_bytes > 0) {
sc_error("taf_unobfuscate:"
" warning: %ld unhandled bytes in the buffer\n", used_bytes);
}
if (taf->is_unterminated)
sc_fatal("taf_unobfuscate: unterminated final data slab\n");
/* Return successfully. */
sc_free(buffer);
return TRUE;
}
#define BUFFER_SIZE 16384
/*
* taf_decompress()
*
* Decompress a version 4.0 TAF
*/
static sc_bool taf_decompress(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
#if defined(USE_ZLIB)
Common::SeekableReadStream *src = (Common::SeekableReadStream *)opaque;
assert(src);
Common::MemoryWriteStreamDynamic dest(DisposeAfterUse::YES);
size_t startingPos = src->pos();
if (!Common::inflateZlibHeaderless(&dest, src))
return false;
// Iterate through pushing data out to the taf file
const byte *pTemp = dest.getData();
int bytesRemaining = dest.size();
while (bytesRemaining > 0) {
int consumed = taf_append_buffer(taf, pTemp, bytesRemaining);
bytesRemaining -= consumed;
}
taf->total_in_bytes = src->pos() - startingPos;
return true;
#else
return false;
#endif
}
/*
* taf_read_raw()
*
* Read an uncompressed version 4.0 TAF save chunk used by ScummVM
*/
static sc_bool taf_read_raw(sc_tafref_t taf, sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
byte *buffer = new byte[BUFFER_SIZE];
size_t bytesRead, bytesLeft = 0;
size_t totalBytes, bytesWritten;
for (;;) {
bytesRead = callback(opaque, buffer + bytesLeft, BUFFER_SIZE - bytesLeft);
if ((bytesLeft + bytesRead) == 0)
break;
totalBytes = bytesLeft + bytesRead;
bytesWritten = taf_append_buffer(taf, buffer, totalBytes);
bytesLeft = totalBytes - bytesWritten;
if (bytesLeft)
Common::copy(buffer + bytesWritten, buffer + totalBytes, buffer);
}
delete[] buffer;
return true;
}
/*
* taf_create_from_callback()
*
* Create a TAF structure from data read in by repeated calls to the
* callback() function. Callback() should return the count of bytes placed
* in the buffer, or 0 if no more (end of file).
*/
static sc_tafref_t taf_create_from_callback(sc_read_callbackref_t callback,
void *opaque, sc_bool is_gamefile) {
sc_tafref_t taf;
sc_bool status = FALSE;
assert(callback);
/* Create an empty TAF structure. */
taf = taf_create_empty();
/*
* Determine the TAF file version in use. For saved games, we always use
* version 4.0 format. For others, it's determined from the header.
*/
if (is_gamefile) {
sc_int in_bytes;
/*
* Read in the ADRIFT header for game files. Start by reading in the
* shorter header common to all.
*/
in_bytes = callback(opaque, taf->header, VERSION_HEADER_SIZE);
if (in_bytes != VERSION_HEADER_SIZE) {
sc_error("taf_create: not enough data for standard TAF header\n");
taf_destroy(taf);
return NULL;
}
/*
* Compare the header with the known TAF signatures, and set TAF version
* appropriately.
*/
if (memcmp(taf->header, V400_SIGNATURE, VERSION_HEADER_SIZE) == 0) {
/* Read in the version 4.0 header extension. */
in_bytes = callback(opaque,
taf->header + VERSION_HEADER_SIZE,
V400_HEADER_EXTRA);
if (in_bytes != V400_HEADER_EXTRA) {
sc_error("taf_create:"
" not enough data for extended TAF header\n");
taf_destroy(taf);
return NULL;
}
taf->version = TAF_VERSION_400;
} else if (memcmp(taf->header, V390_SIGNATURE, VERSION_HEADER_SIZE) == 0)
taf->version = TAF_VERSION_390;
else if (memcmp(taf->header, V380_SIGNATURE, VERSION_HEADER_SIZE) == 0)
taf->version = TAF_VERSION_380;
else {
taf_destroy(taf);
return NULL;
}
} else {
/* Saved games are always considered to be for ScummVM, version 5.0. */
taf->version = TAF_VERSION_500;
}
/*
* Call the appropriate game file reader function. For version 4.0 games,
* data is compressed with Zlib. For version 3.9 and version 3.8 games,
* it's obfuscated with the Visual Basic PRNG.
*/
switch (taf->version) {
case TAF_VERSION_500:
status = taf_read_raw(taf, callback, opaque, is_gamefile);
break;
case TAF_VERSION_400:
status = taf_decompress(taf, callback, opaque, is_gamefile);
break;
case TAF_VERSION_390:
case TAF_VERSION_380:
status = taf_unobfuscate(taf, callback, opaque, is_gamefile);
break;
default:
sc_fatal("taf_create: invalid version\n");
}
if (!status) {
taf_destroy(taf);
return NULL;
}
/* Return successfully. */
return taf;
}
/*
* taf_create()
* taf_create_tas()
*
* Public entry points for taf_create_from_callback(). Return a taf object
* constructed from either *.TAF (game) or *.TAS (saved game state) file data.
*/
sc_tafref_t taf_create(sc_read_callbackref_t callback, void *opaque) {
return taf_create_from_callback(callback, opaque, TRUE);
}
sc_tafref_t taf_create_tas(sc_read_callbackref_t callback, void *opaque) {
return taf_create_from_callback(callback, opaque, FALSE);
}
/*
* taf_first_line()
*
* Iterator rewind function, reset current slab location to TAF data start.
*/
void taf_first_line(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* Set current locations to TAF start. */
taf->current_slab = 0;
taf->current_offset = 0;
}
/*
* taf_next_line()
*
* Iterator function, return the next line of data from a TAF, or NULL
* if no more lines.
*/
const sc_char *taf_next_line(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* If there is a next line, return it and advance current. */
if (taf->current_slab < taf->slab_count) {
sc_char *line;
/* Get the effective address of the current line. */
line = (sc_char *) taf->slabs[taf->current_slab].data;
line += taf->current_offset;
/*
* Advance to the next line. The + 2 skips the NULs used to replace the
* carriage return and line feed.
*/
taf->current_offset += strlen(line) + 2;
if (taf->current_offset >= taf->slabs[taf->current_slab].size) {
taf->current_slab++;
taf->current_offset = 0;
}
return line;
}
/* No more lines, so return NULL. */
return NULL;
}
/*
* taf_more_lines()
*
* Iterator end function, returns TRUE if more TAF lines are readable.
*/
sc_bool taf_more_lines(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/* Return TRUE if not at TAF data end. */
return taf->current_slab < taf->slab_count;
}
/*
* taf_get_game_data_length()
*
* Returns the number of bytes read to decompress the game. Resources are
* appended to the TAF file after the game, so this value allows them to
* be located.
*/
sc_int taf_get_game_data_length(sc_tafref_t taf) {
assert(taf_is_valid(taf));
/*
* Return the count of bytes inflated; this includes the TAF header length
* for TAF, rather than TAS, files. For TAS files, the count of file bytes
* read is irrelevant, and is never used.
*/
return taf->total_in_bytes;
}
/*
* taf_get_version()
*
* Return the version number of the TAF file, 400, 390, or 380.
*/
sc_int taf_get_version(sc_tafref_t taf) {
assert(taf_is_valid(taf));
assert(taf->version != TAF_VERSION_NONE);
return taf->version;
}
/*
* taf_debug_is_taf_string()
* taf_debug_dump()
*
* Print out a complete TAF structure. The first function is a helper for
* properties debugging, indicating if a given address is a string in a TAF
* slab, and therefore safe to print.
*/
sc_bool taf_debug_is_taf_string(sc_tafref_t taf, const void *addr) {
const sc_byte *const addr_ = (const sc_byte * const)addr;
sc_int index_;
/*
* Compare pointer, by address directly, against all memory contained in
* the TAF slabs. Return TRUE if in range.
*/
for (index_ = 0; index_ < taf->slab_count; index_++) {
if (addr_ >= taf->slabs[index_].data
&& addr_ < taf->slabs[index_].data + taf->slabs[index_].size)
return TRUE;
}
return FALSE;
}
void taf_debug_dump(sc_tafref_t taf) {
sc_int index_, current_slab, current_offset;
assert(taf_is_valid(taf));
/* Dump complete structure. */
sc_trace("TAFfile: debug dump follows...\n");
sc_trace("taf->header =");
for (index_ = 0; index_ < (sc_int) sizeof(taf->header); index_++)
sc_trace(" %02x", taf->header[index_]);
sc_trace("\n");
sc_trace("taf->version = %s\n",
taf->version == TAF_VERSION_400 ? "4.00" :
taf->version == TAF_VERSION_390 ? "3.90" :
taf->version == TAF_VERSION_380 ? "3.80" : "[Unknown]");
sc_trace("taf->slabs = \n");
for (index_ = 0; index_ < taf->slab_count; index_++) {
sc_trace("%3ld : %p, %ld bytes\n", index_,
taf->slabs[index_].data, taf->slabs[index_].size);
}
sc_trace("taf->slab_count = %ld\n", taf->slab_count);
sc_trace("taf->slabs_allocated = %ld\n", taf->slabs_allocated);
sc_trace("taf->current_slab = %ld\n", taf->current_slab);
sc_trace("taf->current_offset = %ld\n", taf->current_offset);
/* Save current location. */
current_slab = taf->current_slab;
current_offset = taf->current_offset;
/* Print out taf lines using taf iterators. */
sc_trace("\ntaf iterators:\n");
taf_first_line(taf);
for (index_ = 0; taf_more_lines(taf); index_++)
sc_trace("%5ld %s\n", index_, taf_next_line(taf));
/* Restore current location. */
taf->current_slab = current_slab;
taf->current_offset = current_offset;
}
} // End of namespace Adrift
} // End of namespace Glk
|