dotnes.templates 0.1.0-alpha1

This is a prerelease version of dotnes.templates.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet new install dotnes.templates::0.1.0-alpha1                
This package contains a .NET Template Package you can call from the shell/command line.

.NES ("dot" NES)

dot NES logo

.NET for the NES game console!

Gif of NES Emulator launching from VS Code

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:

Screenshot of the NES 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.

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.1.0-alpha1.nupkg
   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2023-09-14 14:37:38 .....         8192         3169  ref\net7.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.

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 68 9/20/2024
0.1.1-alpha 525 5/21/2024
0.1.0-alpha1 345 9/14/2023