internal void ResetControllerDefinition(bool subframe) { ControllerDefinition = null; ControllerDeck = ControllerSettings.Instantiate(ppu.LightGunCallback); ControllerDefinition = ControllerDeck.ControllerDef; // controls other than the deck ControllerDefinition.BoolButtons.Add("Power"); ControllerDefinition.BoolButtons.Add("Reset"); if (Board is FDS b) { ControllerDefinition.BoolButtons.Add("FDS Eject"); for (int i = 0; i < b.NumSides; i++) { ControllerDefinition.BoolButtons.Add("FDS Insert " + i); } } if (_isVS) { ControllerDefinition.BoolButtons.Add("Insert Coin P1"); ControllerDefinition.BoolButtons.Add("Insert Coin P2"); ControllerDefinition.BoolButtons.Add("Service Switch"); } // Add in the reset timing axis for subneshawk if (subframe) { ControllerDefinition.AddAxis("Reset Cycle", 0.RangeTo(500000), 0); } ControllerDefinition.MakeImmutable(); }
public ControllerAdapter( List <NPortInfoT> allPorts, IDictionary <int, string> config, Func <string, string> overrideName, bool hasCds, ref SystemInfo systemInfo, HashSet <string> hiddenPorts, string controllerDeckName) { var ret = new ControllerDefinition { Name = controllerDeckName, CategoryLabels = { { "Power", "System" }, { "Reset", "System" }, { "Previous Disk", "System" }, { "Next Disk", "System" }, } }; var finalDevices = new List <string>(); var switchPreviousFrame = new List <byte>(); for (int port = 0, devByteStart = 0; port < allPorts.Count; port++) { var portInfo = allPorts[port]; var deviceName = config.ContainsKey(port) ? config[port] : portInfo.DefaultDeviceShortName; finalDevices.Add(deviceName); if (hiddenPorts.Contains(portInfo.ShortName)) { continue; } var devices = portInfo.Devices; var device = devices.FirstOrDefault(a => a.ShortName == deviceName); if (device == null) { Console.WriteLine($"Warn: unknown controller device {deviceName}"); device = devices.FirstOrDefault(a => a.ShortName == portInfo.DefaultDeviceShortName); if (device == null) { throw new InvalidOperationException($"Fail: unknown controller device {portInfo.DefaultDeviceShortName}"); } } ActualPortData.Add(new PortResult { Port = portInfo, Device = device }); var deviceInfo = device; var category = portInfo.FullName + " - " + deviceInfo.FullName; var inputs = deviceInfo.Inputs .OrderBy(a => a.ConfigOrder); foreach (var input in inputs) { if (input.Type == InputType.Padding) { continue; } var bitSize = (int)input.BitSize; var bitOffset = (int)input.BitOffset; var byteStart = devByteStart + bitOffset / 8; bitOffset %= 8; var baseName = input.Name; if (baseName != null) { baseName = overrideName(baseName); } var name = input.Type == InputType.ResetButton ? "Reset" : $"P{port + 1} {baseName}"; switch (input.Type) { case InputType.ResetButton: case InputType.Button: case InputType.ButtonCanRapid: { // var data = inputInfo.Extra.AsButton(); // TODO: Wire up data.ExcludeName if (input.Type != InputType.ResetButton) { ret.BoolButtons.Add(name); ret.CategoryLabels[name] = category; } _thunks.Add((c, b) => { if (c.IsPressed(name)) { b[byteStart] |= (byte)(1 << bitOffset); } }); break; } case InputType.Switch: { var data = input.Extra.AsSwitch(); if (data.Positions.Count > 8) { throw new NotImplementedException("Need code changes to support Mdfn switch with more than 8 positions"); } // fake switches as a series of push downs that select each state // imagine the "gear" selector on a Toyota Prius var si = switchPreviousFrame.Count; // [si]: position of this switch on the previous frame switchPreviousFrame.Add((byte)data.DefaultPosition); // [si + 1]: bit array of the previous state of each selector button switchPreviousFrame.Add(0); var names = data.Positions.Select(p => $"{name}: Set {p.Name}").ToArray(); if (!input.Name.StartsWith("AF ") && !input.Name.EndsWith(" AF") && !input.Name.StartsWith("Autofire ")) // hack: don't support some devices { foreach (var n in names) { { ret.BoolButtons.Add(n); ret.CategoryLabels[n] = category; } } } _thunks.Add((c, b) => { var val = _switchPreviousFrame[si]; var allOldPressed = _switchPreviousFrame[si + 1]; byte allNewPressed = 0; for (var i = 0; i < names.Length; i++) { var mask = (byte)(1 << i); var oldPressed = allOldPressed & mask; var newPressed = c.IsPressed(names[i]) ? mask : (byte)0; if (newPressed > oldPressed) { val = (byte)i; } allNewPressed |= newPressed; } _switchPreviousFrame[si] = val; _switchPreviousFrame[si + 1] = allNewPressed; b[byteStart] |= (byte)(val << bitOffset); }); break; } case InputType.Axis: { var data = input.Extra.AsAxis(); var fullName = $"{name} {overrideName(data.NameNeg)} / {overrideName(data.NamePos)}"; ret.AddAxis(fullName, 0.RangeTo(0xFFFF), 0x8000, (input.Flags & AxisFlags.InvertCo) != 0); ret.CategoryLabels[fullName] = category; _thunks.Add((c, b) => { var val = c.AxisValue(fullName); b[byteStart] = (byte)val; b[byteStart + 1] = (byte)(val >> 8); }); break; } case InputType.AxisRel: { var data = input.Extra.AsAxis(); var fullName = $"{name} {input.Extra.AsAxis().NameNeg} / {input.Extra.AsAxis().NamePos}"; // TODO: Mednafen docs say this range should be [-32768, 32767], and inspecting the code // reveals that a 16 bit value is read, but using anywhere near this full range makes // PCFX mouse completely unusable. Maybe this is some TAS situation where average users // will want a 1/400 multiplier on sensitivity but TASers might want one frame screenwide movement? ret.AddAxis(fullName, (-127).RangeTo(127), 0, (input.Flags & AxisFlags.InvertCo) != 0); ret.CategoryLabels[fullName] = category; _thunks.Add((c, b) => { var val = c.AxisValue(fullName); b[byteStart] = (byte)val; b[byteStart + 1] = (byte)(val >> 8); }); break; } case InputType.PointerX: { // I think the core expects to be sent some sort of 16 bit integer, but haven't investigated much ret.AddAxis(name, systemInfo.PointerOffsetX.RangeTo(systemInfo.PointerScaleX), systemInfo.PointerOffsetX); _thunks.Add((c, b) => { var val = c.AxisValue(name); b[byteStart] = (byte)val; b[byteStart + 1] = (byte)(val >> 8); }); break; } case InputType.PointerY: { // I think the core expects to be sent some sort of 16 bit integer, but haven't investigated much ret.AddAxis(name, systemInfo.PointerOffsetY.RangeTo(systemInfo.PointerScaleY), systemInfo.PointerOffsetY); _thunks.Add((c, b) => { var val = c.AxisValue(name); b[byteStart] = (byte)val; b[byteStart + 1] = (byte)(val >> 8); }); break; } case InputType.ButtonAnalog: { ret.AddAxis(name, 0.RangeTo(0xFFFF), 0); ret.CategoryLabels[name] = category; _thunks.Add((c, b) => { var val = c.AxisValue(name); b[byteStart] = (byte)val; b[byteStart + 1] = (byte)(val >> 8); }); break; } case InputType.Status: // TODO: wire up statuses to something (not controller, of course) break; default: { throw new NotImplementedException($"Unimplemented button type {input.Type}"); } } } devByteStart += (int)deviceInfo.ByteLength; if (devByteStart > MAX_INPUT_DATA) { throw new NotImplementedException($"More than {MAX_INPUT_DATA} input data bytes"); } } ret.BoolButtons.Add("Power"); ret.BoolButtons.Add("Reset"); if (hasCds) { ret.BoolButtons.Add("Previous Disk"); ret.BoolButtons.Add("Next Disk"); } Definition = ret; finalDevices.Add(null); Devices = finalDevices.ToArray(); _switchPreviousFrame = switchPreviousFrame.ToArray(); }
public void HardReset() { cpu = new MOS6502X <CpuLink>(new CpuLink(this)) { BCD_Enabled = false }; ppu = new PPU(this); ram = new byte[0x800]; CIRAM = new byte[0x800]; // wire controllers // todo: allow changing this ControllerDeck = ControllerSettings.Instantiate(ppu.LightGunCallback); // set controller definition first time only if (ControllerDefinition == null) { ControllerDefinition = new ControllerDefinition(ControllerDeck.GetDefinition()) { Name = "NES Controller" }; // controls other than the deck ControllerDefinition.BoolButtons.Add("Power"); ControllerDefinition.BoolButtons.Add("Reset"); if (Board is FDS b) { ControllerDefinition.BoolButtons.Add("FDS Eject"); for (int i = 0; i < b.NumSides; i++) { ControllerDefinition.BoolButtons.Add("FDS Insert " + i); } } if (_isVS) { ControllerDefinition.BoolButtons.Add("Insert Coin P1"); ControllerDefinition.BoolButtons.Add("Insert Coin P2"); ControllerDefinition.BoolButtons.Add("Service Switch"); } } // Add in the reset timing axis for subneshawk if (using_reset_timing && ControllerDefinition.Axes.Count == 0) { ControllerDefinition.AddAxis("Reset Cycle", 0.RangeTo(500000), 0); } // don't replace the magicSoundProvider on reset, as it's not needed // if (magicSoundProvider != null) magicSoundProvider.Dispose(); // set up region switch (_display_type) { case DisplayType.PAL: apu = new APU(this, apu, true); ppu.region = PPU.Region.PAL; cpuclockrate = 1662607; VsyncNum = cpuclockrate * 2; VsyncDen = 66495; cpu_sequence = cpu_sequence_PAL; _display_type = DisplayType.PAL; ClockRate = 5320342.5; break; case DisplayType.NTSC: apu = new APU(this, apu, false); ppu.region = PPU.Region.NTSC; cpuclockrate = 1789773; VsyncNum = cpuclockrate * 2; VsyncDen = 59561; cpu_sequence = cpu_sequence_NTSC; ClockRate = 5369318.1818181818181818181818182; break; // this is in bootgod, but not used at all case DisplayType.Dendy: apu = new APU(this, apu, false); ppu.region = PPU.Region.Dendy; cpuclockrate = 1773448; VsyncNum = cpuclockrate; VsyncDen = 35464; cpu_sequence = cpu_sequence_NTSC; _display_type = DisplayType.Dendy; ClockRate = 5320342.5; break; default: throw new Exception("Unknown displaytype!"); } blip.SetRates((uint)cpuclockrate, 44100); BoardSystemHardReset(); // apu has some specific power up bahaviour that we will emulate here apu.NESHardReset(); if (SyncSettings.InitialWRamStatePattern != null && SyncSettings.InitialWRamStatePattern.Any()) { for (int i = 0; i < 0x800; i++) { ram[i] = SyncSettings.InitialWRamStatePattern[i % SyncSettings.InitialWRamStatePattern.Count]; } } else { // check fceux's PowerNES and FCEU_MemoryRand function for more information: // relevant games: Cybernoid; Minna no Taabou no Nakayoshi Daisakusen; Huang Di; and maybe mechanized attack for (int i = 0; i < 0x800; i++) { if ((i & 4) != 0) { ram[i] = 0xFF; } else { ram[i] = 0x00; } } } SetupMemoryDomains(); // some boards cannot have specific values in RAM upon initialization // Let's hard code those cases here // these will be defined through the gameDB exclusively for now. if (cart.GameInfo != null) { if (cart.GameInfo.Hash == "60FC5FA5B5ACCAF3AEFEBA73FC8BFFD3C4DAE558" || // Camerica Golden 5 cart.GameInfo.Hash == "BAD382331C30B22A908DA4BFF2759C25113CC26A" || // Camerica Golden 5 cart.GameInfo.Hash == "40409FEC8249EFDB772E6FFB2DCD41860C6CCA23" // Camerica Pegasus 4-in-1 ) { ram[0x701] = 0xFF; } if (cart.GameInfo.Hash == "68ABE1E49C9E9CCEA978A48232432C252E5912C0") // Dancing Blocks { ram[0xEC] = 0; ram[0xED] = 0; } if (cart.GameInfo.Hash == "00C50062A2DECE99580063777590F26A253AAB6B") // Silva Saga { for (int i = 0; i < Board.Wram.Length; i++) { Board.Wram[i] = 0xFF; } } } }