*
* HexD. A program that quickly dumps any file(s) in hex format on standard
* out or a file.
*
* Version history:
*
* Magnus Holmgren:
* V1.0  24-11-91 : First release (portable C-code).
*
* V2.0  21-02-93 : Not portable any more, requires AmigaDOS 2.0.
*                : Downcoded to assembler. Many enhanchements.
*
* Warning: This source is a bit messy! Major cleanup could be done. But
* that would make the program larger! :) :)
*

	OPT	O+,D-

	INCDIR	INC:
	INCLUDE	exec/types.i
	INCLUDE	exec/funcdef.i
	INCLUDE	exec/alerts.i
	INCLUDE	exec/execbase.i
	INCLUDE	exec/exec_lib.i
	INCLUDE	exec/memory.i
	INCLUDE	dos/dosasl.i
	INCLUDE	dos/dosextens.i
	INCLUDE	dos/dos_lib.i
	INCLUDE	graphics/text.i
	INCLUDE	intuition/intuition.i


*
* Flags structure
*

	STRUCTURE Flags,0
	APTR	f_Files			; The files to show
	APTR	f_To			; Output file
	APTR	f_Width			; Width of output
	LONG	f_All			; Enter subdirectories?
	LONG	f_Append		; Append to TO file?
	LONG	f_Hex			; Hex only output?
	LONG	f_Ascii			; Ascii only output?
	LONG	f_NoOffset		; Show offset?
	LONG	f_Header		; Show a header?
	LONG	f_Cursor		; Hide cursor?
	LONG	f_Page			; Show page prompt?
	LABEL	f_SIZEOF		; Size of flags struct

* To make it easier to keep both up-to-date

TEMPLATE	MACRO
		dc.b	"FILES/M,TO/K,WIDTH/K/N,ALL/S,APPEND/S,HEX/S,ASCII/S,"
		dc.b	"NOOFFSET/S,HEADER/S,CURSOR/S,PAGE/S",0
		ENDM

* Maximum length allowed for one filename

FILENAMESIZE	EQU	512

* Maximum width to display on screen. Minimal width is "floating"...

MAXWIDTH	EQU	512

* Default width (if out file is interactive, get default from there instead)

DEFWIDTH	EQU	75

* Starting value for the dt_YPos variable

INITYPOS	EQU	3

*
* Data structure. All data used by us. Gets allocated, for purity.
*

	STRUCTURE DataTable,0
	APTR	dt_DOSBase
	APTR	dt_SysBase
	APTR	dt_InitialSP		; Stack pointer at start
	APTR	dt_InFile		; File we currently are reading
	APTR	dt_OutFile		; File we are writing data to
	APTR	dt_RdArgs		; RDArgs structure
	STRUCT	dt_Flags,f_SIZEOF	; Flags array
	ALIGNLONG			; Better long-align AnchorPath (probably not needed here)
	STRUCT	dt_AP,ap_SIZEOF		; AnchorPath, for pattern matching
	STRUCT	dt_APNameBuffer,FILENAMESIZE	; Filename buffer (part of AnchorPath)
	ALIGNLONG			; Long align again, for speed
	STRUCT	dt_ReadBuffer,MAXWIDTH+2	; Buffer for reading data
	ALIGNLONG			; Long align again, for speed
	STRUCT	dt_WriteBuffer,MAXWIDTH+2	; Buffer while building output string
	ALIGNWORD			; Better word-align
	LONG	dt_BytePos		; Byte position in file
	UWORD	dt_Width		; Number of chars/row
	UWORD	dt_Height		; Number of rows (for pager if > 0)
	UWORD	dt_YPos			; Current output line (for pager)
	UWORD	dt_MinWidth		; Minimal width allowed
	UWORD	dt_EndOfHex		; Position on line at which hex part ends
	UWORD	dt_Response		; Buffer for pager
	UBYTE	dt_DisplayFormat	; How to show the data
	UBYTE	dt_OutOpened		; We have opened Out (cleanup)
	UBYTE	dt_InOpened		; We have opened In (cleanup)
	UBYTE	dt_Matching		; An MatchFirst() have been used without,
					; a corresponding MatchEnd() (cleanup).
	LABEL	dt_SIZEOF		; Size of data table


*
* Different values in dt_DisplayFormat byte:
*

BOTH	EQU	0			; Both Ascii and Hex
ASCII	EQU	1			; Only Ascii
HEX	EQU	2			; Only Hex

*
* Macro to generate the "minimal width array". Take the dt_DisplayFormat
* byte, add NOOFFSET (currently 4) if f_NoOffset is TRUE, and use that value
* (shifted properly) to index in this word-array, to get the *REAL* minimal
* width (in chars) allowed.
*

NOOFFSET	EQU	4

MINWIDTHARRAY	MACRO
	dc.w	14,11,10,0,6,3,2,0
	ENDM

* A few handy macros

* Call a library routine

SYS	MACRO
	jsr	_LVO\1(a6)
	ENDM

* Call a subroutine

CALL	MACRO
	bsr	\1
	ENDM

* Quickly clear a data register

CLD	MACRO
	moveq	#0,d\1
	ENDM

* Quickly clear an address register

CLA	MACRO
	sub.l	a\1,a\1
	ENDM

*
* Push register list to stack. Remembers the last list used. If 'NOREM'
* is the second argument, the pushed list will not be remembered.
*

PUSHM	MACRO
	movem.l	\1,-(sp)
	IFNC	'\2','NOREM'
PUSHM_COUNT		SET	PUSHM_COUNT+1
PUSHM_\<PUSHM_COUNT>	REG     \1
	ENDC
	ENDM

*
* Restore register list from stack. If no argument, restore last register
* list remembered.
*

POPM	MACRO
	IFC	'\1',''
	movem.l (sp)+,PUSHM_\<PUSHM_COUNT>
	ELSEIF
	movem.l	(sp)+,\1
	ENDC
	ENDM

