Allows using .net languages to create real working SNES roms!
This works by providing a .net library that abstracts functions and globals used in creating SNES games. Once a
project using the DotnetSnes.Core library is compiled, the resulting DLL is transpiled to C using the
Dotnet To C transpiler (dntc). The game's C code is then compiled against
the PvSnesLib SDK toolchain to create a working rom.
Due to the limitations of the SNES, it is not always possible to write idiomatic C# for a working rom. For example:
- Minimal
Systemlevel type support - No dynamic allocations are supported (thus no reference type support)
- Minimizing variables on the stack is important
- Pointer/address tracking is common to save memory
Even with these limitations, it is still possible to create real SNES games, though there might be instances where it may be necessary to go to a lower level than C# supports.
Note: The dntc transpiler has its own limitations on what MSIL operations it supports and doesn't yet support, so it's possible to hit code that should work but the transpiler does not yet have support for.
There are several things to be aware of before getting started
- The PVSnesLib SDK works best under Linux. If developing on windows, you'll want to do the final compilation of the SNES rom under WSL, but obviously you can use windows emulators to test the resulting rom.
- This repository should be cloned using
sshcredentials. This is because all submodules (and submodules within submodules) are usinggit@addresses and I have not yet figured out a good way to allow them to be mixed. See the Github SSH key docs for help. - Make sure to do a full recursive submodule update after cloning (e.g.
git submodule update --init --recursive)
export PVSNESLIB_HOME=(yourpath)/DotnetSnes/pvsneslibsudo apt-get update(if needed)sudo apt-get install cmakesudo apt-get install g++sudo apt-get install dotnet-sdk-8.0
- Clone the DotnetSnes repository
- Do a recursive submodule initialization on the clone to get dependent projects
git submodule update --init --recursive
cdinto thepvsneslib/directory and runmake- This will make sure the pvsneslib toolchain is now built
The Hello World is a basic example that shows a bare minimum SNES rom that prints text to the screen.
cdinto src/DotnetSnes.Example.HellowWorld/- run
make
You should see Build finished successfully ! and a HelloWorld.sfc should now exist in your bin/Release/net8.0
folder. This is your SNES rom that you can load into an emulator, or save to flash cart.
mario.mp4
This is a C# port of the PVSnesLib LikeMario example, which demonstrates how to manage objects, tmx tile maps, audio, and game pad input.
Important classes are:
- Globals.cs contains assembly labels for where content exists in the baked in rom.
- Game.cs contains the logic for initializing the game
- Mario.cs contains the logic for showing and controlling the mario object
To build:
cdinto [src/DotnetSnes.Example.LikeMario]- run 'make'
The LikeMario.sfc rom file should now exist in the bin/Release/net8.0 folder.
To start a brand-new project:
- Create a new .net class library project
- Reference the
DotnetSnes.Corelibrary- This provides core functionality needed by SNES roms
- Reference the
Dntc.Attributeslibrary- This provides pretty important attributes thatare useful for the transpilation process
- Create a
public static int Main()function declaration to declare your rom's entry point - Add the
[CustomFunctionName("main")]attribute to yourMain()function, to force it to havemainas the function name after transpiling to C. This allows the SDK to know it's the entry point. - Create a file named
Makefilewith the following contents:
.SUFFIXES:
SNES_NAME ?= HelloWorld # Replace with the name you want your rom to have
SNES_DLL ?= $(ARTIFACTS_ROOT)/DotnetSnes.Example.HelloWorld.dll # Replace with the DLL your csproj will generate
ARTIFACTS_ROOT ?= $(abspath bin/Release/net8.0)
DNSNES_CORE_DIR ?= $(abspath ../DotnetSnes.Core) # Replace with the relative path to the DotnetSnes.Core project
DNTC_TOOL_CSPROJ ?= $(abspath ../../dntc/Dntc.Cli/Dntc.Cli.csproj) #
PVSNESLIB_HOME ?= $(abspath ../../pvsneslib) # Replace with the relative path to the pvsneslib folder from the repo
DNSNES_BASE_MAKEFILE ?= $(abspath ../Makefile.base) # Replace with the relative path to the DotnetSnes Makefile.base file
include $(DNSNES_BASE_MAKEFILE)
# PVSNESLIB
include ${PVSNESLIB_HOME}/devkitsnes/snes_rules
PVSNESLIB_DEVKIT_TOOLS = $(PVSNESLIB_HOME)/devkitsnes/tools- If any content/game assets are needed to be added to the ROM, include them in the directory and create
game_assetsrules for them at the bottom of theMakefile. For example:
game_assets: mario_sprite.pic tiles.pic map_1_1.m16 mariofont.pic musics mariojump.brr
mario_sprite.pic: mario_sprite.bmp
@echo convert sprites ... $(notdir $@)
$(GFXCONV) -s 16 -o 16 -u 16 -p -t bmp -i $<
tiles.pic: tiles.png
@echo convert map tileset... $(notdir $@)
$(GFXCONV) -s 8 -o 16 -u 16 -p -m -i $<
map_1_1.m16: map_1_1.tmj tiles.pic
@echo convert map tiled ... $(notdir $@)
$(TMXCONV) $< tiles.map
mariofont.pic: mariofont.bmp
@echo convert font with no tile reduction ... $(notdir $@)
$(GFXCONV) -s 8 -o 2 -u 16 -e 1 -p -t bmp -m -R -i $<- Create a
manifest.jsonfile in the same directory as yourcsproj. This instructs the transpiler on how to convert your project from a .net DLL to C.
{
"AssemblyDirectory": ".",
"AssembliesToLoad": [
"DotnetSnes.Core.dll",
"DotnetSnes.Example.LikeMario.dll"
],
"PluginAssembly": "DotnetSnes.Core.dll",
"OutputDirectory": ".",
"SingleGeneratedSourceFileName": "LikeMario.c",
"MethodsToTranspile": [
"System.Int32 DotnetSnes.Example.LikeMario.Game::Main()"
],
"GlobalsToTranspile": []
}- Make the following updates to the
manifest.jsonfile:- Change the second
AssembliesToLoadvalue fromDotnetSnes.Example.LikeMario.dllto the dll being generated by your project. - Change
SingleGeneratedSourceFileNamefromLikeMario.cto be<RomName>.c, where<RomName>is the same name specified in theSNES_NAMEvalue in theMakefile - Set
MethodsToTranspileto point towards yourMainfunction id. In most cases, this is just a matter of changingDotnetSnes.Example.LikeMario.Gameto the full namespace + static class name that contains yourMainfunction.
- Change the second
- Make sure your .net project copies the
manifest.jsonfile and any other content files needed to the output directory on build. - Add code to your
Main()function cdinto your project directory and typemake.
That should compile your project, and if successful will build the SNES rom in the directory specified by the
ARTIFACTS_ROOT in your Makefile.
If your code references game content specified in assembly files, those locations in the assembly files are referenced
by labels. In C code these are referenced by defining an extern char for the label name. In .net projects, you can
reference these by annotating a field with a [AssemblyLabel("labelName")] attribute.