' -------------------------------------------------------------------------- '
' Absolute Assembly 2.1 by Petter Holmberg, -97.                             '
'                                                                            '
' I've found some bugs in Absolute Assembly 2.0, so here's a new version.    '
' For those of you who haven't used AbsAsm before, here's what it does:      '
'                                                                            '
' The program will let you choose a text file with Assembly source code,     '
' a destination file name and a string variable name. The result will be a   '
' set of commented BASIC string declaration lines in the destination file,   '
' ready to be executed with CALL ABSOLUTE.                                   '
' For example, you may have a text file looking like this:                   '
'                                                                            '
' ; A Useless Program example                                                '
'                                                                            '
'           XOR AX, AX    ; Set AX to 0                                      '
' Loopinit:                                                                  '
'           MOV CX, 4     ; Prepare for a loop                               '
' Increase: INC AX        ; Incease AX by 1                                  '
'           LOOP Increase ; Loop 8 times                                     '
'           CMP AX, 8     ; Is AX 8?                                         '
'           JNZ Loopinit  ; No. Go back to Loopinit                          '
'           RETF          ; Back to BASIC                                    '
'                                                                            '
' Run it through this program and you will get these lines moved into a      '
' selected BASIC program:                                                    '
'                                                                            '
' asm$ = ""                                                                  '
' asm$ = asm$ + CHR$(&H31) + CHR$(&HC0)            ' XOR AX,AX               '
' asm$ = asm$ + CHR$(&HB9) + CHR$(&H4) + CHR$(&H0) ' Loopinit: MOV CX,0004   '
' asm$ = asm$ + CHR$(&H40)                         ' Increase: INC AX        '
' asm$ = asm$ + CHR$(&HE2) + CHR$(&HFD)            ' LOOP Increase           '
' asm$ = asm$ + CHR$(&H3D) + CHR$(&H8) + CHR$(&H0) ' CMP AX,0008             '
' asm$ = asm$ + CHR$(&H75) + CHR$(&HF5)            ' JNZ Loopinit            '
' asm$ = asm$ + CHR$(&HCB)                         ' RETF                    '
'                                                                            '
' Absolute Assembly takes use of DEBUG, the program shipped with MS-DOS.     '
' DEBUG only supports 8088/8086 instructions, but it's still a good tool for '
' getting machine language out of Assembly instructions. And since QBASIC    '
'                                                                            '
' Absolute Assembly 2.1 features:                                            '
' * Support for blank lines and lines with just comments or lables.          '
' * Support for double Assembly commands, like REP STOSB.                    '
' * Option to automatically add CALL ABSOLUTE lines to output file.          '
' * All Assembly source lines printed to BASIC file in the same column.      '
' * Option to merge lines directly into a BASIC file.                        '
' * Auto-detection of QuickBASIC binary files to ensure safe merging.        '
' * Handling of errors and bugs in the sourcefile.                           '
'                                                                            '
' Notes:                                                                     '
' * Comments must start with a semicolon, (;).                               '
' * The maximum number of labels are 256. You shouldn't need half as much.   '
' * The maximum number of letters for a label are 16. It's easy to change    '
' * the program to accept a larger number, but it's probably not necessary.  '
' * A line label must be immediately followed by a colon, (:). Do NOT use a  '
'   colon after the label name in jump-instructions.                         '
' * Never use Assembly opcodes, numbers or single letters as labels.         '
' * Do not name labels so that the name includes the letters REP, REPE or    '
' * REPZ after each other, for example RepeatLoop:                           '
'   "Label:" and "label:" are processed as the same label.                   '
' * If no code string name is specified, it will be asm$ as default.         '
' * This program was made in QB45, but you should be able to run it in       '
'   QBASIC, PDS, VBDOS and PB as well.                                       '
' * If the program locks up, it's probably while running DEBUG. If this      '
'   happens, reboot your computer and check your source code for lines with  '
'   other things than Assembly instrucions, comments, labels or spaces.      '
' * You may use this program freely; distribute it, modify it, learn from it '
'   or erase it from your hard drive. ;) Just be sure to credit me in the    '
'   programs where you have used Absolute Assembly for some of the code.     '
' * If your computer gets damaged while using this program, don't blame me.  '
' * If you use Absolute Assembly in a program, please mail me and tell me    '
'   about it!                                                                '
' * Any comments/suggestions/bug reports etc. can be sent to:                '
'   petter.holmberg@usa.net                                                  '
' * Enjoy the program and have fun! Petter Holmberg, Sweden.                 '
' -------------------------------------------------------------------------- '