*
* Works like PUSHM, but works on single registers only (needed since GenIm2
* won't optimize "single-register movem" :( ).
*

PUSH	MACRO
	move.l	\1,-(sp)
	IFNC	'\2','NOREM'
PUSH_COUNT		SET	PUSH_COUNT+1
PUSH_\<PUSH_COUNT>	EQUR	\1
	ENDC
	ENDM

*
* Like POPM, but a signle register.
*

POP	MACRO
	IFC	'\1',''
	move.l (sp)+,PUSH_\<PUSH_COUNT>
	ELSEIF
	move.l	(sp)+,\1
	ENDC
	ENDM

* Init some counters for the PUSH(M)/POP(M) macros

PUSHM_COUNT	SET	0
PUSH_COUNT	SET	0


	SECTION	Code,CODE

*
* Init data area, open needed libraries.
*

Init	move.l	4.w,a6			; First get SysBase
	CLA	1
	SYS	FindTask
	move.l	d0,a5			; Our process struct
	lea	DOSName_Msg(pc),a1	; dos.library
	CLD	0			; Any version
	SYS	OpenLibrary		; Try to open it
	move.l	d0,a3			; Save DOSBase
	tst.l	d0			; Did we get it?
	bne.b	.dos_ok			; Yep

	move.l	#AT_Recovery|AG_OpenLib|AO_DOSLib,d7	; Alert! No DOS!
	SYS	Alert			; Show alert
	move.l	#RETURN_FAIL,d0		; Return failure
	rts				; And end

 .dos_ok:
	cmp.w	#36,LIB_VERSION(a6)	; Do we have OS 2.04+?
	bgt	.os_ok			; Yep

	move.l	a3,a6			; Get DOSBase
	SYS	Output			; Get output handle
	move.l	d0,d1
	beq.b	.no_out			; No output

	lea	NeedOS2_Msg(pc),a0	; We need OS 2.04+
	move.l	a0,d2
	move.l	#NeedOS2_SIZEOF,d3	; Size of message
	SYS	Write			; Inform user about problem
 .no_out:
	moveq	#ERROR_INVALID_RESIDENT_LIBRARY,d0
	move.l	d0,pr_Result2(a5)	; Set secondary result
	move.l	#RETURN_FAIL,d0		; Return failure
	rts				; And end

 .os_ok:
	move.l	#dt_SIZEOF,d0		; Size of data struct
	move.l	#MEMF_CLEAR,d1		; Clear the memory
	SYS	AllocVec		; Try to allocate DataTable
	tst.l	d0			; Got it?
	bne.b	.mem_ok			; Yep

	move.l	a3,a6			; DOSBase
	move.l	#ERROR_NO_FREE_STORE,d1	; Ran out of memory
	lea	PrgName_Msg(pc),a0
	move.l	a0,d2			; Header for error message
	SYS	PrintFault		; Show error message
	move.l	#ERROR_NO_FREE_STORE,d1	; Ran out of memory
	SYS	SetIoErr		; Set return code
	move.l	#RETURN_FAIL,d0		; Return failure
	rts				; End

 .mem_ok:
	move.l	d0,a4			; Get data structure
	move.l	a3,dt_DOSBase(a4)	; Init some data
	move.l	a6,dt_SysBase(a4)
	move.l	sp,dt_InitialSP(a4)
	CALL	Main			; Call main part
	move.l	#RETURN_OK,d0		; Ok return
	CLD	1			; Fall through to CleanExit


*
* CleanExit( __D0 ULONG code, __D1 ULONG res2 );
*
* Clean up everything and exit gracefully. IoErr() will be set to res2.
* code is returned to the user. This function never returns.
*

CleanExit

	move.l	dt_DOSBase(a4),a6	; Get DOSBase
	move.l	dt_InitialSP(a4),sp	; Restore original SP
	move.l	d0,d6			; Save error codes
	move.l	d1,d7
	lea	CursorOn_Msg(pc),a0
	move.l	a0,d1
	SYS	PutStr			; Restore cursor, if hidden
	move.l	dt_RdArgs(a4),d1	; Any RDArgs structure?
	beq.b	.no_rdargs		; No

	SYS	FreeArgs		; Free RDArgs structure
 .no_rdargs:
 	tst.b	dt_InOpened(a4)		; Have we opened the in file?
	beq.b	.no_in			; No

	move.l	dt_InFile(a4),d1	; Any input file left open?
	beq.b	.no_in			; No

	SYS	Close			; Close input file
 .no_in:
 	tst.b	dt_OutOpened(a4)	; Have we opened the out file?
	beq.b	.no_out			; No

	move.l	dt_OutFile(a4),d1	; Any output file left open?
	beq.b	.no_out			; No

	SYS	Close			; Close output file
 .no_out:
 	tst.b	dt_Matching(a4)		; Any unbalanced MatchFirst()?
	beq.b	.no_match		; Nope

	lea	dt_AP(a4),a0
	move.l	a0,d1			; AnchorPath
	SYS	MatchEnd		; Clean up matching stuff
 .no_match:
	move.l	a6,a3			; Save DOSBase
	move.l	dt_SysBase(a4),a6
	move.l	a4,a1			; Get DataTable
	SYS	FreeVec			; Free it
	exg	a3,a6			; Get DOSBase again
	move.l	d7,d1			; Get secondary result
	SYS	SetIoErr		; Set result2
	move.l	a6,a1			; DOSBase
	move.l	a3,a6			; SysBase
	SYS	CloseLibrary		; Close dos.library
	move.l	d6,d0			; Get return code
	rts				; And exit


*
* VOID PrintFault( __A0 header, __D0 ULONG result2 );
*
* Print reason for failure and exit.
*

PrintFault

	move.l	d0,d1
	move.l	d1,d3			; Save result2
	move.l	a0,d2			; Error header
	SYS	PrintFault		; Print message
	move.l	#RETURN_FAIL,d0		; Return code
	move.l	d3,d1			; And reason for it
	CALL	CleanExit		; Exit


*
* Main part. All "basic" data have been properly set up
*

Main	move.l	dt_DOSBase(a4),a6	; We use DOS a lot

	** Set some default values **

	move.w	#FILENAMESIZE,dt_AP+ap_Strlen(a4)
	move.l	#SIGBREAKF_CTRL_C,dt_AP+ap_BreakBits(a4)
	move.w	#DEFWIDTH,dt_Width(a4)
	move.w	#INITYPOS,dt_YPos(a4)

	** Parse arguments **

	lea	Template_Msg(pc),a0
	move.l	a0,d1			; Template
	lea	dt_Flags(a4),a0
	move.l	a0,d2			; Flags array
	CLD	3			; No RDArgs structure
	SYS	ReadArgs		; Parse arguments
	move.l	d0,dt_RdArgs(a4)	; Save RDArgs struct
	bne.b	.args_ok		; No errors

	SYS	IoErr			; Why failure?
	lea	PrgName_Msg(pc),a0
	CALL	PrintFault

 .args_ok:
	tst.l	dt_Flags+f_Hex(a4)	; Only hex part?
	beq.b	.no_hex			; No

	move.b	#HEX,dt_DisplayFormat(a4)	; Only show hex part
 .no_hex:
	tst.l	dt_Flags+f_Ascii(a4)	; Only Ascii part?
	beq.b	.no_ascii		; No

	move.b	#ASCII,d0		; Default is Ascii mode
	cmp.b	#HEX,dt_DisplayFormat(a4)	; Hex mode already requested?
	bne.b	.no_ascii_hex		; Nope

	move.b	#BOTH,d0		; Else use Both mode
 .no_ascii_hex:
	move.b	d0,dt_DisplayFormat(a4)	; Set display mode
 .no_ascii:
	move.l	dt_Flags+f_Width(a4),d0	; Any width pointer?
	beq.b	.no_width		; No

	move.l	d0,a0			; Get pointer
	move.l	(a0),d1			; And the value
	move.w	d1,dt_Width(a4)		; Save it
 .no_width:
	lea	MinWidthArray(pc),a0	; Get array for finding proper value
	CLD	0
	move.b	dt_DisplayFormat(a4),d0	; Get display format
	tst.l	dt_Flags+f_NoOffset(a4)	; Any offset?
	beq.b	.offset			; Yep

	add.w	#NOOFFSET,d0		; Add no offset value
 .offset:
	add.w	d0,d0			; Make value index words
	move.w	(a0,d0.w),d0		; Get minimal width
	move.w	d0,dt_MinWidth(a4)	; Save value
	move.w	dt_Width(a4),d1		; To small?
	cmp.w	d0,d1
	bge.b	.min_ok			; No

 .size_err:
	lea	WidthError_Msg(pc),a0	; Format string
	CALL	Printf1			; Show error message
	move.l	#RETURN_FAIL,d0		; Return failure
	CLD	1			; No suitable ERROR_* code
	CALL	CleanExit		; Exit

 .min_ok:
	cmp.w	#MAXWIDTH,d1		; To large?
	bgt.b	.size_err		; Yes

	** Open TO file, if any **

	move.l	dt_Flags+f_To(a4),d1	; Any TO file?
	beq	.no_to			; Nope

	move.l	#MODE_NEWFILE,d2	; Default is create the file
	tst.l	dt_Flags+f_Append(a4)	; Append to file?
	beq.b	.del_old		; No

	move.l	#MODE_READWRITE,d2	; Don't erase any old file

 .del_old:
	SYS	Open			; Try to open it
	move.l	d0,dt_OutFile(a4)	; Save pointer
	bne.b	.to_ok			; TO file opened ok

	SYS	IoErr			; Get error code
	move.l	d0,d3			; Save it
	move.l	dt_Flags+f_To(a4),d0	; Get TO file
	lea	NoToFile_Msg(pc),a0	; Format string
	CALL	Printf1			; Print error message
	move.l	d3,d0			; IoErr() error code
	CLA	0			; Header already printed
	CALL	PrintFault

 .to_ok:
	st	dt_OutOpened(a4)	; Flag that we have opened the out file
	tst.l	dt_Flags+f_Append(a4)	; Append to file?
	beq.b	.out_ok			; No

	move.l	#0,d1
	SYS	SetIoErr		; Clear error code
	move.l	dt_OutFile(a4),d1	; File
	move.l	#0,d2			; Offset
	move.l	#OFFSET_END,d3		; Seek to end of file
	SYS	Seek
	move.l	d0,d3			; Save current pos
	SYS	IoErr			; Get error code
	moveq	#-1,d1
	cmp.l	d1,d3			; Seek ok?
	beq.b	.seek_error		; No

	tst.l	d0			; Any error?
	beq.b	.out_ok			; No

 .seek_error:
	move.l	d0,d3
	move.l	dt_Flags+f_To(a4),d0	; Get TO file
	lea	NoAppend_Msg(pc),a0	; Format string
	CALL	Printf1			; Print error message
	move.l	d3,d0			; IoErr() error code
	CLA	0			; Header already printed
	CALL	PrintFault

 .no_to:
	SYS	Output			; Get standard output
	move.l	d0,dt_OutFile(a4)	; Save pointer
	bne.b	.out_ok

	move.l	#RETURN_FAIL,d0		; Return failure
	CLD	1			; Dunno why this should happen.. :)
	CALL	CleanExit		; Exit

 .out_ok:
	move.l	dt_OutFile(a4),d1	; Output file
	SYS	IsInteractive		; Interactive file?
	tst.l	d0
	bne.b	.match			; Yep

	clr.l	dt_Flags+f_Page(a4)	; Disable pager
	st	dt_Flags+f_Cursor(a4)	; Make it != NULL

	** Do all pattern matching **

 .match:
	move.l	dt_Flags+f_Files(a4),a3	; Get files array
	lea	dt_AP(a4),a2		; Get AnchorPath struct

	** Walk through all FILE arguments **

 .next_file:
	tst.l	(a3)			; End of filename array?
	beq	.done			; Yep

	** And pattern match this argument **

	move.l	(a3)+,d1		; Get name/pattern
	move.l	a2,d2			; AnchorPath
	SYS	MatchFirst		; Start matching this name
	st	dt_Matching(a4)		; We have called MatchFirst()
	tst.l	d0
	bne.b	.error			; Error!

 .next_match:
	tst.l	ap_Info+fib_DirEntryType(a2)	; Type of entry?
	beq.b	.matchnext		; Not file, nor dir (??)

	ble.b	.file			; A file

	bclr	#APB_DIDDIR,ap_Flags(a2)	; Just did a dir?
	bne.b	.matchnext		; Yep, continue processing

	tst.l	dt_Flags+f_All(a4)	; Enter directories?
	beq.b	.matchnext		; Nope

	bset	#APB_DODIR,ap_Flags(a2)	; Else enter dir
	bra.b	.matchnext		; And continue

 .file:	CALL	DumpFile		; Handle that file

 .matchnext:
	move.l	a2,d1			; AnchorPath
	SYS	MatchNext		; Do next file
	tst.l	d0			; Everything ok?
	beq.b	.next_match		; Yep

 .error:
	move.l	d0,d7			; Save error code
	move.l	a2,d1			; AnchorPath
	SYS	MatchEnd		; End matching
	sf	dt_Matching(a4)		; MatchFirst() "syncronised"
	cmp.l	#ERROR_NO_MORE_ENTRIES,d7	; Done with this round?
	beq	.next_file		; Yep, do next file

	cmp.l	#ERROR_BREAK,d7		; Break'ed?
	bne.b	.no_break		; No

	CALL	PrintBreak		; Print break message
	move.l	#RETURN_OK,d0		; Return Ok
	CLD	1
	CALL	CleanExit		; Exit

 .no_break:
	move.l	d7,d0			; Failure reason
	lea	PrgName_Msg(pc),a0
	CALL	PrintFault

 .done:	move.l	dt_Flags+f_Files(a4),a0	; Get filenames array
	tst.l	(a0)			; Any files specified at all?
	bne.b	.rts			; Yep

	SYS	Input			; Get standard input
	move.l	d0,dt_InFile(a4)	; Save handle
	beq.b	.rts			; No handle

	move.l	d0,d1
	SYS	Flush			; To be on the safe side (we are going to read)
	CALL	HexDump			; Dump the file
 .rts:	rts


