/* Public Domain - By Ross Ridge */ /* * This programme demonstrates just how small of a TSR you can write * entirely in C. When compiled with Borland C++ 3.1 the resident * portion of this programme is only 64 bytes long, as reported by * MS-DOS's MEM command. The function of this programme is trivial, * but possibly useful. It acts as replacement Caps Lock keyboard * light by displaying a character in the upper-right hand corner of * the screen according to the current state of the Caps Lock key. * * The TSR achieves such a small memory footprint because, unlike a * normal TSR, it doesn't "Terminate and Stay Resident." IT installs * an interrupt service routine (ISR) at the end of memory (or the end * of a UMB), and then exits normally. * * This programme uses a number of Borland C++ specific features, * like segment pointers (the _seg keyword), pragmas to set segment * names and CS relative near pointers. The use of the last feature * in particular will probably make this hard to port other compilers. * */ #include #include #include #include #include #include #if 1 #define INT21() geninterrupt(0x21) #define get_strategy() (_AX=0x5800, INT21(), _AX) #define set_strategy(strat) (_BL = (strat), _BH = 0, _AX = 0x5801, INT21()) #define get_umb_link_state() (_AX=0x5802, INT21(), _AL) #define set_umb_link_state(state) (_BX = (state), _AX = 0x5803, INT21()) #define set_current_psp(psp) (_BX = (psp), _AH = 0x50, INT21()) #define get_mode() (_AH = 0x0f, geninterrupt(0x10), _AL) #else /* Function versions of the above macro's are provided for those who don't like register variable (eg. _AX). None of them are used in the ISR so your choice here has no effect on the size of the resident portion of the programme. */ union REGS regs; unsigned short get_strategy(void) { regs.x.ax = 0x5800; intdos(®s, ®s); return regs.x.ax; } void set_strategy(unsigned char strat) { regs.x.ax = 0x5801; regs.h.bl = strat; regs.h.bh = 0; intdos(®s, ®s); } unsigned char get_umb_link_state(void) { regs.x.ax = 0x5802; intdos(®s, ®s); return regs.h.al; } void set_umb_link_state(unsigned short state) { regs.x.ax = 0x5803; regs.x.bx = state; intdos(®s, ®s); } void set_current_psp(unsigned short psp) { regs.h.ah = 0x50; regs.x.bx = psp; intdos(®s, ®s); } unsigned char get_mode(void) { regs.h.ah = 0x0F; int86(0x10, ®s, ®s); return regs.h.al; } #endif #define MDA_SEG ((unsigned short _seg *)0xB000) #define CGA_SEG ((unsigned short _seg *)0xB800) /* Imports from ISR.C */ extern void interrupt (far isr)(void); extern void interrupt (*far old_isr)(void); extern char far end_isr; extern unsigned short _seg *far screen_seg; int main(void) { char _seg *new_psp; unsigned short old_strat; unsigned short isr_size; unsigned short isr_paras; int old_state; int ret; char _seg *new_env; if (_osmajor < 3) { fprintf(stderr, "MS-DOS 3.0 or better required.\n"); return 1; } /* Calculate the size of the ISR of the programme. */ isr_size = FP_OFF(&end_isr) - FP_OFF(isr); isr_paras = (FP_OFF(&end_isr) + 15) / 16; if (isr_paras < 4) { isr_paras = 4; /* need to be at least 48 bytes */ } if (allocmem(2, (unsigned *)&new_env) != -1) { abort(); } _fmemcpy(new_env + 0, "\0\001\0C:\\ISR.EXE\0\0\0", 32); /* If possible load the ISR into a UMB, otherwise just try to load it at the end of memory. (Windows doesn't like that though.) */ old_strat = get_strategy(); if (_osmajor >= 5 && _osmajor != 10) { old_state = get_umb_link_state(); set_umb_link_state(1); set_strategy(0x82); } else { set_strategy(0x02); } /* Now allocate and zero out the memory for the ISR */ if (allocmem(isr_paras, (unsigned *) &new_psp) != -1) { abort(); } _fmemset(new_psp + 0, 0, isr_paras * 16); /* Give ownership the ISR's memory to the ISR itself. */ * (short far *) (new_psp + 0x2C) = (short) new_env; set_current_psp((unsigned) new_psp); ret = setblock((unsigned) new_psp, isr_paras); set_current_psp(_psp); /* Restore the old allocation strategy and UMB link state */ set_strategy(old_strat); if (_osmajor >= 5 && _osmajor != 10) { set_umb_link_state(old_state); } if (ret != -1) { abort(); } #if 0 _fmemset(MK_FP((unsigned) new_psp - 1, 0x0e), 0, 2); #else /* A hack to put a name in to the MCB of our ISR's memory block. This lets MEM display something more interesting than nothing or garbage as the owner of this block. */ _fmemcpy(MK_FP((unsigned) new_psp - 1, 0x08), "ISR\0\0\0\0", 8); #endif /* Initialize the ISR's data */ old_isr = getvect(0x1c); if ((get_mode() & 0x7f) == 7) { screen_seg = MDA_SEG; } else { screen_seg = CGA_SEG; } _fmemcpy(new_psp + FP_OFF(isr), isr, isr_size); /* Install the ISR in to timer tick interrupt and we're done. */ setvect(0x1c, (void interrupt (*)(void)) (new_psp + FP_OFF(isr))); printf("Old strat: %d; _osmajor: %d\n", old_strat, _osmajor); printf("Link state: %d\n", get_umb_link_state()); printf("isr = %Fp; new_psp = %Fp\n", (void far *)isr, (void far *)new_psp); printf("isr_size = %u\n", isr_size); printf("isr_paras = %u\n", isr_paras); printf("mcb_name = '%Fs'\n", MK_FP((unsigned)new_psp - 1, 0x8)); return 0; }