aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/glulxe/glulxe.h
blob: eacc51fd56c8e3269133668f7d79584776684b1a (plain)
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
/* 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.
 *
 */

#ifndef GLK_GLULXE
#define GLK_GLULXE

#include "common/scummsys.h"
#include "glk/glk_api.h"
#include "glk/glulxe/glulxe_types.h"

namespace Glk {
namespace Glulxe {

/**
 * Glulxe game interpreter
 */
class Glulxe : public GlkAPI {
public:
	bool vm_exited_cleanly;
	strid_t gamefile;
	uint gamefile_start, gamefile_len;
	char *init_err, *init_err2;

	unsigned char *memmap;
	unsigned char *stack;

	uint ramstart;
	uint endgamefile;
	uint origendmem;
	uint stacksize;
	uint startfuncaddr;
	uint checksum;
	uint stackptr;
	uint frameptr;
	uint pc;
	uint origstringtable;
	uint stringtable;
	uint valstackbase;
	uint localsbase;
	uint endmem;
	uint protectstart, protectend;
	uint prevpc;

	void (*Glulxe::stream_char_handler)(unsigned char ch);
	void (*Glulxe::stream_unichar_handler)(uint ch);

	/**
	 * \defgroup accel fields
	 * @{
	 */

	uint classes_table;     ///< class object array
	uint indiv_prop_start;  ///< first individual prop ID
	uint class_metaclass;   ///< "Class" class object
	uint object_metaclass;  ///< "Object" class object
	uint routine_metaclass; ///< "Routine" class object
	uint string_metaclass;  ///< "String" class object
	uint self;              ///< address of global "self"
	uint num_attr_bytes;    ///< number of attributes / 8
	uint cpv__start;        ///< array of common prop defaults
	accelentry_t **accelentries;

	/**@}*/

	/**
	 * \defgroup heap fields
	 * @{
	 */

	uint heap_start = 0;	///< zero for inactive heap
	int alloc_count = 0;

	/* The heap_head/heap_tail is a doubly-linked list of blocks, both
	   free and allocated. It is kept in address order. It should be
	   complete -- that is, the first block starts at heap_start, and each
	   block ends at the beginning of the next block, until the last one,
	   which ends at endmem.

	   (Heap_start is never the same as end_mem; if there is no heap space,
	   then the heap is inactive and heap_start is zero.)

	   Adjacent free blocks may be merged at heap_alloc() time.

	   ### To make alloc more efficient, we could keep a separate
	   free-list. To make free more efficient, we could keep a hash
	   table of allocations.
	 */
	heapblock_t *heap_head = NULL;
	heapblock_t *heap_tail = NULL;

	/**@}*/
protected:
	/**
	 * \defgroup glkop fields
	 * @{
	 */

	/**
	 * The library_select_hook is called every time the VM blocks for input.
	 * The app might take this opportunity to autosave, for example.
	 */
	void (*library_select_hook)(uint);

	arrayref_t *arrays;

	/**
	 * The list of hash tables, for the classes.
	 */
	int num_classes;
	classtable_t **classes;

	/**@}*/
protected:
	/**
	 * \defgroup accel support methods
	 * @{
	 */

	void accel_error(const char *msg);
	uint func_1_z__region(uint argc, uint *argv);

	/**
	 * The old set of accel functions (2 through 7) are deprecated; they behave badly if the Inform 6
	 * NUM_ATTR_BYTES option (parameter 7) is changed from its default value (7). They will not be removed,
	 * but new games should use functions 8 through 13 instead
	 */
	uint func_2_cp__tab(uint argc, uint *argv);
	uint func_3_ra__pr(uint argc, uint *argv);
	uint func_4_rl__pr(uint argc, uint *argv);
	uint func_5_oc__cl(uint argc, uint *argv);
	uint func_6_rv__pr(uint argc, uint *argv);
	uint func_7_op__pr(uint argc, uint *argv);

	/**
	 * Here are the newer functions, which support changing NUM_ATTR_BYTES.
	   These call get_prop_new() instead of get_prop()
	 */
	uint func_8_cp__tab(uint argc, uint *argv);
	uint func_9_ra__pr(uint argc, uint *argv);
	uint func_10_rl__pr(uint argc, uint *argv);
	uint func_11_oc__cl(uint argc, uint *argv);
	uint func_12_rv__pr(uint argc, uint *argv);
	uint func_13_op__pr(uint argc, uint *argv);
	int obj_in_class(uint obj);

