Tutorial 170: Run an Unchanged XNA 4.0 C# Game on CNA.NET
What you’ll learn: how CNA.NET runs original XNA 4.0 C# source on CNA’s native runtime, how to write the thin wrapper project, and how to use the game’s original content.
Status: CNA.NET is beta and source-first: you build CNA’s C ABI and CNA.NET from source, and each CNA.NET revision admits exactly one C ABI version (0.44.0 at this snapshot). The steps below follow the projects’ own documentation; they were not re-run for this page, and Linux x86_64 is the platform on which they were qualified.
The idea: wrap, don’t port
An XNA 4.0 game is C# source plus XNA-built content. CNA.NET supplies the Microsoft.Xna.Framework types that source expects, implemented over CNA’s native runtime. So the game’s .cs files stay exactly as they are; what you add is a small SDK-style project that tells modern .NET which files to compile and how XNA built them. That is how cna-cs-samples runs 84 original Microsoft sample programs: a shared build file carries the settings every 2010-era sample needs, and each sample’s own project carries only its identity.
1. Build the two native and managed halves
# CNA's C ABI, in the configuration CNA.NET qualifies on Linux
cmake -S cna -B cna/build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCNA_GRAPHICS_RENDERER=OPENGLES3 -DCNA_BUILD_C_API=ON -DCNA_EASYGL_COMPILED_EFFECTS=ON
cmake --build cna/build --target cna_c_api --parallel
# CNA.NET
dotnet build cna-cs/CNA.sln -c Release
export CNA_CS_ROOT="$PWD/cna-cs"
export CNA_NATIVE_LIBRARY="$PWD/cna/build/modules/c-api/libcna_c_api.so"
The CNA checkout needs its sibling repositories (sharp-runtime on its next branch, easy-gl, meta-gl) as described in Tutorial 02. Compiled effects are enabled because games that load XNA .xnb effects need them.
2. Add the wrapper project
Leave the original folder untouched and add a project beside it. Its essential parts are the XNA profile the game was built for, the exact list of source files the original project compiled, a reference to CNA.XnaCompat with its MSBuild targets, and the content:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>WinExe</OutputType>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<XnaProfile>HiDef</XnaProfile>
<DefineConstants>$(DefineConstants);WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="../Platformer/*.cs" /> <!-- list exactly what the original compiled -->
<ProjectReference Include="$(CnaCsRoot)/src/CNA.XnaCompat/CNA.XnaCompat.csproj" />
<None Include="../Platformer/Content/**" LinkBase="Content" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<Import Project="$(CnaCsRoot)/src/CNA.XnaCompat/build/CNA.XnaCompat.targets" />
</Project>
This is a sketch of the shape, not a file copied from a repository. In cna-cs-samples the same settings live in one shared Directory.Build.props/.targets pair, and Platformer’s own project is only this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- The original project's graphics profile; CNA.XnaCompat.targets embeds it as XNA did. -->
<XnaProfile>HiDef</XnaProfile>
<RootNamespace>Platformer</RootNamespace>
<AssemblyName>Platformer</AssemblyName>
<StartupObject>Platformer.Program</StartupObject>
</PropertyGroup>
</Project>
Why the extra switches matter: implicit usings and nullable reference types are modern C# defaults that the 2010-era code predates, and a glob of every .cs file can pick up files the original project deliberately excluded. XnaProfile decides whether GraphicsDeviceManager starts in Reach or HiDef, exactly as the profile embedded by XNA’s build did.
3. Content
Use the game’s original XNA-built .xnb files; CNA reads them directly. Linux file names are case-sensitive, so check that the names on disk match what the code asks for. XNA songs are WMA, which CNA does not decode: put an .ogg (or .oga/.qoa) with the same base name beside the unchanged song .xnb. If you only have the source content, build it with XNA 4.0’s own content pipeline — cna-cs-samples has a script that does this under Wine.
4. Build and run
dotnet build Platformer.CNA.csproj -c Release
dotnet run --project Platformer.CNA.csproj -c Release
If the native library is not found or has the wrong ABI version, the loader stops with a message naming what it tried; CNA_NATIVE_DIAGNOSTICS=1 adds detail. If the game throws, a CNA.CnaException in the stack trace means the refusal came from the native side; compare with the game’s C++ port in cna-samples to tell a CNA issue from a binding issue.
Where this has been shown to work
In cna-cs-samples, 83 Microsoft sample projects (84 programs) built this way from byte-identical upstream source run on Linux (on private X displays), single-threaded in headless Chromium, and on the x86_64 Android emulator; dozens of other XNA games from books and open-source projects were run the same way. Interactive browsers, physical Android devices, Windows, macOS and iOS are not qualified yet. The browser and Android heads, and coming from FNA or MonoGame, are covered in Migrating C# games to CNA.NET.
Next
Tutorial 171 starts a new C# game from the dotnet new template instead.