dotnes 0.1.0-alpha1

This is a prerelease version of dotnes.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package dotnes --version 0.1.0-alpha1                
NuGet\Install-Package dotnes -Version 0.1.0-alpha1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="dotnes" Version="0.1.0-alpha1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add dotnes --version 0.1.0-alpha1                
#r "nuget: dotnes, 0.1.0-alpha1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install dotnes as a Cake Addin
#addin nuget:?package=dotnes&version=0.1.0-alpha1&prerelease

// Install dotnes as a Cake Tool
#tool nuget:?package=dotnes&version=0.1.0-alpha1&prerelease                

.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.

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net7.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on dotnes:

Repository Stars
jonathanpeppers/dotnes
.NET for the NES game console
Version Downloads Last updated
0.2.0-alpha 73 9/20/2024
0.1.1-alpha 205 5/21/2024
0.1.0-alpha1 110 9/14/2023