*
* DumpFile();
*
* Simply dump the file in the AnchorPath to dt_OutFile, using suitable
* parameters (as found in the DataTable ). Prints a header if that was
* requested. May exit if fatal error, breaked or similar.
*

DumpFile

	PUSHM	d0-3/a0-2/a6
	move.l	dt_DOSBase(a4),a6	; We only use DOS calls here
	lea	dt_AP+ap_Buf(a4),a2	; Pathname buffer
	move.l	a2,d1			; File name
	move.l	#MODE_OLDFILE,d2	; Old file
	SYS	Open			; Try to open it
	move.l	d0,dt_InFile(a4)	; Save handle
	bne.b	.open_ok		; File opened ok

	SYS	IoErr			; Why didn't file open?
	move.l	d0,d3			; Save reason
	move.l	a2,d0			; Argument
	lea	NoInFile_Msg(pc),a0	; Format string
	CALL	Printf1			; Print error message
	move.l	d3,d0			; Why file didn't open
	CLA	0			; No header
	CALL	PrintFault		; Print "reason"

 .open_ok:
 	st	dt_InOpened(a4)		; We have opened the in file
	tst.l	dt_Flags+f_Header(a4)	; Show a header?
	beq.b	.write_ok		; No

	move.l	dt_OutFile(a4),d1	; Output file
	lea	Header_Msg(pc),a0
	move.l	a0,d2			; Format string
	PUSH	a2			; Argument
	move.l	sp,d3			; Pointer to argument array
	SYS	VFPrintf		; Print header
	add.l	#4,sp			; Restore stack
	tst.l	d0			; Write ok?
	bgt.b	.write_ok		; Yes

 .write_error:
	SYS	IoErr			; Get error message
	lea	WriteError_Msg(pc),a0	; Couldn't write
	CALL	PrintFault

 .write_ok:
	CALL	HexDump			; Do it
	move.l	dt_InFile(a4),d1	; Input file
	SYS	Close			; Close it (can hardly fail, since we read the file... ;)
	clr.l	dt_InFile(a4)		; Clear pointer
	sf	dt_InOpened(a4)		; No infile opened by us
	POPM
	rts


