File Coverage

blib/lib/Games/Rezrov/ZHeader.pm
Criterion Covered Total %
statement 173 225 76.8
branch 11 50 22.0
condition 3 15 20.0
subroutine 43 48 89.5
pod 0 7 0.0
total 230 345 66.6


line stmt bran cond sub pod time code
1             # ZHeader: version-specific information and settings
2             # for each game
3              
4             package Games::Rezrov::ZHeader;
5              
6 1     1   467 use Games::Rezrov::ZConst;
  1         3  
  1         30  
7 1     1   786 use Games::Rezrov::Inliner;
  1         2  
  1         27  
8              
9 1     1   5 use strict;
  1         1  
  1         34  
10              
11 1     1   5 use constant FLAGS_1 => 0x01; # one byte
  1         2  
  1         47  
12 1     1   5 use constant FLAGS_2 => 0x10; # TWO BYTES
  1         1  
  1         42  
13             # location of various flags in the header
14              
15             # see spec section 11:
16 1     1   5 use constant RELEASE_NUMBER => 0x02;
  1         2  
  1         42  
17 1     1   5 use constant PAGED_MEMORY_ADDRESS => 0x04;
  1         1  
  1         41  
18 1     1   5 use constant FIRST_INSTRUCTION_ADDRESS => 0x06;
  1         1  
  1         54  
19 1     1   5 use constant DICTIONARY_ADDRESS => 0x08;
  1         2  
  1         39  
20 1     1   5 use constant OBJECT_TABLE_ADDRESS => 0x0a;
  1         2  
  1         43  
21 1     1   12 use constant GLOBAL_VARIABLE_ADDRESS => 0x0c;
  1         2  
  1         49  
22 1     1   5 use constant STATIC_MEMORY_ADDRESS => 0x0e;
  1         2  
  1         36  
23 1     1   4 use constant SERIAL_CODE => 0x12;
  1         2  
  1         41  
24 1     1   5 use constant ABBREV_TABLE_ADDRESS => 0x18;
  1         2  
  1         37  
25 1     1   5 use constant FILE_LENGTH => 0x1a;
  1         16  
  1         48  
26 1     1   6 use constant CHECKSUM => 0x1c;
  1         2  
  1         43  
27              
28 1     1   5 use constant STATUS_NOT_AVAILABLE => 0x10; # bit 4 (#5)
  1         2  
  1         39  
29 1     1   5 use constant SCREEN_SPLITTING_AVAILABLE => 0x20; # bit 5 (#6)
  1         1  
  1         55  
30              
31             # Flags 1
32 1     1   5 use constant TANDY => 0x08;
  1         1  
  1         44  
33              
34             # Flags 2
35 1     1   5 use constant TRANSCRIPT_ON => 0x01; # bit 0
  1         2  
  1         36  
36 1     1   5 use constant FORCE_FIXED => 0x02; # bit 1
  1         1  
  1         41  
37 1     1   5 use constant REQUEST_STATUS_REDRAW => 0x04; # bit 2
  1         2  
  1         42  
38              
39             # Flags 2, v5+:
40 1     1   5 use constant WANTS_PICTURES => 0x08;
  1         2  
  1         44  
41 1     1   5 use constant WANTS_UNDO => 0x10;
  1         2  
  1         38  
42 1     1   5 use constant WANTS_MOUSE => 0x20;
  1         2  
  1         45  
43 1     1   4 use constant WANTS_COLOR => 0x40;
  1         2  
  1         54  
44 1     1   5 use constant WANTS_SOUND => 0x80;
  1         1  
  1         47  
45             # Flags 2, v6+:
46 1     1   6 use constant WANTS_MENUS => 0x0100; # ??
  1         1  
  1         41  
47              
48 1     1   5 use constant BACKGROUND_COLOR => 0x2c;
  1         2  
  1         34  
49 1     1   5 use constant FOREGROUND_COLOR => 0x2d;
  1         2  
  1         68  
50             # 8.3.2, 8.3.3
51              
52 1     1   4 use constant SCREEN_HEIGHT_LINES => 0x20;
  1         2  
  1         37  
53 1     1   5 use constant SCREEN_WIDTH_CHARS => 0x21;
  1         2  
  1         41  
54 1     1   4 use constant SCREEN_WIDTH_UNITS => 0x22;
  1         2  
  1         80  
55 1     1   12 use constant SCREEN_HEIGHT_UNITS => 0x24;
  1         2  
  1         60  
