2011-01-11 02:55:51 +00:00
|
|
|
|
using System;
|
2013-10-27 22:07:40 +00:00
|
|
|
|
|
2014-07-03 15:05:02 +00:00
|
|
|
|
using BizHawk.Common.StringExtensions;
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2013-11-14 19:33:13 +00:00
|
|
|
|
using BizHawk.Emulation.Common.Components;
|
2016-03-04 13:37:09 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Components;
|
2014-01-22 01:14:36 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Components.Z80;
|
2011-01-11 02:55:51 +00:00
|
|
|
|
|
|
|
|
|
/*****************************************************
|
2011-01-21 03:59:50 +00:00
|
|
|
|
TODO:
|
2011-01-11 02:55:51 +00:00
|
|
|
|
+ HCounter
|
|
|
|
|
+ Try to clean up the organization of the source code.
|
2011-02-16 04:45:59 +00:00
|
|
|
|
+ Lightgun/Paddle/etc if I get really bored
|
2014-03-13 04:15:05 +00:00
|
|
|
|
+ Mode 1 not implemented in VDP TMS modes. (I dont have a test case in SG1000 or Coleco)
|
2012-11-26 01:44:17 +00:00
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
**********************************************************/
|
|
|
|
|
|
2013-11-13 23:36:21 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
2011-01-11 02:55:51 +00:00
|
|
|
|
{
|
2014-04-25 01:19:57 +00:00
|
|
|
|
[CoreAttributes(
|
|
|
|
|
"SMSHawk",
|
|
|
|
|
"Vecna",
|
|
|
|
|
isPorted: false,
|
2017-05-06 00:05:36 +00:00
|
|
|
|
isReleased: true)]
|
2014-12-12 01:58:12 +00:00
|
|
|
|
[ServiceNotApplicable(typeof(IDriveLight))]
|
2015-08-06 00:12:09 +00:00
|
|
|
|
public sealed partial class SMS : IEmulator, ISaveRam, IStatable, IInputPollable, IRegionable,
|
2015-10-30 05:00:57 +00:00
|
|
|
|
IDebuggable, ISettable<SMS.SMSSettings, SMS.SMSSyncSettings>, ICodeDataLogger
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
2014-08-23 19:06:37 +00:00
|
|
|
|
[CoreConstructor("SMS", "SG", "GG")]
|
2014-03-06 04:43:36 +00:00
|
|
|
|
public SMS(CoreComm comm, GameInfo game, byte[] rom, object settings, object syncSettings)
|
2011-06-11 22:15:08 +00:00
|
|
|
|
{
|
2014-12-04 03:38:30 +00:00
|
|
|
|
ServiceProvider = new BasicServiceProvider(this);
|
2014-03-06 04:43:36 +00:00
|
|
|
|
Settings = (SMSSettings)settings ?? new SMSSettings();
|
|
|
|
|
SyncSettings = (SMSSyncSettings)syncSettings ?? new SMSSyncSettings();
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm = comm;
|
2014-12-05 01:56:45 +00:00
|
|
|
|
MemoryCallbacks = new MemoryCallbackSystem();
|
2014-03-22 04:46:01 +00:00
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
IsGameGear = game.System == "GG";
|
2014-05-31 23:17:39 +00:00
|
|
|
|
IsSG1000 = game.System == "SG";
|
2014-10-19 01:22:47 +00:00
|
|
|
|
RomData = rom;
|
2016-02-28 13:07:02 +00:00
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
if (RomData.Length % BankSize != 0)
|
2017-04-23 18:28:15 +00:00
|
|
|
|
{
|
2014-10-19 01:22:47 +00:00
|
|
|
|
Array.Resize(ref RomData, ((RomData.Length / BankSize) + 1) * BankSize);
|
2017-04-23 18:28:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
RomBanks = (byte)(RomData.Length / BankSize);
|
|
|
|
|
|
2015-08-06 00:12:09 +00:00
|
|
|
|
Region = DetermineDisplayType(SyncSettings.DisplayType, game.Region);
|
|
|
|
|
if (game["PAL"] && Region != DisplayType.PAL)
|
2014-03-13 04:49:39 +00:00
|
|
|
|
{
|
2015-08-06 00:12:09 +00:00
|
|
|
|
Region = DisplayType.PAL;
|
2014-03-18 03:03:53 +00:00
|
|
|
|
CoreComm.Notify("Display was forced to PAL mode for game compatibility.");
|
2014-03-13 04:49:39 +00:00
|
|
|
|
}
|
2017-04-23 18:28:15 +00:00
|
|
|
|
|
|
|
|
|
if (IsGameGear)
|
|
|
|
|
{
|
2015-08-06 00:12:09 +00:00
|
|
|
|
Region = DisplayType.NTSC; // all game gears run at 60hz/NTSC mode
|
2017-04-23 18:28:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 00:12:09 +00:00
|
|
|
|
RegionStr = SyncSettings.ConsoleRegion;
|
2017-04-23 18:28:15 +00:00
|
|
|
|
if (RegionStr == "Auto")
|
|
|
|
|
{
|
|
|
|
|
RegionStr = DetermineRegion(game.Region);
|
|
|
|
|
}
|
2014-03-22 05:57:27 +00:00
|
|
|
|
|
2015-08-06 00:12:09 +00:00
|
|
|
|
if (game["Japan"] && RegionStr != "Japan")
|
2014-03-13 04:49:39 +00:00
|
|
|
|
{
|
2015-08-06 00:12:09 +00:00
|
|
|
|
RegionStr = "Japan";
|
2014-03-18 03:03:53 +00:00
|
|
|
|
CoreComm.Notify("Region was forced to Japan for game compatibility.");
|
2014-03-13 04:49:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
if ((game.NotInDatabase || game["FM"]) && SyncSettings.EnableFM && !IsGameGear)
|
2017-04-23 18:28:15 +00:00
|
|
|
|
{
|
2014-10-19 01:22:47 +00:00
|
|
|
|
HasYM2413 = true;
|
2017-04-23 18:28:15 +00:00
|
|
|
|
}
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2017-04-23 18:28:15 +00:00
|
|
|
|
Cpu = new Z80A
|
|
|
|
|
{
|
|
|
|
|
RegisterSP = 0xDFF0,
|
|
|
|
|
ReadHardware = ReadPort,
|
|
|
|
|
WriteHardware = WritePort,
|
|
|
|
|
MemoryCallbacks = MemoryCallbacks
|
|
|
|
|
};
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2015-08-06 00:12:09 +00:00
|
|
|
|
Vdp = new VDP(this, Cpu, IsGameGear ? VdpMode.GameGear : VdpMode.SMS, Region);
|
2015-01-14 22:37:37 +00:00
|
|
|
|
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(Vdp);
|
2014-10-19 01:22:47 +00:00
|
|
|
|
PSG = new SN76489();
|
|
|
|
|
YM2413 = new YM2413();
|
|
|
|
|
SoundMixer = new SoundMixer(YM2413, PSG);
|
|
|
|
|
if (HasYM2413 && game["WhenFMDisablePSG"])
|
2017-04-23 18:28:15 +00:00
|
|
|
|
{
|
2014-10-19 01:22:47 +00:00
|
|
|
|
SoundMixer.DisableSource(PSG);
|
2017-04-23 18:28:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-09 16:24:43 +00:00
|
|
|
|
ActiveSoundProvider = HasYM2413 ? (IAsyncSoundProvider)SoundMixer : PSG;
|
2016-12-11 17:14:42 +00:00
|
|
|
|
_fakeSyncSound = new FakeSyncSound(ActiveSoundProvider, 735);
|
|
|
|
|
(ServiceProvider as BasicServiceProvider).Register<ISoundProvider>(_fakeSyncSound);
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
SystemRam = new byte[0x2000];
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (game["CMMapper"])
|
|
|
|
|
InitCodeMastersMapper();
|
2014-03-18 02:23:10 +00:00
|
|
|
|
else if (game["CMMapperWithRam"])
|
|
|
|
|
InitCodeMastersMapperRam();
|
2014-03-06 04:43:36 +00:00
|
|
|
|
else if (game["ExtRam"])
|
|
|
|
|
InitExt2kMapper(int.Parse(game.OptionValue("ExtRam")));
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
else if (game["KoreaMapper"])
|
|
|
|
|
InitKoreaMapper();
|
|
|
|
|
else if (game["MSXMapper"])
|
|
|
|
|
InitMSXMapper();
|
|
|
|
|
else if (game["NemesisMapper"])
|
|
|
|
|
InitNemesisMapper();
|
2014-03-17 03:00:07 +00:00
|
|
|
|
else if (game["TerebiOekaki"])
|
|
|
|
|
InitTerebiOekaki();
|
2014-03-06 04:43:36 +00:00
|
|
|
|
else
|
|
|
|
|
InitSegaMapper();
|
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
if (Settings.ForceStereoSeparation && !IsGameGear)
|
|
|
|
|
{
|
|
|
|
|
if (game["StereoByte"])
|
|
|
|
|
{
|
|
|
|
|
ForceStereoByte = byte.Parse(game.OptionValue("StereoByte"));
|
|
|
|
|
}
|
2017-04-23 18:28:15 +00:00
|
|
|
|
|
2014-02-27 00:28:05 +00:00
|
|
|
|
PSG.StereoPanning = ForceStereoByte;
|
2014-10-19 01:22:47 +00:00
|
|
|
|
}
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
if (SyncSettings.AllowOverlock && game["OverclockSafe"])
|
|
|
|
|
Vdp.IPeriod = 512;
|
2011-08-04 03:20:54 +00:00
|
|
|
|
|
2014-10-19 01:22:47 +00:00
|
|
|
|
if (Settings.SpriteLimit)
|
|
|
|
|
Vdp.SpriteLimit = true;
|
2011-09-19 00:39:28 +00:00
|
|
|
|
|
2014-03-05 02:20:22 +00:00
|
|
|
|
if (game["3D"])
|
|
|
|
|
IsGame3D = true;
|
|
|
|
|
|
2014-03-05 05:09:20 +00:00
|
|
|
|
if (game["BIOS"])
|
|
|
|
|
{
|
|
|
|
|
Port3E = 0xF7; // Disable cartridge, enable BIOS rom
|
|
|
|
|
InitBiosMapper();
|
|
|
|
|
}
|
2014-03-05 12:49:27 +00:00
|
|
|
|
else if (game.System == "SMS")
|
2014-03-05 05:09:20 +00:00
|
|
|
|
{
|
2015-08-06 00:12:09 +00:00
|
|
|
|
BiosRom = comm.CoreFileProvider.GetFirmware("SMS", RegionStr, false);
|
2014-03-05 05:09:20 +00:00
|
|
|
|
|
2017-06-22 20:40:18 +00:00
|
|
|
|
if (BiosRom == null)
|
|
|
|
|
{
|
|
|
|
|
throw new MissingFirmwareException("No BIOS found");
|
|
|
|
|
}
|
|
|
|
|
else if (!game["RequireBios"] && !SyncSettings.UseBIOS)
|
|
|
|
|
{
|
|
|
|
|
// we are skipping the BIOS
|
|
|
|
|
// but only if it won't break the game
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Port3E = 0xF7;
|
|
|
|
|
}
|
2014-03-13 04:49:39 +00:00
|
|
|
|
}
|
2014-03-22 04:46:01 +00:00
|
|
|
|
|
|
|
|
|
if (game["SRAM"])
|
|
|
|
|
SaveRAM = new byte[int.Parse(game.OptionValue("SRAM"))];
|
|
|
|
|
else if (game.NotInDatabase)
|
|
|
|
|
SaveRAM = new byte[0x8000];
|
|
|
|
|
|
2014-03-13 04:49:39 +00:00
|
|
|
|
SetupMemoryDomains();
|
2015-10-30 05:00:57 +00:00
|
|
|
|
|
|
|
|
|
//this manages the linkage between the cpu and mapper callbacks so it needs running before bootup is complete
|
|
|
|
|
((ICodeDataLogger)this).SetCDL(null);
|
|
|
|
|
|
2016-02-28 17:01:12 +00:00
|
|
|
|
InputCallbacks = new InputCallbackSystem();
|
|
|
|
|
|
2016-02-28 13:07:02 +00:00
|
|
|
|
Tracer = new TraceBuffer { Header = Cpu.TraceHeader };
|
|
|
|
|
|
|
|
|
|
var serviceProvider = ServiceProvider as BasicServiceProvider;
|
|
|
|
|
serviceProvider.Register<ITraceable>(Tracer);
|
|
|
|
|
serviceProvider.Register<IDisassemblable>(new Disassembler());
|
2016-06-27 03:41:44 +00:00
|
|
|
|
Vdp.ProcessOverscan();
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-06 00:05:36 +00:00
|
|
|
|
// Constants
|
|
|
|
|
private const int BankSize = 16384;
|
|
|
|
|
|
|
|
|
|
// ROM
|
|
|
|
|
private byte[] RomData;
|
|
|
|
|
private byte RomBank0, RomBank1, RomBank2, RomBank3;
|
|
|
|
|
private byte RomBanks;
|
|
|
|
|
private byte[] BiosRom;
|
|
|
|
|
|
|
|
|
|
// Machine resources
|
|
|
|
|
private Z80A Cpu;
|
|
|
|
|
private byte[] SystemRam;
|
|
|
|
|
public VDP Vdp;
|
|
|
|
|
private SN76489 PSG;
|
|
|
|
|
private YM2413 YM2413;
|
|
|
|
|
public bool IsGameGear { get; set; }
|
|
|
|
|
public bool IsSG1000 { get; set; }
|
|
|
|
|
|
|
|
|
|
private bool HasYM2413 = false;
|
|
|
|
|
private IController _controller;
|
|
|
|
|
|
2017-05-08 16:42:22 +00:00
|
|
|
|
private int _frame = 0;
|
2017-05-06 00:05:36 +00:00
|
|
|
|
|
|
|
|
|
private byte Port01 = 0xFF;
|
|
|
|
|
private byte Port02 = 0xFF;
|
|
|
|
|
private byte Port3E = 0xAF;
|
|
|
|
|
private byte Port3F = 0xFF;
|
|
|
|
|
|
|
|
|
|
private byte ForceStereoByte = 0xAD;
|
|
|
|
|
private bool IsGame3D = false;
|
|
|
|
|
|
|
|
|
|
public DisplayType Region { get; set; }
|
|
|
|
|
|
|
|
|
|
|
2014-12-23 01:58:12 +00:00
|
|
|
|
private ITraceable Tracer { get; set; }
|
2014-12-05 00:05:40 +00:00
|
|
|
|
|
2014-03-22 05:57:27 +00:00
|
|
|
|
string DetermineRegion(string gameRegion)
|
|
|
|
|
{
|
2014-03-25 02:12:42 +00:00
|
|
|
|
if (gameRegion == null)
|
|
|
|
|
return "Export";
|
2014-03-22 05:57:27 +00:00
|
|
|
|
if (gameRegion.IndexOf("USA") >= 0)
|
|
|
|
|
return "Export";
|
|
|
|
|
if (gameRegion.IndexOf("Europe") >= 0)
|
|
|
|
|
return "Export";
|
|
|
|
|
if (gameRegion.IndexOf("World") >= 0)
|
|
|
|
|
return "Export";
|
|
|
|
|
if (gameRegion.IndexOf("Brazil") >= 0)
|
|
|
|
|
return "Export";
|
|
|
|
|
if (gameRegion.IndexOf("Australia") >= 0)
|
|
|
|
|
return "Export";
|
|
|
|
|
return "Japan";
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private DisplayType DetermineDisplayType(string display, string region)
|
2014-03-22 05:57:27 +00:00
|
|
|
|
{
|
|
|
|
|
if (display == "NTSC") return DisplayType.NTSC;
|
|
|
|
|
if (display == "PAL") return DisplayType.PAL;
|
2014-03-25 02:12:42 +00:00
|
|
|
|
if (region != null && region == "Europe") return DisplayType.PAL;
|
2014-03-22 05:57:27 +00:00
|
|
|
|
return DisplayType.NTSC;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-30 05:00:57 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The ReadMemory callback for the mapper
|
|
|
|
|
/// </summary>
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private Func<ushort, byte> ReadMemory;
|
2015-10-30 05:00:57 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The WriteMemory callback for the wrapper
|
|
|
|
|
/// </summary>
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private Action<ushort, byte> WriteMemory;
|
2015-10-30 05:00:57 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// A dummy FetchMemory that simply reads the memory
|
|
|
|
|
/// </summary>
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private byte FetchMemory_StubThunk(ushort address, bool first)
|
2015-10-30 05:00:57 +00:00
|
|
|
|
{
|
|
|
|
|
return ReadMemory(address);
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private byte ReadPort(ushort port)
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
2014-03-17 03:56:42 +00:00
|
|
|
|
port &= 0xFF;
|
|
|
|
|
if (port < 0x40) // General IO ports
|
|
|
|
|
{
|
|
|
|
|
switch (port)
|
|
|
|
|
{
|
|
|
|
|
case 0x00: return ReadPort0();
|
|
|
|
|
case 0x01: return Port01;
|
|
|
|
|
case 0x02: return Port02;
|
|
|
|
|
case 0x03: return 0x00;
|
|
|
|
|
case 0x04: return 0xFF;
|
|
|
|
|
case 0x05: return 0x00;
|
|
|
|
|
case 0x06: return 0xFF;
|
|
|
|
|
case 0x3E: return Port3E;
|
|
|
|
|
default: return 0xFF;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (port < 0x80) // VDP Vcounter/HCounter
|
|
|
|
|
{
|
|
|
|
|
if ((port & 1) == 0)
|
|
|
|
|
return Vdp.ReadVLineCounter();
|
|
|
|
|
else
|
|
|
|
|
return 0x50; // TODO Vdp.ReadHLineCounter();
|
|
|
|
|
}
|
|
|
|
|
if (port < 0xC0) // VDP data/control ports
|
|
|
|
|
{
|
|
|
|
|
if ((port & 1) == 0)
|
|
|
|
|
return Vdp.ReadData();
|
|
|
|
|
else
|
|
|
|
|
return Vdp.ReadVdpStatus();
|
|
|
|
|
}
|
|
|
|
|
switch (port)
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
|
|
|
|
case 0xC0:
|
|
|
|
|
case 0xDC: return ReadControls1();
|
|
|
|
|
case 0xC1:
|
|
|
|
|
case 0xDD: return ReadControls2();
|
|
|
|
|
case 0xF2: return HasYM2413 ? YM2413.DetectionValue : (byte)0xFF;
|
2014-03-17 03:56:42 +00:00
|
|
|
|
default: return 0xFF;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private void WritePort(ushort port, byte value)
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
2014-03-17 03:56:42 +00:00
|
|
|
|
port &= 0xFF;
|
|
|
|
|
if (port < 0x40) // general IO ports
|
|
|
|
|
{
|
|
|
|
|
switch (port & 0xFF)
|
|
|
|
|
{
|
|
|
|
|
case 0x01: Port01 = value; break;
|
|
|
|
|
case 0x02: Port02 = value; break;
|
|
|
|
|
case 0x06: PSG.StereoPanning = value; break;
|
|
|
|
|
case 0x3E: Port3E = value; break;
|
|
|
|
|
case 0x3F: Port3F = value; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (port < 0x80) // PSG
|
|
|
|
|
PSG.WritePsgData(value, Cpu.TotalExecutedCycles);
|
|
|
|
|
else if (port < 0xC0) // VDP
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
2014-03-17 03:56:42 +00:00
|
|
|
|
if ((port & 1) == 0)
|
|
|
|
|
Vdp.WriteVdpData(value);
|
|
|
|
|
else
|
|
|
|
|
Vdp.WriteVdpControl(value);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
2014-03-17 03:56:42 +00:00
|
|
|
|
else if (port == 0xF0 && HasYM2413) YM2413.RegisterLatch = value;
|
|
|
|
|
else if (port == 0xF1 && HasYM2413) YM2413.Write(value);
|
|
|
|
|
else if (port == 0xF2 && HasYM2413) YM2413.DetectionValue = value;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-28 17:01:12 +00:00
|
|
|
|
private string _region;
|
|
|
|
|
private string RegionStr
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
2016-02-28 17:01:12 +00:00
|
|
|
|
get { return _region; }
|
2011-06-27 01:24:26 +00:00
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (value.NotIn(validRegions))
|
2014-02-26 20:18:48 +00:00
|
|
|
|
{
|
2016-02-28 17:01:12 +00:00
|
|
|
|
throw new Exception("Passed value " + value + " is not a valid region!");
|
|
|
|
|
}
|
2013-12-24 01:06:17 +00:00
|
|
|
|
|
2016-02-28 17:01:12 +00:00
|
|
|
|
_region = value;
|
2013-12-24 01:06:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-28 17:01:12 +00:00
|
|
|
|
|
|
|
|
|
private readonly string[] validRegions = { "Export", "Japan", "Auto" };
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
2014-03-22 04:46:01 +00:00
|
|
|
|
}
|