*
* VOID HexDump( VOID );
*
* Dump dt_InFile on dt_OutFile. Gets its paramteres from the DataTable.
* Checks height, width, paging (if so, when) etc.
*
* Register usage:
* D7 - Bytes per row
* D5 - Number of bytes in last read operation
* A3 - Pointer in work buffer
*

HexDump

	PUSHM	d0-7/a0-3/a6
	move.l	dt_DOSBase(a4),a6	; We only use DOS calls
	CALL	CalcBytesPerRow		; Get # of bytes that fits on one row
	move.l	d0,d7			; Save value
	move.l	d7,d5			; Need to init # of bytes read
	clr.l	dt_BytePos(a4)		; Start at byte 0 :)

	tst.l	dt_Flags+f_Cursor(a4)	; Hide cursor?
	bne.b	.next_row		; No

	move.l	dt_OutFile(a4),d1	; Output file
	lea	CursorOff_Msg(pc),a0
	move.l	a0,d2
	SYS	FPuts			; Hide cursor
	tst.l	d0
	beq.b	.next_row		; No write error

 .write_error:
	SYS	IoErr
	lea	WriteError_Msg(pc),a0
	CALL	PrintFault

 .next_row:
	CALL	CheckBreak		; Break pressed?
	tst.l	d0
	bne	.done			; Yep, CTRL-D pressed

	cmp.l	d7,d5			; Did last read fill whole buffer?
	blt	.done			; No, must be end of file

	** Get data for next output line **

	CLD	1
	SYS	SetIoErr		; Clear IoErr() code
	move.l	dt_InFile(a4),d1	; Input file
	lea	dt_ReadBuffer(a4),a0
	move.l	a0,d2			; Read buffer
	move.l	#1,d3			; Element size
	move.l	d7,d4			; Number of elements
	SYS	FRead			; Read data
	move.l	d0,d5			; Save amount read
	SYS	IoErr			; Get error code
	tst.l	d0			; Save it
	ble.b	.read_ok		; No error

 .read_error:
	lea	ReadError_Msg(pc),a0
	CALL	PrintFault

 .read_ok:
	tst.l	d5			; End of input?
	beq	.done			; Yep

	** Build output string **

	lea	dt_WriteBuffer(a4),a3	; Start of write buffer
	lea	HexTable(pc),a0		; Hex conversion table
	move.l	#$0f,d1			; Mask for hex conversion
	move.l	#4,d2			; Steps to shift for each char
	tst.l	dt_Flags+f_NoOffset(a4)	; Display offset?
	bne.b	.no_offset		; No

	move.l	dt_BytePos(a4),d0	; Get current byte position
	move.l	#5,d3			; Count
	add.l	#6,a3			; Start char + 1
 .make_offset:
 	move.l	d0,d4			; Get value
 	lsr.l	d2,d0			; Shift to next char
 	and.l	d1,d4			; Get lower bits
	move.b	0(a0,d4.w),-(a3)	; Get hex char
	dbra	d3,.make_offset		; Loop back

	add.l	#6,a3			; Go to end of converted text
	move.b	#':',(a3)+
	move.b	#' ',(a3)+
 .no_offset:
	cmp.b	#ASCII,dt_DisplayFormat(a4)	; Only Ascii?
	beq.b	.ascii			; Yes

	lea	dt_ReadBuffer(a4),a1	; Read buffer