56              
57 1     1   6 use constant FONT_WIDTH_UNITS_V5 => 0x26;
  1         1  
  1         45  
58 1     1   5 use constant FONT_WIDTH_UNITS_V6 => 0x27;
  1         3  
  1         39  
59              
60 1     1   5 use constant FONT_HEIGHT_UNITS_V5 => 0x27;
  1         2  
  1         51  
61 1     1   5 use constant FONT_HEIGHT_UNITS_V6 => 0x26;
  1         2  
  1         54  
62              
63 1     1   5 use constant ROUTINES_OFFSET => 0x28;
  1         2  
  1         42  
64 1     1   5 use constant STRINGS_OFFSET => 0x2a;
  1         1  
  1         85  
65              
66 1         7 use Games::Rezrov::MethodMaker ([],
67             qw(
68             abbrev_table_address
69             file_checksum
70             release_number
71             paged_memory_address
72             object_table_address
73             global_variable_address
74             static_memory_address
75             first_instruction_address
76             dictionary_address
77             serial_code
78             file_length
79             story
80             version
81             object_bytes
82             attribute_bytes
83             pointer_size
84             max_properties
85             max_objects
86             attribute_starter
87             object_count
88             encoded_word_length
89             is_time_game
90             strings_offset
91             routines_offset
92 1     1   706 ));
  1         3  
