\APEX.XPL	FEB-25-89	VERSION 1.8-X18
\COPYRIGHT 1980-1989, P.J.R. BOYLE.

\REVISION HISTORY:
\NOV-DEC-85, V1.8, 96 FILES MAX, AND NUMEROUS OTHER MODS, LOREN BLANEY.
\MAR-11-86, MODIFIED FOR THE 68000, L.B. (MARKED %%%)
\SEP-DEC-86, MODIFIED FOR NEW SYSTEM PAGE LAYOUT.
\MAR-13-87, CHANGED "SY" COMMAND FOR AMIGA AND FIXED MISC. LITTLE BUGS, L.B.
\APR-09-87, CHANGED STRING AND BLIT CONVENTIONS.
\FEB-25-89, ADDED OPENO(0) TO MAIN AND REMOVED OPENI(0) IN MAIN LOOP
\
\NOTES:
\This version is loaded using LOADX (LOAD residing at $3000). The WS command
\ is obsolete. These are the commands:
\
\	LOADX APEX
\	CHANGE DEFAULTS (N/Y)? Y
\	76800
\	76800
\	7C000
\	76800
\	NO
\	CTRL-P
\	SAVE SYSTEM.SYS
\
\The 68000 version retains the directory structure from the 6502 version.
\Note that the 16-bit values appear to have their bytes swapped to the 68000.
\
\In this code the word "device" is often used synonymously with "unit".
\The messages have been tailored for the Apple's 40-column, uppercase-only
\ display.
\
\CONTENTS:
\ NEXT CROUT CROUTX TXT NUMOUT NUMERIC HEX NUMIN ALPHANUM MOVENAME
\ VERIFY DOREBEGIN ERROR FILERR
\ PRDATE PRWEEKDAY PRSYSDATE PRNAME PRENTRY PRTDEV
\ VALDRV CHKFILE NAME FIND SORT DIRSORT MOVIT GETDIR PUTDIR 
\ WRTDIR RDDIR CHKDIR LOOKUP FINDFL
\ CMDGET CMDSTART CMDWS CMDNEW CMDSYS CMDSIZE CMDDATE
\ SHOWDIR CMDDIR CMDBD CMDTITLE CMDZERO CMDDF CMDDO
\ CHKNAME ENTER CLEAR REMOVE COPY PACK CLOFIL
\ CMDSQ CMDMAKE CMDDELETE CMDSAVE
\ OPENOT OPENIN CMDOPEN CMDCLOSE
\ CMDLIST CMDRENAME CMDUNLOCK CMDSUB
\ FIXSAV LOOKER DOCMD RUN MAIN

code
ABS=0,		RAN=1,		REM=2,		RESERVE=3,	SWAP=4,
REBEGIN=6,	CHIN=7,		CHOUT=8,	CRLF=9,		INTOUT=11,
TEXT=12,	OPENI=13,	OPENO=14,	CLOSE=15,
SPACE=18,	RERUN=19,	FWRITE=30,	FREAD=31,	FRUN=28,
FGET=33,	FASAVE=34,	FSAVE=35,	BLIT=36;	\%%%

\THE DIRECTORY:
addr	FNAME,		\THE NAME AND EXTENSION FOR EACH FILE
	FSTAT,		\THE STATUS FOR EACH FILE
	STAB,		\SORT TABLE, ORDER IN ASCENDING "FBLK"S
	NUMVAL,		\LAST ELEMENT OF "STAB" ARRAY (MAXSTB)
	DIRCHG,		\"$A5" INDICATES DIRECTORY NEEDS TO BE SORTED
	PRDEV,		\DEFAULT DEVICE NUMBER
	DFNAME,		\THE DEFAULT FILE NAME AND EXTENTION
	TITLE,		\TITLE OF THE VOLUME
	UNUSED,		\UNUSED SPACE
	FLAGS;		\I.E: PACK, BACKUP, CHECK, UNLOCKED, ABORT, EXTENDED DIR
\16-BIT ARRAYS IN THE DIRECTORY:				%%%
addr	FBLK,		\THE FIRST BLOCK OF EACH FILE
	LBLK,		\THE LAST BLOCK
	FEMBLK,		\EMPTY BLOCKS FILE LIST (FIRST BLOCK)
	LEMBLK,		\ (LAST BLOCK) SORTED LARGEST SIZE FIRST
	PMAXB,		\MAXIMUM BLOCK NUMBER (= UNIT SIZE -1)
	APEXID,		\A 4-BYTE VALUE USED TO RECOGNIZE AN APEX DISK
	VOLUME,		\UNIQUE VOLUME (DIRECTORY) ID NUMBER
	DIRDAT,		\DIRECTORY DATE (SYSTEM DATE)
	FDATE;		\DATE FOR EACH FILE

\SOME ABSOLUTE ADDRESS:
addr	SYSPAG,		\SYSTEM PAGE BASE ADDRESS
	CMDBUF;		\.CMD FILE BUFFER

\THE SYSTEM GLOBALS:
addr	LOCNAM,		\LOCAL NAME OF FILE (FROM TYPED IN COMMAND)
	MOSTR,		\ARRAY OF MONTH NAMES (STRING)
	RNAM;		\SYSTEM-WIDE DEFAULT FILE NAME AND EXTENSION
int	ABOFLG,		\FLAG: ABORT COMMAND FILES UPON ERRORS
	ACTDEV,		\DEVICE (UNIT) NUMBER OF ACTIVE DEVICE
	ARG1,		\COMMAND ARGUMENT
	ARG2,		\COMMAND ARGUMENT
	BACKBLK,	\BLOCK TO PUT BACKUP DIR IN
	BAKFLG,		\FLAG: BACKUPS WANTED
	BITS,		\ARRAY: BIT TABLE
	BLKSIZ,		\SIZE OF A BLOCK IN BYTES
	CHAR,		\LAST CHARACTER TYPED IN
	CHKFLG,		\FLAG: CHECKS WANTED
	CONHT,		\HEIGHT OF CONSOLE (LINES)
	CONWID,		\WIDTH OF CONSOLE (CHARACTERS)
	DIRBLK,		\BLOCK WHERE DIRECTORY STARTS
	DIRDEV,		\DEVICE WE HAVE THE DIRECTORY OF IN MEMORY
	DIRSIZ,		\DIRECTORY SIZE IN BLOCKS
	DRVSET,		\FLAG: DRIVE (UNIT) SPECIFIED
	FIRBLK,		\FIRST BLOCK OF FILE
	FLNO,		\NUMBER OF FILE
	GOTFILE,	\FLAG: 'PROC' NAME GOT A FILE NAME (NOT JUST A SPACE)
	INDATE,		\DATE OF LAST INFILE
	INSIZE,		\SIZE OF LAST OPENED INFILE (BLOCKS)
	LASBLK,		\LAST BLOCK OF FILE
	LINECTR,	\LINE COUNTER (TO PAUSE ON SCREEN BOUNDARIES)
	LOCDEV,		\DEVICE (UNIT) USER ASKED FOR IN TYPED IN COMMAND
	MAXBLK,		\MAXIMUM BLOCK NUMBER ALLOWED ON UNIT
	MAXFL,		\HIGHEST FILE NO.
	MAXSTB,		\LAST ELEMENT OF "STAB" ARRAY (NUMVAL)
	PAKFLG,		\FLAG: PACKING WANTED
	RDEV,		\DEFAULT UNIT NUMBER
	SPECIAL,	\BIT ARRAY: BACK UP, SIZE LIMIT, KEEP DATE
	SWAPFLG,	\FLAG: SWAPPING AREA IS VALID
	SWITCH,		\COMMAND SWITCH--I.E. CHAR FOLLOWING "/"
	SYSDAT,		\SYSTEM DATE
	SYSDEV,		\THE CURRENT SYSTEM DEVICE
	USERBLK;	\FIRST USER BLOCK (AFTER DIR AND RESCOD.SYS)

\32-BIT INTEGERS IN SYSTEM PAGE
int	INLBLK,		\LOW BLOCK NUMBER
	INHBLK,		\HIGH BLOCK NUMBER
	OTLBLK,		\FIRST BLOCK OF OUTPUT FILE (LOW BYTE)
	OTHBLK,		\LAST BLOCK OF OUTPUT FILE
	SYSBLK,		\BLOCK SYSTEM FILE IS IN
	SWPBLK,		\BLOCK SWAP FILE IS IN
	USRMEM,		\USER BASE ADDRESS
	PROSIZ,		\USER PROGRAM SIZE IN PAGES (NOT INCL SYSPAG)
	MAXTBL,		\BLOCK HANDLER LIMIT TABLE (MAX BLOCK + 1 OF SUB-DIRS)
	OFFTBL,		\BLOCK HANDLER OFFSET TABLE (BASE BLOCKS OF SUB-DIRS)
	CMDPTR,		\POINTER INTO COMMAND FILE BUFFER (COMBUF)
	;
int	DIRLEN;		\ARRAY: LENGTHS (IN WORDS) OF SEGMENTS OF THE DIRECTORY
def	MAXSEG=10;	\THE LAST SEGMENT (ELEMENT) IN "DIRLEN"
def	EXTPAT=$A5;	\FLAG PATTERN INDICATING EXTENDED DIR IS USED

\FOR MAIN:
int	HASH,		\COMMAND CODE
	II,		\SCRATCH
	COLD,		\FLAG: COLD START
	FILENO;		\RUN FILE NUMBER

\ASCII CONSTANTS:
def	BEL=$07, TAB=$09, LF=$0A, FF=$0C, CR=$0D, EOF=$1A, SP=$20;

\FILE STATUS IN THE DIRECTORY (FSTAT):
def	NULL=0, TENTATIVE=255, REPLACE=254, VALID=1;

\FILE STATUS IN SYSTEM PAGE (INFLG, OTFLG):
def	NOFILE=0, SETUP=1, CLOSED=255;

\SYSTEM REENTRY CONDITIONS (SYSENF):
def	SWAPIN=254, SAVEIN=255, BOOTIN=253, RELOAD=252;

\FAILED FLAG
def	NONE=$FFFF;

\DEFINE SOME OFFSETS INTO THE SYSTEM PAGE:	%%%
\(SEE MAIN FOR ADDITIONAL DEFINITIONS)
def	DEXTO=$5B,	\DEFAULT EXT FOR OUTPUT FILE
	DEXTI=$61,	\DEFAULT EXT FOR INPUT FILE
	DEFAUL=$67,	\SPECIAL DEFAULT FLAG BYTE

	SYSENF=$100,	\FLAG SHOWING REENTRY CONDITION
	SYSUNT=$101,	\UNIT SYSTEM IS ON
	DATOFF=$10A,	\SYSTEM DATE (SYSDAT)
	DATOF1=$10B,
	DATOF2=$10C,
	LOKMSK=$113,	\BIT ARRAY OF WRITE-LOCKED UNITS
	\++UNTUPD=$114,\	\BIT ARRAY SHOWING UNITS NEEDING ATTENTION
	DEFUNT=$115,	\USER'S DEFAULT UNIT

\OUTPUT FILE INFORMATION:
	OTFLG=$15E,	\OUTPUT FILE STATUS FLAGS (1=SETUP)
	OTNO=$15F,	\OUTPUT FILE NUMBER IN DIRECTORY
	OTUNT=$160,	\UNIT NUMBER OUTPUT FILE IS ON

\INPUT FILE INFORMATION:
	INFLG=$16A,	\INPUT FILE STATUS FLAG
	INNO=$16B,	\INPUT FILE NUMBER IN DIRECTORY
	INUNT=$16C,	\DEVICE NUMBER INPUT FILE IS ON

	CMDMODE=$28A,	\FLAG: COMMAND MODE (.CMD FILE)
	;

