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