Skip to content

Commit

Permalink
UI: Added Firmwares tab in emulation settings to view/manage firmware…
Browse files Browse the repository at this point in the history
… files
  • Loading branch information
SourMesen committed Dec 19, 2024
1 parent c447eaf commit 71ecb09
Show file tree
Hide file tree
Showing 15 changed files with 516 additions and 138 deletions.
2 changes: 2 additions & 0 deletions Core/Shared/Emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ bool Emulator::InternalLoadRom(VirtualFile romFile, VirtualFile patchFile, bool
_console->SaveBattery();
}

_soundMixer->StopAudio();

if(!forPowerCycle) {
_movieManager->Stop();
}
Expand Down
94 changes: 3 additions & 91 deletions Core/Shared/FirmwareHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

enum class FirmwareType
{
CX4,
DSP1,
DSP1B,
DSP2,
Expand All @@ -32,7 +31,9 @@ enum class FirmwareType
WonderSwan,
WonderSwanColor,
SwanCrystal,
Ymf288AdpcmRom
Ymf288AdpcmRom,
SmsBootRom,
GgBootRom
};

struct MissingFirmwareMessage
Expand All @@ -41,102 +42,13 @@ struct MissingFirmwareMessage
FirmwareType Firmware;
uint32_t Size = 0;
uint32_t AltSize = 0;
const char* FileHashes[5] = {};

