Pre-built release

Use this path if you downloaded a packaged NEShim release and want to configure it for your game without recompiling.


1. Rename the executable

If you want your game to appear as MyGame.exe rather than NEShim.exe, rename only the exe:

NEShim.exe → MyGame.exe

The app host has the assembly name (NEShim) baked into it as a binary string at publish time. It always looks for NEShim.dll, NEShim.runtimeconfig.json, and NEShim.deps.json by that fixed name regardless of what the exe file is called. Renaming those files will prevent the app from launching.

If you want the underlying assembly name to change as well (so that NEShim.dll itself is renamed), that requires a source build with <AssemblyName> changed in the csproj — see Building from source.

The exe icon in a pre-built release is fixed at whatever was embedded when the binary was built. If you need a custom exe icon, use Building from source instead. The taskbar and window icon at runtime are set correctly by the binary regardless.

The .pdb files are debug symbols and can be omitted from distribution builds entirely.


2. Set the window title

In config.json, set windowTitle to your game’s name:

{
  "windowTitle": "My Game Title"
}

3. Configure Steam App ID

  1. Register your game in the Steamworks partner dashboard and obtain your App ID.
  2. Replace the contents of steam_appid.txt (in the output directory, next to the exe) with your App ID — a plain integer, no trailing newline:
1234560

4. Steamworks native library

The Steamworks native library is not included in the NEShim release package. Before uploading to Steam, copy it from the Steamworks.NET 2025.163.0 release zip into the output directory alongside the exe. Use the copy bundled with the wrapper — it is matched to the wrapper version. The current build targets Steamworks.NET 2025.163.0.

Platform File
Windows (NEShim-win-x64) steam_api64.dll
Linux (NEShim-linux-x64) libsteam_api.so, renamed to libsteam_api64.so

Linux naming gotcha: the zip’s Linux redistributable is named libsteam_api.so, but the Steamworks.NET wrapper’s DllImport targets are compiled against the literal name steam_api64, which .NET’s Linux native-library resolution maps to libsteam_api64.so. Deploying the file under its zip-default name causes SteamAPI.Init() to throw internally (native library not found); SteamManager.Initialize catches this, logs it, and disables Steam entirely — the app keeps running with no crash, but achievements, overlay, and DLC ownership checks are all silently unavailable. Rename it before including it in your Linux depot.

Include the appropriate file in each platform’s Steam depot; Valve does not inject it automatically. Once it is in your depot, Steam distributes it to players as part of the normal game install.

If you ever need to upgrade to a newer Steamworks.NET version, use the copy bundled inside the Steamworks.NET release zip — it is pre-matched to the wrapper version.


5. Configure Steam Auto-Cloud

NEShim reads and writes save files to the local filesystem only. Cloud sync is handled entirely by Steam Auto-Cloud configured in the Steamworks partner dashboard — no code changes are required.

Files to sync

Path pattern Contents
saves\* Manual save states (slot0.stateslot7.state), slot metadata (.meta), and the auto-save (autosave.state)
game.srm Battery-backed RAM — the cartridge save for games like Zelda and Metroid

Do not sync config.json — it is the publisher configuration layer and should remain as shipped. Player preferences (volume, video filter, input bindings, language, etc.) are stored in user.json under %APPDATA%\<WindowTitle>\ (or the equivalent Proton path on Steam Deck). That directory is outside the Steam install tree, so Steam never touches it during updates or cloud sync — no Auto-Cloud rule is needed for it.

Steamworks dashboard setup

  1. Navigate to your app and open Cloud → Cloud Settings.
  2. Set the Quota to at least 10 MB (NES states are typically 10–50 KB each).
  3. Under Root Overrides, add two entries with root GameInstall: one for saves\* and one for game.srm.
  4. Publish the cloud configuration.

Limitations

  • Conflict resolution is opaque. Steam uses last-write-wins. There is no in-game conflict UI.
  • A crash can lose up to ~5 minutes of progress. The auto-save fires when the in-game menu opens, every ~5 minutes during active play, and on graceful exit — so the worst-case exposure window on a crash is one periodic interval. See Auto-save in the configuration reference.
  • Manual slot saves are immediately safe. Each manual save writes synchronously; Steam picks it up on the next sync.
  • NEShim collects no data. There is no telemetry or automatic crash reporting. A crash.log is written locally on crash but never transmitted. For your Steam store privacy policy, any applicable data collection comes from Steam itself (playtime, achievements, cloud saves) and is covered by Valve’s Privacy Policy. See Network activity and telemetry.

6. Rename the Steam Input VDF

In the output directory, rename game_actions_0.vdf to game_actions_<YourAppID>.vdf. The file contents do not change — only the filename.