	/**
	 * Look up a property entry.
	 */
	uint get_prop(uint obj, uint id);

	/**
	 * Look up a property entry. This is part of the newer set of accel functions (8 through 13),
	 * which support increasing NUM_ATTR_BYTES. It is identical to get_prop() except that it calls
	 * the new versions of func_5 and func_2
	 */
	uint get_prop_new(uint obj, uint id);

	 /**@}*/

	/**
	 * \defgroup glkop support methods
	 * @{
	 */

	/**
	 * Build a hash table to hold a set of Glk objects.
	 */
	classtable_t *new_classtable(uint firstid);

	/**
	 * Find a Glk object in the appropriate hash table.
	 */
	void *classes_get(int classid, uint objid);

	/**
	 * Put a Glk object in the appropriate hash table. If origid is zero, invent a new
	 * unique ID for it.
	 */
	classref_t *classes_put(int classid, void *obj, uint origid);

	/**
	 * Delete a Glk object from the appropriate hash table.
	 */
	void classes_remove(int classid, void *obj);

	long glulxe_array_locate(void *array, uint len, char *typecode, gidispatch_rock_t objrock, int *elemsizeref);
	gidispatch_rock_t glulxe_array_restore(long bufkey, uint len, char *typecode, void **arrayref);

	char *grab_temp_c_array(uint addr, uint len, int passin);
	void release_temp_c_array(char *arr, uint addr, uint len, int passout);
	uint *grab_temp_i_array(uint addr, uint len, int passin);
	void release_temp_i_array(uint *arr, uint addr, uint len, int passout);
	void **grab_temp_ptr_array(uint addr, uint len, int objclass, int passin);
	void release_temp_ptr_array(void **arr, uint addr, uint len, int objclass, int passout);

	/**
	 * This reads through the prototype string, and pulls Floo objects off the stack. It also works out the maximal number
	 * of gluniversal_t objects which could be used by the Glk call in question. It then allocates space for them.
	 */
	void prepare_glk_args(const char *proto, dispatch_splot_t *splot);

	/**
	 * This long and unpleasant function translates a set of Floo objects into a gluniversal_t array. It's recursive, too,
	 * to deal with structures.
	 */
	void parse_glk_args(dispatch_splot_t *splot, const char **proto, int depth, int *argnumptr, uint subaddress, int subpassin);
		
	/**
	 * This is about the reverse of parse_glk_args().
	 */
	void unparse_glk_args(dispatch_splot_t *splot, const char **proto, int depth,
		int *argnumptr, uint subaddress, int subpassout);

	/**
	 * Create a string identifying this game. We use the first 64 bytes of the memory map, encoded as hex,
	 */
	char *get_game_id();

	/**@}*/
public:
	/**
	 * Constructor
	 */
	Glulxe(OSystem *syst, const GlkGameDescription &gameDesc);

	/**
	 * Run the game
	 */
	void runGame();

	/**
	 * Returns the running interpreter type
	 */
	virtual InterpreterType getInterpreterType() const override { return INTERPRETER_GLULXE; }

	/**
	 * Load a savegame from the passed stream
	 */
	virtual Common::Error loadGameData(strid_t file) override;

	/**
	 * Save the game to the passed stream
	 */
	virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override;

	/**
	 * \defgroup Main access methods
	 * @{
	 */
	void set_library_start_hook(void(*)(void));
	void set_library_autorestore_hook(void(*)(void));

	/**
	 * Display an error in the error window, and then exit.
	 */
	void fatal_error_handler(const char *str, const char *arg, bool useVal, int val);