MissingFirmwareMessage(const char* filename, FirmwareType type, uint32_t size, uint32_t altSize = 0)
{
Filename = filename;
Firmware = type;
Size = size;
AltSize = altSize;

switch(type) {
case FirmwareType::CX4: FileHashes[0] = "AE8D4D1961B93421FF00B3CAA1D0F0CE7783E749772A3369C36B3DBF0D37EF18"; break;
case FirmwareType::DSP1: FileHashes[0] = "91E87D11E1C30D172556BED2211CCE2EFA94BA595F58C5D264809EF4D363A97B"; break;
case FirmwareType::DSP1B: FileHashes[0] = "D789CB3C36B05C0B23B6C6F23BE7AA37C6E78B6EE9CEAC8D2D2AA9D8C4D35FA9"; break;
case FirmwareType::DSP2: FileHashes[0] = "03EF4EF26C9F701346708CB5D07847B5203CF1B0818BF2930ACD34510FFDD717"; break;
case FirmwareType::DSP3: FileHashes[0] = "0971B08F396C32E61989D1067DDDF8E4B14649D548B2188F7C541B03D7C69E4E"; break;
case FirmwareType::DSP4: FileHashes[0] = "752D03B2D74441E430B7F713001FA241F8BBCFC1A0D890ED4143F174DBE031DA"; break;
case FirmwareType::ST010: FileHashes[0] = "FA9BCED838FEDEA11C6F6ACE33D1878024BDD0D02CC9485899D0BDD4015EC24C"; break;
case FirmwareType::ST011: FileHashes[0] = "8B2B3F3F3E6E29F4D21D8BC736B400BC988B7D2214EBEE15643F01C1FEE2F364"; break;
case FirmwareType::ST018: FileHashes[0] = "6DF209AB5D2524D1839C038BE400AE5EB20DAFC14A3771A3239CD9E8ACD53806"; break;

case FirmwareType::Satellaview:
FileHashes[0] = "27CFDB99F7E4252BF3740D420147B63C4C88616883BC5E7FE43F2F30BF8C8CBB"; //Japan, no DRM
FileHashes[1] = "A49827B45FF9AC9CF5B4658190E1428E59251BC82D8A63D8E9E0F71E439F008F"; //English, no DRM
FileHashes[2] = "3CE321496EDC5D77038DE2034EB3FB354D7724AFD0BC7FD0319F3EB5D57B984D"; //Japan, original
FileHashes[3] = "77D94D64D745014BF8B51280A4204056CDEB9D41EA30EAE80DBC006675BEBEF8"; //English, DRM
break;

case FirmwareType::Gameboy:
FileHashes[0] = "CF053ECCB4CCAFFF9E67339D4E78E98DCE7D1ED59BE819D2A1BA2232C6FCE1C7";
FileHashes[1] = "26E71CF01E301E5DC40E987CD2ECBF6D0276245890AC829DB2A25323DA86818E";
break;

case FirmwareType::GameboyColor:
FileHashes[0] = "B4F2E416A35EEF52CBA161B159C7C8523A92594FACB924B3EDE0D722867C50C7";
FileHashes[1] = "3A307A41689BEE99A9A32EA021BF45136906C86B2E4F06C806738398E4F92E45";
break;

case FirmwareType::GameboyAdvance:
FileHashes[0] = "FD2547724B505F487E6DCB29EC2ECFF3AF35A841A77AB2E85FD87350ABD36570";
break;

case FirmwareType::Sgb1GameboyCpu: FileHashes[0] = "0E4DDFF32FC9D1EEAAE812A157DD246459B00C9E14F2F61751F661F32361E360"; break;
case FirmwareType::Sgb2GameboyCpu: FileHashes[0] = "FD243C4FB27008986316CE3DF29E9CFBCDC0CD52704970555A8BB76EDBEC3988"; break;

case FirmwareType::SGB1:
FileHashes[0] = "BBA9C269273BEDB9B38BD5EB23BFAA6E509B8DECC7CB80BB5513905AF04F4CEB"; //Rev 0 (Japan)
FileHashes[1] = "C6C4DAAB5C899B69900C460787DE6089EDABE94B760F96D9F583D30CC0A5BB30"; //Rev 1 (Japan+USA)
FileHashes[2] = "A75160F7B89B1F0E20FD2F6441BB86285C7378DB5035EF6885485EAFF6059376"; //Rev 2 (World)
break;

case FirmwareType::SGB2:
FileHashes[0] = "C172498A23D1176672931BAB33B629C7D28F914A43DCA9E540B8AF1B37CCF2C6"; //SGB2 (Japan)
break;

case FirmwareType::FDS:
FileHashes[0] = "99C18490ED9002D9C6D999B9D8D15BE5C051BDFA7CC7E73318053C9A994B0178";
FileHashes[1] = "A0A9D57CBACE21BF9C85C2B85E86656317F0768D7772ACC90C7411AB1DBFF2BF";
break;

case FirmwareType::StudyBox:
FileHashes[0] = "365F84C86F7F7C3AAA2042D78494D41448E998EC5A89AC1B5FECB452951D514C";
break;

case FirmwareType::PceSuperCd:
FileHashes[0] = "E11527B3B96CE112A037138988CA72FD117A6B0779C2480D9E03EAEBECE3D9CE";
break;

case FirmwareType::PceGamesExpress:
FileHashes[0] = "4B86BB96A48A4CA8375FC0109631D0B1D64F255A03B01DE70594D40788BA6C3D";
FileHashes[1] = "DA173B20694C2B52087B099B8C44E471D3EE08A666C90D4AFD997F8E1382ADD8";
break;

case FirmwareType::ColecoVision:
FileHashes[0] = "990BF1956F10207D8781B619EB74F89B00D921C8D45C95C334C16C8CCECA09AD";
FileHashes[1] = "FB4A898EB93B19B36773D87A6C70EB28F981B2686BEBDD2D431B05DCDF9CFFD4";
break;

case FirmwareType::WonderSwan:
FileHashes[0] = "BF4480DBEA1C47C8B54CE7BE9382BC1006148F431FBE4277E136351FA74F635E";
break;

case FirmwareType::WonderSwanColor:
FileHashes[0] = "F5A5C044D84CE1681F94E9EF74287CB989784497BE5BD5108DF17908DFA55DB2";
break;

case FirmwareType::SwanCrystal:
FileHashes[0] = "82E96ADDF5AB1CE09A84B6EEDAA904E4CA432756851F7E0CC0649006C183834D";
break;

case FirmwareType::Ymf288AdpcmRom:
FileHashes[0] = "53AFD0FA9C62EDA3E2BE939E23F3ADF48A2AF8AD37BB1640261726C5D5ADEBA8";
break;

default:
throw std::runtime_error("Unexpected firmware type");
}
}
};

