1//
2//  ZoomZMachine.m
3//  ZoomCocoa
4//
5//  Created by Andrew Hunter on Wed Sep 10 2003.
6//  Copyright (c) 2003 Andrew Hunter. All rights reserved.
7//
8
9#import "ZoomZMachine.h"
10#import "ZoomServer.h"
11
12#include "sys/time.h"
13
14
15#include "zmachine.h"
16#include "interp.h"
17#include "random.h"
18#include "file.h"
19#include "zscii.h"
20#include "display.h"
21#include "rc.h"
22#include "stream.h"
23#include "blorb.h"
24#include "v6display.h"
25#include "state.h"
26#include "debug.h"
27#include "zscii.h"
28
29@implementation ZoomZMachine
30
31- (id) init {
32    self = [super init];
33
34    if (self) {
35        display = nil;
36        machineFile = NULL;
37
38        inputBuffer = [[NSMutableString allocWithZone: [self zone]] init];
39        outputBuffer = [[ZBuffer allocWithZone: [self zone]] init];
40        lastFile = nil;
41		terminatingCharacter = 0;
42
43        windows[0] = windows[1] = windows[2] = nil;
44
45        int x;
46        for(x=0; x<3; x++) {
47            windowBuffer[x] = [[NSMutableAttributedString alloc] init];
48        }
49
50		[[NSNotificationCenter defaultCenter] addObserver: self
51												 selector: @selector(flushBuffers)
52													 name: ZBufferNeedsFlushingNotification
53												   object: nil];
54    }
55
56    return self;
57}
58
59- (void) dealloc {
60	[[NSNotificationCenter defaultCenter] removeObserver: self];
61
62    if (windows[0])
63        [windows[0] release];
64    if (windows[1])
65        [windows[1] release];
66    if (windows[2])
67        [windows[2] release];
68
69    int x;
70    for (x=0; x<3; x++) {
71        [windowBuffer[x] release];
72    }
73
74    [display release];
75    [inputBuffer release];
76    [outputBuffer release];
77
78    mainMachine = nil;
79
80    if (lastFile) [lastFile release];
81
82    if (machineFile) {
83        close_file(machineFile);
84    }
85	if (storyData) [storyData release];
86
87    [super dealloc];
88}
89
90- (NSString*) description {
91    return @"Zoom 1.1.5 ZMachine object";
92}
93
94- (void) connectionDied: (NSNotification*) notification {
95	NSLog(@"Connection died!");
96	abort();
97}
98
99// = Setup =
100- (void) loadStoryFile: (NSData*) storyFile {
101    // Create the machine file
102	storyData = [storyFile retain];
103    ZDataFile* file = [[ZDataFile alloc] initWithData: storyFile];
104    machineFile = open_file_from_object([file autorelease]);
105
106	// Start initialising the Z-Machine
107	// (We do this so that we can load a save state at any time after this call)
108
109	wasRestored = NO;
110
111    // RNG
112    struct timeval tv;
113    gettimeofday(&tv, NULL);
114    random_seed(tv.tv_sec^tv.tv_usec);
115
116    // Some default options
117	// rc_load(); // DELETEME: TEST FOR BUG
118	rc_hash = hash_create();
119
120	rc_defgame = malloc(sizeof(rc_game));
121	rc_defgame->name = "";
122	rc_defgame->interpreter = 3;
123	rc_defgame->revision = 'Z';
124	rc_defgame->fonts = NULL;
125	rc_defgame->n_fonts = 0;
126	rc_defgame->colours = NULL;
127	rc_defgame->n_colours = 0;
128	rc_defgame->gamedir = rc_defgame->savedir = rc_defgame->sounds = rc_defgame->graphics = NULL;
129	rc_defgame->xsize = 80;
130	rc_defgame->ysize = 25;
131	rc_defgame->antialias = 1;
132	rc_defgame->fg_col = [display foregroundColour];
133	rc_defgame->bg_col = [display backgroundColour];
134
135	hash_store(rc_hash, "default", 7, rc_defgame);
136
137    // Load the story
138    machine.story_length = get_size_of_file(machineFile);
139    zmachine_load_file(machineFile, &machine);
140	machine.blorb = blorb_loadfile(NULL);
141
142	// Set up the rc system (we do this twice: this particular case helps with the )
143    rc_set_game(zmachine_get_serial(), Word(ZH_release), Word(ZH_checksum));
144}
145
146// = Running =
147- (void) startRunningInDisplay: (in byref NSObject<ZDisplay>*) disp {
148    NSAutoreleasePool* mainPool = [[NSAutoreleasePool alloc] init];
149
150	// Remember the display
151    display = [disp retain];
152
153	// Set up colours
154	if (rc_defgame) {
155		rc_defgame->fg_col = [display foregroundColour];
156		rc_defgame->bg_col = [display backgroundColour];
157	}
158
159    // OK, we can now set up the ZMachine and get running
160	rc_defgame->interpreter = [display interpreterVersion];
161	rc_defgame->revision	= [display interpreterRevision];
162
163    // Setup the display
164    windows[0] = NULL;
165    windows[1] = NULL;
166    windows[2] = NULL;
167
168    // Cycle the autorelease pool
169    displayPool = [[NSAutoreleasePool alloc] init];
170
171    switch (machine.header[0]) {
172        case 3:
173            // Status window
174
175        case 4:
176        case 5:
177        case 7:
178        case 8:
179            // Upper/lower window
180            windows[0] = [[display createLowerWindow] retain];
181            windows[1] = [[display createUpperWindow] retain];
182            windows[2] = [[display createUpperWindow] retain];
183            break;
184
185        case 6:
186			windows[0] = [[display createPixmapWindow] retain];
187            break;
188    }
189
190    int x;
191    for (x=0; x<3; x++) {
192        [(NSDistantObject*)windows[x] setProtocolForProxy: @protocol(ZWindow)];
193    }
194
195    // Setup the display, etc
196    rc_set_game(zmachine_get_serial(), Word(ZH_release), Word(ZH_checksum));
197    display_initialise();
198
199	// Clear the display to the default colours
200	if (!wasRestored) {
201		display_set_colour(rc_get_foreground(), rc_get_background());
202		display_clear();
203	}
204
205	if (wasRestored) zmachine_setup_header();
206
207    // Start running the machine
208    switch (machine.header[0])
209    {
210#ifdef SUPPORT_VERSION_3
211        case 3:
212            display_split(1, 1);
213
214            display_set_colour(rc_get_foreground(), rc_get_background()); display_set_font(0);
215            display_set_window(0);
216            if (!wasRestored) zmachine_run(3, NULL); else zmachine_runsome(3, machine.zpc);
217            break;
218#endif
219#ifdef SUPPORT_VERSION_4
220        case 4:
221            if (!wasRestored) zmachine_run(4, NULL); else zmachine_runsome(4, machine.zpc);
222            break;
223#endif
224#ifdef SUPPORT_VERSION_5
225        case 5:
226            if (!wasRestored) zmachine_run(5, NULL); else zmachine_runsome(5, machine.zpc);
227            break;
228        case 7:
229            if (!wasRestored) zmachine_run(7, NULL); else zmachine_runsome(7, machine.zpc);
230            break;
231        case 8:
232            if (!wasRestored) zmachine_run(8, NULL); else zmachine_runsome(8, machine.zpc);
233            break;
234#endif
235#ifdef SUPPORT_VERSION_6
236        case 6:
237            v6_startup();
238            v6_set_cursor(1,1);
239
240            if (!wasRestored) zmachine_run(6, NULL); else zmachine_runsome(6, machine.zpc);
241            break;
242#endif
243
244        default:
245            zmachine_fatal("Unsupported ZMachine version %i", machine.header[0]);
246            break;
247    }
248
249	stream_flush_buffer();
250	display_flush();
251
252    display_finalise();
253    [mainPool release];
254
255	display_exit(0);
256}
257
258// = Debugging =
259void cocoa_debug_handler(ZDWord pc) {
260	[mainMachine breakpoint: pc];
261}
262
263- (void) breakpoint: (ZDWord) pc {
264	if (display) {
265		// Notify the display of the breakpoint
266		waitingForBreakpoint = YES;
267		[self flushBuffers];
268		[display hitBreakpointAt: pc];
269
270		// Wait for the display to request resumption
271		NSAutoreleasePool* breakpointPool = [[NSAutoreleasePool alloc] init];
272
273		while (waitingForBreakpoint && (mainMachine != nil)) {
274			[breakpointPool release];
275			breakpointPool = [[NSAutoreleasePool alloc] init];
276
277			[mainLoop acceptInputForMode: NSDefaultRunLoopMode
278							  beforeDate: [NSDate distantFuture]];
279		}
280
281		[breakpointPool release];
282	}
283}
284
285- (void) continueFromBreakpoint {
286	if (!waitingForBreakpoint) {
287		[NSException raise: @"BreakpointException" format: @"Attempt to call a continuation function when Zoom was not waiting at a breakpoint"];
288		return;
289	}
290
291	waitingForBreakpoint = NO;
292}
293
294- (void) stepFromBreakpoint {
295	if (!waitingForBreakpoint) {
296		[NSException raise: @"BreakpointException" format: @"Attempt to call a continuation function when Zoom was not waiting at a breakpoint"];
297		return;
298	}
299
300	debug_set_temp_breakpoints(debug_step_over);
301	waitingForBreakpoint = NO;
302}
303
304- (void) stepIntoFromBreakpoint {
305	if (!waitingForBreakpoint) {
306		[NSException raise: @"BreakpointException" format: @"Attempt to call a continuation function when Zoom was not waiting at a breakpoint"];
307		return;
308	}
309
310	debug_set_temp_breakpoints(debug_step_into);
311	waitingForBreakpoint = NO;
312}
313
314- (void) finishFromBreakpoint {
315	if (!waitingForBreakpoint) {
316		[NSException raise: @"BreakpointException" format: @"Attempt to call a continuation function when Zoom was not waiting at a breakpoint"];
317		return;
318	}
319
320	debug_set_temp_breakpoints(debug_step_out);
321	waitingForBreakpoint = NO;
322}
323
324- (NSData*) staticMemory {
325	NSData* result = [NSData dataWithBytesNoCopy: machine.memory
326										  length: machine.story_length<65536?machine.story_length:65536];
327
328	return result;
329}
330
331// Macros from interp.c (copy those back for preference if they ever need to change)
332#define UnpackR(x) (machine.packtype==packed_v4?4*((ZUWord)x):(machine.packtype==packed_v8?8*((ZUWord)x):4*((ZUWord)x)+machine.routine_offset))
333#define UnpackS(x) (machine.packtype==packed_v4?4*((ZUWord)x):(machine.packtype==packed_v8?8*((ZUWord)x):4*((ZUWord)x)+machine.string_offset))
334#define Obj3(x) (machine.memory + GetWord(machine.header, ZH_objs) + 62+(((x)-1)*9))
335#define Obj4(x) ((machine.memory + (GetWord(machine.header, ZH_objs))) + 126 + ((((ZUWord)x)-1)*14))
336#define GetPropAddr4(x) (((x)[12]<<8)|(x)[13])
337
338- (int) zRegion: (int) addr {
339	// Being a port of the Z__Region function from Inform
340	if (addr > 0x7fff) addr |= 0xffff0000;
341	int top = addr;
342
343	if (machine.version == 6 || machine.version == 7) top >>= 1; // (?? might be wrong ??)
344
345	// Outside the file?
346	if (((unsigned)top&0xffff) > Word(0x1a)) return 0;
347
348	// Is this an object?
349	if (addr >= 1 && addr <= debug_syms.largest_object) return 1;
350
351	// Is this a string?
352	ZDWord strUnpack = UnpackS(addr);
353	if (strUnpack >= debug_syms.stringarea) return 3;
354
355	// Is this a routine?
356	ZDWord routUnpack = UnpackR(addr);
357	if (routUnpack >= debug_syms.codearea) return 2;
358
359	// Is unknown
360	return 0;
361}
362
363- (unsigned) typeMasksForValue: (unsigned) value {
364	unsigned mask = 0;
365
366	// Get the region
367	int region = [self zRegion: value];
368
369	// Is value a valid object number?
370	if (region == 1) {
371		mask |= ZValueObject;
372	}
373
374	// Is value a valid routine number?
375	if (region == 2) {
376		// Check through the list of routines for this one
377		ZDWord routineAddr = UnpackR(value);
378		int x;
379		BOOL isRoutine = NO;
380
381		for (x=0; x<debug_syms.nroutines; x++) {
382			if (debug_syms.routine[x].start == routineAddr) isRoutine = YES;
383		}
384
385		if (isRoutine) mask |= ZValueRoutine;
386	}
387
388	// Is value a valid string number?
389	if (region == 3) {
390		mask |= ZValueString;
391	}
392
393	return mask;
394}
395
396static NSString* zscii_to_string(ZByte* buf) {
397	int len;
398	int* unistr = zscii_to_unicode(buf, &len);
399
400	int x;
401	int strLen = 0;
402
403	for (x=0; unistr[x]!=0; x++) strLen++;
404
405	unichar* cBuf = malloc(sizeof(unichar)*(strLen+1));
406
407	for (x=0; x<strLen; x++) {
408		if (unistr[x] <= 0xffff) cBuf[x] = unistr[x]; else cBuf[x] = '?';
409	}
410	cBuf[strLen] = 0;
411
412	NSString* res = [NSString stringWithCharacters: cBuf
413											length: strLen];
414
415	free(cBuf);
416	return res;
417}
418
419- (NSString*) descriptionForValue: (unsigned) value {
420	NSMutableString* description = [NSMutableString string];
421	unsigned mask = [self typeMasksForValue: value];
422
423	// If the value could be an object, get the name
424	if ((mask&ZValueObject)) {
425		ZUWord uarg1 = value&0xffff;
426
427		// 'Object (' at the start of the string
428		[description appendString: @"Object ("];
429
430		// Built-in name of the object if available
431		debug_symbol* symbol;
432		for (symbol = debug_syms.first_symbol; symbol != NULL; symbol = symbol->next) {
433			if (symbol->type == dbg_object &&
434				symbol->data.object.number == uarg1) {
435				if (symbol->data.object.name != NULL &&
436					symbol->data.object.name[0] != 0) {
437					[description appendFormat: @"%s ", symbol->data.object.name];
438				}
439				break;
440			}
441		}
442
443		// Short name of the object
444		if (machine.version <= 3) {
445			ZByte* obj;
446			ZByte* prop;
447
448			obj = Obj3(uarg1);
449			prop = machine.memory + ((obj[7]<<8)|obj[8]) + 1;
450
451			[description appendFormat: @"\"%@\"", zscii_to_string(prop)];
452		} else {
453			ZByte* obj;
454			ZByte* prop;
455
456			obj = Obj4(uarg1);
457			prop = Address((ZUWord)GetPropAddr4(obj)+1);
458
459			[description appendFormat: @"\"%@\"", zscii_to_string(prop)];
460		}
461
462		[description appendString: @") "];
463	}
464
465	// If the value could be a string, convert it
466	if ((mask&ZValueString)) {
467		[description appendFormat: @"String (\"%@\") ", zscii_to_string(machine.memory + UnpackS(value))];
468	}
469
470	// If the value could be a routine, find the name
471	if ((mask&ZValueRoutine)) {
472		ZDWord routineAddr = UnpackR(value);
473		int x;
474
475		for (x=0; x<debug_syms.nroutines; x++) {
476			if (debug_syms.routine[x].start == routineAddr) {
477				[description appendFormat: @"Routine ([ %s; ]) ", debug_syms.routine[x].name];
478			}
479		}
480	}
481
482	// If nothing, then just use the value
483	if ([description length] <= 0) {
484		int signedValue = value;
485		if (signedValue > 0x7fff) signedValue &= 0xffff0000;
486
487		[description appendFormat: @"%i", signedValue];
488	}
489
490	return description;
491}
492
493- (void) loadDebugSymbolsFrom: (NSString*) symbolFile
494			   withSourcePath: (NSString*) sourcePath {
495	debug_load_symbols((char*)[symbolFile cString], (char*)[sourcePath cString]);
496
497	// Setup our debugger callback
498	debug_set_bp_handler(cocoa_debug_handler);
499}
500
501- (int) evaluateExpression: (NSString*) expression {
502	debug_address addr;
503
504	addr = debug_find_address(machine.zpc);
505
506	debug_expr = malloc(sizeof(int) * ([expression length]+1));
507	int x;
508	for (x=0; x<[expression length]; x++) {
509		debug_expr[x] = [expression characterAtIndex: x];
510	}
511	debug_expr[x] = 0;
512
513	debug_expr_routine = addr.routine;
514	debug_error = NULL;
515	debug_expr_pos = 0;
516	debug_eval_parse();
517	free(debug_expr);
518
519	if (debug_error != NULL) return 0x7fffffff;
520
521	return debug_eval_result;
522}
523
524- (void) setBreakpointAt: (int) address {
525	debug_set_breakpoint(address, 0, 0);
526}
527
528- (BOOL) setBreakpointAtName: (NSString*) name {
529	int address = [self addressForName: name];
530
531	if (address >= 0) {
532		[self setBreakpointAt: address];
533		return YES;
534	} else {
535		return NO;
536	}
537}
538
539- (void) removeBreakpointAt: (int) address {
540	debug_clear_breakpoint(debug_get_breakpoint(address));
541}
542
543- (void) removeBreakpointAtName: (NSString*) name {
544	int address = [self addressForName: name];
545
546	if (address >= 0) {
547		[self removeBreakpointAt: address];
548	}
549}
550
551- (void) removeAllBreakpoints {
552	while (debug_nbps > 0) {
553		// NOT temporary breakpoints
554		int index = 0;
555		while (index < debug_nbps && debug_bplist[index].temporary != 0) index++;
556		if (index >= debug_nbps) break;
557
558		debug_clear_breakpoint(debug_bplist + index);
559	}
560}
561
562- (int) addressForName: (NSString*) name {
563	return debug_find_named_address([name cString]);
564}
565
566- (NSString*) nameForAddress: (int) address {
567	debug_address addr = debug_find_address(address);
568
569	if (addr.routine != NULL) {
570		return [NSString stringWithCString: addr.routine->name];
571	}
572
573	return nil;
574}
575
576- (NSString*) sourceFileForAddress: (int) address {
577	debug_address addr = debug_find_address(address);
578
579	if (addr.line == NULL) return nil;
580
581	return [NSString stringWithCString: debug_syms.files[addr.line->fl].realname];
582}
583
584- (NSString*) routineForAddress: (int) address {
585	debug_address addr = debug_find_address(address);
586
587	if (addr.routine == NULL) return nil;
588
589	return [NSString stringWithCString: addr.routine->name];
590}
591
592- (int) lineForAddress: (int) address {
593	debug_address addr = debug_find_address(address);
594
595	if (addr.line == NULL) return -1;
596
597	return addr.line->ln;
598}
599
600- (int) characterForAddress: (int) address {
601	debug_address addr = debug_find_address(address);
602
603	if (addr.line == NULL) return -1;
604
605	return addr.line->ch;
606}
607
608// = Autosave =
609- (NSData*) createGameSave {
610	// Create a save game, for autosave purposes
611	int len;
612
613	if (machine.autosave_pc <= 0) return nil;
614
615	void* gameData = state_compile(&machine.stack, machine.autosave_pc, &len, 1);
616
617	NSData* result = [NSData dataWithBytes: gameData length: len];
618
619	free(gameData);
620
621	return result;
622}
623
624- (NSData*) storyFile {
625	return storyData;
626}
627
628- (NSString*) restoreSaveState: (NSData*) saveData {
629	const ZByte* gameData = [saveData bytes];
630
631	// NOTE: suppresses a warning (but it should be OK)
632	if (!state_decompile((ZByte*)gameData, &machine.stack, &machine.zpc, [saveData length])) {
633		NSLog(@"ZoomServer: restoreSaveState: failed");
634		return [NSString stringWithCString: state_fail()];
635	} else {
636		zmachine_setup_header();
637
638		// Must do the same setup tasks as zmachine_run would do
639		switch (machine.memory[0]) {
640			case 3:
641				machine.packtype = packed_v3;
642				break;
643
644			case 4:
645			case 5:
646				machine.packtype = packed_v4;
647				break;
648
649			case 8:
650				machine.packtype = packed_v8;
651				break;
652
653			case 6:
654			case 7:
655				machine.packtype = packed_v6;
656				machine.routine_offset = 8*Word(ZH_routines);
657				machine.string_offset = 8*Word(ZH_staticstrings);
658				break;
659		}
660
661		int x;
662		for (x=0; x<UNDO_LEVEL; x++) {
663			machine.undo[x] = NULL;
664		}
665
666		// Note that we're restoring, not restarting
667		wasRestored = YES;
668	}
669
670	return nil;
671}
672
673// = Receiving text/characters =
674- (void) inputText: (NSString*) text {
675    [inputBuffer appendString: text];
676}
677
678- (void) inputTerminatedWithCharacter: (unsigned int) termChar {
679	terminatingCharacter = termChar;
680}
681
682- (void) inputMouseAtPositionX: (int) posX
683							 Y: (int) posY {
684	mousePosX = posX;
685	mousePosY = posY;
686}
687
688- (int)	terminatingCharacter {
689	return terminatingCharacter;
690}
691
692- (int) mousePosX {
693	return mousePosX;
694}
695
696- (int) mousePosY {
697	return mousePosY;
698}
699
700// = Receiving files =
701- (void) filePromptCancelled {
702    if (lastFile) {
703        [lastFile release];
704        lastFile = nil;
705        lastSize = -1;
706    }
707
708    filePromptFinished = YES;
709}
710
711- (void) promptedFileIs: (NSObject<ZFile>*) file
712                   size: (int) size {
713    if (lastFile) [lastFile release];
714
715    lastFile = [file retain];
716    lastSize = size;
717
718    filePromptFinished = YES;
719}
720
721- (void) filePromptStarted {
722    filePromptFinished = NO;
723    if (lastFile) {
724        [lastFile release];
725        lastFile = nil;
726    }
727}
728
729- (BOOL) filePromptFinished {
730    return filePromptFinished;
731}
732
733- (NSObject<ZFile>*) lastFile {
734    return lastFile;
735}
736
737- (int) lastSize {
738    return lastSize;
739}
740
741- (void) clearFile {
742    if (lastFile) {
743        [lastFile release];
744        lastFile = nil;
745    }
746}
747
748// = Our own functions =
749- (NSObject<ZWindow>*) windowNumber: (int) num {
750    if (num < 0 || num > 2) {
751        NSLog(@"*** BUG - window %i does not exist", num);
752        return nil;
753    }
754
755    return windows[num];
756}
757
758- (NSObject<ZDisplay>*) display {
759    return display;
760}
761
762- (NSMutableString*) inputBuffer {
763    return inputBuffer;
764}
765
766// = Buffering =
767
768- (ZBuffer*) buffer {
769    return outputBuffer;
770}
771
772- (void) flushBuffers {
773    [display flushBuffer: outputBuffer];
774    [outputBuffer release];
775    outputBuffer = [[ZBuffer allocWithZone: [self zone]] init];
776}
777
778// = Display size =
779
780- (void) displaySizeHasChanged {
781    zmachine_resize_display(display_get_info());
782}
783
784@end
785
786// = Fatal errors and warnings =
787void zmachine_fatal(char* format, ...) {
788	char fatalBuf[512];
789	va_list  ap;
790
791	va_start(ap, format);
792	vsnprintf(fatalBuf, 512, format, ap);
793	va_end(ap);
794
795	fatalBuf[511] = 0;
796
797	stream_flush_buffer();
798	display_flush();
799
800	[[mainMachine display] displayFatalError: [NSString stringWithFormat: @"%s (PC=#%x)", fatalBuf, machine.zpc]];
801	NSLog(@"%s (PC=#%x)", fatalBuf, machine.zpc);
802
803	display_exit(1);
804}
805
806void zmachine_warning(char* format, ...) {
807	char fatalBuf[512];
808	va_list  ap;
809
810	va_start(ap, format);
811	vsnprintf(fatalBuf, 512, format, ap);
812	va_end(ap);
813
814	fatalBuf[511] = 0;
815
816#ifdef DEBUG
817	NSLog(@"Warning: %s", fatalBuf);
818#endif
819
820	stream_flush_buffer();
821	display_flush();
822
823	[[mainMachine display] displayWarning: [NSString stringWithFormat: @"%s (PC=#%x)", fatalBuf, machine.zpc]];
824}
825