-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathmojozork.c
2159 lines (1844 loc) · 67.6 KB
/
mojozork.c
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
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* MojoZork; a simple, just-for-fun implementation of Infocom's Z-Machine.
*
* Please see the file LICENSE.txt in the source's root directory.
*
* This file written by Ryan C. Gordon.
*/
// The Z-Machine specifications 1.1:
// https://inform-fiction.org/zmachine/standards/z1point1/index.html
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <time.h>
#define MOJOZORK_DEBUGGING 0
static inline void dbg(const char *fmt, ...)
{
#if MOJOZORK_DEBUGGING
va_list ap;
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
#endif
} // dbg
#if !MOJOZORK_DEBUGGING
#define FIXME(what)
#else
#define FIXME(what) { \
static int seen = 0; \
if (!seen) { \
seen = 1; \
dbg("FIXME: %s\n", what); \
} \
}
#endif
// the "_t" drives me nuts. :/
typedef uint8_t uint8;
typedef int8_t sint8;
typedef uint16_t uint16;
typedef int16_t sint16;
typedef uint32_t uint32;
typedef int32_t sint32;
typedef uint64_t uint64;
typedef int64_t sint64;
typedef size_t uintptr;
// !!! FIXME: maybe kill these.
#define READUI8(ptr) *(ptr++)
#define READUI16(ptr) ((((uint16) ptr[0]) << 8) | ((uint16) ptr[1])); ptr += sizeof (uint16)
#define WRITEUI16(dst, src) { *(dst++) = (uint8) ((src >> 8) & 0xFF); *(dst++) = (uint8) (src & 0xFF); }
typedef void (*OpcodeFn)(void);
typedef struct
{
const char *name;
OpcodeFn fn;
} Opcode;
typedef struct ZHeader
{
uint8 version;
uint8 flags1;
uint16 release;
uint16 himem_addr;
uint16 pc_start; // in ver6, packed address of main()
uint16 dict_addr;
uint16 objtab_addr;
uint16 globals_addr;
uint16 staticmem_addr; // offset of static memory, also: size of dynamic mem.
uint16 flags2;
char serial_code[7]; // six ASCII chars in ver2. In ver3+: ASCII of completion date: YYMMDD
uint16 abbrtab_addr; // abbreviations table
uint16 story_len;
uint16 story_checksum;
// !!! FIXME: more fields here, all of which are ver4+
} ZHeader;
typedef struct ZMachineState
{
uint32 instructions_run;
uint8 *story;
uintptr story_len;
ZHeader header;
uint32 logical_pc;
const uint8 *pc; // program counter
uint16 *sp; // stack pointer
uint16 bp; // base pointer
int quit;
int step_completed; // possibly time to break out of the Z-Machine simulation loop.
uint16 stack[2048]; // !!! FIXME: make this dynamic?
uint16 operands[8];
uint8 operand_count;
char alphabet_table[78];
const char *startup_script;
char *story_filename;
int status_bar_enabled;
char *status_bar;
uintptr status_bar_len;
uint16 current_window;
uint16 upper_window_line_count; // if 0, there is no window split.
// this is kinda wasteful (we could pack the 89 opcodes in their various forms
// into separate arrays and strip off the metadata bits) but it simplifies
// some things to just have a big linear array.
Opcode opcodes[256];
// The extended ones, however, only have one form, so we pack that tight.
Opcode extended_opcodes[30];
void (*split_window)(const uint16 oldval, const uint16 newval);
void (*set_window)(const uint16 oldval, const uint16 newval);
void (*writestr)(const char *str, const uintptr slen);
#if defined(__GNUC__) || defined(__clang__)
void (*die)(const char *fmt, ...) __attribute__((noreturn));
#else
void (*die)(const char *fmt, ...);
#endif
} ZMachineState;
static ZMachineState *GState = NULL;
static uint8 *get_virtualized_mem_ptr(const uint16 offset);
static uint16 remap_objectid(const uint16 objid);
#ifndef MULTIZORK
static uint8 *get_virtualized_mem_ptr(const uint16 offset) { return GState->story + offset; }
static uint16 remap_objectid(const uint16 objid) { return objid; }
#endif
// The Z-Machine can't directly address 32-bits, but this needs to expand past 16 bits when we multiply by 2, 4, or 8, etc.
static uint8 *unpackAddress(const uint32 addr)
{
if (GState->header.version <= 3)
return (GState->story + (addr * 2));
else if (GState->header.version <= 5)
return (GState->story + (addr * 4));
else if (GState->header.version <= 6)
GState->die("write me"); // 4P + 8R_O Versions 6 and 7, for routine calls ... or 4P + 8S_O Versions 6 and 7, for print_paddr
else if (GState->header.version <= 8)
return (GState->story + (addr * 8));
GState->die("FIXME Unsupported version for packed addressing");
return NULL;
} // unpackAddress
static uint8 *varAddress(const uint8 var, const int writing)
{
if (var == 0) // top of stack
{
if (writing)
{
if ((GState->sp-GState->stack) >= (sizeof (GState->stack) / sizeof (GState->stack[0])))
GState->die("Stack overflow");
dbg("push stack\n");
return (uint8 *) GState->sp++;
} // if
else
{
if (GState->sp == GState->stack)
GState->die("Stack underflow"); // nothing on the stack at all?
const uint16 numlocals = GState->bp ? GState->stack[GState->bp-1] : 0;
if ((GState->bp + numlocals) >= (GState->sp-GState->stack))
GState->die("Stack underflow"); // no stack data left in this frame.
dbg("pop stack\n");
return (uint8 *) --GState->sp;
} // else
} // if
else if ((var >= 0x1) && (var <= 0xF)) // local var.
{
if (GState->stack[GState->bp-1] <= (var-1))
GState->die("referenced unallocated local var #%u (%u available)", (unsigned int) (var-1), (unsigned int) GState->stack[GState->bp-1]);
return (uint8 *) &GState->stack[GState->bp + (var-1)];
} // else if
// else, global var
FIXME("check for overflow, etc");
return (GState->story + GState->header.globals_addr) + ((var-0x10) * sizeof (uint16));
} // varAddress
static void opcode_call(void)
{
uint8 args = GState->operand_count;
const uint16 *operands = GState->operands;
const uint8 storeid = *(GState->pc++);
// no idea if args==0 should be the same as calling addr 0...
if ((args == 0) || (operands[0] == 0)) // legal no-op; store 0 to return value and bounce.
{
uint8 *store = varAddress(storeid, 1);
WRITEUI16(store, 0);
} // if
else
{
const uint8 *routine = unpackAddress(operands[0]);
GState->logical_pc = (uint32) (routine - GState->story);
const uint8 numlocals = *(routine++);
if (numlocals > 15)
GState->die("Routine has too many local variables (%u)", numlocals);
FIXME("check for stack overflow here");
*(GState->sp++) = (uint16) storeid; // save where we should store the call's result.
// next instruction to run upon return.
const uint32 pcoffset = (uint32) (GState->pc - GState->story);
*(GState->sp++) = (pcoffset & 0xFFFF);
*(GState->sp++) = ((pcoffset >> 16) & 0xFFFF);
*(GState->sp++) = GState->bp; // current base pointer before the call.
*(GState->sp++) = numlocals; // number of locals we're allocating.
GState->bp = (uint16) (GState->sp-GState->stack);
sint8 i;
if (GState->header.version <= 4)
{
for (i = 0; i < numlocals; i++, routine += sizeof (uint16))
*(GState->sp++) = *((uint16 *) routine); // leave it byteswapped when moving to the stack.
} // if
else
{
for (i = 0; i < numlocals; i++)
*(GState->sp++) = 0;
} // else
args--; // remove the return address from the count.
if (args > numlocals) // it's legal to have more args than locals, throw away the extras.
args = numlocals;
const uint16 *src = operands + 1;
uint8 *dst = (uint8 *) (GState->stack + GState->bp);
for (i = 0; i < args; i++)
{
WRITEUI16(dst, src[i]);
} // for
GState->pc = routine;
// next call to runInstruction() will execute new routine.
} // else
} // opcode_call
static void doReturn(const uint16 val)
{
FIXME("newer versions start in a real routine, but still aren't allowed to return from it.");
if (GState->bp == 0)
GState->die("Stack underflow in return operation");
dbg("popping stack for return\n");
dbg("returning: initial pc=%X, bp=%u, sp=%u\n", (unsigned int) (GState->pc-GState->story), (unsigned int) GState->bp, (unsigned int) (GState->sp-GState->stack));
GState->sp = GState->stack + GState->bp; // this dumps all the locals and data pushed on the stack during the routine.
GState->sp--; // dump our copy of numlocals
GState->bp = *(--GState->sp); // restore previous frame's base pointer, dump it from the stack.
GState->sp -= 2; // point to start of our saved program counter.
const uint32 pcoffset = ((uint32) GState->sp[0]) | (((uint32) GState->sp[1]) << 16);
GState->pc = GState->story + pcoffset; // next instruction is one following our original call.
const uint8 storeid = (uint8) *(--GState->sp); // pop the result storage location.
dbg("returning: new pc=%X, bp=%u, sp=%u\n", (unsigned int) (GState->pc-GState->story), (unsigned int) GState->bp, (unsigned int) (GState->sp-GState->stack));
uint8 *store = varAddress(storeid, 1); // and store the routine result.
WRITEUI16(store, val);
} // doReturn
static void opcode_ret(void)
{
doReturn(GState->operands[0]);
} // opcode_ret
static void opcode_rtrue(void)
{
doReturn(1);
} // opcode_rtrue
static void opcode_rfalse(void)
{
doReturn(0);
} // opcode_rfalse
static void opcode_ret_popped(void)
{
uint8 *ptr = varAddress(0, 0); // top of stack.
const uint16 result = READUI16(ptr);
doReturn(result);
} // opcode_ret_popped
static void opcode_push(void)
{
uint8 *store = varAddress(0, 1); // top of stack.
WRITEUI16(store, GState->operands[0]);
} // opcode_push
static void opcode_pull(void)
{
const uint8 *ptr = varAddress(0, 0); // top of stack.
const uint16 val = READUI16(ptr);
uint8 *store = varAddress((uint8) GState->operands[0], 1);
WRITEUI16(store, val);
} // opcode_pull
static void opcode_pop(void)
{
varAddress(0, 0); // this causes a pop.
} // opcode_pop
static void updateStatusBar(void);
static void opcode_show_status(void)
{
updateStatusBar();
} // opcode_show_status
static void opcode_add(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const sint16 result = ((sint16) GState->operands[0]) + ((sint16) GState->operands[1]);
WRITEUI16(store, result);
} // opcode_add
static void opcode_sub(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const sint16 result = ((sint16) GState->operands[0]) - ((sint16) GState->operands[1]);
WRITEUI16(store, result);
} // opcode_sub
static void doBranch(int truth)
{
const uint8 branch = *(GState->pc++);
const int farjump = (branch & (1<<6)) == 0;
const int onTruth = (branch & (1<<7)) ? 1 : 0;
const uint8 byte2 = farjump ? *(GState->pc++) : 0;
if (truth == onTruth) // take the branch?
{
sint16 offset = (sint16) (branch & 0x3F);
if (farjump)
{
if (offset & (1 << 5))
offset |= 0xC0; // extend out sign bit.
offset = (offset << 8) | ((sint16) byte2);
} // else
if (offset == 0) // return false from current routine.
doReturn(0);
else if (offset == 1) // return true from current routine.
doReturn(1);
else
GState->pc = (GState->pc + offset) - 2; // branch.
} // if
} // doBranch
static void opcode_je(void)
{
const uint16 a = GState->operands[0];
sint8 i;
for (i = 1; i < GState->operand_count; i++)
{
if (a == GState->operands[i])
{
doBranch(1);
return;
} // if
} // for
doBranch(0);
} // opcode_je
static void opcode_jz(void)
{
doBranch((GState->operands[0] == 0) ? 1 : 0);
} // opcode_jz
static void opcode_jl(void)
{
doBranch((((sint16) GState->operands[0]) < ((sint16) GState->operands[1])) ? 1 : 0);
} // opcode_jl
static void opcode_jg(void)
{
doBranch((((sint16) GState->operands[0]) > ((sint16) GState->operands[1])) ? 1 : 0);
} // opcode_jg
static void opcode_test(void)
{
doBranch((GState->operands[0] & GState->operands[1]) == GState->operands[1]);
} // opcode_test
static void opcode_jump(void)
{
// this opcode is not a branch instruction, and doesn't follow those rules.
FIXME("make sure GState->pc is valid");
GState->pc = (GState->pc + ((sint16) GState->operands[0])) - 2;
} // opcode_jump
static void opcode_div(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
if (GState->operands[1] == 0)
GState->die("Division by zero");
const uint16 result = (uint16) (((sint16) GState->operands[0]) / ((sint16) GState->operands[1]));
WRITEUI16(store, result);
} // opcode_div
static void opcode_mod(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
if (GState->operands[1] == 0)
GState->die("Division by zero");
const uint16 result = (uint16) (((sint16) GState->operands[0]) % ((sint16) GState->operands[1]));
WRITEUI16(store, result);
} // opcode_div
static void opcode_mul(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = (uint16) (((sint16) GState->operands[0]) * ((sint16) GState->operands[1]));
WRITEUI16(store, result);
} // opcode_mul
static void opcode_or(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = (GState->operands[0] | GState->operands[1]);
WRITEUI16(store, result);
} // opcode_or
static void opcode_and(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = (GState->operands[0] & GState->operands[1]);
WRITEUI16(store, result);
} // opcode_and
static void opcode_not(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = ~GState->operands[0];
WRITEUI16(store, result);
} // opcode_not
static void opcode_inc_chk(void)
{
uint8 *store = varAddress((uint8) GState->operands[0], 1);
sint16 val = READUI16(store);
store -= sizeof (uint16);
val++;
WRITEUI16(store, (uint16) val);
doBranch( (((sint16) val) > ((sint16) GState->operands[1])) ? 1 : 0 );
} // opcode_inc_chk
static void opcode_inc(void)
{
uint8 *store = varAddress((uint8) GState->operands[0], 1);
sint16 val = (sint16) READUI16(store);
store -= sizeof (uint16);
val++;
WRITEUI16(store, (uint16) val);
} // opcode_inc
static void opcode_dec_chk(void)
{
uint8 *store = varAddress((uint8) GState->operands[0], 1);
sint16 val = (sint16) READUI16(store);
store -= sizeof (uint16);
val--;
WRITEUI16(store, (uint16) val);
doBranch( (((sint16) val) < ((sint16) GState->operands[1])) ? 1 : 0 );
} // opcode_dec_chk
static void opcode_dec(void)
{
uint8 *store = varAddress((uint8) GState->operands[0], 1);
sint16 val = (sint16) READUI16(store);
store -= sizeof (uint16);
val--;
WRITEUI16(store, (uint16) val);
} // opcode_dec
static void opcode_load(void)
{
const uint8 *valptr = varAddress((uint8) (GState->operands[0] & 0xFF), 0);
const uint16 val = READUI16(valptr);
uint8 *store = varAddress(*(GState->pc++), 1);
WRITEUI16(store, val);
} // opcode_load
static void opcode_loadw(void)
{
uint16 *store = (uint16 *) varAddress(*(GState->pc++), 1);
FIXME("can only read from dynamic or static memory (not highmem).");
FIXME("how does overflow work here? Do these wrap around?");
const uint16 offset = (GState->operands[0] + (GState->operands[1] * 2));
const uint16 *src = (const uint16 *) get_virtualized_mem_ptr(offset);
*store = *src; // copy from bigendian to bigendian: no byteswap.
} // opcode_loadw
static void opcode_loadb(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
FIXME("can only read from dynamic or static memory (not highmem).");
FIXME("how does overflow work here? Do these wrap around?");
const uint16 offset = (GState->operands[0] + GState->operands[1]);
const uint8 *src = get_virtualized_mem_ptr(offset);
const uint16 value = *src; // expand out to 16-bit before storing.
WRITEUI16(store, value);
} // opcode_loadb
static void opcode_storew(void)
{
FIXME("can only write to dynamic memory.");
FIXME("how does overflow work here? Do these wrap around?");
const uint16 offset = (GState->operands[0] + (GState->operands[1] * 2));
uint8 *dst = get_virtualized_mem_ptr(offset);
const uint16 src = GState->operands[2];
WRITEUI16(dst, src);
} // opcode_storew
static void opcode_storeb(void)
{
FIXME("can only write to dynamic memory.");
FIXME("how does overflow work here? Do these wrap around?");
const uint16 offset = (GState->operands[0] + GState->operands[1]);
uint8 *dst = get_virtualized_mem_ptr(offset);
const uint8 src = (uint8) GState->operands[2];
*dst = src;
} // opcode_storeb
static void opcode_store(void)
{
uint8 *store = varAddress((uint8) (GState->operands[0] & 0xFF), 1);
const uint16 src = GState->operands[1];
WRITEUI16(store, src);
} // opcode_store
static uint8 *getObjectPtr(const uint16 objid);
#ifndef MULTIZORK
static uint8 *getObjectPtr(const uint16 objid)
{
if (objid == 0)
GState->die("Object id #0 referenced");
if ((GState->header.version <= 3) && (objid > 255))
GState->die("Invalid object id referenced");
uint8 *ptr = GState->story + GState->header.objtab_addr;
ptr += 31 * sizeof (uint16); // skip properties defaults table
ptr += 9 * (objid-1); // find object in object table
return ptr;
} // getObjectPtr
#endif
static void opcode_test_attr(void)
{
const uint16 objid = GState->operands[0];
const uint16 attrid = GState->operands[1];
uint8 *ptr = getObjectPtr(objid);
if (GState->header.version <= 3)
{
ptr += (attrid / 8);
doBranch((*ptr & (0x80 >> (attrid & 7))) ? 1 : 0);
} // if
else
{
GState->die("write me");
} // else
} // opcode_test_attr
static void opcode_set_attr(void)
{
const uint16 objid = GState->operands[0];
const uint16 attrid = GState->operands[1];
uint8 *ptr = getObjectPtr(objid);
if (GState->header.version <= 3)
{
ptr += (attrid / 8);
*ptr |= 0x80 >> (attrid & 7);
} // if
else
{
GState->die("write me");
} // else
} // opcode_set_attr
static void opcode_clear_attr(void)
{
const uint16 objid = GState->operands[0];
const uint16 attrid = GState->operands[1];
uint8 *ptr = objid ? getObjectPtr(objid) : NULL;
if (ptr == NULL) {
return; // Zork 1 will trigger this on "go X" where "x" isn't a direction, so ignore it.
}
if (GState->header.version <= 3)
{
ptr += (attrid / 8);
*ptr &= ~(0x80 >> (attrid & 7));
} // if
else
{
GState->die("write me");
} // else
} // opcode_clear_attr
static uint8 *getObjectPtrParent(const uint8 *objptr)
{
if (GState->header.version <= 3)
{
const uint16 parent = objptr[4];
return parent ? getObjectPtr(parent) : NULL;
}
else
{
GState->die("write me");
return NULL;
} // else
} // getGetObjectPtrParent
static void unparentObject(const uint16 _objid)
{
const uint16 objid = remap_objectid(_objid);
uint8 *objptr = getObjectPtr(objid);
uint8 *parentptr = getObjectPtrParent(objptr);
if (parentptr != NULL) // if NULL, no need to remove it.
{
uint8 *ptr = parentptr + 6; // 4 to skip attrs, 2 to skip to child.
while (*ptr != objid) // if not direct child, look through sibling list...
ptr = getObjectPtr(*ptr) + 5; // get sibling field.
*ptr = *(objptr + 5); // obj sibling takes obj's place.
} // if
} // unparentObject
static void opcode_insert_obj(void)
{
const uint16 objid = remap_objectid(GState->operands[0]);
const uint16 dstid = remap_objectid(GState->operands[1]);
uint8 *objptr = getObjectPtr(objid);
uint8 *dstptr = getObjectPtr(dstid);
if (GState->header.version <= 3)
{
unparentObject(objid); // take object out of its original tree first.
// now reinsert in the right place.
*(objptr + 4) = (uint8) dstid; // parent field: new destination
*(objptr + 5) = *(dstptr + 6); // sibling field: new dest's old child.
*(dstptr + 6) = (uint8) objid; // dest's child field: object being moved.
} // if
else
{
GState->die("write me"); // fields are different in ver4+.
} // else
} // opcode_insert_obj
static void opcode_remove_obj(void)
{
const uint16 objid = GState->operands[0];
uint8 *objptr = getObjectPtr(objid);
if (GState->header.version > 3)
GState->die("write me"); // fields are different in ver4+.
else
{
unparentObject(objid); // take object out of its original tree first.
// now clear out object's relationships...
*(objptr + 4) = 0; // parent field: zero.
*(objptr + 5) = 0; // sibling field: zero.
} // else
} // opcode_remove_obj
static uint8 *getObjectProperty(const uint16 objid, const uint32 propid, uint8 *_size);
#ifndef MULTIZORK
static uint8 *getObjectProperty(const uint16 objid, const uint32 propid, uint8 *_size)
{
uint8 *ptr = getObjectPtr(objid);
if (GState->header.version <= 3)
{
ptr += 7; // skip to properties address field.
const uint16 addr = READUI16(ptr);
ptr = GState->story + addr;
ptr += (*ptr * 2) + 1; // skip object name to start of properties.
while (1)
{
const uint8 info = *(ptr++);
const uint16 num = (info & 0x1F); // 5 bits for the prop id.
const uint8 size = ((info >> 5) & 0x7) + 1; // 3 bits for prop size.
// these go in descending numeric order, and should fail
// the interpreter if missing. We use 0xFFFFFFFF internally to mean "first property".
if ((num == propid) || (propid == 0xFFFFFFFF)) // found it?
{
if (_size)
*_size = size;
return ptr;
} // if
else if (num < propid) // we're past it.
break;
ptr += size; // try the next property.
} // while
} // if
else
{
GState->die("write me");
} // else
return NULL;
} // getObjectProperty
#endif
// this returns the zscii string for the object!
static const uint8 *getObjectShortName(const uint16 objid)
{
const uint8 *ptr = getObjectPtr(objid);
if (GState->header.version <= 3)
{
ptr += 7; // skip to properties address field.
const uint16 addr = READUI16(ptr);
return GState->story + addr + 1; // +1 to skip z-char count.
} // if
else
{
GState->die("write me");
} // else
return NULL;
} // getObjectShortName
static void opcode_put_prop(void)
{
const uint16 objid = GState->operands[0];
const uint16 propid = GState->operands[1];
const uint16 value = GState->operands[2];
uint8 size = 0;
uint8 *ptr = getObjectProperty(objid, propid, &size);
if (!ptr)
GState->die("Lookup on missing object property (obj=%X, prop=%X)", (unsigned int) objid, (unsigned int) propid);
else if (size == 1)
*ptr = (value & 0xFF);
else
{
WRITEUI16(ptr, value);
} // else
} // opcode_put_prop
static uint16 getDefaultObjectProperty(const uint16 propid)
{
if ( ((GState->header.version <= 3) && (propid > 31)) ||
((GState->header.version >= 4) && (propid > 63)) )
{
FIXME("Should we die here?");
return 0;
} // if
const uint8 *values = (GState->story + GState->header.objtab_addr);
values += (propid-1) * sizeof (uint16);
const uint16 result = READUI16(values);
return result;
} // getDefaultObjectProperty
static void opcode_get_prop(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 objid = GState->operands[0];
const uint16 propid = GState->operands[1];
uint16 result = 0;
uint8 size = 0;
uint8 *ptr = getObjectProperty(objid, propid, &size);
if (!ptr)
result = getDefaultObjectProperty(propid);
else if (size == 1)
result = *ptr;
else
{
result = READUI16(ptr);
} // else
WRITEUI16(store, result);
} // opcode_get_prop
static void opcode_get_prop_addr(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 objid = GState->operands[0];
const uint16 propid = GState->operands[1];
uint8 *ptr = getObjectProperty(objid, propid, NULL);
const uint16 result = ptr ? ((uint16) (ptr-GState->story)) : 0;
WRITEUI16(store, result);
} // opcode_get_prop_addr
static void opcode_get_prop_len(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
uint16 result;
if (GState->operands[0] == 0)
result = 0; // this must return 0, to avoid a bug in older Infocom games.
else if (GState->header.version <= 3)
{
const uint16 offset = GState->operands[0];
const uint8 *ptr = get_virtualized_mem_ptr(offset);
const uint8 info = ptr[-1]; // the size field.
result = ((info >> 5) & 0x7) + 1; // 3 bits for prop size.
} // if
else
{
GState->die("write me");
} // else
WRITEUI16(store, result);
} // opcode_get_prop_len
static void opcode_get_next_prop(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 objid = GState->operands[0];
const int firstProp = (GState->operands[1] == 0);
uint16 result = 0;
uint8 size = 0;
uint8 *ptr = getObjectProperty(objid, firstProp ? 0xFFFFFFFF : GState->operands[1], &size);
if (!ptr)
GState->die("get_next_prop on missing property obj=%X, prop=%X", (unsigned int) objid, (unsigned int) GState->operands[1]);
else if (GState->header.version <= 3)
result = ptr[firstProp ? -1 : ((sint8) size)] & 0x1F; // 5 bits for the prop id.
else
GState->die("write me");
WRITEUI16(store, result);
} // opcode_get_next_prop
static void opcode_jin(void)
{
const uint16 objid = GState->operands[0];
const uint16 parentid = GState->operands[1];
const uint8 *objptr = objid ? getObjectPtr(objid) : NULL;
if (objptr == NULL) {
return; // Zork 1 will trigger this on "go X" where "x" isn't a direction.
}
if (GState->header.version <= 3)
doBranch((((uint16) objptr[4]) == parentid) ? 1 : 0);
else
GState->die("write me"); // fields are different in ver4+.
} // opcode_jin
static uint16 getObjectRelationship(const uint16 objid, const uint8 relationship)
{
const uint8 *objptr = getObjectPtr(objid);
if (GState->header.version <= 3)
return objptr[relationship];
else
GState->die("write me"); // fields are different in ver4+.
return 0;
} // getObjectRelationship
static void opcode_get_parent(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = getObjectRelationship(GState->operands[0], 4);
WRITEUI16(store, result);
} // opcode_get_parent
static void opcode_get_sibling(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = getObjectRelationship(GState->operands[0], 5);
WRITEUI16(store, result);
doBranch((result != 0) ? 1: 0);
} // opcode_get_sibling
static void opcode_get_child(void)
{
uint8 *store = varAddress(*(GState->pc++), 1);
const uint16 result = getObjectRelationship(GState->operands[0], 6);
WRITEUI16(store, result);
doBranch((result != 0) ? 1: 0);
} // opcode_get_child
static void opcode_new_line(void)
{
GState->writestr("\n", 1);
} // opcode_new_line
static char decode_zscii_char(const uint16 val)
{
char ch = 0;
FIXME("ver6+ has a few more valid codes");
// only a few values are valid ZSCII codes for output.
if ((val >= 32) && (val <= 126))
ch = (char) val; // FIXME: we assume you have an ASCII terminal for now.
else if (val == 13) // newline
ch = '\n';
else if (val == 0)
/* val==0 is "valid" but produces no output. */ ;
else if ((val >= 155) && (val <= 251))
{ FIXME("write me: extended ZSCII characters"); ch = '?'; }
else
ch = '?'; // this is illegal, but we'll be nice.
return ch;
} // decode_zscii_char
static uintptr decode_zscii(const uint8 *_str, const int abbr, char *buf, uintptr *_buflen)
{
// ZCSII encoding is so nasty.
uintptr buflen = *_buflen;
uintptr decoded_chars = 0;
const uint8 *str = _str;
uint16 code = 0;
uint8 alphabet = 0;
uint8 useAbbrTable = 0;
uint8 zscii_collector = 0;
uint16 zscii_code = 0;
do
{
code = READUI16(str);
// characters are 5 bits each, packed three to a 16-bit word.
sint8 i;
for (i = 10; i >= 0; i -= 5)
{
int newshift = 0;
char printVal = 0;
const uint8 ch = ((code >> i) & 0x1F);
if (zscii_collector)
{
if (zscii_collector == 2)
zscii_code |= ((uint16) ch) << 5;
else
zscii_code |= ((uint16) ch);
zscii_collector--;
if (!zscii_collector)
{
printVal = decode_zscii_char(zscii_code);
if (printVal)
{
decoded_chars++;
if (buflen)
{
*(buf++) = printVal;
buflen--;
} // if
} // if
alphabet = useAbbrTable = 0;
zscii_code = 0;
} // if
continue;
} // if
else if (useAbbrTable)
{
if (abbr)
GState->die("Abbreviation strings can't use abbreviations");
//FIXME("Make sure offset is sane");
const uintptr index = ((32 * (((uintptr) useAbbrTable) - 1)) + (uintptr) ch);
const uint8 *ptr = (GState->story + GState->header.abbrtab_addr) + (index * sizeof (uint16));
const uint16 abbraddr = READUI16(ptr);
uintptr abbr_decoded_chars = buflen;
decode_zscii(GState->story + (abbraddr * sizeof (uint16)), 1, buf, &abbr_decoded_chars);
decoded_chars += abbr_decoded_chars;
buf += (buflen < abbr_decoded_chars) ? buflen : abbr_decoded_chars;
buflen = (buflen < abbr_decoded_chars) ? 0 : (buflen - abbr_decoded_chars);
useAbbrTable = 0;
alphabet = 0; // FIXME: no shift locking in ver3+, but ver1 needs it.
continue;
} // if