'===========================================================================
' Subject: Base64 encoder/decoder for qb      Date: 01-16-99 (10:59)       
'  Author: Marc van den Dikkenberg            Code: QB, Qbasic, PDS        
'  Origin: excel@xs4all.nl                  Packet: BINARY.ABC
'===========================================================================
'===========================================================================
' Subject: BASE64 ENCODER/DECODER FOR QB      Date: 01-16-99 (10:59)       
'  Author: Marc van den Dikkenberg            Code: QB, QBasic, PDS        
'  Origin: excel@xs4all.nl                  Packet: BINARY.ABC
'===========================================================================
DECLARE FUNCTION DECODE64$ (szEncoded AS STRING, iEndOfText AS INTEGER)
DECLARE FUNCTION ENCODE64$ (szUnEncoded AS STRING)
DECLARE SUB InitDecode64 ()

'-------------------------------------------------------------------------
' BASE64 Encoding / Decoding
' Original VBDOS Version by G. Balla, 1996 (Public Domain)
' QB 4.5 Conversion by Marc van den Dikkenberg, 1999
'--------------------------------------------------------------------------

DIM SHARED icChopMask  AS INTEGER  ' Constant 8-bit mask (Faster than using string constants)
DIM SHARED icBitShift  AS INTEGER  ' Constant shift mask (Faster than using string constants)
DIM SHARED icStartMask AS INTEGER  ' Initial mask value  (Faster than using string constants)

DIM SHARED iRollOver   AS INTEGER  ' Decoded Roll over value
DIM SHARED iHighMask   AS INTEGER  ' Mask high bits of each char
DIM SHARED iShift      AS INTEGER  ' Multiplier shift value
DIM SHARED iLowShift   AS INTEGER  ' Mask low bits of each char

DIM SHARED szAlphabet  AS STRING   ' Decode/Encode Lookup Table
DIM SHARED szTemp      AS STRING   ' Working string

Original$ = "This is a test-string!"
A$ = ENCODE64$(Original$)
PRINT A$

InitDecode64

B$ = DECODE64$(A$, -1)
PRINT B$

'----------------------------------------------------------------------------------------------
' Name:         DECODE64$
' Type:         String function
' Description:  decodes a string from BASE64 to binary format
'
' Arguements:   Source string to decode (szEncoded)
'               Last string in file to decode flag (iEndOfText)
' Returns:      Un-Encoded binary string.
'
' Errors:       Generates an Error via ERROR statement.
' Error code:   253 = No string to decode
'                   -----------------------------------------------
' NOTES:    Must call INITDECODE64 before sending 1st string of an encoded file.
'
'           A DOEVENTS occurs near the end of the FOR/NEXT loop
'                   -----------------------------------------------
' Global variables: szAlphabet         ' Decode/Encode lookup table
'                   szTemp             ' Working string
'
'                   iRollOver          ' Decode/Encode rollover value
'                   iHighMask          ' High bit mask
'                   iShift             ' Multiplier value
'
'
' Local variables:  iChar              ' Ascii Char value from Encoded string
'                   iPtr               ' Lookup table pointer
'                   iCounter           ' FOR/NEXT counter
'
'----------------------------------------------------------------------------------------------
FUNCTION DECODE64$ (szEncoded AS STRING, iEndOfText AS INTEGER)

' Create variables
    DIM iPtr       AS INTEGER
    DIM iChar      AS INTEGER
    DIM iCounter AS INTEGER

' Check if empty decoded string.
' If Empty, return NUL and Generate error 254
    IF LEN(szEncoded) = 0 THEN
        DECODE64$ = ""
        ERROR 253
        EXIT FUNCTION
    END IF

' Initialize working string
    szTemp = ""

' Begin Decoding
    FOR iCounter = 1 TO LEN(szEncoded)

' Get next alphabet
        iChar = ASC(MID$(szEncoded, iCounter, 1))

' Get Decoded value
        iPtr = INSTR(szAlphabet, CHR$(iChar)) - 1

' Check if character is valid
        IF iPtr >= 0 THEN

' Char is valid, process it
            IF iShift = icBitShift THEN

' 1st char in block of 4, keep high part of character
                iRollOver = (iPtr * iShift) AND icChopMask

' Reset masks for next character
                iHighMask = &H30
                iLowShift = icStartMask
                iShift = icStartMask

            ELSE

' Start saving decoded character
                szTemp = szTemp + CHR$(iRollOver OR ((iPtr AND iHighMask) / iLowShift))

' Calculate next mask and shift values
                iRollOver = (iPtr * iShift) AND icChopMask
                iShift = iShift * icBitShift
                iHighMask = (iHighMask \ icBitShift) OR &H30
                iLowShift = iLowShift / icBitShift

                IF iShift > 256 THEN
                    iShift = icBitShift
                    iLowShift = 0
                END IF
            END IF

        END IF
    NEXT

' Concat last character if required
    IF (iShift > icBitShift AND iShift < 256) THEN

' Character remaining in    iRollOver
        IF iEndOfText THEN

' Last string to decode in file
            szTemp = szTemp + CHR$(iRollOver)
        END IF
    END IF

' Exit wth decoded string
    DECODE64$ = szTemp
END FUNCTION

