Base64: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
(Base 64 Encoder Function written in Native NSIS)
 
m (comment fail.. two > four)
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== BASE64 Encoding/Decoding Functions ==
As a result of needing to send data in a URL from my NSIS Installer, and because I didn't want to include another dependency, 99999999 wrote the below Base64 Encoder.


== BASE64 Encoding Function ==
'''Please note:''' this header depends on the [[CharToASCII]] header.
As a result of needing to send data in a URL from my NSIS Installer, and because I didn't want to include another dependency, I wrote the below Base64 Encoder.


Usage is as follows:
Forum thread: http://forums.winamp.com/showthread.php?t=297879 "Base64 Encoder in NSIS"


${Base64_Encode} "THIS WILL BE ENCODED"
Usage example as follows:
Pop $0
<highlight-nsis>
; $0 now equals VEhJUyBXSUxMIEJFIEVOQ09ERUQ=
!include "CharToASCII.nsh"
!include "Base64.nsh"
OutFile "$%temp%\temp.exe"
Section
  ${Base64_Encode} "THIS WILL BE ENCODED"
  Pop $0
  ; $0 now equals VEhJUyBXSUxMIEJFIEVOQ09ERUQ=
  MessageBox MB_OK "$0"
SectionEnd
</highlight-nsis>
 
Also there is a variant for encoding in a URL safe form, ${Base64_URLEncode}, which just uses a different encoding table, (but a standard one,) from the original version.  I don't know how this works with Binary Data, but it works great with string data, which you need to put into a consistent form. 
 
Animaether added decoding routines.
 
Forum thread: http://forums.winamp.com/showthread.php?t=322673 "Base64 decoder"
 
<highlight-nsis>
!include "CharToASCII.nsh"
!include "Base64.nsh"
OutFile "$%temp%\temp.exe"
Section
  ${Base64_Decode} "VEhJUyBXSUxMIEJFIERFQ09ERUQ="
  Pop $0
  ; $0 now equals "THIS WILL BE DECODED"
  MessageBox MB_OK "$0"
SectionEnd
</highlight-nsis>


Also there is a variant for encoding in a URL safe form, ${Base64_URLEncode}, which just uses a different encodinging table, (but a standard one,) from the original version.
As with the encoding functions, a URL safe form exists in the form of ${Base64_URLDecode}.


== The header code ==
<highlight-nsis>
<highlight-nsis>
!ifndef BASE64_NSH
!ifndef BASE64_NSH
!define BASE64_NSH
!define BASE64_NSH
!include registers.nsh
!include specialstring.nsh


!define BASE64_ENCODINGTABLE "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
!define BASE64_ENCODINGTABLE "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
Line 23: Line 49:
!define BASE64_PADDING "="
!define BASE64_PADDING "="


VAR OCTETVALUE
VAR   OCTETVALUE
VAR  BASE64TEMP
VAR  BASE64TEMP


Line 30: Line 56:


!macro  Base64_Encode _cleartext
!macro  Base64_Encode _cleartext
${SaveRegisters}
  push $R0
push `${_cleartext}`
  push $R1
push `${BASE64_ENCODINGTABLE}`
  push $R2
Call Base64_Encode
  push $0
Pop $BASE64TEMP
  push $1
${RestoreRegisters}
  push $2
Push $BASE64TEMP
  push $3
  push $4
  push $5
  push $6
  push $7
  push `${_cleartext}`
  push `${BASE64_ENCODINGTABLE}`
  Call Base64_Encode
  Pop $BASE64TEMP
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
  pop $R2
  pop $R1
  pop $R0
  Push $BASE64TEMP
!macroend
!macroend


!macro  Base64_URLEncode _cleartext
!macro  Base64_URLEncode _cleartext
${SaveRegisters}
  push $R0
push `${_cleartext}`
  push $R1
push `${BASE64_ENCODINGTABLEURL}`
  push $R2
Call Base64_Encode
  push $0
Pop $BASE64TEMP
  push $1
${RestoreRegisters}
  push $2
Push $BASE64TEMP
  push $3
  push $4
  push $5
  push $6
  push $7
  push `${_cleartext}`
  push `${BASE64_ENCODINGTABLEURL}`
  Call Base64_Encode
  Pop $BASE64TEMP
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
  pop $R2
  pop $R1
  pop $R0
  Push $BASE64TEMP
