Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ jobs:
strategy:
matrix:
platform: [
{netversion: 6.x, targetframework: net6.0, aot: false, singleFile: true, aotString: ""},
{netversion: 8.x, targetframework: net8.0, aot: false, singleFile: true, aotString: ""},
{netversion: 8.x, targetframework: net8.0, aot: true, singleFile: false, aotString: " - AoT"}
{netversion: 10.x, targetframework: net10.0, aot: false, singleFile: true, aotString: ""},
{netversion: 10.x, targetframework: net10.0, aot: true, singleFile: false, aotString: " - AoT"}
]
fail-fast: false
runs-on: windows-2025-vs2026
Expand Down Expand Up @@ -84,7 +83,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.x

- name: Install dependencies
run: |
Expand Down Expand Up @@ -130,7 +129,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.x

- name: Install dependencies
run: |
Expand Down Expand Up @@ -181,7 +180,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x
dotnet-version: 10.x

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-format-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Format
Expand Down
10 changes: 5 additions & 5 deletions COMPILING.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
## Windows

1) Open the solution in Visual Studio 2022
1) Open the solution in Visual Studio (2022 or 2026)
2) Compile as `Release`/`x64`
3) Set the startup project to the `UI` project and run

## Linux

To build under Linux you need a version of Clang or GCC that supports C++17.

