Chapter 11: Bonus Content

When I wrote Assembly Arithmetic Algorithms for DOS, I had planned for chapter 10 to be the final chapter. However, it seemed like a good idea to include the original source code for the C version of chastehex. Although this book is not about the C programming language, C programmers can gain insight from seeing the original 150 lines of code and comments of the main source file compared to the Assembly version.

I also want my readers to have a useful program that they can compile and run on any operating system or architecture. It is with great pleasure that I present the portable ANSI C version of chastehex!

chastehex main.c

  1 /*
  2 This is the original C version of chastehex upon which the Assembly versions were based.
  3 */
  4 
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include "chastelib.h"
  8 
  9 FILE* fp; /*file pointer*/
 10 char bytes[17]; /*the byte buffer for hex and text dumping*/
 11 int count=1; /*keeps track of how many bytes were read during each row*/
 12 
 13 /*outputs the ASCII text to the right of the hex field*/
 14 void textdump()
 15 {
 16  int a,x=0;
 17 
 18  x=count;
 19  while(x<0x10)
 20  {
 21   putstr("   ");
 22   x++;
 23  }
 24 
 25  x=0;
 26  while(x<count)
 27  {
 28   a=bytes[x];
 29   if( a < 0x20 || a > 0x7E ){a='.';bytes[x]=a;}
 30   x++;
 31  }
 32  bytes[x]=0;
 33 
 34  putstr(bytes);
 35 }
 36 
 37 /*outputs up to 16 bytes on each row in hexadecimal*/
 38 
 39 void hexdump()
 40 {
 41  int x,address=0;
 42  x=0;
 43  while((count=fread(bytes,1,16,fp)))
 44  {
 45   int_width=8;
 46   putint(address);
 47   putstr(" ");
 48 
 49   int_width=2;
 50   x=0;
 51   while(x<count)
 52   {
 53    putint(bytes[x]&0xFF);
 54    putstr(" ");
 55    x++;
 56   }
 57   textdump();
 58   putstr("\n");
 59 
 60   address+=count;
 61  }
 62  putstr("EOF\n");
 63 }
 64 
 65 int main(int argc, char *argv[])
 66 {
 67  int argx,x,c;
 68    
 69  radix=0x10; /*set radix for integer output*/
 70  int_width=1; /*set default integer width*/
 71 
 72  if(argc==1)
 73  {
 74   putstr
 75   (
 76    "Welcome to chastehex! The tool for reading and writing bytes of a file!\n\n"
 77    "To hexdump an entire file:\n\n\tchastehex file\n\n"
 78    "To read a single byte at an address:\n\n\tchastehex file address\n\n"
 79    "To write a single byte at an address:\n\n\tchastehex file address value\n\n"
 80   );
 81   return 0;
 82  }
 83 
 84  if(argc>1)
 85  {
 86   fp=fopen(argv[1],"rb+");
 87   if(fp==NULL)
 88   {
 89    putstr(argv[1]);
 90    putstr("\nFailed to open file\n");
 91    return 1;
 92   }
 93   else
 94   {
 95    putstr(argv[1]);
 96    putstr("\n");
 97   }
 98  }
 99 
100  if(argc==2)
101  {
102   hexdump(); /*hex dump only if filename given*/
103  }
104 
105  if(argc>2)
106  {
107   x=strint(argv[2]); /*extract a number from the argument string after the filename*/
108   fseek(fp,x,SEEK_SET); /*seek to the address given in argument*/
109  }
110 
111  /*read a byte at address of second arg*/
112  if(argc==3)
113  {
114   c=fgetc(fp);
115   int_width=8;
116   putint(x);
117   putstr(" ");
118   if(c==EOF){putstr("EOF");}
119   else
120   {
121    int_width=2;
122    putint(c);
123   }
124   putstr("\n");
125  }
126 
127  /*any arguments past the address are hex bytes to be written*/
128  if(argc>3)
129  {
130   argx=3;
131   while(argx<argc)
132   {
133    c=strint(argv[argx]);
134    int_width=8;
135    putint(x);
136    putstr(" ");
137    int_width=2;
138    putint(c);
139    putstr("\n");
140    fputc(c,fp);
141    x++;
142    argx++;
143   }
144  }
145  
146  fclose(fp);
147  return 0;
148 }
149 
150 /* gcc -Wall -ansi -pedantic main.c -o chastehex */