7. Configure Steam Input

Upload the renamed VDF and the default controller bindings to the Steamworks partner dashboard.

Upload the action definition file

  1. Open the Steamworks partner dashboard for your app.
  2. Go to Steam Input → Default Configuration.
  3. Upload game_actions_<YourAppID>.vdf as the Game Actions file.

The VDF defines two action sets — Gameplay and Menu — that NEShim switches between automatically. Optionally customise the localization block with your game’s terminology.

Upload default controller bindings

The controller_bindings/ directory contains a pre-built default configuration for each supported controller type. Upload each file in the Steamworks dashboard as the Default Configuration for its controller type:

File Controller type
xbox360.vdf Xbox 360
xboxone.vdf Xbox One / Xbox Series X|S / Xbox One Elite
neptune.vdf Steam Deck
ps4.vdf PlayStation 4 DualShock 4
ps5.vdf PlayStation 5 DualSense
switch_pro.vdf Nintendo Switch Pro Controller
steam_controller.vdf Valve Steam Controller

Without these defaults, players must configure their controller bindings manually from the Steam overlay. With them, supported controllers work immediately at first launch.


8. Set up achievements in Steamworks

Before achievements can fire in-game, they must be registered in the Steamworks partner dashboard:

  1. Navigate to Achievements for your app.
  2. Create each achievement with an API Name (e.g. ACH_FIRST_WIN). This name is the steamId field in achievements.json.
  3. Add a name, description, and icon for each achievement.
  4. Add translated names and descriptions for each supported language. Steam returns the achievement display name in the user’s current game language — the unlock pop-up shown in-game pulls the name directly from Steam, so translations entered here appear automatically with no extra code. Missing languages fall back to the English name.
  5. Publish the achievements from the dashboard.

9. Author and seal achievements.json

  1. Create achievements.json in the output directory (alongside the exe).
  2. Compute your ROM’s SHA1 hash (see Finding the ROM SHA1 hash).
  3. Author the achievement definitions. See Achievement system for the full field reference.

Example:

{
  "A1B2C3D4E5F60718293A4B5C6D7E8F90A1B2C3D4": {
    "memoryDomain": "System Bus",
    "achievements": [
      {
        "steamId":    "ACH_FIRST_WIN",
        "address":    255,
        "bytes":      1,
        "encoding":   "binary",
        "comparison": "equals",
        "value":      1
      }
    ]
  }
}
  1. Seal the file using your private key:
pub-utils --key-file private_key.txt achievements.json

Verify all definitions are listed as [sealed] in the output. Never edit achievements.json after sealing without re-sealing — any changed definition will fail signature verification and silently stop firing.

Protecting your achievements

There is no default signing key — achievements are disabled until you configure one. Set achievementPublicKey in config.json to enable and protect achievements without rebuilding from source:

  1. Generate your own keypair:

    pub-utils --gen-keypair
    
  2. Set achievementPublicKey in config.json to the printed public key:

    {
      "achievementPublicKey": "MFkwEwYHKo..."
    }
    
  3. Re-seal your achievements.json with your private key:

    pub-utils --key-file private_key.txt achievements.json
    

Store the private key securely (1Password, local file outside source control, or CI secret). See Achievement system — Key management.

If you need the public key baked into the binary itself, use Building from source instead.


10. Prepare artwork and audio assets

All artwork paths in config.json are relative to the executable directory.

Config field Purpose Recommended size
mainMenuBackgroundPath Full-screen background on the pre-game menu 1920×1080 px (16:9). Stretched to fill the window — aspect-ratio distortion occurs if source doesn’t match the window.
sidebarLeftPath Image in the left letterbox bar during gameplay ~302×1080 px at 1080p (1:3.6 portrait). Cover-scaled; see sizing note below.
sidebarRightPath Image in the right letterbox bar during gameplay Same as left sidebar.
mainMenuMusicPath Looping audio for the pre-game menu MP3 or WAV. Plays with fade-in/fade-out transitions.

Sidebar image sizing: Each sidebar bar spans the full window height and is a narrow portrait strip. With the default Pixel Perfect (8:7 PAR) filter on a 16:9 display, each bar is approximately ~201×720 px at 720p, ~302×1080 px at 1080p, and ~402×1440 px at 1440p (all overscanMode: "Normal", 240 rows). On Steam Deck (16:10 at 1280×800) bars are narrower — approximately ~152×800 px. The bars are about 10–16 px narrower in Overscan mode (224 rows). Images are cover-scaled, so exact pixel counts don’t need to match; design at an aspect ratio of roughly 1:3.6 portrait for 16:9 displays. On Steam Deck the bars are narrower relative to their height (~1:5.3), so keep key art near the centre column of your image. Sidebar art always renders at its original colours — Color Effects do not apply to it.


