Author: Chastity White Rose

  • Professional vs Open Source

    I totally spent 5 hours writing a program in Assembly Language. I realized that what I am doing is something that cannot be done for money in any way. The best that I can do is to learn the technical skills and then continue working on my books and API references I plan to write.

    But as far as my programs themselves, they don’t fit the model of how the world works. In a job, you are constantly pressured to do as much work in as short of a time as possible. Therefore, you are paid, hired, or fired based on how fast the program can be written for the client, regardless of whether it works correctly or has bugs or security flaws.

    But when I write computer software for myself, I am the only one to decide whether it meets my standards. I have said many times over the past 20 years that I would not want a job as a programmer. This is because I am only interested in the things I want to do. I find that I am at peace when the things I do are not attached to the love of money.

    I believe that money and the corporate world actually ruins top quality work. There are also things that the Open-Source Software movement has made possible that could never be done under a company with a proprietary system. Strangers who don’t even know each other offer improvements on programming forums to people out of the goodness of their heart with no financial incentive.

    I see something similar in the world of Chess. People who are playing for fun can enjoy the game at a higher level than those who are stressed out competing in tournaments to win money. I sometimes feel myself pulled in a direction I didn’t know existed. I will work to explore this feeling I get where I achieve inner peace for a moment when I am having pure fun and losing track of the time.

    I used to feel this way when playing video games as a kid. Now I get it from writing books, blog posts, and computer programs. I still enjoy games though. I plan to eventually getting back into my games but I have had a busy life lately.

  • Windows chastehex

    This is the source code of the Windows Assembly version of my program I invented called chastehex. I still need to record a video tutorial for how to use it, but it also displays a help message that is self-explanatory. However, most people are do not know how to assemble this from source. The main purpose of posting it on my blog is for my own backup. There is also an executable available on Github for those who want to try it out.

    https://github.com/chastitywhiterose/chastehex/tree/main/asm/windows

    It can arbitrarily read to or write from any address of a file with the correct arguments. It was designed to be part of a script to modify sections of files without requiring the installation of a hex editor. The best part is that it is free and open source because I wrote it and I say so. If you read the Github repository, you will also see that I released it under the GNU GENERAL PUBLIC LICENSE Version 3.

    main.asm

    format PE console
    include 'win32ax.inc'
    include 'chastelibw32.asm'
    
    main:
    
    mov [radix],16 ; Choose radix for integer output.
    mov [int_width],1
    
    ;get command line argument string
    call [GetCommandLineA]
    
    mov [arg_start],eax ;store start of arg string
    
    ;short routine to find the length of the string
    ;and whether arguments are present
    mov ebx,eax
    find_arg_length:
    cmp [ebx], byte 0
    jz found_arg_length
    inc ebx
    jmp find_arg_length
    found_arg_length:
    ;at this point, ebx has the address of last byte in string which contains a zero
    ;we will subtract to get and store the length of the string
    mov [arg_end],ebx
    sub ebx,eax
    mov eax,ebx
    mov [arg_length],eax
    
    ;display the arg string to make sure it is working correctly
    ;mov eax,[arg_start]
    ;call putstring
    ;call putline
    
    ;print the length in bytes of the arg string
    ;mov eax,[arg_length]
    ;call putint
    
    ;this loop will filter the string, replacing all spaces with zero
    mov ebx,[arg_start]
    arg_filter:
    cmp byte [ebx],' '
    ja notspace ; if char is above space, leave it alone
    mov byte [ebx],0 ;otherwise it counts as a space, change it to a zero
    notspace:
    inc ebx
    cmp ebx,[arg_end]
    jnz arg_filter
    
    arg_filter_end:
    
    ;optionally print first arg (name of program)
    ;mov eax,[arg_start]
    ;call putstring
    ;call putline
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz help
    
    mov [file_name],eax
    mov eax,file_open_message
    call putstring
    mov eax,[file_name]
    call putstring
    call putline
    
    jmp open_sesame
    
    help:
    
    mov eax,help_message
    call putstring
    
    jmp args_none
    
    open_sesame:
    
    ;open a file with the CreateFileA function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
    
    push 0           ;NULL: We are not using a template file
    push 0x80        ;FILE_ATTRIBUTE_NORMAL
    push 3           ;OPEN_EXISTING
    push 0           ;NULL: No security attributes
    push 0           ;NULL: Share mode irrelevant. Only this program reads the file.
    push 0x10000000  ;GENERIC_ALL access mode (Read+Write)
    push [file_name] ;
    call [CreateFileA]
    
    ;check eax for file handle or error code
    ;call putint
    cmp eax,-1
    jnz file_ok
    
    mov eax,file_error_message
    call putstring
    call [GetLastError]
    call putint
    jmp args_none ;end program if the file was not opened
    
    ;this label is jumped to when the file is opened correctly
    file_ok:
    
    mov [file_handle],eax
    
    mov [int_newline],0 ;disable automatic printing of newlines after putint
    ;we will be manually printing spaces or newlines depending on context
    
    ;before we proceed, we also check for more arguments.
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz hexdump ;proceed to normal hex dump if no more args
    
    ;otherwise interpret the arg as a hex address to seek to
    
    call strint
    mov [file_offset],eax
    mov eax,file_seek_message
    call putstring
    mov eax,[file_offset]
    call putint
    call putline
    
    ;seek to address of file with SetFilePointer function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointer
    push 0             ;seek from beginning of file (SEEK_SET)
    push 0             ;NULL: We are not using a 64 bit address
    push [file_offset] ;where we are seeking to
    push [file_handle] ;seek within this file
    call [SetFilePointer]
    
    ;check for more args
    call get_next_arg
    cmp eax,[arg_end]
    jz read_one_byte ;proceed to read one byte mode
    
    ;otherwise, write the rest of the arguments as bytes to the file!
    write_bytes:
    call strint
    mov [byte_array],al
    
    ;write only 1 byte using Win32 WriteFile system call.
    push 0              ;Optional Overlapped Structure 
    push 0              ;Optionally Store Number of Bytes Written
    push 1              ;Number of bytes to write
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [WriteFile]
    
    mov eax,[file_offset]
    inc [file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    ;check for more args
    call get_next_arg
    cmp eax,[arg_end]
    jnz write_bytes
    ;continue write if the args still exist
    ;otherwise end program
    jmp args_none
    
    read_one_byte:
    
    ;read only 1 byte using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 1              ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [ReadFile]
    
    cmp [bytes_read],1 
    jb print_EOF ;if less than one bytes read, there is an error
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    jmp args_none
    
    hexdump:
    
    ;read bytes using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 16             ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [ReadFile]     ;all the data is in place, do the write thing!
    
    mov eax,[bytes_read]
    ;call putint
    ;mov eax,byte_array
    ;call putstring
    
    cmp [bytes_read],1 
    jb print_EOF ;if less than one bytes read, there is an error
    
    call print_bytes_row
    
    jmp hexdump
    
    print_EOF:
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,end_of_file
    call putstring
    call putline
    
    jmp args_none
    
    
    
    
    ;this loop is very safe because it only prints arguments if they are valid
    ;if the end of the args are reached by comparison of eax with [arg_end]
    ;then it will jump to args_none and proceed from there
    args_list:
    call get_next_arg
    cmp eax,[arg_end]
    jz args_none
    call putstring
    call putline
    jmp args_list
    args_none:
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    arg_start  dd 0 ;start of arg string
    arg_end    dd 0 ;address of the end of the arg string
    arg_length dd 0 ;length of arg string
    arg_spaces dd 0 ;how many spaces exist in the arg command line
    
    ;variables for managing file IO.
    file_name dd 0
    bytes_read dd 0 ;how many bytes are read with ReadFile operation
    byte_array db 16 dup '?',0
    file_handle dd 0
    file_offset dd 0
    
    ;variables for displaying messages
    file_open_message db 'opening: ',0
    file_seek_message db 'seek: ',0
    file_error_message db 'error: ',0
    end_of_file db 'EOF',0
    read_error_message db 'Failure during reading of file. Error number: ',0
    
    help_message db 'Welcome to chastehex! The tool for reading and writing bytes of a file!',0Ah,0Ah
    db 'To hexdump an entire file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'To read a single byte at an address:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'To write a single byte at an address:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah
    db 'The file must exist before you launch the program.',0Ah
    db 'This design was to prevent accidentally opening a mistyped filename.',0Ah,0
    
    ;function to move ahead to the next art
    ;only works after the filter has been applied to turn all spaces into zeroes
    get_next_arg:
    mov ebx,[arg_start]
    find_zero:
    cmp byte [ebx],0
    jz found_zero
    inc ebx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    find_non_zero:
    cmp ebx,[arg_end]
    jz arg_finish ;if ebx is already at end, nothing left to find
    cmp byte [ebx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc ebx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_start],ebx ; save this index to variable
    mov eax,ebx ;but also save it to ax register for use
    ret
    ;we can know that there are no more arguments when
    ;the either [arg_start] or eax are equal to [arg_end]
    
    
    
    ;this function prints a row of bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov ebx,byte_array
    mov ecx,[bytes_read]
    add [file_offset],ecx
    next_byte:
    mov eax,0
    mov al,[ebx]
    mov [int_width],2
    call putint
    call putspace
    
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_byte
    
    call putline
    
    ret
    

    chastelibw32.asm

    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ; function to print zero terminated string pointed to by register eax
    
    stdout dd 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax ; copy eax to ebx as well. Now both registers have the address of the main_string
    
    putstring_strlen_start: ; this loop finds the lenge of the string as part of the putstring function
    
    cmp [ebx],byte 0 ; compare byte at address ebx with 0
    jz putstring_strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub ebx,eax ;ebx will now have correct number of bytes
    
    ;Write String using Win32 WriteFile system call.
    push 0              ;Optional Overlapped Structure 
    push 0              ;Optionally Store Number of Bytes Written
    push ebx            ;Number of bytes to write
    push eax            ;address of string to print
    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    call [WriteFile]    ;all the data is in place, do the write thing!
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret ; this is the end of the putstring function return to calling location
    
    ;this is the location in memory where digits are written to by the putint function
    int_string     db 32 dup '?' ;enough bytes to hold maximum size 32-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 0Ah,0
    
    radix dd 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dd 8
    
    ;this function creates a string of the integer in eax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads eax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov ebx,int_newline-1 ;find address of lowest digit(just before the newline 0Ah)
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add edx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub edx,10
    add edx,'A'
    
    save_digit:
    
    mov [ebx],dl
    cmp eax,0
    jz intstr_end
    dec ebx
    inc ecx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp ecx,[int_width]
    jnb end_zeros
    dec ebx
    mov [ebx],byte '0'
    inc ecx
    jmp prefix_zeros
    end_zeros:
    
    mov eax,ebx ; now that the digits have been written to the string, display it!
    
    ret
    
    
    ; function to print string form of whatever integer is in eax
    ; 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 eax
    push ebx
    push ecx
    push edx
    
    call intstr
    
    call putstring
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ;this function converts a string pointed to by eax into an integer returned in eax 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 ebx,eax ;copy string address from eax to ebx because eax will be replaced soon!
    mov eax,0
    
    read_strint:
    mov ecx,0 ; zero ecx so only lower 8 bits are used
    mov cl,[ebx]
    inc ebx
    cmp cl,0 ; compare byte at address edx 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 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
    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 ecx,[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 edx,0 ;zero edx because it is used in mul sometimes
    mul [radix]    ;mul eax with radix
    add eax,ecx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    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 eax
    mov eax,space
    call putstring
    pop eax
    ret
    
    putline:
    push eax
    mov eax,line
    call putstring
    pop eax
    ret
    
  • Chastity Windows Reverse Engineering Notes

    The Windows version of FASM includes header files for the Windows API. It also includes some examples, but not a single one of them were a simple “Hello World” console program.

    Fortunately, I was able to find one that actually assembled and ran on the FASM forum. Below is the source code.

    format PE console
    include 'win32ax.inc'
    .code
    start:
    invoke  WriteConsole, <invoke GetStdHandle,STD_OUTPUT_HANDLE>,"Hello World!",12,0
    invoke  ExitProcess,0
    .end start
    

    To get it working required me to keep the include files in a location I remembered and set the include environment variable. I found this out from the FASM Windows documentation.

    set include=C:\fasm\INCLUDE

    However, there was another problem. The example that I found online and got working uses macros, most specifically, one called “invoke”. While this works if you include the headers, it hides the details of what is actually happening. Therefore, I decided to reverse engineer the process by using NOP instructions to sandwich the bytes of machine code.

    90 hex is the byte for NOP (No OPeration). So to extract the macro call that exits the program, I use this.

    db 10h dup 90h
    invoke  ExitProcess,0
    db 10h dup 90h
    

    Then I disassemble the executable and find the actual instructions given.

    ndisasm main.exe -b 32 > disasm.txt

    As simple as this method is, it actually works. For example, this output is given as part of the output.

    0000022D  90                nop
    0000022E  90                nop
    0000022F  90                nop
    00000230  90                nop
    00000231  90                nop
    00000232  90                nop
    00000233  90                nop
    00000234  90                nop
    00000235  90                nop
    00000236  90                nop
    00000237  90                nop
    00000238  90                nop
    00000239  90                nop
    0000023A  90                nop
    0000023B  90                nop
    0000023C  90                nop
    0000023D  6A00              push dword 0x0
    0000023F  FF1548204000      call dword near [0x402048]
    00000245  90                nop
    00000246  90                nop
    00000247  90                nop
    00000248  90                nop
    00000249  90                nop
    0000024A  90                nop
    0000024B  90                nop
    0000024C  90                nop
    0000024D  90                nop
    0000024E  90                nop
    0000024F  90                nop
    00000250  90                nop
    00000251  90                nop
    00000252  90                nop
    00000253  90                nop
    00000254  90                nop
    

    There can be no mistake that it is that location between the NOPs where the relevant code is. Therefore, I replaced the macro that exits the program with this.

    ;Exit the process with code 0
     push 0
     call [ExitProcess]
    

    What I learned

    As I repeated the same process for the other macros, I found that the way system calls in Windows work is that the numbers are pushed onto the stack in the reverse order they are needed. I was able to decode the macros and get a working program without the use of “invoke”. Here is the full source!

    format PE console
    include 'win32ax.inc'
    
    main:
    
    ;Write 13 bytes from a string to standard output
    push 0              ;this must be zero. I have no idea why!  
    push 13             ;number of bytes to write
    push main_string    ;address of string to print
    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    call [WriteConsole] ;all the data is in place, do the write thing!
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    main_string db 'Hello World!',0Ah
    

    I don’t know much about the Windows API, but I did discover some helpful information when I searched the names of these functions that were part of the original macros.

    https://learn.microsoft.com/en-us/windows/console/getstdhandle
    https://learn.microsoft.com/en-us/windows/console/writeconsole

    Why I did this

    You might wonder why I even bothered to get a working Windows API program in Assembly Language. After all, I am a Linux user to the extreme. However, since Windows is the most used operating system for the average person, I figured that if I write any useful programs in Assembly for 32-bit Linux, I can probably port them over to Windows by changing just a few things.

    Since my toy programs are designed to write text to the console anyway and I don’t do GUI stuff unless I am programming a game in C with SDL, I now have enough information from this small Hello World example to theoretically write anything to the console that I might want to in an official Windows executable.

    Obviously I need to learn a lot more for bigger programs but this is the first Assembly program I have ever gotten working for Windows, despite my great success with DOS and Linux, which are easier because they are better documented and ARE TAUGHT BETTER by others. People programming Assembly in Windows have been ruined by macros which hide the actual instructions being used. As I learn how these things work, I will be sure to pass on the information to others!

  • New DOS program: chastehex

    I finally rewrote my chastehex program in DOS Assembly Language. Unlike the Linux version, it is not prepared to handle files over 64 kilobytes. There may be ways to improve upon it but writing this program was more of a proof of concept than anything because I already wrote the C version and the Linux 32 bit Assembly version. When this is assembled using FASM, it becomes a .com file which can be run inside DOSBox.

    If no arguments are used when running the program, it will display a brief message explaining the 3 modes of usage.

    org 100h     ;DOS programs start at this address
    
    mov word [radix],16 ; can choose radix for integer output!
    
    mov ch,0     ;zero ch (upper half of cx)
    mov cl,[80h] ;load length of the command string
    cmp cx,0
    jnz args_exist
    
    mov ax,help
    call putstring
    
    jmp ending
    
    args_exist:
    mov dx,81h   ;Point dx to the beginning of string
    inc dx       ;go to next char
    dec cx       ;but subtract 1 from count
    mov [arg_index],dx ;save index to variable so dx is free to change as needed
    
    ;find the end of the string based on length
    mov ax,dx
    add ax,cx
    ;now we know where the string ends.
    mov [arg_string_end],ax ;this is the end of the arg string. important for later
    ;call putint ; print address where entire arg string ends
    
    ;this routine replaces all non printable characters with zero in the arg string
    mov bx,dx
    filter:
    cmp byte [bx],' '
    ja notspace ; if char is above space, leave it alone
    mov byte [bx],0 ;otherwise it counts as a space, change it to a zero
    notspace:
    inc bx
    cmp bx,[arg_string_end] ;are we at the end of the arg string?
    jnz filter ;if not at end, continue the filter
    
    filter_end:
    mov byte [bx],0 ;terminate the ending with a zero for safety
    
    ;now that the argument string is prepared, we will try to use the first argument as a filename to open
    
    mov ah,3Dh ;call number for DOS open existing file
    mov al,2   ;file access: 0=read,1=write,2=read+write
    mov dx,[arg_index] ;string address to interpret as filename
    int 21h ;DOS call to finalize open function
    
    mov [file_handle],ax
    
    jc file_error ;if carry flag is set, we have an error, otherwise, file is open
    
    file_opened:
    mov ax,dx
    call putstring
    call putline
    ;mov ax,file_opened_message
    ;call putstring
    ;mov ax,[file_handle]
    ;call putint
    jmp use_file
    
    ;this section prints error message and then ends the program if file error found
    file_error: ;prints error code2=file not found
    mov ax,dx
    call putstring
    call putline
    mov ax,file_error_message
    call putstring
    mov ax,[file_handle]
    call putint
    jmp arg_loop_end
    
    ;how we use the file depends on the number of arguments given
    ;if no arguments other than the filename exist, we do a regular hex dump
    use_file:
    
    call get_next_arg ;get address of next arg and return into ax register
    cmp ax,[arg_string_end] ;this time, if ax equals end of string, we hex dump and then end the program later
    jz hexdump ;jump to hexdump section
    
    ;otherwise, if there are more args, as contains next arg
    ;then we use the strint function to transform it into a number
    ;call putstring
    ;call putline
    
    call strint ;turn string at address ax into a number returned in ax
    ;call putint
    
    ;this number will be out new offset to seek to
    mov [file_offset],ax
    
    mov ah,42h           ;lseek call number
    mov al,0            ;seek origin 00h start of file,01h current file position,02h end of file
    mov bx,[file_handle]
    mov cx,0            ;upper word of offet
    mov dx,[file_offset]
    int 21h
    
    jc arg_loop_end ;end program if seek error (though I can't imagine how it would fail)
    
    ;check if there are any more args
    call get_next_arg
    cmp ax,[arg_string_end]
    jz dump_byte ;jump to dump_byte section and continue with read mode
    mov [int_newline],0 ;disable auto newline printing
    jmp arg_loop ;otherwise we jump to the arg loop and write the values as bytes starting at offset
    
    ;this next section is the reading mode that reads one byte. It only executes if we have not provided bytes to write to the new address
    ;because we have an argument for an address we will read only this byte and display it
    dump_byte:
    
    mov ah,3Fh           ;call number for read function
    mov bx,[file_handle] ;store file handle to read from in bx
    mov cx,1             ;we are reading only 1 byte
    mov dx,byte_array    ;store the bytes here
    int 21h
    
    mov cx,ax ;number of bytes read
    
    mov [int_newline],0 ;disable auto newline printing
    ;set width to 8 and display offset
    mov [int_width],8
    mov ax,[file_offset]
    call putint
    call putspace
    
    cmp cx,1
    jz not_eof ;skip past here as long as one byte was read otherwise show EOF
    mov ax,end_of_file
    call putstring
    jmp arg_loop_end
    not_eof:
    
    mov ah,0 ;zero upper half of ax
    mov al,[byte_array]
    
    mov [int_width],2
    call putint
    ;call putline
    
    jmp arg_loop_end ;we are done so we end the program
    
    hexdump:
    
    ;we start the loop with a call to read exactly 16 bytes
    
    mov ah,3Fh           ;call number for read function
    mov bx,[file_handle] ;store file handle to read from in bx
    mov cx,16            ;we are reading sixteen bytes
    mov dx,byte_array    ;store the bytes here
    int 21h
    
    ;call putint ;check the number of bytes read
    
    ;important note: the number of bytes read should be 16 or less and this is not an error
    ;zero is expected if we are at the end of the file.
    ;however, if it is zero, we print an EOF message and exit
    
    cmp ax,0
    jnz print_row
    mov ax,end_of_file
    call putstring
    jmp arg_loop_end
    
    print_row:
    
    mov cx,ax ;number of bytes read
    
    mov [int_newline],0 ;disable auto newline printing
    ;set width to 8 and display offset
    mov [int_width],8
    mov ax,[file_offset]
    call putint
    call putspace
    add [file_offset],cx ;next offset will show correctly
    
    mov ah,0 ;zero upper half of ax
    mov bx,byte_array
    
    mov [int_width],2
    
    print_byte:
    mov al,[bx]
    call putint
    call putspace
    inc bx
    dec cx
    cmp cx,0
    jnz print_byte
    call putline
    
    jmp hexdump ;jump back to hexdump and attempt another read of a row
    
    ;this loop processes the rest of the arguments
    ;it interprets each one as a byte to write to the current offset
    ;this loop should only execute if a file name and address have already been given
    arg_loop:
    mov ax,[arg_index] ;get address of current arg
    ;call putstring
    
    call strint ;turn string at address ax into a number returned in ax
    
    mov [byte_array],al
    
    mov ah,40h           ; select DOS function 40h write 
    mov bx,[file_handle] ;store file handle to write to in bx
    mov cx,1             ;write 1 byte to this file
    mov dx,byte_array    ;write from this address
    int 21h
    
    ;set width to 8 and display offset
    mov [int_width],8
    mov ax,[file_offset]
    inc [file_offset]
    call putint
    call putspace
    mov [int_width],2
    mov ah,0
    mov al,[byte_array]
    call putint
    call putline
    
    call get_next_arg ;get address of next arg and return into ax register
    
    cmp ax,[arg_string_end] ;if the ax register contains address of the end of args string, end program to avoid failure
    jz arg_loop_end
    jmp arg_loop
    
    arg_loop_end: ;this is the correct end of the program
    
    ;close the file if it is open
    mov ah,3Eh
    mov bx,[file_handle]
    int 21h
    
    ending:
    mov ax,4C00h ; Exit program
    int 21h
    
    arg_string_end dw 0
    arg_index dw 0
    file_error_message db 'Could not open the file! Error number: ',0
    file_opened_message db 'The file is open with handle: ',0
    file_handle dw 0
    read_error_message db 'Failure during reading of file. Error number: ',0
    end_of_file db 'EOF',0
    
    ;where we will store data from the file
    byte_array db 16 dup '?'
    file_offset dw 0
    
    
    ;function to move ahead to the next art
    ;only works after the filter has been applied to turn all spaces into zeroes
    
    get_next_arg:
    mov bx,[arg_index] ;dx has address of current arg
    find_zero:
    cmp byte [bx],0
    jz found_zero
    inc bx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    find_non_zero:
    cmp bx,[arg_string_end]
    jz arg_finish ;if bx is already at end, nothing left to find
    cmp byte [bx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc bx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_index],bx ; save this index to variable
    mov ax,bx ;but also save it to ax register for use
    ret
    
    include 'chastelib16.asm'
    
    help db 'Welcome to chastehex! The tool for reading and writing bytes of a file!',0Ah
    db 'To hexdump an entire file:',0Ah,9,'chastehex file',0Ah
    db 'To read a single byte at an address:',0Ah,9,'chastehex file address',0Ah
    db 'To write a single byte at an address:',0Ah,9,'chastehex file address value',0Ah
    db 'The file must exist before you launch the program.',0Ah
    db 'This design was to prevent accidentally opening a mistyped filename.',0Ah,0
    

    Below is what is inside the ‘chastelib16.asm’ file which is included by the above source file. I keep it separate because it is my own standard library which was designed for everything needed to build chastehex. Routines for printing strings and numbers are the largest part. It also has the strint function which converts strings into numbers. That is how it interprets the second and beyond arguments as hexadecimal numbers.

    ; 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 [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,[stdout]            ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    
    
    ;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 [bx],byte '0'
    inc cx
    jmp prefix_zeros
    end_zeros:
    
    mov ax,bx ; store string in ax for display later
    
    ret
    
    
    
    ;function to print string form of whatever integer is in eax
    ;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 eax into an integer returned in eax 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 eax will be replaced soon!
    mov ax,0
    
    read_strint:
    mov cx,0 ; zero ecx so only lower 8 bits are used
    mov cl,[bx]
    inc bx
    cmp cl,0 ; compare byte at address edx 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 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
    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 edx because it is used in mul sometimes
    mul word [radix]    ;mul eax 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
    
  • Life and Career Update

    A lot of cool things have been happening in my life lately. Most of it is good news. I have not been posting on my blog much because I have not had the time. I started a new department at Walmart and so far I enjoy it better than stocking. I work in Maintenance which really means I am helping clean the store. Sometimes emptying trash, sometimes sweeping or scrubbing the floor, and sometimes cleaning the bathrooms, which is difficult only because there are a lot of little things to remember such as filling the supplies and also mopping the floor in the correct way.

    But I think what I love most about my new job as a Maintenance Mop Monkey is how my brain is free to think of other things while I am working because it takes a lot less concentration than stocking does. What am I thinking of during this time? The image below is a fine example of my thoughts!

    I have been programming in Assembly Language for DOS lately. I find Computer Programming to be suspiciously relaxing because the world of math and computers is one where humans can’t hurt me.

    The only risk I face while on my computer is when I get on social media and see hateful things directed at Transgender people, immigrants, or people in other countries. There are plenty of things I could say about what is going on in the world. However, I am not going to do any of that because I have realized that to become a better writer, programmer, and Chess player, I must maintain my supreme autistic focus because that is my gift from God.

    Anyway, when computer programming, there are only numbers. The arithmetic operations of addition, subtraction, multiplication, and division never change and I can count on them more than I can people. My time is better spent improving my code and working on my current book project for teaching DOS Assembly Language. I have even created a simple cover for it.

    The theme is black background with white text because this is the way the DOS (Disk Operating System) looks.

    The goal of this book is a little bit different than most of my books. In fact the target audience for this book is very niche. People don’t write programs for DOS unless it it for fun. There is no money in being able to write programs for DOS because the rest of the world has moved to Windows, MacOS, and Linux which support 32 and 64 bit systems with more capability and 16 bit DOS.

    Instead, this book will be my first computer programming book for the purpose of both teaching Assembly Language to the nerds of the world and as an exercise of my Technical Writing skills. This will be the third technical writing book.

    The first book was Chastity’s Chess Chapters for teaching Chess to people of every kind.

    The second was Minimal Markdown for Authors for teaching other writers how to format and publish their books using similar methods to what I have used.

    This next book, Assembly Arithmetic Algorithms, will be my entry into publishing books about computer programming. This special interest of mine has gone largely ignored because people who write computer programs are *not normal people at all! Computer programming takes concentration, lack of a social life, and the ability to read a lot! I read programming tutorials and books when I have free time. Some of them are free online and others I have bought from various sources in paperback and digital form.

    I can’t claim to be a great programmer, but what I can do is squeeze the information I have learned into a form that allows other people to learn the same things faster. Aside from teaching Chess, I also volunteer to teach computer programming to friends who are interested in learning. I can easily throw together lessons based on questions they have. Where I will find the time for this? I don’t have a clue.

    But the goal of writing books and recording video tutorials is so that eventually I can have a career that does not depend on working at Walmart forever. Although Walmart is going fairly well, I know that the wrong people in management can take away my job if they feel like it. What they cannot take away is my writing, math, and computer skills which I hope will make a difference for people even after I am long dead.

    Also, my Creative Writing diploma finally came!

    I found my time with Full Sail University to be quite positive. I was inspired as I learn the potential there is for someone with the patience to write. My experience with Open Source Software will also be an asset to me because I can use tools that no college or tech company even will know about.

    As I think about possible careers, I think how much potential I have in terms of experience at previous jobs but also skills of writing and technical details that most people simply haven’t learned because it takes someone with the willingness to learn for the fun of it even when there is no promise of financial gain. If I do make money in a future career using my skills, that is great, but it is not the primary motive.

    I remember the words of Carol Keepes who used to be my Assistant Manager back in 2012 when I was a courtesy clerk at Hy-Vee. She said “90% of what people do, they wouldn’t be doing if it wasn’t about money.” I think about this often and it is the reason I have made decisions in life that look foolish to most of the world. I want to make sure that I did things because I really loved the process rather than viewing them as a means to an end.

    See my post The Prayer of Saint Chastity for clarification on my priorities.