Break off BoardName from IEmulator into a separate IBoardInfo service
This commit is contained in:
parent
083d9bec0e
commit
ded1c2d7b7
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using LuaInterface;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
|
@ -9,6 +10,9 @@ namespace BizHawk.Client.Common
|
|||
[RequiredService]
|
||||
public IEmulator Emulator { get; set; }
|
||||
|
||||
[OptionalService]
|
||||
public IBoardInfo BoardInfo { get; set; }
|
||||
|
||||
public GameInfoLuaLibrary(Lua lua)
|
||||
: base(lua) { }
|
||||
|
||||
|
@ -93,7 +97,7 @@ namespace BizHawk.Client.Common
|
|||
)]
|
||||
public string GetBoardType()
|
||||
{
|
||||
return Emulator.BoardName ?? string.Empty;
|
||||
return BoardInfo?.BoardName ?? string.Empty;
|
||||
}
|
||||
|
||||
[LuaMethodAttributes(
|
||||
|
|
|
@ -285,9 +285,9 @@ namespace BizHawk.Client.Common.MovieConversionExtensions
|
|||
movie.GameName = "NULL";
|
||||
}
|
||||
|
||||
if (Global.Emulator.BoardName != null)
|
||||
if (Global.Emulator.HasBoardInfo())
|
||||
{
|
||||
movie.BoardName = Global.Emulator.BoardName;
|
||||
movie.BoardName = Global.Emulator.AsBoardInfo().BoardName;
|
||||
}
|
||||
|
||||
if (Global.Emulator.HasRegions())
|
||||
|
|
|
@ -1495,7 +1495,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private void NESSubMenu_DropDownOpened(object sender, EventArgs e)
|
||||
{
|
||||
FDSControlsMenuItem.Enabled = Emulator.BoardName == "FDS";
|
||||
var boardName = Emulator.HasBoardInfo() ? Emulator.AsBoardInfo().BoardName : null;
|
||||
FDSControlsMenuItem.Enabled = boardName == "FDS";
|
||||
|
||||
VSControlsMenuItem.Enabled =
|
||||
VSSettingsMenuItem.Enabled =
|
||||
|
@ -1514,7 +1515,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private void FdsControlsMenuItem_DropDownOpened(object sender, EventArgs e)
|
||||
{
|
||||
FdsEjectDiskMenuItem.Enabled = Emulator.BoardName == "FDS";
|
||||
var boardName = Emulator.HasBoardInfo() ? Emulator.AsBoardInfo().BoardName : null;
|
||||
FdsEjectDiskMenuItem.Enabled = boardName == "FDS";
|
||||
|
||||
while (FDSControlsMenuItem.DropDownItems.Count > 1)
|
||||
{
|
||||
|
|
|
@ -3517,9 +3517,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
loader.Rom.RomData.HashMD5());
|
||||
}
|
||||
|
||||
if (Emulator.BoardName != null)
|
||||
if (Emulator.HasBoardInfo())
|
||||
{
|
||||
Console.WriteLine("Core reported BoardID: \"{0}\"", Emulator.BoardName);
|
||||
Console.WriteLine("Core reported BoardID: \"{0}\"", Emulator.AsBoardInfo().BoardName);
|
||||
}
|
||||
|
||||
// restarts the lua console if a different rom is loaded.
|
||||
|
|
|
@ -163,7 +163,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
current.GI = ldr.Game;
|
||||
current.CoreType = emu.GetType();
|
||||
emu.Controller = new Controller(emu.ControllerDefinition);
|
||||
current.BoardName = emu.BoardName;
|
||||
current.BoardName = emu.HasBoardInfo() ? emu.AsBoardInfo().BoardName : null;
|
||||
// hack
|
||||
if (emu is Emulation.Cores.Nintendo.GBA.VBANext)
|
||||
{
|
||||
|
|
|
@ -125,6 +125,7 @@
|
|||
<Compile Include="Interfaces\IInputCallbackSystem.cs" />
|
||||
<Compile Include="Interfaces\IMemoryCallbackSystem.cs" />
|
||||
<Compile Include="Interfaces\IEmulatorServiceProvider.cs" />
|
||||
<Compile Include="Interfaces\Services\IBoardInfo.cs" />
|
||||
<Compile Include="Interfaces\Services\ICreateGameDBEntries.cs" />
|
||||
<Compile Include="Interfaces\Services\ISoundProvider.cs" />
|
||||
<Compile Include="Interfaces\Services\ICodeDataLogger.cs" />
|
||||
|
|
|
@ -327,6 +327,21 @@ namespace BizHawk.Emulation.Common.IEmulatorExtensions
|
|||
return core.ServiceProvider.GetService<ICreateGameDBEntries>();
|
||||
}
|
||||
|
||||
public static bool HasBoardInfo(this IEmulator core)
|
||||
{
|
||||
if (core == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return core.ServiceProvider.HasService<IBoardInfo>();
|
||||
}
|
||||
|
||||
public static IBoardInfo AsBoardInfo(this IEmulator core)
|
||||
{
|
||||
return core.ServiceProvider.GetService<IBoardInfo>();
|
||||
}
|
||||
|
||||
// TODO: a better place for these
|
||||
public static bool IsImplemented(this MethodInfo info)
|
||||
{
|
||||
|
|
|
@ -61,11 +61,6 @@ namespace BizHawk.Emulation.Common
|
|||
/// </summary>
|
||||
bool DeterministicEmulation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifying information about a "mapper" or similar capability. null if no such useful distinction can be drawn
|
||||
/// </summary>
|
||||
string BoardName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resets the Frame and Lag counters, and any other similar counters a core might implement
|
||||
/// </summary>
|
||||
|
@ -77,4 +72,5 @@ namespace BizHawk.Emulation.Common
|
|||
/// <seealso cref="BizHawk.Emulation.Common.CoreComm" />
|
||||
CoreComm CoreComm { get; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
namespace BizHawk.Emulation.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// An <seealso cref="IEmulatorService"/> that returns cart/mapper/board information about the Game hardware itself, if available
|
||||
/// Currently the board name is the only property but this could be expanded to support more detail informations
|
||||
/// </summary>
|
||||
/// <seealso cref="IEmulator"/>
|
||||
/// <seealso cref="IEmulatorService"/>
|
||||
public interface IBoardInfo : IEmulatorService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the identifying information about a "mapper", cart, board or similar capability.
|
||||
/// </summary>
|
||||
string BoardName { get; }
|
||||
}
|
||||
}
|
|
@ -52,8 +52,6 @@ namespace BizHawk.Emulation.Cores.Calculators
|
|||
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Calculators
|
|||
"zeromus",
|
||||
isPorted: false,
|
||||
isReleased: true)]
|
||||
[ServiceNotApplicable(typeof(ISoundProvider), typeof(ISaveRam), typeof(IRegionable), typeof(IDriveLight))]
|
||||
[ServiceNotApplicable(typeof(ISoundProvider), typeof(ISaveRam), typeof(IRegionable), typeof(IDriveLight), typeof(IBoardInfo))]
|
||||
public partial class TI83 : IEmulator, IVideoProvider, IStatable, IDebuggable, IInputPollable, ISettable<TI83.TI83Settings, object>
|
||||
{
|
||||
[CoreConstructor("TI83")]
|
||||
|
|
|
@ -21,8 +21,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
FrameAdv(render, rendersound);
|
||||
}
|
||||
|
||||
public string BoardName => null;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
"fool",
|
||||
isPorted: true,
|
||||
isReleased: true)]
|
||||
[ServiceNotApplicable(typeof(ISaveRam), typeof(IRegionable))]
|
||||
[ServiceNotApplicable(typeof(ISaveRam), typeof(IRegionable), typeof(IBoardInfo))]
|
||||
public partial class AppleII : IEmulator, ISoundProvider, IVideoProvider, IStatable, IDriveLight
|
||||
{
|
||||
static AppleII()
|
||||
|
|
|
@ -132,8 +132,6 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64
|
|||
public CoreComm CoreComm { get; private set; }
|
||||
[SaveState.DoNotSave]
|
||||
public string SystemId { get { return "C64"; } }
|
||||
[SaveState.DoNotSave]
|
||||
public string BoardName { get { return null; } }
|
||||
[SaveState.SaveWithName("DeterministicEmulation")]
|
||||
public bool DeterministicEmulation { get; set; }
|
||||
[SaveState.SaveWithName("Frame")]
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
isPorted: false,
|
||||
isReleased: true)]
|
||||
[ServiceNotApplicable(typeof(ISaveRam), typeof(IDriveLight))]
|
||||
public partial class Atari2600 : IEmulator, IStatable, IDebuggable, IInputPollable,
|
||||
public partial class Atari2600 : IEmulator, IStatable, IDebuggable, IInputPollable, IBoardInfo,
|
||||
IRegionable, ICreateGameDBEntries, ISettable<Atari2600.A2600Settings, Atari2600.A2600SyncSettings>
|
||||
{
|
||||
private readonly GameInfo _game;
|
||||
|
@ -69,8 +69,6 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
|
||||
public string SystemId => "A26";
|
||||
|
||||
public string BoardName => _mapper.GetType().Name;
|
||||
|
||||
public CoreComm CoreComm { get; }
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get { return Atari2600ControllerDefinition; } }
|
||||
|
@ -106,6 +104,9 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|||
};
|
||||
}
|
||||
|
||||
// IBoardInfo
|
||||
public string BoardName => _mapper.GetType().Name;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
_frame = 0;
|
||||
|
|
|
@ -99,8 +99,6 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800
|
|||
|
||||
public GameInfo game;
|
||||
|
||||
public string BoardName => null;
|
||||
|
||||
public void FrameAdvance(bool render, bool rendersound)
|
||||
{
|
||||
_frame++;
|
||||
|
|
|
@ -153,8 +153,6 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx
|
|||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
public string BoardName => null;
|
||||
|
||||
public CoreComm CoreComm { get; }
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
@ -204,6 +204,5 @@ namespace BizHawk.Emulation.Cores.ColecoVision
|
|||
|
||||
private GameInfo _game;
|
||||
public CoreComm CoreComm { get; }
|
||||
public string BoardName => null;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Intellivision
|
||||
{
|
||||
public sealed partial class Intellivision : IEmulator
|
||||
public sealed partial class Intellivision : IEmulator, IBoardInfo
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
|
@ -134,8 +134,6 @@ namespace BizHawk.Emulation.Cores.Intellivision
|
|||
|
||||
public bool DeterministicEmulation => true;
|
||||
|
||||
public string BoardName => _cart.BoardName;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
_frame = 0;
|
||||
|
@ -147,5 +145,8 @@ namespace BizHawk.Emulation.Cores.Intellivision
|
|||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
// IBoardInfo
|
||||
public string BoardName => _cart.BoardName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Intellivision
|
|||
isReleased: true)]
|
||||
[ServiceNotApplicable(typeof(ISaveRam), typeof(IDriveLight), typeof(IRegionable))]
|
||||
public sealed partial class Intellivision : IEmulator, IStatable, IInputPollable, IDisassemblable,
|
||||
IDebuggable, ISettable<Intellivision.IntvSettings, Intellivision.IntvSyncSettings>
|
||||
IBoardInfo, IDebuggable, ISettable<Intellivision.IntvSettings, Intellivision.IntvSyncSettings>
|
||||
{
|
||||
[CoreConstructor("INTV")]
|
||||
public Intellivision(CoreComm comm, GameInfo game, byte[] rom, object Settings, object SyncSettings)
|
||||
|
|
|
@ -110,8 +110,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
|
|||
|
||||
public bool DeterministicEmulation { get; }
|
||||
|
||||
public string BoardName => null;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -129,7 +129,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA
|
|||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
/// <summary>
|
||||
/// set in the ROM internal header
|
||||
/// </summary>
|
||||
|
|
|
@ -5,7 +5,7 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class Gameboy : IEmulator
|
||||
public partial class Gameboy : IEmulator, IBoardInfo
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
portedUrl: "http://gambatte.sourceforge.net/")]
|
||||
[ServiceNotApplicable(typeof(IDriveLight), typeof(IDriveLight))]
|
||||
public partial class Gameboy : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IStatable, IInputPollable, ICodeDataLogger,
|
||||
IDebuggable, ISettable<Gameboy.GambatteSettings, Gameboy.GambatteSyncSettings>
|
||||
IBoardInfo, IDebuggable, ISettable<Gameboy.GambatteSettings, Gameboy.GambatteSyncSettings>
|
||||
{
|
||||
/// <summary>
|
||||
/// the nominal length of one frame
|
||||
|
|
|
@ -3,7 +3,7 @@ using BizHawk.Emulation.Common;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class GambatteLink : IEmulator
|
||||
public partial class GambatteLink : IEmulator, IBoardInfo
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
isReleased: true)]
|
||||
[ServiceNotApplicable(typeof(IDriveLight))]
|
||||
public partial class GambatteLink : IEmulator, IVideoProvider, ISoundProvider, IInputPollable, ISaveRam, IStatable, ILinkable,
|
||||
IDebuggable, ISettable<GambatteLink.GambatteLinkSettings, GambatteLink.GambatteLinkSyncSettings>, ICodeDataLogger
|
||||
IBoardInfo, IDebuggable, ISettable<GambatteLink.GambatteLinkSettings, GambatteLink.GambatteLinkSyncSettings>, ICodeDataLogger
|
||||
{
|
||||
public GambatteLink(CoreComm comm, GameInfo leftinfo, byte[] leftrom, GameInfo rightinfo, byte[] rightrom, object Settings, object SyncSettings, bool deterministic)
|
||||
{
|
||||
|
|
|
@ -265,8 +265,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
|
|||
|
||||
public string SystemId { get { return "N64"; } }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
public CoreComm CoreComm { get; private set; }
|
||||
|
||||
public DisplayType Region { get { return _display_type; } }
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
|||
isReleased: true
|
||||
)]
|
||||
public partial class NES : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable,
|
||||
ISettable<NES.NESSettings, NES.NESSyncSettings>
|
||||
IBoardInfo, ISettable<NES.NESSettings, NES.NESSyncSettings>
|
||||
{
|
||||
static readonly bool USE_DATABASE = true;
|
||||
public RomStatus RomStatus;
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
|||
portedUrl: "https://github.com/kode54/QuickNES"
|
||||
)]
|
||||
[ServiceNotApplicable(typeof(IDriveLight))]
|
||||
public partial class QuickNES : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IInputPollable,
|
||||
public partial class QuickNES : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IInputPollable, IBoardInfo,
|
||||
IStatable, IDebuggable, ISettable<QuickNES.QuickNESSettings, QuickNES.QuickNESSyncSettings>, Cores.Nintendo.NES.INESPPUViewable
|
||||
{
|
||||
static readonly LibQuickNES QN;
|
||||
|
|
|
@ -108,8 +108,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
|||
_settings.ForceDeterminism
|
||||
&& (CurrentProfile == "Compatibility" || CurrentProfile == "Accuracy");
|
||||
|
||||
public string BoardName { get; }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
_timeFrameCounter = 0;
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
|||
{
|
||||
IsSGB = true;
|
||||
SystemId = "SNES";
|
||||
BoardName = "SGB";
|
||||
ser.Register<IBoardInfo>(new SGBBoardInfo());
|
||||
|
||||
_currLoadParams = new LoadParams()
|
||||
{
|
||||
|
@ -198,6 +198,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
|
|||
|
||||
public bool IsSGB { get; }
|
||||
|
||||
private class SGBBoardInfo : IBoardInfo
|
||||
{
|
||||
public string BoardName => "SGB";
|
||||
}
|
||||
|
||||
public string CurrentProfile
|
||||
{
|
||||
get
|
||||
|
|
|
@ -55,7 +55,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X
|
|||
|
||||
public string SystemId { get { return "SNES"; } }
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
public string BoardName { get { return null; } }
|
||||
public CoreComm CoreComm { get; private set; }
|
||||
|
||||
#region IVideoProvider
|
||||
|
|
|
@ -52,8 +52,6 @@ namespace BizHawk.Emulation.Cores.PCEngine
|
|||
|
||||
public bool DeterministicEmulation => true;
|
||||
|
||||
public string BoardName => null;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
// this should just be a public setter instead of a new method.
|
||||
|
|
|
@ -317,8 +317,6 @@ namespace BizHawk.Emulation.Cores.Sega.Genesis
|
|||
public bool DeterministicEmulation { get { return true; } }
|
||||
public string SystemId { get { return "GEN"; } }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
var buf = new byte[141501 + SaveRAM.Length];
|
||||
|
|
|
@ -67,8 +67,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -302,8 +302,6 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn
|
|||
public string SystemId { get { return "SAT"; } }
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -62,11 +62,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
|
|||
get { return true; }
|
||||
}
|
||||
|
||||
public string BoardName
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -57,11 +57,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx64
|
|||
get { return true; }
|
||||
}
|
||||
|
||||
public string BoardName
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
|
|
|
@ -44,8 +44,6 @@ namespace BizHawk.Emulation.Cores.Sony.PSP
|
|||
public string SystemId { get { return "PSP"; } }
|
||||
public CoreComm CoreComm { get; private set; }
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
PPSSPPDll.LogCB logcallback = null;
|
||||
Queue<string> debugmsgs = new Queue<string>();
|
||||
PPSSPPDll.Input input = new PPSSPPDll.Input();
|
||||
|
|
|
@ -141,8 +141,6 @@ namespace BizHawk.Emulation.Cores.Sony.PSX
|
|||
ControllerDefinition = CreateControllerDefinition(_SyncSettings);
|
||||
}
|
||||
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
private int[] frameBuffer = new int[0];
|
||||
private Random rand = new Random();
|
||||
public CoreComm CoreComm { get; private set; }
|
||||
|
|
|
@ -105,7 +105,6 @@ namespace BizHawk.Emulation.Cores.WonderSwan
|
|||
|
||||
public string SystemId { get { return "WSWAN"; } }
|
||||
public bool DeterministicEmulation { get; private set; }
|
||||
public string BoardName { get { return null; } }
|
||||
|
||||
#region Debugging
|
||||
|
||||
|
|
Loading…
Reference in New Issue