INT 21h AH=31h ends the program like INT 21h AH=4Ch, but shrinks its block to DX paragraphs and keeps them, so the code in them goes on running whenever an interrupt it hooked comes: mouse drivers, keyboard enhancers, pop-up utilities and DOSKEY are made this way. The program's other memory blocks stay allocated to it, and the parent sees AH = 3 from INT 21h AH=4Dh.
The resident part goes first in the file and the installer after it, so the installer itself is not kept: DX covers the PSP and the code up to the end of the resident part, rounded up to paragraphs. In a .COM, offsets count from the start of the PSP, so the offset of the end label is already the number of bytes to keep:
.MODEL TINY.CODEORG 100hSTART: JMP INSTALLOLD1C DD ? ; the previous INT 1Ch handlerNEW1C PROC FAR ; resident: called 18.2 times a secondPUSH ESPUSH AXMOV AX, 0B800h ; the colour text screenMOV ES, AXINC BYTE PTR ES:[158] ; the top right character changesPOP AXPOP ESJMP CS:OLD1C ; chain to the old handler, which IRETsNEW1C ENDPRESEND LABEL BYTE ; everything from here is droppedINSTALL:MOV AX, 351Ch ; DOS: get vector 1Ch in ES:BXINT 21hMOV WORD PTR OLD1C, BXMOV WORD PTR OLD1C+2, ESMOV AX, 251Ch ; DOS: set vector 1Ch to DS:DXMOV DX, OFFSET NEW1CINT 21hMOV ES, DS:[2Ch] ; free our copy of the environmentMOV AH, 49hINT 21hMOV DX, OFFSET RESEND + 15 ; bytes from the PSP, rounded upMOV CL, 4SHR DX, CL ; in paragraphsMOV AX, 3100h ; DOS: stay resident, return code 0INT 21hEND START
Writing the resident part
A handler runs in the middle of some other program. DS and ES are that program's, so the handler reaches its own variables through CS (CS:OLD1C), saves every register it changes, and ends with IRET or by jumping on to the handler it replaced. DOS is not reentrant: a handler for a hardware interrupt (the timer, the keyboard) must not call INT 21h while DOS is busy, which the InDOS flag from INT 21h AH=34h tells. Freeing the environment, as above, saves a few hundred bytes.
Why there is no runnable example
A resident program stays until the machine restarts. Nothing in DOS unloads it unless it was written to unload itself, and each run of an example would load one more copy and hook the vector again. Examples in this guide leave the machine as they found it, so this one is a shape to copy into a project of your own; restart the box to get rid of what it leaves behind.