11. Verify audio settings

Setting Recommendation
volume Set a comfortable default (e.g. 80) so the game doesn’t start at maximum volume.
audioFilter Choose a starting default for new players. "Default" matches real NES hardware output. "Warm" softens the high-frequency edge. "Saturation" adds a mild mid-level boost that works well on laptop and TV speakers. See the audio filter reference for all options.

12. Test

Before uploading to Steam:

  1. Copy the entire output directory to a machine without .NET installed to verify the self-contained runtime works.
  2. Launch through Steam (not directly from Explorer) to verify:
    • Steam overlay appears when Shift+Tab is pressed.
    • Gamepad input works via Steam Input if configured.
    • Achievements fire when conditions are met.
    • The game icon appears correctly in the Steam library.
  3. Verify the auto-save and save state slots work (save, quit, reload).
  4. Verify battery RAM persistence if the game uses it.

Steam Deck

NEShim runs on Steam Deck natively via the Linux x64 build (SDL_GPU/Vulkan path) or via Proton using the Windows x64 build (D3D11/DXVK path). No configuration changes are required for either path. See the Steam Deck guide for a full description of what is applied automatically on Deck.


Release checklist

  • NEShim.exe renamed to MyGame.exe (only the exe; all other NEShim.* files stay as-is)
  • windowTitle set in config.json
  • steam_appid.txt updated with your production App ID
  • Steamworks native library copied from Steamworks.NET release zip into each platform’s output directory (steam_api64.dll for Windows, libsteam_api.so for Linux renamed to libsteam_api64.so) and included in the corresponding Steam depots
  • Steam Auto-Cloud configured in the Steamworks dashboard (saves\* and game.srm under GameInstall root; config.json excluded — player preferences live in AppData user.json, which Steam cannot touch)
  • game_actions_0.vdf renamed to game_actions_<appid>.vdf
  • Renamed VDF uploaded to Steamworks dashboard under Steam Input → Default Configuration
  • Each controller_bindings/*.vdf uploaded as Default Configuration for its controller type
  • All achievements created in the Steamworks dashboard with matching API names
  • Translated achievement names and descriptions added in Steamworks dashboard for each supported language
  • Signing keypair generated with pub-utils --gen-keypair; achievementPublicKey set in config.json; private key stored outside source control (if protecting achievements)
  • achievements.json authored and sealed with pub-utils --key-file <keyfile>
  • lang/*.json files present for each supported language (built-in files ship automatically; add any extras alongside the exe)
  • Supported languages list set in Steamworks dashboard under Store Presence → Basic Info
  • Localized store descriptions and screenshots uploaded in Steamworks dashboard for each supported language
  • Artwork and music assets in place and referenced in config.json
  • Audio defaults verified in config.json
  • Release passes local smoke test (saves, Steam overlay, achievements)
  • Localization tested locally for each supported language (set "language": "<code>" in config.json, launch outside Steam)

Deployed file layout

Windows (NEShim-win-x64)

MyGame/
├── MyGame.exe                  ← renamed from NEShim.exe (only the exe can be renamed)
├── NEShim.dll                  ← must keep this name; baked into the app host
├── NEShim.deps.json
├── NEShim.runtimeconfig.json
├── NEShim.Signing.dll
├── BizHawk.dll
├── steam_api64.dll             ← from Steamworks.NET release zip; include in your Windows depot
├── steam_appid.txt
├── game_actions_1234560.vdf
├── controller_bindings/
│   ├── xbox360.vdf
│   ├── xboxone.vdf
│   ├── neptune.vdf
│   ├── ps4.vdf
│   ├── ps5.vdf
│   ├── switch_pro.vdf
│   └── steam_controller.vdf
├── config.json
├── achievements.json
├── game.nes
├── saves/                      ← created automatically on first save
├── game.srm                    ← created automatically if game uses battery RAM
├── art/
│   ├── menu_bg.png
│   ├── sidebar_left.png
│   └── sidebar_right.png
├── audio/
│   └── menu_theme.mp3
└── [.NET runtime files...]

Linux (NEShim-linux-x64)

The layout is identical, with these differences:

  • No .exe extension: the executable is NEShim (or MyGame if renamed)
  • libsteam_api64.so replaces steam_api64.dll — note the name change: the Steamworks.NET release zip ships this file as libsteam_api.so, but it must be renamed to libsteam_api64.so in your depot. The wrapper’s DllImport resolves to libsteam_api64.so; under the zip’s default name, Steam init fails at startup (caught internally, so the app still runs, but achievements/overlay/DLC checks are silently disabled)
  • The .NET runtime files include Linux-specific binaries