	/**
	 * Display a warning in the error window, and then continue.
	 */
	void nonfatal_warning_handler(const char *str, const char *arg, bool useVal, int val);
#define fatal_error(s)  (fatal_error_handler((s), nullptr, false, 0))
#define fatal_error_2(s1, s2)  (fatal_error_handler((s1), (s2), false, 0))
#define fatal_error_i(s, v)  (fatal_error_handler((s), nullptr, true, (v)))
#define nonfatal_warning(s) (nonfatal_warning_handler((s), nullptr, false, 0))
#define nonfatal_warning_2(s1, s2) (nonfatal_warning_handler((s1), (s2), false, 0))
#define nonfatal_warning_i(s, v) (nonfatal_warning_handler((s), nullptr, true, (v)))

	/**
	 * \defgroup Files access methods
	 * @{
	 */

	 /**
	  * Validates the game file, and if it's invalid, displays an error dialog
	  */
	bool is_gamefile_valid();

	int locate_gamefile(int isblorb);
	
	/**@}*/

	/**
	 * \defgroup Vm access methods
	 * @{
	 */

	void setup_vm(void);
	void finalize_vm(void);
	void vm_restart(void);
	uint change_memsize(uint newlen, int internal);
	uint *pop_arguments(uint count, uint addr);
	void verify_address(uint addr, uint count);
	void verify_address_write(uint addr, uint count);
	void verify_array_addresses(uint addr, uint count, uint size);

	/**@}*/

	/**
	 * \defgroup Exec access methods
	 * @{
	 */

	 /**
	  * The main interpreter loop. This repeats until the program is done
	  */
	void execute_loop();

	/**@}*/

	/**
	 * \defgroup Operand access methods
	 * @{
	 */	operandlist_t *fast_operandlist[0x80];
	void init_operands(void);
	operandlist_t *lookup_operandlist(uint opcode);
	void parse_operands(oparg_t *opargs, operandlist_t *oplist);
	void store_operand(uint desttype, uint destaddr, uint storeval);
	void store_operand_s(uint desttype, uint destaddr, uint storeval);
	void store_operand_b(uint desttype, uint destaddr, uint storeval);

	/**@}*/

	/**
	 * \defgroup Func access methods
	 * @{
	 */

	/**
	 * This writes a new call frame onto the stack, at stackptr. It leaves frameptr pointing
	 * to the frame (ie, the original stackptr value.) argc and argv are an array of arguments.
	 * Note that if argc is zero, argv may be NULL.
	 */
	void enter_function(uint addr, uint argc, uint *argv);
	
	/**
	 * Pop the current call frame off the stack. This is very simple.
	*/
	void leave_function();

	/**
	 * Push the magic four values on the stack: result destination, PC, and frameptr.
	 */
	void push_callstub(uint desttype, uint destaddr);

	/**
	 * Remove the magic four values from the stack, and use them. The returnvalue, whatever it is,
	 * is put at the result destination; the PC and frameptr registers are set.
	*/
	void pop_callstub(uint returnvalue);

	/**
	 * Remove the magic four values, but interpret them as a string restart state.
	 * Returns zero if it's a termination stub, or returns the restart address. The bitnum is extra.
	 */
	uint pop_callstub_string(int *bitnum);

	/**@}*/

	/**
	 * \defgroup Strings access methods
	 * @{
	 */

	void stream_num(int val, int inmiddle, int charnum);
	void stream_string(uint addr, int inmiddle, int bitnum);
	uint stream_get_table(void);
	void stream_set_table(uint addr);
	void stream_get_iosys(uint *mode, uint *rock);
	void stream_set_iosys(uint mode, uint rock);
	char *make_temp_string(uint addr);
	uint *make_temp_ustring(uint addr);
	void free_temp_string(const char *str);
	void free_temp_ustring(const uint *str);

	/**@}*/

	/**
	 * \defgroup Heap access methods
	 * @{
	 */

	/**
	 * Set the heap state to inactive, and free the block lists. This is called when the game
	 * starts or restarts.
	 */
	void heap_clear(void);

	/**
	 * Returns whether the heap is active.
	 */
	int heap_is_active() const;

	/**
	 * Returns the start address of the heap, or 0 if the heap is not active.
	 */
	uint heap_get_start() const;

	/**
	 * Allocate a block. If necessary, activate the heap and/or extend memory. This may not be
	 * available at all; #define FIXED_MEMSIZE if you want the interpreter to unconditionally refuse.
	 * Returns the memory address of the block, or 0 if the operation failed.
	 */
	uint heap_alloc(uint len);