ON ERROR GOTO ErrorHandler

' -------------------------------------------------------------------------- '
' Declaration of constants and arrays:                                       '
' -------------------------------------------------------------------------- '

CONST rundebug$ = "C:\DOS\DEBUG.EXE" ' Change this if you have DEBUG on
				     ' another location.
CONST tempfile1$ = "TEMPFIL1.TXT"    ' Change this if the filename already
				     ' is in use.
CONST tempfile2$ = "TEMPFIL2.TXT"    ' Change this if the filename already
				     ' is in use.
CONST tempfile3$ = "TEMPFIL3.TXT"    ' Change this if the filename already
				     ' is in use.
CONST tempfile4$ = "TEMPFIL4.TXT"    ' Change this if the filename already
				     ' is in use.
CONST errorfile$ = "ERRORS.TXT"      ' Change this if the filename already
				     ' is in use.

TYPE labeltype                       ' Usertype for storing of labels.
  labelname AS STRING * 16           ' Change this if you want longer labels.
  labelpos AS STRING * 4
  labelnum AS INTEGER
END TYPE

DIM label(1 TO 256) AS labeltype     ' Array for storing of labels.

Start:

numlabels% = 0                       ' Label counter.
linecounter% = 1                     ' Line counter.
errorcounter% = 0                    ' Error counter.

' -------------------------------------------------------------------------- '
' Ask for settings:                                                          '
' -------------------------------------------------------------------------- '

CLS

PRINT "Absolute Assembly 2.1 by Petter Holmberg, -97."
PRINT

INPUT "Assembly source text file      : ", sourcefilename$
INPUT "BASIC destination file         : ", destfilename$
INPUT "Name of code string            : ", codestring$

codestring$ = LTRIM$(RTRIM$(codestring$))
IF codestring$ = "" THEN codestring$ = "asm$"
IF RIGHT$(codestring$, 1) <> "$" THEN codestring$ = codestring$ + "$"

PRINT "Append to destfile? (y/n)      : ";

DO
   kbd$ = INKEY$
   IF LCASE$(kbd$) = "n" THEN writemethod% = 0
   IF LCASE$(kbd$) = "y" THEN writemethod% = 1
LOOP UNTIL LCASE$(kbd$) = "n" OR LCASE$(kbd$) = "y"

PRINT LCASE$(kbd$)

PRINT "Add CALL ABSOLUTE lines? (y/n) : ";

DO
   kbd$ = INKEY$
   IF LCASE$(kbd$) = "n" THEN callabs% = 0
   IF LCASE$(kbd$) = "y" THEN callabs% = 1
LOOP UNTIL LCASE$(kbd$) = "n" OR LCASE$(kbd$) = "y"

PRINT LCASE$(kbd$)

PRINT

' -------------------------------------------------------------------------- '
' Warn the user if the BASIC destination file is a QuickBASIC binary file:   '
' -------------------------------------------------------------------------- '

IF writemethod% = 1 THEN
 DIM readstring AS STRING * 3

 OPEN destfilename$ FOR BINARY AS #1
 GET #1, , readstring

 IF readstring = CHR$(252) + CHR$(0) + CHR$(1) THEN
  PRINT "BASIC destination file is probably a QuickBASIC binary file"
  PRINT "Continue anyway? (y/n)         : "
 
  DO
     kbd$ = INKEY$
     IF UCASE$(kbd$) = "N" THEN END
  LOOP UNTIL UCASE$(kbd$) = "Y"
 END IF
 
 CLOSE #1
END IF

' -------------------------------------------------------------------------- '
' Get rid of blank lines, comments and double instructions in sourcefile:    '
' -------------------------------------------------------------------------- '

conversiontime! = TIMER

PRINT "Modifying source file..."

OPEN sourcefilename$ FOR INPUT AS #1
OPEN tempfile1$ FOR OUTPUT AS #2