!macroend
!macroend


Function Base64_Encode
  pop $R2 ; Encoding table
  pop $R0 ; Clear Text
  StrCpy "$R1" "" # The result
  StrLen $1 "$R0"
  StrCpy $0 0
  ${WHILE} $0 < $1
    # Copy 3 characters, and for each character push their value.
    StrCpy $OCTETVALUE 0
    StrCpy $5 $0
    StrCpy $4 "$R0" 1 $5
    ${CharToASCII} $4 "$4"
    IntOp $OCTETVALUE $4 << 16
    IntOp $5 $5 + 1
    ${IF} $5 < $1
      StrCpy $4 "$R0" 1 $5
      ${CharToASCII} $4 "$4"
      IntOp $4 $4 << 8
      IntOp $OCTETVALUE $OCTETVALUE + $4
      IntOp $5 $5 + 1
      ${IF} $5 < $1
        StrCpy $4 "$R0" 1 $5
        ${CharToASCII} $4 "$4"
        IntOp $OCTETVALUE $OCTETVALUE + $4
      ${ENDIF}
    ${ENDIF}
    # Now take the 4 indexes from the encoding table, based on 6bits each of the octet's value.
    IntOp $4 $OCTETVALUE >> 18
    IntOp $4 $4 & 63
    StrCpy $5  "$R2" 1 $4
    StrCpy $R1  "$R1$5"
    IntOp $4 $OCTETVALUE >> 12
    IntOp $4 $4 & 63
    StrCpy $5  "$R2" 1 $4
    StrCpy $R1  "$R1$5"
    StrCpy $6 $0
    StrCpy $7 2
    IntOp $6 $6 + 1
    ${IF} $6 < $1
      IntOp $4 $OCTETVALUE >> 6
      IntOp $4 $4 & 63
      StrCpy $5  "$R2" 1 $4
      StrCpy $R1  "$R1$5"
      IntOp $7 $7 - 1
    ${ENDIF}
    IntOp $6 $6 + 1
    ${IF} $6 < $1
      IntOp $4 $OCTETVALUE & 63
      StrCpy $5  "$R2" 1 $4
      StrCpy $R1  "$R1$5"
      IntOp $7 $7 - 1
    ${ENDIF}
    # If there is any padding required, we now write that here.
    ${IF} $7 > 0
      ${WHILE} $7 > 0
        StrCpy $R1 "$R1${BASE64_PADDING}"
        IntOp $7 $7 - 1
      ${ENDWHILE}
    ${ENDIF}
    IntOp $0 $0 + 3
  ${ENDWHILE}
  Push "$R1"
FunctionEnd
!define Base64_Decode "!insertmacro Base64_Decode"
!define Base64_URLDecode "!insertmacro Base64_URLDecode"
!macro  Base64_Decode _encodedtext
  push `${_encodedtext}`
  push `${BASE64_ENCODINGTABLE}`
  Call Base64_Decode
!macroend


!macro  Base64_URLDecode _encodedtext
  push `${_encodedtext}`
  push `${BASE64_ENCODINGTABLEURL}`
  Call Base64_Decode
!macroend


Function Base64_Encode
Function base64_Decode
pop $R2 ; Encoding table
            ; Stack: strBase64table strEncoded
pop $R0 ; Clear Text
  Push $9  ; Stack: $9 strBase64table strEncoded  ; $9 = strDecoded
StrCpy "$R1" "" # The result
  Exch 2    ; Stack: strEncoded strBase64table $9
  Exch      ; Stack: strBase64table strEncoded $9
  Exch $0  ; Stack: $0 strEncoded $9              ; $0 = strBase64table
  Exch      ; Stack: strEncoded $0 $9
  Exch $1  ; Stack: $1 $0 $9                      ; $1 = strEncoded


StrLen $1 "$R0"
  Push $2  ; strBase64table.length
StrCpy $0 0
  Push $3  ; strEncoded.length
  Push $4  ; strBase64table.counter
  Push $5  ; strEncoded.counter
  Push $6  ; strBase64table.char
  Push $7  ; strEncoded.char


${WHILE} $0 < $1
  Push $R0  ; 6bit-group.counter