;	lea	HexTable(pc),a0		; Hex conversion table
;	move.l	#$0f,d1			; Mask for hex conversion
;	move.l	#4,d2			; Steps to shift for each char
	move.l	#1,d3			; Count
	move.l	#3,d4			; Space mask
	CLD	0			; Better clear work registers
	CLD	6
 .make_hex:
	move.b	(a1)+,d6		; Get char
	move.l	d6,d0
	lsr.l	d2,d0			; Get high nybble
	move.b	(a0,d0.w),(a3)+		; Get hex char
	and.l	d1,d6			; Get low nybble
	move.b	(a0,d6.w),(a3)+		; Get hex char
	move.l	d3,d0			; Get offset in line
	and.l	d4,d0			; Mask out lower 2 bits
	bne.b	.next_make_hex		; No time for space

	move.b	#' ',(a3)+		; Write a space
 .next_make_hex:
 	add.l	#1,d3			; Increase counter
	cmp.l	d3,d5			; All bytes on this row done?
	bge.b	.make_hex		; No

	clr.b	(a3)			; NULL terminate string so far
	cmp.b	#BOTH,dt_DisplayFormat(a4)	; Both Ascii and Hex?
	bne.b	.row_done		; Nope, this row is finished

	lea	dt_WriteBuffer(a4),a0
	add.w	dt_EndOfHex(a4),a0	; Address of end of Hex-part
	move.l	a0,d0
	sub.l	a3,d0
	bra.b	.start_pad

 .pad_space:
	move.b	#' ',(a3)+		; Pad out with spaces
 .start_pad:
 	dbra	d0,.pad_space

 .ascii:
	lea	dt_ReadBuffer(a4),a0	; Read data
	lea	AsciiTable(pc),a1	; Conversion table
	move.l	d5,d0			; Bytes/line
	sub.l	#1,d0			; Prepare for dbra
	CLD	1
	move.b	#"'",(a3)+		; Start of ascii part
 .next_ascii:
	move.b	(a0)+,d1		; Get next byte
	move.b	(a1,d1.w),(a3)+		; Show proper char
	dbra	d0,.next_ascii

	move.b	#"'",(a3)+		; End of ascii part
 .row_done:
 	move.b	#10,(a3)+		; Place a linefeed at the end
	clr.b	(a3)			; And terminate the string
	add.l	d5,dt_BytePos(a4)	; Increment byte position counter
	move.l	dt_OutFile(a4),d1	; Output file
	lea	dt_WriteBuffer(a4),a0
	move.l	a0,d2			; Write buffer
	SYS	FPuts			; Write out string
	tst.l	d0			; Write ok?
	bmi	.write_error		; No

	tst.l	dt_Flags+f_Page(a4)	; Page output?
	beq	.next_row		; No

	tst.w	dt_Height(a4)		; Any height?
	beq	.next_row		; No, something is wrong...

	** Show a page prompt, and wait for response **

	add.w	#1,dt_YPos(a4)		; Next row
	move.w	dt_YPos(a4),d0
	cmp.w	dt_Height(a4),d0	; Time for a prompt?
	bls	.next_row		; No

	move.w	#INITYPOS,dt_YPos(a4)	; Reset YPos
	move.l	dt_OutFile(a4),d4
	move.l	d4,d1			; Output file
	SYS	Flush			; Make sure prompt is visible (and "enable" reading)
	tst.l	d0			; Write ok?
	beq.b	.prompt_error		; No

	move.l	d4,d1			; Output file
	move.l	#1,d2			; Select RAW mode
	SYS	SetMode			; Go to RAW mode
	tst.l	d0			; All ok?
	beq.b	.prompt_error		; No

	move.l	d4,d1			; Output file
	lea	PagePrompt_Msg(pc),a0
	move.l	a0,d2			; Page prompt
	SYS	FPuts			; Show it
	tst.l	d0			; All ok
	bge.b	.prompt_ok		; Yep

 .prompt_error:
 	SYS	IoErr			; Get failure reason
 	move.l	d0,d3			; Save it
	move.l	d4,d1			; Output file
	move.l	#0,d2			; Go to CON mode
	SYS	SetMode			; Try to restore normal "CON" mode
	move.l	d4,d1			; Output file
	lea	EndPrompt_Msg(pc),a0
	move.l	a0,d2			; Try to erase prompt
	SYS	FPuts			; Do it
	move.l	d3,d0			; Error code
	lea	PromptError_Msg(pc),a0	; Error eader
	CALL	PrintFault

 .prompt_ok:
	move.l	d4,d1			; Output file
	SYS	Flush			; Make sure prompt is visible (and "enable" reading)
	tst.l	d0			; Write ok?
	beq.b	.prompt_error		; No

	move.l	d4,d1			; Output file
	lea	dt_Response(a4),a0
	move.l	a0,d2			; Read buffer
	move.l	#1,d3			; No. of bytes to read
	SYS	Read			; Wait for a keypress
	move.l	d0,d6			; Save read length
	bmi.b	.prompt_error		; Error!

	move.l	d4,d1			; Output file
	lea	EndPrompt_Msg(pc),a0
	move.l	a0,d2			; Remove prompt
	SYS	FPuts			; Do it
	tst.l	d0			; All ok?
	bmi	.prompt_error		; No

	move.l	d4,d1			; Output file
	SYS	Flush			; Make sure prompt gets removed
	tst.l	d0			; All ok?
	beq	.prompt_error		; No

	move.l	d4,d1			; Output file
	move.l	#0,d2			; Goto CON mode
	SYS	SetMode			; Restore normal console mode
	tst.l	d0			; All ok?
	beq	.prompt_error		; No

	tst.l	d6			; Read ok?
	beq.b	.file_break		; EOF => File breaked

	tst.l	dt_Flags+f_Cursor(a4)	; Hide cursor?
	bne.b	.no_hide		; No

	move.l	d4,d1			; Output file
	lea	CursorOff_Msg(pc),a0
	move.l	a0,d2			; Hide cursor
	SYS	FPuts			; Do it
	tst.l	d0			; All ok?
	bmi	.prompt_error		; No

 .no_hide:
 	CALL	CalcBytesPerRow
 	move.l	d0,d7
	move.b	dt_Response(a4),d0	; Get key user pressed
	cmp.b	#3,d0			; Ctrl-C?
	beq	.break			; Yep

	cmp.b	#4,d0			; Ctrl-D?
	bne.b	.0			; No

 .file_break:
	CALL	PrintFileBreak		; Show break message
	bra	.done			; And end this file

 .0:	cmp.b	#27,d0			; Esc?
	beq	.break			; Yep

	cmp.b	#'s',d0			; Skip page prompts for this file
	bne.b	.1

 .clr_height:
	clr.w	dt_Height(a4)		; Stop paging for this file
	bra	.cont

 .1:	cmp.b	#'S',d0			; Skip page prompts for this file
	beq.b	.clr_height

	cmp.b	#8,d0			; Backspace => Previous page
	bne.b	.4

	CLD	1
	SYS	SetIoErr		; Clear any error code
	CLD	0
	move.w	dt_Height(a4),d0	; Get height
	add.w	d0,d0			; * 2 (really skip 2 pages)
	sub.w	#3,d0			; A little overlap + page prompts space
	mulu	d7,d0			; Pager only active if this is != 0
	sub.l	d0,dt_BytePos(a4)	; Go backwards
	bge.b	.seek_ok		; No seek beyond start

	clr.l	dt_BytePos(a4)		; Reset pos to start
 .seek_ok:
	move.w	#INITYPOS-1,dt_YPos(a4)	; Reset YPos
	move.l	dt_InFile(a4),d1	; Input file
	move.l	dt_BytePos(a4),d2	; Offset to seek to
	move.l	#OFFSET_BEGINNING,d3	; Offset is relative to start
	SYS	Seek			; Do the seeking
	move.l	d0,d3			; Save result
	SYS	IoErr			; Any error?
	exg.l	d0,d3
	tst.l	d0			; Check seek result
	bmi	.read_error		; Error!

	tst.l	d3			; IoErr() returned error?
	bne	.read_error		; Yep

	lea	FF_Msg(pc),a0
	move.l	dt_OutFile(a4),d1	; Output file
	move.l	a0,d2			; Code to clear screen
	SYS	FPuts			; Clear it
	tst.l	d0			; All ok?
	bmi	.write_error		; Nope

	move.l	dt_OutFile(a4),d1	; Output file
	SYS	Flush			; Make sure cursor is restored
	tst.l	d0			; All ok?
	beq	.write_error		; No

	bra.b	.cont			; And continue with next row etc.

 .4:	cmp.b	#'=',d0			; Disable page prompt for rest of files
	bne.b	.2

	clr.l	dt_Flags+f_Page(a4)	; Disable paging
	bra.b	.cont

 .2:	cmp.b	#13,d0			; Return (= new line)?
 	bne.b	.cont

	move.w	#30000,dt_YPos(a4)	; Only do one line...
 .cont:	bra	.next_row		; And continue with next row

 .break:
	CALL	PrintBreak		; Break pressed
	CLD	0			; No error
	CLD	1
	CALL	CleanExit		; Exit

	** All done. Clean up and return **

 .done:
	tst.l	dt_Flags+f_Cursor(a4)	; Cursor hidden?
	bne.b	.end			; No

	move.l	dt_OutFile(a4),d1	; Output file
	lea	CursorOn_Msg(pc),a0
	move.l	a0,d2			; Restore cursor
	SYS	FPuts			; Do it
	tst.l	d0			; All ok?
	bmi	.write_error		; No

	move.l	dt_OutFile(a4),d1	; Output file
	SYS	Flush			; Make sure cursor is restored
	tst.l	d0			; All ok?
	beq	.write_error		; No
 .end:
	POPM
	rts


