The Modern .NET Show

S07E18 - Jonathan Peppers Unleashes Code Chaos: How .NET Meets the NES

Sponsors

Support for this episode of The Modern .NET Show comes from the following sponsors. Please take a moment to learn more about their products and services:

Please also see the full sponsor message(s) in the episode transcription for more details of their products and services, and offers exclusive to listeners of The Modern .NET Show.

Thank you to the sponsors for supporting the show.

Embedded Player

S07E18 - Jonathan Peppers Unleashes Code Chaos: How .NET Meets the NES
The Modern .NET Show

S07E18 - Jonathan Peppers Unleashes Code Chaos: How .NET Meets the NES

Supporting The Show

If this episode was interesting or useful to you, please consider supporting the show with one of the above options.

Episode Summary

This episode features an in-depth conversation with Jonathan Peppers, a .NET engineer at Microsoft, who shares insights about his intriguing project, .NES. The project revolves around the idea of writing C# code that can run on the classic Nintendo Entertainment System (NES) by transforming it into an appropriate binary format. Jonathan explains how he first envisioned a command line tool that could streamline this process, allowing users to create NES games directly through familiar C# syntax.

Throughout our conversation, we delve into the technicalities of .NES, starting with Jonathan’s journey in developing this tool alongside his regular responsibilities at Microsoft. He works primarily with .NET for Android and .NET MAUI, and he draws parallels between the common practices in mobile development and the intricacies of retro game programming. Jonathan emphasizes the value of continually learning through projects, especially when they involve significant unknowns that challenge his skills and understanding.

We discuss how .NES operates at a high level, breaking down how C# code is compiled into Intermediate Language (IL) and subsequently transformed into the 6502 assembly language—the foundation for NES games. Jonathan explains the complexities involved, such as the need to manage string tables, understand memory layouts, and implement hardware interactions crucial for game functionality. The conversation highlights the challenges of translating modern programming concepts into this retro programming landscape, especially regarding limitations inherent to the hardware.

Jonathan also shares exciting developments he has achieved with .NES, such as incorporating sprite handling and user input. Despite the progress, he acknowledges the ongoing challenges, including the intricacies of implementing complex game logic typically found in classic titles like Super Mario Brothers. As he shares instances from his own learning experiences amid building .NES, listeners gain insights into the iterative process of software development and the importance of embracing the struggle that often accompanies learning.

Throughout the episode, Jonathan invites collaboration with listeners interested in retro development or enhancing the .NES project further. He opens the floor for discussions on topics like AOT (Ahead-of-Time compilation) and the broader implications of building tools that allow modern programming languages to interface with much older hardware systems. This episode not only serves as a technical deep dive into a fascinating subject but also embodies a spirit of curiosity and innovation, encouraging developers to explore the intersections between old and new technologies.

Episode Transcription

When you program for the NES you deeply need to understand the hardware, right. And that’s not a thing; like as a .NET developer you don’t really know what a register is, or like or a bus, or like NES has a thing called a PPU

- Jonathan Peppers

Welcome friends to The Modern .NET Show; the premier .NET podcast, focusing entirely on the knowledge, tools, and frameworks that all .NET developers should have in their toolbox. We are the go-to podcast for .NET developers worldwide, and I am your host: Jamie “GaProgMan” Taylor.

In this episode, Jonathan Peppers joins us to talk about something which is a little out of the ordinary for us here: programming the Nintendo Entertainment System but in C#. We talk about the process behind his (some would say absurd) idea for an AOT transpiler which can convert a subset of C# over to the Assembler required to write and publish a NES game.

So you think about that example, what I described there on the NES side is actually very similar to what’s on the IL side, is that in IL, you have a string, right? It goes and looks up in a string table, the contents of the string, and puts it on a stack, and then it calls vram_write, and then it’s the runtimes job to actually like make that happen at runtime; or in the case of an AOT compiler it would emit, you know, native machine code that does the same thing.

- Jonathan Peppers