# Copy 3 characters, and for each character push their value.
  Push $R1  ; 6bit-group.a
StrCpy $OCTETVALUE 0
  Push $R2  ; 6bit-group.b
  Push $R3  ; 6bit-group.c
  Push $R4  ; 6bit-group.d


StrCpy $5 $0
  Push $R5  ; bit-group.tempVar.a
StrCpy $4 "$R0" 1 $5
  Push $R6  ; bit-group.tempVar.b
${CharToASCII} $4 "$4"


IntOp $OCTETVALUE $4 << 16
  Push $R7  ; 8bit-group.A
  Push $R8  ; 8bit-group.B
  Push $R9  ; 8bit-group.C


IntOp $5 $5 + 1
  StrCpy $9 "" ; Result string
${IF} $5 < $1
StrCpy $4 "$R0" 1 $5
${CharToASCII} $4 "$4"


IntOp $4 $4 << 8
  StrLen $2 "$0" ; Get the length of the base64 table into $2
IntOp $OCTETVALUE $OCTETVALUE + $4
  StrLen $3 "$1" ; Get the length of the encoded text into $3
  IntOp $3 $3 - 1 ; Subtract one as the StrCpy offset is zero-based


IntOp $5 $5 + 1
  StrCpy $R0 4 ; Initialize the 6bit-group.counter
${IF} $5 < $1
StrCpy $4 "$R0" 1 $5
${CharToASCII} $4 "$4"


IntOp $OCTETVALUE $OCTETVALUE + $4
  ${ForEach} $5 0 $3 + 1 ; Loop over the encoded string
${ENDIF}
    StrCpy $7 $1 1 $5 ; Grab the character at the loop counter's index
${ENDIF}


# Now take the 4 indexes from the encoding table, based on 6bits each of the octet's value.
    ${If} $7 == "${BASE64_PADDING}" ; If it's the padding char
IntOp $4 $OCTETVALUE >> 18
      Push 0 ; Push value 0 (no impact on decoded string)
IntOp $4 $4 & 63
    ${Else} ; Otherwise
StrCpy $5 "$R2" 1 $4
      ${ForEach} $4 0 $2 + 1 ; Loop over the base64 lookup table
StrCpy $R1  "$R1$5"
        StrCpy $6 $0 1 $4 ; Grab the character at this loop counter's index
        ${If} $6 S== $7 ; If that character matches the encoded string character
          ${ExitFor} ; Exit this loop early
        ${EndIf}
      ${Next}
      Push $4 ; Push the lookup's index to the stack
    ${EndIf}


IntOp $4 $OCTETVALUE >> 12
    IntOp $R0 $R0 - 1 ; Decrease the 6bit-group counter
IntOp $4 $4 & 63
StrCpy $5 "$R2" 1 $4
StrCpy $R1  "$R1$5"


StrCpy $6 $0
    ${If} $R0 = 0 ; If that counter reaches zero
StrCpy $7 2
      ; Pop the index values off the stack to variables
      Pop $R4
      Pop $R3
      Pop $R2
      Pop $R1


IntOp $6 $6 + 1
      ; The way the base64 decoding works is like this...
${IF} $6 < $1
      ; Normal ASCII has 8 bits, base64 has 6 bits.
IntOp $4 $OCTETVALUE >> 6
      ; Those 8 bits need to be presented as 6 bits somehow
IntOp $4 $4 & 63
      ; Turns out you can easily do that by taking their common multiple: 24
StrCpy $5 "$R2" 1 $4
      ; This results in 3 8bit characters per each 4 6bit characters:
StrCpy $R1  "$R1$5"
      ; AAAAAAAA BBBBBBBB CCCCCCCC
IntOp $7 $7 - 1
      ; aaaaaabb bbbbcccc ccdddddd
${ENDIF}


IntOp $6 $6 + 1
      ; So to go back to AAAAAAAA, you need:
${IF} $6 < $1
      ;  aaaaaa shifted two bits to the left
IntOp $4 $OCTETVALUE & 63
      ;  the two left-most bits of bbbbbb,
StrCpy $5 "$R2" 1 $4
      ;    which you can do by shifting it four bits to the right
StrCpy $R1  "$R1$5"
      IntOp $R5 $R1 << 2
