Chastity’s Chess Blog

  • 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.
    */
    
    

  • Programming Updates

    This is my Chess blog and I want you to know I still play Chess every day but until I have inspiration to write about Chess, I have more programming news to share. I have been doing a TON of Assembly language programming as part of the new book I am writing on DOS programming. Chapter 8 includes some Linux examples to show people the similarity between writing assembly for Linux and how similar it is to DOS.

    When the book is finished, I will probably make a version of the book specifically for Linux because I have a lot more details to share.

    The following text is what happens when I “git pull” on my repository on my Windows PC. This shows all the files that were updated on my Linux computer over the past few weeks. I do all my coding on Linux but the power of git allows me to upload everything to github from Linux and then download it to Windows as a backup. I really do everything I can not to lose this code because it is my math soul at full power!

    Microsoft Windows [Version 10.0.26200.8037]
    (c) Microsoft Corporation. All rights reserved.

    C:\Users\chand\Documents\git\Chastity-Code-Cookbook>git pull
    remote: Enumerating objects: 252, done.
    remote: Counting objects: 100% (250/250), done.
    remote: Compressing objects: 100% (136/136), done.
    remote: Total 201 (delta 126), reused 137 (delta 63), pack-reused 0 (from 0)
    Receiving objects: 100% (201/201), 49.53 KiB | 551.00 KiB/s, done.
    Resolving deltas: 100% (126/126), completed with 36 local objects.
    From https://github.com/chastitywhiterose/Chastity-Code-Cookbook
    f5e931c..c46c7c9 main -> origin/main
    Updating f5e931c..c46c7c9
    Fast-forward
    …/chapter 7 examples/chastelib-C/chastelib.h | 119 ++++++—-
    …/chapter 7 examples/chastelib-C/main.c | 22 +-
    …/chapter 7 examples/chastelib-C/readme.md | 51 ++++
    …/chapter 7 examples/chastelib-DOS/chastelib.h | 143 ++++++++++++
    …/chapter 7 examples/chastelib-DOS/main.asm | 20 +-
    …/chapter 7 examples/chastelib-DOS/main.c | 48 ++++
    …/chapter 7 examples/chastelib-DOS/main.com | Bin 0 -> 443 bytes
    …/fasm_32-bit_putstring/main | Bin 0 -> 172 bytes
    …/fasm_32-bit_putstring/main.asm | 56 +++++
    …/fasm_32-bit_putstring}/makefile | 0
    …/fasm_64-bit_putstring/main | Bin 0 -> 224 bytes
    …/fasm_64-bit_putstring/main.asm | 58 +++++
    …/fasm_64-bit_putstring/makefile | 4 +
    …/gasm_64-bit_putstring/main | Bin 0 -> 4488 bytes
    …/gasm_64-bit_putstring/main.s | 52 +++++
    …/gasm_64-bit_putstring/makefile | 5 +
    …/nasm_32-bit_putstring/main | Bin 0 -> 4324 bytes
    …/nasm_32-bit_putstring/main.asm | 55 +++++
    …/nasm_32-bit_putstring/main.o | Bin 0 -> 688 bytes
    …/nasm_32-bit_putstring/makefile | 5 +
    …/nasm_64-bit_putstring/main | Bin 0 -> 4888 bytes
    …/nasm_64-bit_putstring/main.asm | 55 +++++
    …/nasm_64-bit_putstring/main.o | Bin 0 -> 928 bytes
    …/nasm_64-bit_putstring/makefile | 8 +
    code/asm/fasm/dos/chastelib-DOS/main-output.txt | 257 ———————
    code/asm/fasm/dos/chastelib-DOS/main.asm | 20 +-
    code/asm/fasm/linux-64/chaste-lib64/main | Bin 737 -> 0 bytes
    code/asm/fasm/linux-64/chaste-lib64/main.asm | 52 —–
    code/asm/fasm/linux-64/chastehex64/chastelib64.asm | 50 +++-
    code/asm/fasm/linux-64/chastehex64/main | Bin 1634 -> 1657 bytes
    …/{chaste-lib64 => chastelib64}/chasteio64.asm | 0
    …/{chaste-lib64 => chastelib64}/chastelib64.asm | 50 +++-
    code/asm/fasm/linux-64/chastelib64/main | Bin 0 -> 824 bytes
    code/asm/fasm/linux-64/chastelib64/main.asm | 64 +++++
    code/asm/fasm/linux-64/chastelib64/makefile | 4 +
    code/asm/fasm/linux/chastecmp/chastelib32.asm | 37 ++-
    code/asm/fasm/linux/chastecmp/main | Bin 0 -> 1024 bytes
    code/asm/fasm/linux/chastehex/chastelib32.asm | 25 +-
    code/asm/fasm/linux/chastelib/chastelib32.asm | 25 +-
    code/asm/fasm/linux/chastelib/main.asm | 16 +-
    code/asm/fasm/linux/fasm_32-bit_putstring/main | Bin 0 -> 172 bytes
    code/asm/fasm/linux/fasm_32-bit_putstring/main.asm | 56 +++++
    code/asm/fasm/linux/fasm_32-bit_putstring/makefile | 4 +
    code/asm/fasm/linux/fasm_64-bit_putstring/main | Bin 0 -> 224 bytes
    code/asm/fasm/linux/fasm_64-bit_putstring/main.asm | 58 +++++
    code/asm/fasm/linux/fasm_64-bit_putstring/makefile | 4 +
    …/chastelib-diff-32vs64 (2026)/chastelib32.asm | 252 ++++++++++++++++++++
    …/chastelib-diff-32vs64 (2026)/chastelib64.asm | 254 ++++++++++++++++++++
    …/mixed-bits/chastelib-diff-32vs64 (2026)/main32 | Bin 0 -> 672 bytes
    …/chastelib-diff-32vs64 (2026)/main32.asm | 64 +++++
    …/mixed-bits/chastelib-diff-32vs64 (2026)/main64 | Bin 0 -> 824 bytes
    …/chastelib-diff-32vs64 (2026)/main64.asm | 64 +++++
    …/chastelib-diff-32vs64 (2026)/makefile | 18 ++
    …/chastelib32-test | Bin
    …/chastelib32-test.asm | 0
    …/chastelib32.asm | 0
    …/chastelib64-test | Bin
    …/chastelib64-test.asm | 0
    …/chastelib64.asm | 0
    …/makefile | 0
    code/asm/gas/gasm_32-bit_putstring/main | Bin 0 -> 4380 bytes
    code/asm/gas/gasm_32-bit_putstring/main.s | 53 +++++
    code/asm/gas/gasm_32-bit_putstring/makefile | 5 +
    code/asm/gas/gasm_64-bit_putstring/main | Bin 0 -> 4488 bytes
    code/asm/gas/gasm_64-bit_putstring/main.s | 53 +++++
    code/asm/gas/gasm_64-bit_putstring/makefile | 5 +
    code/asm/gas/hello/hello | Bin 0 -> 4448 bytes
    code/asm/gas/hello/hello.s | 29 +++
    code/asm/gas/hello/makefile | 5 +
    code/asm/nasm/nasm_32-bit_putstring/main | Bin 0 -> 4324 bytes
    code/asm/nasm/nasm_32-bit_putstring/main.asm | 55 +++++
    code/asm/nasm/nasm_32-bit_putstring/main.o | Bin 0 -> 688 bytes
    code/asm/nasm/nasm_32-bit_putstring/makefile | 5 +
    code/asm/nasm/nasm_64-bit_putstring/main | Bin 0 -> 4888 bytes
    code/asm/nasm/nasm_64-bit_putstring/main.asm | 55 +++++
    code/asm/nasm/nasm_64-bit_putstring/main.o | Bin 0 -> 928 bytes
    code/asm/nasm/nasm_64-bit_putstring/makefile | 8 +
    code/c/ncurses/ncurses_chastelib/chastelib.h | 143 ++++++++++++
    …/ncurses/ncurses_chastelib/chastelib_ncurses.h | 109 ———
    code/c/ncurses/ncurses_chastelib/main | Bin 16936 -> 17104 bytes
    code/c/ncurses/ncurses_chastelib/main.c | 28 +–
    code/c/std/chastelib_core/chastelib.h | 56 +++–
    code/c/std/chastelib_core/main | Bin 16416 -> 0 bytes
    code/c/std/chastelib_core/main.c | 12 +-
    code/c/std/chastelib_extensions/float/chastelib.h | 87 ++++—
    code/c/std/chastelib_extensions/float/main | Bin 16432 -> 16512 bytes
    code/c/std/chastelib_extensions/format/chastelib.h | 87 ++++—
    code/c/std/chastelib_extensions/format/main | Bin 16400 -> 16480 bytes
    code/c/std/chastelib_extensions/input/chastelib.h | 87 ++++—
    …/chastelib_extensions/input/chastelib_input.h | 4 +-
    code/c/std/chastelib_extensions/input/main | Bin 16536 -> 16616 bytes
    …/c/std/chastelib_extensions/ncurses/chastelib.h | 143 ++++++++++++
    …/ncurses/chastelib_ncurses.h | 109 ———
    code/c/std/chastelib_extensions/ncurses/main | Bin 16936 -> 17104 bytes
    code/c/std/chastelib_extensions/ncurses/main.c | 28 +–
    code/cpp/chastelib_pp_core/chastelib.hpp | 49 ++–
    code/cpp/chastelib_pp_core/main | Bin 16360 -> 16440 bytes
    code/cpp/chastelib_pp_core/main.cpp | 22 +-
    ebook.epub | Bin 85989 -> 85986 bytes
    99 files changed, 2494 insertions(+), 818 deletions(-)
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-C/readme.md
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-DOS/chastelib.h
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-DOS/main.c
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter 7 examples/chastelib-DOS/main.com
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_32-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_32-bit_putstring/main.asm
    rename code/asm/fasm/{linux-64/chaste-lib64 => dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_32-bit_putstring}/makefile (100%)
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/fasm_64-bit_putstring/makefile
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/gasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/gasm_64-bit_putstring/main.s
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/gasm_64-bit_putstring/makefile
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/main.o
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_32-bit_putstring/makefile
    create mode 100755 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/main.o
    create mode 100644 code/asm/fasm/dos/AAA-DOS-book-examples/chapter_8_linux_putstring/nasm_64-bit_putstring/makefile
    delete mode 100644 code/asm/fasm/dos/chastelib-DOS/main-output.txt
    delete mode 100755 code/asm/fasm/linux-64/chaste-lib64/main
    delete mode 100644 code/asm/fasm/linux-64/chaste-lib64/main.asm
    rename code/asm/fasm/linux-64/{chaste-lib64 => chastelib64}/chasteio64.asm (100%)
    rename code/asm/fasm/linux-64/{chaste-lib64 => chastelib64}/chastelib64.asm (76%)
    create mode 100755 code/asm/fasm/linux-64/chastelib64/main
    create mode 100644 code/asm/fasm/linux-64/chastelib64/main.asm
    create mode 100644 code/asm/fasm/linux-64/chastelib64/makefile
    create mode 100755 code/asm/fasm/linux/chastecmp/main
    create mode 100755 code/asm/fasm/linux/fasm_32-bit_putstring/main
    create mode 100644 code/asm/fasm/linux/fasm_32-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/linux/fasm_32-bit_putstring/makefile
    create mode 100755 code/asm/fasm/linux/fasm_64-bit_putstring/main
    create mode 100644 code/asm/fasm/linux/fasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/fasm/linux/fasm_64-bit_putstring/makefile
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/chastelib32.asm
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/chastelib64.asm
    create mode 100755 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main32
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main32.asm
    create mode 100755 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main64
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/main64.asm
    create mode 100644 code/asm/fasm/mixed-bits/chastelib-diff-32vs64 (2026)/makefile
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib32-test (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib32-test.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib32.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib64-test (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib64-test.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/chastelib64.asm (100%)
    rename code/asm/fasm/mixed-bits/{chastelib-test-32-64 => chastelib-test-32-64 (2025)}/makefile (100%)
    create mode 100755 code/asm/gas/gasm_32-bit_putstring/main
    create mode 100644 code/asm/gas/gasm_32-bit_putstring/main.s
    create mode 100644 code/asm/gas/gasm_32-bit_putstring/makefile
    create mode 100755 code/asm/gas/gasm_64-bit_putstring/main
    create mode 100644 code/asm/gas/gasm_64-bit_putstring/main.s
    create mode 100644 code/asm/gas/gasm_64-bit_putstring/makefile
    create mode 100755 code/asm/gas/hello/hello
    create mode 100644 code/asm/gas/hello/hello.s
    create mode 100644 code/asm/gas/hello/makefile
    create mode 100755 code/asm/nasm/nasm_32-bit_putstring/main
    create mode 100644 code/asm/nasm/nasm_32-bit_putstring/main.asm
    create mode 100644 code/asm/nasm/nasm_32-bit_putstring/main.o
    create mode 100644 code/asm/nasm/nasm_32-bit_putstring/makefile
    create mode 100755 code/asm/nasm/nasm_64-bit_putstring/main
    create mode 100644 code/asm/nasm/nasm_64-bit_putstring/main.asm
    create mode 100644 code/asm/nasm/nasm_64-bit_putstring/main.o
    create mode 100644 code/asm/nasm/nasm_64-bit_putstring/makefile
    create mode 100644 code/c/ncurses/ncurses_chastelib/chastelib.h
    delete mode 100644 code/c/ncurses/ncurses_chastelib/chastelib_ncurses.h
    delete mode 100755 code/c/std/chastelib_core/main
    create mode 100644 code/c/std/chastelib_extensions/ncurses/chastelib.h
    delete mode 100644 code/c/std/chastelib_extensions/ncurses/chastelib_ncurses.h

  • AAA DOS Chapter 7: Translating Assembly to Other Programming Languages

    This post is a newly written chapter for my recently published book “Assembly Arithmetic Algorithms:16-bit DOS Edition“. I published it on Leanpub which is a place you can publish books even before they are completely finished. I already had a couple of sales of this book and I was surprised because DOS is pretty much a dead platform except for hobbyists who need an easy environment to learn assembly language with. I still have a few more chapters I plan to add to it but this post is one of the best of them. A lot of my best documented code is here.

    Chapter 7: Translating Assembly to Other Programming Languages

    This chapter is going to be a weird one, because most people don’t start with assembly language before moving to higher level languages. In fact most people would probably recommend against Assembly as a first programming language.

    But for the purpose of this chapter alone, I will be assuming that you have been following the first 6 chapters of this Assembly book and want to know how this knowledge can be used to translate the Assembly into other languages like C and C++. This is actually very easy to do because the other languages are easier and have built in functions for you to use.

    So what I did is write a test suite program. It makes use of the core 4 of my chastelib functions (putstring,putint,intstr,strint) as well as some other utility functions just for displaying single characters, lines, and spaces.

    In this chapter, I will be including the entire source code of the main program “main.asm” as well as “chastelib16.asm” which is the included file containing all the useful output functions. Snippets of these have been included throughout the book but by including them all in this chapter, you can be sure that you have the most updated and commented version of the source code. This will become more important later when I show you the C equivalent program.

    main.asm

    org 100h
    
    main:
    
    mov eax,main_string
    call putstring
    
    mov word[radix],16           ; can choose radix for integer output!
    mov word[int_width],1
    mov byte[int_newline],0
    
    mov ax,input_string_int  ;address of input string to convert to integer using current radix
    call strint              ;call strint to return the string in eax register
    mov bx,ax                ;bx=ax (copy the converted value returned in ax to bx)
    
    mov ax,0
    loop1:
    
    mov word[radix],2            ;set radix to binary
    mov word[int_width],8        ;width of 8 for maximum 8 bits
    call putint
    call putspace
    mov word[radix],16           ;set radix to hexadecimal
    mov word[int_width],2        ;width of 8 for maximum 8 bits
    call putint
    call putspace
    mov word[radix],10           ;set radix to decimal (what humans read)
    mov word[int_width],3        ;width of 8 for maximum 8 bits
    call putint
    
    cmp al,0x20
    jb not_char
    cmp al,0x7E
    ja not_char
    
    call putspace
    call putchar
    
    not_char:                ;jump here if character is outside range to print
    
    call putline
    
    inc ax
    cmp ax,bx;
    jnz loop1
    
    mov ax,4C00h ;DOS system call number ah=0x4C to exit program with ah=0x00 as return value
    int 21h      ;DOS interrupt to exit the program with numbers on previous line
    
    ;A string to test if output works
    main_string db 'This program is the official test suite for the DOS Assembly version of chastelib.',0Ah,0
    
    ;test string of integer for input
    input_string_int db '100',0
    
    include 'chastelib16.asm' ; use %include if assembling with NASM instead of FASM.
    
    ; This 16 bit DOS Assembly source has been formatted for the FASM assembler.
    ; In order to run it, you will need the DOSBOX emulator or something similar.
    ; First, assemble it into a binary file. FASM will automatically add
    ; the .com extension because of the "org 100h" command.
    ;
    ;	fasm main.asm
    ;
    ; Then you will need to open DOSBOX and mount the folder that it is in.
    ; For example:
    ;
    ;	mount c ~/.dos
    ;	c:
    ;
    ;	Then, you will be able to just run the main.com.
    ;
    ;	main
    

    chastelib16.asm

    ; 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
    
    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 byte[bx],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
    
    
    
    ;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
    
    ;this is the end of the integer string optional line feed and terminating zero
    ;clever use of this label can change the ending to be a different character when needed 
    
    int_newline db 0Dh,0Ah,0 ;the proper way to end a line in DOS/Windows
    
    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_newline-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 byte[bx], '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
    
    
    
    
    
    
    
    
    ;this function converts a string pointed to by ax into an integer returned in ax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov bx,ax ;copy string address from ax to bx because ax will be replaced soon!
    mov ax,0
    
    read_strint:
    mov cx,0 ; zero cx so only lower 8 bits are used
    mov cl,[bx] ;copy byte/character at address bx to cl register (lowest part of cx)
    inc bx ;increment bx to be ready for next character
    cmp cl,0 ; compare this byte with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a decimal digit, but it could be perhaps an alphabet character
    ;which could be a digit in a higher base like hexadecimal
    ;we will check for that possibility next
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp cx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov dx,0 ;zero dx because it is used in mul sometimes
    mul word [radix]    ;mul ax with radix
    add ax,cx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    
    
    
    ;returns in al register a character from the keyboard
    getchr:
    
    mov ah,1
    int 21h
    
    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
    

    Now that you have the full source code. You can either copy and paste it from the PDF or epub edition (if you purchased the Leanpub edition) or you can download it directly from the github repository I have linked to at least twice in this book already.

    But you don’t even have to assembly and run it to see what it does because I am going to show you the entire output that it generates!

    Assembly Test Suite Output

    This program is the official test suite for the DOS Assembly version of chastelib.
    00000000 00 000
    00000001 01 001
    00000010 02 002
    00000011 03 003
    00000100 04 004
    00000101 05 005
    00000110 06 006
    00000111 07 007
    00001000 08 008
    00001001 09 009
    00001010 0A 010
    00001011 0B 011
    00001100 0C 012
    00001101 0D 013
    00001110 0E 014
    00001111 0F 015
    00010000 10 016
    00010001 11 017
    00010010 12 018
    00010011 13 019
    00010100 14 020
    00010101 15 021
    00010110 16 022
    00010111 17 023
    00011000 18 024
    00011001 19 025
    00011010 1A 026
    00011011 1B 027
    00011100 1C 028
    00011101 1D 029
    00011110 1E 030
    00011111 1F 031
    00100000 20 032  
    00100001 21 033 !
    00100010 22 034 "
    00100011 23 035 #
    00100100 24 036 $
    00100101 25 037 %
    00100110 26 038 &
    00100111 27 039 '
    00101000 28 040 (
    00101001 29 041 )
    00101010 2A 042 *
    00101011 2B 043 +
    00101100 2C 044 ,
    00101101 2D 045 -
    00101110 2E 046 .
    00101111 2F 047 /
    00110000 30 048 0
    00110001 31 049 1
    00110010 32 050 2
    00110011 33 051 3
    00110100 34 052 4
    00110101 35 053 5
    00110110 36 054 6
    00110111 37 055 7
    00111000 38 056 8
    00111001 39 057 9
    00111010 3A 058 :
    00111011 3B 059 ;
    00111100 3C 060 <
    00111101 3D 061 =
    00111110 3E 062 >
    00111111 3F 063 ?
    01000000 40 064 @
    01000001 41 065 A
    01000010 42 066 B
    01000011 43 067 C
    01000100 44 068 D
    01000101 45 069 E
    01000110 46 070 F
    01000111 47 071 G
    01001000 48 072 H
    01001001 49 073 I
    01001010 4A 074 J
    01001011 4B 075 K
    01001100 4C 076 L
    01001101 4D 077 M
    01001110 4E 078 N
    01001111 4F 079 O
    01010000 50 080 P
    01010001 51 081 Q
    01010010 52 082 R
    01010011 53 083 S
    01010100 54 084 T
    01010101 55 085 U
    01010110 56 086 V
    01010111 57 087 W
    01011000 58 088 X
    01011001 59 089 Y
    01011010 5A 090 Z
    01011011 5B 091 [
    01011100 5C 092 \
    01011101 5D 093 ]
    01011110 5E 094 ^
    01011111 5F 095 _
    01100000 60 096 `
    01100001 61 097 a
    01100010 62 098 b
    01100011 63 099 c
    01100100 64 100 d
    01100101 65 101 e
    01100110 66 102 f
    01100111 67 103 g
    01101000 68 104 h
    01101001 69 105 i
    01101010 6A 106 j
    01101011 6B 107 k
    01101100 6C 108 l
    01101101 6D 109 m
    01101110 6E 110 n
    01101111 6F 111 o
    01110000 70 112 p
    01110001 71 113 q
    01110010 72 114 r
    01110011 73 115 s
    01110100 74 116 t
    01110101 75 117 u
    01110110 76 118 v
    01110111 77 119 w
    01111000 78 120 x
    01111001 79 121 y
    01111010 7A 122 z
    01111011 7B 123 {
    01111100 7C 124 |
    01111101 7D 125 }
    01111110 7E 126 ~
    01111111 7F 127
    10000000 80 128
    10000001 81 129
    10000010 82 130
    10000011 83 131
    10000100 84 132
    10000101 85 133
    10000110 86 134
    10000111 87 135
    10001000 88 136
    10001001 89 137
    10001010 8A 138
    10001011 8B 139
    10001100 8C 140
    10001101 8D 141
    10001110 8E 142
    10001111 8F 143
    10010000 90 144
    10010001 91 145
    10010010 92 146
    10010011 93 147
    10010100 94 148
    10010101 95 149
    10010110 96 150
    10010111 97 151
    10011000 98 152
    10011001 99 153
    10011010 9A 154
    10011011 9B 155
    10011100 9C 156
    10011101 9D 157
    10011110 9E 158
    10011111 9F 159
    10100000 A0 160
    10100001 A1 161
    10100010 A2 162
    10100011 A3 163
    10100100 A4 164
    10100101 A5 165
    10100110 A6 166
    10100111 A7 167
    10101000 A8 168
    10101001 A9 169
    10101010 AA 170
    10101011 AB 171
    10101100 AC 172
    10101101 AD 173
    10101110 AE 174
    10101111 AF 175
    10110000 B0 176
    10110001 B1 177
    10110010 B2 178
    10110011 B3 179
    10110100 B4 180
    10110101 B5 181
    10110110 B6 182
    10110111 B7 183
    10111000 B8 184
    10111001 B9 185
    10111010 BA 186
    10111011 BB 187
    10111100 BC 188
    10111101 BD 189
    10111110 BE 190
    10111111 BF 191
    11000000 C0 192
    11000001 C1 193
    11000010 C2 194
    11000011 C3 195
    11000100 C4 196
    11000101 C5 197
    11000110 C6 198
    11000111 C7 199
    11001000 C8 200
    11001001 C9 201
    11001010 CA 202
    11001011 CB 203
    11001100 CC 204
    11001101 CD 205
    11001110 CE 206
    11001111 CF 207
    11010000 D0 208
    11010001 D1 209
    11010010 D2 210
    11010011 D3 211
    11010100 D4 212
    11010101 D5 213
    11010110 D6 214
    11010111 D7 215
    11011000 D8 216
    11011001 D9 217
    11011010 DA 218
    11011011 DB 219
    11011100 DC 220
    11011101 DD 221
    11011110 DE 222
    11011111 DF 223
    11100000 E0 224
    11100001 E1 225
    11100010 E2 226
    11100011 E3 227
    11100100 E4 228
    11100101 E5 229
    11100110 E6 230
    11100111 E7 231
    11101000 E8 232
    11101001 E9 233
    11101010 EA 234
    11101011 EB 235
    11101100 EC 236
    11101101 ED 237
    11101110 EE 238
    11101111 EF 239
    11110000 F0 240
    11110001 F1 241
    11110010 F2 242
    11110011 F3 243
    11110100 F4 244
    11110101 F5 245
    11110110 F6 246
    11110111 F7 247
    11111000 F8 248
    11111001 F9 249
    11111010 FA 250
    11111011 FB 251
    11111100 FC 252
    11111101 FD 253
    11111110 FE 254
    11111111 FF 255
    

    main.c (The C Test Suite)

    #include <stdio.h>
    #include <stdlib.h>
    #include "chastelib.h"
    
    int main(int argc, char *argv[])
    {
     int a=0,b;
    
     radix=16;
     int_width=1;
    
     putstring("This program is the official test suite for the C version of chastelib.\n");
    
     b=strint("100");
     while(a<b)
     {
      radix=2;
      int_width=8;
      putint(a);
      putstring(" ");
      radix=16;
      int_width=2;
      putint(a);
      putstring(" ");
      radix=10;
      int_width=3;
      putint(a);
    
      if(a>=0x20 && a<=0x7E)
      {
       putstring(" ");
       putchar(a);
      }
    
      putstring("\n");
      a+=1;
     }
      
     return 0;
    }
    

    The C version above does the exact same steps as the assembly version, but is calling functions that do very much the same steps as the assembly version. I will show you the contents of the included file “chastelib.h” next. Be prepared for a slightly less painful mess of code! However, much like the Assembly version it is heavily commented to help with understanding it.

    chastelib.h (The C chastelib library)

    /*
     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 integer conversion function always references 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 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.
    */
    
    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 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.
    */
    
    void putstring(const char *s)
    {
     int c=0;
     const char *p=s;
     while(*p++){c++;} 
     fwrite(s,1,c,stdout);
    }
    
    /*
     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.
    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;
     if( radix<2 || radix>36 ){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{printf("Error: %c is not an alphanumeric character!\n",c);break;}
      if(c>=radix){printf("Error: %c is not a valid character for radix %i\n",*s,radix);break;}
      i*=radix;
      i+=c;
      s++;
     }
     return 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.
    */
    

    Now that you have witnessed the largest dump of code to ever be included in a chapter of a book, I want you to look it over carefully and you will notice that there is almost direct equivalence between the Assembly version and the C version.

    Here is a detailed breakdown of how both versions operate despite the language syntax looking completel different.

    • putstring finds the terminating zero to calculate string length and prints that length of bytes. It achieves this by using the fwrite function which is part of the standard library. Just like the “ah=40h” DOS call, it must be given the arguments to say “Start at this address and write exactly this number of bytes to standard output!”. It also uses tons of pointer arithmetic in both the C and assembly versions. In this case, I would argue that the Assembly version might be easier to read than the C version. C lets a person create some weird looking code with the syntax used for pointers

    • intstr converts an integer into a string at a specific predetermined address and then returns a pointer to this address from the function. In both the C and Assembly version, the process of repeated division by the radix (also known as number base) while the integer is above zero or the string has not reached the minimum width or length I want the string to have. It will prefix the string with extra zeros just so it lines up perfectly as you say in the output earlier in this chapter.

    • putint is merely a convenience function the calls intstr and then putstr to convert and print an integer in one step. Functions are designed to repeat frequent operations to reduce code size and save programmer time. Though to be honest, if your time was valuable to you, you probably wouldn’t be reading a DOS assembly language book. Thanks for reading my book anyway!

    • strint does the opposite of intstr as you might guess from its name. It converts a string into an integer. Its usefulness is not fully obvious here because the example program reads a predefined string. Ordinarily, you would get user input from either the keyboard during the program or from command line arguments passed to the program before it starts. One small piece of advice though, if you want to accept user input, C is a better language than Assembly because I haven’t been successful in getting anyone to assembly and run my DOS assembly programs anyway!

    You probably noticed other functions like putchar, putline, and putspace. I made these convenience functions in assembly because there are times when you need to print a character to separate numbers. Usually spaces and newlines are the most important. The putchar function exists in the C standard library since at least 1989 and probably much earlier.

    Portable Assembly Language

    C has been called a portable assembly language because C compilers translate C code into assembly language and then link it with the precompiled functions in other libraries. In short, they do the opposite process of what I have done in this chapter. I wrote these string and integer output routines because they didn’t exist in assembly language by default.

    C already has printf,putchar, and fwrite. These are more than enough to handle outputting text including numbers without having to use the functions I have written. But in any case, I provided them in this chapter for helping people understand how these operations are done.

    But when you are trying to write programs for DOS, assembly is still better because there are not enough easy to find C compilers that still work in 16 bit DOS mode. There was one made by the company Borland known as Turbo C. If you are lucky enough to find it on the internet and get it running, you might enjoy it.

    C++ is a programming language that came after C and includes everything C has plus more. However, this book is about assembly and this chapter was but a brief introduction to the idea of translating assembly language into C for the purpose of having something portable to all platforms.

    My other book, Chastity’s Code Cookbook uses mostly C code as an introduction to programming. If you liked this chapter, consider reading it for more nerdy programming content.

  • Hexplore Assembly Demo 1

    I don’t know why I did this to myself, but I rewrote my Hexplore program in pure Intel Assembly language for Linux.

    The source is twice as big as chastehex and is split between 4 source files. This iprogram s a full interactive hex editor that runs in a terminal. Using only Linux system calls and ANSI escape sequences, I can display all the text where I want in the terminal and even do fancy things like highlight the currently selected byte with green.

    Every time you switch to the previous/next page with the “Page Up” or “Page_Down” keys, it changes the file offset of a file named RAM and reads the new data. But it also saves the current page back to the file before switching pages or ending the program.

    The entire executable produced from my source and FASM is less than 3 kilobytes and runs extremely fast. Obviously, this assembly source is not portable to other platforms like the C version is, but it was a fun challenge!

  • Chaste Tris Repository Update 2026

    For the past 5 years, I have had the repository of my Chaste Tris game hosted on GitHub. However, the code has been out of date compared to my local machine, and also very badly organized. I have taken a bit of time this weekend, besides doing school work, to organize the repositories for the 3 games I have made: Chaste Tris, Chaste Puyo, and Chaste Panel.

    The reasons for this are many. First of all, having the code Open Source does nobody any good if it is too confusing for them. Also, I included the GPL3 license in all the repositories so that it is clear that this is Free Software. The repositories for these three games are below.

    https://github.com/chastitywhiterose/chastetris
    https://github.com/chastitywhiterose/Chaste-Puyo
    https://github.com/chastitywhiterose/Chaste-Panel

    Recently, I have been reading and watching a lot of content about the terms Open Source vs. Free Software and why these terms are usually, but not always, the same thing. Following an email conversation I had with Richard Stallman, I believe I can make some analogies that will help the average person understand it better.

    When something is Open Source, it means the source code is available for you to read, but it may have a license restricting you from using it to modify or fork from the Software to make your own version. Just having the source available does not do much good if you are restricted legally from making the best use of it.

    Free Software, on the other hand, is concerned with the Freedom to use the Software as you wish. I believe this section from the GPL3 makes it very clear.

    *When we speak of free Software, we are referring to Freedom, not price. Our General Public Licenses are designed to make sure that you have the Freedom to distribute copies of free Software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the Software or use pieces of it in new free programs, and that you know you can do these things.

    To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the Software, or if you modify it: responsibilities to respect the Freedom of others.

    For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

    To make the difference between Open Source and Free Software even clearer. Consider that some people think of Veganism as a diet that involves not eating animals, but don’t understand the ethical reasons for this. They might think it is a healthy diet to eat only plants, which is true, but it is not the primary motivation. These people may still buy or wear fur or leather, hunt animals for sport, or breed puppies to sell them. Their diet may be Vegan but their mindset is not.

    Free Software advocates who care about the Freedom of people to control their Software are like ethical Vegans who are trying to communicate clearly what it is all about. Just as an ethical Vegan like me is concerned with avoiding hurting animals, a Free Software Advocate cares about users controlling their Software in terms of performance, portability, privacy(as in not being spied on by Microsoft or Apple), and the ability to choose which Software they want to use, and if they are smart enough, invent their own.

    As someone who has been working with Free Software for many years, I care about this distinction as much as Richard Stallman, but I also know that many people are using Open Source and mean the same thing. I don’t believe that these people should be shamed for using the terms they have learned to use. I just believe we need to hold people accountable when they advertise something as Open Source but then restrict users in a way that makes the source closed to being used in the way that people like me have always meant when they said Open Source.

    Also, fun fact, did you know that the source code for Chaste Tris has always been included in the Steam release? You have to navigate to the installed files, but the source is always there.

    https://store.steampowered.com/app/1986120/Chaste_Tris/

    My Tetris game was never meant to compete with existing Tetris games, but it contains all of the elements required to make a cross-platform game. I still dream of making a small original game, unlike anything the world has seen before. Just like Chaste Tris, it will also be Free as in Freedom, just like all of the best Software has been.

  • Hexsplore update 2

    I have the ability to now type in and set the bytes in memory as well as display useful information on how to use the program. The idea is that it should show everything you need to know on the screen, just like the nano text editor does.

    Source is here:

    https://github.com/chastitywhiterose/Chastity-Code-Cookbook/tree/main/code/c/ansi/hexplore

  • The War I can Win

    I am a programmer who knows clever tricks I convert integers to bases two to thirty six By using character arrays as strings I can show you amazing math things

    While others talk about politics and sports I read about computers of all sorts Programming languages are all the same When you see arithmetic as a game

    I don’t write code for a job to be paid But to understand the video games I played To see what works and find out why To create, use, study, share, and modify

    I support Free Software and Open Source It is a philosophy that I can endorse Who can own a number, even a single bit? Or tell me what I am allowed to do with it?

    I fight for users who love the games of old Who rebel and don’t do what we are told I made a Tetris game and they said it was no good And that I broke copyright like no one should

    But who decides which person can play a game In math and entertainment there is no shame Can anyone own a letter color or a square? I will fight and debate you if you even dare

    Because integer math is the one war I can win I wrote the code myself and committed no sin And I can tell you that for every byte array There is a game hidden that only a hacker can play

    To humans I am nothing but just a transgender freak And evil men can kill my body because it is weak But the computer has no bias and means me no harm. In this digital world I am safe and I feel no alarm

    No matter what compiler, language, or tool When I code the people see I am not a fool If you take my computer, I am still in control Because the numbers are part of me deep in my soul

    This world is often sad but I still have a great dream Where we all work together as part of the same team And that every person can experience the ultimate thrill Of the binary code that flows where no man can ever kill

  • 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.
    */
    
  • RISC-V hexdump program

    I wrote a small program in Risc-V that does a hex dump similarly to the one in chastehex. This is not the full chastehex program but it at least mimics the hexdump feature perfectly. Here is a screenshot of it working.

    Source Code

    #hexdump for RISC-V emulator: rars
    .data
    title: .asciz "hexdump program in RISC-V assembly language\n\n"
    
    # test string of integer for input
    test_int: .asciz "10011101001110011110011"
    hex_message: .asciz "Hex Dump of File: "
    file_message_yes: .asciz "The file is open.\n"
    file_message_no: .asciz "The file could not be opened.\n"
    file_data: .byte '?':16
               .byte 0
    space_three: .asciz "   "
    
    #this is the location in memory where digits are written to by the putint function
    int_string: .byte '?':32
    int_newline: .byte 10,0
    radix: .byte 2
    int_width: .byte 4
    
    argc: .word 0
    argv: .word 0
    
    .text
    main:
    
    # at the beginning of the program a0 has the number of arguments
    # so we will save it in the argc variable
    la t1,argc
    sw a0,0(t1)
    
    # at the beginning of the program a1 has a pointer to the argument strings
    # so we save it because we may need a1 for system calls
    la t1,argv
    sw a1,0(t1)
    
    #Now that the argument data is stored away, we can access it even if it is overwritten.
    #For example, the putstring function uses a0 for system call number 4, which prints a string
    
    la s0,title
    jal putstring
    
    li t0,16    #change radix
    la t1,radix
    sb t0,0(t1)
    
    li t0,1    #change width
    la t1,int_width
    sb t0,0(t1)
    
    
    # next, we load argc from the memory so we can display the number of arguments
    la t1,argc
    lw s0,0(t1)
    #jal putint
    
    beq s0,zero,exit # if the number of arguments is zero, exit the program because nothing else to print
    
    # this section processes the filename and opens the file from the first argument
    
    jal next_argument
    #jal putstring
    mv s7,s0 #save the filename in register s7 so we can use it any time
    
    li a7,1024 # open file call number
    mv a0,s7  # copy filename for the open call
    li a1,0    # read only access for the file we will open (rars does not support read+write mode)
    ecall
    
    mv s0,a0
    #jal putint
    
    blt s0,zero,file_error # branch if argc is not equal to zero
    
    mv s6,s0 # save the find handle in register s6
    la s0,file_message_yes
    #jal putstring
    jal hexdump
    
    j exit
    
    file_error:
    
    la s0,file_message_no
    jal putstring
    
    
    j exit
    
    exit:
    li a7, 10     # exit syscall
    ecall
    
    # this is the hexdump function
    
    hexdump:
    addi sp,sp,-4
    sw ra,0(sp)
    
    la s0,hex_message
    jal putstring
    mv s0,s7
    jal putstring
    jal putline
    
    li t0,0    #disable automatic newlines after putint
    la t1,int_newline
    sb t0,0(t1)
    
    li, s5,0 # we will use s5 register as current offset
    
    hex_read_row:
    li a7,63        # read system call
    mv a0,s6        # file handle
    la a1,file_data # where to store data
    li a2,16        # how many bytes to read
    ecall           # a0 will have number of bytes read after this call
    
    mv s3,a0 #save a0 to s3 to keep count of how many bytes read
    mv s2,a0 #save a0 to s2 to keep count of how many bytes read
    
    beq a0,zero,hexdump_end
    
    li s0,8    #change width
    la s1,int_width
    sb s0,0(s1)
    
    mv s0,s5
    add s5,s5,s3
    jal putint
    jal putspace
    
    li s0,2    #change width to 2 for the bytes printed this row
    la s1,int_width
    sb s0,0(s1)
    
    la s1,file_data
    hex_row_print:
    lb s0,0(s1)
    jal putint
    jal putspace
    addi s1,s1,1
    
    addi s2,s2,-1
    bne s2,zero,hex_row_print
    
    #pad the row with extra spaces
    
    mv t2,s3
    li t3,16
    extra_row_space:
    beq t2,t3,extra_row_space_complete
    la s0,space_three
    jal putstring
    addi t2,t2,1
    j extra_row_space
    extra_row_space_complete:
    
    #now the hex form of the bytes are printed
    #we will filter the text form and also print it
    
    li s2,0
    la s1,file_data
    char_filter:
    lb s0,0(s1)
    
    #if char is below 0x20 or above 0x7E, it is outside the range of printable characters
    
    li t5,0x20
    blt s0,t5,not_printable
    li t5,0x7E
    bgt s0,t5,not_printable
    
    j next_char_index
    
    not_printable:
    li s0,'.'
    sb s0,0(s1)
    
    next_char_index:
    addi s1,s1,1
    addi s2,s2,1
    blt s2,s3,char_filter
    
    li s0,0
    #add s1,s1,s3
    sb s0,0(s1)   #terminate string with a zero
    
    la s0,file_data
    jal putstring
    
    
    jal putline
    
    j hex_read_row
    
    hexdump_end:
    lw ra,0(sp)
    addi sp,sp,4
    jr ra
    
    # this function gets the next command line argument and returns it in s0
    # it also decrements the argc variable so that it can be checked for 0 to exit the program if needed by the main program
    
    next_argument:
    
    la t1,argv
    lw t0,0(t1) #load the string pointer located in argv into t0 register
    lw s0,0(t0) #load the data being pointed to by t0 into s0 for displaying the string
    addi t0,t0,4 #add 4 to the pointer
    sw t0,0(t1)  #store the pointer so it will be loaded at the next string if the loop continues
    
    # load the number of arguments from memory, subtract 1, store back to memory
    # then use to compare and loop if nonzero
    la t1,argc
    lw t0,0(t1)
    
    addi t0,t0,-1
    sw t0,0(t1)
    
    jr ra
    
    
    putline:
    li a7,11
    li a0,10
    ecall
    jr ra
    
    putspace:
    li a7,11
    li a0,' '
    ecall
    jr ra
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    putstring:
    li a7,4      # load immediate, v0 = 4 (4 is print string system call)
    mv a0,s0  # load address of string to print into a0
    ecall
    jr ra
    
    #this is the intstr function, the ultimate integer to string conversion function
    #just like the Intel Assembly version, it can convert an integer into a string
    #radixes 2 to 36 are supported. Digits higher than 9 will be capital letters
    
    intstr:
    
    la t1,int_newline # load target index address of lowest digit
    addi t1,t1,-1
    
    lb t2,radix     # load value of radix into t2
    lb t4,int_width # load value of int_width into t4
    li t3,1         # load current number of digits, always 1
    
    digits_start:
    
    remu t0,s0,t2 # t0=remainder of the previous division
    divu s0,s0,t2 # s0=s0/t2 (divide s0 by the radix value in t2)
    
    li t5,10 # load t5 with 10 because RISC-V does not allow constants for branches
    blt t0,t5,decimal_digit
    bge t0,t5,hexadecimal_digit
    
    decimal_digit: # we go here if it is only a digit 0 to 9
    addi t0,t0,'0'
    j save_digit
    
    hexadecimal_digit:
    addi t0,t0,-10
    addi t0,t0,'A'
    
    save_digit:
    sb t0,(t1) # store byte from t0 at address t1
    beq s0,zero,intstr_end
    addi t1,t1,-1
    addi t3,t3,1
    j digits_start
    
    intstr_end:
    
    li t0,'0'
    prefix_zeros:
    bge t3,t4,end_zeros
    addi t1,t1,-1
    sb t0,(t1) # store byte from t0 at address t1
    addi t3,t3,1
    j prefix_zeros
    end_zeros:
    
    mv s0,t1
    
    jr ra
    
    #this function calls intstr to convert the s0 register into a string
    #then it uses a system call to print the string
    #it also uses the stack to save the value of s0 and ra (return address)
    
    putint:
    addi sp,sp,-8
    sw ra,0(sp)
    sw s0,4(sp)
    jal intstr
    #print string
    li a7,4      # load immediate, v0 = 4 (4 is print string system call)
    mv a0,s0  # load address of string to print into a0
    ecall
    lw ra,0(sp)
    lw s0,4(sp)
    addi sp,sp,8
    jr ra
    
    
    
    
    # RISC-V does not allow constants for branches
    # Because of this fact, the RISC-V version of strint
    # requires a lot more code than the MIPS version
    # Whatever value I wanted to compare in the branch statement
    # was placed in the t5 register on the line before the conditional branch
    # Even though it is completely stupid, it has proven to work
    
    strint:
    
    mv t1,s0 # copy string address from s0 to t1
    li s0,0
    
    lb t2,radix     # load value of radix into t2
    
    read_strint:
    lb t0,(t1)
    addi t1,t1,1
    beq t0,zero,strint_end
    
    #if char is below '0' or above '9', it is outside the range of these and is not a digit
    li t5,'0'
    blt t0,t5,not_digit
    li t5,'9'
    bgt t0,t5,not_digit
    
    #but if it is a digit, then correct and process the character
    is_digit:
    andi t0,t0,0xF
    j process_char
    
    not_digit:
    #it isn't a digit, but it could be perhaps and alphabet character
    #which is a digit in a higher base
    
    #if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    li t5,'A'
    blt t0,t5,not_upper
    li t5,'Z'
    bgt t0,t5,not_upper
    
    is_upper:
    li t5,'A'
    sub t0,t0,t5
    addi t0,t0,10
    j process_char
    
    not_upper:
    
    #if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    li t5,'a'
    blt t0,t5,not_lower
    li t5,'z'
    bgt t0,t5,not_lower
    
    is_lower:
    li t5,'a'
    sub t0,t0,t5
    addi t0,t0,10
    j process_char
    
    not_lower:
    
    #if we have reached this point, result invalid and end function
    #this is only reached if the byte was not a valid digit or alphabet character
    j strint_end
    
    process_char:
    
    bgt t0,t2 strint_end #;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    
    mul s0,s0,t2 # multiply s0 by the radix
    add s0,s0,t0     # add the correct value of this digit
    
    j read_strint # jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    jr ra
    

    I don’t yet know a good place for sharing this code. I need a good RISC-V community. Leave me a comment if you have suggestions. In the meantime, I am using this blog, and my github account to back up all these amazing assembly programs I have been writing. RISC-V seems very weird but I am starting to understand it better.

  • RISC-V Assembly chastelib

    I have translated the MIPS version of my chastelib library into RISC-V. The source includes some relevant changes between the two architectures. Both RISC-V and MIPS are very different from Intel Assembly. However, now that the translation is complete, I can run them in simulators and create almost any program that I would have the skill to do in Intel assembly.

    I learned this language from this book by Robert Winkler.
    https://leanpub.com/riscvassemblyprogramming

    # This is the source of the RISC-V version of chastelib
    # The four basic functions have been translated from Intel x86 Assembly
    # They are as follows
    #
    # putstring (prints string pointed to by s0 register)
    # intstr (converts integer in s0 register to a string)
    # putint (prints integer in s0 register by use of the previous two functions)
    # strint (converts a string pointed to by s0 register into an integer in s0)
    #
    # Most importantly, the intstr and strint functions depend on a global variable (radix)
    # In fact, these two functions are the foundation of everything.
    # They can convert to and from any radix from 2 to 36
    #
    # There is also a MIPS assembly source of this library.
    # The primary differences between the two assembly languages from my experience are:
    # RISC-V requires registers for all branch comparisons
    # RISC-V uses ecall but MIPS uses syscall
    # RISC-V selects system call with a7 but MIPS uses $v0
    # RISC-V does not use dollar signs ($) in the name of registers but MIPS does 
    
    .data
    title: .asciz "A test of Chastity's integer and string conversion functions.\n"
    
    # test string of integer for input
    test_int: .asciz "10011101001110011110011"
    
    #this is the location in memory where digits are written to by the putint function
    int_string: .byte '?':32
    int_newline: .byte 10,0
    radix: .byte 2
    int_width: .byte 4
    
    .text
    main:
    
    la s0,title
    jal putstring
    
    li s0,0 #we will load the $s0 register with the number we want to convert to string
    li s1,16
    
    loop:
    jal putint
    addi s0,s0,1
    blt s0,s1,loop
    
    la s0,test_int # convert string to integer
    jal strint
    
    li t0,10    #change radix
    la t1,radix
    sb t0,0(t1)
    
    li t0,8    #change width
    la t1,int_width
    sb t0,0(t1)
    
    jal putint
    
    li   a7, 10     # exit syscall
    ecall
    
    putstring:
    li a7,4      # load immediate, v0 = 4 (4 is print string system call)
    mv a0,s0  # load address of string to print into a0
    ecall
    jr ra
    
    #this is the intstr function, the ultimate integer to string conversion function
    #just like the Intel Assembly version, it can convert an integer into a string
    #radixes 2 to 36 are supported. Digits higher than 9 will be capital letters
    
    intstr:
    
    la t1,int_newline # load target index address of lowest digit
    addi t1,t1,-1
    
    lb t2,radix     # load value of radix into t2
    lb t4,int_width # load value of int_width into t4
    li t3,1         # load current number of digits, always 1
    
    digits_start:
    
    remu t0,s0,t2 # t0=remainder of the previous division
    divu s0,s0,t2 # s0=s0/t2 (divide s0 by the radix value in t2)
    
    li t5,10 # load t5 with 10 because RISC-V does not allow constants for branches
    blt t0,t5,decimal_digit
    bge t0,t5,hexadecimal_digit
    
    decimal_digit: # we go here if it is only a digit 0 to 9
    addi t0,t0,'0'
    j save_digit
    
    hexadecimal_digit:
    addi t0,t0,-10
    addi t0,t0,'A'
    
    save_digit:
    sb t0,(t1) # store byte from t0 at address t1
    beq s0,zero,intstr_end
    addi t1,t1,-1
    addi t3,t3,1
    j digits_start
    
    intstr_end:
    
    li t0,'0'
    prefix_zeros:
    bge t3,t4,end_zeros
    addi t1,t1,-1
    sb t0,(t1) # store byte from t0 at address t1
    addi t3,t3,1
    j prefix_zeros
    end_zeros:
    
    mv s0,t1
    
    jr ra
    
    #this function calls intstr to convert the s0 register into a string
    #then it uses a system call to print the string
    #it also uses the stack to save the value of s0 and ra (return address)
    
    putint:
    sw ra,0(sp)
    sw s0,4(sp)
    jal intstr
    #print string
    li a7,4      # load immediate, v0 = 4 (4 is print string system call)
    mv a0,s0  # load address of string to print into a0
    ecall
    lw ra,0(sp)
    lw s0,4(sp)
    jr ra
    
    
    
    
    # RISC-V does not allow constants for branches
    # Because of this fact, the RISC-V version of strint
    # requires a lot more code than the MIPS version
    # Whatever value I wanted to compare in the branch statement
    # was placed in the t5 register on the line before the conditional branch
    # Even though it is completely stupid, it has proven to work
    
    strint:
    
    mv t1,s0 # copy string address from s0 to t1
    li s0,0
    
    lb t2,radix     # load value of radix into t2
    
    read_strint:
    lb t0,(t1)
    addi t1,t1,1
    beq t0,zero,strint_end
    
    #if char is below '0' or above '9', it is outside the range of these and is not a digit
    li t5,'0'
    blt t0,t5,not_digit
    li t5,'9'
    bgt t0,t5,not_digit
    
    #but if it is a digit, then correct and process the character
    is_digit:
    andi t0,t0,0xF
    j process_char
    
    not_digit:
    #it isn't a digit, but it could be perhaps and alphabet character
    #which is a digit in a higher base
    
    #if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    li t5,'A'
    blt t0,t5,not_upper
    li t5,'Z'
    bgt t0,t5,not_upper
    
    is_upper:
    li t5,'A'
    sub t0,t0,t5
    addi t0,t0,10
    j process_char
    
    not_upper:
    
    #if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    li t5,'a'
    blt t0,t5,not_lower
    li t5,'z'
    bgt t0,t5,not_lower
    
    is_lower:
    li t5,'a'
    sub t0,t0,t5
    addi t0,t0,10
    j process_char
    
    not_lower:
    
    #if we have reached this point, result invalid and end function
    #this is only reached if the byte was not a valid digit or alphabet character
    j strint_end
    
    process_char:
    
    bgt t0,t2 strint_end #;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    
    mul s0,s0,t2 # multiply s0 by the radix
    add s0,s0,t0     # add the correct value of this digit
    
    j read_strint # jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    jr ra