DO
   LINE INPUT #1, readline$

   sourceline$ = sourceline$ + UCASE$(readline$)
  
   IF INSTR(sourceline$, ";") THEN
    sourceline$ = LEFT$(sourceline$, INSTR(sourceline$, ";") - 1)
   END IF

   sourceline$ = LTRIM$(RTRIM$(sourceline$))

   IF INSTR(sourceline$, "REPE") THEN
    PRINT #2, LEFT$(sourceline$, INSTR(sourceline$, "REPE") + 2) + "Z"
    sourceline$ = RIGHT$(sourceline$, LEN(sourceline$) - INSTR(sourceline$, "REPE") - 3)
    sourceline$ = LTRIM$(RTRIM$(sourceline$))
   ELSEIF INSTR(sourceline$, "REPZ") THEN
    PRINT #2, LEFT$(sourceline$, INSTR(sourceline$, "REPZ") + 3)
    sourceline$ = RIGHT$(sourceline$, LEN(sourceline$) - INSTR(sourceline$, "REPZ") - 3)
    sourceline$ = LTRIM$(RTRIM$(sourceline$))
   ELSEIF INSTR(sourceline$, "REP") THEN
    PRINT #2, LEFT$(sourceline$, INSTR(sourceline$, "REP") + 2) + "Z"
    sourceline$ = RIGHT$(sourceline$, LEN(sourceline$) - INSTR(sourceline$, "REP") - 2)
    sourceline$ = LTRIM$(RTRIM$(sourceline$))
   END IF

   IF RIGHT$(sourceline$, 1) <> ":" THEN
    IF LEN(sourceline$) > 0 THEN PRINT #2, sourceline$
    sourceline$ = ""
   END IF

LOOP UNTIL EOF(1)

CLOSE #2
CLOSE #1

' -------------------------------------------------------------------------- '
' Insert DEBUG instructions and Assembly source code into tempfile and take  '
' care of lables:                                                            '
' -------------------------------------------------------------------------- '

OPEN tempfile1$ FOR INPUT AS #1
OPEN tempfile2$ FOR OUTPUT AS #2

DO
   LINE INPUT #1, sourceline$

   IF INSTR(sourceline$, ":") THEN
    numlabels% = numlabels% + 1
    label(numlabels%).labelname = LEFT$(sourceline$, INSTR(sourceline$, ":") - 1)
    sourceline$ = MID$(sourceline$, INSTR(sourceline$, ":") + 1)
    label(numlabels%).labelnum = linecounter%
   END IF

   sourceline$ = LTRIM$(RTRIM$(sourceline$))
   PRINT #2, sourceline$

   linecounter% = linecounter% + 1
LOOP UNTIL EOF(1)

CLOSE #2
CLOSE #1

OPEN tempfile2$ FOR INPUT AS #1
OPEN tempfile3$ FOR OUTPUT AS #2

PRINT #2, "a"

DO
   LINE INPUT #1, sourceline$

   FOR labelscan% = 1 TO numlabels%
      IF INSTR(sourceline$, LTRIM$(RTRIM$(label(labelscan%).labelname))) THEN
       sourceline$ = LEFT$(sourceline$, INSTR(sourceline$, LTRIM$(RTRIM$(label(labelscan%).labelname))) - 1) + "100"
      END IF
   NEXT labelscan%

   PRINT #2, sourceline$
LOOP UNTIL EOF(1)

CLOSE #1

PRINT #2, ""
PRINT #2, "u 100, 100"
PRINT #2, "q"

CLOSE #2

' -------------------------------------------------------------------------- '
' Run DEBUG to find machine language code length in bytes and then update    '
' the tempfile with the correct byte length:                                 '
' All errors detected will be written to the file specified in errorfile$    '
' -------------------------------------------------------------------------- '

PRINT "Detecting machine language code length..."

SHELL rundebug$ + "<" + tempfile3$ + ">" + tempfile3$

OPEN tempfile3$ FOR INPUT AS #1
OPEN errorfile$ FOR OUTPUT AS #2

linecounter% = 0

DO
   oldline$ = midline$
   midline$ = newline$
   LINE INPUT #1, newline$
   SEEK #1, SEEK(1) + 2
   IF INSTR(newline$, "^") THEN
    PRINT
    PRINT "Error in line"; RTRIM$(STR$(linecounter% - 1)); ":"
    PRINT midline$
    PRINT newline$
    PRINT #2, "Error in line"; RTRIM$(STR$(linecounter% - 1)); ":"
    PRINT #2, midline$
    PRINT #2, newline$
    errorcounter% = errorcounter% + 1
    linecounter% = linecounter% - 1
   END IF
   linecounter% = linecounter% + 1
LOOP UNTIL newline$ = "-u 100, 100"

CLOSE #2
CLOSE #1

IF errorcounter% > 0 THEN
 KILL tempfile1$
 KILL tempfile2$
 KILL tempfile3$
 PRINT
 PRINT "Error(s) in source code detected. Look in "; errorfile$; " for information."
 END
END IF

asmlength$ = MID$(oldline$, INSTR(oldline$, ":") + 1, 4)