Additionally, SDL2 and the [.NET 8 SDK](https://learn.microsoft.com/en-us/dotnet/core/install/linux) must also be installed.
Additionally, SDL2 and the [.NET 10 SDK](https://learn.microsoft.com/en-us/dotnet/core/install/linux) must also be installed.

Once SDL2 and the .NET 8 SDK are installed, run `make` to compile with Clang.
Once SDL2 and the .NET 10 SDK are installed, run `make` to compile with Clang.
To compile with GCC instead, use `USE_GCC=true make`.
**Note:** Mesen usually runs faster when built with Clang instead of GCC.


## macOS

To build macOS, install SDL2 (i.e via Homebrew) and the [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0).
To build macOS, install SDL2 (i.e via Homebrew) and the [.NET 10 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0).

Once SDL2 and the .NET 8 SDK are installed, run `make`.
Once SDL2 and the .NET 10 SDK are installed, run `make`.
2 changes: 1 addition & 1 deletion Linux/appimage/appimage-arm64.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

export PUBLISHFLAGS="-r linux-arm64 --no-self-contained false -p:PublishSingleFile=true -p:PublishReadyToRun=true"
export PUBLISHFLAGS="-r linux-arm64 -p:PublishSingleFile=true -p:PublishReadyToRun=true"
make -j$(nproc) -O LTO=true STATICLINK=true SYSTEM_LIBEVDEV=false

curl -SL https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage -o appimagetool
Expand Down
2 changes: 1 addition & 1 deletion Linux/appimage/appimage.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

export PUBLISHFLAGS="-r linux-x64 --no-self-contained false -p:PublishSingleFile=true -p:PublishReadyToRun=true"
export PUBLISHFLAGS="-r linux-x64 -p:PublishSingleFile=true -p:PublishReadyToRun=true"
make -j$(nproc) -O LTO=true STATICLINK=true SYSTEM_LIBEVDEV=false

curl -SL https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -o appimagetool
Expand Down
21 changes: 7 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,20 @@ We are currently just doing development builds. When we launch stable releases,

[![Mesen](https://github.com/nesdev-org/MesenCE/actions/workflows/build.yml/badge.svg)](https://github.com/nesdev-org/MesenCE/actions/workflows/build.yml)

#### <ins>Native builds</ins> (recommended) ####
#### <ins>Recommended</ins>

These builds don't require .NET to be installed. They load more quickly and are recommended over the .NET builds.

* [Windows 10 / 11](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28Windows%20-%20net8.0%20-%20AoT%29.zip)
* [Windows 10 / 11](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28Windows%20-%20net10.0%20-%20AoT%29.zip)
* [Linux x64](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28Linux%20-%20ubuntu-22.04%20-%20clang_aot%29.zip) (requires **SDL2**)
* [Linux ARM64](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28Linux%20-%20ubuntu-22.04-arm%20-%20clang_aot%29.zip) (requires **SDL2**)
* [macOS - Intel](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28macOS%20-%20macos-15-intel%20-%20clang_aot%29.zip) (requires **SDL2**)
* [macOS - Apple Silicon](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28macOS%20-%20macos-15%20-%20clang_aot%29.zip) (requires **SDL2**)

#### <ins>.NET builds</ins> ####

These builds use .NET, which for some builds comes bundled and for others must be installed.
They all require **.NET 8** except the Windows 7 / 8 build, which requires **.NET 6**.
For Linux and macOS, **SDL2** must also be installed. For AppImage builds, **FUSE** (such as libfuse2) must be installed.
#### <ins>AppImages</ins> (Linux)

* [Windows 7 / 8](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28Windows%20-%20net6.0%29.zip) (requires **.NET 6**)
* [Linux x64 - AppImage](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20(Linux%20x64%20-%20AppImage).zip) (requires **FUSE** and **SDL2**)
* [Linux ARM64](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20%28Linux%20-%20ubuntu-22.04-arm%20-%20clang%29.zip) (requires **.NET 8** and **SDL2**)
* [Linux ARM64 - AppImage](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20(Linux%20ARM64%20-%20AppImage).zip) (requires **FUSE** and **SDL2**)
For AppImage builds, **FUSE** (such as libfuse2) and **SDL2** must be installed.

* [Linux x64 (AppImage)](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20(Linux%20x64%20-%20AppImage).zip)
* [Linux ARM64 (AppImage)](https://nightly.link/nesdev-org/MesenCE/workflows/build/master/Mesen%20(Linux%20ARM64%20-%20AppImage).zip)

#### <ins>Notes</ins> ####

Expand All @@ -53,7 +46,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md)

Mesen is available under the GPL V3 license. Full text here: <http://www.gnu.org/licenses/gpl-3.0.en.html>

Copyright (C) 2014-2025 Sour, 2026 contributors
Copyright (C) 2014-2026 Sour, 2026 contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
19 changes: 19 additions & 0 deletions UI/.filenesting.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"help": "https://go.microsoft.com/fwlink/?linkid=866610",
"root": true,

"dependentFileProviders": {
"add": {
"extensionToExtension": {
"add": {
".cs": [
".axaml.cs"
],
".designer.cs": [
".resx"
]
}
}
}
}
}
1 change: 1 addition & 0 deletions UI/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<l:EnumConverter x:Key="Enum" />
<u:FontNameConverter x:Key="FontNameConverter" />
<u:NullTextConverter x:Key="NullTextConverter" />
<u:EnumMatchConverter x:Key="EnumMatchConverter" />

<VisualBrush x:Key="ViewerBgBrush" TileMode="Tile" Stretch="None" AlignmentX="Left" AlignmentY="Top" SourceRect="0,0,5,5" DestinationRect="0,0,5,5">
<VisualBrush.Visual>
Expand Down
3 changes: 3 additions & 0 deletions UI/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public override void Initialize()
e.Handled = true;
};

#if DEBUG
this.AttachDeveloperTools();
#endif
AvaloniaXamlLoader.Load(this);
ResourceHelper.LoadResources();
}
Expand Down
99 changes: 49 additions & 50 deletions UI/Config/AudioConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Mesen.Interop;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using Mesen.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -10,54 +9,54 @@

namespace Mesen.Config
{
public class AudioConfig : BaseConfig<AudioConfig>
public partial class AudioConfig : BaseConfig<AudioConfig>
{
[Reactive] public string AudioDevice { get; set; } = "";
[Reactive] public bool EnableAudio { get; set; } = true;
[Reactive] public bool DisableDynamicSampleRate { get; set; } = false;

[Reactive][MinMax(0, 100)] public UInt32 MasterVolume { get; set; } = 100;
[Reactive] public AudioSampleRate SampleRate { get; set; } = AudioSampleRate._48000;
[Reactive][MinMax(15, 300)] public UInt32 AudioLatency { get; set; } = 60;

[Reactive] public bool MuteSoundInBackground { get; set; } = false;
[Reactive] public bool ReduceSoundInBackground { get; set; } = true;
[Reactive] public bool ReduceSoundInFastForward { get; set; } = false;
[Reactive][MinMax(0, 100)] public int VolumeReduction { get; set; } = 75;

[Reactive] public bool ReverbEnabled { get; set; } = false;
[Reactive][MinMax(1, 10)] public UInt32 ReverbStrength { get; set; } = 5;
[Reactive][MinMax(1, 30)] public UInt32 ReverbDelay { get; set; } = 10;

[Reactive] public bool CrossFeedEnabled { get; set; } = false;
[Reactive][MinMax(0, 100)] public UInt32 CrossFeedRatio { get; set; } = 0;

[Reactive] public bool EnableEqualizer { get; set; } = false;
[Reactive][MinMax(-20.0, 20.0)] public double Band1Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band2Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band3Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band4Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band5Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band6Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band7Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band8Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band9Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band10Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band11Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band12Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band13Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band14Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band15Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band16Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band17Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band18Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band19Gain { get; set; } = 0;
[Reactive][MinMax(-20.0, 20.0)] public double Band20Gain { get; set; } = 0;

[Reactive] public bool AudioPlayerEnableTrackLength { get; set; } = true;
[Reactive][MinMax(0, 9999)] public UInt32 AudioPlayerTrackLength { get; set; } = 120;
[Reactive] public bool AudioPlayerAutoDetectSilence { get; set; } = true;
[Reactive][MinMax(0, 999999)] public UInt32 AudioPlayerSilenceDelay { get; set; } = 3;
[ObservableProperty] public partial string AudioDevice { get; set; } = "";
[ObservableProperty] public partial bool EnableAudio { get; set; } = true;
[ObservableProperty] public partial bool DisableDynamicSampleRate { get; set; } = false;

[ObservableProperty][MinMax(0, 100)] public partial UInt32 MasterVolume { get; set; } = 100;
[ObservableProperty] public partial AudioSampleRate SampleRate { get; set; } = AudioSampleRate._48000;
[ObservableProperty][MinMax(15, 300)] public partial UInt32 AudioLatency { get; set; } = 60;

[ObservableProperty] public partial bool MuteSoundInBackground { get; set; } = false;
[ObservableProperty] public partial bool ReduceSoundInBackground { get; set; } = true;
[ObservableProperty] public partial bool ReduceSoundInFastForward { get; set; } = false;
[ObservableProperty][MinMax(0, 100)] public partial int VolumeReduction { get; set; } = 75;

[ObservableProperty] public partial bool ReverbEnabled { get; set; } = false;
[ObservableProperty][MinMax(1, 10)] public partial UInt32 ReverbStrength { get; set; } = 5;
[ObservableProperty][MinMax(1, 30)] public partial UInt32 ReverbDelay { get; set; } = 10;

[ObservableProperty] public partial bool CrossFeedEnabled { get; set; } = false;
[ObservableProperty][MinMax(0, 100)] public partial UInt32 CrossFeedRatio { get; set; } = 0;

[ObservableProperty] public partial bool EnableEqualizer { get; set; } = false;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band1Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band2Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band3Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band4Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band5Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band6Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band7Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band8Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band9Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band10Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band11Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band12Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band13Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band14Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band15Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band16Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band17Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band18Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band19Gain { get; set; } = 0;
[ObservableProperty][MinMax(-20.0, 20.0)] public partial double Band20Gain { get; set; } = 0;

[ObservableProperty] public partial bool AudioPlayerEnableTrackLength { get; set; } = true;
[ObservableProperty][MinMax(0, 9999)] public partial UInt32 AudioPlayerTrackLength { get; set; } = 120;
[ObservableProperty] public partial bool AudioPlayerAutoDetectSilence { get; set; } = true;
[ObservableProperty][MinMax(0, 999999)] public partial UInt32 AudioPlayerSilenceDelay { get; set; } = 3;

public void ApplyConfig()
{
Expand Down
13 changes: 6 additions & 7 deletions UI/Config/AudioPlayerConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Mesen.Interop;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using Mesen.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -10,11 +9,11 @@

namespace Mesen.Config
{
public class AudioPlayerConfig : BaseConfig<AudioPlayerConfig>
public partial class AudioPlayerConfig : BaseConfig<AudioPlayerConfig>
{
[Reactive] public UInt32 Volume { get; set; } = 100;
[Reactive] public bool Repeat { get; set; } = false;
[Reactive] public bool Shuffle { get; set; } = false;
[ObservableProperty] public partial UInt32 Volume { get; set; } = 100;
[ObservableProperty] public partial bool Repeat { get; set; } = false;
[ObservableProperty] public partial bool Shuffle { get; set; } = false;

public void ApplyConfig()
{
Expand Down
6 changes: 3 additions & 3 deletions UI/Config/BaseConfig.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Mesen.Utilities;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
using Mesen.Utilities;
using System;
using System.Text.Json;

namespace Mesen.Config
{
public class BaseConfig<T> : ReactiveObject where T : class
public partial class BaseConfig<T> : ObservableObject where T : class
{
public T Clone()
{
Expand Down
4 changes: 2 additions & 2 deletions UI/Config/BaseWindowConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Mesen.Config
{
public class BaseWindowConfig<T> : BaseConfig<T> where T : class
public partial class BaseWindowConfig<T> : BaseConfig<T> where T : class
{
public MesenSize WindowSize { get; set; } = new PixelSize(0, 0);
public MesenPoint WindowLocation { get; set; } = new PixelPoint(0, 0);
Expand All @@ -30,7 +30,7 @@ public void SaveWindowSettings(Window wnd)
public void LoadWindowSettings(Window wnd)
{
//Update _restoreBounds when size/position changes
wnd.GetPropertyChangedObservable(Window.ClientSizeProperty).Subscribe(x => UpdateRestoreBounds(wnd));
wnd.SizeChanged += (s, e) => UpdateRestoreBounds(wnd);
wnd.PositionChanged += (s, e) => UpdateRestoreBounds(wnd);

if(WindowSize.Width != 0 && WindowSize.Height != 0) {
Expand Down
12 changes: 6 additions & 6 deletions UI/Config/CheatCodes.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Mesen.Interop;
using Mesen.Utilities;
using Mesen.ViewModels;
using ReactiveUI.Fody.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -70,12 +70,12 @@ public static void ApplyCheats(IEnumerable<CheatCode> cheats)
}
}

public class CheatCode : ViewModelBase
public partial class CheatCode : ViewModelBase
{
[Reactive] public string Description { get; set; } = "";
[Reactive] public CheatType Type { get; set; }
[Reactive] public bool Enabled { get; set; } = true;
[Reactive] public string Codes { get; set; } = "";
[ObservableProperty] public partial string Description { get; set; } = "";
[ObservableProperty] public partial CheatType Type { get; set; }
[ObservableProperty] public partial bool Enabled { get; set; } = true;
[ObservableProperty] public partial string Codes { get; set; } = "";

public List<InteropCheatCode> ToInteropCheats()
{
Expand Down
4 changes: 2 additions & 2 deletions UI/Config/CheatWindowConfig.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Avalonia;
using ReactiveUI.Fody.Helpers;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -8,7 +8,7 @@

namespace Mesen.Config
{
public class CheatWindowConfig : BaseWindowConfig<CheatWindowConfig>
public partial class CheatWindowConfig : BaseWindowConfig<CheatWindowConfig>
{
public bool DisableAllCheats { get; set; } = false;
public List<int> ColumnWidths { get; set; } = new();
Expand Down
2 changes: 2 additions & 0 deletions UI/Config/ConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ public static bool ProcessSwitch(string switchArg)
object? cfg = ConfigManager.Config;
PropertyInfo? property;
for(int i = 0; i < switchPath.Length; i++) {
#pragma warning disable IL2075 // 'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.
property = cfg.GetType().GetProperty(switchPath[i], BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
#pragma warning restore IL2075 // 'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.
if(property == null) {
//Invalid switch name
return false;
Expand Down
Loading
Loading