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
System
level 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
ssh
credentials. 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/pvsneslib
sudo apt-get update
(if needed)sudo apt-get install cmake
sudo 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
cd
into 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.
cd
into 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:
cd
into [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.Core
library- This provides core functionality needed by SNES roms
- Reference the
Dntc.Attributes
library- 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 havemain
as the function name after transpiling to C. This allows the SDK to know it's the entry point. - Create a file named
Makefile
with 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_assets
rules 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.json
file 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.json
file:- Change the second
AssembliesToLoad
value fromDotnetSnes.Example.LikeMario.dll
to the dll being generated by your project. - Change
SingleGeneratedSourceFileName
fromLikeMario.c
to be<RomName>.c
, where<RomName>
is the same name specified in theSNES_NAME
value in theMakefile
- Set
MethodsToTranspile
to point towards yourMain
function id. In most cases, this is just a matter of changingDotnetSnes.Example.LikeMario.Game
to the full namespace + static class name that contains yourMain
function.
- Change the second
- Make sure your .net project copies the
manifest.json
file and any other content files needed to the output directory on build. - Add code to your
Main()
function cd
into 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.