*
* LONG CalcBytesPerRow( VOID );
*
* Calculates the number of bytes that will fit on one row.
* Also sets the dt_Height variable properly (if possible).
*

CalcBytesPerRow

	PUSHM	d1-2
	CALL	GetWinSize		; Try to get size of console window
	CLD	0
	CLD	1
	move.w	dt_Width(a4),d0		; Get requested width of line
	tst.l	dt_Flags+f_NoOffset(a4)	; Show offset?
	bne.b	.offset			; No

	sub.w	#8,d0			; Make room for offset
	moveq	#8,d1
 .offset:
	cmp.b	#ASCII,dt_DisplayFormat(a4)	; Only Ascii?
	bne.b	.not_ascii		; No

	sub.w	#2,d0			; For the single-quotes
	bra.b	.bytes_done

 .not_ascii:
	cmp.b	#HEX,dt_DisplayFormat(a4)	; Only Hex?
	bne.b	.both			; No

	lsl.w	#2,d0			; * 4
	divu	#9,d0			; And make it bytes/row
	bra.b	.bytes_done

 .both:	sub.w	#2,d0			; Remove some space (single-quotes)
	lsl.w	#2,d0			; * 4
	divu	#13,d0			; And make it bytes/row
	CLD	2
	move.w	d0,d2
	lsl.w	#3,d2			; * 8
	add.w	d0,d2			; And add old value again => * 9
	lsr.w	#2,d2			; / 4
	add.w	d2,d1			; Add hex part
	move.w	d0,d2
	and.w	#3,d2			; Even multiple of 4?
	beq.b	.bytes_done		; Yep

	addq.w	#1,d1

 .bytes_done:
 	move.w	d1,dt_EndOfHex(a4)	; Save end pos for hex part
	and.l	#$0000ffff,d0		; Clear upper word
 	POPM
 	rts