	/**
	 * Free a heap block. If necessary, deactivate the heap.
	 */
	void heap_free(uint addr);

	/**
	 * Create an array of words, in the VM serialization format:
	 *
	 *	 heap_start
	 *	 alloc_count
	 *	 addr of first block
	 *	 len of first block
	 *	 ...
	 *
	 * (Note that these are uint values -- native byte ordering. Also, the blocks will be in address order,
	 * which is a stricter guarantee than the VM specifies; that'll help in heap_apply_summary().)
	 *
	 * If the heap is inactive, store NULL. Return 0 for success; otherwise, the operation failed.
	 *
	 * The array returned in summary must be freed with glulx_free() after the caller uses it.
	 */
	int heap_get_summary(uint *valcount, uint **summary);

	/**
	 * Given an array of words in the above format, set up the heap to contain it. As noted above,
	 * the caller must ensure that the blocks are in address order. When this is called, the heap
	 * must be inactive.
	 *
	 * Return 0 for success. Otherwise the operation failed (and, most likely, caused a fatal error).
	*/
	int heap_apply_summary(uint valcount, uint *summary);

	/**@}*/

	/**
	 * \defgroup Serial access methods
	 * @{
	 */

	int max_undo_level;
	int init_serial(void);
	void final_serial(void);
	uint perform_save(strid_t str);
	uint perform_restore(strid_t str, int fromshell);
	uint perform_saveundo(void);
	uint perform_restoreundo(void);
	uint perform_verify(void);

	/**@}*/

	/**
	 * \defgroup Search access methods
	 * @{
	 */

	uint linear_search(uint key, uint keysize,
		uint start, uint structsize, uint numstructs,
		uint keyoffset, uint options);
	uint binary_search(uint key, uint keysize,
		uint start, uint structsize, uint numstructs,
		uint keyoffset, uint options);
	uint linked_search(uint key, uint keysize,
		uint start, uint keyoffset, uint nextoffset,
		uint options);

	/**@}*/

	/**
	 * \defgroup Osdepend access methods
	 * @{
	 */

	void *glulx_malloc(uint len);
	void *glulx_realloc(void *ptr, uint len);
	void glulx_free(void *ptr);
	void glulx_setrandom(uint seed);
	uint glulx_random(void);
	void glulx_sort(void *addr, int count, int size,
		int(*comparefunc)(void *p1, void *p2));

	/**@}*/

	/**
	 * \defgroup Gestalt access methods
	 * @{
	 */

	uint do_gestalt(uint val, uint val2);

	/**@}*/

	/**
	 * \defgroup Glkop access methods
	 * @{
	 */

	 /**
	  * glkop section initialization
	  */
	void glkopInit();

	void set_library_select_hook(void(*func)(uint));

	bool init_dispatch();

	/**
	 * The object registration/unregistration callbacks that the library calls
	 * to keep the hash tables up to date.
	 */
	gidispatch_rock_t glulxe_classtable_register(void *obj, uint objclass);

	gidispatch_rock_t glulxe_classtable_register_existing(void *obj, uint objclass, uint dispid);

	void glulxe_classtable_unregister(void *obj, uint objclass, gidispatch_rock_t objrock);

	gidispatch_rock_t glulxe_retained_register(void *array, uint len, char *typecode);
	void glulxe_retained_unregister(void *array, uint len, char *typecode, gidispatch_rock_t objrock);

	/**
	 * Turn a list of Glulx arguments into a list of Glk arguments, dispatch the function call, and return the result.
	 */
	uint perform_glk(uint funcnum, uint numargs, uint *arglist);

	/**
	 * Read the prefixes of an argument string -- the "<>&+:#!" chars.
	 */
	const char *read_prefix(const char *cx, int *isref, int *isarray, int *passin, int *passout,
		int *nullok, int *isretained, int *isreturn);

	/**
	 * This is used by some interpreter code which has to, well, find a Glk stream given its ID.
	 */
	strid_t find_stream_by_id(uint objid);

	/**
	 * Return the ID of a given Glk window.
	 */
	uint find_id_for_window(winid_t win);

	/**
	 * Return the ID of a given Glk stream.
	 */
	uint find_id_for_stream(strid_t str);

