diff --git a/Binary/Apps/Makefile b/Binary/Apps/Makefile
new file mode 100644
index 00000000..a979319b
--- /dev/null
+++ b/Binary/Apps/Makefile
@@ -0,0 +1,7 @@
+TOOLS = ../../Tools
+SUBDIRS = Tunes
+
+include $(TOOLS)/Makefile.inc
+
+clean::
+ rm -f *.bin *.com *.img *.rom *.pdf *.log *.eeprom *.COM *.BIN
diff --git a/Binary/Apps/Tunes/Makefile b/Binary/Apps/Tunes/Makefile
new file mode 100644
index 00000000..b643ef0f
--- /dev/null
+++ b/Binary/Apps/Tunes/Makefile
@@ -0,0 +1,6 @@
+TOOLS = ../../../Tools
+
+include $(TOOLS)/Makefile.inc
+
+clean::
+ rm -f *.pt? *.mym
diff --git a/Binary/Makefile b/Binary/Makefile
new file mode 100644
index 00000000..7175f9f2
--- /dev/null
+++ b/Binary/Makefile
@@ -0,0 +1,7 @@
+TOOLS = ../Tools
+SUBDIRS = Apps
+
+include $(TOOLS)/Makefile.inc
+
+clobber::
+ rm -f *.bin *.com *.img *.rom *.pdf *.log *.eeprom
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..f63a0567
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+all:
+ cd Tools/unix ; make
+ cd Source ; make
+ cd Source/Images ; make
+
+clean:
+ cd Tools/unix ; make clean
+ cd Source ; make clean
+ cd Binary ; make clean
+
+clobber:
+ cd Tools/unix ; make clobber
+ cd Source ; make clobber
+ cd Binary ; make clobber
+
+diff:
+ cd Source ; make diff
+
diff --git a/Readme.unix b/Readme.unix
new file mode 100644
index 00000000..fcc12ca3
--- /dev/null
+++ b/Readme.unix
@@ -0,0 +1,7 @@
+prerequisites:
+
+gcc
+yacc
+make
+g++
+
diff --git a/Source/Apps/FAT/Makefile b/Source/Apps/FAT/Makefile
new file mode 100644
index 00000000..51bf7f98
--- /dev/null
+++ b/Source/Apps/FAT/Makefile
@@ -0,0 +1,6 @@
+OBJECTS = FAT.COM
+NODELETE = FAT.COM
+DEST = ../../../Binary/Apps
+
+TOOLS=../../../Tools
+include $(TOOLS)/Makefile.inc
diff --git a/Source/Apps/FDU/FDU.asm b/Source/Apps/FDU/FDU.asm
index 281901ec..c27cf356 100644
--- a/Source/Apps/FDU/FDU.asm
+++ b/Source/Apps/FDU/FDU.asm
@@ -3346,7 +3346,8 @@ FXRR5: INC C ; [04] POINT TO DATA PORT
;
; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
- .ORG (($ & 0FF00H) + 40H)
+ .FILL (($ & 0FF00H) + 40H) - $
+ ; .ORG (($ & 0FF00H) + 40H)
.ENDIF
;
IFXRR:
@@ -3374,7 +3375,8 @@ IFXRRX .EQU $ - IFXRR
;
; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
- .ORG (($ & 0FF00H) + 40H)
+ .FILL (($ & 0FF00H) + 40H) - $
+ ; .ORG (($ & 0FF00H) + 40H)
.ENDIF
;
FFXRR:
@@ -3452,7 +3454,8 @@ FXRW5: INC C ; [04] POINT TO DATA PORT
;
; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
- .ORG (($ & 0FF00H) + 40H)
+ .FILL (($ & 0FF00H) + 40H) - $
+ ; .ORG (($ & 0FF00H) + 40H)
.ENDIF
;
IFXRW:
@@ -3478,7 +3481,8 @@ IFXRW2: EI
;
; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!!
.IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H))
- .ORG (($ & 0FF00H) + 40H)
+ .FILL (($ & 0FF00H) + 40H) - $
+ ; .ORG (($ & 0FF00H) + 40H)
.ENDIF
;
FFXRW:
diff --git a/Source/Apps/FDU/Makefile b/Source/Apps/FDU/Makefile
new file mode 100644
index 00000000..a7c26e7c
--- /dev/null
+++ b/Source/Apps/FDU/Makefile
@@ -0,0 +1,10 @@
+OBJECTS = FDU.COM
+DOCS = FDU.TXT
+DEST = ../../../Binary/Apps
+DOCDEST = ../../../Doc
+TOOLS = ../../../Tools
+include $(TOOLS)/Makefile.inc
+
+%.COM: %.asm
+ $(TASM) $< $@
+
diff --git a/Source/Apps/Makefile b/Source/Apps/Makefile
new file mode 100644
index 00000000..bc549c46
--- /dev/null
+++ b/Source/Apps/Makefile
@@ -0,0 +1,14 @@
+OBJECTS = SysGen.com Survey.com \
+ SysCopy.com Assign.com Format.com Talk.com OSLdr.com Mode.com RTC.com \
+ Timer.com IntTest.com
+OTHERS = *.hex *.com
+SUBDIRS = XM FDU FAT Tune
+DEST = ../../Binary/Apps
+TOOLS =../../Tools
+
+include $(TOOLS)/Makefile.inc
+
+USETASM = 1
+
+Survey.com: USETASM=0
+
diff --git a/Source/Apps/RTC.asm b/Source/Apps/RTC.asm
index e3ee710e..c6f7b645 100644
--- a/Source/Apps/RTC.asm
+++ b/Source/Apps/RTC.asm
@@ -75,7 +75,7 @@ LOOP:
; uses BC
;
; based on following algorithm:
-:
+;
; const
; hextab : string = ('0','1','2','3','4','5','6','7','8',
; '9','A','B','C','D','E','F');
diff --git a/Source/Apps/Tune/Makefile b/Source/Apps/Tune/Makefile
new file mode 100644
index 00000000..ff962a15
--- /dev/null
+++ b/Source/Apps/Tune/Makefile
@@ -0,0 +1,9 @@
+SUBDIRS = Tunes
+OBJECTS = Tune.com
+DEST = ../../../Binary/Apps
+TOOLS = ../../../Tools
+
+include $(TOOLS)/Makefile.inc
+
+Tune.com: Tune.asm
+ $(TASM) Tune.asm Tune.com
diff --git a/Source/Apps/Tune/Tune.asm b/Source/Apps/Tune/Tune.asm
index 3355e76a..2526fccf 100644
--- a/Source/Apps/Tune/Tune.asm
+++ b/Source/Apps/Tune/Tune.asm
@@ -2336,7 +2336,7 @@ T_PACK .DB $06EC*2/256,$06EC*2
;
; MYMPLAY - Player for MYM-tunes
; MSX-version by Marq/Lieves!Tuore & Fit 30.1.2000
-:
+;
; 1.2.2000 - Added the disk loader. Thanks to Yzi & Plaque for examples.
; 7.2.2000 - Removed one unpack window -> freed 1.7kB memory
;
diff --git a/Source/Apps/Tune/Tunes/Makefile b/Source/Apps/Tune/Tunes/Makefile
new file mode 100644
index 00000000..8c5f4697
--- /dev/null
+++ b/Source/Apps/Tune/Tunes/Makefile
@@ -0,0 +1,9 @@
+OBJECTS = *.pt3 *.mym
+NODELETE = $(OBJECTS)
+DEST = ../../../../Binary/Apps/Tunes
+TOOLS = ../../../../Tools
+
+include $(TOOLS)/Makefile.inc
+
+clobber::
+ -rm -f $(DEST)/*.pt3 $(DEST)/*.mym
diff --git a/Source/Apps/XM/Makefile b/Source/Apps/XM/Makefile
new file mode 100644
index 00000000..72aed531
--- /dev/null
+++ b/Source/Apps/XM/Makefile
@@ -0,0 +1,12 @@
+OBJECTS = xm.com xmuf.com
+DEST = ../../../Binary/Apps
+TOOLS = ../../../Tools
+OTHERS = *.hex
+
+include $(TOOLS)/Makefile.inc
+
+xm.com: xmdm125.hex xmhb.hex
+ $(ZXCC) $(CPM)/MLOAD25 XM=xmdm125,xmhb
+
+xmuf.com: xmdm125.hex xmuf.hex
+ $(ZXCC) $(CPM)/MLOAD25 XMUF=xmdm125,xmuf
diff --git a/Source/BPBIOS/Makefile b/Source/BPBIOS/Makefile
new file mode 100644
index 00000000..3eda8896
--- /dev/null
+++ b/Source/BPBIOS/Makefile
@@ -0,0 +1,42 @@
+VERSIONS = \
+ 33t 33tbnk \
+ 33n 33nbnk \
+ 34t 34tbnk \
+ 34n 34nbnk \
+ 41tbnk 41nbnk
+
+HD0IMG = ../../Binary/hd_bp.img
+IMGFILES = $(foreach ver,$(VERSIONS),bp$(ver).img)
+DISTFILES = *.zex *.rel myterm.z3t
+
+OTHERS = zcpr33n.rel zcpr33t.rel \
+ bpbio-ww.rel bpsys.dat bpsys.bak bpbio-ww.err def-ww.lib
+
+TOOLS = ../../Tools
+
+SUBDIRS = ZCPR33 NZFCP13 Z34RCP11
+include $(TOOLS)/Makefile.inc
+
+$(HD0IMG): $(IMGFILES)
+ $(CPMCP) -f wbw_hd0 $(HD0IMG) $(IMGFILES) $(DISTFILES) 0:
+
+zcpr33n.rel zcpr33t.rel:
+ (cd ZCPR33 ; make)
+
+all:: $(HD0IMG)
+
+clobber::
+ rm -f $(HD0IMG)
+
+%.img: zcpr33n.rel zcpr33t.rel
+ $(eval VER := $(subst .img,,$(subst bp,,$@)))
+ cp def-ww-z$(VER).lib def-ww.lib
+ rm -f bpbio-ww.rel
+ $(ZXCC) $(CPM)/ZMAC -BPBIO-WW -/P
+ mv bpbio-ww.prn bp$(VER).prn
+ cp bp$(VER).dat bpsys.dat
+ $(ZXCC) ./bpbuild.com -bpsys.dat 0 < bpbld1.rsp
+ cp bpsys.img bpsys.dat
+ $(ZXCC) ./bpbuild.com -bpsys.dat 0 < bpbld2.rsp
+ mv bpsys.img bp$(VER).img
+
diff --git a/Source/BPBIOS/NZFCP13/Makefile b/Source/BPBIOS/NZFCP13/Makefile
new file mode 100644
index 00000000..614a877d
--- /dev/null
+++ b/Source/BPBIOS/NZFCP13/Makefile
@@ -0,0 +1,5 @@
+OBJECTS = nzfcp13.rel
+OTHERS =
+TOOLS = ../../../Tools
+
+include $(TOOLS)/Makefile.inc
diff --git a/Source/BPBIOS/Z34RCP11/Makefile b/Source/BPBIOS/Z34RCP11/Makefile
new file mode 100644
index 00000000..7d8b55f8
--- /dev/null
+++ b/Source/BPBIOS/Z34RCP11/Makefile
@@ -0,0 +1,5 @@
+OBJECTS = z34rcp11.rel
+TOOLS = ../../../Tools
+DEST =
+
+include $(TOOLS)/Makefile.inc
diff --git a/Source/BPBIOS/ZCPR33/Makefile b/Source/BPBIOS/ZCPR33/Makefile
new file mode 100644
index 00000000..62450918
--- /dev/null
+++ b/Source/BPBIOS/ZCPR33/Makefile
@@ -0,0 +1,18 @@
+OBJECTS = zcpr33n.rel zcpr33t.rel
+OTHERS = z3basen.lib z3baset.lib
+TOOLS = ../../../Tools
+DEST = ..
+
+include $(TOOLS)/Makefile.inc
+
+DIFFPATH = $(DIFFTO)/Source/BPBIOS
+
+zcpr33t.rel: ../z3baset.lib
+ cp ../z3baset.lib z3baset.lib
+ $(ZXCC) $(CPM)/ZMAC -zcpr33t.z80 -/P
+ rm z3baset.lib
+
+zcpr33n.rel: ../z3basen.lib
+ cp ../z3basen.lib z3basen.lib
+ $(ZXCC) $(CPM)/ZMAC -zcpr33n.z80 -/P
+ rm z3basen.lib
diff --git a/Source/BPBIOS/def-ww.lib b/Source/BPBIOS/def-ww.lib
deleted file mode 100644
index 0eef1948..00000000
--- a/Source/BPBIOS/def-ww.lib
+++ /dev/null
@@ -1,373 +0,0 @@
-;:::::::::::::::::::::::::::::::::::::::::::::::**********************
-; B/P BIOS Configuration and Equate File. ** System Dependant **
-; - D-X Designs Pty Ltd P112 CPU Board - **********************
-; Tailor your system here.
-;
-; 30 Aug 01 - Cleaned up for GPL release. HFB
-; 11 May 97 - Added GIDE and adjusted HD equates. HFB
-; 5 Jan 97 - Reformatted to Standard. HFB
-; 10 Jun 96 - Initial Test Release. HFB
-;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
-; BIOS Configuration Equates and Macros
-
-DATE MACRO
- DEFB '17 Jan 14' ; Date of this version
- ENDM
-
-AUTOCL MACRO
- DEFB 8,'ZEX Z41 ',0 ; Autostart command line
- ENDM
-
-;--- Basic System and Z-System Section ---
-
-MOVCPM EQU no ; Integrate into MOVCPM "type" loader?
- IF MOVCPM
-VERS EQU 13H ; Version number in BCD (Hex) (Major/Minor)
- ELSE
-VERS EQU 21H ; Version number w/Device Swapping permitted
- ENDIF
-BANKED EQU YES ; Is this a banked BIOS?
-ZSDOS2 EQU YES ; Yes = Banked Dos, No = CP/M 2.2 Compatible
-INROM EQU NO ; Alternate bank in ROM?
-MHZ EQU 18 ; Set to Speed in MHZ (6/9/12/16/18/24)
-FASTWB EQU YES ; Yes if restoring CPR from banked RAM
- ; ..No if restoring from Drive A
-Z3 EQU YES ; Include ZCPR init code?
-HAVIOP EQU NO ; Include IOP code into Jump table?
-INTPXY EQU YES ; Internal HBIOS Mini Proxy
-CONF_T EQU NO ; Set for Segment Configuration T
-CONF_N EQU YES ; Set for Segment Configuration N
-
-;--- Memory configuration Section --- (Expansion Memory configured here)
-
-IBMOVS EQU NO ; Yes = Inter-bank Moves allowed (Z180/64180)
- ; No = Include Common RAM transfer buffer
-;--- Character Device Section ---
-
-MORDEV EQU NO ; YES = Include any extra Char Device Drivers
- ; NO = Only use the 4 defined Char Devices
-ESCC_B EQU no ; Include ESCC Channel B Driver?
- ; The following two devices result in non-standard data rates
- ; with the standard 16.00 MHz crystal in the P112. If a more
- ; "standard" crystal is used (12.288, 18.432, 24.576 MHz etc)
- ; is used, the ports become usable.
- ; Driver code for ASCI0 and ASCI1 includes an option for
- ; assembling Polled or Interrupt-driven buffered input.
- ; Select the desired option for ASCI0 with the BUFFA0 flag,
- ; and BUFFA1 for ASCI1.
-ASCI_0 EQU false ; Include ASCI0 Driver?
-BUFFA0 EQU false ; Use buffered ASCI0 Input Driver?
-ASCI_1 EQU false ; Include ASCI1 Driver?
-BUFFA1 EQU false ; Use buffered ASCI1 Input Driver?
-
-QSIZE EQU 32 ; size of interrupt typeahead buffers (if used)
- ; ..must be 2^n with n<8
-RTSCTS EQU no ; Include RTS/CTS code on Serial Outputs?
-XONOFF EQU no ; Include Xon/Xoff handshaking in Serial lines?
-
-;--- Clock and Time Section ---
-
-CLOCK EQU YES ; Include ZSDOS Clock Driver Code?
-DS1202 EQU YES ; Use Dallas DS-1202 instead of Interrupt RTC?
-CLKSET EQU YES ; Allow DS-1202 Clock Sets? (Error if No)
-TICTOC EQU NO ;== NOT USED IN P112 ("heartbeat" count)
-
-;--- Floppy Diskette Section ---
-
-BIOERM EQU yes ; Print BIOS error messages?
-CALCSK EQU YES ; Calculate skew table?
-AUTOSL EQU YES ; Auto select floppy formats?
- ; If AUTOSL=True, the next two are active...
-FDDMA EQU no ; Use DMA Control for Floppy Drive Transfers?
-FLOPYH EQU no ; Include "Hi-Density" Floppy Formats?
-FLOPY8 EQU no ; Include 8" Floppy Formats?
-MORDPB EQU NO ; Include additional Floppy DPB Formats?
-
-;--- RAM Disk Section ---
-
-RAMDSK EQU YES ; YES = Make RAM-Disk Code, NO = No code made
-
-;--- Hard Disk Section ---
-
-HARDDSK EQU YES ; YES = Add Hard-disk Code, NO = Floppy Only
- ; (Pick 1 of 3 options below)
-SCSI EQU NO ; YES = Use SCSI Driver
-IDE EQU NO ; YES = Use IDE Driver
-SIMHDSK EQU NO ; YES = Use SIMH HDSK Driver
-HBDSK EQU YES ; YES = Use HBIOS Disk Driver
-HDDMA EQU NO ; Use DMA-Controlled Hard Disk Data Transfers?
- ; (DMA not implemented for GIDE)
-UNIT_0 EQU YES ; Hard Disk Physical Unit 1
-UNIT_1 EQU YES ; Hard Disk Physical Unit 2
-UNIT_2 EQU YES ; Hard Disk Physical Unit 3
-
-;--- Logical Drive Section ---
-
-DRV_A EQU no ; Set each of these equates for the drive and
-DRV_B EQU no ; partition complement of your system. Assume
-DRV_C EQU no ; that A-D are Floppies.
-DRV_D EQU no
-DRV_E EQU yes ; Assume that E-L and N-P are Hard Disk
-DRV_F EQU yes ; Partitions
-DRV_G EQU yes
-DRV_H EQU yes
-DRV_I EQU yes
-DRV_J EQU yes
-DRV_K EQU yes
-DRV_L EQU yes
-DRV_M EQU RAMDSK ; This is Yes for RAM drive
-DRV_N EQU yes
-DRV_O EQU ~RAMDSK ; Use HBIOS RAM disk if BPBIOS RAM disk is not enabled
-DRV_P EQU no
-
-;========== Configuration Unique Equates (P112) ===========
-;>>>>>>>>>>>>>>>>>>>>>>>>>>> W A R N I N G <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-;>>> Do NOT Alter these unless you KNOW what you're doing <<<
-;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-
-REFRSH EQU NO ; Set to NO for only Static RAM, needed for
- ; systems with dynamic RAMs.
-NOWAIT EQU NO ; Set to NO to use configured Wait States in
- ; Hard Disk Driver. Yes to eliminate Waits.
-
-;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-; For Z-180/HD64180 systems, The Bank numbers should reflect Physical
-; memory in 32k increments. In P112, the ROM occupies the first 32k
-; increment and is ambiguously addressed occupying 0-1FFFFH. The upper
-; memory bounds (BNKU, BNK3 and BNKM) should be set for your configuration.
-
-BNK0 EQU BID_USR ; First TPA Bank (switched in/out) 40000H
-BNK1 EQU BID_HB ; Second TPA Bank (Common Bank) 48000H
-BNK2 EQU BID_SYS ; System Bank (BIOS, DOS, CPR) 50000H
-BNKU EQU 00H ; User Area Bank 58000H
- ; (set to 0 to disable)
-BNK3 EQU BID_RAMD ; First Bank for RAM disk 60000H
-BNKM EQU BID_RAMM ; Maximum Bank # F8000H
- ; With both on-board RAMs only (MEM1 or MEM2),
- ; the maximum Bank number is 11 (0BH).
-
-;=========== CPU-dependent Equates, Zilog Z-180/Hitachi HD64180 ==========
-
-CNTLA0 EQU 00H ; Control Port ASCI 0
-CNTLA1 EQU 01H ; Control Port ASCI 1
-STAT0 EQU 04H ; Serial port 0 Status
-STAT1 EQU 05H ; Serial port 1 Status
-TDR0 EQU 06H ; Serial port 0 Output Data
-TDR1 EQU 07H ; Serial port 1 Output Data
-RDR0 EQU 08H ; Serial port 0 Input Data
-RDR1 EQU 09H ; Serial Port 1 Input Data
-CNTR EQU 0AH ; HD64180 Counter port
-TMDR0L EQU 0CH ; HD64180 DMA channel reg (low)
-TMDR0H EQU 0DH ; HD64180 DMA channel reg (hi)
-RLDR0L EQU 0EH ; CTC0 Reload Count, Low
-RLDR0H EQU 0FH ; CTC0 Reload Count, High
-TCR EQU 10H ; Interrupt Control Register
-TMDR1L EQU 14H ; Timer Data Reg Ch1 (Low)
-TMDR1H EQU 15H ; Timer Data Reg Ch1 (High)
-RLDR1L EQU 16H ; Timer Reload Reg Ch1 (Low)
-RLDR1H EQU 17H ; Timer Reload Reg Ch1 (High)
-FRC EQU 18H ; Free-Running Counter
-CCR EQU 1FH ; CPU Control Register (ZS8180/Z80182)
-SAR0L EQU 20H ; DMA Channel 0 Register start (8 ports)
-MAR1L EQU 28H ; DMA Channel 1 Register start (8 ports)
-DSTAT EQU 30H ; DMA Status/Control port
-DMODE EQU 31H ; DMA Mode Control port
-DCNTL EQU 32H ; DMA/WAIT Control Register
-IL EQU 33H ; Interrupt Segment Register
-ITC EQU 34H ; Interrupt/Trap Control Register
-RCR EQU 36H ; HD64180 Refresh Control register
-CBR EQU 38H ; MMU Common Base Register
-BBR EQU 39H ; MMU Bank Base Register
-CBAR EQU 3AH ; MMU Common/Bank Area Register
-OMCR EQU 3EH ; Operation Mode Control Reg
-ICR EQU 3FH ; I/O Control Register
-
-; Some bit definitions used with the Z-180 on-chip peripherals:
-
-TDRE EQU 02H ; ACSI Transmitter Buffer Empty
-RDRF EQU 80H ; ACSI Received Character available
-
-;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-; Extended Features of Z80182 for P112
-
-WSGCS EQU 0D8H ; Wait-State Generator CS
-ENH182 EQU 0D9H ; Z80182 Enhancements Register
-PINMUX EQU 0DFH ; Interrupt Edge/Pin Mux Register
-RAMUBR EQU 0E6H ; RAM End Boundary
-RAMLBR EQU 0E7H ; RAM Start Boundary
-ROMBR EQU 0E8H ; ROM Boundary
-FIFOCTL EQU 0E9H ; FIFO Control Register
-RTOTC EQU 0EAH ; RX Time-Out Time Constant
-TTOTC EQU 0EBH ; TX Time-Out Time Constant
-FCR EQU 0ECH ; FIFO Register
-SCR EQU 0EFH ; System Pin Control
-RBR EQU 0F0H ; MIMIC RX Buffer Register (R)
-THR EQU 0F0H ; MIMIN TX Holding Register (W)
-IER EQU 0F1H ; Interrupt Enable Register
-LCR EQU 0F3H ; Line Control Register
-MCR EQU 0F4H ; Modem Control Register
-LSR EQU 0F5H ; Line Status Register
-MDMSR EQU 0F6H ; Modem Status Register
-MSCR EQU 0F7H ; MIMIC Scratch Register
-DLATL EQU 0F8H ; Divisor Latch (Low)
-DLATM EQU 0F9H ; Divisor Latch (High)
-TTCR EQU 0FAH ; TX Time Constant
-RTCR EQU 0FBH ; RX Time Constant
-IVEC EQU 0FCH ; MIMIC Interrupt Vector
-MIMIE EQU 0FDH ; MIMIC Interrupt Enable Register
-IUSIP EQU 0FEH ; MIMIC Interrupt Under-Service Register
-MMCR EQU 0FFH ; MIMIC Master Control Register
-
-; Z80182 PIO Registers
-
-DDRA EQU 0EDH ; Data Direction Register A
-DRA EQU 0EEH ; Port A Data
-DDRB EQU 0E4H ; Data Direction Register B
-DRB EQU 0E5H ; Data B Data
-DDRC EQU 0DDH ; Data Direction Register C
-DRC EQU 0DEH ; Data C Data
-
-;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-; ESCC Registers on Z80182
-
-SCCACNT EQU 0E0H ; ESCC Control Channel A
-SCCAD EQU 0E1H ; ESCC Data Channel A
-SCCBCNT EQU 0E2H ; ESCC Control Channel B
-SCCBD EQU 0E3H ; ESCC Data Channel B
-
-; [E]SCC Internal Register Definitions
-
-RR0 EQU 00H
-RR1 EQU 01H
-RR2 EQU 02H
-RR3 EQU 03H
-RR6 EQU 06H
-RR7 EQU 07H
-RR10 EQU 0AH
-RR12 EQU 0CH
-RR13 EQU 0DH
-RR15 EQU 0FH
-
-WR0 EQU 00H
-WR1 EQU 01H
-WR2 EQU 02H
-WR3 EQU 03H
-WR4 EQU 04H
-WR5 EQU 05H
-WR6 EQU 06H
-WR7 EQU 07H
-WR9 EQU 09H
-WR10 EQU 0AH
-WR11 EQU 0BH
-WR12 EQU 0CH
-WR13 EQU 0DH
-WR14 EQU 0EH
-WR15 EQU 0FH
-
-; FDC37C665/6 Parallel Port in Standard AT Mode
-
-DPORT EQU 8CH ; Data Port
-SPORT EQU 8DH ; Status Port
-CPORT EQU 8EH ; Control Port
-
-; FDC37C665/6 Configuration Control (access internal registers)
-
-CFCNTL EQU 90H ; Configuration control port
-CFDATA EQU 91H ; Configuration data port
-
-; FDC37C665/6 Floppy Controller on P112 (Intel 80277 compatible)
-
-DCR EQU 92H ; Drive Control Register (Digital Output)
-MSR EQU 94H ; Main Status Register
-DR EQU 95H ; Data/Command Register
-DRR EQU 97H ; Data Rate Register/Disk Changed Bit in B7
-
-_DMA EQU 0A0H ; Diskette DMA Address
-
-; FDC37C665/6 Serial Port (National 16550 compatible)
-
-_RBR EQU 68H ;R Receiver Buffer
-_THR EQU 68H ;W Transmit Holding Reg
-_IER EQU 69H ;RW Interrupt-Enable Reg
-_IIR EQU 6AH ;R Interrupt Ident. Reg
-_FCR EQU 6AH ;W FIFO Control Reg
-_LCR EQU 6BH ;RW Line Control Reg
-_MCR EQU 6CH ;RW Modem Control Reg
-_LSR EQU 6DH ;RW Line Status Reg
-_MMSR EQU 6EH ;RW Modem Status Reg
-_SCR EQU 6FH ;N/A Scratch Reg. (not avail in XT)
-_DDL EQU 68H ;RW Divisor LSB | wih DLAB
-_DLM EQU 69H ;RW Divisor MSB | set High
-
-;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-; Equates for the National DP8490/NCR 5380 Prototype SCSI controller
-
- IF HARDDSK
-NCR EQU 40H ; Base of NCR 5380
-
-; 5380 Chip Registers
-
-NCRDAT EQU NCR ; Current SCSI Data (Read)
- ; Output Data Register (Write)
-NCRCMD EQU NCR+1 ; Initiator Command Register (Read/Write)
-NCRMOD EQU NCR+2 ; Mode Register (Read/Write)
-NCRTGT EQU NCR+3 ; Target Command Register (Read/Write)
-NCRBUS EQU NCR+4 ; Current SCSI Bus Status (Read)
-NCRST EQU NCR+5 ; Bus & Status Register (Read)
- ; Start DMA Send (Write)
-NCRINT EQU NCR+7 ; Reset Parity/Interrupt (Read)
- ; Start DMA Initiator Receive (Write)
-DMAACK EQU NCR+8 ; SCSI Dack IO Port (Read/Write)
-
-; Bit Assignments for NCR 5380 Ports as indicated
-
-B_ARST EQU 10000000B ; Assert *RST (NCRCMD)
-B_AACK EQU 00010000B ; Assert *ACK (NCRCMD)
-B_ASEL EQU 00000100B ; Assert *SEL (NCRCMD)
-B_ABUS EQU 00000001B ; Assert *Data Bus (NCRCMD)
-
-B_BSY EQU 01000000B ; *Busy (NCRBUS)
-B_REQ EQU 00100000B ; *Request (NCRBUS)
-B_MSG EQU 00010000B ; *Message (NCRBUS)
-B_CD EQU 00001000B ; *Command/Data (NCRBUS)
-B_IO EQU 00000100B ; *I/O (NCRBUS)
-B_SEL EQU 00000010B ; *Select (NCRBUS)
-
-B_PHAS EQU 00001000B ; Phase Match (NCRST)
-B_BBSY EQU 00000100B ; Bus Busy (NCRST)
-
-B_MBSY EQU 00000100B ; Monitor Busy Flag (NCRMOD)
-B_DMA EQU 00000010B ; DMA Mode of transfer (NCRMOD)
- ENDIF ;harddsk
-
-;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-; Equates reflecting GIDE Base address from Address Jumpers (if GIDE added)
-; Set the base GIDE equate to the jumper setting on the GIDE board.
-
- IF IDE
-GIDE EQU 50H ; Set base of 16 byte address range
-
-IDEDOR EQU GIDE+6 ; Digital Output Register
-IDEDat EQU GIDE+8 ; IDE Data Register (16-bit wide)
-IDEErr EQU GIDE+9 ; IDE Error Register
-IDESCnt EQU GIDE+0AH ; IDE Sector Count Register
-IDESNum EQU GIDE+0BH ; IDE Sector Number Register
-IDECLo EQU GIDE+0CH ; IDE Cylinder Number (Low)
-IDECHi EQU GIDE+0DH ; IDE Cylinter Number (High)
-IDESDH EQU GIDE+0EH ; IDE S-Drive-Head Register
-IDECmd EQU GIDE+0FH ; IDE Command/Status Register
-
-CMDHOM EQU 10H ; Home Drive Heads
-CMDRD EQU 20H ; Read Sector Command (w/retry)
-CMDWR EQU 30H ; Write Sector Command (w/retry)
-CMDVER EQU 40H ; Verify Sector(s) Command (w/retry)
-CMDFMT EQU 50H ; Format Track Command
-CMDDIAG EQU 90H ; Execute Diagnostics Command
-CMDINIT EQU 91H ; Initialize Drive Params Command
-CMDPW0 EQU 0E0H ; Low Range of Power Control Commands
-CMDPW3 EQU 0E3H ; High Range of Power Control Commands
-CMDPWQ EQU 0E5H ; Power Status Query Command
-CMDID EQU 0ECH ; Read Drive Ident Data Command
- ENDIF ;ide
-;=================== End Unique Equates =======================
-
\ No newline at end of file
diff --git a/Source/CBIOS/Makefile b/Source/CBIOS/Makefile
new file mode 100644
index 00000000..46e4b507
--- /dev/null
+++ b/Source/CBIOS/Makefile
@@ -0,0 +1,10 @@
+OBJECTS = cbios_wbw.bin cbios_una.bin
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+cbios_wbw.bin: cbios.asm
+ $(TASM) -dPLTWBW $< $@ cbios_wbw.lst
+
+cbios_una.bin: cbios.asm
+ $(TASM) -dPLTUNA $< $@ cbios_una.lst
+
diff --git a/Source/CPM22/Makefile b/Source/CPM22/Makefile
new file mode 100644
index 00000000..d13a30cc
--- /dev/null
+++ b/Source/CPM22/Makefile
@@ -0,0 +1,12 @@
+SYSFILES = cpm_wbw.sys cpm_una.sys
+OBJECTS = CCP.bin BDOS.bin CCP22.bin BDOS22.bin OS2CCP.bin OS3BDOS.bin \
+ ccpb03.bin bdosb01.bin loader.bin $(SYSFILES)
+OTHERS = *.hex
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+cpm_wbw.sys: loader.bin OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_wbw.bin
+ cat loader.bin OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_wbw.bin > cpm_wbw.sys
+
+cpm_una.sys: loader.bin OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_una.bin
+ cat loader.bin OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_una.bin > cpm_una.sys
diff --git a/Source/CPM22/ccpb03.asm b/Source/CPM22/ccpb03.asm
index dd3c8462..5fef8040 100644
--- a/Source/CPM22/ccpb03.asm
+++ b/Source/CPM22/ccpb03.asm
@@ -803,7 +803,7 @@ RESETDR:LD A,(CHGDRV) ; DRIVE CHANGE INDICATED?
;**************************************************************
;
.IF MON
-MONITOR:RST 38
+MONITOR:RST 38H
.ENDIF
;
diff --git a/Source/CPM3/Makefile b/Source/CPM3/Makefile
new file mode 100644
index 00000000..3f86379a
--- /dev/null
+++ b/Source/CPM3/Makefile
@@ -0,0 +1,72 @@
+#
+# this makefile does double duty. it serves as the top level make
+# and as the invoked make for the different ways that the cpm3 is built
+#
+# it does this by overriding OBJECTS in an invoked sub-make
+#
+OBJECTS = cpmldr.com cpm3res cpm3bnk zpmbios3 cpm3.sys gencpm.dat
+OTHERS = cpmldr.rel biosldr.rel cpm3res.sys cpm3bnk.sys zpmbios3.spr
+
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+BIOSOBJS = bioskrnl.rel scb.rel boot.rel chario.rel
+BIOSOBJS += move.rel drvtbl.rel diskio.rel
+COMMA := ,
+NULL :=
+SPACE := $(NULL) $(NULL)
+BIOSNAMES := $(subst $(SPACE),$(COMMA),$(basename $(BIOSOBJS)))
+
+DEFCPM3 = bnk
+#DEFCPM3 = res
+
+clean:: biosclean
+ rm -f bios3.spr bnkbios3.spr zpmbios3.spr cpmldr.com gencpm.dat options.lib
+
+biosclean:
+ rm -f $(BIOSOBJS)
+
+cpm3res:
+ make biosclean
+ cp optres.lib options.lib
+ cp genres.dat gencpm.dat
+ make OBJECTS=bios3.spr
+ $(ZXCC) gencpm -auto -display
+ mv cpm3.sys cpm3res.sys
+ rm gencpm.dat
+
+cpm3bnk:
+ make biosclean
+ cp optbnk.lib options.lib
+ cp genbnk.dat gencpm.dat
+ make OBJECTS=bnkbios3.spr
+ $(ZXCC) gencpm -auto -display
+ mv cpm3.sys cpm3bnk.sys
+ rm gencpm.dat
+
+zpmbios3:
+ make biosclean
+ cp optzpm.lib options.lib
+ cp genbnk.dat gencpm.dat
+ make OBJECTS=zpmbios3.spr
+ rm gencpm.dat
+
+bios3.spr: $(BIOSOBJS)
+ $(ZXCC) $(CPM)/LINK -bios3[OS]=$(BIOSNAMES)
+
+bnkbios3.spr: $(BIOSOBJS)
+ $(ZXCC) $(CPM)/LINK -bnkbios3[B]=$(BIOSNAMES)
+
+zpmbios3.spr: $(BIOSOBJS)
+ $(ZXCC) $(TOOLS)/cpm/bin/LINK -zpmbios3[B]=$(BIOSNAMES)
+
+cpmldr.com: cpmldr.rel biosldr.rel
+ $(ZXCC) $(TOOLS)/cpm/bin/LINK -cpmldr[L100]=cpmldr,biosldr
+ rm -f cpmldr.sym
+
+cpm3.sys: cpm3$(DEFCPM3).sys
+ cp cpm3$(DEFCPM3).sys cpm3.sys
+
+gencpm.dat: gen$(DEFCPM3).dat
+ cp gen$(DEFCPM3).dat gencpm.dat
+
diff --git a/Source/Forth/Makefile b/Source/Forth/Makefile
new file mode 100644
index 00000000..3e2cfd8c
--- /dev/null
+++ b/Source/Forth/Makefile
@@ -0,0 +1,4 @@
+OBJECTS = camel80.bin
+TOOLS = ../../Tools
+OTHERS = *.rel
+include $(TOOLS)/Makefile.inc
diff --git a/Source/HBIOS/Build.sh b/Source/HBIOS/Build.sh
new file mode 100755
index 00000000..4faf1991
--- /dev/null
+++ b/Source/HBIOS/Build.sh
@@ -0,0 +1,122 @@
+#!/bin/bash
+
+CPMCP=../../Tools/`uname`/cpmcp
+
+# positional arguments
+platform=$1
+config=$2
+romsize=$3
+romname=$4
+
+# prompt if no match
+platforms=($(find Config -name \*.asm -print | \
+ sed -e 's,Config/,,' -e 's/_.*$//' | sort -u))
+
+while ! echo ${platforms[@]} | grep -q -w -s "$platform" ; do
+ echo -n "Enter platform [" ${platforms[@]} "] :"
+ read platform
+done
+
+configs=$(find Config -name ${platform}_\* -print | \
+ sed -e 's,Config/,,' -e "s/${platform}_//" -e "s/.asm//")
+while ! echo ${configs[@]} | grep -s -w -q "$config" ; do
+ echo -n "Enter config for $platform [" ${configs[@]} "] :"
+ read config
+done
+configfile=Config/${platform}_${config}.asm
+
+while [ ! '(' "$romsize" = 1024 -o "$romsize" = 512 ')' ] ; do
+ echo -n "Romsize :"
+ read romsize
+done
+
+if [ -z "$romname" ] ; then
+ romname=${platform}_${config}
+fi
+echo Building for $romname for $platform $config $romsize
+
+if [ $platform == UNA ] ; then
+ CBIOS=../CBIOS/cbios_una.bin
+else
+ CBIOS=../CBIOS/cbios_wbw.bin
+fi
+
+Apps=(assign fdu format mode osldr rtc survey syscopy sysgen talk timer xm inttest)
+timestamp=$(date +%Y-%m-%d)
+
+blankfile=Blank${romsize}.dat
+romdiskfile=RomDisk.tmp
+romfmt=wbw_rom${romsize}
+outdir=../../Binary
+
+echo "creating empty rom disk of size $romsize in $blankfile"
+LANG=en_US.US-ASCII tr '\000' '\345' build.inc
+; RomWBW Configured for $platform $config $timestamp
+;
+#DEFINE TIMESTAMP "$timestamp"
+;
+ROMSIZE .EQU $romsize
+;
+#INCLUDE "$configfile"
+;
+EOF
+
+cp ../CPM22/OS2CCP.bin ccp.bin
+cp ../CPM22/OS3BDOS.bin bdos.bin
+cp ../ZCPR-DJ/zcpr.bin zcpr.bin
+cp ../ZSDOS/zsdos.bin zsdos.bin
+cp ../Forth/camel80.bin camel80.bin
+
+make -f Makefile dbgmon.bin prefix.bin romldr.bin eastaegg.bin nascom.bin \
+ tastybasic.bin imgpad.bin imgpad0.bin
+if [ $platform != UNA ] ; then
+ make -f Makefile hbios_rom.bin hbios_app.bin hbios_img.bin
+fi
+
+cat ccp.bin bdos.bin $CBIOS >cpm.bin
+cat zcpr.bin zsdos.bin $CBIOS >zsys.bin
+
+cat prefix.bin cpm.bin >cpm.sys
+cat prefix.bin zsys.bin >zsys.sys
+
+cat romldr.bin eastaegg.bin dbgmon.bin cpm.bin zsys.bin >osimg.bin
+cat camel80.bin nascom.bin tastybasic.bin imgpad0.bin >osimg1.bin
+
+echo "Building ${romsize}KB $romname ROM disk data file..."
+cp $blankfile $romdiskfile
+$CPMCP -f $romfmt $romdiskfile ../RomDsk/ROM_${romsize}KB/*.* 0:
+
+if [ $(find ../RomDsk/$platform -type f -print 2>/dev/null | wc -l) -gt 0 ] ; then
+ $CPMCP -f $romfmt $romdiskfile ../RomDsk/$platform/*.* 0:
+fi
+
+for i in ${apps[@]} ; do
+ $CPMCP -f $romfmt $romdiskfile ../../Binary/Apps/$i.com 0:
+done
+
+for i in *.sys ; do
+ $CPMCP -f $romfmt $romdiskfile $i 0:
+done
+
+if [ $platform != UNA ] ; then
+ cp osimg.bin $outdir/UNA_WBW_SYS.bin
+ cp $romdiskfile $outdir/UNA_WBW_ROM$romsize.bin
+ cat ../UBIOS/UNA-BIOS.BIN osimg.bin ../UBIOS/FSFAT.BIN $romdiskfile >$romname.rom
+else
+ cat hbios_rom.bin osimg.bin osimg1.bin osimg.bin $romdiskfile >$romname.rom
+ cat hbios_app.bin osimg.bin > $romname.com
+ cat hbios_img.bin osimg.bin > $romname.img
+fi
+
+#rm $romdiskfile
diff --git a/Source/HBIOS/Makefile b/Source/HBIOS/Makefile
new file mode 100644
index 00000000..c227ab1a
--- /dev/null
+++ b/Source/HBIOS/Makefile
@@ -0,0 +1,10 @@
+OBJECTS = ZETA2_std.rom
+SUBDIRS =
+DEST = ../../Binary/Apps
+TOOLS =../../Tools
+OTHERS = *.bin *.z80 cpm.sys zsys.sys Build.inc
+include $(TOOLS)/Makefile.inc
+
+ZETA2_std.rom:
+ bash Build.sh ZETA2 std 512
+
diff --git a/Source/HBIOS/eastaegg.asm b/Source/HBIOS/eastaegg.asm
index 24744f11..23744cb5 100644
--- a/Source/HBIOS/eastaegg.asm
+++ b/Source/HBIOS/eastaegg.asm
@@ -17,6 +17,11 @@
;
#include "std.asm"
+; *** HACK TO GET THIS TO BUILD UNDER UNA ***
+#IF (BIOS == BIOS_UNA)
+#INCLUDE "hbios.inc"
+#ENDIF
+
cr .equ 0dh
lf .equ 0ah
eos .equ 00h
diff --git a/Source/HBIOS/hbios.inc b/Source/HBIOS/hbios.inc
index 026fdd4e..444bae8b 100644
--- a/Source/HBIOS/hbios.inc
+++ b/Source/HBIOS/hbios.inc
@@ -125,12 +125,12 @@ VDADEV_CVDU .EQU $10 ; ECB COLOR VDU - MOS 8563
VDADEV_NEC .EQU $20 ; ECB UPD7220 - NEC UPD7220
VDADEV_TMS .EQU $30 ; N8 ONBOARD VDA SUBSYSTEM - TMS 9918
VDADEV_VGA .EQU $40 ; ECB VGA3 - HITACHI HD6445
-;
-; EMULATION TYPES
-;
-EMUTYP_NONE .EQU 0 ; NONE
-EMUTYP_TTY .EQU 1 ; TTY
-EMUTYP_ANSI .EQU 2 ; ANSI
+;;
+;; EMULATION TYPES - moved to std.asm
+;;
+;EMUTYP_NONE .EQU 0 ; NONE
+;EMUTYP_TTY .EQU 1 ; TTY
+;EMUTYP_ANSI .EQU 2 ; ANSI
;
; HBIOS CONTROL BLOCK OFFSETS
; WARNING: THESE OFFSETS WILL CHANGE SIGNIFICANTLY BETWEEN RELEASES
diff --git a/Source/HBIOS/nascom.asm b/Source/HBIOS/nascom.asm
index 561354d5..51517cc9 100644
--- a/Source/HBIOS/nascom.asm
+++ b/Source/HBIOS/nascom.asm
@@ -29,6 +29,11 @@
;
#INCLUDE "std.asm"
;
+; *** HACK TO GET THIS TO BUILD UNDER UNA ***
+#IF (BIOS == BIOS_UNA)
+#INCLUDE "hbios.inc"
+#ENDIF
+;
; CUSTOMIZATION
;
ABBRERR .EQU FALSE ; Choose between long error message and abbreviated error messages.
@@ -302,26 +307,26 @@ WORDS: .BYTE 'E'+80H,"ND" ; PEND:
.BYTE 'R'+80H,"EM" ; REM:
.BYTE 'S'+80H,"TOP" ; STOP:
.BYTE 'O'+80H,"UT" ; POUT:
- .BYTE 'O'+80H,"N" : ON:
+ .BYTE 'O'+80H,"N" ; ON:
.BYTE 'N'+80H,"ULL" ; NULL:
- .BYTE 'W'+80H,"AIT" : WAIT:
- .BYTE 'D'+80H,"EF" : DEF:
- .BYTE 'P'+80H,"OKE" : POKE:
- .BYTE 'D'+80H,"OKE" : DOKE:
- .BYTE 'S'+80H,"CREEN" : REM: NOT IMPLEMENTED
- .BYTE 'L'+80H,"INES" : LINES
- .BYTE 'C'+80H,"LS" : CLS:
- .BYTE 'W'+80H,"IDTH" : WIDTH:
- .BYTE 'B'+80H,"YE" : MONITR:
- .BYTE 'S'+80H,"ET" : PSET:
+ .BYTE 'W'+80H,"AIT" ; WAIT:
+ .BYTE 'D'+80H,"EF" ; DEF:
+ .BYTE 'P'+80H,"OKE" ; POKE:
+ .BYTE 'D'+80H,"OKE" ; DOKE:
+ .BYTE 'S'+80H,"CREEN" ; REM: NOT IMPLEMENTED
+ .BYTE 'L'+80H,"INES" ; LINES
+ .BYTE 'C'+80H,"LS" ; CLS:
+ .BYTE 'W'+80H,"IDTH" ; WIDTH:
+ .BYTE 'B'+80H,"YE" ; MONITR:
+ .BYTE 'S'+80H,"ET" ; PSET:
.BYTE 'R'+80H,"ESET" ; RESET:
- .BYTE 'P'+80H,"RINT" : PRINT:
- .BYTE 'C'+80H,"ONT" : CONT:
- .BYTE 'L'+80H,"IST" : LIST:
- .BYTE 'C'+80H,"LEAR" : CLEAR:
- .BYTE 'P'+80H,"LAY" : PLAY: WAS CLOAD
- .BYTE 'C'+80H,"SAVE" : REM: NOT IMPLEMENTED
- .BYTE 'N'+80H,"EW" : NEW
+ .BYTE 'P'+80H,"RINT" ; PRINT:
+ .BYTE 'C'+80H,"ONT" ; CONT:
+ .BYTE 'L'+80H,"IST" ; LIST:
+ .BYTE 'C'+80H,"LEAR" ; CLEAR:
+ .BYTE 'P'+80H,"LAY" ; PLAY: WAS CLOAD
+ .BYTE 'C'+80H,"SAVE" ; REM: NOT IMPLEMENTED
+ .BYTE 'N'+80H,"EW" ; NEW
.BYTE 'T'+80H,"AB("
.BYTE 'T'+80H,"O"
diff --git a/Source/HBIOS/romldr.asm b/Source/HBIOS/romldr.asm
index 9fb43746..5b0a9420 100644
--- a/Source/HBIOS/romldr.asm
+++ b/Source/HBIOS/romldr.asm
@@ -316,6 +316,7 @@ MATD: LD B,A
MATD1: CP 10 ; DO A RANGE CHECK
JR NC,MATX ; NOT VALID, HANDLE IT BELOW
;
+#IF (BIOS == BIOS_WBW)
PUSH BC
PUSH AF ; HOW MANY DISK
LD B,BF_SYSGET ; DEVICES DO WE
@@ -323,6 +324,9 @@ MATD1: CP 10 ; DO A RANGE CHECK
RST 08 ; SYSTEM ?
POP AF
POP BC
+#ELSE
+ LD E,9 ; HACK TO HANDLE UNA, NEED TO FIX
+#ENDIF
; JR MATD2 ; IF MORE THEN 9 ; UNCOMMENT TO TEST DOUBLE CHAR ENTRY
CP 10 ; THEN WE NEED TO GET
JR NC,MATD2 ; ANOTHER CHARACTER
@@ -441,7 +445,10 @@ MENU_N .EQU ((MENU_E - MENU_S) / MENU_V) ; NUMBER OF MENU ITEMS
; SYSTEM REBOOT HANDLER
;==================================================================================================
;
-REBOOT: LD DE,STR_REBOOT ; POINT TO MESSAGE
+REBOOT:
+;
+#IF (BIOS == BIOS_WBW)
+ LD DE,STR_REBOOT ; POINT TO MESSAGE
CALL WRITESTR ; PRINT IT
#IF (DSKYENABLE)
LD HL,MSG_BOOT ; POINT TO BOOT MESSAGE
@@ -450,6 +457,12 @@ REBOOT: LD DE,STR_REBOOT ; POINT TO MESSAGE
LD A,BID_BOOT ; BOOT BANK
LD HL,0 ; ADDRESS ZERO
CALL HB_BNKCALL ; DOES NOT RETURN
+#ENDIF
+#IF (BIOS == BIOS_UNA)
+ ; UMMM... NEED TO DO SOMETHING HERE...
+ LD DE,STR_INVALID ; SET ERROR STRING MESSAGE
+ JP MENU ; AND RESTART MENU LOOP
+#ENDIF
;
;==================================================================================================
; ROM IMAGE LOAD HANDLER
diff --git a/Source/HBIOS/std.asm b/Source/HBIOS/std.asm
index aa6ae0aa..7dc8aa40 100644
--- a/Source/HBIOS/std.asm
+++ b/Source/HBIOS/std.asm
@@ -44,9 +44,6 @@ PLT_EZZ80 .EQU 9 ; EASY Z80
PLT_SCZ180 .EQU 10 ; SCZ180
PLT_DYNO .EQU 11 ; DYNO MICRO-ATX MOTHERBOARD
;
-#IF (BIOS == BIOS_WBW)
-#INCLUDE "hbios.inc"
-#ENDIF
;
; CPU TYPES
;
@@ -304,6 +301,12 @@ USELZSA2 .EQU FALSE ; USE COMPRESSED FONTS.
KBD_US .EQU 0 ; US ENGLISH
KBD_DE .EQU 1 ; GERMAN
;
+; EMULATION TYPES
+;
+EMUTYP_NONE .EQU 0 ; NONE
+EMUTYP_TTY .EQU 1 ; TTY
+EMUTYP_ANSI .EQU 2 ; ANSI
+;
; DEVICE DRIVER TO BE INITIALIZED FIRST. FIRST CIO DRIVER, UNIT 0 INITIALIZED BECOMES PRIMARY CONSOLE.
; IS AN INDEX INTO THE ENABLED INITIALIZATION DRIVER LIST i.e. ASCI, UART, SIO, ACIA, PIO, UF ETC.
; EXAMPLE: IF ONLY UART, SIO AND PIO ARE ENABLE AND THE SIO IS DESIRED AS THE PRIMARY CONSOLE,
@@ -313,6 +316,11 @@ FORCECON .EQU 0 ; DEFAULT IS TO FOLLOW NORMAL SEQUENCE
;
#INCLUDE "build.inc" ; INCLUDE USER CONFIG, ADD VARIANT, TIMESTAMP, & ROMSIZE
;
+#IF (BIOS == BIOS_WBW)
+#INCLUDE "hbios.inc"
+#ENDIF
+;
+;
; INCLUDE Z180 REGISTER DEFINITIONS
;
#IF (BIOS == BIOS_WBW)
diff --git a/Source/HBIOS/tastybasic.asm b/Source/HBIOS/tastybasic.asm
index 3366737b..7c907dfa 100644
--- a/Source/HBIOS/tastybasic.asm
+++ b/Source/HBIOS/tastybasic.asm
@@ -40,6 +40,11 @@ TBC_LOC .equ 0
#else ; RomWBW
#include "std.asm"
#endif
+; *** HACK TO GET THIS TO BUILD UNDER UNA ***
+#IF (BIOS == BIOS_UNA)
+#INCLUDE "hbios.inc"
+#ENDIF
+
.org TBC_LOC
start:
ld sp,stack ; ** Cold Start **
diff --git a/Source/Images/Makefile b/Source/Images/Makefile
new file mode 100644
index 00000000..5b9e0a6a
--- /dev/null
+++ b/Source/Images/Makefile
@@ -0,0 +1,93 @@
+#
+# this makefile subsumes all the work done in Build.cmd, Build{Hd,Fd}.*
+#
+SYSTEMS = cpm_wbw.sys cpm_una.sys zsys_wbw.sys zsys_una.sys
+FDIMGS = fd_cpm22.img fd_zsdos.img fd_nzcom.img fd_cpm3.img fd_zpm3.img fd_ws4.img
+HDIMGS = hd_cpm22.img hd_zsdos.img hd_nzcom.img hd_cpm3.img hd_zpm3.img hd_ws4.img hd_bp.img
+OBJECTS = $(FDIMGS) $(HDIMGS)
+OTHERS = $(SYSTEMS) blank144 blankhd
+
+DEST=../../Binary
+
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+#
+# create the os's, which are the bootloader, ccp, bdos and bios
+#
+cpm_wbw.sys:
+ cat $$($(CASEFN) \
+ ../BL/bl.bin \
+ ../CPM22/os2ccp.bin ../CPM22/OS3BDOS.BIN \
+ ../CBIOS/cbios_wbw.bin) > cpm_wbw.sys
+
+cpm_una.sys:
+ cat $$($(CASEFN) \
+ ../BL/bl.bin \
+ ../CPM22/os2ccp.bin ../CPM22/OS3BDOS.BIN \
+ ../CBIOS/cbios_una.bin) > cpm_una.sys
+
+zsys_wbw.sys:
+ cat $$($(CASEFN) \
+ ../BL/bl.bin \
+ ../ZCPR-DJ/zcpr.bin ../ZSDOS/ZSDOS.BIN \
+ ../CBIOS/cbios_wbw.bin) > zsys_wbw.sys
+
+zsys_una.sys:
+ cat $$($(CASEFN) \
+ ../BL/bl.bin \
+ ../ZCPR-DJ/zcpr.bin ../ZSDOS/ZSDOS.BIN \
+ ../CBIOS/cbios_una.bin) > zsys_una.sys
+
+#
+# this somewhat impenetrable and fragile code is used to build each of the images
+# at build time, a few variables are set (sys, fmt, type, size, d) based on the
+# target to build. first, we build an empty image using the a tr, dd pipeline.
+# we then scan the d_{d}/u* directories, copying in files to user numbers
+# then process the d_{d}.txt file, copying in those files, and finally maybe put
+# an OS at the start of each image
+#
+blank144:
+ @echo Making Blank Floppy of size 1440k
+ @LANG=en_US.US-ASCII tr '\000' '\345' /dev/null
+
+HDSIZE := $(shell expr 128 '*' 65)
+
+blankhd:
+ @echo Making Blank Hd of size $(HDSIZE)k
+ @LANG=en_US.US-ASCII tr '\000' '\345' /dev/null
+
+%.img:: $(SYSTEMS) blank144 blankhd
+ @sys= ; \
+ case $@ in (*cpm22*) sys=cpm_wbw.sys;; (*zsdos* | *nzcom) sys=zsys_wbw.sys;; esac ; \
+ if echo $@ | grep -q ^f ; then \
+ fmt=wbw_fd144 ; type=fd_ ; proto=blank144 ; \
+ else \
+ fmt=wbw_hd0 ; type=hd_ ; proto=blankhd ; \
+ fi ; \
+ d=$$(echo $(basename $@) | sed s/$$type//) ; \
+ echo Generating $@ ; \
+ cp $$proto $@ ; \
+ for u in $$(seq 0 15) ; do \
+ dir=d_$$d/u$$u ; \
+ if [ -d $$dir ] ; then \
+ echo " " copying directory $$dir ; \
+ $(CPMCP) -f $$fmt $@ $$($(CASEFN) $$dir/*.*) $$u: ; \
+ fi ; \
+ done ; \
+ if [ -f d_$$d.txt ] ; then \
+ echo " " copying files from d_$$d.txt ; \
+ grep -v ^# d_$$d.txt | tr -d '\r' | while read file user ; do \
+ rf=$$($(CASEFN) $$file) ; \
+ if [ "$$rf" = nofile ] ; then \
+ echo " " $$file missing ; \
+ else \
+ $(CPMCP) -f $$fmt $@ $$rf $$user ; \
+ fi ; \
+ done ; \
+ fi ; \
+ if [ "$$sys" ] ; then \
+ echo copying system $$sys to $@ ; \
+ dd if=$$sys of=$@ conv=notrunc 2>/dev/null ; \
+ fi
+
diff --git a/Source/Makefile b/Source/Makefile
new file mode 100644
index 00000000..06d19b78
--- /dev/null
+++ b/Source/Makefile
@@ -0,0 +1,11 @@
+NOTDONE = Doc
+SUBDIRS = Prop
+SUBDIRS += Apps CPM22 ZCPR ZCPR-DJ ZSDOS CBIOS CPM3
+SUBDIRS += ZPM3
+SUBDIRS += Forth
+NOTDONE += Fonts
+SUBDIRS += BPBIOS
+SUBDIRS += HBIOS
+SUBDIRS += Images
+TOOLS = ../Tools
+include $(TOOLS)/Makefile.inc
diff --git a/Source/Prop/Makefile b/Source/Prop/Makefile
new file mode 100644
index 00000000..ceb364ca
--- /dev/null
+++ b/Source/Prop/Makefile
@@ -0,0 +1,4 @@
+SUBDIRS = Spin
+TOOLS = ../../Tools
+OTHERS = *.list
+include $(TOOLS)/Makefile.inc
diff --git a/Source/Prop/Spin/Makefile b/Source/Prop/Spin/Makefile
new file mode 100644
index 00000000..fccfb5fe
--- /dev/null
+++ b/Source/Prop/Spin/Makefile
@@ -0,0 +1,7 @@
+OBJECTS = PropIO.eeprom PropIO2.eeprom ParPortProp.eeprom
+OTHERS = *.list
+DEST = ../../../Binary
+TOOLS = ../../../Tools
+include $(TOOLS)/Makefile.inc
+
+DIFFPATH := $(DIFFTO)/Binary
diff --git a/Source/ZCPR-DJ/Makefile b/Source/ZCPR-DJ/Makefile
new file mode 100644
index 00000000..47584a38
--- /dev/null
+++ b/Source/ZCPR-DJ/Makefile
@@ -0,0 +1,10 @@
+
+OBJECTS= zcpr.bin
+
+OTHERS = zcpr.rel
+
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+zcpr.bin: zcpr.rel
+ $(ZXCC) $(TOOLS)/cpm/bin/L80 -zcpr,zcpr.bin/n/e
diff --git a/Source/ZCPR/Makefile b/Source/ZCPR/Makefile
new file mode 100644
index 00000000..4e35f6d1
--- /dev/null
+++ b/Source/ZCPR/Makefile
@@ -0,0 +1,12 @@
+
+OBJECTS = zcpr.bin bdloc.com
+
+OTHERS = *.hex
+
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+zcpr.bin: zcpr.asm
+ $(ZXCC) $(CPM)/MAC -$< -$$PO
+ $(ZXCC) $(CPM)/MLOAD25 -$@=zcpr.hex
+
diff --git a/Source/ZPM3/Makefile b/Source/ZPM3/Makefile
new file mode 100644
index 00000000..344c6543
--- /dev/null
+++ b/Source/ZPM3/Makefile
@@ -0,0 +1,42 @@
+OBJECTS = zpmldr.com cpm3.sys zinstal.zpm startzpm.com zccp.com \
+ setz3.com clrhist.com autotog.com cpmldr.com
+OTHERS = biosldr.rel gencpm.com gencpm.dat bnkbios3.spr
+TOOLS =../../Tools
+
+include $(TOOLS)/Makefile.inc
+
+zpmldr.com: zpm3ldr.rel biosldr.rel
+ $(ZXCC) $(CPM)/LINK -ZPMLDR[L100]=ZPM3LDR,BIOSLDR
+
+cpm3.sys: gencpm.com gencpm.dat bnkbios3.spr
+ $(ZXCC) gencpm -auto -display
+
+bnkbios3.spr: ../CPM3/zpmbios3.spr
+ cp $< $@
+
+gencpm.dat: ../CPM3/genbnk.dat
+ cp $< $@
+
+gencpm.com: ../CPM3/gencpm.com
+ cp $< $@
+
+biosldr.rel: ../CPM3/biosldr.rel
+ cp $< $@
+
+cpmldr.com: ../CPM3/cpmldr.com
+ cp $< $@
+
+zccp.com: ../ZCCP/ccp.com
+ cp $< $@
+
+zinstal.zpm: ../ZCCP/zinstal.zpm
+ cp $< $@
+
+startzpm.com: ../ZCCP/startzpm.com
+ cp $< $@
+
+setz3.com: setz3.z80
+
+clrhist.com: clrhist.z80
+
+autotog.com: autotog.z80
diff --git a/Source/ZPM3/autotog.z80 b/Source/ZPM3/autotog.z80
index b59a6663..48977581 100644
--- a/Source/ZPM3/autotog.z80
+++ b/Source/ZPM3/autotog.z80
@@ -45,13 +45,13 @@ ACPoff equ 85h ; Offset in SCB base page of Auto Command Prompting bit
HELPmsg:
db ' SYNTAX:'
db 10,13
- db ' AUTOTOG Toggles the state of the Auto Command Prompting'
+ db ' AUTOTOG',9,'Toggles the state of the Auto Command Prompting'
db 10,13
- db ' AUTOTOG ON Enables Auto Command Prompting'
+ db ' AUTOTOG ON',9,'Enables Auto Command Prompting'
db 10,13
- db ' AUTOTOG OFF Disables Auto Command Prompting'
+ db ' AUTOTOG OFF',9,'Disables Auto Command Prompting'
db 10,13
- db ' AUTOTOG // Displays a brief help message'
+ db ' AUTOTOG //',9,'Displays a brief help message'
db '$'
ONmsg:
db 'ZPM3 Auto Command Prompting is now enabled. Toggle with ^Q.'
@@ -128,4 +128,4 @@ ACPaddr: ; Save the address of the ACP bit here too.
db 03bh
db 0 ; Get operation
-
\ No newline at end of file
+
diff --git a/Source/ZSDOS/Makefile b/Source/ZSDOS/Makefile
new file mode 100644
index 00000000..483a2915
--- /dev/null
+++ b/Source/ZSDOS/Makefile
@@ -0,0 +1,11 @@
+OBJECTS = zsdos.bin
+OTHERS = zsdos.rel zsdos.err
+TOOLS = ../../Tools
+include $(TOOLS)/Makefile.inc
+
+zsdos.rel: zsdos.z80
+ $(ZXCC) $(CPM)/ZMAC -$< -/P
+
+zsdos.bin: zsdos.rel
+ $(ZXCC) $(CPM)/LINK -$@=$<[LD800]
+
diff --git a/Tools/Makefile.inc b/Tools/Makefile.inc
new file mode 100644
index 00000000..9614238a
--- /dev/null
+++ b/Tools/Makefile.inc
@@ -0,0 +1,156 @@
+#
+# try to use suffix rules whenever possible in any of the lower makefiles
+# in the case of exceptions, just use an explicit build rule
+#
+# there are also some bizarre things being done with case-sensitive sources
+# make is very much case-sensitive, so we use this. if your underlying
+# filesystem is not case-preserving, forget it.
+#
+# .asm: TASM sources, except somewheres it is MAC or RMAC
+# .z80: Z80ASM sources, except ZSDOS, where they are ZMAC
+# .azm: zsm sources
+#
+UNAME := $(shell uname)
+
+#
+# since this file is included from below, it's handy to have an idea
+# where we are relative to the tree
+#
+TREEROOT := $(shell cd $(TOOLS)/.. ; pwd)
+HERE := $(shell pwd)
+RELPATH := $(subst $(TREEROOT),,$(HERE))
+
+#
+# where's a copy of this tree for windows so we can diff binaries
+#
+DIFFTO := /Volumes/Github/RomWBW
+DIFFPATH := $(DIFFTO)/$(RELPATH)
+
+#
+# this is a script that resolves a filename in a case-insensitive way
+# to be used in diff'ing objects
+#
+CASEFN = $(TOOLS)/unix/casefn.sh
+
+ZXCC=$(TOOLS)/$(UNAME)/zx
+TASM=$(TOOLS)/$(UNAME)/uz80as
+OPENSPIN=$(TOOLS)/$(UNAME)/openspin
+BSTC=$(TOOLS)/$(UNAME)/bstc
+CPMCP=$(TOOLS)/$(UNAME)/cpmcp
+
+#
+# directory containing cpm binaries
+#
+CPM=$(TOOLS)/cpm/bin
+
+%.com: %.asm
+ if [ "$(USETASM)" = 1 ] ; then \
+ $(TASM) $< $@ ; \
+ else \
+ $(ZXCC) $(CPM)/MAC -$< -$$PO ; \
+ $(ZXCC) $(CPM)/MLOAD25 -tmp.bin=$*.hex ; \
+ mv tmp.bin $@ ; \
+ rm -f $$($(CASEFN) $*.hex) ; \
+ fi
+
+%.hex: %.asm
+ $(ZXCC) $(CPM)/MAC -$< -$$PO ; \
+
+%.bin: %.ASM
+ $(ZXCC) $(CPM)/MAC -$< -$$PO
+ $(ZXCC) $(CPM)/MLOAD25 -tmp.bin=$*.hex
+ mv tmp.bin $@
+ rm -f $$($(CASEFN) $*.hex)
+
+%.com: %.z80
+ $(ZXCC) $(CPM)/Z80ASM -$(basename $<)/F ; \
+ mv $$($(CASEFN) $@) tmp.com ; mv tmp.com $@
+
+%.bin: %.asm
+ $(TASM) $< $@
+
+%.rel: %.asm
+ $(ZXCC) $(CPM)/RMAC -$<
+
+%.rel: %.z80
+ $(ZXCC) $(CPM)/Z80ASM -$(basename $<)/MF
+
+%.hex: %.180
+ $(ZXCC) $(CPM)/SLR180 -$(basename $<)/HF
+
+%.rel: %.azm
+ $(ZXCC) $(CPM)/ZSM =$<
+
+%.bin: %.rel
+ $(ZXCC) $(CPM)/LINK -$@=$<
+
+%.rel: %.mac
+ $(ZXCC) $(CPM)/M80 -=$(basename $<)
+
+ifeq ($(UNAME), Linux)
+%.eeprom: %.spin
+ $(BSTC) -e -l $<
+endif
+
+#
+# darwin bstc won't run, since mac os does not do 32 bit binaries any more
+# openspin ought to work
+#
+ifeq ($(UNAME), Darwin)
+%.eeprom: %.spin
+ $(OPENSPIN) -e $<
+endif
+
+#
+# first target is default
+#
+all:: $(OBJECTS)
+ @for dir in $(SUBDIRS) ; do \
+ ( echo "building in `pwd`/$$dir" ; cd "$$dir" ; make all ) ; \
+ done
+ @if [ "$(DEST)" ] ; then for file in $(OBJECTS) ; do \
+ mkdir -p $(DEST) ; \
+ echo copy $$file to $(DEST) ; \
+ cp $$($(CASEFN) $$file) $(DEST) ; \
+ done ; fi
+ @if [ "$(DOCDEST)" ] ; then for file in $(DOCS) ; do \
+ mkdir -p $(DOCDEST) ; \
+ echo copy $$file to $(DOCDEST) ; \
+ cp $$($(CASEFN) $$file) $(DOCDEST) ; \
+ done ; fi
+
+clean::
+ -rm -f $$($(CASEFN) make.out *.sym *.lst *.prn *.diff *.dump $(OTHERS) $(filter-out $(NODELETE),$(OBJECTS)))
+ @for dir in $(SUBDIRS) ; do \
+ ( echo "cleaning in `pwd`/$$dir" ; cd "$$dir" ; make clean ) ; \
+ done
+
+clobber:: clean
+ -rm -f $$($(CASEFN) $(filter-out $(NODELETE),$(OBJECTS)))
+ @for dir in $(SUBDIRS) ; do \
+ ( echo "clobbering in `pwd`/$$dir" ; cd "$$dir" ; make clobber ) ; \
+ done
+
+#
+# this is used to verify that the unix and windows tool chains are generating
+# the same objects
+#
+diff::
+ @for dir in $(SUBDIRS) ; do \
+ ( echo "diff in $(HERE)/$$dir" ; cd "$$dir" ; make diff ) ; \
+ done
+ @for i in $(OBJECTS) ; do \
+ sf=$$($(CASEFN) $$i) ; \
+ df=$$($(CASEFN) $(DIFFPATH)/$$i) ; \
+ if [ -f $$df -a -f $$sf ] ; then \
+ if ! cmp -s $$sf $$df ; then \
+ echo $$sf and $$df differ ; \
+ if [ "$(VERBOSEDIFF)" ] ; then \
+ cmp -bl $$sf $$df ; \
+ hexdump -Cv $$sf > $$sf.dump ; \
+ hexdump -Cv $$df > $$(basename $$df).dump.diff ; \
+ fi \
+ fi \
+ fi \
+ done
+
diff --git a/Tools/unix/Makefile b/Tools/unix/Makefile
new file mode 100644
index 00000000..8df479a5
--- /dev/null
+++ b/Tools/unix/Makefile
@@ -0,0 +1,27 @@
+#
+# build the tools for linux and Darwin
+#
+UNAME := $(shell uname)
+ifeq ($(UNAME), Linux)
+ SUFFIX=linux
+endif
+ifeq ($(UNAME), Darwin)
+ SUFFIX=osx
+endif
+
+SUBDIRS= bst uz80as zx cpmtools
+
+all:
+ @for i in $(SUBDIRS) ; do \
+ (cd $$i ; make all ) \
+ done
+
+clobber:
+ @for i in $(SUBDIRS) ; do \
+ (cd $$i ; make clobber ) \
+ done
+
+clean:
+ @for i in $(SUBDIRS) ; do \
+ (cd $$i ; make clean ) \
+ done
diff --git a/Tools/unix/bst/Makefile b/Tools/unix/bst/Makefile
new file mode 100644
index 00000000..6a6978c4
--- /dev/null
+++ b/Tools/unix/bst/Makefile
@@ -0,0 +1,27 @@
+#
+# build the propeller tools for linux and Darwin
+#
+UNAME := $(shell uname)
+ifeq ($(UNAME), Linux)
+ SUFFIX=linux
+endif
+ifeq ($(UNAME), Darwin)
+ SUFFIX=osx
+endif
+
+DEST = ../../$(UNAME)
+
+all: $(DEST)
+ -for i in *.$(SUFFIX) ; do \
+ cp $$i $(DEST)/$$(basename $$i .$(SUFFIX)) ; \
+ done
+
+$(DEST):
+ mkdir $(DEST)
+
+clobber:
+ -for i in *.$(SUFFIX) ; do \
+ rm $(DEST)/$$(basename $$i .$(SUFFIX)) ; \
+ done
+
+clean:
diff --git a/Tools/unix/bst/bstc.linux b/Tools/unix/bst/bstc.linux
new file mode 100755
index 00000000..200c412e
Binary files /dev/null and b/Tools/unix/bst/bstc.linux differ
diff --git a/Tools/unix/bst/bstc.osx b/Tools/unix/bst/bstc.osx
new file mode 100755
index 00000000..d69cbe56
Binary files /dev/null and b/Tools/unix/bst/bstc.osx differ
diff --git a/Tools/unix/bst/bstl.linux b/Tools/unix/bst/bstl.linux
new file mode 100755
index 00000000..03ee91cc
Binary files /dev/null and b/Tools/unix/bst/bstl.linux differ
diff --git a/Tools/unix/bst/bstl.osx b/Tools/unix/bst/bstl.osx
new file mode 100755
index 00000000..18179de2
Binary files /dev/null and b/Tools/unix/bst/bstl.osx differ
diff --git a/Tools/unix/bst/openspin.linux b/Tools/unix/bst/openspin.linux
new file mode 100755
index 00000000..8bc88e5d
Binary files /dev/null and b/Tools/unix/bst/openspin.linux differ
diff --git a/Tools/unix/bst/openspin.osx b/Tools/unix/bst/openspin.osx
new file mode 100755
index 00000000..f55a8131
Binary files /dev/null and b/Tools/unix/bst/openspin.osx differ
diff --git a/Tools/unix/casefn.sh b/Tools/unix/casefn.sh
new file mode 100755
index 00000000..2b2939dd
--- /dev/null
+++ b/Tools/unix/casefn.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+# given filename on the command line, echo the form of the file that
+# actually can be opened. this needs to do filesystem case shenanigans
+#
+# we don't handle files with embedded spaces, a horrible idea anyway
+#
+search=/tmp/cn.search.$$
+all=/tmp/cn.all.$$
+in=/tmp/cn.in.$$
+
+function cleanup {
+ rm -f $all $search $in
+}
+
+trap cleanup EXIT
+cleanup
+
+if [ $# -lt 1 ] ; then
+ exit 0
+fi
+
+#
+# normalize to lower case all input file names
+#
+
+for infn in $* ; do
+ dirn=$(dirname $infn)
+ df=
+ for dl in ${dirs[@]} ; do
+ if [ $dl == $dirn ] ; then
+ df=$dl
+ break;
+ fi
+ done
+ if [ -z $df ] ; then
+ dirs+=( $dirn )
+ fi
+ echo -n $dirn/ >> $in
+ basename $infn | tr '[A-Z]' '[a-z]' >> $in
+done
+
+sort -u $in > $search
+#echo search:
+#cat $search
+
+here=$(pwd)
+
+#
+# build join list of file names and lower case forms
+#
+rm -f $in
+for dn in ${dirs[@]} ; do
+ cd $here
+ cd $dn
+ for i in * ; do
+ echo $dn/$(echo "$i" | tr '[A-Z]' '[a-z]')",$dn/$i" >> $in
+ done
+done
+sort $in > $all
+
+join -t, -o 1.2 $all $search | sort -u > $in
+if [ $(wc -l < $in) -gt 0 ] ; then
+ cat $in
+ exit 0
+fi
+
+echo nofile
+exit 2
diff --git a/Tools/unix/cpmtools/COPYING b/Tools/unix/cpmtools/COPYING
new file mode 100644
index 00000000..44325404
--- /dev/null
+++ b/Tools/unix/cpmtools/COPYING
@@ -0,0 +1,676 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
+
diff --git a/Tools/unix/cpmtools/Makefile b/Tools/unix/cpmtools/Makefile
new file mode 100644
index 00000000..12c1f508
--- /dev/null
+++ b/Tools/unix/cpmtools/Makefile
@@ -0,0 +1,55 @@
+#
+# cpmtools makefile stripped down to remove autoconf
+#
+
+UNAME := $(shell uname)
+DEST = ../../$(UNAME)
+
+CC = gcc
+CFLAGS = -g
+
+DEFFORMAT = ibm-3740
+DEVICE = posix
+CPPFLAGS = -DDISKDEFS=\"$(DISKDEFS)\" -DFORMAT=\"$(DEFFORMAT)\"
+
+DEVICEOBJ = device_posix.o
+
+OBJECTS = cpmls cpmrm cpmcp cpmchmod cpmchattr mkfs.cpm fsck.cpm
+
+all: $(OBJECTS) $(DEST)
+ cp $(OBJECTS) $(DEST)
+
+cpmls: cpmls.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ cpmls.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+cpmrm: cpmrm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ cpmrm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+cpmcp: cpmcp.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ cpmcp.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+cpmchmod: cpmchmod.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ cpmchmod.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+cpmchattr: cpmchattr.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ cpmchattr.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+mkfs.cpm: mkfs.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ mkfs.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+fsck.cpm: fsck.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ fsck.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+fsed.cpm: fsed.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+ $(CC) $(LDFLAGS) -o $@ fsed.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ)
+
+$(DEST):
+ mkdir -p $(DEST)
+
+clean:
+ -rm -f *.o $(OBJECTS)
+
+clobber: clean
+ -for i in $(OBJECTS) ; do \
+ rm -f $(DEST)/$$i ; \
+ done
diff --git a/Tools/unix/cpmtools/badfs/Makefile b/Tools/unix/cpmtools/badfs/Makefile
new file mode 100644
index 00000000..75c4ee00
--- /dev/null
+++ b/Tools/unix/cpmtools/badfs/Makefile
@@ -0,0 +1,7 @@
+#
+# Dummy makefile
+#
+all:
+clean:
+distclean:
+
diff --git a/Tools/unix/cpmtools/badfs/blocknumber b/Tools/unix/cpmtools/badfs/blocknumber
new file mode 100644
index 00000000..9259cacb
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/blocknumber differ
diff --git a/Tools/unix/cpmtools/badfs/doubleext b/Tools/unix/cpmtools/badfs/doubleext
new file mode 100644
index 00000000..8ee7d65b
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/doubleext differ
diff --git a/Tools/unix/cpmtools/badfs/extension b/Tools/unix/cpmtools/badfs/extension
new file mode 100644
index 00000000..cc02845f
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/extension differ
diff --git a/Tools/unix/cpmtools/badfs/extno b/Tools/unix/cpmtools/badfs/extno
new file mode 100644
index 00000000..5948b0ee
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/extno differ
diff --git a/Tools/unix/cpmtools/badfs/hugecom b/Tools/unix/cpmtools/badfs/hugecom
new file mode 100644
index 00000000..3d8fea49
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/hugecom differ
diff --git a/Tools/unix/cpmtools/badfs/label b/Tools/unix/cpmtools/badfs/label
new file mode 100644
index 00000000..982928d1
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/label differ
diff --git a/Tools/unix/cpmtools/badfs/lcr b/Tools/unix/cpmtools/badfs/lcr
new file mode 100644
index 00000000..da20f4b5
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/lcr differ
diff --git a/Tools/unix/cpmtools/badfs/multipleblocks b/Tools/unix/cpmtools/badfs/multipleblocks
new file mode 100644
index 00000000..52c1d922
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/multipleblocks differ
diff --git a/Tools/unix/cpmtools/badfs/name b/Tools/unix/cpmtools/badfs/name
new file mode 100644
index 00000000..67265aca
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/name differ
diff --git a/Tools/unix/cpmtools/badfs/recordcount b/Tools/unix/cpmtools/badfs/recordcount
new file mode 100644
index 00000000..e8633a56
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/recordcount differ
diff --git a/Tools/unix/cpmtools/badfs/status b/Tools/unix/cpmtools/badfs/status
new file mode 100644
index 00000000..33da2481
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/status differ
diff --git a/Tools/unix/cpmtools/badfs/timestamps b/Tools/unix/cpmtools/badfs/timestamps
new file mode 100644
index 00000000..a434cc96
Binary files /dev/null and b/Tools/unix/cpmtools/badfs/timestamps differ
diff --git a/Tools/unix/cpmtools/config.h b/Tools/unix/cpmtools/config.h
new file mode 100644
index 00000000..2b5cd544
--- /dev/null
+++ b/Tools/unix/cpmtools/config.h
@@ -0,0 +1,57 @@
+/* config.h. Generated from config.h.in by configure. */
+#define HAVE_FCNTL_H 1
+#define HAVE_LIMITS_H 1
+#define HAVE_UNISTD_H 1
+#define HAVE_WINDOWS_H 0
+#define HAVE_WINIOCTL_H 0
+#define HAVE_LIBDSK_H 0
+#define HAVE_SYS_TYPES_H 1
+#define HAVE_SYS_STAT_H 1
+#define HAVE_MODE_T 0
+#define NEED_NCURSES 0
+#define HAVE_NCURSES_NCURSES_H 0
+
+#if HAVE_SYS_STAT_H
+#include
+#endif
+
+#if HAVE_SYS_TYPES_H
+#include
+#endif
+
+#if HAVE_LIMITS_H
+#include
+#endif
+
+#if HAVE_UNISTD_H
+#include
+#endif
+
+#if HAVE_WINDOWS_H
+#include
+#endif
+
+#if HAVE_WINIOCTL_H
+#include
+#endif
+
+#if HAVE_LIBDSK_H
+#include
+#endif
+
+#if HAVE_FCNTL_H
+#include
+#endif
+
+#ifndef _POSIX_PATH_MAX
+#define _POSIX_PATH_MAX _MAX_PATH
+#endif
+
+#include
+
+/* Define either for large file support, if your OS needs them. */
+/* #undef _FILE_OFFSET_BITS */
+/* #undef _LARGE_FILES */
+
+/* Define if using dmalloc */
+/* #undef USE_DMALLOC */
diff --git a/Tools/unix/cpmtools/cpm.5 b/Tools/unix/cpmtools/cpm.5
new file mode 100644
index 00000000..9f11ff98
--- /dev/null
+++ b/Tools/unix/cpmtools/cpm.5
@@ -0,0 +1,300 @@
+.\" Believe it or not, reportedly there are nroffs which do not know \(en
+.if n .ds en -
+.if t .ds en \(en
+.TH CPM 5 "October 25, 2014" "CP/M tools" "File formats"
+.SH NAME \"{{{roff}}}\"{{{
+cpm \- CP/M disk and file system format
+.\"}}}
+.SH DESCRIPTION \"{{{
+.SS "Characteristic sizes" \"{{{
+Each CP/M disk format is described by the following specific sizes:
+.RS
+.sp
+Sector size in bytes
+.br
+Number of tracks
+.br
+Number of sectors
+.br
+Block size
+.br
+Number of directory entries
+.br
+Logical sector skew
+.br
+Number of reserved system tracks (optional)
+.br
+Offset to start of volume (optional and not covered by operating system,
+but disk driver specific)
+.sp
+.RE
+A block is the smallest allocatable storage unit. CP/M supports block
+sizes of 1024, 2048, 4096, 8192 and 16384 bytes. Unfortunately, this
+format specification is not stored on the disk and there are lots of
+formats. Accessing a block is performed by accessing its sectors, which
+are stored with the given software skew.
+.\"}}}
+.SS "Device areas" \"{{{
+A CP/M disk contains four areas:
+.RS
+.sp
+Volume offset (optional and not covered by operating system, but disk driver specific)
+.br
+System tracks (optional)
+.br
+Directory
+.br
+Data
+.sp
+.RE
+The system tracks store the boot loader and CP/M itself. In order to save
+disk space, there are non-bootable formats which omit those system tracks.
+The term \fIdisk capacity\fP always excludes the space for system tracks.
+Note that there is no bitmap or list for free blocks. When accessing a
+drive for the first time, CP/M builds this bitmap in core from the directory.
+.LP
+A hard disk can have the additional notion of a \fIvolume offset\fP to
+locate the start of the drive image (which may or may not have system
+tracks associated with it). The base unit for volume offset is byte
+count from the beginning of the physical disk, but specifiers of
+\fIK\fP, \fIM\fP, \fIT\fP or \fIS\fP may be appended to denote
+kilobytes, megabytes, tracks or sectors. If provided, a specifier
+must immediately follow the numeric value with no whitespace. For
+convenience upper and lower case are both accepted and only the first
+letter is significant, thus 2KB, 8MB, 1000trk and 16sec are valid
+values. The \fBoffset\fP must appear subsequent to track, sector and sector
+length values for the sector and track units to work.
+.\"}}}
+.SS "Directory entries" \"{{{
+The directory is a sequence of directory entries (also called extents),
+which contain 32 bytes of the following structure:
+.RS
+.sp
+.ta 3n 6n 9n 12n 15n 18n 21n 24n 27n 30n 33n 36n 39n 42n 45n
+St F0 F1 F2 F3 F4 F5 F6 F7 E0 E1 E2 Xl Bc Xh Rc
+.br
+Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al
+.sp
+.RE
+.\"{{{ St = status
+\fBSt\fP is the status; possible values are:
+.RS
+.sp
+0\*(en15: used for file, status is the user number
+.br
+16\*(en31: used for file, status is the user number (P2DOS)
+or used for password extent (CP/M 3 or higher)
+.br
+32: disc label
+.br
+33: time stamp (P2DOS)
+.br
+0xE5: unused
+.sp
+.RE
+.\"}}}
+.LP
+.\"{{{ F0-E2 = file name and extension
+\fBF0\*(enE2\fP are the file name and its extension. They may consist of
+any printable 7 bit ASCII character but: \fB< > . , ; : = ? * [ ]\fP.
+The file name must not be empty, the extension may be empty. Both are
+padded with blanks. The highest bit of each character of the file name
+and extension is used as attribute. The attributes have the following
+meaning:
+.RS
+.sp
+F0: requires set wheel byte (Backgrounder II)
+.br
+F1: public file (P2DOS, ZSDOS), forground-only command (Backgrounder II)
+.br
+F2: date stamp (ZSDOS), background-only commands (Backgrounder II)
+.br
+F7: wheel protect (ZSDOS)
+.br
+E0: read-only
+.br
+E1: system file
+.br
+E2: archived
+.sp
+.RE
+Public files (visible under each user number) are not supported by CP/M
+2.2, but there is a patch and some free CP/M clones support them without
+any patches.
+.LP
+The wheel byte is (by default) the memory location at 0x4b. If it is
+zero, only non-privileged commands may be executed.
+.\"}}}
+.LP
+.\"{{{ Xl, Xh = extent number
+\fBXl\fP and \fBXh\fP store the extent number. A file may use more than
+one directory entry, if it contains more blocks than an extent can hold.
+In this case, more extents are allocated and each of them is numbered
+sequentially with an extent number. If a physical extent stores more than
+16k, it is considered to contain multiple logical extents, each pointing
+to 16k data, and the extent number of the last used logical extent
+is stored. Note: Some formats decided to always store only one logical
+extent in a physical extent, thus wasting extent space. CP/M 2.2 allows
+512 extents per file, CP/M 3 and higher allow up to 2048. Bit 5\*(en7 of
+Xl are 0, bit 0\*(en4 store the lower bits of the extent number. Bit 6
+and 7 of Xh are 0, bit 0\*(en5 store the higher bits of the extent number.
+.\"}}}
+.LP
+.\"{{{ Rc, Bc = record count, byte count
+\fBRc\fP and \fBBc\fP determine the length of the data used by this extent. The
+physical extent is divided into logical extents, each of them being 16k
+in size (a physical extent must hold at least one logical extent, e.g. a
+blocksize of 1024 byte with two-byte block pointers is not allowed).
+Rc stores the number of 128 byte records of the last used logical extent.
+Bc stores the number of bytes in the last used record. The value 0 means
+128 for backward compatibility with CP/M 2.2, which did not support Bc.
+ISX records the number of unused instead of used bytes in Bc.
+.\"}}}
+.LP
+.\"{{{ Al = allocated blocks
+\fBAl\fP stores block pointers. If the disk capacity minus boot
+tracks but including the directory area is less than 256 blocks, Al
+is interpreted as 16 byte-values, otherwise as 8 double-byte-values.
+Since the directory area is not subtracted, the directory area starts
+with block 0 and files can never allocate block 0, which is why this
+value can be given a new meaning: A block pointer of 0 marks a hole in
+the file. If a hole covers the range of a full extent, the extent will
+not be allocated. In particular, the first extent of a file does not
+neccessarily have extent number 0. A file may not share blocks with other
+files, as its blocks would be freed if the other files is erased without
+a following disk system reset. CP/M returns EOF when it reaches a hole,
+whereas UNIX returns zero-value bytes, which makes holes invisible.
+.\"}}}
+.\"}}}
+.SS "Native time stamps" \"{{{
+P2DOS and CP/M Plus support time stamps, which are stored in each fourth
+directory entry. This entry contains the time stamps for
+the extents using the previous three directory entries. Note that you
+really have time stamps for each extent, no matter if it is the first
+extent of a file or not. The structure of time stamp entries is:
+.RS
+.sp
+1 byte status 0x21
+.br
+8 bytes time stamp for third-last directory entry
+.br
+2 bytes unused
+.br
+8 bytes time stamp for second-last directory entry
+.br
+2 bytes unused
+.br
+8 bytes time stamp for last directory entry
+.sp
+.RE
+A time stamp consists of two dates: Creation and modification date (the
+latter being recorded when the file is closed). CP/M Plus further
+allows optionally to record the access instead of creation date as first
+time stamp.
+.RS
+.sp
+2 bytes (little-endian) days starting with 1 at 01-01-1978
+.br
+1 byte hour in BCD format
+.br
+1 byte minute in BCD format
+.sp
+.RE
+All time stamps are stored in local time.
+.\"}}}
+.SS "DateStamper time stamps" \"{{{
+The DateStamper software added functions to the BDOS to manage
+time stamps by allocating a read only file with the name "!!!TIME&.DAT"
+in the very first directory entry, covering the very first data
+blocks. It contains one entry per directory entry with the
+following structure of 16 bytes:
+.RS
+.sp
+5 bytes create datefield
+.br
+5 bytes access datefield
+.br
+5 bytes modify datefield
+.br
+1 byte magic number/checksum
+.sp
+.RE
+The magic number is used for the first 7 entries of each 128-byte record
+and contains the characters \fB!\fP, \fB!\fP, \fB!\fP, \fBT\fP, \fBI\fP,
+\fBM\fP and \fBE\fP. The checksum is used on every 8th entry (last entry
+in 128-byte record) and is the sum of the first 127 bytes of the record.
+Each datefield has this structure:
+.RS
+.sp
+1 byte BCD coded year (no century, so it is sane assuming any year < 70
+means 21st century)
+.br
+1 byte BCD coded month
+.br
+1 byte BCD coded day
+.br
+1 byte BCD coded hour or, if the high bit is set, the high byte of a
+counter for systems without real time clock
+.br
+1 byte BCD coded minute, or the low byte of the counter
+.sp
+.DE
+.\"}}}
+.SS "Disc labels" \"{{{
+CP/M Plus support disc labels, which are stored in an arbitrary directory
+entry.
+The structure of disc labels is:
+.RS
+.sp
+1 byte status 0x20
+.br
+\fBF0\*(enE2\fP are the disc label
+.br
+1 byte mode: bit 7 activates password protection, bit 6 causes time stamps on
+access, but 5 causes time stamps on modifications, bit 4 causes time stamps on
+creation and bit 0 is set when a label exists. Bit 4 and 6 are exclusively set.
+.br
+1 byte password decode byte: To decode the password, xor this byte with the password
+bytes in reverse order. To encode a password, add its characters to get the
+decode byte.
+.br
+2 reserved bytes
+.br
+8 password bytes
+.br
+4 bytes label creation time stamp
+.br
+4 bytes label modification time stamp
+.sp
+.RE
+.\"}}}
+.SS "Passwords" \"{{{
+CP/M Plus supports passwords, which are stored in an arbitrary directory
+entry.
+The structure of these entries is:
+.RS
+.sp
+1 byte status (user number plus 16)
+.br
+\fBF0\*(enE2\fP are the file name and its extension.
+.br
+1 byte password mode: bit 7 means password required for reading, bit 6 for writing
+and bit 5 for deleting.
+.br
+1 byte password decode byte: To decode the password, xor this byte with the password
+bytes in reverse order. To encode a password, add its characters to get the
+decode byte.
+.br
+2 reserved bytes
+.br
+8 password bytes
+.sp
+.RE
+.\"}}}
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR mkfs.cpm (1),
+.IR fsck.cpm (1),
+.IR fsed.cpm (1),
+.IR cpmls (1)
+.\"}}}
diff --git a/Tools/unix/cpmtools/cpm.ps b/Tools/unix/cpmtools/cpm.ps
new file mode 100644
index 00000000..6c631338
--- /dev/null
+++ b/Tools/unix/cpmtools/cpm.ps
@@ -0,0 +1,478 @@
+%!PS-Adobe-3.0
+%%Creator: groff version 1.19
+%%CreationDate: Sun Feb 3 19:48:55 2013
+%%DocumentNeededResources: font Times-Roman
+%%+ font Times-Bold
+%%+ font Times-Italic
+%%DocumentSuppliedResources: procset grops 1.19 0
+%%Pages: 4
+%%PageOrder: Ascend
+%%DocumentMedia: Default 595 842 0 () ()
+%%Orientation: Portrait
+%%EndComments
+%%BeginDefaults
+%%PageMedia: Default
+%%EndDefaults
+%%BeginProlog
+%%BeginResource: procset grops 1.19 0
+/setpacking where{
+pop
+currentpacking
+true setpacking
+}if
+/grops 120 dict dup begin
+/SC 32 def
+/A/show load def
+/B{0 SC 3 -1 roll widthshow}bind def
+/C{0 exch ashow}bind def
+/D{0 exch 0 SC 5 2 roll awidthshow}bind def
+/E{0 rmoveto show}bind def
+/F{0 rmoveto 0 SC 3 -1 roll widthshow}bind def
+/G{0 rmoveto 0 exch ashow}bind def
+/H{0 rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def
+/I{0 exch rmoveto show}bind def
+/J{0 exch rmoveto 0 SC 3 -1 roll widthshow}bind def
+/K{0 exch rmoveto 0 exch ashow}bind def
+/L{0 exch rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def
+/M{rmoveto show}bind def
+/N{rmoveto 0 SC 3 -1 roll widthshow}bind def
+/O{rmoveto 0 exch ashow}bind def
+/P{rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def
+/Q{moveto show}bind def
+/R{moveto 0 SC 3 -1 roll widthshow}bind def
+/S{moveto 0 exch ashow}bind def
+/T{moveto 0 exch 0 SC 5 2 roll awidthshow}bind def
+/SF{
+findfont exch
+[exch dup 0 exch 0 exch neg 0 0]makefont
+dup setfont
+[exch/setfont cvx]cvx bind def
+}bind def
+/MF{
+findfont
+[5 2 roll
+0 3 1 roll
+neg 0 0]makefont
+dup setfont
+[exch/setfont cvx]cvx bind def
+}bind def
+/level0 0 def
+/RES 0 def
+/PL 0 def
+/LS 0 def
+/MANUAL{
+statusdict begin/manualfeed true store end
+}bind def
+/PLG{
+gsave newpath clippath pathbbox grestore
+exch pop add exch pop
+}bind def
+/BP{
+/level0 save def
+1 setlinecap
+1 setlinejoin
+72 RES div dup scale
+LS{
+90 rotate
+}{
+0 PL translate
+}ifelse
+1 -1 scale
+}bind def
+/EP{
+level0 restore
+showpage
+}bind def
+/DA{
+newpath arcn stroke
+}bind def
+/SN{
+transform
+.25 sub exch .25 sub exch
+round .25 add exch round .25 add exch
+itransform
+}bind def
+/DL{
+SN
+moveto
+SN
+lineto stroke
+}bind def
+/DC{
+newpath 0 360 arc closepath
+}bind def
+/TM matrix def
+/DE{
+TM currentmatrix pop
+translate scale newpath 0 0 .5 0 360 arc closepath
+TM setmatrix
+}bind def
+/RC/rcurveto load def
+/RL/rlineto load def
+/ST/stroke load def
+/MT/moveto load def
+/CL/closepath load def
+/Fr{
+setrgbcolor fill
+}bind def
+/setcmykcolor where{
+pop
+/Fk{
+setcmykcolor fill
+}bind def
+}if
+/Fg{
+setgray fill
+}bind def
+/FL/fill load def
+/LW/setlinewidth load def
+/Cr/setrgbcolor load def
+/setcmykcolor where{
+pop
+/Ck/setcmykcolor load def
+}if
+/Cg/setgray load def
+/RE{
+findfont
+dup maxlength 1 index/FontName known not{1 add}if dict begin
+{
+1 index/FID ne{def}{pop pop}ifelse
+}forall
+/Encoding exch def
+dup/FontName exch def
+currentdict end definefont pop
+}bind def
+/DEFS 0 def
+/EBEGIN{
+moveto
+DEFS begin
+}bind def
+/EEND/end load def
+/CNT 0 def
+/level1 0 def
+/PBEGIN{
+/level1 save def
+translate
+div 3 1 roll div exch scale
+neg exch neg exch translate
+0 setgray
+0 setlinecap
+1 setlinewidth
+0 setlinejoin
+10 setmiterlimit
+[]0 setdash
+/setstrokeadjust where{
+pop
+false setstrokeadjust
+}if
+/setoverprint where{
+pop
+false setoverprint
+}if
+newpath
+/CNT countdictstack def
+userdict begin
+/showpage{}def
+/setpagedevice{}def
+}bind def
+/PEND{
+clear
+countdictstack CNT sub{end}repeat
+level1 restore
+}bind def
+end def
+/setpacking where{
+pop
+setpacking
+}if
+%%EndResource
+%%BeginFeature: *PageSize Default
+<< /PageSize [ 595 842 ] /ImagingBBox null >> setpagedevice
+%%EndFeature
+%%IncludeResource: font Times-Roman
+%%IncludeResource: font Times-Bold
+%%IncludeResource: font Times-Italic
+grops begin/DEFS 1 dict def DEFS begin/u{.001 mul}bind def end/RES 72
+def/PL 841.89 def/LS false def/ENC0[/asciicircum/asciitilde/Scaron
+/Zcaron/scaron/zcaron/Ydieresis/trademark/quotesingle/Euro/.notdef
+/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
+/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
+/.notdef/.notdef/.notdef/space/exclam/quotedbl/numbersign/dollar/percent
+/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen
+/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon
+/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O
+/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/circumflex
+/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y
+/z/braceleft/bar/braceright/tilde/.notdef/quotesinglbase/guillemotleft
+/guillemotright/bullet/florin/fraction/perthousand/dagger/daggerdbl
+/endash/emdash/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/hungarumlaut
+/dotaccent/breve/caron/ring/ogonek/quotedblleft/quotedblright/oe/lslash
+/quotedblbase/OE/Lslash/.notdef/exclamdown/cent/sterling/currency/yen
+/brokenbar/section/dieresis/copyright/ordfeminine/guilsinglleft
+/logicalnot/minus/registered/macron/degree/plusminus/twosuperior
+/threesuperior/acute/mu/paragraph/periodcentered/cedilla/onesuperior
+/ordmasculine/guilsinglright/onequarter/onehalf/threequarters
+/questiondown/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE
+/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex
+/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis
+/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn
+/germandbls/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla
+/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis
+/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash
+/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def
+/Times-Italic@0 ENC0/Times-Italic RE/Times-Bold@0 ENC0/Times-Bold RE
+/Times-Roman@0 ENC0/Times-Roman RE
+%%EndProlog
+%%Page: 1 1
+%%BeginPageSetup
+BP
+%%EndPageSetup
+/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415
+(formats CPM\(5\))2.5 F/F1 10.95/Times-Bold@0 SF -.219(NA)72 84 S(ME)
+.219 E F0(cpm \255 CP/M disk and \214le system format)108 96 Q F1
+(DESCRIPTION)72 112.8 Q/F2 10/Times-Bold@0 SF(Characteristic sizes)87
+124.8 Q F0(Each CP/M disk format is described by the follo)108 136.8 Q
+(wing speci\214c sizes:)-.25 E(Sector size in bytes)144 160.8 Q
+(Number of tracks)144 172.8 Q(Number of sectors)144 184.8 Q(Block size)
+144 196.8 Q(Number of directory entries)144 208.8 Q(Logical sector sk)
+144 220.8 Q -.25(ew)-.1 G(Number of reserv)144 232.8 Q
+(ed system tracks \(optional\))-.15 E(Of)144 244.8 Q(fset to start of v)
+-.25 E(olume \(optional\))-.2 E 2.848(Ab)108 268.8 S .348
+(lock is the smallest allocatable storage unit.)-2.848 F .347
+(CP/M supports block sizes of 1024, 2048, 4096, 8192 and)5.348 F .207
+(16384 bytes.)108 280.8 R(Unfortunately)5.207 E 2.707(,t)-.65 G .208(hi\
+s format speci\214cation is not stored on the disk and there are lots o\
+f formats.)-2.707 F(Accessing a block is performed by accessing its sec\
+tors, which are stored with the gi)108 292.8 Q -.15(ve)-.25 G 2.5(ns).15
+G(oftw)-2.5 E(are sk)-.1 E -.25(ew)-.1 G(.)-.4 E F2(De)87 309.6 Q
+(vice ar)-.15 E(eas)-.18 E F0 2.5(AC)108 321.6 S
+(P/M disk contains three areas:)-2.5 E -1.29(Vo)144 345.6 S(lume of)1.29
+E(fset \(optional\))-.25 E(System tracks \(optional\))144 357.6 Q
+(Directory)144 369.6 Q(Data)144 381.6 Q .058
+(The system tracks store the boot loader and CP/M itself.)108 405.6 R
+.058(In order to sa)5.058 F .358 -.15(ve d)-.2 H .057
+(isk space, there are non-bootable).15 F 1.55
+(formats which omit those system tracks.)108 417.6 R 1.55(The term)6.55
+F/F3 10/Times-Italic@0 SF 1.55(disk capacity)4.05 F F0(al)4.05 E -.1(wa)
+-.1 G 1.55(ys e).1 F 1.55(xcludes the space for system)-.15 F 2.748
+(tracks. Note)108 429.6 R .248
+(that there is no bitmap or list for free blocks.)2.748 F .248
+(When accessing a dri)5.248 F .548 -.15(ve f)-.25 H .248
+(or the \214rst time, CP/M).15 F -.2(bu)108 441.6 S
+(ilds this bitmap in core from the directory).2 E(.)-.65 E 3.15(Ah)108
+458.4 S .65(ard disk can ha)-3.15 F .95 -.15(ve t)-.2 H .65
+(he additional notion of a).15 F F3 .65(volume of)3.15 F(fset)-.18 E F0
+.65(to locate the start of the dri)3.15 F .95 -.15(ve i)-.25 H .65
+(mage \(which).15 F .531(may or may not ha)108 470.4 R .831 -.15(ve s)
+-.2 H .531(ystem tracks associated with it\). The base unit for v).15 F
+.53(olume of)-.2 F .53(fset is byte count from)-.25 F 1.224(the be)108
+482.4 R 1.224(ginning of the ph)-.15 F 1.224(ysical disk, b)-.05 F 1.225
+(ut speci\214ers of)-.2 F F3(K)3.725 E F0(,)A F3(M)3.725 E F0(,)A F3(T)
+3.725 E F0(or)3.725 E F3(S)3.725 E F0 1.225
+(may be appended to denote kilobytes,)3.725 F(me)108 494.4 Q -.05(ga)
+-.15 G .806(bytes, tracks or sectors.).05 F .806(If pro)5.806 F .805
+(vided, a speci\214er must immediately follo)-.15 F 3.305(wt)-.25 G .805
+(he numeric v)-3.305 F .805(alue with no)-.25 F 2.881(whitespace. F)108
+506.4 R .381(or con)-.15 F -.15(ve)-.4 G .381(nience upper and lo).15 F
+.381(wer case are both accepted and only the \214rst letter is signi\
+\214cant,)-.25 F .02(thus 2KB, 8MB, 1000trk and 16sec are v)108 518.4 R
+.019(alid v)-.25 F .019(alues. Of)-.25 F .019
+(fset must appear subsequent to track, sector and sec-)-.25 F
+(tor length v)108 530.4 Q(alues.)-.25 E F2(Dir)87 547.2 Q
+(ectory entries)-.18 E F0 .408
+(The directory is a sequence of directory entries \(also called e)108
+559.2 R .409(xtents\), which contain 32 bytes of the follo)-.15 F(w-)
+-.25 E(ing structure:)108 571.2 Q 4.16(St F0)144 595.2 R 1.94
+(F1 F2 F3 F4 F5 F6 F7 E0)4.44 F 1.39(E1 E2 Xl)3.89 F 1.39(Bc Xh)5 F(Rc)
+2.78 E 2.5(Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al)144 607.2 R
+F2(St)108 631.2 Q F0(is the status; possible v)2.5 E(alues are:)-.25 E
+(0\21115: used for \214le, status is the user number)144 655.2 Q .795(1\
+6\21131: used for \214le, status is the user number \(P2DOS\) or used f\
+or passw)144 667.2 R .794(ord e)-.1 F .794(xtent \(CP/M 3 or)-.15 F
+(higher\))144 679.2 Q(32: disc label)144 691.2 Q
+(33: time stamp \(P2DOS\))144 703.2 Q(0xE5: unused)144 715.2 Q
+(CP/M tools)72 768 Q(February 18, 2012)151.35 E(1)192.2 E 0 Cg EP
+%%Page: 2 2
+%%BeginPageSetup
+BP
+%%EndPageSetup
+/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415
+(formats CPM\(5\))2.5 F/F1 10/Times-Bold@0 SF(F0\211E2)108 84 Q F0 .412
+(are the \214le name and its e)2.912 F 2.913(xtension. The)-.15 F 2.913
+(ym)-.15 G .413(ay consist of an)-2.913 F 2.913(yp)-.15 G .413
+(rintable 7 bit ASCII character b)-2.913 F(ut:)-.2 E F1(<)2.913 E 3.362
+(>.,;:=?*[])108 96 S F0 5.862(.T)-3.362 G .862
+(he \214le name must not be empty)-5.862 F 3.361(,t)-.65 G .861(he e)
+-3.361 F .861(xtension may be empty)-.15 F 5.861(.B)-.65 G .861
+(oth are padded with)-5.861 F 2.831(blanks. The)108 108 R .331
+(highest bit of each character of the \214le name and e)2.831 F .331
+(xtension is used as attrib)-.15 F 2.832(ute. The)-.2 F(attrib)2.832 E
+(utes)-.2 E(ha)108 120 Q .3 -.15(ve t)-.2 H(he follo).15 E
+(wing meaning:)-.25 E(F0: requires set wheel byte \(Backgrounder II\))
+144 144 Q(F1: public \214le \(P2DOS, ZSDOS\), for)144 156 Q
+(ground-only command \(Backgrounder II\))-.18 E
+(F2: date stamp \(ZSDOS\), background-only commands \(Backgrounder II\))
+144 168 Q(F7: wheel protect \(ZSDOS\))144 180 Q(E0: read-only)144 192 Q
+(E1: system \214le)144 204 Q(E2: archi)144 216 Q -.15(ve)-.25 G(d).15 E
+.338(Public \214les \(visible under each user number\) are not supporte\
+d by CP/M 2.2, b)108 240 R .338(ut there is a patch and some)-.2 F
+(free CP/M clones support them without an)108 252 Q 2.5(yp)-.15 G
+(atches.)-2.5 E .827(The wheel byte is \(by def)108 268.8 R .828
+(ault\) the memory location at 0x4b)-.1 F 5.828(.I)-.4 G 3.328(fi)-5.828
+G 3.328(ti)-3.328 G 3.328(sz)-3.328 G .828(ero, only non-pri)-3.328 F
+(vile)-.25 E .828(ged commands)-.15 F(may be e)108 280.8 Q -.15(xe)-.15
+G(cuted.).15 E F1(Xl)108 297.6 Q F0(and)2.546 E F1(Xh)2.546 E F0 .046
+(store the e)2.546 F .046(xtent number)-.15 F 5.046(.A)-.55 G .045
+(\214le may use more than one directory entry)-2.5 F 2.545(,i)-.65 G
+2.545(fi)-2.545 G 2.545(tc)-2.545 G .045(ontains more blocks)-2.545 F
+.21(than an e)108 309.6 R .21(xtent can hold.)-.15 F .21
+(In this case, more e)5.21 F .21
+(xtents are allocated and each of them is numbered sequentially)-.15 F
+.457(with an e)108 321.6 R .457(xtent number)-.15 F 5.457(.I)-.55 G
+2.957(fap)-5.457 G -.05(hy)-2.957 G .457(sical e).05 F .456
+(xtent stores more than 16k, it is considered to contain multiple logi-)
+-.15 F .234(cal e)108 333.6 R .234
+(xtents, each pointing to 16k data, and the e)-.15 F .234
+(xtent number of the last used logical e)-.15 F .235(xtent is stored.)
+-.15 F(Note:)5.235 E 1.55(Some formats decided to al)108 345.6 R -.1(wa)
+-.1 G 1.549(ys store only one logical e).1 F 1.549(xtent in a ph)-.15 F
+1.549(ysical e)-.05 F 1.549(xtent, thus w)-.15 F 1.549(asting e)-.1 F
+(xtent)-.15 E 2.81(space. CP/M)108 357.6 R .31(2.2 allo)2.81 F .31
+(ws 512 e)-.25 F .31(xtents per \214le, CP/M 3 and higher allo)-.15 F
+2.811(wu)-.25 G 2.811(pt)-2.811 G 2.811(o2)-2.811 G 2.811(048. Bit)
+-2.811 F .311(5\2117 of Xl are 0, bit)2.811 F .577(0\2114 store the lo)
+108 369.6 R .577(wer bits of the e)-.25 F .576(xtent number)-.15 F 5.576
+(.B)-.55 G .576
+(it 6 and 7 of Xh are 0, bit 0\2115 store the higher bits of the)-5.576
+F -.15(ex)108 381.6 S(tent number).15 E(.)-.55 E F1(Rc)108 398.4 Q F0
+(and)2.946 E F1(Bc)2.946 E F0 .446
+(determine the length of the data used by this e)2.946 F 2.946
+(xtent. The)-.15 F(ph)2.947 E .447(ysical e)-.05 F .447(xtent is di)-.15
+F .447(vided into logical)-.25 F -.15(ex)108 410.4 S .156
+(tents, each of them being 16k in size \(a ph).15 F .156(ysical e)-.05 F
+.156(xtent must hold at least one logical e)-.15 F .156
+(xtent, e.g. a block-)-.15 F .053(size of 1024 byte with tw)108 422.4 R
+.054(o-byte block pointers is not allo)-.1 F 2.554(wed\). Rc)-.25 F .054
+(stores the number of 128 byte records of)2.554 F .457
+(the last used logical e)108 434.4 R 2.957(xtent. Bc)-.15 F .456
+(stores the number of bytes in the last used record.)2.957 F .456(The v)
+5.456 F .456(alue 0 means 128)-.25 F .654(for backw)108 446.4 R .654
+(ard compatibility with CP/M 2.2, which did not support Bc.)-.1 F .655
+(ISX records the number of unused)5.655 F(instead of used bytes in Bc.)
+108 458.4 Q F1(Al)108 475.2 Q F0 .9(stores block pointers.)3.4 F .899(I\
+f the disk capacity is less than 256 blocks, Al is interpreted as 16 by\
+te-v)5.9 F(alues,)-.25 E .243(otherwise as 8 double-byte-v)108 487.2 R
+2.743(alues. A)-.25 F .243
+(block pointer of 0 marks a hole in the \214le.)2.743 F .243
+(If a hole co)5.243 F -.15(ve)-.15 G .243(rs the range).15 F .341
+(of a full e)108 499.2 R .341(xtent, the e)-.15 F .341
+(xtent will not be allocated.)-.15 F .34(In particular)5.341 F 2.84(,t)
+-.4 G .34(he \214rst e)-2.84 F .34
+(xtent of a \214le does not neccessarily)-.15 F(ha)108 511.2 Q .479 -.15
+(ve ex)-.2 H .179(tent number 0.).15 F 2.679<418c>5.179 G .18
+(le may not share blocks with other \214les, as its blocks w)-2.679 F
+.18(ould be freed if the other)-.1 F .822
+(\214les is erased without a follo)108 523.2 R .822
+(wing disk system reset.)-.25 F .822
+(CP/M returns EOF when it reaches a hole, whereas)5.822 F
+(UNIX returns zero-v)108 535.2 Q(alue bytes, which mak)-.25 E
+(es holes in)-.1 E(visible.)-.4 E F1(Nati)87 552 Q .2 -.1(ve t)-.1 H
+(ime stamps).1 E F0 1.053(P2DOS and CP/M Plus support time stamps, whic\
+h are stored in each fourth directory entry)108 564 R 6.054(.T)-.65 G
+1.054(his entry)-6.054 F 1.3(contains the time stamps for the e)108 576
+R 1.299(xtents using the pre)-.15 F 1.299
+(vious three directory entries.)-.25 F 1.299(Note that you really)6.299
+F(ha)108 588 Q 1.294 -.15(ve t)-.2 H .994(ime stamps for each e).15 F
+.994(xtent, no matter if it is the \214rst e)-.15 F .994
+(xtent of a \214le or not.)-.15 F .995(The structure of time)5.994 F
+(stamp entries is:)108 600 Q 2.5(1b)144 624 S(yte status 0x21)-2.5 E 2.5
+(8b)144 636 S(ytes time stamp for third-last directory entry)-2.5 E 2.5
+(2b)144 648 S(ytes unused)-2.5 E 2.5(8b)144 660 S
+(ytes time stamp for second-last directory entry)-2.5 E 2.5(2b)144 672 S
+(ytes unused)-2.5 E 2.5(8b)144 684 S
+(ytes time stamp for last directory entry)-2.5 E 2.872(At)108 708 S .372
+(ime stamp consists of tw)-2.872 F 2.872(od)-.1 G .372(ates: Creation a\
+nd modi\214cation date \(the latter being recorded when the \214le)
+-2.872 F .935(is closed\).)108 720 R .936(CP/M Plus further allo)5.935 F
+.936(ws optionally to record the access instead of creation date as \
+\214rst time)-.25 F(CP/M tools)72 768 Q(February 18, 2012)151.35 E(2)
+192.2 E 0 Cg EP
+%%Page: 3 3
+%%BeginPageSetup
+BP
+%%EndPageSetup
+/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415
+(formats CPM\(5\))2.5 F(stamp.)108 84 Q 2.5(2b)144 108 S
+(ytes \(little-endian\) days starting with 1 at 01-01-1978)-2.5 E 2.5
+(1b)144 120 S(yte hour in BCD format)-2.5 E 2.5(1b)144 132 S
+(yte minute in BCD format)-2.5 E/F1 10/Times-Bold@0 SF
+(DateStamper time stamps)87 160.8 Q F0 .552(The DateStamper softw)108
+172.8 R .552(are added functions to the BDOS to manage time stamps by a\
+llocating a read only)-.1 F .441(\214le with the name "!!!TIME&.D)108
+184.8 R -1.11(AT)-.4 G 2.941("i)1.11 G 2.941(nt)-2.941 G .441(he v)
+-2.941 F .441(ery \214rst directory entry)-.15 F 2.941(,c)-.65 G -.15
+(ove)-2.941 G .441(ring the v).15 F .442(ery \214rst data blocks.)-.15 F
+(It)5.442 E(contains one entry per directory entry with the follo)108
+196.8 Q(wing structure of 16 bytes:)-.25 E 2.5(5b)144 220.8 S
+(ytes create date\214eld)-2.5 E 2.5(5b)144 232.8 S
+(ytes access date\214eld)-2.5 E 2.5(5b)144 244.8 S
+(ytes modify date\214eld)-2.5 E 2.5(1b)144 256.8 S(yte checksum)-2.5 E
+.237(The checksum is only used on e)108 280.8 R -.15(ve)-.25 G .236(ry \
+8th entry \(last entry in 128-byte record\) and is the sum of the \214r\
+st 127).15 F(bytes of the record.)108 292.8 Q
+(Each date\214eld has this structure:)5 E 2.5(1b)144 316.8 S
+(yte BCD coded year \(no century)-2.5 E 2.5(,s)-.65 G 2.5(oi)-2.5 G 2.5
+(ti)-2.5 G 2.5(ss)-2.5 G(ane assuming an)-2.5 E 2.5(yy)-.15 G
+(ear < 70 means 21st century\))-2.5 E 2.5(1b)144 328.8 S
+(yte BCD coded month)-2.5 E 2.5(1b)144 340.8 S(yte BCD coded day)-2.5 E
+2.608(1b)144 352.8 S .108(yte BCD coded hour or)-2.608 F 2.608(,i)-.4 G
+2.608(ft)-2.608 G .108(he high bit is set, the high byte of a counter f\
+or systems without real)-2.608 F(time clock)144 364.8 Q 2.5(1b)144 376.8
+S(yte BCD coded minute, or the lo)-2.5 E 2.5(wb)-.25 G
+(yte of the counter)-2.5 E F1(Disc labels)87 405.6 Q F0 .258(CP/M Plus \
+support disc labels, which are stored in an arbitrary directory entry)
+108 417.6 R 5.257(.T)-.65 G .257(he structure of disc labels)-5.257 F
+(is:)108 429.6 Q 2.5(1b)144 453.6 S(yte status 0x20)-2.5 E F1(F0\211E2)
+144 465.6 Q F0(are the disc label)2.5 E 2.886(1b)144 477.6 S .386
+(yte mode: bit 7 acti)-2.886 F -.25(va)-.25 G .386(tes passw).25 F .387
+(ord protection, bit 6 causes time stamps on access, b)-.1 F .387
+(ut 5 causes)-.2 F .874(time stamps on modi\214cations, bit 4 causes ti\
+me stamps on creation and bit 0 is set when a label)144 489.6 R -.15(ex)
+144 501.6 S 2.5(ists. Bit).15 F 2.5(4a)2.5 G(nd 6 are e)-2.5 E(xclusi)
+-.15 E -.15(ve)-.25 G(ly set.).15 E 3.45(1b)144 513.6 S .95(yte passw)
+-3.45 F .95(ord decode byte: T)-.1 F 3.45(od)-.8 G .951(ecode the passw)
+-3.45 F .951(ord, xor this byte with the passw)-.1 F .951(ord bytes in)
+-.1 F(re)144 525.6 Q -.15(ve)-.25 G(rse order).15 E 5(.T)-.55 G 2.5(oe)
+-5.8 G(ncode a passw)-2.5 E
+(ord, add its characters to get the decode byte.)-.1 E 2.5(2r)144 537.6
+S(eserv)-2.5 E(ed bytes)-.15 E 2.5(8p)144 549.6 S(assw)-2.5 E(ord bytes)
+-.1 E 2.5(4b)144 561.6 S(ytes label creation time stamp)-2.5 E 2.5(4b)
+144 573.6 S(ytes label modi\214cation time stamp)-2.5 E F1 -.1(Pa)87
+602.4 S(ssw).1 E(ords)-.1 E F0 1.484(CP/M Plus supports passw)108 614.4
+R 1.484(ords, which are stored in an arbitrary directory entry)-.1 F
+6.484(.T)-.65 G 1.484(he structure of these)-6.484 F(entries is:)108
+626.4 Q 2.5(1b)144 650.4 S(yte status \(user number plus 16\))-2.5 E F1
+(F0\211E2)144 662.4 Q F0(are the \214le name and its e)2.5 E(xtension.)
+-.15 E 3.171(1b)144 674.4 S .671(yte passw)-3.171 F .671
+(ord mode: bit 7 means passw)-.1 F .672
+(ord required for reading, bit 6 for writing and bit 5 for)-.1 F
+(deleting.)144 686.4 Q 3.451(1b)144 698.4 S .951(yte passw)-3.451 F .951
+(ord decode byte: T)-.1 F 3.451(od)-.8 G .951(ecode the passw)-3.451 F
+.95(ord, xor this byte with the passw)-.1 F .95(ord bytes in)-.1 F(re)
+144 710.4 Q -.15(ve)-.25 G(rse order).15 E 5(.T)-.55 G 2.5(oe)-5.8 G
+(ncode a passw)-2.5 E(ord, add its characters to get the decode byte.)
+-.1 E 2.5(2r)144 722.4 S(eserv)-2.5 E(ed bytes)-.15 E(CP/M tools)72 768
+Q(February 18, 2012)151.35 E(3)192.2 E 0 Cg EP
+%%Page: 4 4
+%%BeginPageSetup
+BP
+%%EndPageSetup
+/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415
+(formats CPM\(5\))2.5 F 2.5(8p)144 84 S(assw)-2.5 E(ord bytes)-.1 E/F1
+10.95/Times-Bold@0 SF(SEE ALSO)72 112.8 Q/F2 10/Times-Italic@0 SF
+(mkfs.cpm)108 124.8 Q F0(\(1\),).32 E F2(fsc)2.5 E(k.cpm)-.2 E F0
+(\(1\),).32 E F2(fsed.cpm)2.5 E F0(\(1\),).32 E F2(cpmls)2.5 E F0(\(1\))
+.27 E(CP/M tools)72 768 Q(February 18, 2012)151.35 E(4)192.2 E 0 Cg EP
+%%Trailer
+end
+%%EOF
diff --git a/Tools/unix/cpmtools/cpmchattr.1 b/Tools/unix/cpmtools/cpmchattr.1
new file mode 100644
index 00000000..f130d5c2
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmchattr.1
@@ -0,0 +1,92 @@
+.TH CPMCHATTR 1 "October 25, 2014" "CP/M tools" "User commands"
+.SH NAME \"{{{roff}}}\"{{{
+cpmchattr \- change file attributes on CP/M files
+.\"}}}
+.SH SYNOPSIS \"{{{
+.ad l
+.B cpmchattr
+.RB [ \-f
+.IR format ]
+.I image
+.I attrib
+.I file-pattern
+\&...
+.ad b
+.\"}}}
+.SH DESCRIPTION \"{{{
+\fBCpmchattr\fP changes the file attributes for files on CP/M disks.
+.\"}}}
+.SH OPTIONS \"{{{
+.IP "\fB\-f\fP \fIformat\fP"
+Use the given CP/M disk \fIformat\fP instead of the default format.
+.IP "\fB\-T\fP \fIlibdsk-type\fP"
+libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images
+(requires building cpmtools with support for libdsk).
+.IP "\fIattrib\fP"
+Set the file attributes as given.
+.\"}}}
+.SH "FILE ATTRIBUTES" \"{{{
+The file attribute string can contain the characters
+1,2,3,4,r,s,a,n and m.
+The meanings of these are:
+.TP
+.B 1-4
+The CP/M "user attributes" F1-F4. CP/M does not assign any
+meaning to these attributes, though MP/M does.
+.TP
+.B r
+The file is read-only. This is the same as using
+.I cpmchmod(1)
+to revoke write permissions.
+.TP
+.B s
+The file is a system file. This attribute can also be set by
+.I cpmchmod(1).
+.TP
+.B a
+The file has been backed up.
+.TP
+.B n
+Reset all attributes to zero. So the string "n1r" resets all attributes and
+then sets F1 and Read-Only.
+.TP
+.B m
+Attributes after an m are unset rather than set. The string "12m34" sets
+atttributes F1 and F2, and unsets F3 and F4.
+.\"}}}
+.SH "RETURN VALUE" \"{{{
+Upon successful completion, exit code 0 is returned.
+.\"}}}
+.SH ERRORS \"{{{
+Any errors are indicated by exit code 1.
+.\"}}}
+.SH ENVIRONMENT \"{{{
+CPMTOOLSFMT Default format
+.\"}}}
+.SH FILES \"{{{
+${prefix}/share/diskdefs CP/M disk format definitions
+.\"}}}
+.SH AUTHORS \"{{{
+This program is copyright 1997\(en2012 Michael Haardt
+ and copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR cpmls (1),
+.IR cpmchmod (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/cpmchattr.c b/Tools/unix/cpmtools/cpmchattr.c
new file mode 100644
index 00000000..be39130b
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmchattr.c
@@ -0,0 +1,119 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+const char cmd[]="cpmchattr";
+
+int main(int argc, char *argv[]) /*{{{*/
+{
+ /* variables */ /*{{{*/
+ const char *err;
+ const char *image;
+ const char *format;
+ const char *devopts=NULL;
+ int c,i,usage=0,exitcode=0;
+ struct cpmSuperBlock drive;
+ struct cpmInode root;
+ int gargc;
+ char **gargv;
+ const char *attrs;
+ /*}}}*/
+
+ /* parse options */ /*{{{*/
+ if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
+ while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c)
+ {
+ case 'T': devopts=optarg; break;
+ case 'f': format=optarg; break;
+ case 'h':
+ case '?': usage=1; break;
+ }
+
+ if (optind>=(argc-2)) usage=1;
+ else
+ {
+ image=argv[optind++];
+ attrs = argv[optind++];
+ }
+
+ if (usage)
+ {
+ fprintf(stderr,"Usage: %s [-f format] [-T dsktype] image [NMrsa1234] pattern ...\n",cmd);
+ exit(1);
+ }
+ /*}}}*/
+ /* open image */ /*{{{*/
+ if ((err=Device_open(&drive.dev, image, O_RDWR, devopts)))
+ {
+ fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err);
+ exit(1);
+ }
+ if (cpmReadSuper(&drive,&root,format)==-1)
+ {
+ fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
+ exit(1);
+ }
+ /*}}}*/
+ cpmglob(optind,argc,argv,&root,&gargc,&gargv);
+ for (i=0; i and copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR cpmls (1),
+.IR chmod (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/cpmchmod.c b/Tools/unix/cpmtools/cpmchmod.c
new file mode 100644
index 00000000..ad146965
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmchmod.c
@@ -0,0 +1,89 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+const char cmd[]="cpmchmod";
+
+int main(int argc, char *argv[]) /*{{{*/
+{
+ /* variables */ /*{{{*/
+ const char *err;
+ const char *image;
+ const char *format;
+ const char *devopts=NULL;
+ int c,i,usage=0,exitcode=0;
+ struct cpmSuperBlock drive;
+ struct cpmInode root;
+ int gargc;
+ char **gargv;
+ unsigned int mode;
+ /*}}}*/
+
+ /* parse options */ /*{{{*/
+ if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
+ while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c)
+ {
+ case 'T': devopts=optarg; break;
+ case 'f': format=optarg; break;
+ case 'h':
+ case '?': usage=1; break;
+ }
+
+ if (optind>=(argc-2)) usage=1;
+ else
+ {
+ image=argv[optind++];
+ if (!sscanf(argv[optind++], "%o", &mode)) usage=1;
+ }
+
+ if (usage)
+ {
+ fprintf(stderr,"Usage: %s [-f format] image mode pattern ...\n",cmd);
+ exit(1);
+ }
+ /*}}}*/
+ /* open image */ /*{{{*/
+ if ((err=Device_open(&drive.dev, image, O_RDWR, devopts)))
+ {
+ fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err);
+ exit(1);
+ }
+ if (cpmReadSuper(&drive,&root,format)==-1)
+ {
+ fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
+ exit(1);
+ }
+ /*}}}*/
+ cpmglob(optind,argc,argv,&root,&gargc,&gargv);
+ for (i=0; i. The Windows port is copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR cpmls (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/cpmcp.c b/Tools/unix/cpmtools/cpmcp.c
new file mode 100644
index 00000000..561c451f
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmcp.c
@@ -0,0 +1,299 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+const char cmd[]="cpmcp";
+static int text=0;
+static int preserve=0;
+
+/**
+ * Return the user number.
+ * @param s CP/M filename in 0[0]:aaaaaaaa.bbb format.
+ * @returns The user number or -1 for no match.
+ */
+static int userNumber(const char *s) /*{{{*/
+{
+ if (isdigit(*s) && *(s+1)==':') return (*s-'0');
+ if (isdigit(*s) && isdigit(*(s+1)) && *(s+2)==':') return (10*(*s-'0')+(*(s+1)-'0'));
+ return -1;
+}
+/*}}}*/
+
+/**
+ * Copy one file from CP/M to UNIX.
+ * @param root The inode for the root directory.
+ * @param src The CP/M filename in 00aaaaaaaabbb format.
+ * @param dest The UNIX filename.
+ * @returns 0 for success, 1 for error.
+ */
+static int cpmToUnix(const struct cpmInode *root, const char *src, const char *dest) /*{{{*/
+{
+ struct cpmInode ino;
+ int exitcode=0;
+
+ if (cpmNamei(root,src,&ino)==-1) { fprintf(stderr,"%s: can not open `%s': %s\n",cmd,src,boo); exitcode=1; }
+ else
+ {
+ struct cpmFile file;
+ FILE *ufp;
+
+ cpmOpen(&ino,&file,O_RDONLY);
+ if ((ufp=fopen(dest,text ? "w" : "wb"))==(FILE*)0) { fprintf(stderr,"%s: can not create %s: %s\n",cmd,dest,strerror(errno)); exitcode=1; }
+ else
+ {
+ int crpending=0;
+ int ohno=0;
+ int res;
+ char buf[4096];
+
+ while ((res=cpmRead(&file,buf,sizeof(buf)))>0)
+ {
+ int j;
+
+ for (j=0; j=argc) usage();
+ image=argv[optind++];
+
+ if (userNumber(argv[optind])>=0) /* cpm -> unix? */ /*{{{*/
+ {
+ int i;
+ struct stat statbuf;
+
+ for (i=optind; i<(argc-1); ++i) if (userNumber(argv[i])==-1) usage();
+ todir=((argc-optind)>2);
+ if (stat(argv[argc-1],&statbuf)==-1) { if (todir) usage(); }
+ else if (S_ISDIR(statbuf.st_mode)) todir=1; else if (todir) usage();
+ readcpm=1;
+ }
+ /*}}}*/
+ else if (userNumber(argv[argc-1])>=0) /* unix -> cpm */ /*{{{*/
+ {
+ int i;
+
+ todir=0;
+ for (i=optind; i<(argc-1); ++i) if (userNumber(argv[i])>=0) usage();
+ if ((argc-optind)>2 && *(strchr(argv[argc-1],':')+1)!='\0') usage();
+ if (*(strchr(argv[argc-1],':')+1)=='\0') todir=1;
+ readcpm=0;
+ }
+ /*}}}*/
+ else usage();
+ /*}}}*/
+ /* open image file */ /*{{{*/
+ if ((err=Device_open(&super.dev,image,readcpm ? O_RDONLY : O_RDWR, devopts)))
+ {
+ fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err);
+ exit(1);
+ }
+ if (cpmReadSuper(&super,&root,format)==-1)
+ {
+ fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
+ exit(1);
+ }
+ /*}}}*/
+ if (readcpm) /* copy from CP/M to UNIX */ /*{{{*/
+ {
+ int i;
+ char *last=argv[argc-1];
+
+ cpmglob(optind,argc-1,argv,&root,&gargc,&gargv);
+ /* trying to copy multiple files to a file? */
+ if (gargc>1 && !todir) usage();
+ for (i=0; i=' ' && !((c)&~0x7f) && (c)!='<' && (c)!='>' && (c)!='.' && (c)!=',' && (c)!=';' && (c)!=':' && (c)!='=' && (c)!='?' && (c)!='*' && (c)!= '[' && (c)!=']')
+#define EXTENT(low,high) (((low)&0x1f)|(((high)&0x3f)<<5))
+#define EXTENTL(extent) ((extent)&0x1f)
+#define EXTENTH(extent) (((extent>>5))&0x3f)
+
+#endif
diff --git a/Tools/unix/cpmtools/cpmfs.c b/Tools/unix/cpmtools/cpmfs.c
new file mode 100644
index 00000000..eaba3ad4
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmfs.c
@@ -0,0 +1,1920 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "cpmdir.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+/* #defines */ /*{{{*/
+#undef CPMFS_DEBUG
+
+/* Number of _used_ bits per int */
+
+#define INTBITS ((int)(sizeof(int)*8))
+
+/* Convert BCD datestamp digits to binary */
+
+#define BCD2BIN(x) ((((x)>>4)&0xf)*10 + ((x)&0xf))
+
+#define BIN2BCD(x) (((((x)/10)&0xf)<<4) + (((x)%10)&0xf))
+
+/* There are four reserved directory entries: ., .., [passwd] and [label].
+The first two of them refer to the same inode. */
+
+#define RESERVED_ENTRIES 4
+
+/* CP/M does not support any kind of inodes, so they are simulated.
+Inode 0-(maxdir-1) correlate to the lowest extent number (not the first
+extent of the file in the directory) of a file. Inode maxdir is the
+root directory, inode maxdir+1 is the optional passwd file and inode
+maxdir+2 the optional disk label. */
+
+#define RESERVED_INODES 3
+
+#define PASSWD_RECLEN 24
+/*}}}*/
+
+extern char **environ;
+const char *boo;
+static mode_t s_ifdir=1;
+static mode_t s_ifreg=1;
+
+/* memcpy7 -- Copy string, leaving 8th bit alone */ /*{{{*/
+static void memcpy7(char *dest, const char *src, int count)
+{
+ while (count--)
+ {
+ *dest = ((*dest) & 0x80) | ((*src) & 0x7F);
+ ++dest;
+ ++src;
+ }
+}
+/*}}}*/
+
+/* file name conversions */
+/* splitFilename -- split file name into name and extension */ /*{{{*/
+static int splitFilename(const char *fullname, int type, char *name, char *ext, int *user)
+{
+ int i,j;
+
+ assert(fullname!=(const char*)0);
+ assert(name!=(char*)0);
+ assert(ext!=(char*)0);
+ assert(user!=(int*)0);
+ memset(name,' ',8);
+ memset(ext,' ',3);
+ if (!isdigit(fullname[0]) || !isdigit(fullname[1]))
+ {
+ boo="illegal CP/M filename";
+ return -1;
+ }
+ *user=10*(fullname[0]-'0')+(fullname[1]-'0');
+ fullname+=2;
+ if ((fullname[0]=='\0') || *user>=((type&CPMFS_HI_USER) ? 32 : 16))
+ {
+ boo="illegal CP/M filename";
+ return -1;
+ }
+ for (i=0; i<8 && fullname[i] && fullname[i]!='.'; ++i) if (!ISFILECHAR(i,fullname[i]))
+ {
+ boo="illegal CP/M filename";
+ return -1;
+ }
+ else name[i]=toupper(fullname[i]);
+ if (fullname[i]=='.')
+ {
+ ++i;
+ for (j=0; j<3 && fullname[i]; ++i,++j) if (!ISFILECHAR(1,fullname[i]))
+ {
+ boo="illegal CP/M filename";
+ return -1;
+ }
+ else ext[j]=toupper(fullname[i]);
+ if (i==1 && j==0)
+ {
+ boo="illegal CP/M filename";
+ return -1;
+ }
+ }
+ return 0;
+}
+/*}}}*/
+/* isMatching -- do two file names match? */ /*{{{*/
+static int isMatching(int user1, const char *name1, const char *ext1, int user2, const char *name2, const char *ext2)
+{
+ int i;
+
+ assert(name1!=(const char*)0);
+ assert(ext1!=(const char*)0);
+ assert(name2!=(const char*)0);
+ assert(ext2!=(const char*)0);
+ if (user1!=user2) return 0;
+ for (i=0; i<8; ++i) if ((name1[i]&0x7f)!=(name2[i]&0x7f)) return 0;
+ for (i=0; i<3; ++i) if ((ext1[i]&0x7f)!=(ext2[i]&0x7f)) return 0;
+ return 1;
+}
+/*}}}*/
+
+/* time conversions */
+/* cpm2unix_time -- convert CP/M time to UTC */ /*{{{*/
+static time_t cpm2unix_time(int days, int hour, int min)
+{
+ /* CP/M stores timestamps in local time. We don't know which */
+ /* timezone was used and if DST was in effect. Assuming it was */
+ /* the current offset from UTC is most sensible, but not perfect. */
+
+ int year,days_per_year;
+ static int days_per_month[]={31,0,31,30,31,30,31,31,30,31,30,31};
+ char **old_environ;
+ static char gmt0[]="TZ=GMT0";
+ static char *gmt_env[]={ gmt0, (char*)0 };
+ struct tm tms;
+ time_t lt,t;
+
+ time(<);
+ t=lt;
+ tms=*localtime(<);
+ old_environ=environ;
+ environ=gmt_env;
+ lt=mktime(&tms);
+ lt-=t;
+ tms.tm_sec=0;
+ tms.tm_min=((min>>4)&0xf)*10+(min&0xf);
+ tms.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
+ tms.tm_mday=1;
+ tms.tm_mon=0;
+ tms.tm_year=78;
+ tms.tm_isdst=-1;
+ for (;;)
+ {
+ year=tms.tm_year+1900;
+ days_per_year=((year%4)==0 && ((year%100) || (year%400)==0)) ? 366 : 365;
+ if (days>days_per_year)
+ {
+ days-=days_per_year;
+ ++tms.tm_year;
+ }
+ else break;
+ }
+ for (;;)
+ {
+ days_per_month[1]=(days_per_year==366) ? 29 : 28;
+ if (days>days_per_month[tms.tm_mon])
+ {
+ days-=days_per_month[tms.tm_mon];
+ ++tms.tm_mon;
+ }
+ else break;
+ }
+ t=mktime(&tms)+(days-1)*24*3600;
+ environ=old_environ;
+ t-=lt;
+ return t;
+}
+/*}}}*/
+/* unix2cpm_time -- convert UTC to CP/M time */ /*{{{*/
+static void unix2cpm_time(time_t now, int *days, int *hour, int *min)
+{
+ struct tm *tms;
+ int i;
+
+ tms=localtime(&now);
+ *min=((tms->tm_min/10)<<4)|(tms->tm_min%10);
+ *hour=((tms->tm_hour/10)<<4)|(tms->tm_hour%10);
+ for (i=1978,*days=0; i<1900+tms->tm_year; ++i)
+ {
+ *days+=365;
+ if (i%4==0 && (i%100!=0 || i%400==0)) ++*days;
+ }
+ *days += tms->tm_yday+1;
+}
+/*}}}*/
+/* ds2unix_time -- convert DS to Unix time */ /*{{{*/
+static time_t ds2unix_time(const struct dsEntry *entry)
+{
+ struct tm tms;
+ int yr;
+
+ if (entry->minute==0 &&
+ entry->hour==0 &&
+ entry->day==0 &&
+ entry->month==0 &&
+ entry->year==0) return 0;
+
+ tms.tm_isdst = -1;
+ tms.tm_sec = 0;
+ tms.tm_min = BCD2BIN( entry->minute );
+ tms.tm_hour = BCD2BIN( entry->hour );
+ tms.tm_mday = BCD2BIN( entry->day );
+ tms.tm_mon = BCD2BIN( entry->month ) - 1;
+
+ yr = BCD2BIN(entry->year);
+ if (yr<70) yr+=100;
+ tms.tm_year = yr;
+
+ return mktime(&tms);
+}
+/*}}}*/
+/* unix2ds_time -- convert Unix to DS time */ /*{{{*/
+static void unix2ds_time(time_t now, struct dsEntry *entry)
+{
+ struct tm *tms;
+ int yr;
+
+ if ( now==0 )
+ {
+ entry->minute=entry->hour=entry->day=entry->month=entry->year = 0;
+ }
+ else
+ {
+ tms=localtime(&now);
+ entry->minute = BIN2BCD( tms->tm_min );
+ entry->hour = BIN2BCD( tms->tm_hour );
+ entry->day = BIN2BCD( tms->tm_mday );
+ entry->month = BIN2BCD( tms->tm_mon + 1 );
+
+ yr = tms->tm_year;
+ if ( yr>100 ) yr -= 100;
+ entry->year = BIN2BCD( yr );
+ }
+}
+/*}}}*/
+
+/* allocation vector bitmap functions */
+/* alvInit -- init allocation vector */ /*{{{*/
+static void alvInit(const struct cpmSuperBlock *d)
+{
+ int i,j,offset,block;
+
+ assert(d!=(const struct cpmSuperBlock*)0);
+ /* clean bitmap */ /*{{{*/
+ memset(d->alv,0,d->alvSize*sizeof(int));
+ /*}}}*/
+ /* mark directory blocks as used */ /*{{{*/
+ *d->alv=(1<<((d->maxdir*32+d->blksiz-1)/d->blksiz))-1;
+ /*}}}*/
+ for (i=0; imaxdir; ++i) /* mark file blocks as used */ /*{{{*/
+ {
+ if (d->dir[i].status>=0 && d->dir[i].status<=(d->type&CPMFS_HI_USER ? 31 : 15))
+ {
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"alvInit: allocate extent %d\n",i);
+#endif
+ for (j=0; j<16; ++j)
+ {
+ block=(unsigned char)d->dir[i].pointers[j];
+ if (d->size>=256) block+=(((unsigned char)d->dir[i].pointers[++j])<<8);
+ if (block && blocksize)
+ {
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"alvInit: allocate block %d\n",block);
+#endif
+ offset=block/INTBITS;
+ d->alv[offset]|=(1<alvSize; ++i)
+ {
+ for (j=0,bits=drive->alv[i]; j=drive->size)
+ {
+ boo="device full";
+ return -1;
+ }
+ drive->alv[i] |= (1<>= 1;
+ }
+ }
+ boo="device full";
+ return -1;
+}
+/*}}}*/
+
+/* logical block I/O */
+/* readBlock -- read a (partial) block */ /*{{{*/
+static int readBlock(const struct cpmSuperBlock *d, int blockno, char *buffer, int start, int end)
+{
+ int sect, track, counter;
+
+ assert(d);
+ assert(blockno>=0);
+ assert(buffer);
+ if (blockno>=d->size)
+ {
+ boo="Attempting to access block beyond end of disk";
+ return -1;
+ }
+ if (end<0) end=d->blksiz/d->secLength-1;
+ sect=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)%d->sectrk;
+ track=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)/d->sectrk;
+ for (counter=0; counter<=end; ++counter)
+ {
+ const char *err;
+
+ assert(d->skewtab[sect]>=0);
+ assert(d->skewtab[sect]sectrk);
+ if (counter>=start && (err=Device_readSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter))))
+ {
+ boo=err;
+ return -1;
+ }
+ ++sect;
+ if (sect>=d->sectrk)
+ {
+ sect = 0;
+ ++track;
+ }
+ }
+ return 0;
+}
+/*}}}*/
+/* writeBlock -- write a (partial) block */ /*{{{*/
+static int writeBlock(const struct cpmSuperBlock *d, int blockno, const char *buffer, int start, int end)
+{
+ int sect, track, counter;
+
+ assert(blockno>=0);
+ assert(blocknosize);
+ assert(buffer!=(const char*)0);
+ if (end < 0) end=d->blksiz/d->secLength-1;
+ sect = (blockno*(d->blksiz/d->secLength))%d->sectrk;
+ track = (blockno*(d->blksiz/d->secLength))/d->sectrk+d->boottrk;
+ for (counter = 0; counter<=end; ++counter)
+ {
+ const char *err;
+
+ if (counter>=start && (err=Device_writeSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter))))
+ {
+ boo=err;
+ return -1;
+ }
+ ++sect;
+ if (sect>=d->sectrk)
+ {
+ sect=0;
+ ++track;
+ }
+ }
+ return 0;
+}
+/*}}}*/
+
+/* directory management */
+/* findFileExtent -- find first/next extent for a file */ /*{{{*/
+static int findFileExtent(const struct cpmSuperBlock *sb, int user, const char *name, const char *ext, int start, int extno)
+{
+ boo="file already exists";
+ for (; startmaxdir; ++start)
+ {
+ if
+ (
+ ((unsigned char)sb->dir[start].status)<=(sb->type&CPMFS_HI_USER ? 31 : 15)
+ && (extno==-1 || (EXTENT(sb->dir[start].extnol,sb->dir[start].extnoh)/sb->extents)==(extno/sb->extents))
+ && isMatching(user,name,ext,sb->dir[start].status,sb->dir[start].name,sb->dir[start].ext)
+ ) return start;
+ }
+ boo="file not found";
+ return -1;
+}
+/*}}}*/
+/* findFreeExtent -- find first free extent */ /*{{{*/
+static int findFreeExtent(const struct cpmSuperBlock *drive)
+{
+ int i;
+
+ for (i=0; imaxdir; ++i) if (drive->dir[i].status==(char)0xe5) return (i);
+ boo="directory full";
+ return -1;
+}
+/*}}}*/
+/* updateTimeStamps -- convert time stamps to CP/M format */ /*{{{*/
+static void updateTimeStamps(const struct cpmInode *ino, int extent)
+{
+ struct PhysDirectoryEntry *date;
+ int i;
+ int ca_min,ca_hour,ca_days,u_min,u_hour,u_days;
+
+ if (!S_ISREG(ino->mode)) return;
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"CPMFS: updating time stamps for inode %d (%d)\n",extent,extent&3);
+#endif
+ unix2cpm_time(ino->sb->cnotatime ? ino->ctime : ino->atime,&ca_days,&ca_hour,&ca_min);
+ unix2cpm_time(ino->mtime,&u_days,&u_hour,&u_min);
+ if ((ino->sb->type&CPMFS_CPM3_DATES) && (date=ino->sb->dir+(extent|3))->status==0x21)
+ {
+ ino->sb->dirtyDirectory=1;
+ switch (extent&3)
+ {
+ case 0: /* first entry */ /*{{{*/
+ {
+ date->name[0]=ca_days&0xff; date->name[1]=ca_days>>8;
+ date->name[2]=ca_hour;
+ date->name[3]=ca_min;
+ date->name[4]=u_days&0xff; date->name[5]=u_days>>8;
+ date->name[6]=u_hour;
+ date->name[7]=u_min;
+ break;
+ }
+ /*}}}*/
+ case 1: /* second entry */ /*{{{*/
+ {
+ date->ext[2]=ca_days&0xff; date->extnol=ca_days>>8;
+ date->lrc=ca_hour;
+ date->extnoh=ca_min;
+ date->blkcnt=u_days&0xff; date->pointers[0]=u_days>>8;
+ date->pointers[1]=u_hour;
+ date->pointers[2]=u_min;
+ break;
+ }
+ /*}}}*/
+ case 2: /* third entry */ /*{{{*/
+ {
+ date->pointers[5]=ca_days&0xff; date->pointers[6]=ca_days>>8;
+ date->pointers[7]=ca_hour;
+ date->pointers[8]=ca_min;
+ date->pointers[9]=u_days&0xff; date->pointers[10]=u_days>>8;
+ date->pointers[11]=u_hour;
+ date->pointers[12]=u_min;
+ break;
+ }
+ /*}}}*/
+ }
+ }
+}
+/*}}}*/
+/* updateDsStamps -- set time in datestamper file */ /*{{{*/
+static void updateDsStamps(const struct cpmInode *ino, int extent)
+{
+ int yr;
+ struct tm *cpm_time;
+ struct dsDate *stamp;
+
+ if (!S_ISREG(ino->mode)) return;
+ if ( !(ino->sb->type&CPMFS_DS_DATES) ) return;
+
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"CPMFS: updating ds stamps for inode %d (%d)\n",extent,extent&3);
+#endif
+
+ /* Get datestamp struct */
+ stamp = ino->sb->ds+extent;
+
+ unix2ds_time( ino->mtime, &stamp->modify );
+ unix2ds_time( ino->ctime, &stamp->create );
+ unix2ds_time( ino->atime, &stamp->access );
+
+ ino->sb->dirtyDs = 1;
+}
+/*}}}*/
+/* readTimeStamps -- read CP/M time stamp */ /*{{{*/
+static int readTimeStamps(struct cpmInode *i, int lowestExt)
+{
+ /* variables */ /*{{{*/
+ struct PhysDirectoryEntry *date;
+ int u_days=0,u_hour=0,u_min=0;
+ int ca_days=0,ca_hour=0,ca_min=0;
+ int protectMode=0;
+ /*}}}*/
+
+ if ( (i->sb->type&CPMFS_CPM3_DATES) && (date=i->sb->dir+(lowestExt|3))->status==0x21 )
+ {
+ switch (lowestExt&3)
+ {
+ case 0: /* first entry of the four */ /*{{{*/
+ {
+ ca_days=((unsigned char)date->name[0])+(((unsigned char)date->name[1])<<8);
+ ca_hour=(unsigned char)date->name[2];
+ ca_min=(unsigned char)date->name[3];
+ u_days=((unsigned char)date->name[4])+(((unsigned char)date->name[5])<<8);
+ u_hour=(unsigned char)date->name[6];
+ u_min=(unsigned char)date->name[7];
+ protectMode=(unsigned char)date->ext[0];
+ break;
+ }
+ /*}}}*/
+ case 1: /* second entry */ /*{{{*/
+ {
+ ca_days=((unsigned char)date->ext[2])+(((unsigned char)date->extnol)<<8);
+ ca_hour=(unsigned char)date->lrc;
+ ca_min=(unsigned char)date->extnoh;
+ u_days=((unsigned char)date->blkcnt)+(((unsigned char)date->pointers[0])<<8);
+ u_hour=(unsigned char)date->pointers[1];
+ u_min=(unsigned char)date->pointers[2];
+ protectMode=(unsigned char)date->pointers[3];
+ break;
+ }
+ /*}}}*/
+ case 2: /* third one */ /*{{{*/
+ {
+ ca_days=((unsigned char)date->pointers[5])+(((unsigned char)date->pointers[6])<<8);
+ ca_hour=(unsigned char)date->pointers[7];
+ ca_min=(unsigned char)date->pointers[8];
+ u_days=((unsigned char)date->pointers[9])+(((unsigned char)date->pointers[10])<<8);
+ u_hour=(unsigned char)date->pointers[11];
+ u_min=(unsigned char)date->pointers[12];
+ protectMode=(unsigned char)date->pointers[13];
+ break;
+ }
+ /*}}}*/
+ }
+ if (i->sb->cnotatime)
+ {
+ i->ctime=cpm2unix_time(ca_days,ca_hour,ca_min);
+ i->atime=0;
+ }
+ else
+ {
+ i->ctime=0;
+ i->atime=cpm2unix_time(ca_days,ca_hour,ca_min);
+ }
+ i->mtime=cpm2unix_time(u_days,u_hour,u_min);
+ }
+ else
+ {
+ i->atime=i->mtime=i->ctime=0;
+ protectMode=0;
+ }
+
+ return protectMode;
+}
+/*}}}*/
+/* readDsStamps -- read datestamper time stamp */ /*{{{*/
+static void readDsStamps(struct cpmInode *i, int lowestExt)
+{
+ struct dsDate *stamp;
+
+ if ( !(i->sb->type&CPMFS_DS_DATES) ) return;
+
+ /* Get datestamp */
+ stamp = i->sb->ds+lowestExt;
+
+ i->mtime = ds2unix_time(&stamp->modify);
+ i->ctime = ds2unix_time(&stamp->create);
+ i->atime = ds2unix_time(&stamp->access);
+}
+/*}}}*/
+
+/* match -- match filename against a pattern */ /*{{{*/
+static int recmatch(const char *a, const char *pattern)
+{
+ int first=1;
+
+ assert(a);
+ assert(pattern);
+ while (*pattern)
+ {
+ switch (*pattern)
+ {
+ case '*':
+ {
+ if (*a=='.' && first) return 1;
+ ++pattern;
+ while (*a) if (recmatch(a,pattern)) return 1; else ++a;
+ break;
+ }
+ case '?':
+ {
+ if (*a) { ++a; ++pattern; } else return 0;
+ break;
+ }
+ default: if (tolower(*a)==tolower(*pattern)) { ++a; ++pattern; } else return 0;
+ }
+ first=0;
+ }
+ return (*pattern=='\0' && *a=='\0');
+}
+
+int match(const char *a, const char *pattern)
+{
+ int user;
+ char pat[255];
+
+ assert(a);
+ assert(pattern);
+ assert(strlen(pattern)<255);
+ if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; }
+ else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; }
+ else user=-1;
+ if (user==-1) sprintf(pat,"??%s",pattern);
+ else sprintf(pat,"%02d%s",user,pattern);
+ return recmatch(a,pat);
+}
+
+/*}}}*/
+/* cpmglob -- expand CP/M style wildcards */ /*{{{*/
+void cpmglob(int optin, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv)
+{
+ struct cpmFile dir;
+ int entries,dirsize=0;
+ struct cpmDirent *dirent=(struct cpmDirent*)0;
+ int gargcap=0,i,j;
+
+ *gargv=(char**)0;
+ *gargc=0;
+ cpmOpendir(root,&dir);
+ entries=0;
+ dirsize=8;
+ dirent=malloc(sizeof(struct cpmDirent)*dirsize);
+ while (cpmReaddir(&dir,&dirent[entries]))
+ {
+ ++entries;
+ if (entries==dirsize) dirent=realloc(dirent,sizeof(struct cpmDirent)*(dirsize*=2));
+ }
+ for (i=optin; ilibdskGeometry[0] = '\0';
+ d->type=0;
+ if (
+ (fp=fopen("diskdefs","r"))==(FILE*)0 &&
+ (ddenv && ((fp=fopen(DISKDEFS,"r"))==(FILE*)0)) &&
+ (fp=fopen(DISKDEFS,"r"))==(FILE*)0)
+ {
+ fprintf(stderr,"%s: Neither `diskdefs' nor `" DISKDEFS "' could be opened.\n",cmd);
+ exit(1);
+ }
+ while (fgets(line,sizeof(line),fp)!=(char*)0)
+ {
+ int argc;
+ char *argv[2];
+ char *s;
+
+ /* Allow inline comments preceded by ; or # */
+ s = strchr(line, '#');
+ if (s) strcpy(s, "\n");
+ s = strchr(line, ';');
+ if (s) strcpy(s, "\n");
+
+ for (argc=0; argc<1 && (argv[argc]=strtok(argc ? (char*)0 : line," \t\n")); ++argc);
+ if ((argv[argc]=strtok((char*)0,"\n"))!=(char*)0) ++argc;
+ if (insideDef)
+ {
+ if (argc==1 && strcmp(argv[0],"end")==0)
+ {
+ insideDef=0;
+ d->size=(d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz;
+ if (d->extents==0) d->extents=((d->size>=256 ? 8 : 16)*d->blksiz)/16384;
+ if (d->extents==0) d->extents=1;
+ if (found) break;
+ }
+ else if (argc==2)
+ {
+ if (strcmp(argv[0],"seclen")==0) d->secLength=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"tracks")==0) d->tracks=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"sectrk")==0) d->sectrk=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"blocksize")==0) d->blksiz=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"maxdir")==0) d->maxdir=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"skew")==0) d->skew=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"skewtab")==0)
+ {
+ int pass,sectors;
+
+ for (pass=0; pass<2; ++pass)
+ {
+ sectors=0;
+ for (s=argv[1]; *s; )
+ {
+ int phys;
+ char *end;
+
+ phys=strtol(s,&end,10);
+ if (pass==1) d->skewtab[sectors]=phys;
+ if (end==s)
+ {
+ fprintf(stderr,"%s: invalid skewtab `%s' at `%s'\n",cmd,argv[1],s);
+ exit(1);
+ }
+ s=end;
+ ++sectors;
+ if (*s==',') ++s;
+ }
+ if (pass==0) d->skewtab=malloc(sizeof(int)*sectors);
+ }
+ }
+ else if (strcmp(argv[0],"boottrk")==0) d->boottrk=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"offset")==0)
+ {
+ off_t val;
+ unsigned int multiplier;
+ char *endptr;
+
+ errno=0;
+ multiplier=1;
+ val = strtol(argv[1],&endptr,10);
+ if ((errno==ERANGE && val==LONG_MAX)||(errno!=0 && val<=0))
+ {
+ fprintf(stderr,"%s: invalid offset value \"%s\" - %s\n",cmd,argv[1],strerror(errno));
+ exit(1);
+ }
+ if (endptr==argv[1])
+ {
+ fprintf(stderr,"%s: offset value \"%s\" is not a number\n",cmd,argv[1]);
+ exit(1);
+ }
+ if (*endptr!='\0')
+ {
+ /* Have a unit specifier */
+ switch (toupper(*endptr))
+ {
+ case 'K':
+ multiplier=1024;
+ break;
+ case 'M':
+ multiplier=1024*1024;
+ break;
+ case 'T':
+ if (d->sectrk<0||d->tracks<0||d->secLength<0)
+ {
+ fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength\n",cmd);
+ exit(1);
+ }
+ multiplier=d->sectrk*d->secLength;
+ break;
+ case 'S':
+ if (d->sectrk<0||d->tracks<0||d->secLength<0)
+ {
+ fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength\n",cmd);
+ exit(1);
+ }
+ multiplier=d->secLength;
+ break;
+ default:
+ fprintf(stderr,"%s: unknown unit specifier \"%c\"\n",cmd,*endptr);
+ exit(1);
+ }
+ }
+ if (val*multiplier>INT_MAX)
+ {
+ fprintf(stderr,"%s: effective offset is out of range\n",cmd);
+ exit(1);
+ }
+ d->offset=val*multiplier;
+ }
+ else if (strcmp(argv[0],"logicalextents")==0) d->extents=strtol(argv[1],(char**)0,0);
+ else if (strcmp(argv[0],"os")==0)
+ {
+ if (strcmp(argv[1],"2.2" )==0) d->type|=CPMFS_DR22;
+ else if (strcmp(argv[1],"3" )==0) d->type|=CPMFS_DR3;
+ else if (strcmp(argv[1],"isx" )==0) d->type|=CPMFS_ISX;
+ else if (strcmp(argv[1],"p2dos")==0) d->type|=CPMFS_P2DOS;
+ else if (strcmp(argv[1],"zsys" )==0) d->type|=CPMFS_ZSYS;
+ else
+ {
+ fprintf(stderr, "%s: invalid OS type `%s'\n", cmd, argv[1]);
+ exit(1);
+ }
+ }
+ else if (strcmp(argv[0], "libdsk:format")==0)
+ {
+ strncpy(d->libdskGeometry, argv[1], sizeof(d->libdskGeometry) - 1);
+ d->libdskGeometry[sizeof(d->libdskGeometry) - 1] = 0;
+ }
+ }
+ else if (argc>0 && argv[0][0]!='#' && argv[0][0]!=';')
+ {
+ fprintf(stderr,"%s: invalid keyword `%s'\n",cmd,argv[0]);
+ exit(1);
+ }
+ }
+ else if (argc==2 && strcmp(argv[0],"diskdef")==0)
+ {
+ insideDef=1;
+ d->skew=1;
+ d->extents=0;
+ d->type=CPMFS_DR22;
+ d->skewtab=(int*)0;
+ d->offset=0;
+ d->boottrk=d->secLength=d->sectrk=d->tracks=-1;
+ d->libdskGeometry[0] = 0;
+ if (strcmp(argv[1],format)==0) found=1;
+ }
+ }
+ fclose(fp);
+ if (!found)
+ {
+ fprintf(stderr,"%s: unknown format %s\n",cmd,format);
+ exit(1);
+ }
+ if (d->boottrk<0)
+ {
+ fprintf(stderr, "%s: boottrk parameter invalid or missing from diskdef\n",cmd);
+ exit(1);
+ }
+ if (d->secLength<0)
+ {
+ fprintf(stderr, "%s: secLength parameter invalid or missing from diskdef\n",cmd);
+ exit(1);
+ }
+ if (d->sectrk<0)
+ {
+ fprintf(stderr, "%s: sectrk parameter invalid or missing from diskdef\n",cmd);
+ exit(1);
+ }
+ if (d->tracks<0)
+ {
+ fprintf(stderr, "%s: tracks parameter invalid or missing from diskdef\n",cmd);
+ exit(1);
+ }
+ return 0;
+}
+/*}}}*/
+/* amsReadSuper -- read super block from amstrad disk */ /*{{{*/
+static int amsReadSuper(struct cpmSuperBlock *d, const char *format)
+{
+ unsigned char boot_sector[512], *boot_spec;
+ const char *err;
+
+ Device_setGeometry(&d->dev,512,9,40,0,"pcw180");
+ if ((err=Device_readSector(&d->dev, 0, 0, (char *)boot_sector)))
+ {
+ fprintf(stderr,"%s: Failed to read Amstrad superblock (%s)\n",cmd,err);
+ exit(1);
+ }
+ boot_spec=(boot_sector[0] == 0 || boot_sector[0] == 3)?boot_sector:(unsigned char*)0;
+ /* Check for JCE's extension to allow Amstrad and MSDOS superblocks
+ * in the same sector (for the PCW16)
+ */
+ if
+ (
+ (boot_sector[0] == 0xE9 || boot_sector[0] == 0xEB)
+ && !memcmp(boot_sector + 0x2B, "CP/M", 4)
+ && !memcmp(boot_sector + 0x33, "DSK", 3)
+ && !memcmp(boot_sector + 0x7C, "CP/M", 4)
+ ) boot_spec = boot_sector + 128;
+ if (boot_spec==(unsigned char*)0)
+ {
+ fprintf(stderr,"%s: Amstrad superblock not present\n",cmd);
+ exit(1);
+ }
+ /* boot_spec[0] = format number: 0 for SS SD, 3 for DS DD
+ [1] = single/double sided and density flags
+ [2] = cylinders per side
+ [3] = sectors per cylinder
+ [4] = Physical sector shift, 2 => 512
+ [5] = Reserved track count
+ [6] = Block shift
+ [7] = No. of directory blocks
+ */
+ d->type = 0;
+ d->type |= CPMFS_DR3; /* Amstrads are CP/M 3 systems */
+ d->secLength = 128 << boot_spec[4];
+ d->tracks = boot_spec[2];
+ if (boot_spec[1] & 3) d->tracks *= 2;
+ d->sectrk = boot_spec[3];
+ d->blksiz = 128 << boot_spec[6];
+ d->maxdir = (d->blksiz / 32) * boot_spec[7];
+ d->skew = 1; /* Amstrads skew at the controller level */
+ d->skewtab = (int*)0;
+ d->boottrk = boot_spec[5];
+ d->offset = 0;
+ d->size = (d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz;
+ d->extents = ((d->size>=256 ? 8 : 16)*d->blksiz)/16384;
+ d->libdskGeometry[0] = 0; /* LibDsk can recognise an Amstrad superblock
+ * and autodect */
+
+ return 0;
+}
+/*}}}*/
+/* cpmCheckDs -- read all datestamper timestamps */ /*{{{*/
+int cpmCheckDs(struct cpmSuperBlock *sb)
+{
+ int dsoffset, dsblks, dsrecs, off, i;
+ unsigned char *buf;
+
+ if (!isMatching(0,"!!!TIME&","DAT",sb->dir->status,sb->dir->name,sb->dir->ext)) return -1;
+
+ /* Offset to ds file in alloc blocks */
+ dsoffset=(sb->maxdir*32+(sb->blksiz-1))/sb->blksiz;
+
+ dsrecs=(sb->maxdir+7)/8;
+ dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz;
+
+ /* Allocate buffer */
+ sb->ds=malloc(dsblks*sb->blksiz);
+
+ /* Read ds file in its entirety */
+ off=0;
+ for (i=dsoffset; ids)+off,0,-1)==-1) return -1;
+ off+=sb->blksiz;
+ }
+
+ /* Verify checksums */
+ buf = (unsigned char *)sb->ds;
+ for (i=0; ids);
+ sb->ds = (struct dsDate *)0;
+ return -1;
+ }
+ buf += 128;
+ }
+ return 0;
+}
+/*}}}*/
+/* cpmReadSuper -- get DPB and init in-core data for drive */ /*{{{*/
+int cpmReadSuper(struct cpmSuperBlock *d, struct cpmInode *root, const char *format)
+{
+ while (s_ifdir && !S_ISDIR(s_ifdir)) s_ifdir<<=1;
+ assert(s_ifdir);
+ while (s_ifreg && !S_ISREG(s_ifreg)) s_ifreg<<=1;
+ assert(s_ifreg);
+ if (strcmp(format,"amstrad")==0) amsReadSuper(d,format);
+ else diskdefReadSuper(d,format);
+ boo = Device_setGeometry(&d->dev,d->secLength,d->sectrk,d->tracks,d->offset,d->libdskGeometry);
+ if (boo) return -1;
+
+ if (d->skewtab==(int*)0) /* generate skew table */ /*{{{*/
+ {
+ int i,j,k;
+
+ if (( d->skewtab = malloc(d->sectrk*sizeof(int))) == (int*)0)
+ {
+ boo=strerror(errno);
+ return -1;
+ }
+ memset(d->skewtab,0,d->sectrk*sizeof(int));
+ for (i=j=0; isectrk; ++i,j=(j+d->skew)%d->sectrk)
+ {
+ while (1)
+ {
+ assert(isectrk);
+ assert(jsectrk);
+ for (k=0; kskewtab[k]!=j; ++k);
+ if (ksectrk;
+ else break;
+ }
+ d->skewtab[i]=j;
+ }
+ }
+ /*}}}*/
+ /* initialise allocation vector bitmap */ /*{{{*/
+ {
+ d->alvSize=((d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz+INTBITS-1)/INTBITS;
+ if ((d->alv=malloc(d->alvSize*sizeof(int)))==(int*)0)
+ {
+ boo=strerror(errno);
+ return -1;
+ }
+ }
+ /*}}}*/
+ /* allocate directory buffer */ /*{{{*/
+ assert(sizeof(struct PhysDirectoryEntry)==32);
+ if ((d->dir=malloc(((d->maxdir*32+d->blksiz-1)/d->blksiz)*d->blksiz))==(struct PhysDirectoryEntry*)0)
+ {
+ boo=strerror(errno);
+ return -1;
+ }
+ /*}}}*/
+ if (d->dev.opened==0) /* create empty directory in core */ /*{{{*/
+ {
+ memset(d->dir,0xe5,d->maxdir*32);
+ }
+ /*}}}*/
+ else /* read directory in core */ /*{{{*/
+ {
+ int i,blocks,entry;
+
+ blocks=(d->maxdir*32+d->blksiz-1)/d->blksiz;
+ entry=0;
+ for (i=0; idir+entry),0,-1)==-1) return -1;
+ entry+=(d->blksiz/32);
+ }
+ }
+ /*}}}*/
+ alvInit(d);
+ if (d->type&CPMFS_CPM3_OTHER) /* read additional superblock information */ /*{{{*/
+ {
+ int i;
+
+ /* passwords */ /*{{{*/
+ {
+ int passwords=0;
+
+ for (i=0; imaxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) ++passwords;
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"getformat: found %d passwords\n",passwords);
+#endif
+ if ((d->passwdLength=passwords*PASSWD_RECLEN))
+ {
+ if ((d->passwd=malloc(d->passwdLength))==(char*)0)
+ {
+ boo="out of memory";
+ return -1;
+ }
+ for (i=0,passwords=0; imaxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31)
+ {
+ int j,pb;
+ char *p=d->passwd+(passwords++*PASSWD_RECLEN);
+
+ p[0]='0'+(d->dir[i].status-16)/10;
+ p[1]='0'+(d->dir[i].status-16)%10;
+ for (j=0; j<8; ++j) p[2+j]=d->dir[i].name[j]&0x7f;
+ p[10]=(d->dir[i].ext[0]&0x7f)==' ' ? ' ' : '.';
+ for (j=0; j<3; ++j) p[11+j]=d->dir[i].ext[j]&0x7f;
+ p[14]=' ';
+ pb=(unsigned char)d->dir[i].lrc;
+ for (j=0; j<8; ++j) p[15+j]=((unsigned char)d->dir[i].pointers[7-j])^pb;
+#ifdef CPMFS_DEBUG
+ p[23]='\0';
+ fprintf(stderr,"getformat: %s\n",p);
+#endif
+ p[23]='\n';
+ }
+ }
+ }
+ /*}}}*/
+ /* disc label */ /*{{{*/
+ for (i=0; imaxdir; ++i) if (d->dir[i].status==(char)0x20)
+ {
+ int j;
+
+ d->cnotatime=d->dir[i].extnol&0x10;
+ if (d->dir[i].extnol&0x1)
+ {
+ d->labelLength=12;
+ if ((d->label=malloc(d->labelLength))==(char*)0)
+ {
+ boo="out of memory";
+ return -1;
+ }
+ for (j=0; j<8; ++j) d->label[j]=d->dir[i].name[j]&0x7f;
+ for (j=0; j<3; ++j) d->label[8+j]=d->dir[i].ext[j]&0x7f;
+ d->label[11]='\n';
+ }
+ else
+ {
+ d->labelLength=0;
+ }
+ break;
+ }
+ if (i==d->maxdir)
+ {
+ d->cnotatime=1;
+ d->labelLength=0;
+ }
+ /*}}}*/
+ }
+ /*}}}*/
+ else
+ {
+ d->passwdLength=0;
+ d->cnotatime=1;
+ d->labelLength=0;
+ }
+ d->root=root;
+ d->dirtyDirectory = 0;
+ root->ino=d->maxdir;
+ root->sb=d;
+ root->mode=(s_ifdir|0777);
+ root->size=0;
+ root->atime=root->mtime=root->ctime=0;
+
+ d->dirtyDs=0;
+ if (cpmCheckDs(d)==0) d->type|=CPMFS_DS_DATES;
+ else d->ds=(struct dsDate*)0;
+
+ return 0;
+}
+/*}}}*/
+/* syncDs -- write all datestamper timestamps */ /*{{{*/
+static int syncDs(const struct cpmSuperBlock *sb)
+{
+ if (sb->dirtyDs)
+ {
+ int dsoffset, dsblks, dsrecs, off, i;
+ unsigned char *buf;
+
+ dsrecs=(sb->maxdir+7)/8;
+
+ /* Re-calculate checksums */
+ buf = (unsigned char *)sb->ds;
+ for ( i=0; imaxdir*32+(sb->blksiz-1))/sb->blksiz;
+ dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz;
+
+ off=0;
+ for (i=dsoffset; ids))+off,0,-1)==-1) return -1;
+ off+=sb->blksiz;
+ }
+ }
+ return 0;
+}
+/*}}}*/
+/* cpmSync -- write directory back */ /*{{{*/
+int cpmSync(struct cpmSuperBlock *sb)
+{
+ if (sb->dirtyDirectory)
+ {
+ int i,blocks,entry;
+
+ blocks=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz;
+ entry=0;
+ for (i=0; idir+entry),0,-1)==-1) return -1;
+ entry+=(sb->blksiz/32);
+ }
+ sb->dirtyDirectory=0;
+ }
+ if (sb->type&CPMFS_DS_DATES) syncDs(sb);
+ return 0;
+}
+/*}}}*/
+/* cpmUmount -- free super block */ /*{{{*/
+void cpmUmount(struct cpmSuperBlock *sb)
+{
+ cpmSync(sb);
+ if (sb->type&CPMFS_DS_DATES) free(sb->ds);
+ free(sb->alv);
+ free(sb->skewtab);
+ free(sb->dir);
+ if (sb->passwdLength) free(sb->passwd);
+}
+/*}}}*/
+
+/* cpmNamei -- map name to inode */ /*{{{*/
+int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i)
+{
+ /* variables */ /*{{{*/
+ int user;
+ char name[8],extension[3];
+ int highestExtno,highestExt=-1,lowestExtno,lowestExt=-1;
+ int protectMode=0;
+ /*}}}*/
+
+ if (!S_ISDIR(dir->mode))
+ {
+ boo="No such file";
+ return -1;
+ }
+ if (strcmp(filename,".")==0 || strcmp(filename,"..")==0) /* root directory */ /*{{{*/
+ {
+ *i=*dir;
+ return 0;
+ }
+ /*}}}*/
+ else if (strcmp(filename,"[passwd]")==0 && dir->sb->passwdLength) /* access passwords */ /*{{{*/
+ {
+ i->attr=0;
+ i->ino=dir->sb->maxdir+1;
+ i->mode=s_ifreg|0444;
+ i->sb=dir->sb;
+ i->atime=i->mtime=i->ctime=0;
+ i->size=i->sb->passwdLength;
+ return 0;
+ }
+ /*}}}*/
+ else if (strcmp(filename,"[label]")==0 && dir->sb->labelLength) /* access label */ /*{{{*/
+ {
+ i->attr=0;
+ i->ino=dir->sb->maxdir+2;
+ i->mode=s_ifreg|0444;
+ i->sb=dir->sb;
+ i->atime=i->mtime=i->ctime=0;
+ i->size=i->sb->labelLength;
+ return 0;
+ }
+ /*}}}*/
+ if (splitFilename(filename,dir->sb->type,name,extension,&user)==-1) return -1;
+ /* find highest and lowest extent */ /*{{{*/
+ {
+ int extent;
+
+ i->size=0;
+ extent=-1;
+ highestExtno=-1;
+ lowestExtno=2049;
+ while ((extent=findFileExtent(dir->sb,user,name,extension,extent+1,-1))!=-1)
+ {
+ int extno=EXTENT(dir->sb->dir[extent].extnol,dir->sb->dir[extent].extnoh);
+
+ if (extno>highestExtno)
+ {
+ highestExtno=extno;
+ highestExt=extent;
+ }
+ if (extnosize=highestExtno*16384;
+ if (dir->sb->size<256) for (block=15; block>=0; --block)
+ {
+ if (dir->sb->dir[highestExt].pointers[block]) break;
+ }
+ else for (block=7; block>=0; --block)
+ {
+ if (dir->sb->dir[highestExt].pointers[2*block] || dir->sb->dir[highestExt].pointers[2*block+1]) break;
+ }
+ if (dir->sb->dir[highestExt].blkcnt) i->size+=((dir->sb->dir[highestExt].blkcnt&0xff)-1)*128;
+ if (dir->sb->type & CPMFS_ISX)
+ {
+ i->size += (128 - dir->sb->dir[highestExt].lrc);
+ }
+ else
+ {
+ i->size+=dir->sb->dir[highestExt].lrc ? (dir->sb->dir[highestExt].lrc&0xff) : 128;
+ }
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"cpmNamei: size=%ld\n",(long)i->size);
+#endif
+ }
+ /*}}}*/
+ i->ino=lowestExt;
+ i->mode=s_ifreg;
+ i->sb=dir->sb;
+
+ /* read timestamps */ /*{{{*/
+ protectMode = readTimeStamps(i,lowestExt);
+ /*}}}*/
+
+ /* Determine the inode attributes */
+ i->attr = 0;
+ if (dir->sb->dir[lowestExt].name[0]&0x80) i->attr |= CPM_ATTR_F1;
+ if (dir->sb->dir[lowestExt].name[1]&0x80) i->attr |= CPM_ATTR_F2;
+ if (dir->sb->dir[lowestExt].name[2]&0x80) i->attr |= CPM_ATTR_F3;
+ if (dir->sb->dir[lowestExt].name[3]&0x80) i->attr |= CPM_ATTR_F4;
+ if (dir->sb->dir[lowestExt].ext [0]&0x80) i->attr |= CPM_ATTR_RO;
+ if (dir->sb->dir[lowestExt].ext [1]&0x80) i->attr |= CPM_ATTR_SYS;
+ if (dir->sb->dir[lowestExt].ext [2]&0x80) i->attr |= CPM_ATTR_ARCV;
+ if (protectMode&0x20) i->attr |= CPM_ATTR_PWDEL;
+ if (protectMode&0x40) i->attr |= CPM_ATTR_PWWRITE;
+ if (protectMode&0x80) i->attr |= CPM_ATTR_PWREAD;
+
+ if (dir->sb->dir[lowestExt].ext[1]&0x80) i->mode|=01000;
+ i->mode|=0444;
+ if (!(dir->sb->dir[lowestExt].ext[0]&0x80)) i->mode|=0222;
+ if (extension[0]=='C' && extension[1]=='O' && extension[2]=='M') i->mode|=0111;
+
+ readDsStamps(i,lowestExt);
+
+ return 0;
+}
+/*}}}*/
+/* cpmStatFS -- statfs */ /*{{{*/
+void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf)
+{
+ int i;
+ struct cpmSuperBlock *d;
+
+ d=ino->sb;
+ buf->f_bsize=d->blksiz;
+ buf->f_blocks=d->size;
+ buf->f_bfree=0;
+ buf->f_bused=-(d->maxdir*32+d->blksiz-1)/d->blksiz;
+ for (i=0; ialvSize; ++i)
+ {
+ int temp,j;
+
+ temp = *(d->alv+i);
+ for (j=0; jsize)
+ {
+ if (1&temp)
+ {
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"cpmStatFS: block %d allocated\n",(i*INTBITS+j));
+#endif
+ ++buf->f_bused;
+ }
+ else ++buf->f_bfree;
+ }
+ temp >>= 1;
+ }
+ }
+ buf->f_bavail=buf->f_bfree;
+ buf->f_files=d->maxdir;
+ buf->f_ffree=0;
+ for (i=0; imaxdir; ++i)
+ {
+ if (d->dir[i].status==(char)0xe5) ++buf->f_ffree;
+ }
+ buf->f_namelen=11;
+}
+/*}}}*/
+/* cpmUnlink -- unlink */ /*{{{*/
+int cpmUnlink(const struct cpmInode *dir, const char *fname)
+{
+ int user;
+ char name[8],extension[3];
+ int extent;
+ struct cpmSuperBlock *drive;
+
+ if (!S_ISDIR(dir->mode))
+ {
+ boo="No such file";
+ return -1;
+ }
+ drive=dir->sb;
+ if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
+ if ((extent=findFileExtent(drive,user,name,extension,0,-1))==-1) return -1;
+ drive->dirtyDirectory=1;
+ drive->dir[extent].status=(char)0xe5;
+ do
+ {
+ drive->dir[extent].status=(char)0xe5;
+ } while ((extent=findFileExtent(drive,user,name,extension,extent+1,-1))>=0);
+ alvInit(drive);
+ return 0;
+}
+/*}}}*/
+/* cpmRename -- rename */ /*{{{*/
+int cpmRename(const struct cpmInode *dir, const char *old, const char *new)
+{
+ struct cpmSuperBlock *drive;
+ int extent;
+ int olduser;
+ char oldname[8], oldext[3];
+ int newuser;
+ char newname[8], newext[3];
+
+ if (!S_ISDIR(dir->mode))
+ {
+ boo="No such file";
+ return -1;
+ }
+ drive=dir->sb;
+ if (splitFilename(old,dir->sb->type, oldname, oldext,&olduser)==-1) return -1;
+ if (splitFilename(new,dir->sb->type, newname, newext,&newuser)==-1) return -1;
+ if ((extent=findFileExtent(drive,olduser,oldname,oldext,0,-1))==-1) return -1;
+ if (findFileExtent(drive,newuser,newname, newext,0,-1)!=-1)
+ {
+ boo="file already exists";
+ return -1;
+ }
+ do
+ {
+ drive->dirtyDirectory=1;
+ drive->dir[extent].status=newuser;
+ memcpy7(drive->dir[extent].name, newname, 8);
+ memcpy7(drive->dir[extent].ext, newext, 3);
+ } while ((extent=findFileExtent(drive,olduser,oldname,oldext,extent+1,-1))!=-1);
+ return 0;
+}
+/*}}}*/
+/* cpmOpendir -- opendir */ /*{{{*/
+int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp)
+{
+ if (!S_ISDIR(dir->mode))
+ {
+ boo="No such file";
+ return -1;
+ }
+ dirp->ino=dir;
+ dirp->pos=0;
+ dirp->mode=O_RDONLY;
+ return 0;
+}
+/*}}}*/
+/* cpmReaddir -- readdir */ /*{{{*/
+int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent)
+{
+ /* variables */ /*{{{*/
+ struct PhysDirectoryEntry *cur=(struct PhysDirectoryEntry*)0;
+ char buf[2+8+1+3+1]; /* 00foobarxy.zzy\0 */
+ char *bufp;
+ int hasext;
+ /*}}}*/
+
+ if (!(S_ISDIR(dir->ino->mode))) /* error: not a directory */ /*{{{*/
+ {
+ boo="not a directory";
+ return -1;
+ }
+ /*}}}*/
+ while (1)
+ {
+ int i;
+
+ if (dir->pos==0) /* first entry is . */ /*{{{*/
+ {
+ ent->ino=dir->ino->sb->maxdir;
+ ent->reclen=1;
+ strcpy(ent->name,".");
+ ent->off=dir->pos;
+ ++dir->pos;
+ return 1;
+ }
+ /*}}}*/
+ else if (dir->pos==1) /* next entry is .. */ /*{{{*/
+ {
+ ent->ino=dir->ino->sb->maxdir;
+ ent->reclen=2;
+ strcpy(ent->name,"..");
+ ent->off=dir->pos;
+ ++dir->pos;
+ return 1;
+ }
+ /*}}}*/
+ else if (dir->pos==2)
+ {
+ if (dir->ino->sb->passwdLength) /* next entry is [passwd] */ /*{{{*/
+ {
+ ent->ino=dir->ino->sb->maxdir+1;
+ ent->reclen=8;
+ strcpy(ent->name,"[passwd]");
+ ent->off=dir->pos;
+ ++dir->pos;
+ return 1;
+ }
+ /*}}}*/
+ }
+ else if (dir->pos==3)
+ {
+ if (dir->ino->sb->labelLength) /* next entry is [label] */ /*{{{*/
+ {
+ ent->ino=dir->ino->sb->maxdir+2;
+ ent->reclen=7;
+ strcpy(ent->name,"[label]");
+ ent->off=dir->pos;
+ ++dir->pos;
+ return 1;
+ }
+ /*}}}*/
+ }
+ else if (dir->pos>=RESERVED_ENTRIES && dir->pos<(int)dir->ino->sb->maxdir+RESERVED_ENTRIES)
+ {
+ int first=dir->pos-RESERVED_ENTRIES;
+
+ if ((cur=dir->ino->sb->dir+(dir->pos-RESERVED_ENTRIES))->status>=0 && cur->status<=(dir->ino->sb->type&CPMFS_HI_USER ? 31 : 15))
+ {
+ /* determine first extent for the current file */ /*{{{*/
+ for (i=0; iino->sb->maxdir; ++i) if (i!=(dir->pos-RESERVED_ENTRIES))
+ {
+ if (isMatching(cur->status,cur->name,cur->ext,dir->ino->sb->dir[i].status,dir->ino->sb->dir[i].name,dir->ino->sb->dir[i].ext) && EXTENT(cur->extnol,cur->extnoh)>EXTENT(dir->ino->sb->dir[i].extnol,dir->ino->sb->dir[i].extnoh)) first=i;
+ }
+ /*}}}*/
+ if (first==(dir->pos-RESERVED_ENTRIES))
+ {
+ ent->ino=dir->pos-RESERVED_INODES;
+ /* convert file name to UNIX style */ /*{{{*/
+ buf[0]='0'+cur->status/10;
+ buf[1]='0'+cur->status%10;
+ for (bufp=buf+2,i=0; i<8 && (cur->name[i]&0x7f)!=' '; ++i) *bufp++=tolower(cur->name[i]&0x7f);
+ for (hasext=0,i=0; i<3 && (cur->ext[i]&0x7f)!=' '; ++i)
+ {
+ if (!hasext) { *bufp++='.'; hasext=1; }
+ *bufp++=tolower(cur->ext[i]&0x7f);
+ }
+ *bufp='\0';
+ /*}}}*/
+ assert(bufp<=buf+sizeof(buf));
+ ent->reclen=bufp-buf;
+ strcpy(ent->name,buf);
+ ent->off=dir->pos;
+ ++dir->pos;
+ return 1;
+ }
+ }
+ }
+ else return 0;
+ ++dir->pos;
+ }
+}
+/*}}}*/
+/* cpmStat -- stat */ /*{{{*/
+void cpmStat(const struct cpmInode *ino, struct cpmStat *buf)
+{
+ buf->ino=ino->ino;
+ buf->mode=ino->mode;
+ buf->size=ino->size;
+ buf->atime=ino->atime;
+ buf->mtime=ino->mtime;
+ buf->ctime=ino->ctime;
+}
+/*}}}*/
+/* cpmOpen -- open */ /*{{{*/
+int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode)
+{
+ if (S_ISREG(ino->mode))
+ {
+ if ((mode&O_WRONLY) && (ino->mode&0222)==0)
+ {
+ boo="permission denied";
+ return -1;
+ }
+ file->pos=0;
+ file->ino=ino;
+ file->mode=mode;
+ return 0;
+ }
+ else
+ {
+ boo="not a regular file";
+ return -1;
+ }
+}
+/*}}}*/
+/* cpmRead -- read */ /*{{{*/
+int cpmRead(struct cpmFile *file, char *buf, int count)
+{
+ int findext=1,findblock=1,extent=-1,block=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
+ int blocksize=file->ino->sb->blksiz;
+ int extcap;
+
+ extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize;
+ if (extcap>16384) extcap=16384*file->ino->sb->extents;
+ if (file->ino->ino==(ino_t)file->ino->sb->maxdir+1) /* [passwd] */ /*{{{*/
+ {
+ if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos;
+ if (count) memcpy(buf,file->ino->sb->passwd+file->pos,count);
+ file->pos+=count;
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"cpmRead passwd: read %d bytes, now at position %ld\n",count,(long)file->pos);
+#endif
+ return count;
+ }
+ /*}}}*/
+ else if (file->ino->ino==(ino_t)file->ino->sb->maxdir+2) /* [label] */ /*{{{*/
+ {
+ if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos;
+ if (count) memcpy(buf,file->ino->sb->label+file->pos,count);
+ file->pos+=count;
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"cpmRead label: read %d bytes, now at position %ld\n",count,(long)file->pos);
+#endif
+ return count;
+ }
+ /*}}}*/
+ else while (count>0 && file->posino->size)
+ {
+ char buffer[16384];
+
+ if (findext)
+ {
+ extentno=file->pos/16384;
+ extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno);
+ nextextpos=(file->pos/extcap)*extcap+extcap;
+ findext=0;
+ findblock=1;
+ }
+ if (findblock)
+ {
+ if (extent!=-1)
+ {
+ int start,end,ptr;
+
+ ptr=(file->pos%extcap)/blocksize;
+ if (file->ino->sb->size>=256) ptr*=2;
+ block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
+ if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
+ if (block==0)
+ {
+ memset(buffer,0,blocksize);
+ }
+ else
+ {
+ start=(file->pos%blocksize)/file->ino->sb->secLength;
+ end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength;
+ if (readBlock(file->ino->sb,block,buffer,start,end)==-1)
+ {
+ if (got==0) got=-1;
+ break;
+ }
+ }
+ }
+ nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
+ findblock=0;
+ }
+ if (file->pospos%blocksize];
+ ++file->pos;
+ ++got;
+ --count;
+ }
+ else if (file->pos==nextextpos) findext=1; else findblock=1;
+ }
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"cpmRead: read %d bytes, now at position %ld\n",got,(long)file->pos);
+#endif
+ return got;
+}
+/*}}}*/
+/* cpmWrite -- write */ /*{{{*/
+int cpmWrite(struct cpmFile *file, const char *buf, int count)
+{
+ int findext=1,findblock=-1,extent=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1;
+ int blocksize=file->ino->sb->blksiz;
+ int extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize;
+ int block=-1,start=-1,end=-1,ptr=-1,last=-1;
+ char buffer[16384];
+
+ while (count>0)
+ {
+ if (findext) /*{{{*/
+ {
+ extentno=file->pos/16384;
+ extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno);
+ nextextpos=(file->pos/extcap)*extcap+extcap;
+ if (extent==-1)
+ {
+ if ((extent=findFreeExtent(file->ino->sb))==-1) return (got==0 ? -1 : got);
+ file->ino->sb->dir[extent]=file->ino->sb->dir[file->ino->ino];
+ memset(file->ino->sb->dir[extent].pointers,0,16);
+ file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
+ file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
+ file->ino->sb->dir[extent].blkcnt=0;
+ file->ino->sb->dir[extent].lrc=0;
+ time(&file->ino->ctime);
+ updateTimeStamps(file->ino,extent);
+ updateDsStamps(file->ino,extent);
+ }
+ findext=0;
+ findblock=1;
+ }
+ /*}}}*/
+ if (findblock) /*{{{*/
+ {
+ ptr=(file->pos%extcap)/blocksize;
+ if (file->ino->sb->size>=256) ptr*=2;
+ block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr];
+ if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8;
+ if (block==0) /* allocate new block, set start/end to cover it */ /*{{{*/
+ {
+ if ((block=allocBlock(file->ino->sb))==-1) return (got==0 ? -1 : got);
+ file->ino->sb->dir[extent].pointers[ptr]=block&0xff;
+ if (file->ino->sb->size>=256) file->ino->sb->dir[extent].pointers[ptr+1]=(block>>8)&0xff;
+ start=0;
+ end=(blocksize-1)/file->ino->sb->secLength;
+ memset(buffer,0,blocksize);
+ time(&file->ino->ctime);
+ updateTimeStamps(file->ino,extent);
+ updateDsStamps(file->ino,extent);
+ }
+ /*}}}*/
+ else /* read existing block and set start/end to cover modified parts */ /*{{{*/
+ {
+ start=(file->pos%blocksize)/file->ino->sb->secLength;
+ end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength;
+ if (file->pos%file->ino->sb->secLength)
+ {
+ if (readBlock(file->ino->sb,block,buffer,start,start)==-1)
+ {
+ if (got==0) got=-1;
+ break;
+ }
+ }
+ if (end!=start && (file->pos+count-1)ino->sb,block,buffer+end*file->ino->sb->secLength,end,end)==-1)
+ {
+ if (got==0) got=-1;
+ break;
+ }
+ }
+ }
+ /*}}}*/
+ nextblockpos=(file->pos/blocksize)*blocksize+blocksize;
+ findblock=0;
+ }
+ /*}}}*/
+ /* fill block and write it */ /*{{{*/
+ file->ino->sb->dirtyDirectory=1;
+ while (file->pos!=nextblockpos && count)
+ {
+ buffer[file->pos%blocksize]=*buf++;
+ ++file->pos;
+ if (file->ino->sizepos) file->ino->size=file->pos;
+ ++got;
+ --count;
+ }
+ (void)writeBlock(file->ino->sb,block,buffer,start,end);
+ time(&file->ino->mtime);
+ if (file->ino->sb->size<256) for (last=15; last>=0; --last)
+ {
+ if (file->ino->sb->dir[extent].pointers[last])
+ {
+ break;
+ }
+ }
+ else for (last=14; last>0; last-=2)
+ {
+ if (file->ino->sb->dir[extent].pointers[last] || file->ino->sb->dir[extent].pointers[last+1])
+ {
+ last/=2;
+ break;
+ }
+ }
+ if (last>0) extentno+=(last*blocksize)/extcap;
+ file->ino->sb->dir[extent].extnol=EXTENTL(extentno);
+ file->ino->sb->dir[extent].extnoh=EXTENTH(extentno);
+ file->ino->sb->dir[extent].blkcnt=((file->pos-1)%16384)/128+1;
+ if (file->ino->sb->type & CPMFS_EXACT_SIZE)
+ {
+ file->ino->sb->dir[extent].lrc = (128 - (file->pos%128)) & 0x7F;
+ }
+ else
+ {
+ file->ino->sb->dir[extent].lrc=file->pos%128;
+ }
+ updateTimeStamps(file->ino,extent);
+ updateDsStamps(file->ino,extent);
+ /*}}}*/
+ if (file->pos==nextextpos) findext=1;
+ else if (file->pos==nextblockpos) findblock=1;
+ }
+ return got;
+}
+/*}}}*/
+/* cpmClose -- close */ /*{{{*/
+int cpmClose(struct cpmFile *file)
+{
+ return 0;
+}
+/*}}}*/
+/* cpmCreat -- creat */ /*{{{*/
+int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode)
+{
+ int user;
+ char name[8],extension[3];
+ int extent;
+ struct cpmSuperBlock *drive;
+ struct PhysDirectoryEntry *ent;
+
+ if (!S_ISDIR(dir->mode))
+ {
+ boo="No such file or directory";
+ return -1;
+ }
+ if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1;
+#ifdef CPMFS_DEBUG
+ fprintf(stderr,"cpmCreat: %s -> %d:%-.8s.%-.3s\n",fname,user,name,extension);
+#endif
+ if (findFileExtent(dir->sb,user,name,extension,0,-1)!=-1) return -1;
+ drive=dir->sb;
+ if ((extent=findFreeExtent(dir->sb))==-1) return -1;
+ ent=dir->sb->dir+extent;
+ drive->dirtyDirectory=1;
+ memset(ent,0,32);
+ ent->status=user;
+ memcpy(ent->name,name,8);
+ memcpy(ent->ext,extension,3);
+ ino->ino=extent;
+ ino->mode=s_ifreg|mode;
+ ino->size=0;
+
+ time(&ino->atime);
+ time(&ino->mtime);
+ time(&ino->ctime);
+ ino->sb=dir->sb;
+ updateTimeStamps(ino,extent);
+ updateDsStamps(ino,extent);
+ return 0;
+}
+/*}}}*/
+
+/* cpmAttrGet -- get CP/M attributes */ /*{{{*/
+int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib)
+{
+ *attrib = ino->attr;
+ return 0;
+}
+/*}}}*/
+/* cpmAttrSet -- set CP/M attributes */ /*{{{*/
+int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib)
+{
+ struct cpmSuperBlock *drive;
+ int extent;
+ int user;
+ char name[8], extension[3];
+
+ memset(name, 0, sizeof(name));
+ memset(extension, 0, sizeof(extension));
+ drive = ino->sb;
+ extent = ino->ino;
+
+ drive->dirtyDirectory=1;
+ /* Strip off existing attribute bits */
+ memcpy7(name, drive->dir[extent].name, 8);
+ memcpy7(extension, drive->dir[extent].ext, 3);
+ user = drive->dir[extent].status;
+
+ /* And set new ones */
+ if (attrib & CPM_ATTR_F1) name[0] |= 0x80;
+ if (attrib & CPM_ATTR_F2) name[1] |= 0x80;
+ if (attrib & CPM_ATTR_F3) name[2] |= 0x80;
+ if (attrib & CPM_ATTR_F4) name[3] |= 0x80;
+ if (attrib & CPM_ATTR_RO) extension[0] |= 0x80;
+ if (attrib & CPM_ATTR_SYS) extension[1] |= 0x80;
+ if (attrib & CPM_ATTR_ARCV) extension[2] |= 0x80;
+
+ do
+ {
+ memcpy(drive->dir[extent].name, name, 8);
+ memcpy(drive->dir[extent].ext, extension, 3);
+ } while ((extent=findFileExtent(drive, user,name,extension,extent+1,-1))!=-1);
+
+ /* Update the stored (inode) copies of the file attributes and mode */
+ ino->attr=attrib;
+ if (attrib&CPM_ATTR_RO) ino->mode&=~(S_IWUSR|S_IWGRP|S_IWOTH);
+ else ino->mode|=(S_IWUSR|S_IWGRP|S_IWOTH);
+
+ return 0;
+}
+/*}}}*/
+/* cpmChmod -- set CP/M r/o & sys */ /*{{{*/
+int cpmChmod(struct cpmInode *ino, mode_t mode)
+{
+ /* Convert the chmod() into a chattr() call that affects RO */
+ int newatt = ino->attr & ~CPM_ATTR_RO;
+
+ if (!(mode & (S_IWUSR|S_IWGRP|S_IWOTH))) newatt |= CPM_ATTR_RO;
+ return cpmAttrSet(ino, newatt);
+}
+/*}}}*/
+/* cpmUtime -- set timestamps */ /*{{{*/
+void cpmUtime(struct cpmInode *ino, struct utimbuf *times)
+{
+ ino->atime = times->actime;
+ ino->mtime = times->modtime;
+ time(&ino->ctime);
+ updateTimeStamps(ino,ino->ino);
+ updateDsStamps(ino,ino->ino);
+}
+/*}}}*/
diff --git a/Tools/unix/cpmtools/cpmfs.h b/Tools/unix/cpmtools/cpmfs.h
new file mode 100644
index 00000000..06126c86
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmfs.h
@@ -0,0 +1,206 @@
+#ifndef CPMFS_H
+#define CPMFS_H
+
+#include
+#include
+#include
+
+#ifdef _WIN32
+ #include
+ #include
+ /* To make it compile on NT: extracts from Linux 2.0 *
+ * and */
+ #define __S_IFMT 0170000 /* These bits determine file type. */
+ #define __S_IFDIR 0040000 /* Directory. */
+ #define __S_IFREG 0100000 /* Regular file. */
+ #define __S_IWUSR 0000200 /* Writable for user. */
+ #define __S_IWGRP 0000200 /* Writable for group. */
+ #define __S_IWOTH 0000200 /* Writable for others. */
+
+ #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask))
+ #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask))
+ /* These bits are defined in Borland C++ 5 but not in MS Visual C++ */
+ #ifndef S_ISDIR
+ # define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
+ #endif
+ #ifndef S_ISREG
+ # define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
+ #endif
+ #ifndef S_IWUSR
+ #define S_IWUSR __S_IWUSR
+ #endif
+ #ifndef S_IWGRP
+ #define S_IWGRP __S_IWGRP
+ #endif
+ #ifndef S_IWOTH
+ #define S_IWOTH __S_IWOTH
+ #endif
+
+ #include /* For open(), lseek() etc. */
+ #ifndef HAVE_MODE_T
+ typedef int mode_t;
+ #endif
+#endif
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#include "device.h"
+
+/* CP/M file attributes */
+#define CPM_ATTR_F1 1
+#define CPM_ATTR_F2 2
+#define CPM_ATTR_F3 4
+#define CPM_ATTR_F4 8
+/* F5-F8 are banned in CP/M 2 & 3, F7 is used by ZSDOS */
+#define CPM_ATTR_RO 256 /* Read-only */
+#define CPM_ATTR_SYS 512 /* System */
+#define CPM_ATTR_ARCV 1024 /* Archive */
+#define CPM_ATTR_PWDEL 2048 /* Password required to delete */
+#define CPM_ATTR_PWWRITE 4096 /* Password required to write */
+#define CPM_ATTR_PWREAD 8192 /* Password required to read */
+
+typedef int cpm_attr_t;
+
+struct cpmInode
+{
+ ino_t ino;
+ mode_t mode;
+ off_t size;
+ cpm_attr_t attr;
+ time_t atime;
+ time_t mtime;
+ time_t ctime;
+ struct cpmSuperBlock *sb;
+};
+
+struct cpmFile
+{
+ mode_t mode;
+ off_t pos;
+ struct cpmInode *ino;
+};
+
+struct cpmDirent
+{
+ ino_t ino;
+ off_t off;
+ size_t reclen;
+ char name[2+8+1+3+1]; /* 00foobarxy.zzy\0 */
+};
+
+struct cpmStat
+{
+ ino_t ino;
+ mode_t mode;
+ off_t size;
+ time_t atime;
+ time_t mtime;
+ time_t ctime;
+};
+
+#define CPMFS_HI_USER (0x1<<0) /* has user numbers up to 31 */
+#define CPMFS_CPM3_DATES (0x1<<1) /* has CP/M+ style time stamps */
+#define CPMFS_CPM3_OTHER (0x1<<2) /* has passwords and disc label */
+#define CPMFS_DS_DATES (0x1<<3) /* has datestamper timestamps */
+#define CPMFS_EXACT_SIZE (0x1<<4) /* has reverse exact file size */
+
+#define CPMFS_DR22 0
+#define CPMFS_P2DOS (CPMFS_CPM3_DATES|CPMFS_HI_USER)
+#define CPMFS_DR3 (CPMFS_CPM3_DATES|CPMFS_CPM3_OTHER|CPMFS_HI_USER)
+#define CPMFS_ISX (CPMFS_EXACT_SIZE)
+#define CPMFS_ZSYS (CPMFS_HI_USER)
+
+struct dsEntry
+{
+ char year;
+ char month;
+ char day;
+ char hour;
+ char minute;
+};
+
+struct dsDate
+{
+ struct dsEntry create;
+ struct dsEntry access;
+ struct dsEntry modify;
+ char checksum;
+};
+
+struct cpmSuperBlock
+{
+ struct Device dev;
+
+ int secLength;
+ int tracks;
+ int sectrk;
+ int blksiz;
+ int maxdir;
+ int skew;
+ int boottrk;
+ off_t offset;
+ int type;
+ int size;
+ int extents; /* logical extents per physical extent */
+ struct PhysDirectoryEntry *dir;
+ int alvSize;
+ int *alv;
+ int *skewtab;
+ int cnotatime;
+ char *label;
+ size_t labelLength;
+ char *passwd;
+ size_t passwdLength;
+ struct cpmInode *root;
+ int dirtyDirectory;
+ struct dsDate *ds;
+ int dirtyDs;
+ char libdskGeometry[256];
+};
+
+struct cpmStatFS
+{
+ long f_bsize;
+ long f_blocks;
+ long f_bfree;
+ long f_bused;
+ long f_bavail;
+ long f_files;
+ long f_ffree;
+ long f_namelen;
+};
+
+extern const char cmd[];
+extern const char *boo;
+
+int match(const char *a, const char *pattern);
+void cpmglob(int opti, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv);
+
+int cpmReadSuper(struct cpmSuperBlock *drive, struct cpmInode *root, const char *format);
+int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i);
+void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf);
+int cpmUnlink(const struct cpmInode *dir, const char *fname);
+int cpmRename(const struct cpmInode *dir, const char *old, const char *newname);
+int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp);
+int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent);
+void cpmStat(const struct cpmInode *ino, struct cpmStat *buf);
+int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib);
+int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib);
+int cpmChmod(struct cpmInode *ino, mode_t mode);
+int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode);
+int cpmRead(struct cpmFile *file, char *buf, int count);
+int cpmWrite(struct cpmFile *file, const char *buf, int count);
+int cpmClose(struct cpmFile *file);
+int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode);
+void cpmUtime(struct cpmInode *ino, struct utimbuf *times);
+int cpmSync(struct cpmSuperBlock *sb);
+void cpmUmount(struct cpmSuperBlock *sb);
+int cpmCheckDs(struct cpmSuperBlock *sb);
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
diff --git a/Tools/unix/cpmtools/cpmls.1 b/Tools/unix/cpmtools/cpmls.1
new file mode 100644
index 00000000..91326400
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmls.1
@@ -0,0 +1,75 @@
+.TH CPMLS 1 "October 25, 2014" "CP/M tools" "User commands"
+.SH NAME \"{{{roff}}}\"{{{
+cpmls \- list sorted contents of directory
+.\"}}}
+.SH SYNOPSIS \"{{{
+.ad l
+.B cpmls
+.RB [ \-f
+.IR format ]
+.RB [ \-T
+.IR libdsk-type ]
+.RB [ \-d | \-D | \-F | \-A | \-l [ \-c ][ \-i ]]
+.I image
+.RI [ file-pattern "...]"
+.ad b
+.\"}}}
+.SH DESCRIPTION \"{{{
+\fBCpmls\fP lists the sorted contents of the directory.
+.\"}}}
+.SH OPTIONS \"{{{
+.IP "\fB\-f\fP \fIformat\fP"
+Use the given CP/M disk \fIformat\fP instead of the default format.
+.IP "\fB\-T\fP \fIlibdsk-type\fP"
+libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images
+(requires building cpmtools with support for libdsk).
+.IP \fB\-d\fP
+Old CP/M 2.2 dir output.
+.IP \fB\-D\fP
+P2DOS 2.3 ddir-like output.
+.IP \fB\-F\fp
+CP/M 3.x dir output.
+.IP \fB\-A\fp
+E2fs lsattr-like output.
+.IP \fB\-l\fP
+Long UNIX-style directory listing including size, time stamp and user number.
+.IP \fB\-c\fP
+Output the creation time, not the modification time.
+.IP \fB\-i\fP
+Print index number of each file.
+.\"}}}
+.SH "RETURN VALUE" \"{{{
+Upon successful completion, exit code 0 is returned.
+.\"}}}
+.SH ERRORS \"{{{
+Any errors are indicated by exit code 1.
+.\"}}}
+.SH ENVIRONMENT \"{{{
+CPMTOOLSFMT Default format
+.\"}}}
+.SH FILES \"{{{
+${prefix}/share/diskdefs CP/M disk format definitions
+.\"}}}
+.SH AUTHORS \"{{{
+This program is copyright 1997\(en2012 Michael Haardt
+. The Windows port is copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR cpmcp (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/cpmls.c b/Tools/unix/cpmtools/cpmls.c
new file mode 100644
index 00000000..3721d2e0
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmls.c
@@ -0,0 +1,400 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+/* variables */ /*{{{*/
+static const char * const month[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" };
+/*}}}*/
+
+/* namecmp -- compare two entries */ /*{{{*/
+static int namecmp(const void *a, const void *b)
+{
+ if (**((const char * const *)a)=='[') return -1;
+ return strcmp(*((const char * const *)a),*((const char * const *)b));
+}
+/*}}}*/
+/* olddir -- old style output */ /*{{{*/
+static void olddir(char **dirent, int entries)
+{
+ int i,j,k,l,user,announce;
+
+ announce=0;
+ for (user=0; user<32; ++user)
+ {
+ for (i=l=0; itm_mday,month[tmp->tm_mon],tmp->tm_year+1900,tmp->tm_hour,tmp->tm_min);
+ }
+ else if (statbuf.ctime) printf(" ");
+ if (statbuf.ctime)
+ {
+ tmp=localtime(&statbuf.ctime);
+ printf(" %02d-%s-%04d %02d:%02d",tmp->tm_mday,month[tmp->tm_mon],tmp->tm_year+1900,tmp->tm_hour,tmp->tm_min);
+ }
+ putchar('\n');
+ ++l;
+ }
+ }
+ if (announce==2) announce=1;
+ }
+ printf("%5.1d Files occupying %6.1ldK",l,(buf.f_bused*buf.f_bsize)/1024);
+ printf(", %7.1ldK Free.\n",(buf.f_bfree*buf.f_bsize)/1024);
+ }
+ else printf("No files found\n");
+}
+/*}}}*/
+/* old3dir -- old CP/M Plus style long output */ /*{{{*/
+static void old3dir(char **dirent, int entries, struct cpmInode *ino)
+{
+ struct cpmStatFS buf;
+ struct cpmStat statbuf;
+ struct cpmInode file;
+
+ if (entries)
+ {
+ int i,j,k,l,announce,user, attrib;
+ int totalBytes=0,totalRecs=0;
+
+ qsort(dirent,entries,sizeof(char*),namecmp);
+ cpmStatFS(ino,&buf);
+ announce=1;
+ for (l=0,user=0; user<32; ++user)
+ {
+ for (i=0; itm_mon+1,tmp->tm_mday,tmp->tm_year%100,tmp->tm_hour,tmp->tm_min);
+ }
+ else if (statbuf.ctime) printf(" ");
+ if (statbuf.ctime)
+ {
+ tmp=localtime(&statbuf.ctime);
+ printf("%02d/%02d/%02d %02d:%02d",tmp->tm_mon+1,tmp->tm_mday,tmp->tm_year%100,tmp->tm_hour,tmp->tm_min);
+ }
+ putchar('\n');
+ ++l;
+ }
+ }
+ if (announce==2) announce=1;
+ }
+ printf("\nTotal Bytes = %6.1dk ",(totalBytes+1023)/1024);
+ printf("Total Records = %7.1d ",totalRecs);
+ printf("Files Found = %4.1d\n",l);
+ printf("Total 1k Blocks = %6.1ld ",(buf.f_bused*buf.f_bsize)/1024);
+ printf("Used/Max Dir Entries For Drive A: %4.1ld/%4.1ld\n",buf.f_files-buf.f_ffree,buf.f_files);
+ }
+ else printf("No files found\n");
+}
+/*}}}*/
+/* ls -- UNIX style output */ /*{{{*/
+static void ls(char **dirent, int entries, struct cpmInode *ino, int l, int c, int iflag)
+{
+ int i,user,announce,any;
+ time_t now;
+ struct cpmStat statbuf;
+ struct cpmInode file;
+
+ time(&now);
+ qsort(dirent,entries,sizeof(char*),namecmp);
+ announce=0;
+ any=0;
+ for (user=0; user<32; ++user)
+ {
+ announce=0;
+ for (i=0; itm_mon],tmp->tm_mday);
+ if ((c ? statbuf.ctime : statbuf.mtime)<(now-182*24*3600)) printf("%04d ",tmp->tm_year+1900);
+ else printf("%02d:%02d ",tmp->tm_hour,tmp->tm_min);
+ }
+ printf("%s\n",dirent[i]+2);
+ }
+ }
+ }
+}
+/*}}}*/
+/* lsattr -- output something like e2fs lsattr */ /*{{{*/
+static void lsattr(char **dirent, int entries, struct cpmInode *ino)
+{
+ int i,user,announce,any;
+ struct cpmStat statbuf;
+ struct cpmInode file;
+ cpm_attr_t attrib;
+
+ qsort(dirent,entries,sizeof(char*),namecmp);
+ announce=0;
+ any=0;
+ for (user=0; user<32; ++user)
+ {
+ announce=0;
+ for (i=0; i. The Windows port is copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR cpmls (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/cpmrm.c b/Tools/unix/cpmtools/cpmrm.c
new file mode 100644
index 00000000..65430da6
--- /dev/null
+++ b/Tools/unix/cpmtools/cpmrm.c
@@ -0,0 +1,77 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+const char cmd[]="cpmrm";
+
+int main(int argc, char *argv[]) /*{{{*/
+{
+ /* variables */ /*{{{*/
+ const char *err;
+ const char *image;
+ const char *format;
+ const char *devopts=NULL;
+ int c,i,usage=0,exitcode=0;
+ struct cpmSuperBlock drive;
+ struct cpmInode root;
+ int gargc;
+ char **gargv;
+ /*}}}*/
+
+ /* parse options */ /*{{{*/
+ if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
+ while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c)
+ {
+ case 'T': devopts=optarg; break;
+ case 'f': format=optarg; break;
+ case 'h':
+ case '?': usage=1; break;
+ }
+
+ if (optind>=(argc-1)) usage=1;
+ else image=argv[optind++];
+
+ if (usage)
+ {
+ fprintf(stderr,"Usage: %s [-f format] [-T dsktype] image pattern ...\n",cmd);
+ exit(1);
+ }
+ /*}}}*/
+ /* open image */ /*{{{*/
+ if ((err=Device_open(&drive.dev, image, O_RDWR, devopts)))
+ {
+ fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err);
+ exit(1);
+ }
+ if (cpmReadSuper(&drive,&root,format)==-1)
+ {
+ fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
+ exit(1);
+ }
+ /*}}}*/
+ cpmglob(optind,argc,argv,&root,&gargc,&gargv);
+ for (i=0; i
+#include
+#include
+#include
+
+#include "device.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+static const char *lookupFormat(DSK_GEOMETRY *geom, const char *name)
+{
+ dsk_format_t fmt = FMT_180K;
+ const char *fname;
+
+ while (dg_stdformat(NULL, fmt, &fname, NULL) == DSK_ERR_OK)
+ {
+ if (!strcmp(name, fname))
+ {
+ dg_stdformat(geom, fmt, &fname, NULL);
+ return NULL;
+ }
+ ++fmt;
+ }
+ return "Unrecognised LibDsk geometry specification";
+}
+
+/* Device_open -- Open an image file */ /*{{{*/
+const char *Device_open(struct Device *this, const char *filename, int mode, const char *deviceOpts)
+{
+ char *format;
+ char driverName[80];
+ const char *boo;
+ dsk_err_t e;
+
+ /* Assume driver name & format name both fit in 80 characters, rather than
+ * malloccing the exact size */
+ if (deviceOpts == NULL)
+ {
+ e = dsk_open(&this->dev, filename, NULL, NULL);
+ format = NULL;
+ }
+ else
+ {
+ strncpy(driverName, deviceOpts, 79);
+ driverName[79] = 0;
+ format = strchr(driverName, ',');
+ if (format)
+ {
+ *format = 0;
+ ++format;
+ }
+ e = dsk_open(&this->dev, filename, driverName, NULL);
+ }
+ this->opened = 0;
+ if (e) return dsk_strerror(e);
+ this->opened = 1;
+ if (format)
+ {
+ boo = lookupFormat(&this->geom, format);
+ if (boo) return boo;
+ }
+ else
+ {
+ dsk_getgeom(this->dev, &this->geom);
+ }
+ return NULL;
+}
+/*}}}*/
+/* Device_setGeometry -- Set disk geometry */ /*{{{*/
+const char *Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry)
+{
+ char *boo;
+
+ this->secLength=secLength;
+ this->sectrk=sectrk;
+ this->tracks=tracks;
+ /* Must be an even multiple of sector size */
+ assert(offset%secLength==0);
+ this->offset=offset;
+ /* If a geometry is named in diskdefs, use it */
+ if (libdskGeometry && libdskGeometry[0])
+ {
+ return lookupFormat(&this->geom, libdskGeometry);
+ }
+
+ this->geom.dg_secsize = secLength;
+ this->geom.dg_sectors = sectrk;
+ /* Did the autoprobe guess right about the number of sectors & cylinders? */
+ if (this->geom.dg_cylinders * this->geom.dg_heads == tracks) return NULL;
+ /* Otherwise we guess: <= 43 tracks: single-sided. Else double. This
+ * fails for 80-track single-sided if there are any such beasts */
+ if (tracks <= 43)
+ {
+ this->geom.dg_cylinders = tracks;
+ this->geom.dg_heads = 1;
+ }
+ else
+ {
+ this->geom.dg_cylinders = tracks/2;
+ this->geom.dg_heads = 2;
+ }
+ return NULL;
+}
+/*}}}*/
+/* Device_close -- Close an image file */ /*{{{*/
+const char *Device_close(struct Device *this)
+{
+ dsk_err_t e;
+ this->opened=0;
+ e = dsk_close(&this->dev);
+ return (e?dsk_strerror(e):(const char*)0);
+}
+/*}}}*/
+/* Device_readSector -- read a physical sector */ /*{{{*/
+const char *Device_readSector(const struct Device *this, int track, int sector, char *buf)
+{
+ dsk_err_t e;
+ e = dsk_lread(this->dev, &this->geom, buf, (track * this->sectrk) + sector + this->offset/this->secLength);
+ return (e?dsk_strerror(e):(const char*)0);
+}
+/*}}}*/
+/* Device_writeSector -- write physical sector */ /*{{{*/
+const char *Device_writeSector(const struct Device *this, int track, int sector, const char *buf)
+{
+ dsk_err_t e;
+ e = dsk_lwrite(this->dev, &this->geom, buf, (track * this->sectrk) + sector + this->offset/this->secLength);
+ return (e?dsk_strerror(e):(const char*)0);
+}
+/*}}}*/
diff --git a/Tools/unix/cpmtools/device_posix.c b/Tools/unix/cpmtools/device_posix.c
new file mode 100644
index 00000000..5a28dcdc
--- /dev/null
+++ b/Tools/unix/cpmtools/device_posix.c
@@ -0,0 +1,82 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "device.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+/* Device_open -- Open an image file */ /*{{{*/
+const char *Device_open(struct Device *this, const char *filename, int mode, const char *deviceOpts)
+{
+ this->fd=open(filename,mode);
+ this->opened=(this->fd==-1?0:1);
+ return ((this->fd==-1)?strerror(errno):(const char*)0);
+}
+/*}}}*/
+/* Device_setGeometry -- Set disk geometry */ /*{{{*/
+const char *Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry)
+{
+ this->secLength=secLength;
+ this->sectrk=sectrk;
+ this->tracks=tracks;
+ this->offset=offset;
+ return NULL;
+}
+/*}}}*/
+/* Device_close -- Close an image file */ /*{{{*/
+const char *Device_close(struct Device *this)
+{
+ this->opened=0;
+ return ((close(this->fd)==-1)?strerror(errno):(const char*)0);
+}
+/*}}}*/
+/* Device_readSector -- read a physical sector */ /*{{{*/
+const char *Device_readSector(const struct Device *this, int track, int sector, char *buf)
+{
+ int res;
+
+ assert(this);
+ assert(sector>=0);
+ assert(sectorsectrk);
+ assert(track>=0);
+ assert(tracktracks);
+ assert(buf);
+ if (lseek(this->fd,(off_t)(((sector+track*this->sectrk)*this->secLength)+this->offset),SEEK_SET)==-1)
+ {
+ return strerror(errno);
+ }
+ if ((res=read(this->fd, buf, this->secLength)) != this->secLength)
+ {
+ if (res==-1)
+ {
+ return strerror(errno);
+ }
+ else memset(buf+res,0,this->secLength-res); /* hit end of disk image */
+ }
+ return (const char*)0;
+}
+/*}}}*/
+/* Device_writeSector -- write physical sector */ /*{{{*/
+const char *Device_writeSector(const struct Device *this, int track, int sector, const char *buf)
+{
+ assert(sector>=0);
+ assert(sectorsectrk);
+ assert(track>=0);
+ assert(tracktracks);
+ if (lseek(this->fd,(off_t)(((sector+track*this->sectrk)*this->secLength)+this->offset),SEEK_SET)==-1)
+ {
+ return strerror(errno);
+ }
+ if (write(this->fd, buf, this->secLength) == this->secLength) return (const char*)0;
+ return strerror(errno);
+}
+/*}}}*/
diff --git a/Tools/unix/cpmtools/device_win32.c b/Tools/unix/cpmtools/device_win32.c
new file mode 100644
index 00000000..18294432
--- /dev/null
+++ b/Tools/unix/cpmtools/device_win32.c
@@ -0,0 +1,670 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+
+#include "cpmdir.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+/* types */ /*{{{*/
+#define PHYSICAL_SECTOR_1 1 /* First physical sector */
+
+/* Use the INT13 interface rather than INT25/INT26. This appears to
+ * improve performance, but is less well tested. */
+#define USE_INT13
+
+/* Windows 95 disk I/O functions - based on Stan Mitchell's DISKDUMP.C */
+#define VWIN32_DIOC_DOS_IOCTL 1 /* DOS ioctl calls 4400h-4411h */
+#define VWIN32_DIOC_DOS_INT25 2 /* absolute disk read, DOS int 25h */
+#define VWIN32_DIOC_DOS_INT26 3 /* absolute disk write, DOS int 26h */
+#define VWIN32_DIOC_DOS_INT13 4 /* BIOS INT13 functions */
+
+typedef struct _DIOC_REGISTERS {
+ DWORD reg_EBX;
+ DWORD reg_EDX;
+ DWORD reg_ECX;
+ DWORD reg_EAX;
+ DWORD reg_EDI;
+ DWORD reg_ESI;
+ DWORD reg_Flags;
+ }
+ DIOC_REGISTERS, *PDIOC_REGISTERS;
+
+#define LEVEL0_LOCK 0
+#define LEVEL1_LOCK 1
+#define LEVEL2_LOCK 2
+#define LEVEL3_LOCK 3
+#define LEVEL1_LOCK_MAX_PERMISSION 0x0001
+
+#define DRIVE_IS_REMOTE 0x1000
+#define DRIVE_IS_SUBST 0x8000
+
+/*********************************************************
+ **** Note: all MS-DOS data structures must be packed ****
+ **** on a one-byte boundary. ****
+ *********************************************************/
+#pragma pack(1)
+
+typedef struct _DISKIO {
+ DWORD diStartSector; /* sector number to start at */
+ WORD diSectors; /* number of sectors */
+ DWORD diBuffer; /* address of buffer */
+ }
+ DISKIO, *PDISKIO;
+
+typedef struct MID {
+ WORD midInfoLevel; /* information level, must be 0 */
+ DWORD midSerialNum; /* serial number for the medium */
+ char midVolLabel[11]; /* volume label for the medium */
+ char midFileSysType[8]; /* type of file system as 8-byte ASCII */
+ }
+ MID, *PMID;
+
+typedef struct driveparams { /* Disk geometry */
+ BYTE special;
+ BYTE devicetype;
+ WORD deviceattrs;
+ WORD cylinders;
+ BYTE mediatype;
+ /* BPB starts here */
+ WORD bytespersector;
+ BYTE sectorspercluster;
+ WORD reservedsectors;
+ BYTE numberofFATs;
+ WORD rootdirsize;
+ WORD totalsectors;
+ BYTE mediaid;
+ WORD sectorsperfat;
+ WORD sectorspertrack;
+ WORD heads;
+ DWORD hiddensectors;
+ DWORD bigtotalsectors;
+ BYTE reserved[6];
+ /* BPB ends here */
+ WORD sectorcount;
+ WORD sectortable[80];
+ } DRIVEPARAMS, *PDRIVEPARAMS;
+/*}}}*/
+
+static char *strwin32error(void) /*{{{*/
+{
+ static char buffer[1024];
+
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
+ (LPTSTR)buffer,
+ 1023, NULL);
+ return buffer;
+}
+/*}}}*/
+static BOOL LockVolume( HANDLE hDisk ) /*{{{*/
+{
+ DWORD ReturnedByteCount;
+
+ return DeviceIoControl( hDisk, FSCTL_LOCK_VOLUME, NULL, 0, NULL,
+ 0, &ReturnedByteCount, NULL );
+}
+/*}}}*/
+static BOOL UnlockVolume( HANDLE hDisk ) /*{{{*/
+{
+ DWORD ReturnedByteCount;
+
+ return DeviceIoControl( hDisk, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL,
+ 0, &ReturnedByteCount, NULL );
+}
+/*}}}*/
+static BOOL DismountVolume( HANDLE hDisk ) /*{{{*/
+{
+ DWORD ReturnedByteCount;
+
+ return DeviceIoControl( hDisk, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL,
+ 0, &ReturnedByteCount, NULL );
+}
+/*}}}*/
+static int GetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/
+ {
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+ reg.reg_EAX = 0x440d; /* IOCTL for block device */
+ reg.reg_EBX = volume; /* one-based drive number */
+ reg.reg_ECX = 0x0860; /* Get Device params */
+ reg.reg_EDX = (DWORD)pParam;
+ reg.reg_Flags = 1; /* preset the carry flag */
+
+ bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+ if ( !bResult || (reg.reg_Flags & 1) )
+ return (reg.reg_EAX & 0xffff);
+
+ return 0;
+ }
+/*}}}*/
+static int SetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/
+ {
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+ reg.reg_EAX = 0x440d; /* IOCTL for block device */
+ reg.reg_EBX = volume; /* one-based drive number */
+ reg.reg_ECX = 0x0840; /* Set Device params */
+ reg.reg_EDX = (DWORD)pParam;
+ reg.reg_Flags = 1; /* preset the carry flag */
+
+ bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+ if ( !bResult || (reg.reg_Flags & 1) )
+ return (reg.reg_EAX & 0xffff);
+
+ return 0;
+ }
+/*}}}*/
+static int GetMediaID( HANDLE hVWin32Device, int volume, MID* pMid ) /*{{{*/
+ {
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+ reg.reg_EAX = 0x440d; /* IOCTL for block device */
+ reg.reg_EBX = volume; /* one-based drive number */
+ reg.reg_ECX = 0x0866; /* Get Media ID */
+ reg.reg_EDX = (DWORD)pMid;
+ reg.reg_Flags = 1; /* preset the carry flag */
+
+ bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+ if ( !bResult || (reg.reg_Flags & 1) )
+ return (reg.reg_EAX & 0xffff);
+
+ return 0;
+ }
+/*}}}*/
+static int VolumeCheck(HANDLE hVWin32Device, int volume, WORD* flags ) /*{{{*/
+{
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+ reg.reg_EAX = 0x4409; /* Is Drive Remote */
+ reg.reg_EBX = volume; /* one-based drive number */
+ reg.reg_Flags = 1; /* preset the carry flag */
+
+ bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+ if ( !bResult || (reg.reg_Flags & 1) )
+ return (reg.reg_EAX & 0xffff);
+
+ *flags = (WORD)(reg.reg_EDX & 0xffff);
+ return 0;
+}
+/*}}}*/
+static int LockLogicalVolume(HANDLE hVWin32Device, int volume, int lock_level, int permissions) /*{{{*/
+{
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+ reg.reg_EAX = 0x440d; /* generic IOCTL */
+ reg.reg_ECX = 0x084a; /* lock logical volume */
+ reg.reg_EBX = volume | (lock_level << 8);
+ reg.reg_EDX = permissions;
+ reg.reg_Flags = 1; /* preset the carry flag */
+
+ bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+ if ( !bResult || (reg.reg_Flags & 1) )
+ return (reg.reg_EAX & 0xffff);
+
+ return 0;
+}
+/*}}}*/
+static int UnlockLogicalVolume( HANDLE hVWin32Device, int volume ) /*{{{*/
+{
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+ reg.reg_EAX = 0x440d;
+ reg.reg_ECX = 0x086a; /* lock logical volume */
+ reg.reg_EBX = volume;
+ reg.reg_Flags = 1; /* preset the carry flag */
+
+ bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+ if ( !bResult || (reg.reg_Flags & 1) ) return -1;
+ return 0;
+}
+/*}}}*/
+static int w32mode(int mode) /*{{{*/
+{
+ switch(mode)
+ {
+ case O_RDONLY: return GENERIC_READ;
+ case O_WRONLY: return GENERIC_WRITE;
+ }
+ return GENERIC_READ | GENERIC_WRITE;
+}
+/*}}}*/
+
+/* Device_open -- Open an image file */ /*{{{*/
+const char *Device_open(struct Device *sb, const char *filename, int mode, const char *deviceOpts)
+{
+ /* Windows 95/NT: floppy drives using handles */
+ if (strlen(filename) == 2 && filename[1] == ':') /* Drive name */
+ {
+ char vname[20];
+ DWORD dwVers;
+
+ sb->fd = -1;
+ dwVers = GetVersion();
+
+ if (dwVers & 0x80000000L) /* Win32s (3.1) or Win32c (Win95) */
+ {
+ int lock, driveno, res, permissions;
+ unsigned short drive_flags;
+ MID media;
+
+ vname[0] = toupper(filename[0]);
+ driveno = vname[0] - 'A' + 1; /* 1=A: 2=B: */
+ sb->drvtype = CPMDRV_WIN95;
+ sb->hdisk = CreateFile( "\\\\.\\vwin32",
+ 0,
+ 0,
+ NULL,
+ 0,
+ FILE_FLAG_DELETE_ON_CLOSE,
+ NULL );
+ if (!sb->hdisk)
+ {
+ return "Failed to open VWIN32 driver.";
+ }
+ if (VolumeCheck(sb->hdisk, driveno, &drive_flags))
+ {
+ CloseHandle(sb->hdisk);
+ return "Invalid drive";
+ }
+ res = GetMediaID( sb->hdisk, driveno, &media );
+ if ( res )
+ {
+ const char *lboo = NULL;
+
+ if ( res == ERROR_INVALID_FUNCTION &&
+ (drive_flags & DRIVE_IS_REMOTE ))
+ lboo = "Network drive";
+ else if (res == ERROR_ACCESS_DENIED) lboo = "Access denied";
+ /* nb: It's perfectly legitimate for GetMediaID() to fail; most CP/M */
+ /* CP/M disks won't have a media ID. */
+
+ if (lboo != NULL)
+ {
+ CloseHandle(sb->hdisk);
+ return lboo;
+ }
+ }
+ if (!res &&
+ (!memcmp( media.midFileSysType, "CDROM", 5 ) ||
+ !memcmp( media.midFileSysType, "CD001", 5 ) ||
+ !memcmp( media.midFileSysType, "CDAUDIO", 5 )))
+ {
+ CloseHandle(sb->hdisk);
+ return "CD-ROM drive";
+ }
+ if (w32mode(mode) & GENERIC_WRITE)
+ {
+ lock = LEVEL0_LOCK; /* Exclusive access */
+ permissions = 0;
+ }
+ else
+ {
+ lock = LEVEL1_LOCK; /* Allow other processes access */
+ permissions = LEVEL1_LOCK_MAX_PERMISSION;
+ }
+ if (LockLogicalVolume( sb->hdisk, driveno, lock, permissions))
+ {
+ CloseHandle(sb->hdisk);
+ return "Could not acquire a lock on the drive.";
+ }
+
+ sb->fd = driveno; /* 1=A: 2=B: etc - we will need this later */
+
+ }
+ else
+ {
+ sprintf(vname, "\\\\.\\%s", filename);
+ sb->drvtype = CPMDRV_WINNT;
+ sb->hdisk = CreateFile(vname, /* Name */
+ w32mode(mode), /* Access mode */
+ FILE_SHARE_READ|FILE_SHARE_WRITE, /*Sharing*/
+ NULL, /* Security attributes */
+ OPEN_EXISTING, /* See MSDN */
+ 0, /* Flags & attributes */
+ NULL); /* Template file */
+
+ if (sb->hdisk != INVALID_HANDLE_VALUE)
+ {
+ sb->fd = 1; /* Arbitrary value >0 */
+ if (LockVolume(sb->hdisk) == FALSE) /* Lock drive */
+ {
+ char *lboo = strwin32error();
+ CloseHandle(sb->hdisk);
+ sb->fd = -1;
+ return lboo;
+ }
+ }
+ else return strwin32error();
+ }
+ sb->opened = 1;
+ return NULL;
+ }
+
+ /* Not a floppy. Treat it as a normal file */
+
+ mode |= O_BINARY;
+ sb->fd = open(filename, mode);
+ if (sb->fd == -1) return strerror(errno);
+ sb->drvtype = CPMDRV_FILE;
+ sb->opened = 1;
+ return NULL;
+}
+/*}}}*/
+/* Device_setGeometry -- Set disk geometry */ /*{{{*/
+const char * Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry)
+{
+ int n;
+
+ this->secLength=secLength;
+ this->sectrk=sectrk;
+ this->tracks=tracks;
+ // Bill Buckels - add this->offset
+ this->offset=offset;
+
+
+ // Bill Buckels - not sure what to do here
+ if (this->drvtype == CPMDRV_WIN95)
+ {
+ DRIVEPARAMS drvp;
+ memset(&drvp, 0, sizeof(drvp));
+ if (GetDriveParams( this->hdisk, this->fd, &drvp )) return "GetDriveParams failed";
+
+ drvp.bytespersector = secLength;
+ drvp.sectorspertrack = sectrk;
+ drvp.totalsectors = sectrk * tracks;
+
+/* Guess the cylinder/head configuration from the track count. This will
+ * get single-sided 80-track discs wrong, but it's that or double-sided
+ * 40-track (or add cylinder/head counts to diskdefs)
+ */
+ if (tracks < 44)
+ {
+ drvp.cylinders = tracks;
+ drvp.heads = 1;
+ }
+ else
+ {
+ drvp.cylinders = tracks / 2;
+ drvp.heads = 2;
+ }
+
+/* Set up "reasonable" values for the other members */
+
+ drvp.sectorspercluster = 1024 / secLength;
+ drvp.reservedsectors = 1;
+ drvp.numberofFATs = 2;
+ drvp.sectorcount = sectrk;
+ drvp.rootdirsize = 64;
+ drvp.mediaid = 0xF0;
+ drvp.hiddensectors = 0;
+ drvp.sectorsperfat = 3;
+ for (n = 0; n < sectrk; n++)
+ {
+ drvp.sectortable[n*2] = n + PHYSICAL_SECTOR_1; /* Physical sector numbers */
+ drvp.sectortable[n*2+1] = secLength;
+ }
+ drvp.special = 6;
+/* We have not set:
+
+ drvp.mediatype
+ drvp.devicetype
+ drvp.deviceattrs
+
+ which should have been read correctly by GetDriveParams().
+ */
+ SetDriveParams( this->hdisk, this->fd, &drvp );
+ }
+ return NULL;
+}
+/*}}}*/
+/* Device_close -- Close an image file */ /*{{{*/
+const char *Device_close(struct Device *sb)
+{
+ sb->opened = 0;
+ switch(sb->drvtype)
+ {
+ case CPMDRV_WIN95:
+ UnlockLogicalVolume(sb->hdisk, sb->fd );
+ if (!CloseHandle( sb->hdisk )) return strwin32error();
+ return NULL;
+
+ case CPMDRV_WINNT:
+ DismountVolume(sb->hdisk);
+ UnlockVolume(sb->hdisk);
+ if (!CloseHandle(sb->hdisk)) return strwin32error();
+ return NULL;
+ }
+ if (close(sb->fd)) return strerror(errno);
+ return NULL;
+}
+/*}}}*/
+/* Device_readSector -- read a physical sector */ /*{{{*/
+const char *Device_readSector(const struct Device *drive, int track, int sector, char *buf)
+{
+ int res;
+ off_t offset;
+
+ assert(sector>=0);
+ assert(sectorsectrk);
+ assert(track>=0);
+ assert(tracktracks);
+
+ offset = ((sector+track*drive->sectrk)*drive->secLength);
+
+ if (drive->drvtype == CPMDRV_WINNT)
+ {
+ LPVOID iobuffer;
+ DWORD bytesread;
+
+ // Bill Buckels - add drive->offset
+ if (SetFilePointer(drive->hdisk, offset+drive->offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE)
+ {
+ return strwin32error();
+ }
+ iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE);
+ if (!iobuffer)
+ {
+ return strwin32error();
+ }
+ res = ReadFile(drive->hdisk, iobuffer, drive->secLength, &bytesread, NULL);
+ if (!res)
+ {
+ char *lboo = strwin32error();
+ VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
+ return lboo;
+ }
+
+ memcpy(buf, iobuffer, drive->secLength);
+ VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
+
+ if (bytesread < (unsigned)drive->secLength)
+ {
+ memset(buf + bytesread, 0, drive->secLength - bytesread);
+ }
+ return NULL;
+ }
+
+ // Bill Buckels - not sure what to do here
+ if (drive->drvtype == CPMDRV_WIN95)
+ {
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+#ifdef USE_INT13
+ int cyl, head;
+
+ if (drive->tracks < 44) { cyl = track; head = 0; }
+ else { cyl = track/2; head = track & 1; }
+
+ reg.reg_EAX = 0x0201; /* Read 1 sector */
+ reg.reg_EBX = (DWORD)buf;
+ reg.reg_ECX = (cyl << 8) | (sector + PHYSICAL_SECTOR_1);
+ reg.reg_EDX = (head << 8) | (drive->fd - 1);
+ reg.reg_Flags = 1; /* preset the carry flag */
+ bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+#else
+ DISKIO di;
+
+ reg.reg_EAX = drive->fd - 1; /* zero-based volume number */
+ reg.reg_EBX = (DWORD)&di;
+ reg.reg_ECX = 0xffff; /* use DISKIO structure */
+ reg.reg_Flags = 1; /* preset the carry flag */
+ di.diStartSector = sector+track*drive->sectrk;
+ di.diSectors = 1;
+ di.diBuffer = (DWORD)buf;
+ bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT25,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+
+#endif
+ if ( !bResult || (reg.reg_Flags & 1) )
+ {
+ if (GetLastError()) return strwin32error();
+ return "Unknown read error.";
+ }
+ return 0;
+ }
+
+ // Bill Buckels - add drive->offset
+ if (lseek(drive->fd,offset+drive->offset,SEEK_SET)==-1)
+ {
+ return strerror(errno);
+ }
+ if ((res=read(drive->fd, buf, drive->secLength)) != drive->secLength)
+ {
+ if (res==-1)
+ {
+ return strerror(errno);
+ }
+ else memset(buf+res,0,drive->secLength-res); /* hit end of disk image */
+ }
+ return NULL;
+}
+/*}}}*/
+/* Device_writeSector -- write physical sector */ /*{{{*/
+const char *Device_writeSector(const struct Device *drive, int track, int sector, const char *buf)
+{
+ off_t offset;
+ int res;
+
+ assert(sector>=0);
+ assert(sectorsectrk);
+ assert(track>=0);
+ assert(tracktracks);
+
+ offset = ((sector+track*drive->sectrk)*drive->secLength);
+
+ if (drive->drvtype == CPMDRV_WINNT)
+ {
+ LPVOID iobuffer;
+ DWORD byteswritten;
+
+ // Bill Buckels - add drive->offset
+ if (SetFilePointer(drive->hdisk, offset+drive->offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE)
+ {
+ return strwin32error();
+ }
+ iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE);
+ if (!iobuffer)
+ {
+ return strwin32error();
+ }
+ memcpy(iobuffer, buf, drive->secLength);
+ res = WriteFile(drive->hdisk, iobuffer, drive->secLength, &byteswritten, NULL);
+ if (!res || (byteswritten < (unsigned)drive->secLength))
+ {
+ char *lboo = strwin32error();
+ VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
+ return lboo;
+ }
+
+ VirtualFree(iobuffer, drive->secLength, MEM_RELEASE);
+ return NULL;
+ }
+
+ // Bill Buckels - not sure what to do here
+ if (drive->drvtype == CPMDRV_WIN95)
+ {
+ DIOC_REGISTERS reg;
+ BOOL bResult;
+ DWORD cb;
+
+#ifdef USE_INT13
+ int cyl, head;
+
+ if (drive->tracks < 44) { cyl = track; head = 0; }
+ else { cyl = track/2; head = track & 1; }
+
+ reg.reg_EAX = 0x0301; /* Write 1 sector */
+ reg.reg_EBX = (DWORD)buf;
+ reg.reg_ECX = (cyl << 8) | (sector + PHYSICAL_SECTOR_1);
+ reg.reg_EDX = (head << 8) | (drive->fd - 1);
+ reg.reg_Flags = 1; /* preset the carry flag */
+ bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+#else
+ DISKIO di;
+
+ reg.reg_EAX = drive->fd - 1; /* zero-based volume number */
+ reg.reg_EBX = (DWORD)&di;
+ reg.reg_ECX = 0xffff; /* use DISKIO structure */
+ reg.reg_Flags = 1; /* preset the carry flag */
+ di.diStartSector = sector+track*drive->sectrk;
+ di.diSectors = 1;
+ di.diBuffer = (DWORD)buf;
+ bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT26,
+ ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 );
+#endif
+
+ if ( !bResult || (reg.reg_Flags & 1) )
+ {
+ if (GetLastError()) return strwin32error();
+ return "Unknown write error.";
+ }
+ return NULL;
+ }
+
+ // Bill Buckels - add drive->offset
+ if (lseek(drive->fd,offset+drive->offset, SEEK_SET)==-1)
+ {
+ return strerror(errno);
+ }
+ if (write(drive->fd, buf, drive->secLength) == drive->secLength) return NULL;
+ return strerror(errno);
+}
+/*}}}*/
diff --git a/Tools/unix/cpmtools/diskdefs b/Tools/unix/cpmtools/diskdefs
new file mode 100644
index 00000000..630317a0
--- /dev/null
+++ b/Tools/unix/cpmtools/diskdefs
@@ -0,0 +1,1396 @@
+diskdef ibm-3740
+ seclen 128
+ tracks 77
+ sectrk 26
+ blocksize 1024
+ maxdir 64
+ skew 6
+ boottrk 2
+ os 2.2
+end
+
+diskdef 4mb-hd
+ seclen 128
+ tracks 1024
+ sectrk 32
+ blocksize 2048
+ maxdir 256
+ skew 1
+ boottrk 0
+ os p2dos
+end
+
+diskdef pcw
+ seclen 512
+ tracks 40
+ sectrk 9
+ blocksize 1024
+ maxdir 64
+ skew 1
+ boottrk 1
+ os 3
+ libdsk:format pcw180
+end
+
+diskdef pc1.2m
+ seclen 512
+ tracks 80
+ # this format uses 15 sectors per track, but 30 per cylinder
+ sectrk 30
+ blocksize 4096
+ maxdir 256
+ skew 1
+ boottrk 0
+ os 3
+end
+
+# CP/M 86 on 1.44MB floppies
+diskdef cpm86-144feat
+ seclen 512
+ tracks 160
+ sectrk 18
+ blocksize 4096
+ maxdir 256
+ skew 1
+ boottrk 2
+ os 3
+ libdsk:format ibm1440
+end
+
+# CP/M 86 on 720KB floppies
+diskdef cpm86-720
+ seclen 512
+ tracks 160
+ sectrk 9
+ blocksize 2048
+ maxdir 256
+ skew 1
+ boottrk 2
+ os 3
+end
+
+diskdef cf2dd
+ seclen 512
+ tracks 160
+ sectrk 9
+ blocksize 2048
+ maxdir 256
+ skew 1
+ boottrk 1
+ os 3
+ libdsk:format pcw720
+end
+
+#amstrad: values are read from super block (special name hardcoded)
+
+# Royal alphatronic
+# setfdprm /dev/fd1 dd ssize=256 cyl=40 sect=16 head=2
+diskdef alpha
+ seclen 256
+ tracks 40
+ sectrk 32
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Apple II CP/M skew o Apple II DOS 3.3 skew
+diskdef apple-do
+ seclen 256
+ tracks 35
+ sectrk 16
+ blocksize 1024
+ maxdir 64
+ skewtab 0,6,12,3,9,15,14,5,11,2,8,7,13,4,10,1
+ boottrk 3
+ os 2.2
+end
+
+# Apple II CP/M skew o Apple II PRODOS skew
+diskdef apple-po
+ seclen 256
+ tracks 35
+ sectrk 16
+ blocksize 1024
+ maxdir 64
+ skewtab 0,9,3,12,6,15,1,10,4,13,7,8,2,11,5,14
+ boottrk 3
+ os 2.2
+end
+
+# MYZ80 hard drive (only works with libdsk, because it has a 256-byte header)
+diskdef myz80
+ seclen 1024
+ tracks 64
+ sectrk 128
+ blocksize 4096
+ maxdir 1024
+ skew 1
+ boottrk 0
+ os 3
+ libdsk:format pcw720
+end
+
+# Despite being Amstrad formats, CPC System and CPC Data don't have an Amstrad
+# superblock. You'll need to use libdsk to access them because the Linux
+# and Windows kernel drivers won't touch them.
+diskdef cpcsys
+ seclen 512
+ tracks 40
+ sectrk 9
+ blocksize 1024
+ maxdir 64
+ skew 1
+ boottrk 2
+ os 3
+ libdsk:format cpcsys
+end
+diskdef cpcdata
+ seclen 512
+ tracks 40
+ sectrk 9
+ blocksize 1024
+ maxdir 64
+ skew 1
+ boottrk 0
+ os 3
+ libdsk:format cpcdata
+end
+
+# after being read in with no sector skew.
+diskdef nigdos
+ seclen 512
+ # NigDos double sided disk format, 42 tracks * 2 sides
+ tracks 84
+ sectrk 10
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 0
+ # this format wastes half of the directory entry
+ logicalextents 1
+ os 3
+end
+
+diskdef epsqx10
+ seclen 512
+ tracks 40
+ sectrk 20
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+diskdef ibm-8ss
+ seclen 512
+ tracks 40
+ sectrk 8
+ blocksize 1024
+ maxdir 64
+ skew 0
+ boottrk 1
+ os 2.2
+end
+
+diskdef ibm-8ds
+ seclen 512
+ tracks 40
+ sectrk 8
+ blocksize 1024
+ maxdir 64
+ skew 0
+ boottrk 1
+ os 2.2
+end
+
+diskdef electroglas
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 256
+ skew 0
+ boottrk 1
+ os 3
+end
+
+# IBM CP/M-86
+# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48 head=1
+diskdef ibmpc-514ss
+ seclen 512
+ tracks 40
+ sectrk 8
+ blocksize 1024
+ maxdir 64
+ skew 1
+ boottrk 1
+ os 2.2
+ libdsk:format ibm160
+end
+
+# IBM CP/M-86
+# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48
+diskdef ibmpc-514ds
+ seclen 512
+ tracks 80
+ sectrk 8
+ blocksize 2048
+ maxdir 64
+ skew 0
+ boottrk 2
+ os 2.2
+ libdsk:format ibm320
+end
+
+diskdef p112
+ seclen 512
+ tracks 160
+ sectrk 18
+ blocksize 2048
+ maxdir 256
+ skew 1
+ boottrk 2
+ os 3
+end
+
+diskdef p112-old
+ seclen 512
+ tracks 160
+ sectrk 18
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 1
+ os 3
+end
+
+diskdef gide-cfa
+ seclen 512
+ tracks 1000
+ sectrk 16
+ blocksize 4096
+ maxdir 1024
+ skew 0
+ boottrk 2
+ os 3
+end
+
+diskdef gide-cfb
+ seclen 512
+ tracks 1000
+ sectrk 16
+ blocksize 4096
+ maxdir 1024
+ skew 0
+ boottrk 0
+# Start of second partition
+ offset 1000trk
+ os 3
+end
+
+# AT&T/Olivetti Word Processor
+diskdef attwp
+ seclen 256
+ tracks 80
+ sectrk 32
+ blocksize 2048
+ maxdir 128
+ boottrk 1
+ logicalextents 1
+ skewtab 0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15,16,18,20,22,24,26,28,30,17,19,21,23,25,27,29,31
+ os 2.2
+end
+
+# setfdprm /dev/fd0 zerobased SS DD ssize=512 cyl=40 sect=10 head=1
+# Kaypro II
+diskdef kpii
+ seclen 512
+ tracks 40
+ sectrk 10
+ blocksize 1024
+ maxdir 64
+ skew 0
+ boottrk 1
+ os 2.2
+end
+
+# setfdprm /dev/fd0 zerobased DS DD ssize=512 cyl=40 sect=10 head=2
+# Kayro IV
+diskdef kpiv
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 64
+ skew 0
+ boottrk 1
+ os 2.2
+end
+
+# setfdprm /dev/fd0 dd sect=10
+diskdef interak
+ seclen 512
+ tracks 80
+ sectrk 20
+ blocksize 4096
+ maxdir 256
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Timex FDD3000 3"
+diskdef fdd3000
+ seclen 256
+ tracks 40
+ sectrk 16
+ blocksize 1024
+ maxdir 128
+ boottrk 4
+ os 2.2
+ skew 7
+end
+
+# Timex FDD3000 3"
+diskdef fdd3000_2
+ seclen 256
+ tracks 40
+ sectrk 16
+ blocksize 1024
+ maxdir 128
+ boottrk 2
+ os 2.2
+ skew 5
+end
+
+# Robotron 1715
+diskdef 1715
+ seclen 1024
+ tracks 40
+ sectrk 5
+ blocksize 1024
+ maxdir 64
+ skew 0
+ boottrk 3
+ os 2.2
+end
+
+# Robotron 1715 with SCP3
+diskdef 17153
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 4
+ os 3
+end
+
+#DDR
+diskdef scp624
+ seclen 256
+ tracks 160
+ sectrk 16
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+diskdef scp640
+ seclen 256
+ tracks 160
+ sectrk 16
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+diskdef scp780
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+diskdef scp800
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+diskdef z9001
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 192
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+# Visual Technology Visual 1050 computer
+diskdef v1050
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 3
+end
+
+# Microbee 40 track 5.25" disks
+diskdef microbee40
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 128
+ skewtab 1,4,7,0,3,6,9,2,5,8
+ boottrk 2
+ os 2.2
+end
+
+diskdef dreamdisk40
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 128
+ skewtab 1,4,7,0,3,6,9,2,5,8
+ boottrk 2
+ os 2.2
+end
+
+diskdef dreamdisk80
+ seclen 512
+ tracks 160
+ sectrk 10
+ blocksize 2048
+ maxdir 256
+ skewtab 1,4,7,0,3,6,9,2,5,8
+ boottrk 2
+ os 2.2
+end
+
+diskdef rc759
+ seclen 1024
+ tracks 154
+ sectrk 8
+ blocksize 2048
+ maxdir 512
+ boottrk 4
+ os 3
+end
+
+# ICL Comet: 40 track 5.25" Single Sided
+#
+diskdef icl-comet-525ss
+ seclen 512
+ tracks 40
+ sectrk 10
+ blocksize 1024
+ maxdir 64
+ skewtab 0,3,6,9,2,5,8,1,4,7
+ boottrk 2
+ os 2.2
+end
+
+diskdef z80pack-hd
+ seclen 128
+ tracks 255
+ sectrk 128
+ blocksize 2048
+ maxdir 1024
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+diskdef z80pack-hdb
+ seclen 128
+ tracks 256
+ sectrk 16384
+ blocksize 16384
+ maxdir 8192
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+# Bondwell 12 and 14 disk images in IMD raw binary format
+diskdef bw12
+ seclen 256
+ tracks 40
+ sectrk 18
+ blocksize 2048
+ maxdir 64
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+diskdef bw14
+ seclen 256
+ tracks 80
+ sectrk 18
+ blocksize 2048
+ maxdir 64
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+############################
+# north star cp/m disks
+############################
+
+#North Star floppy 360K
+
+diskdef nsfd
+ seclen 512
+ tracks 70
+ sectrk 10
+ blocksize 2048
+ maxdir 64
+ skew 5
+ boottrk 2
+ os 2.2
+end
+
+
+#North Star CP/M Virtual-Disk file on Hard Disk
+# prepared with allocation factor = 4
+# as in "CR CPMB 4000 4"
+# needs to be copied off hard drive before you can
+# work on it with cpmtools
+
+diskdef nshd4
+ seclen 512
+ tracks 512
+ sectrk 16
+ blocksize 4096
+ maxdir 256
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+
+#North Star CP/M Virtual-Disk file on Hard Disk
+# prepared with allocation factor = 8
+# as in "CR CPMB 6000 8"
+# needs to be copied off hard drive before you can
+# work on it with cpmtools
+
+diskdef nshd8
+ seclen 512
+ tracks 1024
+ sectrk 16
+ blocksize 8192
+ maxdir 256
+ skew 0
+ boottrk 0
+ os 2.2
+end
+
+# Northstar Micro-Disk System MDS-A-D 175
+diskdef mdsad175
+ seclen 512
+ blocksize 1024
+ tracks 35
+ maxdir 64
+ boottrk 2
+ sectrk 10
+ skew 5
+ os 2.2
+end
+
+
+# Northstar Micro-Disk System MDS-A-D 350
+diskdef mdsad350
+ seclen 512
+ blocksize 2048
+ tracks 70
+ maxdir 64
+ boottrk 2
+ sectrk 10
+ skew 5
+ os 2.2
+end
+
+
+# Osborne 1
+diskdef osborne1
+ seclen 1024
+ tracks 40
+ sectrk 5
+ blocksize 1024
+ maxdir 64
+ boottrk 3
+ os 2.2
+end
+
+# Osborne Nuevo/Vixen/4
+diskdef osborne4
+ seclen 1024
+ tracks 80
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 2
+ boottrk 2
+ os 2.2
+end
+
+# Lobo Max-80 8" CP/M 2
+diskdef lobo2
+ seclen 256
+ tracks 77
+ sectrk 30
+ blocksize 2048
+ maxdir 64
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+#Lobo Max-80 8" CP/M 3
+diskdef lobo3
+ seclen 512
+ tracks 77
+ sectrk 17
+ blocksize 2048
+ maxdir 64
+ skew 0
+ boottrk 2
+ os 3
+end
+
+# PRO CP/M RZ50 DZ format (Perhaps only 79 tracks should be used?)
+diskdef dec_pro
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 128
+ skew 2
+ boottrk 2
+ os 2.2
+end
+
+# TDOS with DateStamper
+diskdef tdos-ds
+ seclen 1024
+ tracks 77
+ sectrk 16
+ blocksize 2048
+ maxdir 256
+ skew 0
+ boottrk 1
+ os zsys
+end
+
+# The following entires are tested and working
+# Most of the images are either from Don Maslin's archive or from
+# Dave Dunfield's site, but not all - they are noted as well as
+# their size.
+
+# PMC Micromate
+# Dave Dunfield's Imagedisk information from DSK conversion from IMD:
+# IMageDisk Utility 1.18 / Mar 07 2012
+# IMD 1.14: 10/03/2007 11:13:27
+# PMC-101 MicroMate
+# CP/M Plus
+# System Master
+# Assuming 1:1 for Binary output
+# 0/0 250 kbps DD 5x1024
+# 80 tracks(40/40), 400 sectors (12 Compressed)
+# Entry derived from above - image size = 409,600, from Dave Dunfield
+diskdef pmc101
+ seclen 1024
+ tracks 80
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 3
+end
+
+# BEGIN td143ssdd8 Turbo Dos 1.43 - SSDD 8" - 512 x 16
+# Test OK - image size = 630,784, from Don Maslin's archive
+diskdef td143ssdd8
+ seclen 512
+ tracks 77
+ sectrk 9
+ blocksize 1024
+ maxdir 64
+ skew 0
+ boottrk 0
+ os 2.2
+# DENSITY MFM ,LOW
+end
+
+# BEGIN headsdd8 Heath H89, Magnolia CP/M - SSDD 8" - 512 x 16
+# Test OK - image size = 630,784, from Don Maslin's archive
+diskdef heassdd8
+ seclen 512
+ tracks 77
+ sectrk 16
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+# DENSITY MFM ,LOW
+end
+
+# Morrow Designs Micro-Decision DOUBLE
+# 64k CP/M Vers. 2.2 Rev.2.3 SIDED
+# Copyright '76, '77, '78, '79, '80
+# Digital Research
+# Copyright 1982,1983 Morrow Designs, Inc.
+# Assuming 1:1 for Binary output
+# 0/0 250 kbps DD 5x1024
+# 80 tracks(40/40), 400 sectors (128 Compressed)
+# Entry derived from above data
+# Test OK - image siae = 409600, from Dave Dunfield
+diskdef mordsdd
+ seclen 1024
+ tracks 80
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 3
+ boottrk 2
+ OS 2.2
+end
+
+
+# BEGIN morsddd Morrow MD2 - SSDD 48 tpi 5.25" - 1024 x 5
+# Test OK - image size = 204,800, from Don Maslin's archive
+# Also tested with image from Dave Dunfield
+diskdef morsddd
+ seclen 1024
+ tracks 40
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 3
+ boottrk 2
+ os 2.2
+# DENSITY MFM ,LOW
+# BSH 4 BLM 15 EXM 1 DSM 94 DRM 127 AL0 0C0H AL1 0 OFS 2
+end
+
+# BEGIN osb1sssd Osborne 1 - SSSD 48 tpi 5.25" - 256 x 10
+# Test OK - image size = 102,400, from Don Maslin's archive
+diskdef osb1sssd
+ seclen 256
+ tracks 40
+ sectrk 10
+ blocksize 2048
+ maxdir 64
+ skew 2
+ boottrk 3
+ os 2.2
+# DENSITY MFM ,LOW
+# BSH 4 BLM 15 EXM 1 DSM 45 DRM 63 AL0 080H AL1 0 OFS 3
+end
+
+# BEGIN ampdsdd Ampro - DSDD 48 tpi 5.25" - 512 x 10
+# Test OK - image size = 409,600, from Don Maslin's archive
+diskdef ampdsdd
+ seclen 1024
+ tracks 80
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+ libdsk:format ampro400d
+# DENSITY MFM ,LOW
+# BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2
+end
+
+# BEGIN ampdsdd80 Ampro - DSDD 96 tpi 5.25" - 512 x 10
+# Test OK - image size = 819,200, from Don Maslin's archive
+diskdef ampdsdd80
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+ libdsk:format ampro800
+# DENSITY MFM ,LOW
+# BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2
+end
+
+# BEGIN altdsdd Altos - DSDD 5" - 512 x 9
+# Test OK - both CP/M and MP/M - image size = 737,280, from Dave Dunfield
+diskdef altdsdd
+ seclen 512
+ tracks 160
+ sectrk 9
+ blocksize 4096
+ maxdir 177
+ skew 0
+ boottrk 2
+ os 3
+# DENSITY MFM ,HIGH
+# BSH 5 BLM 31 EXM 3 DSM 176 DRM 176 AL0 0C0H AL1 0 OFS 2
+end
+
+# BEGIN trsomsssd TRS-80 Model 1, Omikron CP/M - SSSD 48 tpi 5.25" - 128 x 18
+# Test OK - image size = 80,640, from TRS-80 Yahoo Group posting
+diskdef trsomsssd
+ seclen 128
+ tracks 35
+ sectrk 18
+ blocksize 1024
+ maxdir 64
+ skew 4
+ boottrk 3
+ os 2.2
+# DENSITY FM ,LOW
+# BSH 3 BLM 7 EXM 0 DSM 71 DRM 63 AL0 0C0H AL1 0 OFS 3
+end
+
+# Memotech type 03, ie: 3.5" or 5.25", D/S, D/D, S/T
+# 40 tracks, 2 sides, 16 sectors/track, 256 bytes/sector
+# Bytes on the media = 2*40*16*256 = 327680
+# CP/M sees 26 128 byte records per track (similar to 8" disks).
+# Tracks = 327680/(26*128) = 98
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = ((98-2)*26*128)/2048 = 156, which agrees with DPB
+
+diskdef memotech-type03
+ seclen 128
+ tracks 98
+ sectrk 26
+ blocksize 2048
+ maxdir 64
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 07, ie: 3.5" or 5.25", D/S, D/D, D/T
+# 80 tracks, 2 sides, 16 sectors/track, 256 bytes/sector
+# Bytes on the media = 2*80*16*256 = 655360
+# CP/M sees 26 128 byte records per track (similar to 8" disks).
+# Tracks = 655360/(26*128) = 196
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = ((196-2)*26*128)/2048 = 315, which agrees with DPB
+
+diskdef memotech-type07
+ seclen 128
+ tracks 196
+ sectrk 26
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 43, ie: 1MB Silicon Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for Silicon Discs includes blocks on the last incomplete track
+# Tracks = 1048576/(26*128) = 315.07
+# Data is in 4096 byte blocks, on track 2 onwards
+# Blocks = (1048576-2*26*128)/4096 = 254, which agrees with DPB
+# Blocks = ((315-2)*26*128)/4096 = 254, so we don't need the 0.07 track
+diskdef memotech-type43
+ seclen 128
+ tracks 315
+ sectrk 26
+ blocksize 4096
+ maxdir 256
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 47, ie: 2MB Silicon Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for Silicon Discs includes blocks on the last incomplete track
+# Tracks = 2097152/(26*128) = 630.15
+# Data is in 4096 byte blocks, on track 2 onwards
+# Blocks = (2097152-2*26*128)/4096 = 510, which agrees with DPB
+# Blocks = ((630-2)*26*128)/4096 = 510, so we don't need the 0.15 track
+diskdef memotech-type47
+ seclen 128
+ tracks 630
+ sectrk 26
+ blocksize 4096
+ maxdir 256
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 4B, ie: 4MB Silicon Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for Silicon Discs includes blocks on the last incomplete track
+# Tracks = 4194304/(26*128) = 1260.3
+# Data is in 4096 byte blocks, on track 2 onwards
+# Blocks = (4194304-2*26*128)/4096 = 1022, which agrees with DPB
+# Blocks = ((1260-2)*26*128)/4096 = 1022, so we don't need the 0.3 track
+diskdef memotech-type4B
+ seclen 128
+ tracks 1260
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 4F, ie: 8MB Silicon Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for Silicon Discs includes blocks on the last incomplete track
+# Tracks = 8388608/(26*128) = 2520.61
+# Data is in 4096 byte blocks, on track 2 onwards
+# Blocks = (8388608-2*26*128)/4096 = 2046, which agrees with DPB
+# Blocks = ((2520-2)*26*128)/4096 = 2045, so we need the extra 0.61 track
+diskdef memotech-type4F
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 18, ie: 8MB SD Card
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for SD Cards includes blocks on the last incomplete track
+# Tracks = 8388608/(26*128) = 2520.61
+# Data is in 4096 byte blocks, on track 2 onwards
+# Blocks = (8388608-2*26*128)/4096 = 2046, which agrees with DPB
+# Blocks = ((2520-2)*26*128)/4096 = 2045, so we need the extra 0.61 track
+diskdef memotech-type18
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 19, ie: 8MB SD Card
+diskdef memotech-type19
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 8M
+end
+
+# Memotech type 1A, ie: 8MB SD Card
+diskdef memotech-type1A
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 16M
+end
+
+# Memotech type 1B, ie: 8MB SD Card
+diskdef memotech-type1B
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 24M
+end
+
+# Memotech type 1C, ie: 8MB SD Card
+diskdef memotech-type1C
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 32M
+end
+
+# Memotech type 1D, ie: 8MB SD Card
+diskdef memotech-type1D
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 40M
+end
+
+# Memotech type 1E, ie: 8MB SD Card
+diskdef memotech-type1E
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 48M
+end
+
+# Memotech type 1F, ie: 8MB SD Card
+diskdef memotech-type1F
+ seclen 128
+ tracks 2521
+ sectrk 26
+ blocksize 4096
+ maxdir 512
+ skew 1
+ boottrk 2
+ os 2.2
+ offset 56M
+end
+
+# Memotech type 50, ie: 256KB RAM Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for RAM Discs includes blocks on the last incomplete track
+# Tracks = 262144/(26*128) = 78.76
+# Data is in 1024 byte blocks, on track 2 onwards
+# Blocks = (262144-2*26*128)/1024 = 249, which agrees with DPB
+# Blocks = ((78-2)*26*128)/1024 = 247, so we need the extra 0.76 track
+diskdef memotech-type50
+ seclen 128
+ tracks 79
+ sectrk 26
+ blocksize 1024
+ maxdir 64
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 51, ie: 512KB RAM Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for RAM Discs includes blocks on the last incomplete track
+# Tracks = 524288/(26*128) = 157.53
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = (524288-2*26*128)/2048 = 252, which agrees with DPB
+# Blocks = ((157-2)*26*128)/2048 = 251, so we need the extra 0.53 track
+diskdef memotech-type51
+ seclen 128
+ tracks 158
+ sectrk 26
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 51, as used in Italy, ie: 480KB RAM Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for RAM Discs includes blocks on the last incomplete track
+# Tracks = 491520/(26*128) = 147.69
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = (491520-2*26*128)/2048 = 236, which agrees with DPB
+# Blocks = ((147-2)*26*128)/2048 = 235, so we need the extra 0.69 track
+diskdef memotech-type51-italy
+ seclen 128
+ tracks 148
+ sectrk 26
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 51, after S2R64.COM, ie: 448KB RAM Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for RAM Discs includes blocks on the last incomplete track
+# Tracks = 458752/(26*128) = 137.84
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = (458752-2*26*128)/2048 = 220, which agrees with DPB, after S2R64.COM
+# Blocks = ((137-2)*26*128)/2048 = 219, so we need the extra 0.84 track
+diskdef memotech-type51-s2r64
+ seclen 128
+ tracks 138
+ sectrk 26
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 51, after S2R.COM, ie: 144KB RAM Disc
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for RAM Discs includes blocks on the last incomplete track
+# Tracks = 147456/(26*128) = 44.3
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = (147456-2*26*128)/2048 = 68, which agrees with DPB, after S2R.COM
+# Blocks = ((44-2)*26*128)/2048 = 68, so we don't need the extra 0.3 track
+diskdef memotech-type51-s2r
+ seclen 128
+ tracks 44
+ sectrk 26
+ blocksize 2048
+ maxdir 128
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Memotech type 52, ie: 320KB RAM Disc
+# Added for REMEMOTECH
+# CP/M sees 26 128 byte records per track
+# Note: Unlike common practice with real physical disks, with real geometry,
+# the DPB for RAM Discs includes blocks on the last incomplete track
+# Tracks = 327680/(26*128) = 98.46
+# Data is in 2048 byte blocks, on track 2 onwards
+# Blocks = (327680-2*26*128)/2048 = 156
+# Blocks = ((98-2)*26*128)/2048 = 156, so we don't need the extra 0.46 track
+# This type very deliberately and conveniently exactly matches type 03
+diskdef memotech-type52
+ seclen 128
+ tracks 98
+ sectrk 26
+ blocksize 2048
+ maxdir 64
+ skew 1
+ boottrk 2
+ os 2.2
+end
+
+# Research Machines 380Z/480Z 5.25" "Single Density" or "MDS" format.
+# All tracks are formatted FM 16x128.
+diskdef rm-sd
+ seclen 128
+ tracks 40
+ sectrk 16
+ blocksize 1024
+ maxdir 64
+ skew 3
+ boottrk 3
+ os 2.2
+end
+
+# Research Machines 380Z/480Z 5.25" "Double Density" or "MD" format.
+# Track 0 is formatted FM 16x128; 1+ are MFM 9x512.
+# If you're working with an image file, make sure that track 0 is
+# padded to be the same size as the other tracks.
+diskdef rm-dd
+ seclen 512
+ tracks 40
+ sectrk 9
+ blocksize 1024
+ maxdir 64
+ skew 5
+ boottrk 3
+ os 2.2
+end
+
+# Research Machines 380Z/480Z 5.25" "Quad Density" or "MQ" format.
+# Track 0 is formatted FM 16x128; 1+ are MFM 9x512.
+diskdef rm-qd
+ seclen 512
+ tracks 80
+ sectrk 9
+ blocksize 2048
+ maxdir 128
+ skew 5
+ boottrk 3
+ os 2.2
+end
+
+# Ampro Little Board Z80 running CP/M 2.21
+# BEGIN AMP1 Ampro - SSDD 48 tpi 5.25"
+# DENSITY MFM, LOW
+# CYLINDERS 40 SIDES 1 SECTORS 10,512 SKEW 2
+# SIDE1 0 1,2,3,4,5,6,7,8,9,10
+# BSH 4 BLM 15 EXM 1 DSM 94 DRM 63 AL0 080H AL1 0 OFS 2
+# END
+
+diskdef amp1
+ seclen 512 #= Sectors xx,512
+ tracks 40 #= (Cylinders * Sides) = 40*1 = 40
+ sectrk 10 #= Sectors 10,xxx
+ blocksize 2048 #= (128*(BLM+1)) = 2048
+ maxdir 64 #(DRM+1) = 64
+ skew 0 #= SKEW = 0
+ boottrk 2 #= OFS = 2
+ os 2.2
+end
+
+#BEGIN AMP2 Ampro - DSDD 48 tpi 5.25"
+#DENSITY MFM, LOW
+#CYLINDERS 40 SIDES 2
+#SECTORS 10,512
+#SKEW 2
+#SIDE1 0 17,18,19,20,21,22,23,24,25,26
+#SIDE2 1 17,18,19,20,21,22,23,24,25,26
+#ORDER SIDES
+#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2
+#END
+
+# setfdprm /dev/fd0 DS DD ssize=512 cyl=40 sect=10 head=2
+diskdef amp2
+ seclen 512
+ tracks 80
+ sectrk 10
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+#BEGIN AMP3 Ampro - SSDD 96 tpi 3.5"
+#DENSITY MFM, LOW
+#CYLINDERS 80 SIDES 1 SECTORS 5,1024 SKEW 2
+#SIDE1 0 1,2,3,4,5
+#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2
+#END
+
+# setfdprm /dev/fd0 SS DD ssize=1024 cyl=80 sect=5 head=1
+diskdef amp3
+ seclen 1024
+ tracks 80
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+#BEGIN AMP4 Ampro - DSDD 96 tpi 3.5"
+#DENSITY MFM, LOW
+#CYLINDERS 80 SIDES 2 SECTORS 5,1024 SKEW 2
+#SIDE1 0 17,18,19,20,21
+#SIDE2 1 17,18,19,20,21
+#ORDER SIDES
+#BSH 4 BLM 15 EXM 0 DSM 394 DRM 255 AL0 0F0H AL1 0 OFS 2
+#END
+
+# setfdprm /dev/fd0 DS DD ssize=1024 cyl=80 sect=5 head=2
+diskdef amp4
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 256
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+#BEGIN AMP5 Ampro - SSDD 3.5"
+#DENSITY MFM, LOW
+#CYLINDERS 80 SIDES 1 SECTORS 5,1024 SKEW 2
+#SIDE1 0 1,2,3,4,5
+#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2
+#END
+
+# setfdprm /dev/fd0 SS DD ssize=1024 cyl=80 sect=5 head=1
+diskdef amp5
+ seclen 1024
+ tracks 80
+ sectrk 5
+ blocksize 2048
+ maxdir 128
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+#BEGIN AMP6 Ampro - DSDD 3.5"
+#DENSITY MFM, LOW
+#CYLINDERS 80 SIDES 2 SECTORS 5,1024 SKEW 2
+#SIDE1 0 17,18,19,20,21
+#SIDE2 1 17,18,19,20,21
+#ORDER SIDES
+#BSH 4 BLM 15 EXM 0 DSM 394 DRM 255 AL0 0F0H AL1 0 OFS 2
+#END
+
+# setfdprm /dev/fd0 DS DD ssize=1024 cyl=80 sect=5 head=2
+diskdef amp6
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 256
+ skew 0
+ boottrk 2
+ os 2.2
+end
+
+diskdef ampro800
+ seclen 1024
+ tracks 160
+ sectrk 5
+ blocksize 2048
+ maxdir 256
+ skew 0
+ boottrk 2
+ os 2.2
+end
diff --git a/Tools/unix/cpmtools/fsck.cpm.1 b/Tools/unix/cpmtools/fsck.cpm.1
new file mode 100644
index 00000000..a167ef0e
--- /dev/null
+++ b/Tools/unix/cpmtools/fsck.cpm.1
@@ -0,0 +1,80 @@
+.TH FSCK.CPM 1 "October 25, 2014" "CP/M tools" "User commands"
+.SH NAME ..\"{{{roff}}}\"{{{
+fsck.cpm \- check a CP/M file system
+.\"}}}
+.SH SYNOPSIS .\"{{{
+.ad l
+.B fsck.cpm
+.RB [ \-f
+.IR format ]
+.RB [ \-n ]
+.I image
+.ad b
+.\"}}}
+.SH DESCRIPTION .\"{{{
+\fBfsck.cpm\fP is used to check and repair a CP/M file system. After
+reading the directory, it makes two passes. The first pass checks extent
+fields for range and format violations (bad status, extent number, last
+record byte count, file name, extension, block number, record count,
+size of \&.COM files, time stamp format, invalid password characters,
+invalid time stamp mode). The second pass checks extent connectivity
+(multiple allocated blocks and duplicate directory entries).
+.P
+\fBfsck.cpm\fP can not yet repair all errors.
+.\"}}}
+.SH OPTIONS .\"{{{
+.IP "\fB\-f\fP \fIformat\fP"
+Use the given CP/M disk \fIformat\fP instead of the default format.
+.IP "\fB\-T\fP \fIlibdsk-type\fP"
+libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images
+(requires building cpmtools with support for libdsk).
+.IP "\fB\-n\fP"
+Open the file system read-only and do not repair any errors.
+.\"}}}
+.SH "RETURN VALUE" .\"{{{
+Upon successful completion, exit code 0 is returned.
+.\"}}}
+.SH ERRORS .\"{{{
+Any errors are indicated by exit code 1.
+.\"}}}
+.SH FILES .\"{{{
+${prefix}/share/diskdefs CP/M disk format definitions
+.\"}}}
+.SH ENVIRONMENT \"{{{
+CPMTOOLSFMT Default format
+.\"}}}
+.SH DIAGNOSTICS .\"{{{
+.IP "\fIimage\fP: \fIused\fP/\fItotal\fP files (\fIn\fP.\fIn\fP% non-contiguos), \fIused\fP/\fItotal\fP blocks"
+No inconsistencies could be found. The number of used files actually
+is the number of used extents. Since a file may use more than
+one extent, this may be greather than the actual number of files, but a
+correct measure would not reflect how many files could still be created
+at most. A file is considered fragmented, if sequential data blocks
+pointed to by the same extent do not have sequential block numbers.
+The number of used blocks includes the blocks used for system tracks
+and the directory.
+.\"}}}
+.SH AUTHORS .\"{{{
+This program is copyright 1997\(en2012 Michael Haardt
+. The Windows port is copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" .\"{{{
+.IR fsck (8),
+.IR mkfs.cpm (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/fsck.cpm.c b/Tools/unix/cpmtools/fsck.cpm.c
new file mode 100644
index 00000000..585015b0
--- /dev/null
+++ b/Tools/unix/cpmtools/fsck.cpm.c
@@ -0,0 +1,632 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmdir.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+/* #defines */ /*{{{*/
+/* your favourite password *:-) */
+
+#define T0 'G'
+#define T1 'E'
+#define T2 'H'
+#define T3 'E'
+#define T4 'I'
+#define T5 'M'
+#define T6 ' '
+#define T7 ' '
+
+#define PB ((char)(T0+T1+T2+T3+T4+T5+T6+T7))
+#define P0 ((char)(T7^PB))
+#define P1 ((char)(T6^PB))
+#define P2 ((char)(T5^PB))
+#define P3 ((char)(T4^PB))
+#define P4 ((char)(T3^PB))
+#define P5 ((char)(T2^PB))
+#define P6 ((char)(T1^PB))
+#define P7 ((char)(T0^PB))
+/*}}}*/
+
+/* types */ /*{{{*/
+enum Result { OK=0, MODIFIED=1, BROKEN=2 };
+/*}}}*/
+/* variables */ /*{{{*/
+static int norepair=0;
+/*}}}*/
+
+/* bcdCheck -- check format and range of BCD digit */ /*{{{*/
+static int bcdCheck(int n, int max, const char *msg, const char *unit, int extent1, int extent2)
+{
+ if (((n>>4)&0xf)>10 || (n&0xf)>10 || (((n>>4)&0xf)*10+(n&0xf))>=max)
+ {
+ printf("Error: Bad %s %s (extent=%d/%d, %s=%02x)\n",msg,unit,extent1,extent2,unit,n&0xff);
+ return -1;
+ }
+ else return 0;
+}
+/*}}}*/
+/* pwdCheck -- check password */ /*{{{*/
+static int pwdCheck(int extent, const char *pwd, char decode)
+{
+ char c;
+ int i;
+
+ for (i=0; i<8; ++i) if ((c=((char)(pwd[7-i]^decode)))<' ' || c&0x80)
+ {
+ printf("Error: non-printable character in password (extent=%d, password=",extent);
+ for (i=0; i<8; ++i)
+ {
+ c=pwd[7-i]^decode;
+ if (c<' ' || c&0x80)
+ {
+ putchar('\\'); putchar('0'+((c>>6)&0x01));
+ putchar('0'+((c>>3)&0x03));
+ putchar('0'+(c&0x03));
+ }
+ else putchar(c);
+ }
+ printf(")\n");
+ return -1;
+ }
+ return 0;
+}
+/*}}}*/
+/* ask -- ask user and return answer */ /*{{{*/
+static int ask(const char *msg)
+{
+ while (1)
+ {
+ char buf[80];
+
+ if (norepair) return 0;
+ printf("%s [Y]? ",msg); fflush(stdout);
+ if (fgets(buf,sizeof(buf),stdin)==(char*)0) exit(1);
+ switch (toupper(buf[0]))
+ {
+ case '\n':
+ case 'Y': return 1;
+ case 'N': return 0;
+ }
+ }
+}
+/*}}}*/
+/* prfile -- print file name */ /*{{{*/
+static char *prfile(struct cpmSuperBlock *sb, int extent)
+{
+ struct PhysDirectoryEntry *dir;
+ static char name[80];
+ char *s=name;
+ int i;
+ char c;
+
+ dir=sb->dir+extent;
+ for (i=0; i<8; ++i)
+ {
+ c=dir->name[i];
+ if ((c&0x7f)<' ')
+ {
+ *s++='\\'; *s++=('0'+((c>>6)&0x01));
+ *s++=('0'+((c>>3)&0x03));
+ *s++=('0'+(c&0x03));
+ }
+ else *s++=(c&0x7f);
+ }
+ *s++='.';
+ for (i=0; i<3; ++i)
+ {
+ c=dir->ext[i];
+ if ((c&0x7f)<' ')
+ {
+ *s++='\\'; *s++=('0'+((c>>6)&0x01));
+ *s++=('0'+((c>>3)&0x03));
+ *s++=('0'+(c&0x03));
+ }
+ else *s++=(c&0x7f);
+ }
+ *s='\0';
+ return name;
+}
+/*}}}*/
+/* fsck -- file system check */ /*{{{*/
+static int fsck(struct cpmInode *root, const char *image)
+{
+ /* variables */ /*{{{*/
+ enum Result ret=OK;
+ int extent,extent2;
+ struct PhysDirectoryEntry *dir,*dir2;
+ struct cpmSuperBlock *sb=root->sb;
+ /*}}}*/
+
+ /* Phase 1: check extent fields */ /*{{{*/
+ printf("Phase 1: check extent fields\n");
+ for (extent=0; extentmaxdir; ++extent)
+ {
+ char *status;
+ int usedBlocks=0;
+
+ dir=sb->dir+extent;
+ status=&dir->status;
+ if (*status>=0 && *status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* directory entry */ /*{{{*/
+ {
+ /* check name and extension */ /*{{{*/
+ {
+ int i;
+ char *c;
+
+ for (i=0; i<8; ++i)
+ {
+ c=&(dir->name[i]);
+ if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f))
+ {
+ printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
+ if (ask("Remove file"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ break;
+ }
+ else ret|=BROKEN;
+ }
+ }
+ if (*status==(char)0xe5) continue;
+ for (i=0; i<3; ++i)
+ {
+ c=&(dir->ext[i]);
+ if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f))
+ {
+ printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
+ if (ask("Remove file"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ break;
+ }
+ else ret|=BROKEN;
+ }
+ }
+ if (*status==(char)0xe5) continue;
+ }
+ /*}}}*/
+ /* check extent number */ /*{{{*/
+ if ((dir->extnol&0xff)>0x1f)
+ {
+ printf("Error: Bad lower bits of extent number (extent=%d, name=\"%s\", low bits=%d)\n",extent,prfile(sb,extent),dir->extnol&0xff);
+ if (ask("Remove file"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ if (*status==(char)0xe5) continue;
+ if ((dir->extnoh&0xff)>0x3f)
+ {
+ printf("Error: Bad higher bits of extent number (extent=%d, name=\"%s\", high bits=%d)\n",extent,prfile(sb,extent),dir->extnoh&0xff);
+ if (ask("Remove file"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ if (*status==(char)0xe5) continue;
+ /*}}}*/
+ /* check last record byte count */ /*{{{*/
+ if ((dir->lrc&0xff)>128)
+ {
+ printf("Error: Bad last record byte count (extent=%d, name=\"%s\", lrc=%d)\n",extent,prfile(sb,extent),dir->lrc&0xff);
+ if (ask("Clear last record byte count"))
+ {
+ dir->lrc=(char)0;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ if (*status==(char)0xe5) continue;
+ /*}}}*/
+ /* check block number range */ /*{{{*/
+ {
+ int block,min,max,i;
+
+ min=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz;
+ max=sb->size;
+ for (i=0; i<16; ++i)
+ {
+ block=dir->pointers[i]&0xff;
+ if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8;
+ if (block>0)
+ {
+ ++usedBlocks;
+ if (block=max)
+ {
+ printf("Error: Bad block number (extent=%d, name=\"%s\", block=%d)\n",extent,prfile(sb,extent),block);
+ if (ask("Remove file"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ break;
+ }
+ else ret|=BROKEN;
+ }
+ }
+ }
+ if (*status==(char)0xe5) continue;
+ }
+ /*}}}*/
+ /* check number of used blocks ? */ /*{{{*/
+ /*}}}*/
+ /* check record count */ /*{{{*/
+ {
+ int i,min,max,recordsInBlocks,used=0;
+
+ min=(dir->extnol%sb->extents)*16/sb->extents;
+ max=((dir->extnol%sb->extents)+1)*16/sb->extents;
+ assert(minpointers[i] || (sb->size>=256 && dir->pointers[i+1])) ++used;
+ if (sb->size >= 256) ++i;
+ }
+ recordsInBlocks=(((unsigned char)dir->blkcnt)*128+sb->blksiz-1)/sb->blksiz;
+ if (recordsInBlocks!=used)
+ {
+ printf("Error: Bad record count (extent=%d, name=\"%s\", record count=%d)\n",extent,prfile(sb,extent),dir->blkcnt&0xff);
+ if (ask("Remove file"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ if (*status==(char)0xe5) continue;
+ }
+ /*}}}*/
+ /* check for too large .com files */ /*{{{*/
+ if (((EXTENT(dir->extnol,dir->extnoh)==3 && dir->blkcnt>=126) || EXTENT(dir->extnol,dir->extnoh)>=4) && (dir->ext[0]&0x7f)=='C' && (dir->ext[1]&0x7f)=='O' && (dir->ext[2]&0x7f)=='M')
+ {
+ printf("Warning: Oversized .COM file (extent=%d, name=\"%s\")\n",extent,prfile(sb,extent));
+ }
+ /*}}}*/
+ }
+ /*}}}*/
+ else if ((sb->type==CPMFS_P2DOS || sb->type==CPMFS_DR3) && *status==33) /* check time stamps ? */ /*{{{*/
+ {
+ unsigned long created,modified;
+ char s;
+
+ if ((s=sb->dir[extent2=(extent&~3)].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for first of the three extents */ /*{{{*/
+ {
+ bcdCheck(dir->name[2],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2);
+ bcdCheck(dir->name[3],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2);
+ bcdCheck(dir->name[6],24,"modification date","hour",extent,extent2);
+ bcdCheck(dir->name[7],60,"modification date","minute",extent,extent2);
+ created=(dir->name[4]+(dir->name[1]<<8))*(0x60*0x60)+dir->name[2]*0x60+dir->name[3];
+ modified=(dir->name[0]+(dir->name[5]<<8))*(0x60*0x60)+dir->name[6]*0x60+dir->name[7];
+ if (sb->cnotatime && modifieddir[extent2=(extent&~3)+1].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for second */ /*{{{*/
+ {
+ bcdCheck(dir->lrc,24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2);
+ bcdCheck(dir->extnoh,60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2);
+ bcdCheck(dir->pointers[1],24,"modification date","hour",extent,extent2);
+ bcdCheck(dir->pointers[2],60,"modification date","minute",extent,extent2);
+ created=(dir->ext[2]+(dir->extnol<<8))*(0x60*0x60)+dir->lrc*0x60+dir->extnoh;
+ modified=(dir->blkcnt+(dir->pointers[0]<<8))*(0x60*0x60)+dir->pointers[1]*0x60+dir->pointers[2];
+ if (sb->cnotatime && modifieddir[extent2=(extent&~3)+2].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for third */ /*{{{*/
+ {
+ bcdCheck(dir->pointers[7],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2);
+ bcdCheck(dir->pointers[8],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2);
+ bcdCheck(dir->pointers[11],24,"modification date","hour",extent,extent2);
+ bcdCheck(dir->pointers[12],60,"modification date","minute",extent,extent2);
+ created=(dir->pointers[5]+(dir->pointers[6]<<8))*(0x60*0x60)+dir->pointers[7]*0x60+dir->pointers[8];
+ modified=(dir->pointers[9]+(dir->pointers[10]<<8))*(0x60*0x60)+dir->pointers[11]*0x60+dir->pointers[12];
+ if (sb->cnotatime && modifiedtype==CPMFS_DR3 && *status==32) /* disc label */ /*{{{*/
+ {
+ unsigned long created,modified;
+
+ bcdCheck(dir->pointers[10],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent);
+ bcdCheck(dir->pointers[11],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent);
+ bcdCheck(dir->pointers[14],24,"modification date","hour",extent,extent);
+ bcdCheck(dir->pointers[15],60,"modification date","minute",extent,extent);
+ created=(dir->pointers[8]+(dir->pointers[9]<<8))*(0x60*0x60)+dir->pointers[10]*0x60+dir->pointers[11];
+ modified=(dir->pointers[12]+(dir->pointers[13]<<8))*(0x60*0x60)+dir->pointers[14]*0x60+dir->pointers[15];
+ if (sb->cnotatime && modifiedextnol&0x40 && dir->extnol&0x10)
+ {
+ printf("Error: Bit 4 and 6 can only be exclusively be set (extent=%d, label byte=0x%02x)\n",extent,(unsigned char)dir->extnol);
+ if (ask("Time stamp on creation"))
+ {
+ dir->extnol&=~0x40;
+ ret|=MODIFIED;
+ }
+ else if (ask("Time stamp on access"))
+ {
+ dir->extnol&=~0x10;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ if (dir->extnol&0x80 && pwdCheck(extent,dir->pointers,dir->lrc))
+ {
+ char msg[80];
+
+ sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7);
+ if (ask(msg))
+ {
+ dir->pointers[0]=P0;
+ dir->pointers[1]=P1;
+ dir->pointers[2]=P2;
+ dir->pointers[3]=P3;
+ dir->pointers[4]=P4;
+ dir->pointers[5]=P5;
+ dir->pointers[6]=P6;
+ dir->pointers[7]=P7;
+ dir->lrc=PB;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ }
+ /*}}}*/
+ else if (sb->type==CPMFS_DR3 && *status>=16 && *status<=31) /* password */ /*{{{*/
+ {
+ /* check name and extension */ /*{{{*/
+ {
+ int i;
+ char *c;
+
+ for (i=0; i<8; ++i)
+ {
+ c=&(dir->name[i]);
+ if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f))
+ {
+ printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
+ if (ask("Clear password entry"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ break;
+ }
+ else ret|=BROKEN;
+ }
+ }
+ if (*status==(char)0xe5) continue;
+ for (i=0; i<3; ++i)
+ {
+ c=&(dir->ext[i]);
+ if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f))
+ {
+ printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i);
+ if (ask("Clear password entry"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ break;
+ }
+ else ret|=BROKEN;
+ }
+ }
+ if (*status==(char)0xe5) continue;
+ }
+ /*}}}*/
+ /* check password */ /*{{{*/
+ if (dir->extnol&(0x80|0x40|0x20) && pwdCheck(extent,dir->pointers,dir->lrc))
+ {
+ char msg[80];
+
+ sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7);
+ if (ask(msg))
+ {
+ dir->pointers[0]=P0;
+ dir->pointers[1]=P1;
+ dir->pointers[2]=P2;
+ dir->pointers[3]=P3;
+ dir->pointers[4]=P4;
+ dir->pointers[5]=P5;
+ dir->pointers[6]=P6;
+ dir->pointers[7]=P7;
+ dir->lrc=PB;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ }
+ /*}}}*/
+ }
+ /*}}}*/
+ else if (*status!=(char)0xe5) /* bad status */ /*{{{*/
+ {
+ printf("Error: Bad status (extent=%d, name=\"%s\", status=0x%02x)\n",extent,prfile(sb,extent),*status&0xff);
+ if (ask("Clear entry"))
+ {
+ *status=(char)0xE5;
+ ret|=MODIFIED;
+ }
+ else ret|=BROKEN;
+ continue;
+ }
+ /*}}}*/
+ }
+ /*}}}*/
+ /* Phase 2: check extent connectivity */ /*{{{*/
+ printf("Phase 2: check extent connectivity\n");
+ /* check multiple allocated blocks */ /*{{{*/
+ for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
+ {
+ int i,j,block,block2;
+
+ for (i=0; i<16; ++i)
+ {
+ block=dir->pointers[i]&0xff;
+ if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8;
+ for (extent2=0; extent2maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
+ {
+ for (j=0; j<16; ++j)
+ {
+ block2=dir2->pointers[j]&0xff;
+ if (sb->size>=256) block2+=(dir2->pointers[++j]&0xff)<<8;
+ if (block!=0 && block2!=0 && block==block2 && !(extent==extent2 && i==j))
+ {
+ printf("Error: Multiple allocated block (extent=%d,%d, name=\"%s\"",extent,extent2,prfile(sb,extent));
+ printf(",\"%s\" block=%d)\n",prfile(sb,extent2),block);
+ ret|=BROKEN;
+ }
+ }
+ }
+ }
+ }
+ /*}}}*/
+ /* check multiple extents */ /*{{{*/
+ for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
+ {
+ for (extent2=0; extent2maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
+ {
+ if (extent!=extent2 && EXTENT(dir->extnol,dir->extnoh)==EXTENT(dir2->extnol,dir2->extnoh) && dir->status==dir2->status)
+ {
+ int i;
+
+ for (i=0; i<8 && (dir->name[i]&0x7f)==(dir2->name[i]&0x7f); ++i);
+ if (i==8)
+ {
+ for (i=0; i<3 && (dir->ext[i]&0x7f)==(dir2->ext[i]&0x7f); ++i);
+ if (i==3)
+ {
+ printf("Error: Duplicate extent (extent=%d,%d)\n",extent,extent2);
+ ret|=BROKEN;
+ }
+ }
+ }
+ }
+ }
+ /*}}}*/
+ /*}}}*/
+ if (ret==0) /* print statistics */ /*{{{*/
+ {
+ struct cpmStatFS statfsbuf;
+ int fragmented=0,borders=0;
+
+ cpmStatFS(root,&statfsbuf);
+ for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15))
+ {
+ int i,block,previous=-1;
+
+ for (i=0; i<16; ++i)
+ {
+ block=dir->pointers[i]&0xff;
+ if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8;
+ if (previous!=-1)
+ {
+ if (block!=0 && block!=(previous+1)) ++fragmented;
+ ++borders;
+ }
+ previous=block;
+ }
+ }
+ fragmented=(borders ? (1000*fragmented)/borders : 0);
+ printf("%s: %ld/%ld files (%d.%d%% non-contigous), %ld/%ld blocks\n",image,statfsbuf.f_files-statfsbuf.f_ffree,statfsbuf.f_files,fragmented/10,fragmented%10,statfsbuf.f_blocks-statfsbuf.f_bfree,statfsbuf.f_blocks);
+ }
+ /*}}}*/
+ return ret;
+}
+/*}}}*/
+
+const char cmd[]="fsck.cpm";
+
+/* main */ /*{{{*/
+int main(int argc, char *argv[])
+{
+ const char *err;
+ const char *image;
+ const char *format;
+ const char *devopts=NULL;
+ int c,usage=0;
+ struct cpmSuperBlock sb;
+ struct cpmInode root;
+ enum Result ret;
+
+ if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
+ while ((c=getopt(argc,argv,"T:f:nh?"))!=EOF) switch(c)
+ {
+ case 'f': format=optarg; break;
+ case 'T': devopts=optarg; break;
+ case 'n': norepair=1; break;
+ case 'h':
+ case '?': usage=1; break;
+ }
+
+ if (optind!=(argc-1)) usage=1;
+ else image=argv[optind++];
+
+ if (usage)
+ {
+ fprintf(stderr,"Usage: %s [-f format] [-n] image\n",cmd);
+ exit(1);
+ }
+ if ((err=Device_open(&sb.dev, image, (norepair ? O_RDONLY : O_RDWR), devopts)))
+ {
+ if ((err=Device_open(&sb.dev, image,O_RDONLY, devopts)))
+ {
+ fprintf(stderr,"%s: cannot open %s: %s\n",cmd,image,err);
+ exit(1);
+ }
+ else
+ {
+ fprintf(stderr,"%s: cannot open %s for writing, no repair possible\n",cmd,image);
+ }
+ }
+ if (cpmReadSuper(&sb,&root,format)==-1)
+ {
+ fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
+ exit(1);
+ }
+ ret=fsck(&root,image);
+ if (ret&MODIFIED)
+ {
+ if (cpmSync(&sb)==-1)
+ {
+ fprintf(stderr,"%s: write error on %s: %s\n",cmd,image,strerror(errno));
+ ret|=BROKEN;
+ }
+ fprintf(stderr,"%s: FILE SYSTEM ON %s MODIFIED",cmd,image);
+ if (ret&BROKEN) fprintf(stderr,", PLEASE CHECK AGAIN");
+ fprintf(stderr,"\n");
+ }
+ cpmUmount(&sb);
+ if (ret&BROKEN) return 2;
+ else return 0;
+}
+/*}}}*/
diff --git a/Tools/unix/cpmtools/fsed.cpm.1 b/Tools/unix/cpmtools/fsed.cpm.1
new file mode 100644
index 00000000..a6d58738
--- /dev/null
+++ b/Tools/unix/cpmtools/fsed.cpm.1
@@ -0,0 +1,62 @@
+.TH FSED.CPM 1 "October 25, 2014" "CP/M tools" "User commands"
+.SH NAME ..\"{{{roff}}}\"{{{
+fsed.cpm \- edit a CP/M file system
+.\"}}}
+.SH SYNOPSIS .\"{{{
+.ad l
+.B fsed.cpm
+.RB [ \-f
+.IR format ]
+.I image
+.ad b
+.\"}}}
+.SH DESCRIPTION .\"{{{
+\fBfsed.cpm\fP edits a CP/M file system on an image file or device.
+It knows about the system, directory and data area, using sector skew on
+the last two. Directory entries are decoded. The interactive usage is
+self-explanatory.
+.\"}}}
+.SH OPTIONS .\"{{{
+.IP "\fB\-f\fP \fIformat\fP"
+Use the given CP/M disk \fIformat\fP instead of the default format.
+.IP "\fB\-T\fP \fIlibdsk-type\fP"
+libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images
+(requires building cpmtools with support for libdsk).
+.\"}}}
+.SH "RETURN VALUE" .\"{{{
+Upon successful completion, exit code 0 is returned.
+.\"}}}
+.SH ERRORS .\"{{{
+Any errors are indicated by exit code 1.
+.\"}}}
+.SH ENVIRONMENT \"{{{
+CPMTOOLSFMT Default format
+.\"}}}
+.SH FILES .\"{{{
+${prefix}/share/diskdefs CP/M disk format definitions
+.\"}}}
+.SH AUTHORS \"{{{
+This program is copyright 1997\(en2012 Michael Haardt
+. The Windows port is copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" .\"{{{
+.IR fsck.cpm (1),
+.IR mkfs.cpm (1),
+.IR cpmls (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/fsed.cpm.c b/Tools/unix/cpmtools/fsed.cpm.c
new file mode 100644
index 00000000..f120b77c
--- /dev/null
+++ b/Tools/unix/cpmtools/fsed.cpm.c
@@ -0,0 +1,748 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#if NEED_NCURSES
+#if HAVE_NCURSES_NCURSES_H
+#include
+#else
+#include
+#endif
+#else
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+
+#include "cpmfs.h"
+#include "getopt_.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+
+extern char **environ;
+
+static char *mapbuf;
+
+static struct tm *cpmtime(char lday, char hday, char hour, char min) /*{{{*/
+{
+ static struct tm tm;
+ unsigned long days=(lday&0xff)|((hday&0xff)<<8);
+ int d;
+ unsigned int md[12]={31,0,31,30,31,30,31,31,30,31,30,31};
+
+ tm.tm_sec=0;
+ tm.tm_min=((min>>4)&0xf)*10+(min&0xf);
+ tm.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
+ tm.tm_mon=0;
+ tm.tm_year=1978;
+ tm.tm_isdst=-1;
+ if (days) --days;
+ while (days>=(d=(((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 366 : 365)))
+ {
+ days-=d;
+ ++tm.tm_year;
+ }
+ md[1]=((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 29 : 28;
+ while (days>=md[tm.tm_mon])
+ {
+ days-=md[tm.tm_mon];
+ ++tm.tm_mon;
+ }
+ tm.tm_mday=days+1;
+ tm.tm_year-=1900;
+ return &tm;
+}
+/*}}}*/
+static void info(struct cpmSuperBlock *sb, const char *format, const char *image) /*{{{*/
+{
+ const char *msg;
+
+ clear();
+ msg="File system characteristics";
+ move(0,(COLS-strlen(msg))/2); printw(msg);
+ move(2,0); printw(" Image: %s",image);
+ move(3,0); printw(" Format: %s",format);
+ move(4,0); printw(" File system: ");
+ switch (sb->type)
+ {
+ case CPMFS_DR22: printw("CP/M 2.2"); break;
+ case CPMFS_P2DOS: printw("P2DOS 2.3"); break;
+ case CPMFS_DR3: printw("CP/M Plus"); break;
+ }
+
+ move(6,0); printw(" Sector length: %d",sb->secLength);
+ move(7,0); printw(" Number of tracks: %d",sb->tracks);
+ move(8,0); printw(" Sectors per track: %d",sb->sectrk);
+
+ move(10,0);printw(" Block size: %d",sb->blksiz);
+ move(11,0);printw("Number of directory entries: %d",sb->maxdir);
+ move(12,0);printw(" Logical sector skew: %d",sb->skew);
+ move(13,0);printw(" Number of system tracks: %d",sb->boottrk);
+ move(14,0);printw(" Logical extents per extent: %d",sb->extents);
+ move(15,0);printw(" Allocatable data blocks: %d",sb->size-(sb->maxdir*32+sb->blksiz-1)/sb->blksiz);
+
+ msg="Any key to continue";
+ move(23,(COLS-strlen(msg))/2); printw(msg);
+ getch();
+}
+/*}}}*/
+static void map(struct cpmSuperBlock *sb) /*{{{*/
+{
+ const char *msg;
+ char bmap[18*80];
+ int secmap,sys,directory;
+ int pos;
+
+ clear();
+ msg="Data map";
+ move(0,(COLS-strlen(msg))/2); printw(msg);
+
+ secmap=(sb->tracks*sb->sectrk+80*18-1)/(80*18);
+ memset(bmap,' ',sizeof(bmap));
+ sys=sb->boottrk*sb->sectrk;
+ memset(bmap,'S',sys/secmap);
+ directory=(sb->maxdir*32+sb->secLength-1)/sb->secLength;
+ memset(bmap+sys/secmap,'D',directory/secmap);
+ memset(bmap+(sys+directory)/secmap,'.',sb->sectrk*sb->tracks/secmap);
+
+ for (pos=0; pos<(sb->maxdir*32+sb->secLength-1)/sb->secLength; ++pos)
+ {
+ int entry;
+
+ Device_readSector(&sb->dev,sb->boottrk+pos/(sb->sectrk*sb->secLength),pos/sb->secLength,mapbuf);
+ for (entry=0; entrysecLength/32 && (pos*sb->secLength/32)+entrymaxdir; ++entry)
+ {
+ int i;
+
+ if (mapbuf[entry*32]>=0 && mapbuf[entry*32]<=(sb->type==CPMFS_P2DOS ? 31 : 15))
+ {
+ for (i=0; i<16; ++i)
+ {
+ int sector;
+
+ sector=mapbuf[entry*32+16+i]&0xff;
+ if (sb->size>=256) sector|=(((mapbuf[entry*32+16+ ++i]&0xff)<<8));
+ if (sector>0 && sector<=sb->size)
+ {
+ /* not entirely correct without the last extent record count */
+ sector=sector*(sb->blksiz/sb->secLength)+sb->sectrk*sb->boottrk;
+ memset(bmap+sector/secmap,'#',sb->blksiz/(sb->secLength*secmap));
+ }
+ }
+ }
+ }
+ }
+
+ for (pos=0; pos<(int)sizeof(bmap); ++pos)
+ {
+ move(2+pos%18,pos/18);
+ addch(bmap[pos]);
+ }
+ move(21,0); printw("S=System area D=Directory area #=File data .=Free");
+ msg="Any key to continue";
+ move(23,(COLS-strlen(msg))/2); printw(msg);
+ getch();
+}
+/*}}}*/
+static void data(struct cpmSuperBlock *sb, const char *buf, unsigned long int pos) /*{{{*/
+{
+ int offset=(pos%sb->secLength)&~0x7f;
+ unsigned int i;
+
+ for (i=0; i<128; ++i)
+ {
+ move(4+(i>>4),(i&0x0f)*3+!!(i&0x8)); printw("%02x",buf[i+offset]&0xff);
+ if (pos%sb->secLength==i+offset) attron(A_REVERSE);
+ move(4+(i>>4),50+(i&0x0f)); printw("%c",isprint(buf[i+offset]) ? buf[i+offset] : '.');
+ attroff(A_REVERSE);
+ }
+ move(4+((pos&0x7f)>>4),((pos&0x7f)&0x0f)*3+!!((pos&0x7f)&0x8)+1);
+}
+/*}}}*/
+
+const char cmd[]="fsed.cpm";
+
+int main(int argc, char *argv[]) /*{{{*/
+{
+ /* variables */ /*{{{*/
+ const char *devopts=(const char*)0;
+ char *image;
+ const char *err;
+ struct cpmSuperBlock drive;
+ struct cpmInode root;
+ const char *format;
+ int c,usage=0;
+ off_t pos;
+ chtype ch;
+ int reload;
+ char *buf;
+ /*}}}*/
+
+ /* parse options */ /*{{{*/
+ if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
+ while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c)
+ {
+ case 'f': format=optarg; break;
+ case 'T': devopts=optarg; break;
+ case 'h':
+ case '?': usage=1; break;
+ }
+
+ if (optind!=(argc-1)) usage=1;
+ else image=argv[optind++];
+
+ if (usage)
+ {
+ fprintf(stderr,"Usage: fsed.cpm [-f format] image\n");
+ exit(1);
+ }
+ /*}}}*/
+ /* open image */ /*{{{*/
+ if ((err=Device_open(&drive.dev,image,O_RDONLY,devopts)))
+ {
+ fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err);
+ exit(1);
+ }
+ if (cpmReadSuper(&drive,&root,format)==-1)
+ {
+ fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo);
+ exit(1);
+ }
+ /*}}}*/
+ /* alloc sector buffers */ /*{{{*/
+ if ((buf=malloc(drive.secLength))==(char*)0 || (mapbuf=malloc(drive.secLength))==(char*)0)
+ {
+ fprintf(stderr,"fsed.cpm: can not allocate sector buffer (%s).\n",strerror(errno));
+ exit(1);
+ }
+ /*}}}*/
+ /* init curses */ /*{{{*/
+ initscr();
+ noecho();
+ raw();
+ nonl();
+ idlok(stdscr,TRUE);
+ idcok(stdscr,TRUE);
+ keypad(stdscr,TRUE);
+ clear();
+ /*}}}*/
+
+ pos=0;
+ reload=1;
+ do
+ {
+ /* display position and load data */ /*{{{*/
+ clear();
+ move(2,0); printw("Byte %8lu (0x%08lx) ",pos,pos);
+ if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
+ {
+ printw("Physical sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1);
+ }
+ else
+ {
+ printw("Sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1);
+ printw("(physical %3d) ",drive.skewtab[(pos/drive.secLength)%drive.sectrk]+1);
+ }
+ printw("Offset %5lu ",pos%drive.secLength);
+ printw("Track %5lu",pos/(drive.secLength*drive.sectrk));
+ move(LINES-3,0); printw("N)ext track P)revious track");
+ move(LINES-2,0); printw("n)ext record p)revious record f)orward byte b)ackward byte");
+ move(LINES-1,0); printw("i)nfo q)uit");
+ if (reload)
+ {
+ if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
+ {
+ err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),(pos/drive.secLength)%drive.sectrk,buf);
+ }
+ else
+ {
+ err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),drive.skewtab[(pos/drive.secLength)%drive.sectrk],buf);
+ }
+ if (err)
+ {
+ move(4,0); printw("Data can not be read: %s",err);
+ }
+ else reload=0;
+ }
+ /*}}}*/
+
+ if /* position before end of system area */ /*{{{*/
+ (pos<(drive.boottrk*drive.sectrk*drive.secLength))
+ {
+ const char *msg;
+
+ msg="System area"; move(0,(COLS-strlen(msg))/2); printw(msg);
+ move(LINES-3,36); printw("F)orward 16 byte B)ackward 16 byte");
+ if (!reload) data(&drive,buf,pos);
+ switch (ch=getch())
+ {
+ case 'F': /* next 16 byte */ /*{{{*/
+ {
+ if (pos+16<(drive.sectrk*drive.tracks*(off_t)drive.secLength))
+ {
+ if (pos/drive.secLength!=(pos+16)/drive.secLength) reload=1;
+ pos+=16;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'B': /* previous 16 byte */ /*{{{*/
+ {
+ if (pos>=16)
+ {
+ if (pos/drive.secLength!=(pos-16)/drive.secLength) reload=1;
+ pos-=16;
+ }
+ break;
+ }
+ /*}}}*/
+ }
+ }
+ /*}}}*/
+ else if /* position before end of directory area */ /*{{{*/
+ (pos<(drive.boottrk*drive.sectrk*drive.secLength+drive.maxdir*32))
+ {
+ const char *msg;
+ unsigned long entrystart=(pos&~0x1f)%drive.secLength;
+ int entry=(pos-(drive.boottrk*drive.sectrk*drive.secLength))>>5;
+ int offset=pos&0x1f;
+
+ msg="Directory area"; move(0,(COLS-strlen(msg))/2); printw(msg);
+ move(LINES-3,36); printw("F)orward entry B)ackward entry");
+
+ move(13,0); printw("Entry %3d: ",entry);
+ if /* free or used directory entry */ /*{{{*/
+ ((buf[entrystart]>=0 && buf[entrystart]<=(drive.type==CPMFS_P2DOS ? 31 : 15)) || buf[entrystart]==(char)0xe5)
+ {
+ int i;
+
+ if (buf[entrystart]==(char)0xe5)
+ {
+ if (offset==0) attron(A_REVERSE);
+ printw("Free");
+ attroff(A_REVERSE);
+ }
+ else printw("Directory entry");
+ move(15,0);
+ if (buf[entrystart]!=(char)0xe5)
+ {
+ printw("User: ");
+ if (offset==0) attron(A_REVERSE);
+ printw("%2d",buf[entrystart]);
+ attroff(A_REVERSE);
+ printw(" ");
+ }
+ printw("Name: ");
+ for (i=0; i<8; ++i)
+ {
+ if (offset==1+i) attron(A_REVERSE);
+ printw("%c",buf[entrystart+1+i]&0x7f);
+ attroff(A_REVERSE);
+ }
+ printw(" Extension: ");
+ for (i=0; i<3; ++i)
+ {
+ if (offset==9+i) attron(A_REVERSE);
+ printw("%c",buf[entrystart+9+i]&0x7f);
+ attroff(A_REVERSE);
+ }
+ move(16,0); printw("Extent: %3d",((buf[entrystart+12]&0xff)+((buf[entrystart+14]&0xff)<<5))/drive.extents);
+ printw(" (low: ");
+ if (offset==12) attron(A_REVERSE);
+ printw("%2d",buf[entrystart+12]&0xff);
+ attroff(A_REVERSE);
+ printw(", high: ");
+ if (offset==14) attron(A_REVERSE);
+ printw("%2d",buf[entrystart+14]&0xff);
+ attroff(A_REVERSE);
+ printw(")");
+ move(17,0); printw("Last extent record count: ");
+ if (offset==15) attron(A_REVERSE);
+ printw("%3d",buf[entrystart+15]&0xff);
+ attroff(A_REVERSE);
+ move(18,0); printw("Last record byte count: ");
+ if (offset==13) attron(A_REVERSE);
+ printw("%3d",buf[entrystart+13]&0xff);
+ attroff(A_REVERSE);
+ move(19,0); printw("Data blocks:");
+ for (i=0; i<16; ++i)
+ {
+ unsigned int block=buf[entrystart+16+i]&0xff;
+ if (drive.size>=256)
+ {
+ printw(" ");
+ if (offset==16+i || offset==16+i+1) attron(A_REVERSE);
+ printw("%5d",block|(((buf[entrystart+16+ ++i]&0xff)<<8)));
+ attroff(A_REVERSE);
+ }
+ else
+ {
+ printw(" ");
+ if (offset==16+i) attron(A_REVERSE);
+ printw("%3d",block);
+ attroff(A_REVERSE);
+ }
+ }
+ }
+ /*}}}*/
+ else if /* disc label */ /*{{{*/
+ (buf[entrystart]==0x20 && drive.type==CPMFS_DR3)
+ {
+ int i;
+ const struct tm *tm;
+ char s[30];
+
+ if (offset==0) attron(A_REVERSE);
+ printw("Disc label");
+ attroff(A_REVERSE);
+ move(15,0);
+ printw("Label: ");
+ for (i=0; i<11; ++i)
+ {
+ if (i+1==offset) attron(A_REVERSE);
+ printw("%c",buf[entrystart+1+i]&0x7f);
+ attroff(A_REVERSE);
+ }
+ move(16,0);
+ printw("Bit 0,7: ");
+ if (offset==12) attron(A_REVERSE);
+ printw("Label %s",buf[entrystart+12]&1 ? "set" : "not set");
+ printw(", password protection %s",buf[entrystart+12]&0x80 ? "set" : "not set");
+ attroff(A_REVERSE);
+ move(17,0);
+ printw("Bit 4,5,6: ");
+ if (offset==12) attron(A_REVERSE);
+ printw("Time stamp ");
+ if (buf[entrystart+12]&0x10) printw("on create, ");
+ else printw("not on create, ");
+ if (buf[entrystart+12]&0x20) printw("on modification, ");
+ else printw("not on modifiction, ");
+ if (buf[entrystart+12]&0x40) printw("on access");
+ else printw("not on access");
+ attroff(A_REVERSE);
+ move(18,0);
+ printw("Password: ");
+ for (i=0; i<8; ++i)
+ {
+ char printable;
+
+ if (offset==16+(7-i)) attron(A_REVERSE);
+ printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
+ printw("%c",isprint(printable) ? printable : ' ');
+ attroff(A_REVERSE);
+ }
+ printw(" XOR value: ");
+ if (offset==13) attron(A_REVERSE);
+ printw("0x%02x",buf[entrystart+13]&0xff);
+ attroff(A_REVERSE);
+ move(19,0);
+ printw("Created: ");
+ tm=cpmtime(buf[entrystart+24],buf[entrystart+25],buf[entrystart+26],buf[entrystart+27]);
+ if (offset==24 || offset==25) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==26) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==27) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+ printw(" Updated: ");
+ tm=cpmtime(buf[entrystart+28],buf[entrystart+29],buf[entrystart+30],buf[entrystart+31]);
+ if (offset==28 || offset==29) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==30) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==31) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+ }
+ /*}}}*/
+ else if /* time stamp */ /*{{{*/
+ (buf[entrystart]==0x21 && (drive.type==CPMFS_P2DOS || drive.type==CPMFS_DR3))
+ {
+ const struct tm *tm;
+ char s[30];
+
+ if (offset==0) attron(A_REVERSE);
+ printw("Time stamps");
+ attroff(A_REVERSE);
+ move(15,0);
+ printw("3rd last extent: Created/Accessed ");
+ tm=cpmtime(buf[entrystart+1],buf[entrystart+2],buf[entrystart+3],buf[entrystart+4]);
+ if (offset==1 || offset==2) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==3) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==4) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+ printw(" Modified ");
+ tm=cpmtime(buf[entrystart+5],buf[entrystart+6],buf[entrystart+7],buf[entrystart+8]);
+ if (offset==5 || offset==6) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==7) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==8) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+
+ move(16,0);
+ printw("2nd last extent: Created/Accessed ");
+ tm=cpmtime(buf[entrystart+11],buf[entrystart+12],buf[entrystart+13],buf[entrystart+14]);
+ if (offset==11 || offset==12) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==13) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==14) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+ printw(" Modified ");
+ tm=cpmtime(buf[entrystart+15],buf[entrystart+16],buf[entrystart+17],buf[entrystart+18]);
+ if (offset==15 || offset==16) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==17) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==18) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+
+ move(17,0);
+ printw(" Last extent: Created/Accessed ");
+ tm=cpmtime(buf[entrystart+21],buf[entrystart+22],buf[entrystart+23],buf[entrystart+24]);
+ if (offset==21 || offset==22) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==23) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==24) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+ printw(" Modified ");
+ tm=cpmtime(buf[entrystart+25],buf[entrystart+26],buf[entrystart+27],buf[entrystart+28]);
+ if (offset==25 || offset==26) attron(A_REVERSE);
+ strftime(s,sizeof(s),"%x",tm);
+ printw("%s",s);
+ attroff(A_REVERSE);
+ printw(" ");
+ if (offset==27) attron(A_REVERSE);
+ printw("%2d",tm->tm_hour);
+ attroff(A_REVERSE);
+ printw(":");
+ if (offset==28) attron(A_REVERSE);
+ printw("%02d",tm->tm_min);
+ attroff(A_REVERSE);
+ }
+ /*}}}*/
+ else if /* password */ /*{{{*/
+ (buf[entrystart]>=16 && buf[entrystart]<=31 && drive.type==CPMFS_DR3)
+ {
+ int i;
+
+ if (offset==0) attron(A_REVERSE);
+ printw("Password");
+ attroff(A_REVERSE);
+
+ move(15,0);
+ printw("Name: ");
+ for (i=0; i<8; ++i)
+ {
+ if (offset==1+i) attron(A_REVERSE);
+ printw("%c",buf[entrystart+1+i]&0x7f);
+ attroff(A_REVERSE);
+ }
+ printw(" Extension: ");
+ for (i=0; i<3; ++i)
+ {
+ if (offset==9+i) attron(A_REVERSE);
+ printw("%c",buf[entrystart+9+i]&0x7f);
+ attroff(A_REVERSE);
+ }
+
+ move(16,0);
+ printw("Password required for: ");
+ if (offset==12) attron(A_REVERSE);
+ if (buf[entrystart+12]&0x80) printw("Reading ");
+ if (buf[entrystart+12]&0x40) printw("Writing ");
+ if (buf[entrystart+12]&0x20) printw("Deleting ");
+ attroff(A_REVERSE);
+
+ move(17,0);
+ printw("Password: ");
+ for (i=0; i<8; ++i)
+ {
+ char printable;
+
+ if (offset==16+(7-i)) attron(A_REVERSE);
+ printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
+ printw("%c",isprint(printable) ? printable : ' ');
+ attroff(A_REVERSE);
+ }
+ printw(" XOR value: ");
+ if (offset==13) attron(A_REVERSE);
+ printw("0x%02x",buf[entrystart+13]&0xff);
+ attroff(A_REVERSE);
+ }
+ /*}}}*/
+ else /* bad status */ /*{{{*/
+ {
+ printw("Bad status ");
+ if (offset==0) attron(A_REVERSE);
+ printw("0x%02x",buf[entrystart]);
+ attroff(A_REVERSE);
+ }
+ /*}}}*/
+ if (!reload) data(&drive,buf,pos);
+ switch (ch=getch())
+ {
+ case 'F': /* next entry */ /*{{{*/
+ {
+ if (pos+32<(drive.sectrk*drive.tracks*(off_t)drive.secLength))
+ {
+ if (pos/drive.secLength!=(pos+32)/drive.secLength) reload=1;
+ pos+=32;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'B': /* previous entry */ /*{{{*/
+ {
+ if (pos>=32)
+ {
+ if (pos/drive.secLength!=(pos-32)/drive.secLength) reload=1;
+ pos-=32;
+ }
+ break;
+ }
+ /*}}}*/
+ }
+ }
+ /*}}}*/
+ else /* data area */ /*{{{*/
+ {
+ const char *msg;
+
+ msg="Data area"; move(0,(COLS-strlen(msg))/2); printw(msg);
+ if (!reload) data(&drive,buf,pos);
+ ch=getch();
+ }
+ /*}}}*/
+
+ /* process common commands */ /*{{{*/
+ switch (ch)
+ {
+ case 'n': /* next record */ /*{{{*/
+ {
+ if (pos+128<(drive.sectrk*drive.tracks*(off_t)drive.secLength))
+ {
+ if (pos/drive.secLength!=(pos+128)/drive.secLength) reload=1;
+ pos+=128;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'p': /* previous record */ /*{{{*/
+ {
+ if (pos>=128)
+ {
+ if (pos/drive.secLength!=(pos-128)/drive.secLength) reload=1;
+ pos-=128;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'N': /* next track */ /*{{{*/
+ {
+ if ((pos+drive.sectrk*drive.secLength)<(drive.sectrk*drive.tracks*drive.secLength))
+ {
+ pos+=drive.sectrk*drive.secLength;
+ reload=1;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'P': /* previous track */ /*{{{*/
+ {
+ if (pos>=drive.sectrk*drive.secLength)
+ {
+ pos-=drive.sectrk*drive.secLength;
+ reload=1;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'b': /* byte back */ /*{{{*/
+ {
+ if (pos)
+ {
+ if (pos/drive.secLength!=(pos-1)/drive.secLength) reload=1;
+ --pos;
+ }
+ break;
+ }
+ /*}}}*/
+ case 'f': /* byte forward */ /*{{{*/
+ {
+ if (pos+1
+#include
+#include
+#include
+
+#ifdef __VMS
+# include
+#endif
+
+#ifdef _LIBC
+# include
+#else
+#if 0
+# include
+# define _(msgid) gettext (msgid)
+#else
+# define _(msgid) (msgid)
+#endif
+#endif
+
+#if defined _LIBC && defined USE_IN_LIBIO
+# include
+#endif
+
+#ifndef attribute_hidden
+# define attribute_hidden
+#endif
+
+/* Unlike standard Unix `getopt', functions like `getopt_long'
+ let the user intersperse the options with the other arguments.
+
+ As `getopt_long' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Using `getopt' or setting the environment variable POSIXLY_CORRECT
+ disables permutation.
+ Then the application's behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt_int.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Keep a global copy of all internal members of getopt_data. */
+
+static struct _getopt_data getopt_data;
+
+
+#if defined HAVE_DECL_GETENV && !HAVE_DECL_GETENV
+extern char *getenv ();
+#endif
+
+#ifdef _LIBC
+/* Stored original parameters.
+ XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+extern int __libc_argc;
+extern char **__libc_argv;
+
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+# ifdef USE_NONOPTION_FLAGS
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+# endif
+
+# ifdef USE_NONOPTION_FLAGS
+# define SWAP_FLAGS(ch1, ch2) \
+ if (d->__nonoption_flags_len > 0) \
+ { \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+ }
+# else
+# define SWAP_FLAGS(ch1, ch2)
+# endif
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (char **argv, struct _getopt_data *d)
+{
+ int bottom = d->__first_nonopt;
+ int middle = d->__last_nonopt;
+ int top = d->optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ /* First make sure the handling of the `__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (d->__nonoption_flags_len > 0 && top >= d->__nonoption_flags_max_len)
+ {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc (top + 1);
+ if (new_str == NULL)
+ d->__nonoption_flags_len = d->__nonoption_flags_max_len = 0;
+ else
+ {
+ memset (__mempcpy (new_str, __getopt_nonoption_flags,
+ d->__nonoption_flags_max_len),
+ '\0', top + 1 - d->__nonoption_flags_max_len);
+ d->__nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ d->__first_nonopt += (d->optind - d->__last_nonopt);
+ d->__last_nonopt = d->optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+static const char *
+_getopt_initialize (int argc, char **argv, const char *optstring,
+ int posixly_correct, struct _getopt_data *d)
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ d->__first_nonopt = d->__last_nonopt = d->optind;
+
+ d->__nextchar = NULL;
+
+ d->__posixly_correct = posixly_correct || !!getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ d->__ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ d->__ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (d->__posixly_correct)
+ d->__ordering = REQUIRE_ORDER;
+ else
+ d->__ordering = PERMUTE;
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ if (!d->__posixly_correct
+ && argc == __libc_argc && argv == __libc_argv)
+ {
+ if (d->__nonoption_flags_max_len == 0)
+ {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ d->__nonoption_flags_max_len = -1;
+ else
+ {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = d->__nonoption_flags_max_len = strlen (orig_str);
+ if (d->__nonoption_flags_max_len < argc)
+ d->__nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *) malloc (d->__nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ d->__nonoption_flags_max_len = -1;
+ else
+ memset (__mempcpy (__getopt_nonoption_flags, orig_str, len),
+ '\0', d->__nonoption_flags_max_len - len);
+ }
+ }
+ d->__nonoption_flags_len = d->__nonoption_flags_max_len;
+ }
+ else
+ d->__nonoption_flags_len = 0;
+#endif
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options.
+
+ If POSIXLY_CORRECT is nonzero, behave as if the POSIXLY_CORRECT
+ environment variable were set. */
+
+int
+_getopt_internal_r (int argc, char **argv, const char *optstring,
+ const struct option *longopts, int *longind,
+ int long_only, int posixly_correct, struct _getopt_data *d)
+{
+ int print_errors = d->opterr;
+ if (optstring[0] == ':')
+ print_errors = 0;
+
+ if (argc < 1)
+ return -1;
+
+ d->optarg = NULL;
+
+ if (d->optind == 0 || !d->__initialized)
+ {
+ if (d->optind == 0)
+ d->optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize (argc, argv, optstring,
+ posixly_correct, d);
+ d->__initialized = 1;
+ }
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0' \
+ || (d->optind < d->__nonoption_flags_len \
+ && __getopt_nonoption_flags[d->optind] == '1'))
+#else
+# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')
+#endif
+
+ if (d->__nextchar == NULL || *d->__nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (d->__last_nonopt > d->optind)
+ d->__last_nonopt = d->optind;
+ if (d->__first_nonopt > d->optind)
+ d->__first_nonopt = d->optind;
+
+ if (d->__ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ exchange ((char **) argv, d);
+ else if (d->__last_nonopt != d->optind)
+ d->__first_nonopt = d->optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (d->optind < argc && NONOPTION_P)
+ d->optind++;
+ d->__last_nonopt = d->optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (d->optind != argc && !strcmp (argv[d->optind], "--"))
+ {
+ d->optind++;
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ exchange ((char **) argv, d);
+ else if (d->__first_nonopt == d->__last_nonopt)
+ d->__first_nonopt = d->optind;
+ d->__last_nonopt = argc;
+
+ d->optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (d->optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (d->__first_nonopt != d->__last_nonopt)
+ d->optind = d->__first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (d->__ordering == REQUIRE_ORDER)
+ return -1;
+ d->optarg = argv[d->optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ d->__nextchar = (argv[d->optind] + 1
+ + (longopts != NULL && argv[d->optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[d->optind][1] == '-'
+ || (long_only && (argv[d->optind][2]
+ || !strchr (optstring, argv[d->optind][1])))))
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar))
+ {
+ if ((unsigned int) (nameend - d->__nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else if (long_only
+ || pfound->has_arg != p->has_arg
+ || pfound->flag != p->flag
+ || pfound->val != p->val)
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[d->optind]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[d->optind]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ d->optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ d->optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+ int n;
+#endif
+
+ if (argv[d->optind - 1][1] == '-')
+ {
+ /* --option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("\
+%s: option `--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#else
+ fprintf (stderr, _("\
+%s: option `--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#endif
+ }
+ else
+ {
+ /* +option or -option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("\
+%s: option `%c%s' doesn't allow an argument\n"),
+ argv[0], argv[d->optind - 1][0],
+ pfound->name);
+#else
+ fprintf (stderr, _("\
+%s: option `%c%s' doesn't allow an argument\n"),
+ argv[0], argv[d->optind - 1][0],
+ pfound->name);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (n >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#endif
+ }
+
+ d->__nextchar += strlen (d->__nextchar);
+
+ d->optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (d->optind < argc)
+ d->optarg = argv[d->optind++];
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option `%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[d->optind][1] == '-'
+ || strchr (optstring, *d->__nextchar) == NULL)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+ int n;
+#endif
+
+ if (argv[d->optind][1] == '-')
+ {
+ /* --option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: unrecognized option `--%s'\n"),
+ argv[0], d->__nextchar);
+#else
+ fprintf (stderr, _("%s: unrecognized option `--%s'\n"),
+ argv[0], d->__nextchar);
+#endif
+ }
+ else
+ {
+ /* +option or -option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[d->optind][0], d->__nextchar);
+#else
+ fprintf (stderr, _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[d->optind][0], d->__nextchar);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (n >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#endif
+ }
+ d->__nextchar = (char *) "";
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *d->__nextchar++;
+ char *temp = strchr (optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*d->__nextchar == '\0')
+ ++d->optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+ int n;
+#endif
+
+ if (d->__posixly_correct)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: illegal option -- %c\n"),
+ argv[0], c);
+#else
+ fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c);
+#endif
+ }
+ else
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ n = __asprintf (&buf, _("%s: invalid option -- %c\n"),
+ argv[0], c);
+#else
+ fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (n >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#endif
+ }
+ d->optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ d->optind++;
+ }
+ else if (d->optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+#endif
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `d->optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ d->optarg = argv[d->optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '=';
+ nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar))
+ {
+ if ((unsigned int) (nameend - d->__nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[d->optind]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[d->optind]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ d->optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ d->optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option `-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr, _("\
+%s: option `-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#endif
+ }
+
+ d->__nextchar += strlen (d->__nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (d->optind < argc)
+ d->optarg = argv[d->optind++];
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option `%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2
+ |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[d->optind - 1]);
+#endif
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen (d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ d->__nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ d->optind++;
+ }
+ else
+ d->optarg = NULL;
+ d->__nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0')
+ {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ d->optind++;
+ }
+ else if (d->optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ if (__asprintf (&buf, _("\
+%s: option requires an argument -- %c\n"),
+ argv[0], c) >= 0)
+ {
+ _IO_flockfile (stderr);
+
+ int old_flags2 = ((_IO_FILE *) stderr)->_flags2;
+ ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL;
+
+ __fxprintf (NULL, "%s", buf);
+
+ ((_IO_FILE *) stderr)->_flags2 = old_flags2;
+ _IO_funlockfile (stderr);
+
+ free (buf);
+ }
+#else
+ fprintf (stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+#endif
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ d->optarg = argv[d->optind++];
+ d->__nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+_getopt_internal (int argc, char **argv, const char *optstring,
+ const struct option *longopts, int *longind,
+ int long_only, int posixly_correct)
+{
+ int result;
+
+ getopt_data.optind = optind;
+ getopt_data.opterr = opterr;
+
+ result = _getopt_internal_r (argc, argv, optstring, longopts, longind,
+ long_only, posixly_correct, &getopt_data);
+
+ optind = getopt_data.optind;
+ optarg = getopt_data.optarg;
+ optopt = getopt_data.optopt;
+
+ return result;
+}
+
+/* glibc gets a LSB-compliant getopt.
+ Standalone applications get a POSIX-compliant getopt. */
+#if _LIBC
+enum { POSIXLY_CORRECT = 0 };
+#else
+enum { POSIXLY_CORRECT = 1 };
+#endif
+
+int
+getopt (int argc, char *const *argv, const char *optstring)
+{
+ return _getopt_internal (argc, (char **) argv, optstring, NULL, NULL, 0,
+ POSIXLY_CORRECT);
+}
+
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt (argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/Tools/unix/cpmtools/getopt1.c b/Tools/unix/cpmtools/getopt1.c
new file mode 100644
index 00000000..7fa0c4e5
--- /dev/null
+++ b/Tools/unix/cpmtools/getopt1.c
@@ -0,0 +1,171 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+ Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98,2004,2006
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program 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.
+
+ This program 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.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifdef _LIBC
+# include
+#else
+# include "config.h"
+# include "getopt_.h"
+#endif
+#include "getopt_int.h"
+
+#include
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (int argc, char *__getopt_argv_const *argv, const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, (char **) argv, options, long_options,
+ opt_index, 0, 0);
+}
+
+int
+_getopt_long_r (int argc, char **argv, const char *options,
+ const struct option *long_options, int *opt_index,
+ struct _getopt_data *d)
+{
+ return _getopt_internal_r (argc, argv, options, long_options, opt_index,
+ 0, 0, d);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only (int argc, char *__getopt_argv_const *argv,
+ const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, (char **) argv, options, long_options,
+ opt_index, 1, 0);
+}
+
+int
+_getopt_long_only_r (int argc, char **argv, const char *options,
+ const struct option *long_options, int *opt_index,
+ struct _getopt_data *d)
+{
+ return _getopt_internal_r (argc, argv, options, long_options, opt_index,
+ 1, 0, d);
+}
+
+
+#ifdef TEST
+
+#include
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ static struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long (argc, argv, "abc:d:0123456789",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/Tools/unix/cpmtools/getopt_.h b/Tools/unix/cpmtools/getopt_.h
new file mode 100644
index 00000000..615ef9a3
--- /dev/null
+++ b/Tools/unix/cpmtools/getopt_.h
@@ -0,0 +1,226 @@
+/* Declarations for getopt.
+ Copyright (C) 1989-1994,1996-1999,2001,2003,2004,2005,2006,2007
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program 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.
+
+ This program 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.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef _GETOPT_H
+
+#ifndef __need_getopt
+# define _GETOPT_H 1
+#endif
+
+/* Standalone applications should #define __GETOPT_PREFIX to an
+ identifier that prefixes the external functions and variables
+ defined in this header. When this happens, include the
+ headers that might declare getopt so that they will not cause
+ confusion if included after this file. Then systematically rename
+ identifiers so that they do not collide with the system functions
+ and variables. Renaming avoids problems with some compilers and
+ linkers. */
+#if defined __GETOPT_PREFIX && !defined __need_getopt
+# include
+# include
+# include
+# undef __need_getopt
+# undef getopt
+# undef getopt_long
+# undef getopt_long_only
+# undef optarg
+# undef opterr
+# undef optind
+# undef optopt
+# define __GETOPT_CONCAT(x, y) x ## y
+# define __GETOPT_XCONCAT(x, y) __GETOPT_CONCAT (x, y)
+# define __GETOPT_ID(y) __GETOPT_XCONCAT (__GETOPT_PREFIX, y)
+# define getopt __GETOPT_ID (getopt)
+# define getopt_long __GETOPT_ID (getopt_long)
+# define getopt_long_only __GETOPT_ID (getopt_long_only)
+# define optarg __GETOPT_ID (optarg)
+# define opterr __GETOPT_ID (opterr)
+# define optind __GETOPT_ID (optind)
+# define optopt __GETOPT_ID (optopt)
+#endif
+
+/* Standalone applications get correct prototypes for getopt_long and
+ getopt_long_only; they declare "char **argv". libc uses prototypes
+ with "char *const *argv" that are incorrect because getopt_long and
+ getopt_long_only can permute argv; this is required for backward
+ compatibility (e.g., for LSB 2.0.1).
+
+ This used to be `#if defined __GETOPT_PREFIX && !defined __need_getopt',
+ but it caused redefinition warnings if both unistd.h and getopt.h were
+ included, since unistd.h includes getopt.h having previously defined
+ __need_getopt.
+
+ The only place where __getopt_argv_const is used is in definitions
+ of getopt_long and getopt_long_only below, but these are visible
+ only if __need_getopt is not defined, so it is quite safe to rewrite
+ the conditional as follows:
+*/
+#if !defined __need_getopt
+# if defined __GETOPT_PREFIX
+# define __getopt_argv_const /* empty */
+# else
+# define __getopt_argv_const const
+# endif
+#endif
+
+/* If __GNU_LIBRARY__ is not already defined, either we are being used
+ standalone, or this is the first header included in the source file.
+ If we are being used with glibc, we need to include , but
+ that does not exist if we are standalone. So: if __GNU_LIBRARY__ is
+ not defined, include , which will pull in for us
+ if it's from glibc. (Why ctype.h? It's guaranteed to exist and it
+ doesn't flood the namespace with stuff the way some other headers do.) */
+#if !defined __GNU_LIBRARY__
+# include
+#endif
+
+#ifndef __THROW
+# ifndef __GNUC_PREREQ
+# define __GNUC_PREREQ(maj, min) (0)
+# endif
+# if defined __cplusplus && __GNUC_PREREQ (2,8)
+# define __THROW throw ()
+# else
+# define __THROW
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+#ifndef __need_getopt
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+ const char *name;
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+# define no_argument 0
+# define required_argument 1
+# define optional_argument 2
+#endif /* need getopt */
+
+
+/* Get definitions and prototypes for functions to process the
+ arguments in ARGV (ARGC of them, minus the program name) for
+ options given in OPTS.
+
+ Return the option character from OPTS just read. Return -1 when
+ there are no more options. For unrecognized options, or options
+ missing arguments, `optopt' is set to the option letter, and '?' is
+ returned.
+
+ The OPTS string is a list of characters which are recognized option
+ letters, optionally followed by colons, specifying that that letter
+ takes an argument, to be placed in `optarg'.
+
+ If a letter in OPTS is followed by two colons, its argument is
+ optional. This behavior is specific to the GNU `getopt'.
+
+ The argument `--' causes premature termination of argument
+ scanning, explicitly telling `getopt' that there are no more
+ options.
+
+ If OPTS begins with `-', then non-option arguments are treated as
+ arguments to the option '\1'. This behavior is specific to the GNU
+ `getopt'. If OPTS begins with `+', or POSIXLY_CORRECT is set in
+ the environment, then do not permute arguments. */
+
+extern int getopt (int ___argc, char *const *___argv, const char *__shortopts)
+ __THROW;
+
+#ifndef __need_getopt
+extern int getopt_long (int ___argc, char *__getopt_argv_const *___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind)
+ __THROW;
+extern int getopt_long_only (int ___argc, char *__getopt_argv_const *___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind)
+ __THROW;
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Make sure we later can get all the definitions and declarations. */
+#undef __need_getopt
+
+#endif /* getopt.h */
diff --git a/Tools/unix/cpmtools/getopt_int.h b/Tools/unix/cpmtools/getopt_int.h
new file mode 100644
index 00000000..401579fd
--- /dev/null
+++ b/Tools/unix/cpmtools/getopt_int.h
@@ -0,0 +1,131 @@
+/* Internal declarations for getopt.
+ Copyright (C) 1989-1994,1996-1999,2001,2003,2004
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ This program 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.
+
+ This program 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.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef _GETOPT_INT_H
+#define _GETOPT_INT_H 1
+
+extern int _getopt_internal (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ int __long_only, int __posixly_correct);
+
+
+/* Reentrant versions which can handle parsing multiple argument
+ vectors at the same time. */
+
+/* Data type for reentrant functions. */
+struct _getopt_data
+{
+ /* These have exactly the same meaning as the corresponding global
+ variables, except that they are used for the reentrant
+ versions of getopt. */
+ int optind;
+ int opterr;
+ int optopt;
+ char *optarg;
+
+ /* Internal members. */
+
+ /* True if the internal members have been initialized. */
+ int __initialized;
+
+ /* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+ char *__nextchar;
+
+ /* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters, or by calling getopt.
+
+ PERMUTE is the default. We permute the contents of ARGV as we
+ scan, so that eventually all the non-options are at the end.
+ This allows options to be given in any order, even with programs
+ that were not written to expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were
+ written to expect options and other ARGV-elements in any order
+ and that care about the ordering of the two. We describe each
+ non-option ARGV-element as if it were the argument of an option
+ with character code 1. Using `-' as the first character of the
+ list of option characters selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+ enum
+ {
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+ } __ordering;
+
+ /* If the POSIXLY_CORRECT environment variable is set
+ or getopt was called. */
+ int __posixly_correct;
+
+
+ /* Handle permutation of arguments. */
+
+ /* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first
+ of them; `last_nonopt' is the index after the last of them. */
+
+ int __first_nonopt;
+ int __last_nonopt;
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ int __nonoption_flags_max_len;
+ int __nonoption_flags_len;
+# endif
+};
+
+/* The initializer is necessary to set OPTIND and OPTERR to their
+ default values and to clear the initialization flag. */
+#define _GETOPT_DATA_INITIALIZER { 1, 1 }
+
+extern int _getopt_internal_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ int __long_only, int __posixly_correct,
+ struct _getopt_data *__data);
+
+extern int _getopt_long_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind,
+ struct _getopt_data *__data);
+
+extern int _getopt_long_only_r (int ___argc, char **___argv,
+ const char *__shortopts,
+ const struct option *__longopts,
+ int *__longind,
+ struct _getopt_data *__data);
+
+#endif /* getopt_int.h */
diff --git a/Tools/unix/cpmtools/mkfs.cpm.1 b/Tools/unix/cpmtools/mkfs.cpm.1
new file mode 100644
index 00000000..b31fb7ce
--- /dev/null
+++ b/Tools/unix/cpmtools/mkfs.cpm.1
@@ -0,0 +1,70 @@
+.TH MKFS.CPM 1 "October 25, 2014" "CP/M tools" "User commands"
+.SH NAME \"{{{roff}}}\"{{{
+mkfs.cpm \- make a CP/M file system
+.\"}}}
+.SH SYNOPSIS \"{{{
+.ad l
+.B mkfs.cpm
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boot ]
+.RB [ \-L
+.IR label ]
+.RB [ \-t ]
+.I image
+.ad b
+.\"}}}
+.SH DESCRIPTION \"{{{
+\fBmkfs.cpm\fP makes a CP/M file system on an image file or device.
+.\"}}}
+.SH OPTIONS \"{{{
+.IP "\fB\-f\fP \fIformat\fP"
+Use the given CP/M disk \fIformat\fP instead of the default format.
+.IP "\fB\-b\fP \fIbootblock\fP"
+Write the contents of the file \fIbootblock\fP to the system tracks
+instead of filling them with 0xe5. This option can be used up to four
+times. The file contents (typically boot block, CCP, BDOS and BIOS)
+are written to sequential sectors, padding with 0xe5 if needed.
+.IP "\fB\-L\fP \fIlabel\fP"
+Label the file system. This is only supported by CP/M Plus.
+.IP "\fB\-t\fP"
+Create time stamps.
+.\"}}}
+.SH "RETURN VALUE" \"{{{
+Upon successful completion, exit code 0 is returned.
+.\"}}}
+.SH ERRORS \"{{{
+Any errors are indicated by exit code 1.
+.\"}}}
+.SH ENVIRONMENT \"{{{
+CPMTOOLSFMT Default format
+.\"}}}
+.SH FILES \"{{{
+${prefix}/share/diskdefs CP/M disk format definitions
+.\"}}}
+.SH AUTHORS \"{{{
+This program is copyright 1997\(en2012 Michael Haardt
+. The Windows port is copyright 2000, 2001, 2011 John Elliott
+.
+.PP
+This program 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 3 of the License, or
+(at your option) any later version.
+.PP
+This program 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.
+.PP
+You should have received a copy of the GNU General Public License along
+with this program. If not, write to the Free Software Foundation, Inc.,
+59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.\"}}}
+.SH "SEE ALSO" \"{{{
+.IR fsck.cpm (1),
+.IR cpmls (1),
+.IR mkfs (1),
+.IR cpm (5)
+.\"}}}
diff --git a/Tools/unix/cpmtools/mkfs.cpm.c b/Tools/unix/cpmtools/mkfs.cpm.c
new file mode 100644
index 00000000..2c37bdfd
--- /dev/null
+++ b/Tools/unix/cpmtools/mkfs.cpm.c
@@ -0,0 +1,234 @@
+/* #includes */ /*{{{C}}}*//*{{{*/
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "getopt_.h"
+#include "cpmfs.h"
+
+#ifdef USE_DMALLOC
+#include
+#endif
+/*}}}*/
+/* #defines */ /*{{{*/
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+/*}}}*/
+
+/* mkfs -- make file system */ /*{{{*/
+static int mkfs(struct cpmSuperBlock *drive, const char *name, const char *format, const char *label, char *bootTracks, int timeStamps)
+{
+ /* variables */ /*{{{*/
+ unsigned int i;
+ char buf[128];
+ char firstbuf[128];
+ int fd;
+ unsigned int bytes;
+ unsigned int trkbytes;
+ /*}}}*/
+
+ /* open image file */ /*{{{*/
+ if ((fd = open(name, O_BINARY|O_CREAT|O_WRONLY, 0666)) < 0)
+ {
+ boo=strerror(errno);
+ return -1;
+ }
+ /*}}}*/
+ /* write system tracks */ /*{{{*/
+ /* this initialises only whole tracks, so it skew is not an issue */
+ trkbytes=drive->secLength*drive->sectrk;
+ for (i=0; iboottrk; i+=drive->secLength) if (write(fd, bootTracks+i, drive->secLength)!=(ssize_t)drive->secLength)
+ {
+ boo=strerror(errno);
+ close(fd);
+ return -1;
+ }
+ /*}}}*/
+ /* write directory */ /*{{{*/
+ memset(buf,0xe5,128);
+ bytes=drive->maxdir*32;
+ if (bytes%trkbytes) bytes=((bytes+trkbytes)/trkbytes)*trkbytes;
+ if (timeStamps && (drive->type==CPMFS_P2DOS || drive->type==CPMFS_DR3)) buf[3*32]=0x21;
+ memcpy(firstbuf,buf,128);
+ if (drive->type==CPMFS_DR3)
+ {
+ time_t now;
+ struct tm *t;
+ int min,hour,days;
+
+ firstbuf[0]=0x20;
+ for (i=0; i<11 && *label; ++i,++label) firstbuf[1+i]=toupper(*label&0x7f);
+ while (i<11) firstbuf[1+i++]=' ';
+ firstbuf[12]=timeStamps ? 0x11 : 0x01; /* label set and first time stamp is creation date */
+ memset(&firstbuf[13],0,1+2+8);
+ if (timeStamps)
+ {
+ int year;
+
+ /* Stamp label. */
+ time(&now);
+ t=localtime(&now);
+ min=((t->tm_min/10)<<4)|(t->tm_min%10);
+ hour=((t->tm_hour/10)<<4)|(t->tm_hour%10);
+ for (year=1978,days=0; year<1900+t->tm_year; ++year)
+ {
+ days+=365;
+ if (year%4==0 && (year%100!=0 || year%400==0)) ++days;
+ }
+ days += t->tm_yday + 1;
+ firstbuf[24]=firstbuf[28]=days&0xff; firstbuf[25]=firstbuf[29]=days>>8;
+ firstbuf[26]=firstbuf[30]=hour;
+ firstbuf[27]=firstbuf[31]=min;
+ }
+ }
+ for (i=0; itype==CPMFS_P2DOS || drive->type==CPMFS_DR3)) /*{{{*/
+ {
+ int offset,j;
+ struct cpmInode ino, root;
+ static const char sig[] = "!!!TIME";
+ unsigned int records;
+ struct dsDate *ds;
+ struct cpmSuperBlock super;
+ const char *err;
+
+ if ((err=Device_open(&super.dev,name,O_RDWR,NULL)))
+ {
+ fprintf(stderr,"%s: can not open %s (%s)\n",cmd,name,err);
+ exit(1);
+ }
+ cpmReadSuper(&super,&root,format);
+
+ records=root.sb->maxdir/8;
+ if (!(ds=malloc(records*128)))
+ {
+ cpmUmount(&super);
+ return -1;
+ }
+ memset(ds,0,records*128);
+ offset=15;
+ for (i=0; ids=ds;
+ root.sb->dirtyDs=1;
+ cpmUmount(&super);
+ }
+ /*}}}*/
+
+ return 0;
+}
+/*}}}*/
+
+const char cmd[]="mkfs.cpm";
+
+int main(int argc, char *argv[]) /*{{{*/
+{
+ char *image;
+ const char *format;
+ int c,usage=0;
+ struct cpmSuperBlock drive;
+ struct cpmInode root;
+ const char *label="unlabeled";
+ int timeStamps=0;
+ size_t bootTrackSize,used;
+ char *bootTracks;
+ const char *boot[4]={(const char*)0,(const char*)0,(const char*)0,(const char*)0};
+
+ if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT;
+ while ((c=getopt(argc,argv,"b:f:L:th?"))!=EOF) switch(c)
+ {
+ case 'b':
+ {
+ if (boot[0]==(const char*)0) boot[0]=optarg;
+ else if (boot[1]==(const char*)0) boot[1]=optarg;
+ else if (boot[2]==(const char*)0) boot[2]=optarg;
+ else if (boot[3]==(const char*)0) boot[3]=optarg;
+ else usage=1;
+ break;
+ }
+ case 'f': format=optarg; break;
+ case 'L': label=optarg; break;
+ case 't': timeStamps=1; break;
+ case 'h':
+ case '?': usage=1; break;
+ }
+
+ if (optind!=(argc-1)) usage=1;
+ else image=argv[optind++];
+
+ if (usage)
+ {
+ fprintf(stderr,"Usage: %s [-f format] [-b boot] [-L label] [-t] image\n",cmd);
+ exit(1);
+ }
+ drive.dev.opened=0;
+ cpmReadSuper(&drive,&root,format);
+ bootTrackSize=drive.boottrk*drive.secLength*drive.sectrk;
+ if ((bootTracks=malloc(bootTrackSize))==(void*)0)
+ {
+ fprintf(stderr,"%s: can not allocate boot track buffer: %s\n",cmd,strerror(errno));
+ exit(1);
+ }
+ memset(bootTracks,0xe5,bootTrackSize);
+ used=0;
+ for (c=0; c<4 && boot[c]; ++c)
+ {
+ int fd;
+ size_t size;
+
+ if ((fd=open(boot[c],O_BINARY|O_RDONLY))==-1)
+ {
+ fprintf(stderr,"%s: can not open %s: %s\n",cmd,boot[c],strerror(errno));
+ exit(1);
+ }
+ size=read(fd,bootTracks+used,bootTrackSize-used);
+#if 0
+ fprintf(stderr,"%d %04x %s\n",c,used+0x800,boot[c]);
+#endif
+ if (size%drive.secLength) size=(size|(drive.secLength-1))+1;
+ used+=size;
+ close(fd);
+ }
+ if (mkfs(&drive,image,format,label,bootTracks,timeStamps)==-1)
+ {
+ fprintf(stderr,"%s: can not make new file system: %s\n",cmd,boo);
+ exit(1);
+ }
+ else exit(0);
+}
+/*}}}*/
diff --git a/Tools/unix/uz80as/Makefile b/Tools/unix/uz80as/Makefile
new file mode 100644
index 00000000..d158f4dd
--- /dev/null
+++ b/Tools/unix/uz80as/Makefile
@@ -0,0 +1,66 @@
+# ===========================================================================
+# uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+# ===========================================================================
+
+DEST = ../../`uname`
+CC = gcc
+CFLAGS = -g
+
+OBJECTS = ngetopt.o main.o options.o \
+ utils.o err.o incl.o sym.o \
+ expr.o exprint.o pp.o list.o \
+ prtable.o uz80as.o targets.o \
+ z80.o gbcpu.o \
+ dp2200.o i4004.o \
+ i8008.o i8048.o \
+ i8051.o i8080.o \
+ mos6502.o mc6800.o
+
+SOURCES = \
+ config.h \
+ ngetopt.c ngetopt.h \
+ main.c \
+ options.c options.h \
+ utils.c utils.h \
+ err.c err.h \
+ incl.c incl.h \
+ sym.c sym.h \
+ expr.c expr.h \
+ exprint.c exprint.h \
+ pp.c pp.h \
+ list.c list.h \
+ prtable.c prtable.h \
+ uz80as.c uz80as.h \
+ targets.c targets.h \
+ z80.c \
+ gbcpu.c \
+ dp2200.c \
+ i4004.c \
+ i8008.c \
+ i8048.c \
+ i8051.c \
+ i8080.c \
+ mos6502.c \
+ mc6800.c
+
+all: uz80as $(DEST)
+ cp uz80as $(DEST)
+
+$(DEST):
+ mkdir -p $(DEST)
+
+clobber: clean
+ -rm -f uz80as $(DEST)/uz80as
+
+clean:
+ -rm -f $(OBJECTS)
+
+uz80as: $(OBJECTS)
+ $(CC) $(CFLAGS) -o uz80as $(OBJECTS)
+
+test: test.asm uz80as
+ ./uz80as test.asm
+ cat test.lst
+
+.c.o:
+ $(CC) $(CFLAGS) -I. -c $< -o $@
diff --git a/Tools/unix/uz80as/config.h b/Tools/unix/uz80as/config.h
new file mode 100644
index 00000000..cca14cbd
--- /dev/null
+++ b/Tools/unix/uz80as/config.h
@@ -0,0 +1,29 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Years of copyright */
+#define COPYRIGHT_YEARS "2018"
+
+/* Name of package */
+#define PACKAGE "uz80as"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "jorge.giner@hotmail.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "uz80as"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "uz80as 1.10"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "uz80as"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "https://jorgicor.niobe.org/uz80as"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.10"
+
+/* Version number of package */
+#define VERSION "1.10"
diff --git a/Tools/unix/uz80as/dp2200.c b/Tools/unix/uz80as/dp2200.c
new file mode 100644
index 00000000..c6599247
--- /dev/null
+++ b/Tools/unix/uz80as/dp2200.c
@@ -0,0 +1,208 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Datapoint 2200.
+ * ===========================================================================
+ */
+
+/*
+ * Datapoint 2200 Version I, 2K to 8K mem (program counter 13 bits).
+ * Datapoint 2200 Version II, 2K to 16K mem (protram counter 14 bits).
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: ADA,ADB,ADC,ADD,ADH,ADL,ADM,
+ * ACA,ACB,ACC,ACD,ACH,ACL,ACM,
+ * SUA,SUB,SUC,SUD,SUH,SUL,SUM,
+ * SBA,SBB,SBC,SBD,SBH,SBL,SBM,
+ * NDA,NDB,NDC,NDD,NDH,NDL,NDM,
+ * XRA,XRB,XRC,XRD,XRH,XRL,XRM,
+ * ORA,ORB,ORC,ORD,ORH,ORL,ORM,
+ * CPA,CPB,CPC,CPD,CPH,CPL,CPM
+ * c: NOP,LAB,LAC,LAD,LAE,LAH,LAL,LAM,
+ * LBA,LBC,LBD,LBE,LBH,LBL,LBM,
+ * LCA,LCB,LCD,LCE,LCH,LCL,LCM,
+ * LDA,LDB,LDC,LDE,LDH,LDL,LDM,
+ * LEA,LEB,LEC,LED,LEH,LEL,LEM,
+ * LHA,LHB,LHC,LHD,LHE,LHL,LHM,
+ * LLA,LLB,LLC,LLD,LLE,LLH,LLM,
+ * LMA,LMB,LMC,LMD,LME,LMH,LML,HALT
+ * d: ADR,STATUS,DATA,WRITE,COM1,COM2,COM3,COM4
+ * BEEP,CLICK,DECK1,DECK2,
+ * RBK,WBK,BSP,SF,SB,REWND,TSTOP
+ * e: RFC,RFS,RTC,RTS,RFZ,RFP,RTZ,RTP
+ * f: JFC,JFZ,JFS,JFP,JTC,JTZ,JTS,JTP
+ * g: AD,SU,ND,OR,AC,SB,XR,CP
+ * h: LA,LB,LC,LD,LE,LH,LL
+ * i: CFC,CFZ,CFS,CFP,CTC,CTZ,CTS,CTP
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: (op << 1) + lastbyte
+ * g: (op << 4) | lastbyte
+ */
+
+const struct matchtab s_matchtab_dp2200[] = {
+ { "SLC", "02.", 3, 0 },
+ { "SRC", "0A.", 3, 0 },
+ { "RETURN", "07.", 3, 0 },
+ { "INPUT", "41.", 3, 0 },
+ { "b", "80c0.", 3, 0 },
+ { "c", "C0c0.", 3, 0 },
+ { "EX d", "51f0.", 3, 0 },
+ { "e", "03b0.", 3, 0 },
+ { "g a", "04b0.d1.", 3, 0, "e8" },
+ { "h a", "06b0.d1.", 3, 0, "e8"},
+ { "f a", "40b0.e1", 3, 0 },
+ { "i a", "42b0.e1", 3, 0 },
+ { "JMP a", "44.e0", 3, 0 },
+ { "CALL a", "46.e0", 3, 0 },
+ /* version II */
+ { "BETA", "10.", 2, 0 },
+ { "DI", "20.", 2, 0 },
+ { "POP", "30.", 2, 0 },
+ { "ALPHA", "18.", 2, 0 },
+ { "EI", "28.", 2, 0 },
+ { "PUSH", "38.", 2, 0 },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+"ADA", "ADB", "ADC", "ADD", "ADE", "ADH", "ADL", "ADM",
+"ACA", "ACB", "ACC", "ACD", "ACE", "ACH", "ACL", "ACM",
+"SUA", "SUB", "SUC", "SUD", "SUE", "SUH", "SUL", "SUM",
+"SBA", "SBB", "SBC", "SBD", "SBE", "SBH", "SBL", "SBM",
+"NDA", "NDB", "NDC", "NDD", "NDE", "NDH", "NDL", "NDM",
+"XRA", "XRB", "XRC", "XRD", "XRE", "XRH", "XRL", "XRM",
+"ORA", "ORB", "ORC", "ORD", "ORE", "ORH", "ORL", "ORM",
+"CPA", "CPB", "CPC", "CPD", "CPE", "CPH", "CPL", "CPM",
+NULL };
+
+static const char *const cval[] = {
+"NOP", "LAB", "LAC", "LAD", "LAE", "LAH", "LAL", "LAM",
+"LBA", "", "LBC", "LBD", "LBE", "LBH", "LBL", "LBM",
+"LCA", "LCB", "", "LCD", "LCE", "LCH", "LCL", "LCM",
+"LDA", "LDB", "LDC", "", "LDE", "LDH", "LDL", "LDM",
+"LEA", "LEB", "LEC", "LED", "", "LEH", "LEL", "LEM",
+"LHA", "LHB", "LHC", "LHD", "LHE", "", "LHL", "LHM",
+"LLA", "LLB", "LLC", "LLD", "LLE", "LLH", "", "LLM",
+"LMA", "LMB", "LMC", "LMD", "LME", "LMH", "LML", "HALT",
+NULL };
+
+static const char *const dval[] = {
+"ADR", "STATUS", "DATA", "WRITE", "COM1", "COM2", "COM3", "COM4",
+"", "", "", "", "BEEP", "CLICK", "DECK1", "DECK2",
+"RBK", "WBK", "", "BSP", "SF", "SB", "REWND", "TSTOP",
+NULL };
+
+static const char *const eval[] = { "RFC", "RFZ", "RFS", "RFP",
+ "RTC", "RTZ", "RTS", "RTP",
+ NULL };
+
+static const char *const fval[] = { "JFC", "JFZ", "JFS", "JFP",
+ "JTC", "JTZ", "JTS", "JTP",
+ NULL };
+
+static const char *const gval[] = { "AD", "AC", "SU", "SB",
+ "ND", "XR", "OR", "CP",
+ NULL };
+
+static const char *const hval[] = { "LA", "LB", "LC", "LD",
+ "LE", "LH", "LL",
+ NULL };
+
+static const char *const ival[] = { "CFC", "CFZ", "CFS", "CFP",
+ "CTC", "CTZ", "CTS", "CTP",
+ NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval,
+ gval, hval, ival
+};
+
+static int match_dp2200(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'i') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_dp2200(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': b += (vs[i] << 1); break;
+ case 'g': b |= (vs[i] << 4); break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_dp2200(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_dp2200(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'n') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_dp2200 = {
+ .id = "dp2200",
+ .descr = "Datapoint 2200 Version I",
+ .matcht = s_matchtab_dp2200,
+ .matchf = match_dp2200,
+ .genf = gen_dp2200,
+ .pat_char_rewind = pat_char_rewind_dp2200,
+ .pat_next_str = pat_next_str_dp2200,
+ .mask = 1
+};
+
+const struct target s_target_dp2200ii = {
+ .id = "dp2200ii",
+ .descr = "Datapoint 2200 Version II",
+ .matcht = s_matchtab_dp2200,
+ .matchf = match_dp2200,
+ .genf = gen_dp2200,
+ .pat_char_rewind = pat_char_rewind_dp2200,
+ .pat_next_str = pat_next_str_dp2200,
+ .mask = 2
+};
diff --git a/Tools/unix/uz80as/err.c b/Tools/unix/uz80as/err.c
new file mode 100644
index 00000000..b6745ae8
--- /dev/null
+++ b/Tools/unix/uz80as/err.c
@@ -0,0 +1,196 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Error reporting.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "err.h"
+#include "incl.h"
+
+#ifndef ASSERT_H
+#include
+#endif
+
+#ifndef CTYPE_H
+#include
+#endif
+
+#ifndef STDARG_H
+#include
+#endif
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STDLIB_H
+#include
+#endif
+
+#ifndef STRING_H
+#include
+#endif
+
+/* Max number of errors before halt. */
+#define MAXERR 64
+
+int s_nerrors;
+
+static void eprfl(void)
+{
+ fprintf(stderr, "%s:%d: ", curfile()->name, curfile()->linenum);
+}
+
+static void eprwarn(void)
+{
+ fputs(_("warning:"), stderr);
+ fputc(' ', stderr);
+}
+
+/* Print the characters in [p, q[ to stderr. */
+void echars(const char *p, const char *q)
+{
+ while (*p != '\0' && p != q) {
+ fputc(*p, stderr);
+ p++;
+ }
+}
+
+/*
+ * Print a space, an opening parenthesis, the characters in [p, q[,
+ * and a closing parenthesis to stderr.
+ */
+void epchars(const char *p, const char *q)
+{
+ fputs(" (", stderr);
+ echars(p, q);
+ fputs(")", stderr);
+}
+
+/*
+ * Increments the number of errors, and exit with failure if
+ * maximum number of errors allowed is reached.
+ */
+void newerr(void)
+{
+ s_nerrors++;
+ if (s_nerrors >= MAXERR) {
+ eprogname();
+ fprintf(stderr, _("exiting: too many errors"));
+ enl();
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void evprint(int warn, const char *ecode, va_list args)
+{
+ if (nfiles() > 0)
+ eprfl();
+ else
+ eprogname();
+
+ if (warn)
+ eprwarn();
+
+ assert(ecode != NULL);
+ vfprintf(stderr, ecode, args);
+}
+
+/* Prints only the printable characters, the rst as space. */
+static void eprint_printable(const char *p)
+{
+ for (; *p != '\0'; p++) {
+ if (isprint(*p))
+ putc(*p, stderr);
+ else
+ putc(' ', stderr);
+ }
+}
+
+/* Prints the line and a marker pointing to the charcater q inside line. */
+void eprcol(const char *line, const char *q)
+{
+ putc(' ', stderr);
+ eprint_printable(line);
+ fputs("\n ", stderr);
+ while (line != q) {
+ putc(' ', stderr);
+ line++;
+ }
+ fputs("^\n", stderr);
+}
+
+/*
+ * Like fprintf but prints to stderr.
+ * If we are parsing any file (incl.c), print first the file and the line.
+ * If not, print first the program name.
+ */
+void eprint(const char *ecode, ...)
+{
+ va_list args;
+
+ va_start(args, ecode);
+ evprint(0, ecode, args);
+ va_end(args);
+}
+
+/* Same as eprint, but print "warning: " before ecode str. */
+void wprint(const char *ecode, ...)
+{
+ va_list args;
+
+ va_start(args, ecode);
+ evprint(1, ecode, args);
+ va_end(args);
+}
+
+/* Print \n on stderr. */
+void enl(void)
+{
+ fputc('\n', stderr);
+}
+
+/* Print the program name on stderr. */
+void eprogname(void)
+{
+ fprintf(stderr, PACKAGE": ");
+}
+
+/* Call malloc, but if no memory, print that error and exit with failure. */
+void *emalloc(size_t n)
+{
+ void *p;
+
+ if ((p = malloc(n)) == NULL) {
+ eprint(_("malloc fail\n"));
+ exit(EXIT_FAILURE);
+ }
+ // printf("emalloc: %d = %x\n", n, p);
+ return p;
+}
+
+/* Call realloc, but if no memory, print that error and exit with failure. */
+void *erealloc(void *p, size_t n)
+{
+ // void *q = p;
+ if ((p = realloc(p, n)) == NULL) {
+ eprint(_("realloc fail\n"));
+ exit(EXIT_FAILURE);
+ }
+ // printf("erealloc: %x %d = %x\n", q, n, p);
+ return p;
+}
+
+/* Call fopen, but if any error, print it and exit with failure. */
+FILE *efopen(const char *fname, const char *ops)
+{
+ FILE *fp;
+
+ if ((fp = fopen(fname, ops)) == NULL) {
+ eprint(_("cannot open file %s\n"), fname);
+ exit(EXIT_FAILURE);
+ }
+ return fp;
+}
diff --git a/Tools/unix/uz80as/err.h b/Tools/unix/uz80as/err.h
new file mode 100644
index 00000000..86d8e1dc
--- /dev/null
+++ b/Tools/unix/uz80as/err.h
@@ -0,0 +1,32 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Error reporting.
+ * ===========================================================================
+ */
+
+#ifndef ERR_H
+#define ERR_H
+
+#ifndef STDIO_H
+#define STDIO_H
+#include
+#endif
+
+#define _(str) (str)
+
+extern int s_nerrors;
+
+void newerr(void);
+void eprogname(void);
+void echars(const char *p, const char *q);
+void epchars(const char *p, const char *q);
+void eprint(const char *ecode, ...);
+void wprint(const char *ecode, ...);
+void eprcol(const char *line, const char *q);
+void enl(void);
+void *emalloc(size_t n);
+void *erealloc(void *p, size_t n);
+FILE *efopen(const char *fname, const char *ops);
+
+#endif
diff --git a/Tools/unix/uz80as/expr.c b/Tools/unix/uz80as/expr.c
new file mode 100644
index 00000000..2897461f
--- /dev/null
+++ b/Tools/unix/uz80as/expr.c
@@ -0,0 +1,445 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Expression parsing.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "expr.h"
+#include "utils.h"
+#include "err.h"
+#include "sym.h"
+
+#ifndef ASSERT_H
+#include
+#endif
+
+#ifndef CTYPE_H
+#include
+#endif
+
+#ifndef LIMITS_H
+#include
+#endif
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STDLIB_H
+#include
+#endif
+
+/* Max nested expressions. */
+#define ESTKSZ 16
+#define ESTKSZ2 (ESTKSZ*2)
+
+/* Return -1 on syntax error.
+ * *p must be a digit already.
+ * *q points to one past the end of the number without suffix.
+ */
+static int takenum(const char *p, const char *q, int radix)
+{
+ int k, n;
+
+ n = 0;
+ while (p != q) {
+ k = hexval(*p);
+ p++;
+ if (k >= 0 && k < radix)
+ n = n * radix + k;
+ else
+ return -1;
+ }
+ return n;
+}
+
+/* Go to the end of a number (advance all digits or letters). */
+static const char *goendnum(const char *p)
+{
+ const char *q;
+
+ for (q = p; isalnum(*q); q++)
+ ;
+ return q;
+}
+
+/*
+ * Returns NULL on error.
+ * '*p' must be a digit already.
+ */
+static const char *getnum(const char *p, int *v)
+{
+ int n;
+ char c;
+ const char *q;
+
+ assert(isdigit(*p));
+
+ n = 0;
+ q = goendnum(p) - 1;
+ if (isalpha(*q)) {
+ c = toupper(*q);
+ if (c == 'H') {
+ n = takenum(p, q, 16);
+ } else if (c == 'D') {
+ n = takenum(p, q, 10);
+ } else if (c == 'O') {
+ n = takenum(p, q, 8);
+ } else if (c == 'B') {
+ n = takenum(p, q, 2);
+ } else {
+ return NULL;
+ }
+ } else {
+ n = takenum(p, q + 1, 10);
+ }
+
+ if (n < 0)
+ return NULL;
+
+ *v = n;
+ return q + 1;
+}
+
+/*
+ * Gets a number that was prefixed.
+ * Returns NULL on error.
+ */
+static const char *getpnum(const char *p, int radix, int *v)
+{
+ const char *q;
+ int n;
+
+ q = goendnum(p);
+ n = takenum(p, q, radix);
+ if (n < 0)
+ return NULL;
+ *v = n;
+ return q;
+}
+
+/* Left shift */
+static int shl(int r, int n)
+{
+ n &= int_precission();
+ return r << n;
+}
+
+
+/* Portable arithmetic right shift. */
+static int ashr(int r, int n)
+{
+ n &= int_precission();
+ if (r & INT_MIN) {
+ return ~(~r >> n);
+ } else {
+ return r >> n;
+ }
+}
+
+/* Parses expression pointed by 'p'.
+ * If success, returns pointer to the end of parsed expression, and
+ * 'v' contains the calculated value of the expression.
+ * Returns NULL if a syntactic error has occurred.
+ * Operators are evaluated left to right.
+ * To allow precedence use parenthesis.
+ * 'linepc' is the program counter to consider when we find the $ current
+ * pointer location symbol ($).
+ * 'allowfr' stands for 'allow forward references'. We will issue an error
+ * if we find a label that is not defined.
+ * 'ecode' will be valid if NULL is returned. NULL can be passed as ecode.
+ * 'ep' is the pointer to the position where the error ocurred. NULL can be
+ * passed as ep.
+ */
+const char *expr(const char *p, int *v, int linepc, int allowfr,
+ enum expr_ecode *ecode, const char **ep)
+{
+ int si, usi;
+ const char *q;
+ char last;
+ int stack[ESTKSZ2];
+ int uopstk[ESTKSZ];
+ int r, n;
+ struct sym *sym;
+ int err;
+ enum expr_ecode ec;
+
+ ec = EXPR_E_NO_EXPR;
+ err = 0;
+ usi = 0;
+ si = 0;
+ r = 0;
+ last = 'V'; /* first void */
+loop:
+ p = skipws(p);
+ if (*p == '(') {
+ if (last == 'n') {
+ goto end;
+ } else {
+ if (si >= ESTKSZ2) {
+ eprint(_("expression too complex\n"));
+ exit(EXIT_FAILURE);
+ }
+ stack[si++] = last;
+ stack[si++] = r;
+ p++;
+ r = 0;
+ last = 'v'; /* void */
+ }
+ } else if (*p == ')') {
+ if (last != 'n') {
+ ec = EXPR_E_CPAR;
+ goto esyntax;
+ } else if (si == 0) {
+ goto end;
+ } else {
+ p++;
+ n = r;
+ r = stack[--si];
+ last = (char) stack[--si];
+ goto oper;
+ }
+ } else if (*p == '+') {
+ p++;
+ if (last == 'n')
+ last = '+';
+ } else if (*p == '-') {
+ if (last == 'n') {
+ p++;
+ last = '-';
+ } else {
+ goto uoper;
+ }
+ } else if (*p == '~') {
+ goto uoper;
+ } else if (*p == '!') {
+ if (*(p + 1) == '=') {
+ if (last != 'n') {
+ ec = EXPR_E_OPER;
+ goto esyntax;
+ } else {
+ p += 2;
+ last = 'N';
+ }
+ } else {
+ goto uoper;
+ }
+ } else if (*p == '*') {
+ if (last == 'n') {
+ last = *p++;
+ } else {
+ p++;
+ n = linepc;
+ goto oper;
+ }
+ } else if (*p == '/' || *p == '&' || *p == '|'
+ || *p == '^')
+ {
+ if (last != 'n') {
+ ec = EXPR_E_OPER;
+ goto esyntax;
+ } else {
+ last = *p++;
+ }
+ } else if (*p == '>') {
+ if (last != 'n') {
+ ec = EXPR_E_OPER;
+ goto esyntax;
+ }
+ p++;
+ if (*p == '=') {
+ last = 'G';
+ p++;
+ } else if (*p == '>') {
+ last = 'R';
+ p++;
+ } else {
+ last = '>';
+ }
+ } else if (*p == '<') {
+ if (last != 'n') {
+ ec = EXPR_E_OPER;
+ goto esyntax;
+ }
+ p++;
+ if (*p == '=') {
+ last = 'S';
+ p++;
+ } else if (*p == '<') {
+ last = 'L';
+ p++;
+ } else {
+ last = '<';
+ }
+ } else if (*p == '=') {
+ if (last != 'n') {
+ ec = EXPR_E_OPER;
+ goto esyntax;
+ }
+ p++;
+ if (*p == '=')
+ p++;
+ last = '=';
+ } else if (*p == '\'') {
+ if (last == 'n')
+ goto end;
+ p++;
+ n = *p++;
+ if (*p != '\'') {
+ ec = EXPR_E_CHAR;
+ goto esyntax;
+ }
+ p++;
+ goto oper;
+ } else if (*p == '$') {
+ if (last == 'n')
+ goto end;
+ p++;
+ if (hexval(*p) < 0) {
+ n = linepc;
+ goto oper;
+ }
+ q = getpnum(p, 16, &n);
+ if (q == NULL) {
+ p--;
+ ec = EXPR_E_HEX;
+ goto esyntax;
+ }
+ p = q;
+ goto oper;
+ } else if (*p == '@') {
+ if (last == 'n')
+ goto end;
+ p++;
+ q = getpnum(p, 8, &n);
+ if (q == NULL) {
+ p--;
+ ec = EXPR_E_OCTAL;
+ goto esyntax;
+ }
+ p = q;
+ goto oper;
+ } else if (*p == '%') {
+ if (last == 'n') {
+ last = *p;
+ p++;
+ } else {
+ p++;
+ q = getpnum(p, 2, &n);
+ if (q == NULL) {
+ ec = EXPR_E_BIN;
+ goto esyntax;
+ }
+ p = q;
+ goto oper;
+ }
+ } else if (isdigit(*p)) {
+ if (last == 'n')
+ goto end;
+ q = getnum(p, &n);
+ if (q == NULL) {
+ ec = EXPR_E_DEC;
+ goto esyntax;
+ }
+ p = q;
+ goto oper;
+ } else if (isidc0(*p)) {
+ if (last == 'n')
+ goto end;
+ q = p;
+ while (isidc(*p))
+ p++;
+ sym = lookup(q, p, 0, 0);
+ if (sym == NULL) {
+ n = 0;
+ if (!allowfr) {
+ err = 1;
+ eprint(_("undefined label"));
+ epchars(q, p);
+ enl();
+ newerr();
+ }
+ } else {
+ n = sym->val;
+ }
+ goto oper;
+ } else if (last == 'V') {
+ goto esyntax;
+ } else if (last != 'n') {
+ ec = EXPR_E_SYNTAX;
+ goto esyntax;
+ } else {
+end: if (v != NULL)
+ *v = r;
+ return p;
+ }
+ goto loop;
+uoper:
+ if (last == 'n')
+ goto end;
+ if (usi >= ESTKSZ) {
+ eprint(_("expression too complex\n"));
+ exit(EXIT_FAILURE);
+ }
+ uopstk[usi++] = *p++;
+ goto loop;
+oper:
+ while (usi > 0) {
+ usi--;
+ switch (uopstk[usi]) {
+ case '~': n = ~n; break;
+ case '-': n = -n; break;
+ case '!': n = !n; break;
+ }
+ }
+ switch (last) {
+ case 'V': r = n; break;
+ case 'v': r = n; break;
+ case '+': r += n; break;
+ case '-': r -= n; break;
+ case '*': r *= n; break;
+ case '&': r &= n; break;
+ case '|': r |= n; break;
+ case '^': r ^= n; break;
+ case '=': r = r == n; break;
+ case '<': r = r < n; break;
+ case '>': r = r > n ; break;
+ case 'G': r = r >= n; break;
+ case 'S': r = r <= n; break;
+ case 'N': r = r != n; break;
+ /* This would be logical right shift:
+ * case 'R': r = (unsigned int) r >> n; break;
+ */
+ case 'R': r = ashr(r, n); break;
+ case 'L': r = shl(r, n); break;
+ case '~': r = ~n; break;
+ case '%':
+ if (n != 0) {
+ r %= n;
+ } else if (!err && !allowfr) {
+ err = 1;
+ eprint(_("modulo by zero\n"));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case '/':
+ if (n != 0) {
+ r /= n;
+ } else if (!err && !allowfr) {
+ err = 1;
+ eprint(_("division by zero\n"));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ last = 'n';
+ goto loop;
+esyntax:
+ if (ecode != NULL)
+ *ecode = ec;
+ if (ep != NULL)
+ *ep = p;
+ return NULL;
+}
diff --git a/Tools/unix/uz80as/expr.h b/Tools/unix/uz80as/expr.h
new file mode 100644
index 00000000..e1e55fc2
--- /dev/null
+++ b/Tools/unix/uz80as/expr.h
@@ -0,0 +1,26 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Expression parsing.
+ * ===========================================================================
+ */
+
+#ifndef EXPR_H
+#define EXPR_H
+
+enum expr_ecode {
+ EXPR_E_NO_EXPR, /* There was no expression parsed. */
+ EXPR_E_SYNTAX, /* Syntax error. */
+ EXPR_E_CPAR,
+ EXPR_E_OPER,
+ EXPR_E_CHAR,
+ EXPR_E_HEX,
+ EXPR_E_OCTAL,
+ EXPR_E_BIN,
+ EXPR_E_DEC,
+};
+
+const char *expr(const char *p, int *v, int linepc, int allowfr,
+ enum expr_ecode *ecode, const char **ep);
+
+#endif
diff --git a/Tools/unix/uz80as/exprint.c b/Tools/unix/uz80as/exprint.c
new file mode 100644
index 00000000..67699636
--- /dev/null
+++ b/Tools/unix/uz80as/exprint.c
@@ -0,0 +1,32 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Expression error reporting.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "exprint.h"
+#include "err.h"
+
+static const char *expr_get_error_str(enum expr_ecode ecode)
+{
+ switch (ecode) {
+ case EXPR_E_NO_EXPR: return _("expression expected\n");
+ case EXPR_E_SYNTAX: return _("syntax error in expression\n");
+ case EXPR_E_CPAR: return _("unexpected ')'\n");
+ case EXPR_E_OPER: return _("misplaced operator\n");
+ case EXPR_E_CHAR: return _("invalid character code\n");
+ case EXPR_E_HEX: return _("invalid hexadecimal constant\n");
+ case EXPR_E_OCTAL: return _("invalid octal constant\n");
+ case EXPR_E_BIN: return _("invalid binary constant\n");
+ case EXPR_E_DEC: return _("invalid decimal constant\n");
+ default: return "\n";
+ }
+}
+
+void exprint(enum expr_ecode ecode, const char *pline, const char *ep)
+{
+ eprint(expr_get_error_str(ecode));
+ eprcol(pline, ep);
+}
diff --git a/Tools/unix/uz80as/exprint.h b/Tools/unix/uz80as/exprint.h
new file mode 100644
index 00000000..00b43c2f
--- /dev/null
+++ b/Tools/unix/uz80as/exprint.h
@@ -0,0 +1,17 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Expression error reporting.
+ * ===========================================================================
+ */
+
+#ifndef EXPRINT_H
+#define EXPRINT_H
+
+#ifndef EXPR_H
+#include "expr.h"
+#endif
+
+void exprint(enum expr_ecode ecode, const char *pline, const char *ep);
+
+#endif
diff --git a/Tools/unix/uz80as/gbcpu.c b/Tools/unix/uz80as/gbcpu.c
new file mode 100644
index 00000000..d6b347f3
--- /dev/null
+++ b/Tools/unix/uz80as/gbcpu.c
@@ -0,0 +1,301 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Sharp LR35902 (Nintendo Gameboy CPU).
+ * ===========================================================================
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+#if 0
+Opcode Z80 GMB
+ ---------------------------------------
+ 08 EX AF,AF LD (nn),SP
+ 10 DJNZ PC+dd STOP
+ 22 LD (nn),HL LDI (HL),A
+ 2A LD HL,(nn) LDI A,(HL)
+ 32 LD (nn),A LDD (HL),A
+ 3A LD A,(nn) LDD A,(HL)
+ D3 OUT (n),A -
+ D9 EXX RETI
+ DB IN A,(n) -
+ DD -
+ E0 RET PO LD (FF00+n),A
+ E2 JP PO,nn LD (FF00+C),A
+ E3 EX (SP),HL -
+ E4 CALL PO,nn -
+ E8 RET PE ADD SP,dd
+ EA JP PE,nn LD (nn),A
+ EB EX DE,HL -
+ EC CALL PE,nn -
+ ED -
+ F0 RET P LD A,(FF00+n)
+ F2 JP P,nn LD A,(FF00+C)
+ F4 CALL P,nn -
+ F8 RET M LD HL,SP+dd
+ FA JP M,nn LD A,(nn)
+ FC CALL M,nn -
+ FD -
+ CB3X SLL r/(HL) SWAP r/(HL)
+#endif
+
+/* pat:
+ * a: expr
+ * b: B,C,D,E,H,L,A
+ * c: *
+ * d: BC,DE,HL,SP
+ * e: *
+ * f: BC,DE,HL,AF
+ * g: ADD,ADC,SUB,SBC,AND,XOR,OR,CP
+ * h: INC,DEC
+ * i: *
+ * j: *
+ * k: *
+ * l: BIT,RES,SET
+ * m: *
+ * n: NZ,Z,NC,C
+ * o: RLC,RRC,RL,RR,SLA,SRA,SWAP,SRL
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: (op << 4) | lastbyte
+ * g: (op << 6) | lastbyte
+ * h: if op >= FF00 output last byte and then op as 8 bit value;
+ * else output (lastbyte | 0x0A) and output op as word
+ * (no '.' should follow)
+ * i: relative jump to op
+ * j: possible value to RST
+ * k: possible value to IM
+ * l: *
+ * m: check arithmetic used with A register
+ * n: check arithmetic used without A register
+ */
+
+const struct matchtab s_matchtab_gbcpu[] = {
+ { "LD b,b", "40b0c1.", 1, 0 },
+ { "LD b,(HL)", "46b0.", 1, 0 },
+ { "LD A,(C)", "F2.", 1, 0 }, // * LD A,(FF00+C)
+ { "LD A,(BC)", "0A.", 1, 0 },
+ { "LD A,(DE)", "1A.", 1, 0 },
+ { "LD A,(HLI)", "2A.", 1, 0 }, // *
+ { "LD A,(HLD)", "3A.", 1, 0 }, // *
+ { "LD A,(a)", "F0h0", 1, 0 }, // * LD A,(nn) & LD A,(FF00+n)
+ { "LD b,a", "06b0.d1.", 1, 0, "e8" },
+ { "LD SP,HL", "F9.", 1, 0 },
+ { "LDHL SP,a", "F8.d0.", 1, 0, "e8" }, // * LD HL,SP+n
+ { "LD d,a", "01f0.e1", 1, 0 },
+ { "LD (C),A", "E2.", 1, 0 }, // * LD (FF00+C),A
+ { "LD (HL),b", "70c0.", 1, 0 },
+ { "LD (HL),a", "36.d0.", 1, 0, "e8" },
+ { "LD (HLI),A", "22.", 1, 0 }, // *
+ { "LD (HLD),A", "32.", 1, 0 }, // *
+ { "LD (BC),A", "02.", 1, 0 },
+ { "LD (DE),A", "12.", 1, 0 },
+ { "LD (a),A", "E0h0", 1, 0 }, // * LD (nn),A & LD (FF00+n),A
+ { "LD (a),SP", "08.e0", 1, 0 }, // *
+ { "LDH A,(a)", "F0.d0.", 1, 0, "e8" }, // * LD A,(FF00+n)
+ { "LDH (a),A", "E0.d0.", 1, 0, "e8" }, // * LD (FF00+n),A
+ { "PUSH f", "C5f0.", 1, 0 },
+ { "POP f", "C1f0.", 1, 0 },
+ { "ADD HL,d", "09f0.", 1, 0 },
+ { "ADD SP,a", "E8.d0.", 1, 0, "e8" }, // *
+ { "g A,b", "m080b0c1.", 1, 0 },
+ { "g A,(HL)", "m086b0.", 1, 0 },
+ { "g A,a", "m0C6b0.d1.", 1, 0, "e8" },
+ { "g b", "n080b0c1.", 1, 0 },
+ { "g (HL)", "n086b0.", 1, 0 },
+ { "g a", "n0C6b0.d1.", 1, 0, "e8" },
+ { "h b", "04b1c0.", 1, 0 },
+ { "h (HL)", "34c0.", 1, 0 },
+ { "INC d", "03f0.", 1, 0 },
+ { "DEC d", "0Bf0.", 1, 0 },
+ { "DAA", "27.", 1, 0 },
+ { "CPL", "2F.", 1, 0 },
+ { "CCF", "3F.", 1, 0 },
+ { "SCF", "37.", 1, 0 },
+ { "NOP", "00.", 1, 0 },
+ { "HALT", "76.", 1, 0 },
+ { "DI", "F3.", 1, 0 },
+ { "EI", "FB.", 1, 0 },
+ { "RLCA", "07.", 1, 0 },
+ { "RLA", "17.", 1, 0 },
+ { "RRCA", "0F.", 1, 0 },
+ { "RRA", "1F.", 1, 0 },
+ { "o b", "CB.00b0c1.", 1, 0 },
+ { "o (HL)", "CB.06b0.", 1, 0 },
+ { "l a,b", "CB.00g0b1c2.", 1, 0, "b3" },
+ { "l a,(HL)", "CB.06g0b1.", 1, 0, "b3" },
+ { "JP (HL)", "E9.", 1, 0 },
+ { "JP n,a", "C2b0.e1", 1, 0 }, // *
+ { "JP a", "C3.e0", 1, 0 },
+ { "JR n,a", "20b0.i1.", 1, 0, "r8" },
+ { "JR a", "18.i0.", 1, 0, "r8" },
+ { "STOP", "10.00.", 1, 0 }, // *
+ { "CALL n,a", "C4b0.e1", 1, 0 }, // *
+ { "CALL a", "CD.e0", 1, 0 },
+ { "RETI", "D9.", 1, 0 }, // *
+ { "RET n", "C0b0.", 1, 0 },
+ { "RET", "C9.", 1, 0 },
+ { "RST a", "C7j0.", 1, 0, "ss" },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = { "B", "C", "D", "E",
+ "H", "L", "", "A", NULL };
+static const char *const dval[] = { "BC", "DE", "HL", "SP", NULL };
+static const char *const fval[] = { "BC", "DE", "HL", "AF", NULL };
+static const char *const gval[] = { "ADD", "ADC", "SUB", "SBC",
+ "AND", "XOR", "OR", "CP", NULL };
+static const char *const hval[] = { "INC", "DEC", NULL };
+static const char *const lval[] = { "", "BIT", "RES", "SET", NULL };
+static const char *const nval[] = { "NZ", "Z", "NC", "C", NULL };
+static const char *const oval[] = { "RLC", "RRC", "RL", "RR",
+ "SLA", "SRA", "SWAP", "SRL", NULL };
+static const char *const nullv[] = { NULL };
+
+static const char *const *const valtab[] = {
+ bval, nullv, dval, nullv, fval,
+ gval, hval, nullv, nullv, nullv,
+ lval, nullv, nval, oval
+};
+
+static int match_gbcpu(char c, const char *p, const char **q)
+{
+ int v;
+
+ switch (c) {
+ case 'b':
+ case 'd':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'l':
+ case 'n':
+ case 'o':
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ break;
+ default:
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_gbcpu(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int w, b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': b |= (vs[i] << 4); break;
+ case 'g': b |= (vs[i] << 6); break;
+ case 'h': w = vs[i] & 0xffff;
+ if (w >= 0xff00) {
+ genb(b, s_pline_ep);
+ b = 0;
+ genb(w & 0xff, s_pline_ep);
+ } else {
+ b |= 0x0A;
+ genb(b, s_pline_ep);
+ b = 0;
+ genb(w & 0xff, s_pline_ep);
+ genb(w >> 8, s_pline_ep);
+ }
+ break;
+ case 'i': b = (vs[i] - savepc - 2); break;
+ case 'j': if (s_pass > 0 && (vs[i] & ~56) != 0) {
+ eprint(_("invalid RST argument (%d)\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= vs[i];
+ break;
+ case 'k': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 2)) {
+ eprint(_("invalid IM argument (%d)\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b = 0x46;
+ if (vs[i] == 1)
+ b = 0x56;
+ else if (vs[i] == 2)
+ b = 0x5E;
+ break;
+ case 'm': if (s_pass == 0 && !s_extended_op) {
+ if (vs[i] != 0 && vs[i] != 1 && vs[i] != 3) {
+ eprint(_("unofficial syntax\n"));
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ }
+ break;
+ case 'n': if (s_pass == 0 && !s_extended_op) {
+ if (vs[i] == 0 || vs[i] == 1 || vs[i] == 3) {
+ eprint(_("unofficial syntax\n"));
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ }
+ break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_gbcpu(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_gbcpu(void)
+{
+ const char *s;
+
+ switch (s_pat_char) {
+ case 'b':
+ case 'd':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'l':
+ case 'n':
+ case 'o':
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ break;
+ default:
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_gbcpu = {
+ .id = "gbcpu",
+ .descr = "Sharp LR35902 (Nintendo Gameboy CPU)",
+ .matcht = s_matchtab_gbcpu,
+ .matchf = match_gbcpu,
+ .genf = gen_gbcpu,
+ .pat_char_rewind = pat_char_rewind_gbcpu,
+ .pat_next_str = pat_next_str_gbcpu,
+ .mask = 1
+};
diff --git a/Tools/unix/uz80as/i4004.c b/Tools/unix/uz80as/i4004.c
new file mode 100644
index 00000000..98649abd
--- /dev/null
+++ b/Tools/unix/uz80as/i4004.c
@@ -0,0 +1,189 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Intel 4004.
+ * Intel 4040.
+ * ===========================================================================
+ */
+
+/* Intel 4004. Max. memory 4K (12 bit addresses).
+ * Intel 4040. Max. memory 8K (13 bit addresses).
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: ADD,SUB,LD,XCH,BBL,LDM
+ * c: WRM,WMP,WRR,WPM,WR0,WR1,WR2,WR3
+ * SBM,RDM,RDR,ADM,RD0,RD1,RD2,RD3
+ * d: CLB,CLC,IAC,CMC,CMA,RAL,RAR,TCC,
+ * DAC,TCS,STC,DAA,KBP,DCL,
+ * e: HLT,BBS,LCR,OR4,OR5,AN6,AN7
+ * DB0,DB1,SB0,SB1,EIN,DIN,RPM
+ * f: 0P,1P,2P,3P,4P,5P,6P,7P
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: op | lastbyte, op in [0-15]
+ * g: op | lastbyte, op in [0,2,4,6,8,10,12,14]
+ * h: output (op & 0xff0000 >> 8) | lastbyte;
+ * then ouput op as 8 bit value
+ * i: (op << 4) | lastbyte
+ * j: (op << 1) | lastbyte
+ */
+
+static const struct matchtab s_matchtab_i4004[] = {
+ { "NOP", "00.", 1, 0 },
+ { "JCN a,a", "10f0.d1.", 1, 0, "b4e8" },
+ { "FIM f,a", "20j0.d1.", 1, 0, "e8" },
+ { "FIM a,a", "20g0.d1.", 1, 0, "ppe8" },
+ { "SRC f", "21j0.", 1, 0 },
+ { "SRC a", "21g0.", 1, 0, "pp" },
+ { "FIN f", "30j0.", 1, 0 },
+ { "FIN a", "30g0.", 1, 0, "pp" },
+ { "JIN f", "31j0.", 1, 0 },
+ { "JIN a", "31g0.", 1, 0, "pp" },
+ { "JUN a", "40h0", 1, 0 },
+ { "JMS a", "50h0", 1, 0 },
+ { "INC a", "60f0.", 1, 0, "b4" },
+ { "ISZ a,a", "70f0.d1.", 1, 0, "b4e8" },
+ { "b a", "80i0f1.", 1, 0, "b4" },
+ { "c", "E0c0.", 1, 0 },
+ { "d", "F0c0.", 1, 0 },
+ { "e", "00c0.", 2, 0 },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+"ADD", "SUB", "LD", "XCH", "BBL", "LDM",
+NULL };
+
+static const char *const cval[] = {
+"WRM", "WMP", "WRR", "WPM", "WR0", "WR1", "WR2", "WR3",
+"SBM", "RDM", "RDR", "ADM", "RD0", "RD1", "RD2", "RD3",
+NULL };
+
+static const char *const dval[] = {
+"CLB", "CLC", "IAC", "CMC", "CMA", "RAL", "RAR", "TCC",
+"DAC", "TCS", "STC", "DAA", "KBP", "DCL",
+NULL };
+
+static const char *const eval[] = {
+"", "HLT", "BBS", "LCR", "OR4", "OR5", "AN6", "AN7",
+"DB0", "DB1", "SB0", "SB1", "EIN", "DIN", "RPM",
+NULL };
+
+static const char *const fval[] = {
+"0P", "1P", "2P", "3P", "4P", "5P", "6P", "7P",
+NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval
+};
+
+static int match_i4004(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'f') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_i4004(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 15)) {
+ eprint(_("argument (%d) must be in range [0-15]\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= vs[i];
+ break;
+ case 'g': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 14 || (vs[i] & 1))) {
+ eprint(
+ _("argument (%d) must be an even number in range [0-14]\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= vs[i];
+ break;
+ case 'h': b |= ((vs[i] >> 8) & 0x0f);
+ genb(b, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ break;
+ case 'i': b |= (vs[i] << 4); break;
+ case 'j': b |= (vs[i] << 1); break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_i4004(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_i4004(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'f') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_i4004 = {
+ .id = "i4004",
+ .descr = "Intel 4004",
+ .matcht = s_matchtab_i4004,
+ .matchf = match_i4004,
+ .genf = gen_i4004,
+ .pat_char_rewind = pat_char_rewind_i4004,
+ .pat_next_str = pat_next_str_i4004,
+ .mask = 1
+};
+
+const struct target s_target_i4040 = {
+ .id = "i4040",
+ .descr = "Intel 4040",
+ .matcht = s_matchtab_i4004,
+ .matchf = match_i4004,
+ .genf = gen_i4004,
+ .pat_char_rewind = pat_char_rewind_i4004,
+ .pat_next_str = pat_next_str_i4004,
+ .mask = 3
+};
+
diff --git a/Tools/unix/uz80as/i8008.c b/Tools/unix/uz80as/i8008.c
new file mode 100644
index 00000000..a7fd9f58
--- /dev/null
+++ b/Tools/unix/uz80as/i8008.c
@@ -0,0 +1,220 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Intel 8008.
+ * ===========================================================================
+ */
+
+/* Max. memory 16K (14 bits addresses). */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: ADA,ADB,ADC,ADD,ADH,ADL,ADM,
+ * ACA,ACB,ACC,ACD,ACH,ACL,ACM,
+ * SUA,SUB,SUC,SUD,SUH,SUL,SUM,
+ * SBA,SBB,SBC,SBD,SBH,SBL,SBM,
+ * NDA,NDB,NDC,NDD,NDH,NDL,NDM,
+ * XRA,XRB,XRC,XRD,XRH,XRL,XRM,
+ * ORA,ORB,ORC,ORD,ORH,ORL,ORM,
+ * CPA,CPB,CPC,CPD,CPH,CPL,CPM
+ * c: NOP,LAB,LAC,LAD,LAE,LAH,LAL,LAM,
+ * LBA,LBB,LBC,LBD,LBE,LBH,LBL,LBM,
+ * LCA,LCB,LCC,LCD,LCE,LCH,LCL,LCM,
+ * LDA,LDB,LDC,LDD,LDE,LDH,LDL,LDM,
+ * LEA,LEB,LEC,LED,LEE,LEH,LEL,LEM,
+ * LHA,LHB,LHC,LHD,LHE,LHH,LHL,LHM,
+ * LLA,LLB,LLC,LLD,LLE,LLH,LLL,LLM,
+ * LMA,LMB,LMC,LMD,LME,LMH,LML,HLT
+ * d: JFC,CFC,JMP,CAL,JFZ,CFZ,
+ * JFS,CFS,JFP,CFP,
+ * JTC,CTC,JTZ,CTZ,
+ * JTS,CTS,JTP,CTP
+ * e: INB,INC,IND,INE,INH,INL
+ * f: DCB,DCC,DCD,DCE,DCH,DCL
+ * g: ADI,ACI,SUI,SBI,NDI,XRI,ORI,CPI
+ * h: LAI,LBI,LCI,LDI,LEI,LHI,LLI,LMI
+ * i: RFC,RFS,RTC,RTS,RFZ,RFP,RTZ,RTP
+ * j: RLC,RRC,RAL,RAR
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: (op << 1) | lastbyte, op in [0-7]
+ * g: (op << 1) | lastbyte, op in [8-31]
+ * h: (op << 4) | lastbyte
+ * i: (op << 1) | lastbyte
+ * j: (op << 3) | lastbyte, op in [0-7]
+ */
+
+static const struct matchtab s_matchtab_i8008[] = {
+ { "RET", "07.", 1, 0 },
+ { "j", "02b0.", 1, 0 },
+ { "i", "03b0.", 1, 0 },
+ { "h a", "06b0.d1.", 1, 0, "e8" },
+ { "g a", "04b0.d1.", 1, 0, "e8" },
+ { "e", "00b0.", 1, 0 },
+ { "f", "01b0.", 1, 0 },
+ { "RST a", "05j0.", 1, 0, "b3" },
+ { "d a", "40i0.e1", 1, 0 },
+ { "INP a", "41f0.", 1, 0, "b3" },
+ { "OUT a", "41g0.", 1, 0, "kk" },
+ { "b", "80c0.", 1, 0 },
+ { "c", "C0c0.", 1, 0 },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+"ADA", "ADB", "ADC", "ADD", "ADE", "ADH", "ADL", "ADM",
+"ACA", "ACB", "ACC", "ACD", "ACE", "ACH", "ACL", "ACM",
+"SUA", "SUB", "SUC", "SUD", "SUE", "SUH", "SUL", "SUM",
+"SBA", "SBB", "SBC", "SBD", "SBE", "SBH", "SBL", "SBM",
+"NDA", "NDB", "NDC", "NDD", "NDE", "NDH", "NDL", "NDM",
+"XRA", "XRB", "XRC", "XRD", "XRE", "XRH", "XRL", "XRM",
+"ORA", "ORB", "ORC", "ORD", "ORE", "ORH", "ORL", "ORM",
+"CPA", "CPB", "CPC", "CPD", "CPE", "CPH", "CPL", "CPM",
+NULL };
+
+static const char *const cval[] = {
+"NOP", "LAB", "LAC", "LAD", "LAE", "LAH", "LAL", "LAM",
+"LBA", "LBB", "LBC", "LBD", "LBE", "LBH", "LBL", "LBM",
+"LCA", "LCB", "LCC", "LCD", "LCE", "LCH", "LCL", "LCM",
+"LDA", "LDB", "LDC", "LDD", "LDE", "LDH", "LDL", "LDM",
+"LEA", "LEB", "LEC", "LED", "LEE", "LEH", "LEL", "LEM",
+"LHA", "LHB", "LHC", "LHD", "LHE", "LHH", "LHL", "LHM",
+"LLA", "LLB", "LLC", "LLD", "LLE", "LLH", "LLL", "LLM",
+"LMA", "LMB", "LMC", "LMD", "LME", "LMH", "LML", "HLT",
+NULL };
+
+static const char *const dval[] = {
+"JFC", "CFC", "JMP", "CAL", "JFZ", "CFZ", "", "",
+"JFS", "CFS", "", "", "JFP", "CFP", "", "",
+"JTC", "CTC", "", "", "JTZ", "CTZ", "", "",
+"JTS", "CTS", "", "", "JTP", "CTP",
+NULL };
+
+static const char *const eval[] = { "", "INB", "INC", "IND",
+ "INE", "INH", "INL",
+ NULL };
+
+static const char *const fval[] = { "", "DCB", "DCC", "DCD",
+ "DCE", "DCH", "DCL",
+ NULL };
+
+static const char *const gval[] = { "ADI", "ACI", "SUI", "SBI",
+ "NDI", "XRI", "ORI", "CPI",
+ NULL };
+
+static const char *const hval[] = { "LAI", "LBI", "LCI", "LDI",
+ "LEI", "LHI", "LLI", "LMI",
+ NULL };
+
+static const char *const ival[] = { "RFC", "RFZ", "RFS", "RFP",
+ "RTC", "RTZ", "RTS", "RTP",
+ NULL };
+
+static const char *const jval[] = { "RLC", "RRC", "RAL", "RAR",
+ NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval,
+ gval, hval, ival, jval
+};
+
+static int match_i8008(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'j') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_i8008(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 7)) {
+ eprint(_("argument (%d) must be in range [0-7]\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= (vs[i] << 1);
+ break;
+ case 'g': if (s_pass > 0 && (vs[i] < 8 || vs[i] > 31)) {
+ eprint(_("argument (%d) must be in range [8-31]\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= (vs[i] << 1);
+ break;
+ case 'h': b |= (vs[i] << 4); break;
+ case 'i': b |= (vs[i] << 1); break;
+ case 'j': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 7)) {
+ eprint(_("argument (%d) must be in range [0-7]\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= (vs[i] << 3);
+ break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_i8008(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_i8008(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'j') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_i8008 = {
+ .id = "i8008",
+ .descr = "Intel 8008",
+ .matcht = s_matchtab_i8008,
+ .matchf = match_i8008,
+ .genf = gen_i8008,
+ .pat_char_rewind = pat_char_rewind_i8008,
+ .pat_next_str = pat_next_str_i8008,
+ .mask = 1
+};
+
diff --git a/Tools/unix/uz80as/i8048.c b/Tools/unix/uz80as/i8048.c
new file mode 100644
index 00000000..1526d252
--- /dev/null
+++ b/Tools/unix/uz80as/i8048.c
@@ -0,0 +1,276 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Intel 8021.
+ * Intel 8022.
+ * Intel 8041.
+ * Intel 8048.
+ * ===========================================================================
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: R0,R1,R2,R3,R4,R5,R6,R7
+ * c: R0,R1
+ * d: P1,P2
+ * e: P4,P5,P6,P7
+ * f: JB0,JB1,JB2,JB3,JB4,JB5,JB6,JB7
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: output lastbyte | ((op & 0x700) >> 3)
+ * output op as 8 bit value
+ * (no '.' should follow)
+ * g: (op << 5) | lastbyte
+ */
+
+static const struct matchtab s_matchtab_i8048[] = {
+ { "NOP", "00.", 1, 0 },
+ { "ADD A,b", "68c0.", 1, 0 },
+ { "ADD A,@c", "60c0.", 1, 0 },
+ { "ADD A,#a", "03.d0.", 1, 0, "e8" },
+ { "ADDC A,b", "78c0.", 1, 0 },
+ { "ADDC A,@c", "70c0.", 1, 0 },
+ { "ADDC A,#a", "13.d0.", 1, 0, "e8" },
+ { "ANL A,b", "58c0.", 1, 0 },
+ { "ANL A,@c", "50c0.", 1, 0 },
+ { "ANL A,#a", "53.d0.", 1, 0, "e8" },
+ { "ANL BUS,#a", "98.d0.", 32, 0, "e8" },
+ { "ANL d,#a", "98c0.d1.", 4, 0, "e8" },
+ { "ANLD e,A", "9Cc0.", 1, 0 },
+ { "CALL a", "14f0", 1, 0 },
+ { "CLR A", "27.", 1, 0 },
+ { "CLR C", "97.", 1, 0 },
+ { "CLR F1", "A5.", 4, 0 },
+ { "CLR F0", "85.", 4, 0 },
+ { "CPL A", "37.", 1, 0 },
+ { "CPL C", "A7.", 1, 0 },
+ { "CPL F0", "95.", 4, 0 },
+ { "CPL F1", "B5.", 4, 0 },
+ { "DA A", "57.", 1, 0 },
+ { "DEC A", "07.", 1, 0 },
+ { "DEC b", "C8c0.", 4, 0 },
+ { "DIS I", "15.", 2, 0 },
+ { "DIS TCNTI", "35.", 2, 0 },
+ { "DJNZ b,a", "E8c0.d1.", 1, 0, "e8" },
+ { "EN DMA", "E5.", 64, 0 },
+ { "EN FLAGS", "F5.", 64, 0 },
+ { "EN I", "05.", 2, 0 },
+ { "EN TCNTI", "25.", 2, 0 },
+ { "ENT0 CLK", "75.", 32, 0 },
+ { "IN A,DBB", "22.", 64, 0 },
+ { "IN A,P0", "08.", 8, 0 },
+ { "IN A,d", "08c0.", 1, 0 },
+ { "INC A", "17.", 1, 0 },
+ { "INC b", "18c0.", 1, 0 },
+ { "INC @c", "10c0.", 1, 0 },
+ { "INS A,BUS", "08.", 32, 0 },
+ { "f a", "12g0.d1.", 4, 0, "e8" },
+ { "JC a", "F6.d0.", 1, 0, "e8" },
+ { "JF0 a", "B6.d0.", 4, 0, "e8" },
+ { "JF1 a", "76.d0.", 4, 0, "e8" },
+ { "JMP a", "04f0", 1, 0, "e11" },
+ { "JMPP @A", "B3.", 1, 0 },
+ { "JNC a", "E6.d0.", 1, 0, "e8" },
+ { "JNI a", "86.d0.", 32, 0, "e8" },
+ { "JNIBF a", "D6.d0.", 64, 0, "e8" },
+ { "JNT0 a", "26.d0.", 2, 0, "e8" },
+ { "JNT1 a", "46.d0.", 1, 0, "e8" },
+ { "JNZ a", "96.d0.", 1, 0, "e8" },
+ { "JOBF a", "86.d0.", 64, 0, "e8" },
+ { "JTF a", "16.d0.", 1, 0, "e8" },
+ { "JT0 a", "36.d0.", 2, 0, "e8" },
+ { "JT1 a", "56.d0.", 1, 0, "e8" },
+ { "JZ a", "C6.d0.", 1, 0, "e8" },
+ { "MOV A,#a", "23.d0.", 1, 0, "e8" },
+ { "MOV A,PSW", "C7.", 4, 0 },
+ { "MOV A,b", "F8c0.", 1, 0 },
+ { "MOV A,@c", "F0c0.", 1, 0 },
+ { "MOV A,T", "42.", 1, 0 },
+ { "MOV PSW,A", "D7.", 4, 0 },
+ { "MOV b,A", "A8c0.", 1, 0 },
+ { "MOV b,#a", "B8c0.d1.", 1, 0, "e8" },
+ { "MOV @c,A", "A0c0.", 1, 0 },
+ { "MOV @c,#a", "B0c0.d1.", 1, 0, "e8" },
+ { "MOV STS,A", "90.", 64, 0 },
+ { "MOV T,A", "62.", 1, 0 },
+ { "MOVD A,e", "0Cc0.", 1, 0 },
+ { "MOVD e,A", "3Cc0.", 1, 0 },
+ { "MOVP A,@A", "A3.", 1, 0 },
+ { "MOVP3 A,@A", "E3.", 4, 0 },
+ { "MOVX A,@c", "80c0.", 32, 0 },
+ { "MOVX @c,A", "90c0.", 32, 0 },
+ { "NOP", "00.", 1, 0 },
+ { "ORL A,b", "48c0.", 1, 0 },
+ { "ORL A,@c", "40c0.", 1, 0 },
+ { "ORL A,#a", "43.d0.", 1, 0, "e8" },
+ { "ORL BUS,#a", "88.d0.", 32, 0, "e8" },
+ { "ORL d,#a", "88c0.d1.", 4, 0, "e8" },
+ { "ORLD e,A", "8Cc0.", 1, 0 },
+ { "OUT DBB,A", "02.", 64, 0 },
+ { "OUTL BUS,A", "02.", 32, 0 },
+ { "OUTL P0,A", "90.", 8, 0 },
+ { "OUTL d,A", "38c0.", 1, 0 },
+ { "RAD", "80.", 16, 0 },
+ { "RET", "83.", 1, 0 },
+ { "RETR", "93.", 4, 0 },
+ { "RETI", "93.", 16, 0 },
+ { "RL A", "E7.", 1, 0 },
+ { "RLC A", "F7.", 1, 0 },
+ { "RR A", "77.", 1, 0 },
+ { "RRC A", "67.", 1, 0 },
+ { "SEL AN0", "85.", 16, 0 },
+ { "SEL AN1", "95.", 16, 0 },
+ { "SEL MB0", "E5.", 32, 0 },
+ { "SEL MB1", "F5.", 32, 0 },
+ { "SEL RB0", "C5.", 4, 0 },
+ { "SEL RB1", "D5.", 4, 0 },
+ { "STOP TCNT", "65.", 1, 0 },
+ { "STRT CNT", "45.", 1, 0 },
+ { "STRT T", "55.", 1, 0 },
+ { "SWAP A", "47.", 1, 0 },
+ { "XCH A,b", "28c0.", 1, 0 },
+ { "XCH A,@c", "20c0.", 1, 0 },
+ { "XCHD A,@c", "30c0.", 1, 0 },
+ { "XRL A,b", "D8c0.", 1, 0 },
+ { "XRL A,@c", "D0c0.", 1, 0 },
+ { "XRL A,#a", "D3.d0.", 1, 0, "e8" },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
+NULL };
+
+static const char *const cval[] = {
+"R0", "R1",
+NULL };
+
+static const char *const dval[] = {
+"", "P1", "P2",
+NULL };
+
+static const char *const eval[] = {
+"P4", "P5", "P6", "P7",
+NULL };
+
+static const char *const fval[] = {
+"JB0", "JB1", "JB2", "JB3", "JB4", "JB5", "JB6", "JB7",
+NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval
+};
+
+static int match_i8048(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'f') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_i8048(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': b |= ((vs[i] & 0x700) >> 3);
+ genb(b, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ break;
+ case 'g': b |= (vs[i] << 5);
+ break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_i8048(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_i8048(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'f') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_i8041 = {
+ .id = "i8041",
+ .descr = "Intel 8041",
+ .matcht = s_matchtab_i8048,
+ .matchf = match_i8048,
+ .genf = gen_i8048,
+ .pat_char_rewind = pat_char_rewind_i8048,
+ .pat_next_str = pat_next_str_i8048,
+ .mask = 71
+};
+
+const struct target s_target_i8048 = {
+ .id = "i8048",
+ .descr = "Intel 8048",
+ .matcht = s_matchtab_i8048,
+ .matchf = match_i8048,
+ .genf = gen_i8048,
+ .pat_char_rewind = pat_char_rewind_i8048,
+ .pat_next_str = pat_next_str_i8048,
+ .mask = 39
+};
+
+const struct target s_target_i8021 = {
+ .id = "i8021",
+ .descr = "Intel 8021",
+ .matcht = s_matchtab_i8048,
+ .matchf = match_i8048,
+ .genf = gen_i8048,
+ .pat_char_rewind = pat_char_rewind_i8048,
+ .pat_next_str = pat_next_str_i8048,
+ .mask = 9
+};
+
+const struct target s_target_i8022 = {
+ .id = "i8022",
+ .descr = "Intel 8022",
+ .matcht = s_matchtab_i8048,
+ .matchf = match_i8048,
+ .genf = gen_i8048,
+ .pat_char_rewind = pat_char_rewind_i8048,
+ .pat_next_str = pat_next_str_i8048,
+ .mask = 27
+};
+
diff --git a/Tools/unix/uz80as/i8051.c b/Tools/unix/uz80as/i8051.c
new file mode 100644
index 00000000..034e1292
--- /dev/null
+++ b/Tools/unix/uz80as/i8051.c
@@ -0,0 +1,244 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Intel 8051.
+ * ===========================================================================
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: R0,R1,R2,R3,R4,R5,R6,R7
+ * c: R0,R1
+ * d: ADD,ADDC,ORL,ANL,XRL,SUBB,XCH,MOV
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: output lastbyte | ((op & 0x700) >> 3)
+ * output op as 8 bit value
+ * (no '.' should follow)
+ * g: relative jump -2
+ * h: relative jump -3
+ * i: ouput op as big endian word
+ * j: b + (op << 4)
+ */
+
+static const struct matchtab s_matchtab_i8051[] = {
+ { "ACALL a", "11f0", 1, 0, "e11" },
+ { "d A,b", "08j0c1.", 1, 0 },
+ { "d A,@c", "06j0c1.", 1, 0 },
+ { "ADD A,#a", "24.d0.", 1, 0, "e8" },
+ { "ADDC A,#a", "34.d0.", 1, 0, "e8" },
+ { "ORL A,#a", "44.d0.", 1, 0, "e8" },
+ { "ANL A,#a", "54.d0.", 1, 0, "e8" },
+ { "XRL A,#a", "64.d0.", 1, 0, "e8" },
+ { "SUBB A,#a", "94.d0.", 1, 0, "e8" },
+ { "MOV A,#a", "74.d0.", 1, 0, "e8" },
+ { "d A,a", "05j0.d1.", 1, 0, "e8" },
+ // { "ADD A,b", "28c0.", 1, 0 },
+ // { "ADD A,@c", "26c0.", 1, 0 },
+ // { "ADD A,a", "25.d0.", 1, 0, "e8" },
+ // { "ADDC A,b", "38c0.", 1, 0 },
+ // { "ADDC A,@c", "36c0.", 1, 0 },
+ // { "ADDC A,a", "35.d0.", 1, 0, "e8" },
+ { "AJMP a", "01f0", 1, 0, "e11" },
+ // { "ANL A,b", "58c0.", 1, 0 },
+ // { "ANL A,@c", "56c0.", 1, 0 },
+ // { "ANL A,a", "55.d0.", 1, 0, "e8" },
+ { "ANL C,/a", "B0.d0.", 1, 0, "e8" },
+ { "ANL C,a", "82.d0.", 1, 0, "e8" },
+ { "ANL a,A", "52.d0.", 1, 0 },
+ { "ANL a,#a", "53.d0.d1.", 1, 0, "e8e8" },
+ { "CJNE A,#a,a", "B4.d0.h1.", 1, 0, "e8r8" },
+ { "CJNE b,#a,a", "B8c0.d1.h2.", 1, 0, "e8r8" },
+ { "CJNE @c,#a,a", "B6c0.d1.h2.", 1, 0, "e8r8" },
+ { "CJNE A,a,a", "B5.d0.h1.", 1, 0, "e8r8" },
+ { "CLR A", "E4.", 1, 0 },
+ { "CLR C", "C3.", 1, 0 },
+ { "CLR a", "C2.d0.", 1, 0, "e8" },
+ { "CPL A", "F4.", 1, 0 },
+ { "CPL C", "B3.", 1, 0 },
+ { "CPL a", "B2.d0.", 1, 0, "e8" },
+ { "DA A", "D4.", 1, 0 },
+ { "DEC A", "14.", 1, 0 },
+ { "DEC b", "18c0.", 1, 0 },
+ { "DEC @c", "16c0.", 1, 0 },
+ { "DEC a", "15.d0.", 1, 0, "e8" },
+ { "DIV AB", "84.", 1, 0 },
+ { "DJNZ b,a", "D8c0.g1.", 1, 0, "r8" },
+ { "DJNZ a,a", "D5.d0.h1.", 1, 0, "e8r8" },
+ { "INC DPTR", "A3.", 1, 0 },
+ { "INC A", "04.", 1, 0 },
+ { "INC b", "08c0.", 1, 0 },
+ { "INC @c", "06c0.", 1, 0 },
+ { "INC a", "05.d0.", 1, 0, "e8" },
+ { "JB a,a", "20.d0.h1.", 1, 0, "e8r8" },
+ { "JBC a,a", "10.d0.h1.", 1, 0, "e8r8" },
+ { "JC a", "40.g0.", 1, 0, "r8" },
+ { "JMP @A+DPTR", "73.", 1, 0 },
+ { "JNB a,a", "30.d0.h1.", 1, 0, "e8r8" },
+ { "JNC a", "50.g0.", 1, 0, "r8" },
+ { "JNZ a", "70.g0.", 1, 0, "r8" },
+ { "JZ a", "60.g0.", 1, 0, "r8" },
+ { "LCALL a", "12.i0", 1, 0 },
+ { "LJMP a", "02.i0", 1, 0 },
+ // { "MOV A,b", "E8c0.", 1, 0 },
+ // { "MOV A,@c", "E6c0.", 1, 0 },
+ // { "MOV A,a", "E5.d0.", 1, 0, "e8" }, // MOV A,ACC not valid?
+ { "MOV b,A", "F8c0.", 1, 0 },
+ { "MOV b,#a", "78c0.d1.", 1, 0, "e8" },
+ { "MOV b,a", "A8c0.d1.", 1, 0, "e8" },
+ { "MOV @c,A", "F6c0.", 1, 0 },
+ { "MOV @c,#a", "76c0.d1.", 1, 0, "e8" },
+ { "MOV @c,a", "A6c0.d1.", 1, 0, "e8" },
+ { "MOV C,a", "A2.d0.", 1, 0, "e8" },
+ { "MOV DPTR,#a", "90.i0", 1, 0, "e8" },
+ { "MOV a,A", "F5.d0.", 1, 0, "e8" },
+ { "MOV a,C", "92.d0.", 1, 0, "e8" },
+ { "MOV a,b", "88c1.d0.", 1, 0, "e8" },
+ { "MOV a,@c", "86c1.d0.", 1, 0, "e8" },
+ { "MOV a,#a", "75.d0.d1.", 1, 0, "e8e8" },
+ { "MOV a,a", "85.d1.d0.", 1, 0, "e8e8" },
+ { "MOVC A,@A+DPTR", "93.", 1, 0 },
+ { "MOVC A,@A+PC", "83.", 1, 0 },
+ { "MOVX A,@c", "E2c0.", 1, 0 },
+ { "MOVX A,@DPTR", "E0.", 1, 0 },
+ { "MOVX @c,A", "F2c0.", 1, 0 },
+ { "MOVX @DPTR,A", "F0.", 1, 0 },
+ { "MUL AB", "A4.", 1, 0 },
+ { "NOP", "00.", 1, 0 },
+ // { "ORL A,b", "48c0.", 1, 0 },
+ // { "ORL A,@c", "46c0.", 1, 0 },
+ // { "ORL A,a", "45.d0.", 1, 0, "e8" },
+ { "ORL C,/a", "A0.d0.", 1, 0, "e8" },
+ { "ORL C,a", "72.d0.", 1, 0, "e8" },
+ { "ORL a,A", "42.d0.", 1, 0, "e8" },
+ { "ORL a,#a", "43.d0.d1.", 1, 0, "e8e8" },
+ { "POP a", "D0.d0.", 1, 0, "e8" },
+ { "PUSH a", "C0.d0.", 1, 0, "e8" },
+ { "RET", "22.", 1, 0 },
+ { "RETI", "32.", 1, 0 },
+ { "RL A", "23.", 1, 0 },
+ { "RLC A", "33.", 1, 0 },
+ { "RR A", "03.", 1, 0 },
+ { "RRC A", "13.", 1, 0 },
+ { "SETB C", "D3.", 1, 0 },
+ { "SETB a", "D2.d0.", 1, 0, "e8" },
+ { "SJMP a", "80.g0.", 1, 0, "r8" },
+ // { "SUBB A,b", "98c0.", 1, 0 },
+ // { "SUBB A,@c", "96c0.", 1, 0 },
+ // { "SUBB A,a", "95.d0.", 1, 0, "e8" },
+ { "SWAP A", "C4.", 1, 0 },
+ // { "XCH A,b", "C8c0.", 1, 0 },
+ // { "XCH A,@c", "C6c0.", 1, 0 },
+ // { "XCH A,a", "C5.d0.", 1, 0, "e8" },
+ { "XCHD A,@c", "D6c0.", 1, 0 },
+ // { "XRL A,b", "68c0.", 1, 0 },
+ // { "XRL A,@c", "66c0.", 1, 0 },
+ // { "XRL A,a", "65.d0.", 1, 0, "e8" },
+ { "XRL a,A", "62.d0.", 1, 0, "e8" },
+ { "XRL a,#a", "63.d0.d1.", 1, 0, "e8e8" },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
+NULL };
+
+static const char *const cval[] = {
+"R0", "R1",
+NULL };
+
+static const char *const dval[] = {
+"", "", "ADD", "ADDC", "ORL", "ANL", "XRL", "",
+"", "SUBB", "", "", "XCH", "", "MOV",
+NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval
+};
+
+static int match_i8051(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'd') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_i8051(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': b |= ((vs[i] & 0x700) >> 3);
+ genb(b, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ break;
+ case 'g': b = (vs[i] - savepc - 2); break;
+ break;
+ case 'h': b = (vs[i] - savepc - 3); break;
+ break;
+ case 'i': genb(vs[i] >> 8, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ break;
+ case 'j': b += (vs[i] << 4); break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_i8051(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_i8051(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'd') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_i8051 = {
+ .id = "i8051",
+ .descr = "Intel 8051",
+ .matcht = s_matchtab_i8051,
+ .matchf = match_i8051,
+ .genf = gen_i8051,
+ .pat_char_rewind = pat_char_rewind_i8051,
+ .pat_next_str = pat_next_str_i8051,
+ .mask = 1
+};
+
diff --git a/Tools/unix/uz80as/i8080.c b/Tools/unix/uz80as/i8080.c
new file mode 100644
index 00000000..e0f7f51b
--- /dev/null
+++ b/Tools/unix/uz80as/i8080.c
@@ -0,0 +1,214 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Intel 8080.
+ * ===========================================================================
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: B,C,D,E,H,L,M,A
+ * c: B,D,H,SP
+ * d: B,D
+ * e: B,D,H,PSW
+ * f: JNZ,JZ,JNC,JC,JPO,JPE,JP,JM
+ * g: CNZ,CZ,CNC,CC,CPO,CPE,CP,CM
+ * h: RNZ,RZ,RNC,RC,RPO,RPE,RP,RM
+ * i: B,C,D,E,H,L,A
+ * j: ADD,ADC,SUB,SBB,ANA,XRA,ORA,CMP
+ * k: RLC,RRC,RAL,RAR
+ * l: ADI,ACI,SUI,SBI,ANI,XRI,ORI,CPI
+ * m: SHLD,LHLD,STA,LDA
+ * n: DI,EI
+ * o: OUT,IN
+ * p: STC,CMC
+ * q: POP,PUSH
+ * r: STAX,LDAX
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: (op << 4) | lastbyte
+ * g: possible value to RST
+ * h: (op << 2) | lastbyte
+ */
+
+static const struct matchtab s_matchtab_i8080[] = {
+ { "MOV M,i", "70c0.", 3, 0 },
+ { "MOV i,M", "46b0.", 3, 0 },
+ { "MOV i,i", "40b0c1.", 3, 0 },
+ { "MVI b,a", "06b0.d1.", 3, 0, "e8" },
+ { "LXI c,a", "01f0.e1", 3, 0 },
+ { "m a", "22b0.e1", 3, 0 },
+ { "r d", "02b0f1.", 3, 0 },
+ { "XCHG", "EB.", 3, 0 },
+ { "j b", "80b0c1.", 3, 0 },
+ { "l a", "C6b0.d1.", 3, 0, "e8" },
+ { "INR b", "04b0.", 3, 0 },
+ { "DCR b", "05b0.", 3, 0 },
+ { "INX c", "03f0.", 3, 0 },
+ { "DCX c", "0Bf0.", 3, 0 },
+ { "DAD c", "09f0.", 3, 0 },
+ { "DAA", "27.", 3, 0 },
+ { "k", "07b0.", 3, 0 },
+ { "CMA", "2F.", 3, 0 },
+ { "p", "37b0.", 3, 0 },
+ { "JMP a", "C3.e0", 3, 0 },
+ { "f a", "C2b0.e1", 3, 0 },
+ { "CALL a", "CD.e0", 3, 0 },
+ { "g a", "C4b0.e1", 3, 0 },
+ { "RET", "C9.", 3, 0 },
+ { "h", "C0b0.", 3, 0 },
+ { "RST a", "C7g0.", 3, 0, "b3" },
+ { "PCHL", "E9.", 3, 0 },
+ { "q e", "C1h0f1.", 3, 0 },
+ { "XTHL", "E3.", 3, 0 },
+ { "SPHL", "F9.", 3, 0 },
+ { "o a", "D3b0.d1.", 3, 0, "e8" },
+ { "n", "F3b0.", 3, 0 },
+ { "HLT", "76.", 3, 0 },
+ { "NOP", "00.", 3, 0 },
+ /* 8085 added instructions */
+ { "RIM", "20.", 2, 0 },
+ { "SIM", "30.", 2, 0 },
+ { "ARHL", "10.", 2, 2 },
+ { "DSUB", "08.", 2, 2 },
+ { "RDEL", "18.", 2, 2 },
+ { "LDHI a", "28.d0.", 2, 2, "e8" },
+ { "LDSI a", "38.d0.", 2, 2, "e8" },
+ { "RSTV", "CB.", 2, 2 },
+ { "SHLX", "D9.", 2, 2 },
+ { "LHLX", "ED.", 2, 2 },
+ { "JNK a", "DD.e0", 2, 2 },
+ { "JNX5 a", "DD.e0", 2, 2 },
+ { "JNUI a", "DD.e0", 2, 2 },
+ { "JK a", "FD.e0", 2, 2 },
+ { "JX5 a", "FD.e0", 2, 2 },
+ { "JUI a", "FD.e0", 2, 2 },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = { "B", "C", "D", "E",
+ "H", "L", "M", "A", NULL };
+static const char *const cval[] = { "B", "D", "H", "SP", NULL };
+static const char *const dval[] = { "B", "D", NULL };
+static const char *const eval[] = { "B", "D", "H", "PSW", NULL };
+static const char *const fval[] = { "JNZ", "JZ", "JNC", "JC",
+ "JPO", "JPE", "JP", "JM", NULL };
+static const char *const gval[] = { "CNZ", "CZ", "CNC", "CC",
+ "CPO", "CPE", "CP", "CM", NULL };
+static const char *const hval[] = { "RNZ", "RZ", "RNC", "RC",
+ "RPO", "RPE", "RP", "RM", NULL };
+static const char *const ival[] = { "B", "C", "D", "E",
+ "H", "L", "", "A", NULL };
+static const char *const jval[] = { "ADD", "ADC", "SUB", "SBB",
+ "ANA", "XRA", "ORA", "CMP", NULL };
+static const char *const kval[] = { "RLC", "RRC", "RAL", "RAR", NULL };
+static const char *const lval[] = { "ADI", "ACI", "SUI", "SBI",
+ "ANI", "XRI", "ORI", "CPI", NULL };
+static const char *const mval[] = { "SHLD", "LHLD", "STA", "LDA", NULL };
+static const char *const nval[] = { "DI", "EI", NULL };
+static const char *const oval[] = { "OUT", "IN", NULL };
+static const char *const pval[] = { "STC", "CMC", NULL };
+static const char *const qval[] = { "POP", "PUSH", NULL };
+static const char *const rval[] = { "STAX", "LDAX", NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval,
+ gval, hval, ival, jval, kval,
+ lval, mval, nval, oval, pval,
+ qval, rval
+};
+
+static int match_i8080(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'r') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_i8080(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': b |= (vs[i] << 4); break;
+ case 'g': if (s_pass > 0 && (vs[i] & ~7) != 0) {
+ eprint(_("invalid RST argument (%d)\n"),
+ vs[i]);
+ eprcol(s_pline, s_pline_ep);
+ newerr();
+ }
+ b |= (vs[i] << 3);
+ break;
+ case 'h': b |= (vs[i] << 2); break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_i8080(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_i8080(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'r') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_i8080 = {
+ .id = "i8080",
+ .descr = "Intel 8080",
+ .matcht = s_matchtab_i8080,
+ .matchf = match_i8080,
+ .genf = gen_i8080,
+ .pat_char_rewind = pat_char_rewind_i8080,
+ .pat_next_str = pat_next_str_i8080,
+ .mask = 1
+};
+
+const struct target s_target_i8085 = {
+ .id = "i8085",
+ .descr = "Intel 8085",
+ .matcht = s_matchtab_i8080,
+ .matchf = match_i8080,
+ .genf = gen_i8080,
+ .pat_char_rewind = pat_char_rewind_i8080,
+ .pat_next_str = pat_next_str_i8080,
+ .mask = 2
+};
diff --git a/Tools/unix/uz80as/incl.c b/Tools/unix/uz80as/incl.c
new file mode 100644
index 00000000..e11d4675
--- /dev/null
+++ b/Tools/unix/uz80as/incl.c
@@ -0,0 +1,81 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Include file stack.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "incl.h"
+#include "utils.h"
+#include "err.h"
+
+#ifndef ASSERT_H
+#include
+#endif
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STDLIB_H
+#include
+#endif
+
+/* Max number of nested included files. */
+#define NFILES 128
+
+/* Number of nested files. */
+static int s_nfiles;
+
+/* Current file. */
+static struct incfile *s_curfile;
+
+/* Get the current file. Never returns NULL. */
+struct incfile *curfile(void)
+{
+ assert(s_curfile != NULL);
+ return s_curfile;
+}
+
+/* The number of nested files. 0 means no file loaded. */
+int nfiles(void)
+{
+ return s_nfiles;
+}
+
+/* Leave the current included file. */
+void popfile(void)
+{
+ struct incfile *ifile;
+
+ assert(s_curfile != NULL);
+ fclose(s_curfile->fin);
+ ifile = s_curfile;
+ s_curfile = ifile->prev;
+ free(ifile);
+ s_nfiles--;
+}
+
+/* Include a file whose name is [p, q[. */
+void pushfile(const char *p, const char *q)
+{
+ struct incfile *ifile;
+
+ if (s_nfiles == NFILES) {
+ eprint(_("maximum number of nested includes exceeded (%d)\n"),
+ NFILES);
+ exit(EXIT_FAILURE);
+ }
+
+ // printf("pushfile: %s\n", p);
+ ifile = emalloc((sizeof *ifile) + (q - p) + 1);
+ ifile->name = (char *) ((unsigned char *) ifile + sizeof *ifile);
+ copychars(ifile->name, p, q);
+
+ ifile->fin = efopen(ifile->name, "r");
+ ifile->linenum = 0;
+ ifile->prev = s_curfile;
+ s_curfile = ifile;
+ s_nfiles++;
+}
diff --git a/Tools/unix/uz80as/incl.h b/Tools/unix/uz80as/incl.h
new file mode 100644
index 00000000..1da62f61
--- /dev/null
+++ b/Tools/unix/uz80as/incl.h
@@ -0,0 +1,28 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Include file stack.
+ * ===========================================================================
+ */
+
+#ifndef INCL_H
+#define INCL_H
+
+#ifndef STDIO_H
+#define STDIO_H
+#include
+#endif
+
+struct incfile {
+ struct incfile *prev;
+ FILE *fin;
+ int linenum;
+ char *name;
+};
+
+void pushfile(const char *p, const char *q);
+void popfile(void);
+struct incfile *curfile(void);
+int nfiles(void);
+
+#endif
diff --git a/Tools/unix/uz80as/list.c b/Tools/unix/uz80as/list.c
new file mode 100644
index 00000000..f340e30b
--- /dev/null
+++ b/Tools/unix/uz80as/list.c
@@ -0,0 +1,164 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Assembly listing generation.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "list.h"
+#include "err.h"
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STLIB_H
+#include
+#endif
+
+#ifndef STRING_H
+#include
+#endif
+
+static FILE *s_list_file;
+
+/* N characters in current line. */
+static int s_nchars;
+
+/* Generated data bytes in this line. */
+static int s_nbytes;
+
+/* Line text. */
+static const char *s_line;
+
+/* Line number. */
+static int s_linenum;
+
+/* Program counter. */
+static int s_pc;
+
+/* Number of current nested files. */
+static int s_nfiles;
+
+/* If listing line number, etc are generated or just the line. */
+int s_codes = 1;
+
+/* If listing is enabled or not. */
+int s_list_on = 1;
+
+/* If we are skipping lines. */
+static int s_skip_on = 0;
+
+void list_open(const char *fname)
+{
+ s_list_file = fopen(fname, "w");
+ if (s_list_file == NULL) {
+ eprint(_("cannot open file %s\n"), fname);
+ }
+}
+
+void list_close(void)
+{
+ if (s_list_file != NULL)
+ fclose(s_list_file);
+}
+
+static void prhead(void)
+{
+ int i, j, n;
+
+ s_nchars = fprintf(s_list_file, "%-.4d", s_linenum);
+
+ n = 7 - s_nchars;
+ if (n <= 0)
+ n = 1;
+ j = 0;
+ if (s_nfiles > 0)
+ j = s_nfiles - 1;
+ if (j > n)
+ j = n;
+ for (i = 0; i < j; i++)
+ fputc('+', s_list_file);
+ j = n - j;
+ while (j--)
+ fputc(' ', s_list_file);
+ s_nchars += n;
+
+ s_nchars += fprintf(s_list_file, "%.4X", s_pc);
+ if (s_skip_on)
+ fputc('~', s_list_file);
+ else
+ fputc(' ', s_list_file);
+ s_nchars += 1;
+}
+
+static void prline(void)
+{
+ if (s_line == NULL) {
+ fputs("\n", s_list_file);
+ } else {
+ if (s_codes) {
+ while (s_nchars < 24) {
+ s_nchars++;
+ fputc(' ', s_list_file);
+ }
+ }
+ fprintf(s_list_file, "%s\n", s_line);
+ s_line = NULL;
+ }
+ s_nchars = 0;
+ s_nbytes = 0;
+}
+
+void list_startln(const char *line, int linenum, int pc, int nested_files)
+{
+ if (s_list_file == NULL)
+ return;
+ s_linenum = linenum;
+ s_pc = pc;
+ s_line = line;
+ s_nchars = 0;
+ s_nbytes = 0;
+ s_nfiles = nested_files;
+}
+
+void list_setpc(int pc)
+{
+ s_pc = pc;
+}
+
+void list_skip(int on)
+{
+ s_skip_on = on;
+}
+
+void list_eject(void)
+{
+ if (s_list_file == NULL || !s_list_on)
+ return;
+}
+
+void list_genb(int b)
+{
+ if (s_list_file == NULL || !s_codes || !s_list_on)
+ return;
+ if (s_nchars == 0)
+ prhead();
+ if (s_nbytes >= 4) {
+ prline();
+ prhead();
+ }
+ s_nchars += fprintf(s_list_file, "%2.2X ", (b & 0xff));
+ s_nbytes++;
+ s_pc++;
+}
+
+void list_endln(void)
+{
+ if (s_list_file == NULL || !s_list_on)
+ return;
+ if (s_codes && s_nchars == 0)
+ prhead();
+ prline();
+}
diff --git a/Tools/unix/uz80as/list.h b/Tools/unix/uz80as/list.h
new file mode 100644
index 00000000..7868cd29
--- /dev/null
+++ b/Tools/unix/uz80as/list.h
@@ -0,0 +1,23 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Assembly listing generation.
+ * ===========================================================================
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+extern int s_codes;
+extern int s_list_on;
+
+void list_open(const char *fname);
+void list_close(void);
+void list_startln(const char *line, int linenum, int pc, int nested_files);
+void list_setpc(int pc);
+void list_skip(int on);
+void list_eject(void);
+void list_genb(int b);
+void list_endln(void);
+
+#endif
diff --git a/Tools/unix/uz80as/main.c b/Tools/unix/uz80as/main.c
new file mode 100644
index 00000000..e3e3ff1e
--- /dev/null
+++ b/Tools/unix/uz80as/main.c
@@ -0,0 +1,303 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * main(), handling of command line options.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "ngetopt.h"
+#include "options.h"
+#include "utils.h"
+#include "err.h"
+#include "uz80as.h"
+#include "prtable.h"
+#include "targets.h"
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STDLIB_H
+#include
+#endif
+
+#ifndef STRING_H
+#include
+#endif
+
+#ifndef CTYPE_H
+#include
+#endif
+
+void print_copyright(FILE *f)
+{
+ static const char *copyright =
+"Copyright (C) " COPYRIGHT_YEARS " Jorge Giner Cordero.\n";
+
+ fputs(copyright, f);
+}
+
+static void print_license(FILE *f)
+{
+ static const char *license[] = {
+"Permission is hereby granted, free of charge, to any person obtaining",
+"a copy of this software and associated documentation files (the",
+"\"Software\"), to deal in the Software without restriction, including",
+"without limitation the rights to use, copy, modify, merge, publish,",
+"distribute, sublicense, and/or sell copies of the Software, and to",
+"permit persons to whom the Software is furnished to do so, subject to",
+"the following conditions:",
+"",
+"The above copyright notice and this permission notice shall be included",
+"in all copies or substantial portions of the Software.",
+"",
+"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,",
+"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF",
+"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.",
+"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY",
+"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,",
+"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE",
+"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
+ };
+
+ int i;
+
+ for (i = 0; i < NELEMS(license); i++) {
+ fprintf(f, "%s\n", license[i]);
+ }
+}
+
+static void print_version(FILE *f)
+{
+ const struct target *p;
+
+ fputs(PACKAGE_STRING, f);
+ fputs("\n", f);
+ fputs("Targets:", f);
+ for (p = first_target(); p != NULL; p = next_target()) {
+ fprintf(f, " %s", p->id);
+ }
+ fputs("\n", f);
+}
+
+static void print_help(const char *argv0)
+{
+ static const char *help =
+"Usage: %s [OPTION]... ASM_FILE [OBJ_FILE [LST_FILE]]\n"
+"\n"
+"Assemble ASM_FILE into OBJ_FILE and generate the listing LST_FILE.\n"
+"If not specified, OBJ_FILE is ASM_FILE with the extension changed to .obj.\n"
+"If not specified, LST_FILE is ASM_FILE with the extension changed to .lst.\n"
+"\n"
+"Options:\n"
+" -h, --help Display this help and exit.\n"
+" -V, --verbose be chatty.\n"
+" -v, --version Output version information and exit.\n"
+" -l, --license Display the license text and exit.\n"
+" -d, --define=MACRO Define a macro.\n"
+" -f, --fill=n Fill memory with value n.\n"
+" -q, --quiet Do not generate the listing file.\n"
+" -x, --extended Enable extended instruction syntax.\n"
+" -u, --undocumented Enable undocumented instructions.\n"
+" -t, --target=NAME Select the target micro. z80 is the default.\n"
+" -e, --list-targets List the targets supported.\n"
+"\n"
+"Examples:\n"
+" " PACKAGE " p.asm Assemble p.asm into p.obj\n"
+" " PACKAGE " p.asm p.bin Assemble p.asm into p.bin\n"
+" " PACKAGE " -d\"MUL(a,b) (a*b)\" p.asm Define the macro MUL and assemble p.asm\n"
+"\n"
+"Report bugs to: <" PACKAGE_BUGREPORT ">.\n"
+"Home page: <" PACKAGE_URL ">.\n";
+
+ printf(help, argv0);
+}
+
+/*
+ * Get the filename part of fname (that is, for ../../fname.abc, get
+ * fname.abc).
+ * Then substitute the extension .abd by .ext or append .ext.
+ */
+static char *mkfname(const char *fname, const char *ext)
+{
+ size_t alen, elen;
+ const char *p, *q;
+ char *s;
+
+ alen = strlen(fname);
+ elen = strlen(ext);
+
+ /* Find start of filename in path string */
+ p = fname + alen;
+ while (p > fname && *p != '/' && *p != '\\')
+ p--;
+
+ if (*p == '/' || *p == '\\')
+ p++;
+
+ /* Find the extension */
+ q = fname + alen;
+ while (q > p && *q != '.')
+ q--;
+
+ if (*q != '.')
+ q = fname + alen;
+
+ s = emalloc((q - p) + 1 + elen + 1);
+ if (q > p)
+ memmove(s, p, (q - p));
+ s[q - p] = '\0';
+ strcat(s, ".");
+ strcat(s, ext);
+ return s;
+}
+
+static void parse_fill_byte(const char *optarg)
+{
+ int hi, lo;
+
+ if (strlen(optarg) != 2)
+ goto error;
+
+ if ((hi = hexval(optarg[0])) < 0)
+ goto error;
+ if ((lo = hexval(optarg[1])) < 0)
+ goto error;
+
+ s_mem_fillval = hi * 16 + lo;
+ return;
+
+error: eprogname();
+ fprintf(stderr, _("invalid command line fill value (%s)\n"), optarg);
+ eprogname();
+ fprintf(stderr, " ");
+ fprintf(stderr, _("Please, use two hexadecimal digits.\n"));
+ exit(EXIT_FAILURE);
+}
+
+static void parse_target_id(const char *optarg)
+{
+ const struct target *p;
+
+ p = find_target(optarg);
+ if (p == NULL) {
+ eprogname();
+ fprintf(stderr, _("invalid target '%s'\n"), optarg);
+ exit(EXIT_FAILURE);
+ } else {
+ s_target_id = p->id;
+ }
+}
+
+static void list_targets(FILE *f)
+{
+ const struct target *p;
+
+ for (p = first_target(); p != NULL; p = next_target()) {
+ fprintf(f, "%-14s%s\n", p->id, p->descr);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ struct ngetopt ngo;
+
+ static struct ngetopt_opt ops[] = {
+ { "define", 1, 'd' },
+ { "extended", 0, 'x' },
+ { "list-targets", 0, 'e' },
+ { "fill", 1, 'f' },
+ { "help", 0, 'h' },
+ { "license", 0, 'l' },
+ { "quiet", 0, 'q' },
+ { "target", 1, 't' },
+ { "undocumented", 0, 'u' },
+ { "version", 0, 'v' },
+ { "print-table", 1, 0 },
+ { "print-delta", 1, 0 },
+ { "verbose", 0, 'V' },
+ { NULL, 0, 0 },
+ };
+
+ ngetopt_init(&ngo, argc, argv, ops);
+ do {
+ c = ngetopt_next(&ngo);
+ switch (c) {
+ case 'V':
+ verbose++;
+ break;
+ case 'v':
+ print_version(stdout);
+ exit(EXIT_SUCCESS);
+ case 'h':
+ print_help(argv[0]);
+ exit(EXIT_SUCCESS);
+ case 'l':
+ print_copyright(stdout);
+ fputs("\n", stdout);
+ print_license(stdout);
+ exit(EXIT_SUCCESS);
+ case 't':
+ parse_target_id(ngo.optarg);
+ break;
+ case 'e':
+ list_targets(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'd':
+ predefine(ngo.optarg);
+ break;
+ case 'f':
+ parse_fill_byte(ngo.optarg);
+ break;
+ case 'q':
+ s_listing = 0;
+ break;
+ case 'x':
+ s_extended_op = 1;
+ break;
+ case 'u':
+ s_undocumented_op = 1;
+ break;
+ case '?':
+ eprint(_("unrecognized option %s\n"),
+ ngo.optarg);
+ exit(EXIT_FAILURE);
+ case ':':
+ eprint(_("%s needs an argument\n"),
+ ngo.optarg);
+ exit(EXIT_FAILURE);
+ case ';':
+ eprint(_("%s does not allow for arguments\n"),
+ ngo.optarg);
+ exit(EXIT_FAILURE);
+ case 0:
+ if (strcmp(ngo.optstr, "print-table") == 0) {
+ print_table(stdout, ngo.optarg);
+ exit(EXIT_SUCCESS);
+ }
+ }
+ } while (c != -1);
+
+ if (argc == ngo.optind) {
+ eprint(_("wrong number of arguments\n"));
+ exit(EXIT_FAILURE);
+ }
+
+ s_asmfname = argv[ngo.optind];
+
+ if (argc - ngo.optind > 1)
+ s_objfname = argv[ngo.optind + 1];
+ else
+ s_objfname = mkfname(s_asmfname, "obj");
+
+ if (argc - ngo.optind > 2)
+ s_lstfname = argv[ngo.optind + 2];
+ else
+ s_lstfname = mkfname(s_asmfname, "lst");
+
+ uz80as();
+ return 0;
+}
diff --git a/Tools/unix/uz80as/mc6800.c b/Tools/unix/uz80as/mc6800.c
new file mode 100644
index 00000000..4ff80be5
--- /dev/null
+++ b/Tools/unix/uz80as/mc6800.c
@@ -0,0 +1,338 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Motorola 6800, 6801.
+ * ===========================================================================
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: NEGA,COMA,LSRA,RORA,ASRA,
+ * ROLA,DECA,INCA,TSTA,CLRA
+ * c: NEGB,COMB,LSRB,RORB,ASRB,
+ * ROLB,DECB,INCB,TSTB,CLRB
+ * d: NEG,COM,LSR,ROR,ASR,
+ * ROL,DEC,INC,TST,JMP,CLR
+ * e: SUBA,CMPA,SBCA,ANDA,BITA,LDAA,
+ * EORA,ADCA,ORAA,ADDA
+ * f: SUBB,CMPB,SBCB,ANDB,BITB,LDAB,
+ * EORB,ADCB,ORAB,ADDB
+ * g: INX,DEX,CLV,SEV,CLC,SEC,CLI,SEI
+ * h: BRA,BHI,BLS,BCC,BCS,BNE,BEQ,
+ * BVC,BVS,BPL,BMI,BGE,BLT,BGT,BLE
+ * i: TSX,INS,PULA,PULB,DES,TXS,PSHA,
+ * PSHB,RTS,RTI,WAI,SWI
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: ouput op as big endian word (no '.' should follow)
+ * g: if op<=$ff output lastbyte and output op as byte
+ * else output (lastbyte | 0x20) and output op as big endian word
+ * (no '.' should follow)
+ * h: relative - 2
+ * i: relative - 4
+ * i: relative - 5
+ */
+
+static const struct matchtab s_matchtab_mc6800[] = {
+ { "NOP", "01.", 1, 0 },
+ { "TAP", "06.", 1, 0 },
+ { "TPA", "07.", 1, 0 },
+ { "g", "08c0.", 1, 0 },
+ { "SBA", "10.", 1, 0 },
+ { "CBA", "11.", 1, 0 },
+ { "TAB", "16.", 1, 0 },
+ { "TBA", "17.", 1, 0 },
+ { "DAA", "19.", 1, 0 },
+ { "ABA", "1B.", 1, 0 },
+ { "i", "30c0.", 1, 0 },
+ { "h a", "20c0.h1.", 1, 0, "r8" },
+ { "b", "40c0.", 1, 0 },
+ { "c", "50c0.", 1, 0 },
+ { "d a,X", "60c0.d1.", 1, 0, "e8" },
+ { "d a,Y", "18.60c0.d1.", 8, 0, "e8" },
+ { "d a", "70c0.f1", 1, 0, "e16" },
+ { "e #a", "80c0.d1.", 1, 0, "e8" },
+ { "f #a", "C0c0.d1.", 1, 0, "e8" },
+ { "e >a", "B0c0.f1", 1, 0, "e16" },
+ { "f >a", "F0c0.f1", 1, 0, "e16" },
+ { "e a,X", "A0c0.d1.", 1, 0, "e8" },
+ { "f a,X", "E0c0.d1.", 1, 0, "e8" },
+ { "e a,Y", "18.A0c0.d1.", 8, 0, "e8" },
+ { "f a,Y", "18.E0c0.d1.", 8, 0, "e8" },
+ { "e a", "90c0g1", 1, 0 },
+ { "f a", "D0c0g1", 1, 0 },
+ { "STAA >a", "B7.f0", 1, 0, "e16" },
+ { "STAA a,X", "A7.d0.", 1, 0, "e8" },
+ { "STAA a,Y", "18.A7.d0.", 8, 0, "e8" },
+ { "STAA a", "97g0", 1, 0 },
+ { "STAB >a", "F7.f0", 1, 0, "e16" },
+ { "STAB a,X", "E7.d0.", 1, 0, "e8" },
+ { "STAB a,Y", "18.E7.d0.", 8, 0, "e8" },
+ { "STAB a", "D7g0", 1, 0 },
+ { "CPX #a", "8C.f0", 1, 0, "e16" },
+ { "CPX >a", "BC.f0", 1, 0, "e16" },
+ { "CPX a,X", "AC.d0.", 1, 0, "e8" },
+ { "CPX a,Y", "CD.AC.d0.", 8, 0, "e8" },
+ { "CPX a", "9Cg0", 1, 0 },
+ { "LDS #a", "8E.f0", 1, 0, "e16" },
+ { "LDS >a", "BE.f0", 1, 0, "e16" },
+ { "LDS a,X", "AE.d0.", 1, 0, "e8" },
+ { "LDS a,Y", "18.AE.d0.", 8, 0, "e8" },
+ { "LDS a", "9Eg0", 1, 0 },
+ { "STS >a", "BF.f0", 1, 0, "e16" },
+ { "STS a,X", "AF.d0.", 1, 0, "e8" },
+ { "STS a,Y", "18.AF.d0.", 8, 0, "e8" },
+ { "STS a", "9Fg0", 1, 0 },
+ { "LDX #a", "CE.f0", 1, 0, "e16" },
+ { "LDX >a", "FE.f0", 1, 0, "e16" },
+ { "LDX a,X", "EE.d0.", 1, 0, "e8" },
+ { "LDX a,Y", "CD.EE.d0.", 8, 0, "e8" },
+ { "LDX a", "DEg0", 1, 0 },
+ { "STX >a", "FF.f0", 1, 0, "e16" },
+ { "STX a,X", "EF.d0.", 1, 0, "e8" },
+ { "STX a,Y", "CD.EF.d0.", 8, 0, "e8" },
+ { "STX a", "DFg0", 1, 0 },
+ { "BSR a", "8D.h0.", 1, 0, "r8" },
+ { "JSR >a", "BD.f0", 4, 0, "e16" },
+ { "JSR a,X", "AD.d0.", 1, 0, "e8" },
+ { "JSR a,Y", "18.AD.d0.", 8, 0, "e8" },
+ { "JSR a", "BD.f0", 2, 0, "e16" },
+ { "JSR a", "9Dg0", 4, 0 },
+ { "ABX", "3A.", 4, 0 },
+ { "ADDD #a", "C3.f0", 4, 0, "e16" },
+ { "ADDD >a", "F3.f0", 4, 0, "e16" },
+ { "ADDD a,X", "E3.d0.", 4, 0, "e8" },
+ { "ADDD a,Y", "18.E3.d0.", 8, 0, "e8" },
+ { "ADDD a", "D3g0", 4, 0 },
+ { "ASLD", "05.", 4, 0 },
+ { "LSLD", "05.", 4, 0 },
+ { "BHS a", "24.h0.", 4, 0, "r8" },
+ { "BLO a", "25.h0.", 4, 0, "r8" },
+ { "BRN a", "21.h0.", 4, 0, "r8" },
+ { "LDD #a", "CC.f0", 4, 0, "e16" },
+ { "LDD >a", "FC.f0", 4, 0, "e16" },
+ { "LDD a,X", "EC.d0.", 4, 0, "e8" },
+ { "LDD a,Y", "18.EC.d0.", 8, 0, "e8" },
+ { "LDD a", "DCg0", 4, 0 },
+ { "LSL a,X", "68.d0.", 4, 0, "e8" },
+ { "LSL a,Y", "18.68.d0.", 8, 0, "e8" },
+ { "LSL a", "78.f0", 4, 0, "e16" },
+ { "LSRD", "04.", 4, 0 },
+ { "MUL", "3D.", 4, 0 },
+ { "PSHX", "3C.", 4, 0 },
+ { "PSHY", "18.3C.", 8, 0 },
+ { "PULX", "38.", 4, 0 },
+ { "PULY", "18.38.", 8, 0 },
+ { "STD >a", "FD.f0", 4, 0, "e16" },
+ { "STD a,X", "ED.d0.", 4, 0, "e8" },
+ { "STD a,Y", "18.ED.d0.", 8, 0, "e8" },
+ { "STD a", "DDg0", 4, 0 },
+ { "SUBD #a", "83.f0", 4, 0, "e16" },
+ { "SUBD >a", "B3.f0", 4, 0, "e16" },
+ { "SUBD a,X", "A3.d0.", 4, 0, "e8" },
+ { "SUBD a,Y", "18.A3.d0.", 8, 0, "e8" },
+ { "SUBD a", "93g0", 4, 0 },
+ { "TEST", "00.", 8, 0 },
+ { "IDIV", "02.", 8, 0 },
+ { "FDIV", "03.", 8, 0 },
+ { "BRSET a,X,a,a", "1E.d0.d1.i2.", 8, 0, "e8e8r8" },
+ { "BRSET a,Y,a,a", "18.1E.d0.d1.j2.", 8, 0, "e8e8r8" },
+ { "BRSET a,a,a", "12.d0.d1.i2.", 8, 0, "e8e8r8" },
+ { "BRCLR a,X,a,a", "1F.d0.d1.i2.", 8, 0, "e8e8r8" },
+ { "BRCLR a,Y,a,a", "18.1F.d0.d1.j2.", 8, 0, "e8e8r8" },
+ { "BRCLR a,a,a", "13.d0.d1.i2.", 8, 0, "e8e8r8" },
+ { "BSET a,X,a", "1C.d0.d1.", 8, 0, "e8e8" },
+ { "BSET a,Y,a", "18.1C.d0.d1.", 8, 0, "e8e8" },
+ { "BSET a,a", "14.d0.d1.", 8, 0, "e8e8" },
+ { "BCLR a,X,a", "1D.d0.d1.", 8, 0, "e8e8" },
+ { "BCLR a,Y,a", "18.1D.d0.d1.", 8, 0, "e8e8" },
+ { "BCLR a,a", "15.d0.d1.", 8, 0, "e8e8" },
+ { "LSLA", "48.", 8, 0 },
+ { "LSLB", "58.", 8, 0 },
+ { "XGDX", "8F.", 8, 0 },
+ { "STOP", "CF.", 8, 0 },
+ { "ABY", "18.3A.", 8, 0 },
+ { "CPY #a", "18.8C.f0", 8, 0, "e16" },
+ { "CPY >a", "18.BC.f0", 8, 0, "e16" },
+ { "CPY a,X", "1A.AC.d0.", 8, 0, "e8" },
+ { "CPY a,Y", "18.AC.d0.", 8, 0, "e8" },
+ { "CPY a", "18.9Cg0", 8, 0 },
+ { "DEY", "18.09.", 8, 0 },
+ { "INY", "18.08.", 8, 0 },
+ { "LDY #a", "18.CE.f0", 8, 0, "e16" },
+ { "LDY >a", "18.FE.f0", 8, 0, "e16" },
+ { "LDY a,X", "1A.EE.d0.", 8, 0, "e8" },
+ { "LDY a,Y", "18.EE.d0.", 8, 0, "e8" },
+ { "LDY a", "18.DEg0", 8, 0 },
+ { "STY >a", "18.FF.f0", 8, 0, "e16" },
+ { "STY a,X", "1A.EF.d0.", 8, 0, "e8" },
+ { "STY a,Y", "18.EF.d0.", 8, 0, "e8" },
+ { "STY a", "18.DFg0", 8, 0 },
+ { "TSY", "18.30.", 8, 0 },
+ { "TYS", "18.35.", 8, 0 },
+ { "XGDY", "18.8F.", 8, 0 },
+ { "CPD #a", "1A.83.f0", 8, 0, "e16" },
+ { "CPD >a", "1A.B3.f0", 8, 0, "e16" },
+ { "CPD a,X", "1A.A3.d0.", 8, 0, "e8" },
+ { "CPD a,Y", "CD.A3.d0.", 8, 0, "e8" },
+ { "CPD a", "1A.93g0", 8, 0 },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+"NEGA", "", "", "COMA", "LSRA", "", "RORA", "ASRA",
+"ASLA", "ROLA", "DECA", "", "INCA", "TSTA", "", "CLRA",
+NULL };
+
+static const char *const cval[] = {
+"NEGB", "", "", "COMB", "LSRB", "", "RORB", "ASRB",
+"ASLB", "ROLB", "DECB", "", "INCB", "TSTB", "", "CLRB",
+NULL };
+
+static const char *const dval[] = {
+"NEG", "", "", "COM", "LSR", "", "ROR", "ASR",
+"ASL", "ROL", "DEC", "", "INC", "TST", "JMP", "CLR",
+NULL };
+
+static const char *const eval[] = {
+"SUBA", "CMPA", "SBCA", "", "ANDA", "BITA", "LDAA", "",
+"EORA", "ADCA", "ORAA", "ADDA",
+NULL };
+
+static const char *const fval[] = {
+"SUBB", "CMPB", "SBCB", "", "ANDB", "BITB", "LDAB", "",
+"EORB", "ADCB", "ORAB", "ADDB",
+NULL };
+
+static const char *const gval[] = {
+"INX", "DEX", "CLV", "SEV", "CLC", "SEC", "CLI", "SEI",
+NULL };
+
+static const char *const hval[] = {
+"BRA", "", "BHI", "BLS", "BCC", "BCS", "BNE", "BEQ",
+"BVC", "BVS", "BPL", "BMI", "BGE", "BLT", "BGT", "BLE",
+NULL };
+
+static const char *const ival[] = {
+"TSX", "INS", "PULA", "PULB", "DES", "TXS", "PSHA",
+"PSHB", "", "RTS", "", "RTI", "", "", "WAI", "SWI",
+NULL };
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval,
+ gval, hval, ival
+};
+
+static int match_mc6800(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'i') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_mc6800(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b;
+
+ b = *eb;
+ switch (p) {
+ case 'f': genb(vs[i] >> 8, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ break;
+ case 'g': if (vs[i] <= 255) {
+ genb(b, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ } else {
+ genb(b | 0x20, s_pline_ep);
+ genb(vs[i] >> 8, s_pline_ep);
+ genb(vs[i], s_pline_ep);
+ }
+ break;
+ case 'h': b = (vs[i] - savepc - 2);
+ break;
+ case 'i': b = (vs[i] - savepc - 4);
+ break;
+ case 'j': b = (vs[i] - savepc - 5);
+ break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_mc6800(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_mc6800(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'i') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_mc6800 = {
+ .id = "mc6800",
+ .descr = "Motorola 6800",
+ .matcht = s_matchtab_mc6800,
+ .matchf = match_mc6800,
+ .genf = gen_mc6800,
+ .pat_char_rewind = pat_char_rewind_mc6800,
+ .pat_next_str = pat_next_str_mc6800,
+ .mask = 3
+};
+
+const struct target s_target_mc6801 = {
+ .id = "mc6801",
+ .descr = "Motorola 6801",
+ .matcht = s_matchtab_mc6800,
+ .matchf = match_mc6800,
+ .genf = gen_mc6800,
+ .pat_char_rewind = pat_char_rewind_mc6800,
+ .pat_next_str = pat_next_str_mc6800,
+ .mask = 5
+};
+
+const struct target s_target_m68hc11 = {
+ .id = "m68hc11",
+ .descr = "Motorola 68HC11",
+ .matcht = s_matchtab_mc6800,
+ .matchf = match_mc6800,
+ .genf = gen_mc6800,
+ .pat_char_rewind = pat_char_rewind_mc6800,
+ .pat_next_str = pat_next_str_mc6800,
+ .mask = 13
+};
diff --git a/Tools/unix/uz80as/mos6502.c b/Tools/unix/uz80as/mos6502.c
new file mode 100644
index 00000000..f1041b24
--- /dev/null
+++ b/Tools/unix/uz80as/mos6502.c
@@ -0,0 +1,385 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * MOS Technology 6502.
+ * Rockwell R6501.
+ * California Micro Devices G65SC02.
+ * Rockwell R65C02.
+ * Rockwell R65C29.
+ * Western Design Center W65C02S.
+ * ===========================================================================
+ */
+
+/* mos6502, the original
+ *
+ * g65sc02 California Micro Devices, adds to mos6502:
+ * - zp ADC,AND,CMP,EOR,LDA,ORA,SBC,STA
+ * - DEC A, INC A
+ * - JMP (abs,X)
+ * - BRA
+ * - PHX,PHY,PLX,PLY
+ * - STZ
+ * - TRB
+ * - TSB
+ * - More addressing modes for BIT, etc
+ *
+ * r6501 Rockwell, adds to mos6502:
+ * - BBR, BBS
+ * - RMB, SMB
+ *
+ * r65c02 Rockwell, adds the instructions of the g65sc02 and r6501
+ *
+ * r65c29 Rockwell, adds to r65c02:
+ * - MUL
+ *
+ * w65c02s Western Design Center, adds to r65c02:
+ * - STP,WAI
+ */
+
+#include "pp.h"
+#include "err.h"
+#include "options.h"
+#include "uz80as.h"
+#include
+
+/* pat:
+ * a: expr
+ * b: ORA,AND,EOR,ADC,STA,LDA,CMP,SBC
+ * c: ORA,AND,EOR,ADC,LDA,CMP,SBC
+ * d: PHP,CLC,PLP,SEC,PHA,CLI,PLA,SEI,
+ * DEY,TYA,TAY,CLV,INY,CLD,INX,SED
+ * e: ASL,ROL,LSR,ROR
+ * f: DEC, INC
+ * g: BPL,BMI,BVC,BVS,BCC,BCS,BNE,BEQ
+ * h: TXA,TXS,TAX,TSX,DEX,NOP
+ * i: CPY,CPX
+ * j: TSB,TRB
+ * k: BBR0,BBR1,BBR2,BBR3,BBR4,BBR5,BBR6,BBR6,
+ * BBS0,BBS1,BBS2,BBS3,BBS4,BBS5,BBS6,BBS7
+ * l: RMB0,RMB1,RMB2,RMB3,RMB4,RMB5,EMB6,RMB7,
+ * SMB0,SMB1,SMB2,SMB3,SMB4,SMB5,SMB6,SMB7
+ * m: PHY,PLY
+ * n: PHX,PLX
+ * o: INC, DEC
+ *
+ * gen:
+ * .: output lastbyte
+ * b: (op << 3) | lastbyte
+ * c: op | lastbyte
+ * d: lastbyte = op as 8 bit value
+ * e: output op as word (no '.' should follow)
+ * f: (op << 5) | lastbyte
+ * g: if op <= $FF output last byte and then op as 8 bit value;
+ * else output (lastbyte | 0x08) and output op as word
+ * (no '.' should follow)
+ * h: (op << 4) | lastbyte
+ * i: relative jump to op (-2)
+ * j: if op <= $FF output $64 and op as 8 bit
+ * else output $9C and op as word
+ * (no '.' should follow)
+ * k: if op <= $FF ouput $74 and op as 8 bit
+ * else output $9E and op as word
+ * (no '.' should follow)
+ * l: relative jump to op (-3)
+ */
+
+static const struct matchtab s_matchtab_mos6502[] = {
+ { "BRK", "00.", 1, 0 },
+ { "JSR a", "20.e0", 1, 0 },
+ { "RTI", "40.", 1, 0 },
+ { "RTS", "60.", 1, 0 },
+ { "h", "8Ah0.", 1, 0 },
+ { "d", "08h0.", 1, 0 },
+ { "c #a", "09f0.d1.", 1, 0, "e8" },
+ { "b (a,X)", "01f0.d1.", 1, 0, "e8" },
+ { "b (a),Y", "11f0.d1.", 1, 0, "e8" },
+ { "b (a)", "12f0.d1.", 2, 0, "e8" },
+ { "b a", "05f0g1", 1, 0 },
+ { "b a,X", "15f0g1", 1, 0 },
+ { "b a,Y", "19f0.e1", 1, 0 },
+ { "e A", "0Af0.", 1, 0 },
+ { "e a", "06f0g1", 1, 0 },
+ { "e a,X", "16f0g1", 1, 0 },
+ { "STX a", "86g0", 1, 0 },
+ { "STX a,Y", "96.d0.", 1, 0, "e8" },
+ { "LDX #a", "A2.d0.", 1, 0, "e8" },
+ { "LDX a", "A6g0", 1, 0 },
+ { "LDX a,Y", "B6g0", 1, 0 },
+ { "o A", "1Af0.", 2, 0 },
+ { "f a", "C6f0g1", 1, 0 },
+ { "f a,X", "D6f0g1", 1, 0 },
+ { "g a", "10f0.i1.", 1, 0, "r8" },
+ { "BIT #a", "89.d0.", 2, 0, "e8" },
+ { "BIT a", "24g0", 1, 0 },
+ { "BIT a,X", "34g0", 2, 0 },
+ { "JMP (a)", "6C.e0", 1, 0 },
+ { "JMP (a,X)", "7C.e0", 2, 0 },
+ { "JMP a", "4C.e0", 1, 0 },
+ { "STY a", "84g0", 1, 0 },
+ { "STY a,X", "94.d0.", 1, 0, "e8" },
+ { "LDY #a", "A0.d0.", 1, 0, "e8" },
+ { "LDY a", "A4g0", 1, 0 },
+ { "LDY a,X", "B4g0", 1, 0 },
+ { "i #a", "C0f0.d1.", 1, 0, "e8" },
+ { "i a", "C4f0g1", 1, 0 },
+ { "j a", "04h0g1", 2, 0 },
+ { "k a,a", "0Fh0.d1.l2.", 4, 0, "e8r8" },
+ { "l a", "07h0.d1.", 4, 0, "e8" },
+ { "m", "5Af0.", 2, 0 },
+ { "n", "DAf0.", 2, 0 },
+ { "BRA a", "80.i0.", 2, 0, "r8" },
+ { "STZ a,X", "k1", 2, 0 },
+ { "STZ a", "j1", 2, 0 },
+ { "MUL", "02.", 8, 0 },
+ { "WAI", "CB.", 16, 0 },
+ { "STP", "DB.", 16, 0 },
+ { NULL, NULL },
+};
+
+static const char *const bval[] = {
+ "ORA", "AND", "EOR", "ADC",
+ "STA", "LDA", "CMP", "SBC",
+ NULL
+};
+
+static const char *const cval[] = {
+ "ORA", "AND", "EOR", "ADC",
+ "", "LDA", "CMP", "SBC", NULL
+};
+
+static const char *const dval[] = {
+ "PHP", "CLC", "PLP", "SEC",
+ "PHA", "CLI", "PLA", "SEI",
+ "DEY", "TYA", "TAY", "CLV",
+ "INY", "CLD", "INX", "SED",
+ NULL
+};
+
+
+static const char *const eval[] = {
+ "ASL", "ROL", "LSR", "ROR",
+ NULL
+};
+
+static const char *const fval[] = {
+ "DEC", "INC",
+ NULL
+};
+
+static const char *const gval[] = {
+ "BPL", "BMI", "BVC", "BVS",
+ "BCC", "BCS", "BNE", "BEQ",
+ NULL
+};
+
+static const char *const hval[] = {
+ "TXA", "TXS", "TAX", "TSX",
+ "DEX", "", "NOP",
+ NULL
+};
+
+static const char *const ival[] = {
+ "CPY", "CPX",
+ NULL
+};
+
+static const char *const jval[] = {
+ "TSB", "TRB",
+ NULL
+};
+
+static const char *const kval[] = {
+ "BBR0", "BBR1", "BBR2", "BBR3",
+ "BBR4", "BBR5", "BBR6", "BBR7",
+ "BBS0", "BBS1", "BBS2", "BBS3",
+ "BBS4", "BBS5", "BBS6", "BBS7",
+ NULL
+};
+
+static const char *const lval[] = {
+ "RMB0", "RMB1", "RMB2", "RMB3",
+ "RMB4", "RMB5", "RMB6", "RMB7",
+ "SMB0", "SMB1", "SMB2", "SMB3",
+ "SMB4", "SMB5", "SMB6", "SMB7",
+ NULL
+};
+
+static const char *const mval[] = {
+ "PHY", "PLY",
+ NULL
+};
+
+static const char *const nval[] = {
+ "PHX", "PLX",
+ NULL
+};
+
+static const char *const oval[] = {
+ "INC", "DEC",
+ NULL
+};
+
+static const char *const *const valtab[] = {
+ bval, cval, dval, eval, fval,
+ gval, hval, ival, jval, kval,
+ lval, mval, nval, oval
+};
+
+static int match_mos6502(char c, const char *p, const char **q)
+{
+ int v;
+
+ if (c <= 'o') {
+ v = mreg(p, valtab[(int) (c - 'b')], q);
+ } else {
+ v = -1;
+ }
+
+ return v;
+}
+
+static int gen_mos6502(int *eb, char p, const int *vs, int i, int savepc)
+{
+ int b, w;
+
+ b = *eb;
+ switch (p) {
+ case 'f': b |= (vs[i] << 5); break;
+ case 'g': w = vs[i] & 0xffff;
+ if (w <= 0xff) {
+ genb(b, s_pline_ep);
+ b = 0;
+ genb(w, s_pline_ep);
+ } else {
+ b |= 0x08;
+ genb(b, s_pline_ep);
+ b = 0;
+ genb(w, s_pline_ep);
+ genb(w >> 8, s_pline_ep);
+ }
+ break;
+ case 'h': b |= (vs[i] << 4); break;
+ case 'i': b = (vs[i] - savepc - 2); break;
+ case 'j': w = vs[i] & 0xffff;
+ if (w <= 0xff) {
+ genb(0x64, s_pline_ep);
+ b = 0;
+ genb(w, s_pline_ep);
+ } else {
+ genb(0x9C, s_pline_ep);
+ b = 0;
+ genb(w, s_pline_ep);
+ genb(w >> 8, s_pline_ep);
+ }
+ break;
+ case 'k': w = vs[i] & 0xffff;
+ if (w <= 0xff) {
+ genb(0x74, s_pline_ep);
+ b = 0;
+ genb(w, s_pline_ep);
+ } else {
+ genb(0x9E, s_pline_ep);
+ b = 0;
+ genb(w, s_pline_ep);
+ genb(w >> 8, s_pline_ep);
+ }
+ break;
+ case 'l': b = (vs[i] - savepc - 3); break;
+ default:
+ return -1;
+ }
+
+ *eb = b;
+ return 0;
+}
+
+static int s_pat_char = 'b';
+static int s_pat_index;
+
+static void pat_char_rewind_mos6502(int c)
+{
+ s_pat_char = c;
+ s_pat_index = 0;
+};
+
+static const char *pat_next_str_mos6502(void)
+{
+ const char *s;
+
+ if (s_pat_char >= 'b' && s_pat_char <= 'o') {
+ s = valtab[(int) (s_pat_char - 'b')][s_pat_index];
+ if (s != NULL) {
+ s_pat_index++;
+ }
+ } else {
+ s = NULL;
+ }
+
+ return s;
+};
+
+const struct target s_target_mos6502 = {
+ .id = "mos6502",
+ .descr = "MOS Technology 6502",
+ .matcht = s_matchtab_mos6502,
+ .matchf = match_mos6502,
+ .genf = gen_mos6502,
+ .pat_char_rewind = pat_char_rewind_mos6502,
+ .pat_next_str = pat_next_str_mos6502,
+ .mask = 1
+};
+
+const struct target s_target_r6501 = {
+ .id = "r6501",
+ .descr = "Rockwell R6501",
+ .matcht = s_matchtab_mos6502,
+ .matchf = match_mos6502,
+ .genf = gen_mos6502,
+ .pat_char_rewind = pat_char_rewind_mos6502,
+ .pat_next_str = pat_next_str_mos6502,
+ .mask = 5
+};
+
+const struct target s_target_g65sc02 = {
+ .id = "g65sc02",
+ .descr = "California Micro Devices G65SC02",
+ .matcht = s_matchtab_mos6502,
+ .matchf = match_mos6502,
+ .genf = gen_mos6502,
+ .pat_char_rewind = pat_char_rewind_mos6502,
+ .pat_next_str = pat_next_str_mos6502,
+ .mask = 3
+};
+
+const struct target s_target_r65c02 = {
+ .id = "r65c02",
+ .descr = "Rockwell R65C02",
+ .matcht = s_matchtab_mos6502,
+ .matchf = match_mos6502,
+ .genf = gen_mos6502,
+ .pat_char_rewind = pat_char_rewind_mos6502,
+ .pat_next_str = pat_next_str_mos6502,
+ .mask = 7
+};
+
+const struct target s_target_r65c29 = {
+ .id = "r65c29",
+ .descr = "Rockwell R65C29, R65C00/21",
+ .matcht = s_matchtab_mos6502,
+ .matchf = match_mos6502,
+ .genf = gen_mos6502,
+ .pat_char_rewind = pat_char_rewind_mos6502,
+ .pat_next_str = pat_next_str_mos6502,
+ .mask = 15
+};
+
+const struct target s_target_w65c02s = {
+ .id = "w65c02s",
+ .descr = "Western Design Center W65C02S",
+ .matcht = s_matchtab_mos6502,
+ .matchf = match_mos6502,
+ .genf = gen_mos6502,
+ .pat_char_rewind = pat_char_rewind_mos6502,
+ .pat_next_str = pat_next_str_mos6502,
+ .mask = 027
+};
diff --git a/Tools/unix/uz80as/ngetopt.c b/Tools/unix/uz80as/ngetopt.c
new file mode 100644
index 00000000..c0404012
--- /dev/null
+++ b/Tools/unix/uz80as/ngetopt.c
@@ -0,0 +1,237 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Handling of command line options, similar to getopt.
+ * ===========================================================================
+ */
+
+/*
+ * Changes:
+ *
+ * - Jul 22 2018: long options without short option character recognized.
+ *
+ */
+
+#include "ngetopt.h"
+
+#ifndef STRING_H
+#include
+#endif
+
+static int find_short_opt(int val, struct ngetopt_opt *ops)
+{
+ int i;
+
+ i = 0;
+ while (ops[i].name != NULL) {
+ if (ops[i].val > 0 && ops[i].val == val)
+ return i;
+ i++;
+ }
+
+ return -1;
+}
+
+static int find_long_opt(char *str, struct ngetopt_opt *ops)
+{
+ int i;
+ const char *p, *q;
+
+ i = 0;
+ while (ops[i].name != NULL) {
+ p = ops[i].name;
+ q = str;
+ while (*p != '\0' && *p == *q) {
+ p++;
+ q++;
+ }
+ if (*p == '\0' && (*q == '\0' || *q == '=')) {
+ return i;
+ }
+ i++;
+ }
+
+ return -1;
+}
+
+void ngetopt_init(struct ngetopt *p, int argc, char *const *argv,
+ struct ngetopt_opt *ops)
+{
+ p->argc = argc;
+ p->argv = argv;
+ p->ops = ops;
+ p->optind = 1;
+ p->subind = 0;
+ strcpy(p->str, "-X");
+}
+
+static int get_short_opt(struct ngetopt *p)
+{
+ int i;
+ char *opt;
+
+ opt = p->argv[p->optind];
+ i = find_short_opt(opt[p->subind], p->ops);
+ if (i < 0) {
+ /* unrecognized option */
+ p->str[1] = (char) opt[p->subind];
+ p->optarg = p->str;
+ p->subind++;
+ return '?';
+ }
+
+ if (!p->ops[i].has_arg) {
+ /* it's ok */
+ p->subind++;
+ return p->ops[i].val;
+ }
+
+ /* needs an argument */
+ if (opt[p->subind + 1] != '\0') {
+ /* the argument is the suffix */
+ p->optarg = &opt[p->subind + 1];
+ p->subind = 0;
+ p->optind++;
+ return p->ops[i].val;
+ }
+
+ /* the argument is the next token */
+ p->optind++;
+ p->subind = 0;
+ if (p->optind < p->argc) {
+ p->optarg = p->argv[p->optind];
+ p->optind++;
+ return p->ops[i].val;
+ }
+
+ /* ups, argument missing */
+ p->str[1] = (char) p->ops[i].val;
+ p->optarg = p->str;
+ return ':';
+}
+
+static int get_opt(struct ngetopt *p)
+{
+ int i;
+ char *opt, *optnext;
+
+ /* all arguments consumed */
+ if (p->optind >= p->argc)
+ return -1;
+
+ opt = p->argv[p->optind];
+ if (opt[0] != '-') {
+ /* non option */
+ return -1;
+ }
+
+ /* - */
+ if (opt[1] == '\0') {
+ /* stdin */
+ return -1;
+ }
+
+ if (opt[1] != '-') {
+ /* -xxxxx */
+ p->subind = 1;
+ return get_short_opt(p);
+ }
+
+ /* -- */
+ if (opt[2] == '\0') {
+ /* found "--" */
+ p->optind++;
+ return -1;
+ }
+
+ /* long option */
+ i = find_long_opt(&opt[2], p->ops);
+ if (i < 0) {
+ /* not found */
+ p->optind++;
+ p->optarg = opt;
+ while (*opt != '\0' && *opt != '=') {
+ opt++;
+ }
+ *opt = '\0';
+ return '?';
+ }
+
+ /* found, go to end of option */
+ optnext = opt + 2 + strlen(p->ops[i].name);
+
+ if (*optnext == '\0' && !p->ops[i].has_arg) {
+ /* doesn't need arguments */
+ p->optind++;
+ p->optstr = opt + 2;
+ return p->ops[i].val;
+ }
+
+ if (*optnext == '=' && !p->ops[i].has_arg) {
+ /* does not need arguments but argument supplied */
+ *optnext = '\0';
+ p->optarg = opt;
+ return ';';
+ }
+
+ /* the argument is the next token */
+ if (*optnext == '\0') {
+ p->optind++;
+ if (p->optind < p->argc) {
+ p->optstr = opt + 2;
+ p->optarg = p->argv[p->optind];
+ p->optind++;
+ return p->ops[i].val;
+ }
+
+ /* ups, argument missing */
+ p->optarg = opt;
+ p->optind++;
+ return ':';
+ }
+
+ /* *optnext == '=' */
+ *optnext = '\0';
+ p->optstr = opt + 2;
+ p->optarg = optnext + 1;
+ p->optind++;
+ return p->ops[i].val;
+}
+
+/*
+ * If ok:
+ *
+ * - For a long option with a zero value single character option, 0 is
+ * returned, optstr is the string of the long option (without '-' or '--')
+ * and optarg is the option argument or NULL.
+ *
+ * - For anything else the single option character is returned and optarg
+ * is the option argument or NULL.
+ *
+ * If the option is not recognized, '?' is returned, and optarg is the
+ * literal string of the option not recognized (already with '-' or '--'
+ * prefixed).
+ *
+ * If the option is recognized but the argument is missing, ':' is
+ * returned and optarg is the option as supplied (with '-' or '--' prefixed).
+ *
+ * If the option is recognized and it is a long option followed by '=', but the
+ * option does not take arguments, ';' is returned and optarg is the option
+ * (with '-' or '--' prefixed).
+ *
+ * -1 is returned if no more options.
+ */
+int ngetopt_next(struct ngetopt *p)
+{
+ if (p->subind == 0)
+ return get_opt(p);
+
+ /* p->subind > 0 */
+ if (p->argv[p->optind][p->subind] != '\0')
+ return get_short_opt(p);
+
+ /* no more options in this list of short options */
+ p->subind = 0;
+ p->optind++;
+ return get_opt(p);
+}
diff --git a/Tools/unix/uz80as/ngetopt.h b/Tools/unix/uz80as/ngetopt.h
new file mode 100644
index 00000000..31ff8a60
--- /dev/null
+++ b/Tools/unix/uz80as/ngetopt.h
@@ -0,0 +1,40 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Handling of command line options, similar to getopt.
+ * ===========================================================================
+ */
+
+#ifndef NGETOPT_H
+#define NGETOPT_H
+
+/*
+ * Changelog:
+ *
+ * - Jul 21 2018: long options without short option character recognized.
+ *
+ */
+
+struct ngetopt_opt {
+ const char *name;
+ int has_arg;
+ int val;
+};
+
+struct ngetopt {
+ char *optstr;
+ char *optarg;
+ /* private */
+ int optind;
+ int argc;
+ char *const *argv;
+ struct ngetopt_opt *ops;
+ int subind;
+ char str[3];
+};
+
+void ngetopt_init(struct ngetopt *p, int argc, char *const *argv,
+ struct ngetopt_opt *ops);
+int ngetopt_next(struct ngetopt *p);
+
+#endif
diff --git a/Tools/unix/uz80as/options.c b/Tools/unix/uz80as/options.c
new file mode 100644
index 00000000..c3c24cd2
--- /dev/null
+++ b/Tools/unix/uz80as/options.c
@@ -0,0 +1,33 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Global options, normally coming from the command line.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "options.h"
+#include "err.h"
+
+const char *s_asmfname; /* Name of source file. */
+const char *s_objfname; /* Name of generated binary file. */
+const char *s_lstfname; /* Name of listing file. */
+const char *s_target_id = "z80"; /* ID of target */
+int s_listing = 1; /* If we generate the listing file or not. */
+int s_extended_op = 0; /* Allow extended instruction syntax. */
+int s_undocumented_op = 0; /* Allow undocumented instructions. */
+int s_mem_fillval = 0; /* Default value to fill the 64K memory. */
+
+/* Command line macro definitions. */
+struct predef *s_predefs;
+
+/* Predefine a macro in the command line that must persist between passes. */
+void predefine(const char *text)
+{
+ struct predef *pdef;
+
+ pdef = emalloc(sizeof(*pdef));
+ pdef->name = text;
+ pdef->next = s_predefs;
+ s_predefs = pdef;
+}
diff --git a/Tools/unix/uz80as/options.h b/Tools/unix/uz80as/options.h
new file mode 100644
index 00000000..c23ed913
--- /dev/null
+++ b/Tools/unix/uz80as/options.h
@@ -0,0 +1,29 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Global options, normally coming from the command line.
+ * ===========================================================================
+ */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+/* Predefined macro at the command line. */
+struct predef {
+ struct predef *next;
+ const char *name;
+};
+
+extern const char *s_asmfname;
+extern const char *s_objfname;
+extern const char *s_lstfname;
+extern const char *s_target_id;
+extern int s_listing;
+extern int s_extended_op;
+extern int s_undocumented_op;
+extern int s_mem_fillval;
+extern struct predef *s_predefs;
+
+void predefine(const char *name);
+
+#endif
diff --git a/Tools/unix/uz80as/pp.c b/Tools/unix/uz80as/pp.c
new file mode 100644
index 00000000..b2d6a067
--- /dev/null
+++ b/Tools/unix/uz80as/pp.c
@@ -0,0 +1,741 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Preprocessor.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "pp.h"
+#include "utils.h"
+#include "err.h"
+#include "incl.h"
+#include "expr.h"
+#include "exprint.h"
+
+#ifndef CTYPE_H
+#include
+#endif
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STDLIB_H
+#include
+#endif
+
+#ifndef STRING_H
+#include
+#endif
+
+/* Max number of macros. */
+#define NMACROS 1000
+
+/* Closest prime to NMACROS / 4. */
+#define MACTABSZ 241
+
+/* Max number of macro arguments. */
+#define NPARAMS 20
+
+#define DEFINESTR "DEFINE"
+#define DEFCONTSTR "DEFCONT"
+#define INCLUDESTR "INCLUDE"
+#define IFSTR "IF"
+#define IFDEFSTR "IFDEF"
+#define IFNDEFSTR "IFNDEF"
+#define ENDIFSTR "ENDIF"
+#define ELSESTR "ELSE"
+
+/*
+ * Macro.
+ *
+ * For example, the macro:
+ *
+ * #define SUM(a,b) (a+b)
+ *
+ * is:
+ *
+ * name = SUM
+ * pars = a\0b\0
+ * ppars[0] points to &pars[0], that is to "a"
+ * ppars[1] points to &pars[2], that is to "b"
+ * npars = 2
+ * text is "(a+b)"
+ */
+struct macro {
+ struct macro *next; /* Next in hash chain. */
+ char *name; /* Identifier. */
+ char *pars; /* String with params separated by '\0'. */
+ char *text; /* Text to expand. */
+ char *ppars[NPARAMS]; /* Pointers to the beginning of each param. */
+ int npars; /* Valid number of params in ppars. */
+};
+
+/* Hash table of preprocessor symbols. */
+static struct macro *s_mactab[MACTABSZ];
+
+/* Preprocessing line buffers. */
+static char s_ppbuf[2][LINESZ];
+
+/* If we are discarding lines; if not 0, level of if. */
+int s_skipon;
+
+/* Number of nested #if or #ifdef or #ifndef. */
+static int s_nifs;
+
+/* Last defined macro. */
+static struct macro *s_lastmac;
+
+/* Number of macros in table. */
+static int s_nmacs;
+
+/* The preprocessed line, points to one of s_ppbuf. */
+char *s_pline;
+
+/* Current program counter. */
+int s_pc;
+
+/* Current pass. */
+int s_pass;
+
+
+/* Only valid while in the call to pp_line(). */
+static const char *s_line; /* original line */
+static const char *s_line_ep; /* pointer inside s_line for error reporting */
+
+/*
+ * Copy [p, q[ to [dp, dq[.
+ */
+static char *copypp(char *dp, char *dq, const char *p, const char *q)
+{
+ while (dp < dq && p < q)
+ *dp++ = *p++;
+ return dp;
+}
+
+/*
+ * Find the 'argnum' argument in 'args' and return a pointer to it.
+ *
+ * 'args' is a list of arguments "([id [,id]*).
+ * 'argnum' is the argument number to find.
+ *
+ * Return not found.
+ */
+static const char *findarg(const char *args, int argnum)
+{
+ if (*args == '(') {
+ do {
+ args++;
+ if (argnum == 0)
+ return args;
+ argnum--;
+ while (*args != '\0' && *args != ','
+ && *args != ')')
+ {
+ args++;
+ }
+ } while (*args == ',');
+ }
+ return NULL;
+}
+
+/*
+ * Find the 'argnum' argument in 'args' and copy it to [dp, dq[.
+ *
+ * 'args' points to a list of arguments "([id [,id]*).
+ * 'argnum' is the argument number to copy.
+ *
+ * Return the new 'dp' after copying.
+ */
+static char *copyarg(char *dp, char *dq, const char *args, int argnum)
+{
+ const char *p;
+
+ p = findarg(args, argnum);
+ if (p == NULL)
+ return dp;
+
+ while (dp < dq && *p != '\0' && *p != ',' && *p != ')')
+ *dp++ = *p++;
+ return dp;
+}
+
+/*
+ * Sees if [idp, idq[ is a parameter of the macro 'pps'.
+ * If it is, return the number of parameter.
+ * Else return -1.
+ */
+static int findparam(const char *idp, const char *idq, struct macro *pps)
+{
+ int i;
+ const char *p, *r;
+
+ for (i = 0; i < pps->npars; i++) {
+ p = pps->ppars[i];
+ r = idp;
+ while (*p != '\0' && r < idq && *p == *r) {
+ p++;
+ r++;
+ }
+ if (*p == '\0' && r == idq)
+ return i;
+ }
+ return -1;
+}
+
+/*
+ * Lookup the string in [p, q[ in 's_mactab'.
+ * Return the symbol or NULL if it is not in the table.
+ */
+static struct macro *pplookup(const char *p, const char *q)
+{
+ int h;
+ struct macro *nod;
+
+ h = hash(p, q, MACTABSZ);
+ for (nod = s_mactab[h]; nod != NULL; nod = nod->next)
+ if (scmp(p, q, nod->name) == 0)
+ return nod;
+
+ return nod;
+}
+
+/*
+ * Expand macro in [dp, dq[.
+ *
+ * 'pps' is the macro to expand.
+ * 'args' points to the start of the arguments to substitute, if any.
+ *
+ * Return new dp.
+ */
+static char *expandid(char *dp, char *dq, struct macro *pps, const char *args)
+{
+ const char *p, *q;
+ int validid, argnum;
+
+ validid = 1;
+ p = pps->text;
+ while (*p != '\0' && dp < dq) {
+ if (isidc0(*p)) {
+ for (q = p; isidc(*q); q++)
+ ;
+ if (validid) {
+ argnum = findparam(p, q, pps);
+ if (argnum >= 0)
+ dp = copyarg(dp, dq, args, argnum);
+ else
+ dp = copypp(dp, dq, p, q);
+ } else {
+ dp = copypp(dp, dq, p, q);
+ }
+ p = q;
+ validid = 1;
+ } else {
+ validid = !isidc(*p);
+ *dp++ = *p++;
+ }
+ }
+ return dp;
+}
+
+/*
+ * If 'p' points the the start of an argument list, that is, '(',
+ * point to one character past the first ')' after 'p'.
+ * Else return 'p'.
+ */
+static const char *skipargs(const char *p)
+{
+ if (*p == '(') {
+ while (*p != '\0' && *p != ')')
+ p++;
+ if (*p == ')')
+ p++;
+ }
+ return p;
+}
+
+/*
+ * Expand macros found in 'p' (null terminated) into [dp, dq[.
+ * dq must be writable to put a final '\0'.
+ */
+static int expand_line(char *dp, char *dq, const char *p)
+{
+ char *op;
+ int expanded, validid;
+ const char *s;
+ struct macro *nod;
+
+ validid = 1;
+ expanded = 0;
+ while (dp < dq && *p != '\0' && *p != ';') {
+ if (*p == '\'' && *(p + 1) != '\0' && *(p + 2) == '\'') {
+ /* characters */
+ dp = copypp(dp, dq, p, p + 3);
+ p += 3;
+ validid = 1;
+ } else if (*p == '\"') {
+ /* strings */
+ s = p;
+ p++;
+ while (*p != '\0' && *p != '\"')
+ p++;
+ if (*p == '\"')
+ p++;
+ dp = copypp(dp, dq, s, p);
+ validid = 1;
+ } else if (isidc0(*p)) {
+ s = p;
+ while (isidc(*p))
+ p++;
+ if (validid) {
+ nod = pplookup(s, p);
+ if (nod != NULL) {
+ op = dp;
+ dp = expandid(dp, dq, nod, p);
+ expanded = dp != op;
+ p = skipargs(p);
+ } else {
+ dp = copypp(dp, dq, s, p);
+ }
+ } else {
+ dp = copypp(dp, dq, s, p);
+ }
+ validid = 1;
+ } else {
+ validid = *p != '.' && !isalnum(*p);
+ *dp++ = *p++;
+ }
+ }
+ *dp = '\0';
+ return expanded;
+}
+
+/*
+ * Expand macros found in 'p' (null terminated).
+ * Return a pointer to an internal preprocessed line (null terminated).
+ */
+static char *expand_line0(const char *p)
+{
+ int iter, expanded;
+ char *np, *nq, *op;
+
+ iter = 0;
+ np = &s_ppbuf[iter & 1][0];
+ nq = &s_ppbuf[iter & 1][LINESZ - 1];
+ expanded = expand_line(np, nq, p);
+ /* TODO: recursive macro expansion limit */
+ while (expanded && iter < 5) {
+ op = np;
+ iter++;
+ np = &s_ppbuf[iter & 1][0];
+ nq = &s_ppbuf[iter & 1][LINESZ - 1];
+ expanded = expand_line(np, nq, op);
+ }
+ return np;
+}
+
+/*
+ * Check if 'p' starts with the preprocessor directive 'ucq', that must be in
+ * upper case.
+ * 'p' can have any case.
+ * After the preprocessor directive must be a space or '\0'.
+ * Return 1 if all the above is true. 0 otherwise.
+ */
+static int isppid(const char *p, const char *ucq)
+{
+ while (*p != '\0' && *ucq != '\0' && toupper(*p) == *ucq) {
+ p++;
+ ucq++;
+ }
+ return (*ucq == '\0') && (*p == '\0' || isspace(*p));
+}
+
+/*
+ * Define a macro.
+ *
+ * [idp, idq[ is the macro id.
+ * [ap, aq[ is the macro argument list. If ap == aq there are no arguments.
+ * [tp, tq[ is the macro text.
+ */
+static void define(const char *idp, const char *idq,
+ const char *tp, const char *tq,
+ const char *ap, const char *aq)
+{
+ int h;
+ char *p;
+ struct macro *nod;
+
+ h = hash(idp, idq, MACTABSZ);
+ for (nod = s_mactab[h]; nod != NULL; nod = nod->next) {
+ if (scmp(idp, idq, nod->name) == 0) {
+ /* Already defined. */
+ return;
+ }
+ }
+
+ s_nmacs++;
+ if (s_nmacs >= NMACROS) {
+ eprint(_("maximum number of macros exceeded (%d)\n"), NMACROS);
+ exit(EXIT_FAILURE);
+ }
+
+ nod = emalloc((sizeof *nod) + (idq - idp) + (aq - ap) + 2);
+ nod->text = emalloc(tq - tp + 1);
+ nod->name = (char *) ((unsigned char *) nod + (sizeof *nod));
+ nod->pars = nod->name + (idq - idp + 1);
+
+ copychars(nod->name, idp, idq);
+ copychars(nod->text, tp, tq);
+ copychars(nod->pars, ap, aq);
+
+ // printf("DEF %s(%s) %s\n", nod->name, nod->pars, nod->text);
+
+ /* We don't check whether the arguments are different. */
+
+ /*
+ * Make ppars point to each argument and null terminate each one.
+ * Count the number of arguments.
+ */
+ nod->npars = 0;
+ p = nod->pars;
+ while (*p != '\0') {
+ nod->ppars[nod->npars++] = p;
+ while (*p != '\0' && *p != ',')
+ p++;
+ if (*p == ',')
+ *p++ = '\0';
+ }
+
+ nod->next = s_mactab[h];
+ s_mactab[h] = nod;
+ s_lastmac = nod;
+}
+
+/* Add the text [p, q[ to the last macro text. */
+static void defcont(const char *p, const char *q)
+{
+ char *nt;
+ size_t len;
+
+ len = strlen(s_lastmac->text);
+ nt = erealloc(s_lastmac->text, (q - p) + len + 1);
+ copychars(nt + len, p, q);
+ s_lastmac->text = nt;
+}
+
+/*
+ * If 'p' points to a valid identifier start, go to the end of the identifier.
+ * Else return 'p'.
+ */
+static const char *getid(const char *p)
+{
+ if (isidc0(*p)) {
+ while (isidc(*p))
+ p++;
+ }
+ return p;
+}
+
+/* Issues error in a macro definition. */
+static void macdeferr(int cmdline, const char *estr, const char *ep)
+{
+ if (cmdline) {
+ eprint(_("error in command line macro definition\n"));
+ }
+ eprint(estr);
+ eprcol(s_line, ep);
+ if (cmdline) {
+ exit(EXIT_FAILURE);
+ } else {
+ newerr();
+ }
+}
+
+/* Parse macro definition. */
+static void pmacdef(const char *p, int cmdline)
+{
+ const char *q, *ap, *aq, *idp, *idq;
+
+ idp = p;
+ idq = getid(idp);
+ if (idq == idp) {
+ macdeferr(cmdline, _("identifier excepted\n"), p);
+ return;
+ }
+ p = idq;
+ ap = aq = p;
+ if (*p == '(') {
+ p++;
+ ap = p;
+ while (isidc0(*p)) {
+ p = getid(p);
+ if (*p != ',')
+ break;
+ p++;
+ }
+ if (*p != ')') {
+ macdeferr(cmdline, _("')' expected\n"), p);
+ return;
+ }
+ aq = p;
+ p++;
+ }
+ if (*p != '\0' && !isspace(*p)) {
+ macdeferr(cmdline, _("space expected\n"), p);
+ return;
+ }
+ p = skipws(p);
+ /* go to the end */
+ for (q = p; *q != '\0'; q++)
+ ;
+ /* go to the first non white from the end */
+ while (q > p && isspace(*(q - 1)))
+ q--;
+ define(idp, idq, p, q, ap, aq);
+}
+
+/* Parse #define. */
+static void pdefine(const char *p)
+{
+ p = skipws(p + sizeof(DEFINESTR) - 1);
+ pmacdef(p, 0);
+}
+
+/* Parse #defcont. */
+static void pdefcont(const char *p)
+{
+ const char *q;
+
+ p = skipws(p + sizeof(DEFCONTSTR) - 1);
+
+ /* go to the end */
+ for (q = p; *q != '\0'; q++)
+ ;
+
+ /* go to the first non white from the end */
+ while (q > p && isspace(*(q - 1)))
+ q--;
+
+ if (p == q) {
+ /* nothing to add */
+ return;
+ }
+
+ if (s_lastmac == NULL) {
+ eprint(_("#DEFCONT without a previous #DEFINE\n"));
+ eprcol(s_line, s_line_ep);
+ newerr();
+ return;
+ }
+
+ defcont(p, q);
+}
+
+/* Parse #include. */
+static void pinclude(const char *p)
+{
+ const char *q;
+
+ p = skipws(p + sizeof(INCLUDESTR) - 1);
+ if (*p != '\"') {
+ eprint(_("#INCLUDE expects a filename between quotes\n"));
+ eprcol(s_line, p);
+ newerr();
+ return;
+ }
+ q = ++p;
+ while (*q != '\0' && *q != '\"')
+ q++;
+ if (*q != '\"') {
+ wprint(_("no terminating quote\n"));
+ eprcol(s_line, q);
+ }
+ pushfile(p, q);
+}
+
+/*
+ * Parse #ifdef or #ifndef.
+ * 'idsz' is the length of the string 'ifdef' or 'ifndef', plus '\0'.
+ * 'ifdef' must be 1 if we are #ifdef, 0 if #ifndef.
+ */
+static void pifdef(const char *p, size_t idsz, int ifdef)
+{
+ const char *q;
+ struct macro *nod;
+
+ s_nifs++;
+ if (s_skipon)
+ return;
+
+ p = skipws(p + idsz - 1);
+ if (!isidc0(*p)) {
+ s_skipon = s_nifs;
+ eprint(_("identifier expected\n"));
+ eprcol(s_line, p);
+ newerr();
+ return;
+ }
+ q = p;
+ while (isidc(*q))
+ q++;
+ nod = pplookup(p, q);
+ if (ifdef == (nod != NULL))
+ s_skipon = 0;
+ else
+ s_skipon = s_nifs;
+}
+
+/* Parse #else. */
+static void pelse(const char *p)
+{
+ if (s_nifs == 0) {
+ eprint(_("unbalanced #ELSE\n"));
+ eprcol(s_line, s_line_ep);
+ newerr();
+ return;
+ }
+
+ if (s_skipon && s_nifs == s_skipon)
+ s_skipon = 0;
+ else if (!s_skipon)
+ s_skipon = s_nifs;
+}
+
+/* Parse #endif. */
+static void pendif(const char *p)
+{
+ if (s_nifs == 0) {
+ eprint(_("unbalanced #ENDIF\n"));
+ eprcol(s_line, s_line_ep);
+ newerr();
+ return;
+ }
+
+ if (s_skipon && s_nifs == s_skipon)
+ s_skipon = 0;
+ s_nifs--;
+}
+
+/*
+ * Parse #if.
+ */
+static void pif(const char *p)
+{
+ int v;
+ enum expr_ecode ex_ec;
+ const char *ep;
+
+ s_nifs++;
+ if (s_skipon)
+ return;
+
+ p = skipws(p + sizeof(IFSTR) - 1);
+ if (!expr(p, &v, s_pc, 0, &ex_ec, &ep)) {
+ s_skipon = 1;
+ exprint(ex_ec, s_line, ep);
+ newerr();
+ return;
+ }
+
+ if (v == 0)
+ s_skipon = s_nifs;
+ else
+ s_skipon = 0;
+}
+
+/*
+ * Parse a preprocessor line.
+ * 'p' points to the next character after the '#'.
+ */
+static int
+parse_line(const char *p)
+{
+ if (isppid(p, IFDEFSTR)) {
+ pifdef(p, sizeof IFDEFSTR, 1);
+ } else if (isppid(p, IFNDEFSTR)) {
+ pifdef(p, sizeof IFNDEFSTR, 0);
+ } else if (isppid(p, IFSTR)) {
+ pif(p);
+ } else if (isppid(p, ELSESTR)) {
+ pelse(p);
+ } else if (isppid(p, ENDIFSTR)) {
+ pendif(p);
+ } else if (s_skipon) {
+ ;
+ } else if (isppid(p, INCLUDESTR)) {
+ pinclude(p);
+ } else if (isppid(p, DEFINESTR)) {
+ pdefine(p);
+ } else if (isppid(p, DEFCONTSTR)) {
+ pdefcont(p);
+ } else {
+ return 0;
+/*
+ eprint(_("unknown preprocessor directive\n"));
+ eprcol(s_line, s_line_ep);
+ newerr();
+*/
+ }
+ return 1;
+}
+
+/*
+ * Preprocess 'line' in 's_pline'.
+ * In this module, while we are preprocessing:
+ * s_line is the original line.
+ * s_line_ep is a pointer inside line that we keep for error reporting.
+ */
+void pp_line(const char *line)
+{
+ const char *p;
+
+ s_line = line;
+ s_line_ep = line;
+
+ p = skipws(line);
+ if ((*p == '#') || (*p == '.')) {
+ s_line_ep = p;
+ if (parse_line(p + 1)) {
+ s_ppbuf[0][0] = '\0';
+ s_pline = &s_ppbuf[0][0];
+ return;
+ }
+ }
+ if (s_skipon) {
+ s_ppbuf[0][0] = '\0';
+ s_pline = &s_ppbuf[0][0];
+ return;
+ }
+ s_pline = expand_line0(line);
+}
+
+/* Reset the module for other passes. */
+void pp_reset(void)
+{
+ int i;
+ struct macro *nod, *cur;
+
+ s_nmacs = 0;
+ s_nifs = 0;
+ s_skipon = 0;
+ s_lastmac = NULL;
+ for (i = 0; i < MACTABSZ; i++) {
+ nod = s_mactab[i];
+ while (nod != NULL) {
+ cur = nod;
+ nod = nod->next;
+ free(cur->text);
+ free(cur);
+ }
+ }
+ memset(s_mactab, 0, MACTABSZ * sizeof(s_mactab[0]));
+}
+
+void pp_define(const char *mactext)
+{
+ s_line = mactext;
+ s_line_ep = mactext;
+ pmacdef(mactext, 1);
+ s_lastmac = NULL;
+}
diff --git a/Tools/unix/uz80as/pp.h b/Tools/unix/uz80as/pp.h
new file mode 100644
index 00000000..74a6d6f2
--- /dev/null
+++ b/Tools/unix/uz80as/pp.h
@@ -0,0 +1,23 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Preprocessor.
+ * ===========================================================================
+ */
+
+#ifndef PP_H
+#define PP_H
+
+/* Max line length after macro expansion + '\0'. */
+#define LINESZ 512
+
+extern char *s_pline;
+extern int s_pc;
+extern int s_pass;
+extern int s_skipon;
+
+void pp_line(const char *line);
+void pp_reset(void);
+void pp_define(const char *name);
+
+#endif
diff --git a/Tools/unix/uz80as/prtable.c b/Tools/unix/uz80as/prtable.c
new file mode 100644
index 00000000..1c3be97f
--- /dev/null
+++ b/Tools/unix/uz80as/prtable.c
@@ -0,0 +1,343 @@
+#include "prtable.h"
+#include "err.h"
+#include "targets.h"
+#include "uz80as.h"
+
+#ifndef STDLIB_H
+#include
+#endif
+
+#ifndef CTYPE_H
+#include
+#endif
+
+#ifndef STRING_H
+#include
+#endif
+
+enum { STRSZ = 32 };
+
+struct itext {
+ struct itext *next;
+ int undoc;
+ char str[STRSZ];
+};
+
+struct ilist {
+ struct itext *head;
+ int nelems;
+};
+
+struct itable {
+ struct itext **table;
+ int nelems;
+};
+
+static char s_buf[STRSZ];
+
+static void nomem(const char *str)
+{
+ eprogname();
+ fprintf(stderr, _("not enough memory (%s)\n"), str);
+ exit(EXIT_FAILURE);
+}
+
+static int compare(const void *pa, const void *pb)
+{
+ const struct itext * const *ia;
+ const struct itext * const *ib;
+
+ ia = (const struct itext * const *) pa;
+ ib = (const struct itext * const *) pb;
+ return strcmp((*ia)->str, (*ib)->str);
+}
+
+/*
+ * Returns a new allocated itable of pointers that point to each element in the
+ * list ilist, alphabetically sorted.
+ */
+static struct itable *sort_list(struct ilist *ilist)
+{
+ int n;
+ struct itable *itable;
+ struct itext *p;
+
+ if ((itable = calloc(1, sizeof(*itable))) == NULL) {
+ return NULL;
+ }
+ itable->nelems = 0;
+
+ if (ilist->nelems == 0) {
+ return itable;
+ }
+
+ itable->table = malloc(ilist->nelems * sizeof(*itable->table));
+ if (itable->table == NULL) {
+ free(itable);
+ return NULL;
+ }
+
+ for (n = 0, p = ilist->head;
+ p != NULL && n < ilist->nelems;
+ p = p->next, n++)
+ {
+ itable->table[n] = p;
+ }
+ itable->nelems = n;
+
+ qsort(itable->table, itable->nelems, sizeof(*itable->table), compare);
+ return itable;
+}
+
+static void print_itable(struct itable *itable, FILE *f)
+{
+ int i, col;
+ struct itext *p;
+
+ if (itable == NULL) {
+ return;
+ }
+
+ fputs("@multitable @columnfractions .25 .25 .25 .25\n", f);
+ col = 0;
+ for (i = 0; i < itable->nelems; i++) {
+ p = itable->table[i];
+ if (col == 0) {
+ fputs("@item ", f);
+ } else {
+ fputs("@tab ", f);
+ }
+ if (p->undoc) {
+ fputs("* ", f);
+ }
+ fprintf(f, "%s\n", p->str);
+ col++;
+ if (col >= 4) {
+ col = 0;
+ }
+ }
+ fputs("@end multitable\n", f);
+}
+
+#if 0
+static void print_ilist(struct ilist *ilist, FILE *f)
+{
+ int col;
+ struct itext *p;
+
+ if (ilist == NULL) {
+ return;
+ }
+
+ fputs("@multitable @columnfractions .25 .25 .25 .25\n", f);
+ col = 0;
+ for (p = ilist->head; p != NULL; p = p->next) {
+ if (col == 0) {
+ fputs("@item ", f);
+ } else {
+ fputs("@tab ", f);
+ }
+ if (p->undoc) {
+ fputs("* ", f);
+ }
+ fprintf(f, "%s\n", p->str);
+ col++;
+ if (col >= 4) {
+ col = 0;
+ }
+ }
+ fputs("@end multitable\n", f);
+}
+#endif
+
+static void bufset(int i, char c)
+{
+ if (i >= STRSZ) {
+ eprogname();
+ fputs(_("prtable: please, increase s_buf size\n"), stderr);
+ exit(EXIT_FAILURE);
+ } else {
+ s_buf[i] = c;
+ }
+}
+
+static void gen_inst2(struct ilist *ilist, char *instr, int undoc)
+{
+ struct itext *p;
+
+ if ((p = malloc(sizeof(*p))) == NULL) {
+ nomem("gen_inst2");
+ }
+
+ snprintf(p->str, STRSZ, "%s", instr);
+ p->undoc = undoc;
+ p->next = ilist->head;
+ ilist->head = p;
+ ilist->nelems++;
+}
+
+static void gen_inst(struct ilist *ilist, const struct target *t,
+ unsigned char undoc, const char *p, size_t bufi,
+ const char *pr)
+{
+ size_t bufk;
+ const char *s;
+
+ while (*p) {
+ if (!islower(*p)) {
+ if (*p == '@') {
+ bufset(bufi++, '@');
+ }
+ bufset(bufi++, *p);
+ p++;
+ } else if (*p == 'a') {
+ if (pr == NULL) {
+ bufset(bufi++, 'e');
+ } else if (pr[0] && pr[1]) {
+ if (pr[0] == pr[1]) {
+ bufset(bufi++, pr[0]);
+ pr += 2;
+ } else if (isdigit(pr[1])) {
+ bufset(bufi++, *pr);
+ pr++;
+ while (isdigit(*pr)) {
+ bufset(bufi++, *pr);
+ pr++;
+ }
+ } else {
+ bufset(bufi++, pr[0]);
+ bufset(bufi++, pr[1]);
+ pr += 2;
+ }
+ } else {
+ bufset(bufi++, 'e');
+ }
+ p++;
+ } else {
+ break;
+ }
+ }
+
+ if (*p == '\0') {
+ bufset(bufi, '\0');
+ gen_inst2(ilist, s_buf, t->mask & undoc);
+ } else {
+ t->pat_char_rewind(*p);
+ while ((s = t->pat_next_str()) != NULL) {
+ if (s[0] != '\0') {
+ bufset(bufi, '\0');
+ bufk = bufi;
+ while (*s != '\0') {
+ bufset(bufk++, *s);
+ s++;
+ }
+ bufset(bufk, '\0');
+ gen_inst(ilist, t, undoc, p + 1, bufk, pr);
+ }
+ }
+ }
+}
+
+/* Generate a list of instructions. */
+static struct ilist *gen_list(const struct target *t, unsigned char mask2,
+ int delta)
+{
+ int i, pr;
+ const struct matchtab *mt;
+ struct ilist *ilist;
+
+ if ((ilist = calloc(1, sizeof(*ilist))) == NULL) {
+ return NULL;
+ }
+
+ i = 0;
+ mt = t->matcht;
+ while (mt[i].pat != NULL) {
+ pr = 0;
+ if (t->mask == 1 && (mt[i].mask & 1)) {
+ pr = 1;
+ } else if (delta) {
+ if ((mt[i].mask & t->mask) &&
+ !(mt[i].mask & mask2))
+ {
+ pr = 1;
+ }
+ } else if (t->mask & mt[i].mask) {
+ pr = 1;
+ }
+ if (pr) {
+ gen_inst(ilist, t, mt[i].undoc, mt[i].pat,
+ 0, mt[i].pr);
+ }
+ i++;
+ }
+
+ return ilist;
+}
+
+/*
+ * Prints the instruction set of a target or if target_id is "target2,target1"
+ * prints the instructions in target2 not in target1.
+ */
+void print_table(FILE *f, const char *target_id)
+{
+ struct ilist *ilist;
+ struct itable *itable;
+ const struct target *t, *t2;
+ char target1[STRSZ];
+ const char *target2;
+ unsigned char mask2;
+ int delta;
+
+ /* check if we have "target" or "target,target" as arguments */
+ if ((target2 = strchr(target_id, ',')) != NULL) {
+ delta = 1;
+ snprintf(target1, sizeof(target1), "%s", target_id);
+ target1[target2 - target_id] = '\0';
+ target2++;
+ } else {
+ delta = 0;
+ snprintf(target1, sizeof(target1), "%s", target_id);
+ target2 = NULL;
+ }
+
+ t = find_target(target1);
+ if (t == NULL) {
+ eprogname();
+ fprintf(stderr, _("invalid target '%s'\n"), target1);
+ exit(EXIT_FAILURE);
+ }
+
+ if (target2) {
+ t2 = find_target(target2);
+ if (t2 == NULL) {
+ eprogname();
+ fprintf(stderr, _("invalid target '%s'\n"), target2);
+ exit(EXIT_FAILURE);
+ }
+ if (t->matcht != t2->matcht) {
+ eprogname();
+ fprintf(stderr, _("unrelated targets %s,%s\n"),
+ target1, target2);
+ exit(EXIT_FAILURE);
+ }
+ mask2 = t2->mask;
+ } else {
+ mask2 = 1;
+ }
+
+ if ((ilist = gen_list(t, mask2, delta)) == NULL) {
+ nomem("gen_list");
+ }
+
+ if ((itable = sort_list(ilist)) == NULL) {
+ nomem("sort_list");
+ }
+
+ print_itable(itable, f);
+
+ /* We don't free ilist nor itable for now, since this is called
+ * from main and then the program terminated.
+ */
+}
+
diff --git a/Tools/unix/uz80as/prtable.h b/Tools/unix/uz80as/prtable.h
new file mode 100644
index 00000000..41eb646a
--- /dev/null
+++ b/Tools/unix/uz80as/prtable.h
@@ -0,0 +1,11 @@
+#ifndef PRTABLE_H
+#define PRTABLE_H
+
+#ifndef STDIO_H
+#define STDIO_H
+#include
+#endif
+
+void print_table(FILE *f, const char *target_id);
+
+#endif
diff --git a/Tools/unix/uz80as/sym.c b/Tools/unix/uz80as/sym.c
new file mode 100644
index 00000000..53fc2625
--- /dev/null
+++ b/Tools/unix/uz80as/sym.c
@@ -0,0 +1,103 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Symbol table for labels.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "sym.h"
+#include "utils.h"
+#include "err.h"
+
+#ifndef STDIO_H
+#include
+#endif
+
+#ifndef STDLIB_H
+#include
+#endif
+
+/*
+ * Maximum number of symbols (labels) allowed.
+ * Must not be more than 64K.
+ */
+#define NSYMS 15000
+
+/* Closest prime to NSYMS / 4. */
+#define SYMTABSZ 3739
+
+/*
+ * Nodes for the s_symtab hash table.
+ * The symbol at index 0 is never used.
+ */
+static struct sym s_symlist[NSYMS];
+
+/*
+ * Hash table of indexes into s_symlist.
+ * 0 means that the bucket is empty.
+ */
+static unsigned short s_symtab[SYMTABSZ];
+
+/* Next free symbol in s_symlist. Note: 0 not used. */
+static int s_nsyms = 1;
+
+/*
+ * Lookups the string in [p, q[ in s_symtab.
+ * If !insert, returns the symbol or NULL if it is not in the table.
+ * If insert, inserts the symbol in the table if it is not there, and
+ * sets its .val to 'pc'.
+ */
+struct sym *lookup(const char *p, const char *q, int insert, int pc)
+{
+ int h, k;
+ struct sym *nod;
+
+ if (q - p > SYMLEN - 1) {
+ /* Label too long, don't add. */
+ eprint(_("label too long"));
+ epchars(p, q);
+ enl();
+ newerr();
+ /*
+ * This would truncate:
+ * q = p + (SYMLEN - 1);
+ */
+ return NULL;
+ }
+
+ h = hash(p, q, SYMTABSZ);
+ for (k = s_symtab[h]; k != 0; k = s_symlist[k].next) {
+ if (scmp(p, q, s_symlist[k].name) == 0) {
+ if (insert) {
+ if (!s_symlist[k].isequ) {
+ wprint("duplicate label (%s)\n",
+ s_symlist[k].name);
+ }
+ }
+ return &s_symlist[k];
+ }
+ }
+
+ if (insert) {
+ if (s_nsyms == NSYMS) {
+ eprint(_("maximum number of labels exceeded (%d)\n"),
+ NSYMS);
+ exit(EXIT_FAILURE);
+ }
+
+ nod = &s_symlist[s_nsyms];
+ nod->next = s_symtab[h];
+ s_symtab[h] = (unsigned short) s_nsyms;
+ s_nsyms++;
+
+ k = 0;
+ while (p != q && k < SYMLEN - 1)
+ nod->name[k++] = *p++;
+ nod->name[k] = '\0';
+ nod->val = pc;
+ return nod;
+ }
+
+ return NULL;
+}
diff --git a/Tools/unix/uz80as/sym.h b/Tools/unix/uz80as/sym.h
new file mode 100644
index 00000000..43108c68
--- /dev/null
+++ b/Tools/unix/uz80as/sym.h
@@ -0,0 +1,23 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Symbol table for labels.
+ * ===========================================================================
+ */
+
+#ifndef SYM_H
+#define SYM_H
+
+/* Max symbol length + '\0'. */
+#define SYMLEN 32
+
+struct sym {
+ char name[SYMLEN]; /* null terminated string */
+ int val; /* value of symbol */
+ unsigned short next; /* index into symlist; 0 is no next */
+ unsigned char isequ; /* if val comes from EQU */
+};
+
+struct sym *lookup(const char *p, const char *q, int insert, int pc);
+
+#endif
diff --git a/Tools/unix/uz80as/targets.c b/Tools/unix/uz80as/targets.c
new file mode 100644
index 00000000..742ef674
--- /dev/null
+++ b/Tools/unix/uz80as/targets.c
@@ -0,0 +1,96 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Target list.
+ * ===========================================================================
+ */
+
+#include "targets.h"
+#include "uz80as.h"
+
+#ifndef STRING_H
+#include
+#endif
+
+extern const struct target s_target_z80;
+extern const struct target s_target_hd64180;
+extern const struct target s_target_gbcpu;
+extern const struct target s_target_dp2200;
+extern const struct target s_target_dp2200ii;
+extern const struct target s_target_i4004;
+extern const struct target s_target_i4040;
+extern const struct target s_target_i8008;
+extern const struct target s_target_i8021;
+extern const struct target s_target_i8022;
+extern const struct target s_target_i8041;
+extern const struct target s_target_i8048;
+extern const struct target s_target_i8051;
+extern const struct target s_target_i8080;
+extern const struct target s_target_i8085;
+extern const struct target s_target_mos6502;
+extern const struct target s_target_r6501;
+extern const struct target s_target_g65sc02;
+extern const struct target s_target_r65c02;
+extern const struct target s_target_r65c29;
+extern const struct target s_target_w65c02s;
+extern const struct target s_target_mc6800;
+extern const struct target s_target_mc6801;
+extern const struct target s_target_m68hc11;
+
+static const struct target *s_targets[] = {
+ &s_target_z80,
+ &s_target_hd64180,
+ &s_target_gbcpu,
+ &s_target_dp2200,
+ &s_target_dp2200ii,
+ &s_target_i4004,
+ &s_target_i4040,
+ &s_target_i8008,
+ &s_target_i8021,
+ &s_target_i8022,
+ &s_target_i8041,
+ &s_target_i8048,
+ &s_target_i8051,
+ &s_target_i8080,
+ &s_target_i8085,
+ &s_target_mos6502,
+ &s_target_r6501,
+ &s_target_g65sc02,
+ &s_target_r65c02,
+ &s_target_r65c29,
+ &s_target_w65c02s,
+ &s_target_mc6800,
+ &s_target_mc6801,
+ &s_target_m68hc11,
+ NULL,
+};
+
+static int s_index;
+
+const struct target *find_target(const char *id)
+{
+ const struct target **p;
+
+ for (p = s_targets; *p != NULL; p++) {
+ if (strcmp(id, (*p)->id) == 0) {
+ return *p;
+ }
+ }
+
+ return NULL;
+}
+
+const struct target *first_target(void)
+{
+ s_index = 0;
+ return next_target();
+}
+
+const struct target *next_target(void)
+{
+ if (s_targets[s_index] != NULL) {
+ return s_targets[s_index++];
+ } else {
+ return NULL;
+ }
+}
diff --git a/Tools/unix/uz80as/targets.h b/Tools/unix/uz80as/targets.h
new file mode 100644
index 00000000..12b6f64c
--- /dev/null
+++ b/Tools/unix/uz80as/targets.h
@@ -0,0 +1,18 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Target list.
+ * ===========================================================================
+ */
+
+#ifndef TARGETS_H
+#define TARGETS_H
+
+struct target;
+
+const struct target *find_target(const char *id);
+
+const struct target *first_target(void);
+const struct target *next_target(void);
+
+#endif
diff --git a/Tools/unix/uz80as/test.asm b/Tools/unix/uz80as/test.asm
new file mode 100644
index 00000000..cfc0d169
--- /dev/null
+++ b/Tools/unix/uz80as/test.asm
@@ -0,0 +1,22 @@
+#DEFINE MENU_L(M1,M2,M3,M4,M5,M6,M7,M8,M9,M10) \
+#DEFCONT \ .DB M1
+#DEFCONT \ .DB M2
+#DEFCONT \ .DB M3
+#DEFCONT \ .DW M4
+#DEFCONT \ .DW M5
+#DEFCONT \ .DW M6
+#DEFCONT \ .DW M7
+#DEFCONT \ .DB M8
+#DEFCONT \ .DB M9
+#DEFCONT \ .DB M10
+
+KY_CL .equ 1
+MON_LOC .equ 1
+MON_SIZ .equ 1
+BID_USR .equ 1
+MON_SERIAL .equ 1
+BID_CUR .equ 1
+
+MENU_S: MENU_L("~Monitor$ ", "M", KY_CL, MON_SERIAL, 1000h, MON_LOC, MON_SIZ, BID_CUR, BID_USR, "Monitor$ ")
+
+ .end
diff --git a/Tools/unix/uz80as/test.lst b/Tools/unix/uz80as/test.lst
new file mode 100644
index 00000000..94a84b15
--- /dev/null
+++ b/Tools/unix/uz80as/test.lst
@@ -0,0 +1,38 @@
+0001 0000 #DEFINE MENU_L(M1,M2,M3,M4,M5,M6,M7,M8,M9,M10) \
+0002 0000 #DEFCONT \ .DB M1
+0003 0000 #DEFCONT \ .DB M2
+0004 0000 #DEFCONT \ .DB M3
+0005 0000 #DEFCONT \ .DW M4
+0006 0000 #DEFCONT \ .DW M5
+0007 0000 #DEFCONT \ .DW M6
+0008 0000 #DEFCONT \ .DW M7
+0009 0000 #DEFCONT \ .DB M8
+0010 0000 #DEFCONT \ .DB M9
+0011 0000 #DEFCONT \ .DB M10
+0012 0000
+0013 0000 KY_CL .equ 1
+0014 0000 MON_LOC .equ 1
+0015 0000 MON_SIZ .equ 1
+0016 0000 BID_USR .equ 1
+0017 0000 MON_SERIAL .equ 1
+0018 0000 BID_CUR .equ 1
+0019 0000
+0020 0000 MENU_S: MENU_L("~Monitor$ ", "M", KY_CL, MON_SERIAL, 1000h, MON_LOC, MON_SIZ, BID_CUR, BID_USR, "Monitor$ ")
+0020 0000
+0020 0000 7E 4D 6F 6E
+0020 0004 69 74 6F 72
+0020 0008 24 20
+0020 000A 4D
+0020 000B 01
+0020 000C 01 00
+0020 000E 00 10
+0020 0010 01 00
+0020 0012 01 00
+0020 0014 01
+0020 0015 01
+0020 0016 4D 6F 6E 69
+0020 001A 74 6F 72 24
+0020 001E 20 20 20 20
+0020 0022 20
+0021 0023
+0022 0023 .end
diff --git a/Tools/unix/uz80as/test.obj b/Tools/unix/uz80as/test.obj
new file mode 100644
index 00000000..3660484d
Binary files /dev/null and b/Tools/unix/uz80as/test.obj differ
diff --git a/Tools/unix/uz80as/utils.c b/Tools/unix/uz80as/utils.c
new file mode 100644
index 00000000..a361f2fc
--- /dev/null
+++ b/Tools/unix/uz80as/utils.c
@@ -0,0 +1,137 @@
+/* ===========================================================================
+ * uz80as, an assembler for the Zilog Z80 and several other microprocessors.
+ *
+ * Generic functions.
+ * ===========================================================================
+ */
+
+#include "config.h"
+#include "utils.h"
+
+#ifndef CTYPE_H
+#include
+#endif
+
+#ifndef LIMITS_H
+#include