Expand Down
33 changes: 33 additions & 0 deletions UI/Controls/FirmwareSelect.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="using:Mesen.Controls"
xmlns:l="using:Mesen.Localization"
mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="50"
x:Name="root"
x:Class="Mesen.Controls.FirmwareSelect"
>
<Grid DataContext="{Binding ElementName=root}" ColumnDefinitions="*,Auto,Auto,Auto" RowDefinitions="Auto">
<TextBox Text="{Binding Filename}" Grid.Column="0" IsReadOnly="True" />
<Image
Grid.Column="1"
Stretch="None"
Source="/Assets/Warning.png"
ToolTip.Tip="{Binding WarningMessage}"
ToolTip.ShowDelay="0"
IsVisible="{Binding WarningMessage.Length}"
Margin="3 0"
/>
<Button Content="{l:Translate btnSelect}" Click="btnBrowse_OnClick" Grid.Column="2" />
<c:IconButton
Grid.Column="3"
ToolTip.Tip="{l:Translate btnDelete}"
Icon="Assets/Close.png"
Command="{Binding DeleteFirmware}"
Height="21"
Width="21"
/>
</Grid>
</UserControl>
123 changes: 123 additions & 0 deletions UI/Controls/FirmwareSelect.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Metadata;
using Mesen.Config;
using Mesen.Interop;
using Mesen.Localization;
using Mesen.Utilities;
using Mesen.Windows;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Mesen.Controls;

public class FirmwareSelect : UserControl
{
public static readonly StyledProperty<FirmwareType> FirmwareTypeProperty = AvaloniaProperty.Register<FirmwareSelect, FirmwareType>(nameof(FirmwareType));
public static readonly StyledProperty<string> FilenameProperty = AvaloniaProperty.Register<FirmwareSelect, string>(nameof(Filename), "");
public static readonly StyledProperty<string> WarningMessageProperty = AvaloniaProperty.Register<FirmwareSelect, string>(nameof(WarningMessage), "");

public FirmwareType FirmwareType
{
get { return GetValue(FirmwareTypeProperty); }
set { SetValue(FirmwareTypeProperty, value); }
}

public string Filename
{
get { return GetValue(FilenameProperty); }
set { SetValue(FilenameProperty, value); }
}

public string WarningMessage
{
get { return GetValue(WarningMessageProperty); }
set { SetValue(WarningMessageProperty, value); }
}

public FirmwareSelect()
{
InitializeComponent();
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
ValidateFirmware();
base.OnAttachedToVisualTree(e);
}

private void ValidateFirmware()
{
bool incorrectSize = false;
long fileSize = 0;

Filename = "";
WarningMessage = "";

FirmwareFiles firmwareFiles = FirmwareType.GetFirmwareInfo();
string? path = firmwareFiles.Names.Select(filename => Path.Combine(ConfigManager.FirmwareFolder, filename)).Where(File.Exists).FirstOrDefault();
if(path != null) {
fileSize = new FileInfo(path).Length;
Filename = Path.GetFileName(path);

foreach(FirmwareFileInfo firmwareInfo in firmwareFiles) {
if(fileSize == firmwareInfo.Size) {
string hash = FirmwareHelper.GetFileHash(path);
if(Array.IndexOf(firmwareInfo.Hashes, hash) < 0) {
WarningMessage = ResourceHelper.GetMessage("UnknownFirmwareHash", firmwareInfo.Hashes[0], hash);
}
incorrectSize = false;
break;
} else {
incorrectSize = true;
}
}
}

if(incorrectSize) {
WarningMessage = ResourceHelper.GetMessage("InvalidFirmwareSize", FirmwareType.GetFirmwareInfo()[0].Size, fileSize);
}
}

private async void btnBrowse_OnClick(object sender, RoutedEventArgs e)
{
while(true) {
string? selectedFile = await FileDialogHelper.OpenFile(null, VisualRoot, FileDialogHelper.FirmwareExt);
if(selectedFile?.Length > 0 && File.Exists(selectedFile)) {
if(await FirmwareHelper.SelectFirmwareFile(FirmwareType, selectedFile, VisualRoot)) {
break;
}
} else {
break;
}
}
ValidateFirmware();
}

public async void DeleteFirmware(object parameter)
{
string path = Path.Combine(ConfigManager.FirmwareFolder, Filename);
if(File.Exists(path)) {
DialogResult result = await MesenMsgBox.Show(VisualRoot, "PromptDeleteFirmware", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, path);
if(result == DialogResult.OK) {
File.Delete(path);
}
}
ValidateFirmware();
}

[DependsOn(nameof(Filename))]
public bool CanDeleteFirmware(object parameter)
{
return Filename != null && Filename.Length > 0 && File.Exists(Path.Combine(ConfigManager.FirmwareFolder, Filename));
}
}
12 changes: 9 additions & 3 deletions UI/Controls/OptionSection.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@
<ItemsControl.Template>
<ControlTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Margin="0 0 0 5">
<Image
Margin="2 0 5 0"
Stretch="None"
IsVisible="{Binding Icon.Length, ElementName=root}"
Source="{Binding IconSource, ElementName=root}"
VerticalAlignment="Center"
/>
<TextBlock
Text="{Binding Header, ElementName=root}"
Foreground="{DynamicResource ThemeControlHighlightLowBrush}"
FontSize="13"
Margin="0 0 0 5"
VerticalAlignment="Center"
/>
<ContentControl Content="{Binding Button, ElementName=root}" />
</StackPanel>
<StackPanel Margin="10 0 0 0">
<ItemsPresenter
Expand Down
24 changes: 20 additions & 4 deletions UI/Controls/OptionSection.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Mesen.Utilities;

