1- const { slice } = require < {
2- slice : < T extends defined > ( arr : T [ ] , start : number , stop ?: number ) => T [ ] ;
1+ const { toBinary, getCharAt } = require < {
2+ toBinary : ( int : number ) => string ;
3+ getCharAt : ( str : string , pos : number ) => string ;
34} > ( "./util.lua" ) ;
45
5- function stringToBytes ( str : string ) {
6- const result = [ ] ;
7-
8- for ( let i = 0 ; i < str . size ( ) ; i ++ ) {
9- result . push ( string . byte ( str , i + 1 ) [ 0 ] ) ;
10- }
11-
12- return result ;
13- }
14-
15- // Adapted from https://github.com/un-ts/ab64/blob/main/src/ponyfill.ts#L24
16- const _atob = ( asc : string ) => {
17- const b64CharList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" ;
18-
19- const b64Chars = string . split ( b64CharList , "" ) ;
20-
21- const b64Table = b64Chars . reduce < Record < string , number > > ( ( acc , char , index ) => {
22- acc [ char ] = index ;
23- return acc ;
24- } , { } ) ;
25-
26- const fromCharCode = string . char ;
27-
28- asc = string . gsub ( asc , "%s+" , "" ) [ 0 ] ;
29- asc += string . char ( ...slice ( stringToBytes ( "==" ) , 2 - ( asc . size ( ) & 3 ) ) ) ;
30-
31- let u24 : number ;
32- let binary = "" ;
33- let r1 : number ;
34- let r2 : number ;
35-
36- for ( let i = 0 ; i < asc . size ( ) ; i ++ ) {
37- u24 =
38- ( b64Table [ string . byte ( asc , i ++ ) [ 0 ] ] << 18 ) |
39- ( b64Table [ string . byte ( asc , i ++ ) [ 0 ] ] << 12 ) |
40- ( ( r1 = b64Table [ string . byte ( asc , i ++ ) [ 0 ] ] ) << 6 ) |
41- ( r2 = b64Table [ string . byte ( asc , i ++ ) [ 0 ] ] ) ;
42- binary +=
43- r1 === 64
44- ? fromCharCode ( ( u24 >> 16 ) & 255 )
45- : r2 === 64
46- ? fromCharCode ( ( u24 >> 16 ) & 255 , ( u24 >> 8 ) & 255 )
47- : fromCharCode ( ( u24 >> 16 ) & 255 , ( u24 >> 8 ) & 255 , u24 & 255 ) ;
48- }
49-
50- return binary ;
51- } ;
6+ const BASE64_CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
527
538// Adapted from https://gist.github.com/jonleighton/958841
54- export function atob ( buf : number [ ] ) : string {
9+ export function encode ( buf : number [ ] ) : string {
5510 let base64 = "" ;
56- const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
5711
5812 const byteLength = buf . size ( ) ;
5913 const byteRemainder = byteLength % 3 ;
@@ -75,10 +29,10 @@ export function atob(buf: number[]): string {
7529
7630 // Convert the raw binary segments to the appropriate ASCII encoding
7731 base64 +=
78- string . char ( string . byte ( encodings , a + 1 ) [ 0 ] ) +
79- string . char ( string . byte ( encodings , b + 1 ) [ 0 ] ) +
80- string . char ( string . byte ( encodings , c + 1 ) [ 0 ] ) +
81- string . char ( string . byte ( encodings , d + 1 ) [ 0 ] ) ;
32+ string . char ( string . byte ( BASE64_CHAR , a + 1 ) [ 0 ] ) +
33+ string . char ( string . byte ( BASE64_CHAR , b + 1 ) [ 0 ] ) +
34+ string . char ( string . byte ( BASE64_CHAR , c + 1 ) [ 0 ] ) +
35+ string . char ( string . byte ( BASE64_CHAR , d + 1 ) [ 0 ] ) ;
8236 }
8337
8438 // Deal with the remaining bytes and padding
@@ -90,7 +44,7 @@ export function atob(buf: number[]): string {
9044 // Set the 4 least significant bits to zero
9145 b = ( chunk & 3 ) << 4 ;
9246
93- base64 += string . byte ( encodings , a ) [ 0 ] + string . byte ( encodings , b ) [ 0 ] + "==" ;
47+ base64 += string . byte ( BASE64_CHAR , a ) [ 0 ] + string . byte ( BASE64_CHAR , b ) [ 0 ] + "==" ;
9448 } else if ( byteRemainder === 2 ) {
9549 chunk = ( buf [ mainLength ] << 8 ) | buf [ mainLength + 1 ] ;
9650
@@ -101,11 +55,50 @@ export function atob(buf: number[]): string {
10155 c = ( chunk & 15 ) << 2 ;
10256
10357 base64 +=
104- string . char ( string . byte ( encodings , a + 1 ) [ 0 ] ) +
105- string . char ( string . byte ( encodings , b + 1 ) [ 0 ] ) +
106- string . char ( string . byte ( encodings , c + 1 ) [ 0 ] ) +
58+ string . char ( string . byte ( BASE64_CHAR , a + 1 ) [ 0 ] ) +
59+ string . char ( string . byte ( BASE64_CHAR , b + 1 ) [ 0 ] ) +
60+ string . char ( string . byte ( BASE64_CHAR , c + 1 ) [ 0 ] ) +
10761 "=" ;
10862 }
10963
11064 return base64 ;
11165}
66+
67+ // FIXME: Ideally, you'd want to use bit math and mask off bytes and stuff,
68+ // but I'm lazy, so this logic uses string manipulation instead
69+ export function decode ( base64 : string ) : number [ ] {
70+ // Strip padding from base64
71+ base64 = base64 . split ( "=" ) [ 0 ] . gsub ( "%s" , "" ) [ 0 ] ;
72+
73+ // Convert base64 chars to lookup table offsets
74+ const chars = [ ] ;
75+ for ( let i = 1 ; i <= base64 . size ( ) ; i ++ ) {
76+ const char = getCharAt ( base64 , i ) ;
77+ const [ pos ] = string . find ( BASE64_CHAR , char ) ;
78+
79+ pos !== undefined ? chars . push ( pos - 1 ) : error ( "invalid base64 data" ) ;
80+ }
81+
82+ // Convert offsets to 6 bit binary numbers
83+ const bin = chars . map ( toBinary ) ;
84+
85+ // Combine all binary numbers into one
86+ let combinedBin = "" ;
87+ bin . forEach ( ( b ) => ( combinedBin += b ) ) ;
88+
89+ // Split the combined binary number into smaller ones of 8 bits each
90+ const intermediaryBin = [ ] ;
91+ while ( combinedBin . size ( ) > 0 ) {
92+ intermediaryBin . push ( string . sub ( combinedBin , 1 , 8 ) ) ;
93+ combinedBin = string . sub ( combinedBin , 9 , combinedBin . size ( ) ) ;
94+ }
95+
96+ // Convert each individual 8 bit binary number to a base 10 integer
97+ const decoded = [ ] ;
98+ for ( let i = 0 ; i < intermediaryBin . size ( ) - 1 ; i ++ ) {
99+ const byte = tonumber ( intermediaryBin [ i ] , 2 ) ;
100+ decoded . push ( byte !== undefined ? byte : error ( "got invalid byte while decoding base64" ) ) ;
101+ }
102+
103+ return decoded ;
104+ }
0 commit comments