PRINT "Source code byte length detected."

OPEN tempfile3$ FOR INPUT AS #1
OPEN tempfile4$ FOR OUTPUT AS #2

SEEK #1, SEEK(1) + 5

PRINT #2, "a"

DO
   LINE INPUT #1, sourceline$
   SEEK #1, SEEK(1) + 2
   sourceline$ = RIGHT$(sourceline$, LEN(sourceline$) - 10)
   IF LEN(sourceline$) > 0 THEN PRINT #2, sourceline$
LOOP UNTIL sourceline$ = ""

CLOSE #1

PRINT #2, ""
PRINT #2, "u 100, "; asmlength$
PRINT #2, "q"

CLOSE #2

' -------------------------------------------------------------------------- '
' Run DEBUG again to create a full output file:                              '
' -------------------------------------------------------------------------- '

SHELL rundebug$ + "<" + tempfile4$ + ">" + tempfile3$

' -------------------------------------------------------------------------- '
' Scan through DEBUG output file to get the byte offsets for the lines with  '
' labels in the sourcefile:                                                  '
' -------------------------------------------------------------------------- '

OPEN tempfile3$ FOR INPUT AS #1

DO
   LINE INPUT #1, readline$
LOOP UNTIL INSTR(readline$, "-u 100,")

SEEK #1, SEEK(1) + 2

labelcounter% = 1
linecounter% = 1

DO
   LINE INPUT #1, readline$
   IF label(labelcounter%).labelnum = linecounter% THEN
    label(labelcounter%).labelpos = MID$(readline$, 6, 4)
    labelcounter% = labelcounter% + 1
   END IF
   linecounter% = linecounter% + 1
LOOP UNTIL readline$ = "-q"
  
CLOSE #1

' -------------------------------------------------------------------------- '
' Update tempfile with the correct byte offset numbers for lines with labels '
' in the sourcefile:                                                         '
' -------------------------------------------------------------------------- '

OPEN tempfile2$ FOR INPUT AS #1
OPEN tempfile3$ FOR OUTPUT AS #2

PRINT #2, "a"

DO
   LINE INPUT #1, sourceline$
   FOR labelscan% = 1 TO numlabels%
      IF INSTR(sourceline$, LTRIM$(RTRIM$(label(labelscan%).labelname))) THEN
       sourceline$ = LEFT$(sourceline$, INSTR(sourceline$, LTRIM$(RTRIM$(label(labelscan%).labelname))) - 1) + label(labelscan%).labelpos
      END IF
   NEXT labelscan%
   PRINT #2, sourceline$
LOOP UNTIL EOF(1)

CLOSE #1

PRINT #2, ""
PRINT #2, "u 100, "; asmlength$
PRINT #2, "q"

CLOSE #2

PRINT "Label conversion completed."

' -------------------------------------------------------------------------- '
' Make a final pass through DEBUG to create final output file:               '
' -------------------------------------------------------------------------- '

SHELL rundebug$ + "<" + tempfile3$ + ">" + tempfile3$

PRINT "DEBUG output file successfully created."

' -------------------------------------------------------------------------- '
' Calculate the longest possible BASIC string declaration line:              '
' -------------------------------------------------------------------------- '

linecounter% = 1
longestline% = 0
maxsourcelength% = 0
maxcodelength% = 0

OPEN tempfile3$ FOR INPUT AS #1

DO
   LINE INPUT #1, readline$
LOOP UNTIL INSTR(readline$, "-u 100,")

SEEK #1, SEEK(1) + 2

DO
   LINE INPUT #1, readline$

   linelength% = LEN(RTRIM$(MID$(readline$, 11, 14)))

   IF linelength% >= longestline% THEN
    codelength% = 0
    FOR findcodelength% = 1 TO linelength% STEP 2
       IF MID$(RTRIM$(MID$(readline$, 11, 14)), findcodelength%, 1) = "0" THEN
	codelength% = codelength% + 1
       ELSE
	codelength% = codelength% + 2
       END IF
    NEXT findcodelength%
    IF maxsourcelength% < codelength% THEN maxsourcelength% = codelength%
    longestline% = linelength%
   END IF

   linecounter% = linecounter + 1
LOOP UNTIL readline$ = "-q"

maxcodelength% = LEN(codestring$) + 3 + LEN(codestring$) + (longestline% \ 2) * 11 + maxsourcelength%

CLOSE #1

' -------------------------------------------------------------------------- '
' Transform the source code to BASIC string declaration lines:               '
' -------------------------------------------------------------------------- '