\----------------------------------------------------------------------\

proc	PUT16(ARRAY, INDEX, VALUE);
\STORE A 16-BIT VALUE INTO THE DIRECTORY ARRAY ENTRY AT "INDEX"
\NOTE THE BYTE ORDER IS LOW BYTE, HIGH BYTE
addr	ARRAY;
int	INDEX, VALUE;
begin
INDEX:= INDEX + INDEX;			\DOUBLE FOR WORD ENTRIES
ARRAY(INDEX):= VALUE;			\STORE LOW BYTE
ARRAY(INDEX+1):= SWAP(VALUE);		\STORE HIGH BYTE
end;	\PUT16



func	GET16(ARRAY, INDEX);
\RETURN A 16-BIT VALUE FROM THE DIRECTORY ARRAY ENTRY AT "INDEX"
\NOTE THE BYTE ORDER IS LOW BYTE, HIGH BYTE
addr	ARRAY;
int	INDEX;
begin
INDEX:= INDEX + INDEX;			\DOUBLE FOR WORD ENTRIES
return ARRAY(INDEX) + SWAP(ARRAY(INDEX+1));
end;	\GET16



proc	NEXT;		\GET NEXT CHARACTER, SET SWITCH IF ANY


	func	GETCH;
	\RETURN "CHAR" FROM KEYBOARD, CONVERT TO UPPERCASE
	begin
	CHAR:= CHIN(0);
	if CHAR>=^a & CHAR<=^z then CHAR:= CHAR-32;
	end;	\GETCH


begin
GETCH;
if CHAR=^/ then
	begin
	GETCH;
	SWITCH:= CHAR;
	GETCH;
	end;
end;	\NEXT



proc	CROUT;		\DO A CRLF
CRLF(0);



proc	CROUTX;		\CROUT, BUT PAUSE ON SCREEN BOUNDARIES
int	I;
begin
if LINECTR>=CONHT then [I:= CHIN(1); LINECTR:= 0];
CROUT;
LINECTR:= LINECTR + 1;
end;	\CROUTX



proc	TXT(STR);	\OUTPUT A STRING
addr	STR;
TEXT(0,STR);



proc	NUMOUT(I);	\OUTPUT AN INTEGER
int	I;
INTOUT(0,I);



func	NUMERIC;	\RETURNS 'TRUE' IF LAST CHARACTER READ WAS A DIGIT
return CHAR>=^0 & CHAR<=^9;



func	HEX;		\RETURNS 'TRUE' IF LAST CHARACTER WAS A HEX DIGIT
return NUMERIC ! (CHAR>=^A & CHAR<=^F);