IntOp $7 $7 - 1
      IntOp $R6 $R2 >> 4
${ENDIF}
      IntOp $R5 $R5 | $R6
      IntFmt $R7 "%c" $R5 ; IntFmt turns the resulting 8bit value to a character


# If there is any padding required, we now write that here.
      ; For BBBBBBBB, you need:
${IF} $7 > 0
      ;  the four least significant bits of bbbbbb
StrCpy $R1 "$R1${BASE64_PADDING}"
      ;    which you can get by binary OR'ing with 2^4-1 = 15
${ENDIF}
      ;  the four most significant bits of cccccc
      ;    which you can get by just shifting it two bits to the right
      IntOp $R5 $R2 & 15
      InTop $R5 $R5 << 4
      IntOp $R6 $R3 >> 2
      IntOp $R5 $R5 | $R6
      IntFmt $R8 "%c" $R5


IntOp $0 $0 + 3
      ; For CCCCCCCC, the procedure is entirely similar.
${ENDWHILE}
      IntOp $R5 $R3 & 3
      IntOp $R5 $R5 << 6
      IntOp $R5 $R5 | $R4
      IntFmt $R9 "%c" $R5


Push "$R1"
      StrCpy $9 "$9$R7$R8$R9" ; Tack it all onto the result
      StrCpy $R0 4 ; Reset the 6bit-group counter
    ${EndIf}
  ${Next}


  ; Done.  Now let's restore the user's variables
  Pop $R9
  Pop $R8
  Pop $R7
  Pop $R6
  Pop $R5
  Pop $R4
  Pop $R3
  Pop $R2
  Pop $R1
  Pop $R0
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
  Exch $9  ; Stack: strDecoded
FunctionEnd
FunctionEnd
!endif ;BASE64_NSH
!endif ;BASE64_NSH
</highlight-nsis>

Latest revision as of 01:01, 25 September 2010

BASE64 Encoding/Decoding Functions

As a result of needing to send data in a URL from my NSIS Installer, and because I didn't want to include another dependency, 99999999 wrote the below Base64 Encoder.

Please note: this header depends on the CharToASCII header.

Forum thread: http://forums.winamp.com/showthread.php?t=297879 "Base64 Encoder in NSIS"

Usage example as follows:

!include "CharToASCII.nsh"
!include "Base64.nsh"
OutFile "$%temp%\temp.exe"
Section
  ${Base64_Encode} "THIS WILL BE ENCODED"
  Pop $0
  ; $0 now equals VEhJUyBXSUxMIEJFIEVOQ09ERUQ=
  MessageBox MB_OK "$0"
SectionEnd

Also there is a variant for encoding in a URL safe form, ${Base64_URLEncode}, which just uses a different encoding table, (but a standard one,) from the original version. I don't know how this works with Binary Data, but it works great with string data, which you need to put into a consistent form.

Animaether added decoding routines.

Forum thread: http://forums.winamp.com/showthread.php?t=322673 "Base64 decoder"

!include "CharToASCII.nsh"
!include "Base64.nsh"
OutFile "$%temp%\temp.exe"
Section
  ${Base64_Decode} "VEhJUyBXSUxMIEJFIERFQ09ERUQ="
  Pop $0
  ; $0 now equals "THIS WILL BE DECODED"
  MessageBox MB_OK "$0"
SectionEnd

As with the encoding functions, a URL safe form exists in the form of ${Base64_URLDecode}.

The header code

!ifndef BASE64_NSH
!define BASE64_NSH
 
!define BASE64_ENCODINGTABLE "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
!define BASE64_ENCODINGTABLEURL "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
 
!define BASE64_PADDING "="
 
VAR   OCTETVALUE
VAR  BASE64TEMP
 
!define Base64_Encode "!insertmacro Base64_Encode"
!define Base64_URLEncode "!insertmacro Base64_URLEncode"
 
!macro  Base64_Encode _cleartext
  push $R0
  push $R1
  push $R2
  push $0
  push $1
  push $2
  push $3
  push $4
  push $5
  push $6
  push $7
  push `${_cleartext}`
  push `${BASE64_ENCODINGTABLE}`
  Call Base64_Encode
  Pop $BASE64TEMP
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
  pop $R2
  pop $R1
  pop $R0
  Push $BASE64TEMP