'----------------------------------------------------------------------------------------------
' Name:         ENCODE64$
' Type:         String function
' Description:  Encodes a string into a BASE64 coded string
'
' Arguement:    Source string to encode (szUnEncoded)
' Returns:      Encoded BASE64 string.
'
' Errors:       Generates an Error via ERROR statement.
' Error code:   254 = No string to encode
'                       returns NUL string
'                   -----------------------------------------------
' NOTES:    If sending more than 1 string per file, ensure that each source string
'               is a multiple of 3 bytes or premature padded characters are added.
'               The last string of a file is exempt from this rule.
'
'           Each string can be 16383 bytes in length to satisfy VB's string capacity limitation.
'
'           Encoded blocks should be written as sub block lengths of about 76 characters and
'               added CR/LF added to each sub-block.
'               This is what WINCODE does and should satisfy most UUE browsers.
'
'           A DOEVENT occurs near the end of the FOR/NEXT loop
'
'                   -----------------------------------------------
' Global variables: szAlphabet     ' Encoding/Decoding lookup table
'                   szTemp         ' Working string
'                   icChopMask     ' Constant 8-bit mask for speed
'                   icBitShift     ' Constant shift mask for speed
'
'                   iRollOver      ' Decode/Encode rollover value
'                   iHighMask      ' High bit mask
'                   iShift         ' Multiplier value
'
' Local variables:  icLowFill      ' Constant Low 2-bit fill value for speed
'
'                   iLowMask       ' Low bits mask
'                   iChar          ' Ascii char value from Un coded string
'                   iPtr           ' Lookup table pointer
'
'                   iCounter       ' For/Next loop counter
'----------------------------------------------------------------------------------------------
FUNCTION ENCODE64$ (szUnEncoded AS STRING)

' Create variables
    DIM icLowFill  AS INTEGER

    DIM iChar      AS INTEGER
    DIM iLowMask   AS INTEGER
    DIM iPtr       AS INTEGER

    DIM iCounter AS INTEGER


' Check if empty decoded string.
' If Empty, return NUL and Generate error 254
    IF LEN(szUnEncoded) = 0 THEN
        ENCODE64$ = ""
        ERROR 254
        EXIT FUNCTION
    END IF

' Initialize lookup dictionary and constants
    szAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

    icBitShift = 4
    icChopMask = 255
    icLowFill = 3


' Initialize Masks
    szTemp = ""

    iHighMask = &HFC
    iLowMask = &H3
    iShift = &H10
    iRollOver = 0


' Begin Encoding process
    FOR iCounter = 1 TO LEN(szUnEncoded)

' Fetch ascii character in decoded string
        iChar = ASC(MID$(szUnEncoded, iCounter, 1))

' Calculate Alphabet lookup pointer
        iPtr = ((iChar AND iHighMask) \ (iLowMask + 1)) OR iRollOver

' Roll bit patterns
        iRollOver = (iChar AND iLowMask) * iShift

' Concatenate encoded character to working encoded string
        szTemp = szTemp + MID$(szAlphabet, iPtr + 1, 1)

' Adjust masks
        iHighMask = (iHighMask * icBitShift) AND icChopMask
        iLowMask = iLowMask * icBitShift + icLowFill
        iShift = iShift \ icBitShift

' If last character in block, concat last RollOver and
'   reset masks
        IF iHighMask = 0 THEN
            szTemp = szTemp + MID$(szAlphabet, iRollOver + 1, 1)
            iRollOver = 0
            iHighMask = &HFC
            iLowMask = &H3
            iShift = &H10
        END IF

    NEXT iCounter

' If RollOver remains, concat it to the working string
    IF iShift < &H10 THEN
        szTemp = szTemp + MID$(szAlphabet, iRollOver + 1, 1)
    END IF

' Pad encoded string with required '=' pad characters
    iPtr = (LEN(szTemp) MOD 4)
    IF iPtr THEN szTemp = szTemp + STRING$(4 - iPtr, "=")

' Return encoded string
    ENCODE64$ = szTemp
END FUNCTION

'----------------------------------------------------------------------------------------------
' Name:         INITDECODE64
' Type:         Sub procedure
' Description:  Initializes local shared variables for DECODE64$
'                   Required because many encoded files cannot be decoded in a single string
'
' Used:         Before decoding a BASE64 file.  (DECODE64$)
' Arguements:   None
' Returns:      Nothing
'
' Errors:       None
'
' Global Variables used:
'               szAlphabet             Encode/Decode Table lookup
'
'               icChopMask             Constant value in variable to speed process
'               icBitShift             Constant value in variable to speed process
'               icStartMask            Constant value in variable to speed process
'
'               iShift                 Multiplier shift value
'               iLowShift              Mask Low bits of each char
'               iRollOver              Decoded Roll over value from prev char in string
'               iHighMask              Mask high bits of each char
'----------------------------------------------------------------------------------------------
SUB InitDecode64

' Initialize 2nd encoding pass lookup dictionary
    szAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

' Initialize Constants
    icChopMask = 255
    icBitShift = 4
    icStartMask = &H10

' Initialize Masks
    iShift = icBitShift
    iLowShift = 0
    iRollOver = 0
    iHighMask = -1
END SUB