func	NUMIN;		\GET AN UNSIGNED DECIMAL OR HEX INTEGER
int	I,ADD;
begin
while (not NUMERIC) & (CHAR#^$) do NEXT;
I:= 0;
if CHAR=^$ then
	begin						\HEX
	NEXT;
	while HEX do
		begin
		if CHAR<^A then ADD:= CHAR -^0
		else ADD:= CHAR -^A +10;
		I:= I*16 +ADD;
		NEXT;
		end;
	end
else	while NUMERIC do [I:= I *10 +CHAR -^0; NEXT];	\DECIMAL
return I;
end;	\NUMIN



func	ALPHANUM;	\RETURNS 'TRUE' IF LAST CHAR WAS ALPHANUMERIC OR "?"
return NUMERIC ! (CHAR>=^A & CHAR<=^Z) ! CHAR=^?;



proc	MOVENAME(A,B);	\MOVE A FILE NAME FROM A TO B
addr	A, B;
int	I;
for I:= 0,10 do B(I):= A(I);

\----------------------------------------------------------------------\

func	VERIFY;
begin
TXT(" - ARE YOU SURE (N/Y)? ");
OPENI(0);
NEXT;
return CHAR = ^Y;
end;	\VERIFY



proc	DOREBEGIN;	\RESTART APEX ON ERRORS
begin
if ABOFLG then				\ABORT COMMAND FILE (IF ANY)
	SYSPAG(CMDMODE):= false;
SYSPAG(SYSENF):= BOOTIN;
REBEGIN;
end;	\DOREBEGIN



proc	ERROR(LINE);	\ERROR HANDLER
addr	LINE;
begin
CHOUT(0,BEL);
TXT(if RAN(10)<3 then "OOPS - " else "NOPE - ");
TXT(LINE);
DOREBEGIN;
end;	\ERROR



proc	FILERR;		\FILE-NOT-FOUND ERROR HANDLER
int	I;
addr	LINE;
begin
CHOUT(0,BEL);
TXT("I CAN'T FIND ");
NUMOUT(LOCDEV); CHOUT(0,^:);
for I:= 0,7 do
    if LOCNAM(I)#SP
	then CHOUT(0,LOCNAM(I));
if LOCNAM(8)#SP then
	begin
	CHOUT(0,^.);
	for I:= 8,10 do
		CHOUT(0,LOCNAM(I));
	end;
DOREBEGIN;
end;	\FILERR



proc	FORMERR;
ERROR("NO FILES OF THAT FORM");

\----------------------------------------------------------------------\

proc	PRDATE(DATE);	\OUTPUT THE DATE, E.G: NOV-07-85
int	DATE;
int	DAY,MO,I;


	proc	NUM2(N);
	int	N;
	begin
	if N<10 then CHOUT(0,^0);
	NUMOUT(N);
	end;	\NUM2


begin
if DATE<=0 then [TXT("NO DATE  "); return];
DATE:= DATE/32;
DAY:= REM(0);
DATE:= DATE/16;
MO:= REM(0);

MO:= (MO-1)*3;
for I:= 0,2 do CHOUT(0,MOSTR(MO+I));
CHOUT(0,^-);
NUM2(DAY);
CHOUT(0,^-);
NUM2(DATE+76);
end;	\PRDATE



proc	PRWEEKDAY(DATE);	\PRINT THE DAY OF THE WEEK
int	DATE;
int	DAY,MO,YR,X,I;
addr	DAYWRD;
begin
DAYWRD:= "MONTUEWEDTHUFRISATSUN";
if DATE<=0 then return;

YR:= DATE/32;
DAY:= REM(0);
YR:= YR/16;
MO:= REM(0);
if MO<=2 then [MO:= MO+10; YR:= YR-1] else MO:= MO-2;
X:= 3 *( REM(((26*MO - 2)/10 + DAY + YR + YR/4 + 60) /7) );
for I:= 0,2 do CHOUT(0,DAYWRD(X+I));
end;	\PRWEEKDAY



proc	PRSYSDATE;	\PRINT THE SYSTEM DATE, E.G: THU, NOV-07-85
begin
PRWEEKDAY(SYSDAT);
TXT(", ");
PRDATE(SYSDAT);
end;	\PRSYSDATE



proc	PRNAME(FILE);	\OUTPUT A FILE NAME
addr	FILE;
int	K;
begin
for K:= 0,7 do CHOUT(0,FILE(K));
CHOUT(0,^.);
for K:= 8,10 do CHOUT(0,FILE(K));
end;	\PRNAME



proc	PRENTRY(FILE,FLAG);	\PRINT FILE ENTRY, E.G:
int	FILE,FLAG;		\FLAG:	FILENAME.EXT  123   NOV-08-85  200-222
int	MIN, MAX, SIZE;		\ELSE:	FILENAME.EXT  123
begin
PRNAME(FNAME +FILE*11);
MIN:= GET16(FBLK,FILE);
MAX:= GET16(LBLK,FILE);
SIZE:= MAX-MIN+1;
TXT("  "); NUMOUT(SIZE);
if not FLAG then return;

while SIZE<10000 do
	begin
	CHOUT(0,SP);
	if SIZE>1000 then SIZE:= 10000		\(LIMIT PROBLEM)
	else SIZE:= SIZE*10;
	end;
CHOUT(0,SP);
if GET16(FDATE,FILE) = SYSDAT then TXT("TODAY    ")
else PRDATE(GET16(FDATE,FILE));
TXT("  ");
NUMOUT(MIN); CHOUT(0,^-); NUMOUT(MAX);
end;	\PRENTRY



proc	PRTDEV(FILE);	\PRINT UNIT AND FILE ENTRY
int	FILE;
begin
NUMOUT(DIRDEV);
CHOUT(0,^:);
PRENTRY(FILE,CONWID>60);	\SHOW LONG FORM IF CONSOLE IS WIDE ENOUGH
end;	\PRTDEV

\----------------------------------------------------------------------\

proc	VALDRV(DRV);	\CHECK FOR VALID UNIT NUMBER
int	DRV;
int	I;
begin
if DRV<0 ! DRV>7 then ERROR("BAD UNIT NUMBER");
end;	\VALDRV



proc	CHKFILE;
begin
if CHAR#SP then ERROR("NEED FILE NAME");
end;	\CHKFILE



proc	NAME(DEFAULT,DDEV);
\GET A FILE NAME FROM THE OPERATOR AND PUT IT INTO "LOCNAM".
\ SET TO DEFAULT EXTENSION IF NONE WAS GIVEN. SET TO SYSTEM DEFAULT NAME (RNAM)
\ IF NONE WAS GIVEN. EXPAND *'S INTO FIELDS OF ?'S.
\OUTPUTS:	LOCNAM		FILE NAME AND EXTENSION
\		LOCDEV		DEVICE (UNIT) NUMBER
\		GOTFILE		FLAG: A FILE WAS INPUT (NOT JUST A SPACE)
\		DRVSET		FLAG: A DEVICE NUMBER WAS EXPLICITLY SPECIFIED
\		ARG1, ARG2	GENERAL PURPOSE NUMERIC ARGUMENTS
\INPUTS:	RNAM		SYSTEM-WIDE DEFAULT FILE NAME AND EXTENSION
\NOTE: THE LOADER (LOAD.XPL) DEPENDS ON THIS ROUTINE HAVING A ONE-CHARACTER
\ LOOK AHEAD.
\
addr	DEFAULT;		\DEFAULT EXTENSION
int	DDEV;			\DEFAULT DEVICE NUMBER
int	K;
begin
GOTFILE:= false;
while CHAR=SP do NEXT;
if NUMERIC then
	[LOCDEV:= NUMIN; DRVSET:= true]
else	[LOCDEV:= DDEV; DRVSET:= false];
VALDRV(LOCDEV);
if CHAR=^: then [NEXT; GOTFILE:= true];
K:= 0;
while ALPHANUM do
	begin
	LOCNAM(K):= CHAR;
	if K<8 then K:= K+1;
	NEXT;
	GOTFILE:= true;
	end;
if CHAR=^* then		\FILL OUT THE REST OF THE NAME WITH "?"
	[GOTFILE:= true;
	for K:= K,7 do LOCNAM(K):= ^?;
	NEXT]
else	for K:= K,7 do LOCNAM(K):= SP;

if CHAR=^. then
	begin
	GOTFILE:= true;
	NEXT;
	K:= 8;
	while ALPHANUM do
		begin
		LOCNAM(K):= CHAR;
		if K<11 then K:= K+1;
		NEXT;
		end;
	if CHAR=^* then
		[for K:= K,10 do LOCNAM(K):= ^?;
		NEXT]
	else for K:= K,10 do LOCNAM(K):= SP;
	end
else	begin
	LOCNAM(8):= DEFAULT(0);
	LOCNAM(9):= DEFAULT(1);
	LOCNAM(10):= DEFAULT(2);
	end;

if LOCNAM(0)=SP then
	for K:= 0,7 do LOCNAM(K):= RNAM(K);

if CHAR=^= then
	begin
	ARG1:= NUMIN;
	if CHAR=^, then ARG2:= NUMIN
	else ARG2:= NONE;
	end
else [ARG1:= NONE; ARG2:= NONE];
end;	\NAME



func	FIND(BSIZ);
\FIND THE SMALLEST EMPTY WHICH IS "BSIZ" OR MORE.
\IF THERE IS NONE, THEN FIND THE LARGEST AVAILABLE.
\SET "FIRBLK" AND "LASBLK" TO IT. RETURN ITS SIZE.
\INPUTS:	FEMBLK		EMPTY BLOCKS ARRAY (FIRST BLOCK) 
\		LEMBLK		(LAST BLOCK)
int	BSIZ;		\THE BLOCK SIZE WE'RE LOOKING FOR
int	LMAX,		\SIZE OF MAX EMPTY
	FMAX,		\FILE NUMBER OF MAX EMPTY
	I,SIZE;
begin
I:= 5;
LMAX:= 0; FMAX:= 0;
loop	begin		\SCAN LIST OF EMPTY BLOCKS FROM SMALLEST TO BIGGEST
	SIZE:= if GET16(FEMBLK,I)=0 then 0
		else GET16(LEMBLK,I) - GET16(FEMBLK,I) + 1;
	if SIZE>LMAX then [LMAX:= SIZE; FMAX:= I];
	if SIZE>=BSIZ then quit;
	if I=0 then quit;
	I:= I-1;
	end;
if LMAX=0 then
	begin
	FIRBLK:= 0;
	LASBLK:= 0;
	return 0;
	end;
if SIZE<BSIZ then I:= FMAX;
LASBLK:= GET16(LEMBLK,I);
FIRBLK:= GET16(FEMBLK,I);
return LASBLK-FIRBLK+1;
end;	\FIND

\----------------------------------------------------------------------\

proc	SORT(VAL,PTR,MAX);
\QUICKSORT POINTER ARRAY, "PTR", (0-"MAX"). "VAL" IS THE CORRESPONDENT
\ 16-BIT ARRAY OF INTEGER VALUES.
addr	VAL;		\%%%
addr	PTR;
int	MAX;
int	N,KEY,L,R,T;
begin
N:= ((MAX+2)/2)-1;
KEY:= GET16(VAL,PTR(N));
L:= 0; R:= MAX;
loop	begin
	while GET16(VAL,PTR(L)) < KEY do L:= L+1;
	while (GET16(VAL,PTR(R)) >= KEY) & (R>0) do R:= R-1;
	if L>=R then quit;
	T:= PTR(L);
	PTR(L):= PTR(R);
	PTR(R):= T;
	end;
if GET16(VAL,PTR(R))>KEY then
	begin
	T:= PTR(R);
	PTR(R):= PTR(N);
	PTR(N):= T;
	end;
if R>0 then SORT(VAL,PTR,R);
if MAX-R-1>0 then SORT(VAL,PTR+R+1,MAX-R-1);
end;	\SORT



proc	DIRSORT;	\SORT DIRECTORY BY "FBLK" AND EMPTY SIZE
\INPUTS: MAXFL		MAXIMUM POSSIBLE FILE NUMBER
\	STAB		SORT TABLE (IN DIRECTORY)
\	FBLK		FIRST BLOCK (IN DIRECTORY)
int	I,J,K,
	FEMB,LEMB,	\FIRST & LAST EMPTY BLOCKS
	MAXEM,		\MAX
	ESIZ;		\EMPTY SIZE
addr	FSTB,
	FRESIZ;		\ARRAY: FREE SIZE	%%%
begin
\SORT THE FILES INTO ASCENDING "FIRST BLOCK NUMBERS" (FBLK)
J:= 0;
for I:= 0,MAXFL do
	if FSTAT(I)=VALID then
		[STAB(J):= I; J:= J+1];
MAXSTB:= J-1;
NUMVAL(0):= MAXSTB;
SORT(FBLK,STAB,MAXSTB);

\PRODUCE THE EMPTIES LIST
MAXEM:= MAXSTB+1;
FSTB:= RESERVE(MAXEM+1);
FRESIZ:= RESERVE(2*(MAXEM+1));
J:= 0;
for I:= 0,MAXEM do
	begin
	FEMB:= if I=0 then USERBLK
		else GET16(LBLK,STAB(I-1)) +1;
	LEMB:= if I=MAXEM then MAXBLK
		else GET16(FBLK,STAB(I)) -1;
	ESIZ:= LEMB-FEMB+1;
	if ESIZ>0 then
		begin
		FSTB(J):= I;
		PUT16(FRESIZ,I,ESIZ);
		J:= J+1;
		end;
	end;
MAXEM:= J-1;

\SORT THE EMPTIES LIST BY SIZE
if MAXEM>=0 then
	SORT(FRESIZ,FSTB,MAXEM);

\COPY THE EMPTIES LIST INTO THE DIRECTORY, BIGGEST FIRST
I:= MAXEM;
K:= 0;
while (K<6) & (I>=0) do
	begin
	J:= FSTB(I);
	PUT16(FEMBLK, K, if J=0 then USERBLK
		else GET16(LBLK,STAB(J-1)) +1);
	PUT16(LEMBLK, K, if J>MAXSTB then MAXBLK
		else GET16(FBLK,STAB(J)) -1);
	K:= K+1;
	I:= I-1;
	end;
for I:= K,5 do [PUT16(FEMBLK,I,0); PUT16(LEMBLK,I,0)];
\FOR TESTS WE PRINT IT
\TEXT(0,"DIAG, EMPTIES LIST:"); CROUT;
\for I:= 0,5 do
\	begin
\	INTOUT(0,GET16(FEMBLK,I)); CHOUT(0,^,); INTOUT(0,GET16(LEMBLK,I));
\	CROUT;
\	end;
DIRCHG(0):= 0;			\DIRECTORY IS NOW SORTED
end;	\DIRSORT

\----------------------------------------------------------------------\
\
\DISK MAP:
\   0  1  2  3  4   5  6  7  8   9  10 11 12  13 14 15 16  17
\ BOOT +-- XD --+   +-- XB --+   +-- DI --+   +-- BD --+   +-- RESCOD -->
\      +------ EXTENDED -----+   +------ PRIMARY ------+

proc	MOVIT(AB1,AB2,LEN);	\MOVE "LEN" WORDS INTO B1 FROM B2
int	AB1,AB2,LEN,LEN2;	\B1 & B2 ARE LEFT POINTING TO THE LAST POSITION
int	B1, B2, I;
begin
B1:= AB1(0);
B2:= AB2(0);
LEN2:= LEN+LEN;
BLIT(B2, B1, LEN2);
AB1(0):= B1 + LEN2;
AB2(0):= B2 + LEN2;
end;	\MOVIT



proc	GETDIR(DEV, BAKDIR);	\READ IN THE DIRECTORY
int	DEV, BAKDIR;

int	I, EXTDIR, BASE1, BASE2, BASE3;
begin
EXTDIR:= RESERVE(1024);

\READ THE EXTENDED DIR INTO "EXTDIR"
FREAD(DEV, if BAKDIR then 5 else 1, EXTDIR, DIRSIZ);

\READ PRIMARY DIRECTORY INTO THE BIG DIRECTORY SPACE
FREAD(DEV, if BAKDIR then 13 else 9, FNAME, DIRSIZ);

\MERGE THE EXTENDED DIRECTORY INTO THE PRIMARY DIRECTORY
BASE1:= FNAME+528;	\(FSTAT)
BASE2:= EXTDIR+528;
BASE3:= FSTAT;
for I:= 0,MAXSEG do
	begin
	MOVIT(addr BASE3, addr BASE1, DIRLEN(I));
	MOVIT(addr BASE3, addr BASE2, DIRLEN(I));
	end;
BLIT(EXTDIR, FNAME+528, 528);

MAXFL:= if FLAGS(7)=EXTPAT then 95 else 47;
end;	\GETDIR




proc	PUTDIR(DEV, BAKDIR);	\WRITE THE DIRECTORY
int	DEV, BAKDIR;
int	I, EXTDIR, BASE1, BASE2, BASE3;
begin
EXTDIR:= RESERVE(1024);

\SEPARATE THE BIG DIR INTO THE PRIMARY DIR AND THE EXTENDED DIR
BLIT(FNAME+528, EXTDIR, 528);
BASE1:= FNAME+528;
BASE2:= EXTDIR+528;
BASE3:= FSTAT;
for I:= 0,MAXSEG do
	begin
	MOVIT(addr BASE1, addr BASE3, DIRLEN(I));
	MOVIT(addr BASE2, addr BASE3, DIRLEN(I));
	end;

\IF EXTENDED DIR IS USED THEN WRITE "EXTDIR" INTO THE EXTENDED DIR
if FLAGS(7)=EXTPAT then
	FWRITE(DEV, if BAKDIR then 5 else 1, EXTDIR, DIRSIZ);

\WRITE THE PRIMARY DIR
FWRITE(DEV, if BAKDIR then 13 else 9, FNAME, DIRSIZ);

\NOW FIX THE BIG DIR
BLIT(EXTDIR, FNAME+528, 528);
end;	\PUTDIR

\----------------------------------------------------------------------\

proc	LOCKERR;
begin
if FLAGS(3) then else ERROR("UNIT IS WRITE LOCKED");
end;	\LOCKERR



proc	WRTDIR;		\WRITE THE DIRECTORY; MAKE SURE IT'S SORTED
addr	BLOCK;
def	VOLOFF=74;	\OFFSET INTO 3RD BLOCK FOR VOLUME NUMBER (IN WORDS)
begin
BLOCK:= RESERVE(BLKSIZ);
FREAD(DIRDEV,DIRBLK+3,BLOCK,1);
if GET16(BLOCK,VOLOFF) # GET16(VOLUME,0) then
	ERROR("YOU CHANGED DISKS!");	\YOU IDIOT!
LOCKERR;

DIRSORT;
\++SYSPAG(UNTUPD):= SYSPAG(UNTUPD) & (not BITS(DIRDEV));
APEXID(0):= ^a;
APEXID(1):= ^p;
APEXID(2):= ^e;
APEXID(3):= ^x;
PUTDIR(DIRDEV,false);
end;	\WRTDIR



proc	RDDIR(DEV);	\READ DIRECTORY FROM DEVICE "DEV"
int	DEV;
begin
VALDRV(DEV);
DIRDEV:= DEV;
GETDIR(DIRDEV,false);

MAXBLK:= GET16(PMAXB,0);
MAXSTB:= NUMVAL(0);
if MAXSTB>MAXFL then MAXSTB:= -1;	\IN CASE IT'S EMPTY

\SET THE SYSTEM PAGE DEVICE SIZE TABLE
MAXTBL(DEV):= OFFTBL(DEV) + MAXBLK + 1;

\SET THE WRITE LOCK BIT IF THE FLAGS SAY SO (FLAG: TRUE = WRITE ENABLE = 1 BIT)
if FLAGS(3) then SYSPAG(LOKMSK):= SYSPAG(LOKMSK) ! BITS(DEV)
else SYSPAG(LOKMSK):= SYSPAG(LOKMSK) & (not BITS(DEV));

if DIRCHG(0)=$A5 & APEXID(0)=^a & APEXID(1)=^p & APEXID(2)=^e & APEXID(3)=^x then
\IT'S VERY LIKELY AN APEX DISK WHICH NEEDS SORTING.
\If an external program changed the directory then we fix it here.
\ In this version, we do not fix units unless we read the directory for
\ Apex purposes. That is, we don't fix them until we use them.
\ This affects programs which use FILEX, e.g. BASIC, in that if we go
\ directly from something that affects the directory, say COPY.SAV,
\ into BASIC without reading the directory with Apex then we will
\ give FILEX an incorrect STAB array. FILEX should detect this error,
\(not done yet)
\+++	! ((SYSPAG(UNTUPD) & BITS(DEV)) # 0)

\If we can, we sort and re-write, if not we just sort:
	if FLAGS(3) then WRTDIR else DIRSORT;
end;	\RDDIR



proc	CHKDIR(DEV);	\MAKE SURE THE DIRECTORY IS IN MEMORY
int	DEV;
begin
if DIRDEV#DEV then RDDIR(DEV);
end;	\CHKDIR



func	LOOKUP(FILE);
\LOOKUP THE FILENAME IN "LOCNAM" BEGINNING AT DIRECTORY ENTRY NUMBER "FILE".
\TAKE "?" AS WILD CHARACTERS.
int	FILE;
int	FILE11, L;
begin
CHKDIR(LOCDEV);
loop	begin
	if FSTAT(FILE)=VALID then
		begin
		FILE11:= FILE*11;
		L:= 0;
		loop	begin
			if (LOCNAM(L) # ^?) & (FNAME(FILE11+L) # LOCNAM(L))
				then quit;
			L:= L+1;
			if L>=11 then return FILE;
			end;
		end;
	FILE:= FILE+1;
	if FILE>MAXFL then return NONE;
	end;
end;	\LOOKUP



func	FINDFL(DEV,FILNAM);	\RETURN THE FILE NUMBER OF "FILNAM"
int	DEV;
addr	FILNAM;
int	I;
begin
LOCDEV:= DEV;
for I:= 0,9 do LOCNAM(I):= FILNAM(I);
LOCNAM(10):= FILNAM(10);
return LOOKUP(0);
end;	\FINDFL

\----------------------------------------------------------------------\

proc	CMDGET;						\COMMAND: GET
int	FILENO;
begin
CHKFILE;
NAME("SAV",ACTDEV);
FILENO:= LOOKUP(0);
if FILENO=NONE then FILERR;
TXT(" INFILE: "); PRTDEV(FILENO); CROUT;
FGET(LOCDEV,GET16(FBLK,FILENO));	\(NEVER RETURNS)
end;	\CMDGET



proc	CMDSTART(FLAG);					\COMMANDS: START & SWAP
int	FLAG;
begin
if not SWAPFLG then
	begin
	TXT("I WILL SWAP TO UNKNOWN STATE");
	if not VERIFY then return;
	end;
if FLAG then FRUN(SYSDEV,SWPBLK(0)) else FGET(SYSDEV,SWPBLK(0));
end;	\CMDSTART



\proc	CMDWS;						\\COMMAND: WS
\int	I,FILE;						\\WRITE SYSTEM
\begin
\while CHAR=SP do NEXT;
\if NUMERIC then ACTDEV:= NUMIN;
\TXT("RE-WRITE SYSTEM ON UNIT "); NUMOUT(ACTDEV);
\if not VERIFY then return;
\LOCKERR;

\FILE:= FINDFL(ACTDEV,"SYSTEM  SYS");
\if FILE=NONE then ERROR("NOT A SYSTEM UNIT");
\FASAVE(LOCDEV,GET16(FBLK,FILE));
\end;	\\CMDWS



proc	CMDNEW(DEV);					\COMMAND: NEW
int	DEV;		\NEW SYSTEM UNIT
begin
VALDRV(DEV);
SYSDEV:= DEV;
RDDIR(DEV);
RDEV:= PRDEV(0);
SYSPAG(DEFUNT):= RDEV;
MOVENAME(DFNAME, RNAM);
PAKFLG:= FLAGS(0)#0;
BAKFLG:= FLAGS(1)#0;
CHKFLG:= FLAGS(2)#0;
ABOFLG:= FLAGS(5)#0;

if SYSPAG(DATOF2) # ( $FF & (not (SYSPAG(DATOFF)&SYSPAG(DATOF1))) ) then
	begin		\WE DON'T HAVE A VALID SYSTEM DATE IN MEMORY
	TXT("DATE FROM SYSTEM UNIT: ");	\GET IT FROM THE DISKETTE
	SYSDAT:= GET16(DIRDAT,0);
	SYSPAG(DATOFF):= SYSDAT;
	SYSPAG(DATOF1):= SWAP(SYSDAT);
	SYSPAG(DATOF2):= not (SYSPAG(DATOFF)&SYSPAG(DATOF1));
	PRSYSDATE;
	CROUT;
	end
end;	\CMDNEW


\STRIDE VERSION:
\proc	CMDSYS;						\\COMMAND: SYSTEM
\int	UNIT;
\addr	MEM;		\\BASE ADDRESS OF RESCODE
\ext	BOOT =$404;	\\ENTRY POINT TO BOOT.68K
\begin
\MEM:= $0400;
\while CHAR=SP do NEXT;
\if NUMERIC then
\	begin
\	UNIT:= NUMIN;
\	FREAD(UNIT, 0, MEM, $80);	\\READ IN RESCOD.SYS, ETC.
\	MEM($1500 +SYSUNT):= UNIT;
\	BOOT;
\	end
\else	begin
\	TXT("SYSTEM DEVICE: "); NUMOUT(SYSDEV); CROUT;
\	end;
\end;	\\CMDSYS



\AMIGA VERSION:
proc	CMDSYS;						\COMMAND: SYSTEM
int	UNIT;
addr	MEM;		\BASE ADDRESS OF RESCODE
ext	VSTART =$400;	\ENTRY POINT TO RESTART APEX
begin
MEM:= $0400;
while CHAR=SP do NEXT;
if NUMERIC then
	begin
	UNIT:= NUMIN;
	MEM(SYSUNT):= UNIT;
	VSTART;
	end
else	begin
	TXT("SYSTEM DEVICE: "); NUMOUT(SYSDEV); CROUT;
	end;
end;	\CMDSYS



proc	CMDSIZE;					\COMMAND: SIZE
\OUTPUTS:	MAXBLK
\		PMAXB
begin
while CHAR=SP do NEXT;
if NUMERIC then ACTDEV:= NUMIN;
if CHAR=^: then NEXT;

CHKDIR(ACTDEV);
if CHAR=^= then
	begin
	TXT("NOW ");
	MAXBLK:= NUMIN - 1;
	PUT16(PMAXB, 0, MAXBLK);
	WRTDIR;
	RDDIR(ACTDEV);		\to change the protections!	????
	end;
TXT("UNIT "); NUMOUT(ACTDEV); TXT(" HAS SIZE "); NUMOUT(MAXBLK+1); CROUT;
end;	\CMDSIZE

\----------------------------------------------------------------------\
		
proc	CMDDATE;					\COMMAND: DATE
int	COUNT, FILENO, NEWDATE;


func	GETDATE(DFDATE);	\GET A DATE FROM THE OPERATOR
int	DFDATE;		\DEFAULT DATE
int	I, MO, DAY, YR;
addr	STR;


	func	GETMO(STR);	\RETURN THE NUMBER OF THE MONTH
	addr	STR;	\STRING CONTAINING THE SPELLED-OUT MONTH NAME
	int	I,J,K,MO;
	begin
	J:= 0;
	for MO:= 1,12 do
		begin
		K:= 0;
		for I:= 0,2 do
			begin
			if STR(I)=MOSTR(J) then K:= K+1;
			J:= J+1;
			end;
		if K=3 then return MO;
		end;
	return 0;		\RETURN "0" IF MONTH NOT FOUND
	end;	\GETMO


begin	\GETDATE
DFDATE:= DFDATE/32;
DAY:= REM(0);
YR:= DFDATE/16 +76;
MO:= REM(0);
NEXT;
loop	begin
	while not ALPHANUM do [if CHAR=CR then quit; NEXT];
	if NUMERIC then I:= NUMIN
	else	begin
		STR:= RESERVE(3);
		for I:= 0,2 do [STR(I):= CHAR; if CHAR=CR then quit; NEXT];
		I:= GETMO(STR);
		end;
	if I<1 ! I>12 then quit;
	MO:= I;

	while not NUMERIC do [if CHAR=CR then quit; NEXT];
	I:= NUMIN;
	if I<1 ! I>31 then quit;
	DAY:= I;

	while not NUMERIC do [if CHAR=CR then quit; NEXT];
	YR:= REM(NUMIN/100);
	quit;
	end;
return ((YR - 76)*16 + MO)*32 + DAY;
end;	\GETDATE



begin	\CMDDATE
if CHAR=SP then
	begin			\A PARTICULAR FILE WAS SPECIFIED
	NAME(RNAM+8,ACTDEV);
	COUNT:= 0;
	FILENO:= LOOKUP(0);
	while FILENO#NONE do
		begin
		COUNT:= COUNT+1;
		PUT16(FDATE, FILENO, SYSDAT);
		TXT(" REDATE: "); PRTDEV(FILENO); CROUT;
		FILENO:= LOOKUP(FILENO+1);
		end;
	if COUNT=0 then FORMERR;

	if COUNT>=2 then
		[if VERIFY then WRTDIR else DIRDEV:= $FF]
	else	WRTDIR;

	end
else	begin
	CHKDIR(SYSDEV);
	loop	begin
		TXT("NEW DATE (MM-DD-YY)? ");
		OPENI(0);
		NEWDATE:= GETDATE(SYSDAT);
		if NEWDATE >= SYSDAT then quit;
		if VERIFY then quit;
		end;
	SYSDAT:= NEWDATE;
	PUT16(DIRDAT, 0, SYSDAT);
	SYSPAG(DATOFF):= SYSDAT;
	SYSPAG(DATOF1):= SWAP(SYSDAT);
	SYSPAG(DATOF2):= not (SYSPAG(DATOFF) & SYSPAG(DATOF1));

	TXT("TODAY IS "); PRSYSDATE; CROUT;
	WRTDIR;
	end;
end;	\CMDDATE

\----------------------------------------------------------------------\

proc	SHOWDIR;	\SHOW A DIRECTORY (MAIN OR BACKUP)
\INPUTS: MAXSTB
addr	ARRAY;		\ARRAY FOR SORTING ASSORTED ITEMS	%%%
int	I,
	SUM,		\SUM OF FREE BLOCKS
	FILENO,
	FLAG,		\FOUND A FILE OF SPECIFIED FORM
	COLS;		\COLUMNS OF FILE NAMES


	func	RADIX40(STR);	\RETURNS A 16-BIT VALUE FOR A 3-CHAR STRING
	addr	STR;		\LEGAL CHARS ARE: A-Z, 0-9, AND SPACE.
	int	I, C;
	
		func	CODE(CH);	\RETURN A CHARACTER CODE
		int	CH;
		begin
		if CH>=^A \& CH<=^Z\ then return CH-$40;	\(1-26)
		if CH>=^0 \& CH<=^9\ then return CH-21;	\(27-36)
		return 0;					\(FOR SPACE)
		end;	\CODE
	
	begin
	C:= 0;
	for I:= 0,2 do
		C:= C*40 + CODE(STR(I));
	return C;
	end;	\RADIX40


begin
OPENI(1);
LINECTR:= 1;
COLS:= CONWID/20;

PRSYSDATE; TXT("   ");						\%%%
TXT("VOL: "); NUMOUT(GET16(VOLUME,0));
TXT("   UNIT: "); NUMOUT(DIRDEV);
if FLAGS(3) then else TXT("   (LOCKED)"); CROUTX;
TITLE(63):= 0;			\MAKE SURE GARBAGE TITLE IS TERMINATED
TXT(TITLE); CROUTX;

if GOTFILE then
	begin			\A PARTICULAR FILE WAS SPECIFIED (NOT JUST
	FLAG:= true;		\ A SPACE)
	FILENO:= LOOKUP(0);
	while FILENO#NONE do
		begin
		FLAG:= false;
		PRENTRY(FILENO,true); CROUTX;
		FILENO:= LOOKUP(FILENO+1);
		end;
	if FLAG then FORMERR;
	end
else	begin
	if SWITCH#SP then
		begin
		ARRAY:= RESERVE((MAXFL+1)*2);
		case SWITCH of
		  ^N:	for I:= 0,MAXFL do				\NAME
				PUT16(ARRAY, I, RADIX40(FNAME + I*11));
		  ^E:	for I:= 0,MAXFL do				\EXT
				PUT16(ARRAY, I, RADIX40(FNAME + I*11 + 8));
		  ^S:	for I:= 0,MAXFL do				\SIZE
				PUT16(ARRAY, I, GET16(LBLK,I)-GET16(FBLK,I));
		  ^D:	[ARRAY:= FDATE]					\DATE
		other	ARRAY:= FBLK;					\BLOCK
		SORT(ARRAY,STAB,MAXSTB);
		DIRDEV:= $FF;		\(DON'T KEEP A STRANGE STAB)
		end;
	for I:= 0,MAXSTB do
		begin		\NO FILE SPECIFIED, SHOW THEM ALL
		if SWITCH=SP then
			begin			\SHORT FORM
			PRNAME(FNAME +STAB(I)*11);
			if REM(I/COLS)=COLS-1 ! I=MAXSTB then CROUTX
			else TXT("       ");
			end
		else	begin			\LONG FORM
			PRENTRY(STAB(I),true); CROUTX;
			end;
		end;
	end;
SUM:= MAXBLK - USERBLK + 1;
for I:= 0,MAXFL do
    if FSTAT(I)=VALID then
	begin
	SUM:= SUM - (GET16(LBLK,I) - GET16(FBLK,I) + 1);
	end;
if SUM<0 then SUM:= 0;
TXT("FILES: "); NUMOUT(MAXSTB+1);
TXT("   FREE: "); NUMOUT(SUM);
TXT("   MAX: "); NUMOUT(FIND(32767));
TXT("   SIZE: "); NUMOUT(MAXBLK+1); CROUTX;			\%%%
CROUTX;
end;	\SHOWDIR



proc	CMDDIR;						\COMMAND: DIRECTORY
begin
NAME(RNAM+8,ACTDEV);
CHKDIR(LOCDEV);
SHOWDIR;
end;	\CMDDIR



proc	CMDBD;						\COMMAND BD
begin
NAME(RNAM+8,ACTDEV);
ACTDEV:= LOCDEV;
VALDRV(ACTDEV);
if SWITCH=^B then
	begin
	TXT("BACKING DIRECTORY ON UNIT "); NUMOUT(ACTDEV); CROUT;
	GETDIR(ACTDEV,false);
	PUTDIR(ACTDEV,true);
	end
else	begin
	GETDIR(ACTDEV,true);
	DIRDEV:= ACTDEV;
	MAXBLK:= GET16(PMAXB,0);
	if SWITCH=^W then
		begin
		TXT("ABOUT TO RE-WRITE DIRECTORY ON UNIT "); NUMOUT(ACTDEV);
		if VERIFY then PUTDIR(ACTDEV,false);
		end		\(WRITE LOCK NOT CHECKED)
	else	begin
		DIRSORT;
		SHOWDIR;
		end;
	end;
DIRDEV:= $FF;		\WE DO NOT HAVE A VALID DRIVE IN MEMORY
end;	\CMDBD

\----------------------------------------------------------------------\

proc	CMDTITLE;					\COMMAND: TITLE
int	I;
begin
while CHAR=SP do NEXT;
if NUMERIC then ACTDEV:= NUMIN;
CHKDIR(ACTDEV);		\MAKE SURE WE HAVE A VALID DIRECTORY
while not ALPHANUM do NEXT;
while CHAR=^: ! CHAR=SP do NEXT;
I:= 0;
while CHAR#CR do
	begin
	TITLE(I):= CHAR;
	if I<31 then I:= I+1;
	NEXT;
	end;
TITLE(I):= 0;				\TERMINATE TITLE STRING
PUT16( VOLUME, 0, ABS(SYSDAT*256 + RAN(256)) );
LOCKERR;
PUTDIR(DIRDEV,false);		\(DON'T WRTDIR BECAUSE WE DIDN'T CHANGE DISKS)
end;	\CMDTITLE



proc	CMDZERO;					\COMMAND: ZERO
int	I;
begin
while CHAR=SP do NEXT;
if NUMERIC then ACTDEV:= NUMIN;
CHKDIR(ACTDEV);
TXT("ABOUT TO ZERO UNIT "); NUMOUT(DIRDEV); CROUT;
TITLE(63):= 0;			\MAKE SURE GARBAGE TITLE IS TERMINATED
TXT(TITLE); CROUT;

if not VERIFY then return;
for I:= 0,MAXFL do FSTAT(I):= NULL;
TITLE(0):= CR; TITLE(1):= 0;
FLAGS(7):= EXTPAT;		\ENABLE EXTENDED DIRECTORY
WRTDIR;
end;	\CMDZERO



proc	CMDDF;						\COMMAND: DF
addr	BLOCK;


	proc	SHOW(STR,FLAG);
	addr	STR;
	int	FLAG;
	begin
	TXT(STR);
	CHOUT(0,if FLAG then ^T else ^F);
	end;	\SHOW


begin	\CMDDF
TXT("DEFAULT NAME:	");
if CHAR=SP then
	begin
	NAME(RNAM+8, RDEV);
	BLOCK:= RESERVE(256);
	FREAD(SYSDEV, DIRBLK+3, BLOCK, 1);

	MOVENAME(LOCNAM, RNAM);
	MOVENAME(LOCNAM, BLOCK+$4D);

	RDEV:= LOCDEV;
	SYSPAG(DEFUNT):= LOCDEV;
	BLOCK($4A):= LOCDEV;

	FWRITE(SYSDEV, DIRBLK+3, BLOCK, 1);
	DIRDEV:= $FF;		\FORCE READ OF ALTERED DIRECTORY
end;
NUMOUT(RDEV); TXT(":"); PRNAME(RNAM); CROUT;
SHOW("BACKUP:	", BAKFLG);
SHOW("	ABORT:	", ABOFLG);
CROUT;
SHOW("PACK:	", PAKFLG);
SHOW("	CHECK:	", CHKFLG);
CROUT;
CROUT;
end;	\CMDDF



proc	CMDDO(FL);					\COMMANDS: DO & NO
int	FL;
int	HASH;
begin
while CHAR=SP do NEXT;
HASH:= CHAR;
if CHAR#CR then NEXT else ERROR("NEED FLAG");
HASH:= HASH + SWAP(CHAR);

CHKDIR(SYSDEV);
case HASH of
  $4150\PA\: [FLAGS(0):= FL; PAKFLG:= FL];
  $4142\BA\: [FLAGS(1):= FL; BAKFLG:= FL];
  $4843\CH\: [FLAGS(2):= FL; CHKFLG:= FL];
  $4241\AB\: [FLAGS(5):= FL; ABOFLG:= FL]
other ERROR("FLAG DOES NOT EXIST");
WRTDIR;
end;	\CMDDO

\----------------------------------------------------------------------\

proc	CHKNAME;	\CHECK FOR VALID NAME IN "LOCNAM"
int	K;
begin
if (LOCNAM(8)=^B) & (LOCNAM(9)=^A) & (LOCNAM(10)=^K) then
	ERROR("YOU MAY NOT MAKE .BAK FILES");
for K:= 0,10 do
    if LOCNAM(K)=^? then
	ERROR("OUT FILE CANNOT HAVE ?'S OR *'S");
end;	\CHKNAME



proc	ENTER;
\ENTER A TENTATIVE OUTPUT FILE AND ITS BLOCKS INTO THE DIRECTORY.
\DON'T RESERVE THE BLOCKS, DON'T MARK IT VALID.
\IF "BACKUP" IS ENABLED, MARK IT "TENATIVE", OTHERWISE MARK IT "REPLACE".
\THE FILE WILL BE MARKED VALID AND BACKUPS WILL BE MAKE WHEN IT IS CLOSED.
begin
CHKNAME;

\FIND AN EMPTY DIR SLOT
FLNO:= 0;
while FSTAT(FLNO)=VALID do
	begin
	FLNO:= FLNO+1;
	if FLNO>MAXFL then
		ERROR("DIRECTORY IS FULL");
	end;
\NOW COPY THE NAME INTO IT
MOVENAME(LOCNAM, FNAME +FLNO*11);
PUT16(FBLK,FLNO,FIRBLK);
PUT16(LBLK,FLNO,LASBLK);
FSTAT(FLNO):= if (SPECIAL&1)&BAKFLG then TENTATIVE else REPLACE;
PUT16(FDATE, FLNO, if SPECIAL&4 then INDATE else SYSDAT);
end;	\ENTER

\----------------------------------------------------------------------\

proc	CLEAR(FILE);	\REMOVE AN ENTRY FROM THE DIRECTORY
int	FILE,MIN,MAX,I;
begin
if FSTAT(FILE)#VALID then return;
FSTAT(FILE):= NULL;
TXT("REMOVING ");
PRTDEV(FILE);
CROUT;
end;	\CLEAR



proc	REMOVE;		\REMOVE ANY COLLISIONS WITH "LOCNAM"
int	FILENO;
begin
FILENO:= LOOKUP(0);
if FILENO#NONE then CLEAR(FILENO);
end;	\REMOVE



proc	COPY(FRBLK,TOBLK,SIZE);	\(USED ONLY BY PACKING AND CHECKING)
int	FRBLK,TOBLK,SIZE,BUFSIZ,XFER;
addr	BUFFER;
begin			\CHECK ONLY IF TOBLK<0
if TOBLK>0 & FRBLK<TOBLK then ERROR("COPY TROUBLE");
BUFSIZ:= $40;
\BUFFER:= RESERVE($100 *BUFSIZ);

BUFFER:= $3000;		\WARNING! CLOBBERS USER SPACE ($3000 - $6FFF)
SWAPFLG:= false;	\INDICATE INVALID USER MEMORY

while SIZE > 0 do
	begin
	XFER:= if SIZE>BUFSIZ then BUFSIZ else SIZE;
	FREAD(LOCDEV,FRBLK,BUFFER,XFER);
	FRBLK:= FRBLK+XFER;
	if TOBLK >= 0 then
		begin
		FWRITE(LOCDEV,TOBLK,BUFFER,XFER);
		TOBLK:= TOBLK + XFER;
		end;
	SIZE:= SIZE-XFER;
	end;
end;	\COPY



proc	PACK(FILE);
int	FILE,SIZE;
begin
if not PAKFLG then return;		\RETURN IF PACK IS OFF
SIZE:= GET16(LBLK,FILE) - GET16(FBLK,FILE) + 1;
if FIND(SIZE) < SIZE then return;
if GET16(FBLK,FILE) <= FIRBLK then return;
					\WE CAN PACK IT, SO...
TXT("PACKING: "); PRTDEV(FILE);
TXT("  TO: "); NUMOUT(FIRBLK); CROUT;

COPY(GET16(FBLK,FILE), FIRBLK, SIZE);
PUT16(FBLK, FILE, FIRBLK);
PUT16(LBLK, FILE, FIRBLK+SIZE-1);
end;	\PACK



proc	CLOFIL(FILE,PAF);
\CLOSE THE TENTATIVE FILE BY DIRECTORY NUMBER.
\ASSUME IT HAS BEEN ENTERED. REMOVE COLLISIONS.
int	FILE,PAF;
int	S;
begin
S:= FSTAT(FILE);
if S#TENTATIVE & S#REPLACE then
	begin
	TXT("
???
");
	return;
	end;
MOVENAME(FNAME +FILE*11, LOCNAM);
REMOVE;
FSTAT(FILE):= VALID;
TXT("CLOSING: ");
PRTDEV(FILE);
CROUT;
if PAF then PACK(FILE);
if CHKFLG then COPY(GET16(FBLK,FILE), -1, GET16(LBLK,FILE)-GET16(FBLK,FILE)+1);
end;	\CLOFIL

\----------------------------------------------------------------------\

proc	CMDSQ;						\COMMAND: SQUASH
int	UBL, FILENO;
\THIS IS INEFFECIENT BECAUSE OF THE (STUPID) DIRECTORY SORT IN WRTDIR


	proc	SQ(FILENO);
	int	FILENO;
	int	FBL, LBL, SIZ, FL;
	begin
	FL:= STAB(FILENO);
	FBL:= GET16(FBLK, FL);
	LBL:= GET16(LBLK, FL);
	SIZ:= LBL - FBL + 1;
	if UBL < FBL then
		begin
		TXT("MOVING: "); PRTDEV(FL);
		TXT("  TO: "); NUMOUT(UBL);
		CROUT;
		COPY(FBL, UBL, SIZ);
		PUT16(FBLK, FL, UBL);
		PUT16(LBLK, FL, UBL+SIZ-1);
		WRTDIR;
		end;
	UBL:= GET16(LBLK, FL) + 1;
	end;	\SQ


begin	\CMDSQ
while CHAR=SP do NEXT;			\GET THE UNIT NUMBER, IF SPECIFIED
if NUMERIC then ACTDEV:= NUMIN;		\ AND READ IN THE DIRECTORY
CHKDIR(ACTDEV);				\MAKE SURE WE HAVE A VALID DIRECTORY
LOCDEV:= ACTDEV;			\(BECAUSE OF COPY)

UBL:= USERBLK;
for FILENO:= 0, MAXSTB do SQ(FILENO);

WRTDIR;
PUTDIR(ACTDEV,true);			\ALSO UPDATE BACKUP DIRECTORY
end;	\CMDSQ

\----------------------------------------------------------------------\

proc	CMDMAKE;					\COMMAND: MAKE
int	K, FILENO, TEXT;
addr	BLOCK;
begin
CHKFILE;
NAME(RNAM+8,ACTDEV);

FILENO:= LOOKUP(0);
if FILENO#NONE then
	begin
	TXT("I WILL MUNCH OLD ONE! ");
	if not VERIFY then return else REMOVE;
	end;
if ARG1=NONE then
	begin
	ARG1:= 1;
	ARG2:= NONE;
	TEXT:= true;
	end
else TEXT:= false;

if ARG2=NONE then
	begin
	if FIND(ARG1) < ARG1 then
		ERROR("NOT ENOUGH SPACE ON THAT UNIT");
	LASBLK:= FIRBLK+ARG1-1;
	end
else	begin
	FIRBLK:= ARG2;
	LASBLK:= ARG2+ARG1-1;
	end;
ENTER;
CLOFIL(FLNO,false);		\(DON'T ALLOW PACKING)
WRTDIR;
if TEXT then
	begin
	BLOCK:= RESERVE(256);
	BLOCK(0):= $1A;
	FWRITE(LOCDEV,FIRBLK,BLOCK,1);	
	end;
end;	\CMDMAKE



proc	CMDDELETE;					\COMMAND: DELETE
int	FLAG,FILENO;
begin
CHKFILE;
NAME("BAK",ACTDEV);
FLAG:= false;
FILENO:= LOOKUP(0);
while FILENO#NONE do
	begin
	CLEAR(FILENO);
	FLAG:= true;
	FILENO:= LOOKUP(FILENO+1);
	end;
if FLAG then
	begin
	if VERIFY then WRTDIR else RDDIR(LOCDEV);
	end
else ERROR("NO SUCH FILES FOUND");
end;	\CMDDELETE



proc	CMDSAVE;					\COMMAND: SAVE
\SAVE THE CURRENT MEMORY IMAGE. NOTE THAT PART OF IT MAY BE IN SCRATCH.SYS
int	SIZE, BLOCK, A;
begin
CHKFILE;
NAME("SAV",ACTDEV);
if not SWAPFLG then
	begin
	TXT("SAVED AREA MAY NOT BE VALID");
	if not VERIFY then REBEGIN;
	end;
BLOCK:= RESERVE(256);
FREAD(SYSDEV,SWPBLK(0),BLOCK,1);		\GET SYSPAG FROM SCRATCH.SYS
if ARG1#NONE then
	begin
	if ARG2=NONE then
		ERROR("NEED ENDING ADDRESS");
	if ARG1 >= ARG2 then					\%%%
		ERROR("ILLEGAL ADDRESS RANGE");
	A:= BLOCK + USRMEM - SYSPAG;
	A(0):= ARG1;

	SIZE:= (ARG2-ARG1)/256 +1;	\SIZE IN BLOCKS OF .SAV FILE
	A:= BLOCK + PROSIZ - SYSPAG;	\ NOT INCLUDING SYSPAG
	A(0):= SIZE;

	FWRITE(SYSDEV,SWPBLK(0),BLOCK,1);
	end;
A:= BLOCK + PROSIZ - SYSPAG;
SIZE:= A(0) +1;				\SIZE IN BLOCKS INCLUDING SYSPAG
CHKDIR(LOCDEV);
if FIND(SIZE)<SIZE then ERROR("NOT ENOUGH SPACE ON THAT UNIT");
LASBLK:= FIRBLK+SIZE-1;
ENTER;					\RECORD .SAV FILE IN THE DIRECTORY
WRTDIR;
SYSPAG(INFLG):= NOFILE;
SYSPAG(INNO):= FLNO;
SYSPAG(INUNT):= LOCDEV;
FSAVE(LOCDEV,FIRBLK);
\This last statement does not return directly. It loads SCRATCH.SYS into
\ memory then writes the .SAV file. It restarts Apex with a "re-entry"
\ condition which ultimately runs FIXSAV.
end;	\CMDSAVE

\----------------------------------------------------------------------\

proc	OPENOT;
\IF SWITCH IS "R", THEN OPEN EXISTING FILE ELSE OPEN A SUITABLE TENTATIVE
\ AS AN OUTPUT FILE FOR THE USER. USE THE NAME IN "LOCNAM".
int	FILENO,SIZE,TARGSIZ,MINSIZ;
begin
if LOCNAM(0)=SP then return;
CHKDIR(LOCDEV);
if SWITCH=^R then
	begin
	\(NOTE THAT THE FILE MAY BE WRITTEN EVEN IF IT IS NOT CLOSED)
	FILENO:= LOOKUP(0);
	if FILENO=NONE then FILERR;
	PUT16(FDATE, FILENO, SYSDAT);
	WRTDIR;
	OTLBLK(0):= GET16(FBLK,FILENO);				\%%%
	OTHBLK(0):= GET16(LBLK,FILENO);				\%%%
	end
else	begin
	\if INSIZE>0 then TARGSIZ:= INSIZE+(INSIZE/2)+10
	\else TARGSIZ:= (GET16(LEMBLK,0) - GET16(FEMBLK,0) + 1) /2;

	MINSIZ:= 1;
	if (SPECIAL&2) & (INSIZE>0) then MINSIZ:= INSIZE+1;	\IF SIZE LIMIT
	if ARG1#NONE then [MINSIZ:= ARG1; TARGSIZ:= MINSIZ];

	\SIZE:= FIND(TARGSIZ);
	SIZE:= FIND(32767);	\USE THE BIGGEST AVAIL--THE HELL WITH BISECTION
	ENTER;
	if SIZE<MINSIZ then ERROR("OUT FILE IS TOO SMALL");
	WRTDIR;
	OTLBLK(0):= FIRBLK;					\%%%
	OTHBLK(0):= LASBLK;					\%%%
	FILENO:= FLNO;
	end;

SYSPAG(OTFLG):= SETUP;
SYSPAG(OTNO):= FILENO;
SYSPAG(OTUNT):= LOCDEV;
TXT("OUTFILE: "); PRTDEV(FILENO); CROUT;

PUTDIR(DIRDEV,true);		\UPDATE BACKUP DIRECTORY
end;	\OPENOT



proc	OPENIN;		\OPEN USER'S INPUT FILE
int	FILENO;
begin
\OPEN USER'S INPUT FILE
if LOCNAM(0)=SP then return;
CHKDIR(LOCDEV);
FILENO:= LOOKUP(0);
if FILENO=NONE then FILERR;
INLBLK(0):= GET16(FBLK,FILENO);					\%%%
INHBLK(0):= GET16(LBLK,FILENO);					\%%%
SYSPAG(INFLG):= SETUP;
SYSPAG(INNO):= FILENO;
SYSPAG(INUNT):= LOCDEV;
INSIZE:= GET16(LBLK,FILENO) - GET16(FBLK,FILENO) +1;
INDATE:= GET16(FDATE,FILENO);
TXT(" INFILE: "); PRTDEV(FILENO); CROUT;
end;	\OPENIN



proc	CMDOPEN(DEFO,DEFI);				\COMMAND: OPEN
\"@" means replace with another extension. If the operator doesn't type one in,
\ then use the default extension (set by "SET"). If the default extension is
\ "@@@", then use the system-wide default extension.
addr	DEFO,DEFI,OUTNAM;
int	I,OUTDEV,INFL,OTFL, FLIPFL, T, GOTFL;
begin
OUTNAM:= RESERVE(11);
if DEFO(0)=SP & DEFI(0)=SP then return;
if CHAR#SP then return;	\("CHKFILE" IS NOT USED BECAUSE OF "PNTDIR", ETC.)
NEXT;			\SKIP THE SPACE AFTER "OPEN"

OTFL:= false; INFL:= false;
GOTFL:= CHAR=SP;
NAME("@@@",ACTDEV);
if GOTFILE ! GOTFL then OTFL:= true;

FLIPFL:= CHAR=^>;
if CHAR=^< ! FLIPFL then
	begin				\GET INPUT FILE NAME
	MOVENAME(LOCNAM, OUTNAM);
	OUTDEV:= LOCDEV;
	NEXT;
	GOTFL:= CHAR=SP;
	NAME("@@@",ACTDEV);
	if GOTFILE ! GOTFL then INFL:= true;
	end
else	begin				\INFILE = OUTFILE
	MOVENAME(LOCNAM, OUTNAM);
	OUTDEV:= LOCDEV;
	if DEFO(0)#^@ then
		for I:= 0,2 do OUTNAM(I+8):= DEFO(I);
	INFL:= true;
	OTFL:= true;
	end;

if FLIPFL then
	begin			\FLIP (SWAP) INPUT AND OUTPUT FILES
	T:= INFL; INFL:= OTFL; OTFL:= T;
	T:= LOCDEV; LOCDEV:= OUTDEV; OUTDEV:= T;
	for I:= 0,10 do
		[T:= LOCNAM(I); LOCNAM(I):= OUTNAM(I); OUTNAM(I):= T];
	end;

if LOCNAM(8)=^@ then			\GET INFILE EXTENSION
	if DEFI(0)#^@ then for I:= 0,2 do LOCNAM(8+I):= DEFI(I)
	else for I:= 8,10 do LOCNAM(I):= RNAM(I);
if INFL & (DEFI(0)#SP) then OPENIN;

if OUTNAM(8)=^@ then			\GET OUTFILE EXTENSION
	if DEFO(0)#^@ then for I:= 0,2 do OUTNAM(8+I):= DEFO(I)
	else for I:= 8,10 do OUTNAM(I):= RNAM(I);
MOVENAME(OUTNAM, LOCNAM);
LOCDEV:= OUTDEV;
if OTFL & (DEFO(0)#SP) then OPENOT;
end;	\CMDOPEN



proc	CMDCLOSE;					\COMMAND: CLOSE
int	I,FL,RSAV,FILENO;
begin
ACTDEV:= SYSPAG(OTUNT);			\GET OUTPUT FILE UNIT NUMBER
LOCDEV:= ACTDEV;
CHKDIR(ACTDEV);
FL:= SYSPAG(OTNO);
if FSTAT(FL)=TENTATIVE then
	begin
	MOVENAME(FNAME +FL*11, LOCNAM);
	FILENO:= LOOKUP(0);
	if FILENO#NONE then
		begin
		\RESOLVE THE COLLISION
		RSAV:= FILENO;
		LOCNAM(8):= ^B; LOCNAM(9):= ^A; LOCNAM(10):= ^K;
		FILENO:= LOOKUP(0);
		if FILENO#NONE then CLEAR(FILENO);
		TXT("BACKING: "); PRTDEV(RSAV); CROUT;
		I:= RSAV*11+8;
		FNAME(I):= ^B;
		FNAME(I+1):= ^A;
		FNAME(I+2):= ^K;
		end;
	end
else if FSTAT(FL)#REPLACE then
	begin
	TXT("OUTPUT FILE NOT OPEN
");
	return;
	end;
PUT16(LBLK, FL, OTHBLK(0));
CLOFIL(FL,true);
WRTDIR;
end;	\CMDCLOSE

\----------------------------------------------------------------------\

proc	CMDLIST;					\COMMAND: LIST
int	CHAR, DEV, BLK, HBLK, SIZ, I;
addr	BUFFER;
begin
CHKFILE;
CMDOPEN("   ","@@@");
if SYSPAG(INFLG)#SETUP then FILERR;

\(DISK BUFFERING IS DONE THIS WAY TO AVOID TYING UP MEMORY WITH AN INPUT BUFFER)
DEV:= SYSPAG(INUNT);
BLK:= INLBLK(0);
HBLK:= INHBLK(0);
BUFFER:= RESERVE(4*BLKSIZ);

OPENI(1);
LINECTR:= 1;
loop	begin
	SIZ:= 4;			\DON'T READ BEYOND END OF UNIT
	if HBLK-BLK < 3 then SIZ:= HBLK -BLK +1;
 	FREAD(DEV, BLK, BUFFER, SIZ);
	BLK:= BLK+4;
	for I:= 0,1023 do
		begin
		CHAR:= BUFFER(I);
		if CHAR>=$20 then CHOUT(0,CHAR)	\(FOR SPEED)
		else	case CHAR of
			  CR: CROUTX;
			  LF: ;				\IGNORE THE DAMN THINGS
			  FF: [CROUTX; CROUTX; CROUTX];	\DON'T ERASE ANYTHING
			  EOF: quit
			other CHOUT(0,CHAR);
		end;
	end;
CROUTX;
SYSPAG(OTFLG):= NOFILE;
SYSPAG(INFLG):= NOFILE;
end;	\CMDLIST



proc	CMDRENAME;					\COMMAND: RENAME
\RENAME NAMEOLD TO BE NAMENEW     NAMENEW<NAMEOLD     NAMEOLD>NAMENEW
int	I, T, FILENO, DEV, FLIPFL, GOTFL, COUNT;
addr	NAMENEW, NAMEOLD, NAME2NEW, NAME2OLD;
begin
NAMENEW:= RESERVE(11);
NAMEOLD:= RESERVE(11);
NAME2NEW:= RESERVE(11);
NAME2OLD:= RESERVE(11);

CHKFILE;
NAME(RNAM+8,ACTDEV);
MOVENAME(LOCNAM, NAMENEW);	\NAMENEW := LOCNAM
if (CHAR#^< & CHAR#^>) then ERROR("UNCLEAR SYNTAX");
FLIPFL:= CHAR=^>;

DEV:= LOCDEV;
NEXT;
GOTFL:= CHAR=SP;
NAME(RNAM+8,DEV);
MOVENAME(LOCNAM, NAMEOLD);
if DEV#LOCDEV then ERROR("CANNOT CHANGE UNIT");
if not GOTFL & not GOTFILE then ERROR("NEED FILE NAME");	\(SPACE IS OK)
if FLIPFL then
	begin		\SWAP (FLIP) OLD AND NEW
	MOVENAME(NAMENEW, NAME2NEW);
	MOVENAME(NAMEOLD, NAMENEW);
	MOVENAME(NAME2NEW, NAMEOLD);
	end;

COUNT:= 0;
MOVENAME(NAMEOLD, LOCNAM);	\LOOKUP THE OLD, EXISTING NAME(S)
FILENO:= LOOKUP(0);
while FILENO#NONE do		\FOR ALL OF THE OLD, EXISTING NAMES...
	begin
	COUNT:= COUNT+1;
	\REPLACE ?'S IN THE NEW NAME WITH CORRESPONDING CHAR IN OLD NAME
	MOVENAME(FNAME +FILENO*11, NAME2OLD);	\GET THE EXISTING NAME
	MOVENAME(NAMENEW, NAME2NEW);
	for I:= 0,10 do
		if NAME2NEW(I)=^? then NAME2NEW(I):= NAME2OLD(I);

	\DON'T ALLOW: RENAME SAMENAME.SSS<*.*
	MOVENAME(NAME2NEW, LOCNAM);
	if LOOKUP(0)#NONE then ERROR("FILE NAME IS USED");

	TXT(" RENAME: "); PRTDEV(FILENO); CROUT;
	MOVENAME(NAME2NEW, FNAME +FILENO*11);
	TXT("  TO BE: "); PRTDEV(FILENO); CROUT;

	MOVENAME(NAMEOLD, LOCNAM);	\LOOKUP THE OLD, EXISTING NAME(S)
	FILENO:= LOOKUP(FILENO+1);
	end;

if COUNT=0 then FORMERR;
if COUNT>=2 then
	[if VERIFY then WRTDIR else DIRDEV:= $FF]
else	WRTDIR;
end;	\CMDRENAME

\----------------------------------------------------------------------\

proc	CMDUNLOCK(BOOL);				\COMMANDS: LOCK & UNLOCK
int	BOOL;		\UNLOCK IF TRUE
begin
while CHAR=SP do NEXT;
if NUMERIC then ACTDEV:= NUMIN;
MAXTBL(ACTDEV):= $7FFF;			\ALLOW THE READ NO MATTER WHAT	????
CHKDIR(ACTDEV);
FLAGS(3):= BOOL;			\SET FLAG AS WE WILL WANT IT

SYSPAG(LOKMSK):= SYSPAG(LOKMSK) ! BITS(ACTDEV);	\UNLOCK FOR THIS WRITE
if MAXBLK<16 then PUT16(PMAXB, 0, 16);		\FORCE MINIMUM SIZE
MAXTBL(DIRDEV):= $7FFF;			\ALLOW THIS WRITE
PUTDIR(DIRDEV,false);			\(ALL TO SET ONE BYTE)

RDDIR(ACTDEV);				\SET "LOKMSK" (THE HARD WAY)
TXT("UNIT "); NUMOUT(ACTDEV); TXT(" IS ");
if FLAGS(3) then TXT("UNLOCKED") else TXT("WRITE LOCKED");
CROUT;
end;	\CMDUNLOCK



proc	CMDSUB;						\COMMAND: SUB
int	FILE, SIZE;
begin
NAME("SBD",ACTDEV);
LOCNAM(8):= ^S;			\MAKE SURE IT'S A SUB-DIRECTORY
LOCNAM(9):= ^B;
LOCNAM(10):= ^D;
if GOTFILE then			\IF WE'VE GOT A FILE (NOT JUST A SPACE) THEN
	begin			\ MOVE TO SUB-DIRECTORY
	CHKDIR(LOCDEV);
	FILE:= LOOKUP(0);
	if FILE=NONE then ERROR("NO SUCH SUB-DIRECTORY");

	OFFTBL(LOCDEV):= OFFTBL(LOCDEV) + GET16(FBLK,FILE);

	SIZE:= GET16(LBLK,FILE) - GET16(FBLK,FILE) + 1;
	MAXTBL(LOCDEV):= OFFTBL(LOCDEV) + SIZE;		\SO WE CAN DIDDLE

	RDDIR(LOCDEV);			\NOW FORCE SIZE TO BE CORRECT
	if SIZE#MAXBLK+1 then
		begin
		TXT("HEY! SIZE IS WRONG. I WILL FIX IT."); CROUT;	\????
		MAXTBL(LOCDEV):= OFFTBL(LOCDEV) + SIZE;	\SO WE CAN DIDDLE
		if FLAGS(3) then
			begin
			MAXBLK:= SIZE-1;
			PUT16(PMAXB, 0, MAXBLK);
			WRTDIR;
			RDDIR(LOCDEV);		
			end
		else	begin
			TXT("OOPS! I CAN'T, YOU LOCKED IT!"); CROUT;
			end;
		end;
	TXT("THE DEED IS DONE. SIZE: ");
	NUMOUT(MAXTBL(LOCDEV) - OFFTBL(LOCDEV)); CROUT;
	end
else	begin				\RESTORE TO PARENT DIRECTORY
	OFFTBL(LOCDEV):= 0;
	RDDIR(LOCDEV);
	MAXTBL(LOCDEV):= MAXBLK +1;
	TXT("UNIT "); NUMOUT(LOCDEV); TXT(" RESET. SIZE: ");
	NUMOUT(MAXTBL(LOCDEV) - OFFTBL(LOCDEV)); CROUT;
	end;
end;	\CMDSUB

\----------------------------------------------------------------------\

proc	FIXSAV;		\CLOSE THE .SAV FILE STARTED FROM "CMDSAVE"
\THIS PROCEDURE CANNOT BE CALLED DIRECTLY FROM "CMDSAVE" BECAUSE APEX MAY
\ NOT BE IN MEMORY WHEN THE .SAV FILE IS WRITTEN.
int	FL;
begin
LOCDEV:= SYSPAG(INUNT);
CHKDIR(LOCDEV);
FL:= SYSPAG(INNO);
CLOFIL(FL,false);		\(PACKING IS UNNECESSARY BECAUSE .SAV FILE
WRTDIR;				\ IS PUT INTO THE FIRST BIGGEST EMPTY LOCATION
end;	\FIXSAV



func	LOOKER(DEV);
\A RECURSIVE ROUTINE TO SEARCH ALL SUB-DIRECTORIES ON A UNIT AND RETURN
\ THE FIRST BLOCK OF A FILE (RELATIVE TO THE DIRECTORY AT THE TIME OF CALL).
int	DEV;
int	FILE,HOLDAD,REPLY,FIRBLK,BASEAD;
addr	HOLD,SBDNAM;
begin
LOCDEV:= DEV;
FILE:= LOOKUP(0);
if FILE#NONE then return GET16(FBLK,FILE);
\TRIVIAL CASE TAKEN CARE OF, SO GO ONE DEEPER

HOLD:= RESERVE(11);
MOVENAME(LOCNAM, HOLD);
SBDNAM:= "????????SBD";
HOLDAD:= OFFTBL(DEV);
FILE:= -1;
loop	begin					\LOOK FOR A SBD
	MOVENAME(SBDNAM, LOCNAM);
	FILE:= LOOKUP(FILE+1);
	if FILE=NONE then [REPLY:= NONE; quit];
						\FOUND ONE SO SUB TO IT
	BASEAD:= GET16(FBLK,FILE);
	OFFTBL(DEV):= BASEAD + OFFTBL(DEV);
	RDDIR(DEV);
						\AND RECURSE
	MOVENAME(HOLD, LOCNAM);
	FIRBLK:= LOOKER(DEV);
	if FIRBLK#NONE then [REPLY:= FIRBLK+BASEAD; quit];
	OFFTBL(DEV):= HOLDAD;
	RDDIR(DEV);
	end;

OFFTBL(DEV):= HOLDAD;
RDDIR(DEV);
MOVENAME(HOLD, LOCNAM);
return REPLY;
end;	\LOOKER



proc	DOCMD(BLK);	\EXECUTE A .CMD FILE BEGINNING AT BLOCK "BLK"
int	BLK;
int	I,J,K,CH,ARGSIZ;
addr	BLOCK,ARG;


	proc	INSERT(CH);
	int	CH;
	begin
	if CH=LF then return;
	if I>254 then ERROR(".CMD FILE IS TOO BIG");
	CMDBUF(I):= CH;
	I:= I+1;
	end;	\INSERT


begin	\DOCMD
ARG:= RESERVE(80);
while CHAR=SP do NEXT;		\("1:#" WON'T WORK OTHERWISE)
I:= 0;
while CHAR#CR do		\GET ARGUMENT (IF ANY) FROM COMMAND LINE
	begin
	if I<79 then [ARG(I):= CHAR; I:= I+1];
	NEXT;
	end;
ARGSIZ:= I-1;

BLOCK:= RESERVE(256);
FREAD(ACTDEV,BLK,BLOCK,1);		\READ IN .CMD FILE
J:= 0; I:= 0;
repeat	begin
	CH:= BLOCK(J); J:= J+1;
	case CH of
	  ^#: 	for K:= 0,ARGSIZ do INSERT(ARG(K));	\INSERT ARGUMENT
	  ^^:	begin					\INSERT CTRL CHAR
		CH:= BLOCK(J); J:= J+1;
		if CH>=$40 then INSERT(CH-$40);
		end
	other INSERT(CH);
	end
until CH=EOF;
SYSPAG(CMDMODE):= true;
CMDPTR(0):= CMDBUF;
REBEGIN;
end;	\DOCMD



proc	RUN(BLK);	\RUN A .SAV FILE BEGINNING AT BLOCK "BLK"
int	BLK;
int	FIRBLK,I,DEV;
addr	BLOCK,DEFO,DEFI;
begin
if BLK=NONE then return;
BLOCK:= RESERVE(256);
DEFO:= RESERVE(3);
DEFI:= RESERVE(3);
FREAD(ACTDEV,BLK,BLOCK,1);
for I:= 0,2 do [DEFO(I):= BLOCK(DEXTO+I)];
for I:= 0,2 do [DEFI(I):= BLOCK(DEXTI+I)];
SPECIAL:= BLOCK(DEFAUL);
DEV:= ACTDEV;
ACTDEV:= RDEV;
CMDOPEN(DEFO,DEFI);
CROUT;
FRUN(DEV,BLK);		\(NEVER RETURNS)
end;	\RUN

\======================================================================\

begin	\MAIN
BITS:= [$01,$02,$04,$08,$10,$20,$40,$80];
MOSTR:= "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
DIRLEN:= [24, 48, 48, 6, 6, 24, 8, 16, 16, 48, 4];
CHAR:= CR;

SYSPAG:= $0400;				\LOCATION OF RESIDENT SYSTEM PAGE
USRMEM:= $432;				\ABSOLUTE ADDRESSES IN SYSTEM PAGE
PROSIZ:= $436;
SYSBLK:= $502;
SWPBLK:= $506;
OTLBLK:= $556;
OTHBLK:= $55A;
INLBLK:= $562;
INHBLK:= $566;
MAXTBL:= $0586;
OFFTBL:= $05C6;

CONWID:= 80;				\GET CONSOLE DIMENSIONS
CONHT:= 24;

CMDPTR:= $698;
CMDBUF:= $6A8;

BLKSIZ:= 256;				\SIZE OF A BLOCK IN BYTES
DIRBLK:= 9;				\LOCATION OF DIRECTORY BLOCK
DIRSIZ:= 4;				\SIZE OF DIRECTORY IN "BLKSIZ" BLOCKS
BACKBLK:= DIRBLK + DIRSIZ;		\LOCATION OF BACKUP DIRECTORY
USERBLK:= BACKBLK + DIRSIZ;		\START OF USER FILE SPACE

MAXFL:= 95;				\SELECT SO THAT DIRSIZ IS RIGHT

					\RESERVE THE ARRAYS
II:= (MAXFL+1)*2;
					\BLOCKS 0-2
FNAME:= RESERVE(8*BLKSIZ);					\%%%
FSTAT:= FNAME + ((MAXFL+1)*11);
FBLK:= FSTAT + (MAXFL+1);
LBLK:= FBLK + (II);
					\BLOCK 3
FEMBLK:= LBLK + (II);
LEMBLK:= FEMBLK + (24);
STAB:= LEMBLK + (24);
NUMVAL:= STAB + (MAXFL+1);
DIRCHG:= NUMVAL + (1);
PRDEV:= DIRCHG + (1);
PMAXB:= PRDEV + (1);
DFNAME:= PMAXB + (2);
UNUSED:= DFNAME + (11);
TITLE:= UNUSED + (16);
UNUSED:= TITLE + (64);
APEXID:= UNUSED + (24);
VOLUME:= APEXID + (4);
DIRDAT:= VOLUME + (2);
UNUSED:= DIRDAT + (2);
FDATE:= UNUSED + (32);
FLAGS:= FDATE + (II);
\FLAGS + (16);
					\NON-DIR ARRAYS:
LOCNAM:= RESERVE(11);
RNAM:= RESERVE(11);

OPENI(0);   OPENO(0);

\There are these ways in which this program may have been entered:
\A) Cold reboot from power off. In that case we expect that the
\   system will have loaded with an invalid system date. The flag COLD
\   is used to identify this state.
\B) We could have been re-entered after an error dropout (REBEGIN). In this
\   case only, the RERUN flag will be true.
\C) We could have been entered from the resident code, with or
\   without a reload. The resident code provides the flag SYSENF (system entry
\   flag) to distinguish these:
\	RELOAD=$FC == reloaded from scratch (no history)
\	RENTER=$FD == re-entered without saving the swap space.
\	SAVER=$FE == reloaded after saving swap space into "SCRATCH.SYS".
\	FSAVE=$FF == reloaded after writing a .SAV file to disk.
\	FASAVE=$F0 == reloaded after writing "SYSTEM.SYS"

\IS THIS A COLD START REBOOT?
COLD:= not (SYSPAG(DATOFF) & SYSPAG(DATOF1));
COLD:= (SYSPAG(DATOF2) # (COLD&$FF));
if COLD then
	begin
	CHOUT(0,FF);
	TXT("

        A P E X
       / \
      /   \

	 BY

COMPUTER APPLICATIONS
4334 EAST 17TH AVENUE
DENVER COLORADO 80220


TYPE ^"HELP^" FOR INSTRUCTIONS

");
	end
else CROUT;

if not RERUN then		\NOT AN ERROR RESTART--SHOW WE'RE BACK IN APEX
	[TXT("-- APEX, V1.8x18 --"); CROUT];

CMDNEW(SYSPAG(SYSUNT));
SYSDAT:= SYSPAG(DATOFF) + SWAP(SYSPAG(DATOF1));

if SYSPAG(SYSENF) = RELOAD then
	[TXT("RELOADED FROM UNIT "); NUMOUT(SYSDEV); CROUT];

if SYSPAG(OTFLG)=CLOSED then		\CLOSE ANY NEW TENTATIVES LYING AROUND
	[CMDCLOSE; SYSPAG(OTFLG):= NOFILE];

SWAPFLG:= false;
if SYSPAG(SYSENF)=SAVEIN then		\WE HAVE A .SAV FILE TO UPDATE
	[FIXSAV; SWAPFLG:= true];	\OUR SWAP AREA MUST BE VALID
if SYSPAG(SYSENF)=SWAPIN then
	SWAPFLG:= true;

SYSPAG(OTFLG):= NOFILE; SYSPAG(INFLG):= NOFILE;	\ERASE ANY OPEN FILES

\NOW CHECK FOR UNITS NEEDING ATTENTION:
\REMOVED - SEE RDDIR
\++for II:= 0,7 do if (SYSPAG(UNTUPD) & BITS(II))#0 then RDDIR(II);
		
if COLD then
	begin				\IF COLD START, RUN "STARTUP.CMD"
	COLD:= false;
	SYSPAG(SYSENF):= 0;
	ACTDEV:= SYSDEV;
	FILENO:= FINDFL(SYSDEV,"STARTUP CMD");
	if FILENO#NONE then DOCMD(GET16(FBLK,FILENO));
	end;

loop	begin				\COMMAND DECODER
	SWITCH:= SP;
	SPECIAL:= 1;
	INSIZE:= 0;
	INDATE:= SYSDAT;		\DEFAULT DATE
	TXT("APX>");
\	OPENI(0);
	NEXT;
	NAME("CMD",SYSDEV);
	if GOTFILE then
		begin			\IS COMMAND
		ACTDEV:= LOCDEV;
		if SWITCH#^E & LOCNAM(8)=^C & LOCNAM(9)=^M & LOCNAM(10)=^D then
			begin
			FILENO:= LOOKUP(0);
			if FILENO#NONE then DOCMD(GET16(FBLK,FILENO));
			end;

		LOCNAM(8):= ^S;		\FORCE IT TO A ".SAV" FILE
		LOCNAM(9):= ^A;
		LOCNAM(10):= ^V;
		FILENO:= LOOKUP(0);
		if FILENO#NONE then RUN(GET16(FBLK,FILENO));	\(NEVER RETURNS)
	
		if DRVSET then
			begin		\TRY A DEEP SEARCH ON THE SYSTEM UNIT
			if ACTDEV#SYSDEV then FILERR;
			RUN(LOOKER(ACTDEV));
			FILERR;
			end;

		\NOT A VALID FILE NAME SO IT MUST BE A COMMAND
		ACTDEV:= RDEV;
		HASH:= LOCNAM(0) + SWAP(LOCNAM(1));
		case HASH of
		   17735\GE\: CMDGET;
		   21587\ST\: CMDSTART(true);
		   22355\SW\: CMDSTART(false);
		\  21335\\WS\\: CMDWS;
		   17742\NE\: CMDNEW(SYSPAG(SYSUNT));
		   22867\SY\: CMDSYS;
		   18771\SI\: CMDSIZE;
		   16708\DA\: CMDDATE;
		   18756\DI\: CMDDIR;
		   17474\BD\: CMDBD;
		   18772\TI\: CMDTITLE;
		   17754\ZE\: CMDZERO;
		   17988\DF\: CMDDF;
		   20292\DO\: CMDDO(true);
		   20302\NO\: CMDDO(false);
		   16717\MA\: CMDMAKE;
		   $5153\SQ\: CMDSQ;
		   17732\DE\: CMDDELETE;
		   16723\SA\: CMDSAVE;
		   20559\OP\: CMDOPEN("@@@","@@@");
		   19523\CL\: CMDCLOSE;
		   18764\LI\: CMDLIST;
		   17746\RE\: CMDRENAME;
		   20300\LO\: CMDUNLOCK(false);
		   20053\UN\: CMDUNLOCK(true);
		   21843\SU\: CMDSUB
		other [CHOUT(0,BEL); TXT("I BEG YOUR PARDON?"); DOREBEGIN];
		end;
	end;
end;	\MAIN
LOCK(true);
		   21843\SU\: CMDSUB
		other [CHOUT(0,BEL); TXT("I BEG YOUR PARDON?"); DOREBEGIN];
		end;
	end;
end