!macroend
 
!macro  Base64_URLEncode _cleartext
  push $R0
  push $R1
  push $R2
  push $0
  push $1
  push $2
  push $3
  push $4
  push $5
  push $6
  push $7
  push `${_cleartext}`
  push `${BASE64_ENCODINGTABLEURL}`
  Call Base64_Encode
  Pop $BASE64TEMP
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
  pop $R2
  pop $R1
  pop $R0
  Push $BASE64TEMP
!macroend
 
Function Base64_Encode
  pop $R2 ; Encoding table
  pop $R0 ; Clear Text
  StrCpy "$R1" "" # The result
 
  StrLen $1 "$R0"
  StrCpy $0 0
 
  ${WHILE} $0 < $1
    # Copy 3 characters, and for each character push their value.
    StrCpy $OCTETVALUE 0
 
    StrCpy $5 $0
    StrCpy $4 "$R0" 1 $5
    ${CharToASCII} $4 "$4"
 
    IntOp $OCTETVALUE $4 << 16
 
    IntOp $5 $5 + 1
    ${IF} $5 < $1
      StrCpy $4 "$R0" 1 $5
      ${CharToASCII} $4 "$4"
 
      IntOp $4 $4 << 8
      IntOp $OCTETVALUE $OCTETVALUE + $4
 
      IntOp $5 $5 + 1
      ${IF} $5 < $1
        StrCpy $4 "$R0" 1 $5
        ${CharToASCII} $4 "$4"
 
        IntOp $OCTETVALUE $OCTETVALUE + $4
      ${ENDIF}
    ${ENDIF}
 
    # Now take the 4 indexes from the encoding table, based on 6bits each of the octet's value.
    IntOp $4 $OCTETVALUE >> 18
    IntOp $4 $4 & 63
    StrCpy $5   "$R2" 1 $4
    StrCpy $R1  "$R1$5"
 
    IntOp $4 $OCTETVALUE >> 12
    IntOp $4 $4 & 63
    StrCpy $5   "$R2" 1 $4
    StrCpy $R1  "$R1$5"
 
    StrCpy $6 $0
    StrCpy $7 2
 
    IntOp $6 $6 + 1
    ${IF} $6 < $1
      IntOp $4 $OCTETVALUE >> 6
      IntOp $4 $4 & 63
      StrCpy $5   "$R2" 1 $4
      StrCpy $R1  "$R1$5"
      IntOp $7 $7 - 1
    ${ENDIF}
 
    IntOp $6 $6 + 1
    ${IF} $6 < $1
      IntOp $4 $OCTETVALUE & 63
      StrCpy $5   "$R2" 1 $4
      StrCpy $R1  "$R1$5"
      IntOp $7 $7 - 1
    ${ENDIF}
 
    # If there is any padding required, we now write that here.
    ${IF} $7 > 0
      ${WHILE} $7 > 0
        StrCpy $R1 "$R1${BASE64_PADDING}"
        IntOp $7 $7 - 1
      ${ENDWHILE}
    ${ENDIF}
 
    IntOp $0 $0 + 3
  ${ENDWHILE}
 
  Push "$R1"
FunctionEnd
 
 
!define Base64_Decode "!insertmacro Base64_Decode"
!define Base64_URLDecode "!insertmacro Base64_URLDecode"
 
!macro  Base64_Decode _encodedtext
  push `${_encodedtext}`
  push `${BASE64_ENCODINGTABLE}`
  Call Base64_Decode
!macroend
 
!macro  Base64_URLDecode _encodedtext
  push `${_encodedtext}`
  push `${BASE64_ENCODINGTABLEURL}`
  Call Base64_Decode
!macroend
 