Along the way, we talk about that Ahead-of-Time compilation is, have a brief intro to what IL is (that’s what your C# code is compiled to before running it), and how all of that fits in with .NES—the wonderful name for Jon’s AOT NES compiler.

Anyway, without further ado, let’s sit back, open up a terminal, type in dotnet new podcast and we’ll dive into the core of Modern .NET.

Jamie : [0:00] So welcome to the show Jonathan. My goodness, this is an episode that I’ve wanted to try and arrange for a while, ever since I saw a talk that you gave at one of the many .NET Confs about .NET NET, . NET NES; we’ll get on to which it is in a moment. But welcome to the show.

Jonathan : [0:22] How are you yeah? Yeah, I think don’t know if it’s pronounced .NET NES or .NES. But .NES sounds good because it looks like .NET like with an S changed to T. So either one is fine with me though, yeah.

Jamie : [0:39] Okay, cool. I also really, really dig the the logo that’s on the website - on the github repo, sorry. It’s really cool. I’ll put, maybe, a link to; I’ll definitely put a link to the repo in the show notes, so folks can go and get that. And, if it’s possible, I might try and put the logo in the the episode art as well, because it is really cool.

Jonathan : [1:00] Yeah, I had a contributor send me that. So I gave a talk at Microsoft Build about this project and that week it was, like, I got PRs from people I’d never seen and, “here’s a logo,” and it’s like, and so i had to look up the person’s name; but maybe we can follow up in the notes who who did that but yeah.

Jamie : [1:27] Yeah, yeah. I’ll make a point of adding that. I do like that because, like every open source project has to have a logo, right? Otherwise it’s not a “real” project, right?

Jonathan : [1:37] Yeah. Well sometimes I find myself like asking ChatGPT to make one too soon, you know, before the project’s done but.

Jamie : [1:52] Yeah, okay. So, I mean, we’ve been talking for a little while about the project and the logo and stuff, and I’ve kind of introduced you to the folks listening. But, like, I guess in the nicest possible way, who are you and what do you do?

Jonathan : [2:08] So I’m an engineer on .NET at Microsoft, specifically .NET for Android and also .NET MAUI. So, you know, .NET MAUI is a cross-platform framework that sits on top of iOS and Android things. And so, as you might imagine, there’s an Android team, there’s an iOS team. But I do a lot of work on performance, maybe MSBuild things. And, you know, this .NES project was kind of, you know, like a fun thing I did on the side that, you know, turned into more; is basically what it was.

Jamie : [2:52] Cool. So, okay, right. So I’ve got a question. We’ve talked a little bit about .NES. Let’s discuss it, right? We’ve talked around it. We haven’t actually said what it is. So for the folks listening, what is .NET NES? .NES?

Jonathan : [3:13] So I imagined a few years ago, this is probably during the pandemic, how cool it would be is if you could add the command line, could type dotnet new nes and then type dotnet run and then an NES emulator pop up, right? And you’d have like a C# project, you know, running an NES, valid NES game or whatever.

Jonathan : [3:42] And so initially where I started was very rudimentary. It was like, that workflow worked, but all I did to start with was figure out like the string table and how to find the, the string you wrote in “hello, world, " how to change it. Right. And so that was like version zero, I don’t know, 0.001. I don’t know. And I had that working for a year or two, and this was a project I would come back to off and on, get more of it working. I would get different pieces working.

Jonathan : [4:20] And at some point I felt like it was fully functional. I had no to do’s left in the code base. And I thought, well, “let’s release it. " And then I thought, “oh, let’s submit talks about this.” Right. And so that’s kind of where I ended up.

Jonathan : [4:40] I would not claim that it’s complete by any means like, we can get into how it works more, but at a high level, it compiles a C# assembly as normal. A lot of things in the language aren’t going to work. For example, you can’t create a class and allocate on the heap. None of those things exist. What you’re writing looks more like a C program. But, you know, of course, in C#, you have just a program.cs with statements in it.

Jonathan : [5:20] And so what it can do is it looks at the output of the C# compiler, which is IL, and it generates the equivalent, like, NES binary format that any emulator will load up happily and not know any difference, right?

Jamie : [5:44] So I have like 100 questions.

Jamie : [5:54] From my understanding then, you’re typing some C# code that looks a little bit more like C, but that’s totally fine. You type some C# code, you do dotnet build, some magic happens and you’re able to run it as if it’s a Nintendo Entertainment System game, inside of a Nintendo Entertainment System emulator. That, in itself, is genius.

Jamie : [6:16] Like, I guess the first question is: you said that it was doing some lookups and sort of transpiling the code from one thing to another, is it like… how does that bit, well this is going to get super technical super quick. So how does that work then?

Jonathan : [6:32] Yeah. Well I talk about the different parts for sure. So, if you think about my day job, that is what I work on is that like you can type dotnet new android and you get an Android project; and then if you do dotnet run you use an android emulator, and run your .NET program on Android. So you could see where there’s a parallel there in my day job, for sure.

Jonathan : [6:56] The process… So .NET’s build system is, if you’ve seen it, it’s in msbuild, it’s it’s kind of arcane but we’ve had it for many years. But you can extend it to do almost anything, right. And this is kind of an example of doing that. You can write, not only XML files that describe what the build does, but you can write C# code that will run during the build to do extra things, right. And so on Android we do things like we compile Java code, do all kinds of crazy things, zip up all the files together, sign the thing, deploy it.

Jonathan : [7:46] So this is not nearly as complicated as what I work on at my day job in some ways. But what was more complicated is, is that writing the, so I knew the msbuild part quite well. That’s my day job. But knowing the, you know, NES, you know, it’s 6502 assembly language, like learning what that is, because it’s a slightly older than I am. It’s a little before my time. If I was maybe 10 years older, I maybe would have actually coded in 6502, but this was kind of my first learning experience with it.

Jonathan : [8:30] And so where I was is, I also knew quite a bit about what the C# compiler outputs. Like in my day job that’s another thing we do is modify C# code during the build. So I also knew that side of it. So then the missing piece was like looping over the IL, and if I can describe loosely what that is it’ll be it’s almost like a state machine, of different operations so it’ll be like, “put the integer 1 on the stack,” “put the integer 2 on the stack,” and then, “call this method,” so it looks close to the C# code but in a more machine readable way. And so you can imagine how you would look at this stream of, I guess you could call them operations, expressions, and convert to the equivalent NES operation. And one reason that I wanted to do the NES is that its instruction set is so small. Like it was so long ago that you can literally look at a table and it’s like maybe a 10 by 10 table. Of all the instructions and half of the, you know, blocks are empty, right? So IL is actually more complicated than the NES has. And so some instructions end up being like 10 NES instructions, for example.

Jonathan : [10:12] But if you think about it, this is what .NET does on all the platforms, right? So let’s say you’re on Windows and you run a C# program. The only difference is instead of doing it at build time, they actually do it at runtime by default. So the core CLR runtime is some code that’s written in C or C++ that knows how to load IL and convert it to your machine’s instructions at runtime, and very quickly as well. Like that was one thing I didn’t have to worry about here, was that I’m doing it build time. So I [had] no worries about performance you know.

Jonathan : [11:02] So if we want to talk a little bit about AOT, that’s basically what I did here: is I wrote an AOT compiler for the NES. And .NET has different forms of AOT.

Jamie : [11:21] I’m sorry, but just like that phrase, “I wrote an AOT compiler for the NES.”

Jonathan : [11:30] Yeah, yeah.

Jonathan : [11:32] And the different AOT technologies, you know, that .NET has, some of them are written in like C, you know, I had the—I don’t know—luxury that I could just write everything in C#, right? So I felt like it was an easier time. So, like, if we wanted to talk about AOT a little bit on Android, you know the product I work on, also iOS we use a form of AOT that came from Mono or came from Xamarin. And it’s a technology that, like, basically runs Mono’s JIT compiler at build time and then outputs some files. And then we put the files in the app. And the reason this was required originally is iOS can’t actually JIT because Apple restricts it at runtime. You can’t create or write to a piece of memory and execute it. Like, you can’t do that on iOS at all. And so Xamarin brought this technology for that reason, but it also was useful on Android because it could make apps launch more quickly.

Jamie : [12:58] So if we could just break for a second there and just cover a few things that you were saying then. So for those, just so that… because we have people who listen from all sorts different levels in their .NET journey. So when I type my C# code and I hit build, it will produce a binary of some kind. On, I guess, on on a Windows machine it might produce an exe and on a Linux or a Mac… on the, sorry, a Mac or a Linux-on-the-desktop it might produce like some kind of dll, that then has to be loaded and run. And what we were saying is that the C# code is converted into IL—which is literally stands for Intermediary Language, or Intermediate Language I’m not sure, it’s one of those two. And then when you run the JIT compiler—the just-in-time compiler—comes along and converts those; I’ve never been sure whether it’s single line at a time or a couple of lines at a time, but I feel like it’s probably a single line or a batch of lines at a time, so it could do branch prediction and stuff; and then converts that, like you said, to the byte code for the CPU architecture of the machine that you are running it on right.

Jonathan : [14:10] Yeah, yeah. It does it per method. And so it gets complicated when you think about it because of things like when it first sees a type for the first time, it has to run the static constructor of the method or JIT the static constructor of the method. And then it can do the method. So there’s definitely some timing there.

Jonathan : [14:37] So maybe I could talk about a console app. I’m trying to think the easiest thing here. So if you created just the console app, you can imagine it’s a visual studio or dotnet new console. When you go to build, be that F5 or dotnet run or dotnet build, it’s going to run the MSBuild build engine, which will run a bunch of logic. And then in the middle of that, call the C# compiler. And so the C C# compiler will output MSIL, Microsoft Intermediate Language, in a DLL file.

Jonathan : [15:21] But there’s, like I did as I hooked into the build after that step to make .NES work. But you could also do additional things. So like, let’s say you published your console app, and you wanted it to be self-contained. So what that means is you want to run it on your grandma’s computer and she doesn’t have .NE, right. And so in that case, it can actually create a file that you can share, and it has .NET contained within the console app. So you can do that. It’s dotnet publish —self-contained.

Jonathan : [16:08] And you may have to set another property to say, produce a single file. And when you do that it’ll create a native exe for Windows, for example, and it basically zips up and embeds all the IL inside. And so when you click the exe to run it, or launch it, however, it has the runtime inside. So it has a copy of the C/C++ code that knows how to extract all your IL, at runtime convert the IL to instructions that’ll run on the that current computer. And it’s actually pretty amazing when you think about, like, the tech behind, you know, what what makes hello world run right like yeah.

Jamie : [16:57] It’s like using a car to start a car, right.

Jonathan : [17:02] Yeah, yeah. So you can go a step further with that. Like, that same console app we’re talking about, by default it will use the runtime it’s called Core CLR. That’s the one that’s stable, it’s in .NET 9, it’ll keep going. But you can also publish it a different way. So you can say, add a setting, “publish AOT equal true.” And this is not Mono’s AOT. This is a new AOT that different people have been working on called native AOT. And so instead of producing this exe with all the IL compressed and zipped inside of it, it actually does a very similar thing to what .NES does: it has an AOT compiler that, outputs just native code. So there’s there’s a lot of choices in .NET when you get down to it you know so.

Jamie : [18:08] Right. And I can imagine that… there’s a lot of hand waving wizardry going on from my perspective, because the AOT stuff is wizardry from my perspective. But I can imagine that, like, in a 10,000 foot view… Is is the AOT, let’s say the native AOT, is that doing it’s like the pre-compilation, right. It’s doing that step during your your publish time, it’s taking all of your IL and converting it into that bytecode for you. Is that what’s happening?

Jonathan : [18:39] Yeah, yeah. So they have msbuild logic that after the normal build, they’ll run a step called the trimmer, and we use this on mobile as well. But it will remove any code that it it thinks isn’t going to be called to get the app smaller, right. And then, after that, it will run a step called ILC—or IL Compile, you could think of it is that, right—that calls a native like C/C++ chain to write native code instead of IL. And so if you created a native AOT app and sent it to someone, like, to them it’s just like another native app. Like, it’s almost like you could have written it in CI’m sure you could inspect the binary and figure out that .NET is involved inside, but for what, you know, the OS is concerned, it’s like just a native thing, right? Like, so, you know.

Jamie : [19:52] Okay. And and that pre-compilation stuff, that native AOT, I guess the benefit… one of the benefits of that is that perhaps it may run a little faster, might use slightly fewer resources because it’s not converting the IL code to native code at runtime, right. It’s already done that bit. And so my guess is, and and well okay, so i know that in most cases it will i’m just sort of like filling in for the listeners who may not know what AOT is; it’s obviously going to produce a binary that’s a fraction smaller, and runs a little faster, right. I guess that’s one of your benefits.

Jonathan : [20:34] Yeah. So let’s say that you had a method, I don’t know let’s say that it’s got 100 lines of code this method. If it was with native AOT all the code is already compiled and It just basically runs it, right? Whereas if it’s using the JIT on Core CLR, it first goes, reads through the IL, and then does a step of converting the code to the machine’s architecture. So native AOT is going to be smaller because of the trimming.

Jonathan : [21:15] Also start quicker because there’s no JITting step. And then the the memory usage will also be smaller because it doesn’t have a copy of the IL in memory. So sometimes they’ll say, like “working set” is what people will say is the metric that’s much smaller in native AOT. But then if you think about like, an ASP .NET web service: the JIT can be better in some ways.

Jonathan : [21:46] So let’s say that you have a monster web server that’s running your ASP .NET whatever web API. The JIT can actually better optimise code paths because it has a tiered JIT; where it will first get some code out there as quick as it can, that’s like tier zero/tier one. And then if the same code path is called over and over it has an opportunity to optimise the code path further. And so using Core CLR in a web service may actually be what you want, because you’ll get more throughput, you’ll get have higher memory usage and startup will be a bit slower. But those things you may not care about in a web service, right? Like, usually you’re not restarting your web server all the time, hopefully, right? So, yeah.

Jonathan : [22:47] So native AOT is, you know, it’s really useful for small things that you’re going to run or maybe mobile apps, which is where, you know, my daily work is where we’re interested in it. So, because mobile apps, you’ve got that like time where a customer is tapping on an app icon and they want the app to be there quickly and, it’s not trying to use your computer resources 100% like a web server would so.

Jamie : [23:23] Yeah, okay. So then one of the the many drawbacks of doing native AOT—and we’ll get back to .NES in a moment, because you were about to talk about how that’s linked to .NES before I stopped you. So like one of the the drawbacks of doing native AOT, because like somebody’s listening along going, “why don’t I just do this anyway?” right, and I guess one of the drawbacks is longer build times, more time spent doing that build, and then maybe there’s some stuff that you can’t do, right.

Jonathan : [24:00] Yeah, yeah. Native AOT does have some limitations. When you think about that trimming step I mentioned, there’s a whole class of things that you can do in .NET that a trimmer can’t figure out. And so there are a set of build warnings that will tell you this, the trimmer or AOT warnings. And it’s usually around the use of reflection. So you can use system reflection as long as they can figure out what’s happening at build time. So if you’re turning a string into a .NET type, that’s the kind of thing that won’t work in native AOT. So like, imagine you’re parsing some JSON or something like that.

Jonathan : [24:51] What you would do instead of using System.Text.Json as is, is you would have to use their source generator. And so what that does is, at build time, you get this JSON parsing logic that knows the types. And then, you know, the native AOT compiler is completely happy with that, right? It’s almost as if you would have written the parsing code by hand is what the source generator outputs. But then if you imagine JsonObject.Parse and then put a string in there, it is probably not happy with that type of implementation.

Jamie : [25:39] Yeah, sure. And that is because, in your example there where you’re parsing or deserializing that JSON back to some object or other, something somewhere, presumably with reflection, has to go look at that C# object and go, “Okay, cool. I’ll create a new instance of this, and then figure out how to map the keys and values to those properties,” using whatever magic reflection does. I appreciate reflection, but I’ve never really looked really deeply into it. It’s one of those things that’s on my list of things to do to understand C# and .NET a lot better, but like the fact that a program can interrogate itself is just is wild to me.

Jonathan : [26:27] Yeah. Well, and it’s also slow. So there’s two kind of two wins there is that you know if you get a piece of code to work with native AOT its probably going to be faster in general because it made you get rid of the reflection; so it’s kind of a double win. And what would happen, let’s say you didn’t fix it What would potentially happen is you’d get a build warning, and then at runtime if the type was removed, like it wasn’t used anywhere in your program, you would get like a type not found exception, method not found exception, that kind of thing. So I do think it is easy to to figure out what to do to fix it, as long as there is like a good, alternative you know so yeah.

Jamie : [27:22] Yeah. That’s really cool. I like using a source generator to throw some code in there to figure out the bits that you might need. Like in your example there of: if you included a source generator for some JSON parsing, it would figure it all out for you. That’s the other side of the magic wizardry is code that writes itself. That just blows my mind.

Jonathan : [27:48] Yeah. I’ve not written a source generator. That is a thing that… it’s another thing to learn. Learning MSBuild is one thing, but learning Roslyn and the C# compiler is another. It’s a lot of…

Jamie : [28:03] It’s a whole can of worms

Jamie : [28:06] Cool. So now that we’ve had a little bit of primer on AOT, how does that all fit in with .NES thing? Because I, kind of, I interrupted you; it was quite rude when I did that. But like how do we get from C# through AOT to a Nintendo Entertainment System compatible game?

Jonathan : [28:27] Yeah. So you can imagine I wrote some C# code that… there are libraries that let you open IL with C#; as you can imagine the Roslyn C# compiler itself uses some of these, right . So the one I use is a library System.Reflection.Metadata, and that just lets you open IL files, like DLL files, and and look at the types inside, and then look at the instructions and you could do what you will with them.

Jonathan : [29:04] So then on the other side, so you can you can imagine I have this loop looking at the, you know instructions in your program.cs. And then on the other side I want to output, you know, a 6502 binary. And to do this I, kind of, I took like a… maybe it’s a reverse engineering approach, but I found a really nice website called 8bitworkshop.com, and what it lets you do is in the browser you can write NES games in C and you can just like download the rom and load it in an emulator.

Jonathan : [29:51] You can also see the disassembly of the C code, which is kind of what I needed to see. You can also see some of the memory instructions if you wanted to debug the game. But that was my starting point: is that I took literally, “hello, world!” in that website and then looked at… like designed what a C# equivalent would be, and then got the IL and then tried to write the same, you know, binary file on the other way, right.

Jonathan : [30:30] So this took quite a while. In the beginning, like I said, I just replaced the strings around learned that, “hey at this index, the string table starts here, it’s ASCII. Each string is delimited with, you know, a zero byte.” And so you could see where I could turn a C# string into the table.

Jonathan : [30:54] And then at that time, I also just took slices of the ROM and saved them as different files. So the very first version was, “here, copy half of this file. Okay, write the string table. Okay, write a few, and maybe it was like one instruction. Okay write the last part of this file,” and then that worked. And so that was like the light bulb moment of me like, “Oh, maybe this thing could really work in the end, right.”

Jonathan : [31:23] And so since since then i actually… so if you write an NES game in C, the library that’s most commonly use is called neslib, and it looks a lot like, you know, what C# APIs would use, and so I leaned into that library. But some of the things I had to do was to write the contents of neslib in the beginning of the NES rom so that it can be used by, you know, developers who want to call it, right And then I write the users program, and then probably a few tables at the end this is what what you end up with. Does that explain a little bit? It’s hard.

Jamie : [32:19] Yeah, it does yeah. So then, just to dial back a little bit then, so you were saying that you you took the output of let’s say a , “hello, world!” from 8bitworkshop.com and you reverse engineered how to get some of, maybe msbuild, to take some of your IL and convert into that assembler, I guess, or that C#… It will be assembler, won’t be C++ It’ll be, yeah. Or is it writing the binary? Like how—okay, so real quick how does, like, an NES rom work? Like, the file that… because you’re you’re writing to that in the correct format, right?

Jonathan : [33:02] Yeah, yeah. So I could have chosen to output assembly text files or C files, and call some other compiler to compile them; but I instead, maybe I don’t know what made me choose this path, but I wrote the binary directly. And so what that means is, in C# you’d use a type called BinaryWriter, and that just lets you write any byte sequence you want. And so you can just write the full rom using BinaryWriter and that’s all it really does.

Jonathan : [33:46] So the NES file format is interesting. It’s got a header that’s like so many bytes. And it’s literally the ASCII letters “NES” and then some other random bits. And then that’s the first like 10 bytes of the file. So I wrote code that did exactly that, right? Like called BinaryWriter and wrote the header, right? And so I’m like, “okay, I wrote the header.” And so then it’s learning the rest of the structure: is you’ve got a table of just random code, you’ve got a table of strings, you’ve got… there’s there’s tables of different—well , what’s the easiest way to explain this?

Jonathan : [34:39] So when you program for the NES you deeply need to understand the hardware, right. And that’s not a thing; like as a .NET developer you don’t really know what a register is, or like or a bus, or like NES has a thing called a PPU; I guess what is that? Something Processing Unit? Pixel Processing Unit, yeah, yeah. So there’s a table of data that also has an image, like a single sprite sheet of your entire game.

Jonathan : [35:18] So there’s a table to interact with all the hardware to have a sprite sheet. And that’s all in the format of the ROM. And like I said, this was like a multi-year passion project of me figuring out. So like along the way, let’s say I figured out the, you know, the sprite table we talked about, you know, that was one piece. Oh, but the project’s not done. You know, let’s say then I figured out, how to get a full C# program emitted, you know, that’s part of it. Well but it’s still not done, right. Like it took a while to get the whole thing end-to-end working, you know.

Jamie : [36:06] Okay. So what came next then? So you have this table of tables of data, and tables of presumably instructions. How did you get from where you were, where you had a little bit working to the whole thing, the whole schmear, the whole shebang? Like what… it’s breaking my mind how you are writing out… you’re effectively taking C# code, converting it into the IL from the build, and then somehow doing some magic, and then writing binary out to disk, that then is interpreted by a NES emulator. And just works. Like what?

Jonathan : [36:55] Yeah yeah. Yeah, so maybe we could talk about an instruction. So what Hello World NES has in it, I’m going to just pull up an example so I’m talking to it.

Jonathan : [37:13] So you call… “Hello, World!” has, let’s say, seven method calls to kind of set up the NES. And then at the bottom is just an infinite loop that if you exited the loop, what would happen is the NES would just keep reading at random instructions. Like it would just keep reading the rest of the ROM as if it were code. And so who knows what would happen, right? But let’s say, imagine there’s one function called, it’s actually called vram_write. But imagine that that says “Console.WriteLine” and then you put a string in it, right? So that method vram_write, not only do I need to write like its implementation into the ROM, but I need to write an instruction that says, “hey, go call this function and here’s the string that you need,” right? So that’s probably several instructions. It’s like, “go to the string table,” “find string number zero,” “put it on the stack, " call vram_write,” and then vram_write knows where to look for the string.

Jonathan : [38:35] So you think about that example, what I described there on the NES side is actually very similar to what’s on the IL side, is that in IL, you have a string, right? It goes and looks up in a string table, the contents of the string, and puts it on a stack, and then it calls vram_write, and then it’s the runtimes job to actually like make that happen at runtime; or in the case of an AOT compiler it would emit, you know, native machine code that does the same thing. If that helps describe it a little better.

Jamie : [39:19] Yeah. Yeah, I mean it’s making lots more sense to me now right

Jamie : [39:29] So, okay. Stupid question time then: so how soon Super Mario Brothers?

Jonathan : [39:37] So I don’t have as much time as I would like, you know, to work on this. You know, I’ve got kids, you know. But, occasionally I will pick up the project and continue on. And so I did that, this, I can’t remember what month it was a few months ago. We had like a hackathon week and I actually had a co-worker help me, and he created the .NET bot in 8-bit workshop; and it has like a little Visual Editor. And so our goal was to get sprites working, because so far all I had was text, which is not a very good NES game, right. And then we also would like input to work, right? And so input does work and sprites work.

Jonathan : [40:30] However, the part that’s tricky, as you might imagine as I described this, is to implement input, you would write code in C. Let’s go back to C you would write like “if the left direction pad is down, do this,” right. “If the right d-pad is down, do this.” “If a is pressed, do this.” To do that logic, there’s a lot of branching, a lot of if else, and then also you have to do a binary… like, and to compare enums. Like, it’s kind of like you would do in C#, right. So all of those features together were not in my AOT compiler: complicated branching, binary and, right. So we got part of the way, like, within one week: we got sprites working. Input would work in a simple if statement, but not enough to that you could make the sprite move around the screen, right.

Jonathan : [41:44] We wanted to be able to use the D pad, or arrow keys, or whatever, and just move that .NET bot around. And we got close. If I had more free time, I think I could do it in another, you know, day or two, you know. So, but that is the goal. It’s like, a long-term goal. If I keep working on this, or if other people are interested, feel free, hit me up, is to find an open-source Mario game and port it to C#. And that would be our ultimate goal. First port it to C#, probably not take that long. In today’s world, Copilot could probably do it. But getting that program to actually work is probably, you know, a bit more involved right.

Jonathan : [42:37] Wow. I’m just imagining now, like, the next big global hackathon is: to get, like, “The .NET Bot Adventures: NES Edition” running or something.

Jonathan : [42:48] Yeah, yeah. And what’s cool… I have a couple of, like, physical hardware devices that can load NES roms, and any of my roms that I’ve made, they all work is what’s amazing to me.

Jonathan : [43:07] That’s awesome! Like, you get all your school friends together, and you fire up, I don’t know like a… I can’t even remember the name of them but, like I know which ones you mean: like you can get them in cartridge form, or you can get them as like a physical form factor. Like the NES Classic or whatever, right. You load that on there, and you’re like, “hey guys, check this out. I made this.”

Jonathan : [43:30] Yeah, yeah. You can get like a GameBoy-like device. You can get another one that’s interesting: there’s a device called an EverDrive, and what it is, is like an a physical NES cartridge that has an SD Card inside it, and you could put any rom you want, and then put that in an original NES and it’ll run it. So I don’t own one of those, like, I don’t have that hardware; but that sounds like the ultimate thing to to test on.

Jamie : [44:06] Yeah, actually. Rather than running it through an emulator, running it on the actual device.

Jonathan : [44:11] Yeah. Another device I have is… one I actually have is called the Retron5. And what it lets you do is is use the physical controllers. and then it has an HTML output, and then you can use the physical cartridges. But you can also just pop an SD card in it and run any rom, right? So I have used that to test my games and seen them run on a TV, but…

Jamie : [44:46] “Hey, kids, come check this out.”

Jonathan : [44:48] They’re like, “it just says, hello, world. What’s the deal?”

Jamie : [44:53] “This is the worst game ever, Dad. What are you doing?”

Jamie : [44:58] That’s awesome.

Jamie : [45:00] Okay. And so, like you said at the beginning, this isn’t just something that you just kind of went, “I know what I’ll do. I have no experience with any of these tools. I’ll just do it.” It’s kind of related to your day job a little bit, right? And I think that’s one of the things that folks, kind of, need to remember is: that with you working on the teams that you’re working on, this is without doing it for NES stuff, this is kind of what you’re doing anyway; so you had a lot of… you had a fair amount of background knowledge into how the tooling works. You invented your own tooling as you went, but you know… so like it’s kind of like how if i was building, say I don’t know, a WebAPI for something, some personal project, right. This is kind of along those lines for you, right.

Jonathan : [45:49] Yeah. Yeah. So I was a total noob about NES programming, like 6502 assembly, like yeah. But I knew the rest. And I think if you’re gonna do a project for fun, and I always think, you know, make sure there’s a super huge unknown in there that you’ll learn, right? Like, that’s part of why I did it. I wanted to learn a little computer history. But it also really, I mean, I learned about MSBuild. I’ve learned about IL. Like, you know, there are other skills in there that are useful, you know? For sure.


A Request To You All

If you're enjoying this show, would you mind sharing it with a colleague? Check your podcatcher for a link to show notes, which has an embedded player within it and a transcription and all that stuff, and share that link with them. I'd really appreciate it if you could indeed share the show.

But if you'd like other ways to support it, you could:

  • Leave a rating or review on your podcatcher of choice
  • Consider buying the show a coffee
    • The BuyMeACoffee link is available on each episode's show notes page
    • This is a one-off financial support option
  • Become a patron
    • This is a monthly subscription-based financial support option
    • And a link to that is included on each episode's show notes page as well

I would love it if you would share the show with a friend or colleague or leave a rating or review. The other options are completely up to you, and are not required at all to continue enjoying the show.

Anyway, let's get back to it.


Jamie : [46:28] That’s really cool. Yeah, because like, if I look back on my own, because I remember you at the beginning of this conversation, you were saying that like, “the 6502 is old enough, so that if I were born 10 years earlier, I would have been programming with it. " And what I’ll say about me is, one of the first bits of hardware that I ever programmed in an education setting was a Motorola 68K.

Jamie : [46:57] There was this control board. On the computer we were using, you had to drop out to DOS, as it was running Windows 98, I think, drop out to DOS, pull up this DOS-based IDE, type a whole bunch of assembler into it, and hit run, and it would compile the binary and send it over the parallel port onto this device. Yeah, so I feel like I’m perhaps behind you a little bit… in ahead of you a little bit, in years… but behind you a little bit in that aspect. I don’t know.

Jonathan : [47:33] Well, so that’s… 6502 it’s an interesting time because other computers use the same assembly language. So, like, the Commodore64 like you know people are older than me probably know better. But back then, you could write assembly, and then you could maybe get your program to work on multiple machines, which is kind of interesting. Like, to us today, that’s not a thing, right? Like, you can’t just take Windows assembly and run it on a Mac, right?

Jonathan : [48:11] So, yeah. My earliest computer memory… So I used DOS when I was a kid, for sure. But all I knew how to do was to like change to the A drive and run the game that I wanted to run, right. Like that’s, that was my beginning. And then a few years later, you would have to know how to type, “win” to boot windows, right? Like the two things that, that I remember in the early days, you know. But I never… I didn’t code until I was like 18 or something like that, right, in my life. So there’s a whole thing I missed, being too young there, you know. I think there… people talk about getting magazines and writing out assembly programs. Sounds like a fun time, you know so.

Jamie : [49:05] Yeah. So I never managed to do any of that, but like my experience with that flight… it was a Flight68k board, right. And you can still buy them today which is really cool. Every so every so often I go, “ooh! Should I buy one?” But then I think, “no. I don’t have anything with a parallel port, right. So it’s not gonna work.”

Jamie : [49:26] But, yeah that was in the early 2000s, and I was around the age of 16 when I was introduced to that piece of hardware. And at, the time when I was playing around with these things at college—so that’s college: 16 to 18 here in the UK— I found out it’s the same CPU that was in the original Apple hardware, and the same cpu that was in the Sega Genesis/Sega MegaDrive. So like I’d be… remember going down to the library to see if I could find the 68k book to figure out, “wait! Can I use this to program my Genesis at home? This would be awesome!”

Jonathan : [50:09] Yeah. Yeah. Yeah. And it may be back then that, f they had a standard processor, they got it where it was cheap, and then everybody was trying to use the same one in their computer or console, right? I guess it would make sense that the same assembly language would work, you know. So it’s a different time for sure.

Jamie : [50:34] Yeah, absolutely. We’ve gone kind of the other way, right? We’ve standardized on languages rather than the hardware and had the runtimes and the tools build the stuff for the hardware we’re targeting, right? So my understanding is modern game development probably still is in C/C++ with a bit of Assembler where you need to really tweak the hardware. But you’re probably using something like Unity, which has the ability to pull in C# to do some stuff. And when you hit build, it will figure out what you want to run it on. You can go, you know, “build for Nintendo Switch,” and it will pop out a Nintendo Switch thing; or you can do, “build for you know PC,” and it’ll pop out a whole bunch of PC stuff. Which is, yeah, it’s interesting that we’ve kind of swung the other way. right: instead of using one common language and hardware, we’ve gone, “whatever the hardware is they will make the tooling work best,” right.

Jonathan : [51:32] Yeah, and Unity’s interesting. Like, for example, if you publish an iOS project for Unity, I don’t know if it’s still like this, but back when I used it in my day job, it outputs an Xcode project, and then it’s up to you to go from there, take it from there.

Jamie : [51:52] “Best of luck to you.

Jonathan : [51:55] Yeah.

Jamie : [51:57] Wow. That’s amazing. Wow. Okay, cool.

Jamie : [52:02] Yeah, that’s been really cool. l’ve really enjoyed this walk down an interesting path with creating something that will, I guess transpile, directly to 6502 assembler for the NES. Like, it’s something that I feel like a lot of us don’t need to ever do, but knowing that there is a path to convert my C# program into something that could possibly run on a very specific piece of hardware, I think is a journey that maybe IoT people need to be aware of; or like, if we ever do get to a point where there’s some specific hardware, it is possible to convert C# over to that format your hardware, for whatever runtime is running.

Jonathan : [52:55] And if your target device you want to run this on is reasonably modern, you could have gone the other way. Where I went from scratch, because the NES is so old, there’s no chance, right. You could go look at the native AOT compiler and look at what it would take to make it output native code in the new format. And the benefit of going that way is that anything in .NET would work, right? But, of course, there’s going to be different challenges taking that approach, like working with existing code, probably a bit to learn there. And then if you want to try to contribute back the work, you might get code review from the .NET runtime team. But yeah, that’s also an option to go make native AOT run on the new platform.

Jamie : [53:56] Now I’m trying to figure out whether you could go the other way and do native AOT to the NES.

Jonathan : [54:07] Yeah, the part that I don’t know is if you could get the code small enough. You have a very limited number of bytes to work with. So does the GC fit in there? I don’t know. You know, that’s a question.

Jamie : [54:24] Do some extreme trimming.

Jonathan : [54:28] Yeah, yeah.

Jamie : [54:30] Awesome. Well, I mean, I’ve had a huge amount of fun, Jonathan, so thank you for this. And I’ve learned a whole bunch about, like, the tooling that builds my code; as well knowing that like msbuild has this extensibility to be able to throw stuff in to do part of that packaging process, do some extra code. Like, i still want to learn, one of these days, about source generators because that’s just proper magic wizardry. One day I will learn about them, but for now I’m happy just going, “yeah, cool . The source generator, it’s doing some stuff. There is stuff being done there, and I don’t know what it is, and it’s awesome.”

Jonathan : [55:11] Yeah.

Jamie : [55:14] What I’ll say, Jonathan, is that we’re coming close to the end of our allotted time together. I know you’re a very busy person, probably going to go off and play some NES games aren’t you? So if Jonathan’s boss is listening: no he’s not, he’s going to go and do lots some really, really, difficult work, and he needs a raise. that’s what I will say.

Jonathan : [55:34] Sounds good. Yeah, no this has been great. Yeah I’m always happy to talk about this project. If you’re interested you could file an issue. I have a also have a, like, a discord server if you want to just hop in there and chat, we can talk. So anybody who’s listening to this feel free to join in if you want.

Jamie : [56:01] I will get the links for the repo and for the Discord server, and put them into the show notes. So people should definitely join those and take a look. Because, yeah, I mean, the code in the repo is really, really well put together, too. So what I mean by that is it looks great, but also reads really well for people who don’t know what this is. Like, if you know C#, you can follow this repo, right? it’s not really doing anything supremely magical, it’s still the C# that you know and love so folks should definitely go check that out.

Jonathan : [56:39] Yeah. Sounds good, and thank you for yeah kind words.

Jamie : [56:44] Hey, no worries. I only tell the truth, right. So what about if folks want to ask you a question or talk to you about this? Is discord the best place to go, if they join the discord server.

Jonathan : [56:56] Yeah that Also I have discussions enabled on the GitHub repo, so you could go there if that’s easier. I’m on different social media, but i don’t check that quite as bit as GitHub. GitHub is is my ultimate social media, right.

Jamie : [57:13] Yeah, right. That’s what was made for, wasn’t it?

Jamie : [57:19] Amazing. Well Jonathan, I want to thank you for this wonderful conversation. There’s a whole bunch of stuff here that, you know, sometimes we have to step out of our normal path, the normal journey that we take every day into slightly the absurd to learn something new. And I love it. I absolutely do. You’ve inspired me to go figure out if I can find any of my old 68k books and try and do something similar but with .NET. I think that would be cool, right

Jonathan : [57:51] Yeah, definitely possible. Yeah, definitely. You could do the exact same thing i’ll bet, yeah.

Jamie : [57:59] Yeah. Well, I mean the sky’s the limit, right. Why not?

Jamie : [58:02] Amazing. Well, thank you very much Jonathan. I’ve had a wonderful chat today

Jonathan : [58:09] Yeah, thank you.

Wrapping Up

Thank you for listening to this episode of The Modern .NET Show with me, Jamie Taylor. I’d like to thank this episode’s guest for graciously sharing their time, expertise, and knowledge.

Be sure to check out the show notes for a bunch of links to some of the stuff that we covered, and full transcription of the interview. The show notes, as always, can be found at the podcast's website, and there will be a link directly to them in your podcatcher.

And don’t forget to spread the word, leave a rating or review on your podcatcher of choice—head over to dotnetcore.show/review for ways to do that—reach out via our contact page, or join our discord server at dotnetcore.show/discord—all of which are linked in the show notes.

But above all, I hope you have a fantastic rest of your day, and I hope that I’ll see you again, next time for more .NET goodness.

I will see you again real soon. See you later folks.

Follow the show

You can find the show on any of these places