/* * This file is part of zaniWok. * * zaniWok 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, or (at your * option) any later version. See the file COPYING for more * information. * * zaniWok 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. */ #import "Computer.h" #import "Controller.h" #import "compile.h" #import "memory.h" #import "opcodes.h" #import "handlers.h" #import "WozView.h" #import "ZaniWokApp.h" #import "sound.h" #import "PeripheralCard.h" #import #import #import #import #import #define KEY_LA 0 #define KEY_RA 1 #define KEY_LC 2 #define KEY_RC 3 @implementation Computer static void update_display(DPSTimedEntry tag, double now, id self) { check_flush_speaker([self machineState]->speaker_state); [[self wozView] updateScreen:NO :now]; } - init { MemoryElement **r, **w; int i; [super init]; [NXApp loadNibSection:"Computer.nib" owner:self]; /* A good, healthy memset never hurt anyone. */ memset(&machine_state, 0, sizeof machine_state); /* Allocate memory for system memory and base-of-page pointers. */ NX_MALLOC(machine_state.read_base_p, MemoryElement *, 0x100); NX_MALLOC(machine_state.write_base_p, MemoryElement *, 0x100); NX_MALLOC(realmem, MemoryElement, REALMEMSIZE); machine_state.sysmem = realmem + 0x4000; /* Set up the mutex that controls who's setting interrupt bits. */ machine_state.interrupt_mutex = mutex_alloc(); machine_state.pause_mutex = mutex_alloc(); machine_state.has_paused_mutex = mutex_alloc(); /* No execute cthread yet; we'll create one when we power up. */ execute_cthread = NO_CTHREAD; isPaused = NO; appleKey = optionKey = shiftKey = 0; appleKeyBit = NX_NEXTRALTKEYMASK; optionKeyBit = NX_NEXTLALTKEYMASK; bufferKeys = NO; maxPasteLines = 5; translateNL = YES; /* * Initialize the base-of-page pointers (used for bank * switching). */ /* Set up RAM pointers */ r = machine_state.read_base_p; w = machine_state.write_base_p; for (i = 0; i <= 0xC0; i++) *r++ = *w++ = &machine_state.sysmem[i * 0x100]; /* Set up ROM pointers. */ r = &machine_state.read_base_p[0xC1]; w = &machine_state.write_base_p[0xC1]; for (i = 0xC1; i <= 0xFF; i++) { *r++ = &machine_state.sysmem[INTROM + (i - 0xC1) * 0x100]; *w++ = &machine_state.sysmem[JUNK]; /* Don't write over ROMS! */ } /* FIXME */ for (i = 1; i <= 7; i++) { char buf[16]; const char *v; sprintf(buf, "CardInSlot%d", i); v = NXGetDefaultValue([NXApp appName], buf); if (v != NULL && strcmp(v, "none")) [self loadPeripheralCard:v inSlot:i]; } /* Set up the window */ [window setDelegate:self]; [window addToEventMask:(NX_MOUSEMOVEDMASK | NX_LMOUSEDOWNMASK | NX_LMOUSEUPMASK | NX_RMOUSEDOWNMASK | NX_RMOUSEUPMASK | NX_LMOUSEDRAGGED | NX_RMOUSEDRAGGED)]; /* * This may look odd, but it guarantees that the Computer gets * the mouse events. This can't happen if the Computer is the * first responder. */ [window makeFirstResponder:wozView]; [self setNextResponder:[wozView nextResponder]]; [wozView setNextResponder:self]; /* * Tell the WozView about the relevant display flags & memory * areas. */ [wozView setDisplayInfo:&machine_state.flags:machine_state.sysmem]; /* Set up a timed entry to update the display 10 times a second. */ timedEntry = DPSAddTimedEntry(0.1, (DPSTimedEntryProc) update_display, self, 1); return self; } - free { [self powerOff]; /* Free up all the space we allocated. */ NX_FREE(realmem); NX_FREE(machine_state.read_base_p); NX_FREE(machine_state.write_base_p); NX_FREE(machine_state.rw_func_list); NX_FREE(machine_state.routine_func_list); NX_FREE(machine_state.card_selector_list); mutex_free(machine_state.interrupt_mutex); mutex_free(machine_state.pause_mutex); mutex_free(machine_state.has_paused_mutex); return[super free]; } - powerUp { MemoryElement **r, **w; char path[MAXPATHLEN + 1]; id cards[8]; int i; /* Kill the old thread, if it exists. */ [self kill6502thread]; /* Remove all cards. */ for (i = 1; i < 8; i++) { cards[i] = machine_state.peripheral_card[i]; /* Make backup. */ [machine_state.peripheral_card[i] removeFromSlot]; } /* HACK! FIXME! */ machine_state.speaker_state = init_speaker(); assert(machine_state.speaker_state != NULL); /* Turn off all pending interrupts. */ machine_state.interrupt_pending = 0; /* Turn off debug mode. */ machine_state.debug = NO; /* zero out all Apple ][ memory */ memset(realmem, 0, REALMEMSIZE * sizeof(MemoryElement)); #ifdef NOTFULLII /* Hey! I like it! Makes it a real Apple! */ /* * Fill the primary text pages with spaces so we don't get the * annoying "inverse-@" display. */ for (i = 0; i < 0x400; i++) { machine_state.sysmem[TEXT1 + i].mem.byte = 0xA0; machine_state.sysmem[AUXTEXT1 + i].mem.byte = 0xA0; } #endif /* * Allocate memory for the array of read-write handlers and * install default */ NX_REALLOC(machine_state.rw_func_list, typeof(machine_state.rw_func_list[0]), 1); machine_state.rw_func_list[0] = handle_modified_opcode; machine_state.num_rw_funcs = 1; /* Free up any old storage set up for the routine handlers. */ NX_FREE(machine_state.routine_func_list); machine_state.routine_func_list = NULL; machine_state.num_routine_funcs = 0; /* Free up any old storage set up for the card selectors. */ NX_FREE(machine_state.card_selector_list); machine_state.card_selector_list = NULL; machine_state.num_card_selectors = 0; /* Set up the default softswitch/trapped routine handlers. */ setup_default_handlers(&machine_state); /* * Load up the ROMs from either /LocalLibrary or ~/Library. * FIXME */ sprintf(path, "%s/Library/Apple2/ROM_IMAGE", NXHomeDirectory()); if (![self bloadFile:path :INTROM] && ![self bloadFile:"/LocalLibrary/Apple2/ROM_IMAGE" :INTROM]) { NXRunAlertPanel(NULL, "Unable to find ROM_IMAGE in either " "~/Library/Apple2/ROM_IMAGE or " "/LocalLibrary/Apple2/ROM_IMAGE", NULL, NULL, NULL); return nil; } /* Set up all of the registers and CC flags. */ machine_state.pc = (machine_state.sysmem[ROM + 0x2FFC].mem.byte | machine_state.sysmem[ROM + 0x2FFD].mem.byte << 8); machine_state.sp = 0xFF; machine_state.a = 0; machine_state.x = 0; machine_state.y = 0; machine_state.b_flag = 1; /* Doesn't really matter. */ machine_state.c_flag = 0; machine_state.d_flag = 0; machine_state.i_flag = 0; machine_state.n_flag = 0; machine_state.v_flag = 0; machine_state.z_flag = 0; machine_state.cycles = 0; /* * Initialize the base-of-page pointers (used for bank * switching). */ /* Set up RAM pointers */ r = machine_state.read_base_p; w = machine_state.write_base_p; for (i = 0; i <= 0xC0; i++) *r++ = *w++ = &machine_state.sysmem[i * 0x100]; /* Set up ROM pointers. */ r = &machine_state.read_base_p[0xC1]; w = &machine_state.write_base_p[0xC1]; for (i = 0xC1; i <= 0xFF; i++) { *r++ = &machine_state.sysmem[INTROM + (i - 0xC1) * 0x100]; *w++ = &machine_state.sysmem[JUNK]; /* Don't write over ROMS! */ } /* Re-install all of the cards. */ for (i = 1; i < 8; i++) { [cards[i] insert:&machine_state inComputer:self inSlot:i]; machine_state.peripheral_card[i] = cards[i]; } [window makeKeyAndOrderFront:self]; /* Fork off the new 65c02 thread. */ execute_cthread = cthread_fork((cthread_fn_t) exec, (any_t) & machine_state); return self; } - powerOff { int i; /* Nuke the timed entry ASAP */ if (timedEntry != NULL) DPSRemoveTimedEntry(timedEntry); /* Terminate the 6502 thread (if it's running). */ [self kill6502thread]; /* Remove all peripheral cards. */ for (i = 1; i < 8; i++) [[machine_state.peripheral_card[i] removeFromSlot] free]; return self; } - awake { MemoryElement **r, **w; int i; [super awake]; [NXApp loadNibSection:"Computer.nib" owner:self]; /* Allocate memory for system memory and base-of-page pointers. */ NX_MALLOC(machine_state.read_base_p, MemoryElement *, 0x100); NX_MALLOC(machine_state.write_base_p, MemoryElement *, 0x100); NX_MALLOC(realmem, MemoryElement, REALMEMSIZE); machine_state.sysmem = realmem + 0x4000; /* Set up the mutex that controls who's setting interrupt bits. */ machine_state.interrupt_mutex = mutex_alloc(); machine_state.pause_mutex = mutex_alloc(); machine_state.has_paused_mutex = mutex_alloc(); /* No execute cthread yet; we'll create one when we power up. */ execute_cthread = NO_CTHREAD; isPaused = NO; appleKey = optionKey = shiftKey = 0; /* * Initialize the base-of-page pointers (used for bank * switching). */ /* Set up RAM pointers */ r = machine_state.read_base_p; w = machine_state.write_base_p; for (i = 0; i <= 0xC0; i++) *r++ = *w++ = &machine_state.sysmem[i * 0x100]; /* Set up ROM pointers. */ r = &machine_state.read_base_p[0xC1]; w = &machine_state.write_base_p[0xC1]; for (i = 0xC1; i <= 0xFF; i++) { *r++ = &machine_state.sysmem[INTROM + (i - 0xC1) * 0x100]; *w++ = &machine_state.sysmem[JUNK]; /* Don't write over ROMS! */ } #if 1 /* Set up the window */ [window setDelegate:self]; [window addToEventMask:(NX_MOUSEMOVEDMASK | NX_LMOUSEDOWNMASK | NX_LMOUSEUPMASK | NX_RMOUSEDOWNMASK | NX_RMOUSEUPMASK | NX_LMOUSEDRAGGED | NX_RMOUSEDRAGGED)]; /* * This may look odd, but it guarantees that the Computer gets * the mouse events. This can't happen if the Computer is the * first responder. */ [window makeFirstResponder:wozView]; [self setNextResponder:[wozView nextResponder]]; [wozView setNextResponder:self]; #endif /* * Tell the WozView about the relevant display flags & memory * areas. */ [wozView setDisplayInfo:&machine_state.flags:machine_state.sysmem]; /* Set up a timed entry to update the display 10 times a second. */ timedEntry = DPSAddTimedEntry(0.1, (DPSTimedEntryProc) update_display, self, 1); return self; } - read:(NXTypedStream *) stream { [super read:stream]; memset(&machine_state, 0, sizeof machine_state); NXReadTypes(stream, "iiiii", &bufferKeys, &translateNL, &maxPasteLines, &appleKeyBit, &optionKeyBit); NXReadArray(stream, "@", 8, machine_state.peripheral_card); //window = NXReadObject(stream); //wozView = NXReadObject(stream); return self; } - write:(NXTypedStream *) stream { [super write:stream]; NXWriteTypes(stream, "iiiii", &bufferKeys, &translateNL, &maxPasteLines, &appleKeyBit, &optionKeyBit); NXWriteArray(stream, "@", 8, machine_state.peripheral_card); //NXWriteObject(stream, window); //NXWriteObjectReference(stream, wozView); return self; } - reset { mutex_lock(machine_state.interrupt_mutex); machine_state.interrupt_pending |= RESET_PENDING; mutex_unlock(machine_state.interrupt_mutex); return self; } - insertPeripheralCard:aCard inSlot:(int)aSlot { BOOL wasPaused = isPaused; /* * Remove any card that was previously in that slot. nil will * do nothing. */ [[machine_state.peripheral_card[aSlot] removeFromSlot] free]; /* * Pause the 6502 so it doesn't freak out when the card is * inserted. This won't usually be necessary, but it assures * that the card is "instantly" inserted as far as the 6502 can * tell. */ if (!isPaused) [self pause6502thread]; /* Insert the card into the appropriate slot. */ [aCard insert:&machine_state inComputer:self inSlot:aSlot]; machine_state.peripheral_card[aSlot] = aCard; /* Restart the 6502 if it wasn't already inserted. */ if (!wasPaused) [self unpause6502thread]; return self; } /* * Loads and installs a peripheral card. PATH should be the FULL * path of the .a2card directory, and slot must be 1,2,3,4,5,6,7. * If a card is already present in that slot, it is removed and * freed. Returns self if successful, otherwise nil. */ - loadPeripheralCard:(const char *)cardName inSlot:(int)slot { char path[MAXPATHLEN + 1]; id bundle, card; /* Don't crash on NULL string, just to be nice. */ if (cardName == NULL) return nil; /* Figure out where this card is really at. */ if (cardName[0] == '/') strcpy(path, cardName); else /* Look in ~/Library and then /LocalLibrary. */ { sprintf(path, "%s/Library/Apple2/PeripheralCards/%s", NXHomeDirectory(), cardName); if (access(path, R_OK) != 0) sprintf(path, "/LocalLibrary/Apple2/PeripheralCards/%s", cardName); } /* Get the NXBundle for the card. */ bundle = [[NXBundle alloc] initForDirectory:path]; if (bundle == nil) return nil; /* Load up the card. */ card = [[[bundle principalClass] alloc] initFromBundle:bundle]; return[self insertPeripheralCard:card inSlot:slot]; } - cardInSlot:(int)slot { if (slot >= 1 && slot <= 7) return machine_state.peripheral_card[slot]; return nil; } - (MachineState *) machineState { return &machine_state; } - (unsigned char)readByte:(unsigned)addr { return read_mem_byte(&machine_state, addr); } - (unsigned short)readShort:(unsigned)addr { return read_mem_short(&machine_state, addr); } - writeByte:(unsigned)addr :(unsigned char)value { write_mem_byte(&machine_state, addr, value); return self; } - writeShort:(unsigned)addr :(unsigned short)value { write_mem_short(&machine_state, addr, value); return self; } /* * This allows you raw access to a MemoryElement, which may be * necessary when the memory you want to read from is not mapped * in anywhere in the 6502's address space. This routine handles * "trapped" addresses. */ - (unsigned char)readMemoryElement:(MemoryElement *) m { if (m->mem.read_special) return machine_state.rw_func_list[m->mem.read_func] (READ, m, &machine_state); else return m->mem.byte; } /* * This allows you raw access to a MemoryElement, which may be * necessary when the memory you want to write to is not mapped in * anywhere in the 6502's address space. This routine handles * "trapped" addresses. */ - writeMemoryElement:(MemoryElement *) m :(unsigned char)value { m->mem.byte = value; if (m->mem.write_special) machine_state.rw_func_list[m->mem.write_func] (WRITE, m, &machine_state); return self; } /* dst_addr refers to an address in the 6502's memory space. */ - copyBytesToApple:(const unsigned char *)src :(unsigned short)dst_addr :(int)num_bytes { copy_bytes_to_apple(&machine_state, src, dst_addr, num_bytes); return self; } /* src_addr refers to an address in the 6502's memory space. */ - copyBytesFromApple:(unsigned short)src_addr :(unsigned char *)dst :(int)num_bytes { copy_bytes_from_apple(&machine_state, src_addr, dst, num_bytes); return self; } /* * Note that memloc corresponds to the address in sysmem, not in * the 6502's memory space. */ - bloadFile:(const char *)file :(int)memloc { MemoryElement *m; NXStream *s; int c; s = NXMapFile(file, NX_READONLY); if (s == NULL) return nil; /* This is hideously inefficient, but it really doesn't matter. */ for (m = &machine_state.sysmem[memloc]; (c = NXGetc(s)) != EOF; m++) { if (m >= realmem + REALMEMSIZE) { NXCloseMemory(s, NX_FREEBUFFER); return nil; } [self writeMemoryElement:m :c]; } NXCloseMemory(s, NX_FREEBUFFER); return self; } - (BOOL) isPaused { return isPaused; } /* * This routine freezes the 6502 and returns. You can restart it * with unpause6502thread. */ - pause6502thread { if (!isPaused && execute_cthread != NO_CTHREAD) { /* * Grab the mutex that lets the 6502 tell us it has paused. * The 6502 will unlock this once it is about to pause. FIXME * - should use a condition_t for this. */ mutex_lock(machine_state.has_paused_mutex); /* Grab the mutex that will "hold" the 6502. */ mutex_lock(machine_state.pause_mutex); /* Signal to the 6502 that it should pause now. */ mutex_lock(machine_state.interrupt_mutex); machine_state.interrupt_pending |= PAUSE_PENDING; mutex_unlock(machine_state.interrupt_mutex); /* Wait until it is actually paused. */ mutex_lock(machine_state.has_paused_mutex); mutex_unlock(machine_state.has_paused_mutex); isPaused = YES; } return self; } /* Undoes the effect of pause6502thread. */ - unpause6502thread { if (isPaused && execute_cthread != NO_CTHREAD) { mutex_unlock(machine_state.pause_mutex); isPaused = NO; } return self; } - kill6502thread { if (execute_cthread != NO_CTHREAD) { /* Set the poweroff interrupt flag. */ mutex_lock(machine_state.interrupt_mutex); machine_state.interrupt_pending |= POWEROFF_PENDING; mutex_unlock(machine_state.interrupt_mutex); /* If it's paused, unpause it so it can detect the poweroff. */ if (isPaused) [self unpause6502thread]; /* Block until it's dead */ cthread_join(execute_cthread); execute_cthread = NO_CTHREAD; } return self; } - setJoystickXY:(int)x :(int)y { machine_state.pdl[0] = x; machine_state.pdl[1] = y; return self; } - window { return window; } - wozView { return wozView; } /* Changing Configs */ - setBufferKeys:(BOOL) buffer { bufferKeys = buffer; return self; } - setTranslateNL:(BOOL) trans { translateNL = trans; return self; } - setMaxPasteLines:(int)max { maxPasteLines = max; return self; } - changeKeyMap:sender { int t; int ab = 0, ob = 0; t = [[sender selectedCell] tag]; switch (t) { case 0: ab = KEY_RA; ob = KEY_LA; break; case 1: ab = KEY_LC; ob = KEY_LA; break; case 2: ab = KEY_LA; ob = KEY_RA; break; case 3: ab = KEY_RC; ob = KEY_RA; break; } switch (ab) { case KEY_LA: appleKeyBit = NX_NEXTLALTKEYMASK; break; case KEY_RA: appleKeyBit = NX_NEXTRALTKEYMASK; break; case KEY_LC: appleKeyBit = NX_NEXTLCMDKEYMASK; break; case KEY_RC: appleKeyBit = NX_NEXTRCMDKEYMASK; break; } switch (ob) { case KEY_LA: optionKeyBit = NX_NEXTLALTKEYMASK; break; case KEY_RA: optionKeyBit = NX_NEXTRALTKEYMASK; break; case KEY_LC: optionKeyBit = NX_NEXTLCMDKEYMASK; break; case KEY_RC: optionKeyBit = NX_NEXTRCMDKEYMASK; break; } return self; } - (int)appleKeyBit { return appleKeyBit; } - (int)optionKeyBit { return optionKeyBit; } /* Event Handling */ - setAppleKey:(int)upordown { appleKey = upordown; return self; } - setOptionKey:(int)upordown { optionKey = upordown; return self; } - setShiftKey:(int)upordown { shiftKey = upordown; return self; } - flagsChanged:(NXEvent *) theEvent { machine_state.flags.pb0 = appleKey; machine_state.flags.pb1 = optionKey; return self; } - keyDown:(NXEvent *) theEvent { int key = theEvent->data.key.charCode; void *malloc(); //[self setSel:0 :0]; if (theEvent->flags & NX_NUMERICPADMASK) switch (theEvent->data.key.charCode) { case 0xAC: /* Left Arrow */ key = 0x08; break; case 0xAD: /* Up Arrow */ key = 0x0B; break; case 0xAE: /* Right Arrow */ key = 0x15; break; case 0xAF: /* Down Arrow */ key = 0x0A; break; } #if 0 if (bufferKeys) { if ((key == 127) && AppleKey && (theEvent->flags & NX_CONTROLMASK)) { /* * Clear buffer. Apple-Control-Delete, just like GS. Sure * wish the NeXT had a way to do it in other apps. */ keyboardPos = 0; MegaLastKey = 0; return self; } if (!keyboardBuffer) keyboardBuffer = malloc(sizeof(unsigned char) * 32); if (keyboardPos < 32) { keyboardBuffer[keyboardPos] = key; keyboardPos++; } } else { if (!keyboardBuffer) keyboardBuffer = malloc(sizeof(unsigned char) * 32); keyboardPos = 0; keyboardBuffer[keyboardPos] = key; keyboardPos++; } #endif machine_state.key_value = key; machine_state.key_ready = YES; PSobscurecursor(); return self; } - mouseMoved:(NXEvent *) event { NXPoint p = event->location; NXRect rect; int x, y; /* * Get the window's size so we have coord's for the * mouse->joystick xform. */ [window getFrame:&rect]; rect.origin.x = rect.origin.y = 0; /* Convert from the mouse position to joystick coord's. */ x = ((int)((p.x - NX_MIDX(&rect)) / (NX_WIDTH(&rect) *.35) * 128) + 127); y = ((int)-((p.y - NX_MIDY(&rect)) / (NX_HEIGHT(&rect) *.35) * 128) + 127); /* Pin the values between 0 and 255 */ if (x < 0) x = 0; else if (x > 255) x = 255; if (y < 0) y = 0; else if (y > 255) y = 255; [self setJoystickXY:x :y]; return self; } - mouseDragged:(NXEvent *) event { //machine_state.flags.pb0 = 1;/* Just in case. */ return[self mouseMoved:event]; } - rightMouseDragged:(NXEvent *) event { //machine_state.flags.pb1 = 1;/* Just in case. */ return[self mouseMoved:event]; } - (BOOL) acceptsFirstResponder { return YES; } /* Window Delegate Methods */ - windowWillClose:sender { /* Make darn sure they want to close this window. */ if (NXRunAlertPanel(NULL, "Do you really want to close this emulator? " "Any work in progress will be lost.", "Cancel", "Close Emulator", NULL) != NX_ALERTALTERNATE) return nil; [self powerOff]; /* Tell ZaniWokApp to delete us. */ [[NXApp delegate] removeEmulator:self]; #if 0 /* Make sure we get freed up. */ [NXApp delayedFree:self]; #endif return self; } @end