93              
94             #use SelfLoader;
95              
96             1;
97              
98             my $INLINE_CODE = '
99             sub new {
100             my ($type, $zio) = @_;
101             my $self = [];
102             bless $self, $type;
103            
104             my $version = GET_BYTE_AT(0);
105             if ($version < 1 or $version > 8) {
106             die "\nThis does not appear to be a valid game file.\n";
107             } elsif (($version < 3 or $version > 5) and $version != 8) {
108             # } elsif ($version < 3 or $version > 8) {
109             # die "Sorry, only z-code versions 3-8 are supported at present...\nAnd even those need work! :)\n"
110             die "Sorry, only z-code versions 3,4,5 and 8 are supported at present...\nAnd even those need work! :)\n"
111             } else {
112             $self->version($version);
113             }
114              
115             my $f1 = GET_BYTE_AT(FLAGS_1);
116             $self->is_time_game($f1 & 0x02 ? 1 : 0);
117             # a "time" game: 8.2.3.2
118              
119             my $start_rows = Games::Rezrov::StoryFile::rows();
120             my $start_columns = Games::Rezrov::StoryFile::columns();
121              
122             $f1 |= TANDY if Games::Rezrov::ZOptions::TANDY_BIT();
123             # turn on the "tandy bit"
124            
125             if ($version <= 3) {
126             $self->encoded_word_length(6);
127             # 13.3, 13.4
128              
129             # set bits 4 (status line) and 5 (screen splitting) appropriately
130             # depending on the ability of the ZIO implementation
131             if ($zio->can_split()) {
132             # yes
133             $f1 |= SCREEN_SPLITTING_AVAILABLE;
134             $f1 &= ~ STATUS_NOT_AVAILABLE;
135             } else {
136             # no
137             $f1 &= ~ SCREEN_SPLITTING_AVAILABLE;
138             $f1 |= STATUS_NOT_AVAILABLE;
139             }
140              
141             # "bit 6" (#7): variable-pitch font is default?
142             if ($zio->fixed_font_default()) {
143             $f1 |= 0x40;
144             } else {
145             $f1 &= ~0x40;
146             }
147             } else {
148             #
149             # versions 4+
150             #
151             $self->encoded_word_length(9);
152             # 13.3, 13.4
153              
154             if ($version >= 4) {
155             $f1 |= 0x04;
156             # "bit 2" (#3): boldface available
157             $f1 |= 0x08;
158             # "bit 3" (#4): italic available
159             $f1 |= 0x10;
160             # "bit 4" (#5): fixed-font available
161              
162             # $f1 |= 0x80;
163             $f1 &= ~0x80;
164             # "bit 7" (#8): timed input NOT available
165              
166             Games::Rezrov::StoryFile::set_byte_at(30, Games::Rezrov::ZOptions::INTERPRETER_ID());
167             # interpreter number
168             Games::Rezrov::StoryFile::set_byte_at(31, ord "R");
169             # interpreter version; "R" for rezrov
170            
171             $self->set_columns($start_columns);
172             $self->set_rows($start_rows);
173             }
174             if ($version >= 5) {
175             if ($zio->can_use_color()) {
176             # "bit 0" (#1): colors available
177             $f1 |= 0x01;
178             }
179              
180             Games::Rezrov::StoryFile::set_byte_at(BACKGROUND_COLOR, Games::Rezrov::ZConst::COLOR_BLACK);
181             Games::Rezrov::StoryFile::set_byte_at(FOREGROUND_COLOR, Games::Rezrov::ZConst::COLOR_WHITE);
182             # 8.3.3: default foreground and background
183             # FIX ME!
184              
185             my $f2 = Games::Rezrov::StoryFile::get_word_at(FLAGS_2);
186             if ($zio->groks_font_3() and
187             !Games::Rezrov::StoryFile::font_3_disabled()) {
188             # ZIO can decode font 3 characters
189             $f2 |= WANTS_PICTURES;
190             } else {
191             # nope
192             $f2 &= ~ WANTS_PICTURES;
193             }
194            
195             # $f2 |= WANTS_UNDO;
196             $f2 &= ~ WANTS_UNDO;
197             # FIX ME: should we never use this???
198              
199             if ($f2 & WANTS_COLOR) {
200             # 8.3.4: the game wants to use colors
201             # print "wants color!\n";
202             }
203             Games::Rezrov::StoryFile::set_word_at(FLAGS_2, $f2);
204             }
205             if ($version >= 6) {
206             # more unimplemented: see 8.3.2, etc
207             $self->routines_offset(Games::Rezrov::StoryFile::get_word_at(ROUTINES_OFFSET));
208             $self->strings_offset(Games::Rezrov::StoryFile::get_word_at(STRINGS_OFFSET));
209             }
210             }
211              
212             Games::Rezrov::StoryFile::set_byte_at(FLAGS_1, $f1);
213             # write back the header flags
214              
215             $self->release_number(Games::Rezrov::StoryFile::get_word_at(RELEASE_NUMBER));
216             $self->paged_memory_address(Games::Rezrov::StoryFile::get_word_at(PAGED_MEMORY_ADDRESS));
217             $self->first_instruction_address(Games::Rezrov::StoryFile::get_word_at(FIRST_INSTRUCTION_ADDRESS));
218             $self->dictionary_address(Games::Rezrov::StoryFile::get_word_at(DICTIONARY_ADDRESS));
219             $self->object_table_address(Games::Rezrov::StoryFile::get_word_at(OBJECT_TABLE_ADDRESS));
220             $self->global_variable_address(Games::Rezrov::StoryFile::get_word_at(GLOBAL_VARIABLE_ADDRESS));
221             $self->static_memory_address(Games::Rezrov::StoryFile::get_word_at(STATIC_MEMORY_ADDRESS));
222             $self->serial_code(Games::Rezrov::StoryFile::get_string_at(SERIAL_CODE, 6));
223             # see zmach06e.txt
224             $self->abbrev_table_address(Games::Rezrov::StoryFile::get_word_at(ABBREV_TABLE_ADDRESS));
225             $self->file_checksum(Games::Rezrov::StoryFile::get_word_at(CHECKSUM));
226              
227             my $flen = Games::Rezrov::StoryFile::get_word_at(FILE_LENGTH);
228             if ($version <= 3) {
229             # see 11.1.6
230             $flen *= 2;
231             } elsif ($version == 4 || $version == 5) {
232             $flen *= 4;
233             } else {
234             $flen *= 8;
235             }
236             $self->file_length($flen);
237            
238             #
239             # set object "constants" for this version...
240             #
241             if ($version <= 3) {
242             # 12.3.1
243             $self->object_bytes(9);
244             $self->attribute_bytes(4);
245             $self->pointer_size(1);
246             $self->max_properties(31); # 12.2
247             $self->max_objects(255); # 12.3.1
248             } else {
249             # 12.3.2
250             $self->object_bytes(14);
251             $self->attribute_bytes(6);
252             $self->pointer_size(2);
253             $self->max_properties(63); # 12.2
254             $self->max_objects(65535); # 12.3.2
255             }
256             die("check your math!")
257             if (($self->attribute_bytes() + ($self->pointer_size() * 3) + 2)
258             != $self->object_bytes());
259            
260             $self->attribute_starter($self->object_table_address() +
261             ($self->max_properties() * 2));
262            
263             my $obj_space = $self->global_variable_address() - $self->attribute_starter();
264             # how many bytes exist between the start of the object area and
265             # the beginning of the global variable block?
266             my $object_count;
267             if ($obj_space > 0) {
268             # hack:
269             # guess approximate object count; most useful for games later than v3
270             # FIX ME: is this _way_ off? Better to check validity of each object
271             # sequentially, stopping w/invalid pointers, etc?
272             $object_count = $obj_space / $self->object_bytes();
273             $object_count = $self->max_objects()
274             if $object_count > $self->max_objects();
275             } else {
276             # header data not arranged the way we expect; oh well.
277             $object_count = $self->max_objects();
278             }
279             $self->object_count($object_count);
280             # die sprintf "objects: %s\n", $object_count;
281            
282             return $self;
283             }
284              
285             sub get_colors {
286             return (GET_BYTE_AT(FOREGROUND_COLOR),
287             GET_BYTE_AT(BACKGROUND_COLOR));
288             }
289              
290             ';
291              
292             Games::Rezrov::Inliner::inline(\$INLINE_CODE);
293 0 50 33 0 0 0 eval $INLINE_CODE;
  1 50 33 1 0 3  
  1 50 33     3  
  1 50 0     3  
  1 50 0     3  
  1 50       18  
  0 0       0  
  0 0       0  
  1 0       33  
  1 0       3  
  1 0       32  
  1 0       4  
  1 50       4  
  1 50       5  
  1 0       3  
  1 50       29  
  1 50       5  
  0 0       0  
  0 50       0  
  1         2  
  1         3  
  1         11  
  1         2  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         6  
  1         6  
  1         3  
  1         4  
  1         4  
  1         4  
  1         4  
  1         4  
  1         5  
  1         4  
  1         4  
  1         3  
  1         4  
  1         3  
  0         0  
  0         0  
  1         28  
  1         4  
  1         29  
  1         25  
  1         27  
  1         29  
  1         28  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         30  
  1         47  
  1         28  
  1         2  
  1         4  
  0         0  
  0         0  
  1         27  
  1         28  
  1         4  
