Compiling the Game

Compiling the Game

C, while not the most accessible of languages, remains relatively easy to read if written by a skilled programmer. As such, you do not need to know the intricacies of c to follow along

This blog is to act as a sort of journal as I encounter new concepts, try to make sense of them, and then reflect upon the implications. As such, it is not a tutorial or guide. I will make mistakes, ignorant assumptions, and probably get confused/frustrated during the process. I am always open to corrections, insight, questions, and/or criticism. You can find my email address on the links page. Seasoned pros, dm me <3

The compilation process is easy enough, and the official site’s instructions are so good that I would point you there. You can clone the github repo or get a zip/tar.gz from the official website. Take a look at the short video below. I have the game compiled with sdl2 support and sound in under 4 minutes. It is literally just a matter of copying and pasting, with the actual commands differing depending on your OS. The video is in Windows 11 with a combination of MinGW64 and MSYS2.

If you haven’t played Angband, now is the time. After you have descended 100 floors and defeated Morgoth, open the src directory in your favorite editor or IDE.

I will be using a combination of Sublime Text, nvim + kickstart , and CLion, all of which have free versions. Take a moment and browse the directory. If you are anything like me it looks a little overwhelming, but worry not, we are going to make sense of this. Let’s start with ‘main.c’.

C is typically read from the bottom of the file up, starting with the main function, which starts at approximately line 320. I always think of code bases like this as an actual piece of architecture, and the main function is the front door. Inside you will the find skeleton that holds the rest of the program up, sometimes indirectly, sometimes very directly. Because Angband is such a large project, it is a little more nuanced than my analogy, but we shall deal.

The entry to int main(int argc, char \*argv)

Something that jumps out at me right away while browsing Angband’s source is how well it is commented. It stands in stark contrast to something like Brogue. Brogue is sparesely commented, but I would be willing to make an arguement that the code is so idiomatic, you don’t need comments to read it like a choose your own adventure. That argument, however, is for another day.

It is time to get serious

Let’s start from the very beginning, take things slow, and really understand what is going on here. If you have written any c/cpp you are probably familiar with the parameters argc and *argv, argument count and argument vector. Argument count is exactly what it sounds like, it holds the number of arguments in which you passed. Argument vector is a an “array of null-terminated strings representing command-line arguments entered by the user of the program.”1

How does that work exactly? Cpp reference supplies a good example that clarifies things for us.2

A simple c program that prints its arguments to the terminal

All argv0 = argv[0] does is save the name of the program, just like the comment says. One thing to keep in mind is that the arrays in which argv point to, and argv itself are all null terminated. You can see that in the output argv[argc] = 0000000000000000.

So, we have entered the program and saved the file path. What’s next?

The next thing I noticed is all of the ifdefs. These are preprocessor directives and are used for conditional compilation. In essence, only the parts of the program that are needed and/or were specified during the make process are compiled. This allows you to develop multiplatform programs without needing to fork the code base. That’s nice, but it really doesn’t pertain to our mission, which is to learn the game’s architecture. I am making the decision to ignore them for now, and I invite you to do the same. As we get farther along in the code base, we can come back and give the topic the attention it deserves. Until then, let’s take a look at the rest of these local variables.

Main’s local variables

We have what is probably an index with i, a couple of bools who’s names imply they flip to true if a game starts, is selected, or is done. We will pay more attention to them as we get further into the function, but for now just keep them in mind.

Things get really interesting
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    /* Default permissions on files */
    (void)umask(022);

    /* Get the user id */
    player_uid = getuid();


    /* Save the effective GID for later recall */
    player_egid = getegid();

    /* Drop permissions */
    safe_setuid_drop();

This is the good stuff. Because I have spent most of my time programming in the hobby space, I am really ignorant when it comes to systems programming and Unix in particular, despite the fact that my preferred OS is Fedora. 80’s - 90’s UNIX is a vibe, man.

So what is it doing? (void)umask(022) sets the permissions so that the owner of the process can read and write to the appropriate directories while the group in which the owner belongs is only given read access. This provides a safeguard so that someone else’s configs, grave files, save games, etc aren’t accidentally written to by another use. Remember, *nix are multi user OS’s.

Let’s take a closer look at this, because it’s both interesting and extremely important. Legit, this is the good stuff.

When a file is created in *nix, the value of its permissions is 666 (🤘) if a mask is not applied, which is read and write for all, while directories are given the value of 777 if a mask is not applied, which is read, write, and execute for all. How are these numbers derived and how does the command chmod work? Via umask.

Let us get technical

We need to take a quick detour here and talk about masks, bitwise masks, specifically. I am going to let learncpp.com do the teaching here.3

A bit mask is a predefined set of bits that is used to select which specific bits will be modified by subsequent operations. Consider a real-life case where you want to paint a window frame. If you’re not careful, you risk painting not only the window frame, but also the glass itself. You might buy some masking tape and apply it to the glass and any other parts you don’t want painted. Then when you paint, the masking tape blocks the paint from reaching anything you don’t want painted. In the end, only the non-masked parts (the parts you want painted) get painted.

A bit mask essentially performs the same function for bits – the bit mask blocks the bitwise operators from touching bits we don’t want modified, and allows access to the ones we do want modified.

The way this actually works under the hood is really interesting. Because the umask is a ‘mask’ of permissions that should be turned off, the first step is to take the inverse of the umask and turn those zeroes to ones and ones to zeroes. The final permissions are then generated by applying a bitwise AND between the NOT and the umask.

Let’s use 022 as an example, because apparently this is a very common technique. I don’t want to focus on the math, but this video on Khan Academy will get you started with the process if you are interested.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

umask value: 022 (octal)

In binary: 000 010 010 (user, group, other)

Bitwise NOT of umask (~022): 111 101 101

Default file permissions (666): 110 110 110

  110 110 110  (666)
& 111 101 101  (~022)
-------------
  110 100 100  (Result)

Convert the result back to octal: 110 = 6, 100 = 4, 100 = 4.
Which gives us 644.

Per Cambridge, “644 means you can read and write the file or directory and other users can only read it. Suitable for public text files.”4

The next couple of lines of interest seem pretty self explanatory, but we will explore them more next time to see what is going on behind the scenes.

1
2
3
4
5
    /* Get the user id */
    player_uid = getuid();

    /* Save the effective GID for later recall */
    player_egid = getegid();

I am going to try my best to make sure that this remains a thing that I enjoy and continue to do, so I am going to stop here. I will be back in a couple of days and continue working through the main function. Things get interesting once we get to play_game().

Until then, take care of yourself.

Built with Hugo
Theme Stack designed by Jimmy