Asm and Fire Code ีอออออออออออออออออออออออออออออออธ ณ W E L C O M E ณ ณ To the VGA Trainer Program ณ ณ ณ By ณ ณ ณ DENTHOR of ASPHYXIA ณ ณ ณ ิอออออออออออออออออออออออออออออออพ ณ ณ ฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤู ณ ฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤฤู --==[ PART 19 ]==-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= � Introduction Hi there. As promised in Tut 18, this trainer is on assembler. For those people who already know assembler quite well, this tut is also on the flame effect. Okay, here is the total list of ways to get my trainers : http://goth.vironix.co.za/~denthor (WWW) ftp.eng.ufl.edu pub/msdos/demos/code/graph/tutor (FTP) denthor@beastie.cs.und.ac.za Subject : request-list (EMAIL) As well as the BBS numbers shown at the end ... I will add a few ftp sits to that list (x2ftp.oulu.fi etc.) Tut 20? How about 3d shading, hidden surface removal etc? Mail me :) If you would like to contact me, or the team, there are many ways you can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail on the ASPHYXIA BBS. 2) Write to : Grant Smith P.O.Box 270 Kloof 3640 Natal South Africa 3) Call me (Grant Smith) at (031) 73 2129 (leave a message if you call during varsity). Call +27-31-73-2129 if you call from outside South Africa. (It's YOUR phone bill ;-)) 4) Write to denthor@beastie.cs.und.ac.za in E-Mail. 5) Write to asphyxia@beastie.cs.und.ac.za to get to all of us at once. NB : If you are a representative of a company or BBS, and want ASPHYXIA to do you a demo, leave mail to me; we can discuss it. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling quite lonely and want to meet/help out/exchange code with other demo groups. What do you have to lose? Leave a message here and we can work out how to transfer it. We really want to hear from you! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= � Assembler - the short version Okay, there are many assembler trainers out there, many of which are probably better then this one. I will focus on the areas of assembler that I find important ... if you want more, go buy a book (go for the Michael Abrash ones), or scour the 'net for others. First, let us start off with the basic set up of an assembler program. DOSSEG This tells your assembler program to order your segments in the same manner that high level languages do. .MODEL can be : Tiny Code + Data < 64k (Can be made a COM file) Small Code < 64k Data < 64k Medium Code > 64k Data < 64k Compact Code < 64k Data > 64k Large Code > 64k Data > 64k Huge Arrays > 64k .286 Enable 286 instructions ... can be .386 ; .386P etc. .STACK will be the size of your stack. I usually use 200h .DATA Tells the program that the data is about to follow. (Everything after this will be placed in the data segment) .CODE Tells the program that the code is about to follow. (Everything after this will be placed in the code segment) START : Tells the program that this is where the code begins. END START Tells the program that this is where the code ends. To compile and run an assembler file, we run tasm bob tlink bob I personally use tasm, you will have to find out how your assembler works. Now, if we ran the above file as follows : DOSSEG .MODEL SMALL .286 .STACK 200h .DATA .CODE START END START You would think that is would just exit to dos immediately, right? Wrong. You have to specifically give dos back control, by doing the following : START mov ax,4c00h int 21h END START Now if you compiled it, it would run and do nothing. Okay, let us kick off with registers. Firstly : A bit is a value that is either 1 or 0 This is obviously quite limited, but if we start counting in them, we can get larger numbers. Counting with ones and zeros is known as binary, and we call it base 2. Counting in normal decimal is known as base 10, and counting in hexidecimal is known as base 16. Base 2 (Binary) Base 10 (Decimal) Base 16 (Hexidecimal) 0 0 0 1 1 1 10 2 2 11 3 3 100 4 4 101 5 5 110 6 6 111 7 7 1000 8 8 1001 9 9 1010 10 A 1011 11 B 1100 12 C 1101 13 D 1110 14 E 1111 15 F As you can see, you need four bits to count up to 15, and we call this a nibble. With eight bits, we can count up to 255, and we call this a byte. With sixteen bits, we can count up to 65535, and we call this a word. With thirty two bits, we can count up to lots, and we call this a double word. :) A quick note : Converting from binary to hex is actually quite easy. You break up the binary into groups of four bits, starting on the right, and convers these groups of four to hex. 1010 0010 1111 0001 = A 2 F 1 Converting to decimal is a bit more difficult. What you do, is you multiply each number by it's base to the power of it's index ... A2F1 hex = (A*16^3) + (2*16^2) + (F*16^1) + (1*16^0) = (10*4096) + (2*256) + (15*16) + (1) = 40960 + 512 + 240 + 1 = 41713 decimal The same system can be used for binary. To convert from decimal to another base, you divide the decimal value by the desired base, keeping a note of the remainders, and then reading the results backwards. 16 | 41713 16 | 2607 r 1 (41713 / 16 = 2607 r 1) 16 | 162 r F (2607 / 16 = 162 r 15) 16 | 10 r 2 (162 / 16 = 10 r 2) | 0 r A (10 / 16 = 0 r 10) Read the remainders bacckwards, our number is : A2F1 hex. Again, the same method can be used for binary. The reason why hex is popular is obvious ... using bits, it is impossible to get a reasonable base 10 (decimal) system going, and binary get's unwieldly at high values. Don't worry too much though : most assemblers (like Tasm) will convert all your decimal values to hex for you. You have four general purpose registers : AX, BX, CX and DX Think of them as variables that you will always have. On a 286, these registers are 16 bytes long, or one word. As you know, a word consists of two bytes, and in assembler you can access these bytes individualy. They are separated into high bytes and low bytes per word. High Byte | Low Byte 0000 0000 | 0000 0000 bits [--------Word-------] The method of access is easy. The high byte of AX is AH, and the low byte is AL ... you can also access BH, BL, CH, CL, DH and DL. A 386 has extended registers : EAX, EBX, ECX, EDX ... you can access the lower word normally (as AX, with bytes AH and AL), but you cannot access the high word directly ... you must ror EAX,16 (rotate the binary value through 16 bits), after which the high word and low word swap ... do it again to return them. Acessing EAX as a whole is no problem ... mov eax,10 ; add eax,ebx ... these are all vaild. Next come segments. As you have probably heard, computer memory is divided into various 64k segments (note : 64k = 65536 bytes, sound familiar?) A segment register points to which segment you are looking at. An offset register points to how far into that segment you are looking. One way of looking at it is like looking at a 2d array ... the segments are your columns and your offsets are your rows. Segments and offsets are displayed as Segment:Offset ... so $a000:50 would mean the fiftieth byte in segment $a000. The segment registers are ES, DS, SS and CS. A 386 also has FS an GS. These values are words (0-65535), and you cannot access the high or low bytes separately. CS points to you your code segment, and usually if you touch this your program will explode. SS points to your stack segment, again, this baby is dangerous. DS points to your data segment, and can be altered, if you put it back after you use it, and don't use any global variables while it is altered. ES is your extra segment, and you can do what you want with it. The offset registers are DI, SI, IP, SP, BP. Offset registers are generally asscociated with specific segment registers, as follows : ES:DI DS:SI CS:IP SS:SP ... On a 286, BX can be used instead of the above offset registers, and on a 386, any register may be used. DS:BX is therefore valid. If you create a global variable (let's say bob), when you access that variable, the compiler will actually look for it in the data segment. This means that the statement : ax = bob could be ax = ds:[15] A quick note : A value may be signed or unsigned. An unsigned word has a range from 0 to 65535. A signed word is called an integer and has a range -32768 to 32767. With a signed value, if the leftmost bit is equal to 1, the value is in the negative. Next, let us have a look at the stack. Let us say that you want to save the value in ax, use ax to do other things, then restore it to it's origional value afterwards. This is done by utilising the stack. Have a look at the following code : mov ax, 50 ; ax is equal to 50 push ax ; push ax onto the stack mov ax, 27 ; ax is equal to 27 pop ax ; pop ax off the stack At this point, ax is equal to 50. Remember we defined the stack to be 200h further up? This is part of the reason we have it. When you push a value onto the stack, that value is recorded on the stack heap (referenced by SS:SP, SP is incremented) When you pop a value off the stack, the value is placed into the variable you are poping it back in to, SP is decremented and so forth. Note that the computer does not care what you pop the value back in to ... mov ax, 50 push ax pop bx Would set the values of both ax and bx to 50. (there are faster ways of doing this, pushing and poping are fairly fast though) push ax push bx pop ax pop bx would swap the values of ax and bx. As you can see, to pop the values back in to the origional variables, you must pop them back in the opposite direction to which you pushed them. push ax push bx push cx pop cx pop bx pop ax would result in no change for any of the registers. When a procedure is called, all the parameters for that procedure are pushed onto the stack. These can actually be read right off the stack, if you want to. As you have already seen, the mov command moves a value... mov , Note that dest and source must be the same number of bits long... mov ax, dl would not work, and neither would mov cl,bx However, mov cx,dx mov ax,50 mov es,ax are all valid. Shl I have explained before, it is where all the bits in a register are shifted one to the left and a zero added on to the right. This is the eqivalent of multiplying the value by two. Shr works in the opposite direction. Rol does the same, except that the bit that is removed from the left is replaced on the right hand side. Ror works in the opposite direction. div divides the value in ax by value and returns the result in al if value is a byte, placing the remainder in ah. If value is a word, the double word DX:AX is divided by value, the result being placed in ax and the remainder in dx. Note that this only works for unsigned values. idiv does the same as above, but for signed variables. mul If value is a byte, al is multiplied by value and the result is stored in ax. If value is a word, ax is multiplied by value and the result is stored in the double word DX:AX imul does the same as above, but for signed variables. The j* commands are fairly simple : if a condition is met, jump to a certain lable. jz Jump if zero ja Jump above (unsigned) jg Jump greater (signed) and so forth. An example ... cmp ax,50 ; Compare ax to 50 je @Equal ; If they are equal, jump to label @equal call MyProc Runs procedure MyProc and then returns to the next line of code. Procedures are declared as follows : MyProc proc near ret ; Must be here to return from where it was called MyProc endp Variables are also easy : bob db 50 creates a variable bob, a byte, with an initial value of 50. bob2 dw 50 creates a variable bob2, a word, with an initial value of 50. bob3 db 1,2,3,4,5,65,23 creates bob3, an array of 7 bytes. bob4 db 100 dup (?) creates bob4, an array of 100 bytes, with no starting value. Go back and look at tut 7 for a whole lot more assembler commands, and get some sort of reference guide to help you out with others. I personally use the Norton Guides help file to program assembler. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= � Fire Routines To demonstrate how to write an assembler program, we will write a fire routine in 100% assembler. The theory is simple... Set the pallette to go from white to yellow to red to blue to black. Create a 2d array representing the screen on the computer. Place high values at the bottom of the array (screen) for each element, do the following : Take the average of the four elements under it * Current element 123 4 Other elements Get the average of the four elements, and place the result in the current element. Repeat Easy, no? I first saw a fire routine in the Iguana demo, and I just had to do one ;) ... it looks very effective. With the sample file, I have created a batch file, make.bat ... it basically says : tasm fire tlink fire So to build and run the fire program, type : make fire The source file is commented quite well, so there shouldn't be any problems. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= � In closing As you can see, the sample program is in 100% assembler. For the next tut I will return to Pascal, and hopefully your new found assembler skills will help you there too. Byeeeee.... - Denthor The following are official ASPHYXIA distribution sites : ษออออออออออออออออออออออออออหออออออออออออออออหอออออป บBBS Name บTelephone No. บOpen บ ฬออออออออออออออออออออออออออฮออออออออออออออออฮอออออน บASPHYXIA BBS #1 บ+27-31-765-5312 บALL บ บASPHYXIA BBS #2 บ+27-31-765-6293 บALL บ บC-Spam BBS บ410-531-5886 บALL บ บPOP! บ+27-12-661-1257 บALL บ บSoul Asylum บ+358-0-5055041 บALL บ บWasted Image บ407-838-4525 บALL บ บReckless Life บ351-01-716 67 58บALL บ บMach 5 BBS บ+1 319-355-7336 บALL บ บHouse of Horror บ+1 513-734-6470 บALL บ บZero Level บ+39 6-810-9934 บALL บ ศออออออออออออออออออออออออออสออออออออออออออออสอออออผ Leave me mail if you want to become an official Asphyxia BBS distribution site. USES crt,dos; VAR source,dest:string; function Exist(FileName: String): Boolean; { Boolean function that returns True if the file exists;otherwise, it returns False. Closes the file if it exists. } var F: file; begin {$I-} Assign(F, FileName); FileMode := 0; { Set file access to read only } Reset(F); Close(F); {$I+} Exist := (IOResult = 0) and (FileName <> ''); end; Procedure GetNames; BEGIN writeln; writeln ('Conversion from binary to db...'); writeln; Write ('Enter name of binary file --> '); Readln (source); if not exist (source) then BEGIN Writeln (source,' not found! Exiting ...'); writeln; halt; END; Write ('Enter name of desination text file --> '); Readln (dest); writeln; writeln ('Working ...'); END; Procedure Convert; VAR f:text; f2:file; loop1,loop2,loop3:integer; no:byte; msg:string; dir:dirstr; name:namestr; ext:extstr; BEGIN assign (f2,source); reset (f2,1); assign (f,dest); rewrite (f); fsplit (dest, dir, name, ext); while length (name)<8 do name:=name+' '; write (f,name,' db '); loop1:=0; loop3:=filesize (f2); loop2:=0; While not EOF (f2) do BEGIN blockread (f2,no,1); str (no:3,msg); write (f,msg); inc (loop1); inc (loop2); gotoxy (1,wherey); write (loop2,'/',loop3); if (loop1=16) and (not (eof(f2))) then BEGIN loop1:=0; writeln(f); write (f,' db '); END else if not (eof(f2)) then write (f,','); END; close (f); close (f2); writeln; writeln ('Done.'); END; BEGIN Getnames; Convert; END. DOSSEG ; Order the program to order it's segments in the ; same way that high level languages do. .MODEL SMALL ; Different models : ; Tiny : Code + Data < 64k (can be made a COM file) ; Small : Code < 64k ; Data < 64k ; Medium : Code > 64k ; Data < 64k ; Compact: Code < 64k ; Data > 64k ; Large : Code > 64k ; Data > 64k ; Huge : Arrays > 64k .286 ; Enable 286 instructions .STACK 200h .DATA ; Tells compiler that data is to follow. endmessage db "This was the Fire Effect - denthor@beastie.cs.und.ac.za$" ; This is our end message. Must be terminated with a "$" xsize = 80 ; The x-width of our screen in pixels ysize = 112 ; The y-height of our screen in pixels, plus a few extra randseed dw ? ; any number for randomness pallette db 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 8, 0, 0, 9, 0, 0,10 db 2, 0,10, 4, 0, 9, 6, 0, 9, 8, 0, 8,10, 0, 7,12, 0, 7,14, 0, 6,16, 0, 5 db 18, 0, 5,20, 0, 4,22, 0, 4,24, 0, 3,26, 0, 2,28, 0, 2,30, 0, 1,32, 0, 0 db 32, 0, 0,33, 0, 0,34, 0, 0,35, 0, 0,36, 0, 0,36, 0, 0,37, 0, 0,38, 0, 0 db 39, 0, 0,40, 0, 0,40, 0, 0,41, 0, 0,42, 0, 0,43, 0, 0,44, 0, 0,45, 0, 0 db 46, 1, 0,47, 1, 0,48, 2, 0,49, 2, 0,50, 3, 0,51, 3, 0,52, 4, 0,53, 4, 0 db 54, 5, 0,55, 5, 0,56, 6, 0,57, 6, 0,58, 7, 0,59, 7, 0,60, 8, 0,61, 8, 0 db 63, 9, 0,63, 9, 0,63,10, 0,63,10, 0,63,11, 0,63,11, 0,63,12, 0,63,12, 0 db 63,13, 0,63,13, 0,63,14, 0,63,14, 0,63,15, 0,63,15, 0,63,16, 0,63,16, 0 db 63,17, 0,63,17, 0,63,18, 0,63,18, 0,63,19, 0,63,19, 0,63,20, 0,63,20, 0 db 63,21, 0,63,21, 0,63,22, 0,63,22, 0,63,23, 0,63,24, 0,63,24, 0,63,25, 0 db 63,25, 0,63,26, 0,63,26, 0,63,27, 0,63,27, 0,63,28, 0,63,28, 0,63,29, 0 db 63,29, 0,63,30, 0,63,30, 0,63,31, 0,63,31, 0,63,32, 0,63,32, 0,63,33, 0 db 63,33, 0,63,34, 0,63,34, 0,63,35, 0,63,35, 0,63,36, 0,63,36, 0,63,37, 0 db 63,38, 0,63,38, 0,63,39, 0,63,39, 0,63,40, 0,63,40, 0,63,41, 0,63,41, 0 db 63,42, 0,63,42, 0,63,43, 0,63,43, 0,63,44, 0,63,44, 0,63,45, 0,63,45, 0 db 63,46, 0,63,46, 0,63,47, 0,63,47, 0,63,48, 0,63,48, 0,63,49, 0,63,49, 0 db 63,50, 0,63,50, 0,63,51, 0,63,52, 0,63,52, 0,63,52, 0,63,52, 0,63,52, 0 db 63,53, 0,63,53, 0,63,53, 0,63,53, 0,63,54, 0,63,54, 0,63,54, 0,63,54, 0 db 63,54, 0,63,55, 0,63,55, 0,63,55, 0,63,55, 0,63,56, 0,63,56, 0,63,56, 0 db 63,56, 0,63,57, 0,63,57, 0,63,57, 0,63,57, 0,63,57, 0,63,58, 0,63,58, 0 db 63,58, 0,63,58, 0,63,59, 0,63,59, 0,63,59, 0,63,59, 0,63,60, 0,63,60, 0 db 63,60, 0,63,60, 0,63,60, 0,63,61, 0,63,61, 0,63,61, 0,63,61, 0,63,62, 0 db 63,62, 0,63,62, 0,63,62, 0,63,63, 0,63,63, 1,63,63, 2,63,63, 3,63,63, 4 db 63,63, 5,63,63, 6,63,63, 7,63,63, 8,63,63, 9,63,63,10,63,63,10,63,63,11 db 63,63,12,63,63,13,63,63,14,63,63,15,63,63,16,63,63,17,63,63,18,63,63,19 db 63,63,20,63,63,21,63,63,21,63,63,22,63,63,23,63,63,24,63,63,25,63,63,26 db 63,63,27,63,63,28,63,63,29,63,63,30,63,63,31,63,63,31,63,63,32,63,63,33 db 63,63,34,63,63,35,63,63,36,63,63,37,63,63,38,63,63,39,63,63,40,63,63,41 db 63,63,42,63,63,42,63,63,43,63,63,44,63,63,45,63,63,46,63,63,47,63,63,48 db 63,63,49,63,63,50,63,63,51,63,63,52,63,63,52,63,63,53,63,63,54,63,63,55 db 63,63,56,63,63,57,63,63,58,63,63,59,63,63,60,63,63,61,63,63,62,63,63,63 ; Our pallette ... generated elsewhere and brought in screen db xsize*ysize dup (?) ; Virtual screen .CODE ; Tells compiler that code is to follow. Random proc near mov ax,[RandSeed] mov dx,8405h mul dx ; ax*dx with result in dx:ax inc ax mov [RandSeed],ax ret ; Return back to main section Random endp SetUpScreen proc near mov ax,0013h int 10h ; Get into 320x200x256 MCGA mode. mov ax,0a000h mov es,ax xor di,di ; ES:DI is now pointing to the top left hand of the screen cli cld mov dx,3c4h mov ax,604h ; Enter unchained mode out dx,ax mov ax,0F02h ; All planes out dx,ax xor ax,ax mov cx,32767 rep stosw ; Clear the screen mov dx,3D4h mov ax,14h ; Disable dword mode out dx,ax mov ax,0E317h ; Enable byte mode. out dx,ax out dx,ax mov ax,00409h ; Cell height out dx,ax mov si, offset [pallette] mov dx, 3c8h ; Pallette write register mov al, 0 out dx, al ; Start at color zero inc dx mov cx, 768 @PalLoop : outsb ; Write value to port; inc DI dec cx jnz @PalLoop ret SetUpScreen endp START: mov ax,@DATA mov ds,ax ; Moves the segment of the data into DS. call SetUpScreen mov randseed,1234h mov si,offset [screen] mov cx,xsize*ysize xor ax,ax rep stosb ; Clear our virtual screen. @MainLoop : ; ; This next bit puts either 0 or 255 along the very ; bottom row of our virtual screen. ; mov si,offset [screen] add si,xsize*ysize sub si,xsize ; si=ofs(screen)+xsize*ysize-xsize ie. start of last row mov cx,xsize ; loop the entire last row xor dx,dx @Newline : call random mov ds:[si],dl inc si dec cx jnz @Newline ; ; This "softens" the values in the virtual array, ; creating a fire effect. ; mov cx,xsize*ysize sub cx,xsize mov si,offset [screen] add si,xsize @FileLoop : xor ax,ax mov al,ds:[si] add al,ds:[si+1] adc ah,0 add al,ds:[si-1] adc ah,0 add al,ds:[si+xsize] adc ah,0 shr ax,2 jz @zero dec ax @Zero : ; al = ((pos)+(pos+1)+(pos-1)+(pos+80))/4 - 1 mov ds:[si-xsize],al inc si dec cx jnz @FileLoop ; ; This dumps our virtual screen to the VGA screen. ; mov dx, 3dah l1: in al, dx and al, 8h jnz l1 l2: in al, dx and al, 8h jz l2 mov cx,xsize*ysize shr cx,1 mov si,offset [screen] xor di,di rep movsw mov ah,01 int 16h ; Has a key been pressed? jz @MainLoop ; If not, carry on. mov ah,0 int 16h ;get a key, returned in AX ;this is just to clear the keyboard buffer of the key ;press. mov ax,0003h int 10h ; Get into 80x25 text mode mov dx,offset [endmessage] mov ah,09h int 21h ; Dos interrupt 21, subfunction 09 ... print string. ; DS:DX must be pointing to start of string. mov ax,4c00h ; This function exits the program int 21h ; and returns control to DOS. END START fire db 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0 db 0, 8, 0, 0, 9, 0, 0, 10, 2, 0, 10, 4, 0, 9, 6, 0 db 9, 8, 0, 8, 10, 0, 7, 12, 0, 7, 14, 0, 6, 16, 0, 5 db 18, 0, 5, 20, 0, 4, 22, 0, 4, 24, 0, 3, 26, 0, 2, 28 db 0, 2, 30, 0, 1, 32, 0, 0, 32, 0, 0, 33, 0, 0, 34, 0 db 0, 35, 0, 0, 36, 0, 0, 36, 0, 0, 37, 0, 0, 38, 0, 0 db 39, 0, 0, 40, 0, 0, 40, 0, 0, 41, 0, 0, 42, 0, 0, 43 db 0, 0, 44, 0, 0, 45, 0, 0, 46, 1, 0, 47, 1, 0, 48, 2 db 0, 49, 2, 0, 50, 3, 0, 51, 3, 0, 52, 4, 0, 53, 4, 0 db 54, 5, 0, 55, 5, 0, 56, 6, 0, 57, 6, 0, 58, 7, 0, 59 db 7, 0, 60, 8, 0, 61, 8, 0, 63, 9, 0, 63, 9, 0, 63, 10 db 0, 63, 10, 0, 63, 11, 0, 63, 11, 0, 63, 12, 0, 63, 12, 0 db 63, 13, 0, 63, 13, 0, 63, 14, 0, 63, 14, 0, 63, 15, 0, 63 db 15, 0, 63, 16, 0, 63, 16, 0, 63, 17, 0, 63, 17, 0, 63, 18 db 0, 63, 18, 0, 63, 19, 0, 63, 19, 0, 63, 20, 0, 63, 20, 0 db 63, 21, 0, 63, 21, 0, 63, 22, 0, 63, 22, 0, 63, 23, 0, 63 db 24, 0, 63, 24, 0, 63, 25, 0, 63, 25, 0, 63, 26, 0, 63, 26 db 0, 63, 27, 0, 63, 27, 0, 63, 28, 0, 63, 28, 0, 63, 29, 0 db 63, 29, 0, 63, 30, 0, 63, 30, 0, 63, 31, 0, 63, 31, 0, 63 db 32, 0, 63, 32, 0, 63, 33, 0, 63, 33, 0, 63, 34, 0, 63, 34 db 0, 63, 35, 0, 63, 35, 0, 63, 36, 0, 63, 36, 0, 63, 37, 0 db 63, 38, 0, 63, 38, 0, 63, 39, 0, 63, 39, 0, 63, 40, 0, 63 db 40, 0, 63, 41, 0, 63, 41, 0, 63, 42, 0, 63, 42, 0, 63, 43 db 0, 63, 43, 0, 63, 44, 0, 63, 44, 0, 63, 45, 0, 63, 45, 0 db 63, 46, 0, 63, 46, 0, 63, 47, 0, 63, 47, 0, 63, 48, 0, 63 db 48, 0, 63, 49, 0, 63, 49, 0, 63, 50, 0, 63, 50, 0, 63, 51 db 0, 63, 52, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0 db 63, 53, 0, 63, 53, 0, 63, 53, 0, 63, 53, 0, 63, 54, 0, 63 db 54, 0, 63, 54, 0, 63, 54, 0, 63, 54, 0, 63, 55, 0, 63, 55 db 0, 63, 55, 0, 63, 55, 0, 63, 56, 0, 63, 56, 0, 63, 56, 0 db 63, 56, 0, 63, 57, 0, 63, 57, 0, 63, 57, 0, 63, 57, 0, 63 db 57, 0, 63, 58, 0, 63, 58, 0, 63, 58, 0, 63, 58, 0, 63, 59 db 0, 63, 59, 0, 63, 59, 0, 63, 59, 0, 63, 60, 0, 63, 60, 0 db 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 61, 0, 63, 61, 0, 63 db 61, 0, 63, 61, 0, 63, 62, 0, 63, 62, 0, 63, 62, 0, 63, 62 db 0, 63, 63, 0, 63, 63, 1, 63, 63, 2, 63, 63, 3, 63, 63, 4 db 63, 63, 5, 63, 63, 6, 63, 63, 7, 63, 63, 8, 63, 63, 9, 63 db 63, 10, 63, 63, 10, 63, 63, 11, 63, 63, 12, 63, 63, 13, 63, 63 db 14, 63, 63, 15, 63, 63, 16, 63, 63, 17, 63, 63, 18, 63, 63, 19 db 63, 63, 20, 63, 63, 21, 63, 63, 21, 63, 63, 22, 63, 63, 23, 63 db 63, 24, 63, 63, 25, 63, 63, 26, 63, 63, 27, 63, 63, 28, 63, 63 db 29, 63, 63, 30, 63, 63, 31, 63, 63, 31, 63, 63, 32, 63, 63, 33 db 63, 63, 34, 63, 63, 35, 63, 63, 36, 63, 63, 37, 63, 63, 38, 63 db 63, 39, 63, 63, 40, 63, 63, 41, 63, 63, 42, 63, 63, 42, 63, 63 db 43, 63, 63, 44, 63, 63, 45, 63, 63, 46, 63, 63, 47, 63, 63, 48 db 63, 63, 49, 63, 63, 50, 63, 63, 51, 63, 63, 52, 63, 63, 52, 63 db 63, 53, 63, 63, 54, 63, 63, 55, 63, 63, 56, 63, 63, 57, 63, 63 db 58, 63, 63, 59, 63, 63, 60, 63, 63, 61, 63, 63, 62, 63, 63, 63