namespace Mesen.Controls
{
public class OptionSection : ItemsControl
{
public static readonly StyledProperty<string> HeaderProperty = AvaloniaProperty.Register<OptionSection, string>(nameof(Header));
public static readonly StyledProperty<object> ButtonProperty = AvaloniaProperty.Register<OptionSection, object>(nameof(Button));
public static readonly StyledProperty<string> IconProperty = AvaloniaProperty.Register<OptionSection, string>(nameof(Icon), "");
public static readonly StyledProperty<Bitmap?> IconSourceProperty = AvaloniaProperty.Register<OptionSection, Bitmap?>(nameof(IconSource));

public string Header
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}

public object Button
public string Icon
{
get { return GetValue(ButtonProperty); }
set { SetValue(ButtonProperty, value); }
get { return GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}

public Bitmap? IconSource
{
get { return GetValue(IconSourceProperty); }
set { SetValue(IconSourceProperty, value); }
}

static OptionSection()
{
IconProperty.Changed.AddClassHandler<OptionSection>((x, e) => {
x.IconSource = ImageUtilities.BitmapFromAsset(x.Icon);
});
}

public OptionSection()
Expand Down
20 changes: 3 additions & 17 deletions UI/Interop/EmuApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ public string GetRomName()

public enum FirmwareType
{
CX4,
DSP1,
DSP1B,
DSP2,
Expand All @@ -273,7 +272,9 @@ public enum FirmwareType
WonderSwan,
WonderSwanColor,
SwanCrystal,
Ymf288AdpcmRom
Ymf288AdpcmRom,
SmsBootRom,
GgBootRom
}

public struct MissingFirmwareMessage
Expand All @@ -282,21 +283,6 @@ public struct MissingFirmwareMessage
public FirmwareType Firmware;
public UInt32 Size;
public UInt32 AltSize;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public IntPtr[] FileHashes;

public List<string> GetFileHashes()
{
List<string> hashes = new List<string>();
for(int i = 0; i < FileHashes.Length; i++) {
string hash = Marshal.PtrToStringUTF8(FileHashes[i]) ?? "";
if(hash.Length > 0) {
hashes.Add(hash);
}
}
return hashes;
}
}

public struct ExecuteShortcutParams
Expand Down
Loading

0 comments on commit 71ecb09

Please sign in to comment.