Of course, to compile the main.c file, you will need the C version of my standard library. For the most part, these functions are the same as the Assembly versions of the functions presented in this book. However, since C code can’t refer to register names, variable names were chosen based on how they were used. For example, “s” is usually a string, “c” is a character, and “count” is used for the number of bytes used in the fwrite function of the C standard library as part of the putstring function.

chastelib.h

  1 /*
  2  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.
  3  I did it partly for future programming plans and also because it helped me learn a lot in the process about how pointers work
  4  as well as which features the standard library provides, and which things I need to write my own functions for.
  5 
  6  As it turns out, the integer output routines for C are too limited for my tastes. This library corrects this problem.
  7  Using the global variables and functions in this file, integers can be output in bases/radixes 2 to 36.
  8  
  9  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.
 10 */
 11 
 12 /*
 13  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.
 14  The integer conversion function (intstr) always references a pointer to this global string, and this allows other C standard library functions
 15  such as printf to display the integers to standard output or even possibly to files.
 16  This string can be repurposed for absolutely anything I desire.
 17 */
 18 
 19 
 20 #define usl 0x100 /*usl stands for Unsigned or Universal String Length.*/
 21 char int_string[usl+1]; /*global string which will be used to store string of integers. Size is usl+1 for terminating zero*/
 22 
 23 /*radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal*/
 24 int radix=2;
 25 /*default minimum digits for printing integers*/
 26 int int_width=1;
 27 
 28 /*
 29 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).
 30 
 31 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!
 32 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.
 33 */
 34 
 35 char *intstr(unsigned int i)    /*Chastity's supreme integer to string conversion function*/
 36 {
 37  int width=0;                   /*the width or how many digits including prefixed zeros are printed*/
 38  char *s=int_string+usl;        /*a pointer starting to the place where we will end the string with zero*/
 39  *s=0;                          /*set the zero that terminates the string in the C language*/
 40  while(i!=0 || width<int_width) /*loop to fill the string with every required digit plus prefixed zeros*/
 41  {
 42   s--;                          /*decrement the pointer to go left for correct digit placing*/
 43   *s=i%radix;                   /*get the remainder of division by the radix or base*/
 44   i/=radix;                     /*divide the input by radix*/
 45   if(*s<10){*s+='0';}           /*convert digits 0 to 9 to the ASCII character for that digit*/
 46   else{*s=*s+'A'-10;}           /*for digits higher than 9, convert to letters starting at A*/
 47   width++;                      /*increment the width so we know when enough digits are saved*/
 48  }
 49  return s;                      /*return this string to be used by putstr,printf,std::cout or whatever*/
 50 }
 51 
 52 /*
 53  This function prints a string using fwrite.
 54  This algorithm is the best C representation of how my Assembly programs also work.
 55  Its true purpose is to be used in the putint function for conveniently printing integers, 
 56  but it can print any valid string.
 57 */
 58 
 59 int putstring(const char *s)
 60 {
 61  int count=0;              /*used to count how many bytes will be written*/
 62  const char *p=s;          /*pointer used to find terminating zero of string*/
 63  while(*p){p++;}           /*loop until zero found and immediately exit*/
 64  count=p-s;                /*count is the difference of pointers p and s*/
 65  fwrite(s,1,count,stdout); /*https://cppreference.com/w/c/io/fwrite.html*/
 66  return count;             /*return how many bytes were written*/
 67 }
 68 
 69 /*
 70  A function pointer named putstr which is a shorter name for calling putstring
 71  But this doesn't exist just to save bytes of source files. Otherwise I wouldn't have these huge comments!
 72  This exists so that all strings can be redirected to another function for output.
 73  For example, if the strings were written to a log file during a game which didn't use a terminal.
 74  
 75  But the most common use case is "putstr=addstr" when using the ncurses library to manage
 76  terminal control functions for a text based game. Having the putstr pointer allows me to 
 77  include this same source file and use it for ncurses based projects.
 78 */
 79 int (*putstr)(const char *)=putstring;
 80 
 81 /*
 82  This function uses both intstr and putstring to print an integer in the currently selected radix and width.
 83 */
 84 
 85 void putint(unsigned int i)
 86 {
 87  putstr(intstr(i));
 88 }
 89 
 90 /*
 91 The strint_errors variable is used to keep track of how many errors happened in the strint function.
 92 The following errors can occur:
 93 
 94 Radix is not in range 2 to 36
 95 Character is not a number 0 to 9 or alphabet A to Z (in either case)
 96 Character is alphanumeric but is not valid for current radix
 97 
 98 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.
 99 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.
100 */
101 
102 int strint_errors = 0; 
103 
104 /*
105  The strint function is my own replacement for the strtol function from the C standard library.
106  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.
107  However, my function is simpler because it only requires 2 arguments instead of three, and it also does not handle negative numbers.
108 I have never needed negative integers, but if I ever do, I can use the standard functions or write my own in the future.
109 */
110 
111 int strint(const char *s)
112 {
113  int i=0;
114  char c;
115  strint_errors = 0; /*set zero errors before we parse the string*/
116  if( radix<2 || radix>36 ){ strint_errors++; printf("Error: radix %i is out of range!\n",radix);}
117  while( *s == ' ' || *s == '\n' || *s == '\t' ){s++;} /*skip whitespace at beginning*/
118  while(*s!=0)
119  {
120   c=*s;
121   if( c >= '0' && c <= '9' ){c-='0';}
122   else if( c >= 'A' && c <= 'Z' ){c-='A';c+=10;}
123   else if( c >= 'a' && c <= 'z' ){c-='a';c+=10;}
124   else if( c == ' ' || c == '\n' || c == '\t' ){break;}
125   else{ strint_errors++; printf("Error: %c is not an alphanumeric character!\n",*s);break;}
126   if(c>=radix){ strint_errors++; printf("Error: %c is not a valid character for radix %i\n",*s,radix);break;}
127   i*=radix;
128   i+=c;
129   s++;
130  }
131  return i;
132 }
133 
134 
135 /*
136  Those four functions above are the core of chastelib.
137  While there may be extensions written for specific programs, these functions are essential for absolutely every program I write.
138  
139  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.
140  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.
141  
142  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.
143 */

If you come from a background of C programming, you may appreciate how much more readable the C version of the program is compared to the Assembly code. I will not deny that writing Assembly code takes longer and is harder for most people to understand.

However, most of my C programs were the initial prototypes before I wrote the Assembly versions of the same thing. I do this because the C Programming Language provides maximum portability and readability.

But, Assembly language provides the maximum efficiency and control of hardware that I require for my own satisfaction. By using the right combination of C and Assembly, I have the best of both worlds:

  1. C programs available for all devices and operating systems.
  2. Assembly programs optimized for the specific device or operating system that I am using.

There are other tools I have written besides chastehex. However, at this time, chastehex is the only one I have tested and improved enough to be confident that I am providing quality code that can be used, studied, shared, and modified.

When I said that chastehex is not just a program, but a philosophy, I mean that it embodies the four freedoms outlined in the GNU General Public License and also my philosophy of well-written code.

I believe that computer programming is a task for humans to tell computers what we want done. I teach people so that, like me, they can write better software, in any language, and not depend on software vendors who may decide to fix bugs in the software you purchased from them if you bribe them with enough money.

In the modern world, I view code as a fundamental human right because our lives depend on software that works with our hardware and does not work against us and our right to privacy and freedom of speech.