Function base64_Decode
            ; Stack: strBase64table strEncoded
  Push $9   ; Stack: $9 strBase64table strEncoded   ; $9 = strDecoded
  Exch 2    ; Stack: strEncoded strBase64table $9
  Exch      ; Stack: strBase64table strEncoded $9
  Exch $0   ; Stack: $0 strEncoded $9               ; $0 = strBase64table
  Exch      ; Stack: strEncoded $0 $9
  Exch $1   ; Stack: $1 $0 $9                       ; $1 = strEncoded
 
  Push $2   ; strBase64table.length
  Push $3   ; strEncoded.length
  Push $4   ; strBase64table.counter
  Push $5   ; strEncoded.counter
  Push $6   ; strBase64table.char
  Push $7   ; strEncoded.char
 
  Push $R0  ; 6bit-group.counter
  Push $R1  ; 6bit-group.a
  Push $R2  ; 6bit-group.b
  Push $R3  ; 6bit-group.c
  Push $R4  ; 6bit-group.d
 
  Push $R5  ; bit-group.tempVar.a
  Push $R6  ; bit-group.tempVar.b
 
  Push $R7  ; 8bit-group.A
  Push $R8  ; 8bit-group.B
  Push $R9  ; 8bit-group.C
 
  StrCpy $9 "" ; Result string
 
  StrLen $2 "$0" ; Get the length of the base64 table into $2
  StrLen $3 "$1" ; Get the length of the encoded text into $3
  IntOp $3 $3 - 1 ; Subtract one as the StrCpy offset is zero-based
 
  StrCpy $R0 4 ; Initialize the 6bit-group.counter
 
  ${ForEach} $5 0 $3 + 1 ; Loop over the encoded string
    StrCpy $7 $1 1 $5 ; Grab the character at the loop counter's index
 
    ${If} $7 == "${BASE64_PADDING}" ; If it's the padding char
      Push 0 ; Push value 0 (no impact on decoded string)
    ${Else} ; Otherwise
      ${ForEach} $4 0 $2 + 1 ; Loop over the base64 lookup table
        StrCpy $6 $0 1 $4 ; Grab the character at this loop counter's index
        ${If} $6 S== $7 ; If that character matches the encoded string character
          ${ExitFor} ; Exit this loop early
        ${EndIf}
      ${Next}
      Push $4 ; Push the lookup's index to the stack
    ${EndIf}
 
    IntOp $R0 $R0 - 1 ; Decrease the 6bit-group counter
 
    ${If} $R0 = 0 ; If that counter reaches zero
      ; Pop the index values off the stack to variables
      Pop $R4
      Pop $R3
      Pop $R2
      Pop $R1
 
      ; The way the base64 decoding works is like this...
      ; Normal ASCII has 8 bits, base64 has 6 bits.
      ; Those 8 bits need to be presented as 6 bits somehow
      ; Turns out you can easily do that by taking their common multiple: 24
      ; This results in 3 8bit characters per each 4 6bit characters:
      ; AAAAAAAA BBBBBBBB CCCCCCCC
      ; aaaaaabb bbbbcccc ccdddddd
 
      ; So to go back to AAAAAAAA, you need:
      ;   aaaaaa shifted two bits to the left
      ;   the two left-most bits of bbbbbb,
      ;     which you can do by shifting it four bits to the right
      IntOp $R5 $R1 << 2
      IntOp $R6 $R2 >> 4
      IntOp $R5 $R5 | $R6
      IntFmt $R7 "%c" $R5 ; IntFmt turns the resulting 8bit value to a character
 
      ; For BBBBBBBB, you need:
      ;   the four least significant bits of bbbbbb
      ;     which you can get by binary OR'ing with 2^4-1 = 15
      ;   the four most significant bits of cccccc
      ;     which you can get by just shifting it two bits to the right
      IntOp $R5 $R2 & 15
      InTop $R5 $R5 << 4
      IntOp $R6 $R3 >> 2
      IntOp $R5 $R5 | $R6
      IntFmt $R8 "%c" $R5
 
      ; For CCCCCCCC, the procedure is entirely similar.
      IntOp $R5 $R3 & 3
      IntOp $R5 $R5 << 6
      IntOp $R5 $R5 | $R4
      IntFmt $R9 "%c" $R5
 
      StrCpy $9 "$9$R7$R8$R9" ; Tack it all onto the result
      StrCpy $R0 4 ; Reset the 6bit-group counter
    ${EndIf}
  ${Next}
 
  ; Done.  Now let's restore the user's variables
  Pop $R9
  Pop $R8
  Pop $R7
  Pop $R6
  Pop $R5
  Pop $R4
  Pop $R3
  Pop $R2
  Pop $R1
  Pop $R0
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
  Exch $9   ; Stack: strDecoded
FunctionEnd
!endif ;BASE64_NSH