Tag: artificial-intelligence

  • AAA Windows Chapter 11: Command Line Arguments

    This post is another chapter of my book: Assembly Arithmetic Algorithms for Windows.

    In chapter 10, I presented a program that prints a hex dump of a file named “byte.bin”. However, what if we want a hex dump tool that allows the user to provide a filename that they want a hex dump from?

    To do this, we have two main ways. The first would be to call the getstring function, as seen in chapter 6, at the beginning of the program and ask the user for which file they want to open. However, there is a serious problem in this because then every time the file was modified, the user has to retype the filename.

    The Windows API has a function for this and it is called GetCommandLineA.

    https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getcommandlinea

    Using the GetCommandLineA is not complicated by any means because it takes no arguments. It returns a pointer to a null terminated string which contains the program name and all the arguments on the line. In 32-bit mode, the eax register will have the pointer. In 64-bit mode, the rax register will have it, because it is really just the 64-bit extension of eax.

    Because my putstring function already prints the string pointed to by eax or rax, a short program can be constructed to print its own arguments. Below are the minimal examples in both modes to get the command line string, print it, and exit the program with no errors.

    GetCommandLineA example 32-bit

    format PE console
    entry main
    
    include 'win32a.inc'
    include 'chastelib-w32.asm'
    
    main:
    
    call [GetCommandLineA]
    call putstring
    
    push 0
    call [ExitProcess]
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    GetCommandLineA example 64-bit

    format PE64 console
    entry main
    
    include 'win64a.inc'
    include 'chastelib-w64.asm'
    
    main:
    
    call [GetCommandLineA]
    call putstring
    
    sub rsp,40
    mov rcx,0
    call [ExitProcess]
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    Shortening the Windows Prompt

    When using command line arguments, the long prompt of the Windows command line can be annoying because it displays the drive letter and full path to the current directory. Fortunately there is a fix for this.

    For example, I first shortened my command prompt to illustrate what the program does on my machine.

    prompt -$g
    

    Gives me only “->” instead of showing me the full path of which directory I am in. This allows me to conveniently run commands and show you exactly how this program works.

    ->fasm main.asm
    flat assembler  version 1.73.35  (1734819 kilobytes memory)
    3 passes, 1536 bytes.
    
    ->main What is a man?
    main  What is a man?
    ->main A miserable little pile of secrets.
    main  A miserable little pile of secrets.
    

    Notice that every time we are assembling a program with fasm, the name of the source file is an argument to fasm. In a similar way, the second two command examples of me running the main program shows that it prints the entire command line, including the name of the program, which just happens to be “main” by the convention I use while developing.

    However, what if we don’t want the entire command string but only parts of it? That is exactly where it starts getting a little bit complicated. Fortunately, I have written a function called “getarg” which solves the problem completely.

    I must warn you though, the function is about 150 lines of Assembly code and is meant to handle things by many different branches depending on conditions. Here are some odd cases that need to be considered when dealing with command line arguments.

    Design of the getarg function

    The getarg function which you are going to see in the source code later, is arguably the hardest content to understand in this whole book so far. However, with a few notes on the reasoning behind it, it might start to make sense.

    The program name

    The name of the program is not usually relevant in the context of what a program is meant to do. The user should be free to rename the executable to whatever they want and it behave the same. For this reason, my getarg function skips past the program name, main or otherwise.

    The return value

    The convention I use in the getarg function is that it will return 0 if there are no more arguments. Otherwise, it will return a pointer to the string of the next argument, which will have been terminated by zero to prepare it for printing with my putstring function.

    However, there is an exception. The first time getarg is called, it has to call the Windows API function “GetCommandLineA”. At the beginning of the program the “arguments_start” variable is defined to be 0. This variable is replaced with something else after the first call so that it never needs to call the Windows API again after the first time you call getarg.

    It also stores a pointer to the “arguments_end” variable which is the address of the terminating zero in the string returned by GetCommandLineA. These two variables are compared each time you call getarg again. If they are equal, it means there are no more argument strings. Therefore, the function jumps to a label which sets eax or rax to 0.

    Quoted strings

    One of the reasons the getarg function is so long is that it has to account for quoted strings. The default behavior of always delimiting strings by spaces doesn’t work if you are trying to pass a filename which contains spaces. Because we want our hex dump program to be able to handle opening any file, including those with spaces in their name, we need to have the loop which processes strings which are enclosed in single or double quotes. It will process these by going to the next byte after the opening quote and then search until it finds the second quote. When it finds the second quote, it is replaced with 0 for the null termination of that particular argument.

    Purpose

    The purpose of writing the getarg function was to make it possible to port over all of my Linux Assembly programs. You see, in Linux, there is no API function to get the arguments, but they are placed on the stack by the Linux kernel. Processing command line arguments in Linux is easier because even quoted strings are automatically handled for me and the contents of them count as only one argument that is zero terminated like all the rest.

    Although it was a lot of work, the results are worth it because now all of my best programs have been ported to Windows and will be included in future chapters of this book. The first and most basic of these is called chastearg (short for Chastity’s Arguments). The program prints each argument on a separate line. As usual, both 32-bit and 64-bit examples have been provided.

    chastearg 32-bit main.asm

    ;chastearg Assembly source code for FASM
    ;This program prints all the arguments on a separate line
    ;Quoted strings count as one argument and will be on the same line.
    format PE console
    entry main
    
    include 'win32a.inc'
    include 'chastelib-w32.asm'
    include 'getarg-w32.asm'
    
    main:
    
    call getarg ;this first call will get the command string
    cmp eax,0   ;did the getarg function return 0?
    jz main_end ;if eax was zero, there are no args so we end program
    
    ;uncomment these lines to see the whole command string
    ;call putstring
    ;call putline
    
    arg_loop:
    call getarg     ;get the next argument
    cmp eax,0       ;did the getarg function return 0?
    jz arg_loop_end ;if eax was zero, there are no args
    call putstring
    call putline
    jmp arg_loop
    arg_loop_end:
    
    main_end:
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    getarg-w32.asm

    ;This is the Windows 32-bit source of the getarg function 
    
    ;The getarg function makes my assembly code for DOS and Windows easier to read
    ;by writing one function to handle all possible cases I will need for my tools.
    ;
    ;The first time it is run, it returns the whole command string or zero if no args are given
    ;Each time after that, it will give you the next argument, which is a substring of the original.
    ;When no more arguments are available, it will always return zero
    ;The program calling this is expected to check for this error and then terminate
    ;or print a message depending on the goals of that program
    
    ;A word of warning, though, this function has multiple return statements and is long
    ;However, it is fully featured in that it can recognize quoted strings as being the same argument
    ;This brings full compatibility for my DOS and Windows programs that use command line arguments.
    
    ;Because all of my programs are ported to DOS and Windows from Linux
    ;I found there to be conflicts in the way these operating systems did not
    ;handle the quoted strings.
    ;
    ;This same function is available in a separate file for DOS Assembly.
    
    getarg:
    
    mov ebx,[arguments_start] ;get the address of start of arguments
    cmp ebx,0 ;is this address zero? (meaning this function was not called before)
    jz get_arg_data ;if it was zero, then get the argument data for the first execution of this function
    
    ;if the start was not zero, then clearly arguments exist and addresses have been saved
    cmp ebx,[arguments_end]  ;is the address of the start and end the same?
    jnz find_next_string  ;if they are not the same, find the next sub string
    mov eax,0 ;otherwise, return eax as zero and check this in the main program
    
    ret
    
    find_next_string:
    
    mov ebx,[arguments_start] ;get address of current arg
    
    skip_spaces:
    
    cmp byte[ebx],' '   ;is this byte a space?
    jnz skip_spaces_end ;if it is not a space, we can end this loop
    inc ebx ;otherwise, go to next byte
    jmp skip_spaces ;and keep looping till we find non-space
    skip_spaces_end:
    mov eax,ebx ;copy this non-space address to eax register
    
    ;we have found a non-space which is the start of a printable string
    ;but we still have to find the next space and terminate it with a zero!
    
    ;however, there is a special case where we want a string to contain spaces. In this case, I have another routine!
    
    ;check for quoted strings
    cmp byte[ebx],0x22 ;is this a double quote -> "
    jz scan_quoted_string
    cmp byte[ebx],0x27 ;is this a single quote -> '
    jz scan_quoted_string
    
    find_space:
    cmp byte [ebx],' ' ;is this a space?
    jz found_space ;if this was a space, end the loop and terminate with zero
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    cmp byte[ebx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc ebx
    jmp find_space ; this char was not space, go to the next char
    found_space:
    mov byte[ebx],0 ;terminate this string
    
    inc ebx ;but go to the next byte
    mov [arguments_start],ebx ;and set the new start address for the next call
    
    ret ;We can return eax safely knowing the string ends in a zero
    
    scan_quoted_string:
    
    mov cl,byte[ebx] ;mov this quote type to cl
    inc ebx ;go to next byte
    mov eax,ebx ;set eax to this address which is assumed to be the start of a quoted string
    
    find_end_quote:
    cmp byte[ebx],cl ;is this the same quote we started with?
    jz found_end_quote ;if it is, end this loop
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    ;this avoids a crash if I forgot to add the second quotation mark in the arguments
    cmp byte[ebx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc ebx
    jmp find_end_quote
    found_end_quote:
    mov byte[ebx],0 ;terminate this string
    
    inc ebx ;but go to the next byte
    mov [arguments_start],ebx ;and set the new start address for the next call
    
    ret
    
    no_more_args:
    
    mov [arguments_start],ebx ;mov the start to where the string ended
    
    ;now that the start and end addresses are the same
    ;this function will always return zero
    ret
    
    ;this will happen first time this function is called to get the argument data
    get_arg_data:
    
    ;get command line argument string with this Windows API call
    ;it returns a pointer in the eax register
    
    call [GetCommandLineA]
    mov [arguments_start],eax ;but copy it to memory too
    
    
    ;the following loop is a modified strlen loop except that we don't
    ;care about how long the string is. We only need the pointer for where it ends
    
    mov ebx,eax  ;copy eax to ebx to be used in loop to find ending zero
    argend_find: ; this loop finds the zero at the end of the argument string
    cmp [ebx],byte 0 ; compare byte at address ebx with 0
    jz argend_found ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp argend_find
    argend_found:
    mov [arguments_end],ebx ;save ebx address containing the zero byte
    
    mov ebx,eax ;mov the start of the arg string again before the next loop
    
    skip_prog_name:
    cmp byte[ebx],0   ;check for zero first (very important)
    jz no_args        ;if we found a zero before a space, there are no arguments after program name!
    cmp byte[ebx],' ' ;is this byte a space?
    jz skip_prog_name_end ;if it is a space, we can end this loop
    mov byte[ebx],' ' ;turn this byte into a space and therefore delete it
    inc ebx ; go to next byte
    jmp skip_prog_name ;and keep looping till we find space
    skip_prog_name_end:
    mov eax,ebx ;copy this non-space address to eax register
    ;return eax pointing to an address of string with arguments after program name
    ret
    
    no_args:
    mov eax,0
    ;in this case, we return zero because there are no args
    
    ret
    
    ;start and end default to address of zero, which means we have not tested the arguments yet
    arguments_start dd 0
    arguments_end dd 0
    

    chastearg 64-bit main.asm

    ;chastearg Assembly source code for FASM
    ;This program prints all the arguments on a separate line
    ;Quoted strings count as one argument and will be on the same line.
    format PE64 console
    entry main
    
    include 'win64ax.inc'
    include 'chastelib-w64.asm'
    include 'getarg-w64.asm'
    
    main:
    
    call getarg ;this first call will get the command string
    cmp rax,0   ;did the getarg function return 0?
    jz main_end ;if rax was zero, there are no args so we end program
    
    ;uncomment these lines to see the whole command string
    ;call putstring
    ;call putline
    
    arg_loop:
    call getarg     ;get the next argument
    cmp rax,0       ;did the getarg function return 0?
    jz arg_loop_end ;if rax was zero, there are no args
    call putstring
    call putline
    jmp arg_loop
    arg_loop_end:
    
    main_end:
    
    sub rsp,40         ;align stack (required in windows 64-bit)
    mov rcx,0          ;exit code for operating system
    call [ExitProcess] ;Exit the process with code 0
    
    section '.idata' import data readable writeable
    library kernel32, 'KERNEL32.DLL'
    import kernel32,\
     GetStdHandle, 'GetStdHandle',\
     WriteFile, 'WriteFile',\
     ExitProcess, 'ExitProcess',\
     GetCommandLineA, 'GetCommandLineA'
    

    getarg-w64.asm

    ;This is the Windows 64-bit source of the getarg function 
    
    ;The getarg function makes my assembly code for DOS and Windows easier to read
    ;by writing one function to handle all possible cases I will need for my tools.
    ;
    ;The first time it is run, it returns the whole command string or zero if no args are given
    ;Each time after that, it will give you the next argument, which is a substring of the original.
    ;When no more arguments are available, it will always return zero
    ;The program calling this is expected to check for this error and then terminate
    ;or print a message depending on the goals of that program
    
    ;A word of warning, though, this function has multiple return statements and is long
    ;However, it is fully featured in that it can recognize quoted strings as being the same argument
    ;This brings full compatibility for my DOS and Windows programs that use command line arguments.
    
    ;Because all of my programs are ported to DOS and Windows from Linux
    ;I found there to be conflicts in the way these operating systems did not
    ;handle the quoted strings.
    ;
    ;This same function is available in a separate file for DOS Assembly.
    
    getarg:
    
    mov rbx,[arguments_start] ;get the address of start of arguments
    cmp rbx,0 ;is this address zero? (meaning this function was not called before)
    jz get_arg_data ;if it was zero, then get the argument data for the first execution of this function
    
    ;if the start was not zero, then clearly arguments exist and addresses have been saved
    cmp rbx,[arguments_end]  ;is the address of the start and end the same?
    jnz find_next_string  ;if they are not the same, find the next sub string
    mov rax,0 ;otherwise, return rax as zero and check this in the main program
    
    ret
    
    find_next_string:
    
    mov rbx,[arguments_start] ;get address of current arg
    
    skip_spaces:
    
    cmp byte[rbx],' '   ;is this byte a space?
    jnz skip_spaces_end ;if it is not a space, we can end this loop
    inc rbx ;otherwise, go to next byte
    jmp skip_spaces ;and keep looping till we find non-space
    skip_spaces_end:
    mov rax,rbx ;copy this non-space address to rax register
    
    ;we have found a non-space which is the start of a printable string
    ;but we still have to find the next space and terminate it with a zero!
    
    ;however, there is a special case where we want a string to contain spaces. In this case, I have another routine!
    
    ;check for quoted strings
    cmp byte[rbx],0x22 ;is this a double quote -> "
    jz scan_quoted_string
    cmp byte[rbx],0x27 ;is this a single quote -> '
    jz scan_quoted_string
    
    find_space:
    cmp byte [rbx],' ' ;is this a space?
    jz found_space ;if this was a space, end the loop and terminate with zero
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    cmp byte[rbx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc rbx
    jmp find_space ; this char was not space, go to the next char
    found_space:
    mov byte[rbx],0 ;terminate this string
    
    inc rbx ;but go to the next byte
    mov [arguments_start],rbx ;and set the new start address for the next call
    
    ret ;We can return rax safely knowing the string ends in a zero
    
    scan_quoted_string:
    
    mov cl,byte[rbx] ;mov this quote type to cl
    inc rbx ;go to next byte
    mov rax,rbx ;set rax to this address which is assumed to be the start of a quoted string
    
    find_end_quote:
    cmp byte[rbx],cl ;is this the same quote we started with?
    jz found_end_quote ;if it is, end this loop
    
    ;we must also check to see if we have reached the terminating zero of the arguments string
    ;this avoids a crash if I forgot to add the second quotation mark in the arguments
    cmp byte[rbx],0 ;is this byte a zero?
    jz no_more_args ;if yes this string is already terminated
    
    inc rbx
    jmp find_end_quote
    found_end_quote:
    mov byte[rbx],0 ;terminate this string
    
    inc rbx ;but go to the next byte
    mov [arguments_start],rbx ;and set the new start address for the next call
    
    ret
    
    no_more_args:
    
    mov [arguments_start],rbx ;mov the start to where the string ended
    
    ;now that the start and end addresses are the same
    ;this function will always return zero
    ret
    
    ;this will happen first time this function is called to get the argument data
    get_arg_data:
    
    ;get command line argument string with this Windows API call
    ;it returns a pointer in the rax register
    sub rsp,40  ;align stack before Win API functions(required in windows 64-bit)
    call [GetCommandLineA]
    mov [arguments_start],rax ;but copy it to memory too
    add rsp,40  ;restore stack now that WinAPI calls are done
    
    ;the following loop is a modified strlen loop except that we don't
    ;care about how long the string is. We only need the pointer for where it ends
    
    mov rbx,rax  ;copy rax to rbx to be used in loop to find ending zero
    argend_find: ; this loop finds the zero at the end of the argument string
    cmp [rbx],byte 0 ; compare byte at address rbx with 0
    jz argend_found ; if comparison was zero, jump to loop end because we have found the length
    inc rbx
    jmp argend_find
    argend_found:
    mov [arguments_end],rbx ;save rbx address containing the zero byte
    
    mov rbx,rax ;mov the start of the arg string again before the next loop
    
    skip_prog_name:
    cmp byte[rbx],0   ;check for zero first (very important)
    jz no_args        ;if we found a zero before a space, there are no arguments after program name!
    cmp byte[rbx],' ' ;is this byte a space?
    jz skip_prog_name_end ;if it is a space, we can end this loop
    mov byte[rbx],' ' ;turn this byte into a space and therefore delete it
    inc rbx ; go to next byte
    jmp skip_prog_name ;and keep looping till we find space
    skip_prog_name_end:
    mov rax,rbx ;copy this non-space address to rax register
    ;return rax pointing to an address of string with arguments after program name
    ret
    
    no_args:
    mov rax,0
    ;in this case, we return zero because there are no args
    
    ret
    
    ;start and end default to address of zero, which means we have not tested the arguments yet
    arguments_start dq 0
    arguments_end dq 0
    

    Relevance of Arguments

    To conclude this chapter, I will give you my best “argument” as to why command line arguments are usually the best way to provide input to a program.

    Reading from standard input pauses the program and requires the user to enter every detail. Command line arguments provide the input before the program is even started. This means they can be included as batch scripts on Windows so that the same arguments don’t have to be typed by the user.

    If you have a really long script, you need it to be able to run automatically and not ask the user for any choices unless something went seriously wrong. The FASM (the Flat Assembler) takes a filename as an argument and this is also what compilers for other languages do.

    Therefore, despite how difficult it is to explain my code briefly, it is heavily commented and I ask you to come back and review it as your knowledge of Assembly language improves. But beyond that, whether or not you fully understand the getarg function, you will see that it does work because it will be required for the program in the next chapter.

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

  • Should you buy a Chess program to study?

    Analysis of different Chess GUIs available

    One of the great things about Chess is that you can play for free either online or over the board if you have a local friend willing to play.

    Most of the time, a person doesn’t need to spend any money at all on the game of Chess. However, I am not a normal person but someone who wants to know everything about Chess.

    The official website for stockfish (the world’s strongest Chess engine) has a convenient list of the best Graphical User Interfaces and how to install and use them along with the stockfish engine.

    But if you are a new Chess player or you are looking for serious advice on how to improve at the game, I have a serious warning.

    Computers don’t play Chess like humans do. They look ahead and make a move that may make a different 50 moves later. Therefore, playing against the computer or even using a computer to analyze a game can mislead you into thinking a move is bad when it may actually help you win, or maybe it will tell you a move is good but not have the means to tell you why in the same way a human can.

    Therefore, when analyzing which GUI you want to use, I will be talking about which of them helps you better analyze your own games with human reasoning. I will present 4 options. 2 of which are free and 2 of which are programs you can buy.

    Free programs

    What I find myself using the most lately is a free and open source program called En-Croissant. It lets you automatically download a database of the rated games you have played on either chess.com or lichess.

    Frank Willow, the creator of the program published a blog post on lichess.org about it.

    Another great program is liground. It allows using the computer to analyze not only Chess but also variants like Crazyhouse, Shogi, Xiangqi, and many others. If you only play standard Chess you probably won’t make use of it but if you are someone like me who loves all games that are similar to Chess, you may find it helpful just as I have.

    Paid

    The stockfish site I linked to earlier recommends 3 different programs you can buy: Chessbase, Shedder, and Hiarcs. Of these options, only Shredder and Hiarcs are that good.

    Shredder is mostly for playing against the computer. It can keep track of your games and even assign you a rating that goes up or down depending on how you win or lose. However, it’s not that special beyond this. Perhaps if you are looking for a nice program to play against customized strength stockfish and want to download something because you don’t have a reliable internet connection, it’s pretty good. The interface looks really nice and it does let you import and export PNG files of individual games.

    Hiarcs is the better option for me because I don’t use it to play against the computer. I prefer to use it by downloading my own databases of games from lichess and stepping through them. The same can be done with En-Croissant, but the fact that you can open different PGN files and have them in different tabs is a huge bonus. Another thing that is a selling point for me is that it allows me to type in the hex codes for exactly what color squares I want the light and dark squares of the chessboard to be. This is important for someone like me who cares about the appearance of the chessboard for streaming and recording videos.

    I will mention that there is a program called Chessx which is similar to Hiarcs that is free and open source, but the user interface leaves much to be desired. I think you might want to start with it and then if you like it, you might want to buy Hiarcs because it just looks way nicer and does not have trouble loading larger database files.

    Conclusion

    These are my current recommendations for programs serious Chess players might want to use if they have a PC. The open-source free options also work on Linux too if that is important for you.

    Both Shredder and Hiarcs also have mobile app versions too, but I find them to be not as great as the desktop PC version.