dotnes.templates
0.2.0-alpha
dotnet new install dotnes.templates::0.2.0-alpha
.NES ("dot" NES)
.NET for the NES game console!
Contributing
PRs of any kind are welcome! If you have a question, feel free to:
Thanks!
Getting Started
Simply install the template:
dotnet new install dotnes.templates
Create a project:
dotnet new nes
Or use the project template in Visual Studio:
Build and run it as you would a console app:
dotnet run
Of course, you can also just open the project in Visual Studio and hit F5.
Note that Ctrl+F5 currently works better in C# Dev Kit in VS Code.
Check out the video for a full demo:
Anatomy of an NES application
"Hello World" looks something like:
// set palette colors
pal_col(0, 0x02); // set screen to dark blue
pal_col(1, 0x14); // fuchsia
pal_col(2, 0x20); // grey
pal_col(3, 0x30); // white
// write text to name table
vram_adr(NTADR_A(2, 2)); // set address
vram_write("Hello, world!"); // write bytes to video RAM
// enable PPU rendering (turn on screen)
ppu_on_all();
// infinite loop
while (true) ;
This looks very much like "Hello World" in C, taking advantage of the latest C# features in 2023.
By default the APIs like pal_col
, etc. are provided by an implicit
global using static NESLib;
and all code is written within a single
Program.cs
.
Additionally, a chr_generic.s
file is included as your game's "artwork" (lol?):
.segment "CHARS"
.byte $00,$00,$00,$00,$00,$00,$00,$00
...
.byte $B4,$8C,$FC,$3C,$98,$C0,$00,$00
;;
This table of data is used to render sprites, text, etc.
Scope
The types of things I wanted to get working initially:
- An object model for writing NES binaries
- Building a project should produce a
*.nes
binary, that is byte-for-byte identical to a program written in C. - "Hello World" runs
- Byte arrays, and a more advanced sample like
attributetable
run - Local variables work in some form
- Project template, MSBuild support, IDE support
Down the road, I might think about support for:
- Methods
- Structs
- Multiple files
- Some subset of useful BCL methods
How it works
For lack of a better word, .NES is a "transpiler" that takes MSIL and transforms it directly into a working 6502 microprocessor binary that can run in your favorite NES emulator. If you think about .NET's Just-In-Time (JIT) compiler or the various an Ahead-Of-Time (AOT) compilers, .NES is doing something similiar: taking MSIL and turning it into runnable machine code.
To understand further, let's look at the MSIL of a pal_col
method call:
// pal_col((byte)0, (byte)2);
IL_0000: ldc.i4.0
IL_0001: ldc.i4.2
IL_0002: call void [neslib]NES.NESLib::pal_col(uint8, uint8)
In 6502 assembly, this would look something like:
A900 LDA #$00
20A285 JSR pusha
A902 LDA #$02
203E82 JSR _pal_col
You can see how one might envision using System.Reflection.Metadata to iterate over the contents of a .NET assembly and generate 6502 instructions -- that's how this whole idea was born!
Note that the method NESLib.pal_col()
has no actual C# implementation. In
fact! there is only a reference assembly even shipped in .NES:
> 7z l dotnes.0.2.0-alpha.nupkg
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2023-09-14 14:37:38 ..... 8192 3169 ref\net8.0\neslib.dll
If you decompile neslib.dll
, no code is inside:
// Warning! This assembly is marked as a 'reference assembly', which means that it only contains metadata and no executable code.
// neslib, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null
// NES.NESLib
public static void pal_col(byte index, byte color) => throw null;
When generating *.nes
binaries, .NES simply does a lookup for pal_col
to
"jump" to the appropriate subroutine to call it.
.NES also emits the assembly instructions for the actual pal_col
subroutine, a
code snippet of the implementation:
/*
* 823E 8517 STA TEMP ; _pal_col
* 8240 209285 JSR popa
* 8243 291F AND #$1F
* 8245 AA TAX
* 8246 A517 LDA TEMP
* 8248 9DC001 STA $01C0,x
* 824B E607 INC PAL_UPDATE
* 824D 60 RTS
*/
Write(NESInstruction.STA_zpg, TEMP);
Write(NESInstruction.JSR, popa.GetAddressAfterMain(sizeOfMain));
Write(NESInstruction.AND, 0x1F);
Write(NESInstruction.TAX_impl);
Write(NESInstruction.LDA_zpg, TEMP);
Write(NESInstruction.STA_abs_X, PAL_BUF);
Write(NESInstruction.INC_zpg, PAL_UPDATE);
Write(NESInstruction.RTS_impl);
Limitations
This is a hobby project, so only around 5 C# programs are known to work. But to get an idea of what is not available:
- No runtime
- No BCL
- No objects or GC
- No debugger
- Strings are ASCII
What we do have is a way to express an NES program in a single Program.cs
.
Links
To learn more about NES development, I found the following useful:
ANESE License
I needed a simple, small NES emulator to redistribute with .NES that runs on Mac
and Windows. Special thanks to @daniel5151 and
ANESE. This is the default NES emulator
used in the dotnet.anese
package, license
here.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.2.0-alpha | 78 | 9/20/2024 |
0.1.1-alpha | 528 | 5/21/2024 |
0.1.0-alpha1 | 345 | 9/14/2023 |