Gambatte - some cleanup
This commit is contained in:
parent
2de20e956b
commit
21aa648318
BizHawk.Emulation.Cores
|
@ -416,7 +416,7 @@
|
|||
<DependentUpon>Intellivision.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Intellivision\Intellivision.IDisassemblable.cs">
|
||||
<DependentUpon>Intellivision.cs</DependentUpon>
|
||||
<DependentUpon>Intellivision.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Intellivision\Intellivision.IInputPollable.cs">
|
||||
<DependentUpon>Intellivision.cs</DependentUpon>
|
||||
|
@ -447,6 +447,9 @@
|
|||
<Compile Include="Consoles\Nintendo\Gameboy\Gambatte.IDebuggable.cs">
|
||||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\Gambatte.IEmulator.cs">
|
||||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\Gambatte.IMemoryDomains.cs">
|
||||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
||||
{
|
||||
public partial class Gameboy : IEmulator
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
public ControllerDefinition ControllerDefinition => GbController;
|
||||
|
||||
public IController Controller { get; set; }
|
||||
|
||||
public void FrameAdvance(bool render, bool rendersound)
|
||||
{
|
||||
FrameAdvancePrep();
|
||||
if (_syncSettings.EqualLengthFrames)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// target number of samples to emit: length of 1 frame minus whatever overflow
|
||||
uint samplesEmitted = TICKSINFRAME - frameOverflow;
|
||||
Debug.Assert(samplesEmitted * 2 <= soundbuff.Length);
|
||||
if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0)
|
||||
{
|
||||
LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160);
|
||||
}
|
||||
|
||||
// account for actual number of samples emitted
|
||||
_cycleCount += samplesEmitted;
|
||||
frameOverflow += samplesEmitted;
|
||||
|
||||
if (rendersound && !Muted)
|
||||
{
|
||||
ProcessSound((int)samplesEmitted);
|
||||
}
|
||||
|
||||
if (frameOverflow >= TICKSINFRAME)
|
||||
{
|
||||
frameOverflow -= TICKSINFRAME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// target number of samples to emit: always 59.7fps
|
||||
// runfor() always ends after creating a video frame, so sync-up is guaranteed
|
||||
// when the display has been off, some frames can be markedly shorter than expected
|
||||
uint samplesEmitted = TICKSINFRAME;
|
||||
if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0)
|
||||
{
|
||||
LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160);
|
||||
}
|
||||
|
||||
_cycleCount += samplesEmitted;
|
||||
frameOverflow = 0;
|
||||
if (rendersound && !Muted)
|
||||
{
|
||||
ProcessSound((int)samplesEmitted);
|
||||
}
|
||||
}
|
||||
|
||||
if (rendersound && !Muted)
|
||||
{
|
||||
ProcessSoundEnd();
|
||||
}
|
||||
|
||||
FrameAdvancePost();
|
||||
}
|
||||
|
||||
public int Frame { get; private set; }
|
||||
|
||||
public string SystemId => "GB";
|
||||
|
||||
public string BoardName { get; }
|
||||
|
||||
public bool DeterministicEmulation { get; }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
|
||||
// reset frame counters is meant to "re-zero" emulation time wherever it was
|
||||
// so these should be reset as well
|
||||
_cycleCount = 0;
|
||||
frameOverflow = 0;
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (GambatteState != IntPtr.Zero)
|
||||
{
|
||||
LibGambatte.gambatte_destroy(GambatteState);
|
||||
GambatteState = IntPtr.Zero;
|
||||
}
|
||||
|
||||
DisposeSound();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,8 +14,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
isPorted: true,
|
||||
isReleased: true,
|
||||
portedVersion: "SVN 344",
|
||||
portedUrl: "http://gambatte.sourceforge.net/"
|
||||
)]
|
||||
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>
|
||||
|
@ -33,59 +32,60 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
/// <summary>
|
||||
/// keep a copy of the input callback delegate so it doesn't get GCed
|
||||
/// </summary>
|
||||
LibGambatte.InputGetter InputCallback;
|
||||
private LibGambatte.InputGetter InputCallback;
|
||||
|
||||
/// <summary>
|
||||
/// whatever keys are currently depressed
|
||||
/// </summary>
|
||||
LibGambatte.Buttons CurrentButtons = 0;
|
||||
private LibGambatte.Buttons CurrentButtons = 0;
|
||||
|
||||
#region RTC
|
||||
|
||||
/// <summary>
|
||||
/// RTC time when emulation begins.
|
||||
/// </summary>
|
||||
uint zerotime = 0;
|
||||
private readonly uint zerotime = 0;
|
||||
|
||||
/// <summary>
|
||||
/// if true, RTC will run off of real elapsed time
|
||||
/// </summary>
|
||||
bool real_rtc_time = false;
|
||||
private bool real_rtc_time = false;
|
||||
|
||||
LibGambatte.RTCCallback TimeCallback;
|
||||
private LibGambatte.RTCCallback TimeCallback;
|
||||
|
||||
static long GetUnixNow()
|
||||
private static long GetUnixNow()
|
||||
{
|
||||
// because internally the RTC works off of relative time, we don't need to base
|
||||
// this off of any particular canonical epoch.
|
||||
return DateTime.UtcNow.Ticks / 10000000L - 60000000000L;
|
||||
}
|
||||
|
||||
uint GetCurrentTime()
|
||||
private uint GetCurrentTime()
|
||||
{
|
||||
if (real_rtc_time)
|
||||
{
|
||||
return (uint)GetUnixNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong fn = (ulong)Frame;
|
||||
// as we're exactly tracking cpu cycles, this can be pretty accurate
|
||||
fn *= 4389;
|
||||
fn /= 262144;
|
||||
fn += zerotime;
|
||||
return (uint)fn;
|
||||
}
|
||||
|
||||
ulong fn = (ulong)Frame;
|
||||
|
||||
// as we're exactly tracking cpu cycles, this can be pretty accurate
|
||||
fn *= 4389;
|
||||
fn /= 262144;
|
||||
fn += zerotime;
|
||||
return (uint)fn;
|
||||
}
|
||||
|
||||
uint GetInitialTime()
|
||||
private uint GetInitialTime()
|
||||
{
|
||||
if (real_rtc_time)
|
||||
{
|
||||
return (uint)GetUnixNow();
|
||||
else
|
||||
// setting the initial boot time to 0 will cause our zerotime
|
||||
// to function as an initial offset, which is what we want
|
||||
return 0;
|
||||
}
|
||||
|
||||
// setting the initial boot time to 0 will cause our zerotime
|
||||
// to function as an initial offset, which is what we want
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -119,26 +119,40 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
GambatteState = LibGambatte.gambatte_create();
|
||||
|
||||
if (GambatteState == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("gambatte_create() returned null???");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this._syncSettings = (GambatteSyncSettings)SyncSettings ?? new GambatteSyncSettings();
|
||||
_syncSettings = (GambatteSyncSettings)SyncSettings ?? new GambatteSyncSettings();
|
||||
|
||||
// copy over non-loadflag syncsettings now; they won't take effect if changed later
|
||||
zerotime = (uint)this._syncSettings.RTCInitialTime;
|
||||
real_rtc_time = DeterministicEmulation ? false : this._syncSettings.RealTimeRTC;
|
||||
zerotime = (uint)_syncSettings.RTCInitialTime;
|
||||
|
||||
real_rtc_time = !DeterministicEmulation && _syncSettings.RealTimeRTC;
|
||||
|
||||
LibGambatte.LoadFlags flags = 0;
|
||||
|
||||
if (this._syncSettings.ForceDMG)
|
||||
if (_syncSettings.ForceDMG)
|
||||
{
|
||||
flags |= LibGambatte.LoadFlags.FORCE_DMG;
|
||||
if (this._syncSettings.GBACGB)
|
||||
}
|
||||
|
||||
if (_syncSettings.GBACGB)
|
||||
{
|
||||
flags |= LibGambatte.LoadFlags.GBA_CGB;
|
||||
if (this._syncSettings.MulticartCompat)
|
||||
}
|
||||
|
||||
if (_syncSettings.MulticartCompat)
|
||||
{
|
||||
flags |= LibGambatte.LoadFlags.MULTICART_COMPAT;
|
||||
}
|
||||
|
||||
if (LibGambatte.gambatte_load(GambatteState, file, (uint)file.Length, GetCurrentTime(), flags) != 0)
|
||||
{
|
||||
throw new InvalidOperationException("gambatte_load() returned non-zero (is this not a gb or gbc rom?)");
|
||||
}
|
||||
|
||||
// set real default colors (before anyone mucks with them at all)
|
||||
PutSettings((GambatteSettings)Settings ?? new GambatteSettings());
|
||||
|
@ -155,17 +169,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
|
||||
InitMemoryDomains();
|
||||
|
||||
CoreComm.RomStatusDetails = string.Format("{0}\r\nSHA1:{1}\r\nMD5:{2}\r\n",
|
||||
game.Name,
|
||||
file.HashSHA1(),
|
||||
file.HashMD5());
|
||||
CoreComm.RomStatusDetails = $"{game.Name}\r\nSHA1:{file.HashSHA1()}\r\nMD5:{file.HashMD5()}\r\n";
|
||||
|
||||
{
|
||||
byte[] buff = new byte[32];
|
||||
LibGambatte.gambatte_romtitle(GambatteState, buff);
|
||||
string romname = System.Text.Encoding.ASCII.GetString(buff);
|
||||
Console.WriteLine("Core reported rom name: {0}", romname);
|
||||
}
|
||||
byte[] buff = new byte[32];
|
||||
LibGambatte.gambatte_romtitle(GambatteState, buff);
|
||||
string romname = System.Text.Encoding.ASCII.GetString(buff);
|
||||
Console.WriteLine("Core reported rom name: {0}", romname);
|
||||
|
||||
TimeCallback = new LibGambatte.RTCCallback(GetCurrentTime);
|
||||
LibGambatte.gambatte_setrtccallback(GambatteState, TimeCallback);
|
||||
|
@ -181,17 +190,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public IEmulatorServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
#region ALL SAVESTATEABLE STATE GOES HERE
|
||||
|
||||
/// <summary>
|
||||
/// internal gambatte state
|
||||
/// </summary>
|
||||
internal IntPtr GambatteState = IntPtr.Zero;
|
||||
internal IntPtr GambatteState { get; private set; } = IntPtr.Zero;
|
||||
|
||||
public int Frame { get; set; }
|
||||
public int LagCount { get; set; }
|
||||
public bool IsLagFrame { get; set; }
|
||||
|
||||
|
@ -206,7 +211,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
/// number of extra cycles we overran in the last frame
|
||||
/// </summary>
|
||||
private uint frameOverflow = 0;
|
||||
public ulong CycleCount { get { return _cycleCount; } }
|
||||
public ulong CycleCount => _cycleCount;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -221,14 +226,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
}
|
||||
};
|
||||
|
||||
public ControllerDefinition ControllerDefinition
|
||||
{
|
||||
get { return GbController; }
|
||||
}
|
||||
|
||||
public IController Controller { get; set; }
|
||||
|
||||
LibGambatte.Buttons ControllerCallback()
|
||||
private LibGambatte.Buttons ControllerCallback()
|
||||
{
|
||||
InputCallbacks.Call();
|
||||
IsLagFrame = false;
|
||||
|
@ -240,21 +238,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
/// <summary>
|
||||
/// true if the emulator is currently emulating CGB
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsCGBMode()
|
||||
{
|
||||
return (LibGambatte.gambatte_iscgb(GambatteState));
|
||||
return LibGambatte.gambatte_iscgb(GambatteState);
|
||||
}
|
||||
|
||||
private InputCallbackSystem _inputCallbacks = new InputCallbackSystem();
|
||||
|
||||
// low priority TODO: due to certain aspects of the core implementation,
|
||||
// we don't smartly use the ActiveChanged event here.
|
||||
public IInputCallbackSystem InputCallbacks { get { return _inputCallbacks; } }
|
||||
public IInputCallbackSystem InputCallbacks => _inputCallbacks;
|
||||
|
||||
/// <summary>
|
||||
/// for use in dual core
|
||||
/// </summary>
|
||||
/// <param name="ics"></param>
|
||||
public void ConnectInputCallbackSystem(InputCallbackSystem ics)
|
||||
{
|
||||
_inputCallbacks = ics;
|
||||
|
@ -288,12 +285,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
IsLagFrame = true;
|
||||
|
||||
if (Controller.IsPressed("Power"))
|
||||
{
|
||||
LibGambatte.gambatte_reset(GambatteState, GetCurrentTime());
|
||||
}
|
||||
|
||||
if (Tracer.Enabled)
|
||||
{
|
||||
tracecb = MakeTrace;
|
||||
}
|
||||
else
|
||||
{
|
||||
tracecb = null;
|
||||
}
|
||||
|
||||
LibGambatte.gambatte_settracecallback(GambatteState, tracecb);
|
||||
|
||||
LibGambatte.gambatte_setlayers(GambatteState, (_settings.DisplayBG ? 1 : 0) | (_settings.DisplayOBJ ? 2 : 0) | (_settings.DisplayWindow ? 4 : 0 ) );
|
||||
|
@ -302,65 +306,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
internal void FrameAdvancePost()
|
||||
{
|
||||
if (IsLagFrame)
|
||||
{
|
||||
LagCount++;
|
||||
|
||||
if (endofframecallback != null)
|
||||
endofframecallback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
||||
}
|
||||
|
||||
public void FrameAdvance(bool render, bool rendersound)
|
||||
{
|
||||
FrameAdvancePrep();
|
||||
if (_syncSettings.EqualLengthFrames)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// target number of samples to emit: length of 1 frame minus whatever overflow
|
||||
uint samplesEmitted = TICKSINFRAME - frameOverflow;
|
||||
System.Diagnostics.Debug.Assert(samplesEmitted * 2 <= soundbuff.Length);
|
||||
if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0)
|
||||
LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160);
|
||||
|
||||
// account for actual number of samples emitted
|
||||
_cycleCount += (ulong)samplesEmitted;
|
||||
frameOverflow += samplesEmitted;
|
||||
|
||||
if (rendersound && !Muted)
|
||||
{
|
||||
ProcessSound((int)samplesEmitted);
|
||||
}
|
||||
|
||||
if (frameOverflow >= TICKSINFRAME)
|
||||
{
|
||||
frameOverflow -= TICKSINFRAME;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// target number of samples to emit: always 59.7fps
|
||||
// runfor() always ends after creating a video frame, so sync-up is guaranteed
|
||||
// when the display has been off, some frames can be markedly shorter than expected
|
||||
uint samplesEmitted = TICKSINFRAME;
|
||||
if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0)
|
||||
LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160);
|
||||
|
||||
_cycleCount += (ulong)samplesEmitted;
|
||||
frameOverflow = 0;
|
||||
if (rendersound && !Muted)
|
||||
{
|
||||
ProcessSound((int)samplesEmitted);
|
||||
}
|
||||
}
|
||||
|
||||
if (rendersound && !Muted)
|
||||
ProcessSoundEnd();
|
||||
|
||||
FrameAdvancePost();
|
||||
endofframecallback?.Invoke(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
||||
}
|
||||
|
||||
static string MapperName(byte[] romdata)
|
||||
|
||||
private static string MapperName(byte[] romdata)
|
||||
{
|
||||
switch (romdata[0x147])
|
||||
{
|
||||
|
@ -391,11 +345,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
/// <summary>
|
||||
/// throw exception with intelligible message on some kinds of bad rom
|
||||
/// </summary>
|
||||
/// <param name="romdata"></param>
|
||||
static void ThrowExceptionForBadRom(byte[] romdata)
|
||||
private static void ThrowExceptionForBadRom(byte[] romdata)
|
||||
{
|
||||
if (romdata.Length < 0x148)
|
||||
{
|
||||
throw new ArgumentException("ROM is far too small to be a valid GB\\GBC rom!");
|
||||
}
|
||||
|
||||
switch (romdata[0x147])
|
||||
{
|
||||
|
@ -436,30 +391,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
case 0xfd: throw new UnsupportedGameException("\"Bandai TAMA5\" Mapper not supported!");
|
||||
case 0xfe: throw new UnsupportedGameException("\"HuC3\" Mapper not supported!");
|
||||
case 0xff: break;
|
||||
default: throw new UnsupportedGameException(string.Format("Unknown mapper: {0:x2}", romdata[0x147]));
|
||||
default: throw new UnsupportedGameException($"Unknown mapper: {romdata[0x147]:x2}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public string SystemId { get { return "GB"; } }
|
||||
|
||||
public string BoardName { get; private set; }
|
||||
|
||||
public bool DeterministicEmulation { get; private set; }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
// reset frame counters is meant to "re-zero" emulation time wherever it was
|
||||
// so these should be reset as well
|
||||
_cycleCount = 0;
|
||||
frameOverflow = 0;
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; set; }
|
||||
|
||||
#region ppudebug
|
||||
|
||||
public bool GetGPUMemoryAreas(out IntPtr vram, out IntPtr bgpal, out IntPtr sppal, out IntPtr oam)
|
||||
|
@ -488,7 +423,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="lcdc">current value of register $ff40 (LCDC)</param>
|
||||
public delegate void ScanlineCallback(int lcdc);
|
||||
|
@ -496,26 +430,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
/// <summary>
|
||||
/// set up callback
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
/// <param name="line">scanline. -1 = end of frame, -2 = RIGHT NOW</param>
|
||||
public void SetScanlineCallback(ScanlineCallback callback, int line)
|
||||
{
|
||||
if (GambatteState == IntPtr.Zero)
|
||||
// not sure how this is being reached. tried the debugger...
|
||||
return;
|
||||
{
|
||||
return; // not sure how this is being reached. tried the debugger...
|
||||
}
|
||||
|
||||
endofframecallback = null;
|
||||
if (callback == null || line == -1 || line == -2)
|
||||
{
|
||||
scanlinecb = null;
|
||||
LibGambatte.gambatte_setscanlinecallback(GambatteState, null, 0);
|
||||
if (line == -1)
|
||||
{
|
||||
endofframecallback = callback;
|
||||
}
|
||||
else if (line == -2)
|
||||
{
|
||||
callback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
||||
}
|
||||
}
|
||||
else if (line >= 0 && line <= 153)
|
||||
{
|
||||
scanlinecb = delegate()
|
||||
scanlinecb = delegate
|
||||
{
|
||||
callback(LibGambatte.gambatte_cpuread(GambatteState, 0xff40));
|
||||
};
|
||||
|
@ -532,16 +471,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (GambatteState != IntPtr.Zero)
|
||||
{
|
||||
LibGambatte.gambatte_destroy(GambatteState);
|
||||
GambatteState = IntPtr.Zero;
|
||||
}
|
||||
DisposeSound();
|
||||
}
|
||||
|
||||
#region palette
|
||||
|
||||
/// <summary>
|
||||
|
@ -550,7 +479,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
|
|||
public void ChangeDMGColors(int[] colors)
|
||||
{
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
LibGambatte.gambatte_setdmgpalettecolor(GambatteState, (LibGambatte.PalType)(i / 4), (uint)i % 4, (uint)colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCGBColors(GBColors.ColorType type)
|
||||
|
|
Loading…
Reference in New Issue