294             #print $INLINE_CODE;
295             #die "oof";
296             undef $INLINE_CODE;
297              
298             1;
299              
300             #__DATA__
301              
302             sub get_abbreviation_addr {
303 190     190 0 301 my ($self, $entry) = @_;
304             # Spec 3.3: fetch and convert the "word address" of the given entry
305             # in the abbreviations table.
306             # print STDERR "gaa\n";
307 190         5781 my $abbrev_addr = $self->abbrev_table_address() + ($entry * 2);
308 190         577 return Games::Rezrov::StoryFile::get_word_at($abbrev_addr) * 2;
309             # "word address"; only used for abbreviations (packed address
310             # rules do not apply here)
311             }
312              
313             sub set_columns {
314             # 8.4: set the dimensions of the screen.
315             # only needed in v4+
316             # arg: number of columns
317 0     0 0   Games::Rezrov::StoryFile::set_byte_at(SCREEN_WIDTH_CHARS, $_[1]);
318 0 0         if ($_[0]->version >= 5) {
319 0 0         Games::Rezrov::StoryFile::set_byte_at($_[0]->version >= 6 ?
320             FONT_WIDTH_UNITS_V6 : FONT_WIDTH_UNITS_V5, 1);
321 0           Games::Rezrov::StoryFile::set_word_at(SCREEN_WIDTH_UNITS, $_[1]);
322             # ?
323             }
324             }
325              
326             sub set_rows {
327             # arg: number of rows
328 0     0 0   Games::Rezrov::StoryFile::set_byte_at(SCREEN_HEIGHT_LINES, $_[1]);
329 0 0         if ($_[0]->version >= 5) {
330 0 0         Games::Rezrov::StoryFile::set_byte_at($_[0]->version >= 6 ?
331             FONT_HEIGHT_UNITS_V6 : FONT_HEIGHT_UNITS_V5, 1);
332 0           Games::Rezrov::StoryFile::set_word_at(SCREEN_HEIGHT_UNITS, $_[1]);
333             }
334             }
335              
336             sub wants_color {
337             # 8.3.4: does the game want to use colors?
338 0 0   0 0   return Games::Rezrov::StoryFile::get_word_at(FLAGS_2) & WANTS_COLOR ? 1 : 0;
339             }
340              
341             sub fixed_font_forced {
342             # 8.1: fixed-font printing may be forced by the game
343 0 0   0 0   if ($_[0]->version >= 3) {
344             # see section 10
345 0           return Games::Rezrov::StoryFile::get_word_at(FLAGS_2) & FORCE_FIXED;
346             } else {
347 0           return 0;
348             }
349             }
350              
351              
352             1;