Hello readers. My name is Connor McAdams, and I'm a Wine developer currently working at Codeweavers. This is my first job as a programmer, and the path I took to end up here is unusual enough to warrant a blog post I guess. Upon graduating high-school, I had no intentions of becoming a programmer. In fact, I had barely ever written a line of code. What actually lead me to programming was a combination of two hobbies of mine, game-console modding and cars.
From Zero to Hacking my Car - My Introduction to Programming:
My programming journey began when I bought a used Playstation 3, with the intention of modding it to run Homebrew. In order to do this, I needed to flash the NOR chip onboard the PS3 containing the console's firmware with an external flash programmer. There were a few options for getting a flash programmer, but the one that intrigued me the most was a project using a Teensy 2.0++ microcontroller with a custom program flashed onto it. Up to this point, all my console modification experience had been buying pre-programmed modchips to install, so the idea of being able to do something DIY kind of blew my mind. I ended up buying one, it worked, and then I got an idea.
A few years prior, I had purchased an ECU (Engine Control Unit) for my car that had been flashed with a program intended for another engine. It worked, but the air to fuel ratio was off, which lowered performance and also left soot on my rear bumper. The correct program was available online, but the EEPROM flashers were around 80 dollars. This annoyed me, because I had just spent 20 dollars on a Teensy 2.0++, and I felt like it could probably do the job just fine. Overestimating the simplicity of this project, I dove right in.
My first attempts to program my cars EEPROM went pretty poorly. I had very little understanding of how programming or computers worked other than the basic LED blink Arduino sample programs. I started by trying to modify the PS3 flashing program to interface with my cars EEPROM, but couldn't get it to work. From there, I began searching for programs that more closely matched what I was trying to do. I managed to find some, and even tried asking for help, but had no luck. In order to make this work, I was going to need to get a better understanding of programming. Unfortunately, some personal issues came up around this time, so I dropped the project for a bit.
I came back to the project in October of 2016, and started by reading some more on programming. I followed some tutorials, wrote some example applications, and came to a point where I had a decent idea of how things worked. It took a little bit of work, but now that I understood what the code I was copying actually did, I finally began making some progress. Before long, I had managed to successfully read data from the EEPROM. However, when I began trying to write data to it I ran into trouble. No matter what I did, I couldn't get it to work. Again, I went online to ask for help, and while this time I got responses, I didn't know enough to understand how to make use of their suggestions. Even worse, I accidentally managed to fry the EEPROM I had been trying to program. After this, I gave up for awhile, as I couldn't come up with a clear path forward.
It wasn't long after I gave up that I came across an idea. One day, while browsing through the /r/hardware subreddit like I usually did, I saw a post from a user asking how to learn about the way computers worked at a low level. A commenter suggested reading a college textbook titled "Introduction to Computing Systems: From Bits and Gates to C and Beyond," so I decided that was exactly what I would do. I got a copy of the book, and spent around six months reading it front to back, doing all of the provided examples in the process. I came back to my EEPROM project, and now that I knew what I was doing, managed to successfully flash a new EEPROM without frying it. I put it into my car, and it worked. Finally, I had completed my first project.
From Car Hacking to Kernel Hacking:
While working through the aforementioned college textbook's C examples, I began using Linux. I came to like it quite a bit, and wanted to switch over to it full time, ditching Windows. However, there was a problem. My sound card was unsupported in Linux, which was pretty much a deal breaker. Given that I was looking for a new project to work on, I decided I would fix the sound card driver myself, then finally move over to Linux full time. This project seemed pretty difficult, so I also thought of it as a way to prove to myself that I could make it as a programmer, and that my last success hadn't just been a fluke.
The first problem I encountered was compiling a custom kernel so that I could modify the existing driver code. Having only compiled programs within the Arduino IDE at this point, I had no clue about working with larger projects. It took me a while, but eventually I managed to compile a custom kernel. This allowed me to get debugging information in the log, and finally begin making changes to to the driver. However, it was here that I ran into my first issue: I had no clue what to do from this point. The driver for my sound card in Linux had been created by Creative Labs to work for the Google Chromebook Pixel, and while the Pixel and my Sound Blaster Z shared the same core chip, their configurations were very different. Lacking any documentation on the chip from Creative, I realized I would have to come up with a different way to find the correct initialization commands to produce audio output.
I started out by asking for help on the kernel's bugzilla, but didn't get any responses. Without any pointers on where to start, I began researching how the card worked at the lowest level, hoping I would find a way to capture the command data from a working Windows installation. The sound card was based on the Intel HD Audio standard, so I started there. I read the specification sheet, and figured out how commands were sent back and forth between the computer and the sound card. This was done by using a series of circular buffers called the Command Outbound Ring Buffer (CORB) and Response Inbound Ring Buffer (RIRB). The spec sheet said these were DMA buffers, which meant they existed somewhere in memory. So, I had an idea. If I could find a way to detect where these buffers were in Windows, all I would need to do is find a way read from them, dump the commands, repeat them in Linux, and boom, sound would erupt out of my speakers. I began researching how the CORB and RIRB buffers worked, to see if this was possible.
It turns out the addresses of the CORB and RIRB were written to PCI MMIO registers, so if I was able to get access to these registers, I would know where in memory to look for the buffer. I did some Googling, hoping someone else had had the same idea and already created a program for this, but found nothing. The closest thing I found was a blog post talking about doing something similar for a graphics card driver. This helped me realize my idea was possible, but unfortunately it didn't fit my exact use case. After reading the blog post, I managed to get my sound card working in a Windows virtual machine via PCI passthrough, which gave me access to the MMIO register writes, but I would still need to find a way to dump the command buffers. I ended up writing a simple program that took the piped debug data from QEMUs terminal output and, when the ring buffer was at its final address, wrote a command out to the console to dump the data buffer. To my surprise, it worked. I now had the commands necessary to get the sound card into a working state. All that was left to do was analyze them. It took two months, but I was finally beginning to make some progress.
A couple weeks after dumping the commands, I managed to finally get sound out of the card in Linux. Again, I reported these results to bugzilla, and naively told people it wouldn't take more than a week to get my work cleaned up and submitted up stream. The process turned out to be far more difficult than I had initially believed. Prior to this, I had used git, but I had no clue how it actually worked. I had to learn about branches, commits, and creating patches to send to the mailing list. After doing this, I sent my first terrible monster patch, which was rejected. Thankfully, I was given some useful feedback from a maintainer, and with some help from a friend on Freenode (shout out to Mariusz Ceier) I eventually managed to get a series of patches put together that were accepted upstream. With my first open-source contribution completed, and functioning sound in Linux, I began looking for new problems to solve.
From Kernel Hacker to Wine hacker:
This was around the time I began contributing to Wine. Now that I had sound working and had fully switched over from Windows, I wanted to play some games. To be specific, I wanted to play a game called Halo Online, which failed to start under Wine. I started by looking for a place to ask questions, and stumbled upon the #winehackers channel on freenode. Unlike the kernel, the Wine developers seemed much more accessible. I asked for help with my game, and before long, I had a good understanding of what was broken.
The problem was that Wine couldn't decode DXT1/DXT3/DXT5 (referred to as DXTn from here on out) volume textures in hardware, due to OpenGL only having support for 2D DXTn textures. This is partially due to the fact that for a long time the DXTn texture compression algorithm was patented, which meant you needed to pay a licensing fee to decompress them. The original workaround was the GL_EXT_texture_compression_s3tc OpenGL extension, however, it only supported 2D DXTn textures. Lucky for me the patent had just expired, meaning it was possible to write a software decompression function in Wine and upload the decompressed raw textures to the GPU. This is what I set out to do.
Similar to my last project, I began by getting a baseline understanding of the system that I was trying to work on. In this case, OpenGL. I started out by following the tutorials at learnopengl.com until I had a decent understanding of how 3D graphics worked. With that out of the way, I began to tackle the problem of decompressing DXTn textures in software. Luckily, there was quite a bit of documentation on how it worked. After reading a few different sources, I managed to put together a test program that could decompress DXTn textures. At this point, I extracted one of the 3D DXTn textures from the game using apitrace, and fed it into my test program. I then modified one of the example programs from learnopengl.com's tutorials to make it display my decompressed texture. It worked, and from there, I began adapting my work to get it into Wine.
It took me awhile, but eventually I managed to implement the texture decompression inside of WineD3D. Now that I had a working implementation in Wine, I began the process of getting my patches up streamed, which turned out to be pretty similar to the kernel. I sent out my first patches, and after a few review cycles, I finally managed to get my patches accepted into Wine. It had taken three months from start to finish, but I finally managed to make my second contribution to a large open source project.
From Part Time Hacker to Full Time Wine Developer:
I spent a few years after this bouncing around between various projects. I added support for some more sound cards, fixed some Wine bugs, and worked on a few personal projects of my own. The entire time I had been working on these projects, I worked part time at a grocery store. I had always hoped that I would find a way to work on programming full time, and then in Februrary of 2020 I saw that Codeweavers was hiring. This sounded like the perfect opportunity to make this a reality. Even better, from looking at the job description, the skills they were looking for seemed to closely match my own. I applied, got the job, and now I get to work on open-source software full time, which is pretty awesome.
Currently, I'm working on adding more support for Window's accessibility APIs in Wine. This is useful for screen readers, but also for things like detecting when a text input field gains focus. This turns out to be useful for touch screen devices like the upcoming Steam Deck, which uses this data to know when to popup the on screen keyboard. Implementing these APIs has turned out to be a ton of work, as accessibility APIs touch many different parts of code in Wine. In order to implement them properly, I've had to research many different areas of Wine (COM, Windows message queues, Common Controls, and RPC are just a few). Thankfully, now that I work on Wine full time, I have much more time available to learn about these things.
One thing that I think is important to point out to anybody wanting to do this themselves, if you're willing to put in the time, programming is possible without any formal education. It can be really easy to think these things are impossible for just anyone to do, but thanks to the internet much of the required information is available for free. The biggest thing to remember is to keep trying, keep asking questions, and keep seeking out information on the problems you're trying to solve.