Tag: python

  • chastext for DOS

    I wrote a DOS version of the chastext program for simple search and replace. It does have some limitations because command line arguments are handled very different in DOS than they are in Linux. I can’t simple put quotes around two words to have them count as one argument like I can in Linux.

    Aside from that, it seems to work. I can replace individual words in a text file with a different word. I will have a demo video up soon but see the post about the Linux version in the Linux forum to get the basic idea of what it should do.

    I am not trying to recreate sed or awk but a simple find/replace is a worthwhile project for learning something new after I have mastered my chastehex and chastecmp programs. I can manipulate binary files flawlessly because they are predictable so now I am testing my limits on text based processing.

    main.asm

    org 100h     ;DOS programs start at this address
    
    mov word [radix],16 ; can choose radix for integer output!
    
    mov ch,0     ;zero ch (upper half of cx)
    mov cl,[80h] ;load length in bytes of the command string
    cmp cx,0
    jnz args_exist
    
    mov ax,help    ;if no arguments were given, show a help message
    call putstring
    jmp ending     ;and end the program because there is nothing to do
    
    args_exist:
    
    ;Point bx to the beginning of arg string
    ;however, this always contains a space
    mov bx,81h
    
    skip_start_spaces:
    cmp byte [bx],' ' ;is this byte a space?
    jnz skip_start_spaces_end ;if not, we are done skipping spaces
    inc bx ;otherwise, go to next char
    dec cx ;but subtract 1 from character count
    jmp skip_start_spaces
    skip_start_spaces_end:
    
    mov [arg_string_index],bx ; save the location of the first non space in the arg string
    
    ;find the end of the string based on length
    mov ax,bx
    add ax,cx
    mov [arg_string_end],ax ;now we know where the string ends.
    
    ;now bx points to the first non space character in the arguments passed to the DOS program
    ;and we know that [arg_string_end] is where it ends
    
    ;the next step is to filter the arguments into separate zero terminated strings
    ;each space will be changed to a zero (normally)
    ;but we also need to account for spaces inside quotes that are considered part of the string
    ;Linux handles this normally but DOS needs me to write the code to mimic this behavior
    ;because the program needs to function identically for DOS or Linux
    
    mov cl,' ' ;set the default filter character (argument terminator) to a space
    mov ch,0   ;are we currently checking spaces 0 or quote characters 1 as terminators?
    
    ;this loop is the new and improved argument filter
    ;it keeps track of whether we are inside or outside a quote
    ;and also which type of quote started the quote
    ;the actual quote marks are not part of the string unless they
    ;are the opposite quote type than what started the string
    ;The important thing is that spaces can exist inside of quoted strings
    ;as one argument rather than each new word being a new argument
    ;could be important for filenames containing spaces, etc.
    
    argument_filter:
    
    cmp bx,[arg_string_end] ;are we at the end of the arg string?
    jz argument_filter_end       ;if yes, stop the filter and terminate with zero
    
    cmp ch,1       ;are we inside a quoted string?
    jz quote_check ;if yes, don't do anything to the spaces
    
    cmp byte[bx],cl ;compare the byte at address bx to the string terminator
    jnz ignore_char ;if it is not the same, we ignore it
    mov byte[bx],0  ;but if it matches, change it to a zero
    ignore_char:
    
    cmp byte [bx],0x22 ;is this a double quote -> "
    jz start_quote
    cmp byte [bx],0x27 ;is this a single quote -> '
    jz start_quote
    jmp quote_no ;it was not a quote
    
    start_quote:
    
    mov ch,1    ;set ch to 1 to set that we are inside a quote now
    mov cl,[bx] ;save this quote type as the new terminator
    mov byte[bx],0 ;but delete the first quote with zero
    
    ;check for single or double quotes
    quote_check:
    
    cmp [bx],cl ;is this character the same type of quote that started this sub string?
    jnz quote_no ;if it is not, then skip to quote_no section
    
    ;but if it was matching, change this byte to zero
    ;and change cl back to a space
    mov cl,' ' ;cl is now a space
    mov ch,0   ;ch is 0 because now we have ended the quoted string
    mov byte[bx],0 ;delete the end quote with zero
    
    quote_no:
    
    inc bx ;go to the next character
    jmp argument_filter   ;jump back to the beginning of argument filter
    
    argument_filter_end:
    mov byte [bx],0 ;terminate the ending with a zero for safety
    
    ;special case!!!
    ;If the first argument passed began with a quoted string
    ;it would have been changed to a 0 instead. This requires us to add one to the
    ;starting argument string index
    mov bx,[arg_string_index]
    cmp byte[bx],0
    jnz first_argument_was_not_quote
    inc word[arg_string_index] ;add 1 so it points to the next byte before we process arguments
    first_argument_was_not_quote:
    
    
    
    ;now that the argument string is prepared, we will try to use the first argument as a filename to open
    
    mov ah,3Dh                ;call number for DOS open existing file
    mov al,0                  ;file access: 0=read,1=write,2=read+write
    mov dx,[arg_string_index] ;string address to interpret as filename
    int 21h                   ;DOS call to finalize open function
    
    mov [file_handle],ax ;save the file handle
    
    jc file_error ;if carry flag is set, we have an error, otherwise, file is open
    
    file_opened:
    
    mov ax,dx
    ;call putstring
    ;call putline
    jmp use_file ;skip past error message and start using the file
    
    ;this section prints error message and then ends the program if file error found
    
    file_error: ;prints error code2=file not found
    mov ax,dx
    call putstr_and_line
    mov ax,file_error_message
    call putstring
    mov ax,[file_handle]
    call putint
    jmp ending
    
    ;how we use the file depends on the number of arguments given
    ;if no arguments other than the filename exist, we do a regular hex dump
    ;otherwise we look for two more arguments: the search and replace strings
    
    use_file:
    
    call get_next_arg ;get address of next arg and return into ax register
    cmp ax,[arg_string_end] ;this time, if ax equals end of string, we hex dump and then end the program later
    jz textdump ;jump to hexdump section
    
    ;otherwise, we save the address at ax to our search string
    mov [string_search],ax
    ;call putstr_and_line
    
    
    call get_next_arg ;get address of next arg and return into ax register
    cmp ax,[arg_string_end] ;this time, if ax equals end of string, we hex dump and then end the program later
    jz textdump ;jump to hexdump section
    
    ;otherwise, we save the address at ax to our replacement string
    mov [string_replace],ax
    ;call putstr_and_line
    
    ;all other arguments that may exist after this are irrelevant
    
    textdump:
    
    ;we start the loop with a call to read exactly 1 byte
    
    mov ah,3Fh           ;call number for read function
    mov bx,[file_handle] ;store file handle to read from in bx
    mov cx,1             ;we are reading one byte
    mov dx,byte_array    ;store the bytes here
    int 21h
    
    ;call putint ;check the number of bytes read
    
    cmp ax,1        ;check to see if exactly 1 byte was read
    jz file_success ;if true, proceed to display
    ;mov ax,end_of_file
    ;call putstring
    jmp file_close ;otherwise close the file and end program after failure
    
    ; this point is reached if 1 byte was read from the file successfully
    file_success:
    
    ;first, check to see if there is a search string
    ;if there is a search string, skip the normal putchar
    cmp word[string_search],0 
    jnz putchar_skip
    
    ;but if there is not a search string
    ;we will print the last read character
    ;and then jump to the beginning of the textdump loop to print them until EOF
    mov al,[byte_array]
    call putchar
    jmp textdump
    
    putchar_skip:
    
    ;if search string doesn't exist, just jump and repeat the loop
    ;otherwise we continue into the section that compares the input with the search string
    
    mov bx,[string_search]
    
    mov al,[bx]
    mov ah,[byte_array]
    cmp al,ah ;compare the first character of search string with the byte read already
    jz search_start ; if they are equal, skip putchar and begin searching for the string
    
    ;otherwise, if they are not equal, just putchar the last byte read and repeat the loop
    mov al,[byte_array]
    call putchar
    jmp textdump
    
    search_start:
    mov ax,[string_search]
    call strlen ;get the length of the search string
    ;call putint_and_line ; print length of search string only for debugging
    
    ;attempt to read the length-1 bytes because the first one is already read into the byte array
    
    dec ax               ;subtract 1 from ax which holds our length of string
    
    mov dx,byte_array+1  ;store the bytes here
    mov cx,ax            ;we are reading this many bytes to have a string to compare
    mov bx,[file_handle] ;store file handle to read from in bx
    mov ah,3Fh           ;call number for read function
    int 21h
    
    ;do some math to calculate where the string should end
    
    mov bx,dx ;mov into bx the address of second byte in the string
    add bx,ax ;add ax (the return value of the number of characters read)
    mov byte [bx],0 ;terminate the string with zero
    
    mov si,[string_search]
    mov di,byte_array
    
    call strcmp ;compare these two strings
    
    cmp ax,0 ;test if they are the same (if ax returned zero)
    jnz normal_print ;if they are not a match print them unmodified and unquoted
    
    ;but if they are a match, then we either quote them
    ;or replace them if a replacement string is available
    
    cmp word[string_replace],0 ;check to see if a replacement string is available
    jz print_quotes ;if not, skip to the part where we just quote the strings that match
    
    ;otherwise, we will print the replacement string instead of the original!
    
    mov ax,[string_replace]
    call putstring ;print the string
    
    jmp normal_print_skip
    
    print_quotes:
    ;print quotes around matched string
    mov al,'"'
    call putchar
    
    mov ax,byte_array
    call putstring ;print the string
    
    mov al,'"'
    call putchar
    
    jmp normal_print_skip
    
    normal_print: ;print normal / unquoted because it doesn't match
    
    mov ax,byte_array
    call putstring ;print the string
    
    normal_print_skip:
    
    jmp textdump
    
    file_close:
    ;close the file if it is open
    mov ah,3Eh
    mov bx,[file_handle]
    int 21h
    
    ;debugging section I use just to test values
    ;call putline
    ;mov ax,[string_search]
    ;call putstr_and_line
    ;mov ax,[string_replace]
    ;call putstr_and_line
    
    
    ending:
    mov ax,4C00h ; Exit program
    int 21h
    
    ;the strlen and strcmp are named after the equivalent C functions
    ;but are written from scratch by me based on their expected behavior
    
    ;a function to get the length of string in ax and return the integer in ax
    
    strlen:
    
    mov bx,ax ; copy ax to bx. bx will be used as index to the string
    
    strlen_start: ; this loop finds the length of the string as part of the putstring function
    
    cmp [bx],byte 0 ; compare byte at address bx with 0
    jz strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc bx
    jmp strlen_start
    
    strlen_end:
    sub bx,ax ;subtract start pointer from current pointer to get length of string
    
    mov ax,bx ;copy the string length back to eax
    
    ret
    
    ;compare the string at si to the one at di
    
    strcmp:
    
    mov ax,0 ;this will be stay zero unless the strings are different
    
    strcmp_start:
    mov bl,[di]
    cmp bl,0
    jz strcmp_end
    mov bh,[si]
    cmp bh,0
    jz strcmp_end
    
    inc di
    inc si
    
    cmp bl,bh
    jz strcmp_start ;if they are the same, continue to next character
    
    inc ax ;if they were different, eax will be incremented and the function ends
    
    strcmp_end:
    ret
    
    ;function to move ahead to the next argument
    ;only works after the filter has been applied to turn all spaces into zeroes
    
    get_next_arg:
    mov bx,[arg_string_index] ;get address of current arg
    find_zero:
    cmp byte [bx],0
    jz found_zero
    inc bx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    ;once we have found a zero, check to make sure we are not at the end
    
    find_non_zero:
    cmp bx,[arg_string_end]
    jz arg_finish ;if bx is already at end, nothing left to find
    cmp byte [bx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc bx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_string_index],bx ; save this index to the variable
    mov ax,bx ;but also save it to ax register for use in printing or something else
    ret
    
    help db 'chastext by Chastity White Rose',0Dh,0Ah
    db '"cat" or "type" a file without changing it:',0Dh,0Ah,9,'chastext file',0Dh,0Ah
    db 'search for a string and quote it:',0Dh,0Ah,9,'chastext file search',0Dh,0Ah
    db 'replace string:',0Dh,0Ah,9,'chastext file search replace',0Dh,0Ah
    db 'Find or replace any string!',0Dh,0Ah,0
    
    ; About the chastelib variant
    
    ;instead of including chastelib16.asm as a header file
    ;I copy pasted it except that I excluded functions that were not used.
    ;Notably, the strint function is excluded because strint_32 is used instead
    
    ;start of chastelib
    
    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ;this is my best putstring function for DOS because it uses call 40h of interrupt 21h
    ;this means that it works in a similar way to my Linux Assembly code
    ;the plan is to make both my DOS and Linux functions identical except for the size of registers involved
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,1                   ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    ;this is the location in memory where digits are written to by the intstr function
    int_string db 16 dup '?' ;enough bytes to hold maximum size 16-bit binary integer
    int_string_end db 0 ;zero byte terminator for the integer string
    
    radix dw 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dw 8
    
    intstr:
    
    mov bx,int_string_end-1 ;find address of lowest digit(just before the newline 0Ah)
    mov cx,1
    
    digits_start:
    
    mov dx,0;
    div word [radix]
    cmp dx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add dx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub dx,10
    add dx,'A'
    
    save_digit:
    
    mov [bx],dl
    cmp ax,0
    jz intstr_end
    dec bx
    inc cx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp cx,[int_width]
    jnb end_zeros
    dec bx
    mov [bx],byte '0'
    inc cx
    jmp prefix_zeros
    end_zeros:
    
    mov ax,bx ; store string in ax for display later
    
    ret
    
    ;function to print string form of whatever integer is in ax
    ;The radix determines which number base the string form takes.
    ;Anything from 2 to 36 is a valid radix
    ;in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ;this function does not process anything by itself but calls the combination of my other
    ;functions in the order I intended them to be used.
    
    putint: 
    
    push ax
    push bx
    push cx
    push dx
    
    call intstr
    call putstring
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    ;the next utility functions simply print a space or a newline
    ;these help me save code when printing lots of things for debugging
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push ax
    mov ax,space
    call putstring
    pop ax
    ret
    
    putline:
    push ax
    mov ax,line
    call putstring
    pop ax
    ret
    
    ;a function for printing a single character that is the value of al
    
    char: db 0,0
    
    putchar:
    push ax
    mov [char],al
    mov ax,char
    call putstring
    pop ax
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    
    putint_and_space:
    call putint
    call putspace
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    
    putint_and_line:
    call putint
    call putline
    ret
    
    
    ;a small function just for the common operation
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    
    putstr_and_space:
    call putstring
    call putspace
    ret
    
    ;a small function just for the common operation
    ;printing an integer followed by a space
    ;this saves a few bytes in the assembled code
    
    putstr_and_line:
    call putstring
    call putline
    ret
    
    ;end of chastelib
    
    arg_string_index dw 0
    arg_string_end dw 0
    
    file_error_message db 'Could not open the file! Error number: ',0
    file_handle dw 0
    end_of_file db 'EOF',0
    
    ;where we will store data from the file
    bytes_read dw 0
    
    string_search dw 0 ; place to hold the search string pointer
    string_replace dw 0 ; place to hold the replacement string pointer
    
    byte_array db 0x80 dup 0
    
  • chastelib SDL2 extension

    I wrote an extension to chastelib which is really an entire font library on its own. The idea is to emulate a Linux terminal and write text to it except that everything is really done using SDL. The characters printed are from the custom font I used in Chaste Tris. This font is perfect because it is pixelated and predictable.

    Besides the fact that this library allows me to use my favorite bitmap font, it also allows complete control over input. Normally I would have to use ncurses to control a terminal in the same way, but since this is not a real terminal, I can do anything I want.

    Now imagine a game that was based entirely on typing text but was done in SDL. This would allow a cross platform nerd environment for people who like DOS and Linux terminals but would be completely cross platform. It wouldn’t matter if you are on Windows, Mac, or Linux because it would operate the same. I don’t know the details of what this game would have but it might be fun if I ever figure it out.

    full source code of this program

    main.c

    /*
     main.c source file for an SDL2 project by Chastity White Rose
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <SDL.h>
    #include "chastelib.h"
    
    int width=1280,height=720;
    int loop=1;
    SDL_Window *window;
    SDL_Surface *surface;
    SDL_Event e;
    
    /*
    This header file must be included after the above global variables
    because it depends on them.
    */
    #include "chastelib_font_sdl.h"
    #include "chastelib_demo_sdl.h"
    
    int main(int argc, char **argv)
    {
     int x; /*variable to use for whatever I feel like*/
    
     if(SDL_Init(SDL_INIT_VIDEO))
     {
      printf( "SDL could not initialize! SDL_Error: %s\n",SDL_GetError());return -1;
     }
     window=SDL_CreateWindow("SDL2 Program",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,width,height,SDL_WINDOW_SHOWN );
     if(window==NULL){printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );return -1;}
     surface = SDL_GetWindowSurface( window ); /*get surface for this window*/
     SDL_FillRect(surface,NULL,0xFF00FF);
     SDL_UpdateWindowSurface(window);
     printf("SDL Program Compiled Correctly\n");
     
     /*load the font from a file*/
     main_font=chaste_font_load("./font/FreeBASIC Font 8.bmp");
     
     /*change the scale of each character*/
     main_font.char_scale=4; 
     
     /*change the putstr function to the SDL version*/
     putstr=sdl_putstring;
     
     /*or use the version that automatically wraps words of text*/
     putstr=sdl_putstring_wrapped;
    
     /*
     below is an eight line test program to check if everything is correct!
     */
    
     if(0)
     {
      sdl_clear();  /*clear the screen before we begin writing*/
      x=putstr("Hello World\n"); /*draw a string of text to the surface*/
      putstr("string length = ");
      radix=10;
      putint(x);
      putstr("\nPress Esc to continue.\n");
      SDL_UpdateWindowSurface(window); /*update window to show the results*/
      sdl_wait_escape(); /*wait till escape key pressed*/
     }
    
     /*now call a demo function I wrote*/
     sdl_chastelib_test_suite();
    
     if(0)
     {
      sdl_clear();  /*clear the screen before we begin writing*/
      putstr("This program has ended\nPress Esc to close this window.\n");
      SDL_UpdateWindowSurface(window); /*update window to show the results*/
      sdl_wait_escape(); /*wait till escape key pressed*/
     }
     
     SDL_DestroyWindow(window);
     SDL_Quit();
     return 0;
    }
    
    /*
     This source file is an example to be included in the Chastity's Code Cookbook repository.
     This example follows the SDL version 2 which works differently than
     the most up to date version (version 3 at this time).
    
    main-sdl2:
    	gcc -Wall -ansi -pedantic main.c -o main `sdl2-config --cflags --libs` -lm && ./main
    
    */
    

    chastelib_font_sdl.h

    /*
    chastity font SDL2 surface version
    
    SDL surfaces are easy to work with and this was the original way I implemented my own text writing library.
    There is an incomplete version that uses an SDL renderer but offers no advantages over this one.
    */
    
    
    /*
    chastelib font structure
    
    In is version of my SDL2 font extension, a surface is used as an image which contains the printable characters.
    The data in it will be loaded by another function from an image file.
    */
    struct chaste_font
    {
     int char_width; /*width of a character*/
     int char_height; /*height of a character*/
     int char_scale; /*multiplier of original character size used in relevant functions*/
     SDL_Surface *surface; /*the surface of the image of loaded font*/
    };
    
    /*global font that will be reused many times*/
    struct chaste_font main_font;
    
    /*function to load a font and return a structure with the needed data to draw later*/
    struct chaste_font chaste_font_load(char *s)
    {
     struct chaste_font new_font;
     SDL_Surface *temp_surface;
     printf("Loading font: %s\n",s);
    
     /*load bitmap to temporary surface*/
     temp_surface=SDL_LoadBMP(s);
    
     /*convert to same surface as screen for faster blitting*/
     new_font.surface=SDL_ConvertSurface(temp_surface, surface->format, 0);
     
     /*free the temp surface*/
     SDL_FreeSurface(temp_surface); 
    
     if(new_font.surface==NULL){printf( "SDL could not load image! SDL_Error: %s\n",SDL_GetError());return new_font;}
    
     /*
      by default,font height is detected by original image height
      but the font width is the width of the image divided by 95
      because there are exactly 95 characters in the font format that I created.
     */
     new_font.char_width=new_font.surface->w/95; /*there are 95 characters in my font files*/
     new_font.char_height=new_font.surface->h;
    
     if(new_font.char_height==0)
     {
      printf("Something went horribly wrong loading the font from file:\n%s\n",s);
     }
     else
     {
      /*printf("Font loaded correctly\n");*/
      printf("Size of each character in loaded font is %d,%d\n",new_font.char_width,new_font.char_height);
      new_font.char_scale=1;
      printf("Character scale initialized to %d\n\n",new_font.char_scale);
     }
    
     return new_font;
    }
    
    /*global variables to control the cursor in the putchar function*/
    int cursor_x=0,cursor_y=0;
    int line_spacing_pixels=1; /*optionally space lines of text by this many pixels*/
    
    /*
    This function is designed to print a single character to the current surface of the main window
    This means that it can be called repeatedly to write entire strings of text
    */
    
    int sdl_putchar(char c)
    {
     int x,y; /*used as coordinates for source image to blit from*/
     int error=0; /*used only for error checking*/
     SDL_Rect rect_source,rect_dest;
    
      /*
      in the special case of a newline, the cursor is updated to the next line
      but no character is printed.
      */
      if(c=='\n')
      {
       cursor_x=0;
       cursor_y+=main_font.char_height*main_font.char_scale;
       cursor_y+=line_spacing_pixels; /*add space between lines for readability*/
      }
      else
      {
       x=(c-' ')*main_font.char_width; /*the x position of where this char is stored in the font source bitmap*/
       y=0*main_font.char_height;      /*the y position of where this char is stored in the font source bitmap*/
    
       rect_source.x=x;
       rect_source.y=y;
       rect_source.w=main_font.char_width;
       rect_source.h=main_font.char_height;
    
       rect_dest.x=cursor_x;
       rect_dest.y=cursor_y;
       rect_dest.w=main_font.char_width*main_font.char_scale;
       rect_dest.h=main_font.char_height*main_font.char_scale;
    
       /*copy the character to the screen (including scale of character)*/
       error=SDL_BlitScaled(main_font.surface,&rect_source,surface,&rect_dest);
       if(error){printf("Error: %s\n",SDL_GetError());}
       
       /*
       copy the character directly but ignore scale
       this will result in the tiny character from the source font
       and is only intended as a joke
       */
       /*error=SDL_BlitSurface(main_font.surface,&rect_source,surface,&rect_dest);
       if(error){printf("Error: %s\n",SDL_GetError());}*/
    
       cursor_x+=main_font.char_width*main_font.char_scale;
      }
    
     return c;
    }
    
    /*
     This function is the SDL equivalent of my putstring function.
     Besides writing the text to an SDL window, it still writes it to the terminal
     This way I can always read it from the terminal and debug if necessary.
    */
    
    int sdl_putstring(const char *s)
    {
     int count=0;                    /*used to calcular how many bytes will be written*/
     const char *p=s;                /*pointer used to find terminating zero of string*/
     while(*p)
     {
      sdl_putchar(*p); /*print this character to the SDL window using a function I wrote*/
      p++;             /*increment the pointer*/
     } 
     count=p-s;                      /*count is the difference of pointers p and s*/
     fwrite(s,1,count,stdout);       /*https://cppreference.com/w/c/io/fwrite.html*/
     return count;                   /*return how many bytes were written*/
    }
    
    /*
     This function writes a string but wraps the text to always fit the screen.
    */
    
    int sdl_putstring_wrapped(const char *s)
    {
     int count=0;     /*used to calcular how many bytes will be written*/
     const char *p=s; /*pointer used to find terminating zero of string*/
     const char *w;   /*pointer used to check length of string for wrapping text*/
     int wx;          /*x position used to see if we need to wrap words*/
     while(*p)
     {
      w=p; /*the wrap pointer is used in a loop to determine if the "word" will fit on the current line*/
      wx=cursor_x; 
      while(*w>=0x21 && *w<=0x7E) /*while the chars in current word are not special character*/
      {
       wx+=main_font.char_width*main_font.char_scale;
       w++;
      }
      /*if the previous loop goes off the right edge of window, wrap to next line*/
      if(wx>=width)
      {
       cursor_x=0;
       cursor_y+=main_font.char_height*main_font.char_scale;
       cursor_y+=line_spacing_pixels; /*add space between lines for readability*/
       putchar('\n'); /*insert newline to terminal*/
      }
      sdl_putchar(*p); /*print this character to the SDL window using a function I wrote*/
      putchar(*p);     /*print to stdout with libc putchar*/
      p++;             /*increment the pointer*/
     } 
     count=p-s;                      /*count is the difference of pointers p and s*/
     return count;                   /*return how many bytes were written*/
    }
    
    /*
    A function to clear the screen and reset the cursor to the top left
    This makes sense because the Linux clear command does the same thing
    */
    
    void sdl_clear()
    {
     cursor_x=0;cursor_y=0;
     SDL_FillRect(surface,NULL,0x000000);
     
     /*
     these next lines use escape sequences to also clear the terminal
     and reset the terminal cursor so it matches the SDL cursor by this library
    */
     putstring("\x1B[2J"); /*clear the terminal with an escape sequence*/
     putstring("\x1B[H"); /*reset terminal cursor to home*/
    }
    
    /*
     a function with a loop which will only end if we click the X or press escape
     This function serve as a useful way to keep the SDL Window on the screen
     so I can see the text of I have drawn to it.
     It is also something I can copy paste into larger input loops.
    */
    
    void sdl_wait_escape()
    {
     int loop=1;
     while(loop)
     {
      while(SDL_PollEvent(&e))
      {
       if(e.type == SDL_QUIT){loop=0;}
       if(e.type == SDL_KEYUP)
       {
        if(e.key.keysym.sym==SDLK_ESCAPE){loop=0;}
       }
      }
     }
    }
    

    chastelib_demo_sdl.h

    /* chastelib_demo_sdl.h */
    
    int sdl_chastelib_test_suite()
    {
     /*variables required by SDL*/
     int loop=1;
     int key=1;
     SDL_Event e;
    
     int a=0,b,c,d; /*variables for this test program*/
    
     line_spacing_pixels=1; /*empty space in pixels between lines*/
    
     radix=16;
     int_width=1;
    
     /*
      I use strint to set the variables by strings rather than immediate values directly
      Doing it this way looks silly, but it is for the purpose of testing the strint function
     */
     b=strint("10"); /*will always be radix*/
     c=b; /*save what the radix was at the beginning. This will be used later.*/
     d=strint("100"); /*will always be radix squared*/
    
     /*a loop which will only end if we click the X or press escape*/
     while(loop)
     {
      /*start of game loop*/
    
    
    
     if(key) /*start of update on input section*/
     {
      
      sdl_clear();  /*clear the screen before we begin writing*/
    
      main_font.char_scale=3;
      putstr("Official test suite for the C version of chastelib.\nThis version uses SDL2.\n\n");
    
      main_font.char_scale=4; 
    
      /*the actual loop that shows the data for 16 numbers at a time*/
      a=b-c;
      while(a<b)
      {
       radix=2;
       int_width=8;
       putint(a);
       putstr(" ");
       radix=16;
       int_width=2;
       putint(a);
       putstr(" ");
       radix=10;
       int_width=3;
       putint(a);
    
       if(a>=0x20 && a<=0x7E)
       {
        putstr(" ");
        putchar(a);
        sdl_putchar(a);
       }
    
       putstr("\n");
       a+=1;
      }
    
      SDL_UpdateWindowSurface(window); /*update window to show the results*/
     
    } /*end of update on input section*/
    
     key=0; /*key of zero means no input right now*/
    
      /*loop to capture and process input that happens*/
      while(SDL_PollEvent(&e))
      {
       if(e.type == SDL_QUIT){loop=0;}
    
       /*use Escape as a key that can also end this loop*/
       if(e.type == SDL_KEYUP)
       {
        if(e.key.keysym.sym==SDLK_ESCAPE){loop=0;}
       }
    
       if(e.type == SDL_KEYDOWN /*&& e.key.repeat==0*/)
       {
        key=e.key.keysym.sym;
        switch(key)
        {
         /*use q as a key that can also end this loop*/
         case SDLK_q:
          loop=0;
         break;
       
         /*the main 4 directions*/
         case SDLK_UP:
          if(b>c){b--;}
         break;
         case SDLK_DOWN:
          if(b<d){b++;}
         break;
         case SDLK_LEFT:
          if(b>=c+c){b-=c;}
         break;
         case SDLK_RIGHT:
          if(b<=d-c){b+=c;}
         break;
        }
    
    
    
        
       } /*end of SDL_KEYDOWN section*/
    
    
      }
    
      /*end of game loop*/
     }
     
     return 0;
    }
    

    chastelib.h

    /*
     This file is a C library of functions written by Chastity White Rose. The functions are for converting strings into integers and integers into strings.
     I did it partly for future programming plans and also because it helped me learn a lot in the process about how pointers work
     as well as which features the standard library provides, and which things I need to write my own functions for.
    
     As it turns out, the integer output routines for C are too limited for my tastes. This library corrects this problem.
     Using the global variables and functions in this file, integers can be output in bases/radixes 2 to 36.
     
     Although this code is commented, I have also written a readme.md file designed to explain the usage of these functions and the philosophy behind them.
    */
    
    /*
     These following lines define a static array with a size big enough to store the digits of an integer, including padding it with extra zeroes.
     The integer conversion function (intstr) always references a pointer to this global string, and this allows other C standard library functions
     such as printf to display the integers to standard output or even possibly to files.
     This string can be repurposed for absolutely anything I desire.
    */
    
    
    #define usl 0x100 /*usl stands for Unsigned or Universal String Length.*/
    char int_string[usl+1]; /*global string which will be used to store string of integers. Size is usl+1 for terminating zero*/
    
    /*radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal*/
    int radix=2;
    /*default minimum digits for printing integers*/
    int int_width=1;
    
    /*
    The intstr function is one that I wrote because the standard library can display integers as decimal, octal, or hexadecimal, but not any other bases(including binary, which is my favorite).
    
    My function corrects this, and in my opinion, such a function should have been part of the standard library, but I'm not complaining because now I have my own, which I can use forever!
    More importantly, it can be adapted for any programming language in the world if I learn the basics of that language. That being said, C is the best language and I will use it forever.
    */
    
    char *intstr(unsigned int i)    /*Chastity's supreme integer to string conversion function*/
    {
     int width=0;                   /*the width or how many digits including prefixed zeros are printed*/
     char *s=int_string+usl;        /*a pointer starting to the place where we will end the string with zero*/
     *s=0;                          /*set the zero that terminates the string in the C language*/
     while(i!=0 || width<int_width) /*loop to fill the string with every required digit plus prefixed zeros*/
     {
      s--;                          /*decrement the pointer to go left for corrent digit placing*/
      *s=i%radix;                   /*get the remainder of division by the radix or base*/
      i/=radix;                     /*divide the input by radix*/
      if(*s<10){*s+='0';}           /*fconvert digits 0 to 9 to the ASCII character for that digit*/
      else{*s=*s+'A'-10;}           /*for digits higher than 9, convert to letters starting at A*/
      width++;                      /*increment the width so we know when enough digits are saved*/
     }
     return s;                      /*return this string to be used by putstr,printf,std::cout or whatever*/
    }
    
    /*
    The strint_errors variable is used to keep track of how many errors happened in the strint function.
    The following errors can occur:
    
    Radix is not in range 2 to 36
    Character is not a number 0 to 9 or alphabet A to Z (in either case)
    Character is alphanumeric but is not valid for current radix
    
    If any of these errors happen, error messages are printed to let the programmer or user know what went wrong in the string that was passed to the function.
    If getting input from the keyboard, the strint_errors variable can be used in a conditional statement to tell them to try again and recall the code that grabs user input.
    */
    
    int strint_errors = 0; 
    
    /*
     The strint function is my own replacement for the strtol function from the C standard library.
     I didn't technically need to make this function because the functions from stdlib.h can already convert strings from bases 2 to 36 into integers.
     However, my function is simpler because it only requires 2 arguments instead of three, and it also does not handle negative numbers.
    I have never needed negative integers, but if I ever do, I can use the standard functions or write my own in the future.
    */
    
    int strint(const char *s)
    {
     int i=0;
     char c;
     strint_errors = 0; /*set zero errors before we parse the string*/
     if( radix<2 || radix>36 ){ strint_errors++; printf("Error: radix %i is out of range!\n",radix);}
     while( *s == ' ' || *s == '\n' || *s == '\t' ){s++;} /*skip whitespace at beginning*/
     while(*s!=0)
     {
      c=*s;
      if( c >= '0' && c <= '9' ){c-='0';}
      else if( c >= 'A' && c <= 'Z' ){c-='A';c+=10;}
      else if( c >= 'a' && c <= 'z' ){c-='a';c+=10;}
      else if( c == ' ' || c == '\n' || c == '\t' ){break;}
      else{ strint_errors++; printf("Error: %c is not an alphanumeric character!\n",c);break;}
      if(c>=radix){ strint_errors++; printf("Error: %c is not a valid character for radix %i\n",*s,radix);break;}
      i*=radix;
      i+=c;
      s++;
     }
     return i;
    }
    
    /*
     This function prints a string using fwrite.
     This algorithm is the best C representation of how my Assembly programs also work.
     Its true purpose is to be used in the putint function for conveniently printing integers, 
     but it can print any valid string.
    */
    
    int putstring(const char *s)
    {
     int count=0;              /*used to calcular how many bytes will be written*/
     const char *p=s;          /*pointer used to find terminating zero of string*/
     while(*p){p++;}           /*loop until zero found and immediately exit*/
     count=p-s;                /*count is the difference of pointers p and s*/
     fwrite(s,1,count,stdout); /*https://cppreference.com/w/c/io/fwrite.html*/
     return count;             /*return how many bytes were written*/
    }
    
    /*
     A function pointer named putstr which is a shorter name for calling putstring
     But this doesn't exist just to save bytes of source files. Otherwise I wouldn't have these huge comments!
     This exists so that all strings can be redirected to another function for output.
     For example, if the strings were written to a log file during a game which didn't use a terminal.
     
     But the most common use case is "putstr=addstr" when using the ncurses library to manage
     terminal control functions for a text based game. Having the putstr pointer allows me to 
     include this same source file and use it for ncurses based projects.
    */
    int (*putstr)(const char *)=putstring;
    
    /*
     This function uses both intstr and putstring to print an integer in the currently selected radix and width.
    */
    
    void putint(unsigned int i)
    {
     putstr(intstr(i));
    }
    
    /*
     Those four functions above are the core of chastelib.
     While there may be extensions written for specific programs, these functions are essential for absolutely every program I write.
     
     The only reason you would not need them is if you only output numbers in decimal or hexadecimal, because printf in C can do all that just fine.
     However, the reason my core functions are superior to printf is that printf and its family of functions require the user to memorize all the arcane symbols for format specifiers.
     
     The core functions are primarily concerned with standard output and the conversion of strings and integers. They do not deal with input from the keyboard or files. A separate extension will be written for my programs that need these features.
    */
    
    

  • C++ Version of chastehex!

    Hey, remember that chastehex program I wrote in C and several types of Assembly language? Well I finally made a C++ edition of it!

    What is the different between the C and C++ edition? Nothing at all as far as the function of it. However, I made an effort to replace all C library components with their C++ equivalents from the Standard Template library. This basically means the the console and the file input/output use only the iostream and fstream libraries.

    I am in a Programming 1 class with Full Sail University right now and I still don’t know much what I am doing with C++ but I have a good reference for C++ that I use a lot. It has all the functions from the C and C++ libraries sorted by categories.

    https://cppreference.com/index.html

    main.cpp

    #include <iostream>
    #include <fstream>
    using namespace std;
    #include "chastelib.hpp"
    
    std::fstream f;
    char bytes[17]; /*the byte buffer for hex and text dumping*/
    int count=1; /*keeps track of how many bytes were read during each row*/
    
    /*outputs the ASCII text to the right of the hex field*/
    void textdump()
    {
     int a,x=0;
    
     x=count;
     while(x<0x10)
     {
      putstring("   ");
      x++;
     }
    
     x=0;
     while(x<count)
     {
      a=bytes[x];
      if( a < 0x20 || a > 0x7E ){a='.';bytes[x]=a;}
      x++;
     }
     bytes[x]=0;
    
     putstring(bytes);
    }
    
    /*outputs up to 16 bytes on each row in hexadecimal*/
    void hexdump()
    {
     int x,address=0;
     x=0;
     f.read(bytes,16);
     count=f.gcount();
     while(count)
     {
      int_width=8;
      putint(address);
      putstring(" ");
    
      int_width=2;
      x=0;
      while(x<count)
      {
       putint(bytes[x]&0xFF);
       putstring(" ");
       x++;
      }
      textdump();
      putstring("\n");
    
      address+=count;
      f.read(bytes,16);
      count=f.gcount();
     }
    }
    
    int main(int argc, char *argv[])
    {
     int argx,x;
       
     radix=0x10; /*set radix for integer output*/
     int_width=1; /*set default integer width*/
    
     if(argc==1)
     {
      putstring
      (
       "Welcome to chastehex! The tool for reading and writing bytes of a file!\n\n"
       "To hexdump an entire file:\n\n\tchastehex file\n\n"
       "To read a single byte at an address:\n\n\tchastehex file address\n\n"
       "To write a single byte at an address:\n\n\tchastehex file address value\n\n"
      );
      return 0;
     }
    
     if(argc>1)
     {
      f.open(argv[1],ios::in|ios::out|ios::binary);
      if(!f.is_open())
      {
       printf("File \"%s\" cannot be opened.\n",argv[1]);
       return 1;
      }
      else
      {
       putstring(argv[1]);
       putstring("\n");
      }
     }
    
     if(argc==2)
     {
      hexdump(); /*hex dump only if filename given*/
     }
    
     if(argc>2)
     {
      x=strint(argv[2]);
      f.seekp(x);
     }
    
    
    
     /*read a byte at address of second arg*/
     if(argc==3)
     {
      f.read(&bytes[0],1);
      count=f.gcount();
      int_width=8;
      putint(x);
      putstring(" ");
      if(count==0){putstring("EOF");}
      else
      {
       int_width=2;
       putint(bytes[0]&0xFF);
      }
      putstring("\n");
     }
    
     /*any arguments past the address are hex bytes to be written*/
     if(argc>3)
     {
      argx=3;
      while(argx<argc)
      {
       bytes[0]=strint(argv[argx]);
       int_width=8;
       putint(x);
       putstring(" ");
       int_width=2;
       putint(bytes[0]&0xFF);
       putstring("\n");
       f.write(bytes,1);
       x++;
       argx++;
      }
     }
     
     f.close();
     return 0;
    }
    

    chastelib.hpp

    /*
     This file is a library of functions written by Chastity White Rose. The functions are for converting strings into integers and integers into strings.
     I did it partly for future programming plans and also because it helped me learn a lot in the process about how pointers work
     as well as which features the standard library provides and which things I need to write my own functions for.
    
     As it turns out, the integer output routines for C are too limited for my tastes. This library corrects this problem.
     Using the global variables and functions in this file, integers can be output in bases/radixes 2 to 36
    */
    
    /*
     These two lines define a static array with a size big enough to store the digits of an integer including padding it with extra zeroes.
     The function which follows always returns a pointer to this global string and this allows other standard library functions
     such as printf to display the integers to standard output or even possibly to files.
    */
    
    #define usl 32 /*usl stands for Unsigned String Length*/
    char int_string[usl+1]; /*global string which will be used to store string of integers. Size is usl+1 for terminating zero*/
    
     /*radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal*/
    int radix=2;
    /*default minimum digits for printing integers*/
    int int_width=1;
    
    /*
    This function is one that I wrote because the standard library can display integers as decimai, octai, or hexadecimal but not any other bases(including binary which is my favorite).
    My function corrects this and in my opinion such a function should have been part of the standard library but I'm not complaining because now I have my own which I can use forever!
    */
    
    char* intstr(unsigned int i)
    {
     int width=0;
     char *s=int_string+usl;
     *s=0;
     while(i!=0 || width<int_width)
     {
      s--;
      *s=i%radix;
      i/=radix;
      if(*s<10){*s+='0';}else{*s=*s+'A'-10;}
      width++;
     }
    
     return s;
    }
    
    /*
     This function prints a string using cout instead of fwrite.
     This is the best C++ representation of how my Assembly programs also work/
     It's true purpose is to be used in the putint function for conveniently printing integers, 
     but it can print any valid string.
    
     In the original C version, the putstring function was implemented with some pointer math to
     get the length of the string and then fwrite was used:
        fwrite(s,1,c,stdout);
    
     Technically this entire function could have been summed up in one statement:
        cout<<s;
    
     But where is the fun in that? I already had the logic for determining the length of the string.
     I also think that the new way of using << to write to cout is confusing because it is the left shift operator from C.
     Therefore, I wrote the function the following way to rebel against this common practice.
    */
    
    void putstring(const char *s)
    {
     int c=0;
     const char *p=s;
     while(*p++){c++;} 
     cout.write(s,c);
    }
    
    /*
     This function uses both intstr and putstring to print an integer in the currently selected radix and width
    */
    void putint(unsigned int i)
    {
     putstring(intstr(i));
    }
    
    /*
     This function is my own replacement for the strtol function from the C standard library.
     I didn't technically need to make this function because the functions from stdlib.h can already convert strings from bases 2 to 36 into integers.
     However my function is simpler because it only requires 2 arguments instead of three and it also does not handle negative numbers.
     Never have I needed negative integers but if I ever do I can use the standard functions or write my own in the future.
    */
    
    int strint(const char *s)
    {
     int i=0;
     char c;
     if( radix<2 || radix>36 ){ cout << "Error: radix " << i << " is out of range!\n"; return i;}
     while( *s == ' ' || *s == '\n' || *s == '\t' ){s++;} /*skip whitespace at beginning*/
     while(*s!=0)
     {
      c=*s;
      if( c >= '0' && c <= '9' ){c-='0';}
      else if( c >= 'A' && c <= 'Z' ){c-='A';c+=10;}
      else if( c >= 'a' && c <= 'z' ){c-='a';c+=10;}
      else if( c == ' ' || c == '\n' || c == '\t' ){return i;}
      else{ cout << "Error: " << c << " is not an alphanumeric character!\n";return i;}
      if(c>=radix){ cout << "Error: " << c << " is not a valid character for radix " << radix; return i;}
      i*=radix;
      i+=c;
      s++;
     }
     return i;
    }
    
    /*
     Those four functions above are pretty much the entirety of chastelib.
     While there may be extensions written for specific programs, these functions are essential for absolutely every program.
     The only reason you would not need them is if you only output numbers in decimal or hexadecimal, because printf in C can do all that just fine.
    */
    
  • DOS Assembly putstring function

    This is a small program which uses the putstring function I wrote. This function is one of 4 ultimate functions I have created which make up “chastelib”. DOS programming is simpler than Windows and is not that different from Linux in that system calls are done with an interrupt.

    org 100h
    
    main:
    
    mov ax,text
    call putstring
    
    mov ax,4C00h
    int 21h
    
    text db 'Hello World!',0Dh,0Ah,0
    
    ;This section is for the putstring function I wrote.
    ;It will print any zero terminated string that register ax points to
    
    stdout dw 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,[stdout]            ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    

    Anyone can assemble and run this source code, but you will need a DOS emulator like DOSBox in order for it to work. In fact, I have a video showing me assembling and running it inside of DOSBox.

    Lately I have been having a programming phase and am working on a book about programming in DOS. There is no money involved in this because nobody except nerds like me care about DOS. Speaking of nerds, if you follow my blog, don’t forget that this site was set up for teaching Chess. Leave me a comment if you play Chess online or live in Lee’s Summit. I am still playing Chess every day although some of my time has been taken up with programming in Assembly language because it is so much fun.

    If you like this post, you may be interested in my much longer post/book that is all about Assembly programming in DOS.

  • Chastity’s Hex Compare Tool

    Welcome to Chastity’s Hex Compare program also known as “chastecmp”. Enter two filenames as command line arguments such as:

    ./chastecmp file1.txt file2.txt

    It works for any binary files too, not just text. In fact for text comparison you want entirely different tools. This tool can be used to find the tiny differences between files in hexadecimal. It shows only those bytes which are different. I wrote it as a solution to a reddit user who asked how to compare two files in hexadecimal.

    It is an improvement over the Linux “cmp” tool which displays the offsets in decimal and the bytes in octal. Aside from using two different bases in the data, it falls short of usefulness because there are more hex editors than octal editors.

    Here are also some graphical tools I can recommend if you are looking for a GUI instead of my command line program.

    vbindiff
    wxHexeditor

    Below is the full source code:

    #include <stdio.h>
    #include <stdlib.h>
     
    int main(int argc, char *argv[])
    {
     int argx,x;
     FILE* fp[3]; /*file pointers*/
     int c1,c2;
     long flength[3]; /*length of the file opened*/
       
     /*printf("argc=%i\n",argc);*/
    
     if(argc<3)
     {
      printf("Welcome to Chastity's Hex Compare program also known as \"chastecmp\".\n\n");
      printf("Enter two filenames as command line arguments such as:\n");
      printf("%s file1.txt file2.txt\n",argv[0]);
      return 0;
     }
    
     argx=1;
     while(argx<3)
     {
       fp[argx] = fopen(argv[argx], "rb"); /*Try to open the file.*/
       if(!fp[argx]) /*If the pointer is NULL then this becomes true and the file open has failed!*/
       {
        printf("Error: Cannot open file \"%s\": ",argv[argx]);
        printf("No such file or directory\n");
        return 1;
       }
      /*printf("File \"%s\": opened.\n",argv[argx]);*/
    
      printf("fp[%X] = fopen(%s, \"rb\");\n",argx,argv[argx]);
      argx++;
     }
    
     printf("Comparing files %s and %s\n",argv[1],argv[2]);
    
     argx=1;
     while(argx<3)
     {
      fseek(fp[argx],0,SEEK_END); /*go to end of file*/
      flength[argx]=ftell(fp[argx]); /*get position of the file*/
      printf("length of file fp[%X]=%lX\n",argx,flength[argx]);
      fseek(fp[argx],0,SEEK_SET); /*go back to the beginning*/
      argx++;
     }
    
     x=0;
     while(x<flength[1])
     {
      c1 = fgetc(fp[1]);
      c2 = fgetc(fp[2]);
      if(c1!=c2)
      {
       printf("%08X: %02X %02X\n",x,c1,c2);
      }
      x++;  
     }
    
     argx=1;
     while(argx<3)
     {
      fclose(fp[argx]);
      printf("fclose(fp[%X]);\n",argx);
      argx++;
     }
    
     return 0;
    }