*
* LONG CheckBreak( VOID );
*
* Check if Ctrl-C or Ctrl-D have been pressed. If Ctrl-C, exit "normally".
* If Ctrl-D, print that a file break occured, and return TRUE. Else
* return FALSE.
*

CheckBreak

	PUSHM	d1/a0-1/a6
	move.l	dt_SysBase(a4),a6
	CLD	0
	move.l	#SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D,d1	; Bits we are checking
	SYS	SetSignal		; Check which signals we have got
	btst	#SIGBREAKB_CTRL_C,d0	; Ctrl-C pressed?
	beq.b	.no_c			; No

	CALL	PrintBreak
	CLD	0
	CLD	1
	CALL	CleanExit

 .no_c:
	CLD	1			; Default return is FALSE
 	btst	#SIGBREAKB_CTRL_D,d0	; Ctrl-D pressed?
 	beq.b	.done			; No

	CALL	PrintFileBreak
	move.l	#1,d1
 .done:
	move.l	d1,d0			; Get return code
	POPM
	rts


*
* VOID GetWinSize( VOID );
*
* Set the dt_Width and dt_Height fields properly.
* If this routine discovers that dt_Width becomes to small
* (i.e. output window is too narrow), this routine will exit with
* a suitable message.
*

REPORTBUF	EQU	16

GetWinSize

	PUSHM	d0-4/a0-1/a6
	tst.l	dt_Flags+f_Width(a4)	; Width specified?
	beq.b	.get			; Nope, get suitable value

	tst.l	dt_Flags+f_Page(a4)	; Page output?
	beq	.end			; No, no need to do anything..

 .get:	move.l	dt_OutFile(a4),d4	; Output file
	move.l	d4,d1
	SYS	IsInteractive,dt_DOSBase(a4)
	tst.l	d0			; Interactive?
	beq	.no_page		; No

	move.l	d4,d1			; Output file
	SYS	Flush
	tst.l	d0			; All ok?
	beq	.restore_mode		; No

	move.l	d4,d1			; Output file
	move.l	#1,d2			; RAW mode
	SYS	SetMode
	tst.l	d0			; Ok?
	beq	.end			; No

	move.l	d4,d1			; Output file
	lea	GetWinBounds_Msg(pc),a0
	move.l	a0,d2			; Ask for window bounds report
	move.l	#GetWinBounds_Len,d3	; Length of string
	SYS	Write			; Write string
	tst.l	d0			; All ok
	bmi	.restore_mode		; No!

	move.l	d4,d1			; Output file
	move.l	#10000,d2		; Wait .1 secs
	SYS	WaitForChar		; Wait for reply on request
	tst.l	d0			; Anything in return?
	beq	.restore_mode		; No, no need waiting..

	sub.l	#REPORTBUF,sp		; Get stack space for report
	move.l	d4,d1			; Output file
	move.l	sp,d2			; Read buffer
	move.l	#REPORTBUF,d3		; Size of buffer
	SYS	Read			; Get report
	move.l	d0,d3			; Number of chars read
	move.l	d4,d1			; Output file
	move.l	#0,d2			; CON mode
	SYS	SetMode			; Goto CON mode
	cmp.l	#9,d3			; Less than 8 chars read?
	ble	.bad_report		; Yep, bad report

	move.l	sp,a0			; Input buffer
	cmp.b	#';',4(a0)		; A valid return?
	bne.b	.bad_report		; No

	cmp.b	#'r',-1(a0,d3.w)	; More validations..
	bne.b	.bad_report

	add.l	#5,a0			; Get first char of y size
	CLD	0
	move.b	(a0)+,d0		; Get first char
	sub.w	#'0',d0			; Make it "normal" number
	cmp.b	#';',(a0)		; More Y?
	beq.b	.x			; No

	mulu	#10,d0
	add.b	(a0)+,d0		; Next char
	sub.b	#'0',d0			; Convert to number
	cmp.b	#';',(a0)		; More Y?
	beq.b	.y_done			; No

	mulu	#10,d0			; We limit y size to 999 lines... :)
	add.b	(a0)+,d0		; Get last char
	sub.b	#'0',d0			; And convert it
 .y_done:
 	tst.l	dt_Flags+f_Page(a4)	; Page output?
	beq.b	.x			; No

	move.w	d0,dt_Height(a4)	; Save height
 .x:	tst.l	dt_Flags+f_Width(a4)	; Width specified?
	bne.b	.bad_report		; Yep, ignore this part

	addq.l	#1,a0			; Skip separator
	CLD	0
	move.b	(a0)+,d0		; Get first char
	cmp.b	#' ',d0			; Bad one?
	beq.b	.report_done		; Yes

	cmp.b	#';',d0			; Bad?
	beq.b	.report_done		; Yes

	cmp.b	#'r',d0			; Bad?
	beq.b	.report_done		; Yes

	sub.b	#'0',d0			; Make it "normal"
	cmp.b	#' ',(a0)		; Any more proper char?
	beq.b	.report_done		; No

	mulu	#10,d0
	add.b	(a0)+,d0		; Next char
	sub.b	#'0',d0			; Make it "normal"
	cmp.b	#' ',(a0)		; End of report?
	beq.b	.report_done		; Yep

	add.b	(a0)+,d0		; Get last char
	sub.b	#'0',d0
 .report_done:
	move.w	d0,dt_Width(a4)		; Save width
 .bad_report:
	add.l	#REPORTBUF,sp		; Restore stack
 .end:	POPM
	rts

 .restore_mode:
	move.l	d4,d1			; Output file
	move.l	#0,d2			; RAW mode
	SYS	SetMode
	bra.b	.end

 .no_page:
	clr.l	dt_Flags+f_Page(a4)	; Disable page prompts
	st	dt_Flags+f_Cursor(a4)	; Make Flags.Cursor != NULL
	bra.b	.end


