/* Keyboard emulation for Sim. */ #include "sim.h" #include "common.h" #include "kbdemu.h" #include "misc.h" /* The following macros give the same results as TIGCCLIB KEY_ macros, but for the other calc (ie 89 for 92+/V200, 92+/V200 for 89). */ #define KKEY_DOWN (!CALCULATOR?344:340) #define KKEY_DOWNLEFT (!CALCULATOR?345:342) #define KKEY_LEFT (!CALCULATOR?337:338) #define KKEY_RIGHT (!CALCULATOR?340:344) #define KKEY_UP (!CALCULATOR?338:337) #define KKEY_UPRIGHT (!CALCULATOR?342:345) /* A pointer to the AMS Rom Calls table. */ long *OldRCTable = NULL; /* The array used by the handler to convert the return of ngetchx(). Format : emulated_calc_code, this_calc_code. Don't forget to update NgetchxConvSize in kbdasm.asm when modifying this array ! */ const short NgetchxConv[] = { KKEY_DOWN, KEY_DOWN, KKEY_DOWNLEFT, KEY_DOWNLEFT, KKEY_LEFT, KEY_LEFT, KKEY_RIGHT, KEY_RIGHT, KKEY_UP, KEY_UP, KKEY_UPRIGHT, KEY_UPRIGHT }; /* A backup of the long word at 0x104. */ static void *Old104 = NULL; /* Install the handler for the ngetchx() emulation. */ static inline void InstallNgetchxEmu (HARDWARE_PARM_BLOCK *parm_block) { long *rom_calls_table; long *new_table; char *rom_base; /* Save it now ! It will become soon invalid. */ rom_base = ROM_base; /* Create a table of Rom Calls in RAM. */ /* Point to the number of Rom Calls. */ rom_calls_table = *(long**)0x400C8 - 1; /* Place for the table and the number of Rom Calls. */ new_table = xmalloc (*rom_calls_table * sizeof(long) + sizeof (*rom_calls_table)); /* Copy the AMS table to the new one. */ memcpy (new_table, rom_calls_table, *rom_calls_table * sizeof(long) + sizeof (*rom_calls_table)); OldRCTable = rom_calls_table + 1; /* The new vector points just after the number of Rom Calls. */ *(long**)0x400C8 = (new_table += 1); /* Set the pointer to ngetchx() (ROM Call 0x51) to our handler. */ /* No need to add 0x40000, we are already in the ghost space (EXECUTE_IN_GHOST_SPACE). */ (char*)new_table[0x51] = (char*)NgetchxEmuHandler; /* By changing 0xC8, routines getting a pointer to the hardware parameter block Rom Base (GrayOn() for example) will get a wrong value : the and.l #$600000,dreg will return 0. The pointer is at (char*)ROM_base + 0x104. So we write the pointer at 0x104. But this pointer should be less than ROM_base + 0x10000 = 0x10000 to be valid ! This is the role of the buffer PARM_BLOCK : it should be a buffer local to a function, ie on the stack. So PARM_BLOCK < 0x10000. */ memcpy (parm_block, *(void**)((char*)rom_base + 0x104), sizeof (HARDWARE_PARM_BLOCK)); Old104 = *(void**)0x40104; /* make a backup of it */ *(void**)0x40104 = parm_block; } /* Install the handler for the lowlevel emulation (keyboard scanning with _rowread() for example). PRGM is a pointer to the beginning of the program code, PLEN is the size of the program code. */ static inline void InstallLowLevelEmu (char *prgm, unsigned short plen) { char *prgm_end; short instr, instr1, instr2; /* A stack containing the address where a patch was applied, and the old opcode (short). */ char *stack; char *tmp_stack; unsigned short stack_size; /* number of patches in the stack */ /* We need the real addresses for the stack, in the ghost space. */ prgm += 0x40000; stack = xmalloc (2); /* just to create the block */ stack_size = 0; prgm_end = prgm + plen; /* Search all the longwords containing 0x60001B */ while (prgm < prgm_end) { instr = *(short*)(prgm - 2); instr1 = instr & 0b0000111111000000; instr2 = instr & 0b0000000111000000; if (*(long*)prgm == 0x60001B /* It must be a move.b $60001B,dest with dest != d(An) and d(An,Ri) and (abs).w and (abs).l (because the instruction must not be more than 6 bytes long (size of a jsr ().l)). */ /* check if it is move.b $60001B,dest */ && ((instr & 0b1111000000111111) == 0b0001000000111001) /* neither (abs.w) nor (abs.l) */ && instr1 != 0b0000000111000000 && instr1 != 0b0000001111000000 /* neither d(An) nor d(An,Ri) */ && instr2 != 0b0000000101000000 && instr2 != 0b0000000110000000) { stack_size++; stack = xrealloc (stack, stack_size * (sizeof (void*) + sizeof (short))); /* Jump to the end of the stack. */ tmp_stack = stack + (stack_size - 1) * (sizeof (void*) + sizeof (short)); prgm -= 2; /* point to the beginning of the instruction */ *(((char**)tmp_stack)++) = prgm; /* push the address of the instruction */ *(short*)tmp_stack = *(short*)prgm; /* and push the instruction */ /* Patch it : write a 'jsr LowlevelEmuHandler' on this instruction. */ *((short*)prgm)++ = 0x4EB9; *((char**)prgm)++ = (char*)LowlevelEmuHandler; /* Now PRGM points just after this instruction. */ } else (short*)prgm += 1; /* next word */ } /* while (prgm < prgm_end) */ /* Write them in the global variables for the handler in assembly. */ LowLevelKbdPatches = stack; LowLevelKbdPatchesNum = stack_size; } /* Install the handler for the forced lowlevel emulation : necessary for dirty lowlevel keyboard scanning routines that 'btst' directly in $60001B, or for optimized ones that do not read $60001B from an absolute address. PRGM is a pointer to the beginning of the program code, PLEN is the size of the program code. */ static inline void InstallForcedLowLevelEmu (char *prgm, unsigned short plen) { char *prgm_end; prgm_end = prgm + plen; /* - Search ($600018).l, that would be part of : move.w ?,$600018 movea.l #$600018,? lea $600018,? and replace them by $600019. ForcedLowlevelEmuHandler() will catch the address error. - Search ($60001B).l that would be part of : move.b $60001B,? movea.l #$60001B,? lea $60001B,? btst ?,$60001B and replace them by Fake60001B, that will be created by the handler. We cannot check if they are part of real instructions : there are sometimes the 'destination' in the instruction, it would need to write a real disassembler... */ while (prgm < prgm_end - 2) { if (*(long*)prgm == 0x600018) (*(long*)prgm)++; else if (*(long*)prgm == 0x60001B) *(char**)prgm = &Fake60001B; prgm += 2; } /* Install the address error handler. */ OldAddErrorHandler = *(void**)0x4000C; /* No need to add 0x40000, we are already in the ghost space (EXECUTE_IN_GHOST_SPACE). */ *(void**)0x4000C = ForcedLowlevelEmuHandler; } /* Uninstall the handler for the ngetchx() emulation. * Registered with atexit(). */ static void UninstNgetchxEmu (void) { *(long**)0x400C8 = OldRCTable; *(void**)0x40104 = Old104; } /* Registered with atexit(). */ static void UninstLowLevelEmu (void) { free (LowLevelKbdPatches); } static void UninstForcedLowLevelEmu (void) { *(void**)0x4000C = OldAddErrorHandler; } /* Install the handlers for the keyboard emulation. PRGM is a pointer to the beginning of the program code, PLEN is the size of the program code. KBD_EMU_TYPE is the type of emulation asked. EMULATING_THIS_CALC must be TRUE if the user wants to remap the keyboard for a program for *his* calc. PARM_BLOCK must point to a *LOCAL* variable, that exists until the end of the program (see InstallNgetchxEmu()). */ void InstallKbdEmu (void *prgm, unsigned short plen, KbdEmuType kbd_emu_type, BOOL emulating_this_calc, HARDWARE_PARM_BLOCK *parm_block) { /* (We do not need the ngetchx() emulation if EMULATING_THIS_CALC is TRUE) */ if ((kbd_emu_type & KbdEmu_NGETCHX) && !emulating_this_calc) { InstallNgetchxEmu (parm_block); xatexit ((atexit_t)UninstNgetchxEmu); } if (kbd_emu_type & KbdEmu_LOWLEVEL) { InstallLowLevelEmu (prgm, plen); xatexit ((atexit_t)UninstLowLevelEmu); } else if (kbd_emu_type & KbdEmu_FORCED_LOWLEVEL) { InstallForcedLowLevelEmu (prgm, plen); xatexit ((atexit_t)UninstForcedLowLevelEmu); } }