	/**
	 * Return the ID of a given Glk fileref.
	 */
	uint find_id_for_fileref(frefid_t fref);

	/**
	 * Return the ID of a given Glk schannel.
	 */
	uint find_id_for_schannel(schanid_t schan);

	/**@}*/

	/**
	 * \defgroup Profile access methods
	 * @{
	 */

	void setup_profile(strid_t stream, char *filename);
	int init_profile(void);
	void profile_set_call_counts(int flag);
#if VM_PROFILING
	uint profile_opcount;
#define profile_tick() (profile_opcount++)
	int profile_profiling_active(void);
	void profile_in(uint addr, uint stackuse, int accel);
	void profile_out(uint stackuse);
	void profile_fail(char *reason);
	void profile_quit(void);
#else /* VM_PROFILING */
#define profile_tick()         (0)
#define profile_profiling_active()         (0)
#define profile_in(addr, stackuse, accel)  (0)
#define profile_out(stackuse)  (0)
#define profile_fail(reason)   (0)
#define profile_quit()         (0)
#endif /* VM_PROFILING */

#if VM_DEBUGGER
	unsigned long debugger_opcount;
#define debugger_tick() (debugger_opcount++)
	int debugger_load_info_stream(strid_t stream);
	int debugger_load_info_chunk(strid_t stream, uint pos, uint len);
	void debugger_track_cpu(int flag);
	void debugger_set_start_trap(int flag);
	void debugger_set_quit_trap(int flag);
	void debugger_set_crash_trap(int flag);
	void debugger_check_story_file(void);
	void debugger_setup_start_state(void);
	int debugger_ever_invoked(void);
	int debugger_cmd_handler(char *cmd);
	void debugger_cycle_handler(int cycle);
	void debugger_check_func_breakpoint(uint addr);
	void debugger_block_and_debug(char *msg);
	void debugger_handle_crash(char *msg);
	void debugger_handle_quit(void);
#else /* VM_DEBUGGER */
#define debugger_tick()              (0)
#define debugger_check_story_file()  (0)
#define debugger_setup_start_state() (0)
#define debugger_check_func_breakpoint(addr)  (0)
#define debugger_handle_crash(msg)   (0)
#endif /* VM_DEBUGGER */

	/**@}*/

	/**
	 * \defgroup Accel access methods
	 * @{
	 */

	acceleration_func accel_find_func(uint index);
	acceleration_func accel_get_func(uint addr);
	void accel_set_func(uint index, uint addr);
	void accel_set_param(uint index, uint val);

	uint accel_get_param_count() const;
	uint accel_get_param(uint index) const;

	/**
	 * Iterate the entire acceleration table, calling the callback for each (non-nullptr) entry.
	 * This is used only for autosave.
	 */
	void accel_iterate_funcs(void(*func)(uint index, uint addr));

	/**@}*/

	/**
	 * \defgroup Float access methods
	 * @{
	 */
#ifdef FLOAT_SUPPORT

	/* You may have to edit the definition of gfloat32 to make sure it's really
	   a 32-bit floating-point type. */
	typedef float gfloat32;

	/* Uncomment this definition if your gfloat32 type is not a standard
	   IEEE-754 single-precision (32-bit) format. Normally, Glulxe assumes
	   that it can reinterpret-cast IEEE-754 int values into gfloat32
	   values. If you uncomment this, Glulxe switches to lengthier
	   (but safer) encoding and decoding functions. */
	   /* #define FLOAT_NOT_NATIVE (1) */

	   /* float.c */
	int init_float(void);
	uint encode_float(gfloat32 val);
	gfloat32 decode_float(uint val);

	/* Uncomment this definition if your powf() function does not support
	   all the corner cases specified by C99. If you uncomment this,
	   osdepend.c will provide a safer implementation of glulx_powf(). */
	   /* #define FLOAT_COMPILE_SAFER_POWF (1) */

	gfloat32 glulx_powf(gfloat32 val1, gfloat32 val2);

#endif /* FLOAT_SUPPORT */
	/**@}*/
};

extern Glulxe *g_vm;

} // End of namespace Glulxe
} // End of namespace Glk

#endif