*
* LONG Printf1( __A0 STRPTR format, __D0 ULONG arg1 );
*
* Printf format using arg1 as the only argument in the argument array.
* The return is the return value from VPrintf.
*

Printf1

	PUSHM	d1-2/a0-1/a6
	move.l	dt_DOSBase(a4),a6
	PUSH	d0			; Save argument
	move.l	a0,d1			; Format
	move.l	sp,d2			; Argument array
	SYS	VPrintf			; Print string
	addq.l	#4,sp			; Restore stack
	POPM
	rts


*
* VOID PrintBreak( VOID );
*
* Simply print out a break message. May exit, if write failed.
*

PrintBreak

	PUSH	a0
	lea	Break_Msg(pc),a0	; Format string
	CALL	__PrintBreakDummy
	POP
	rts


*
* VOID PrintFileBreak( VOID );
*
* Simply print out a file break message. May exit, if write failed.
*

PrintFileBreak

	PUSH	a0
	lea	FileBreak_Msg(pc),a0	; Format string
	CALL	__PrintBreakDummy
	POP
	rts


*
* VOID __PrintBreakDummy( __A0 STRPTR msg );
*
* "Dummy" for printing break messages.
*

__PrintBreakDummy

	PUSHM	d0/a1/a6
	lea	CursorOn_Msg(pc),a1	; Argument
	move.l	a1,d0
	CALL	Printf1			; Print error message
	tst.l	d0			; All ok?
	bge.b	.end			; Yep

	move.l	dt_DOSBase(a4),a6
	SYS	IoErr			; Reason for failure
	lea	PrgName_Msg(pc),a0
	CALL	PrintFault

 .end:	POPM
	rts


MinWidthArray
	MINWIDTHARRAY

*
* Start of string section
*

	dc.b	"$VER: HexD 2.0 (21.02.93)",0

DOSName_Msg
	dc.b	"dos.library",0

PrgName_Msg
	dc.b	"HexD",0

Template_Msg
	TEMPLATE

Header_Msg
	dc.b	"==== File ""%s"" ====",10,0

MAX3	SET	MAXWIDTH/100
MAX2	SET	(MAXWIDTH-MAX3*100)/10
MAX1	SET	MAXWIDTH-MAX2*10-MAX3*100

WidthError_Msg
	dc.b	"HexD: WIDTH out of range (allowed range: %ld - "
	dc.b	MAX3+'0',MAX2+'0',MAX1+'0',")",10,0

NoToFile_Msg
	dc.b	"HexD: Couldn't open TO file ""%s"":",10,0

NoAppend_Msg
	dc.b	"HexD: Couldn't append to TO file ""%s"":",10,0

NoInFile_Msg
	dc.b	"HexD: Couldn't open ""%s"" for input:",10,0

WriteError_Msg
	dc.b	"HexD: Write error",0

ReadError_Msg
	dc.b	"HexD: Read error",0

PromptError_Msg
	dc.b	"HexD: Prompt error",0

NarrowWindow_Msg
	dc.b	"HexD: Output window is too narrow",10,0

NeedOS2_Msg
	dc.b	"HexD: You need OS 2.04+",10,0

NeedOS2_SIZEOF	EQU	*-NeedOS2_Msg

PagePrompt_Msg
	dc.b	13,$9b,"7m  --More--  "
	dc.b	$9b,$30,$20,$70,$0

EndPrompt_Msg
	dc.b	$9b,"0m",13,$9b,"K",0

Break_Msg
	dc.b	"%sHexD: ***Break",10,0

FileBreak_Msg
	dc.b	"%sHexD: ***File break",10,0

CursorOn_Msg
	dc.b	$9b,$20,$70,$0

CursorOff_Msg
	dc.b	$9b,$30,$20,$70,$0

GetWinBounds_Msg
	dc.b	$9b,"0 q",0
GetWinBounds_Len	EQU	*-GetWinBounds_Msg

FF_Msg	dc.b	12,0

AsciiTable
	dc.b	"································ !""#$%&'()*+,-./0123456789"
	dc.b	":;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrst"
	dc.b	"uvwxyz{|}~································· ¡¢£¤¥¦§¨©ª«¬­®¯"
	dc.b	"°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéê"
	dc.b	"ëìíîïðñòóôõö÷øùúûüýþÿ",0

HexTable
	dc.b	"0123456789abcdef",0

	END
