line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Language::Zcode::Translator::Perl;
|
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
9
|
use strict;
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
70
|
|
4
|
1
|
|
|
1
|
|
7
|
use warnings;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2978
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=head1 NAME
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
Language::Zcode::Translator::Perl - Translate Z-code into Perl code
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=cut
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
@Language::Zcode::Translator::Perl::ISA = qw(Language::Zcode::Translator::Generic);
|
13
|
|
|
|
|
|
|
my $indent = ""; # indent subs for readability
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub new {
|
16
|
1
|
|
|
1
|
0
|
2
|
my ($class, @arg) = @_;
|
17
|
1
|
|
|
|
|
6
|
bless {}, $class;
|
18
|
|
|
|
|
|
|
}
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Write the beginning of the program
|
21
|
|
|
|
|
|
|
sub program_start {
|
22
|
1
|
|
|
1
|
1
|
757
|
my $self = shift;
|
23
|
1
|
|
|
|
|
4
|
my $top = <<'ENDTOP';
|
24
|
|
|
|
|
|
|
#!perl -w
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
use strict;
|
27
|
|
|
|
|
|
|
use Getopt::Std;
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Language::Zcode::Runtime::Opcodes; # Perl translation of complex opcodes
|
30
|
|
|
|
|
|
|
use Language::Zcode::Runtime::State; # save/restore game state
|
31
|
|
|
|
|
|
|
use Language::Zcode::Runtime::IO; # All IO stuff
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# Set constants
|
34
|
|
|
|
|
|
|
use vars qw(%Constants);
|
35
|
|
|
|
|
|
|
CONSTANTS_HERE
|
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
###############
|
38
|
|
|
|
|
|
|
# Read user input
|
39
|
|
|
|
|
|
|
my %opts;
|
40
|
|
|
|
|
|
|
my $Usage = <<"ENDUSAGE";
|
41
|
|
|
|
|
|
|
$0 [-r rows] [-c columns] [-t terminal] [-d]
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
-r, -c say how big to make the screen
|
44
|
|
|
|
|
|
|
-t specifies a "dumb" terminal or slightly smarter "win32" terminal
|
45
|
|
|
|
|
|
|
(hopefully will be adding more terminals soon)
|
46
|
|
|
|
|
|
|
-d debug. Write information about which sub we're in, set \$DEBUG, etc.
|
47
|
|
|
|
|
|
|
ENDUSAGE
|
48
|
|
|
|
|
|
|
getopts("dr:c:t:", \%opts) or die "$Usage\n";
|
49
|
|
|
|
|
|
|
my $DEBUG = defined $opts{d};
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# Build and run the Z-machine
|
52
|
|
|
|
|
|
|
my $Z_Result = Language::Zcode::Runtime::Opcodes::Z_machine(%opts);
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# If Z_Result was an error, do a (non-eval'ed) die to really die.
|
55
|
|
|
|
|
|
|
die $Z_Result if $Z_Result;
|
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
exit;
|
58
|
|
|
|
|
|
|
#############################################
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
ENDTOP
|
61
|
|
|
|
|
|
|
# Version-dependent constants in Z-file become true constants in output
|
62
|
|
|
|
|
|
|
# file
|
63
|
21
|
|
|
|
|
58
|
my $cstr = join("",
|
64
|
|
|
|
|
|
|
"\%Constants = (\n",
|
65
|
1
|
|
|
|
|
18
|
map({ " $_ => $Language::Zcode::Util::Constants{$_},\n" }
|
66
|
|
|
|
|
|
|
sort keys %Language::Zcode::Util::Constants),
|
67
|
|
|
|
|
|
|
");\n"
|
68
|
|
|
|
|
|
|
);
|
69
|
1
|
|
|
|
|
10
|
$top =~ s/^CONSTANTS_HERE/$cstr/m;
|
70
|
1
|
|
|
|
|
11
|
return $top;
|
71
|
|
|
|
|
|
|
}
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=pod
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head3 routine_start
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
This sub writes out a string that starts a sub.
|
78
|
|
|
|
|
|
|
Basically, we need to handle setting local variables the sub was
|
79
|
|
|
|
|
|
|
called with, and declaring an empty eval stack.
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
The much more complicated situation is when we're restoring a game in which
|
82
|
|
|
|
|
|
|
this sub was in the call stack when @save was called. If sub A called B called
|
83
|
|
|
|
|
|
|
C, which saved, then when we restore the save, we'll start executing sub C,
|
84
|
|
|
|
|
|
|
right after the @save command - and we need to set the local variables and eval
|
85
|
|
|
|
|
|
|
stack in C to the values they had when we saved. When we return from C, z_call
|
86
|
|
|
|
|
|
|
will call B, which needs to start executing at the command right after the call
|
87
|
|
|
|
|
|
|
to C. But when we start executing B, for example, the local variables and eval
|
88
|
|
|
|
|
|
|
stack need to be set to the values they had when we called C. (We get that
|
89
|
|
|
|
|
|
|
information from the save file.)
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
Arg 0 of the created sub will be an arrayref. It's empty for normal calls.
|
92
|
|
|
|
|
|
|
However, if we restored a game where this sub was in the call stack, then the
|
93
|
|
|
|
|
|
|
sub will be called with information giving the sub's state when it called the
|
94
|
|
|
|
|
|
|
next sub in the stack (or @save): namely, arg0 will then contain the PC where
|
95
|
|
|
|
|
|
|
we should resume execution, and the values to set the eval stack to.
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
arg1-argn will contain input values for the local variables. If we're
|
98
|
|
|
|
|
|
|
restoring, those values will be the values from the appropriate frame
|
99
|
|
|
|
|
|
|
of the call stack.
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Note: it's legal to pass in too many or too few args.
|
102
|
|
|
|
|
|
|
Set only as many values as were passed in, & don't auto-expand array.
|
103
|
|
|
|
|
|
|
(Important pre-V5, when local var initial values may not be 0)
|
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=cut
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub routine_start {
|
108
|
64
|
|
|
64
|
1
|
151
|
my ($self, $addr, @params) = @_;
|
109
|
64
|
|
|
|
|
151
|
my $name = "rtn$addr";
|
110
|
64
|
|
|
|
|
117
|
$indent = " " x 4;
|
111
|
64
|
|
33
|
|
|
1155
|
my $hex_address = sprintf("%x", ($name =~ /\d+/ && $&));
|
112
|
64
|
|
|
|
|
234
|
my $start = "sub $name {\n";
|
113
|
64
|
|
|
|
|
129
|
my $out_str = <<'ENDRTN1'; # single quotes make life a bit easier
|
114
|
|
|
|
|
|
|
my ($t1, $t2, @stack, @locv);
|
115
|
|
|
|
|
|
|
if (my @frame = @{shift @_}) {
|
116
|
|
|
|
|
|
|
@locv = @{$frame[1]}; @stack = @{$frame[2]}; goto "L$frame[0]";
|
117
|
|
|
|
|
|
|
} else {
|
118
|
|
|
|
|
|
|
@locv = (PUT_VALS_HERE);
|
119
|
|
|
|
|
|
|
@locv[0 .. ($#_ > $#locv ? $#locv : $#_)] = @_;
|
120
|
|
|
|
|
|
|
}
|
121
|
|
|
|
|
|
|
ENDRTN1
|
122
|
64
|
|
|
|
|
513
|
$out_str =~ s/PUT_VALS_HERE/join(", ", @params)/e; # default values
|
|
64
|
|
|
|
|
466
|
|
123
|
64
|
|
|
|
|
1004
|
$out_str =~ s/^(?!$)/$indent/gm;
|
124
|
64
|
|
|
|
|
1039
|
return "$start$out_str";
|
125
|
|
|
|
|
|
|
}
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
sub routine_end {
|
128
|
64
|
|
|
64
|
1
|
916
|
$indent = "";
|
129
|
64
|
|
|
|
|
205
|
return "}\n\n";
|
130
|
|
|
|
|
|
|
}
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
##############################################3
|
134
|
|
|
|
|
|
|
#
|
135
|
|
|
|
|
|
|
# Opcode translations...
|
136
|
|
|
|
|
|
|
# Z_* will later be replaced with values of %parsed
|
137
|
|
|
|
|
|
|
# _SW implements conversion to signed word.
|
138
|
|
|
|
|
|
|
# Branching "?(label)" and results "-> (result)" are not put into these
|
139
|
|
|
|
|
|
|
# translations because they're always handled the same way.
|
140
|
|
|
|
|
|
|
#
|
141
|
|
|
|
|
|
|
# SPEC 2.2: The operations of numerical comparison, multiplication,
|
142
|
|
|
|
|
|
|
# addition, subtraction, division, remainder-after-division and printing of
|
143
|
|
|
|
|
|
|
# numbers are signed; bitwise operations are unsigned.
|
144
|
|
|
|
|
|
|
#
|
145
|
|
|
|
|
|
|
# WARNING!!! If the same Z_* is found twice in the same translation,
|
146
|
|
|
|
|
|
|
# and that Z_* is translated to "pop@stack", bad things could happen!
|
147
|
|
|
|
|
|
|
# So use temporary variables.
|
148
|
|
|
|
|
|
|
# XXX Maybe I should fix this somehow, e.g. s/// add's translation to:
|
149
|
|
|
|
|
|
|
# $Z_A = $parsed{a}; $Z_B = $parsed{b}; _SW#\$Z_A# + _SW#$Z_B#;
|
150
|
|
|
|
|
|
|
# Then I can use $Z_FOO in the translation without fear.
|
151
|
|
|
|
|
|
|
# Only problem is things like make_var, especially var_to_lval
|
152
|
|
|
|
|
|
|
my %replace_trans = (
|
153
|
|
|
|
|
|
|
# Arithmetic ops
|
154
|
|
|
|
|
|
|
add => "_SW#Z_A# + _SW#Z_B#",
|
155
|
|
|
|
|
|
|
'sub' => "_SW#Z_A# - _SW#Z_B#",
|
156
|
|
|
|
|
|
|
mul => "_SW#Z_A# * _SW#Z_B#",
|
157
|
|
|
|
|
|
|
div => "int(_SW#Z_A# / _SW#Z_B#)",
|
158
|
|
|
|
|
|
|
# Perl: # (13 % -5) == -2; Zcode: 13 % -5 = (13 - (-5 * -2)) = 3
|
159
|
|
|
|
|
|
|
# How many times does $y fit into $x; always round towards zero!
|
160
|
|
|
|
|
|
|
# Use commas so we can later set $result = (..., ... , a%b)
|
161
|
|
|
|
|
|
|
mod=>'($t1 = _SW#Z_A#, $t2 = _SW#Z_B#, $t1 - $t2*int($t1/$t2))',
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# logical ops - make sure we get the right number of bits
|
164
|
|
|
|
|
|
|
'or' => "Z_A | Z_B",
|
165
|
|
|
|
|
|
|
'and'=> "Z_A & Z_B",
|
166
|
|
|
|
|
|
|
'not' => "0xffff & ~Z_VALUE",
|
167
|
|
|
|
|
|
|
log_shift => '($t1 = _SW#Z_PLACES#) > 0 ' .
|
168
|
|
|
|
|
|
|
'? Z_NUMBER << $t1 : (Z_NUMBER & 0xffff) >> -$t1',
|
169
|
|
|
|
|
|
|
# The |(...) fills in 1s from the left if bit fifteen (sign bit) is set
|
170
|
|
|
|
|
|
|
art_shift => 'do { $t2 = Z_NUMBER;
|
171
|
|
|
|
|
|
|
($t1 = _SW#Z_PLACES#) > 0 ?
|
172
|
|
|
|
|
|
|
Z_NUMBER << $t1 :
|
173
|
|
|
|
|
|
|
($t2 >> -$t1) | ($t2>>15 && ~(2**-$t1))}',
|
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Jumps (conditional & unconditional)
|
176
|
|
|
|
|
|
|
jump => "goto LZ_LABEL",
|
177
|
|
|
|
|
|
|
# Branch instructions just write their conditions: they'll be added to later
|
178
|
|
|
|
|
|
|
jz => "Z_A == 0",
|
179
|
|
|
|
|
|
|
jg => "_SW#Z_A# > _SW#Z_B#",
|
180
|
|
|
|
|
|
|
jl => "_SW#Z_A# < _SW#Z_B#",
|
181
|
|
|
|
|
|
|
# jump if all given flags in the given bitmap are set
|
182
|
|
|
|
|
|
|
test => '(Z_BITMAP & ($t1 = Z_FLAGS)) == $t1',
|
183
|
|
|
|
|
|
|
# Zspec 1.1 'je 5' is illegal.
|
184
|
|
|
|
|
|
|
# I need to do _SW for the case of je -1 65535
|
185
|
|
|
|
|
|
|
# XXX If I move this to sub_trans, then I can use grep for > 2 args,
|
186
|
|
|
|
|
|
|
# and just test == for 2 args.
|
187
|
|
|
|
|
|
|
je => '$t1 = Z_A, grep {_SW#$t1# == _SW#$_#} (_ARG_LIST)',
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# Stack and Variables
|
190
|
|
|
|
|
|
|
# Note: this is where Z_VARIABLE lives - indirect variables. Beware!
|
191
|
|
|
|
|
|
|
'pop' => 'pop @stack',
|
192
|
|
|
|
|
|
|
'push' => 'push @stack, Z_VALUE',
|
193
|
|
|
|
|
|
|
pull => 'Z_VARIABLE = pop @stack',
|
194
|
|
|
|
|
|
|
store => "Z_VARIABLE = Z_VALUE",
|
195
|
|
|
|
|
|
|
load => "Z_VARIABLE",
|
196
|
|
|
|
|
|
|
# Spec15.html#inc: "This is signed, so -1 increments to 0."
|
197
|
|
|
|
|
|
|
# Spec15.html#dec: "This is signed, so 0 decrements to -1."
|
198
|
|
|
|
|
|
|
inc => "++Z_VARIABLE",
|
199
|
|
|
|
|
|
|
dec => "--Z_VARIABLE",
|
200
|
|
|
|
|
|
|
inc_chk => "_SW#++Z_VARIABLE# > _SW#Z_VALUE#",
|
201
|
|
|
|
|
|
|
dec_chk => "_SW#--Z_VARIABLE# < _SW#Z_VALUE#",
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# Memory access
|
204
|
|
|
|
|
|
|
loadb => '$PlotzMemory::Memory[(Z_ARRAY + Z_BYTE_INDEX) & 0xffff]',
|
205
|
|
|
|
|
|
|
loadw =>
|
206
|
|
|
|
|
|
|
'256*$PlotzMemory::Memory[$t1=(Z_ARRAY + 2*Z_WORD_INDEX) & 0xffff] +
|
207
|
|
|
|
|
|
|
$PlotzMemory::Memory[$t1 + 1]',
|
208
|
|
|
|
|
|
|
storeb =>
|
209
|
|
|
|
|
|
|
'$PlotzMemory::Memory[(Z_ARRAY + Z_BYTE_INDEX) & 0xffff] = Z_VALUE & 0xff',
|
210
|
|
|
|
|
|
|
storew => '$PlotzMemory::Memory[$t1 = (Z_ARRAY + 2*Z_WORD_INDEX) & 0xffff] =
|
211
|
|
|
|
|
|
|
($t2 = Z_VALUE)>>8 & 0xff,
|
212
|
|
|
|
|
|
|
$PlotzMemory::Memory[$t1 + 1] = $t2 & 0xff',
|
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# Return
|
215
|
|
|
|
|
|
|
ret => "return Z_VALUE",
|
216
|
|
|
|
|
|
|
ret_popped => "return (pop \@stack)",
|
217
|
|
|
|
|
|
|
rtrue => "return 1",
|
218
|
|
|
|
|
|
|
rfalse => "return 0",
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Print_*
|
221
|
|
|
|
|
|
|
# print is equivalent to print_addr with address of the literal string!
|
222
|
|
|
|
|
|
|
"print" => '# print "Z_PRINT_STRING"
|
223
|
|
|
|
|
|
|
&write_text(&decode_text(Z_LITERAL_STRING))',
|
224
|
|
|
|
|
|
|
print_ret => '# print "Z_PRINT_STRING"
|
225
|
|
|
|
|
|
|
&write_text(&decode_text(Z_LITERAL_STRING));
|
226
|
|
|
|
|
|
|
&newline();
|
227
|
|
|
|
|
|
|
return(1)',
|
228
|
|
|
|
|
|
|
print_num => "&write_text(_SW#Z_VALUE#)",
|
229
|
|
|
|
|
|
|
print_addr => "&write_text(&decode_text(Z_BYTE_ADDRESS_OF_STRING))",
|
230
|
|
|
|
|
|
|
# This is why we need to store entire program in memory
|
231
|
|
|
|
|
|
|
print_paddr => "&write_text(&decode_text(Z_PACKED_ADDRESS_OF_STRING))",
|
232
|
|
|
|
|
|
|
# XXX We're doing ASCII. Need to do ZSCII
|
233
|
|
|
|
|
|
|
print_char => "&write_zchar(Z_OUTPUT_CHARACTER_CODE)",
|
234
|
|
|
|
|
|
|
new_line => "&newline()",
|
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Other I/O
|
237
|
|
|
|
|
|
|
"read" => "&z_read(Z_TEXT, Z_PARSE, Z_TIME, Z_ROUTINE)",
|
238
|
|
|
|
|
|
|
show_status => "&show_status()",
|
239
|
|
|
|
|
|
|
tokenise => "&z_tokenise(Z_TEXT, Z_PARSE, Z_DICTIONARY, Z_FLAG)",
|
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Streams & windows & cursors
|
242
|
|
|
|
|
|
|
output_stream => "&output_stream(_SW#Z_NUMBER#)", # arg may be < 0
|
243
|
|
|
|
|
|
|
input_stream => "&input_stream(Z_NUMBER)",
|
244
|
|
|
|
|
|
|
split_window => "&split_window(Z_LINES)",
|
245
|
|
|
|
|
|
|
set_window => "&set_window(Z_WINDOW)",
|
246
|
|
|
|
|
|
|
erase_window => "&erase_window(_SW#Z_WINDOW#)", # arg may be < 0
|
247
|
|
|
|
|
|
|
get_cursor => "&get_cursor(Z_ARRAY)",
|
248
|
|
|
|
|
|
|
set_cursor => "&set_cursor(_SW#Z_LINE#, Z_COLUMN, Z_WINDOW)", # < 0 for v6
|
249
|
|
|
|
|
|
|
set_text_style => "&set_text_style(Z_STYLE)",
|
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Objects
|
252
|
|
|
|
|
|
|
get_parent => "get_parent(Z_OBJECT)",
|
253
|
|
|
|
|
|
|
get_child => "get_child(Z_OBJECT)",
|
254
|
|
|
|
|
|
|
get_sibling => "get_sibling(Z_OBJECT)",
|
255
|
|
|
|
|
|
|
jin => "Z_OBJ2 == &get_object(&thing_location(Z_OBJ1, 'parent'))",
|
256
|
|
|
|
|
|
|
print_obj => "&write_text(&decode_text(&thing_location(Z_OBJECT, 'name')))",
|
257
|
|
|
|
|
|
|
insert_obj => "&insert_obj(Z_OBJECT, Z_DESTINATION)",
|
258
|
|
|
|
|
|
|
remove_obj => "&remove_obj(Z_OBJECT)",
|
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# Properties
|
261
|
|
|
|
|
|
|
get_prop => "&get_prop(Z_OBJECT, Z_PROPERTY)",
|
262
|
|
|
|
|
|
|
put_prop => "&put_prop(Z_OBJECT, Z_PROPERTY, Z_VALUE)",
|
263
|
|
|
|
|
|
|
get_next_prop => "&get_next_prop(Z_OBJECT, Z_PROPERTY)",
|
264
|
|
|
|
|
|
|
get_prop_addr => "&get_prop_addr(Z_OBJECT, Z_PROPERTY)",
|
265
|
|
|
|
|
|
|
get_prop_len => "&get_prop_len(Z_PROPERTY_ADDRESS)",
|
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Attributes
|
268
|
|
|
|
|
|
|
set_attr => "&set_attr(Z_OBJECT, Z_ATTRIBUTE)",
|
269
|
|
|
|
|
|
|
clear_attr => "&clear_attr(Z_OBJECT, Z_ATTRIBUTE)",
|
270
|
|
|
|
|
|
|
test_attr => "&test_attr(Z_OBJECT, Z_ATTRIBUTE)",
|
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Save/restore
|
273
|
|
|
|
|
|
|
# XXX Different for v1-3
|
274
|
|
|
|
|
|
|
save => '&save_state(Z_RESTORE_PC, \@locv, \@stack)',
|
275
|
|
|
|
|
|
|
restore => "&restore_state",
|
276
|
|
|
|
|
|
|
# Spec "save_undo": terp must return -1 if it doesn't implement save_undo
|
277
|
|
|
|
|
|
|
save_undo => "-1",
|
278
|
|
|
|
|
|
|
# Spec "restore_undo": illegal for a game to use this if save_undo
|
279
|
|
|
|
|
|
|
# returns -1.
|
280
|
|
|
|
|
|
|
restore_undo => "0",
|
281
|
|
|
|
|
|
|
restart => 'die "Restart\n"',
|
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# Misc
|
284
|
|
|
|
|
|
|
check_arg_count => '@_ >= Z_ARGUMENT_NUMBER',
|
285
|
|
|
|
|
|
|
quit => 'die "Quit\n"',
|
286
|
|
|
|
|
|
|
# Spec 15, 'piracy': "Interpreters are asked to be gullible"
|
287
|
|
|
|
|
|
|
piracy => "1",
|
288
|
|
|
|
|
|
|
random => "&z_random(_SW#Z_RANGE#)",
|
289
|
|
|
|
|
|
|
verify => "&z_verify()",
|
290
|
|
|
|
|
|
|
nop => 1,
|
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# Calls: Z subs are turned into Perl subs
|
293
|
|
|
|
|
|
|
# Use Perl's calling stack instead of building a separate one BUT do some
|
294
|
|
|
|
|
|
|
# bookkeeping (w/ extra args) to be able to save/restore machine state
|
295
|
|
|
|
|
|
|
call_1s =>
|
296
|
|
|
|
|
|
|
'z_call(Z_ROUTINE, \@locv, \@stack, Z_NEXT_PC, Z_RESULT_NUM, _ARG_LIST)',
|
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
);
|
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# All call subs work the same! (store var will be set to undef for call_*n,
|
301
|
|
|
|
|
|
|
# and "result = " will be added to call_s).
|
302
|
|
|
|
|
|
|
@replace_trans{qw(call_2s call_vs call_vs2 call_1n call_2n call_vn call_vn2)} =
|
303
|
|
|
|
|
|
|
($replace_trans{call_1s}) x 7;
|
304
|
|
|
|
|
|
|
#@replace_trans{ qw(call_2n call_vn call_vn2) } = ($replace_trans{call_1n}) x 3;
|
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Translate Z opcode and ops into Perl
|
307
|
|
|
|
|
|
|
my %unimplemented; # keep track of unimplemented opcodes
|
308
|
|
|
|
|
|
|
sub translate_command {
|
309
|
|
|
|
|
|
|
# Keys to %parsed are based on the arguments in the opcode syntax list
|
310
|
|
|
|
|
|
|
# in LZ::Parser::Opcode. There's a few others I put in:
|
311
|
|
|
|
|
|
|
# - opcode, opcode address are the name & address of the opcode
|
312
|
|
|
|
|
|
|
# - result is variable name (or stack top) where we're supposed
|
313
|
|
|
|
|
|
|
# to store the result, if any
|
314
|
|
|
|
|
|
|
# - negate_jump means negate the condition of jump opcodes
|
315
|
|
|
|
|
|
|
# - jump_return means return this value (0 or 1) instead of branching
|
316
|
|
|
|
|
|
|
# if the branch condition is met
|
317
|
|
|
|
|
|
|
# - op is an arrayref to remaining arguments (used for e.g., call_*)
|
318
|
1387
|
|
|
1387
|
1
|
12439
|
my ($self, $href) = @_;
|
319
|
1387
|
|
|
|
|
14555
|
my %parsed = %$href;
|
320
|
1387
|
50
|
|
|
|
6384
|
my $opcode = $parsed{opcode} or return; # totally unknown opcode?
|
321
|
1387
|
|
|
|
|
2396
|
my $command = "OOPS. No Command Here\n"; # command to return
|
322
|
|
|
|
|
|
|
|
323
|
1387
|
|
|
|
|
2712
|
my %sub_trans = (
|
324
|
|
|
|
|
|
|
# There's nothing to see here. Move along...
|
325
|
|
|
|
|
|
|
);
|
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# Build the actual line of Perl code
|
328
|
|
|
|
|
|
|
# XXX Only print labels we actually jump to? Requires separate pass.
|
329
|
1387
|
|
|
|
|
4527
|
my $label = "L$parsed{opcode_address}: ";
|
330
|
|
|
|
|
|
|
# Quetzal stores the byte BEFORE the next command as its restore_pc,
|
331
|
|
|
|
|
|
|
# so we'll eventually call a sub and try to goto that address.
|
332
|
1387
|
50
|
|
|
|
5859
|
$label .= "1; L$parsed{restore_pc}: " if exists $parsed{restore_pc};
|
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
# Translate, e.g., "local1" to language-specific
|
335
|
|
|
|
|
|
|
# code representing second local variable
|
336
|
|
|
|
|
|
|
# Treat key "variable" specially - it's used in "indirect opcodes"
|
337
|
|
|
|
|
|
|
# (See make_var)
|
338
|
1387
|
|
|
|
|
3784
|
foreach my $key (keys %parsed) {
|
339
|
5926
|
|
|
|
|
11228
|
my %skip = map {$_=>1}
|
|
59260
|
|
|
|
|
178724
|
|
340
|
|
|
|
|
|
|
qw(args jump_return label literal_string negate_jump
|
341
|
|
|
|
|
|
|
next_pc opcode opcode_address print_string restore_pc);
|
342
|
5926
|
100
|
|
|
|
31242
|
if (!exists $skip{$key}) {
|
343
|
|
|
|
|
|
|
#warn "$key $parsed{$key}\n";
|
344
|
1696
|
50
|
|
|
|
23541
|
warn "undefined $key\n" if !defined $parsed{$key};
|
345
|
1696
|
|
|
|
|
6423
|
$parsed{$key} = $self->make_var($parsed{$key}, $key eq "variable");
|
346
|
|
|
|
|
|
|
}
|
347
|
|
|
|
|
|
|
}
|
348
|
|
|
|
|
|
|
# Pack addresses
|
349
|
1387
|
|
|
|
|
4281
|
foreach my $key (qw(packed_address_of_string routine)) {
|
350
|
2774
|
100
|
|
|
|
9386
|
if (exists $parsed{$key}) {
|
351
|
372
|
|
|
|
|
1581
|
$parsed{$key} = $self->packed_address_str($parsed{$key}, $key);
|
352
|
|
|
|
|
|
|
}
|
353
|
|
|
|
|
|
|
}
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Turn variable number of args (if any) into a Perl list
|
356
|
|
|
|
|
|
|
# Btw, call_1n takes no args, so arg_list will be "" for call_1n, too
|
357
|
822
|
|
|
|
|
8147
|
my $arg_list = exists $parsed{args}
|
358
|
1387
|
100
|
|
|
|
4911
|
? join(", ", map {$self->make_var($_)} @{$parsed{"args"}})
|
|
355
|
|
|
|
|
987
|
|
359
|
|
|
|
|
|
|
: "";
|
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# Turn Z ops into Perl ops
|
362
|
1387
|
50
|
|
|
|
5865
|
if (exists $replace_trans{$opcode}) {
|
|
|
0
|
|
|
|
|
|
363
|
1387
|
|
|
|
|
3393
|
local $_ = $replace_trans{$opcode};
|
364
|
|
|
|
|
|
|
# Put in actual arguments
|
365
|
|
|
|
|
|
|
# (Note that sometimes there's a letter before the Z,
|
366
|
|
|
|
|
|
|
# but never after the whole key.)
|
367
|
|
|
|
|
|
|
# If there are optional args, then some of the args won't exist.
|
368
|
|
|
|
|
|
|
# First handle things where we set an lval to an rval
|
369
|
1387
|
|
|
|
|
6419
|
s/Z_(\w+)\s+=\s+(.+)/$self->var_to_lval($parsed{lc $1}, $2)/e;
|
|
316
|
|
|
|
|
1485
|
|
370
|
1387
|
|
|
|
|
5536
|
s/([+-]{2})Z_(\w+)/$self->var_to_lval($parsed{lc $2}, $1)/e;
|
|
72
|
|
|
|
|
390
|
|
371
|
1387
|
100
|
|
|
|
7696
|
s/Z_(\w+)/exists $parsed{lc $1} ? $parsed{lc $1}:"undef"/ge;
|
|
2284
|
|
|
|
|
19074
|
|
372
|
1387
|
|
|
|
|
11251
|
s/_ARG_LIST/$arg_list/;
|
373
|
1387
|
|
|
|
|
4666
|
s/, (undef(, )?)?\)(;|$)/)$3/; # clean up unneeded args
|
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# Change numbers to signed/unsigned words.
|
376
|
1387
|
|
|
|
|
2993
|
s/_SW#(.*?)#/$self->signed_word($1)/ge;
|
|
338
|
|
|
|
|
1282
|
|
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# print "$parsed{opcode_address} $command\n";
|
379
|
1387
|
|
|
|
|
4089
|
$command = $_;
|
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
} elsif (exists $sub_trans{$opcode}) {
|
382
|
0
|
|
|
|
|
0
|
$command = &{$sub_trans{$opcode}}();
|
|
0
|
|
|
|
|
0
|
|
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
} else {
|
385
|
0
|
0
|
|
|
|
0
|
warn "Unimplemented opcode $opcode at $parsed{opcode_address}\n"
|
386
|
|
|
|
|
|
|
unless $unimplemented{$opcode}++;
|
387
|
0
|
|
|
|
|
0
|
$command = "&unimplemented_$opcode";
|
388
|
|
|
|
|
|
|
}
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# Handle commands that have a "-> (result)" argument
|
391
|
|
|
|
|
|
|
# (result has already been translated from e.g. 3 to 'local2')
|
392
|
1387
|
100
|
|
|
|
5291
|
$command = $self->var_to_lval($parsed{result}, $command)
|
393
|
|
|
|
|
|
|
if exists $parsed{result};
|
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
# Handle branch instructions
|
396
|
|
|
|
|
|
|
# Do this AFTER store_result, so we get "goto L3 if $c = $a+$b"
|
397
|
|
|
|
|
|
|
# rather than "$c = goto L3 if $c=$a+$b"
|
398
|
|
|
|
|
|
|
# (Note: jump doesn't count as a branch instruction!)
|
399
|
|
|
|
|
|
|
# (This assumes Perl command is pretty simple)
|
400
|
1387
|
100
|
|
|
|
4072
|
if (exists $parsed{negate_jump}) {
|
401
|
155
|
|
|
|
|
285
|
my $action;
|
402
|
155
|
100
|
|
|
|
496
|
if (exists $parsed{jump_return}) {
|
403
|
2
|
|
|
|
|
7
|
$action = "return $parsed{jump_return}";
|
404
|
|
|
|
|
|
|
} else {
|
405
|
153
|
50
|
|
|
|
378
|
die "no label for command!" unless exists $parsed{label};
|
406
|
153
|
|
|
|
|
677
|
$action = "goto L$parsed{label}";
|
407
|
|
|
|
|
|
|
}
|
408
|
155
|
100
|
|
|
|
780
|
my $cond .= $parsed{negate_jump} ? "unless" : "if";
|
409
|
155
|
|
|
|
|
922
|
$command = "$action $cond $command";
|
410
|
|
|
|
|
|
|
}
|
411
|
|
|
|
|
|
|
|
412
|
1387
|
|
|
|
|
4579
|
$command = "$indent$label$command;\n";
|
413
|
|
|
|
|
|
|
|
414
|
1387
|
|
|
|
|
13666
|
return ($command);
|
415
|
|
|
|
|
|
|
}
|
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
# Change the rval created by make_var to an lval (HACKISH!)
|
418
|
|
|
|
|
|
|
# Note that when we get called, make_var has already been called on the lval,
|
419
|
|
|
|
|
|
|
# generating possibly incorrect
|
420
|
|
|
|
|
|
|
# If $lval is an indirect variable (see make_var), then the variable
|
421
|
|
|
|
|
|
|
# is really an RVAL which returns a variable that should be treated as an LVAL!
|
422
|
|
|
|
|
|
|
# E.g., store [g0f] 17 means "set the variable represented by the number
|
423
|
|
|
|
|
|
|
# stored in global_var(15) to 17" so global_var(15) is still treated as an rval.
|
424
|
|
|
|
|
|
|
# If global_var(15) is 0 ("sp"), then treat sp as an LVAL, i.e.,
|
425
|
|
|
|
|
|
|
# push 17 onto the stack
|
426
|
|
|
|
|
|
|
# XXX Now that indirect var is treated differently, can I merge this
|
427
|
|
|
|
|
|
|
# XXX back into make_var, only called with an extra arg?
|
428
|
|
|
|
|
|
|
# Special case: if $rval is ++ or --, then inc/dec the lval.
|
429
|
|
|
|
|
|
|
sub var_to_lval {
|
430
|
479
|
|
|
479
|
0
|
7063
|
my ($self, $lval, $rval) = @_;
|
431
|
479
|
|
|
|
|
1080
|
local $_ = $lval; # for convenience in //'s.
|
432
|
|
|
|
|
|
|
# XXX what's correct protocol for store [sp] sp?
|
433
|
|
|
|
|
|
|
# Pop stack before reading indirect variable?
|
434
|
479
|
|
|
|
|
879
|
my $is_bracket = /bracket_var\(/;
|
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
# $rval = "($rval) % 0x10000";
|
437
|
|
|
|
|
|
|
|
438
|
479
|
100
|
|
|
|
3213
|
if ($is_bracket) { # add $rval to args to bracket_var
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
439
|
27
|
|
|
|
|
128
|
$rval =~ s/^[+-]{2}$/"$&"/; # Yuck!
|
440
|
27
|
|
|
|
|
169
|
s/\)$/, $rval)/;
|
441
|
|
|
|
|
|
|
} elsif (/global_var/) { # global_var(number) -> global_var(number, rval)
|
442
|
72
|
|
|
|
|
198
|
$rval =~ s/^[+-]{2}$/"$&"/; # Yuck!
|
443
|
72
|
|
|
|
|
657
|
s/\)$/, $rval)/;
|
444
|
|
|
|
|
|
|
} elsif (/locv/) { # $locv[num] -> $locv[num] = rval
|
445
|
349
|
100
|
100
|
|
|
2766
|
if ($rval eq "++" || $rval eq "--") {
|
446
|
37
|
|
|
|
|
268
|
my $op = substr($rval, 0, 1);
|
447
|
|
|
|
|
|
|
# XXX this wrong. Spec 15#dec/inc say this should be signed!
|
448
|
|
|
|
|
|
|
# XXX So it should really be:
|
449
|
|
|
|
|
|
|
# $_ = "($_ = ($_
|
450
|
37
|
|
|
|
|
202
|
$_ = "($_ = ($_ $op 1) & 0xffff)";
|
451
|
|
|
|
|
|
|
} else {
|
452
|
312
|
|
|
|
|
1304
|
$_ .= " = $rval";
|
453
|
|
|
|
|
|
|
}
|
454
|
|
|
|
|
|
|
} elsif (/stack/) { # pop @stack -> $stack[@stack] = rval
|
455
|
|
|
|
|
|
|
# push returns number of elements in array. I need the value I pushed.
|
456
|
|
|
|
|
|
|
# If indirect variable, this s/// won't happen.
|
457
|
31
|
|
|
|
|
320
|
s/pop\(\@stack\)/\$stack[\@stack]/;
|
458
|
31
|
100
|
100
|
|
|
217
|
if ($rval eq "++" || $rval eq "--") {
|
459
|
7
|
|
|
|
|
25
|
my $op = substr($rval, 0, 1);
|
460
|
7
|
|
|
|
|
30
|
$_ = "($_ = ($_ $op 1) & 0xffff)";
|
461
|
|
|
|
|
|
|
} else {
|
462
|
24
|
|
|
|
|
75
|
$_ .= " = $rval";
|
463
|
|
|
|
|
|
|
}
|
464
|
|
|
|
|
|
|
} else {
|
465
|
0
|
|
|
|
|
0
|
warn "Unexpected arg to var_to_lval $lval";
|
466
|
|
|
|
|
|
|
}
|
467
|
|
|
|
|
|
|
|
468
|
479
|
|
|
|
|
2902
|
return $_;
|
469
|
|
|
|
|
|
|
}
|
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
# Create a string describing a variable from the given string.
|
472
|
|
|
|
|
|
|
# (e.g., '$locv[2]' from 'local2')
|
473
|
|
|
|
|
|
|
# Indirect variables are a special case - stack doesn't get popped.
|
474
|
|
|
|
|
|
|
# Usually, it'll be a string describing the stack ("sp"), or a local
|
475
|
|
|
|
|
|
|
# or global variable. But if it's an expression in [], then
|
476
|
|
|
|
|
|
|
# e.g., [local2] means the value of the variable stored in
|
477
|
|
|
|
|
|
|
# $locv[2]. If $locv[2] is 11, then we really want the value stored in
|
478
|
|
|
|
|
|
|
# $locv[10]!
|
479
|
|
|
|
|
|
|
sub make_var {
|
480
|
2518
|
|
|
2518
|
0
|
6794
|
my ($self, $var, $is_indirect) = @_;
|
481
|
2518
|
|
|
|
|
6226
|
my $is_bracket = ($var =~ s/\[(.*)\]/$1/);
|
482
|
|
|
|
|
|
|
|
483
|
2518
|
|
|
|
|
8985
|
local $_ = $var;
|
484
|
2518
|
100
|
|
|
|
18777
|
if (/^g([\da-f]+)$/) { # Global variable
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
485
|
161
|
|
|
|
|
475
|
my $var_num = hex($1);
|
486
|
161
|
|
|
|
|
606
|
$_ = "&global_var($var_num)";
|
487
|
|
|
|
|
|
|
} elsif (/^local([\da-f]+)$/) { # Local variable
|
488
|
672
|
|
|
|
|
2362
|
$_ = "\$locv[$1]";
|
489
|
|
|
|
|
|
|
} elsif ($_ eq "sp") { # Stack
|
490
|
|
|
|
|
|
|
# Spec Version 1.1 (draft7): "an indirect reference to the stack
|
491
|
|
|
|
|
|
|
# pointer does not push or pull the top item of the stack - it is read
|
492
|
|
|
|
|
|
|
# or written in place."
|
493
|
|
|
|
|
|
|
# ADK: From testing (winfrotz2002) it apears this is true for
|
494
|
|
|
|
|
|
|
# "load sp". "load [sp]" does pop the stack in getting the number
|
495
|
|
|
|
|
|
|
# of the variable to use. But if sp == 0, "load [sp]" still pops
|
496
|
|
|
|
|
|
|
# only once.
|
497
|
75
|
100
|
100
|
|
|
628
|
$_ = $is_indirect && !$is_bracket ? '$stack[-1]' : 'pop(@stack)';
|
498
|
|
|
|
|
|
|
} elsif (/^\d+$/) {
|
499
|
|
|
|
|
|
|
# Leave the numeric constant as it is
|
500
|
|
|
|
|
|
|
} else { # not a number? What is it?
|
501
|
0
|
|
|
|
|
0
|
warn "Unexpected arg to make_var: '$var'";
|
502
|
|
|
|
|
|
|
# keep the garbage in the output file
|
503
|
|
|
|
|
|
|
}
|
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# Get the value stored in the variable referenced by the current $_
|
506
|
|
|
|
|
|
|
# Pass in local variables & stack so we have their values.
|
507
|
|
|
|
|
|
|
# No, pass in *refs* to local variables & stack, in case the indirect
|
508
|
|
|
|
|
|
|
# var is an lval which references e.g. a local variable which we
|
509
|
|
|
|
|
|
|
# then need to set within indirect_var!
|
510
|
2518
|
100
|
|
|
|
7010
|
$_ = "bracket_var($_, \\\@locv, \\\@stack)"
|
511
|
|
|
|
|
|
|
if $is_bracket;
|
512
|
|
|
|
|
|
|
|
513
|
2518
|
|
|
|
|
16233
|
return $_;
|
514
|
|
|
|
|
|
|
}
|
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
# Convert num to signed_word & unsigned_word. Stolen from Games::Rezrov.
|
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
# Signed word: if high bit is set, take ~ number, else just the number
|
519
|
|
|
|
|
|
|
# IF the expression we're sign'ing is just an integer constant,
|
520
|
|
|
|
|
|
|
# convert it to a signed word constant now.
|
521
|
|
|
|
|
|
|
# Otherwise, the term is a variable, so we just have to put in Perl code
|
522
|
|
|
|
|
|
|
# that will convert it at runtime
|
523
|
|
|
|
|
|
|
# Note that dzip and zip fail *differently* on test.inf wrt signed numbers!
|
524
|
|
|
|
|
|
|
sub signed_word {
|
525
|
338
|
|
|
338
|
0
|
900
|
my ($self, $exp) = @_;
|
526
|
|
|
|
|
|
|
# $exp =~ s/^\((.*)\)$/$1/
|
527
|
|
|
|
|
|
|
# or die "Unexpected expression '$exp' to signed_word\n";
|
528
|
338
|
|
|
|
|
470
|
my $ret;
|
529
|
338
|
100
|
|
|
|
1223
|
if ($exp =~ /^\d+$/) {
|
530
|
90
|
100
|
|
|
|
308
|
$ret = $exp & 0x8000 ? $exp - 0x10000 : $exp;
|
531
|
|
|
|
|
|
|
} else {
|
532
|
|
|
|
|
|
|
# XXX Aha! $ret = "($exp-0x8000) % 0x10000 - 0x8000"
|
533
|
248
|
|
|
|
|
702
|
$ret = "unpack('s', pack('s', $exp))";
|
534
|
|
|
|
|
|
|
}
|
535
|
338
|
|
|
|
|
2178
|
return $ret;
|
536
|
|
|
|
|
|
|
}
|
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
# XXX might need to also explicity cast to unsigned
|
539
|
|
|
|
|
|
|
# when setting variables - see Games::Rezrov::StoryFile
|
540
|
|
|
|
|
|
|
sub unsigned_word {
|
541
|
0
|
|
|
0
|
0
|
0
|
return "unpack('S', pack('s', $_[1]))";
|
542
|
|
|
|
|
|
|
}
|
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
sub newlineify {
|
545
|
0
|
|
|
0
|
0
|
0
|
my $s = pop;
|
546
|
0
|
|
|
|
|
0
|
$s =~ s/\n/\\n/g;
|
547
|
0
|
|
|
|
|
0
|
return $s;
|
548
|
|
|
|
|
|
|
}
|
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
# Write memory to the file, as well as code to read it back
|
551
|
|
|
|
|
|
|
# (and to store original dynamic memory)
|
552
|
|
|
|
|
|
|
sub write_memory {
|
553
|
1
|
|
|
1
|
0
|
7
|
my ($self) = @_;
|
554
|
|
|
|
|
|
|
# Top of package
|
555
|
1
|
|
|
|
|
3
|
my $str = q(
|
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
{
|
558
|
|
|
|
|
|
|
package PlotzMemory;
|
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
use vars qw(@Memory);
|
561
|
|
|
|
|
|
|
my @Dynamic_Orig;
|
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
sub get_byte_at { $Memory[$_[0]] }
|
564
|
|
|
|
|
|
|
sub set_byte_at { $Memory[$_[0]] = $_[1] & 0xff; }
|
565
|
|
|
|
|
|
|
sub get_word_at { ($Memory[$_[0]] << 8) + $Memory[$_[0] + 1]; }
|
566
|
|
|
|
|
|
|
sub set_word_at {
|
567
|
|
|
|
|
|
|
$Memory[$_[0]] = $_[1]>>8;
|
568
|
|
|
|
|
|
|
$Memory[$_[0] + 1] = $_[1] & 0xff;
|
569
|
|
|
|
|
|
|
}
|
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
);
|
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
# change each byte to two hex digits
|
575
|
1
|
|
|
|
|
3
|
my $l = @Language::Zcode::Util::Memory;
|
576
|
1
|
|
|
|
|
4
|
my $flen = $Language::Zcode::Util::Constants{file_length}; # stated length
|
577
|
|
|
|
|
|
|
# Spec1.1: "Padding"
|
578
|
|
|
|
|
|
|
# The standard currently states that story file padding beyond the length
|
579
|
|
|
|
|
|
|
# specified in the header must be all zero bytes. Many Infocom story files
|
580
|
|
|
|
|
|
|
# in fact contain non-zero data in the padding, so interpreters must be
|
581
|
|
|
|
|
|
|
# sure to exclude the padding from checksum calculations.
|
582
|
1
|
|
|
|
|
2
|
my $hexed = "";
|
583
|
1
|
|
|
|
|
5
|
for (my $c = 0; $c < $l; $c+=16) {
|
584
|
|
|
|
|
|
|
# Add hex "line number" & \n's.
|
585
|
736
|
|
|
|
|
1552
|
my $len = $l - $c;
|
586
|
736
|
100
|
|
|
|
3122
|
$len = 16 if $len > 16;
|
587
|
736
|
|
|
|
|
9183
|
$hexed .= sprintf("%06x " . " %02x" x $len . "\n", $c,
|
588
|
|
|
|
|
|
|
@Language::Zcode::Util::Memory[$c .. $c + $len -1]);
|
589
|
|
|
|
|
|
|
}
|
590
|
|
|
|
|
|
|
# Actually, this is $#dynamic, not @dynamic
|
591
|
1
|
|
|
|
|
8
|
my $dynamic_size = $Language::Zcode::Util::Constants{static_memory_address} - 1;
|
592
|
1
|
|
|
|
|
346
|
$str .= <<"ENDUNPACK";
|
593
|
|
|
|
|
|
|
sub read_memory {
|
594
|
|
|
|
|
|
|
# (The map below removes address number and hexifies the other numbers)
|
595
|
|
|
|
|
|
|
my \$c = 0;
|
596
|
|
|
|
|
|
|
# Addr 0 1 2 3 4 5 6 7 8 9 a b c d e f
|
597
|
|
|
|
|
|
|
\@Memory = map {\$c++ % 17 ? hex : ()} qw(
|
598
|
|
|
|
|
|
|
$hexed
|
599
|
|
|
|
|
|
|
);
|
600
|
|
|
|
|
|
|
\@Dynamic_Orig = \@Memory[0 .. $dynamic_size];
|
601
|
|
|
|
|
|
|
}
|
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
sub checksum {
|
604
|
|
|
|
|
|
|
my \$header_size = 0x40; # don't count header bytes.
|
605
|
|
|
|
|
|
|
my \$sum = 0;
|
606
|
|
|
|
|
|
|
for (\@Dynamic_Orig[\$header_size .. $dynamic_size -1],
|
607
|
|
|
|
|
|
|
\@Memory[$dynamic_size .. $flen-1])
|
608
|
|
|
|
|
|
|
{
|
609
|
|
|
|
|
|
|
\$sum += \$_;
|
610
|
|
|
|
|
|
|
}
|
611
|
|
|
|
|
|
|
# 512K * 256 = 128M: definitely less than 2G max integer size for Perl.
|
612
|
|
|
|
|
|
|
# so we don't need to do mod within the for loop
|
613
|
|
|
|
|
|
|
\$sum = \$sum % 0x10000;
|
614
|
|
|
|
|
|
|
return \$sum;
|
615
|
|
|
|
|
|
|
}
|
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
sub get_dynamic_memory {
|
618
|
|
|
|
|
|
|
[\@Memory[0 .. $dynamic_size]];
|
619
|
|
|
|
|
|
|
}
|
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
sub get_orig_dynamic_memory {
|
622
|
|
|
|
|
|
|
[\@Dynamic_Orig];
|
623
|
|
|
|
|
|
|
}
|
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
my \$restore_mem_ref;
|
626
|
|
|
|
|
|
|
sub store_dynamic_memory {
|
627
|
|
|
|
|
|
|
\$restore_mem_ref = shift;
|
628
|
|
|
|
|
|
|
}
|
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Reset memory EXCEPT the couple bits that get saved even during a restart.
|
631
|
|
|
|
|
|
|
sub reset_dynamic_memory {
|
632
|
|
|
|
|
|
|
my \$restoring = shift;
|
633
|
|
|
|
|
|
|
Language::Zcode::Runtime::IO::store_restart_bits();
|
634
|
|
|
|
|
|
|
\@Memory[0 .. $dynamic_size] =
|
635
|
|
|
|
|
|
|
\$restoring ? \@\$restore_mem_ref : \@Dynamic_Orig;
|
636
|
|
|
|
|
|
|
}
|
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
} # End package PlotzMemory
|
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
ENDUNPACK
|
641
|
|
|
|
|
|
|
|
642
|
1
|
|
|
|
|
4178
|
return $str;
|
643
|
|
|
|
|
|
|
}
|
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# This functionality is supplied by the "use Language::Zcode::Runtime" at the
|
646
|
|
|
|
|
|
|
# top of the program (written in program_start)
|
647
|
|
|
|
|
|
|
sub library {
|
648
|
1
|
|
|
1
|
0
|
10
|
return "";
|
649
|
|
|
|
|
|
|
}
|
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
1;
|