Implemented garglk_set_program_name(), garglk_set_program_info(), garglk_set_story_name()
[rodin/chimara.git] / interpreters / glulxe / vm.c
1 /* vm.c: Glulxe code related to the VM overall. Also miscellaneous stuff.
2     Designed by Andrew Plotkin <erkyrath@eblong.com>
3     http://eblong.com/zarf/glulx/index.html
4 */
5
6 #include "glk.h"
7 #include "glulxe.h"
8
9 /* The memory blocks which contain VM main memory and the stack. */
10 unsigned char *memmap = NULL;
11 unsigned char *stack = NULL;
12
13 /* Various memory addresses which are useful. These are loaded in from
14    the game file header. */
15 glui32 ramstart;
16 glui32 endgamefile;
17 glui32 origendmem;
18 glui32 stacksize;
19 glui32 startfuncaddr;
20 glui32 origstringtable;
21 glui32 checksum;
22
23 /* The VM registers. */
24 glui32 stackptr;
25 glui32 frameptr;
26 glui32 pc;
27 glui32 stringtable;
28 glui32 valstackbase;
29 glui32 localsbase;
30 glui32 endmem;
31 glui32 protectstart, protectend;
32
33 void (*stream_char_handler)(unsigned char ch);
34 void (*stream_unichar_handler)(glui32 ch);
35
36 /* setup_vm():
37    Read in the game file and build the machine, allocating all the memory
38    necessary.
39 */
40 void setup_vm()
41 {
42   unsigned char buf[4 * 7];
43   int res;
44
45   pc = 0; /* Clear this, so that error messages are cleaner. */
46
47   /* Read in all the size constants from the game file header. */
48
49   stream_char_handler = NULL;
50   stream_unichar_handler = NULL;
51
52   glk_stream_set_position(gamefile, gamefile_start+8, seekmode_Start);
53   res = glk_get_buffer_stream(gamefile, (char *)buf, 4 * 7);
54   
55   ramstart = Read4(buf+0);
56   endgamefile = Read4(buf+4);
57   origendmem = Read4(buf+8);
58   stacksize = Read4(buf+12);
59   startfuncaddr = Read4(buf+16);
60   origstringtable = Read4(buf+20);
61   checksum = Read4(buf+24);
62
63   /* Set the protection range to (0, 0), meaning "off". */
64   protectstart = 0;
65   protectend = 0;
66
67   /* Do a few sanity checks. */
68
69   if ((ramstart & 0xFF)
70     || (endgamefile & 0xFF) 
71     || (origendmem & 0xFF)
72     || (stacksize & 0xFF)) {
73     nonfatal_warning("One of the segment boundaries in the header is not "
74       "256-byte aligned.");
75   }
76
77   if (ramstart < 0x100 || endgamefile < ramstart || origendmem < endgamefile) {
78     fatal_error("The segment boundaries in the header are in an impossible "
79       "order.");
80   }
81   if (stacksize < 0x100) {
82     fatal_error("The stack size in the header is too small.");
83   }
84   
85   /* Allocate main memory and the stack. This is where memory allocation
86      errors are most likely to occur. */
87   endmem = origendmem;
88   memmap = (unsigned char *)glulx_malloc(origendmem);
89   if (!memmap) {
90     fatal_error("Unable to allocate Glulx memory space.");
91   }
92   stack = (unsigned char *)glulx_malloc(stacksize);
93   if (!stack) {
94     glulx_free(memmap);
95     memmap = NULL;
96     fatal_error("Unable to allocate Glulx stack space.");
97   }
98   stringtable = 0;
99
100   /* Initialize various other things in the terp. */
101   init_operands(); 
102   init_accel();
103   init_serial();
104
105   /* Set up the initial machine state. */
106   vm_restart();
107 }
108
109 /* finalize_vm():
110    Deallocate all the memory and shut down the machine.
111 */
112 void finalize_vm()
113 {
114   if (memmap) {
115     glulx_free(memmap);
116     memmap = NULL;
117   }
118   if (stack) {
119     glulx_free(stack);
120     stack = NULL;
121   }
122 }
123
124 /* vm_restart(): 
125    Put the VM into a state where it's ready to begin executing the
126    game. This is called both at startup time, and when the machine
127    performs a "restart" opcode. 
128 */
129 void vm_restart()
130 {
131   glui32 lx;
132   int res;
133
134   /* Deactivate the heap (if it was active). */
135   heap_clear();
136
137   /* Reset memory to the original size. */
138   lx = change_memsize(origendmem, FALSE);
139   if (lx)
140     fatal_error("Memory could not be reset to its original size.");
141
142   /* Load in all of main memory */
143   glk_stream_set_position(gamefile, gamefile_start, seekmode_Start);
144   for (lx=0; lx<endgamefile; lx++) {
145     res = glk_get_char_stream(gamefile);
146     if (res == -1) {
147       fatal_error("The game file ended unexpectedly.");
148     }
149     if (lx >= protectstart && lx < protectend)
150       continue;
151     memmap[lx] = res;
152   }
153   for (lx=endgamefile; lx<origendmem; lx++) {
154     memmap[lx] = 0;
155   }
156
157   /* Reset all the registers */
158   stackptr = 0;
159   frameptr = 0;
160   pc = 0;
161   stream_set_iosys(0, 0);
162   stream_set_table(origstringtable);
163   valstackbase = 0;
164   localsbase = 0;
165
166   /* Note that we do not reset the protection range. */
167
168   /* Push the first function call. (No arguments.) */
169   enter_function(startfuncaddr, 0, NULL);
170
171   /* We're now ready to execute. */
172 }
173
174 /* change_memsize():
175    Change the size of the memory map. This may not be available at
176    all; #define FIXED_MEMSIZE if you want the interpreter to
177    unconditionally refuse. The internal flag should be true only when
178    the heap-allocation system is calling.
179    Returns 0 for success; otherwise, the operation failed.
180 */
181 glui32 change_memsize(glui32 newlen, int internal)
182 {
183   long lx;
184   unsigned char *newmemmap;
185
186   if (newlen == endmem)
187     return 0;
188
189 #ifdef FIXED_MEMSIZE
190   return 1;
191 #else /* FIXED_MEMSIZE */
192
193   if ((!internal) && heap_is_active())
194     fatal_error("Cannot resize Glulx memory space while heap is active.");
195
196   if (newlen < origendmem)
197     fatal_error("Cannot resize Glulx memory space smaller than it started.");
198
199   if (newlen & 0xFF)
200     fatal_error("Can only resize Glulx memory space to a 256-byte boundary.");
201   
202   newmemmap = (unsigned char *)glulx_realloc(memmap, newlen);
203   if (!newmemmap) {
204     /* The old block is still in place, unchanged. */
205     return 1;
206   }
207   memmap = newmemmap;
208
209   if (newlen > endmem) {
210     for (lx=endmem; lx<newlen; lx++) {
211       memmap[lx] = 0;
212     }
213   }
214
215   endmem = newlen;
216
217   return 0;
218
219 #endif /* FIXED_MEMSIZE */
220 }
221
222 /* pop_arguments():
223    If addr is 0, pop N arguments off the stack, and put them in an array. 
224    If non-0, take N arguments from that main memory address instead.
225    This has to dynamically allocate if there are more than 32 arguments,
226    but that shouldn't be a problem.
227 */
228 glui32 *pop_arguments(glui32 count, glui32 addr)
229 {
230   int ix;
231   glui32 argptr;
232   glui32 *array;
233
234   #define MAXARGS (32)
235   static glui32 statarray[MAXARGS];
236   static glui32 *dynarray = NULL;
237   static glui32 dynarray_size = 0;
238
239   if (count == 0)
240     return NULL;
241
242   if (count <= MAXARGS) {
243     /* Store in the static array. */
244     array = statarray;
245   }
246   else {
247     if (!dynarray) {
248       dynarray_size = count+8;
249       dynarray = glulx_malloc(sizeof(glui32) * dynarray_size);
250       if (!dynarray)
251         fatal_error("Unable to allocate function arguments.");
252       array = dynarray;
253     }
254     else {
255       if (dynarray_size >= count) {
256         /* It fits. */
257         array = dynarray;
258       }
259       else {
260         dynarray_size = count+8;
261         dynarray = glulx_realloc(dynarray, sizeof(glui32) * dynarray_size);
262         if (!dynarray)
263           fatal_error("Unable to reallocate function arguments.");
264         array = dynarray;
265       }
266     }
267   }
268
269   if (!addr) {
270     if (stackptr < valstackbase+4*count) 
271       fatal_error("Stack underflow in arguments.");
272     stackptr -= 4*count;
273     for (ix=0; ix<count; ix++) {
274       argptr = stackptr+4*((count-1)-ix);
275       array[ix] = Stk4(argptr);
276     }
277   }
278   else {
279     for (ix=0; ix<count; ix++) {
280       array[ix] = Mem4(addr);
281       addr += 4;
282     }
283   }
284
285   return array;
286 }
287
288 /* verify_address():
289    Make sure that count bytes beginning with addr all fall within the
290    current memory map. This is called at every memory access if
291    VERIFY_MEMORY_ACCESS is defined in the header file.
292 */
293 void verify_address(glui32 addr, glui32 count)
294 {
295   if (addr >= endmem)
296     fatal_error_i("Memory access out of range", addr);
297   if (count > 1) {
298     addr += (count-1);
299     if (addr >= endmem)
300       fatal_error_i("Memory access out of range", addr);
301   }
302 }
303