FOR modifylabels% = 1 TO numlabels%
   MID$(label(modifylabels%).labelname, 2, 16) = LCASE$(RIGHT$(label(modifylabels%).labelname, 15))
NEXT modifylabels%

OPEN tempfile3$ FOR INPUT AS #1
OPEN tempfile1$ FOR INPUT AS #2

IF writemethod% = 1 THEN
 OPEN destfilename$ FOR APPEND AS #3
ELSE
 OPEN destfilename$ FOR OUTPUT AS #3
END IF

DO
   LINE INPUT #1, readline$
LOOP UNTIL INSTR(readline$, "-u 100,")

SEEK #1, SEEK(1) + 2

PRINT #3, "' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '"
PRINT #3, ""

PRINT #3, codestring$ + " = " + CHR$(34) + CHR$(34)

linecounter% = 1
labelcounter% = 1

DO
   LINE INPUT #1, readline$
   readline$ = RTRIM$(readline$)
   IF NOT EOF(2) THEN LINE INPUT #2, sourceline$ ELSE sourceline$ = ""

   IF readline$ <> "-q" THEN

    sourcedata$ = RTRIM$(MID$(readline$, 11, 14))
    basicline$ = codestring$ + " = " + codestring$

    FOR makebasicline% = 1 TO LEN(sourcedata$) STEP 2
       IF MID$(RTRIM$(MID$(readline$, 11, 14)), makebasicline%, 1) = "0" THEN
	basicline$ = basicline$ + " + CHR$(&H" + MID$(sourcedata$, makebasicline% + 1, 1) + ")"
       ELSE
	basicline$ = basicline$ + " + CHR$(&H" + MID$(sourcedata$, makebasicline%, 2) + ")"
       END IF
    NEXT makebasicline%

    IF LEN(basicline$) < maxcodelength% THEN basicline$ = basicline$ + SPACE$(maxcodelength% - LEN(basicline$))

    basicline$ = basicline$ + " ' "

    IF label(labelcounter%).labelnum = linecounter% THEN
     basicline$ = basicline$ + RTRIM$(label(labelcounter%).labelname) + ": "
     labelcounter% = labelcounter% + 1
    END IF

    asmline$ = (RIGHT$(readline$, (LEN(readline$) - 24)))
    IF INSTR(asmline$, CHR$(9)) THEN MID$(asmline$, INSTR(asmline$, CHR$(9))) = " "
    asmline$ = RTRIM$(asmline$)
    basicline$ = basicline$ + asmline$

    FOR labelscan% = 1 TO numlabels%
       IF INSTR(sourceline$, UCASE$(RTRIM$(label(labelscan%).labelname))) AND INSTR(sourceline$, ":") = 0 THEN
	basicline$ = LEFT$(basicline$, LEN(basicline$) - 4)
	basicline$ = basicline$ + RTRIM$(label(labelscan%).labelname)
       END IF
    NEXT labelscan%

    PRINT #3, basicline$

   END IF
  
   linecounter% = linecounter% + 1
LOOP UNTIL readline$ = "-q"

IF callabs% = 1 THEN
 PRINT #3, ""
 PRINT #3, "offset% = SADD("; codestring$; ")"
 PRINT #3, "DEF SEG = VARSEG("; codestring$; ")"
 PRINT #3, "CALL ABSOLUTE(offset%)"
 PRINT #3, "DEF SEG"
END IF

PRINT #3, ""
PRINT #3, "' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '"

CLOSE #3
CLOSE #2
CLOSE #1

PRINT "Source code successfully moved to BASIC destination file."

KILL tempfile1$
KILL tempfile2$
KILL tempfile3$
KILL tempfile4$

PRINT
PRINT "Time of conversion:"; TIMER - conversiontime!; "seconds."
PRINT

PRINT "Convert another file? (y/n)    : ";

DO
   kbd$ = INKEY$
   IF LCASE$(kbd$) = "n" THEN
    PRINT LCASE$(kbd$)
    END
   END IF
   IF LCASE$(kbd$) = "y" THEN
    PRINT LCASE$(kbd$)
    GOTO Start
   END IF
LOOP

END

' -------------------------------------------------------------------------- '
' Error handler:                                                             '
' -------------------------------------------------------------------------- '

ErrorHandler:

CLEAR
PRINT

PRINT "Whoops! An error occured during program execution."
PRINT "Check the spelling of your filenames and check the sourcefile for illegal lines."
PRINT
PRINT "Press any key to end program..."
DO: LOOP WHILE INKEY$ = ""
END

